@dbx-tools/path 0.9.119 → 0.9.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -7
- package/index.ts +4 -1
- package/lib/index.d.ts +4 -1
- package/lib/index.js +4 -2
- package/lib/src/find.d.ts +8 -5
- package/lib/src/find.js +38 -12
- package/lib/src/git-ignore.d.ts +45 -0
- package/lib/src/git-ignore.js +161 -0
- package/lib/src/scan.d.ts +9 -3
- package/lib/src/scan.js +1 -1
- package/lib/src/watch.d.ts +2 -2
- package/lib/src/watch.js +75 -11
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -3
- package/src/find.ts +55 -11
- package/src/git-ignore.ts +200 -0
- package/src/scan.ts +9 -3
- package/src/watch.ts +93 -13
package/src/watch.ts
CHANGED
|
@@ -10,14 +10,19 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { Stats } from "fs";
|
|
13
|
+
import { readFile } from "node:fs/promises";
|
|
13
14
|
import { isAbsolute, relative, resolve } from "path";
|
|
14
|
-
import {
|
|
15
|
+
import { errorUtils, log } from "@dbx-tools/shared-core";
|
|
16
|
+
import { ChokidarOptions, MatchFunction, watch, type FSWatcher } from "chokidar";
|
|
15
17
|
import { hasMagic } from "glob";
|
|
16
18
|
import { findFiles, type FileFindOptions } from "./find.ts";
|
|
19
|
+
import { GitIgnore } from "./git-ignore.ts";
|
|
17
20
|
import { ignorePathMatcher } from "./ignore.ts";
|
|
18
21
|
import { pathMatchTests } from "./match.ts";
|
|
19
22
|
import { FileScanIgnoreOptions, FileScanOptions, FOLLOW_SYMLINKS_DEFAULT } from "./scan.ts";
|
|
20
23
|
|
|
24
|
+
const logger = log.logger("path/watch");
|
|
25
|
+
|
|
21
26
|
/** Chokidar options extended with the shared dbx-tools ignore configuration. */
|
|
22
27
|
export interface FileWatchOptions
|
|
23
28
|
extends Omit<ChokidarOptions, "ignored">, Omit<FileScanOptions, "ignore"> {
|
|
@@ -25,6 +30,7 @@ export interface FileWatchOptions
|
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
interface NormalizedFileWatchOptions extends Omit<FileWatchOptions, "ignore" | "ignoreOptions"> {
|
|
33
|
+
gitIgnore?: GitIgnore;
|
|
28
34
|
ignored: MatchFunction;
|
|
29
35
|
}
|
|
30
36
|
|
|
@@ -33,15 +39,18 @@ interface NormalizedFileWatchOptions extends Omit<FileWatchOptions, "ignore" | "
|
|
|
33
39
|
* ignore groups so it prunes the same paths {@link findFiles} skips for the
|
|
34
40
|
* same patterns.
|
|
35
41
|
*/
|
|
36
|
-
export function watchFiles(
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
export async function watchFiles(
|
|
43
|
+
paths: string | string[],
|
|
44
|
+
options?: FileWatchOptions,
|
|
45
|
+
): Promise<FSWatcher> {
|
|
46
|
+
const normalized = await toNormalizedFileWatchOptions(options);
|
|
47
|
+
const { gitIgnore, ignored, cwd, followSymlinks, ...chokidarOptions } = normalized;
|
|
39
48
|
const { paths: literals, globs } = partitionPaths(paths);
|
|
40
49
|
|
|
41
50
|
let watchPaths: string[];
|
|
42
51
|
if (globs.length > 0) {
|
|
43
52
|
const distinctWatchPaths = new Set(literals);
|
|
44
|
-
for (const path of findFiles(globs, toFindFilesOptions(normalized))) {
|
|
53
|
+
for await (const path of findFiles(globs, toFindFilesOptions(normalized))) {
|
|
45
54
|
distinctWatchPaths.add(path);
|
|
46
55
|
}
|
|
47
56
|
watchPaths = [...distinctWatchPaths];
|
|
@@ -49,12 +58,21 @@ export function watchFiles(paths: string | string[], options?: FileWatchOptions)
|
|
|
49
58
|
watchPaths = literals;
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
|
|
61
|
+
const watcher = watch(watchPaths, {
|
|
53
62
|
...chokidarOptions,
|
|
54
63
|
cwd,
|
|
55
64
|
followSymlinks,
|
|
56
65
|
ignored,
|
|
57
66
|
});
|
|
67
|
+
if (gitIgnore) {
|
|
68
|
+
attachGitIgnoreWatcher(watcher, gitIgnore, watchPaths, {
|
|
69
|
+
cwd,
|
|
70
|
+
followSymlinks,
|
|
71
|
+
ignoreOptions: options?.ignoreOptions,
|
|
72
|
+
persistent: chokidarOptions.persistent,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return watcher;
|
|
58
76
|
}
|
|
59
77
|
|
|
60
78
|
function partitionPaths(paths: string | string[]): { paths: string[]; globs: string[] } {
|
|
@@ -71,16 +89,23 @@ function partitionPaths(paths: string | string[]): { paths: string[]; globs: str
|
|
|
71
89
|
* built-in groups are consolidated into one chokidar {@link MatchFunction}. When
|
|
72
90
|
* `stats` marks a directory, the matcher also tests `path + "/"` for pruning.
|
|
73
91
|
*/
|
|
74
|
-
function toNormalizedFileWatchOptions(options?: FileWatchOptions):
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
async function toNormalizedFileWatchOptions(options?: FileWatchOptions): Promise<
|
|
93
|
+
NormalizedFileWatchOptions & {
|
|
94
|
+
cwd: string;
|
|
95
|
+
followSymlinks: boolean;
|
|
96
|
+
}
|
|
97
|
+
> {
|
|
78
98
|
const { cwd, ignore, followSymlinks, ignoreOptions, ...chokidarOptions } = options ?? {};
|
|
99
|
+
const root = cwd ?? process.cwd();
|
|
100
|
+
const gitIgnore = ignoreOptions?.git
|
|
101
|
+
? await GitIgnore.load(root, { ignoreOptions, followSymlinks })
|
|
102
|
+
: undefined;
|
|
79
103
|
return {
|
|
80
104
|
...chokidarOptions,
|
|
81
|
-
cwd:
|
|
105
|
+
cwd: root,
|
|
82
106
|
followSymlinks: followSymlinks ?? FOLLOW_SYMLINKS_DEFAULT,
|
|
83
|
-
|
|
107
|
+
...(gitIgnore ? { gitIgnore } : {}),
|
|
108
|
+
ignored: toWatchMatchFunction(ignore, ignoreOptions, root, gitIgnore),
|
|
84
109
|
};
|
|
85
110
|
}
|
|
86
111
|
|
|
@@ -105,6 +130,7 @@ function toWatchMatchFunction(
|
|
|
105
130
|
ignore: FileWatchOptions["ignore"] | undefined,
|
|
106
131
|
ignoreOptions: FileScanIgnoreOptions | undefined,
|
|
107
132
|
cwd: string,
|
|
133
|
+
gitIgnore?: GitIgnore,
|
|
108
134
|
): MatchFunction {
|
|
109
135
|
const patterns: string[] = [];
|
|
110
136
|
const functions: MatchFunction[] = [];
|
|
@@ -121,6 +147,7 @@ function toWatchMatchFunction(
|
|
|
121
147
|
|
|
122
148
|
const matchPath = (path: string, stats?: Stats): boolean => {
|
|
123
149
|
const rel = toCwdRelativePath(path, cwd);
|
|
150
|
+
if (gitIgnore?.ignores(rel, stats?.isDirectory() ?? false)) return true;
|
|
124
151
|
if (matcher(rel)) return true;
|
|
125
152
|
for (const fn of functions) {
|
|
126
153
|
if (fn(rel, stats)) return true;
|
|
@@ -138,6 +165,59 @@ function toWatchMatchFunction(
|
|
|
138
165
|
return matchPath;
|
|
139
166
|
}
|
|
140
167
|
|
|
168
|
+
function attachGitIgnoreWatcher(
|
|
169
|
+
watcher: FSWatcher,
|
|
170
|
+
gitIgnore: GitIgnore,
|
|
171
|
+
watchPaths: readonly string[],
|
|
172
|
+
options: {
|
|
173
|
+
cwd: string;
|
|
174
|
+
followSymlinks: boolean;
|
|
175
|
+
ignoreOptions?: FileScanIgnoreOptions;
|
|
176
|
+
persistent?: boolean;
|
|
177
|
+
},
|
|
178
|
+
): void {
|
|
179
|
+
const builtInIgnore = ignorePathMatcher(options.ignoreOptions);
|
|
180
|
+
const controls = watch(".", {
|
|
181
|
+
atomic: false,
|
|
182
|
+
cwd: options.cwd,
|
|
183
|
+
followSymlinks: options.followSymlinks,
|
|
184
|
+
ignoreInitial: true,
|
|
185
|
+
persistent: options.persistent,
|
|
186
|
+
ignored(path, stats) {
|
|
187
|
+
const relativePath = toCwdRelativePath(path, options.cwd);
|
|
188
|
+
if (GitIgnore.isFile(relativePath)) return false;
|
|
189
|
+
if (builtInIgnore(relativePath) || builtInIgnore(`${relativePath}/`)) return true;
|
|
190
|
+
return stats ? !stats.isDirectory() : false;
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
const reload = (path: string): void => {
|
|
194
|
+
void readFile(resolve(options.cwd, path), "utf8")
|
|
195
|
+
.then((content) => {
|
|
196
|
+
gitIgnore.set(path, content);
|
|
197
|
+
watcher.add([...watchPaths]);
|
|
198
|
+
logger.debug(".gitignore updated", { path });
|
|
199
|
+
})
|
|
200
|
+
.catch((error) => {
|
|
201
|
+
logger.warn("failed to reload .gitignore", {
|
|
202
|
+
path,
|
|
203
|
+
error: errorUtils.toError(error),
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
controls.on("add", reload);
|
|
208
|
+
controls.on("change", reload);
|
|
209
|
+
controls.on("unlink", (path) => {
|
|
210
|
+
gitIgnore.delete(path);
|
|
211
|
+
watcher.add([...watchPaths]);
|
|
212
|
+
logger.debug(".gitignore removed", { path });
|
|
213
|
+
});
|
|
214
|
+
controls.on("error", (error) => watcher.emit("error", error));
|
|
215
|
+
const close = watcher.close.bind(watcher);
|
|
216
|
+
watcher.close = async () => {
|
|
217
|
+
await Promise.all([close(), controls.close()]);
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
141
221
|
/** Aligns chokidar paths with the cwd-relative form {@link findFiles} uses. */
|
|
142
222
|
function toCwdRelativePath(path: string, cwd: string): string {
|
|
143
223
|
if (!isAbsolute(path)) return path;
|
|
@@ -152,7 +232,7 @@ if (import.meta.main) {
|
|
|
152
232
|
console.log("--- Starting File Watcher ---");
|
|
153
233
|
console.log(`Working Directory: ${cwd}`);
|
|
154
234
|
|
|
155
|
-
const watcher = watchFiles("**/src/**", {
|
|
235
|
+
const watcher = await watchFiles("**/src/**", {
|
|
156
236
|
cwd,
|
|
157
237
|
ignoreInitial: false,
|
|
158
238
|
ignore: ["**/index.ts", "**/core/**"],
|