@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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @simplysm/core-node
2
2
 
3
- Simplysm package - Core module (node). Node.js utilities for file system operations, path manipulation, file watching, and type-safe worker threads.
3
+ Core Node.js utilities -- file system operations, path utilities, file watcher, typed worker threads.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,105 +8,119 @@ Simplysm package - Core module (node). Node.js utilities for file system operati
8
8
  npm install @simplysm/core-node
9
9
  ```
10
10
 
11
- ## API Overview
12
-
13
- ### File System Utils (`fsx` namespace)
14
-
15
- | API | Type | Description |
16
- |-----|------|-------------|
17
- | `existsSync` | function | Check if a file or directory exists (sync) |
18
- | `exists` | function | Check if a file or directory exists (async) |
19
- | `mkdirSync` | function | Create a directory recursively (sync) |
20
- | `mkdir` | function | Create a directory recursively (async) |
21
- | `rmSync` | function | Delete a file or directory (sync) |
22
- | `rm` | function | Delete a file or directory with retries (async) |
23
- | `copySync` | function | Copy a file or directory with optional filter (sync) |
24
- | `copy` | function | Copy a file or directory with optional filter (async) |
25
- | `readSync` | function | Read a file as UTF-8 string (sync) |
26
- | `read` | function | Read a file as UTF-8 string (async) |
27
- | `readBufferSync` | function | Read a file as Buffer (sync) |
28
- | `readBuffer` | function | Read a file as Buffer (async) |
29
- | `readJsonSync` | function | Read a JSON file using JsonConvert (sync) |
30
- | `readJson` | function | Read a JSON file using JsonConvert (async) |
31
- | `writeSync` | function | Write data to a file, auto-creates parent dirs (sync) |
32
- | `write` | function | Write data to a file, auto-creates parent dirs (async) |
33
- | `writeJsonSync` | function | Write data to a JSON file (sync) |
34
- | `writeJson` | function | Write data to a JSON file (async) |
35
- | `readdirSync` | function | Read directory contents (sync) |
36
- | `readdir` | function | Read directory contents (async) |
37
- | `statSync` | function | Get file info, follows symlinks (sync) |
38
- | `stat` | function | Get file info, follows symlinks (async) |
39
- | `lstatSync` | function | Get file info, no symlink follow (sync) |
40
- | `lstat` | function | Get file info, no symlink follow (async) |
41
- | `globSync` | function | Search files by glob pattern (sync) |
42
- | `glob` | function | Search files by glob pattern (async) |
43
- | `clearEmptyDirectory` | function | Recursively delete empty directories |
44
- | `findAllParentChildPathsSync` | function | Search for glob matches traversing parent dirs (sync) |
45
- | `findAllParentChildPaths` | function | Search for glob matches traversing parent dirs (async) |
46
-
47
- -> See [docs/fs.md](./docs/fs.md) for details.
48
-
49
- ### Path Utils (`pathx` namespace)
50
-
51
- | API | Type | Description |
52
- |-----|------|-------------|
53
- | `NormPath` | type | Brand type for normalized path |
54
- | `posix` | function | Convert to POSIX-style path (backslash to forward slash) |
55
- | `changeFileDirectory` | function | Change the directory of a file path |
11
+ ## API
12
+
13
+ ### fsx -- File System Utilities
14
+
15
+ Namespace with sync/async file system operations. See [docs/fsx.md](docs/fsx.md) for full API.
16
+
17
+ | Function | Signature | Description |
18
+ |----------|-----------|-------------|
19
+ | `existsSync` | `(targetPath: string): boolean` | Check if path exists (sync) |
20
+ | `exists` | `(targetPath: string): Promise<boolean>` | Check if path exists (async) |
21
+ | `mkdirSync` | `(targetPath: string): void` | Create directory recursively (sync) |
22
+ | `mkdir` | `(targetPath: string): Promise<void>` | Create directory recursively (async) |
23
+ | `rmSync` | `(targetPath: string): void` | Remove file/directory (sync) |
24
+ | `rm` | `(targetPath: string): Promise<void>` | Remove file/directory with retry (async) |
25
+ | `copySync` | `(sourcePath, targetPath, filter?): void` | Copy file/directory (sync) |
26
+ | `copy` | `(sourcePath, targetPath, filter?): Promise<void>` | Copy file/directory (async) |
27
+ | `readSync` | `(targetPath: string): string` | Read file as UTF-8 string (sync) |
28
+ | `read` | `(targetPath: string): Promise<string>` | Read file as UTF-8 string (async) |
29
+ | `readBufferSync` | `(targetPath: string): Buffer` | Read file as Buffer (sync) |
30
+ | `readBuffer` | `(targetPath: string): Promise<Buffer>` | Read file as Buffer (async) |
31
+ | `readJsonSync` | `<T>(targetPath: string): T` | Read and parse JSON file (sync) |
32
+ | `readJson` | `<T>(targetPath: string): Promise<T>` | Read and parse JSON file (async) |
33
+ | `writeSync` | `(targetPath, data): void` | Write file with auto-mkdir (sync) |
34
+ | `write` | `(targetPath, data): Promise<void>` | Write file with auto-mkdir (async) |
35
+ | `writeJsonSync` | `(targetPath, data, options?): void` | Write JSON file (sync) |
36
+ | `writeJson` | `(targetPath, data, options?): Promise<void>` | Write JSON file (async) |
37
+ | `readdirSync` | `(targetPath: string): string[]` | List directory contents (sync) |
38
+ | `readdir` | `(targetPath: string): Promise<string[]>` | List directory contents (async) |
39
+ | `statSync` | `(targetPath: string): fs.Stats` | Get file info, follows symlinks (sync) |
40
+ | `stat` | `(targetPath: string): Promise<fs.Stats>` | Get file info, follows symlinks (async) |
41
+ | `lstatSync` | `(targetPath: string): fs.Stats` | Get file info, no symlink follow (sync) |
42
+ | `lstat` | `(targetPath: string): Promise<fs.Stats>` | Get file info, no symlink follow (async) |
43
+ | `globSync` | `(pattern, options?): string[]` | Glob file search (sync) |
44
+ | `glob` | `(pattern, options?): Promise<string[]>` | Glob file search (async) |
45
+ | `clearEmptyDirectory` | `(dirPath: string): Promise<void>` | Recursively remove empty directories |
46
+ | `findAllParentChildPathsSync` | `(childGlob, fromPath, rootPath?): string[]` | Search parent dirs for glob matches (sync) |
47
+ | `findAllParentChildPaths` | `(childGlob, fromPath, rootPath?): Promise<string[]>` | Search parent dirs for glob matches (async) |
48
+
49
+ ### pathx -- Path Utilities
50
+
51
+ Namespace with path manipulation utilities. See [docs/pathx.md](docs/pathx.md) for full API.
52
+
53
+ | Export | Kind | Description |
54
+ |--------|------|-------------|
55
+ | `NormPath` | type | Branded type for normalized absolute paths |
56
+ | `posix` | function | Convert path to POSIX style (backslash to slash) |
57
+ | `changeFileDirectory` | function | Change the directory portion of a file path |
56
58
  | `basenameWithoutExt` | function | Get filename without extension |
57
- | `isChildPath` | function | Check if a path is a child of another path |
58
- | `norm` | function | Normalize path to absolute NormPath |
59
- | `filterByTargets` | function | Filter files by target path list |
59
+ | `isChildPath` | function | Check if a path is a child of another |
60
+ | `norm` | function | Normalize and resolve path to `NormPath` |
61
+ | `filterByTargets` | function | Filter file paths by target directory prefixes |
60
62
 
61
- -> See [docs/path.md](./docs/path.md) for details.
63
+ ### FsWatcher -- File System Watcher
62
64
 
63
- ### Features
65
+ Chokidar-based file watcher with debounced change events. See [docs/fs-watcher.md](docs/fs-watcher.md) for full API.
64
66
 
65
- | API | Type | Description |
66
- |-----|------|-------------|
67
- | `FsWatcherEvent` | type | File change event type (`add`, `addDir`, `change`, `unlink`, `unlinkDir`) |
68
- | `FsWatcherChangeInfo` | interface | File change information (`event`, `path`) |
69
- | `FsWatcher` | class | Chokidar-based file watcher with event merging |
67
+ | Export | Kind | Description |
68
+ |--------|------|-------------|
69
+ | `FsWatcherEvent` | type | `"add" \| "addDir" \| "change" \| "unlink" \| "unlinkDir"` |
70
+ | `FsWatcherChangeInfo` | interface | Change event info with path and event type |
71
+ | `FsWatcher` | class | Debounced file system watcher |
70
72
 
71
- -> See [docs/features.md](./docs/features.md) for details.
73
+ ### Worker -- Typed Worker Threads
72
74
 
73
- ### Worker System
75
+ Type-safe worker thread wrapper with Proxy-based RPC. See [docs/worker.md](docs/worker.md) for full API.
74
76
 
75
- | API | Type | Description |
76
- |-----|------|-------------|
77
- | `WorkerModule` | interface | Worker module type structure for type inference |
78
- | `PromisifyMethods` | type | Maps method return values to Promise |
79
- | `WorkerProxy` | type | Proxy type with promisified methods + on/off/terminate |
77
+ | Export | Kind | Description |
78
+ |--------|------|-------------|
79
+ | `WorkerModule` | interface | Type structure for worker modules |
80
+ | `PromisifyMethods` | type | Maps sync method signatures to async |
81
+ | `WorkerProxy` | type | Proxy type returned by `Worker.create()` |
80
82
  | `WorkerRequest` | interface | Internal worker request message |
81
- | `WorkerResponse` | type | Internal worker response message |
82
- | `Worker` | object | Type-safe Worker wrapper with `create()` factory |
83
- | `createWorker` | function | Worker factory for use in worker threads |
83
+ | `WorkerResponse` | type | Internal worker response message (union) |
84
+ | `Worker` | const | Factory object with `create()` method |
85
+ | `createWorker` | function | Define a worker module in a worker thread |
84
86
 
85
- -> See [docs/worker.md](./docs/worker.md) for details.
87
+ ## Usage
86
88
 
87
- ## Usage Examples
88
-
89
- ```typescript
90
- import { fsx, pathx, FsWatcher, Worker, createWorker } from "@simplysm/core-node";
89
+ ```ts
90
+ import { fsx, pathx } from "@simplysm/core-node";
91
91
 
92
92
  // File system
93
- const content = await fsx.read("/path/to/file.txt");
94
- await fsx.write("/path/to/output.txt", content);
93
+ await fsx.mkdir("/tmp/mydir");
94
+ await fsx.write("/tmp/mydir/hello.txt", "Hello, world!");
95
+ const content = await fsx.read("/tmp/mydir/hello.txt");
96
+ const config = await fsx.readJson<{ port: number }>("config.json");
95
97
 
96
98
  // Path utilities
97
- const normalized = pathx.norm("/some/path");
98
- const posixPath = pathx.posix("C:\\Users\\test");
99
+ const posixPath = pathx.posix("C:\\Users\\test"); // "C:/Users/test"
100
+ const normalized = pathx.norm("/some/path"); // NormPath
101
+ const isChild = pathx.isChildPath("/a/b/c", "/a/b"); // true
102
+
103
+ // File watcher
104
+ import { FsWatcher } from "@simplysm/core-node";
99
105
 
100
- // File watching
101
106
  const watcher = await FsWatcher.watch(["src/**/*.ts"]);
102
107
  watcher.onChange({ delay: 300 }, (changes) => {
103
108
  for (const { path, event } of changes) {
104
- // handle changes
109
+ console.log(`${event}: ${path}`);
105
110
  }
106
111
  });
112
+ await watcher.close();
113
+
114
+ // Worker threads
115
+ import { Worker, createWorker } from "@simplysm/core-node";
116
+
117
+ // worker.ts
118
+ export default createWorker({
119
+ add: (a: number, b: number) => a + b,
120
+ });
107
121
 
108
- // Workers
122
+ // main.ts
109
123
  const worker = Worker.create<typeof import("./worker")>("./worker.ts");
110
- const result = await worker.add(10, 20);
124
+ const result = await worker.add(10, 20); // 30
111
125
  await worker.terminate();
112
126
  ```
