@typokit/cli 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/bin.d.ts +3 -0
  2. package/dist/bin.d.ts.map +1 -0
  3. package/dist/bin.js +13 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/commands/build.d.ts +42 -0
  6. package/dist/commands/build.d.ts.map +1 -0
  7. package/dist/commands/build.js +302 -0
  8. package/dist/commands/build.js.map +1 -0
  9. package/dist/commands/dev.d.ts +106 -0
  10. package/dist/commands/dev.d.ts.map +1 -0
  11. package/dist/commands/dev.js +536 -0
  12. package/dist/commands/dev.js.map +1 -0
  13. package/dist/commands/generate.d.ts +65 -0
  14. package/dist/commands/generate.d.ts.map +1 -0
  15. package/dist/commands/generate.js +430 -0
  16. package/dist/commands/generate.js.map +1 -0
  17. package/dist/commands/inspect.d.ts +26 -0
  18. package/dist/commands/inspect.d.ts.map +1 -0
  19. package/dist/commands/inspect.js +579 -0
  20. package/dist/commands/inspect.js.map +1 -0
  21. package/dist/commands/migrate.d.ts +70 -0
  22. package/dist/commands/migrate.d.ts.map +1 -0
  23. package/dist/commands/migrate.js +570 -0
  24. package/dist/commands/migrate.js.map +1 -0
  25. package/dist/commands/scaffold.d.ts +70 -0
  26. package/dist/commands/scaffold.d.ts.map +1 -0
  27. package/dist/commands/scaffold.js +483 -0
  28. package/dist/commands/scaffold.js.map +1 -0
  29. package/dist/commands/test.d.ts +56 -0
  30. package/dist/commands/test.d.ts.map +1 -0
  31. package/dist/commands/test.js +248 -0
  32. package/dist/commands/test.js.map +1 -0
  33. package/dist/config.d.ts +20 -0
  34. package/dist/config.d.ts.map +1 -0
  35. package/dist/config.js +69 -0
  36. package/dist/config.js.map +1 -0
  37. package/dist/index.d.ts +30 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +245 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/logger.d.ts +12 -0
  42. package/dist/logger.d.ts.map +1 -0
  43. package/dist/logger.js +33 -0
  44. package/dist/logger.js.map +1 -0
  45. package/package.json +33 -0
  46. package/src/bin.ts +22 -0
  47. package/src/commands/build.ts +433 -0
  48. package/src/commands/dev.ts +822 -0
  49. package/src/commands/generate.ts +640 -0
  50. package/src/commands/inspect.ts +885 -0
  51. package/src/commands/migrate.ts +800 -0
  52. package/src/commands/scaffold.ts +627 -0
  53. package/src/commands/test.ts +353 -0
  54. package/src/config.ts +93 -0
  55. package/src/dev.test.ts +285 -0
  56. package/src/env.d.ts +86 -0
  57. package/src/generate.test.ts +304 -0
  58. package/src/index.test.ts +217 -0
  59. package/src/index.ts +397 -0
  60. package/src/inspect.test.ts +411 -0
  61. package/src/logger.ts +49 -0
  62. package/src/migrate.test.ts +205 -0
  63. package/src/scaffold.test.ts +256 -0
  64. package/src/test.test.ts +230 -0
