@sculptor/cli 0.1.14 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/scaffold.js CHANGED
@@ -1,821 +1,2 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { ensureDir, writeTextFile } from "./fs.js";
4
- const toPascalCase = (value) => value
5
- .split(/[^a-zA-Z0-9]+/)
6
- .filter(Boolean)
7
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
8
- .join("");
9
- const toCamelCase = (value) => {
10
- const pascal = toPascalCase(value);
11
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
12
- };
13
- const toKebabCase = (value) => value
14
- .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
15
- .replace(/[^a-zA-Z0-9]+/g, "-")
16
- .replace(/^-+|-+$/g, "")
17
- .toLowerCase();
18
- const normalizeRelativePath = (value) => value.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
19
- const resolveFileStem = (name, outputDir) => {
20
- if (name) {
21
- return name;
22
- }
23
- const trimmedDir = normalizeRelativePath(outputDir);
24
- const parts = trimmedDir.split("/").filter(Boolean);
25
- const inferred = parts[parts.length - 1];
26
- return inferred ?? "index";
27
- };
28
- const controllerFileName = (name) => `${name}.controller.ts`;
29
- const serviceFileName = (name) => `${name}.service.ts`;
30
- const moduleFileName = (name) => `${name}.module.ts`;
31
- const middlewareFileName = (name) => `${name}.middleware.ts`;
32
- const routeFileName = (name) => `${name}.routes.ts`;
33
- const typeFileName = (name, variant) => {
34
- const suffix = variant === "type" ? "type" : variant;
35
- return `${name}.${suffix}.ts`;
36
- };
37
- const specFileName = (name, suffix) => `${name}.${suffix}.spec.ts`;
38
- const specImportPath = (sourcePath) => normalizeRelativePath(path.posix.relative("src/tests", sourcePath)).replace(/\.ts$/, ".js");
39
- const resolveGeneratorOutputDir = (kind, outputDir) => {
40
- if (outputDir) {
41
- return normalizeRelativePath(outputDir);
42
- }
43
- switch (kind) {
44
- case "controller":
45
- return "src/app/controllers";
46
- case "service":
47
- return "src/app/services";
48
- case "module":
49
- return "src/app/modules";
50
- case "middleware":
51
- return "src/app/middlewares";
52
- case "type":
53
- return "src/app/types";
54
- case "route":
55
- return "src/app/routes";
56
- }
57
- };
58
- const devScriptFor = (devServer) => devServer === "tsx"
59
- ? "tsx src/main.ts"
60
- : "nodemon --watch src --ext ts --exec tsx src/main.ts";
61
- const devDependenciesFor = (devServer) => devServer === "tsx"
62
- ? ""
63
- : ` "nodemon": "^3.1.9",\n`;
64
- const rootPackageJsonTemplate = (appName, version, devServer) => `{
65
- "name": "${toKebabCase(appName)}",
66
- "version": "${version}",
67
- "private": true,
68
- "type": "module",
69
- "scripts": {
70
- "start": "tsx src/main.ts",
71
- "dev": "${devScriptFor(devServer)}",
72
- "build": "tsc -p tsconfig.json",
73
- "lint": "eslint . --ext .ts",
74
- "test": "sc test"
75
- },
76
- "dependencies": {
77
- "express": "^4.21.2",
78
- "reflect-metadata": "^0.2.2"
79
- },
80
- "devDependencies": {
81
- "@types/express": "^4.17.23",
82
- "@types/node": "^22.15.3",
83
- "@types/supertest": "^6.0.3",
84
- "@typescript-eslint/eslint-plugin": "^8.30.1",
85
- "@typescript-eslint/parser": "^8.30.1",
86
- "eslint": "^9.20.0",
87
- ${devDependenciesFor(devServer)}
88
- "supertest": "^7.1.0",
89
- "tsx": "^4.19.4",
90
- "typescript": "^5.8.3",
91
- "vitest": "^2.1.8"
92
- }
93
- }`;
94
- const rootReadmeTemplate = (appName) => `# ${appName}
95
-
96
- ## Scripts
97
-
98
- - \`npm run start\` - start the app
99
- - \`npm run dev\` - start the app in development mode
100
- - \`npm run build\` - compile the app
101
- - \`npm run lint\` - lint the source
102
- - \`npm run test\` - run the test suite through \`sc test\`
103
-
104
- ## Logging
105
-
106
- The scaffold includes \`@sculptor/paws\` and turns logging on by default.
107
-
108
- - \`logging.enabled\` controls whether anything prints
109
- - \`logging.dogMode\` toggles dog personalities vs standard labels
110
-
111
- The default scaffold boots in dog mode so the logger is visible immediately.
112
-
113
- ## Test Harness
114
-
115
- The scaffold generates a Vitest registry under \`src/tests\`:
116
-
117
- - \`src/tests/registry.ts\` collects generated spec entrypoints
118
- - \`src/tests/runner.ts\` imports the registry entries
119
- - \`src/tests/runner.spec.ts\` is the Vitest entrypoint used by \`sc test\`
120
-
121
- When you add new specs under \`src/tests\`, rerun \`sc test\` and the harness will pick them up.
122
- `;
123
- const rootTsconfigTemplate = `{
124
- "compilerOptions": {
125
- "target": "ES2020",
126
- "module": "NodeNext",
127
- "moduleResolution": "NodeNext",
128
- "lib": ["ES2020"],
129
- "strict": true,
130
- "declaration": true,
131
- "sourceMap": true,
132
- "rootDir": "src",
133
- "outDir": "dist",
134
- "esModuleInterop": true,
135
- "allowSyntheticDefaultImports": true,
136
- "experimentalDecorators": true,
137
- "emitDecoratorMetadata": true,
138
- "useDefineForClassFields": false,
139
- "skipLibCheck": true,
140
- "types": ["node"]
141
- },
142
- "include": ["src/**/*.ts"],
143
- "exclude": ["dist", "node_modules"]
144
- }`;
145
- const sculptorTemplate = (mode, devServer, frameworkLock, testing) => `{
146
- "project": {
147
- "srcRoot": "src",
148
- "entryFile": "main.ts",
149
- "devServer": "${devServer}"
150
- },
151
- "logging": {
152
- "enabled": true,
153
- "dogMode": true
154
- },
155
- "routing": {
156
- "style": "${mode}"
157
- },
158
- "testing": {
159
- "generate": ${testing.generate ? "true" : "false"},
160
- "framework": "${testing.framework}"
161
- },
162
- "frameworkLock": ${frameworkLock ? "true" : "false"}
163
- }`;
164
- const eslintConfigTemplate = `import parser from "@typescript-eslint/parser";
165
- import tsPlugin from "@typescript-eslint/eslint-plugin";
166
-
167
- export default [
168
- {
169
- files: ["**/*.ts"],
170
- languageOptions: {
171
- parser,
172
- parserOptions: {
173
- ecmaVersion: "latest",
174
- sourceType: "module"
175
- }
176
- },
177
- plugins: {
178
- "@typescript-eslint": tsPlugin
179
- },
180
- rules: {
181
- "no-unused-vars": "off",
182
- "@typescript-eslint/no-unused-vars": [
183
- "error",
184
- { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }
185
- ],
186
- "@typescript-eslint/no-explicit-any": "off"
187
- }
188
- },
189
- {
190
- ignores: ["dist/**", "node_modules/**"]
191
- }
192
- ];
193
- `;
194
- const vitestConfigTemplate = `import { defineConfig } from "vitest/config";
195
-
196
- export default defineConfig({
197
- test: {
198
- environment: "node",
199
- globals: true
200
- }
201
- });
202
- `;
203
- const mainSpecTemplate = `import type { Server } from "node:http";
204
- import { fileURLToPath } from "node:url";
205
-
206
- import { afterEach, describe, expect, it, vi } from "vitest";
207
-
208
- import { startApp } from "@sculptor/core";
209
- import { registry } from "../registry.js";
210
-
211
- describe("app bootstrap", () => {
212
- let server: Server | undefined;
213
-
214
- afterEach(async () => {
215
- vi.restoreAllMocks();
216
-
217
- if (!server) {
218
- return;
219
- }
220
-
221
- await new Promise<void>((resolve) => {
222
- server?.close(() => resolve());
223
- });
224
- server = undefined;
225
- });
226
-
227
- it("starts the application", async () => {
228
- const logs: string[] = [];
229
- vi.spyOn(console, "log").mockImplementation((...args: unknown[]) => {
230
- logs.push(args.map(String).join(" "));
231
- });
232
-
233
- server = await startApp({
234
- registry,
235
- rootDir: fileURLToPath(new URL("../..", import.meta.url)),
236
- port: 0
237
- });
238
-
239
- expect(logs.join("\\n")).toContain("Local: http://localhost:");
240
- });
241
- });
242
- `;
243
- const appTsconfigTemplate = `{
244
- "compilerOptions": {
245
- "target": "ES2020",
246
- "module": "NodeNext",
247
- "moduleResolution": "NodeNext",
248
- "lib": ["ES2020"],
249
- "strict": true,
250
- "declaration": true,
251
- "sourceMap": true,
252
- "rootDir": "src",
253
- "outDir": "dist",
254
- "esModuleInterop": true,
255
- "allowSyntheticDefaultImports": true,
256
- "experimentalDecorators": true,
257
- "emitDecoratorMetadata": true,
258
- "useDefineForClassFields": false,
259
- "skipLibCheck": true,
260
- "types": ["node"]
261
- },
262
- "include": ["src/**/*.ts"],
263
- "exclude": ["dist", "node_modules"]
264
- }`;
265
- const propsTemplate = `{
266
- "app": {
267
- "port": 3000,
268
- "prefix": "/api"
269
- }
270
- }`;
271
- const mainTemplate = `import { paws } from "@sculptor/paws";
272
- import { startApp } from "@sculptor/core";
273
- import { fileURLToPath } from "node:url";
274
- import { registry } from "./registry.js";
275
-
276
- const appRoot = fileURLToPath(new URL("..", import.meta.url));
277
-
278
- process.env.SCULPTOR_ROOT_DIR = appRoot;
279
- paws.boot();
280
-
281
- void startApp({ registry, rootDir: appRoot });
282
- `;
283
- const registryTemplate = (mode) => {
284
- if (mode === "decorator") {
285
- return `import { HealthController } from "./app/controllers/health.controller.js";
286
-
287
- export const registry = {
288
- controllers: [HealthController],
289
- routes: [],
290
- services: []
291
- };
292
- `;
293
- }
294
- if (mode === "functional") {
295
- return `import { healthRoutes } from "./app/routes/health.routes.js";
296
-
297
- export const registry = {
298
- controllers: [],
299
- routes: [healthRoutes],
300
- services: []
301
- };
302
- `;
303
- }
304
- return `import { HealthController } from "./app/controllers/health.controller.js";
305
- import { healthRoutes } from "./app/routes/health.routes.js";
306
-
307
- export const registry = {
308
- controllers: [HealthController],
309
- routes: [healthRoutes],
310
- services: []
311
- };
312
- `;
313
- };
314
- const healthControllerTemplate = `import { Controller, Get } from "@sculptor/core";
315
-
316
- @Controller("/health")
317
- export class HealthController {
318
- @Get("/")
319
- health() {
320
- return { status: "ok" };
321
- }
322
-
323
- @Get("/ping")
324
- ping() {
325
- return { message: "pong" };
326
- }
327
- }
328
- `;
329
- const healthRoutesTemplate = `import { Router } from "express";
330
-
331
- export const healthRoutes = Router();
332
-
333
- healthRoutes.get("/health", (_req, res) => {
334
- res.json({ status: "ok" });
335
- });
336
-
337
- healthRoutes.get("/ping", (_req, res) => {
338
- res.json({ message: "pong" });
339
- });
340
- `;
341
- const healthServiceTemplate = `export class HealthService {
342
- status() {
343
- return { status: "ok" };
344
- }
345
- }
346
- `;
347
- const healthModuleTemplate = `export class HealthModule {}
348
- `;
349
- const healthControllerSpecTemplate = `import { describe, expect, it } from "vitest";
350
-
351
- import { HealthController } from "../app/controllers/health.controller.js";
352
-
353
- describe("HealthController", () => {
354
- it("returns the expected health payload", () => {
355
- const controller = new HealthController();
356
-
357
- expect(controller.health()).toEqual({ status: "ok" });
358
- expect(controller.ping()).toEqual({ message: "pong" });
359
- });
360
- });
361
- `;
362
- const healthRoutesSpecTemplate = `import express from "express";
363
- import request from "supertest";
364
- import { describe, expect, it } from "vitest";
365
-
366
- import { healthRoutes } from "../app/routes/health.routes.js";
367
-
368
- describe("healthRoutes", () => {
369
- it("serves the health endpoint", async () => {
370
- const app = express();
371
- app.use(healthRoutes);
372
-
373
- await request(app).get("/health").expect(200).expect({ status: "ok" });
374
- await request(app).get("/ping").expect(200).expect({ message: "pong" });
375
- });
376
- });
377
- `;
378
- const testRegistryTemplate = (specs) => `export const testRegistry = [
379
- ${specs.map((spec) => ` "${spec}"`).join(",\n")}
380
- ] as const;
381
- `;
382
- const testRunnerTemplate = () => `import { testRegistry } from "./registry.js";
383
-
384
- for (const spec of testRegistry) {
385
- await import(spec);
386
- }
387
- `;
388
- const testHarnessSpecTemplate = () => `import "./runner.js";
389
- `;
390
- const collectSpecPaths = (dir, rootDir = dir) => {
391
- if (!fs.existsSync(dir)) {
392
- return [];
393
- }
394
- const entries = fs.readdirSync(dir, { withFileTypes: true });
395
- const specs = [];
396
- for (const entry of entries) {
397
- const fullPath = path.join(dir, entry.name);
398
- if (entry.isDirectory()) {
399
- specs.push(...collectSpecPaths(fullPath, rootDir));
400
- continue;
401
- }
402
- if (!entry.isFile()) {
403
- continue;
404
- }
405
- if (!entry.name.endsWith(".spec.ts")) {
406
- continue;
407
- }
408
- const relativePath = normalizeRelativePath(path.relative(rootDir, fullPath));
409
- if (relativePath === "registry.ts" ||
410
- relativePath === "runner.ts" ||
411
- relativePath === "runner.spec.ts") {
412
- continue;
413
- }
414
- specs.push(`./${relativePath.replace(/\.ts$/, ".js")}`);
415
- }
416
- return specs.sort();
417
- };
418
- export const syncTestHarness = (targetDir) => {
419
- const testsDir = path.join(targetDir, "src", "tests");
420
- ensureDir(testsDir);
421
- const specs = collectSpecPaths(testsDir);
422
- writeTextFile(path.join(testsDir, "registry.ts"), testRegistryTemplate(specs));
423
- writeTextFile(path.join(testsDir, "runner.ts"), testRunnerTemplate());
424
- writeTextFile(path.join(testsDir, "runner.spec.ts"), testHarnessSpecTemplate());
425
- };
426
- const controllerSpecTemplate = (name, sourcePath) => {
427
- const importPath = specImportPath(sourcePath);
428
- return `import { describe, expect, it } from "vitest";
429
-
430
- import { ${toPascalCase(name)}Controller } from "${importPath}";
431
-
432
- describe("${toPascalCase(name)}Controller", () => {
433
- it("returns the expected payload", () => {
434
- const controller = new ${toPascalCase(name)}Controller();
435
-
436
- expect(controller.findAll()).toEqual({ resource: "${name}" });
437
- });
438
- });
439
- `;
440
- };
441
- const serviceSpecTemplate = (name, sourcePath) => {
442
- const importPath = specImportPath(sourcePath);
443
- return `import { describe, expect, it } from "vitest";
444
-
445
- import { ${toPascalCase(name)}Service } from "${importPath}";
446
-
447
- describe("${toPascalCase(name)}Service", () => {
448
- it("can be instantiated", () => {
449
- expect(new ${toPascalCase(name)}Service()).toBeInstanceOf(${toPascalCase(name)}Service);
450
- });
451
- });
452
- `;
453
- };
454
- const routeSpecTemplate = (name, sourcePath) => {
455
- const importPath = specImportPath(sourcePath);
456
- return `import express from "express";
457
- import request from "supertest";
458
- import { describe, expect, it } from "vitest";
459
-
460
- import { ${toCamelCase(name)}Routes } from "${importPath}";
461
-
462
- describe("${toCamelCase(name)}Routes", () => {
463
- it("serves the resource endpoint", async () => {
464
- const app = express();
465
- app.use(${toCamelCase(name)}Routes);
466
-
467
- await request(app).get("/${name}").expect(200).expect({ resource: "${name}" });
468
- });
469
- });
470
- `;
471
- };
472
- const middlewareSpecTemplate = (name, sourcePath) => {
473
- const importPath = specImportPath(sourcePath);
474
- return `import { describe, expect, it, vi } from "vitest";
475
-
476
- import { ${toCamelCase(name)}Middleware } from "${importPath}";
477
-
478
- describe("${toCamelCase(name)}Middleware", () => {
479
- it("calls next", () => {
480
- const next = vi.fn();
481
-
482
- ${toCamelCase(name)}Middleware({} as never, {} as never, next);
483
-
484
- expect(next).toHaveBeenCalledTimes(1);
485
- });
486
- });
487
- `;
488
- };
489
- const createDecoratorController = (name, outputDir = "src/app/controllers") => ({
490
- [`${normalizeRelativePath(outputDir)}/${controllerFileName(name)}`]: `import { Controller, Get } from "@sculptor/core";
491
-
492
- @Controller("/${name}")
493
- export class ${toPascalCase(name)}Controller {
494
- @Get("/")
495
- findAll() {
496
- return { resource: "${name}" };
497
- }
498
- }
499
- `
500
- });
501
- const createServiceResource = (name) => ({
502
- [`src/app/services/${serviceFileName(name)}`]: `export class ${toPascalCase(name)}Service {}
503
- `
504
- });
505
- const createModuleResource = (name) => ({
506
- [`src/app/modules/${moduleFileName(name)}`]: `export class ${toPascalCase(name)}Module {}
507
- `
508
- });
509
- const createMiddlewareResource = (name) => ({
510
- [`src/app/middlewares/${middlewareFileName(name)}`]: `import type { NextFunction, Request, Response } from "express";
511
-
512
- export const ${toCamelCase(name)}Middleware = (
513
- _req: Request,
514
- _res: Response,
515
- next: NextFunction
516
- ) => {
517
- next();
518
- };
519
- `
520
- });
521
- const createRouteResource = (name) => ({
522
- [`src/app/routes/${routeFileName(name)}`]: `import { Router } from "express";
523
-
524
- export const ${toCamelCase(name)}Routes = Router();
525
-
526
- ${toCamelCase(name)}Routes.get("/${name}", (_req, res) => {
527
- res.json({ resource: "${name}" });
528
- });
529
- `
530
- });
531
- const createHandlerResource = (name) => ({
532
- [`src/app/handlers/${name}.handler.ts`]: `export const ${toPascalCase(name)}Handler = (
533
- _req: unknown,
534
- res: { json: (value: unknown) => void }
535
- ) => {
536
- res.json({ resource: "${name}" });
537
- };
538
- `
539
- });
540
- const createTypeResource = (name, variant, outputDir) => {
541
- const fileName = typeFileName(name, variant);
542
- const targetDir = normalizeRelativePath(outputDir);
543
- if (variant === "interface") {
544
- return {
545
- [`${targetDir}/${fileName}`]: `export interface ${toPascalCase(name)} {
546
- id?: string;
547
- }
548
- `
549
- };
550
- }
551
- if (variant === "class") {
552
- return {
553
- [`${targetDir}/${fileName}`]: `export class ${toPascalCase(name)} {}
554
- `
555
- };
556
- }
557
- if (variant === "enum") {
558
- return {
559
- [`${targetDir}/${fileName}`]: `export enum ${toPascalCase(name)} {
560
- Default = "default"
561
- }
562
- `
563
- };
564
- }
565
- return {
566
- [`${targetDir}/${fileName}`]: `export type ${toPascalCase(name)} = Record<string, unknown>;
567
- `
568
- };
569
- };
570
- const appShellFiles = (metadata) => ({
571
- "package.json": rootPackageJsonTemplate(metadata.appName, metadata.version, metadata.devServer),
572
- "README.md": rootReadmeTemplate(metadata.appName),
573
- "tsconfig.json": rootTsconfigTemplate,
574
- "sculptor.json": sculptorTemplate(metadata.mode, metadata.devServer, metadata.frameworkLock, metadata.testing),
575
- "props.json": propsTemplate,
576
- "src/main.ts": mainTemplate,
577
- "src/registry.ts": registryTemplate(metadata.mode)
578
- });
579
- export const scaffoldProject = (metadata, targetDir) => {
580
- ensureDir(targetDir);
581
- const rootFiles = {
582
- ...appShellFiles(metadata),
583
- "eslint.config.js": eslintConfigTemplate,
584
- "vitest.config.ts": vitestConfigTemplate,
585
- "src/app/services/health.service.ts": healthServiceTemplate,
586
- "src/app/modules/health.module.ts": healthModuleTemplate
587
- };
588
- if (metadata.mode === "decorator" || metadata.mode === "hybrid") {
589
- rootFiles["src/app/controllers/health.controller.ts"] =
590
- healthControllerTemplate;
591
- }
592
- if (metadata.mode === "functional" || metadata.mode === "hybrid") {
593
- rootFiles["src/app/routes/health.routes.ts"] = healthRoutesTemplate;
594
- }
595
- if (metadata.testing.generate) {
596
- rootFiles["src/tests/main.spec.ts"] = mainSpecTemplate;
597
- if (metadata.mode === "decorator" || metadata.mode === "hybrid") {
598
- rootFiles["src/tests/health.controller.spec.ts"] = healthControllerSpecTemplate;
599
- }
600
- if (metadata.mode === "functional" || metadata.mode === "hybrid") {
601
- rootFiles["src/tests/health.routes.spec.ts"] = healthRoutesSpecTemplate;
602
- }
603
- }
604
- for (const [relativePath, content] of Object.entries(rootFiles)) {
605
- writeTextFile(path.join(targetDir, relativePath), content);
606
- }
607
- for (const dir of [
608
- "src/app/controllers",
609
- "src/app/routes",
610
- "src/app/services",
611
- "src/app/modules",
612
- "src/app/middlewares",
613
- "src/app/handlers",
614
- "src/tests"
615
- ]) {
616
- ensureDir(path.join(targetDir, dir));
617
- }
618
- if (metadata.testing.generate) {
619
- syncTestHarness(targetDir);
620
- }
621
- };
622
- export const generateResourceFiles = (kind, name, mode, devServer = "tsx", outputDir, typeVariant = "type", testingGenerate = false) => {
623
- const resolvedOutputDir = resolveGeneratorOutputDir(kind, outputDir);
624
- const resolvedName = resolveFileStem(name, resolvedOutputDir);
625
- const functionalRouteDir = normalizeRelativePath(outputDir ?? "src/app/routes");
626
- const functionalHandlerDir = normalizeRelativePath(outputDir ?? "src/app/handlers");
627
- const sourceFiles = {};
628
- switch (kind) {
629
- case "controller":
630
- if (mode === "functional") {
631
- Object.assign(sourceFiles, {
632
- [`${functionalRouteDir}/${routeFileName(resolvedName)}`]: createRouteResource(resolvedName)[`src/app/routes/${routeFileName(resolvedName)}`],
633
- [`${functionalHandlerDir}/${resolvedName}.handler.ts`]: createHandlerResource(resolvedName)[`src/app/handlers/${resolvedName}.handler.ts`]
634
- });
635
- break;
636
- }
637
- if (mode === "hybrid") {
638
- Object.assign(sourceFiles, {
639
- ...createDecoratorController(resolvedName, resolvedOutputDir),
640
- [`${functionalRouteDir}/${routeFileName(resolvedName)}`]: createRouteResource(resolvedName)[`src/app/routes/${routeFileName(resolvedName)}`],
641
- [`${functionalHandlerDir}/${resolvedName}.handler.ts`]: createHandlerResource(resolvedName)[`src/app/handlers/${resolvedName}.handler.ts`]
642
- });
643
- break;
644
- }
645
- Object.assign(sourceFiles, createDecoratorController(resolvedName, resolvedOutputDir));
646
- break;
647
- case "service":
648
- Object.assign(sourceFiles, {
649
- [`${resolvedOutputDir}/${serviceFileName(resolvedName)}`]: createServiceResource(resolvedName)[`src/app/services/${serviceFileName(resolvedName)}`]
650
- });
651
- break;
652
- case "module":
653
- Object.assign(sourceFiles, {
654
- [`${resolvedOutputDir}/${moduleFileName(resolvedName)}`]: createModuleResource(resolvedName)[`src/app/modules/${moduleFileName(resolvedName)}`]
655
- });
656
- break;
657
- case "middleware":
658
- Object.assign(sourceFiles, {
659
- [`${resolvedOutputDir}/${middlewareFileName(resolvedName)}`]: createMiddlewareResource(resolvedName)[`src/app/middlewares/${middlewareFileName(resolvedName)}`]
660
- });
661
- break;
662
- case "type":
663
- Object.assign(sourceFiles, createTypeResource(resolvedName, typeVariant, resolvedOutputDir));
664
- break;
665
- case "route":
666
- if (mode === "functional" || mode === "hybrid") {
667
- Object.assign(sourceFiles, {
668
- [`${resolvedOutputDir}/${routeFileName(resolvedName)}`]: createRouteResource(resolvedName)[`src/app/routes/${routeFileName(resolvedName)}`]
669
- });
670
- }
671
- break;
672
- }
673
- if (!testingGenerate) {
674
- return sourceFiles;
675
- }
676
- const testFiles = {};
677
- for (const [filePath] of Object.entries(sourceFiles)) {
678
- const normalizedPath = normalizeRelativePath(filePath);
679
- const baseName = path.posix.basename(normalizedPath, ".ts");
680
- if (normalizedPath.endsWith(".controller.ts")) {
681
- const resourceName = baseName.replace(/\.controller$/, "");
682
- testFiles[`src/tests/${specFileName(resourceName, "controller")}`] =
683
- controllerSpecTemplate(resourceName, normalizedPath);
684
- continue;
685
- }
686
- if (normalizedPath.endsWith(".service.ts")) {
687
- const resourceName = baseName.replace(/\.service$/, "");
688
- testFiles[`src/tests/${specFileName(resourceName, "service")}`] =
689
- serviceSpecTemplate(resourceName, normalizedPath);
690
- continue;
691
- }
692
- if (normalizedPath.endsWith(".routes.ts")) {
693
- const resourceName = baseName.replace(/\.routes$/, "");
694
- testFiles[`src/tests/${specFileName(resourceName, "routes")}`] =
695
- routeSpecTemplate(resourceName, normalizedPath);
696
- continue;
697
- }
698
- if (normalizedPath.endsWith(".middleware.ts")) {
699
- const resourceName = baseName.replace(/\.middleware$/, "");
700
- testFiles[`src/tests/${specFileName(resourceName, "middleware")}`] =
701
- middlewareSpecTemplate(resourceName, normalizedPath);
702
- }
703
- }
704
- return {
705
- ...sourceFiles,
706
- ...testFiles
707
- };
708
- };
709
- export const writeGeneratedFiles = (targetDir, files) => {
710
- for (const [relativePath, content] of Object.entries(files)) {
711
- writeTextFile(path.join(targetDir, relativePath), content);
712
- }
713
- };
714
- export const readModeFromFlags = (flags, fallback) => {
715
- if (flags.includes("--functional")) {
716
- return "functional";
717
- }
718
- if (flags.includes("--decorator")) {
719
- return "decorator";
720
- }
721
- if (flags.includes("--hybrid")) {
722
- return "hybrid";
723
- }
724
- return fallback;
725
- };
726
- export const parseGenerateMode = (mode, fallback) => mode === "functional" || mode === "decorator" || mode === "hybrid" ? mode : fallback;
727
- export const controllerHelp = `# Controller Generator
728
-
729
- ## Usage
730
-
731
- \`\`\`bash
732
- sc generate controller user
733
- sc g c user
734
- \nsc generate controller user in src/app/controllers
735
- \nsc g c user --functional
736
- \`\`\`
737
-
738
- ## Output
739
-
740
- - \`controllers/*.controller.ts\`
741
- - in functional mode, generates \`routes/*.routes.ts\` and \`handlers/*.handler.ts\`
742
- `;
743
- export const moduleHelp = `# Module Generator
744
-
745
- ## Usage
746
-
747
- \`\`\`bash
748
- sc generate module user
749
- sc g mo user
750
- \`\`\`
751
- `;
752
- export const middlewareHelp = `# Middleware Generator
753
-
754
- ## Usage
755
-
756
- \`\`\`bash
757
- sc generate middleware auth
758
- sc g mw auth
759
- \`\`\`
760
- `;
761
- export const typeHelp = `# Type Generator
762
-
763
- ## Usage
764
-
765
- \`\`\`bash
766
- sc generate type user
767
- sc g t user
768
- sc g t user -i
769
- sc g t user -c
770
- sc g t user -e
771
- \`\`\`
772
-
773
- ## Flags
774
-
775
- - \`-i\`, \`-interface\` => \`*.interface.ts\`
776
- - \`-c\`, \`-class\` => \`*.class.ts\`
777
- - \`-e\`, \`-enum\` => \`*.enum.ts\`
778
- - default => \`*.type.ts\`
779
- `;
780
- export const routeHelp = `# Route Generator
781
-
782
- ## Usage
783
-
784
- \`\`\`bash
785
- sc generate route user
786
- sc g r user
787
- \`\`\`
788
-
789
- ## Mode Guard
790
-
791
- - routers can only be scaffolded in functional or hybrid mode
792
- `;
793
- export const generateHelp = `# Generate
794
-
795
- ## Usage
796
-
797
- \`\`\`bash
798
- sc generate controller user
799
- sc generate service user
800
- sc generate module user
801
- sc generate middleware auth
802
- sc generate type user
803
- sc generate route user
804
- \`\`\`
805
-
806
- ## Aliases
807
-
808
- - \`sc g\`
809
- - \`controller\` -> \`c\`
810
- - \`service\` -> \`s\`
811
- - \`module\` -> \`m\` / \`mo\`
812
- - \`middleware\` -> \`mw\`
813
- - \`type\` -> \`t\`
814
- - \`route\` -> \`r\`
815
-
816
- ## Path
817
-
818
- - append \`in <path>\` to write into a custom directory
819
- - default output for types is \`src/app/types\`
820
- `;
1
+ export { controllerHelp, generateHelp, generateResourceFiles, middlewareHelp, moduleHelp, parseGenerateMode, readModeFromFlags, routeHelp, scaffoldProject, syncTestHarness, typeHelp, writeGeneratedFiles } from "../../template-registry/dist/index.js";
821
2
  //# sourceMappingURL=scaffold.js.map