@@ -1,30 +1,30 @@
1
1
  import * as chokidar from "chokidar";
2
2
  import { type NormPath } from "../utils/path";
3
3
  /**
4
- * List of supported file change event types.
4
+ * 지원되는 파일 변경 이벤트 타입 목록.
5
5
  */
6
6
  declare const FS_WATCHER_EVENTS: readonly ["add", "addDir", "change", "unlink", "unlinkDir"];
7
7
  /**
8
- * File change event type.
8
+ * 파일 변경 이벤트 타입.
9
9
  */
10
10
  export type FsWatcherEvent = (typeof FS_WATCHER_EVENTS)[number];
11
11
  /**
12
- * File change information.
12
+ * 파일 변경 정보.
13
13
  */
14
14
  export interface FsWatcherChangeInfo {
15
- /** Change event type */
15
+ /** 변경 이벤트 타입 */
16
16
  event: FsWatcherEvent;
17
- /** Changed file/directory path (normalized) */
17
+ /** 변경된 파일/디렉토리 경로 (정규화됨) */
18
18
  path: NormPath;
19
19
  }
20
20
  /**
21
- * Chokidar-based file system watcher wrapper.
22
- * Merges events that occur within a short time and calls the callback once.
21
+ * Chokidar 기반 파일 시스템 감시 래퍼.
22
+ * 짧은 시간 내에 발생하는 이벤트를 병합하여 콜백을 번만 호출한다.
23
23
  *
24
- * **Note**: The `ignoreInitial` option of chokidar is internally always set to `true`.
25
- * If you pass `options.ignoreInitial: false`, the callback will be called with an empty array on the first `onChange` call,
26
- * but the actual initial file list is not included.
27
- * This is intentional behavior to prevent conflicts with the event merging logic.
24
+ * **주의**: chokidar의 `ignoreInitial` 옵션은 내부적으로 항상 `true`로 설정된다.
25
+ * `options.ignoreInitial: false`를 전달하면 번째 `onChange` 호출 배열로 콜백이 호출되지만,
26
+ * 실제 초기 파일 목록은 포함되지 않는다.
27
+ * 이는 이벤트 병합 로직과의 충돌을 방지하기 위한 의도적인 동작이다.
28
28
  *
29
29
  * @example
30
30
  * const watcher = await FsWatcher.watch(["src/**\/*.ts"]);
@@ -34,16 +34,16 @@ export interface FsWatcherChangeInfo {
34
34
  * }
35
35
  * });
36
36
  *
37
- * // Close
37
+ * // 종료
38
38
  * await watcher.close();
39
39
  */
