@fluojs/cli 1.0.0-beta.4 → 1.0.0-beta.6

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 (40) hide show
  1. package/README.ko.md +97 -3
  2. package/README.md +97 -3
  3. package/dist/cli.d.ts +8 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +201 -4
  6. package/dist/commands/diagnostics.d.ts +15 -0
  7. package/dist/commands/diagnostics.d.ts.map +1 -0
  8. package/dist/commands/diagnostics.js +163 -0
  9. package/dist/commands/new.js +2 -2
  10. package/dist/commands/package-manager.d.ts +9 -0
  11. package/dist/commands/package-manager.d.ts.map +1 -0
  12. package/dist/commands/package-manager.js +63 -0
  13. package/dist/commands/package-workflow.d.ts +20 -0
  14. package/dist/commands/package-workflow.d.ts.map +1 -0
  15. package/dist/commands/package-workflow.js +137 -0
  16. package/dist/commands/scripts.d.ts +38 -0
  17. package/dist/commands/scripts.d.ts.map +1 -0
  18. package/dist/commands/scripts.js +570 -0
  19. package/dist/dev-runner/node-restart-runner.d.ts +55 -0
  20. package/dist/dev-runner/node-restart-runner.d.ts.map +1 -0
  21. package/dist/dev-runner/node-restart-runner.js +317 -0
  22. package/dist/dev-runner/preserve-color-tty.d.ts +2 -0
  23. package/dist/dev-runner/preserve-color-tty.d.ts.map +1 -0
  24. package/dist/dev-runner/preserve-color-tty.js +12 -0
  25. package/dist/generators/manifest.d.ts +24 -0
  26. package/dist/generators/manifest.d.ts.map +1 -1
  27. package/dist/generators/manifest.js +9 -0
  28. package/dist/generators/resource.d.ts +10 -0
  29. package/dist/generators/resource.d.ts.map +1 -0
  30. package/dist/generators/resource.js +23 -0
  31. package/dist/generators/templates/controller.ts.ejs +5 -1
  32. package/dist/generators/templates/request-dto.ts.ejs +3 -0
  33. package/dist/new/scaffold.d.ts.map +1 -1
  34. package/dist/new/scaffold.js +193 -148
  35. package/dist/new/starter-profiles.d.ts.map +1 -1
  36. package/dist/new/starter-profiles.js +13 -13
  37. package/dist/update-check.d.ts +1 -0
  38. package/dist/update-check.d.ts.map +1 -1
  39. package/dist/update-check.js +7 -5
  40. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1,10 +1,14 @@
1
- import { existsSync, readdirSync } from 'node:fs';
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+ import { diagnosticsUsage, runAnalyzeCommand, runDoctorCommand, runInfoCommand } from './commands/diagnostics.js';
4
5
  import { runGenerateCommand } from './commands/generate.js';
5
6
  import { inspectUsage, runInspectCommand } from './commands/inspect.js';
6
7
  import { migrateUsage, runMigrateCommand } from './commands/migrate.js';
7
8
  import { newUsage, runNewCommand } from './commands/new.js';
9
+ import { addUsage, runAddCommand, runUpgradeCommand, upgradeUsage } from './commands/package-workflow.js';
10
+ import { runScriptCommand, scriptUsage } from './commands/scripts.js';
11
+ import { runNodeRestartRunner } from './dev-runner/node-restart-runner.js';
8
12
  import { builtInGeneratorCollection, generatorManifest, generatorOptionSchemas, resolveGeneratorKind } from './generators/manifest.js';
9
13
  import { renderAliasList, renderHelpTable } from './help.js';
10
14
  import { removeUpdateCheckFlags, runCliUpdateCheck } from './update-check.js';
