@fluojs/cli 1.0.0-beta.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.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +155 -0
  3. package/README.md +155 -0
  4. package/bin/fluo.mjs +5 -0
  5. package/dist/cli.d.ts +37 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +292 -0
  8. package/dist/commands/generate.d.ts +40 -0
  9. package/dist/commands/generate.d.ts.map +1 -0
  10. package/dist/commands/generate.js +134 -0
  11. package/dist/commands/inspect.d.ts +30 -0
  12. package/dist/commands/inspect.d.ts.map +1 -0
  13. package/dist/commands/inspect.js +221 -0
  14. package/dist/commands/migrate.d.ts +30 -0
  15. package/dist/commands/migrate.d.ts.map +1 -0
  16. package/dist/commands/migrate.js +173 -0
  17. package/dist/commands/new.d.ts +45 -0
  18. package/dist/commands/new.d.ts.map +1 -0
  19. package/dist/commands/new.js +353 -0
  20. package/dist/generator-types.d.ts +21 -0
  21. package/dist/generator-types.d.ts.map +1 -0
  22. package/dist/generator-types.js +1 -0
  23. package/dist/generators/controller.d.ts +3 -0
  24. package/dist/generators/controller.d.ts.map +1 -0
  25. package/dist/generators/controller.js +22 -0
  26. package/dist/generators/guard.d.ts +3 -0
  27. package/dist/generators/guard.d.ts.map +1 -0
  28. package/dist/generators/guard.js +15 -0
  29. package/dist/generators/interceptor.d.ts +3 -0
  30. package/dist/generators/interceptor.d.ts.map +1 -0
  31. package/dist/generators/interceptor.js +15 -0
  32. package/dist/generators/manifest.d.ts +121 -0
  33. package/dist/generators/manifest.d.ts.map +1 -0
  34. package/dist/generators/manifest.js +130 -0
  35. package/dist/generators/middleware.d.ts +3 -0
  36. package/dist/generators/middleware.d.ts.map +1 -0
  37. package/dist/generators/middleware.js +15 -0
  38. package/dist/generators/module.d.ts +6 -0
  39. package/dist/generators/module.d.ts.map +1 -0
  40. package/dist/generators/module.js +143 -0
  41. package/dist/generators/render.d.ts +2 -0
  42. package/dist/generators/render.d.ts.map +1 -0
  43. package/dist/generators/render.js +17 -0
  44. package/dist/generators/repository.d.ts +3 -0
  45. package/dist/generators/repository.d.ts.map +1 -0
  46. package/dist/generators/repository.js +29 -0
  47. package/dist/generators/request-dto.d.ts +3 -0
  48. package/dist/generators/request-dto.d.ts.map +1 -0
  49. package/dist/generators/request-dto.js +17 -0
  50. package/dist/generators/response-dto.d.ts +3 -0
  51. package/dist/generators/response-dto.d.ts.map +1 -0
  52. package/dist/generators/response-dto.js +17 -0
  53. package/dist/generators/service.d.ts +3 -0
  54. package/dist/generators/service.d.ts.map +1 -0
  55. package/dist/generators/service.js +22 -0
  56. package/dist/generators/templates/controller.test.ts.ejs +21 -0
  57. package/dist/generators/templates/controller.ts.ejs +29 -0
  58. package/dist/generators/templates/guard.ts.ejs +7 -0
  59. package/dist/generators/templates/interceptor.ts.ejs +7 -0
  60. package/dist/generators/templates/middleware.ts.ejs +11 -0
  61. package/dist/generators/templates/module.ts.ejs +9 -0
  62. package/dist/generators/templates/repository.slice.test.ts.ejs +15 -0
  63. package/dist/generators/templates/repository.test.ts.ejs +9 -0
  64. package/dist/generators/templates/repository.ts.ejs +10 -0
  65. package/dist/generators/templates/request-dto.ts.ejs +9 -0
  66. package/dist/generators/templates/response-dto.ts.ejs +3 -0
  67. package/dist/generators/templates/service.test.ts.ejs +21 -0
  68. package/dist/generators/templates/service.ts.ejs +24 -0
  69. package/dist/generators/utils.d.ts +4 -0
  70. package/dist/generators/utils.d.ts.map +1 -0
  71. package/dist/generators/utils.js +18 -0
  72. package/dist/help.d.ts +8 -0
  73. package/dist/help.d.ts.map +1 -0
  74. package/dist/help.js +16 -0
  75. package/dist/index.d.ts +4 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +2 -0
  78. package/dist/new/install.d.ts +51 -0
  79. package/dist/new/install.d.ts.map +1 -0
  80. package/dist/new/install.js +140 -0
  81. package/dist/new/package-spec-resolver.d.ts +4 -0
  82. package/dist/new/package-spec-resolver.d.ts.map +1 -0
  83. package/dist/new/package-spec-resolver.js +397 -0
  84. package/dist/new/prompt.d.ts +56 -0
  85. package/dist/new/prompt.d.ts.map +1 -0
  86. package/dist/new/prompt.js +278 -0
  87. package/dist/new/resolver.d.ts +32 -0
  88. package/dist/new/resolver.d.ts.map +1 -0
  89. package/dist/new/resolver.js +93 -0
  90. package/dist/new/scaffold.d.ts +14 -0
  91. package/dist/new/scaffold.d.ts.map +1 -0
  92. package/dist/new/scaffold.js +2010 -0
  93. package/dist/new/starter-profiles.d.ts +91 -0
  94. package/dist/new/starter-profiles.d.ts.map +1 -0
  95. package/dist/new/starter-profiles.js +347 -0
  96. package/dist/new/types.d.ts +63 -0
  97. package/dist/new/types.d.ts.map +1 -0
  98. package/dist/new/types.js +1 -0
  99. package/dist/registry.d.ts +10 -0
  100. package/dist/registry.d.ts.map +1 -0
  101. package/dist/registry.js +30 -0
  102. package/dist/transforms/nestjs-migrate.d.ts +33 -0
  103. package/dist/transforms/nestjs-migrate.d.ts.map +1 -0
  104. package/dist/transforms/nestjs-migrate.js +891 -0
  105. package/dist/types.d.ts +12 -0
  106. package/dist/types.d.ts.map +1 -0
  107. package/dist/types.js +1 -0
  108. package/package.json +65 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AAMA,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAUnF,OAAO,KAAK,EAAoB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE3E,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAuBF;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiSD;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CA0BjC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CAiG3G"}
