Skip to content

Commit eb95d2d

Browse files
committed
refactor(watcher): ensure we track new files
1 parent 750310f commit eb95d2d

File tree

7 files changed

+254
-62
lines changed

7 files changed

+254
-62
lines changed

packages/astro-plugin-studio/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const pandaStudio = (): AstroIntegration => ({
5252

5353
updateConfig({
5454
vite: {
55+
// @ts-expect-error
5556
plugins: [vitePlugin(configPath!)],
5657
},
5758
})
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { globDirname } from '../src/glob-dirname'
3+
4+
const dir = (str: string) => globDirname([str])[0]
5+
6+
describe('globDirname', () => {
7+
test('should handle various glob patterns correctly', () => {
8+
expect(dir('.')).toMatchInlineSnapshot(`"."`)
9+
expect(dir('.*')).toMatchInlineSnapshot(`"."`)
10+
expect(dir('/.*')).toMatchInlineSnapshot(`"."`)
11+
expect(dir('/.*/')).toMatchInlineSnapshot(`"."`)
12+
expect(dir('a/.*/b')).toMatchInlineSnapshot(`"a"`)
13+
expect(dir('a*/.*/b')).toMatchInlineSnapshot(`"."`)
14+
expect(dir('*/a/b/c')).toMatchInlineSnapshot(`"."`)
15+
expect(dir('*')).toMatchInlineSnapshot(`"."`)
16+
expect(dir('*/')).toMatchInlineSnapshot(`"."`)
17+
expect(dir('*/*')).toMatchInlineSnapshot(`"."`)
18+
expect(dir('*/*/')).toMatchInlineSnapshot(`"."`)
19+
expect(dir('**')).toMatchInlineSnapshot(`"."`)
20+
expect(dir('**/')).toMatchInlineSnapshot(`"."`)
21+
expect(dir('**/*')).toMatchInlineSnapshot(`"."`)
22+
expect(dir('**/*/')).toMatchInlineSnapshot(`"."`)
23+
expect(dir('/*.js')).toMatchInlineSnapshot(`"."`)
24+
expect(dir('*.js')).toMatchInlineSnapshot(`"."`)
25+
expect(dir('**/*.js')).toMatchInlineSnapshot(`"."`)
26+
expect(dir('{a,b}')).toMatchInlineSnapshot(`"."`)
27+
expect(dir('/{a,b}')).toMatchInlineSnapshot(`"."`)
28+
expect(dir('/{a,b}/')).toMatchInlineSnapshot(`"."`)
29+
expect(dir('(a|b)')).toMatchInlineSnapshot(`"."`)
30+
expect(dir('/(a|b)')).toMatchInlineSnapshot(`"."`)
31+
expect(dir('./(a|b)')).toMatchInlineSnapshot(`"."`)
32+
expect(dir('a/(b c)')).toMatchInlineSnapshot(`"a"`)
33+
expect(dir('a/(b c)/')).toMatchInlineSnapshot(`"a"`)
34+
expect(dir('a/(b c)/d')).toMatchInlineSnapshot(`"a"`)
35+
expect(dir('path/to/*.js')).toMatchInlineSnapshot(`"path/to"`)
36+
expect(dir('/root/path/to/*.js')).toMatchInlineSnapshot(`"/root/path/to"`)
37+
expect(dir('chapter/foo [bar]/')).toMatchInlineSnapshot(`"chapter"`)
38+
expect(dir('path/[a-z]')).toMatchInlineSnapshot(`"path"`)
39+
expect(dir('[a-z]')).toMatchInlineSnapshot(`"."`)
40+
expect(dir('path/{to,from}')).toMatchInlineSnapshot(`"path"`)
41+
expect(dir('path/(to|from)')).toMatchInlineSnapshot(`"path"`)
42+
expect(dir('path/(foo bar)/subdir/foo.*')).toMatchInlineSnapshot(`"path"`)
43+
expect(dir('path/!(to|from)')).toMatchInlineSnapshot(`"path"`)
44+
expect(dir('path/?(to|from)')).toMatchInlineSnapshot(`"path"`)
45+
expect(dir('path/+(to|from)')).toMatchInlineSnapshot(`"path"`)
46+
expect(dir('path/*(to|from)')).toMatchInlineSnapshot(`"path"`)
47+
expect(dir('path/@(to|from)')).toMatchInlineSnapshot(`"path"`)
48+
expect(dir('path/!/foo')).toMatchInlineSnapshot(`"path/!/foo"`)
49+
expect(dir('path/?/foo')).toMatchInlineSnapshot(`"path"`)
50+
expect(dir('path/+/foo')).toMatchInlineSnapshot(`"path/+/foo"`)
51+
expect(dir('path/*/foo')).toMatchInlineSnapshot(`"path"`)
52+
expect(dir('path/@/foo')).toMatchInlineSnapshot(`"path/@/foo"`)
53+
expect(dir('path/!/foo/')).toMatchInlineSnapshot(`"path/!/foo/"`)
54+
expect(dir('path/?/foo/')).toMatchInlineSnapshot(`"path"`)
55+
expect(dir('path/+/foo/')).toMatchInlineSnapshot(`"path/+/foo/"`)
56+
expect(dir('path/*/foo/')).toMatchInlineSnapshot(`"path"`)
57+
expect(dir('path/@/foo/')).toMatchInlineSnapshot(`"path/@/foo/"`)
58+
expect(dir('path/**/*')).toMatchInlineSnapshot(`"path"`)
59+
expect(dir('path/**/subdir/foo.*')).toMatchInlineSnapshot(`"path"`)
60+
expect(dir('path/subdir/**/foo.js')).toMatchInlineSnapshot(`"path/subdir"`)
61+
expect(dir('path/!subdir/foo.js')).toMatchInlineSnapshot(`"path/!subdir/foo.js"`)
62+
expect(dir('path/{foo,bar}/')).toMatchInlineSnapshot(`"path"`)
63+
expect(dir('path')).toMatchInlineSnapshot(`"path"`)
64+
expect(dir('path/foo')).toMatchInlineSnapshot(`"path/foo"`)
65+
expect(dir('path/foo/')).toMatchInlineSnapshot(`"path/foo/"`)
66+
expect(dir('path/foo/bar.js')).toMatchInlineSnapshot(`"path/foo/bar.js"`)
67+
})
68+
69+
test('handle escaped characters', () => {
70+
expect(dir('path/\\*\\*/subdir/foo.*')).toMatchInlineSnapshot(`"path/\\*\\*/subdir"`)
71+
expect(dir('path/\\[\\*\\]/subdir/foo.*')).toMatchInlineSnapshot(`"path/\\[\\*\\]/subdir"`)
72+
expect(dir('path/\\*(a|b)/subdir/foo.*')).toMatchInlineSnapshot(`"path"`)
73+
expect(dir('path/\\*/(a|b)/subdir/foo.*')).toMatchInlineSnapshot(`"path/\\*"`)
74+
expect(dir('path/\\*\\(a\\|b\\)/subdir/foo.*')).toMatchInlineSnapshot(`"path/\\*\\(a\\|b\\)/subdir"`)
75+
expect(dir('path/\\[foo bar\\]/subdir/foo.*')).toMatchInlineSnapshot(`"path/\\[foo bar\\]/subdir"`)
76+
expect(dir('path/\\[bar]/')).toMatchInlineSnapshot(`"path/\\[bar]/"`)
77+
expect(dir('path/\\[bar]')).toMatchInlineSnapshot(`"path/\\[bar]"`)
78+
expect(dir('[bar]')).toMatchInlineSnapshot(`"."`)
79+
expect(dir('[bar]/')).toMatchInlineSnapshot(`"."`)
80+
expect(dir('./\\[bar]')).toMatchInlineSnapshot(`"./\\[bar]"`)
81+
expect(dir('\\[bar]/')).toMatchInlineSnapshot(`"\\[bar]/"`)
82+
expect(dir('\\!dir/*')).toMatchInlineSnapshot(`"\\!dir"`)
83+
expect(dir('[bar\\]/')).toMatchInlineSnapshot(`"[bar\\]/"`)
84+
expect(dir('path/foo \\[bar]/')).toMatchInlineSnapshot(`"path/foo \\[bar]/"`)
85+
expect(dir('path/\\{foo,bar}/')).toMatchInlineSnapshot(`"path/\\{foo,bar}/"`)
86+
expect(dir('\\{foo,bar}/')).toMatchInlineSnapshot(`"\\{foo,bar}/"`)
87+
expect(dir('\\{foo,bar\\}/')).toMatchInlineSnapshot(`"\\{foo,bar\\}/"`)
88+
expect(dir('{foo,bar\\}/')).toMatchInlineSnapshot(`"."`)
89+
})
90+
91+
test('should handle glob patterns with braces', () => {
92+
expect(dir('js/t(wo|est)/*.js')).toMatchInlineSnapshot(`"js"`)
93+
expect(dir('js/t/(wo|est)/*.js')).toMatchInlineSnapshot(`"js/t"`)
94+
expect(dir('lib/{components,pages}/**/{test,another}/*.txt')).toMatchInlineSnapshot(`"lib"`)
95+
expect(dir('js/test{0..9}/*.js')).toMatchInlineSnapshot(`"js"`)
96+
expect(dir('js/t[a-z]st}/*.js')).toMatchInlineSnapshot(`"js"`)
97+
expect(dir('path/{../,./,{bar,/baz\\},qux\\}/')).toMatchInlineSnapshot(`"path"`)
98+
expect(dir('path/{../,./,\\{bar,/baz},qux}/')).toMatchInlineSnapshot(`"path"`)
99+
expect(dir('path/\\{../,./,\\{bar,/baz\\},qux\\}/')).toMatchInlineSnapshot(
100+
`"path/\\{../,./,\\{bar,/baz\\},qux\\}/"`,
101+
)
102+
expect(dir('{../,./,{bar,/baz\\},qux\\}/')).toMatchInlineSnapshot(`"."`)
103+
expect(dir('{../,./,{bar,/baz\\},qux\\}')).toMatchInlineSnapshot(`"."`)
104+
expect(dir('path/{,/,bar/{baz,qux\\}}/')).toMatchInlineSnapshot(`"path"`)
105+
expect(dir('path/{,/,bar/{baz,qux}\\}/')).toMatchInlineSnapshot(`"path"`)
106+
})
107+
})

packages/node/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"@pandacss/types": "workspace:*",
4949
"browserslist": "4.23.3",
5050
"chokidar": "4.0.3",
51-
"fast-glob": "3.3.3",
51+
"tinyglobby": "0.2.12",
5252
"fs-extra": "11.2.0",
5353
"glob-parent": "6.0.2",
5454
"is-glob": "4.0.3",
@@ -58,6 +58,7 @@
5858
"package-manager-detector": "0.1.0",
5959
"perfect-debounce": "1.0.0",
6060
"pkg-types": "1.0.3",
61+
"picomatch": "4.0.2",
6162
"pluralize": "8.0.0",
6263
"postcss": "8.4.49",
6364
"prettier": "3.2.5",
@@ -66,6 +67,7 @@
6667
"tsconfck": "3.0.2"
6768
},
6869
"devDependencies": {
70+
"@types/picomatch": "3.0.2",
6971
"@types/fs-extra": "11.0.4",
7072
"@types/glob-parent": "5.1.3",
7173
"@types/is-glob": "4.0.4",

packages/node/src/glob-dirname.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { join } from 'path'
2+
import picomatch from 'picomatch'
3+
4+
export function globDirname(globs: string[]) {
5+
const rootDirs = new Set<string>()
6+
7+
for (const glob of globs) {
8+
const scan = picomatch.scan(glob, { tokens: true })
9+
if (!scan.isGlob) {
10+
rootDirs.add(glob)
11+
continue
12+
}
13+
const nonGlobTokens = scan.tokens?.filter((token: any) => !token.isPrefix && !token.isGlob)
14+
if (nonGlobTokens?.length) {
15+
rootDirs.add(join(...nonGlobTokens.map((token: any) => token.value)))
16+
}
17+
}
18+
19+
if (rootDirs.size === 0) {
20+
return ['.']
21+
}
22+
23+
return Array.from(rootDirs)
24+
}

packages/node/src/node-runtime.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { logger } from '@pandacss/logger'
22
import type { Runtime } from '@pandacss/types'
33
import chokidar from 'chokidar'
4-
import glob from 'fast-glob'
54
import fsExtra from 'fs-extra'
65
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from 'path'
6+
import picomatch from 'picomatch'
7+
import { globSync } from 'tinyglobby'
8+
import { globDirname } from './glob-dirname'
79

810
export const nodeRuntime: Runtime = {
911
cwd() {
@@ -37,7 +39,7 @@ export const nodeRuntime: Runtime = {
3739
ignore.push('**/*.d.ts')
3840
}
3941

40-
return glob.sync(opts.include, { cwd: opts.cwd, ignore, absolute: true })
42+
return globSync(opts.include, { cwd: opts.cwd, ignore, absolute: true })
4143
},
4244
writeFile: fsExtra.writeFile,
4345
writeFileSync: fsExtra.writeFileSync,
@@ -50,16 +52,24 @@ export const nodeRuntime: Runtime = {
5052
watch(options) {
5153
const { include, exclude, cwd, poll } = options
5254
const coalesce = poll || process.platform === 'win32'
53-
const files = glob.sync(include, { cwd, ignore: exclude })
54-
const watcher = chokidar.watch(files, {
55+
56+
const dirnames = globDirname(include)
57+
const isValidPath = picomatch(include, { cwd, ignore: exclude })
58+
const workingDir = cwd || process.cwd()
59+
60+
const watcher = chokidar.watch(dirnames, {
5561
usePolling: poll,
5662
cwd,
63+
ignored(path, stats) {
64+
const relativePath = relative(workingDir, path)
65+
return !!stats?.isFile() && !isValidPath(relativePath)
66+
},
5767
ignoreInitial: true,
5868
ignorePermissionErrors: true,
5969
awaitWriteFinish: coalesce ? { stabilityThreshold: 50, pollInterval: 10 } : false,
6070
})
6171

62-
logger.debug('watch:file', `watching [${include}]`)
72+
logger.debug('watch:file', `Watching [ ${dirnames.join(', ')} ]`)
6373

6474
process.once('SIGINT', async () => {
6575
await watcher.close()

0 commit comments

Comments
 (0)