@simplysm/core-node 13.0.0-beta.1
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/.cache/typecheck-node.tsbuildinfo +1 -0
- package/.cache/typecheck-tests-node.tsbuildinfo +1 -0
- package/README.md +375 -0
- package/dist/core-common/src/common.types.d.ts +74 -0
- package/dist/core-common/src/common.types.d.ts.map +1 -0
- package/dist/core-common/src/env.d.ts +6 -0
- package/dist/core-common/src/env.d.ts.map +1 -0
- package/dist/core-common/src/errors/argument-error.d.ts +25 -0
- package/dist/core-common/src/errors/argument-error.d.ts.map +1 -0
- package/dist/core-common/src/errors/not-implemented-error.d.ts +29 -0
- package/dist/core-common/src/errors/not-implemented-error.d.ts.map +1 -0
- package/dist/core-common/src/errors/sd-error.d.ts +27 -0
- package/dist/core-common/src/errors/sd-error.d.ts.map +1 -0
- package/dist/core-common/src/errors/timeout-error.d.ts +31 -0
- package/dist/core-common/src/errors/timeout-error.d.ts.map +1 -0
- package/dist/core-common/src/extensions/arr-ext.d.ts +15 -0
- package/dist/core-common/src/extensions/arr-ext.d.ts.map +1 -0
- package/dist/core-common/src/extensions/arr-ext.helpers.d.ts +19 -0
- package/dist/core-common/src/extensions/arr-ext.helpers.d.ts.map +1 -0
- package/dist/core-common/src/extensions/arr-ext.types.d.ts +215 -0
- package/dist/core-common/src/extensions/arr-ext.types.d.ts.map +1 -0
- package/dist/core-common/src/extensions/map-ext.d.ts +57 -0
- package/dist/core-common/src/extensions/map-ext.d.ts.map +1 -0
- package/dist/core-common/src/extensions/set-ext.d.ts +36 -0
- package/dist/core-common/src/extensions/set-ext.d.ts.map +1 -0
- package/dist/core-common/src/features/debounce-queue.d.ts +53 -0
- package/dist/core-common/src/features/debounce-queue.d.ts.map +1 -0
- package/dist/core-common/src/features/event-emitter.d.ts +66 -0
- package/dist/core-common/src/features/event-emitter.d.ts.map +1 -0
- package/dist/core-common/src/features/serial-queue.d.ts +47 -0
- package/dist/core-common/src/features/serial-queue.d.ts.map +1 -0
- package/dist/core-common/src/index.d.ts +32 -0
- package/dist/core-common/src/index.d.ts.map +1 -0
- package/dist/core-common/src/types/date-only.d.ts +152 -0
- package/dist/core-common/src/types/date-only.d.ts.map +1 -0
- package/dist/core-common/src/types/date-time.d.ts +96 -0
- package/dist/core-common/src/types/date-time.d.ts.map +1 -0
- package/dist/core-common/src/types/lazy-gc-map.d.ts +80 -0
- package/dist/core-common/src/types/lazy-gc-map.d.ts.map +1 -0
- package/dist/core-common/src/types/time.d.ts +68 -0
- package/dist/core-common/src/types/time.d.ts.map +1 -0
- package/dist/core-common/src/types/uuid.d.ts +35 -0
- package/dist/core-common/src/types/uuid.d.ts.map +1 -0
- package/dist/core-common/src/utils/bytes.d.ts +51 -0
- package/dist/core-common/src/utils/bytes.d.ts.map +1 -0
- package/dist/core-common/src/utils/date-format.d.ts +90 -0
- package/dist/core-common/src/utils/date-format.d.ts.map +1 -0
- package/dist/core-common/src/utils/json.d.ts +34 -0
- package/dist/core-common/src/utils/json.d.ts.map +1 -0
- package/dist/core-common/src/utils/num.d.ts +60 -0
- package/dist/core-common/src/utils/num.d.ts.map +1 -0
- package/dist/core-common/src/utils/obj.d.ts +258 -0
- package/dist/core-common/src/utils/obj.d.ts.map +1 -0
- package/dist/core-common/src/utils/path.d.ts +23 -0
- package/dist/core-common/src/utils/path.d.ts.map +1 -0
- package/dist/core-common/src/utils/primitive.d.ts +18 -0
- package/dist/core-common/src/utils/primitive.d.ts.map +1 -0
- package/dist/core-common/src/utils/str.d.ts +103 -0
- package/dist/core-common/src/utils/str.d.ts.map +1 -0
- package/dist/core-common/src/utils/template-strings.d.ts +84 -0
- package/dist/core-common/src/utils/template-strings.d.ts.map +1 -0
- package/dist/core-common/src/utils/transferable.d.ts +47 -0
- package/dist/core-common/src/utils/transferable.d.ts.map +1 -0
- package/dist/core-common/src/utils/wait.d.ts +19 -0
- package/dist/core-common/src/utils/wait.d.ts.map +1 -0
- package/dist/core-common/src/utils/xml.d.ts +36 -0
- package/dist/core-common/src/utils/xml.d.ts.map +1 -0
- package/dist/core-common/src/zip/sd-zip.d.ts +80 -0
- package/dist/core-common/src/zip/sd-zip.d.ts.map +1 -0
- package/dist/core-node/src/features/fs-watcher.d.ts +70 -0
- package/dist/core-node/src/features/fs-watcher.d.ts.map +1 -0
- package/dist/core-node/src/index.d.ts +7 -0
- package/dist/core-node/src/index.d.ts.map +1 -0
- package/dist/core-node/src/utils/fs.d.ts +197 -0
- package/dist/core-node/src/utils/fs.d.ts.map +1 -0
- package/dist/core-node/src/utils/path.d.ts +75 -0
- package/dist/core-node/src/utils/path.d.ts.map +1 -0
- package/dist/core-node/src/worker/create-worker.d.ts +23 -0
- package/dist/core-node/src/worker/create-worker.d.ts.map +1 -0
- package/dist/core-node/src/worker/types.d.ts +67 -0
- package/dist/core-node/src/worker/types.d.ts.map +1 -0
- package/dist/core-node/src/worker/worker.d.ts +27 -0
- package/dist/core-node/src/worker/worker.d.ts.map +1 -0
- package/dist/features/fs-watcher.js +100 -0
- package/dist/features/fs-watcher.js.map +7 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +7 -0
- package/dist/utils/fs.js +305 -0
- package/dist/utils/fs.js.map +7 -0
- package/dist/utils/path.js +48 -0
- package/dist/utils/path.js.map +7 -0
- package/dist/worker/create-worker.js +85 -0
- package/dist/worker/create-worker.js.map +7 -0
- package/dist/worker/types.js +1 -0
- package/dist/worker/types.js.map +7 -0
- package/dist/worker/worker.js +142 -0
- package/dist/worker/worker.js.map +7 -0
- package/lib/worker-dev-proxy.js +12 -0
- package/package.json +23 -0
- package/src/features/fs-watcher.ts +176 -0
- package/src/index.ts +11 -0
- package/src/utils/fs.ts +550 -0
- package/src/utils/path.ts +128 -0
- package/src/worker/create-worker.ts +141 -0
- package/src/worker/types.ts +86 -0
- package/src/worker/worker.ts +207 -0
- package/tests/utils/fs-watcher.spec.ts +295 -0
- package/tests/utils/fs.spec.ts +754 -0
- package/tests/utils/path.spec.ts +192 -0
- package/tests/worker/fixtures/test-worker.ts +35 -0
- package/tests/worker/sd-worker.spec.ts +183 -0
|
@@ -0,0 +1,295 @@
|
|
|
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("파일 감시 시작", async () => {
|
|
27
|
+
watcher = await FsWatcher.watch([path.join(testDir, "**/*")]);
|
|
28
|
+
expect(watcher).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("옵션과 함께 파일 감시 시작", async () => {
|
|
32
|
+
watcher = await FsWatcher.watch([path.join(testDir, "**/*")], {
|
|
33
|
+
ignoreInitial: false,
|
|
34
|
+
});
|
|
35
|
+
expect(watcher).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("에러 이벤트 발생 시 로깅 처리", async () => {
|
|
39
|
+
// chokidar는 존재하지 않는 경로도 정상적으로 감시 시작함
|
|
40
|
+
// 에러 이벤트는 실제 파일 시스템 오류 시에만 발생
|
|
41
|
+
const nonExistentPath = path.join(testDir, "non-existent-dir-" + Date.now());
|
|
42
|
+
watcher = await FsWatcher.watch([nonExistentPath]);
|
|
43
|
+
|
|
44
|
+
// 에러 핸들러가 등록되어 있어 에러 발생 시에도 크래시하지 않음
|
|
45
|
+
expect(watcher).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
|
|
51
|
+
//#region close
|
|
52
|
+
|
|
53
|
+
describe("close", () => {
|
|
54
|
+
it("감시 종료", async () => {
|
|
55
|
+
watcher = await FsWatcher.watch([path.join(testDir, "**/*")]);
|
|
56
|
+
|
|
57
|
+
// close()가 에러 없이 완료되면 테스트 통과
|
|
58
|
+
await expect(watcher.close()).resolves.toBeUndefined();
|
|
59
|
+
|
|
60
|
+
// 종료 후 watcher 참조 해제
|
|
61
|
+
watcher = undefined;
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
|
|
67
|
+
//#region chaining
|
|
68
|
+
|
|
69
|
+
describe("onChange", () => {
|
|
70
|
+
it("onChange 메서드 체이닝 지원", async () => {
|
|
71
|
+
watcher = await FsWatcher.watch([path.join(testDir, "**/*")]);
|
|
72
|
+
|
|
73
|
+
const fn = vi.fn();
|
|
74
|
+
const result = watcher.onChange({ delay: 100 }, fn);
|
|
75
|
+
|
|
76
|
+
expect(result).toBe(watcher);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("delay 옵션 지정 가능", async () => {
|
|
80
|
+
watcher = await FsWatcher.watch([path.join(testDir, "**/*")]);
|
|
81
|
+
|
|
82
|
+
const fn = vi.fn();
|
|
83
|
+
// delay 옵션이 다양한 값으로 지정 가능해야 함
|
|
84
|
+
expect(() => watcher!.onChange({ delay: 0 }, fn)).not.toThrow();
|
|
85
|
+
expect(() => watcher!.onChange({ delay: 500 }, fn)).not.toThrow();
|
|
86
|
+
expect(() => watcher!.onChange({ delay: 1000 }, fn)).not.toThrow();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
|
|
92
|
+
//#region Types
|
|
93
|
+
|
|
94
|
+
describe("Types", () => {
|
|
95
|
+
it("SdFsWatcherEvent 타입 정의 확인", () => {
|
|
96
|
+
// 이벤트 타입이 올바르게 정의되어 있는지 확인
|
|
97
|
+
const validEvents = ["add", "addDir", "change", "unlink", "unlinkDir"];
|
|
98
|
+
expect(validEvents).toContain("add");
|
|
99
|
+
expect(validEvents).toContain("addDir");
|
|
100
|
+
expect(validEvents).toContain("change");
|
|
101
|
+
expect(validEvents).toContain("unlink");
|
|
102
|
+
expect(validEvents).toContain("unlinkDir");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("SdFsWatcherChangeInfo 구조 확인", () => {
|
|
106
|
+
// 인터페이스 구조 확인용 타입 체크
|
|
107
|
+
const mockChangeInfo = {
|
|
108
|
+
event: "add" as const,
|
|
109
|
+
path: "/test/path",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
expect(mockChangeInfo.event).toBe("add");
|
|
113
|
+
expect(mockChangeInfo.path).toBe("/test/path");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
|
|
119
|
+
//#region 이벤트 병합 (Event Merging)
|
|
120
|
+
|
|
121
|
+
describe("이벤트 병합", () => {
|
|
122
|
+
const DELAY = 300;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 지정 시간 대기 헬퍼 함수.
|
|
126
|
+
*/
|
|
127
|
+
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 이벤트 콜백이 호출될 때까지 대기하는 헬퍼 함수.
|
|
131
|
+
*/
|
|
132
|
+
const waitForChanges = (
|
|
133
|
+
watcherInstance: FsWatcher,
|
|
134
|
+
delay: number,
|
|
135
|
+
): Promise<Array<{ event: string; path: string }>> => {
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
watcherInstance.onChange({ delay }, (changeInfos) => {
|
|
138
|
+
resolve(changeInfos.map((c) => ({ event: c.event, path: c.path })));
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
it("파일 추가 후 변경 시 add 이벤트만 반환", async () => {
|
|
144
|
+
const testFile = path.join(testDir, "test-add-change.txt");
|
|
145
|
+
|
|
146
|
+
watcher = await FsWatcher.watch([testDir]);
|
|
147
|
+
|
|
148
|
+
const changesPromise = waitForChanges(watcher, DELAY);
|
|
149
|
+
|
|
150
|
+
// 파일 추가
|
|
151
|
+
fs.writeFileSync(testFile, "initial");
|
|
152
|
+
|
|
153
|
+
// 짧은 간격으로 파일 변경 (delay 내에서 일어나야 함)
|
|
154
|
+
await wait(50);
|
|
155
|
+
fs.writeFileSync(testFile, "modified");
|
|
156
|
+
|
|
157
|
+
// 이벤트 콜백 호출 대기
|
|
158
|
+
const changes = await changesPromise;
|
|
159
|
+
|
|
160
|
+
// add → change 가 add로 병합되어야 함
|
|
161
|
+
expect(changes.length).toBe(1);
|
|
162
|
+
expect(changes[0].event).toBe("add");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("파일 추가 후 삭제 시 이벤트 없음 또는 변경 없음 처리", async () => {
|
|
166
|
+
const testFile = path.join(testDir, "test-add-unlink.txt");
|
|
167
|
+
|
|
168
|
+
watcher = await FsWatcher.watch([testDir]);
|
|
169
|
+
|
|
170
|
+
const changes: Array<{ event: string; path: string }> = [];
|
|
171
|
+
let resolved = false;
|
|
172
|
+
|
|
173
|
+
const changesPromise = new Promise<void>((resolve) => {
|
|
174
|
+
watcher!.onChange({ delay: DELAY }, (changeInfos) => {
|
|
175
|
+
changes.push(...changeInfos.map((c) => ({ event: c.event, path: c.path })));
|
|
176
|
+
if (!resolved) {
|
|
177
|
+
resolved = true;
|
|
178
|
+
resolve();
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// 파일 추가
|
|
184
|
+
fs.writeFileSync(testFile, "content");
|
|
185
|
+
|
|
186
|
+
// 짧은 간격으로 파일 삭제
|
|
187
|
+
await wait(50);
|
|
188
|
+
fs.unlinkSync(testFile);
|
|
189
|
+
|
|
190
|
+
// 타임아웃과 함께 대기 (이벤트가 발생하지 않을 수 있음)
|
|
191
|
+
await Promise.race([changesPromise, wait(DELAY + 200)]);
|
|
192
|
+
|
|
193
|
+
// add → unlink 가 병합되어 이벤트 없음
|
|
194
|
+
expect(changes.length).toBe(0);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("디렉토리 추가 후 삭제 시 이벤트 없음 또는 변경 없음 처리", async () => {
|
|
198
|
+
const testSubDir = path.join(testDir, "test-addDir-unlinkDir");
|
|
199
|
+
|
|
200
|
+
watcher = await FsWatcher.watch([testDir]);
|
|
201
|
+
|
|
202
|
+
const changes: Array<{ event: string; path: string }> = [];
|
|
203
|
+
let resolved = false;
|
|
204
|
+
|
|
205
|
+
const changesPromise = new Promise<void>((resolve) => {
|
|
206
|
+
watcher!.onChange({ delay: DELAY }, (changeInfos) => {
|
|
207
|
+
changes.push(...changeInfos.map((c) => ({ event: c.event, path: c.path })));
|
|
208
|
+
if (!resolved) {
|
|
209
|
+
resolved = true;
|
|
210
|
+
resolve();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// 디렉토리 추가
|
|
216
|
+
fs.mkdirSync(testSubDir);
|
|
217
|
+
|
|
218
|
+
// 짧은 간격으로 디렉토리 삭제
|
|
219
|
+
await wait(50);
|
|
220
|
+
fs.rmdirSync(testSubDir);
|
|
221
|
+
|
|
222
|
+
// 타임아웃과 함께 대기 (이벤트가 발생하지 않을 수 있음)
|
|
223
|
+
await Promise.race([changesPromise, wait(DELAY + 200)]);
|
|
224
|
+
|
|
225
|
+
// addDir → unlinkDir 가 병합되어 이벤트 없음
|
|
226
|
+
expect(changes.length).toBe(0);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("파일 삭제 후 재생성 시 add 이벤트로 병합", async () => {
|
|
230
|
+
const testFile = path.join(testDir, "test-unlink-add.txt");
|
|
231
|
+
|
|
232
|
+
// 파일 미리 생성
|
|
233
|
+
fs.writeFileSync(testFile, "initial");
|
|
234
|
+
|
|
235
|
+
watcher = await FsWatcher.watch([testDir]);
|
|
236
|
+
|
|
237
|
+
const changesPromise = waitForChanges(watcher, DELAY);
|
|
238
|
+
|
|
239
|
+
// 파일 삭제
|
|
240
|
+
fs.unlinkSync(testFile);
|
|
241
|
+
|
|
242
|
+
// 짧은 간격으로 파일 재생성 (delay 내에서 일어나야 함)
|
|
243
|
+
await wait(50);
|
|
244
|
+
fs.writeFileSync(testFile, "recreated");
|
|
245
|
+
|
|
246
|
+
// 이벤트 콜백 호출 대기
|
|
247
|
+
const changes = await changesPromise;
|
|
248
|
+
|
|
249
|
+
// unlink → add/change 가 add로 병합되어야 함 (나중 이벤트로 덮어씀)
|
|
250
|
+
// 환경에 따라 chokidar가 unlink 없이 change만 발생시킬 수 있음 (WSL2 등)
|
|
251
|
+
expect(changes.length).toBe(1);
|
|
252
|
+
expect(["add", "change"]).toContain(changes[0].event);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("여러 파일 변경 시 올바른 이벤트 병합", async () => {
|
|
256
|
+
const file1 = path.join(testDir, "file1.txt");
|
|
257
|
+
const file2 = path.join(testDir, "file2.txt");
|
|
258
|
+
const file3 = path.join(testDir, "file3.txt");
|
|
259
|
+
|
|
260
|
+
// file3은 미리 생성 (change 이벤트 발생용)
|
|
261
|
+
fs.writeFileSync(file3, "existing");
|
|
262
|
+
|
|
263
|
+
watcher = await FsWatcher.watch([testDir]);
|
|
264
|
+
|
|
265
|
+
const changesPromise = waitForChanges(watcher, DELAY);
|
|
266
|
+
|
|
267
|
+
// file1: 추가만
|
|
268
|
+
fs.writeFileSync(file1, "content1");
|
|
269
|
+
|
|
270
|
+
// file2: 추가 후 삭제 (병합되어 사라짐)
|
|
271
|
+
await wait(50);
|
|
272
|
+
fs.writeFileSync(file2, "content2");
|
|
273
|
+
await wait(50);
|
|
274
|
+
fs.unlinkSync(file2);
|
|
275
|
+
|
|
276
|
+
// file3: 변경
|
|
277
|
+
await wait(50);
|
|
278
|
+
fs.writeFileSync(file3, "modified");
|
|
279
|
+
|
|
280
|
+
// 이벤트 콜백 호출 대기
|
|
281
|
+
const changes = await changesPromise;
|
|
282
|
+
|
|
283
|
+
// file1: add, file2: 병합으로 삭제됨, file3: change
|
|
284
|
+
expect(changes.length).toBe(2);
|
|
285
|
+
|
|
286
|
+
const file1Change = changes.find((c) => c.path.endsWith("file1.txt"));
|
|
287
|
+
const file3Change = changes.find((c) => c.path.endsWith("file3.txt"));
|
|
288
|
+
|
|
289
|
+
expect(file1Change?.event).toBe("add");
|
|
290
|
+
expect(file3Change?.event).toBe("change");
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
//#endregion
|
|
295
|
+
});
|