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