sonamu 0.11.4 → 0.11.6
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/dist/syncer/syncer.d.ts +1 -11
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +29 -47
- package/dist/ui-web/assets/index-5i4uZVES.css +1 -0
- package/dist/ui-web/assets/{index-DV1AAq6W.js → index-C_RUxOdN.js} +42 -42
- package/dist/ui-web/index.html +2 -2
- package/package.json +1 -1
- package/src/syncer/__tests__/http-validator-registry.test.ts +112 -78
- package/src/syncer/syncer.ts +45 -60
- package/dist/ui-web/assets/index-D3QNH-Dv.css +0 -1
package/dist/ui-web/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/sonamu-ui/setting.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>{{projectName}}: Sonamu UI</title>
|
|
8
|
-
<script type="module" crossorigin src="/sonamu-ui/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/sonamu-ui/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/sonamu-ui/assets/index-C_RUxOdN.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/sonamu-ui/assets/index-5i4uZVES.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { access, mkdir, mkdtemp, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
|
|
5
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
7
|
|
|
7
8
|
import { type SonamuConfig } from "../../api/config";
|
|
8
9
|
import { Sonamu } from "../../api/sonamu";
|
|
10
|
+
import { Template__entry_server } from "../../template/implementations/entry-server.template";
|
|
11
|
+
import { Template__http_validators } from "../../template/implementations/http_validators.template";
|
|
12
|
+
import { type Template } from "../../template/template";
|
|
13
|
+
import { TemplateManager } from "../../template/template-manager";
|
|
9
14
|
import { type AbsolutePath } from "../../utils/path-utils";
|
|
10
|
-
import {
|
|
15
|
+
import { findChangedFilesUsingChecksums } from "../checksum";
|
|
16
|
+
import { Syncer } from "../syncer";
|
|
11
17
|
import { actionGenerateHttpValidators } from "../syncer-actions";
|
|
12
18
|
|
|
19
|
+
type ZodCompilerPolicy = NonNullable<SonamuConfig["validation"]>["zodCompiler"];
|
|
20
|
+
|
|
21
|
+
type TestProject = {
|
|
22
|
+
apiRootPath: AbsolutePath;
|
|
23
|
+
lockPath: string;
|
|
24
|
+
registryPath: AbsolutePath;
|
|
25
|
+
syncer: Syncer;
|
|
26
|
+
};
|
|
27
|
+
|
|
13
28
|
function toAbsolutePath(filePath: string): AbsolutePath {
|
|
14
29
|
if (!path.isAbsolute(filePath)) {
|
|
15
30
|
throw new Error(`절대 경로가 필요합니다: ${filePath}`);
|
|
@@ -18,24 +33,14 @@ function toAbsolutePath(filePath: string): AbsolutePath {
|
|
|
18
33
|
return filePath as AbsolutePath;
|
|
19
34
|
}
|
|
20
35
|
|
|
21
|
-
function
|
|
22
|
-
return
|
|
23
|
-
actionCopySharedToTargetsIfNotExists: vi
|
|
24
|
-
.fn<SyncerDependencies["actionCopySharedToTargetsIfNotExists"]>()
|
|
25
|
-
.mockResolvedValue(undefined),
|
|
26
|
-
actionGenerateHttpValidators: vi.fn<SyncerDependencies["actionGenerateHttpValidators"]>(),
|
|
27
|
-
actionGenerateSsrEntryServerIfNotExists: vi
|
|
28
|
-
.fn<SyncerDependencies["actionGenerateSsrEntryServerIfNotExists"]>()
|
|
29
|
-
.mockResolvedValue([]),
|
|
30
|
-
findChangedFilesUsingChecksums: vi.fn<SyncerDependencies["findChangedFilesUsingChecksums"]>(),
|
|
31
|
-
renewChecksums: vi.fn<SyncerDependencies["renewChecksums"]>().mockResolvedValue(undefined),
|
|
32
|
-
} satisfies SyncerDependencies;
|
|
36
|
+
function sha1(content: string): string {
|
|
37
|
+
return createHash("sha1").update(content).digest("hex");
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
const testConfig = {
|
|
36
41
|
api: { dir: "api", route: { prefix: "/api" } },
|
|
37
42
|
i18n: { defaultLocale: "ko", supportedLocales: ["ko"] },
|
|
38
|
-
sync: { targets: [] },
|
|
43
|
+
sync: { targets: ["web"] },
|
|
39
44
|
database: {},
|
|
40
45
|
server: {
|
|
41
46
|
apiConfig: {
|
|
@@ -47,91 +52,120 @@ const testConfig = {
|
|
|
47
52
|
|
|
48
53
|
describe("HTTP validator registry sync 정리", () => {
|
|
49
54
|
const tempRoots: string[] = [];
|
|
55
|
+
let sonamuSnapshot: ReturnType<typeof Sonamu.captureTestingSnapshot>;
|
|
56
|
+
let registeredTemplates: Template[];
|
|
57
|
+
let templateManagerWasAutoloaded: boolean;
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
sonamuSnapshot = Sonamu.captureTestingSnapshot();
|
|
61
|
+
registeredTemplates = TemplateManager.getAllKeys().map((key) => TemplateManager.get(key));
|
|
62
|
+
templateManagerWasAutoloaded = TemplateManager.isAutoloaded;
|
|
63
|
+
|
|
64
|
+
TemplateManager.reset();
|
|
65
|
+
TemplateManager.registerAll([new Template__http_validators(), new Template__entry_server()]);
|
|
66
|
+
});
|
|
50
67
|
|
|
51
68
|
afterEach(async () => {
|
|
52
69
|
vi.restoreAllMocks();
|
|
70
|
+
Sonamu.restoreTestingSnapshot(sonamuSnapshot);
|
|
71
|
+
TemplateManager.reset();
|
|
72
|
+
TemplateManager.registerAll(registeredTemplates);
|
|
73
|
+
TemplateManager.isAutoloaded = templateManagerWasAutoloaded;
|
|
53
74
|
await Promise.all(
|
|
54
75
|
tempRoots.splice(0).map((rootPath) => rm(rootPath, { recursive: true, force: true })),
|
|
55
76
|
);
|
|
56
77
|
});
|
|
57
78
|
|
|
79
|
+
async function createTestProject(zodCompiler: ZodCompilerPolicy): Promise<TestProject> {
|
|
80
|
+
const appRootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-registry-sync-test-"));
|
|
81
|
+
tempRoots.push(appRootPath);
|
|
82
|
+
const apiRootPath = toAbsolutePath(path.join(appRootPath, "api"));
|
|
83
|
+
const registryPath = toAbsolutePath(
|
|
84
|
+
path.join(apiRootPath, "src/application/sonamu.validators.generated.ts"),
|
|
85
|
+
);
|
|
86
|
+
await Promise.all([
|
|
87
|
+
mkdir(path.dirname(registryPath), { recursive: true }),
|
|
88
|
+
mkdir(path.join(appRootPath, "web/src/services"), { recursive: true }),
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
const syncer = new Syncer();
|
|
92
|
+
Sonamu.apiRootPath = apiRootPath;
|
|
93
|
+
Sonamu.config = {
|
|
94
|
+
...testConfig,
|
|
95
|
+
validation: { zodCompiler },
|
|
96
|
+
};
|
|
97
|
+
Sonamu.syncer = syncer;
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
apiRootPath,
|
|
101
|
+
lockPath: path.join(apiRootPath, "sonamu.lock"),
|
|
102
|
+
registryPath,
|
|
103
|
+
syncer,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function readLock(lockPath: string): Promise<Array<{ path: string; checksum: string }>> {
|
|
108
|
+
// SAFETY: renewChecksums가 쓰는 sonamu.lock 직렬화 계약을 검증하는 테스트다.
|
|
109
|
+
return JSON.parse(await readFile(lockPath, "utf-8")) as Array<{
|
|
110
|
+
path: string;
|
|
111
|
+
checksum: string;
|
|
112
|
+
}>;
|
|
113
|
+
}
|
|
114
|
+
|
|
58
115
|
it.each([
|
|
59
116
|
{ label: "opt-out", value: false as const },
|
|
60
117
|
{ label: "JIT", value: { api: "jit" as const } },
|
|
61
|
-
])("$label 정책은
|
|
62
|
-
const
|
|
63
|
-
tempRoots.push(apiRootPath);
|
|
64
|
-
const registryPath = path.join(apiRootPath, "src/application/sonamu.validators.generated.ts");
|
|
65
|
-
await mkdir(path.dirname(registryPath), { recursive: true });
|
|
118
|
+
])("$label 정책은 실제 generated registry를 제거한다", async ({ value }) => {
|
|
119
|
+
const { registryPath } = await createTestProject(value);
|
|
66
120
|
await writeFile(registryPath, "export const validators = new Map();\n");
|
|
67
|
-
Sonamu.apiRootPath = toAbsolutePath(apiRootPath);
|
|
68
|
-
Sonamu.config = {
|
|
69
|
-
...testConfig,
|
|
70
|
-
api: { ...testConfig.api, dir: "." },
|
|
71
|
-
validation: { zodCompiler: value },
|
|
72
|
-
};
|
|
73
121
|
|
|
74
122
|
await actionGenerateHttpValidators();
|
|
75
123
|
|
|
76
124
|
await expect(access(registryPath)).rejects.toMatchObject({ code: "ENOENT" });
|
|
77
125
|
});
|
|
78
126
|
|
|
79
|
-
it("registry-only drift를
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
path.join(apiRootPath, "src/application/sonamu.validators.generated.ts"),
|
|
85
|
-
);
|
|
86
|
-
await mkdir(path.dirname(registryPath), { recursive: true });
|
|
127
|
+
it("registry-only drift를 정본으로 복구하고 갱신된 checksum lock을 남긴다", async () => {
|
|
128
|
+
const { lockPath, registryPath, syncer } = await createTestProject({ api: "aot" });
|
|
129
|
+
await actionGenerateHttpValidators();
|
|
130
|
+
const canonicalRegistry = await readFile(registryPath, "utf-8");
|
|
131
|
+
await syncer.renewChecksums();
|
|
87
132
|
await writeFile(registryPath, "// drifted registry\n");
|
|
88
|
-
|
|
89
|
-
Sonamu.config = {
|
|
90
|
-
...testConfig,
|
|
91
|
-
validation: { zodCompiler: { api: "aot" } },
|
|
92
|
-
};
|
|
93
|
-
const dependencies = createDependencies();
|
|
94
|
-
dependencies.actionGenerateHttpValidators.mockResolvedValue(registryPath);
|
|
95
|
-
dependencies.findChangedFilesUsingChecksums.mockResolvedValue([registryPath]);
|
|
96
|
-
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
133
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
97
134
|
|
|
98
|
-
await
|
|
135
|
+
await syncer.sync();
|
|
99
136
|
|
|
100
|
-
expect(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
it("삭제된 registry가 checksum 변경 목록에서 빠져도 다른 tracked 변경 처리 중 복구한다", async () => {
|
|
105
|
-
const appRootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-missing-registry-test-"));
|
|
106
|
-
tempRoots.push(appRootPath);
|
|
107
|
-
const apiRootPath = path.join(appRootPath, "api");
|
|
108
|
-
const registryPath = toAbsolutePath(
|
|
109
|
-
path.join(apiRootPath, "src/application/sonamu.validators.generated.ts"),
|
|
110
|
-
);
|
|
111
|
-
const unrelatedPath = toAbsolutePath(
|
|
112
|
-
path.join(apiRootPath, "src/application/queries.generated.ts"),
|
|
137
|
+
expect(await readFile(registryPath, "utf-8")).toBe(canonicalRegistry);
|
|
138
|
+
const registryLock = (await readLock(lockPath)).find((entry) =>
|
|
139
|
+
entry.path.endsWith("sonamu.validators.generated.ts"),
|
|
113
140
|
);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Sonamu.config = {
|
|
118
|
-
...testConfig,
|
|
119
|
-
validation: { zodCompiler: { api: "aot" } },
|
|
120
|
-
};
|
|
121
|
-
const order: string[] = [];
|
|
122
|
-
const dependencies = createDependencies();
|
|
123
|
-
dependencies.actionGenerateHttpValidators.mockImplementation(async () => {
|
|
124
|
-
order.push("registry");
|
|
125
|
-
return registryPath;
|
|
141
|
+
expect(registryLock).toEqual({
|
|
142
|
+
path: "api/src/application/sonamu.validators.generated.ts",
|
|
143
|
+
checksum: sha1(canonicalRegistry),
|
|
126
144
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
131
|
-
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
132
|
-
|
|
133
|
-
await new Syncer(dependencies).sync();
|
|
145
|
+
await expect(findChangedFilesUsingChecksums()).resolves.toEqual([]);
|
|
146
|
+
expect(warn).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
134
148
|
|
|
135
|
-
|
|
149
|
+
it("삭제된 registry는 다른 tracked 변경을 처리할 때 lock 갱신 전에 복구한다", async () => {
|
|
150
|
+
const { apiRootPath, lockPath, registryPath, syncer } = await createTestProject({ api: "aot" });
|
|
151
|
+
const otherGeneratedPath = path.join(apiRootPath, "src/application/queries.generated.ts");
|
|
152
|
+
await actionGenerateHttpValidators();
|
|
153
|
+
const canonicalRegistry = await readFile(registryPath, "utf-8");
|
|
154
|
+
await writeFile(otherGeneratedPath, "// original generated output\n");
|
|
155
|
+
await syncer.renewChecksums();
|
|
156
|
+
await unlink(registryPath);
|
|
157
|
+
await writeFile(otherGeneratedPath, "// drifted generated output\n");
|
|
158
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
159
|
+
|
|
160
|
+
await syncer.sync();
|
|
161
|
+
|
|
162
|
+
expect(await readFile(registryPath, "utf-8")).toBe(canonicalRegistry);
|
|
163
|
+
const lock = await readLock(lockPath);
|
|
164
|
+
expect(lock).toContainEqual({
|
|
165
|
+
path: "api/src/application/sonamu.validators.generated.ts",
|
|
166
|
+
checksum: sha1(canonicalRegistry),
|
|
167
|
+
});
|
|
168
|
+
await expect(findChangedFilesUsingChecksums()).resolves.toEqual([]);
|
|
169
|
+
expect(warn.mock.calls.flat().join(" ")).toContain("queries.generated.ts");
|
|
136
170
|
});
|
|
137
171
|
});
|
package/src/syncer/syncer.ts
CHANGED
|
@@ -37,22 +37,6 @@ import * as SyncerActions from "./syncer-actions";
|
|
|
37
37
|
type DiffGroupKey = FileType | "unknown";
|
|
38
38
|
type DiffGroups = Partial<Record<DiffGroupKey, AbsolutePath[]>>;
|
|
39
39
|
|
|
40
|
-
export interface SyncerDependencies {
|
|
41
|
-
actionCopySharedToTargetsIfNotExists: typeof SyncerActions.actionCopySharedToTargetsIfNotExists;
|
|
42
|
-
actionGenerateHttpValidators: typeof SyncerActions.actionGenerateHttpValidators;
|
|
43
|
-
actionGenerateSsrEntryServerIfNotExists: typeof SyncerActions.actionGenerateSsrEntryServerIfNotExists;
|
|
44
|
-
findChangedFilesUsingChecksums: typeof findChangedFilesUsingChecksums;
|
|
45
|
-
renewChecksums: typeof renewChecksums;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const defaultSyncerDependencies: SyncerDependencies = {
|
|
49
|
-
actionCopySharedToTargetsIfNotExists: SyncerActions.actionCopySharedToTargetsIfNotExists,
|
|
50
|
-
actionGenerateHttpValidators: SyncerActions.actionGenerateHttpValidators,
|
|
51
|
-
actionGenerateSsrEntryServerIfNotExists: SyncerActions.actionGenerateSsrEntryServerIfNotExists,
|
|
52
|
-
findChangedFilesUsingChecksums,
|
|
53
|
-
renewChecksums,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
40
|
export class Syncer {
|
|
57
41
|
apis: LoadedApis = [];
|
|
58
42
|
types: LoadedTypes = {};
|
|
@@ -60,8 +44,6 @@ export class Syncer {
|
|
|
60
44
|
workflows: Map<string, WorkflowMetadata[]> = new Map();
|
|
61
45
|
eventEmitter: EventEmitter = new EventEmitter();
|
|
62
46
|
|
|
63
|
-
constructor(private readonly dependencies: SyncerDependencies = defaultSyncerDependencies) {}
|
|
64
|
-
|
|
65
47
|
/**
|
|
66
48
|
* 체크섬이 변경된 부분에 대해 싱크를 진행합니다.
|
|
67
49
|
* dev 서버가 처음 떴을 때, sonamu sync 할 때 실행됩니다. 이후에는 hmrAndSync 경로를 타요.
|
|
@@ -71,14 +53,14 @@ export class Syncer {
|
|
|
71
53
|
// 초기 부트스트랩! 얘네들은 idempotent하고 가볍기 때문에 무지성 실행해도 됩니다.
|
|
72
54
|
// 얘네들은 sonamu.lock에 들어가지도 않고 따라서 HMR 경로를 타지도 않는 친구들입니다.
|
|
73
55
|
// 그래서 아무 때나 그냥 돌려주면 되는데, hmrAndSync에서 매번 하는 것은 낭비이니 여기서 한 번만 합니다.
|
|
74
|
-
await
|
|
75
|
-
await
|
|
56
|
+
await SyncerActions.actionCopySharedToTargetsIfNotExists();
|
|
57
|
+
await SyncerActions.actionGenerateSsrEntryServerIfNotExists();
|
|
76
58
|
|
|
77
59
|
// 바뀐 것이 없으면 그냥 넘어가요.
|
|
78
|
-
const changedFiles = await
|
|
60
|
+
const changedFiles = await findChangedFilesUsingChecksums();
|
|
79
61
|
if (changedFiles.length === 0) {
|
|
80
62
|
if (await this.reconcileHttpValidatorRegistry()) {
|
|
81
|
-
await
|
|
63
|
+
await renewChecksums();
|
|
82
64
|
}
|
|
83
65
|
console.log(chalk.black.bgGreen(centerText("All files are synced!")));
|
|
84
66
|
return;
|
|
@@ -94,7 +76,7 @@ export class Syncer {
|
|
|
94
76
|
await this.reconcileHttpValidatorRegistry();
|
|
95
77
|
|
|
96
78
|
// 싱크 액션이 끝나면 항상 체크섬을 다시 갱신합니다.
|
|
97
|
-
await
|
|
79
|
+
await renewChecksums();
|
|
98
80
|
},
|
|
99
81
|
{ whenThisHappens: "SIGUSR2", waitForUpTo: 20000 },
|
|
100
82
|
);
|
|
@@ -112,7 +94,7 @@ export class Syncer {
|
|
|
112
94
|
}
|
|
113
95
|
|
|
114
96
|
// 변경 목록이 registry 삭제를 놓쳐도 checksum 갱신 전에 정책과 산출물을 다시 맞춥니다.
|
|
115
|
-
await
|
|
97
|
+
await SyncerActions.actionGenerateHttpValidators();
|
|
116
98
|
return true;
|
|
117
99
|
}
|
|
118
100
|
|
|
@@ -351,7 +333,7 @@ export class Syncer {
|
|
|
351
333
|
|
|
352
334
|
// 여기는 별로 중요한 파트는 아닙니다.
|
|
353
335
|
// 아래의 if 전개를 깔끔하게 하려고 만든 DSL 같은 거라서, 무시하셔도 됩니다.
|
|
354
|
-
const { changeMatches,
|
|
336
|
+
const { changeMatches, unhandledPaths, unhandledChangesLeft } = this.changeMatcher(
|
|
355
337
|
diffTypes,
|
|
356
338
|
diffGroups,
|
|
357
339
|
);
|
|
@@ -372,30 +354,24 @@ export class Syncer {
|
|
|
372
354
|
await this.handleConfigChanges();
|
|
373
355
|
}
|
|
374
356
|
|
|
375
|
-
if (changeMatches("
|
|
376
|
-
|
|
357
|
+
if (changeMatches("workflow")) {
|
|
358
|
+
// 뭐 딱히 안 해요. 그래도 이 if 조건은 eval되어야 하기 때문에 남겨둡니다 ㅎㅎ
|
|
377
359
|
}
|
|
378
360
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
);
|
|
382
|
-
if (changeMatches("httpValidatorsGenerated") && !metadataChanged) {
|
|
383
|
-
// registry 자체 drift는 최신 in-memory metadata로 즉시 덮어써 lock 갱신 전에 정합성을 복구합니다.
|
|
384
|
-
await this.dependencies.actionGenerateHttpValidators();
|
|
361
|
+
if (changeMatches("i18n", "entity" /*레이블*/, "config" /*defaultLocale등*/)) {
|
|
362
|
+
await this.handleSonamuDictionaryRelatedChanges();
|
|
385
363
|
}
|
|
386
364
|
|
|
387
|
-
if (
|
|
388
|
-
//
|
|
389
|
-
|
|
390
|
-
await this.
|
|
391
|
-
await this.autoloadApis();
|
|
392
|
-
await this.dependencies.actionGenerateHttpValidators();
|
|
365
|
+
if (changeMatches("entity", "types", "model", "frame", "config", "httpValidatorsGenerated")) {
|
|
366
|
+
// 생성물(httpValidatorsGenerated)에 드리프트가 생긴 경우도 처리합니다.
|
|
367
|
+
// 다른 것 안 바뀌고 딱 httpValidatorsGenerated만 바뀐 경우에도 동등한 처리(=무지성 오토로드)를 합니다. 그냥 이뻐서요 ㅋ
|
|
368
|
+
await this.handleHttpValidatorRelatedChanges();
|
|
393
369
|
}
|
|
394
370
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
371
|
+
// 위에서 처리 안 된 변경들이 있다?
|
|
372
|
+
// 체크섬 관리 대상이긴 한데 위에서 핸들링이 안 된 것이라면 결국 생성물입니다.
|
|
373
|
+
// 이 친구들이 변경된 것은 drift로 간주해도 마땅합니다.
|
|
374
|
+
if (unhandledChangesLeft()) {
|
|
399
375
|
await this.handleDrifts(unhandledPaths());
|
|
400
376
|
}
|
|
401
377
|
|
|
@@ -442,18 +418,18 @@ export class Syncer {
|
|
|
442
418
|
return matching.length > 0;
|
|
443
419
|
};
|
|
444
420
|
|
|
445
|
-
/**
|
|
446
|
-
* changeMatches로 매칭된 것이 하나도 없는지 여부를 가져옵니다.
|
|
447
|
-
*/
|
|
448
|
-
const nothingMatches = () => handled.size === 0;
|
|
449
|
-
|
|
450
421
|
/**
|
|
451
422
|
* 어떤 changeMatches 호출에도 걸리지 않은 FileType들의 실제 파일 경로를 모아서 반환합니다.
|
|
452
423
|
*/
|
|
453
424
|
const unhandledPaths = (): AbsolutePath[] =>
|
|
454
425
|
diffTypes.filter((t) => !handled.has(t)).flatMap((t) => diffGroups[t] ?? []);
|
|
455
426
|
|
|
456
|
-
|
|
427
|
+
/**
|
|
428
|
+
* changeMatches 호출에도 걸리지 않은 FileType들이 존재하는지 확인합니다.
|
|
429
|
+
*/
|
|
430
|
+
const unhandledChangesLeft = () => unhandledPaths().length > 0;
|
|
431
|
+
|
|
432
|
+
return { changeMatches, unhandledPaths, unhandledChangesLeft };
|
|
457
433
|
}
|
|
458
434
|
|
|
459
435
|
async handleTruthSourceChanges(diffGroups: DiffGroups): Promise<void> {
|
|
@@ -542,18 +518,27 @@ export class Syncer {
|
|
|
542
518
|
await SyncerActions.actionSyncSonamuDictionary();
|
|
543
519
|
}
|
|
544
520
|
|
|
521
|
+
async handleHttpValidatorRelatedChanges(): Promise<void> {
|
|
522
|
+
await this.autoloadTypes();
|
|
523
|
+
await this.autoloadModels();
|
|
524
|
+
await this.autoloadApis();
|
|
525
|
+
await SyncerActions.actionGenerateHttpValidators();
|
|
526
|
+
}
|
|
527
|
+
|
|
545
528
|
async handleDrifts(drifts: AbsolutePath[]): Promise<void> {
|
|
546
|
-
if (drifts.length
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
529
|
+
if (drifts.length === 0) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
console.warn(
|
|
534
|
+
chalk.yellow(
|
|
535
|
+
"⚠️ Sonamu가 자동 생성한 파일에 대한 변경이 감지되었습니다. 파일이 Sonamu watcher 외부에서 변경된 것으로 추정됩니다.",
|
|
536
|
+
),
|
|
537
|
+
);
|
|
538
|
+
for (const p of drifts) {
|
|
539
|
+
console.warn(chalk.yellow(` - ${path.relative(Sonamu.appRootPath, p)}`));
|
|
556
540
|
}
|
|
541
|
+
console.warn(chalk.dim(" → `pnpm sonamu sync --force`를 권장합니다."));
|
|
557
542
|
}
|
|
558
543
|
|
|
559
544
|
/**
|
|
@@ -672,6 +657,6 @@ export class Syncer {
|
|
|
672
657
|
* 하위호환용 프록시 메소드입니다.
|
|
673
658
|
*/
|
|
674
659
|
async renewChecksums(): Promise<void> {
|
|
675
|
-
return await
|
|
660
|
+
return await renewChecksums();
|
|
676
661
|
}
|
|
677
662
|
}
|