40
40
  export declare class FsWatcher {
41
41
  /**
42
- * Starts watching files (asynchronous).
43
- * Waits until the ready event is emitted.
42
+ * 파일 감시를 시작한다 (비동기).
43
+ * ready 이벤트가 발생할 때까지 대기한다.
44
44
  *
45
- * @param paths - Array of file/directory paths or glob patterns to watch
46
- * @param options - chokidar options
45
+ * @param paths - 감시할 파일/디렉토리 경로 또는 glob 패턴 배열
46
+ * @param options - chokidar 옵션
47
47
  */
48
48
  static watch(paths: string[], options?: chokidar.ChokidarOptions): Promise<FsWatcher>;
49
49
  private readonly _watcher;
@@ -53,17 +53,17 @@ export declare class FsWatcher {
53
53
  private readonly _logger;
54
54
  private constructor();
55
55
  /**
56
- * Registers a file change event handler.
57
- * Collects events for the specified delay time and calls the callback once.
56
+ * 파일 변경 이벤트 핸들러를 등록한다.
57
+ * 지정된 지연 시간 동안 이벤트를 수집하여 콜백을 호출한다.
58
58
  *
59
- * @param opt.delay - Event merge wait time (ms)
60
- * @param cb - Change event callback
59
+ * @param opt.delay - 이벤트 병합 대기 시간 (ms)
60
+ * @param cb - 변경 이벤트 콜백
61
61
  */
62
62
  onChange(opt: {
63
63
  delay?: number;
64
64
  }, cb: (changeInfos: FsWatcherChangeInfo[]) => void | Promise<void>): this;
65
65
  /**
66
- * Closes the file watcher.
66
+ * 파일 감시자를 종료한다.
67
67
  */
68
68
  close(): Promise<void>;
69
69
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fs-watcher.d.ts","sourceRoot":"","sources":["..\\..\\src\\features\\fs-watcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAKrC,OAAO,EAAE,KAAK,QAAQ,EAAQ,MAAM,eAAe,CAAC;AAyBpD;;GAEG;AACH,QAAA,MAAM,iBAAiB,6DAA8D,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,KAAK,EAAE,cAAc,CAAC;IACtB,+CAA+C;IAC/C,IAAI,EAAE,QAAQ,CAAC;CAChB;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,SAAS;IACpB;;;;;;OAMG;WACU,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC;IAU3F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuB;IACvD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;IAEjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAE5D,OAAO;IA6BP;;;;;;OAMG;IACH,QAAQ,CACN,GAAG,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EACvB,EAAE,EAAE,CAAC,WAAW,EAAE,mBAAmB,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI;IA2EP;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
1
+ {"version":3,"file":"fs-watcher.d.ts","sourceRoot":"","sources":["..\\..\\src\\features\\fs-watcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAKrC,OAAO,EAAE,KAAK,QAAQ,EAAQ,MAAM,eAAe,CAAC;AAyBpD;;GAEG;AACH,QAAA,MAAM,iBAAiB,6DAA8D,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,gBAAgB;IAChB,KAAK,EAAE,cAAc,CAAC;IACtB,4BAA4B;IAC5B,IAAI,EAAE,QAAQ,CAAC;CAChB;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,SAAS;IACpB;;;;;;OAMG;WACU,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC;IAqB3F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuB;IACvD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;IAEjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAE5D,OAAO;IA6BP;;;;;;OAMG;IACH,QAAQ,CACN,GAAG,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EACvB,EAAE,EAAE,CAAC,WAAW,EAAE,mBAAmB,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI;IA2EP;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
@@ -4,125 +4,187 @@ import consola from "consola";
4
4
  import { Minimatch } from "minimatch";
5
5
  import path from "path";
6
6
  import { norm } from "../utils/path.js";
7
+ //#region Helpers
8
+ /** Glob 메타문자 패턴 */
7
9
  const GLOB_CHARS_RE = /[*?{[\]]/;
10
+ /**
11
+ * Glob 패턴에서 기본 디렉토리를 추출한다.
12
+ * @example extractGlobBase("/home/user/src/**\/*.ts") → "/home/user/src"
13
+ */
8
14
  function extractGlobBase(globPath) {
9
- const segments = globPath.split(/[/\\]/);
10
- const baseSegments = [];
11
- for (const seg of segments) {
12
- if (GLOB_CHARS_RE.test(seg)) break;
13
- baseSegments.push(seg);
14
- }
15
- return baseSegments.join(path.sep) || path.sep;
15
+ const segments = globPath.split(/[/\\]/);
16
+ const baseSegments = [];
17
+ for (const seg of segments) {
18
+ if (GLOB_CHARS_RE.test(seg))
19
+ break;
20
+ baseSegments.push(seg);
21
+ }
22
+ return baseSegments.join(path.sep) || path.sep;
16
23
  }
24
+ //#endregion
25
+ //#region Types
26
+ /**
27
+ * 지원되는 파일 변경 이벤트 타입 목록.
28
+ */
17
29
  const FS_WATCHER_EVENTS = ["add", "addDir", "change", "unlink", "unlinkDir"];
18
- class FsWatcher {
19
- /**
20
- * Starts watching files (asynchronous).
21
- * Waits until the ready event is emitted.
22
- *
23
- * @param paths - Array of file/directory paths or glob patterns to watch
24
- * @param options - chokidar options
25
- */
26
- static async watch(paths, options) {
27
- return new Promise((resolve, reject) => {
28
- const watcher = new FsWatcher(paths, options);
29
- watcher._watcher.on("ready", () => {
30
- resolve(watcher);
31
- });
32
- watcher._watcher.on("error", reject);
33
- });
34
- }
35
- _watcher;
36
- _ignoreInitial = true;
37
- _debounceQueues = [];
38
- _globMatchers = [];
39
- _logger = consola.withTag("sd-fs-watcher");
40
- constructor(paths, options) {
41
- const watchPaths = [];
42
- for (const p of paths) {
43
- const posixPath = p.replace(/\\/g, "/");
44
- if (GLOB_CHARS_RE.test(posixPath)) {
45
- this._globMatchers.push(new Minimatch(posixPath, { dot: true }));
46
- watchPaths.push(extractGlobBase(p));
47
- } else {
48
- watchPaths.push(p);
49
- }
30
+ //#endregion
31
+ //#region FsWatcher
32
+ /**
33
+ * Chokidar 기반 파일 시스템 감시 래퍼.
34
+ * 짧은 시간 내에 발생하는 이벤트를 병합하여 콜백을 한 번만 호출한다.
35
+ *
36
+ * **주의**: chokidar의 `ignoreInitial` 옵션은 내부적으로 항상 `true`로 설정된다.
37
+ * `options.ignoreInitial: false`를 전달하면 첫 번째 `onChange` 호출 시 빈 배열로 콜백이 호출되지만,
38
+ * 실제 초기 파일 목록은 포함되지 않는다.
39
+ * 이는 이벤트 병합 로직과의 충돌을 방지하기 위한 의도적인 동작이다.
40
+ *
41
+ * @example
42
+ * const watcher = await FsWatcher.watch(["src/**\/*.ts"]);
43
+ * watcher.onChange({ delay: 300 }, (changes) => {
44
+ * for (const { path, event } of changes) {
45
+ * console.log(`${event}: ${path}`);
46
+ * }
47
+ * });
48
+ *
49
+ * // 종료
50
+ * await watcher.close();
51
+ */
52
+ export class FsWatcher {
53
+ /**
54
+ * 파일 감시를 시작한다 (비동기).
55
+ * ready 이벤트가 발생할 때까지 대기한다.
56
+ *
57
+ * @param paths - 감시할 파일/디렉토리 경로 또는 glob 패턴 배열
58
+ * @param options - chokidar 옵션
59
+ */
60
+ static async watch(paths, options) {
61
+ return new Promise((resolve, reject) => {
62
+ const watcher = new FsWatcher(paths, options);
63
+ const onReady = () => {
64
+ watcher._watcher.removeListener("error", onError);
65
+ resolve(watcher);
66
+ };
67
+ const onError = (err) => {
68
+ watcher._watcher.removeListener("ready", onReady);
69
+ watcher.close().then(() => reject(err), () => reject(err));
70
+ };
71
+ watcher._watcher.once("ready", onReady);
72
+ watcher._watcher.once("error", onError);
73
+ });
74
+ }
75
+ _watcher;
76
+ _ignoreInitial = true;
77
+ _debounceQueues = [];
78
+ _globMatchers = [];
79
+ _logger = consola.withTag("sd-fs-watcher");
80
+ constructor(paths, options) {
81
+ const watchPaths = [];
82
+ for (const p of paths) {
83
+ const posixPath = p.replace(/\\/g, "/");
84
+ if (GLOB_CHARS_RE.test(posixPath)) {
85
+ this._globMatchers.push(new Minimatch(posixPath, { dot: true }));
86
+ watchPaths.push(extractGlobBase(p));
87
+ }
88
+ else {
89
+ watchPaths.push(p);
90
+ }
91
+ }
92
+ // 중복 경로 제거
93
+ const uniquePaths = [...new Set(watchPaths)];
94
+ this._watcher = chokidar.watch(uniquePaths, {
95
+ persistent: true,
96
+ ...options,
97
+ ignoreInitial: true,
98
+ });
99
+ this._ignoreInitial = options?.ignoreInitial ?? this._ignoreInitial;
100
+ // 감시 중 발생하는 오류를 로깅
101
+ this._watcher.on("error", (err) => {
102
+ this._logger.error("FsWatcher 오류:", err);
103
+ });
50
104
  }
51
- const uniquePaths = [...new Set(watchPaths)];
52
- this._watcher = chokidar.watch(uniquePaths, {
53
- persistent: true,
54
- ...options,
55
- ignoreInitial: true
56
- });
57
- this._ignoreInitial = options?.ignoreInitial ?? this._ignoreInitial;
58
- this._watcher.on("error", (err) => {
59
- this._logger.error("FsWatcher error:", err);
60
- });
61
- }
62
- /**
63
- * Registers a file change event handler.
64
- * Collects events for the specified delay time and calls the callback once.
65
- *
66
- * @param opt.delay - Event merge wait time (ms)
67
- * @param cb - Change event callback
68
- */
69
- onChange(opt, cb) {
70
- const fnQ = new DebounceQueue(opt.delay);
71
- this._debounceQueues.push(fnQ);
72
- let changeInfoMap = /* @__PURE__ */ new Map();
73
- if (!this._ignoreInitial) {
74
- fnQ.run(async () => {
75
- await cb([]);
76
- });
105
+ /**
106
+ * 파일 변경 이벤트 핸들러를 등록한다.
107
+ * 지정된 지연 시간 동안 이벤트를 수집하여 콜백을 한 번 호출한다.
108
+ *
109
+ * @param opt.delay - 이벤트 병합 대기 시간 (ms)
110
+ * @param cb - 변경 이벤트 콜백
111
+ */
112
+ onChange(opt, cb) {
113
+ const fnQ = new DebounceQueue(opt.delay);
114
+ this._debounceQueues.push(fnQ);
115
+ let changeInfoMap = new Map();
116
+ // ignoreInitial이 false이면 초기에 빈 배열로 콜백 호출
117
+ if (!this._ignoreInitial) {
118
+ fnQ.run(async () => {
119
+ await cb([]);
120
+ });
121
+ }
122
+ this._watcher.on("all", (event, filePath) => {
123
+ // 지원되는 이벤트만 처리
124
+ if (!FS_WATCHER_EVENTS.includes(event))
125
+ return;
126
+ // glob matcher가 존재하면 패턴 필터링 적용
127
+ if (this._globMatchers.length > 0) {
128
+ const posixFilePath = filePath.replace(/\\/g, "/");
129
+ if (!this._globMatchers.some((m) => m.match(posixFilePath)))
130
+ return;
131
+ }
132
+ /*
133
+ * 이벤트 병합 전략:
134
+ * 같은 파일에 대해 짧은 시간 내에 여러 이벤트가 발생하면, 최종 상태만 전달한다.
135
+ * - add + change → add (생성 직후 수정은 생성으로 간주)
136
+ * - add + unlink → 변경 없음 (생성 직후 삭제는 변경 없음으로 간주)
137
+ * - unlink + add → add (삭제 후 재생성은 생성으로 간주)
138
+ * - 그 외 → 최신 이벤트로 덮어쓰기
139
+ */
140
+ if (!changeInfoMap.has(filePath)) {
141
+ changeInfoMap.set(filePath, event);
142
+ }
143
+ const prevEvent = changeInfoMap.get(filePath);
144
+ if (prevEvent === "add" && event === "change") {
145
+ // add 후 change → add 유지
146
+ changeInfoMap.set(filePath, "add");
147
+ }
148
+ else if ((prevEvent === "add" && event === "unlink") ||
149
+ (prevEvent === "addDir" && event === "unlinkDir")) {
150
+ // add 후 unlink → 변경 없음 (삭제)
151
+ changeInfoMap.delete(filePath);
152
+ }
153
+ else if (prevEvent === "unlink" && (event === "add" || event === "change")) {
154
+ // unlink 후 add/change → add (파일 재생성)
155
+ changeInfoMap.set(filePath, "add");
156
+ }
157
+ else if (prevEvent === "unlinkDir" && event === "addDir") {
158
+ // unlinkDir 후 addDir → addDir (디렉토리 재생성)
159
+ changeInfoMap.set(filePath, "addDir");
160
+ }
161
+ else {
162
+ changeInfoMap.set(filePath, event);
163
+ }
164
+ fnQ.run(async () => {
165
+ if (changeInfoMap.size === 0)
166
+ return;
167
+ const currChangeInfoMap = changeInfoMap;
168
+ changeInfoMap = new Map();
169
+ const changeInfos = Array.from(currChangeInfoMap.entries()).map(([changedPath, evt]) => ({
170
+ path: norm(changedPath),
171
+ event: evt,
172
+ }));
173
+ await cb(changeInfos);
174
+ });
175
+ });
176
+ return this;
77
177
  }
78
- this._watcher.on("all", (event, filePath) => {
79
- if (!FS_WATCHER_EVENTS.includes(event)) return;
80
- if (this._globMatchers.length > 0) {
81
- const posixFilePath = filePath.replace(/\\/g, "/");
82
- if (!this._globMatchers.some((m) => m.match(posixFilePath))) return;
83
- }
84
- if (!changeInfoMap.has(filePath)) {
85
- changeInfoMap.set(filePath, event);
86
- }
87
- const prevEvent = changeInfoMap.get(filePath);
88
- if (prevEvent === "add" && event === "change") {
89
- changeInfoMap.set(filePath, "add");
90
- } else if (prevEvent === "add" && event === "unlink" || prevEvent === "addDir" && event === "unlinkDir") {
91
- changeInfoMap.delete(filePath);
92
- } else if (prevEvent === "unlink" && (event === "add" || event === "change")) {
93
- changeInfoMap.set(filePath, "add");
94
- } else if (prevEvent === "unlinkDir" && event === "addDir") {
95
- changeInfoMap.set(filePath, "addDir");
96
- } else {
97
- changeInfoMap.set(filePath, event);
98
- }
99
- fnQ.run(async () => {
100
- if (changeInfoMap.size === 0) return;
101
- const currChangeInfoMap = changeInfoMap;
102
- changeInfoMap = /* @__PURE__ */ new Map();
103
- const changeInfos = Array.from(currChangeInfoMap.entries()).map(
104
- ([changedPath, evt]) => ({
105
- path: norm(changedPath),
106
- event: evt
107
- })
108
- );
109
- await cb(changeInfos);
110
- });
111
- });
112
- return this;
113
- }
114
- /**
115
- * Closes the file watcher.
116
- */
117
- async close() {
118
- for (const q of this._debounceQueues) {
119
- q.dispose();
178
+ /**
179
+ * 파일 감시자를 종료한다.
180
+ */
181
+ async close() {
182
+ for (const q of this._debounceQueues) {
183
+ q.dispose();
184
+ }
185
+ this._debounceQueues.length = 0;
186
+ await this._watcher.close();
120
187
  }
121
- this._debounceQueues.length = 0;
122
- await this._watcher.close();
123
- }
124
188
  }
125
- export {
126
- FsWatcher
127
- };
128
- //# sourceMappingURL=fs-watcher.js.map
189
+ //#endregion
190
+ //# sourceMappingURL=fs-watcher.js.map
@@ -1,6 +1 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/features/fs-watcher.ts"],
4
- "mappings": "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",
5
- "names": []
6
- }
1
+ {"version":3,"file":"fs-watcher.js","sourceRoot":"","sources":["..\\..\\src\\features\\fs-watcher.ts"],"names":[],"mappings":"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"}