@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.
- package/LICENSE +21 -0
- package/README.ko.md +155 -0
- package/README.md +155 -0
- package/bin/fluo.mjs +5 -0
- package/dist/cli.d.ts +37 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +292 -0
- package/dist/commands/generate.d.ts +40 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +134 -0
- package/dist/commands/inspect.d.ts +30 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +221 -0
- package/dist/commands/migrate.d.ts +30 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +173 -0
- package/dist/commands/new.d.ts +45 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +353 -0
- package/dist/generator-types.d.ts +21 -0
- package/dist/generator-types.d.ts.map +1 -0
- package/dist/generator-types.js +1 -0
- package/dist/generators/controller.d.ts +3 -0
- package/dist/generators/controller.d.ts.map +1 -0
- package/dist/generators/controller.js +22 -0
- package/dist/generators/guard.d.ts +3 -0
- package/dist/generators/guard.d.ts.map +1 -0
- package/dist/generators/guard.js +15 -0
- package/dist/generators/interceptor.d.ts +3 -0
- package/dist/generators/interceptor.d.ts.map +1 -0
- package/dist/generators/interceptor.js +15 -0
- package/dist/generators/manifest.d.ts +121 -0
- package/dist/generators/manifest.d.ts.map +1 -0
- package/dist/generators/manifest.js +130 -0
- package/dist/generators/middleware.d.ts +3 -0
- package/dist/generators/middleware.d.ts.map +1 -0
- package/dist/generators/middleware.js +15 -0
- package/dist/generators/module.d.ts +6 -0
- package/dist/generators/module.d.ts.map +1 -0
- package/dist/generators/module.js +143 -0
- package/dist/generators/render.d.ts +2 -0
- package/dist/generators/render.d.ts.map +1 -0
- package/dist/generators/render.js +17 -0
- package/dist/generators/repository.d.ts +3 -0
- package/dist/generators/repository.d.ts.map +1 -0
- package/dist/generators/repository.js +29 -0
- package/dist/generators/request-dto.d.ts +3 -0
- package/dist/generators/request-dto.d.ts.map +1 -0
- package/dist/generators/request-dto.js +17 -0
- package/dist/generators/response-dto.d.ts +3 -0
- package/dist/generators/response-dto.d.ts.map +1 -0
- package/dist/generators/response-dto.js +17 -0
- package/dist/generators/service.d.ts +3 -0
- package/dist/generators/service.d.ts.map +1 -0
- package/dist/generators/service.js +22 -0
- package/dist/generators/templates/controller.test.ts.ejs +21 -0
- package/dist/generators/templates/controller.ts.ejs +29 -0
- package/dist/generators/templates/guard.ts.ejs +7 -0
- package/dist/generators/templates/interceptor.ts.ejs +7 -0
- package/dist/generators/templates/middleware.ts.ejs +11 -0
- package/dist/generators/templates/module.ts.ejs +9 -0
- package/dist/generators/templates/repository.slice.test.ts.ejs +15 -0
- package/dist/generators/templates/repository.test.ts.ejs +9 -0
- package/dist/generators/templates/repository.ts.ejs +10 -0
- package/dist/generators/templates/request-dto.ts.ejs +9 -0
- package/dist/generators/templates/response-dto.ts.ejs +3 -0
- package/dist/generators/templates/service.test.ts.ejs +21 -0
- package/dist/generators/templates/service.ts.ejs +24 -0
- package/dist/generators/utils.d.ts +4 -0
- package/dist/generators/utils.d.ts.map +1 -0
- package/dist/generators/utils.js +18 -0
- package/dist/help.d.ts +8 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +16 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/new/install.d.ts +51 -0
- package/dist/new/install.d.ts.map +1 -0
- package/dist/new/install.js +140 -0
- package/dist/new/package-spec-resolver.d.ts +4 -0
- package/dist/new/package-spec-resolver.d.ts.map +1 -0
- package/dist/new/package-spec-resolver.js +397 -0
- package/dist/new/prompt.d.ts +56 -0
- package/dist/new/prompt.d.ts.map +1 -0
- package/dist/new/prompt.js +278 -0
- package/dist/new/resolver.d.ts +32 -0
- package/dist/new/resolver.d.ts.map +1 -0
- package/dist/new/resolver.js +93 -0
- package/dist/new/scaffold.d.ts +14 -0
- package/dist/new/scaffold.d.ts.map +1 -0
- package/dist/new/scaffold.js +2010 -0
- package/dist/new/starter-profiles.d.ts +91 -0
- package/dist/new/starter-profiles.d.ts.map +1 -0
- package/dist/new/starter-profiles.js +347 -0
- package/dist/new/types.d.ts +63 -0
- package/dist/new/types.d.ts.map +1 -0
- package/dist/new/types.js +1 -0
- package/dist/registry.d.ts +10 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +30 -0
- package/dist/transforms/nestjs-migrate.d.ts +33 -0
- package/dist/transforms/nestjs-migrate.d.ts.map +1 -0
- package/dist/transforms/nestjs-migrate.js +891 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { GenerateOptions, GeneratorKind } from '../types.js';
|
|
2
|
+
import type { GeneratorManifestEntry } from '../generators/manifest.js';
|
|
3
|
+
/**
|
|
4
|
+
* Structured result returned by {@link runGenerateCommand} for tooling-friendly automation.
|
|
5
|
+
*
|
|
6
|
+
* `generatedFiles` only includes files whose on-disk content changed during the command.
|
|
7
|
+
* `moduleRegistered` reports whether the target schematic participates in automatic module wiring,
|
|
8
|
+
* even when the target module file was already up to date.
|
|
9
|
+
*/
|
|
10
|
+
export type GenerateResult = {
|
|
11
|
+
generatedFiles: string[];
|
|
12
|
+
moduleRegistered: boolean;
|
|
13
|
+
modulePath: string | undefined;
|
|
14
|
+
nextStepHint: string;
|
|
15
|
+
wiringBehavior: GeneratorManifestEntry['wiringBehavior'];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Generates one CLI schematic into a source directory and returns structured wiring metadata.
|
|
19
|
+
*
|
|
20
|
+
* The command keeps generation idempotent where possible: unchanged files are not rewritten, and
|
|
21
|
+
* auto-registered schematics reuse an existing module file when it already contains the required import
|
|
22
|
+
* and registration entry.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const result = runGenerateCommand('service', 'Post', './src');
|
|
27
|
+
*
|
|
28
|
+
* console.log(result.wiringBehavior);
|
|
29
|
+
* console.log(result.nextStepHint);
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @param kind Generator kind to execute.
|
|
33
|
+
* @param name Resource name supplied by the caller before normalization.
|
|
34
|
+
* @param baseDirectory Source directory that should receive the generated domain folder.
|
|
35
|
+
* @param options Optional generation flags that control overwrites and sibling-aware templates.
|
|
36
|
+
* @returns Structured file and wiring metadata for the completed generation run.
|
|
37
|
+
* @throws {Error} When the resource name is invalid, the generator kind is unknown, or the target module source cannot be updated safely.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runGenerateCommand(kind: GeneratorKind, name: string, baseDirectory: string, options?: GenerateOptions): GenerateResult;
|
|
40
|
+
//# sourceMappingURL=generate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,sBAAsB,EAAkB,MAAM,2BAA2B,CAAC;AA+FxF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;CAC1D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,cAAc,CAiD1I"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join, normalize, resolve } from 'node:path';
|
|
3
|
+
import { findGeneratorDefinition } from '../generators/manifest.js';
|
|
4
|
+
import { ensureModuleImport, generateModuleFiles, registerInModule } from '../generators/module.js';
|
|
5
|
+
import { toKebabCase, toPascalCase, toPlural } from '../generators/utils.js';
|
|
6
|
+
function writeFileIfChanged(filePath, content) {
|
|
7
|
+
if (existsSync(filePath) && readFileSync(filePath, 'utf8') === content) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
writeFileSync(filePath, content, 'utf8');
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
function createGeneratorOptions(kind, domainDirectory, kebab, options) {
|
|
14
|
+
return {
|
|
15
|
+
...options,
|
|
16
|
+
hasRepo: options.hasRepo ?? (kind === 'service' ? existsSync(join(domainDirectory, `${kebab}.repo.ts`)) : undefined),
|
|
17
|
+
hasService: options.hasService ?? (kind === 'controller' ? existsSync(join(domainDirectory, `${kebab}.service.ts`)) : undefined)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function assertValidResourceName(name) {
|
|
21
|
+
const kebab = toKebabCase(name);
|
|
22
|
+
if (name.trim().length === 0) {
|
|
23
|
+
throw new Error('Invalid resource name: name must not be empty.');
|
|
24
|
+
}
|
|
25
|
+
if (kebab !== normalize(kebab) || kebab.includes('/') || kebab.includes('\\') || kebab.includes('..')) {
|
|
26
|
+
throw new Error(`Invalid resource name "${name}": must not contain path separators or traversal sequences.`);
|
|
27
|
+
}
|
|
28
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(kebab)) {
|
|
29
|
+
throw new Error(`Invalid resource name "${name}": use letters, numbers, spaces, underscores, or hyphens only.`);
|
|
30
|
+
}
|
|
31
|
+
return kebab;
|
|
32
|
+
}
|
|
33
|
+
function resolveModulePath(domainDirectory, name) {
|
|
34
|
+
const kebab = toKebabCase(name);
|
|
35
|
+
return join(domainDirectory, `${kebab}.module.ts`);
|
|
36
|
+
}
|
|
37
|
+
function readOrCreateModuleSource(modulePath, name) {
|
|
38
|
+
if (existsSync(modulePath)) {
|
|
39
|
+
return readFileSync(modulePath, 'utf8');
|
|
40
|
+
}
|
|
41
|
+
const [moduleFile] = generateModuleFiles(name);
|
|
42
|
+
if (!moduleFile) {
|
|
43
|
+
throw new Error(`Unable to generate module file for resource "${name}".`);
|
|
44
|
+
}
|
|
45
|
+
return moduleFile.content;
|
|
46
|
+
}
|
|
47
|
+
function buildUpdatedModuleSource(moduleSource, arrayKey, className, importPath) {
|
|
48
|
+
let source = moduleSource;
|
|
49
|
+
source = ensureModuleImport(source, className, importPath);
|
|
50
|
+
source = registerInModule(source, arrayKey, className);
|
|
51
|
+
return source;
|
|
52
|
+
}
|
|
53
|
+
function prepareModuleUpdate(domainDirectory, normalizedName, kind, classSuffix, arrayKey) {
|
|
54
|
+
const kebab = toKebabCase(normalizedName);
|
|
55
|
+
const modulePath = resolveModulePath(domainDirectory, normalizedName);
|
|
56
|
+
const className = `${toPascalCase(normalizedName)}${classSuffix}`;
|
|
57
|
+
const importPath = `${kebab}.${kind}`;
|
|
58
|
+
const moduleSource = readOrCreateModuleSource(modulePath, normalizedName);
|
|
59
|
+
return {
|
|
60
|
+
modulePath,
|
|
61
|
+
source: buildUpdatedModuleSource(moduleSource, arrayKey, className, importPath)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Structured result returned by {@link runGenerateCommand} for tooling-friendly automation.
|
|
67
|
+
*
|
|
68
|
+
* `generatedFiles` only includes files whose on-disk content changed during the command.
|
|
69
|
+
* `moduleRegistered` reports whether the target schematic participates in automatic module wiring,
|
|
70
|
+
* even when the target module file was already up to date.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Generates one CLI schematic into a source directory and returns structured wiring metadata.
|
|
75
|
+
*
|
|
76
|
+
* The command keeps generation idempotent where possible: unchanged files are not rewritten, and
|
|
77
|
+
* auto-registered schematics reuse an existing module file when it already contains the required import
|
|
78
|
+
* and registration entry.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const result = runGenerateCommand('service', 'Post', './src');
|
|
83
|
+
*
|
|
84
|
+
* console.log(result.wiringBehavior);
|
|
85
|
+
* console.log(result.nextStepHint);
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @param kind Generator kind to execute.
|
|
89
|
+
* @param name Resource name supplied by the caller before normalization.
|
|
90
|
+
* @param baseDirectory Source directory that should receive the generated domain folder.
|
|
91
|
+
* @param options Optional generation flags that control overwrites and sibling-aware templates.
|
|
92
|
+
* @returns Structured file and wiring metadata for the completed generation run.
|
|
93
|
+
* @throws {Error} When the resource name is invalid, the generator kind is unknown, or the target module source cannot be updated safely.
|
|
94
|
+
*/
|
|
95
|
+
export function runGenerateCommand(kind, name, baseDirectory, options = {}) {
|
|
96
|
+
const normalizedName = name.trim();
|
|
97
|
+
const kebab = assertValidResourceName(normalizedName);
|
|
98
|
+
const generator = findGeneratorDefinition(kind);
|
|
99
|
+
const resolvedBase = resolve(baseDirectory);
|
|
100
|
+
const domainDirectory = join(resolvedBase, toPlural(kebab));
|
|
101
|
+
const generatorOptions = createGeneratorOptions(kind, domainDirectory, kebab, options);
|
|
102
|
+
const files = generator.factory(normalizedName, generatorOptions);
|
|
103
|
+
const moduleRegistration = 'moduleRegistration' in generator ? generator.moduleRegistration : undefined;
|
|
104
|
+
const moduleUpdate = moduleRegistration ? prepareModuleUpdate(domainDirectory, normalizedName, kind, moduleRegistration.classSuffix, moduleRegistration.arrayKey) : undefined;
|
|
105
|
+
mkdirSync(domainDirectory, {
|
|
106
|
+
recursive: true
|
|
107
|
+
});
|
|
108
|
+
const writtenPaths = files.map(file => {
|
|
109
|
+
const filePath = join(domainDirectory, file.path);
|
|
110
|
+
if (!options.force && existsSync(filePath)) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return writeFileIfChanged(filePath, file.content) ? filePath : null;
|
|
114
|
+
}).filter(filePath => filePath !== null);
|
|
115
|
+
let moduleRegistered = false;
|
|
116
|
+
let resolvedModulePath;
|
|
117
|
+
if (moduleUpdate && writeFileIfChanged(moduleUpdate.modulePath, moduleUpdate.source)) {
|
|
118
|
+
moduleRegistered = true;
|
|
119
|
+
resolvedModulePath = moduleUpdate.modulePath;
|
|
120
|
+
if (!writtenPaths.includes(moduleUpdate.modulePath)) {
|
|
121
|
+
writtenPaths.push(moduleUpdate.modulePath);
|
|
122
|
+
}
|
|
123
|
+
} else if (moduleUpdate) {
|
|
124
|
+
moduleRegistered = true;
|
|
125
|
+
resolvedModulePath = moduleUpdate.modulePath;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
generatedFiles: writtenPaths,
|
|
129
|
+
moduleRegistered: moduleRegistered,
|
|
130
|
+
modulePath: resolvedModulePath,
|
|
131
|
+
nextStepHint: generator.nextStepHint,
|
|
132
|
+
wiringBehavior: generator.wiringBehavior
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type CliStream = {
|
|
2
|
+
write(message: string): unknown;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Runtime options for the inspect command when used programmatically.
|
|
6
|
+
*/
|
|
7
|
+
export interface InspectCommandRuntimeOptions {
|
|
8
|
+
/** Current working directory for module resolution. */
|
|
9
|
+
cwd?: string;
|
|
10
|
+
/** Custom stream for error output. */
|
|
11
|
+
stderr?: CliStream;
|
|
12
|
+
/** Custom stream for standard output. */
|
|
13
|
+
stdout?: CliStream;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns the usage information string for the inspect command.
|
|
17
|
+
*
|
|
18
|
+
* @returns Formatted help text including usage and options.
|
|
19
|
+
*/
|
|
20
|
+
export declare function inspectUsage(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Executes the inspect command to visualize the application module graph.
|
|
23
|
+
*
|
|
24
|
+
* @param argv Command line arguments.
|
|
25
|
+
* @param runtime Optional custom runtime configuration for output streams and working directory.
|
|
26
|
+
* @returns Exit code (0 for success, 1 for failure).
|
|
27
|
+
*/
|
|
28
|
+
export declare function runInspectCommand(argv: string[], runtime?: InspectCommandRuntimeOptions): Promise<number>;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=inspect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"AAcA,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,yCAAyC;IACzC,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAgDD;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAarC;AA8ID;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,MAAM,CAAC,CAkEnH"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { FluoFactory, PLATFORM_SHELL } from '@fluojs/runtime';
|
|
4
|
+
import { renderAliasList, renderHelpTable } from '../help.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Runtime options for the inspect command when used programmatically.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const INSPECT_OPTION_HELP = [{
|
|
11
|
+
aliases: [],
|
|
12
|
+
description: 'Emit the runtime platform snapshot/diagnostics payload as JSON (default when no output mode is selected).',
|
|
13
|
+
option: '--json'
|
|
14
|
+
}, {
|
|
15
|
+
aliases: [],
|
|
16
|
+
description: 'Emit platform component dependency chains as a Mermaid diagram.',
|
|
17
|
+
option: '--mermaid'
|
|
18
|
+
}, {
|
|
19
|
+
aliases: [],
|
|
20
|
+
description: 'Bootstrap the application context and emit versioned timing diagnostics.',
|
|
21
|
+
option: '--timing'
|
|
22
|
+
}, {
|
|
23
|
+
aliases: [],
|
|
24
|
+
description: 'Select the exported module symbol name (default: AppModule).',
|
|
25
|
+
option: '--export <name>'
|
|
26
|
+
}, {
|
|
27
|
+
aliases: ['-h'],
|
|
28
|
+
description: 'Show help for the inspect command.',
|
|
29
|
+
option: '--help'
|
|
30
|
+
}];
|
|
31
|
+
function isHelpFlag(value) {
|
|
32
|
+
return value === '--help' || value === '-h';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns the usage information string for the inspect command.
|
|
37
|
+
*
|
|
38
|
+
* @returns Formatted help text including usage and options.
|
|
39
|
+
*/
|
|
40
|
+
export function inspectUsage() {
|
|
41
|
+
return ['Usage: fluo inspect <module-path> [options]', '', 'Options', renderHelpTable(INSPECT_OPTION_HELP, [{
|
|
42
|
+
header: 'Option',
|
|
43
|
+
render: entry => entry.option
|
|
44
|
+
}, {
|
|
45
|
+
header: 'Aliases',
|
|
46
|
+
render: entry => renderAliasList(entry.aliases)
|
|
47
|
+
}, {
|
|
48
|
+
header: 'Description',
|
|
49
|
+
render: entry => entry.description
|
|
50
|
+
}]), '', 'Docs: https://github.com/fluojs/fluo/tree/main/docs/getting-started/quick-start.md'].join('\n');
|
|
51
|
+
}
|
|
52
|
+
function parseInspectArgs(argv) {
|
|
53
|
+
let modulePath;
|
|
54
|
+
let exportName = 'AppModule';
|
|
55
|
+
let json = false;
|
|
56
|
+
let mermaid = false;
|
|
57
|
+
let timing = false;
|
|
58
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
59
|
+
const option = argv[index];
|
|
60
|
+
if (!option) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (option === '--json') {
|
|
64
|
+
json = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (option === '--mermaid') {
|
|
68
|
+
mermaid = true;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (option === '--timing') {
|
|
72
|
+
timing = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (option === '--export') {
|
|
76
|
+
const next = argv[index + 1];
|
|
77
|
+
if (!next || next.startsWith('-')) {
|
|
78
|
+
throw new Error('Expected --export to have a symbol name value.');
|
|
79
|
+
}
|
|
80
|
+
exportName = next;
|
|
81
|
+
index += 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (option.startsWith('-')) {
|
|
85
|
+
throw new Error(`Unknown option for inspect command: ${option}`);
|
|
86
|
+
}
|
|
87
|
+
if (modulePath) {
|
|
88
|
+
throw new Error(`Unexpected extra positional argument: ${option}`);
|
|
89
|
+
}
|
|
90
|
+
modulePath = option;
|
|
91
|
+
}
|
|
92
|
+
if (!modulePath) {
|
|
93
|
+
throw new Error(inspectUsage());
|
|
94
|
+
}
|
|
95
|
+
if (!json && !mermaid && !timing) {
|
|
96
|
+
json = true;
|
|
97
|
+
}
|
|
98
|
+
const selectedModes = [json, mermaid, timing].filter(Boolean).length;
|
|
99
|
+
if (selectedModes > 1) {
|
|
100
|
+
throw new Error('Choose only one inspect output mode: --json, --mermaid, or --timing.');
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
exportName,
|
|
104
|
+
json,
|
|
105
|
+
mermaid,
|
|
106
|
+
modulePath,
|
|
107
|
+
timing
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function resolveRootModule(exportedValue, exportName) {
|
|
111
|
+
if (typeof exportedValue !== 'function') {
|
|
112
|
+
throw new Error(`Export "${exportName}" is not a module class constructor.`);
|
|
113
|
+
}
|
|
114
|
+
return exportedValue;
|
|
115
|
+
}
|
|
116
|
+
function stringifyTiming(timing) {
|
|
117
|
+
const value = timing ?? {
|
|
118
|
+
phases: [],
|
|
119
|
+
totalMs: 0,
|
|
120
|
+
version: 1
|
|
121
|
+
};
|
|
122
|
+
return JSON.stringify(value, null, 2);
|
|
123
|
+
}
|
|
124
|
+
function stringifySnapshot(snapshot) {
|
|
125
|
+
return JSON.stringify(snapshot, null, 2);
|
|
126
|
+
}
|
|
127
|
+
function renderPlatformSnapshotMermaid(snapshot) {
|
|
128
|
+
const lines = ['graph TD'];
|
|
129
|
+
if (snapshot.components.length === 0) {
|
|
130
|
+
lines.push(' EMPTY["No registered platform components"]');
|
|
131
|
+
return lines.join('\n');
|
|
132
|
+
}
|
|
133
|
+
const nodeByComponentId = new Map();
|
|
134
|
+
for (const [index, component] of snapshot.components.entries()) {
|
|
135
|
+
const nodeId = `C${String(index + 1)}`;
|
|
136
|
+
nodeByComponentId.set(component.id, nodeId);
|
|
137
|
+
const summary = [component.id, `kind: ${component.kind}`, `state: ${component.state}`, `readiness: ${component.readiness.status}`, `health: ${component.health.status}`].join('\\n');
|
|
138
|
+
lines.push(` ${nodeId}["${summary}"]`);
|
|
139
|
+
}
|
|
140
|
+
for (const component of snapshot.components) {
|
|
141
|
+
const from = nodeByComponentId.get(component.id);
|
|
142
|
+
if (!from) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
for (const dependency of component.dependencies) {
|
|
146
|
+
const to = nodeByComponentId.get(dependency);
|
|
147
|
+
if (!to) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
lines.push(` ${from} --> ${to}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return lines.join('\n');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Executes the inspect command to visualize the application module graph.
|
|
158
|
+
*
|
|
159
|
+
* @param argv Command line arguments.
|
|
160
|
+
* @param runtime Optional custom runtime configuration for output streams and working directory.
|
|
161
|
+
* @returns Exit code (0 for success, 1 for failure).
|
|
162
|
+
*/
|
|
163
|
+
export async function runInspectCommand(argv, runtime = {}) {
|
|
164
|
+
const stdout = runtime.stdout ?? process.stdout;
|
|
165
|
+
const stderr = runtime.stderr ?? process.stderr;
|
|
166
|
+
const cwd = runtime.cwd ?? process.cwd();
|
|
167
|
+
try {
|
|
168
|
+
if (argv.some(isHelpFlag)) {
|
|
169
|
+
stdout.write(`${inspectUsage()}\n`);
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
const parsed = parseInspectArgs(argv);
|
|
173
|
+
const modulePath = resolve(cwd, parsed.modulePath);
|
|
174
|
+
const importedModule = await import(pathToFileURL(modulePath).href);
|
|
175
|
+
const rootModule = resolveRootModule(importedModule[parsed.exportName], parsed.exportName);
|
|
176
|
+
if (parsed.timing) {
|
|
177
|
+
const context = await FluoFactory.createApplicationContext(rootModule, {
|
|
178
|
+
diagnostics: {
|
|
179
|
+
timing: true
|
|
180
|
+
},
|
|
181
|
+
logger: {
|
|
182
|
+
debug() {},
|
|
183
|
+
error() {},
|
|
184
|
+
log() {},
|
|
185
|
+
warn() {}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
try {
|
|
189
|
+
stdout.write(`${stringifyTiming(context.bootstrapTiming)}\n`);
|
|
190
|
+
} finally {
|
|
191
|
+
await context.close();
|
|
192
|
+
}
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
const context = await FluoFactory.createApplicationContext(rootModule, {
|
|
196
|
+
logger: {
|
|
197
|
+
debug() {},
|
|
198
|
+
error() {},
|
|
199
|
+
log() {},
|
|
200
|
+
warn() {}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
try {
|
|
204
|
+
const platformShell = await context.get(PLATFORM_SHELL);
|
|
205
|
+
const snapshot = await platformShell.snapshot();
|
|
206
|
+
if (parsed.json) {
|
|
207
|
+
stdout.write(`${stringifySnapshot(snapshot)}\n`);
|
|
208
|
+
}
|
|
209
|
+
if (parsed.mermaid) {
|
|
210
|
+
stdout.write(`${renderPlatformSnapshotMermaid(snapshot)}\n`);
|
|
211
|
+
}
|
|
212
|
+
} finally {
|
|
213
|
+
await context.close();
|
|
214
|
+
}
|
|
215
|
+
return 0;
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
218
|
+
stderr.write(`${message}\n`);
|
|
219
|
+
return 1;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type CliStream = {
|
|
2
|
+
write(message: string): unknown;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Runtime configuration for the migrate command.
|
|
6
|
+
*/
|
|
7
|
+
export interface MigrateCommandRuntimeOptions {
|
|
8
|
+
/** Current working directory for path resolution. */
|
|
9
|
+
cwd?: string;
|
|
10
|
+
/** Custom stream for error output. */
|
|
11
|
+
stderr?: CliStream;
|
|
12
|
+
/** Custom stream for standard output. */
|
|
13
|
+
stdout?: CliStream;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns usage information for the migrate command.
|
|
17
|
+
*
|
|
18
|
+
* @returns Formatted help text including usage and options.
|
|
19
|
+
*/
|
|
20
|
+
export declare function migrateUsage(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Executes the migrate command to transform NestJS projects into fluo.
|
|
23
|
+
*
|
|
24
|
+
* @param argv Command line arguments.
|
|
25
|
+
* @param runtime Optional custom runtime configuration for output streams and working directory.
|
|
26
|
+
* @returns Exit code (0 for success, 1 for failure).
|
|
27
|
+
*/
|
|
28
|
+
export declare function runMigrateCommand(argv: string[], runtime?: MigrateCommandRuntimeOptions): Promise<number>;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=migrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAYA,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,yCAAyC;IACzC,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AA6HD;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAgBrC;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,MAAM,CAAC,CAmEnH"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { renderAliasList, renderHelpTable } from '../help.js';
|
|
3
|
+
import { MIGRATION_TRANSFORMS, getWarningCategoryLabel, groupWarningsByCategory, renderTransformList, runNestJsMigration } from '../transforms/nestjs-migrate.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Runtime configuration for the migrate command.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const MIGRATE_OPTION_HELP = [{
|
|
10
|
+
aliases: ['-a'],
|
|
11
|
+
description: 'Apply file changes. Dry-run is the default mode.',
|
|
12
|
+
option: '--apply'
|
|
13
|
+
}, {
|
|
14
|
+
aliases: [],
|
|
15
|
+
description: `Run only selected transforms. Available: ${MIGRATION_TRANSFORMS.join(', ')}.`,
|
|
16
|
+
option: '--only <comma-list>'
|
|
17
|
+
}, {
|
|
18
|
+
aliases: [],
|
|
19
|
+
description: `Skip selected transforms. Available: ${MIGRATION_TRANSFORMS.join(', ')}.`,
|
|
20
|
+
option: '--skip <comma-list>'
|
|
21
|
+
}, {
|
|
22
|
+
aliases: ['-h'],
|
|
23
|
+
description: 'Show help for the migrate command.',
|
|
24
|
+
option: '--help'
|
|
25
|
+
}];
|
|
26
|
+
function isHelpFlag(value) {
|
|
27
|
+
return value === '--help' || value === '-h';
|
|
28
|
+
}
|
|
29
|
+
function parseTransformList(rawValue, optionName) {
|
|
30
|
+
const values = rawValue.split(',').map(value => value.trim()).filter(value => value.length > 0);
|
|
31
|
+
if (values.length === 0) {
|
|
32
|
+
throw new Error(`${optionName} requires a non-empty comma-separated transform list.`);
|
|
33
|
+
}
|
|
34
|
+
const invalid = values.filter(value => !MIGRATION_TRANSFORMS.includes(value));
|
|
35
|
+
if (invalid.length > 0) {
|
|
36
|
+
throw new Error(`Unknown transform(s): ${invalid.join(', ')}. Available transforms: ${MIGRATION_TRANSFORMS.join(', ')}.`);
|
|
37
|
+
}
|
|
38
|
+
return values;
|
|
39
|
+
}
|
|
40
|
+
function parseArgs(argv) {
|
|
41
|
+
let pathArgument;
|
|
42
|
+
let apply = false;
|
|
43
|
+
let onlyTransforms;
|
|
44
|
+
let skipTransforms = [];
|
|
45
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
46
|
+
const arg = argv[index];
|
|
47
|
+
if (arg === '--apply' || arg === '-a') {
|
|
48
|
+
apply = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (arg === '--only' || arg === '--skip') {
|
|
52
|
+
const rawValue = argv[index + 1];
|
|
53
|
+
if (!rawValue || rawValue.startsWith('-')) {
|
|
54
|
+
throw new Error(`Expected ${arg} to have a comma-separated value.`);
|
|
55
|
+
}
|
|
56
|
+
const parsed = parseTransformList(rawValue, arg);
|
|
57
|
+
if (arg === '--only') {
|
|
58
|
+
if (onlyTransforms) {
|
|
59
|
+
throw new Error('Duplicate --only option.');
|
|
60
|
+
}
|
|
61
|
+
onlyTransforms = parsed;
|
|
62
|
+
} else {
|
|
63
|
+
skipTransforms = [...skipTransforms, ...parsed];
|
|
64
|
+
}
|
|
65
|
+
index += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (arg.startsWith('-')) {
|
|
69
|
+
throw new Error(`Unknown option for migrate command: ${arg}`);
|
|
70
|
+
}
|
|
71
|
+
if (pathArgument) {
|
|
72
|
+
throw new Error(`Unexpected extra positional argument: ${arg}`);
|
|
73
|
+
}
|
|
74
|
+
pathArgument = arg;
|
|
75
|
+
}
|
|
76
|
+
if (!pathArgument) {
|
|
77
|
+
throw new Error(migrateUsage());
|
|
78
|
+
}
|
|
79
|
+
const enabled = new Set(onlyTransforms ?? MIGRATION_TRANSFORMS);
|
|
80
|
+
for (const skipped of skipTransforms) {
|
|
81
|
+
enabled.delete(skipped);
|
|
82
|
+
}
|
|
83
|
+
if (enabled.size === 0) {
|
|
84
|
+
throw new Error('No transforms remain after applying --only/--skip filters.');
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
apply,
|
|
88
|
+
path: pathArgument,
|
|
89
|
+
transforms: enabled
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Returns usage information for the migrate command.
|
|
95
|
+
*
|
|
96
|
+
* @returns Formatted help text including usage and options.
|
|
97
|
+
*/
|
|
98
|
+
export function migrateUsage() {
|
|
99
|
+
return ['Usage: fluo migrate <path> [options]', '', 'Options', renderHelpTable(MIGRATE_OPTION_HELP, [{
|
|
100
|
+
header: 'Option',
|
|
101
|
+
render: entry => entry.option
|
|
102
|
+
}, {
|
|
103
|
+
header: 'Aliases',
|
|
104
|
+
render: entry => renderAliasList(entry.aliases)
|
|
105
|
+
}, {
|
|
106
|
+
header: 'Description',
|
|
107
|
+
render: entry => entry.description
|
|
108
|
+
}]), '', 'Next steps:', ' Review warnings in the output, then run again with --apply to write changes.', '', 'Docs: https://github.com/fluojs/fluo/tree/main/docs/getting-started/migrate-from-nestjs.md'].join('\n');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Executes the migrate command to transform NestJS projects into fluo.
|
|
113
|
+
*
|
|
114
|
+
* @param argv Command line arguments.
|
|
115
|
+
* @param runtime Optional custom runtime configuration for output streams and working directory.
|
|
116
|
+
* @returns Exit code (0 for success, 1 for failure).
|
|
117
|
+
*/
|
|
118
|
+
export async function runMigrateCommand(argv, runtime = {}) {
|
|
119
|
+
const stdout = runtime.stdout ?? process.stdout;
|
|
120
|
+
const stderr = runtime.stderr ?? process.stderr;
|
|
121
|
+
try {
|
|
122
|
+
if (argv.some(isHelpFlag)) {
|
|
123
|
+
stdout.write(`${migrateUsage()}\n`);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
const parsed = parseArgs(argv);
|
|
127
|
+
const targetPath = resolve(runtime.cwd ?? process.cwd(), parsed.path);
|
|
128
|
+
const transforms = [...parsed.transforms];
|
|
129
|
+
const report = runNestJsMigration({
|
|
130
|
+
apply: parsed.apply,
|
|
131
|
+
enabledTransforms: parsed.transforms,
|
|
132
|
+
targetPath
|
|
133
|
+
});
|
|
134
|
+
stdout.write(`Mode: ${parsed.apply ? 'apply' : 'dry-run'}\n`);
|
|
135
|
+
stdout.write(`Enabled transforms: ${renderTransformList(transforms)}\n`);
|
|
136
|
+
stdout.write(`Scanned files: ${report.scannedFiles}\n`);
|
|
137
|
+
stdout.write(`Changed files: ${report.changedFiles}\n`);
|
|
138
|
+
stdout.write(`Warnings: ${report.warningCount}\n`);
|
|
139
|
+
if (!parsed.apply && report.changedFiles > 0) {
|
|
140
|
+
stdout.write('Run again with --apply to write transformed files.\n');
|
|
141
|
+
}
|
|
142
|
+
const changedPaths = report.fileResults.filter(fileResult => fileResult.changed).map(fileResult => fileResult.filePath);
|
|
143
|
+
if (changedPaths.length > 0) {
|
|
144
|
+
stdout.write('\nAutomated rewrites:\n');
|
|
145
|
+
for (const filePath of changedPaths) {
|
|
146
|
+
stdout.write(` ${filePath}\n`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const allWarnings = report.fileResults.flatMap(fileResult => fileResult.warnings);
|
|
150
|
+
if (allWarnings.length > 0) {
|
|
151
|
+
stdout.write('\nManual follow-up required:\n');
|
|
152
|
+
const grouped = groupWarningsByCategory(allWarnings);
|
|
153
|
+
for (const [category, warnings] of grouped) {
|
|
154
|
+
stdout.write(`\n [${getWarningCategoryLabel(category)}]\n`);
|
|
155
|
+
for (const warning of warnings) {
|
|
156
|
+
stdout.write(` - ${warning.filePath}:${warning.line} ${warning.message}\n`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (report.warningCount === 0 && report.changedFiles > 0) {
|
|
161
|
+
stdout.write('\nAll transforms applied cleanly. No manual follow-ups detected.\n');
|
|
162
|
+
}
|
|
163
|
+
if (report.warningCount > 0) {
|
|
164
|
+
stdout.write('\nDocs: https://github.com/fluojs/fluo/tree/main/docs/getting-started/migrate-from-nestjs.md\n');
|
|
165
|
+
stdout.write('Use the post-codemod checklist in the migration guide to address each warning category.\n');
|
|
166
|
+
}
|
|
167
|
+
return 0;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
170
|
+
stderr.write(`${message}\n`);
|
|
171
|
+
return 1;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type BootstrapPrompter } from '../new/prompt.js';
|
|
2
|
+
import type { NewCommandOptions } from '../new/types.js';
|
|
3
|
+
type CliStream = {
|
|
4
|
+
write(message: string): unknown;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Runtime dependency overrides for the programmatic `fluo new` entry point.
|
|
8
|
+
*/
|
|
9
|
+
export interface NewCommandRuntimeOptions extends NewCommandOptions {
|
|
10
|
+
cwd?: string;
|
|
11
|
+
interactive?: boolean;
|
|
12
|
+
prompt?: BootstrapPrompter;
|
|
13
|
+
stderr?: CliStream;
|
|
14
|
+
stdin?: {
|
|
15
|
+
isTTY?: boolean;
|
|
16
|
+
};
|
|
17
|
+
stdout?: CliStream;
|
|
18
|
+
userAgent?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Renders CLI help text for `fluo new`.
|
|
22
|
+
*
|
|
23
|
+
* @returns Stable help output for the scaffolding command.
|
|
24
|
+
*/
|
|
25
|
+
export declare function newUsage(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Executes `fluo new` with parsed arguments and scaffold options.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { runNewCommand } from '@fluojs/cli';
|
|
32
|
+
*
|
|
33
|
+
* const exitCode = await runNewCommand(['starter-app', '--package-manager', 'pnpm'], {
|
|
34
|
+
* cwd: '/workspace',
|
|
35
|
+
* skipInstall: true,
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @param argv Command arguments after the `new` or `create` token.
|
|
40
|
+
* @param runtime Optional runtime overrides for prompt resolution, stream output, and scaffold execution.
|
|
41
|
+
* @returns `0` when scaffolding succeeds, otherwise `1` after reporting the failure to `stderr`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function runNewCommand(argv: string[], runtime?: NewCommandRuntimeOptions): Promise<number>;
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=new.d.ts.map
|