-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
…14896) This PR fixes an issue where globs in you `content` configuration escape the current "root" of the project. This can happen if you have a folder, and you need to look up in the tree (e.g.: when looking at another package in a monorepo, or in case of a Laravel project where you want to look at mail templates). This applies a similar strategy we already implement on the Rust side. 1. Expand braces in the globs 2. Move static parts of the `pattern` to the `base` of the glob entry object --- Given a project setup like this: ``` . ├── admin │ ├── my-tailwind.config.ts │ └── src │ ├── abc.jpg │ ├── index.html │ ├── index.js │ └── styles │ └── input.css ├── dashboard │ ├── src │ │ ├── index.html │ │ ├── index.js │ │ ├── input.css │ │ └── pickaday.css │ └── tailwind.config.ts ├── package-lock.json ├── package.json ├── postcss.config.js └── unrelated └── index.html 7 directories, 14 files ``` If you then have this config: ```ts // admin/my-tailwind.config.ts export default { content: { relative: true, files: ['./src/**/*.html', '../dashboard/src/**/*.html'], // ^^ this is the important part, which escapes // the current root of the project. }, theme: { extend: { colors: { primary: 'red', }, }, }, } ``` Then before this change, running the command looks like this: <img width="1760" alt="image" src="https://github.com/user-attachments/assets/60e2dfc7-3751-4432-80e3-8b4b8f1083d4"> After this change, running the command looks like this: <img width="1452" alt="image" src="https://github.com/user-attachments/assets/5c47182c-119c-4732-a253-2dace7086049"> --------- Co-authored-by: Philipp Spiess <[email protected]>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { expect, it } from 'vitest' | ||
import { hoistStaticGlobParts } from './hoist-static-glob-parts' | ||
|
||
it.each([ | ||
// A basic glob | ||
[ | ||
{ base: '/projects/project-a', pattern: './src/**/*.html' }, | ||
[{ base: '/projects/project-a/src', pattern: '**/*.html' }], | ||
], | ||
|
||
// A glob pointing to a folder should result in `**/*` | ||
[ | ||
{ base: '/projects/project-a', pattern: './src' }, | ||
[{ base: '/projects/project-a/src', pattern: '**/*' }], | ||
], | ||
|
||
// A glob pointing to a file, should result in the file as the pattern | ||
[ | ||
{ base: '/projects/project-a', pattern: './src/index.html' }, | ||
[{ base: '/projects/project-a/src', pattern: 'index.html' }], | ||
], | ||
|
||
// A glob going up a directory, should result in the new directory as the base | ||
[ | ||
{ base: '/projects/project-a', pattern: '../project-b/src/**/*.html' }, | ||
[{ base: '/projects/project-b/src', pattern: '**/*.html' }], | ||
], | ||
|
||
// A glob with curlies, should be expanded to multiple globs | ||
[ | ||
{ base: '/projects/project-a', pattern: '../project-{b,c}/src/**/*.html' }, | ||
[ | ||
{ base: '/projects/project-b/src', pattern: '**/*.html' }, | ||
{ base: '/projects/project-c/src', pattern: '**/*.html' }, | ||
], | ||
], | ||
[ | ||
{ base: '/projects/project-a', pattern: '../project-{b,c}/src/**/*.{js,html}' }, | ||
[ | ||
{ base: '/projects/project-b/src', pattern: '**/*.js' }, | ||
{ base: '/projects/project-b/src', pattern: '**/*.html' }, | ||
{ base: '/projects/project-c/src', pattern: '**/*.js' }, | ||
{ base: '/projects/project-c/src', pattern: '**/*.html' }, | ||
], | ||
], | ||
])('should hoist the static parts of the glob: %s', (input, output) => { | ||
expect(hoistStaticGlobParts(input)).toEqual(output) | ||
Check failure on line 47 in packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts GitHub Actions / tests (20, windows-latest, true)src/utils/hoist-static-glob-parts.test.ts > should hoist the static parts of the glob: { base: '/projects/project-a', pattern: './src/**/*.html' }
Check failure on line 47 in packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts GitHub Actions / tests (20, windows-latest, true)src/utils/hoist-static-glob-parts.test.ts > should hoist the static parts of the glob: { base: '/projects/project-a', pattern: './src' }
Check failure on line 47 in packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts GitHub Actions / tests (20, windows-latest, true)src/utils/hoist-static-glob-parts.test.ts > should hoist the static parts of the glob: { base: '/projects/project-a', pattern: './src/index.html' }
Check failure on line 47 in packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts GitHub Actions / tests (20, windows-latest, true)src/utils/hoist-static-glob-parts.test.ts > should hoist the static parts of the glob: { base: '/projects/project-a', pattern: '../project-b/src/**/*.html' }
Check failure on line 47 in packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts GitHub Actions / tests (20, windows-latest, true)src/utils/hoist-static-glob-parts.test.ts > should hoist the static parts of the glob: { base: '/projects/project-a', pattern: '../project-{b,c}/src/**/*.html' }
Check failure on line 47 in packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts GitHub Actions / tests (20, windows-latest, true)src/utils/hoist-static-glob-parts.test.ts > should hoist the static parts of the glob: { base: '/projects/project-a', pattern: '../project-{b,c}/src/**/*.{js,html}' }
|
||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import braces from 'braces' | ||
import path from 'node:path' | ||
|
||
interface GlobEntry { | ||
base: string | ||
pattern: string | ||
} | ||
|
||
export function hoistStaticGlobParts(entry: GlobEntry): GlobEntry[] { | ||
return braces(entry.pattern, { expand: true }).map((pattern) => { | ||
let clone = { ...entry } | ||
let [staticPart, dynamicPart] = splitPattern(pattern) | ||
|
||
// Move static part into the `base`. | ||
if (staticPart !== null) { | ||
clone.base = path.resolve(entry.base, staticPart) | ||
} else { | ||
clone.base = path.resolve(entry.base) | ||
} | ||
|
||
// Move dynamic part into the `pattern`. | ||
if (dynamicPart === null) { | ||
clone.pattern = '**/*' | ||
} else { | ||
clone.pattern = dynamicPart | ||
} | ||
|
||
// If the pattern looks like a file, move the file name from the `base` to | ||
// the `pattern`. | ||
let file = path.basename(clone.base) | ||
if (file.includes('.')) { | ||
clone.pattern = file | ||
clone.base = path.dirname(clone.base) | ||
} | ||
|
||
return clone | ||
}) | ||
} | ||
|
||
// Split a glob pattern into a `static` and `dynamic` part. | ||
// | ||
// Assumption: we assume that all globs are expanded, which means that the only | ||
// dynamic parts are using `*`. | ||
// | ||
// E.g.: | ||
// Original input: `../project-b/**/*.{html,js}` | ||
// Expanded input: `../project-b/**/*.html` & `../project-b/**/*.js` | ||
// Split on first input: ("../project-b", "**/*.html") | ||
// Split on second input: ("../project-b", "**/*.js") | ||
function splitPattern(pattern: string): [staticPart: string | null, dynamicPart: string | null] { | ||
// No dynamic parts, so we can just return the input as-is. | ||
if (!pattern.includes('*')) { | ||
return [pattern, null] | ||
} | ||
|
||
let lastSlashPosition: number | null = null | ||
|
||
for (let i = 0; i < pattern.length; i++) { | ||
let c = pattern[i]; | ||
if (c === '/') { | ||
lastSlashPosition = i | ||
} | ||
|
||
if (c === '*' || c === '!') { | ||
break | ||
} | ||
} | ||
|
||
// Very first character is a `*`, therefore there is no static part, only a | ||
// dynamic part. | ||
if (lastSlashPosition === null) { | ||
return [null, pattern] | ||
} | ||
|
||
let staticPart = pattern.slice(0, lastSlashPosition).trim() | ||
let dynamicPart = pattern.slice(lastSlashPosition + 1).trim() | ||
|
||
return [staticPart || null, dynamicPart || null] | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.