@@ -33,6 +37,34 @@ const TOP_LEVEL_COMMAND_HELP = [{
33
37
  aliases: ['g'],
34
38
  command: 'generate',
35
39
  description: 'Generate a schematic inside an existing fluo application.'
40
+ }, {
41
+ aliases: ['info'],
42
+ command: 'doctor',
43
+ description: 'Print CLI, registry, update-cache, runtime, and project diagnostics.'
44
+ }, {
45
+ aliases: [],
46
+ command: 'analyze',
47
+ description: 'Summarize project diagnostics and point to deeper inspection flows.'
48
+ }, {
49
+ aliases: [],
50
+ command: 'dev',
51
+ description: 'Run the generated project development lifecycle.'
52
+ }, {
53
+ aliases: [],
54
+ command: 'start',
55
+ description: 'Run the generated project production lifecycle.'
56
+ }, {
57
+ aliases: [],
58
+ command: 'build',
59
+ description: 'Run the generated project build lifecycle.'
60
+ }, {
61
+ aliases: [],
62
+ command: 'add',
63
+ description: 'Install @fluojs packages with the detected package manager.'
64
+ }, {
65
+ aliases: [],
66
+ command: 'upgrade',
67
+ description: 'Report latest CLI state and migration workflow guidance.'
36
68
  }, {
37
69
  aliases: [],
38
70
  command: 'inspect',
@@ -41,17 +73,66 @@ const TOP_LEVEL_COMMAND_HELP = [{
41
73
  aliases: [],
42
74
  command: 'migrate',
43
75
  description: 'Run NestJS-to-fluo codemods (dry-run by default).'
76
+ }, {
77
+ aliases: ['--version', '-v'],
78
+ command: 'version',
79
+ description: 'Print the installed fluo CLI version.'
44
80
  }, {
45
81
  aliases: [],
46
82
  command: 'help',
47
83
  description: 'Show top-level or command-specific help.'
48
84
  }];
85
+ const NODE_DEV_RUNNER_COMMAND = '__node-dev-runner';
86
+ const DEV_RUNNER_COMMAND = '__dev-runner';
87
+ function parseDevRunnerRuntime(value) {
88
+ if (value === 'bun' || value === 'cloudflare-workers' || value === 'deno' || value === 'node') {
89
+ return value;
90
+ }
91
+ throw new Error(`Invalid dev runner runtime "${value ?? ''}".`);
92
+ }
93
+ function parseDevRunnerInvocation(argv) {
94
+ if (argv[0] === NODE_DEV_RUNNER_COMMAND) {
95
+ const separatorIndex = argv.indexOf('--');
96
+ return {
97
+ appArgs: separatorIndex >= 0 ? argv.slice(separatorIndex + 1) : argv.slice(1),
98
+ runtime: 'node'
99
+ };
100
+ }
101
+ const runtimeFlagIndex = argv.indexOf('--runtime');
102
+ const runtime = parseDevRunnerRuntime(argv[runtimeFlagIndex + 1]);
103
+ const separatorIndex = argv.indexOf('--');
104
+ if (separatorIndex >= 0) {
105
+ return {
106
+ appArgs: argv.slice(separatorIndex + 1),
107
+ runtime
108
+ };
109
+ }
110
+ const appArgs = argv.slice(1).filter((arg, index, args) => arg !== '--runtime' && args[index - 1] !== '--runtime');
111
+ return {
112
+ appArgs,
113
+ runtime
114
+ };
115
+ }
49
116
  function normalizeGeneratorKind(value) {
50
117
  return resolveGeneratorKind(value);
51
118
  }
52
119
  function isHelpFlag(value) {
53
120
  return value === '--help' || value === '-h';
54
121
  }
122
+ function isVersionCommand(value) {
123
+ return value === 'version' || value === '--version' || value === '-v';
124
+ }
125
+ function isCreationCommand(value) {
126
+ return value === 'new' || value === 'create';
127
+ }
128
+ function readCliVersion() {
129
+ const packageJsonPath = fileURLToPath(new URL('../package.json', import.meta.url));
130
+ const manifest = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
131
+ if (typeof manifest !== 'object' || manifest === null || !('version' in manifest) || typeof manifest.version !== 'string') {
132
+ throw new Error('Unable to determine the installed fluo CLI version.');
133
+ }
134
+ return manifest.version;
135
+ }
55
136
  function generateUsage() {
56
137
  return ['Usage: fluo generate|g <kind> <name> [options]', ' fluo generate|g request-dto|req <feature> <name> [options]', '', 'Schematics', renderHelpTable(GENERATE_KIND_HELP, [{
57
138
  header: 'Schematic',
@@ -173,6 +254,42 @@ function parseGenerateArgs(argv) {
173
254
  }
174
255
  function parseCommand(argv) {
175
256
  const [command] = argv;
257
+ if (command === 'analyze') {
258
+ return {
259
+ argv: argv.slice(1),
260
+ command: 'analyze'
261
+ };
262
+ }
263
+ if (command === 'add') {
264
+ return {
265
+ argv: argv.slice(1),
266
+ command: 'add'
267
+ };
268
+ }
269
+ if (command === 'doctor') {
270
+ return {
271
+ argv: argv.slice(1),
272
+ command: 'doctor'
273
+ };
274
+ }
275
+ if (command === 'info') {
276
+ return {
277
+ argv: argv.slice(1),
278
+ command: 'info'
279
+ };
280
+ }
281
+ if (command === 'build' || command === 'dev' || command === 'start') {
282
+ return {
283
+ argv: argv.slice(1),
284
+ command
285
+ };
286
+ }
287
+ if (command === 'upgrade') {
288
+ return {
289
+ argv: argv.slice(1),
290
+ command: 'upgrade'
291
+ };
292
+ }
176
293
  if (command === 'new' || command === 'create') {
177
294
  return {
178
295
  argv: argv.slice(1),
@@ -225,14 +342,33 @@ export async function runCli(argv = process.argv.slice(2), runtime = {}) {
225
342
  const stdout = runtime.stdout ?? process.stdout;
226
343
  const stderr = runtime.stderr ?? process.stderr;
227
344
  const env = runtime.env ?? process.env;
345
+ const commandRuntime = {
346
+ ...runtime,
347
+ env
348
+ };
228
349
  const updateFlagResult = removeUpdateCheckFlags(argv);
229
350
  const commandArgv = updateFlagResult.argv;
230
351
  try {
352
+ if (commandArgv[0] === NODE_DEV_RUNNER_COMMAND || commandArgv[0] === DEV_RUNNER_COMMAND) {
353
+ const runnerInvocation = parseDevRunnerInvocation(commandArgv);
354
+ return runNodeRestartRunner({
355
+ appArgs: runnerInvocation.appArgs,
356
+ env,
357
+ runtime: runnerInvocation.runtime,
358
+ stderr,
359
+ stdout
360
+ });
361
+ }
362
+ if (isVersionCommand(commandArgv[0])) {
363
+ stdout.write(`${readCliVersion()}\n`);
364
+ return 0;
365
+ }
231
366
  const updateCheckOptions = runtime.updateCheck === false ? undefined : runtime.updateCheck;
232
367
  const updateCheckResult = await runCliUpdateCheck(commandArgv, {
233
368
  ...updateCheckOptions,
234
369
  ci: runtime.ci,
235
370
  env,
371
+ bypassCache: isCreationCommand(commandArgv[0]),
236
372
  interactive: runtime.interactive,
237
373
  skip: updateFlagResult.skipUpdateCheck || runtime.updateCheck === false,
238
374
  stderr,
@@ -255,6 +391,26 @@ export async function runCli(argv = process.argv.slice(2), runtime = {}) {
255
391
  stdout.write(`${generateUsage()}\n`);
256
392
  return 0;
257
393
  }
394
+ if (topic === 'doctor' || topic === 'info') {
395
+ stdout.write(`${diagnosticsUsage('doctor')}\n`);
396
+ return 0;
397
+ }
398
+ if (topic === 'analyze') {
399
+ stdout.write(`${diagnosticsUsage('analyze')}\n`);
400
+ return 0;
401
+ }
402
+ if (topic === 'build' || topic === 'dev' || topic === 'start') {
403
+ stdout.write(`${scriptUsage(topic)}\n`);
404
+ return 0;
405
+ }
406
+ if (topic === 'add') {
407
+ stdout.write(`${addUsage()}\n`);
408
+ return 0;
409
+ }
410
+ if (topic === 'upgrade') {
411
+ stdout.write(`${upgradeUsage()}\n`);
412
+ return 0;
413
+ }
258
414
  if (topic === 'migrate') {
259
415
  stdout.write(`${migrateUsage()}\n`);
260
416
  return 0;
@@ -274,6 +430,26 @@ export async function runCli(argv = process.argv.slice(2), runtime = {}) {
274
430
  stdout.write(`${generateUsage()}\n`);
275
431
  return 0;
276
432
  }
433
+ if ((commandArgv[0] === 'doctor' || commandArgv[0] === 'info') && commandArgv.slice(1).some(isHelpFlag)) {
434
+ stdout.write(`${diagnosticsUsage('doctor')}\n`);
435
+ return 0;
436
+ }
437
+ if (commandArgv[0] === 'analyze' && commandArgv.slice(1).some(isHelpFlag)) {
438
+ stdout.write(`${diagnosticsUsage('analyze')}\n`);
439
+ return 0;
440
+ }
441
+ if ((commandArgv[0] === 'build' || commandArgv[0] === 'dev' || commandArgv[0] === 'start') && commandArgv.slice(1).some(isHelpFlag)) {
442
+ stdout.write(`${scriptUsage(commandArgv[0])}\n`);
443
+ return 0;
444
+ }
445
+ if (commandArgv[0] === 'add' && commandArgv.slice(1).some(isHelpFlag)) {
446
+ stdout.write(`${addUsage()}\n`);
447
+ return 0;
448
+ }
449
+ if (commandArgv[0] === 'upgrade' && commandArgv.slice(1).some(isHelpFlag)) {
450
+ stdout.write(`${upgradeUsage()}\n`);
451
+ return 0;
452
+ }
277
453
  if (commandArgv[0] === 'migrate' && commandArgv.slice(1).some(isHelpFlag)) {
278
454
  stdout.write(`${migrateUsage()}\n`);
279
455
  return 0;
@@ -283,14 +459,35 @@ export async function runCli(argv = process.argv.slice(2), runtime = {}) {
283
459
  return 0;
284
460
  }
285
461
  const parsedCommand = parseCommand(commandArgv);
462
+ if (parsedCommand.command === 'analyze') {
463
+ return runAnalyzeCommand(parsedCommand.argv, commandRuntime);
464
+ }
465
+ if (parsedCommand.command === 'add') {
466
+ return runAddCommand(parsedCommand.argv, commandRuntime);
467
+ }
468
+ if (parsedCommand.command === 'doctor') {
469
+ return runDoctorCommand(parsedCommand.argv, commandRuntime);
470
+ }
471
+ if (parsedCommand.command === 'info') {
472
+ return runInfoCommand(parsedCommand.argv, commandRuntime);
473
+ }
474
+ if (parsedCommand.command === 'build' || parsedCommand.command === 'dev' || parsedCommand.command === 'start') {
475
+ return runScriptCommand(parsedCommand.command, parsedCommand.argv, commandRuntime);
476
+ }
477
+ if (parsedCommand.command === 'upgrade') {
478
+ return runUpgradeCommand(parsedCommand.argv, commandRuntime);
479
+ }
286
480
  if (parsedCommand.command === 'new') {
287
- return runNewCommand(parsedCommand.argv, runtime);
481
+ return runNewCommand(parsedCommand.argv, commandRuntime);
288
482
  }
289
483
  if (parsedCommand.command === 'migrate') {
290
- return runMigrateCommand(parsedCommand.argv, runtime);
484
+ return runMigrateCommand(parsedCommand.argv, commandRuntime);
291
485
  }
292
486
  if (parsedCommand.command === 'inspect') {
293
- return runInspectCommand(parsedCommand.argv, runtime);
487
+ return runInspectCommand(parsedCommand.argv, commandRuntime);
488
+ }
489
+ if (parsedCommand.command !== 'generate') {
490
+ throw new Error(usage());
294
491
  }
295
492
  const targetDirectory = resolve(cwd, parsedCommand.parsed.targetDirectory ?? resolveDefaultTargetDirectory(cwd));
296
493
  const result = runGenerateCommand(parsedCommand.parsed.kind, parsedCommand.parsed.name, targetDirectory, parsedCommand.parsed.options);
@@ -0,0 +1,15 @@
1
+ type CliStream = {
2
+ write(message: string): unknown;
3
+ };
4
+ type DiagnosticRuntimeOptions = {
5
+ cwd?: string;
6
+ env?: NodeJS.ProcessEnv;
7
+ fetchDistTags?: (packageName: string) => Promise<Record<string, string> | undefined>;
8
+ stdout?: CliStream;
9
+ };
10
+ export declare function diagnosticsUsage(command?: 'analyze' | 'doctor' | 'info'): string;
11
+ export declare function runDoctorCommand(argv: string[], runtime?: DiagnosticRuntimeOptions): Promise<number>;
12
+ export declare function runInfoCommand(argv: string[], runtime?: DiagnosticRuntimeOptions): Promise<number>;
13
+ export declare function runAnalyzeCommand(argv: string[], runtime?: DiagnosticRuntimeOptions): Promise<number>;
14
+ export {};
15
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/commands/diagnostics.ts"],"names":[],"mappings":"AAKA,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;IACrF,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,CAAC;AAuHF,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,SAAS,GAAG,QAAQ,GAAG,MAAiB,GAAG,MAAM,CAoB1F;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CAgC9G;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CAO5G;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB/G"}
@@ -0,0 +1,163 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const DEFAULT_PACKAGE_NAME = '@fluojs/cli';
6
+ const DEFAULT_REGISTRY_TIMEOUT_MS = 5_000;
7
+ const EMPTY_ENV = {};
8
+ function isRecord(value) {
9
+ return typeof value === 'object' && value !== null;
10
+ }
11
+ function readJsonFile(filePath) {
12
+ try {
13
+ const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
14
+ return isRecord(parsed) ? parsed : undefined;
15
+ } catch (_error) {
16
+ return undefined;
17
+ }
18
+ }
19
+ function readCliVersion() {
20
+ const packageJsonPath = fileURLToPath(new URL('../../package.json', import.meta.url));
21
+ const manifest = readJsonFile(packageJsonPath);
22
+ return typeof manifest?.version === 'string' ? manifest.version : 'unknown';
23
+ }
24
+ function resolveCacheFile(env) {
25
+ const cacheRoot = env.XDG_CACHE_HOME ?? join(homedir(), '.cache');
26
+ return join(cacheRoot, 'fluo', 'cli-update-check.json');
27
+ }
28
+ function readUpdateCache(env) {
29
+ const cacheFile = resolveCacheFile(env);
30
+ const cache = readJsonFile(cacheFile);
31
+ const checkedAt = typeof cache?.checkedAt === 'number' ? cache.checkedAt : undefined;
32
+ return {
33
+ ageMs: checkedAt === undefined ? undefined : Date.now() - checkedAt,
34
+ checkedAt: checkedAt === undefined ? undefined : new Date(checkedAt).toISOString(),
35
+ latestVersion: typeof cache?.latestVersion === 'string' ? cache.latestVersion : undefined,
36
+ path: cacheFile
37
+ };
38
+ }
39
+ function findProjectManifest(startDirectory) {
40
+ let current = resolve(startDirectory);
41
+ while (true) {
42
+ const candidate = join(current, 'package.json');
43
+ if (existsSync(candidate)) {
44
+ return {
45
+ manifest: readJsonFile(candidate),
46
+ path: candidate
47
+ };
48
+ }
49
+ const parent = dirname(current);
50
+ if (parent === current) {
51
+ return {};
52
+ }
53
+ current = parent;
54
+ }
55
+ }
56
+ async function fetchNpmDistTags(packageName) {
57
+ const controller = new AbortController();
58
+ const timeout = setTimeout(() => controller.abort(), DEFAULT_REGISTRY_TIMEOUT_MS);
59
+ try {
60
+ const response = await fetch(`https://registry.npmjs.org/-/package/${encodeURIComponent(packageName)}/dist-tags`, {
61
+ headers: {
62
+ accept: 'application/json'
63
+ },
64
+ signal: controller.signal
65
+ });
66
+ if (!response.ok) {
67
+ return undefined;
68
+ }
69
+ const payload = await response.json();
70
+ if (!isRecord(payload)) {
71
+ return undefined;
72
+ }
73
+ const distTags = {};
74
+ for (const [key, value] of Object.entries(payload)) {
75
+ if (typeof value === 'string') {
76
+ distTags[key] = value;
77
+ }
78
+ }
79
+ return distTags;
80
+ } catch (_error) {
81
+ return undefined;
82
+ } finally {
83
+ clearTimeout(timeout);
84
+ }
85
+ }
86
+ function formatAge(ageMs) {
87
+ if (ageMs === undefined) {
88
+ return 'unknown';
89
+ }
90
+ const minutes = Math.floor(ageMs / 60_000);
91
+ if (minutes < 60) {
92
+ return `${minutes}m`;
93
+ }
94
+ return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;
95
+ }
96
+ function listScripts(manifest) {
97
+ const scripts = manifest?.scripts;
98
+ if (!isRecord(scripts)) {
99
+ return [];
100
+ }
101
+ return Object.keys(scripts).sort();
102
+ }
103
+ export function diagnosticsUsage(command = 'doctor') {
104
+ if (command === 'analyze') {
105
+ return ['Usage: fluo analyze [options]', '', 'Summarize the current project and point to deeper inspect/report diagnostics.', '', 'Options', ' --help Show help for the analyze command.'].join('\n');
106
+ }
107
+ return [`Usage: fluo ${command} [options]`, '', 'Print CLI, registry, update-cache, runtime, and project diagnostics.', '', 'Options', ` --help Show help for the ${command} command.`].join('\n');
108
+ }
109
+ export async function runDoctorCommand(argv, runtime = {}) {
110
+ if (argv.includes('--help') || argv.includes('-h')) {
111
+ (runtime.stdout ?? process.stdout).write(`${diagnosticsUsage('doctor')}\n`);
112
+ return 0;
113
+ }
114
+ if (argv.length > 0) {
115
+ throw new Error(`Unknown doctor option: ${argv[0]}`);
116
+ }
117
+ const env = runtime.env ?? EMPTY_ENV;
118
+ const stdout = runtime.stdout ?? process.stdout;
119
+ const cwd = resolve(runtime.cwd ?? process.cwd());
120
+ const cache = readUpdateCache(env);
121
+ const distTags = await (runtime.fetchDistTags ?? fetchNpmDistTags)(DEFAULT_PACKAGE_NAME);
122
+ const project = findProjectManifest(cwd);
123
+ const scripts = listScripts(project.manifest);
124
+ stdout.write('fluo doctor\n');
125
+ stdout.write(` CLI version: ${readCliVersion()}\n`);
126
+ stdout.write(` Node.js: ${process.version}\n`);
127
+ stdout.write(` Platform: ${process.platform}/${process.arch}\n`);
128
+ stdout.write(` Package manager signal: ${env.npm_config_user_agent ?? 'unknown'}\n`);
129
+ stdout.write(` npm latest: ${distTags?.latest ?? 'unavailable'}\n`);
130
+ stdout.write(` npm beta: ${distTags?.beta ?? 'unavailable'}\n`);
131
+ stdout.write(` Update cache: ${cache.path}\n`);
132
+ stdout.write(` Cached latest: ${cache.latestVersion ?? 'none'}\n`);
133
+ stdout.write(` Cache checked: ${cache.checkedAt ?? 'never'} (${formatAge(cache.ageMs)} ago)\n`);
134
+ stdout.write(` Project manifest: ${project.path ?? 'not found'}\n`);
135
+ stdout.write(` Project scripts: ${scripts.length > 0 ? scripts.join(', ') : 'none'}\n`);
136
+ return 0;
137
+ }
138
+ export async function runInfoCommand(argv, runtime = {}) {
139
+ if (argv.includes('--help') || argv.includes('-h')) {
140
+ (runtime.stdout ?? process.stdout).write(`${diagnosticsUsage('info')}\n`);
141
+ return 0;
142
+ }
143
+ return runDoctorCommand(argv, runtime);
144
+ }
145
+ export async function runAnalyzeCommand(argv, runtime = {}) {
146
+ if (argv.includes('--help') || argv.includes('-h')) {
147
+ (runtime.stdout ?? process.stdout).write(`${diagnosticsUsage('analyze')}\n`);
148
+ return 0;
149
+ }
150
+ if (argv.length > 0) {
151
+ throw new Error(`Unknown analyze option: ${argv[0]}`);
152
+ }
153
+ const stdout = runtime.stdout ?? process.stdout;
154
+ const cwd = resolve(runtime.cwd ?? process.cwd());
155
+ const project = findProjectManifest(cwd);
156
+ const scripts = listScripts(project.manifest);
157
+ stdout.write('fluo analyze\n');
158
+ stdout.write(` Project manifest: ${project.path ?? 'not found'}\n`);
159
+ stdout.write(` Available scripts: ${scripts.length > 0 ? scripts.join(', ') : 'none'}\n`);
160
+ stdout.write(' Deep inspection: run `fluo inspect <module-path> --report --output <file>` for runtime graph diagnostics.\n');
161
+ stdout.write(' Migration preview: run `fluo migrate <path> --json` for codemod diagnostics.\n');
162
+ return 0;
163
+ }
@@ -264,7 +264,7 @@ export function newUsage() {
264
264
  }, {
265
265
  header: 'Description',
266
266
  render: entry => entry.description
267
- }]), '', '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');
267
+ }]), '', 'Next steps:', ' cd <app-name>', ' pnpm dev # runs fluo dev from the generated package.json script', '', 'Docs: https://github.com/fluojs/fluo/tree/main/docs/getting-started/quick-start.md'].join('\n');
268
268
  }
269
269
 
270
270
  /**
@@ -364,7 +364,7 @@ export async function runNewCommand(argv, runtime = {}) {
364
364
  clackLog.step('Dependency installation skipped');
365
365
  }
366
366
  stdout.write('Done.\n');
367
- 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`);
367
+ stdout.write(`Next steps:\n cd ${answers.targetDirectory}\n ${answers.packageManager === 'npm' ? 'npm run dev' : answers.packageManager === 'bun' ? 'bun run dev' : `${answers.packageManager} dev`} # runs fluo dev\n`);
368
368
  return 0;
369
369
  } catch (error) {
370
370
  if (isCliPromptCancelledError(error)) {
@@ -0,0 +1,9 @@
1
+ type JsonRecord = Record<string, unknown>;
2
+ export declare const SUPPORTED_PACKAGE_MANAGERS: Set<string>;
3
+ export declare function detectPackageManager(options: {
4
+ cwd: string;
5
+ env: NodeJS.ProcessEnv;
6
+ manifest?: JsonRecord;
7
+ }): string;
8
+ export {};
9
+ //# sourceMappingURL=package-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../../src/commands/package-manager.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C,eAAO,MAAM,0BAA0B,aAA0C,CAAC;AA+ClF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,UAAU,CAAA;CAAE,GAAG,MAAM,CA0BpH"}
@@ -0,0 +1,63 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ export const SUPPORTED_PACKAGE_MANAGERS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null;
6
+ }
7
+ function readJsonFile(filePath) {
8
+ try {
9
+ const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
10
+ return isRecord(parsed) ? parsed : undefined;
11
+ } catch (_error) {
12
+ return undefined;
13
+ }
14
+ }
15
+ function packageManagerFromManifest(manifest) {
16
+ const packageManager = manifest.packageManager;
17
+ if (typeof packageManager !== 'string') {
18
+ return undefined;
19
+ }
20
+ const manager = packageManager.split('@')[0];
21
+ return manager && SUPPORTED_PACKAGE_MANAGERS.has(manager) ? manager : undefined;
22
+ }
23
+ function packageManagerFromLockfile(directory) {
24
+ if (existsSync(join(directory, 'pnpm-lock.yaml'))) {
25
+ return 'pnpm';
26
+ }
27
+ if (existsSync(join(directory, 'bun.lockb')) || existsSync(join(directory, 'bun.lock'))) {
28
+ return 'bun';
29
+ }
30
+ if (existsSync(join(directory, 'yarn.lock'))) {
31
+ return 'yarn';
32
+ }
33
+ if (existsSync(join(directory, 'package-lock.json')) || existsSync(join(directory, 'npm-shrinkwrap.json'))) {
34
+ return 'npm';
35
+ }
36
+ return undefined;
37
+ }
38
+ function packageManagerFromUserAgent(env) {
39
+ const userAgentName = env.npm_config_user_agent?.split(' ')[0]?.split('/')[0];
40
+ return userAgentName && SUPPORTED_PACKAGE_MANAGERS.has(userAgentName) ? userAgentName : undefined;
41
+ }
42
+ export function detectPackageManager(options) {
43
+ const startDirectory = resolve(options.cwd);
44
+ let current = startDirectory;
45
+ while (true) {
46
+ const manifestPath = join(current, 'package.json');
47
+ const manifest = current === startDirectory && options.manifest ? options.manifest : existsSync(manifestPath) ? readJsonFile(manifestPath) : undefined;
48
+ const manifestManager = manifest ? packageManagerFromManifest(manifest) : undefined;
49
+ if (manifestManager) {
50
+ return manifestManager;
51
+ }
52
+ const lockfileManager = packageManagerFromLockfile(current);
53
+ if (lockfileManager) {
54
+ return lockfileManager;
55
+ }
56
+ const parent = dirname(current);
57
+ if (parent === current) {
58
+ break;
59
+ }
60
+ current = parent;
61
+ }
62
+ return packageManagerFromUserAgent(options.env) ?? 'pnpm';
63
+ }
@@ -0,0 +1,20 @@
1
+ type CliStream = {
2
+ write(message: string): unknown;
3
+ };
4
+ type PackageWorkflowRuntimeOptions = {
5
+ cwd?: string;
6
+ env?: NodeJS.ProcessEnv;
7
+ fetchDistTags?: (packageName: string) => Promise<Record<string, string> | undefined>;
8
+ spawnCommand?: (command: string, args: string[], options: {
9
+ cwd: string;
10
+ env: NodeJS.ProcessEnv;
11
+ stdio: 'inherit';
12
+ }) => Promise<number>;
13
+ stdout?: CliStream;
14
+ };
15
+ export declare function addUsage(): string;
16
+ export declare function upgradeUsage(): string;
17
+ export declare function runAddCommand(argv: string[], runtime?: PackageWorkflowRuntimeOptions): Promise<number>;
18
+ export declare function runUpgradeCommand(argv: string[], runtime?: PackageWorkflowRuntimeOptions): Promise<number>;
19
+ export {};
20
+ //# sourceMappingURL=package-workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-workflow.d.ts","sourceRoot":"","sources":["../../src/commands/package-workflow.ts"],"names":[],"mappings":"AAGA,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;IACrF,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACxI,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,CAAC;AAgFF,wBAAgB,QAAQ,IAAI,MAAM,CAYjC;AAED,wBAAgB,YAAY,IAAI,MAAM,CASrC;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,6BAAkC,GAAG,OAAO,CAAC,MAAM,CAAC,CAkDhH;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,6BAAkC,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBpH"}