sonamu 0.10.13 → 0.11.0
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/ai/agents/agent.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/error.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/model.d.ts.map +1 -1
- package/dist/ai/providers/rtzr/utils.d.ts.map +1 -1
- package/dist/api/base-frame.d.ts.map +1 -1
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +3 -2
- package/dist/api/validator.d.ts.map +1 -1
- package/dist/auth/audit-log/events.d.ts.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -3
- package/dist/auth/knex-adapter.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/admin.d.ts +4 -4
- package/dist/auth/plugins/wrappers/admin.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/anonymous.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/api-key.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/jwt.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/organization.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/passkey.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/phone-number.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/sso.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/two-factor.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/username.d.ts.map +1 -1
- package/dist/bin/build-config.d.ts +10 -0
- package/dist/bin/build-config.d.ts.map +1 -1
- package/dist/bin/build-config.js +130 -6
- package/dist/bin/fixture.d.ts +7 -4
- package/dist/bin/fixture.d.ts.map +1 -1
- package/dist/bin/fixture.js +161 -13
- package/dist/bin/test-command.d.ts +7 -0
- package/dist/bin/test-command.d.ts.map +1 -1
- package/dist/bin/test-command.js +18 -4
- package/dist/bin/typecheck-web.d.ts +2 -0
- package/dist/bin/typecheck-web.d.ts.map +1 -0
- package/dist/bin/typecheck-web.js +36 -0
- package/dist/cache/decorator.d.ts.map +1 -1
- package/dist/cache/drivers.d.ts.map +1 -1
- package/dist/cache-control/cache-control.d.ts.map +1 -1
- package/dist/compress/compress.d.ts.map +1 -1
- package/dist/cone/cone-generator.js +1 -1
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.types.d.ts +0 -1
- package/dist/database/base-model.types.d.ts.map +1 -1
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/puri-subset.types.d.ts +0 -1
- package/dist/database/puri-subset.types.d.ts.map +1 -1
- package/dist/database/puri-wrapper.d.ts +0 -1
- package/dist/database/puri-wrapper.d.ts.map +1 -1
- package/dist/database/puri.d.ts +0 -1
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.types.d.ts +0 -1
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/transaction-context.d.ts.map +1 -1
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/dict/en.d.ts +58 -58
- package/dist/dict/en.d.ts.map +1 -1
- package/dist/dict/ko.d.ts +58 -58
- package/dist/dict/ko.d.ts.map +1 -1
- package/dist/dict/rc-keys.d.ts.map +1 -1
- package/dist/dict/sonamu-dictionary.d.ts.map +1 -1
- package/dist/dict/sonamu-dictionary.js +2 -2
- package/dist/dict/utils.d.ts.map +1 -1
- package/dist/entity/entity-manager.d.ts +115 -113
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity-manager.js +25 -19
- package/dist/entity/entity-template-cone.d.ts +4 -4
- package/dist/entity/entity.d.ts +6 -1
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +8 -3
- package/dist/env.d.ts.map +1 -1
- package/dist/exceptions/so-exceptions.d.ts.map +1 -1
- package/dist/filter/types.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/logger/configure.d.ts +10 -1
- package/dist/logger/configure.d.ts.map +1 -1
- package/dist/logger/configure.js +30 -5
- package/dist/migration/migrator.d.ts +12 -1
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +77 -30
- package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
- package/dist/migration/slack-confirm.d.ts.map +1 -1
- package/dist/naite/messaging-types.d.ts +0 -1
- package/dist/naite/messaging-types.d.ts.map +1 -1
- package/dist/naite/naite-reporter.d.ts +0 -1
- package/dist/naite/naite-reporter.d.ts.map +1 -1
- package/dist/naite/naite.d.ts.map +1 -1
- package/dist/storage/base-file.d.ts.map +1 -1
- package/dist/storage/buffered-file.d.ts.map +1 -1
- package/dist/storage/drivers.d.ts.map +1 -1
- package/dist/storage/s3-driver.d.ts.map +1 -1
- package/dist/storage/storage-manager.d.ts.map +1 -1
- package/dist/storage/uploaded-file.d.ts.map +1 -1
- package/dist/stream/ws-audience-resolver.d.ts.map +1 -1
- package/dist/stream/ws-audience.d.ts.map +1 -1
- package/dist/stream/ws-cluster-bus.d.ts.map +1 -1
- package/dist/stream/ws-delivery.d.ts.map +1 -1
- package/dist/stream/ws-local-connection-store.d.ts.map +1 -1
- package/dist/stream/ws-presence-store.d.ts.map +1 -1
- package/dist/stream/ws-registry.d.ts.map +1 -1
- package/dist/stream/ws-telemetry-memory.d.ts.map +1 -1
- package/dist/stream/ws-telemetry.d.ts.map +1 -1
- package/dist/stream/ws.d.ts.map +1 -1
- package/dist/syncer/api-parser.js +2 -2
- package/dist/syncer/file-patterns.d.ts +17 -17
- package/dist/syncer/filesystem-dependencies.js +1 -1
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/tasks/step-wrapper.d.ts.map +1 -1
- package/dist/tasks/workflow-manager.d.ts.map +1 -1
- package/dist/template/implementations/entity.template.d.ts +2 -2
- package/dist/template/implementations/entity.template.d.ts.map +1 -1
- package/dist/template/implementations/entry-server.template.d.ts +2 -2
- package/dist/template/implementations/entry-server.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.d.ts +2 -2
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_http.template.d.ts +2 -2
- package/dist/template/implementations/generated_http.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_sso.template.d.ts +2 -2
- package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
- package/dist/template/implementations/http_validators.template.d.ts +2 -2
- package/dist/template/implementations/http_validators.template.d.ts.map +1 -1
- package/dist/template/implementations/init_types.template.d.ts +2 -2
- package/dist/template/implementations/init_types.template.d.ts.map +1 -1
- package/dist/template/implementations/model.template.d.ts +2 -2
- package/dist/template/implementations/model.template.d.ts.map +1 -1
- package/dist/template/implementations/model_test.template.d.ts +2 -2
- package/dist/template/implementations/model_test.template.d.ts.map +1 -1
- package/dist/template/implementations/queries.template.d.ts +2 -2
- package/dist/template/implementations/queries.template.d.ts.map +1 -1
- package/dist/template/implementations/sd.template.d.ts +2 -2
- package/dist/template/implementations/sd.template.d.ts.map +1 -1
- package/dist/template/implementations/sd.template.js +1 -1
- package/dist/template/implementations/services.template.d.ts +2 -2
- package/dist/template/implementations/services.template.d.ts.map +1 -1
- package/dist/template/implementations/view_form.template.d.ts +3 -3
- package/dist/template/implementations/view_form.template.d.ts.map +1 -1
- package/dist/template/implementations/view_id_all_select.template.d.ts +2 -2
- package/dist/template/implementations/view_id_all_select.template.d.ts.map +1 -1
- package/dist/template/implementations/view_list.template.d.ts +2 -2
- package/dist/template/implementations/view_list.template.d.ts.map +1 -1
- package/dist/template/implementations/view_search_input.template.d.ts +2 -2
- package/dist/template/implementations/view_search_input.template.d.ts.map +1 -1
- package/dist/template/template-manager.d.ts.map +1 -1
- package/dist/template/template.d.ts.map +1 -1
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/testing/_relation-graph.d.ts.map +1 -1
- package/dist/testing/data-explorer.d.ts +2 -2
- package/dist/testing/data-explorer.d.ts.map +1 -1
- package/dist/testing/dev-vitest-manager.d.ts.map +1 -1
- package/dist/testing/faker-mappings.d.ts.map +1 -1
- package/dist/testing/fixture-generator.d.ts.map +1 -1
- package/dist/testing/fixture-generator.js +1 -1
- package/dist/testing/fixture-loader.d.ts.map +1 -1
- package/dist/testing/fixture-manager.d.ts +4 -0
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +99 -25
- package/dist/testing/naite-vitest-reporter.d.ts.map +1 -1
- package/dist/testing/parallel-db-manager.d.ts.map +1 -1
- package/dist/tooling/__tests__/helpers/sonamu-test-root.d.ts +10 -0
- package/dist/tooling/__tests__/helpers/sonamu-test-root.d.ts.map +1 -0
- package/dist/tooling/__tests__/helpers/sonamu-test-root.js +42 -0
- package/dist/tooling/cdd-service.d.ts +87 -0
- package/dist/tooling/cdd-service.d.ts.map +1 -0
- package/dist/tooling/cdd-service.js +215 -0
- package/dist/tooling/cli-tooling.d.ts +11 -0
- package/dist/tooling/cli-tooling.d.ts.map +1 -0
- package/dist/tooling/cli-tooling.js +901 -0
- package/dist/tooling/fixture-service.d.ts +29 -0
- package/dist/tooling/fixture-service.d.ts.map +1 -0
- package/dist/tooling/fixture-service.js +53 -0
- package/dist/tooling/index.d.ts +2 -0
- package/dist/tooling/index.d.ts.map +1 -0
- package/dist/tooling/index.js +2 -0
- package/dist/tooling/test-run-client.d.ts +27 -0
- package/dist/tooling/test-run-client.d.ts.map +1 -0
- package/dist/tooling/test-run-client.js +120 -0
- package/dist/tsdown.api.config.js +48 -0
- package/dist/types/types.d.ts +527 -527
- package/dist/types/types.d.ts.map +1 -1
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/utils/formatter.d.ts +6 -0
- package/dist/utils/formatter.d.ts.map +1 -1
- package/dist/utils/formatter.js +42 -11
- package/dist/utils/model.d.ts +1 -1
- package/dist/vector/chunking.d.ts.map +1 -1
- package/dist/vector/embedding.d.ts.map +1 -1
- package/package.json +13 -12
- package/src/api/sonamu.ts +2 -1
- package/src/auth/index.ts +1 -1
- package/src/bin/__tests__/zod-compiler-build.test.ts +67 -0
- package/src/bin/build-config.ts +151 -5
- package/src/bin/fixture.ts +204 -21
- package/src/bin/test-command.ts +27 -4
- package/src/bin/typecheck-web.ts +56 -0
- package/src/dict/sonamu-dictionary.ts +1 -1
- package/src/entity/__tests__/entity-manager-autoload.contract.test.ts +55 -0
- package/src/entity/__tests__/entity-manager-explicit-root-registration.contract.test.ts +107 -0
- package/src/entity/entity-manager.ts +31 -19
- package/src/entity/entity.ts +7 -2
- package/src/logger/__tests__/configure.test.ts +33 -0
- package/src/logger/configure.ts +45 -3
- package/src/migration/__tests__/migrator.test.ts +51 -0
- package/src/migration/migrator.ts +103 -34
- package/src/syncer/api-parser.ts +1 -1
- package/src/testing/fixture-manager.ts +124 -32
- package/src/tooling/__tests__/cdd-rule-bootstrap.contract.test.ts +118 -0
- package/src/tooling/__tests__/cli-tooling-auth.contract.test.ts +72 -0
- package/src/tooling/__tests__/cli-tooling-cone-locale.contract.test.ts +150 -0
- package/src/tooling/__tests__/cli-tooling-entity-patch.contract.test.ts +131 -0
- package/src/tooling/__tests__/cli-tooling-fixture-transfer.contract.test.ts +255 -0
- package/src/tooling/__tests__/cli-tooling-i18n-update.contract.test.ts +104 -0
- package/src/tooling/__tests__/cli-tooling-scaffold.contract.test.ts +86 -0
- package/src/tooling/__tests__/cli-tooling-stub-practice.contract.test.ts +147 -0
- package/src/tooling/__tests__/default-cdd-adapter.contract.test.ts +73 -0
- package/src/tooling/__tests__/helpers/sonamu-test-root.ts +42 -0
- package/src/tooling/__tests__/test-run-client-config.contract.test.ts +154 -0
- package/src/tooling/cdd-service.ts +256 -0
- package/src/tooling/cli-tooling.ts +937 -0
- package/src/tooling/fixture-service.ts +72 -0
- package/src/tooling/index.ts +1 -0
- package/src/tooling/test-run-client.ts +196 -0
- package/src/utils/__tests__/formatter-bin.test.ts +99 -0
- package/src/utils/formatter.ts +74 -10
- package/tsdown.api.config.ts +8 -10
- package/bin/cli.js +0 -75
- package/dist/bin/cli.d.ts +0 -2
- package/dist/bin/cli.d.ts.map +0 -1
- package/dist/bin/cli.js +0 -956
- package/src/bin/cli.ts +0 -1166
package/src/bin/build-config.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
import
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { pathToFileURL } from "node:url";
|
|
4
5
|
|
|
6
|
+
import chalk from "chalk";
|
|
5
7
|
import { type UserConfig } from "tsdown";
|
|
6
8
|
|
|
7
9
|
import { type NormalizedZodCompilerPolicy } from "../api/config";
|
|
10
|
+
import {
|
|
11
|
+
execWithLinePrefix,
|
|
12
|
+
printBuildSummary,
|
|
13
|
+
printTaskFailed,
|
|
14
|
+
printTaskHeader,
|
|
15
|
+
printTaskStart,
|
|
16
|
+
printTaskSuccess,
|
|
17
|
+
} from "../utils/console-util";
|
|
8
18
|
import { exists } from "../utils/fs-utils";
|
|
19
|
+
import { findApiRootPath, findAppRootPath } from "../utils/utils";
|
|
20
|
+
import { loadBuildCompilerPolicy } from "./compiler-policy";
|
|
9
21
|
|
|
10
22
|
export type BuildArtifact<BuildCommandArgs = {}> = {
|
|
11
23
|
name: string;
|
|
@@ -18,6 +30,21 @@ export type BuildArtifact<BuildCommandArgs = {}> = {
|
|
|
18
30
|
|
|
19
31
|
export type ApiTsdownBuildConfig = Pick<UserConfig, "entry" | "plugins">;
|
|
20
32
|
|
|
33
|
+
export function quoteShellArgument(value: string): string {
|
|
34
|
+
// 작은따옴표 자체만 셸 경계를 닫지 못하도록 분리해 리터럴 인수를 보존합니다.
|
|
35
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createWebTypecheckCommand(): string {
|
|
39
|
+
const isSourceModule = path.extname(import.meta.filename) === ".ts";
|
|
40
|
+
const scriptPath = path.join(
|
|
41
|
+
import.meta.dirname,
|
|
42
|
+
`typecheck-web.${isSourceModule ? "ts" : "js"}`,
|
|
43
|
+
);
|
|
44
|
+
const runner = isSourceModule ? "pnpm exec tsx" : quoteShellArgument(process.execPath);
|
|
45
|
+
return `${runner} ${quoteShellArgument(scriptPath)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
21
48
|
type ZodCompilerPluginFactory = (typeof import("zod-compiler/rolldown"))["default"];
|
|
22
49
|
|
|
23
50
|
export interface ApiZodCompilerBuildDependencies {
|
|
@@ -184,8 +211,11 @@ export const API_ARTIFACTS: BuildArtifact<{ configFilePath: string }>[] = [
|
|
|
184
211
|
description: "API 프로젝트 빌드 산출물",
|
|
185
212
|
projectPath: "api",
|
|
186
213
|
preBuildCommand: () => "rm -rf dist",
|
|
187
|
-
buildCommand: ({ configFilePath }) =>
|
|
188
|
-
|
|
214
|
+
buildCommand: ({ configFilePath }) => {
|
|
215
|
+
const configExtension = path.extname(configFilePath);
|
|
216
|
+
const configLoader = [".ts", ".tsx"].includes(configExtension) ? " --config-loader tsx" : "";
|
|
217
|
+
return `tsc --noEmit && pnpm exec tsdown --config ${JSON.stringify(configFilePath)}${configLoader}`;
|
|
218
|
+
},
|
|
189
219
|
},
|
|
190
220
|
];
|
|
191
221
|
|
|
@@ -200,7 +230,7 @@ export const WEB_ARTIFACTS: BuildArtifact[] = [
|
|
|
200
230
|
projectPath: "web",
|
|
201
231
|
preBuildCommand: () => "rm -rf dist/client",
|
|
202
232
|
buildCommand: () =>
|
|
203
|
-
|
|
233
|
+
`${createWebTypecheckCommand()} && vite build --config vite.config.ts --outDir dist/client`,
|
|
204
234
|
},
|
|
205
235
|
{
|
|
206
236
|
name: "Web Server",
|
|
@@ -208,8 +238,124 @@ export const WEB_ARTIFACTS: BuildArtifact[] = [
|
|
|
208
238
|
projectPath: "web",
|
|
209
239
|
preBuildCommand: () => "rm -rf dist/server",
|
|
210
240
|
buildCommand: () =>
|
|
211
|
-
|
|
241
|
+
`${createWebTypecheckCommand()} && vite build --config vite.config.ts --ssr src/entry-server.generated.tsx --outDir dist/server`,
|
|
212
242
|
postBuildCommand: () =>
|
|
213
243
|
"rm -rf ../api/web-dist && mkdir -p ../api/web-dist && cp -r dist/* ../api/web-dist",
|
|
214
244
|
},
|
|
215
245
|
];
|
|
246
|
+
|
|
247
|
+
export async function resolveApiBuildConfigPath(
|
|
248
|
+
apiRootPath: string,
|
|
249
|
+
dependencies: {
|
|
250
|
+
exists: (candidatePath: string) => Promise<boolean>;
|
|
251
|
+
moduleDir: string;
|
|
252
|
+
} = {
|
|
253
|
+
exists,
|
|
254
|
+
moduleDir: import.meta.dirname,
|
|
255
|
+
},
|
|
256
|
+
): Promise<string> {
|
|
257
|
+
const localConfigPath = path.join(apiRootPath, "tsdown.config.ts");
|
|
258
|
+
if (await dependencies.exists(localConfigPath)) {
|
|
259
|
+
console.log(chalk.dim("Using tsdown.config.ts from project root..."));
|
|
260
|
+
return localConfigPath;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
console.log(chalk.dim("Using default tsdown API config from sonamu package..."));
|
|
264
|
+
const compiledConfigPath = path.join(dependencies.moduleDir, "..", "tsdown.api.config.js");
|
|
265
|
+
if (await dependencies.exists(compiledConfigPath)) {
|
|
266
|
+
return compiledConfigPath;
|
|
267
|
+
}
|
|
268
|
+
return path.join(dependencies.moduleDir, "..", "..", "tsdown.api.config.ts");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function runBuildSteps<T>(
|
|
272
|
+
artifact: BuildArtifact<T>,
|
|
273
|
+
options: { cwd: string; buildCommandArgs: T },
|
|
274
|
+
) {
|
|
275
|
+
const steps = [
|
|
276
|
+
{ name: "pre-build", cmd: artifact.preBuildCommand?.() },
|
|
277
|
+
{ name: "build", cmd: artifact.buildCommand(options.buildCommandArgs) },
|
|
278
|
+
{ name: "post-build", cmd: artifact.postBuildCommand?.() },
|
|
279
|
+
].filter((step) => step.cmd !== undefined);
|
|
280
|
+
|
|
281
|
+
for (let index = 0; index < steps.length; index++) {
|
|
282
|
+
const step = steps[index];
|
|
283
|
+
const isLast = index === steps.length - 1;
|
|
284
|
+
try {
|
|
285
|
+
assert(step.cmd);
|
|
286
|
+
printTaskStart(step.name, step.cmd, isLast);
|
|
287
|
+
await execWithLinePrefix(step.cmd, { cwd: options.cwd });
|
|
288
|
+
printTaskSuccess(step.name, isLast);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
printTaskFailed(step.name, isLast);
|
|
291
|
+
throw new Error(`${step.name} failed`, { cause: error });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export async function buildApiCommand(): Promise<void> {
|
|
297
|
+
const apiRootPath = findApiRootPath();
|
|
298
|
+
const baseConfigPath = await resolveApiBuildConfigPath(apiRootPath);
|
|
299
|
+
const policy = await loadBuildCompilerPolicy(apiRootPath);
|
|
300
|
+
const registryPath = path.join(apiRootPath, HTTP_VALIDATOR_REGISTRY_SOURCE_PATH);
|
|
301
|
+
const configFilePath = await createApiZodCompilerBuildWrapper({
|
|
302
|
+
apiRootPath,
|
|
303
|
+
baseConfigPath,
|
|
304
|
+
policy,
|
|
305
|
+
registryPath,
|
|
306
|
+
});
|
|
307
|
+
const temporaryConfigPath = configFilePath === baseConfigPath ? undefined : configFilePath;
|
|
308
|
+
const startedAt = Date.now();
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
for (const artifact of API_ARTIFACTS) {
|
|
312
|
+
printTaskHeader(artifact.name, artifact.description, apiRootPath);
|
|
313
|
+
await runBuildSteps(artifact, {
|
|
314
|
+
cwd: apiRootPath,
|
|
315
|
+
buildCommandArgs: { configFilePath },
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
if (policy.api === "aot") {
|
|
319
|
+
const outputPath = path.join(apiRootPath, HTTP_VALIDATOR_REGISTRY_BUILD_PATH);
|
|
320
|
+
if (!(await exists(outputPath))) {
|
|
321
|
+
throw new Error(`Built HTTP validator registry not found: ${outputPath}`);
|
|
322
|
+
}
|
|
323
|
+
assertApiZodCompilerBuildOutput([
|
|
324
|
+
{ code: await readFile(outputPath, "utf8"), path: outputPath },
|
|
325
|
+
]);
|
|
326
|
+
}
|
|
327
|
+
printBuildSummary("API", true, Date.now() - startedAt);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
printBuildSummary("API", false, Date.now() - startedAt);
|
|
330
|
+
throw error;
|
|
331
|
+
} finally {
|
|
332
|
+
if (temporaryConfigPath !== undefined) await rm(temporaryConfigPath, { force: true });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export async function buildWebCommand({
|
|
337
|
+
skipIfMissing = false,
|
|
338
|
+
}: { skipIfMissing?: boolean } = {}): Promise<void> {
|
|
339
|
+
const appRootPath = findAppRootPath();
|
|
340
|
+
const webPath = path.join(appRootPath, "web");
|
|
341
|
+
if (!(await exists(webPath))) {
|
|
342
|
+
if (skipIfMissing) {
|
|
343
|
+
console.log(chalk.gray("Web 디렉토리가 없으므로 Web 빌드를 건너뜁니다."));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
throw new Error(`web 디렉토리를 찾을 수 없습니다: ${webPath}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const startedAt = Date.now();
|
|
350
|
+
try {
|
|
351
|
+
for (const artifact of WEB_ARTIFACTS) {
|
|
352
|
+
const cwd = path.join(appRootPath, artifact.projectPath);
|
|
353
|
+
printTaskHeader(artifact.name, artifact.description, cwd);
|
|
354
|
+
await runBuildSteps(artifact, { cwd, buildCommandArgs: {} });
|
|
355
|
+
}
|
|
356
|
+
printBuildSummary("Web", true, Date.now() - startedAt);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
printBuildSummary("Web", false, Date.now() - startedAt);
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
}
|
package/src/bin/fixture.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { spawn, type SpawnOptions } from "node:child_process";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
|
|
1
4
|
import chalk from "chalk";
|
|
5
|
+
import knex, { type Knex } from "knex";
|
|
2
6
|
import prompts from "prompts";
|
|
3
7
|
import { z } from "zod";
|
|
4
8
|
|
|
@@ -10,18 +14,22 @@ import { getSonamuEnvironment } from "../env";
|
|
|
10
14
|
import { DataExplorer } from "../testing/data-explorer";
|
|
11
15
|
import { type DataExplorerStrategy } from "../testing/data-explorer";
|
|
12
16
|
import { FixtureGenerator } from "../testing/fixture-generator";
|
|
17
|
+
import { FixtureManager } from "../testing/fixture-manager";
|
|
18
|
+
import { exists } from "../utils/fs-utils";
|
|
13
19
|
|
|
14
|
-
interface FixtureCommandOptions {
|
|
20
|
+
export interface FixtureCommandOptions {
|
|
15
21
|
_?: string[];
|
|
16
22
|
all?: boolean;
|
|
17
23
|
include?: string;
|
|
18
24
|
exclude?: string;
|
|
19
|
-
count?: string;
|
|
25
|
+
count?: string | number;
|
|
20
26
|
"save-to"?: string;
|
|
21
27
|
strategy?: DataExplorerStrategy;
|
|
22
|
-
limit?: string;
|
|
28
|
+
limit?: string | number;
|
|
23
29
|
"use-llm"?: boolean;
|
|
24
30
|
"no-cache"?: boolean;
|
|
31
|
+
nonInteractive?: boolean;
|
|
32
|
+
userMode?: "login" | "dummy";
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
interface SignupBody {
|
|
@@ -34,6 +42,160 @@ interface SignupBody {
|
|
|
34
42
|
|
|
35
43
|
const SignupErrorResponse = z.object({ code: z.string().optional() }).passthrough();
|
|
36
44
|
|
|
45
|
+
function runProcess(
|
|
46
|
+
executable: string,
|
|
47
|
+
args: string[],
|
|
48
|
+
stdio: SpawnOptions["stdio"] = ["ignore", "inherit", "inherit"],
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const child = spawn(executable, args, { stdio });
|
|
52
|
+
child.once("error", reject);
|
|
53
|
+
child.once("exit", (code, signal) => {
|
|
54
|
+
if (code === 0) resolve();
|
|
55
|
+
else
|
|
56
|
+
reject(
|
|
57
|
+
new Error(`${executable} 명령이 ${code ?? signal ?? "unknown"} 상태로 실패했습니다.`),
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function writeMysqlDump(args: string[], outputPath: string) {
|
|
64
|
+
const output = await open(outputPath, "w");
|
|
65
|
+
try {
|
|
66
|
+
await runProcess("mysqldump", args, ["ignore", output.fd, "inherit"]);
|
|
67
|
+
} finally {
|
|
68
|
+
await output.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function runMysqlWithInput(args: string[], inputPath: string) {
|
|
73
|
+
const input = await open(inputPath, "r");
|
|
74
|
+
try {
|
|
75
|
+
await runProcess("mysql", args, [input.fd, "inherit", "inherit"]);
|
|
76
|
+
} finally {
|
|
77
|
+
await input.close();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mysqlConnectionArgs(connection: Knex.ConnectionConfig) {
|
|
82
|
+
return [
|
|
83
|
+
`-h${String(connection.host)}`,
|
|
84
|
+
`-u${String(connection.user)}`,
|
|
85
|
+
`-p${String(connection.password)}`,
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function fixtureInitCommand(): Promise<void> {
|
|
90
|
+
const srcConfig = Sonamu.dbConfig[getSonamuEnvironment()];
|
|
91
|
+
const fixtureConnection =
|
|
92
|
+
/* SAFETY: Sonamu DB 구성은 초기화 시 Knex 연결 구성으로 검증됩니다. */ Sonamu.dbConfig.fixture
|
|
93
|
+
.connection as Knex.ConnectionConfig;
|
|
94
|
+
const testConnection =
|
|
95
|
+
/* SAFETY: Sonamu DB 구성은 초기화 시 Knex 연결 구성으로 검증됩니다. */ Sonamu.dbConfig.test
|
|
96
|
+
.connection as Knex.ConnectionConfig;
|
|
97
|
+
const targets: Array<{ label: string; config: Knex.Config; toSkip?: boolean }> = [
|
|
98
|
+
{ label: "(REMOTE) Fixture DB", config: Sonamu.dbConfig.fixture },
|
|
99
|
+
{
|
|
100
|
+
label: "(LOCAL) Testing DB",
|
|
101
|
+
config: Sonamu.dbConfig.test,
|
|
102
|
+
// 같은 DB를 가리키면 원격 fixture를 다시 덮어쓰지 않습니다.
|
|
103
|
+
toSkip:
|
|
104
|
+
fixtureConnection.host === testConnection.host &&
|
|
105
|
+
fixtureConnection.database === testConnection.database,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const dumpFilename = `/tmp/sonamu-fixture-init-${Date.now()}.sql`;
|
|
110
|
+
const migrationsDump = `/tmp/sonamu-fixture-init-migrations-${Date.now()}.sql`;
|
|
111
|
+
const srcConnection =
|
|
112
|
+
/* SAFETY: Sonamu DB 구성은 초기화 시 Knex 연결 구성으로 검증됩니다. */ srcConfig.connection as Knex.ConnectionConfig;
|
|
113
|
+
await writeMysqlDump(
|
|
114
|
+
[
|
|
115
|
+
...mysqlConnectionArgs(srcConnection),
|
|
116
|
+
"--single-transaction",
|
|
117
|
+
"-d",
|
|
118
|
+
"--no-create-db",
|
|
119
|
+
"--triggers",
|
|
120
|
+
String(srcConnection.database),
|
|
121
|
+
],
|
|
122
|
+
dumpFilename,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const sourceDb = knex(srcConfig);
|
|
126
|
+
try {
|
|
127
|
+
const [[migrations]] = await sourceDb.raw(
|
|
128
|
+
"SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = ? AND table_name = 'knex_migrations'",
|
|
129
|
+
[srcConnection.database],
|
|
130
|
+
);
|
|
131
|
+
if (migrations.count > 0) {
|
|
132
|
+
await writeMysqlDump(
|
|
133
|
+
[
|
|
134
|
+
...mysqlConnectionArgs(srcConnection),
|
|
135
|
+
"--single-transaction",
|
|
136
|
+
"--no-create-db",
|
|
137
|
+
"--triggers",
|
|
138
|
+
String(srcConnection.database),
|
|
139
|
+
"knex_migrations",
|
|
140
|
+
"knex_migrations_lock",
|
|
141
|
+
],
|
|
142
|
+
migrationsDump,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const { label, config, toSkip } of targets) {
|
|
147
|
+
const connection =
|
|
148
|
+
/* SAFETY: targets는 검증된 Sonamu DB 구성에서만 조합됩니다. */ config.connection as Knex.ConnectionConfig;
|
|
149
|
+
if (toSkip === true) {
|
|
150
|
+
console.log(chalk.red(`${label}: Skipped!`));
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const databaseConnection =
|
|
155
|
+
/* SAFETY: targets는 검증된 Sonamu DB 구성에서만 조합됩니다. */ (config.connection ??
|
|
156
|
+
{}) as Knex.ConnectionConfig;
|
|
157
|
+
const db = knex({
|
|
158
|
+
...config,
|
|
159
|
+
connection: { ...databaseConnection, database: undefined },
|
|
160
|
+
});
|
|
161
|
+
try {
|
|
162
|
+
const [[row]] = await db.raw(`SHOW DATABASES LIKE "${connection.database}"`);
|
|
163
|
+
if (row) {
|
|
164
|
+
console.log(chalk.yellow(`${label}: Database "${connection.database}" Already exists`));
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const connectionArgs = mysqlConnectionArgs(connection);
|
|
169
|
+
const database = String(connection.database);
|
|
170
|
+
const escapedDatabase = database.replaceAll("`", "``");
|
|
171
|
+
await runProcess("mysql", [
|
|
172
|
+
...connectionArgs,
|
|
173
|
+
"-e",
|
|
174
|
+
`DROP DATABASE IF EXISTS \`${escapedDatabase}\``,
|
|
175
|
+
]);
|
|
176
|
+
await runProcess("mysql", [
|
|
177
|
+
...connectionArgs,
|
|
178
|
+
"-e",
|
|
179
|
+
`CREATE DATABASE \`${escapedDatabase}\``,
|
|
180
|
+
]);
|
|
181
|
+
await runMysqlWithInput([...connectionArgs, database], dumpFilename);
|
|
182
|
+
if (await exists(migrationsDump)) {
|
|
183
|
+
await runMysqlWithInput([...connectionArgs, database], migrationsDump);
|
|
184
|
+
}
|
|
185
|
+
} finally {
|
|
186
|
+
await db.destroy();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} finally {
|
|
190
|
+
await sourceDb.destroy();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function fixtureCleanupCommand(): Promise<void> {
|
|
195
|
+
await FixtureManager.destroy();
|
|
196
|
+
await Sonamu.destroy();
|
|
197
|
+
}
|
|
198
|
+
|
|
37
199
|
/**
|
|
38
200
|
* username을 일반적인 규칙(영문자 시작, 영문자/숫자만, 1-20자)에 맞도록 정규화합니다.
|
|
39
201
|
*/
|
|
@@ -70,6 +232,11 @@ export async function fixtureGenCommand(options: FixtureCommandOptions) {
|
|
|
70
232
|
}
|
|
71
233
|
} else if (options.include) {
|
|
72
234
|
entityNames = options.include.split(",").map((s: string) => s.trim());
|
|
235
|
+
} else if (options.nonInteractive === true) {
|
|
236
|
+
throw Object.assign(new Error("Fixture gen selector is required."), {
|
|
237
|
+
code: "MISSING_ARGUMENT",
|
|
238
|
+
exitCode: 2,
|
|
239
|
+
});
|
|
73
240
|
} else {
|
|
74
241
|
const result = await prompts({
|
|
75
242
|
type: "multiselect",
|
|
@@ -87,8 +254,8 @@ export async function fixtureGenCommand(options: FixtureCommandOptions) {
|
|
|
87
254
|
entityNames = result.entities;
|
|
88
255
|
}
|
|
89
256
|
|
|
90
|
-
let count = options.count ? Number.parseInt(options.count, 10) : 5;
|
|
91
|
-
if (!options.count) {
|
|
257
|
+
let count = options.count ? Number.parseInt(String(options.count), 10) : 5;
|
|
258
|
+
if (!options.count && options.nonInteractive !== true) {
|
|
92
259
|
const result = await prompts({
|
|
93
260
|
type: "number",
|
|
94
261
|
name: "count",
|
|
@@ -109,25 +276,30 @@ export async function fixtureGenCommand(options: FixtureCommandOptions) {
|
|
|
109
276
|
const hasUser = entityNames.includes("User");
|
|
110
277
|
|
|
111
278
|
if (hasUser) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
279
|
+
let userMode = options.userMode;
|
|
280
|
+
if (userMode === undefined && options.nonInteractive === true) userMode = "dummy";
|
|
281
|
+
if (userMode === undefined) {
|
|
282
|
+
const userModeResult = await prompts({
|
|
283
|
+
type: "select",
|
|
284
|
+
name: "userMode",
|
|
285
|
+
message: "User 엔티티가 포함되어 있습니다. 생성 방식을 선택하세요:",
|
|
286
|
+
choices: [
|
|
287
|
+
{ title: "1. 로그인 가능한 사용자 fixture 생성", value: "login" },
|
|
288
|
+
{ title: "2. 확인용 데이터(로그인 불가) 생성", value: "dummy" },
|
|
289
|
+
],
|
|
290
|
+
});
|
|
291
|
+
userMode = userModeResult.userMode;
|
|
292
|
+
}
|
|
121
293
|
|
|
122
|
-
if (!
|
|
294
|
+
if (!userMode) {
|
|
123
295
|
console.log(chalk.yellow("취소되었습니다."));
|
|
124
296
|
return;
|
|
125
297
|
}
|
|
126
298
|
|
|
127
|
-
if (
|
|
299
|
+
if (userMode === "login") {
|
|
128
300
|
// LLM 사용 여부
|
|
129
301
|
let useLLM = options["use-llm"] ?? false;
|
|
130
|
-
if (
|
|
302
|
+
if (options["use-llm"] === undefined && options.nonInteractive !== true) {
|
|
131
303
|
const llmResult = await prompts({
|
|
132
304
|
type: "confirm",
|
|
133
305
|
name: "useLLM",
|
|
@@ -232,7 +404,7 @@ export async function fixtureGenCommand(options: FixtureCommandOptions) {
|
|
|
232
404
|
}
|
|
233
405
|
|
|
234
406
|
let saveTarget = options["save-to"] || "db";
|
|
235
|
-
if (!options["save-to"]) {
|
|
407
|
+
if (!options["save-to"] && options.nonInteractive !== true) {
|
|
236
408
|
const result = await prompts({
|
|
237
409
|
type: "select",
|
|
238
410
|
name: "saveTarget",
|
|
@@ -266,7 +438,7 @@ export async function fixtureGenCommand(options: FixtureCommandOptions) {
|
|
|
266
438
|
|
|
267
439
|
// LLM 사용 여부 결정
|
|
268
440
|
let useLLM = options["use-llm"] ?? false;
|
|
269
|
-
if (
|
|
441
|
+
if (options["use-llm"] === undefined && options.nonInteractive !== true) {
|
|
270
442
|
const llmResult = await prompts({
|
|
271
443
|
type: "confirm",
|
|
272
444
|
name: "useLLM",
|
|
@@ -387,6 +559,11 @@ export async function fixtureFetchCommand(options: FixtureCommandOptions) {
|
|
|
387
559
|
}
|
|
388
560
|
} else if (options.include) {
|
|
389
561
|
entityNames = options.include.split(",").map((s: string) => s.trim());
|
|
562
|
+
} else if (options.nonInteractive === true) {
|
|
563
|
+
throw Object.assign(new Error("Fixture fetch selector is required."), {
|
|
564
|
+
code: "MISSING_ARGUMENT",
|
|
565
|
+
exitCode: 2,
|
|
566
|
+
});
|
|
390
567
|
} else {
|
|
391
568
|
const result = await prompts({
|
|
392
569
|
type: "multiselect",
|
|
@@ -405,7 +582,7 @@ export async function fixtureFetchCommand(options: FixtureCommandOptions) {
|
|
|
405
582
|
}
|
|
406
583
|
|
|
407
584
|
const strategy: DataExplorerStrategy = options.strategy ?? "recent";
|
|
408
|
-
const limit = options.limit ? Number.parseInt(options.limit, 10) : 10;
|
|
585
|
+
const limit = options.limit ? Number.parseInt(String(options.limit), 10) : 10;
|
|
409
586
|
|
|
410
587
|
// fixture fetch: 현재 환경 데이터를 fixture DB로 import합니다
|
|
411
588
|
const sourceDb = DB.getDB("r");
|
|
@@ -449,6 +626,12 @@ export async function fixtureExploreCommand(options: FixtureCommandOptions) {
|
|
|
449
626
|
|
|
450
627
|
let entityName = options.include;
|
|
451
628
|
|
|
629
|
+
if (!entityName && options.nonInteractive === true) {
|
|
630
|
+
throw Object.assign(new Error("Fixture explore selector is required."), {
|
|
631
|
+
code: "MISSING_ARGUMENT",
|
|
632
|
+
exitCode: 2,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
452
635
|
if (!entityName) {
|
|
453
636
|
const result = await prompts({
|
|
454
637
|
type: "select",
|
|
@@ -470,7 +653,7 @@ export async function fixtureExploreCommand(options: FixtureCommandOptions) {
|
|
|
470
653
|
}
|
|
471
654
|
|
|
472
655
|
const strategy: DataExplorerStrategy = options.strategy ?? "sample";
|
|
473
|
-
const limit = options.limit ? Number.parseInt(options.limit, 10) : 10;
|
|
656
|
+
const limit = options.limit ? Number.parseInt(String(options.limit), 10) : 10;
|
|
474
657
|
|
|
475
658
|
const db = DB.getDB("r");
|
|
476
659
|
const explorer = new DataExplorer(db, EntityManager);
|
package/src/bin/test-command.ts
CHANGED
|
@@ -40,6 +40,13 @@ export interface TestCommandDependencies {
|
|
|
40
40
|
loadConfig: (apiRootPath: string) => Promise<TestCommandConfig>;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export interface LegacyTestCommandInput {
|
|
44
|
+
files?: readonly string[];
|
|
45
|
+
pattern?: string;
|
|
46
|
+
traces?: boolean;
|
|
47
|
+
status?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
43
50
|
const testCommandDependencies: TestCommandDependencies = {
|
|
44
51
|
fetch: globalThis.fetch,
|
|
45
52
|
findApiRootPath,
|
|
@@ -74,10 +81,6 @@ export async function testCommand(
|
|
|
74
81
|
dependencies: TestCommandDependencies = testCommandDependencies,
|
|
75
82
|
): Promise<void> {
|
|
76
83
|
const args = process.argv.slice(3);
|
|
77
|
-
|
|
78
|
-
const config = await loadDevServerConfig(dependencies);
|
|
79
|
-
|
|
80
|
-
// process.argv 파싱: sonamu test [file...] --pattern "이름" --traces --status
|
|
81
84
|
const files: string[] = [];
|
|
82
85
|
let pattern: string | undefined;
|
|
83
86
|
let showTraces = false;
|
|
@@ -96,6 +99,26 @@ export async function testCommand(
|
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
|
|
102
|
+
return runTestCommand({ files, pattern, traces: showTraces, status: showStatus }, dependencies);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function testCommandWithInput(
|
|
106
|
+
input: LegacyTestCommandInput,
|
|
107
|
+
dependencies: TestCommandDependencies = testCommandDependencies,
|
|
108
|
+
): Promise<void> {
|
|
109
|
+
return runTestCommand(input, dependencies);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function runTestCommand(
|
|
113
|
+
input: LegacyTestCommandInput,
|
|
114
|
+
dependencies: TestCommandDependencies,
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
const config = await loadDevServerConfig(dependencies);
|
|
117
|
+
const files = [...(input.files ?? [])];
|
|
118
|
+
const pattern = input.pattern;
|
|
119
|
+
const showTraces = input.traces === true;
|
|
120
|
+
const showStatus = input.status === true;
|
|
121
|
+
|
|
99
122
|
if (showStatus) {
|
|
100
123
|
return testStatusCommand(config, dependencies.fetch);
|
|
101
124
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
|
|
7
|
+
import ts from "@typescript/typescript6";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
12
|
+
const configPath = path.resolve("tsconfig.json");
|
|
13
|
+
const parsedConfig = ts.parseConfigFileTextToJson(configPath, await readFile(configPath, "utf8"));
|
|
14
|
+
if (parsedConfig.error) {
|
|
15
|
+
throw new Error(ts.flattenDiagnosticMessageText(parsedConfig.error.messageText, "\n"));
|
|
16
|
+
}
|
|
17
|
+
const configResult = z
|
|
18
|
+
.object({ references: z.array(z.object({ path: z.string() })).optional() })
|
|
19
|
+
.safeParse(parsedConfig.config);
|
|
20
|
+
if (!configResult.success) {
|
|
21
|
+
throw new Error("tsconfig.json의 project references를 읽을 수 없습니다.", {
|
|
22
|
+
cause: configResult.error,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const config = configResult.data;
|
|
26
|
+
const references = config.references ?? [];
|
|
27
|
+
|
|
28
|
+
if (references.length === 0) {
|
|
29
|
+
throw new Error("tsconfig.json에 typecheck할 project reference가 없습니다.");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const tscPath = path.join(
|
|
33
|
+
path.dirname(requireFromHere.resolve("typescript/package.json")),
|
|
34
|
+
"bin/tsc",
|
|
35
|
+
);
|
|
36
|
+
for (const reference of references) {
|
|
37
|
+
// Web 앱은 선언을 배포하지 않으므로 참조별 타입 검사에서 선언 직렬화만 비활성화합니다.
|
|
38
|
+
await execFileAsync(
|
|
39
|
+
process.execPath,
|
|
40
|
+
[
|
|
41
|
+
tscPath,
|
|
42
|
+
"--noEmit",
|
|
43
|
+
"--composite",
|
|
44
|
+
"false",
|
|
45
|
+
"--declaration",
|
|
46
|
+
"false",
|
|
47
|
+
"--declarationMap",
|
|
48
|
+
"false",
|
|
49
|
+
"--emitDeclarationOnly",
|
|
50
|
+
"false",
|
|
51
|
+
"-p",
|
|
52
|
+
path.resolve(reference.path),
|
|
53
|
+
],
|
|
54
|
+
{ cwd: process.cwd() },
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -3,7 +3,7 @@ import fs from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
|
|
5
5
|
import { Workbook } from "@sheetkit/node";
|
|
6
|
-
import ts from "typescript";
|
|
6
|
+
import ts from "@typescript/typescript6";
|
|
7
7
|
|
|
8
8
|
import { Sonamu } from "../api/sonamu";
|
|
9
9
|
import { EntityManager } from "../entity/entity-manager";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
|
|
3
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
8
|
+
|
|
9
|
+
interface EntityManagerModule {
|
|
10
|
+
readonly EntityManager: {
|
|
11
|
+
isAutoloaded: boolean;
|
|
12
|
+
autoload(doSilent?: boolean, apiRootPath?: string): Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface SonamuModule {
|
|
17
|
+
readonly Sonamu: {
|
|
18
|
+
readonly apiRootPath: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const temporaryRoots: string[] = [];
|
|
23
|
+
|
|
24
|
+
async function importProduction<T>(relativePath: string): Promise<T> {
|
|
25
|
+
const loaded: unknown = await import(
|
|
26
|
+
/* @vite-ignore */ new URL(relativePath, import.meta.url).href
|
|
27
|
+
);
|
|
28
|
+
// SAFETY: 테스트는 명시한 singleton 공개 API만 호출합니다.
|
|
29
|
+
return loaded as T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
await Promise.all(temporaryRoots.splice(0).map((root) => rm(root, { recursive: true })));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("EntityManager 명시적 API 루트", () => {
|
|
37
|
+
it("Sonamu singleton이 초기화되지 않아도 전달한 apiRootPath로 autoload한다", async () => {
|
|
38
|
+
const [{ EntityManager }, { Sonamu }] = await Promise.all([
|
|
39
|
+
importProduction<EntityManagerModule>("../entity-manager.ts"),
|
|
40
|
+
importProduction<SonamuModule>("../../api/sonamu.ts"),
|
|
41
|
+
]);
|
|
42
|
+
const apiRootPath = await mkdtemp(path.join(os.tmpdir(), "sonamu-entity-autoload-"));
|
|
43
|
+
temporaryRoots.push(apiRootPath);
|
|
44
|
+
const previousAutoloaded = EntityManager.isAutoloaded;
|
|
45
|
+
EntityManager.isAutoloaded = false;
|
|
46
|
+
|
|
47
|
+
expect(() => Sonamu.apiRootPath).toThrow(/init/i);
|
|
48
|
+
try {
|
|
49
|
+
await expect(EntityManager.autoload(true, apiRootPath)).resolves.toBeUndefined();
|
|
50
|
+
expect(EntityManager.isAutoloaded).toBe(true);
|
|
51
|
+
} finally {
|
|
52
|
+
EntityManager.isAutoloaded = previousAutoloaded;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|