@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/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @simplysm/core-node
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
12
|
-
|
|
13
|
-
### File System
|
|
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
|
-
| `
|
|
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
|
|
58
|
-
| `norm` | function | Normalize path to
|
|
59
|
-
| `filterByTargets` | function | Filter
|
|
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
|
-
|
|
63
|
+
### FsWatcher -- File System Watcher
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
Chokidar-based file watcher with debounced change events. See [docs/fs-watcher.md](docs/fs-watcher.md) for full API.
|
|
64
66
|
|
|
65
|
-
|
|
|
66
|
-
|
|
67
|
-
| `FsWatcherEvent` | type |
|
|
68
|
-
| `FsWatcherChangeInfo` | interface |
|
|
69
|
-
| `FsWatcher` | class |
|
|
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
|
-
|
|
73
|
+
### Worker -- Typed Worker Threads
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
Type-safe worker thread wrapper with Proxy-based RPC. See [docs/worker.md](docs/worker.md) for full API.
|
|
74
76
|
|
|
75
|
-
|
|
|
76
|
-
|
|
77
|
-
| `WorkerModule` | interface |
|
|
78
|
-
| `PromisifyMethods` | type | Maps method
|
|
79
|
-
| `WorkerProxy` | type | Proxy type
|
|
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` |
|
|
83
|
-
| `createWorker` | function |
|
|
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
|
-
|
|
87
|
+
## Usage
|
|
86
88
|
|
|
87
|
-
|
|
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
|
-
|
|
94
|
-
await fsx.write("/
|
|
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
|
|
98
|
-
const
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
*
|
|
4
|
+
* 지원되는 파일 변경 이벤트 타입 목록.
|
|
5
5
|
*/
|
|
6
6
|
declare const FS_WATCHER_EVENTS: readonly ["add", "addDir", "change", "unlink", "unlinkDir"];
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* 파일 변경 이벤트 타입.
|
|
9
9
|
*/
|
|
10
10
|
export type FsWatcherEvent = (typeof FS_WATCHER_EVENTS)[number];
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* 파일 변경 정보.
|
|
13
13
|
*/
|
|
14
14
|
export interface FsWatcherChangeInfo {
|
|
15
|
-
/**
|
|
15
|
+
/** 변경 이벤트 타입 */
|
|
16
16
|
event: FsWatcherEvent;
|
|
17
|
-
/**
|
|
17
|
+
/** 변경된 파일/디렉토리 경로 (정규화됨) */
|
|
18
18
|
path: NormPath;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Chokidar
|
|
22
|
-
*
|
|
21
|
+
* Chokidar 기반 파일 시스템 감시 래퍼.
|
|
22
|
+
* 짧은 시간 내에 발생하는 이벤트를 병합하여 콜백을 한 번만 호출한다.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
-
* //
|
|
37
|
+
* // 종료
|
|
38
38
|
* await watcher.close();
|
|
39
39
|
*/
|
|
40
40
|
export declare class FsWatcher {
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* 파일 감시를 시작한다 (비동기).
|
|
43
|
+
* ready 이벤트가 발생할 때까지 대기한다.
|
|
44
44
|
*
|
|
45
|
-
* @param paths -
|
|
46
|
-
* @param options - chokidar
|
|
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
|
-
*
|
|
57
|
-
*
|
|
56
|
+
* 파일 변경 이벤트 핸들러를 등록한다.
|
|
57
|
+
* 지정된 지연 시간 동안 이벤트를 수집하여 콜백을 한 번 호출한다.
|
|
58
58
|
*
|
|
59
|
-
* @param opt.delay -
|
|
60
|
-
* @param cb -
|
|
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
|
-
*
|
|
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,
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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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"}
|