@@ -0,0 +1,353 @@
1
+ import { resolve } from 'node:path';
2
+ import { spinner as clackSpinner, log as clackLog } from '@clack/prompts';
3
+ import { renderAliasList, renderHelpTable } from '../help.js';
4
+ import { installDependencies } from '../new/install.js';
5
+ import { collectBootstrapAnswers } from '../new/prompt.js';
6
+ import { scaffoldBootstrapApp } from '../new/scaffold.js';
7
+ import { SUPPORTED_BOOTSTRAP_PLATFORMS, SUPPORTED_BOOTSTRAP_RUNTIMES, SUPPORTED_BOOTSTRAP_SHAPES, SUPPORTED_BOOTSTRAP_TOOLING_PRESETS, SUPPORTED_BOOTSTRAP_TOPOLOGY_MODES, SUPPORTED_BOOTSTRAP_TRANSPORTS } from '../new/starter-profiles.js';
8
+ function shouldUseInteractiveShell(runtime) {
9
+ return runtime.prompt === undefined && runtime.stdout === undefined && runtime.stderr === undefined && (runtime.interactive ?? true) && Boolean(runtime.stdin?.isTTY ?? process.stdin.isTTY);
10
+ }
11
+ function extractDependencyInstallationOutput(error) {
12
+ if (!(error instanceof Error) || !('output' in error)) {
13
+ return undefined;
14
+ }
15
+ const output = error.output;
16
+ return typeof output === 'string' && output.trim().length > 0 ? output : undefined;
17
+ }
18
+ function isHelpFlag(value) {
19
+ return value === '--help' || value === '-h';
20
+ }
21
+
22
+ /**
23
+ * Runtime dependency overrides for the programmatic `fluo new` entry point.
24
+ */
25
+
26
+ const NEW_OPTION_HELP = [{
27
+ aliases: [],
28
+ description: 'Provide the project name without using the positional argument.',
29
+ option: '--name <project-name>'
30
+ }, {
31
+ aliases: [],
32
+ description: 'Select the scaffold shape explicitly (application for HTTP, microservice for the transport-driven starter path, mixed for the API + microservice starter).',
33
+ option: '--shape <application|microservice|mixed>'
34
+ }, {
35
+ aliases: [],
36
+ description: 'Select the transport path explicitly (http for applications, tcp for the runnable microservice starter, plus shipped microservice starter transports).',
37
+ option: '--transport <http|tcp|redis-streams|nats|kafka|rabbitmq|mqtt|grpc>'
38
+ }, {
39
+ aliases: [],
40
+ description: 'Select the runtime explicitly (node, bun, deno, or cloudflare-workers for application starters; node for microservice and mixed starters).',
41
+ option: '--runtime <node|bun|deno|cloudflare-workers>'
42
+ }, {
43
+ aliases: [],
44
+ description: 'Select the platform adapter explicitly (fastify, express, or nodejs on node; bun/deno/cloudflare-workers on their native runtimes; none for microservices).',
45
+ option: '--platform <fastify|express|nodejs|bun|deno|cloudflare-workers|none>'
46
+ }, {
47
+ aliases: [],
48
+ description: 'Select the starter tooling preset explicitly (currently only standard).',
49
+ option: '--tooling <standard>'
50
+ }, {
51
+ aliases: [],
52
+ description: 'Select the starter topology mode explicitly (currently only single-package).',
53
+ option: '--topology <single-package>'
54
+ }, {
55
+ aliases: [],
56
+ description: 'Choose which package manager installs the starter dependencies.',
57
+ option: '--package-manager <pnpm|npm|yarn|bun>'
58
+ }, {
59
+ aliases: [],
60
+ description: 'Write the new app to a custom target directory (always overrides positional name path).',
61
+ option: '--target-directory <path>'
62
+ }, {
63
+ aliases: [],
64
+ description: 'Overwrite files in a non-empty target directory without prompting.',
65
+ option: '--force'
66
+ }, {
67
+ aliases: [],
68
+ description: 'Install starter dependencies after writing files.',
69
+ option: '--install'
70
+ }, {
71
+ aliases: [],
72
+ description: 'Skip starter dependency installation.',
73
+ option: '--no-install'
74
+ }, {
75
+ aliases: [],
76
+ description: 'Initialize a git repository in the generated starter.',
77
+ option: '--git'
78
+ }, {
79
+ aliases: [],
80
+ description: 'Skip git repository initialization in the generated starter.',
81
+ option: '--no-git'
82
+ }, {
83
+ aliases: ['-h'],
84
+ description: 'Show help for the new command.',
85
+ option: '--help'
86
+ }];
87
+ const SUPPORTED_PACKAGE_MANAGERS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
88
+ const SUPPORTED_SHAPES = new Set(SUPPORTED_BOOTSTRAP_SHAPES);
89
+ const SUPPORTED_TRANSPORTS = new Set(SUPPORTED_BOOTSTRAP_TRANSPORTS);
90
+ const SUPPORTED_RUNTIMES = new Set(SUPPORTED_BOOTSTRAP_RUNTIMES);
91
+ const SUPPORTED_PLATFORMS = new Set(SUPPORTED_BOOTSTRAP_PLATFORMS);
92
+ const SUPPORTED_TOOLING_PRESETS = new Set(SUPPORTED_BOOTSTRAP_TOOLING_PRESETS);
93
+ const SUPPORTED_TOPOLOGY_MODES = new Set(SUPPORTED_BOOTSTRAP_TOPOLOGY_MODES);
94
+ function readOptionValue(argv, index, option) {
95
+ const value = argv[index + 1];
96
+ if (!value || value.startsWith('-')) {
97
+ throw new Error(`Expected ${option} to have a value.`);
98
+ }
99
+ return value;
100
+ }
101
+ function setBooleanSelection(currentValue, nextValue, positiveFlag, negativeFlag) {
102
+ if (currentValue !== undefined) {
103
+ throw new Error(`Duplicate ${nextValue ? positiveFlag : negativeFlag} option.`);
104
+ }
105
+ return nextValue;
106
+ }
107
+ function parseArgs(argv) {
108
+ const parsed = {};
109
+ let hasExplicitTargetDirectory = false;
110
+ for (let index = 0; index < argv.length; index += 1) {
111
+ const arg = argv[index];
112
+ switch (arg) {
113
+ case '--name':
114
+ if (parsed.projectName) {
115
+ throw new Error('Duplicate --name option.');
116
+ }
117
+ parsed.projectName = readOptionValue(argv, index, '--name');
118
+ index += 1;
119
+ break;
120
+ case '--package-manager':
121
+ if (parsed.packageManager) {
122
+ throw new Error('Duplicate --package-manager option.');
123
+ }
124
+ parsed.packageManager = readOptionValue(argv, index, '--package-manager');
125
+ if (!SUPPORTED_PACKAGE_MANAGERS.has(parsed.packageManager)) {
126
+ throw new Error(`Invalid --package-manager value "${parsed.packageManager}". Use one of: pnpm, npm, yarn, bun.`);
127
+ }
128
+ index += 1;
129
+ break;
130
+ case '--shape':
131
+ if (parsed.shape) {
132
+ throw new Error('Duplicate --shape option.');
133
+ }
134
+ parsed.shape = readOptionValue(argv, index, '--shape');
135
+ if (!SUPPORTED_SHAPES.has(parsed.shape)) {
136
+ throw new Error(`Invalid --shape value "${parsed.shape}". Use one of: application, microservice, mixed.`);
137
+ }
138
+ index += 1;
139
+ break;
140
+ case '--transport':
141
+ if (parsed.transport) {
142
+ throw new Error('Duplicate --transport option.');
143
+ }
144
+ parsed.transport = readOptionValue(argv, index, '--transport');
145
+ if (!SUPPORTED_TRANSPORTS.has(parsed.transport)) {
146
+ throw new Error('Invalid --transport value "' + parsed.transport + '". Use one of: ' + 'http, tcp, redis-streams, nats, kafka, rabbitmq, mqtt, grpc.');
147
+ }
148
+ index += 1;
149
+ break;
150
+ case '--runtime':
151
+ if (parsed.runtime) {
152
+ throw new Error('Duplicate --runtime option.');
153
+ }
154
+ parsed.runtime = readOptionValue(argv, index, '--runtime');
155
+ if (!SUPPORTED_RUNTIMES.has(parsed.runtime)) {
156
+ throw new Error(`Invalid --runtime value "${parsed.runtime}". Use one of: bun, cloudflare-workers, deno, node.`);
157
+ }
158
+ index += 1;
159
+ break;
160
+ case '--platform':
161
+ if (parsed.platform) {
162
+ throw new Error('Duplicate --platform option.');
163
+ }
164
+ parsed.platform = readOptionValue(argv, index, '--platform');
165
+ if (!SUPPORTED_PLATFORMS.has(parsed.platform)) {
166
+ throw new Error('Invalid --platform value "' + parsed.platform + '". Use one of: bun, cloudflare-workers, deno, fastify, express, nodejs, none.');
167
+ }
168
+ index += 1;
169
+ break;
170
+ case '--tooling':
171
+ if (parsed.tooling) {
172
+ throw new Error('Duplicate --tooling option.');
173
+ }
174
+ parsed.tooling = readOptionValue(argv, index, '--tooling');
175
+ if (!SUPPORTED_TOOLING_PRESETS.has(parsed.tooling)) {
176
+ throw new Error(`Invalid --tooling value "${parsed.tooling}". Use: standard.`);
177
+ }
178
+ index += 1;
179
+ break;
180
+ case '--topology':
181
+ {
182
+ const topologyMode = readOptionValue(argv, index, '--topology');
183
+ if (parsed.topology) {
184
+ throw new Error('Duplicate --topology option.');
185
+ }
186
+ if (!SUPPORTED_TOPOLOGY_MODES.has(topologyMode)) {
187
+ throw new Error(`Invalid --topology value "${topologyMode}". Use: single-package.`);
188
+ }
189
+ parsed.topology = {
190
+ deferred: true,
191
+ mode: topologyMode
192
+ };
193
+ index += 1;
194
+ break;
195
+ }
196
+ case '--target-directory':
197
+ if (hasExplicitTargetDirectory) {
198
+ throw new Error('Duplicate --target-directory option.');
199
+ }
200
+ parsed.targetDirectory = readOptionValue(argv, index, '--target-directory');
201
+ hasExplicitTargetDirectory = true;
202
+ index += 1;
203
+ break;
204
+ case '--force':
205
+ parsed.force = true;
206
+ break;
207
+ case '--install':
208
+ parsed.installDependencies = setBooleanSelection(parsed.installDependencies, true, '--install', '--no-install');
209
+ break;
210
+ case '--no-install':
211
+ parsed.installDependencies = setBooleanSelection(parsed.installDependencies, false, '--install', '--no-install');
212
+ break;
213
+ case '--git':
214
+ parsed.initializeGit = setBooleanSelection(parsed.initializeGit, true, '--git', '--no-git');
215
+ break;
216
+ case '--no-git':
217
+ parsed.initializeGit = setBooleanSelection(parsed.initializeGit, false, '--git', '--no-git');
218
+ break;
219
+ default:
220
+ if (arg.startsWith('-')) {
221
+ throw new Error(`Unknown option for new command: ${arg}`);
222
+ }
223
+ if (parsed.projectName) {
224
+ throw new Error(`Unexpected positional argument: ${arg}`);
225
+ }
226
+ parsed.projectName = arg;
227
+ if (!hasExplicitTargetDirectory) {
228
+ parsed.targetDirectory = `./${arg}`;
229
+ }
230
+ break;
231
+ }
232
+ }
233
+ return parsed;
234
+ }
235
+
236
+ /**
237
+ * Renders CLI help text for `fluo new`.
238
+ *
239
+ * @returns Stable help output for the scaffolding command.
240
+ */
241
+ export function newUsage() {
242
+ return ['Usage: fluo new|create [project-name] [options]', '', 'Options', renderHelpTable(NEW_OPTION_HELP, [{
243
+ header: 'Option',
244
+ render: entry => entry.option
245
+ }, {
246
+ header: 'Aliases',
247
+ render: entry => renderAliasList(entry.aliases)
248
+ }, {
249
+ header: 'Description',
250
+ render: entry => entry.description
251
+ }]), '', 'Next steps:', ' cd <app-name>', ' pnpm dev # or npm run dev / yarn dev / bun run dev', '', 'Docs: https://github.com/fluojs/fluo/tree/main/docs/getting-started/quick-start.md'].join('\n');
252
+ }
253
+
254
+ /**
255
+ * Executes `fluo new` with parsed arguments and scaffold options.
256
+ *
257
+ * @example
258
+ * ```ts
259
+ * import { runNewCommand } from '@fluojs/cli';
260
+ *
261
+ * const exitCode = await runNewCommand(['starter-app', '--package-manager', 'pnpm'], {
262
+ * cwd: '/workspace',
263
+ * skipInstall: true,
264
+ * });
265
+ * ```
266
+ *
267
+ * @param argv Command arguments after the `new` or `create` token.
268
+ * @param runtime Optional runtime overrides for prompt resolution, stream output, and scaffold execution.
269
+ * @returns `0` when scaffolding succeeds, otherwise `1` after reporting the failure to `stderr`.
270
+ */
271
+ export async function runNewCommand(argv, runtime = {}) {
272
+ const stdout = runtime.stdout ?? process.stdout;
273
+ const stderr = runtime.stderr ?? process.stderr;
274
+ try {
275
+ if (argv.some(isHelpFlag)) {
276
+ stdout.write(`${newUsage()}\n`);
277
+ return 0;
278
+ }
279
+ const parsed = parseArgs(argv);
280
+ const partialAnswers = {
281
+ ...parsed,
282
+ initializeGit: parsed.initializeGit ?? runtime.initializeGit,
283
+ installDependencies: parsed.installDependencies ?? runtime.installDependencies ?? (runtime.skipInstall === true ? false : undefined)
284
+ };
285
+ if (!partialAnswers.projectName && !(runtime.interactive ?? runtime.prompt ?? runtime.stdin?.isTTY ?? process.stdin.isTTY)) {
286
+ throw new Error(newUsage());
287
+ }
288
+ const answers = await collectBootstrapAnswers(partialAnswers, runtime.cwd ?? process.cwd(), runtime.userAgent, {
289
+ interactive: runtime.interactive,
290
+ prompt: runtime.prompt,
291
+ stdin: runtime.stdin,
292
+ stdout
293
+ });
294
+ const targetDirectory = resolve(runtime.cwd ?? process.cwd(), answers.targetDirectory);
295
+ const options = {
296
+ ...answers,
297
+ dependencySource: runtime.dependencySource,
298
+ force: parsed.force ?? runtime.force,
299
+ initializeGit: answers.initializeGit,
300
+ installDependencies: false,
301
+ repoRoot: runtime.repoRoot,
302
+ skipInstall: true,
303
+ targetDirectory
304
+ };
305
+ const isInteractiveShell = shouldUseInteractiveShell(runtime);
306
+ let scaffoldSpinner;
307
+ if (!answers.installDependencies && !isInteractiveShell) {
308
+ stdout.write('Skipping dependency installation.\n');
309
+ }
310
+ if (isInteractiveShell) {
311
+ scaffoldSpinner = clackSpinner();
312
+ scaffoldSpinner.start('Scaffolding project files');
313
+ }
314
+ await scaffoldBootstrapApp(options);
315
+ if (scaffoldSpinner) {
316
+ scaffoldSpinner.stop('Project files written');
317
+ }
318
+ if (answers.installDependencies) {
319
+ if (!isInteractiveShell) {
320
+ stdout.write(`Installing dependencies with ${answers.packageManager}...\n`);
321
+ await installDependencies(targetDirectory, answers.packageManager, {
322
+ stderr
323
+ });
324
+ } else {
325
+ const installSpinner = clackSpinner();
326
+ installSpinner.start(`Installing dependencies with ${answers.packageManager}`);
327
+ try {
328
+ await installDependencies(targetDirectory, answers.packageManager, {
329
+ stderr,
330
+ stdio: 'capture'
331
+ });
332
+ installSpinner.stop('Dependencies installed');
333
+ } catch (error) {
334
+ installSpinner.error('Dependency installation failed');
335
+ const output = extractDependencyInstallationOutput(error);
336
+ if (output) {
337
+ stderr.write(`\n[fluo] Full installation log:\n${output}${output.endsWith('\n') ? '' : '\n'}`);
338
+ }
339
+ throw error;
340
+ }
341
+ }
342
+ } else if (isInteractiveShell) {
343
+ clackLog.step('Dependency installation skipped');
344
+ }
345
+ stdout.write('Done.\n');
346
+ stdout.write(`Next steps:\n cd ${answers.targetDirectory}\n ${answers.packageManager === 'npm' ? 'npm run dev' : answers.packageManager === 'bun' ? 'bun run dev' : `${answers.packageManager} dev`}\n`);
347
+ return 0;
348
+ } catch (error) {
349
+ const message = error instanceof Error ? error.message : String(error);
350
+ stderr.write(`${message}\n`);
351
+ return 1;
352
+ }
353
+ }
@@ -0,0 +1,21 @@
1
+ /** Describes one file emitted by a generator factory before it is written to disk. */
2
+ export interface GeneratedFile {
3
+ content: string;
4
+ path: string;
5
+ }
6
+ /** Optional generation flags that influence overwrite behavior and sibling-aware templates. */
7
+ export interface GenerateOptions {
8
+ force?: boolean;
9
+ hasRepo?: boolean;
10
+ hasService?: boolean;
11
+ }
12
+ /**
13
+ * Produces the in-memory files for one schematic/resource pair.
14
+ */
15
+ export type GeneratorFactory = (name: string, options?: GenerateOptions) => GeneratedFile[];
16
+ /** Registry shape used by generator manifests to bind a factory to CLI metadata. */
17
+ export interface GeneratorRegistration {
18
+ factory: GeneratorFactory;
19
+ description?: string;
20
+ }
21
+ //# sourceMappingURL=generator-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator-types.d.ts","sourceRoot":"","sources":["../src/generator-types.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,+FAA+F;AAC/F,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,aAAa,EAAE,CAAC;AAE5F,oFAAoF;AACpF,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { GenerateOptions, GeneratedFile } from '../types.js';
2
+ export declare function generateControllerFiles(name: string, options?: GenerateOptions): GeneratedFile[];
3
+ //# sourceMappingURL=controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/generators/controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKlE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,aAAa,EAAE,CAkBpG"}
@@ -0,0 +1,22 @@
1
+ import { renderTemplate } from './render.js';
2
+ import { toKebabCase, toPascalCase } from './utils.js';
3
+ export function generateControllerFiles(name, options = {}) {
4
+ const kebab = toKebabCase(name);
5
+ const resource = toPascalCase(name);
6
+ const pascal = `${resource}Controller`;
7
+ const service = `${resource}Service`;
8
+ const vars = {
9
+ hasService: options.hasService ?? false,
10
+ kebab,
11
+ resource,
12
+ pascal,
13
+ service
14
+ };
15
+ return [{
16
+ content: renderTemplate('controller.ts.ejs', vars),
17
+ path: `${kebab}.controller.ts`
18
+ }, {
19
+ content: renderTemplate('controller.test.ts.ejs', vars),
20
+ path: `${kebab}.controller.test.ts`
21
+ }];
22
+ }
@@ -0,0 +1,3 @@
1
+ import type { GeneratedFile } from '../types.js';
2
+ export declare function generateGuardFiles(name: string): GeneratedFile[];
3
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/generators/guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAShE"}
@@ -0,0 +1,15 @@
1
+ import { renderTemplate } from './render.js';
2
+ import { toKebabCase, toPascalCase } from './utils.js';
3
+ export function generateGuardFiles(name) {
4
+ const kebab = toKebabCase(name);
5
+ const resource = toPascalCase(name);
6
+ const pascal = `${resource}Guard`;
7
+ return [{
8
+ content: renderTemplate('guard.ts.ejs', {
9
+ kebab,
10
+ resource,
11
+ pascal
12
+ }),
13
+ path: `${kebab}.guard.ts`
14
+ }];
15
+ }
@@ -0,0 +1,3 @@
1
+ import type { GeneratedFile } from '../types.js';
2
+ export declare function generateInterceptorFiles(name: string): GeneratedFile[];
3
+ //# sourceMappingURL=interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../../src/generators/interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAStE"}
@@ -0,0 +1,15 @@
1
+ import { renderTemplate } from './render.js';
2
+ import { toKebabCase, toPascalCase } from './utils.js';
3
+ export function generateInterceptorFiles(name) {
4
+ const kebab = toKebabCase(name);
5
+ const resource = toPascalCase(name);
6
+ const pascal = `${resource}Interceptor`;
7
+ return [{
8
+ content: renderTemplate('interceptor.ts.ejs', {
9
+ kebab,
10
+ resource,
11
+ pascal
12
+ }),
13
+ path: `${kebab}.interceptor.ts`
14
+ }];
15
+ }
@@ -0,0 +1,121 @@
1
+ import type { GeneratorFactory } from '../generator-types.js';
2
+ export type ModuleArrayKey = 'controllers' | 'providers' | 'middleware';
3
+ type ModuleRegistrationDescriptor = {
4
+ arrayKey: ModuleArrayKey;
5
+ classSuffix: string;
6
+ };
7
+ export type GeneratorManifestEntry = {
8
+ aliases: readonly string[];
9
+ description: string;
10
+ factory: GeneratorFactory;
11
+ kind: string;
12
+ moduleRegistration?: ModuleRegistrationDescriptor;
13
+ nextStepHint: string;
14
+ registryAliases?: readonly string[];
15
+ schematic: string;
16
+ wiringBehavior: 'auto-registered' | 'files-only';
17
+ };
18
+ export declare const generatorManifest: readonly [{
19
+ readonly aliases: readonly ["co"];
20
+ readonly description: "Generate a controller (auto-registered in the module controllers array).";
21
+ readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
22
+ readonly kind: "controller";
23
+ readonly moduleRegistration: {
24
+ readonly arrayKey: "controllers";
25
+ readonly classSuffix: "Controller";
26
+ };
27
+ readonly nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then add route handlers.";
28
+ readonly schematic: "controller";
29
+ readonly wiringBehavior: "auto-registered";
30
+ }, {
31
+ readonly aliases: readonly ["gu"];
32
+ readonly description: "Generate a guard (auto-registered as a provider in the module).";
33
+ readonly factory: (name: string) => import("../generator-types.js").GeneratedFile[];
34
+ readonly kind: "guard";
35
+ readonly moduleRegistration: {
36
+ readonly arrayKey: "providers";
37
+ readonly classSuffix: "Guard";
38
+ };
39
+ readonly nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then apply the guard to routes.";
40
+ readonly schematic: "guard";
41
+ readonly wiringBehavior: "auto-registered";
42
+ }, {
43
+ readonly aliases: readonly ["in"];
44
+ readonly description: "Generate an interceptor (auto-registered as a provider in the module).";
45
+ readonly factory: (name: string) => import("../generator-types.js").GeneratedFile[];
46
+ readonly kind: "interceptor";
47
+ readonly moduleRegistration: {
48
+ readonly arrayKey: "providers";
49
+ readonly classSuffix: "Interceptor";
50
+ };
51
+ readonly nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then bind the interceptor to routes.";
52
+ readonly schematic: "interceptor";
53
+ readonly wiringBehavior: "auto-registered";
54
+ }, {
55
+ readonly aliases: readonly ["mi"];
56
+ readonly description: "Generate a middleware (auto-registered in the module middleware array).";
57
+ readonly factory: (name: string) => import("../generator-types.js").GeneratedFile[];
58
+ readonly kind: "middleware";
59
+ readonly moduleRegistration: {
60
+ readonly arrayKey: "middleware";
61
+ readonly classSuffix: "Middleware";
62
+ };
63
+ readonly nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then configure route matching in forRoutes.";
64
+ readonly schematic: "middleware";
65
+ readonly wiringBehavior: "auto-registered";
66
+ }, {
67
+ readonly aliases: readonly ["mo"];
68
+ readonly description: "Generate a standalone module (import it in a parent module to activate).";
69
+ readonly factory: (name: string) => import("../generator-types.js").GeneratedFile[];
70
+ readonly kind: "module";
71
+ readonly nextStepHint: "Import the new module in a parent module's imports array, then run 'pnpm typecheck'.";
72
+ readonly schematic: "module";
73
+ readonly wiringBehavior: "files-only";
74
+ }, {
75
+ readonly aliases: readonly ["repo"];
76
+ readonly description: "Generate a persistence-agnostic repository (auto-registered as a provider).";
77
+ readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
78
+ readonly kind: "repo";
79
+ readonly moduleRegistration: {
80
+ readonly arrayKey: "providers";
81
+ readonly classSuffix: "Repo";
82
+ };
83
+ readonly nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then add data-access methods to the repo stub.";
84
+ readonly registryAliases: readonly ["repository"];
85
+ readonly schematic: "repository";
86
+ readonly wiringBehavior: "auto-registered";
87
+ }, {
88
+ readonly aliases: readonly ["req"];
89
+ readonly description: "Generate a request DTO for route-level data binding and validation (files only — wire it into a controller manually).";
90
+ readonly factory: (name: string) => import("../generator-types.js").GeneratedFile[];
91
+ readonly kind: "request-dto";
92
+ readonly nextStepHint: "Import the DTO in a controller and add it as a parameter with @FromBody or @FromQuery.";
93
+ readonly schematic: "request-dto";
94
+ readonly wiringBehavior: "files-only";
95
+ }, {
96
+ readonly aliases: readonly ["res"];
97
+ readonly description: "Generate a response DTO for typed response payloads (files only — use it as a controller return type).";
98
+ readonly factory: (name: string) => import("../generator-types.js").GeneratedFile[];
99
+ readonly kind: "response-dto";
100
+ readonly nextStepHint: "Import the DTO in a controller and use it as the return type for route handlers.";
101
+ readonly schematic: "response-dto";
102
+ readonly wiringBehavior: "files-only";
103
+ }, {
104
+ readonly aliases: readonly ["s"];
105
+ readonly description: "Generate a service (auto-registered as a provider in the module).";
106
+ readonly factory: (name: string, options: import("../generator-types.js").GenerateOptions | undefined) => import("../generator-types.js").GeneratedFile[];
107
+ readonly kind: "service";
108
+ readonly moduleRegistration: {
109
+ readonly arrayKey: "providers";
110
+ readonly classSuffix: "Service";
111
+ };
112
+ readonly nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then implement business logic.";
113
+ readonly schematic: "service";
114
+ readonly wiringBehavior: "auto-registered";
115
+ }];
116
+ export type GeneratorKind = (typeof generatorManifest)[number]['kind'];
117
+ export type GeneratorDefinition = (typeof generatorManifest)[number];
118
+ export declare function findGeneratorDefinition(kind: GeneratorKind): GeneratorDefinition;
119
+ export declare function resolveGeneratorKind(value: string | undefined): GeneratorKind | undefined;
120
+ export {};
121
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/generators/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAY9D,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,YAAY,CAAC;AAExE,KAAK,4BAA4B,GAAG;IAClC,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,CAAC,EAAE,4BAA4B,CAAC;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,iBAAiB,GAAG,YAAY,CAAC;CAClD,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyFwB,CAAC;AAEvD,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AACvE,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAcrE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,aAAa,GAAG,mBAAmB,CAOhF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAMzF"}