|
1 | 1 | import path from 'node:path' |
2 | 2 | import { isRepoDirty } from '../../packages/@tailwindcss-upgrade/src/utils/git' |
3 | | -import { candidate, css, html, js, json, test, ts, yaml } from '../utils' |
| 3 | +import { candidate, css, html, js, json, test, ts, txt, yaml } from '../utils' |
4 | 4 |
|
5 | 5 | test( |
6 | 6 | 'error when no CSS file with @tailwind is used', |
@@ -171,6 +171,56 @@ test( |
171 | 171 | }, |
172 | 172 | ) |
173 | 173 |
|
| 174 | +test( |
| 175 | + 'only migrates files matched by `config.content` when upgrading from v3 to v4', |
| 176 | + { |
| 177 | + fs: { |
| 178 | + 'package.json': json` |
| 179 | + { |
| 180 | + "dependencies": { |
| 181 | + "tailwindcss": "^3", |
| 182 | + "@tailwindcss/upgrade": "workspace:^" |
| 183 | + } |
| 184 | + } |
| 185 | + `, |
| 186 | + 'tailwind.config.js': js` |
| 187 | + /** @type {import('tailwindcss').Config} */ |
| 188 | + module.exports = { |
| 189 | + content: ['./src/**/*.html'], |
| 190 | + } |
| 191 | + `, |
| 192 | + 'src/index.html': html` |
| 193 | + <div class="order-[0] bg-[--my-red]"></div> |
| 194 | + `, |
| 195 | + 'src/input.css': css` |
| 196 | + @tailwind base; |
| 197 | + @tailwind components; |
| 198 | + @tailwind utilities; |
| 199 | + `, |
| 200 | + 'templates/email.php': html` |
| 201 | + <div class="order-[0] bg-[--my-red]"></div> |
| 202 | + `, |
| 203 | + 'notes/unrelated.txt': `order-[0] bg-[--my-red]`, |
| 204 | + }, |
| 205 | + }, |
| 206 | + async ({ exec, fs, expect }) => { |
| 207 | + await exec('npx @tailwindcss/upgrade') |
| 208 | + |
| 209 | + expect(await fs.dumpFiles('./**/*.{html,php,txt}')).toMatchInlineSnapshot(` |
| 210 | + " |
| 211 | + --- notes/unrelated.txt --- |
| 212 | + order-[0] bg-[--my-red] |
| 213 | +
|
| 214 | + --- src/index.html --- |
| 215 | + <div class="order-0 bg-(--my-red)"></div> |
| 216 | +
|
| 217 | + --- templates/email.php --- |
| 218 | + <div class="order-[0] bg-[--my-red]"></div> |
| 219 | + " |
| 220 | + `) |
| 221 | + }, |
| 222 | +) |
| 223 | + |
174 | 224 | test( |
175 | 225 | `upgrades a v3 project with prefixes to v4`, |
176 | 226 | { |
@@ -2972,6 +3022,198 @@ test( |
2972 | 3022 | }, |
2973 | 3023 | ) |
2974 | 3024 |
|
| 3025 | +test( |
| 3026 | + 'v4 ignores .env files during template migration', |
| 3027 | + { |
| 3028 | + fs: { |
| 3029 | + 'package.json': json` |
| 3030 | + { |
| 3031 | + "dependencies": { |
| 3032 | + "tailwindcss": "^4", |
| 3033 | + "@tailwindcss/upgrade": "workspace:^" |
| 3034 | + } |
| 3035 | + } |
| 3036 | + `, |
| 3037 | + 'src/app.css': css`@import 'tailwindcss';`, |
| 3038 | + 'src/index.html': html` |
| 3039 | + <div class="order-[0]"></div> |
| 3040 | + `, |
| 3041 | + 'src/.env': `TW_TEST_CLASS=order-[0]`, |
| 3042 | + 'src/.env.production': `TW_TEST_CLASS=order-[0]`, |
| 3043 | + }, |
| 3044 | + }, |
| 3045 | + async ({ exec, fs, expect }) => { |
| 3046 | + await exec('npx @tailwindcss/upgrade') |
| 3047 | + |
| 3048 | + expect(await fs.dumpFiles('./src/**/{*,.env,.env.*}')).toMatchInlineSnapshot(` |
| 3049 | + " |
| 3050 | + --- ./src/index.html --- |
| 3051 | + <div class="order-0"></div> |
| 3052 | +
|
| 3053 | + --- ./src/.env --- |
| 3054 | + TW_TEST_CLASS=order-[0] |
| 3055 | +
|
| 3056 | + --- ./src/.env.production --- |
| 3057 | + TW_TEST_CLASS=order-[0] |
| 3058 | +
|
| 3059 | + --- ./src/app.css --- |
| 3060 | + @import 'tailwindcss'; |
| 3061 | + " |
| 3062 | + `) |
| 3063 | + }, |
| 3064 | +) |
| 3065 | + |
| 3066 | +test( |
| 3067 | + 'v4 linked configs respect `content` and still ignore gitignored files', |
| 3068 | + { |
| 3069 | + fs: { |
| 3070 | + 'package.json': json` |
| 3071 | + { |
| 3072 | + "dependencies": { |
| 3073 | + "tailwindcss": "^4", |
| 3074 | + "@tailwindcss/upgrade": "workspace:^" |
| 3075 | + } |
| 3076 | + } |
| 3077 | + `, |
| 3078 | + 'tailwind.config.js': js` |
| 3079 | + /** @type {import('tailwindcss').Config} */ |
| 3080 | + module.exports = { |
| 3081 | + content: ['./src/*.html', './src/*.less'], |
| 3082 | + } |
| 3083 | + `, |
| 3084 | + 'src/app.css': css` |
| 3085 | + @import 'tailwindcss'; |
| 3086 | + @config '../tailwind.config.js'; |
| 3087 | + `, |
| 3088 | + |
| 3089 | + // Ignore all .html files |
| 3090 | + 'src/.gitignore': txt` |
| 3091 | + *.html |
| 3092 | + `, |
| 3093 | + |
| 3094 | + // HTML files are in .gitignore, even though they are explicitly mentioned |
| 3095 | + // in the `content` array. Still ignore them |
| 3096 | + 'src/do-not-migrate-me.html': html` |
| 3097 | + <div class="order-[0]"></div> |
| 3098 | + `, |
| 3099 | + |
| 3100 | + // Should be picked up by auto-content detection |
| 3101 | + 'templates/migrate-me.php': html` |
| 3102 | + <div class="order-[0]"></div> |
| 3103 | + `, |
| 3104 | + |
| 3105 | + // Does not get picked up by auto content detection (because it's a less |
| 3106 | + // file), but was explicitly listed in the `content` array. |
| 3107 | + // |
| 3108 | + // A bit of a hacky way, I admit, but it allows us to differentiate |
| 3109 | + // between git ignored files, auto content detection and explicitly listed |
| 3110 | + // files. |
| 3111 | + 'src/migrate-me.less': html` |
| 3112 | + <div class="order-[0]"></div> |
| 3113 | + `, |
| 3114 | + }, |
| 3115 | + }, |
| 3116 | + async ({ exec, fs, expect }) => { |
| 3117 | + await exec('npx @tailwindcss/upgrade') |
| 3118 | + |
| 3119 | + expect(await fs.dumpFiles('./{src,templates}/**/*')).toMatchInlineSnapshot(` |
| 3120 | + " |
| 3121 | + --- ./src/app.css --- |
| 3122 | + @import 'tailwindcss'; |
| 3123 | + @config '../tailwind.config.js'; |
| 3124 | +
|
| 3125 | + --- ./src/do-not-migrate-me.html --- |
| 3126 | + <div class="order-[0]"></div> |
| 3127 | +
|
| 3128 | + --- ./src/migrate-me.less --- |
| 3129 | + <div class="order-0"></div> |
| 3130 | +
|
| 3131 | + --- ./templates/migrate-me.php --- |
| 3132 | + <div class="order-0"></div> |
| 3133 | + " |
| 3134 | + `) |
| 3135 | + }, |
| 3136 | +) |
| 3137 | + |
| 3138 | +test( |
| 3139 | + 'interrupting template migration does not truncate files', |
| 3140 | + { |
| 3141 | + timeout: 180_000, |
| 3142 | + fs: { |
| 3143 | + 'package.json': json` |
| 3144 | + { |
| 3145 | + "dependencies": { |
| 3146 | + "tailwindcss": "^4", |
| 3147 | + "@tailwindcss/upgrade": "workspace:^" |
| 3148 | + } |
| 3149 | + } |
| 3150 | + `, |
| 3151 | + 'src/app.css': css` @import 'tailwindcss'; `, |
| 3152 | + 'src/index.html': html` |
| 3153 | + <div class="order-[0]"></div> |
| 3154 | + `, |
| 3155 | + 'hook.cjs': js` |
| 3156 | + let fs = require('node:fs/promises') |
| 3157 | + let path = require('node:path') |
| 3158 | + let originalWriteFile = fs.writeFile.bind(fs) |
| 3159 | +
|
| 3160 | + fs.writeFile = async (file, contents, ...rest) => { |
| 3161 | + // Mimic a bad write |
| 3162 | + await originalWriteFile(file, '') // As-if we truncated first |
| 3163 | + console.error('__TRUNCATED_TARGET__') |
| 3164 | + await new Promise((r) => setTimeout(r, 50)) // Wait 50ms to allow us to kill the process |
| 3165 | + await originalWriteFile(file, contents, ...rest) // Write the actual contents |
| 3166 | + } |
| 3167 | + `, |
| 3168 | + 'src/keep.php': ` |
| 3169 | + <?php |
| 3170 | +
|
| 3171 | + return [ |
| 3172 | + 'keep' => 'this file should never be truncated', |
| 3173 | + ]; |
| 3174 | + `, |
| 3175 | + }, |
| 3176 | + }, |
| 3177 | + async ({ spawn, fs, expect }) => { |
| 3178 | + let repeatedCandidates = Array.from( |
| 3179 | + { length: 250 }, |
| 3180 | + () => '<div class="order-[0]"></div>', |
| 3181 | + ).join('\n') |
| 3182 | + |
| 3183 | + for (let i = 0; i < 100; i++) { |
| 3184 | + await fs.write( |
| 3185 | + `src/templates/template-${i}.php`, |
| 3186 | + `<?php\n\n${repeatedCandidates}\n\nreturn ['template' => ${i}];\n`, |
| 3187 | + ) |
| 3188 | + } |
| 3189 | + |
| 3190 | + let originalKeepFile = await fs.read('src/keep.php') |
| 3191 | + let originalTemplate = await fs.read('src/templates/template-0.php') |
| 3192 | + |
| 3193 | + let process = await spawn('npx @tailwindcss/upgrade --force', { |
| 3194 | + env: { |
| 3195 | + NODE_OPTIONS: '--require=./hook.cjs', |
| 3196 | + }, |
| 3197 | + }) |
| 3198 | + |
| 3199 | + // We're only interested once we start migrating the templates |
| 3200 | + await process.onStderr((message) => message.includes('Migrating templates')) |
| 3201 | + |
| 3202 | + // Wait for the trigger that we are mid-write |
| 3203 | + await process.onStderr((message) => message === '__TRUNCATED_TARGET__') |
| 3204 | + |
| 3205 | + // Kill the process |
| 3206 | + await process.dispose() |
| 3207 | + |
| 3208 | + expect(await fs.read('src/keep.php')).toBe(originalKeepFile) |
| 3209 | + expect(await fs.read('src/templates/template-0.php')).toBe(originalTemplate) |
| 3210 | + |
| 3211 | + for (let [file, contents] of await fs.glob('src/**/*.{html,php,css}')) { |
| 3212 | + expect(contents.trim(), `${file} should not be empty after interruption`).not.toBe('') |
| 3213 | + } |
| 3214 | + }, |
| 3215 | +) |
| 3216 | + |
2975 | 3217 | test( |
2976 | 3218 | 'upgrades can run in a pnpm workspace', |
2977 | 3219 | { |
|
0 commit comments