@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,130 @@
1
+ import { generateControllerFiles } from './controller.js';
2
+ import { generateGuardFiles } from './guard.js';
3
+ import { generateInterceptorFiles } from './interceptor.js';
4
+ import { generateMiddlewareFiles } from './middleware.js';
5
+ import { generateModuleFiles } from './module.js';
6
+ import { generateRepoFiles } from './repository.js';
7
+ import { generateRequestDtoFiles } from './request-dto.js';
8
+ import { generateResponseDtoFiles } from './response-dto.js';
9
+ import { generateServiceFiles } from './service.js';
10
+ export const generatorManifest = [{
11
+ aliases: ['co'],
12
+ description: 'Generate a controller (auto-registered in the module controllers array).',
13
+ factory: (name, options) => generateControllerFiles(name, options),
14
+ kind: 'controller',
15
+ moduleRegistration: {
16
+ arrayKey: 'controllers',
17
+ classSuffix: 'Controller'
18
+ },
19
+ nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then add route handlers.",
20
+ schematic: 'controller',
21
+ wiringBehavior: 'auto-registered'
22
+ }, {
23
+ aliases: ['gu'],
24
+ description: 'Generate a guard (auto-registered as a provider in the module).',
25
+ factory: name => generateGuardFiles(name),
26
+ kind: 'guard',
27
+ moduleRegistration: {
28
+ arrayKey: 'providers',
29
+ classSuffix: 'Guard'
30
+ },
31
+ nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then apply the guard to routes.",
32
+ schematic: 'guard',
33
+ wiringBehavior: 'auto-registered'
34
+ }, {
35
+ aliases: ['in'],
36
+ description: 'Generate an interceptor (auto-registered as a provider in the module).',
37
+ factory: name => generateInterceptorFiles(name),
38
+ kind: 'interceptor',
39
+ moduleRegistration: {
40
+ arrayKey: 'providers',
41
+ classSuffix: 'Interceptor'
42
+ },
43
+ nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then bind the interceptor to routes.",
44
+ schematic: 'interceptor',
45
+ wiringBehavior: 'auto-registered'
46
+ }, {
47
+ aliases: ['mi'],
48
+ description: 'Generate a middleware (auto-registered in the module middleware array).',
49
+ factory: name => generateMiddlewareFiles(name),
50
+ kind: 'middleware',
51
+ moduleRegistration: {
52
+ arrayKey: 'middleware',
53
+ classSuffix: 'Middleware'
54
+ },
55
+ nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then configure route matching in forRoutes.",
56
+ schematic: 'middleware',
57
+ wiringBehavior: 'auto-registered'
58
+ }, {
59
+ aliases: ['mo'],
60
+ description: 'Generate a standalone module (import it in a parent module to activate).',
61
+ factory: name => generateModuleFiles(name),
62
+ kind: 'module',
63
+ nextStepHint: "Import the new module in a parent module's imports array, then run 'pnpm typecheck'.",
64
+ schematic: 'module',
65
+ wiringBehavior: 'files-only'
66
+ }, {
67
+ aliases: ['repo'],
68
+ description: 'Generate a persistence-agnostic repository (auto-registered as a provider).',
69
+ factory: (name, options) => generateRepoFiles(name, options),
70
+ kind: 'repo',
71
+ moduleRegistration: {
72
+ arrayKey: 'providers',
73
+ classSuffix: 'Repo'
74
+ },
75
+ nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then add data-access methods to the repo stub.",
76
+ registryAliases: ['repository'],
77
+ schematic: 'repository',
78
+ wiringBehavior: 'auto-registered'
79
+ }, {
80
+ aliases: ['req'],
81
+ description: 'Generate a request DTO for route-level data binding and validation (files only — wire it into a controller manually).',
82
+ factory: name => generateRequestDtoFiles(name),
83
+ kind: 'request-dto',
84
+ nextStepHint: 'Import the DTO in a controller and add it as a parameter with @FromBody or @FromQuery.',
85
+ schematic: 'request-dto',
86
+ wiringBehavior: 'files-only'
87
+ }, {
88
+ aliases: ['res'],
89
+ description: 'Generate a response DTO for typed response payloads (files only — use it as a controller return type).',
90
+ factory: name => generateResponseDtoFiles(name),
91
+ kind: 'response-dto',
92
+ nextStepHint: 'Import the DTO in a controller and use it as the return type for route handlers.',
93
+ schematic: 'response-dto',
94
+ wiringBehavior: 'files-only'
95
+ }, {
96
+ aliases: ['s'],
97
+ description: 'Generate a service (auto-registered as a provider in the module).',
98
+ factory: (name, options) => generateServiceFiles(name, options),
99
+ kind: 'service',
100
+ moduleRegistration: {
101
+ arrayKey: 'providers',
102
+ classSuffix: 'Service'
103
+ },
104
+ nextStepHint: "Run 'pnpm typecheck' to verify module wiring, then implement business logic.",
105
+ schematic: 'service',
106
+ wiringBehavior: 'auto-registered'
107
+ }];
108
+ const generatorByKind = new Map();
109
+ const tokenToKind = new Map();
110
+ for (const entry of generatorManifest) {
111
+ generatorByKind.set(entry.kind, entry);
112
+ tokenToKind.set(entry.kind, entry.kind);
113
+ tokenToKind.set(entry.schematic, entry.kind);
114
+ for (const alias of entry.aliases) {
115
+ tokenToKind.set(alias, entry.kind);
116
+ }
117
+ }
118
+ export function findGeneratorDefinition(kind) {
119
+ const entry = generatorByKind.get(kind);
120
+ if (!entry) {
121
+ throw new Error(`Unknown generator kind: ${kind}`);
122
+ }
123
+ return entry;
124
+ }
125
+ export function resolveGeneratorKind(value) {
126
+ if (!value) {
127
+ return undefined;
128
+ }
129
+ return tokenToKind.get(value);
130
+ }
@@ -0,0 +1,3 @@
1
+ import type { GeneratedFile } from '../types.js';
2
+ export declare function generateMiddlewareFiles(name: string): GeneratedFile[];
3
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/generators/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CASrE"}
@@ -0,0 +1,15 @@
1
+ import { renderTemplate } from './render.js';
2
+ import { toKebabCase, toPascalCase } from './utils.js';
3
+ export function generateMiddlewareFiles(name) {
4
+ const kebab = toKebabCase(name);
5
+ const resource = toPascalCase(name);
6
+ const pascal = `${resource}Middleware`;
7
+ return [{
8
+ content: renderTemplate('middleware.ts.ejs', {
9
+ kebab,
10
+ resource,
11
+ pascal
12
+ }),
13
+ path: `${kebab}.middleware.ts`
14
+ }];
15
+ }
@@ -0,0 +1,6 @@
1
+ import type { GeneratedFile } from '../types.js';
2
+ import type { ModuleArrayKey } from './manifest.js';
3
+ export declare function ensureModuleImport(source: string, className: string, importPath: string): string;
4
+ export declare function generateModuleFiles(name: string): GeneratedFile[];
5
+ export declare function registerInModule(source: string, arrayKey: ModuleArrayKey, className: string): string;
6
+ //# sourceMappingURL=module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/generators/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AA6FpD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAyDhG;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAUjE;AAwDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpG"}
@@ -0,0 +1,143 @@
1
+ import ts from 'typescript';
2
+ import { renderTemplate } from './render.js';
3
+ import { toKebabCase, toPascalCase } from './utils.js';
4
+ const printer = ts.createPrinter({
5
+ newLine: ts.NewLineKind.LineFeed
6
+ });
7
+ function parseSource(source) {
8
+ return ts.createSourceFile('generated.module.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
9
+ }
10
+ function getPropertyNameText(name) {
11
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name)) {
12
+ return name.text;
13
+ }
14
+ return undefined;
15
+ }
16
+ function replaceNodeText(source, sourceFile, node, replacement) {
17
+ return source.slice(0, node.getStart(sourceFile)) + replacement + source.slice(node.getEnd());
18
+ }
19
+ function findModuleDecoratorObject(sourceFile) {
20
+ for (const statement of sourceFile.statements) {
21
+ if (!ts.isClassDeclaration(statement)) {
22
+ continue;
23
+ }
24
+ if (!ts.canHaveDecorators(statement)) {
25
+ continue;
26
+ }
27
+ const decorators = ts.getDecorators(statement);
28
+ if (!decorators) {
29
+ continue;
30
+ }
31
+ for (const decorator of decorators) {
32
+ if (!ts.isCallExpression(decorator.expression)) {
33
+ continue;
34
+ }
35
+ if (!ts.isIdentifier(decorator.expression.expression) || decorator.expression.expression.text !== 'Module') {
36
+ continue;
37
+ }
38
+ const [firstArgument] = decorator.expression.arguments;
39
+ if (firstArgument && ts.isObjectLiteralExpression(firstArgument)) {
40
+ return firstArgument;
41
+ }
42
+ }
43
+ }
44
+ return undefined;
45
+ }
46
+ function findNamedImportSource(sourceFile, className) {
47
+ for (const statement of sourceFile.statements) {
48
+ if (!ts.isImportDeclaration(statement)) {
49
+ continue;
50
+ }
51
+ const importClause = statement.importClause;
52
+ if (!importClause?.namedBindings || !ts.isNamedImports(importClause.namedBindings)) {
53
+ continue;
54
+ }
55
+ if (!importClause.namedBindings.elements.some(element => element.name.text === className)) {
56
+ continue;
57
+ }
58
+ if (ts.isStringLiteral(statement.moduleSpecifier)) {
59
+ return statement.moduleSpecifier.text;
60
+ }
61
+ }
62
+ return undefined;
63
+ }
64
+ function buildImportDeclaration(className, importPath) {
65
+ return ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(className))])), ts.factory.createStringLiteral(`./${importPath}`));
66
+ }
67
+ export function ensureModuleImport(source, className, importPath) {
68
+ const sourceFile = parseSource(source);
69
+ const moduleSpecifier = `./${importPath}`;
70
+ const existingImportSource = findNamedImportSource(sourceFile, className);
71
+ if (existingImportSource) {
72
+ if (existingImportSource === moduleSpecifier) {
73
+ return source;
74
+ }
75
+ throw new Error(`Import collision for ${className}: already imported from "${existingImportSource}" but requested from "${moduleSpecifier}".`);
76
+ }
77
+ for (const statement of sourceFile.statements) {
78
+ if (!ts.isImportDeclaration(statement)) {
79
+ continue;
80
+ }
81
+ if (!ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== moduleSpecifier) {
82
+ continue;
83
+ }
84
+ const importClause = statement.importClause;
85
+ if (!importClause?.namedBindings || !ts.isNamedImports(importClause.namedBindings)) {
86
+ continue;
87
+ }
88
+ const updatedImport = ts.factory.updateImportDeclaration(statement, statement.modifiers, ts.factory.updateImportClause(importClause, importClause.isTypeOnly, importClause.name, ts.factory.updateNamedImports(importClause.namedBindings, [...importClause.namedBindings.elements, ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(className))])), statement.moduleSpecifier, statement.attributes);
89
+ return replaceNodeText(source, sourceFile, statement, printer.printNode(ts.EmitHint.Unspecified, updatedImport, sourceFile));
90
+ }
91
+ const newImportLine = printer.printNode(ts.EmitHint.Unspecified, buildImportDeclaration(className, importPath), sourceFile);
92
+ const imports = sourceFile.statements.filter(ts.isImportDeclaration);
93
+ if (imports.length > 0) {
94
+ const lastImport = imports[imports.length - 1];
95
+ return `${source.slice(0, lastImport.getEnd())}\n${newImportLine}${source.slice(lastImport.getEnd())}`;
96
+ }
97
+ return `${newImportLine}\n${source}`;
98
+ }
99
+ export function generateModuleFiles(name) {
100
+ const kebab = toKebabCase(name);
101
+ const pascal = `${toPascalCase(name)}Module`;
102
+ return [{
103
+ content: renderTemplate('module.ts.ejs', {
104
+ kebab,
105
+ pascal
106
+ }),
107
+ path: `${kebab}.module.ts`
108
+ }];
109
+ }
110
+ function insertIntoModuleArray(source, arrayKey, className) {
111
+ const sourceFile = parseSource(source);
112
+ const moduleMetadata = findModuleDecoratorObject(sourceFile);
113
+ if (!moduleMetadata) {
114
+ throw new Error('Unable to locate @Module metadata object in module file.');
115
+ }
116
+ let alreadyPresent = false;
117
+ let hasTargetProperty = false;
118
+ const updatedProperties = moduleMetadata.properties.map(property => {
119
+ if (!ts.isPropertyAssignment(property) || getPropertyNameText(property.name) !== arrayKey) {
120
+ return property;
121
+ }
122
+ hasTargetProperty = true;
123
+ if (!ts.isArrayLiteralExpression(property.initializer)) {
124
+ throw new Error(`Invalid @Module metadata: "${arrayKey}" must be an array.`);
125
+ }
126
+ if (property.initializer.elements.some(element => element.getText(sourceFile) === className)) {
127
+ alreadyPresent = true;
128
+ return property;
129
+ }
130
+ return ts.factory.updatePropertyAssignment(property, property.name, ts.factory.updateArrayLiteralExpression(property.initializer, [...property.initializer.elements, ts.factory.createIdentifier(className)]));
131
+ });
132
+ if (alreadyPresent) {
133
+ return source;
134
+ }
135
+ if (!hasTargetProperty) {
136
+ updatedProperties.push(ts.factory.createPropertyAssignment(ts.factory.createIdentifier(arrayKey), ts.factory.createArrayLiteralExpression([ts.factory.createIdentifier(className)], true)));
137
+ }
138
+ const updatedModuleMetadata = ts.factory.updateObjectLiteralExpression(moduleMetadata, updatedProperties);
139
+ return replaceNodeText(source, sourceFile, moduleMetadata, printer.printNode(ts.EmitHint.Unspecified, updatedModuleMetadata, sourceFile));
140
+ }
141
+ export function registerInModule(source, arrayKey, className) {
142
+ return insertIntoModuleArray(source, arrayKey, className);
143
+ }
@@ -0,0 +1,2 @@
1
+ export declare function renderTemplate(templateName: string, vars: Record<string, unknown>): string;
2
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/generators/render.ts"],"names":[],"mappings":"AAQA,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAU1F"}
@@ -0,0 +1,17 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import ejs from 'ejs';
5
+ const templatesDir = join(dirname(fileURLToPath(import.meta.url)), 'templates');
6
+ const templateCache = new Map();
7
+ export function renderTemplate(templateName, vars) {
8
+ const templatePath = join(templatesDir, templateName);
9
+ let template = templateCache.get(templatePath);
10
+ if (!template) {
11
+ template = readFileSync(templatePath, 'utf8');
12
+ templateCache.set(templatePath, template);
13
+ }
14
+ return ejs.render(template, vars, {
15
+ escape: s => String(s)
16
+ });
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { GenerateOptions, GeneratedFile } from '../types.js';
2
+ export declare function generateRepoFiles(name: string, _options?: GenerateOptions): GeneratedFile[];
3
+ //# sourceMappingURL=repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/generators/repository.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKlE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,eAAoB,GAAG,aAAa,EAAE,CAmB/F"}
@@ -0,0 +1,29 @@
1
+ import { renderTemplate } from './render.js';
2
+ import { toKebabCase, toPascalCase } from './utils.js';
3
+ export function generateRepoFiles(name, _options = {}) {
4
+ const kebab = toKebabCase(name);
5
+ const resource = toPascalCase(name);
6
+ const pascal = `${resource}Repo`;
7
+ return [{
8
+ content: renderTemplate('repository.ts.ejs', {
9
+ kebab,
10
+ resource,
11
+ pascal
12
+ }),
13
+ path: `${kebab}.repo.ts`
14
+ }, {
15
+ content: renderTemplate('repository.test.ts.ejs', {
16
+ kebab,
17
+ resource,
18
+ pascal
19
+ }),
20
+ path: `${kebab}.repo.test.ts`
21
+ }, {
22
+ content: renderTemplate('repository.slice.test.ts.ejs', {
23
+ kebab,
24
+ resource,
25
+ pascal
26
+ }),
27
+ path: `${kebab}.repo.slice.test.ts`
28
+ }];
29
+ }
@@ -0,0 +1,3 @@
1
+ import type { GeneratedFile } from '../types.js';
2
+ export declare function generateRequestDtoFiles(name: string): GeneratedFile[];
3
+ //# sourceMappingURL=request-dto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-dto.d.ts","sourceRoot":"","sources":["../../src/generators/request-dto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAYrE"}
@@ -0,0 +1,17 @@
1
+ import { renderTemplate } from './render.js';
2
+ import { toKebabCase, toPascalCase } from './utils.js';
3
+ export function generateRequestDtoFiles(name) {
4
+ const kebab = toKebabCase(name);
5
+ const resource = toPascalCase(name);
6
+ const pascal = `${resource}RequestDto`;
7
+ const bodyField = resource.charAt(0).toLowerCase() + resource.slice(1);
8
+ return [{
9
+ content: renderTemplate('request-dto.ts.ejs', {
10
+ kebab,
11
+ resource,
12
+ pascal,
13
+ bodyField
14
+ }),
15
+ path: `${kebab}.request.dto.ts`
16
+ }];
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { GeneratedFile } from '../types.js';
2
+ export declare function generateResponseDtoFiles(name: string): GeneratedFile[];
3
+ //# sourceMappingURL=response-dto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-dto.d.ts","sourceRoot":"","sources":["../../src/generators/response-dto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAYtE"}
@@ -0,0 +1,17 @@
1
+ import { renderTemplate } from './render.js';
2
+ import { toKebabCase, toPascalCase } from './utils.js';
3
+ export function generateResponseDtoFiles(name) {
4
+ const kebab = toKebabCase(name);
5
+ const resource = toPascalCase(name);
6
+ const pascal = `${resource}ResponseDto`;
7
+ const field = resource.charAt(0).toLowerCase() + resource.slice(1);
8
+ return [{
9
+ content: renderTemplate('response-dto.ts.ejs', {
10
+ kebab,
11
+ resource,
12
+ pascal,
13
+ field
14
+ }),
15
+ path: `${kebab}.response.dto.ts`
16
+ }];
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { GenerateOptions, GeneratedFile } from '../types.js';
2
+ export declare function generateServiceFiles(name: string, _options?: GenerateOptions): GeneratedFile[];
3
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/generators/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKlE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,eAAoB,GAAG,aAAa,EAAE,CAkBlG"}
@@ -0,0 +1,22 @@
1
+ import { renderTemplate } from './render.js';
2
+ import { toKebabCase, toPascalCase } from './utils.js';
3
+ export function generateServiceFiles(name, _options = {}) {
4
+ const kebab = toKebabCase(name);
5
+ const resource = toPascalCase(name);
6
+ const pascal = `${resource}Service`;
7
+ const repo = `${resource}Repo`;
8
+ const vars = {
9
+ hasRepo: _options.hasRepo ?? false,
10
+ kebab,
11
+ resource,
12
+ pascal,
13
+ repo
14
+ };
15
+ return [{
16
+ content: renderTemplate('service.ts.ejs', vars),
17
+ path: `${kebab}.service.ts`
18
+ }, {
19
+ content: renderTemplate('service.test.ts.ejs', vars),
20
+ path: `${kebab}.service.test.ts`
21
+ }];
22
+ }
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { <%- pascal %> } from './<%- kebab %>.controller';
4
+
5
+ <% if (hasService) { %>
6
+ class Fake<%- service %> {
7
+ async list<%- resource %>s() {
8
+ return [{ id: '<%- kebab %>-1' }];
9
+ }
10
+ }
11
+ <% } %>
12
+
13
+ describe('<%- pascal %>', () => {
14
+ it('delegates to the service', async () => {
15
+ <% if (hasService) { %>
16
+ await expect(new <%- pascal %>(new Fake<%- service %>() as never).list<%- resource %>s()).resolves.toEqual([{ id: '<%- kebab %>-1' }]);
17
+ <% } else { %>
18
+ await expect(new <%- pascal %>().list<%- resource %>s()).resolves.toEqual([]);
19
+ <% } %>
20
+ });
21
+ });
@@ -0,0 +1,29 @@
1
+ <% if (hasService) { %>
2
+ import { Inject } from '@fluojs/core';
3
+ <% } %>
4
+ import { Controller, Get } from '@fluojs/http';
5
+
6
+ <% if (hasService) { %>
7
+ import { <%- service %> } from './<%- kebab %>.service';
8
+ <% } %>
9
+
10
+ @Controller('/<%- kebab %>')
11
+ <% if (hasService) { %>
12
+ @Inject(<%- service %>)
13
+ <% } %>
14
+ class <%- pascal %> {
15
+ <% if (hasService) { %>
16
+ constructor(private readonly service: <%- service %>) {}
17
+ <% } %>
18
+
19
+ @Get('/')
20
+ async list<%- resource %>s() {
21
+ <% if (hasService) { %>
22
+ return this.service.list<%- resource %>s();
23
+ <% } else { %>
24
+ return [];
25
+ <% } %>
26
+ }
27
+ }
28
+
29
+ export { <%- pascal %> };
@@ -0,0 +1,7 @@
1
+ import type { Guard, GuardContext } from '@fluojs/http';
2
+
3
+ export class <%- pascal %> implements Guard {
4
+ canActivate(context: GuardContext): boolean {
5
+ return true;
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ import type { CallHandler, Interceptor, InterceptorContext } from '@fluojs/http';
2
+
3
+ export class <%- pascal %> implements Interceptor {
4
+ async intercept(context: InterceptorContext, next: CallHandler): Promise<unknown> {
5
+ return next.handle();
6
+ }
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { Middleware, MiddlewareContext, MiddlewareRouteConfig, Next } from '@fluojs/http';
2
+
3
+ export class <%- pascal %> implements Middleware {
4
+ static forRoutes(...routes: string[]): MiddlewareRouteConfig {
5
+ return { middleware: <%- pascal %>, routes };
6
+ }
7
+
8
+ async handle(context: MiddlewareContext, next: Next): Promise<void> {
9
+ await next();
10
+ }
11
+ }
@@ -0,0 +1,9 @@
1
+ import { Module } from '@fluojs/core';
2
+
3
+ @Module({
4
+ controllers: [],
5
+ providers: [],
6
+ })
7
+ class <%- pascal %> {}
8
+
9
+ export { <%- pascal %> };
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { createTestingModule } from '@fluojs/testing';
4
+
5
+ import { <%- resource %>Module } from './<%- kebab %>.module';
6
+ import { <%- pascal %> } from './<%- kebab %>.repo';
7
+
8
+ describe('<%- pascal %> slice template', () => {
9
+ it('resolves from the module graph', async () => {
10
+ const testingModule = await createTestingModule({ rootModule: <%- resource %>Module }).compile();
11
+ const repo = await testingModule.resolve(<%- pascal %>);
12
+
13
+ await expect(repo.list<%- resource %>s()).resolves.toEqual([]);
14
+ });
15
+ });
@@ -0,0 +1,9 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { <%- pascal %> } from './<%- kebab %>.repo';
4
+
5
+ describe('<%- pascal %>', () => {
6
+ it('exposes a list method', () => {
7
+ expect(typeof <%- pascal %>.prototype.list<%- resource %>s).toBe('function');
8
+ });
9
+ });
@@ -0,0 +1,10 @@
1
+
2
+ type <%- resource %>Record = {
3
+ id: string;
4
+ };
5
+
6
+ export class <%- resource %>Repo {
7
+ async list<%- resource %>s(): Promise<<%- resource %>Record[]> {
8
+ return [];
9
+ }
10
+ }
@@ -0,0 +1,9 @@
1
+ import { IsString, MinLength } from '@fluojs/validation';
2
+ import { FromBody } from '@fluojs/http';
3
+
4
+ export class <%- pascal %> {
5
+ @FromBody('<%- bodyField %>')
6
+ @IsString()
7
+ @MinLength(1, { message: '<%- bodyField %> is required' })
8
+ <%- bodyField %> = '';
9
+ }
@@ -0,0 +1,3 @@
1
+ export class <%- pascal %> {
2
+ <%- field %>!: string;
3
+ }
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { <%- pascal %> } from './<%- kebab %>.service';
4
+
5
+ <% if (hasRepo) { %>
6
+ class Fake<%- repo %> {
7
+ list<%- resource %>s() {
8
+ return [{ id: '<%- kebab %>-1' }];
9
+ }
10
+ }
11
+ <% } %>
12
+
13
+ describe('<%- pascal %>', () => {
14
+ it('delegates to the repo', async () => {
15
+ <% if (hasRepo) { %>
16
+ await expect(new <%- pascal %>(new Fake<%- repo %>() as never).list<%- resource %>s()).resolves.toEqual([{ id: '<%- kebab %>-1' }]);
17
+ <% } else { %>
18
+ await expect(new <%- pascal %>().list<%- resource %>s()).resolves.toEqual([]);
19
+ <% } %>
20
+ });
21
+ });
@@ -0,0 +1,24 @@
1
+ <% if (hasRepo) { %>
2
+ import { Inject } from '@fluojs/core';
3
+ <% } %>
4
+
5
+ <% if (hasRepo) { %>
6
+ import { <%- repo %> } from './<%- kebab %>.repo';
7
+ <% } %>
8
+
9
+ <% if (hasRepo) { %>
10
+ @Inject(<%- repo %>)
11
+ <% } %>
12
+ export class <%- pascal %> {
13
+ <% if (hasRepo) { %>
14
+ constructor(private readonly repo: <%- repo %>) {}
15
+ <% } %>
16
+
17
+ async list<%- resource %>s() {
18
+ <% if (hasRepo) { %>
19
+ return this.repo.list<%- resource %>s();
20
+ <% } else { %>
21
+ return [];
22
+ <% } %>
23
+ }
24
+ }