@@ -0,0 +1,217 @@
1
+ // @typokit/cli — Build Command Tests
2
+
3
+ import { describe, it, expect } from "@rstest/core";
4
+ import { parseArgs, createLogger, loadConfig } from "./index.js";
5
+ import type { BuildError } from "./commands/build.js";
6
+ import type { TypoKitConfig } from "./config.js";
7
+
8
+ // ─── parseArgs ───────────────────────────────────────────────
9
+
10
+ describe("parseArgs", () => {
11
+ it("extracts command from argv", () => {
12
+ const result = parseArgs(["node", "typokit", "build"]);
13
+ expect(result.command).toBe("build");
14
+ expect(Object.keys(result.flags).length).toBe(0);
15
+ });
16
+
17
+ it("extracts --verbose flag", () => {
18
+ const result = parseArgs(["node", "typokit", "build", "--verbose"]);
19
+ expect(result.command).toBe("build");
20
+ expect(result.flags["verbose"]).toBe(true);
21
+ });
22
+
23
+ it("extracts -v short flag", () => {
24
+ const result = parseArgs(["node", "typokit", "build", "-v"]);
25
+ expect(result.flags["v"]).toBe(true);
26
+ });
27
+
28
+ it("extracts --root with value", () => {
29
+ const result = parseArgs(["node", "typokit", "build", "--root", "/my/dir"]);
30
+ expect(result.flags["root"]).toBe("/my/dir");
31
+ });
32
+
33
+ it("returns empty command for no args", () => {
34
+ const result = parseArgs(["node", "typokit"]);
35
+ expect(result.command).toBe("");
36
+ });
37
+
38
+ it("collects positional args after command", () => {
39
+ const result = parseArgs(["node", "typokit", "build", "extra1", "extra2"]);
40
+ expect(result.positional).toEqual(["extra1", "extra2"]);
41
+ });
42
+ });
43
+
44
+ // ─── createLogger ────────────────────────────────────────────
45
+
46
+ describe("createLogger", () => {
47
+ it("creates a logger with all methods", () => {
48
+ const logger = createLogger({ verbose: false });
49
+ expect(typeof logger.info).toBe("function");
50
+ expect(typeof logger.success).toBe("function");
51
+ expect(typeof logger.warn).toBe("function");
52
+ expect(typeof logger.error).toBe("function");
53
+ expect(typeof logger.verbose).toBe("function");
54
+ expect(typeof logger.step).toBe("function");
55
+ });
56
+
57
+ it("verbose logger does not throw when verbose is false", () => {
58
+ const logger = createLogger({ verbose: false });
59
+ // Should not throw
60
+ logger.verbose("test message");
61
+ logger.info("test info");
62
+ });
63
+
64
+ it("verbose logger does not throw when verbose is true", () => {
65
+ const logger = createLogger({ verbose: true });
66
+ logger.verbose("test message");
67
+ });
68
+ });
69
+
70
+ // ─── loadConfig ──────────────────────────────────────────────
71
+
72
+ describe("loadConfig", () => {
73
+ it("returns default config for nonexistent directory", async () => {
74
+ const config = await loadConfig("/nonexistent/path/that/does/not/exist");
75
+ expect(config.compiler).toBe("tsc");
76
+ expect(config.outputDir).toBe(".typokit");
77
+ expect(config.distDir).toBe("dist");
78
+ expect(Array.isArray(config.typeFiles)).toBe(true);
79
+ expect(Array.isArray(config.routeFiles)).toBe(true);
80
+ });
81
+
82
+ it("returns default config when no typokit field in package.json", async () => {
83
+ // The monorepo root doesn't have a typokit field
84
+ const config = await loadConfig(".");
85
+ expect(config.compiler).toBe("tsc");
86
+ });
87
+ });
88
+
89
+ // ─── Build Error Types ──────────────────────────────────────
90
+
91
+ describe("BuildError", () => {
92
+ it("BuildError interface has expected shape", () => {
93
+ const err: BuildError = {
94
+ source: "tsc",
95
+ phase: "compile",
96
+ message: "Cannot find module",
97
+ file: "src/app.ts",
98
+ line: 42,
99
+ errorType: "TS2307",
100
+ };
101
+ expect(err.source).toBe("tsc");
102
+ expect(err.phase).toBe("compile");
103
+ expect(err.message).toBe("Cannot find module");
104
+ expect(err.file).toBe("src/app.ts");
105
+ expect(err.line).toBe(42);
106
+ expect(err.errorType).toBe("TS2307");
107
+ });
108
+
109
+ it("BuildError works without optional fields", () => {
110
+ const err: BuildError = {
111
+ source: "transform",
112
+ phase: "transform",
113
+ message: "Parse error",
114
+ };
115
+ expect(err.file).toBeUndefined();
116
+ expect(err.line).toBeUndefined();
117
+ });
118
+ });
119
+
120
+ // ─── Config Types ────────────────────────────────────────────
121
+
122
+ describe("TypoKitConfig", () => {
123
+ it("accepts partial config", () => {
124
+ const config: TypoKitConfig = {
125
+ compiler: "tsup",
126
+ outputDir: ".custom",
127
+ };
128
+ expect(config.compiler).toBe("tsup");
129
+ expect(config.outputDir).toBe(".custom");
130
+ });
131
+
132
+ it("accepts all compiler options", () => {
133
+ const configs: TypoKitConfig[] = [
134
+ { compiler: "tsc" },
135
+ { compiler: "tsup" },
136
+ { compiler: "swc" },
137
+ ];
138
+ expect(configs.length).toBe(3);
139
+ });
140
+ });
141
+
142
+ // ─── Integration: Build with mock project ────────────────────
143
+
144
+ describe("executeBuild integration", () => {
145
+ it("succeeds with no source files (empty project)", async () => {
146
+ const { executeBuild } = await import("./commands/build.js");
147
+
148
+ const logger = createLogger({ verbose: false });
149
+ const result = await executeBuild({
150
+ rootDir: "/nonexistent/empty/project",
151
+ config: {
152
+ typeFiles: [],
153
+ routeFiles: [],
154
+ outputDir: ".typokit",
155
+ distDir: "dist",
156
+ compiler: "tsc",
157
+ compilerArgs: ["--noEmit"],
158
+ },
159
+ logger,
160
+ verbose: false,
161
+ });
162
+
163
+ // Will fail at compile step since /nonexistent doesn't exist
164
+ // but it should handle the error gracefully without throwing
165
+ expect(typeof result.success).toBe("boolean");
166
+ expect(typeof result.duration).toBe("number");
167
+ expect(Array.isArray(result.errors)).toBe(true);
168
+ expect(Array.isArray(result.outputs)).toBe(true);
169
+ });
170
+
171
+ it("reports structured errors on compiler failure", async () => {
172
+ const { executeBuild } = await import("./commands/build.js");
173
+ const logger = createLogger({ verbose: false });
174
+
175
+ const result = await executeBuild({
176
+ rootDir: "/nonexistent/path",
177
+ config: {
178
+ typeFiles: [],
179
+ routeFiles: [],
180
+ outputDir: ".typokit",
181
+ distDir: "dist",
182
+ compiler: "tsc",
183
+ compilerArgs: ["--noEmit", "--project", "nonexistent.json"],
184
+ },
185
+ logger,
186
+ verbose: false,
187
+ });
188
+
189
+ expect(result.success).toBe(false);
190
+ expect(result.errors.length).toBeGreaterThan(0);
191
+ expect(result.duration).toBeGreaterThan(0);
192
+ });
193
+
194
+ it("returns BuildResult shape on failure", async () => {
195
+ const { executeBuild } = await import("./commands/build.js");
196
+ const logger = createLogger({ verbose: true });
197
+
198
+ const result = await executeBuild({
199
+ rootDir: "/nonexistent",
200
+ config: {
201
+ typeFiles: ["src/**/*.types.ts"],
202
+ routeFiles: [],
203
+ outputDir: ".typokit",
204
+ distDir: "dist",
205
+ compiler: "tsc",
206
+ compilerArgs: ["--noEmit"],
207
+ },
208
+ logger,
209
+ verbose: true,
210
+ });
211
+
212
+ expect(typeof result.success).toBe("boolean");
213
+ expect(typeof result.duration).toBe("number");
214
+ expect(Array.isArray(result.outputs)).toBe(true);
215
+ expect(Array.isArray(result.errors)).toBe(true);
216
+ });
217
+ });
package/src/index.ts ADDED
@@ -0,0 +1,397 @@
1
+ // @typokit/cli — Main entry point
2
+
3
+ export { createLogger } from "./logger.js";
4
+ export type { CliLogger } from "./logger.js";
5
+ export { loadConfig } from "./config.js";
6
+ export type { TypoKitConfig } from "./config.js";
7
+ export { executeBuild } from "./commands/build.js";
8
+ export type { BuildCommandOptions, BuildError } from "./commands/build.js";
9
+ export {
10
+ executeDev,
11
+ createDevState,
12
+ detectChangedFiles,
13
+ updateTrackedFiles,
14
+ buildDepGraph,
15
+ getAffectedOutputs,
16
+ isCacheValid,
17
+ updateCache,
18
+ } from "./commands/dev.js";
19
+ export type { DevCommandOptions, DevServerState } from "./commands/dev.js";
20
+ export {
21
+ executeInspect,
22
+ inspectRoutes,
23
+ inspectRoute,
24
+ inspectMiddleware,
25
+ inspectDependencies,
26
+ inspectSchema,
27
+ inspectErrors,
28
+ inspectPerformance,
29
+ inspectServer,
30
+ inspectBuildPipeline,
31
+ } from "./commands/inspect.js";
32
+ export type { InspectOptions, InspectResult } from "./commands/inspect.js";
33
+ export {
34
+ executeGenerate,
35
+ generateDb,
36
+ generateClient,
37
+ generateOpenapi,
38
+ generateTests,
39
+ generateClientCode,
40
+ } from "./commands/generate.js";
41
+ export type {
42
+ GenerateCommandOptions,
43
+ GenerateResult,
44
+ } from "./commands/generate.js";
45
+ export {
46
+ executeMigrate,
47
+ migrateGenerate,
48
+ migrateDiff,
49
+ migrateApply,
50
+ } from "./commands/migrate.js";
51
+ export type {
52
+ MigrateCommandOptions,
53
+ MigrateResult,
54
+ } from "./commands/migrate.js";
55
+ export {
56
+ executeScaffold,
57
+ scaffoldInit,
58
+ scaffoldRoute,
59
+ scaffoldService,
60
+ generateRouteContracts,
61
+ generateRouteHandlers,
62
+ generateRouteMiddleware,
63
+ generateService,
64
+ generatePackageJson,
65
+ generateTsconfig,
66
+ generateAppTs,
67
+ generateTypesTs,
68
+ toPascalCase,
69
+ toCamelCase,
70
+ } from "./commands/scaffold.js";
71
+ export type {
72
+ ScaffoldCommandOptions,
73
+ ScaffoldResult,
74
+ InitOptions,
75
+ } from "./commands/scaffold.js";
76
+ export {
77
+ executeTest,
78
+ detectTestRunner,
79
+ buildRunnerCommand,
80
+ schemasChanged,
81
+ } from "./commands/test.js";
82
+ export type {
83
+ TestCommandOptions,
84
+ TestResult,
85
+ TestRunner,
86
+ } from "./commands/test.js";
87
+
88
+ /** Parse CLI arguments into a structured object */
89
+ export function parseArgs(argv: string[]): {
90
+ command: string;
91
+ flags: Record<string, string | boolean>;
92
+ positional: string[];
93
+ } {
94
+ const flags: Record<string, string | boolean> = {};
95
+ const positional: string[] = [];
96
+ let command = "";
97
+
98
+ // Skip node and script path (argv[0], argv[1])
99
+ const args = argv.slice(2);
100
+
101
+ for (let i = 0; i < args.length; i++) {
102
+ const arg = args[i];
103
+ if (arg.startsWith("--")) {
104
+ const key = arg.slice(2);
105
+ const next = args[i + 1];
106
+ if (next && !next.startsWith("-")) {
107
+ flags[key] = next;
108
+ i++;
109
+ } else {
110
+ flags[key] = true;
111
+ }
112
+ } else if (arg.startsWith("-")) {
113
+ const key = arg.slice(1);
114
+ flags[key] = true;
115
+ } else if (!command) {
116
+ command = arg;
117
+ } else {
118
+ positional.push(arg);
119
+ }
120
+ }
121
+
122
+ return { command, flags, positional };
123
+ }
124
+
125
+ /**
126
+ * Run the CLI with the given argv.
127
+ * Returns the exit code (0 = success, 1 = failure).
128
+ */
129
+ export async function run(argv: string[]): Promise<number> {
130
+ const { resolve } = (await import(/* @vite-ignore */ "path")) as {
131
+ resolve: (...args: string[]) => string;
132
+ };
133
+
134
+ const { command, flags, positional } = parseArgs(argv);
135
+ const verbose = flags["verbose"] === true || flags["v"] === true;
136
+
137
+ const { createLogger: createLog } = await import("./logger.js");
138
+ const logger = createLog({ verbose });
139
+
140
+ if (!command || command === "help") {
141
+ logger.info("Usage: typokit <command> [options]");
142
+ logger.info("");
143
+ logger.info("Commands:");
144
+ logger.info(" build Run the full build pipeline");
145
+ logger.info(" dev Start dev server with watch mode");
146
+ logger.info(" generate:<sub> Generate specific artifacts");
147
+ logger.info(" generate:db Generate DB schema from types");
148
+ logger.info(" generate:client Generate API client from contracts");
149
+ logger.info(" generate:openapi Generate OpenAPI spec (--output <path>)");
150
+ logger.info(" generate:tests Regenerate contract tests");
151
+ logger.info(" migrate:<sub> Database migration management");
152
+ logger.info(
153
+ " migrate:generate Generate migration from type diff (--name <name>)",
154
+ );
155
+ logger.info(
156
+ " migrate:diff Show pending schema changes (--json for JSON)",
157
+ );
158
+ logger.info(
159
+ " migrate:apply Apply pending migrations (--force for destructive)",
160
+ );
161
+ logger.info(
162
+ " inspect <sub> Inspect framework state (routes, schema, etc.)",
163
+ );
164
+ logger.info(" test Run all tests (auto-detects runner)");
165
+ logger.info(" test:contracts Run generated contract tests only");
166
+ logger.info(" test:integration Run integration tests");
167
+ logger.info(
168
+ " init [name] Create a new TypoKit project from template",
169
+ );
170
+ logger.info(" add route <name> Scaffold a new route module");
171
+ logger.info(" add service <name> Scaffold a new service file");
172
+ logger.info("");
173
+ logger.info("Options:");
174
+ logger.info(" --verbose, -v Show detailed output");
175
+ logger.info(
176
+ " --root <dir> Project root directory (default: cwd)",
177
+ );
178
+ logger.info(
179
+ " --json Output as JSON (for inspect commands)",
180
+ );
181
+ logger.info(" --format json Alias for --json");
182
+ logger.info(
183
+ " --runner <runner> Override test runner (jest|vitest|rstest)",
184
+ );
185
+ logger.info(" --debug-port <port> Debug sidecar port (default: 9800)");
186
+ return 0;
187
+ }
188
+
189
+ if (command === "build") {
190
+ const g = globalThis as Record<string, unknown>;
191
+ const proc = g["process"] as { cwd(): string } | undefined;
192
+ const cwd = proc?.cwd() ?? ".";
193
+ const rootDir =
194
+ typeof flags["root"] === "string" ? resolve(flags["root"]) : cwd;
195
+
196
+ const { loadConfig: loadConf } = await import("./config.js");
197
+ const config = await loadConf(rootDir);
198
+
199
+ const { executeBuild: execBuild } = await import("./commands/build.js");
200
+ const result = await execBuild({
201
+ rootDir,
202
+ config,
203
+ logger,
204
+ verbose,
205
+ });
206
+
207
+ return result.success ? 0 : 1;
208
+ }
209
+
210
+ if (command === "dev") {
211
+ const g = globalThis as Record<string, unknown>;
212
+ const proc = g["process"] as { cwd(): string } | undefined;
213
+ const cwd = proc?.cwd() ?? ".";
214
+ const rootDir =
215
+ typeof flags["root"] === "string" ? resolve(flags["root"]) : cwd;
216
+
217
+ const debugPort =
218
+ typeof flags["debug-port"] === "string"
219
+ ? parseInt(flags["debug-port"], 10)
220
+ : 9800;
221
+
222
+ const { loadConfig: loadConf } = await import("./config.js");
223
+ const config = await loadConf(rootDir);
224
+
225
+ const { executeDev: execDev } = await import("./commands/dev.js");
226
+ const { state } = await execDev({
227
+ rootDir,
228
+ config,
229
+ logger,
230
+ verbose,
231
+ debugPort,
232
+ });
233
+
234
+ // Keep running until stopped (dev mode is long-running)
235
+ // The executeDev function sets up watchers and signal handlers
236
+ // We return 0 when it stops
237
+ if (!state.running) {
238
+ return 1;
239
+ }
240
+
241
+ // In a real scenario, we'd await a signal here
242
+ // For now, return 0 to indicate successful start
243
+ return 0;
244
+ }
245
+
246
+ if (command.startsWith("generate:")) {
247
+ const g = globalThis as Record<string, unknown>;
248
+ const proc = g["process"] as { cwd(): string } | undefined;
249
+ const cwd = proc?.cwd() ?? ".";
250
+ const rootDir =
251
+ typeof flags["root"] === "string" ? resolve(flags["root"]) : cwd;
252
+
253
+ const { loadConfig: loadConf } = await import("./config.js");
254
+ const config = await loadConf(rootDir);
255
+
256
+ const subcommand = command.slice("generate:".length);
257
+ const { executeGenerate: execGenerate } =
258
+ await import("./commands/generate.js");
259
+ const result = await execGenerate({
260
+ rootDir,
261
+ config,
262
+ logger,
263
+ subcommand,
264
+ flags,
265
+ verbose,
266
+ });
267
+
268
+ return result.success ? 0 : 1;
269
+ }
270
+
271
+ if (command.startsWith("migrate:")) {
272
+ const g = globalThis as Record<string, unknown>;
273
+ const proc = g["process"] as { cwd(): string } | undefined;
274
+ const cwd = proc?.cwd() ?? ".";
275
+ const rootDir =
276
+ typeof flags["root"] === "string" ? resolve(flags["root"]) : cwd;
277
+
278
+ const { loadConfig: loadConf } = await import("./config.js");
279
+ const config = await loadConf(rootDir);
280
+
281
+ const subcommand = command.slice("migrate:".length);
282
+ const { executeMigrate: execMigrate } =
283
+ await import("./commands/migrate.js");
284
+ const result = await execMigrate({
285
+ rootDir,
286
+ config,
287
+ logger,
288
+ subcommand,
289
+ flags,
290
+ verbose,
291
+ });
292
+
293
+ return result.success ? 0 : 1;
294
+ }
295
+
296
+ if (command === "inspect") {
297
+ const g = globalThis as Record<string, unknown>;
298
+ const proc = g["process"] as { cwd(): string } | undefined;
299
+ const cwd = proc?.cwd() ?? ".";
300
+ const rootDir =
301
+ typeof flags["root"] === "string" ? resolve(flags["root"]) : cwd;
302
+
303
+ const { loadConfig: loadConf } = await import("./config.js");
304
+ const config = await loadConf(rootDir);
305
+
306
+ const { executeInspect: execInspect } =
307
+ await import("./commands/inspect.js");
308
+ const subcommand = positional[0] ?? "";
309
+ const subPositional = positional.slice(1);
310
+
311
+ const result = await execInspect({
312
+ rootDir,
313
+ config,
314
+ logger,
315
+ subcommand,
316
+ positional: subPositional,
317
+ flags,
318
+ });
319
+
320
+ return result.success ? 0 : 1;
321
+ }
322
+
323
+ if (command === "init") {
324
+ const g = globalThis as Record<string, unknown>;
325
+ const proc = g["process"] as { cwd(): string } | undefined;
326
+ const cwd = proc?.cwd() ?? ".";
327
+ const rootDir =
328
+ typeof flags["root"] === "string" ? resolve(flags["root"]) : cwd;
329
+
330
+ const { executeScaffold: execScaffold } =
331
+ await import("./commands/scaffold.js");
332
+ const result = await execScaffold({
333
+ rootDir,
334
+ logger,
335
+ subcommand: "init",
336
+ positional,
337
+ flags,
338
+ verbose,
339
+ });
340
+
341
+ return result.success ? 0 : 1;
342
+ }
343
+
344
+ if (command === "add") {
345
+ const g = globalThis as Record<string, unknown>;
346
+ const proc = g["process"] as { cwd(): string } | undefined;
347
+ const cwd = proc?.cwd() ?? ".";
348
+ const rootDir =
349
+ typeof flags["root"] === "string" ? resolve(flags["root"]) : cwd;
350
+
351
+ const subcommand = positional[0] ?? "";
352
+ const subPositional = positional.slice(1);
353
+
354
+ const { executeScaffold: execScaffold } =
355
+ await import("./commands/scaffold.js");
356
+ const result = await execScaffold({
357
+ rootDir,
358
+ logger,
359
+ subcommand,
360
+ positional: subPositional,
361
+ flags,
362
+ verbose,
363
+ });
364
+
365
+ return result.success ? 0 : 1;
366
+ }
367
+
368
+ if (command === "test" || command.startsWith("test:")) {
369
+ const g = globalThis as Record<string, unknown>;
370
+ const proc = g["process"] as { cwd(): string } | undefined;
371
+ const cwd = proc?.cwd() ?? ".";
372
+ const rootDir =
373
+ typeof flags["root"] === "string" ? resolve(flags["root"]) : cwd;
374
+
375
+ const { loadConfig: loadConf } = await import("./config.js");
376
+ const config = await loadConf(rootDir);
377
+
378
+ const subcommand =
379
+ command === "test" ? "all" : command.slice("test:".length);
380
+
381
+ const { executeTest: execTest } = await import("./commands/test.js");
382
+ const result = await execTest({
383
+ rootDir,
384
+ config,
385
+ logger,
386
+ subcommand,
387
+ flags,
388
+ verbose,
389
+ });
390
+
391
+ return result.success ? 0 : 1;
392
+ }
393
+
394
+ logger.error(`Unknown command: ${command}`);
395
+ logger.info("Run 'typokit help' for usage information.");
396
+ return 1;
397
+ }