@simplysm/core-node 13.0.100 → 14.0.4
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 +93 -79
- package/dist/features/fs-watcher.d.ts +21 -21
- package/dist/features/fs-watcher.d.ts.map +1 -1
- package/dist/features/fs-watcher.js +176 -114
- package/dist/features/fs-watcher.js.map +1 -6
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -6
- package/dist/utils/fs.d.ts +96 -96
- package/dist/utils/fs.d.ts.map +1 -1
- package/dist/utils/fs.js +437 -272
- package/dist/utils/fs.js.map +1 -6
- package/dist/utils/path.d.ts +22 -22
- package/dist/utils/path.js +103 -45
- package/dist/utils/path.js.map +1 -6
- package/dist/worker/create-worker.d.ts +3 -3
- package/dist/worker/create-worker.js +106 -81
- package/dist/worker/create-worker.js.map +1 -6
- package/dist/worker/types.d.ts +14 -14
- package/dist/worker/types.js +4 -1
- package/dist/worker/types.js.map +1 -6
- package/dist/worker/worker.d.ts +5 -5
- package/dist/worker/worker.js +168 -132
- package/dist/worker/worker.js.map +1 -6
- package/docs/fs-watcher.md +107 -0
- package/docs/fsx.md +287 -0
- package/docs/pathx.md +115 -0
- package/docs/worker.md +117 -62
- package/lib/worker-dev-proxy.js +15 -0
- package/package.json +9 -6
- package/src/features/fs-watcher.ts +53 -42
- package/src/index.ts +3 -3
- package/src/utils/fs.ts +111 -120
- package/src/utils/path.ts +26 -26
- package/src/worker/create-worker.ts +10 -10
- package/src/worker/types.ts +14 -14
- package/src/worker/worker.ts +29 -29
- package/docs/features.md +0 -91
- package/docs/fs.md +0 -309
- package/docs/path.md +0 -120
- package/tests/utils/fs-watcher.spec.ts +0 -286
- package/tests/utils/fs.spec.ts +0 -705
- package/tests/utils/path.spec.ts +0 -179
- package/tests/worker/fixtures/test-worker.ts +0 -35
- package/tests/worker/sd-worker.spec.ts +0 -174
package/docs/fs.md
DELETED
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
# File System Utilities
|
|
2
|
-
|
|
3
|
-
File system utilities exported as the `fsx` namespace.
|
|
4
|
-
|
|
5
|
-
```typescript
|
|
6
|
-
import { fsx } from "@simplysm/core-node";
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## Existence Check
|
|
10
|
-
|
|
11
|
-
### `existsSync`
|
|
12
|
-
|
|
13
|
-
Checks if a file or directory exists (synchronous).
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
function existsSync(targetPath: string): boolean;
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
### `exists`
|
|
20
|
-
|
|
21
|
-
Checks if a file or directory exists (asynchronous).
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
function exists(targetPath: string): Promise<boolean>;
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Create Directory
|
|
28
|
-
|
|
29
|
-
### `mkdirSync`
|
|
30
|
-
|
|
31
|
-
Creates a directory (recursive).
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
function mkdirSync(targetPath: string): void;
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### `mkdir`
|
|
38
|
-
|
|
39
|
-
Creates a directory (recursive, asynchronous).
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
function mkdir(targetPath: string): Promise<void>;
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Delete
|
|
46
|
-
|
|
47
|
-
### `rmSync`
|
|
48
|
-
|
|
49
|
-
Deletes a file or directory.
|
|
50
|
-
|
|
51
|
-
The synchronous version fails immediately without retries. Use `rm` for cases with potential transient errors like file locks.
|
|
52
|
-
|
|
53
|
-
```typescript
|
|
54
|
-
function rmSync(targetPath: string): void;
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### `rm`
|
|
58
|
-
|
|
59
|
-
Deletes a file or directory (asynchronous).
|
|
60
|
-
|
|
61
|
-
The asynchronous version retries up to 6 times (500ms interval) for transient errors like file locks.
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
function rm(targetPath: string): Promise<void>;
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Copy
|
|
68
|
-
|
|
69
|
-
### `copySync`
|
|
70
|
-
|
|
71
|
-
Copies a file or directory.
|
|
72
|
-
|
|
73
|
-
If `sourcePath` does not exist, no action is performed and the function returns.
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
function copySync(
|
|
77
|
-
sourcePath: string,
|
|
78
|
-
targetPath: string,
|
|
79
|
-
filter?: (absolutePath: string) => boolean,
|
|
80
|
-
): void;
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**Parameters:**
|
|
84
|
-
- `sourcePath` -- Path of the source to copy
|
|
85
|
-
- `targetPath` -- Destination path for the copy
|
|
86
|
-
- `filter` -- A filter function that determines whether to copy. The **absolute path** of each file/directory is passed. Returns `true` to copy, `false` to exclude. The top-level `sourcePath` is not subject to filtering; the filter function is applied recursively to all children. Returning `false` for a directory skips that directory and all its contents.
|
|
87
|
-
|
|
88
|
-
### `copy`
|
|
89
|
-
|
|
90
|
-
Copies a file or directory (asynchronous). Same behavior as `copySync`.
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
function copy(
|
|
94
|
-
sourcePath: string,
|
|
95
|
-
targetPath: string,
|
|
96
|
-
filter?: (absolutePath: string) => boolean,
|
|
97
|
-
): Promise<void>;
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
## Read File
|
|
101
|
-
|
|
102
|
-
### `readSync`
|
|
103
|
-
|
|
104
|
-
Reads a file as a UTF-8 string.
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
function readSync(targetPath: string): string;
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### `read`
|
|
111
|
-
|
|
112
|
-
Reads a file as a UTF-8 string (asynchronous).
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
function read(targetPath: string): Promise<string>;
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### `readBufferSync`
|
|
119
|
-
|
|
120
|
-
Reads a file as a Buffer.
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
function readBufferSync(targetPath: string): Buffer;
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### `readBuffer`
|
|
127
|
-
|
|
128
|
-
Reads a file as a Buffer (asynchronous).
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
function readBuffer(targetPath: string): Promise<Buffer>;
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### `readJsonSync`
|
|
135
|
-
|
|
136
|
-
Reads a JSON file (using JsonConvert).
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
function readJsonSync<TData = unknown>(targetPath: string): TData;
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### `readJson`
|
|
143
|
-
|
|
144
|
-
Reads a JSON file (using JsonConvert, asynchronous).
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
function readJson<TData = unknown>(targetPath: string): Promise<TData>;
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## Write File
|
|
151
|
-
|
|
152
|
-
### `writeSync`
|
|
153
|
-
|
|
154
|
-
Writes data to a file (auto-creates parent directories).
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
function writeSync(targetPath: string, data: string | Uint8Array): void;
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### `write`
|
|
161
|
-
|
|
162
|
-
Writes data to a file (auto-creates parent directories, asynchronous).
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
function write(targetPath: string, data: string | Uint8Array): Promise<void>;
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### `writeJsonSync`
|
|
169
|
-
|
|
170
|
-
Writes data to a JSON file (using JsonConvert).
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
function writeJsonSync(
|
|
174
|
-
targetPath: string,
|
|
175
|
-
data: unknown,
|
|
176
|
-
options?: {
|
|
177
|
-
replacer?: (this: unknown, key: string | undefined, value: unknown) => unknown;
|
|
178
|
-
space?: string | number;
|
|
179
|
-
},
|
|
180
|
-
): void;
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
### `writeJson`
|
|
184
|
-
|
|
185
|
-
Writes data to a JSON file (using JsonConvert, asynchronous).
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
function writeJson(
|
|
189
|
-
targetPath: string,
|
|
190
|
-
data: unknown,
|
|
191
|
-
options?: {
|
|
192
|
-
replacer?: (this: unknown, key: string | undefined, value: unknown) => unknown;
|
|
193
|
-
space?: string | number;
|
|
194
|
-
},
|
|
195
|
-
): Promise<void>;
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Read Directory
|
|
199
|
-
|
|
200
|
-
### `readdirSync`
|
|
201
|
-
|
|
202
|
-
Reads the contents of a directory.
|
|
203
|
-
|
|
204
|
-
```typescript
|
|
205
|
-
function readdirSync(targetPath: string): string[];
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### `readdir`
|
|
209
|
-
|
|
210
|
-
Reads the contents of a directory (asynchronous).
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
function readdir(targetPath: string): Promise<string[]>;
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## File Information
|
|
217
|
-
|
|
218
|
-
### `statSync`
|
|
219
|
-
|
|
220
|
-
Gets file/directory information (follows symbolic links).
|
|
221
|
-
|
|
222
|
-
```typescript
|
|
223
|
-
function statSync(targetPath: string): fs.Stats;
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### `stat`
|
|
227
|
-
|
|
228
|
-
Gets file/directory information (follows symbolic links, asynchronous).
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
function stat(targetPath: string): Promise<fs.Stats>;
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### `lstatSync`
|
|
235
|
-
|
|
236
|
-
Gets file/directory information (does not follow symbolic links).
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
function lstatSync(targetPath: string): fs.Stats;
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### `lstat`
|
|
243
|
-
|
|
244
|
-
Gets file/directory information (does not follow symbolic links, asynchronous).
|
|
245
|
-
|
|
246
|
-
```typescript
|
|
247
|
-
function lstat(targetPath: string): Promise<fs.Stats>;
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
## Glob
|
|
251
|
-
|
|
252
|
-
### `globSync`
|
|
253
|
-
|
|
254
|
-
Searches for files using a glob pattern.
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
function globSync(pattern: string, options?: GlobOptions): string[];
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Returns an array of absolute paths for matched files.
|
|
261
|
-
|
|
262
|
-
### `glob`
|
|
263
|
-
|
|
264
|
-
Searches for files using a glob pattern (asynchronous).
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
function glob(pattern: string, options?: GlobOptions): Promise<string[]>;
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
Returns an array of absolute paths for matched files.
|
|
271
|
-
|
|
272
|
-
## Utilities
|
|
273
|
-
|
|
274
|
-
### `clearEmptyDirectory`
|
|
275
|
-
|
|
276
|
-
Recursively searches and deletes empty directories under a specified directory. If all child directories are deleted and a parent becomes empty, it will also be deleted.
|
|
277
|
-
|
|
278
|
-
```typescript
|
|
279
|
-
function clearEmptyDirectory(dirPath: string): Promise<void>;
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
### `findAllParentChildPathsSync`
|
|
283
|
-
|
|
284
|
-
Searches for files matching a glob pattern by traversing parent directories from a start path towards the root. Collects all file paths matching the `childGlob` pattern in each directory.
|
|
285
|
-
|
|
286
|
-
```typescript
|
|
287
|
-
function findAllParentChildPathsSync(
|
|
288
|
-
childGlob: string,
|
|
289
|
-
fromPath: string,
|
|
290
|
-
rootPath?: string,
|
|
291
|
-
): string[];
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
**Parameters:**
|
|
295
|
-
- `childGlob` -- Glob pattern to search for in each directory
|
|
296
|
-
- `fromPath` -- Path to start searching from
|
|
297
|
-
- `rootPath` -- Path to stop searching at (if not specified, searches to filesystem root). `fromPath` must be a child path of `rootPath`. Otherwise, searches to the filesystem root.
|
|
298
|
-
|
|
299
|
-
### `findAllParentChildPaths`
|
|
300
|
-
|
|
301
|
-
Asynchronous version of `findAllParentChildPathsSync`.
|
|
302
|
-
|
|
303
|
-
```typescript
|
|
304
|
-
function findAllParentChildPaths(
|
|
305
|
-
childGlob: string,
|
|
306
|
-
fromPath: string,
|
|
307
|
-
rootPath?: string,
|
|
308
|
-
): Promise<string[]>;
|
|
309
|
-
```
|
package/docs/path.md
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# Path Utilities
|
|
2
|
-
|
|
3
|
-
Path utilities exported as the `pathx` namespace.
|
|
4
|
-
|
|
5
|
-
```typescript
|
|
6
|
-
import { pathx } from "@simplysm/core-node";
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## Types
|
|
10
|
-
|
|
11
|
-
### `NormPath`
|
|
12
|
-
|
|
13
|
-
Brand type representing a normalized path. Can only be created through `norm()`.
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
type NormPath = string & { [NORM]: never };
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Functions
|
|
20
|
-
|
|
21
|
-
### `posix`
|
|
22
|
-
|
|
23
|
-
Converts to POSIX-style path (backslash to forward slash).
|
|
24
|
-
|
|
25
|
-
```typescript
|
|
26
|
-
function posix(...args: string[]): string;
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
**Examples:**
|
|
30
|
-
```typescript
|
|
31
|
-
posix("C:\\Users\\test"); // "C:/Users/test"
|
|
32
|
-
posix("src", "index.ts"); // "src/index.ts"
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### `changeFileDirectory`
|
|
36
|
-
|
|
37
|
-
Changes the directory of a file path.
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
function changeFileDirectory(
|
|
41
|
-
filePath: string,
|
|
42
|
-
fromDirectory: string,
|
|
43
|
-
toDirectory: string,
|
|
44
|
-
): string;
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Throws an error if the file is not inside `fromDirectory`.
|
|
48
|
-
|
|
49
|
-
**Example:**
|
|
50
|
-
```typescript
|
|
51
|
-
changeFileDirectory("/a/b/c.txt", "/a", "/x");
|
|
52
|
-
// "/x/b/c.txt"
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### `basenameWithoutExt`
|
|
56
|
-
|
|
57
|
-
Returns the filename (basename) without extension.
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
function basenameWithoutExt(filePath: string): string;
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
**Examples:**
|
|
64
|
-
```typescript
|
|
65
|
-
basenameWithoutExt("file.txt"); // "file"
|
|
66
|
-
basenameWithoutExt("/path/to/file.spec.ts"); // "file.spec"
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### `isChildPath`
|
|
70
|
-
|
|
71
|
-
Checks if `childPath` is a child path of `parentPath`. Returns `false` if the paths are the same.
|
|
72
|
-
|
|
73
|
-
Paths are internally normalized using `norm()` and compared using platform-specific path separators.
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
function isChildPath(childPath: string, parentPath: string): boolean;
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
**Examples:**
|
|
80
|
-
```typescript
|
|
81
|
-
isChildPath("/a/b/c", "/a/b"); // true
|
|
82
|
-
isChildPath("/a/b", "/a/b/c"); // false
|
|
83
|
-
isChildPath("/a/b", "/a/b"); // false (same path)
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### `norm`
|
|
87
|
-
|
|
88
|
-
Normalizes the path and returns it as `NormPath`. Converts to absolute path and normalizes using platform-specific separators.
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
function norm(...paths: string[]): NormPath;
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
**Examples:**
|
|
95
|
-
```typescript
|
|
96
|
-
norm("/some/path"); // NormPath
|
|
97
|
-
norm("relative", "path"); // NormPath (converted to absolute path)
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### `filterByTargets`
|
|
101
|
-
|
|
102
|
-
Filters files based on a list of target paths. Includes files that match or are children of a target path.
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
function filterByTargets(files: string[], targets: string[], cwd: string): string[];
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**Parameters:**
|
|
109
|
-
- `files` -- File paths to filter. Must be absolute paths under `cwd`.
|
|
110
|
-
- `targets` -- Target paths (relative to `cwd`, POSIX style recommended)
|
|
111
|
-
- `cwd` -- Current working directory (absolute path)
|
|
112
|
-
|
|
113
|
-
If `targets` is empty, returns `files` as-is; otherwise returns only files under target paths.
|
|
114
|
-
|
|
115
|
-
**Example:**
|
|
116
|
-
```typescript
|
|
117
|
-
const files = ["/proj/src/a.ts", "/proj/src/b.ts", "/proj/tests/c.ts"];
|
|
118
|
-
filterByTargets(files, ["src"], "/proj");
|
|
119
|
-
// ["/proj/src/a.ts", "/proj/src/b.ts"]
|
|
120
|
-
```
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import os from "os";
|
|
5
|
-
import { FsWatcher } from "../../src/features/fs-watcher";
|
|
6
|
-
|
|
7
|
-
describe("SdFsWatcher", () => {
|
|
8
|
-
const testDir = path.join(os.tmpdir(), "fs-watcher-test-" + Date.now());
|
|
9
|
-
let watcher: FsWatcher | undefined;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(async () => {
|
|
16
|
-
if (watcher != null) {
|
|
17
|
-
await watcher.close();
|
|
18
|
-
watcher = undefined;
|
|
19
|
-
}
|
|
20
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
//#region watch
|
|
24
|
-
|
|
25
|
-
describe("watch", () => {
|
|
26
|
-
it("starts watching files", async () => {
|
|
27
|
-
watcher = await FsWatcher.watch([path.join(testDir, "**/*")]);
|
|
28
|
-
expect(watcher).toBeDefined();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
//#endregion
|
|
34
|
-
|
|
35
|
-
//#region close
|
|
36
|
-
|
|
37
|
-
describe("close", () => {
|
|
38
|
-
it("closes the watcher", async () => {
|
|
39
|
-
watcher = await FsWatcher.watch([path.join(testDir, "**/*")]);
|
|
40
|
-
|
|
41
|
-
// Test passes if close() completes without error
|
|
42
|
-
await expect(watcher.close()).resolves.toBeUndefined();
|
|
43
|
-
|
|
44
|
-
// Release watcher reference after closing
|
|
45
|
-
watcher = undefined;
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
//#endregion
|
|
50
|
-
|
|
51
|
-
//#region chaining
|
|
52
|
-
|
|
53
|
-
describe("onChange", () => {
|
|
54
|
-
it("supports onChange method chaining", async () => {
|
|
55
|
-
watcher = await FsWatcher.watch([path.join(testDir, "**/*")]);
|
|
56
|
-
|
|
57
|
-
const fn = vi.fn();
|
|
58
|
-
const result = watcher.onChange({ delay: 100 }, fn);
|
|
59
|
-
|
|
60
|
-
expect(result).toBe(watcher);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
//#endregion
|
|
65
|
-
|
|
66
|
-
//#region Glob Pattern Filtering
|
|
67
|
-
|
|
68
|
-
describe("glob pattern filtering", () => {
|
|
69
|
-
const DELAY = 300;
|
|
70
|
-
|
|
71
|
-
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
72
|
-
|
|
73
|
-
const waitForChanges = (
|
|
74
|
-
watcherInstance: FsWatcher,
|
|
75
|
-
delay: number,
|
|
76
|
-
): Promise<Array<{ event: string; path: string }>> => {
|
|
77
|
-
return new Promise((resolve) => {
|
|
78
|
-
watcherInstance.onChange({ delay }, (changeInfos) => {
|
|
79
|
-
resolve(changeInfos.map((c) => ({ event: c.event, path: c.path })));
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
it("receives events only for files matching glob pattern", async () => {
|
|
85
|
-
// Glob pattern that watches only .txt files
|
|
86
|
-
const globPattern = path.join(testDir, "**/*.txt");
|
|
87
|
-
|
|
88
|
-
watcher = await FsWatcher.watch([globPattern]);
|
|
89
|
-
|
|
90
|
-
const changesPromise = waitForChanges(watcher, DELAY);
|
|
91
|
-
|
|
92
|
-
// Create .txt file (matches)
|
|
93
|
-
fs.writeFileSync(path.join(testDir, "matched.txt"), "hello");
|
|
94
|
-
|
|
95
|
-
// Create .json file (does not match)
|
|
96
|
-
await wait(50);
|
|
97
|
-
fs.writeFileSync(path.join(testDir, "ignored.json"), "{}");
|
|
98
|
-
|
|
99
|
-
const changes = await changesPromise;
|
|
100
|
-
|
|
101
|
-
// Should only receive events for .txt file
|
|
102
|
-
expect(changes.length).toBe(1);
|
|
103
|
-
expect(changes[0].path).toContain("matched.txt");
|
|
104
|
-
expect(changes[0].event).toBe("add");
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
//#endregion
|
|
109
|
-
|
|
110
|
-
//#region Event Merging
|
|
111
|
-
|
|
112
|
-
describe("event merging", () => {
|
|
113
|
-
const DELAY = 300;
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Helper function to wait for specified time.
|
|
117
|
-
*/
|
|
118
|
-
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Helper function to wait until event callback is called.
|
|
122
|
-
*/
|
|
123
|
-
const waitForChanges = (
|
|
124
|
-
watcherInstance: FsWatcher,
|
|
125
|
-
delay: number,
|
|
126
|
-
): Promise<Array<{ event: string; path: string }>> => {
|
|
127
|
-
return new Promise((resolve) => {
|
|
128
|
-
watcherInstance.onChange({ delay }, (changeInfos) => {
|
|
129
|
-
resolve(changeInfos.map((c) => ({ event: c.event, path: c.path })));
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
it("returns only add event when file is added then modified", async () => {
|
|
135
|
-
const testFile = path.join(testDir, "test-add-change.txt");
|
|
136
|
-
|
|
137
|
-
watcher = await FsWatcher.watch([testDir]);
|
|
138
|
-
|
|
139
|
-
const changesPromise = waitForChanges(watcher, DELAY);
|
|
140
|
-
|
|
141
|
-
// Add file
|
|
142
|
-
fs.writeFileSync(testFile, "initial");
|
|
143
|
-
|
|
144
|
-
// Modify file with short interval (must occur within delay)
|
|
145
|
-
await wait(50);
|
|
146
|
-
fs.writeFileSync(testFile, "modified");
|
|
147
|
-
|
|
148
|
-
// Wait for event callback
|
|
149
|
-
const changes = await changesPromise;
|
|
150
|
-
|
|
151
|
-
// add → change should be merged to add
|
|
152
|
-
expect(changes.length).toBe(1);
|
|
153
|
-
expect(changes[0].event).toBe("add");
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("produces no events or no changes when file is added then deleted", async () => {
|
|
157
|
-
const testFile = path.join(testDir, "test-add-unlink.txt");
|
|
158
|
-
|
|
159
|
-
watcher = await FsWatcher.watch([testDir]);
|
|
160
|
-
|
|
161
|
-
const changes: Array<{ event: string; path: string }> = [];
|
|
162
|
-
let resolved = false;
|
|
163
|
-
|
|
164
|
-
const changesPromise = new Promise<void>((resolve) => {
|
|
165
|
-
watcher!.onChange({ delay: DELAY }, (changeInfos) => {
|
|
166
|
-
changes.push(...changeInfos.map((c) => ({ event: c.event, path: c.path })));
|
|
167
|
-
if (!resolved) {
|
|
168
|
-
resolved = true;
|
|
169
|
-
resolve();
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Add file
|
|
175
|
-
fs.writeFileSync(testFile, "content");
|
|
176
|
-
|
|
177
|
-
// Delete file with short interval
|
|
178
|
-
await wait(50);
|
|
179
|
-
fs.unlinkSync(testFile);
|
|
180
|
-
|
|
181
|
-
// Wait with timeout (event may not occur)
|
|
182
|
-
await Promise.race([changesPromise, wait(DELAY + 200)]);
|
|
183
|
-
|
|
184
|
-
// add → unlink merged, no events
|
|
185
|
-
expect(changes.length).toBe(0);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it("produces no events or no changes when directory is added then deleted", async () => {
|
|
189
|
-
const testSubDir = path.join(testDir, "test-addDir-unlinkDir");
|
|
190
|
-
|
|
191
|
-
watcher = await FsWatcher.watch([testDir]);
|
|
192
|
-
|
|
193
|
-
const changes: Array<{ event: string; path: string }> = [];
|
|
194
|
-
let resolved = false;
|
|
195
|
-
|
|
196
|
-
const changesPromise = new Promise<void>((resolve) => {
|
|
197
|
-
watcher!.onChange({ delay: DELAY }, (changeInfos) => {
|
|
198
|
-
changes.push(...changeInfos.map((c) => ({ event: c.event, path: c.path })));
|
|
199
|
-
if (!resolved) {
|
|
200
|
-
resolved = true;
|
|
201
|
-
resolve();
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// Add directory
|
|
207
|
-
fs.mkdirSync(testSubDir);
|
|
208
|
-
|
|
209
|
-
// Delete directory with short interval
|
|
210
|
-
await wait(50);
|
|
211
|
-
fs.rmdirSync(testSubDir);
|
|
212
|
-
|
|
213
|
-
// Wait with timeout (event may not occur)
|
|
214
|
-
await Promise.race([changesPromise, wait(DELAY + 200)]);
|
|
215
|
-
|
|
216
|
-
// addDir → unlinkDir merged, no events
|
|
217
|
-
expect(changes.length).toBe(0);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it("merges to add event when file is deleted then recreated", async () => {
|
|
221
|
-
const testFile = path.join(testDir, "test-unlink-add.txt");
|
|
222
|
-
|
|
223
|
-
// Pre-create file
|
|
224
|
-
fs.writeFileSync(testFile, "initial");
|
|
225
|
-
|
|
226
|
-
watcher = await FsWatcher.watch([testDir]);
|
|
227
|
-
|
|
228
|
-
const changesPromise = waitForChanges(watcher, DELAY);
|
|
229
|
-
|
|
230
|
-
// Delete file
|
|
231
|
-
fs.unlinkSync(testFile);
|
|
232
|
-
|
|
233
|
-
// Recreate file with short interval (must occur within delay)
|
|
234
|
-
await wait(50);
|
|
235
|
-
fs.writeFileSync(testFile, "recreated");
|
|
236
|
-
|
|
237
|
-
// Wait for event callback
|
|
238
|
-
const changes = await changesPromise;
|
|
239
|
-
|
|
240
|
-
// unlink → add/change should be merged to add (overwritten by later event)
|
|
241
|
-
// Depending on environment, chokidar may only emit change without unlink (WSL2, etc)
|
|
242
|
-
expect(changes.length).toBe(1);
|
|
243
|
-
expect(["add", "change"]).toContain(changes[0].event);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("correctly merges events when multiple files are modified", async () => {
|
|
247
|
-
const file1 = path.join(testDir, "file1.txt");
|
|
248
|
-
const file2 = path.join(testDir, "file2.txt");
|
|
249
|
-
const file3 = path.join(testDir, "file3.txt");
|
|
250
|
-
|
|
251
|
-
// Pre-create file3 (to trigger change event)
|
|
252
|
-
fs.writeFileSync(file3, "existing");
|
|
253
|
-
|
|
254
|
-
watcher = await FsWatcher.watch([testDir]);
|
|
255
|
-
|
|
256
|
-
const changesPromise = waitForChanges(watcher, DELAY);
|
|
257
|
-
|
|
258
|
-
// file1: only add
|
|
259
|
-
fs.writeFileSync(file1, "content1");
|
|
260
|
-
|
|
261
|
-
// file2: add then delete (merged and removed)
|
|
262
|
-
await wait(50);
|
|
263
|
-
fs.writeFileSync(file2, "content2");
|
|
264
|
-
await wait(50);
|
|
265
|
-
fs.unlinkSync(file2);
|
|
266
|
-
|
|
267
|
-
// file3: modify
|
|
268
|
-
await wait(50);
|
|
269
|
-
fs.writeFileSync(file3, "modified");
|
|
270
|
-
|
|
271
|
-
// Wait for event callback
|
|
272
|
-
const changes = await changesPromise;
|
|
273
|
-
|
|
274
|
-
// file1: add, file2: removed by merge, file3: change
|
|
275
|
-
expect(changes.length).toBe(2);
|
|
276
|
-
|
|
277
|
-
const file1Change = changes.find((c) => c.path.endsWith("file1.txt"));
|
|
278
|
-
const file3Change = changes.find((c) => c.path.endsWith("file3.txt"));
|
|
279
|
-
|
|
280
|
-
expect(file1Change?.event).toBe("add");
|
|
281
|
-
expect(file3Change?.event).toBe("change");
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
//#endregion
|
|
286
|
-
});
|