@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,754 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import {
|
|
6
|
+
fsExistsSync,
|
|
7
|
+
fsExists,
|
|
8
|
+
fsMkdirSync,
|
|
9
|
+
fsMkdir,
|
|
10
|
+
fsRmSync,
|
|
11
|
+
fsRm,
|
|
12
|
+
fsCopySync,
|
|
13
|
+
fsCopy,
|
|
14
|
+
fsReadSync,
|
|
15
|
+
fsRead,
|
|
16
|
+
fsReadBufferSync,
|
|
17
|
+
fsReadBuffer,
|
|
18
|
+
fsReadJsonSync,
|
|
19
|
+
fsReadJson,
|
|
20
|
+
fsWriteSync,
|
|
21
|
+
fsWrite,
|
|
22
|
+
fsWriteJsonSync,
|
|
23
|
+
fsWriteJson,
|
|
24
|
+
fsReaddirSync,
|
|
25
|
+
fsReaddir,
|
|
26
|
+
fsStatSync,
|
|
27
|
+
fsStat,
|
|
28
|
+
fsLstatSync,
|
|
29
|
+
fsLstat,
|
|
30
|
+
fsGlobSync,
|
|
31
|
+
fsGlob,
|
|
32
|
+
fsClearEmptyDirectory,
|
|
33
|
+
fsFindAllParentChildPathsSync,
|
|
34
|
+
fsFindAllParentChildPaths,
|
|
35
|
+
} from "../../src/utils/fs";
|
|
36
|
+
import { SdError } from "@simplysm/core-common";
|
|
37
|
+
|
|
38
|
+
describe("fs 함수들", () => {
|
|
39
|
+
const testDir = path.join(os.tmpdir(), "fs-utils-test-" + Date.now());
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
//#region exists
|
|
50
|
+
|
|
51
|
+
describe("fsExistsSync", () => {
|
|
52
|
+
it("존재하는 파일에 대해 true 반환", () => {
|
|
53
|
+
const filePath = path.join(testDir, "test.txt");
|
|
54
|
+
fs.writeFileSync(filePath, "test");
|
|
55
|
+
|
|
56
|
+
expect(fsExistsSync(filePath)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("존재하지 않는 파일에 대해 false 반환", () => {
|
|
60
|
+
const filePath = path.join(testDir, "nonexistent.txt");
|
|
61
|
+
expect(fsExistsSync(filePath)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("존재하는 디렉토리에 대해 true 반환", () => {
|
|
65
|
+
expect(fsExistsSync(testDir)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("fsExists", () => {
|
|
70
|
+
it("존재하는 파일에 대해 true 반환", async () => {
|
|
71
|
+
const filePath = path.join(testDir, "test.txt");
|
|
72
|
+
fs.writeFileSync(filePath, "test");
|
|
73
|
+
|
|
74
|
+
expect(await fsExists(filePath)).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("존재하지 않는 파일에 대해 false 반환", async () => {
|
|
78
|
+
const filePath = path.join(testDir, "nonexistent.txt");
|
|
79
|
+
expect(await fsExists(filePath)).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("존재하는 디렉토리에 대해 true 반환", async () => {
|
|
83
|
+
expect(await fsExists(testDir)).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
|
|
89
|
+
//#region mkdir
|
|
90
|
+
|
|
91
|
+
describe("fsMkdirSync", () => {
|
|
92
|
+
it("디렉토리 생성", () => {
|
|
93
|
+
const dirPath = path.join(testDir, "newdir");
|
|
94
|
+
fsMkdirSync(dirPath);
|
|
95
|
+
|
|
96
|
+
expect(fs.existsSync(dirPath)).toBe(true);
|
|
97
|
+
expect(fs.statSync(dirPath).isDirectory()).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("중첩 디렉토리 생성 (recursive)", () => {
|
|
101
|
+
const dirPath = path.join(testDir, "a/b/c");
|
|
102
|
+
fsMkdirSync(dirPath);
|
|
103
|
+
|
|
104
|
+
expect(fs.existsSync(dirPath)).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("이미 존재하는 디렉토리는 에러 없이 통과", () => {
|
|
108
|
+
expect(() => fsMkdirSync(testDir)).not.toThrow();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("fsMkdir", () => {
|
|
113
|
+
it("비동기로 디렉토리 생성", async () => {
|
|
114
|
+
const dirPath = path.join(testDir, "asyncdir");
|
|
115
|
+
await fsMkdir(dirPath);
|
|
116
|
+
|
|
117
|
+
expect(fs.existsSync(dirPath)).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
|
|
123
|
+
//#region rm
|
|
124
|
+
|
|
125
|
+
describe("fsRmSync", () => {
|
|
126
|
+
it("파일 삭제", () => {
|
|
127
|
+
const filePath = path.join(testDir, "todelete.txt");
|
|
128
|
+
fs.writeFileSync(filePath, "test");
|
|
129
|
+
|
|
130
|
+
fsRmSync(filePath);
|
|
131
|
+
|
|
132
|
+
expect(fs.existsSync(filePath)).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("디렉토리 삭제 (recursive)", () => {
|
|
136
|
+
const dirPath = path.join(testDir, "todelete");
|
|
137
|
+
fs.mkdirSync(dirPath);
|
|
138
|
+
fs.writeFileSync(path.join(dirPath, "file.txt"), "test");
|
|
139
|
+
|
|
140
|
+
fsRmSync(dirPath);
|
|
141
|
+
|
|
142
|
+
expect(fs.existsSync(dirPath)).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("존재하지 않는 경로는 에러 없이 통과", () => {
|
|
146
|
+
expect(() => fsRmSync(path.join(testDir, "nonexistent"))).not.toThrow();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("fsRm", () => {
|
|
151
|
+
it("비동기로 파일 삭제", async () => {
|
|
152
|
+
const filePath = path.join(testDir, "asyncdelete.txt");
|
|
153
|
+
fs.writeFileSync(filePath, "test");
|
|
154
|
+
|
|
155
|
+
await fsRm(filePath);
|
|
156
|
+
|
|
157
|
+
expect(fs.existsSync(filePath)).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
|
|
163
|
+
//#region read/write
|
|
164
|
+
|
|
165
|
+
describe("fsReadSync", () => {
|
|
166
|
+
it("파일 내용을 UTF-8 문자열로 읽기", () => {
|
|
167
|
+
const filePath = path.join(testDir, "read.txt");
|
|
168
|
+
fs.writeFileSync(filePath, "Hello, World!");
|
|
169
|
+
|
|
170
|
+
const content = fsReadSync(filePath);
|
|
171
|
+
|
|
172
|
+
expect(content).toBe("Hello, World!");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("한글 내용 읽기", () => {
|
|
176
|
+
const filePath = path.join(testDir, "korean.txt");
|
|
177
|
+
fs.writeFileSync(filePath, "안녕하세요");
|
|
178
|
+
|
|
179
|
+
const content = fsReadSync(filePath);
|
|
180
|
+
|
|
181
|
+
expect(content).toBe("안녕하세요");
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("fsRead", () => {
|
|
186
|
+
it("비동기로 파일 읽기", async () => {
|
|
187
|
+
const filePath = path.join(testDir, "asyncread.txt");
|
|
188
|
+
fs.writeFileSync(filePath, "async content");
|
|
189
|
+
|
|
190
|
+
const content = await fsRead(filePath);
|
|
191
|
+
|
|
192
|
+
expect(content).toBe("async content");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("fsReadBufferSync", () => {
|
|
197
|
+
it("파일을 Buffer로 읽기", () => {
|
|
198
|
+
const filePath = path.join(testDir, "buffer.txt");
|
|
199
|
+
fs.writeFileSync(filePath, "buffer content");
|
|
200
|
+
|
|
201
|
+
const buffer = fsReadBufferSync(filePath);
|
|
202
|
+
|
|
203
|
+
expect(buffer instanceof Uint8Array).toBe(true);
|
|
204
|
+
expect(buffer.toString()).toBe("buffer content");
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("fsReadBuffer", () => {
|
|
209
|
+
it("비동기로 파일을 Buffer로 읽기", async () => {
|
|
210
|
+
const filePath = path.join(testDir, "asyncbuffer.txt");
|
|
211
|
+
fs.writeFileSync(filePath, "async buffer content");
|
|
212
|
+
|
|
213
|
+
const buffer = await fsReadBuffer(filePath);
|
|
214
|
+
|
|
215
|
+
expect(buffer instanceof Uint8Array).toBe(true);
|
|
216
|
+
expect(buffer.toString()).toBe("async buffer content");
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe("fsWriteSync", () => {
|
|
221
|
+
it("문자열을 파일로 쓰기", () => {
|
|
222
|
+
const filePath = path.join(testDir, "write.txt");
|
|
223
|
+
|
|
224
|
+
fsWriteSync(filePath, "written content");
|
|
225
|
+
|
|
226
|
+
expect(fs.readFileSync(filePath, "utf-8")).toBe("written content");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("Buffer를 파일로 쓰기", () => {
|
|
230
|
+
const filePath = path.join(testDir, "buffer-write.bin");
|
|
231
|
+
const buffer = new Uint8Array([0x00, 0x01, 0x02, 0xff]);
|
|
232
|
+
|
|
233
|
+
fsWriteSync(filePath, buffer);
|
|
234
|
+
|
|
235
|
+
expect(new Uint8Array(fs.readFileSync(filePath))).toEqual(buffer);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("부모 디렉토리가 없으면 자동 생성", () => {
|
|
239
|
+
const filePath = path.join(testDir, "sub/dir/write.txt");
|
|
240
|
+
|
|
241
|
+
fsWriteSync(filePath, "nested content");
|
|
242
|
+
|
|
243
|
+
expect(fs.readFileSync(filePath, "utf-8")).toBe("nested content");
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("fsWrite", () => {
|
|
248
|
+
it("비동기로 파일 쓰기", async () => {
|
|
249
|
+
const filePath = path.join(testDir, "asyncwrite.txt");
|
|
250
|
+
|
|
251
|
+
await fsWrite(filePath, "async written");
|
|
252
|
+
|
|
253
|
+
expect(fs.readFileSync(filePath, "utf-8")).toBe("async written");
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
//#endregion
|
|
258
|
+
|
|
259
|
+
//#region JSON
|
|
260
|
+
|
|
261
|
+
describe("fsReadJsonSync", () => {
|
|
262
|
+
it("JSON 파일 읽기", () => {
|
|
263
|
+
const filePath = path.join(testDir, "data.json");
|
|
264
|
+
fs.writeFileSync(filePath, '{"name": "test", "value": 42}');
|
|
265
|
+
|
|
266
|
+
const data = fsReadJsonSync<{ name: string; value: number }>(filePath);
|
|
267
|
+
|
|
268
|
+
expect(data).toEqual({ name: "test", value: 42 });
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("500자 이상의 잘못된 JSON 파일 읽기 시 truncate된 내용 포함", () => {
|
|
272
|
+
const filePath = path.join(testDir, "long-invalid.json");
|
|
273
|
+
const longContent = "{ invalid " + "x".repeat(600) + " }";
|
|
274
|
+
fs.writeFileSync(filePath, longContent);
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
fsReadJsonSync(filePath);
|
|
278
|
+
expect.fail("에러가 발생해야 함");
|
|
279
|
+
} catch (err) {
|
|
280
|
+
expect((err as Error).message).toContain("...(truncated)");
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe("fsWriteJsonSync", () => {
|
|
286
|
+
it("JSON 파일 쓰기", () => {
|
|
287
|
+
const filePath = path.join(testDir, "output.json");
|
|
288
|
+
const data = { name: "test", value: 42 };
|
|
289
|
+
|
|
290
|
+
fsWriteJsonSync(filePath, data);
|
|
291
|
+
|
|
292
|
+
const content = JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown;
|
|
293
|
+
expect(content).toEqual(data);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("JSON 파일 쓰기 (포맷팅)", () => {
|
|
297
|
+
const filePath = path.join(testDir, "formatted.json");
|
|
298
|
+
const data = { name: "test" };
|
|
299
|
+
|
|
300
|
+
fsWriteJsonSync(filePath, data, { space: 2 });
|
|
301
|
+
|
|
302
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
303
|
+
expect(content).toContain("\n");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("JSON 파일 쓰기 (replacer 옵션)", () => {
|
|
307
|
+
const filePath = path.join(testDir, "replaced.json");
|
|
308
|
+
const data = { name: "test", secret: "hidden" };
|
|
309
|
+
|
|
310
|
+
fsWriteJsonSync(filePath, data, {
|
|
311
|
+
replacer: (_key, value) => (typeof value === "string" && value === "hidden" ? undefined : value),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const content = JSON.parse(fs.readFileSync(filePath, "utf-8")) as Record<string, unknown>;
|
|
315
|
+
expect(content).toEqual({ name: "test" });
|
|
316
|
+
expect(content["secret"]).toBeUndefined();
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe("fsReadJson", () => {
|
|
321
|
+
it("비동기로 JSON 파일 읽기", async () => {
|
|
322
|
+
const filePath = path.join(testDir, "asyncdata.json");
|
|
323
|
+
fs.writeFileSync(filePath, '{"name": "async", "value": 100}');
|
|
324
|
+
|
|
325
|
+
const data = await fsReadJson<{ name: string; value: number }>(filePath);
|
|
326
|
+
|
|
327
|
+
expect(data).toEqual({ name: "async", value: 100 });
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe("fsWriteJson", () => {
|
|
332
|
+
it("비동기로 JSON 파일 쓰기", async () => {
|
|
333
|
+
const filePath = path.join(testDir, "asyncoutput.json");
|
|
334
|
+
const data = { name: "async", value: 100 };
|
|
335
|
+
|
|
336
|
+
await fsWriteJson(filePath, data);
|
|
337
|
+
|
|
338
|
+
const content = JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown;
|
|
339
|
+
expect(content).toEqual(data);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
//#endregion
|
|
344
|
+
|
|
345
|
+
//#region copy
|
|
346
|
+
|
|
347
|
+
describe("fsCopySync", () => {
|
|
348
|
+
it("파일 복사", () => {
|
|
349
|
+
const source = path.join(testDir, "source.txt");
|
|
350
|
+
const target = path.join(testDir, "target.txt");
|
|
351
|
+
fs.writeFileSync(source, "source content");
|
|
352
|
+
|
|
353
|
+
fsCopySync(source, target);
|
|
354
|
+
|
|
355
|
+
expect(fs.readFileSync(target, "utf-8")).toBe("source content");
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("디렉토리 복사 (recursive)", () => {
|
|
359
|
+
const sourceDir = path.join(testDir, "sourceDir");
|
|
360
|
+
const targetDir = path.join(testDir, "targetDir");
|
|
361
|
+
fs.mkdirSync(sourceDir);
|
|
362
|
+
fs.writeFileSync(path.join(sourceDir, "file.txt"), "content");
|
|
363
|
+
fs.mkdirSync(path.join(sourceDir, "sub"));
|
|
364
|
+
fs.writeFileSync(path.join(sourceDir, "sub/nested.txt"), "nested");
|
|
365
|
+
|
|
366
|
+
fsCopySync(sourceDir, targetDir);
|
|
367
|
+
|
|
368
|
+
expect(fs.existsSync(path.join(targetDir, "file.txt"))).toBe(true);
|
|
369
|
+
expect(fs.existsSync(path.join(targetDir, "sub/nested.txt"))).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("존재하지 않는 소스는 무시", () => {
|
|
373
|
+
const source = path.join(testDir, "nonexistent");
|
|
374
|
+
const target = path.join(testDir, "target");
|
|
375
|
+
|
|
376
|
+
expect(() => fsCopySync(source, target)).not.toThrow();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("filter 옵션으로 선택적 복사", () => {
|
|
380
|
+
const sourceDir = path.join(testDir, "filterSource");
|
|
381
|
+
const targetDir = path.join(testDir, "filterTarget");
|
|
382
|
+
fs.mkdirSync(sourceDir);
|
|
383
|
+
fs.writeFileSync(path.join(sourceDir, "include.txt"), "include");
|
|
384
|
+
fs.writeFileSync(path.join(sourceDir, "exclude.log"), "exclude");
|
|
385
|
+
|
|
386
|
+
fsCopySync(sourceDir, targetDir, (p) => !p.endsWith(".log"));
|
|
387
|
+
|
|
388
|
+
expect(fs.existsSync(path.join(targetDir, "include.txt"))).toBe(true);
|
|
389
|
+
expect(fs.existsSync(path.join(targetDir, "exclude.log"))).toBe(false);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("filter가 디렉토리를 제외하면 하위 항목도 건너뛰기", () => {
|
|
393
|
+
const sourceDir = path.join(testDir, "filterDirSource");
|
|
394
|
+
const targetDir = path.join(testDir, "filterDirTarget");
|
|
395
|
+
fs.mkdirSync(sourceDir);
|
|
396
|
+
fs.mkdirSync(path.join(sourceDir, "excluded"));
|
|
397
|
+
fs.mkdirSync(path.join(sourceDir, "included"));
|
|
398
|
+
fs.writeFileSync(path.join(sourceDir, "excluded", "nested.txt"), "nested");
|
|
399
|
+
fs.writeFileSync(path.join(sourceDir, "included", "nested.txt"), "nested");
|
|
400
|
+
|
|
401
|
+
fsCopySync(sourceDir, targetDir, (p) => !p.includes("excluded"));
|
|
402
|
+
|
|
403
|
+
expect(fs.existsSync(path.join(targetDir, "excluded"))).toBe(false);
|
|
404
|
+
expect(fs.existsSync(path.join(targetDir, "excluded", "nested.txt"))).toBe(false);
|
|
405
|
+
expect(fs.existsSync(path.join(targetDir, "included"))).toBe(true);
|
|
406
|
+
expect(fs.existsSync(path.join(targetDir, "included", "nested.txt"))).toBe(true);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
describe("fsCopy", () => {
|
|
411
|
+
it("비동기로 파일 복사", async () => {
|
|
412
|
+
const source = path.join(testDir, "asyncSource.txt");
|
|
413
|
+
const target = path.join(testDir, "asyncTarget.txt");
|
|
414
|
+
fs.writeFileSync(source, "async source content");
|
|
415
|
+
|
|
416
|
+
await fsCopy(source, target);
|
|
417
|
+
|
|
418
|
+
expect(fs.readFileSync(target, "utf-8")).toBe("async source content");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("비동기로 디렉토리 복사 (recursive)", async () => {
|
|
422
|
+
const sourceDir = path.join(testDir, "asyncSourceDir");
|
|
423
|
+
const targetDir = path.join(testDir, "asyncTargetDir");
|
|
424
|
+
fs.mkdirSync(sourceDir);
|
|
425
|
+
fs.writeFileSync(path.join(sourceDir, "file.txt"), "content");
|
|
426
|
+
fs.mkdirSync(path.join(sourceDir, "sub"));
|
|
427
|
+
fs.writeFileSync(path.join(sourceDir, "sub/nested.txt"), "nested");
|
|
428
|
+
|
|
429
|
+
await fsCopy(sourceDir, targetDir);
|
|
430
|
+
|
|
431
|
+
expect(fs.existsSync(path.join(targetDir, "file.txt"))).toBe(true);
|
|
432
|
+
expect(fs.existsSync(path.join(targetDir, "sub/nested.txt"))).toBe(true);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("비동기로 filter 옵션으로 선택적 복사", async () => {
|
|
436
|
+
const sourceDir = path.join(testDir, "asyncFilterSource");
|
|
437
|
+
const targetDir = path.join(testDir, "asyncFilterTarget");
|
|
438
|
+
fs.mkdirSync(sourceDir);
|
|
439
|
+
fs.writeFileSync(path.join(sourceDir, "keep.ts"), "keep");
|
|
440
|
+
fs.writeFileSync(path.join(sourceDir, "skip.js"), "skip");
|
|
441
|
+
|
|
442
|
+
await fsCopy(sourceDir, targetDir, (p) => p.endsWith(".ts"));
|
|
443
|
+
|
|
444
|
+
expect(fs.existsSync(path.join(targetDir, "keep.ts"))).toBe(true);
|
|
445
|
+
expect(fs.existsSync(path.join(targetDir, "skip.js"))).toBe(false);
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
//#endregion
|
|
450
|
+
|
|
451
|
+
//#region readdir
|
|
452
|
+
|
|
453
|
+
describe("fsReaddirSync", () => {
|
|
454
|
+
it("디렉토리 내용 읽기", () => {
|
|
455
|
+
fs.writeFileSync(path.join(testDir, "file1.txt"), "");
|
|
456
|
+
fs.writeFileSync(path.join(testDir, "file2.txt"), "");
|
|
457
|
+
fs.mkdirSync(path.join(testDir, "subdir"));
|
|
458
|
+
|
|
459
|
+
const entries = fsReaddirSync(testDir);
|
|
460
|
+
|
|
461
|
+
expect(entries).toContain("file1.txt");
|
|
462
|
+
expect(entries).toContain("file2.txt");
|
|
463
|
+
expect(entries).toContain("subdir");
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
describe("fsReaddir", () => {
|
|
468
|
+
it("비동기로 디렉토리 내용 읽기", async () => {
|
|
469
|
+
fs.writeFileSync(path.join(testDir, "async1.txt"), "");
|
|
470
|
+
fs.writeFileSync(path.join(testDir, "async2.txt"), "");
|
|
471
|
+
|
|
472
|
+
const entries = await fsReaddir(testDir);
|
|
473
|
+
|
|
474
|
+
expect(entries).toContain("async1.txt");
|
|
475
|
+
expect(entries).toContain("async2.txt");
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
//#endregion
|
|
480
|
+
|
|
481
|
+
//#region stat
|
|
482
|
+
|
|
483
|
+
describe("fsStatSync", () => {
|
|
484
|
+
it("파일 정보 가져오기", () => {
|
|
485
|
+
const filePath = path.join(testDir, "statfile.txt");
|
|
486
|
+
fs.writeFileSync(filePath, "content");
|
|
487
|
+
|
|
488
|
+
const result = fsStatSync(filePath);
|
|
489
|
+
|
|
490
|
+
expect(result.isFile()).toBe(true);
|
|
491
|
+
expect(result.size).toBeGreaterThan(0);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it("디렉토리 정보 가져오기", () => {
|
|
495
|
+
const result = fsStatSync(testDir);
|
|
496
|
+
expect(result.isDirectory()).toBe(true);
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
describe("fsStat", () => {
|
|
501
|
+
it("비동기로 파일 정보 가져오기", async () => {
|
|
502
|
+
const filePath = path.join(testDir, "asyncstatfile.txt");
|
|
503
|
+
fs.writeFileSync(filePath, "async content");
|
|
504
|
+
|
|
505
|
+
const stat = await fsStat(filePath);
|
|
506
|
+
|
|
507
|
+
expect(stat.isFile()).toBe(true);
|
|
508
|
+
expect(stat.size).toBeGreaterThan(0);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
describe("fsLstatSync", () => {
|
|
513
|
+
it("일반 파일 정보 가져오기", () => {
|
|
514
|
+
const filePath = path.join(testDir, "lstatfile.txt");
|
|
515
|
+
fs.writeFileSync(filePath, "content");
|
|
516
|
+
|
|
517
|
+
const stat = fsLstatSync(filePath);
|
|
518
|
+
|
|
519
|
+
expect(stat.isFile()).toBe(true);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("심볼릭 링크는 링크 자체의 정보 반환", () => {
|
|
523
|
+
const targetPath = path.join(testDir, "target.txt");
|
|
524
|
+
const linkPath = path.join(testDir, "link.txt");
|
|
525
|
+
fs.writeFileSync(targetPath, "target content");
|
|
526
|
+
fs.symlinkSync(targetPath, linkPath);
|
|
527
|
+
|
|
528
|
+
const lstatResult = fsLstatSync(linkPath);
|
|
529
|
+
const statResult = fsStatSync(linkPath);
|
|
530
|
+
|
|
531
|
+
// lstat은 심볼릭 링크 자체의 정보를 반환
|
|
532
|
+
expect(lstatResult.isSymbolicLink()).toBe(true);
|
|
533
|
+
expect(lstatResult.isFile()).toBe(false);
|
|
534
|
+
|
|
535
|
+
// stat은 링크 대상의 정보를 반환
|
|
536
|
+
expect(statResult.isSymbolicLink()).toBe(false);
|
|
537
|
+
expect(statResult.isFile()).toBe(true);
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe("fsLstat", () => {
|
|
542
|
+
it("비동기로 파일 정보 가져오기", async () => {
|
|
543
|
+
const filePath = path.join(testDir, "asynclstatfile.txt");
|
|
544
|
+
fs.writeFileSync(filePath, "async content");
|
|
545
|
+
|
|
546
|
+
const stat = await fsLstat(filePath);
|
|
547
|
+
|
|
548
|
+
expect(stat.isFile()).toBe(true);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("비동기로 심볼릭 링크는 링크 자체의 정보 반환", async () => {
|
|
552
|
+
const targetPath = path.join(testDir, "async-target.txt");
|
|
553
|
+
const linkPath = path.join(testDir, "async-link.txt");
|
|
554
|
+
fs.writeFileSync(targetPath, "target content");
|
|
555
|
+
fs.symlinkSync(targetPath, linkPath);
|
|
556
|
+
|
|
557
|
+
const lstatResult = await fsLstat(linkPath);
|
|
558
|
+
const statResult = await fsStat(linkPath);
|
|
559
|
+
|
|
560
|
+
// lstat은 심볼릭 링크 자체의 정보를 반환
|
|
561
|
+
expect(lstatResult.isSymbolicLink()).toBe(true);
|
|
562
|
+
expect(lstatResult.isFile()).toBe(false);
|
|
563
|
+
|
|
564
|
+
// stat은 링크 대상의 정보를 반환
|
|
565
|
+
expect(statResult.isSymbolicLink()).toBe(false);
|
|
566
|
+
expect(statResult.isFile()).toBe(true);
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
//#endregion
|
|
571
|
+
|
|
572
|
+
//#region glob
|
|
573
|
+
|
|
574
|
+
describe("fsGlobSync", () => {
|
|
575
|
+
it("글로브 패턴으로 파일 검색", () => {
|
|
576
|
+
fs.writeFileSync(path.join(testDir, "a.txt"), "");
|
|
577
|
+
fs.writeFileSync(path.join(testDir, "b.txt"), "");
|
|
578
|
+
fs.writeFileSync(path.join(testDir, "c.js"), "");
|
|
579
|
+
|
|
580
|
+
const txtFiles = fsGlobSync(path.join(testDir, "*.txt"));
|
|
581
|
+
|
|
582
|
+
expect(txtFiles.length).toBe(2);
|
|
583
|
+
expect(txtFiles.some((f) => f.endsWith("a.txt"))).toBe(true);
|
|
584
|
+
expect(txtFiles.some((f) => f.endsWith("b.txt"))).toBe(true);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it("중첩 디렉토리 검색", () => {
|
|
588
|
+
fs.mkdirSync(path.join(testDir, "nested"));
|
|
589
|
+
fs.writeFileSync(path.join(testDir, "nested/deep.txt"), "");
|
|
590
|
+
|
|
591
|
+
const files = fsGlobSync(path.join(testDir, "**/*.txt"));
|
|
592
|
+
|
|
593
|
+
expect(files.some((f) => f.endsWith("deep.txt"))).toBe(true);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it("dot: true 옵션으로 숨김 파일 포함", () => {
|
|
597
|
+
fs.writeFileSync(path.join(testDir, ".hidden"), "");
|
|
598
|
+
fs.writeFileSync(path.join(testDir, "visible"), "");
|
|
599
|
+
|
|
600
|
+
const withoutDot = fsGlobSync(path.join(testDir, "*"));
|
|
601
|
+
const withDot = fsGlobSync(path.join(testDir, "*"), { dot: true });
|
|
602
|
+
|
|
603
|
+
expect(withoutDot.some((f) => f.endsWith(".hidden"))).toBe(false);
|
|
604
|
+
expect(withDot.some((f) => f.endsWith(".hidden"))).toBe(true);
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
describe("fsGlob", () => {
|
|
609
|
+
it("비동기 글로브 검색", async () => {
|
|
610
|
+
fs.writeFileSync(path.join(testDir, "async.txt"), "");
|
|
611
|
+
|
|
612
|
+
const files = await fsGlob(path.join(testDir, "*.txt"));
|
|
613
|
+
|
|
614
|
+
expect(files.length).toBeGreaterThan(0);
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
//#endregion
|
|
619
|
+
|
|
620
|
+
//#region clearEmptyDirectoryAsync
|
|
621
|
+
|
|
622
|
+
describe("fsClearEmptyDirectory", () => {
|
|
623
|
+
it("빈 디렉토리 재귀적으로 삭제", async () => {
|
|
624
|
+
const emptyDir = path.join(testDir, "empty/nested/deep");
|
|
625
|
+
fs.mkdirSync(emptyDir, { recursive: true });
|
|
626
|
+
|
|
627
|
+
await fsClearEmptyDirectory(path.join(testDir, "empty"));
|
|
628
|
+
|
|
629
|
+
expect(fs.existsSync(path.join(testDir, "empty"))).toBe(false);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it("파일이 있는 디렉토리는 유지", async () => {
|
|
633
|
+
const dirWithFile = path.join(testDir, "notempty");
|
|
634
|
+
fs.mkdirSync(dirWithFile);
|
|
635
|
+
fs.writeFileSync(path.join(dirWithFile, "file.txt"), "content");
|
|
636
|
+
|
|
637
|
+
await fsClearEmptyDirectory(dirWithFile);
|
|
638
|
+
|
|
639
|
+
expect(fs.existsSync(dirWithFile)).toBe(true);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
//#endregion
|
|
644
|
+
|
|
645
|
+
//#region findAllParentChildPaths
|
|
646
|
+
|
|
647
|
+
describe("fsFindAllParentChildPathsSync", () => {
|
|
648
|
+
it("부모 디렉토리들에서 특정 파일 찾기", () => {
|
|
649
|
+
const deepDir = path.join(testDir, "a/b/c");
|
|
650
|
+
fs.mkdirSync(deepDir, { recursive: true });
|
|
651
|
+
fs.writeFileSync(path.join(testDir, "marker.txt"), "");
|
|
652
|
+
fs.writeFileSync(path.join(testDir, "a/marker.txt"), "");
|
|
653
|
+
|
|
654
|
+
const results = fsFindAllParentChildPathsSync("marker.txt", deepDir, testDir);
|
|
655
|
+
|
|
656
|
+
expect(results.length).toBe(2);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it("매칭되는 파일이 없으면 빈 배열 반환", () => {
|
|
660
|
+
const deepDir = path.join(testDir, "a/b/c");
|
|
661
|
+
fs.mkdirSync(deepDir, { recursive: true });
|
|
662
|
+
|
|
663
|
+
const results = fsFindAllParentChildPathsSync("nonexistent-file.txt", deepDir, testDir);
|
|
664
|
+
|
|
665
|
+
expect(results).toEqual([]);
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
describe("fsFindAllParentChildPaths", () => {
|
|
670
|
+
it("비동기로 부모 디렉토리들에서 특정 파일 찾기", async () => {
|
|
671
|
+
const deepDir = path.join(testDir, "x/y/z");
|
|
672
|
+
fs.mkdirSync(deepDir, { recursive: true });
|
|
673
|
+
fs.writeFileSync(path.join(testDir, "config.json"), "");
|
|
674
|
+
fs.writeFileSync(path.join(testDir, "x/config.json"), "");
|
|
675
|
+
|
|
676
|
+
const results = await fsFindAllParentChildPaths("config.json", deepDir, testDir);
|
|
677
|
+
|
|
678
|
+
expect(results.length).toBe(2);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it("비동기로 매칭되는 파일이 없으면 빈 배열 반환", async () => {
|
|
682
|
+
const deepDir = path.join(testDir, "x/y/z");
|
|
683
|
+
fs.mkdirSync(deepDir, { recursive: true });
|
|
684
|
+
|
|
685
|
+
const results = await fsFindAllParentChildPaths("nonexistent-file.txt", deepDir, testDir);
|
|
686
|
+
|
|
687
|
+
expect(results).toEqual([]);
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
//#endregion
|
|
692
|
+
|
|
693
|
+
//#region 에러 케이스
|
|
694
|
+
|
|
695
|
+
describe("에러 케이스", () => {
|
|
696
|
+
it("존재하지 않는 파일 읽기 시 SdError에 경로 정보 포함", () => {
|
|
697
|
+
const filePath = path.join(testDir, "nonexistent.txt");
|
|
698
|
+
expect(() => fsReadSync(filePath)).toThrow(SdError);
|
|
699
|
+
try {
|
|
700
|
+
fsReadSync(filePath);
|
|
701
|
+
} catch (err) {
|
|
702
|
+
expect((err as Error).message).toContain(filePath);
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it("존재하지 않는 파일 비동기 읽기 시 SdError에 경로 정보 포함", async () => {
|
|
707
|
+
const filePath = path.join(testDir, "nonexistent.txt");
|
|
708
|
+
await expect(fsRead(filePath)).rejects.toThrow(SdError);
|
|
709
|
+
try {
|
|
710
|
+
await fsRead(filePath);
|
|
711
|
+
} catch (err) {
|
|
712
|
+
expect((err as Error).message).toContain(filePath);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it("존재하지 않는 디렉토리 내용 읽기 시 에러 발생", () => {
|
|
717
|
+
expect(() => fsReaddirSync(path.join(testDir, "nonexistent"))).toThrow();
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it("존재하지 않는 파일 stat 시 에러 발생", () => {
|
|
721
|
+
expect(() => fsStatSync(path.join(testDir, "nonexistent.txt"))).toThrow();
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it("잘못된 JSON 형식 파일 읽기 시 SdError에 경로와 내용 정보 포함", () => {
|
|
725
|
+
const filePath = path.join(testDir, "invalid.json");
|
|
726
|
+
const content = "{ invalid json }";
|
|
727
|
+
fs.writeFileSync(filePath, content);
|
|
728
|
+
|
|
729
|
+
expect(() => fsReadJsonSync(filePath)).toThrow(SdError);
|
|
730
|
+
try {
|
|
731
|
+
fsReadJsonSync(filePath);
|
|
732
|
+
} catch (err) {
|
|
733
|
+
expect((err as Error).message).toContain(filePath);
|
|
734
|
+
expect((err as Error).message).toContain(content);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
it("잘못된 JSON 형식 파일 비동기 읽기 시 SdError에 경로와 내용 정보 포함", async () => {
|
|
739
|
+
const filePath = path.join(testDir, "invalid-async.json");
|
|
740
|
+
const content = "{ invalid json }";
|
|
741
|
+
fs.writeFileSync(filePath, content);
|
|
742
|
+
|
|
743
|
+
await expect(fsReadJson(filePath)).rejects.toThrow(SdError);
|
|
744
|
+
try {
|
|
745
|
+
await fsReadJson(filePath);
|
|
746
|
+
} catch (err) {
|
|
747
|
+
expect((err as Error).message).toContain(filePath);
|
|
748
|
+
expect((err as Error).message).toContain(content);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
//#endregion
|
|
754
|
+
});
|