-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmatch.ts
More file actions
149 lines (142 loc) · 5.63 KB
/
Copy pathmatch.ts
File metadata and controls
149 lines (142 loc) · 5.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/**
* @file `glob` (async) and `globSync` — fast-glob wrappers with a
* `node:fs.glob` fast-path when the option surface lines up.
* `canUseNodeFsGlob` is the per-call gate. Trailing-slash workaround for
* fast-glob ignore patterns
* ───────────────────────────────────────────────────────── TL;DR: when you
* pass `ignore: ['**\/dist/']` to fast-glob, the `dist` directory still gets
* walked. Strip the trailing slash before passing it to fast-glob and the
* ignore actually takes effect. Why this exists ─────────────── The gitignore
* convention is to write directory entries with a trailing slash: `dist/`,
* `node_modules/`, `coverage/`. Tools that translate gitignore lines into
* glob patterns (including socket-cli's `globWithGitIgnore` helper,
* npm-packlist, etc.) preserve that slash. fast-glob has TWO independent
* filters that handle the trailing slash differently:
*
* 1. The DEEP filter decides whether to walk INTO a candidate directory. The
* deep filter compiles `**\/dist/` into a regex that requires a trailing
* slash on the input, but it tests `entryPath = 'dist'` (no slash, because
* readdir entries don't include one). So fast-glob walks in anyway.
* 2. The ENTRY filter (post-walk) retries with a trailing slash appended for
* directory entries — so it correctly excludes the results, but only AFTER
* the entire subtree has been walked. Net effect: a `dist/` ignore pattern
* correctly removes contents from the result array, but only after
* walking. On a 300k-file `dist/` under tight memory this is the
* difference between "instant" and "OOM kill". Stripping the trailing
* slash makes it `**\/dist`, which both filters interpret correctly.
*/
import { fromAsync } from '../promises/resolvers'
import {
getFastGlob,
getFs,
getFsPromises,
normalizeGlobResults,
normalizeIgnorePatterns,
} from './_internal'
import type { FastGlobOptions, Pattern } from './types'
import type { Options as FastGlobLibOptions } from 'fast-glob'
import { ObjectKeys } from '../primordials/object'
/**
* Whether the caller's option bag is fully expressible with `node:fs.glob`
* (`cwd` + `exclude`). Any other option means we must fall back to fast-glob,
* which exposes the wider surface.
*
* Exported for unit tests; not part of the public API.
*
* @internal
*/
export function canUseNodeFsGlob(
options: FastGlobOptions | undefined,
): boolean {
if (!options) {
return true
}
// Use ObjectKeys via primordials? Standard for-in is fine here for
// type-narrowed access — the option object is plain.
for (const key of ObjectKeys(options)) {
if (key !== 'cwd' && key !== 'ignore') {
return false
}
}
return true
}
/**
* Asynchronously find files matching glob patterns.
*
* @example
* ;```typescript
* const files = await glob('src/*.ts', { cwd: '/tmp/project' })
* console.log(files) // ['src/index.ts', 'src/utils.ts']
* ```
*/
export async function glob(
patterns: Pattern | Pattern[],
options?: FastGlobOptions,
): Promise<string[]> {
// Strip trailing slashes from ignore patterns before fast-glob sees
// them; otherwise `dist/` from a .gitignore-derived list silently
// walks the whole subtree. See the file header above.
options = { __proto__: null, ...options } as typeof options
const normalizedIgnore = normalizeIgnorePatterns(options?.ignore)
// Prefer node:fs/promises.glob (added v22.0.0, Stable) when the
// option surface lines up. Avoids loading fast-glob entirely.
/* c8 ignore start */
if (canUseNodeFsGlob(options)) {
const fsPromises = getFsPromises()
const out = await fromAsync(
fsPromises.glob(patterns as string | readonly string[], {
...(options?.cwd ? { cwd: options.cwd } : {}),
...(normalizedIgnore ? { exclude: normalizedIgnore } : {}),
}),
)
return normalizeGlobResults(out)
}
/* c8 ignore stop */
/* c8 ignore next - External fast-glob call */
const fastGlob = getFastGlob()
const out = await fastGlob.glob(patterns, {
...(options as FastGlobLibOptions),
...(normalizedIgnore ? { ignore: normalizedIgnore } : {}),
})
return normalizeGlobResults(out)
}
/**
* Synchronously find files matching glob patterns. Wrapper around
* fast-glob.sync.
*
* @example
* ;```typescript
* const files = globSync('*.json', { cwd: '/tmp/project' })
* console.log(files) // ['package.json', 'tsconfig.json']
* ```
*/
export function globSync(
patterns: Pattern | Pattern[],
options?: FastGlobOptions,
): string[] {
// Strip trailing slashes from ignore patterns; same workaround as
// the async `glob` above, see file header.
options = { __proto__: null, ...options } as typeof options
const normalizedIgnore = normalizeIgnorePatterns(options?.ignore)
// Prefer node:fs.globSync (added v22.0.0, Stable) when the option
// surface lines up. Avoids loading fast-glob entirely.
/* c8 ignore start */
if (canUseNodeFsGlob(options)) {
const fs = getFs()
return normalizeGlobResults([
...fs.globSync(patterns as string | readonly string[], {
...(options?.cwd ? { cwd: options.cwd } : {}),
...(normalizedIgnore ? { exclude: normalizedIgnore } : {}),
}),
] as string[])
}
/* c8 ignore stop */
/* c8 ignore next - External fast-glob call */
const fastGlob = getFastGlob()
return normalizeGlobResults(
fastGlob.globSync(patterns, {
...(options as FastGlobLibOptions),
...(normalizedIgnore ? { ignore: normalizedIgnore } : {}),
}),
)
}