@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.
Files changed (44) hide show
  1. package/README.md +93 -79
  2. package/dist/features/fs-watcher.d.ts +21 -21
  3. package/dist/features/fs-watcher.d.ts.map +1 -1
  4. package/dist/features/fs-watcher.js +176 -114
  5. package/dist/features/fs-watcher.js.map +1 -6
  6. package/dist/index.js +6 -7
  7. package/dist/index.js.map +1 -6
  8. package/dist/utils/fs.d.ts +96 -96
  9. package/dist/utils/fs.d.ts.map +1 -1
  10. package/dist/utils/fs.js +437 -272
  11. package/dist/utils/fs.js.map +1 -6
  12. package/dist/utils/path.d.ts +22 -22
  13. package/dist/utils/path.js +103 -45
  14. package/dist/utils/path.js.map +1 -6
  15. package/dist/worker/create-worker.d.ts +3 -3
  16. package/dist/worker/create-worker.js +106 -81
  17. package/dist/worker/create-worker.js.map +1 -6
  18. package/dist/worker/types.d.ts +14 -14
  19. package/dist/worker/types.js +4 -1
  20. package/dist/worker/types.js.map +1 -6
  21. package/dist/worker/worker.d.ts +5 -5
  22. package/dist/worker/worker.js +168 -132
  23. package/dist/worker/worker.js.map +1 -6
  24. package/docs/fs-watcher.md +107 -0
  25. package/docs/fsx.md +287 -0
  26. package/docs/pathx.md +115 -0
  27. package/docs/worker.md +117 -62
  28. package/lib/worker-dev-proxy.js +15 -0
  29. package/package.json +9 -6
  30. package/src/features/fs-watcher.ts +53 -42
  31. package/src/index.ts +3 -3
  32. package/src/utils/fs.ts +111 -120
  33. package/src/utils/path.ts +26 -26
  34. package/src/worker/create-worker.ts +10 -10
  35. package/src/worker/types.ts +14 -14
  36. package/src/worker/worker.ts +29 -29
  37. package/docs/features.md +0 -91
  38. package/docs/fs.md +0 -309
  39. package/docs/path.md +0 -120
  40. package/tests/utils/fs-watcher.spec.ts +0 -286
  41. package/tests/utils/fs.spec.ts +0 -705
  42. package/tests/utils/path.spec.ts +0 -179
  43. package/tests/worker/fixtures/test-worker.ts +0 -35
  44. 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
- });