@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,627 @@
1
+ // @typokit/cli — Scaffold Commands (init, add route, add service)
2
+
3
+ import type { CliLogger } from "../logger.js";
4
+
5
+ export interface ScaffoldCommandOptions {
6
+ /** Project root directory */
7
+ rootDir: string;
8
+ /** Logger instance */
9
+ logger: CliLogger;
10
+ /** Scaffold subcommand: "init", "route", "service" */
11
+ subcommand: string;
12
+ /** Positional arguments (e.g., route/service name) */
13
+ positional: string[];
14
+ /** CLI flags */
15
+ flags: Record<string, string | boolean>;
16
+ /** Whether verbose mode is enabled */
17
+ verbose: boolean;
18
+ }
19
+
20
+ export interface ScaffoldResult {
21
+ /** Whether the command succeeded */
22
+ success: boolean;
23
+ /** Files created */
24
+ filesCreated: string[];
25
+ /** Duration in milliseconds */
26
+ duration: number;
27
+ /** Errors encountered */
28
+ errors: string[];
29
+ }
30
+
31
+ export interface InitOptions {
32
+ /** Project name */
33
+ name: string;
34
+ /** Server adapter to use */
35
+ server: "native" | "fastify" | "hono" | "express";
36
+ /** Database adapter to use */
37
+ db: "drizzle" | "kysely" | "prisma" | "raw" | "none";
38
+ }
39
+
40
+ /** Generate a route contracts.ts template */
41
+ export function generateRouteContracts(name: string): string {
42
+ const pascalName = toPascalCase(name);
43
+ return `// Route contracts for ${name}
44
+ import type { RouteContract } from "@typokit/types";
45
+
46
+ /** ${pascalName} item type */
47
+ export interface ${pascalName} {
48
+ id: string;
49
+ createdAt: string;
50
+ updatedAt: string;
51
+ }
52
+
53
+ /** Create ${pascalName} request body */
54
+ export interface Create${pascalName}Body {
55
+ // TODO: Define create fields
56
+ }
57
+
58
+ /** Update ${pascalName} request body */
59
+ export interface Update${pascalName}Body {
60
+ // TODO: Define update fields
61
+ }
62
+
63
+ /** Route contracts for /${name} */
64
+ export interface ${pascalName}Routes {
65
+ "GET /${name}": RouteContract<Record<string, never>, { limit?: number; offset?: number }, never, ${pascalName}[]>;
66
+ "GET /${name}/:id": RouteContract<{ id: string }, never, never, ${pascalName}>;
67
+ "POST /${name}": RouteContract<Record<string, never>, never, Create${pascalName}Body, ${pascalName}>;
68
+ "PUT /${name}/:id": RouteContract<{ id: string }, never, Update${pascalName}Body, ${pascalName}>;
69
+ "DELETE /${name}/:id": RouteContract<{ id: string }, never, never, void>;
70
+ }
71
+ `;
72
+ }
73
+
74
+ /** Generate a route handlers.ts template */
75
+ export function generateRouteHandlers(name: string): string {
76
+ const pascalName = toPascalCase(name);
77
+ return `// Route handlers for ${name}
78
+ import type { RouteHandler, RequestContext } from "@typokit/types";
79
+ import type { ${pascalName}, Create${pascalName}Body, Update${pascalName}Body } from "./contracts.ts";
80
+
81
+ /** List all ${name} */
82
+ export const list${pascalName}: RouteHandler = async (ctx: RequestContext) => {
83
+ const _query = ctx.query as { limit?: number; offset?: number };
84
+ // TODO: Implement list logic
85
+ return { status: 200, body: [] as ${pascalName}[] };
86
+ };
87
+
88
+ /** Get a single ${name} by ID */
89
+ export const get${pascalName}: RouteHandler = async (ctx: RequestContext) => {
90
+ const { id } = ctx.params as { id: string };
91
+ // TODO: Implement get logic
92
+ return { status: 200, body: { id } as ${pascalName} };
93
+ };
94
+
95
+ /** Create a new ${name} */
96
+ export const create${pascalName}: RouteHandler = async (ctx: RequestContext) => {
97
+ const _body = ctx.body as Create${pascalName}Body;
98
+ // TODO: Implement create logic
99
+ return { status: 201, body: {} as ${pascalName} };
100
+ };
101
+
102
+ /** Update an existing ${name} */
103
+ export const update${pascalName}: RouteHandler = async (ctx: RequestContext) => {
104
+ const { id } = ctx.params as { id: string };
105
+ const _body = ctx.body as Update${pascalName}Body;
106
+ // TODO: Implement update logic
107
+ return { status: 200, body: { id } as ${pascalName} };
108
+ };
109
+
110
+ /** Delete a ${name} */
111
+ export const delete${pascalName}: RouteHandler = async (ctx: RequestContext) => {
112
+ const { id } = ctx.params as { id: string };
113
+ // TODO: Implement delete logic
114
+ void id;
115
+ return { status: 204, body: undefined };
116
+ };
117
+
118
+ /** Default export: all handlers for registration in app.ts */
119
+ export default {
120
+ "GET /${name}": list${pascalName},
121
+ "GET /${name}/:id": get${pascalName},
122
+ "POST /${name}": create${pascalName},
123
+ "PUT /${name}/:id": update${pascalName},
124
+ "DELETE /${name}/:id": delete${pascalName},
125
+ };
126
+ `;
127
+ }
128
+
129
+ /** Generate a route middleware.ts template */
130
+ export function generateRouteMiddleware(name: string): string {
131
+ return `// Route-specific middleware for ${name}
132
+ import type { MiddlewareFn } from "@typokit/types";
133
+
134
+ /**
135
+ * Example middleware for ${name} routes.
136
+ * Add route-specific middleware here (e.g., authorization, rate limiting).
137
+ */
138
+ export const ${toCamelCase(name)}Middleware: MiddlewareFn = async (ctx, next) => {
139
+ // TODO: Implement route-specific middleware
140
+ return next(ctx);
141
+ };
142
+ `;
143
+ }
144
+
145
+ /** Generate a service template */
146
+ export function generateService(name: string): string {
147
+ const pascalName = toPascalCase(name);
148
+ return `// ${pascalName} service — business logic layer
149
+
150
+ /**
151
+ * ${pascalName}Service handles business logic for ${name}.
152
+ * Keep handlers thin — put complex logic here.
153
+ */
154
+ export class ${pascalName}Service {
155
+ /**
156
+ * Example method. Replace with actual business logic.
157
+ */
158
+ async execute(): Promise<void> {
159
+ // TODO: Implement ${name} business logic
160
+ }
161
+ }
162
+
163
+ /** Singleton instance for convenience */
164
+ export const ${toCamelCase(name)}Service = new ${pascalName}Service();
165
+ `;
166
+ }
167
+
168
+ /** Generate package.json for a new project */
169
+ export function generatePackageJson(options: InitOptions): string {
170
+ const deps: Record<string, string> = {
171
+ "@typokit/core": "^0.1.0",
172
+ "@typokit/types": "^0.1.0",
173
+ "@typokit/errors": "^0.1.0",
174
+ "@typokit/cli": "^0.1.0",
175
+ };
176
+
177
+ if (options.server !== "native") {
178
+ deps[`@typokit/server-${options.server}`] = "^0.1.0";
179
+ } else {
180
+ deps["@typokit/server-native"] = "^0.1.0";
181
+ }
182
+
183
+ if (options.db !== "none") {
184
+ deps[`@typokit/db-${options.db}`] = "^0.1.0";
185
+ }
186
+
187
+ return (
188
+ JSON.stringify(
189
+ {
190
+ name: options.name,
191
+ version: "0.1.0",
192
+ type: "module",
193
+ private: true,
194
+ scripts: {
195
+ build: "typokit build",
196
+ dev: "typokit dev",
197
+ test: "typokit test",
198
+ "generate:db": "typokit generate:db",
199
+ "generate:client": "typokit generate:client",
200
+ typecheck: "tsc --noEmit",
201
+ },
202
+ dependencies: deps,
203
+ devDependencies: {
204
+ typescript: "^5.7.0",
205
+ "@typokit/transform-native": "^0.1.0",
206
+ },
207
+ },
208
+ null,
209
+ 2,
210
+ ) + "\n"
211
+ );
212
+ }
213
+
214
+ /** Generate tsconfig.json for a new project */
215
+ export function generateTsconfig(): string {
216
+ return (
217
+ JSON.stringify(
218
+ {
219
+ compilerOptions: {
220
+ target: "ES2022",
221
+ module: "NodeNext",
222
+ moduleResolution: "NodeNext",
223
+ allowImportingTsExtensions: true,
224
+ rewriteRelativeImportExtensions: true,
225
+ strict: true,
226
+ esModuleInterop: true,
227
+ skipLibCheck: true,
228
+ outDir: "dist",
229
+ rootDir: "src",
230
+ declaration: true,
231
+ declarationMap: true,
232
+ sourceMap: true,
233
+ },
234
+ include: ["src"],
235
+ },
236
+ null,
237
+ 2,
238
+ ) + "\n"
239
+ );
240
+ }
241
+
242
+ /** Generate the main app.ts for a new project */
243
+ export function generateAppTs(options: InitOptions): string {
244
+ const serverImport =
245
+ options.server === "native"
246
+ ? `import { nativeServer } from "@typokit/server-native";`
247
+ : `import { ${options.server}Server } from "@typokit/server-${options.server}";`;
248
+
249
+ const serverValue =
250
+ options.server === "native"
251
+ ? "nativeServer()"
252
+ : `${options.server}Server()`;
253
+
254
+ return `// Application entry point — explicit route registration
255
+ import { createApp } from "@typokit/core";
256
+ ${serverImport}
257
+
258
+ export const app = createApp({
259
+ server: ${serverValue},
260
+ middleware: [],
261
+ routes: [
262
+ // Register route modules here:
263
+ // { prefix: "/users", handlers: usersHandlers },
264
+ ],
265
+ });
266
+
267
+ // Start the server
268
+ app.listen({ port: 3000 }).then(() => {
269
+ console.log("Server running on http://localhost:3000");
270
+ });
271
+ `;
272
+ }
273
+
274
+ /** Generate the types.ts seed file for a new project */
275
+ export function generateTypesTs(): string {
276
+ return `// Schema type definitions — the single source of truth
277
+ // TypoKit generates validation, DB schema, OpenAPI, and client types from these interfaces.
278
+ // See: https://github.com/typokit/typokit#schema-types
279
+
280
+ /** @table */
281
+ export interface Example {
282
+ /** @id @generated */
283
+ id: string;
284
+ name: string;
285
+ createdAt: string;
286
+ updatedAt: string;
287
+ }
288
+ `;
289
+ }
290
+
291
+ /** Convert kebab-case or snake_case name to PascalCase */
292
+ export function toPascalCase(name: string): string {
293
+ return name
294
+ .split(/[-_]/)
295
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
296
+ .join("");
297
+ }
298
+
299
+ /** Convert kebab-case or snake_case name to camelCase */
300
+ export function toCamelCase(name: string): string {
301
+ const pascal = toPascalCase(name);
302
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
303
+ }
304
+
305
+ /**
306
+ * Execute `typokit init` — create a new project from template.
307
+ */
308
+ export async function scaffoldInit(
309
+ rootDir: string,
310
+ options: InitOptions,
311
+ logger: CliLogger,
312
+ ): Promise<ScaffoldResult> {
313
+ const start = Date.now();
314
+ const { join } = (await import(/* @vite-ignore */ "path")) as {
315
+ join: (...args: string[]) => string;
316
+ };
317
+ const { mkdirSync, writeFileSync, existsSync } = (await import(
318
+ /* @vite-ignore */ "fs"
319
+ )) as {
320
+ mkdirSync: (p: string, o?: { recursive?: boolean }) => void;
321
+ writeFileSync: (p: string, data: string) => void;
322
+ existsSync: (p: string) => boolean;
323
+ };
324
+
325
+ const projectDir = join(rootDir, options.name);
326
+ const filesCreated: string[] = [];
327
+ const errors: string[] = [];
328
+
329
+ // Check if directory already exists with content
330
+ if (existsSync(projectDir)) {
331
+ errors.push(`Directory "${options.name}" already exists`);
332
+ return {
333
+ success: false,
334
+ filesCreated,
335
+ duration: Date.now() - start,
336
+ errors,
337
+ };
338
+ }
339
+
340
+ try {
341
+ // Create directory structure per Section 4.4
342
+ const dirs = [
343
+ projectDir,
344
+ join(projectDir, "src"),
345
+ join(projectDir, "src", "routes"),
346
+ join(projectDir, "src", "middleware"),
347
+ join(projectDir, "src", "services"),
348
+ ];
349
+
350
+ for (const dir of dirs) {
351
+ mkdirSync(dir, { recursive: true });
352
+ }
353
+
354
+ // Write package.json
355
+ const pkgPath = join(projectDir, "package.json");
356
+ writeFileSync(pkgPath, generatePackageJson(options));
357
+ filesCreated.push(pkgPath);
358
+ logger.info(`Created ${pkgPath}`);
359
+
360
+ // Write tsconfig.json
361
+ const tscPath = join(projectDir, "tsconfig.json");
362
+ writeFileSync(tscPath, generateTsconfig());
363
+ filesCreated.push(tscPath);
364
+ logger.info(`Created ${tscPath}`);
365
+
366
+ // Write src/app.ts
367
+ const appPath = join(projectDir, "src", "app.ts");
368
+ writeFileSync(appPath, generateAppTs(options));
369
+ filesCreated.push(appPath);
370
+ logger.info(`Created ${appPath}`);
371
+
372
+ // Write src/types.ts
373
+ const typesPath = join(projectDir, "src", "types.ts");
374
+ writeFileSync(typesPath, generateTypesTs());
375
+ filesCreated.push(typesPath);
376
+ logger.info(`Created ${typesPath}`);
377
+
378
+ logger.info(`\nProject "${options.name}" created successfully!`);
379
+ logger.info(`\n cd ${options.name}`);
380
+ logger.info(" npm install");
381
+ logger.info(" typokit dev\n");
382
+
383
+ return {
384
+ success: true,
385
+ filesCreated,
386
+ duration: Date.now() - start,
387
+ errors,
388
+ };
389
+ } catch (err: unknown) {
390
+ const msg = err instanceof Error ? err.message : String(err);
391
+ errors.push(msg);
392
+ return {
393
+ success: false,
394
+ filesCreated,
395
+ duration: Date.now() - start,
396
+ errors,
397
+ };
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Execute `typokit add route <name>` — scaffold a route module.
403
+ */
404
+ export async function scaffoldRoute(
405
+ rootDir: string,
406
+ name: string,
407
+ logger: CliLogger,
408
+ ): Promise<ScaffoldResult> {
409
+ const start = Date.now();
410
+ const { join } = (await import(/* @vite-ignore */ "path")) as {
411
+ join: (...args: string[]) => string;
412
+ };
413
+ const { mkdirSync, writeFileSync, existsSync } = (await import(
414
+ /* @vite-ignore */ "fs"
415
+ )) as {
416
+ mkdirSync: (p: string, o?: { recursive?: boolean }) => void;
417
+ writeFileSync: (p: string, data: string) => void;
418
+ existsSync: (p: string) => boolean;
419
+ };
420
+
421
+ const filesCreated: string[] = [];
422
+ const errors: string[] = [];
423
+
424
+ if (!name) {
425
+ errors.push("Route name is required. Usage: typokit add route <name>");
426
+ return {
427
+ success: false,
428
+ filesCreated,
429
+ duration: Date.now() - start,
430
+ errors,
431
+ };
432
+ }
433
+
434
+ const routeDir = join(rootDir, "src", "routes", name);
435
+
436
+ if (existsSync(routeDir)) {
437
+ errors.push(
438
+ `Route directory "${name}" already exists at src/routes/${name}`,
439
+ );
440
+ return {
441
+ success: false,
442
+ filesCreated,
443
+ duration: Date.now() - start,
444
+ errors,
445
+ };
446
+ }
447
+
448
+ try {
449
+ mkdirSync(routeDir, { recursive: true });
450
+
451
+ // contracts.ts — route type contracts
452
+ const contractsPath = join(routeDir, "contracts.ts");
453
+ writeFileSync(contractsPath, generateRouteContracts(name));
454
+ filesCreated.push(contractsPath);
455
+ logger.info(`Created ${contractsPath}`);
456
+
457
+ // handlers.ts — handler implementations
458
+ const handlersPath = join(routeDir, "handlers.ts");
459
+ writeFileSync(handlersPath, generateRouteHandlers(name));
460
+ filesCreated.push(handlersPath);
461
+ logger.info(`Created ${handlersPath}`);
462
+
463
+ // middleware.ts — route-specific middleware
464
+ const middlewarePath = join(routeDir, "middleware.ts");
465
+ writeFileSync(middlewarePath, generateRouteMiddleware(name));
466
+ filesCreated.push(middlewarePath);
467
+ logger.info(`Created ${middlewarePath}`);
468
+
469
+ logger.info(`\nRoute "${name}" scaffolded at src/routes/${name}/`);
470
+ logger.info(" Don't forget to register it in src/app.ts!\n");
471
+
472
+ return {
473
+ success: true,
474
+ filesCreated,
475
+ duration: Date.now() - start,
476
+ errors,
477
+ };
478
+ } catch (err: unknown) {
479
+ const msg = err instanceof Error ? err.message : String(err);
480
+ errors.push(msg);
481
+ return {
482
+ success: false,
483
+ filesCreated,
484
+ duration: Date.now() - start,
485
+ errors,
486
+ };
487
+ }
488
+ }
489
+
490
+ /**
491
+ * Execute `typokit add service <name>` — scaffold a service file.
492
+ */
493
+ export async function scaffoldService(
494
+ rootDir: string,
495
+ name: string,
496
+ logger: CliLogger,
497
+ ): Promise<ScaffoldResult> {
498
+ const start = Date.now();
499
+ const { join } = (await import(/* @vite-ignore */ "path")) as {
500
+ join: (...args: string[]) => string;
501
+ };
502
+ const { mkdirSync, writeFileSync, existsSync } = (await import(
503
+ /* @vite-ignore */ "fs"
504
+ )) as {
505
+ mkdirSync: (p: string, o?: { recursive?: boolean }) => void;
506
+ writeFileSync: (p: string, data: string) => void;
507
+ existsSync: (p: string) => boolean;
508
+ };
509
+
510
+ const filesCreated: string[] = [];
511
+ const errors: string[] = [];
512
+
513
+ if (!name) {
514
+ errors.push("Service name is required. Usage: typokit add service <name>");
515
+ return {
516
+ success: false,
517
+ filesCreated,
518
+ duration: Date.now() - start,
519
+ errors,
520
+ };
521
+ }
522
+
523
+ const servicesDir = join(rootDir, "src", "services");
524
+ const servicePath = join(servicesDir, `${name}.service.ts`);
525
+
526
+ if (existsSync(servicePath)) {
527
+ errors.push(
528
+ `Service "${name}" already exists at src/services/${name}.service.ts`,
529
+ );
530
+ return {
531
+ success: false,
532
+ filesCreated,
533
+ duration: Date.now() - start,
534
+ errors,
535
+ };
536
+ }
537
+
538
+ try {
539
+ mkdirSync(servicesDir, { recursive: true });
540
+
541
+ writeFileSync(servicePath, generateService(name));
542
+ filesCreated.push(servicePath);
543
+ logger.info(`Created ${servicePath}`);
544
+
545
+ logger.info(
546
+ `\nService "${name}" scaffolded at src/services/${name}.service.ts\n`,
547
+ );
548
+
549
+ return {
550
+ success: true,
551
+ filesCreated,
552
+ duration: Date.now() - start,
553
+ errors,
554
+ };
555
+ } catch (err: unknown) {
556
+ const msg = err instanceof Error ? err.message : String(err);
557
+ errors.push(msg);
558
+ return {
559
+ success: false,
560
+ filesCreated,
561
+ duration: Date.now() - start,
562
+ errors,
563
+ };
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Execute scaffold commands dispatcher.
569
+ */
570
+ export async function executeScaffold(
571
+ options: ScaffoldCommandOptions,
572
+ ): Promise<ScaffoldResult> {
573
+ const { rootDir, logger, subcommand, positional, flags } = options;
574
+
575
+ if (subcommand === "init") {
576
+ const name =
577
+ positional[0] ??
578
+ (typeof flags["name"] === "string" ? flags["name"] : "my-app");
579
+ const server = parseServerFlag(flags["server"]);
580
+ const db = parseDbFlag(flags["db"]);
581
+
582
+ return scaffoldInit(rootDir, { name, server, db }, logger);
583
+ }
584
+
585
+ if (subcommand === "route") {
586
+ const name = positional[0] ?? "";
587
+ return scaffoldRoute(rootDir, name, logger);
588
+ }
589
+
590
+ if (subcommand === "service") {
591
+ const name = positional[0] ?? "";
592
+ return scaffoldService(rootDir, name, logger);
593
+ }
594
+
595
+ return {
596
+ success: false,
597
+ filesCreated: [],
598
+ duration: 0,
599
+ errors: [
600
+ `Unknown scaffold subcommand: "${subcommand}". Use: init, route, service`,
601
+ ],
602
+ };
603
+ }
604
+
605
+ /** Parse server adapter flag */
606
+ function parseServerFlag(
607
+ value: string | boolean | undefined,
608
+ ): InitOptions["server"] {
609
+ if (typeof value === "string") {
610
+ const valid = ["native", "fastify", "hono", "express"] as const;
611
+ if (valid.includes(value as (typeof valid)[number])) {
612
+ return value as InitOptions["server"];
613
+ }
614
+ }
615
+ return "native";
616
+ }
617
+
618
+ /** Parse database adapter flag */
619
+ function parseDbFlag(value: string | boolean | undefined): InitOptions["db"] {
620
+ if (typeof value === "string") {
621
+ const valid = ["drizzle", "kysely", "prisma", "raw", "none"] as const;
622
+ if (valid.includes(value as (typeof valid)[number])) {
623
+ return value as InitOptions["db"];
624
+ }
625
+ }
626
+ return "none";
627
+ }