@fluojs/cli 1.0.6 → 2.0.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 (51) hide show
  1. package/README.ko.md +31 -10
  2. package/README.md +31 -10
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +4 -3
  5. package/dist/commands/generate.d.ts +1 -49
  6. package/dist/commands/generate.d.ts.map +1 -1
  7. package/dist/commands/generate.js +1 -214
  8. package/dist/commands/inspect.d.ts +0 -6
  9. package/dist/commands/inspect.d.ts.map +1 -1
  10. package/dist/commands/inspect.js +1 -48
  11. package/dist/commands/new.d.ts +0 -6
  12. package/dist/commands/new.d.ts.map +1 -1
  13. package/dist/commands/new.js +14 -88
  14. package/dist/commands/scripts.d.ts +1 -1
  15. package/dist/commands/scripts.d.ts.map +1 -1
  16. package/dist/commands/scripts.js +16 -7
  17. package/dist/dev-runner/node-restart-runner.d.ts +6 -0
  18. package/dist/dev-runner/node-restart-runner.d.ts.map +1 -1
  19. package/dist/dev-runner/node-restart-runner.js +43 -8
  20. package/dist/generate-command.d.ts +50 -0
  21. package/dist/generate-command.d.ts.map +1 -0
  22. package/dist/generate-command.js +214 -0
  23. package/dist/index.d.ts +4 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -2
  26. package/dist/new/scaffold.d.ts.map +1 -1
  27. package/dist/new/scaffold.js +115 -23
  28. package/dist/new/types.d.ts +0 -2
  29. package/dist/new/types.d.ts.map +1 -1
  30. package/dist/public-generate.d.ts +2 -0
  31. package/dist/public-generate.d.ts.map +1 -0
  32. package/dist/public-generate.js +1 -0
  33. package/dist/public-inspect.d.ts +13 -0
  34. package/dist/public-inspect.d.ts.map +1 -0
  35. package/dist/public-inspect.js +16 -0
  36. package/dist/public-new.d.ts +13 -0
  37. package/dist/public-new.d.ts.map +1 -0
  38. package/dist/public-new.js +16 -0
  39. package/dist/run-cli.d.ts +16 -0
  40. package/dist/run-cli.d.ts.map +1 -0
  41. package/dist/run-cli.js +16 -0
  42. package/dist/studio/sidecar.d.ts.map +1 -1
  43. package/dist/studio/sidecar.js +113 -27
  44. package/dist/types.d.ts +6 -1
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/update-check.d.ts.map +1 -1
  47. package/dist/update-check.js +21 -4
  48. package/dist/usage.d.ts +13 -0
  49. package/dist/usage.d.ts.map +1 -0
  50. package/dist/usage.js +131 -0
  51. package/package.json +3 -3
@@ -17,12 +17,6 @@ export interface NewCommandRuntimeOptions extends NewCommandOptions {
17
17
  stdout?: CliStream;
18
18
  userAgent?: string;
19
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
20
  /**
27
21
  * Executes `fluo new` with parsed arguments and scaffold options.
28
22
  *
@@ -1 +1 @@
1
- {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AAOA,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAWnF,OAAO,KAAK,EAAoB,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAE3E,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAuBF;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0UD;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CA0BjC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CA4G3G"}
1
+ {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,iBAAiB,EAA2B,MAAM,kBAAkB,CAAC;AAWnF,OAAO,KAAK,EAAsC,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAI7F,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAiCF;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiPD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CA6G3G"}
@@ -1,12 +1,12 @@
1
1
  import { resolve } from 'node:path';
2
- import { spinner as clackSpinner, log as clackLog } from '@clack/prompts';
3
- import { renderAliasList, renderHelpTable } from '../help.js';
4
- import { isCliPromptCancelledError } from '../prompt-cancel.js';
2
+ import { log as clackLog, spinner as clackSpinner } from '@clack/prompts';
5
3
  import { installDependencies } from '../new/install.js';
6
4
  import { collectBootstrapAnswers } from '../new/prompt.js';
7
5
  import { resolveBootstrapPlan } from '../new/resolver.js';
8
6
  import { scaffoldBootstrapApp } from '../new/scaffold.js';
9
7
  import { SUPPORTED_BOOTSTRAP_PLATFORMS, SUPPORTED_BOOTSTRAP_RUNTIMES, SUPPORTED_BOOTSTRAP_SHAPES, SUPPORTED_BOOTSTRAP_TOOLING_PRESETS, SUPPORTED_BOOTSTRAP_TOPOLOGY_MODES, SUPPORTED_BOOTSTRAP_TRANSPORTS } from '../new/starter-profiles.js';
8
+ import { isCliPromptCancelledError } from '../prompt-cancel.js';
9
+ import { newUsage } from '../usage.js';
10
10
  function shouldUseInteractiveShell(runtime) {
11
11
  return runtime.prompt === undefined && runtime.stdout === undefined && runtime.stderr === undefined && (runtime.interactive ?? true) && Boolean(runtime.stdin?.isTTY ?? process.stdin.isTTY);
12
12
  }
@@ -20,76 +20,19 @@ function extractDependencyInstallationOutput(error) {
20
20
  function isHelpFlag(value) {
21
21
  return value === '--help' || value === '-h';
22
22
  }
23
+ function readInternalScaffoldOverrides(runtime) {
24
+ const dependencySource = 'dependencySource' in runtime ? runtime.dependencySource : undefined;
25
+ const repoRoot = 'repoRoot' in runtime ? runtime.repoRoot : undefined;
26
+ return {
27
+ dependencySource: dependencySource === 'local' || dependencySource === 'published' ? dependencySource : undefined,
28
+ repoRoot: typeof repoRoot === 'string' ? repoRoot : undefined
29
+ };
30
+ }
23
31
 
24
32
  /**
25
33
  * Runtime dependency overrides for the programmatic `fluo new` entry point.
26
34
  */
27
35
 
28
- const NEW_OPTION_HELP = [{
29
- aliases: [],
30
- description: 'Provide the project name without using the positional argument.',
31
- option: '--name <project-name>'
32
- }, {
33
- aliases: [],
34
- description: 'Select the scaffold shape explicitly (application for HTTP, microservice for the transport-driven starter path, mixed for the API + microservice starter).',
35
- option: '--shape <application|microservice|mixed>'
36
- }, {
37
- aliases: [],
38
- description: 'Select the transport path explicitly (http for applications, tcp for the runnable microservice starter, plus shipped microservice starter transports).',
39
- option: '--transport <http|tcp|redis-streams|nats|kafka|rabbitmq|mqtt|grpc>'
40
- }, {
41
- aliases: [],
42
- description: 'Select the runtime explicitly (node, bun, deno, or cloudflare-workers for application starters; node for microservice and mixed starters).',
43
- option: '--runtime <node|bun|deno|cloudflare-workers>'
44
- }, {
45
- aliases: [],
46
- description: 'Select the platform adapter explicitly (fastify, express, or nodejs on node; bun/deno/cloudflare-workers on their native runtimes; none for microservices).',
47
- option: '--platform <fastify|express|nodejs|bun|deno|cloudflare-workers|none>'
48
- }, {
49
- aliases: [],
50
- description: 'Select the starter tooling preset explicitly (currently only standard).',
51
- option: '--tooling <standard>'
52
- }, {
53
- aliases: [],
54
- description: 'Select the starter topology mode explicitly (currently only single-package).',
55
- option: '--topology <single-package>'
56
- }, {
57
- aliases: [],
58
- description: 'Choose which package manager installs the starter dependencies.',
59
- option: '--package-manager <pnpm|npm|yarn|bun>'
60
- }, {
61
- aliases: [],
62
- description: 'Write the new app to a custom target directory (always overrides positional name path).',
63
- option: '--target-directory <path>'
64
- }, {
65
- aliases: [],
66
- description: 'Overwrite files in a non-empty target directory without prompting.',
67
- option: '--force'
68
- }, {
69
- aliases: [],
70
- description: 'Install starter dependencies after writing files.',
71
- option: '--install'
72
- }, {
73
- aliases: [],
74
- description: 'Skip starter dependency installation.',
75
- option: '--no-install'
76
- }, {
77
- aliases: [],
78
- description: 'Initialize a git repository in the generated starter.',
79
- option: '--git'
80
- }, {
81
- aliases: [],
82
- description: 'Skip git repository initialization in the generated starter.',
83
- option: '--no-git'
84
- }, {
85
- aliases: [],
86
- description: 'Print the resolved scaffold plan without writing files, installing dependencies, or initializing git.',
87
- option: '--print-plan'
88
- }, {
89
- aliases: ['-h'],
90
- description: 'Show help for the new command.',
91
- option: '--help'
92
- }];
93
36
  const SUPPORTED_PACKAGE_MANAGERS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
94
37
  const SUPPORTED_SHAPES = new Set(SUPPORTED_BOOTSTRAP_SHAPES);
95
38
  const SUPPORTED_TRANSPORTS = new Set(SUPPORTED_BOOTSTRAP_TRANSPORTS);
@@ -249,24 +192,6 @@ function renderScaffoldPlanPreview(answers, resolvedTargetDirectory) {
249
192
  return ['fluo new scaffold plan', '', `Project name: ${answers.projectName}`, `Target directory: ${answers.targetDirectory}`, `Resolved target: ${resolvedTargetDirectory}`, `Shape: ${answers.shape}`, `Runtime: ${answers.runtime}`, `Platform: ${answers.platform}`, `Transport: ${answers.transport}`, `Tooling preset: ${answers.tooling}`, `Topology: ${answers.topology.mode}${answers.topology.deferred ? ' (deferred)' : ''}`, `Starter recipe: ${bootstrapPlan.profile.id}`, `Emitter: ${bootstrapPlan.emitter.type}`, `Package manager: ${answers.packageManager}`, `Install dependencies: ${answers.installDependencies ? 'yes' : 'no'}`, `Initialize git: ${answers.initializeGit ? 'yes' : 'no'}`, '', 'Dependencies:', ` runtime: ${renderDependencyList(bootstrapPlan.dependencies.dependencies)}`, ` dev: ${renderDependencyList(bootstrapPlan.dependencies.devDependencies)}`, '', 'Side effects: none. Preview mode does not create files, install dependencies, or initialize git.'].join('\n');
250
193
  }
251
194
 
252
- /**
253
- * Renders CLI help text for `fluo new`.
254
- *
255
- * @returns Stable help output for the scaffolding command.
256
- */
257
- export function newUsage() {
258
- return ['Usage: fluo new|create [project-name] [options]', '', 'Options', renderHelpTable(NEW_OPTION_HELP, [{
259
- header: 'Option',
260
- render: entry => entry.option
261
- }, {
262
- header: 'Aliases',
263
- render: entry => renderAliasList(entry.aliases)
264
- }, {
265
- header: 'Description',
266
- render: entry => entry.description
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
- }
269
-
270
195
  /**
271
196
  * Executes `fluo new` with parsed arguments and scaffold options.
272
197
  *
@@ -313,13 +238,14 @@ export async function runNewCommand(argv, runtime = {}) {
313
238
  stdout.write(`${renderScaffoldPlanPreview(answers, targetDirectory)}\n`);
314
239
  return 0;
315
240
  }
241
+ const internalScaffoldOverrides = readInternalScaffoldOverrides(runtime);
316
242
  const options = {
317
243
  ...answers,
318
- dependencySource: runtime.dependencySource,
244
+ dependencySource: internalScaffoldOverrides.dependencySource,
319
245
  force: parsed.force ?? runtime.force,
320
246
  initializeGit: answers.initializeGit,
321
247
  installDependencies: false,
322
- repoRoot: runtime.repoRoot,
248
+ repoRoot: internalScaffoldOverrides.repoRoot,
323
249
  skipInstall: true,
324
250
  targetDirectory
325
251
  };
@@ -1,4 +1,4 @@
1
- import { startStudioSidecar } from '../studio/sidecar.js';
1
+ import type { startStudioSidecar } from '../studio/sidecar.js';
2
2
  type CliStream = {
3
3
  isTTY?: boolean;
4
4
  write(message: string): unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"scripts.d.ts","sourceRoot":"","sources":["../../src/commands/scripts.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAiD,MAAM,sBAAsB,CAAC;AAGzG,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAMF,KAAK,mBAAmB,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClG,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;IAC/C,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,CAAC;AAGF,KAAK,aAAa,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;AAulB/C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAqB1D;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsFlI"}
1
+ {"version":3,"file":"scripts.d.ts","sourceRoot":"","sources":["../../src/commands/scripts.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAuC,MAAM,sBAAsB,CAAC;AAGpG,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAMF,KAAK,mBAAmB,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClG,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;IAC/C,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,CAAC;AAKF,KAAK,aAAa,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;AAkmB/C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAqB1D;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsFlI"}
@@ -3,7 +3,6 @@ import { existsSync, readFileSync } from 'node:fs';
3
3
  import { delimiter, dirname, join, resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { createStudioDevtoolsNodeImport } from '../studio/runtime-config.js';
6
- import { startStudioSidecar } from '../studio/sidecar.js';
7
6
  import { SUPPORTED_PACKAGE_MANAGERS } from './package-manager.js';
8
7
  const EMPTY_ENV = {};
9
8
  const FAILURE_STDOUT_BUFFER_LIMIT = 16_384;
@@ -119,6 +118,12 @@ function defaultSpawnCommand(command, args, options) {
119
118
  child.on('close', code => resolveExitCode(code ?? 1));
120
119
  });
121
120
  }
121
+ async function loadDefaultStudioSidecarFactory() {
122
+ const {
123
+ startStudioSidecar: startStudioSidecarImplementation
124
+ } = await import('../studio/sidecar.js');
125
+ return startStudioSidecarImplementation;
126
+ }
122
127
  function buildNativeNodeWatchStep(passThrough) {
123
128
  return {
124
129
  command: 'node',
@@ -390,7 +395,7 @@ function projectRuntimeToStudioRuntime(runtime) {
390
395
  function projectDisplayName(project) {
391
396
  return typeof project.manifest.name === 'string' && project.manifest.name.length > 0 ? project.manifest.name : project.directory.split(/[\\/]/).filter(Boolean).at(-1) ?? 'fluo-app';
392
397
  }
393
- function assertStudioSupport(command, studio, projectRuntime, _devRunner) {
398
+ function assertStudioSupport(command, studio, projectRuntime, devRunner, rawWatch) {
394
399
  if (!studio) {
395
400
  return;
396
401
  }
@@ -400,8 +405,12 @@ function assertStudioSupport(command, studio, projectRuntime, _devRunner) {
400
405
  if (projectRuntime !== 'node') {
401
406
  throw new Error(`fluo dev --studio currently supports Node dev runner projects only. ${projectRuntime} Studio support remains experimental until a dedicated bridge is implemented and verified.`);
402
407
  }
408
+ if (devRunner !== 'fluo' || rawWatch) {
409
+ throw new Error('fluo dev --studio requires the fluo-owned Node restart runner. Remove --raw-watch and use --runner fluo so Studio lifecycle events stay attached to the CLI restart boundary.');
410
+ }
403
411
  }
404
- function withStudioDryRunEnv(env, project, projectRuntime) {
412
+ function withStudioDryRunEnv(env, project, projectRuntime, studioPort) {
413
+ const portLabel = studioPort === undefined ? '<auto>' : String(studioPort);
405
414
  return {
406
415
  ...env,
407
416
  FLUO_STUDIO: '1',
@@ -409,7 +418,7 @@ function withStudioDryRunEnv(env, project, projectRuntime) {
409
418
  FLUO_STUDIO_EPOCH: '<generated-at-runtime>',
410
419
  FLUO_STUDIO_RUNTIME: projectRuntimeToStudioRuntime(projectRuntime),
411
420
  FLUO_STUDIO_TOKEN: '<generated-at-runtime>',
412
- FLUO_STUDIO_URL: 'http://127.0.0.1:<auto>'
421
+ FLUO_STUDIO_URL: `http://127.0.0.1:${portLabel}`
413
422
  };
414
423
  }
415
424
  function renderStep(step) {
@@ -619,7 +628,7 @@ export async function runScriptCommand(command, argv, runtime = {}) {
619
628
  throw new Error('--runner is only supported for fluo dev. Use -- to forward --runner to the child command.');
620
629
  }
621
630
  const devRunner = command === 'dev' ? resolveDevRunnerPreference(parsed, env, projectRuntime) : 'fluo';
622
- assertStudioSupport(command, parsed.studio, projectRuntime, devRunner);
631
+ assertStudioSupport(command, parsed.studio, projectRuntime, devRunner, rawWatch);
623
632
  const runnerSteps = buildProjectRunner(command, projectRuntime, parsed.passThrough, {
624
633
  devRunner,
625
634
  rawWatch
@@ -632,10 +641,10 @@ export async function runScriptCommand(command, argv, runtime = {}) {
632
641
  const verbose = parsed.verbose || isEnabledEnvironmentFlag(env.FLUO_VERBOSE);
633
642
  let childEnv = withPipedReporterColorEnv(withProjectLocalBin(withDefaultNodeEnv(env, defaultNodeEnv), project.directory), reporterMode, stdout, stderr);
634
643
  if (parsed.studio && parsed.dryRun) {
635
- childEnv = withStudioDryRunEnv(childEnv, project, projectRuntime);
644
+ childEnv = withStudioDryRunEnv(childEnv, project, projectRuntime, parsed.studioPort);
636
645
  }
637
646
  if (command === 'dev' && parsed.studio && !parsed.dryRun) {
638
- const studioSidecarFactory = runtime.startStudioSidecar ?? startStudioSidecar;
647
+ const studioSidecarFactory = runtime.startStudioSidecar ?? (await loadDefaultStudioSidecarFactory());
639
648
  const studioSidecar = await studioSidecarFactory({
640
649
  appId: projectDisplayName(project),
641
650
  port: parsed.studioPort,
@@ -19,6 +19,11 @@ type RestartSignalTarget = {
19
19
  type RestartWatcherFactory = (target: string, optionsOrListener: {
20
20
  recursive: boolean;
21
21
  } | ((event: string, filename: string | Buffer | null) => void), listener?: (event: string, filename: string | Buffer | null) => void) => FSWatcher;
22
+ type RestartSchedulerHandle = ReturnType<typeof setTimeout> | number;
23
+ type RestartScheduler = {
24
+ clear(handle: RestartSchedulerHandle): void;
25
+ set(callback: () => void, delayMs: number): RestartSchedulerHandle;
26
+ };
22
27
  type ContentChangeGate = {
23
28
  commitBaseline(paths: Iterable<string>): void;
24
29
  hasMeaningfulChange(paths: Iterable<string>): boolean;
@@ -33,6 +38,7 @@ export type NodeRestartRunnerOptions = {
33
38
  signalTarget?: RestartSignalTarget;
34
39
  spawnChild?: RestartChildSpawner;
35
40
  stderr?: RestartRunnerStream;
41
+ restartScheduler?: RestartScheduler;
36
42
  stdout?: RestartRunnerStream;
37
43
  watchTarget?: RestartWatcherFactory;
38
44
  };
@@ -1 +1 @@
1
- {"version":3,"file":"node-restart-runner.d.ts","sourceRoot":"","sources":["../../src/dev-runner/node-restart-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,EAA0D,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAKjG,KAAK,mBAAmB,GAAG;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,mBAAmB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,KAAK,YAAY,CAAC;AACjJ,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9E,KAAK,aAAa,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE1C,KAAK,mBAAmB,GAAG;IACzB,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;IAC1D,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;CAC5D,CAAC;AAEF,KAAK,qBAAqB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,KAAK,IAAI,KAAK,SAAS,CAAC;AAE1O,KAAK,iBAAiB,GAAG;IACvB,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC9C,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;CACvD,CAAC;AAEF,uFAAuF;AACvF,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC,CAAC;AA2LF;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,gBAAgB,EAAE,MAAM,EAAE,cAAc,GAAE,MAAM,EAAoB,GAAG,iBAAiB,CAuB/H;AAkFD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiP7F"}
1
+ {"version":3,"file":"node-restart-runner.d.ts","sourceRoot":"","sources":["../../src/dev-runner/node-restart-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,EAA0D,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAKjG,KAAK,mBAAmB,GAAG;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,mBAAmB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,KAAK,YAAY,CAAC;AACjJ,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9E,KAAK,aAAa,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE1C,KAAK,mBAAmB,GAAG;IACzB,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;IAC1D,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;CAC5D,CAAC;AAEF,KAAK,qBAAqB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,KAAK,IAAI,KAAK,SAAS,CAAC;AAE1O,KAAK,sBAAsB,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,MAAM,CAAC;AAErE,KAAK,gBAAgB,GAAG;IACtB,KAAK,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC5C,GAAG,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAAC;CACpE,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC9C,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;CACvD,CAAC;AAEF,uFAAuF;AACvF,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC,CAAC;AA6NF;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,gBAAgB,EAAE,MAAM,EAAE,cAAc,GAAE,MAAM,EAAoB,GAAG,iBAAiB,CAuB/H;AAkFD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoP7F"}
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { createHash } from 'node:crypto';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
3
  import { existsSync, readdirSync, readFileSync, statSync, watch } from 'node:fs';
4
4
  import { basename, dirname, join, relative, sep } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
@@ -16,6 +16,7 @@ const DEFAULT_IGNORES = ['.cache', '.fluo', '.git', '.turbo', 'coverage', 'dist'
16
16
  const WATCH_FILES = ['.env', 'package.json', 'tsconfig.json', 'tsconfig.build.json'];
17
17
  const SHOW_NODE_RESTART_NOTICE_ENV = 'FLUO_DEV_SHOW_RESTART_NOTICE';
18
18
  const CLEAR_SCREEN = '\u001B[2J\u001B[3J\u001B[H';
19
+ const STUDIO_EPOCH_ENV = 'FLUO_STUDIO_EPOCH';
19
20
  function isEnabledEnvironmentFlag(value) {
20
21
  return value === '1' || value === 'true' || value === 'yes';
21
22
  }
@@ -41,6 +42,34 @@ function resolveStudioIngestEndpoint(env) {
41
42
  return undefined;
42
43
  }
43
44
  }
45
+ function hasStudioDevtoolsConfig(env) {
46
+ return isEnabledEnvironmentFlag(env.FLUO_STUDIO) && Boolean(env.FLUO_STUDIO_TOKEN);
47
+ }
48
+ function createStudioEpoch() {
49
+ return randomUUID();
50
+ }
51
+ function ensureStudioEpoch(env) {
52
+ if (!hasStudioDevtoolsConfig(env)) {
53
+ return undefined;
54
+ }
55
+ env[STUDIO_EPOCH_ENV] ??= createStudioEpoch();
56
+ return env[STUDIO_EPOCH_ENV];
57
+ }
58
+ function advanceStudioEpoch(env) {
59
+ if (!hasStudioDevtoolsConfig(env)) {
60
+ return undefined;
61
+ }
62
+ const epoch = createStudioEpoch();
63
+ env[STUDIO_EPOCH_ENV] = epoch;
64
+ return epoch;
65
+ }
66
+ function withStudioEpoch(env, payload) {
67
+ const epoch = ensureStudioEpoch(env);
68
+ return epoch ? {
69
+ ...payload,
70
+ epoch
71
+ } : payload;
72
+ }
44
73
  function publishStudioLifecycleEvent(env, runtime, type, payload) {
45
74
  const endpoint = resolveStudioIngestEndpoint(env);
46
75
  if (!endpoint || typeof globalThis.fetch !== 'function') {
@@ -48,7 +77,7 @@ function publishStudioLifecycleEvent(env, runtime, type, payload) {
48
77
  }
49
78
  void globalThis.fetch(endpoint, {
50
79
  body: JSON.stringify({
51
- payload,
80
+ payload: withStudioEpoch(env, payload),
52
81
  source: {
53
82
  appId: env.FLUO_STUDIO_APP_ID ?? basename(process.cwd()),
54
83
  runtime: studioRuntimeName(runtime)
@@ -71,7 +100,7 @@ function parseIgnorePatterns(env) {
71
100
  return normalizeIgnorePatterns([...DEFAULT_IGNORES, ...configured]);
72
101
  }
73
102
  function matchesSegmentPattern(segment, pattern) {
74
- if (pattern.startsWith('*.')) {
103
+ if (pattern.startsWith('*')) {
75
104
  return segment.endsWith(pattern.slice(1));
76
105
  }
77
106
  if (pattern.endsWith('*')) {
@@ -264,6 +293,10 @@ export async function runNodeRestartRunner(options) {
264
293
  const spawnChild = options.spawnChild ?? spawn;
265
294
  const stdout = options.stdout ?? process.stdout;
266
295
  const stderr = options.stderr ?? process.stderr;
296
+ const restartScheduler = options.restartScheduler ?? {
297
+ clear: clearTimeout,
298
+ set: setTimeout
299
+ };
267
300
  const watchTarget = options.watchTarget ?? watch;
268
301
  const appArgs = options.appArgs ?? [];
269
302
  const debounceMs = options.debounceMs ?? Number(env.FLUO_DEV_RELOAD_DEBOUNCE_MS ?? DEFAULT_DEBOUNCE_MS);
@@ -278,6 +311,7 @@ export async function runNodeRestartRunner(options) {
278
311
  let restarting = false;
279
312
  let stopping = false;
280
313
  const startChild = (resolveExitCode, cleanup) => {
314
+ ensureStudioEpoch(env);
281
315
  const appCommand = buildAppCommand(runnerRuntime, env, appArgs);
282
316
  publishStudioLifecycleEvent(env, runnerRuntime, 'restart', {
283
317
  phase: 'starting',
@@ -328,10 +362,10 @@ export async function runNodeRestartRunner(options) {
328
362
  };
329
363
  const scheduleRestart = (filePath, resolveExitCode, cleanup) => {
330
364
  pendingRestartPaths.add(filePath);
331
- if (restartTimer) {
332
- clearTimeout(restartTimer);
365
+ if (restartTimer !== undefined) {
366
+ restartScheduler.clear(restartTimer);
333
367
  }
334
- restartTimer = setTimeout(() => {
368
+ restartTimer = restartScheduler.set(() => {
335
369
  const restartPaths = [...pendingRestartPaths];
336
370
  pendingRestartPaths.clear();
337
371
  restartTimer = undefined;
@@ -341,6 +375,7 @@ export async function runNodeRestartRunner(options) {
341
375
  if (env[SHOW_NODE_RESTART_NOTICE_ENV] === '1') {
342
376
  stdout.write(`[fluo] restarting after content change: ${relative(projectDirectory, restartPaths[restartPaths.length - 1] ?? projectDirectory)}\n`);
343
377
  }
378
+ advanceStudioEpoch(env);
344
379
  publishStudioLifecycleEvent(env, runnerRuntime, 'restart', {
345
380
  phase: 'scheduled',
346
381
  reason: `content changed: ${relative(projectDirectory, restartPaths[restartPaths.length - 1] ?? projectDirectory)}`
@@ -414,8 +449,8 @@ export async function runNodeRestartRunner(options) {
414
449
  return;
415
450
  }
416
451
  cleanedUp = true;
417
- if (restartTimer) {
418
- clearTimeout(restartTimer);
452
+ if (restartTimer !== undefined) {
453
+ restartScheduler.clear(restartTimer);
419
454
  restartTimer = undefined;
420
455
  }
421
456
  pendingRestartPaths.clear();
@@ -0,0 +1,50 @@
1
+ import type { GeneratorManifestEntry } from './generators/manifest.js';
2
+ import type { GenerateOptions, GeneratorKind } from './types.js';
3
+ /** Describes how one generated artifact would interact with the workspace. */
4
+ export type GeneratePlanAction = 'create' | 'module-create' | 'module-unchanged' | 'module-update' | 'overwrite' | 'skip' | 'unchanged';
5
+ /** One path-level action reported by generate dry-run previews and structured results. */
6
+ export type GeneratePlanEntry = {
7
+ /** Planned action for this path. */
8
+ action: GeneratePlanAction;
9
+ /** Absolute path affected by the plan entry. */
10
+ path: string;
11
+ };
12
+ /**
13
+ * Structured result returned by {@link runGenerateCommand} for tooling-friendly automation.
14
+ *
15
+ * `generatedFiles` only includes files whose on-disk content changed during the command.
16
+ * `moduleRegistered` reports whether the target schematic participates in automatic module wiring,
17
+ * even when the target module file was already up to date.
18
+ */
19
+ export type GenerateResult = {
20
+ generatedFiles: string[];
21
+ moduleRegistered: boolean;
22
+ modulePath: string | undefined;
23
+ nextStepHint: string;
24
+ plannedFiles: GeneratePlanEntry[];
25
+ wiringBehavior: GeneratorManifestEntry['wiringBehavior'];
26
+ };
27
+ /**
28
+ * Generates one CLI schematic into a source directory and returns structured wiring metadata.
29
+ *
30
+ * The command keeps generation idempotent where possible: unchanged files are not rewritten, and
31
+ * auto-registered schematics reuse an existing module file when it already contains the required import
32
+ * and registration entry.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const result = runGenerateCommand('service', 'Post', './src');
37
+ *
38
+ * console.log(result.wiringBehavior);
39
+ * console.log(result.nextStepHint);
40
+ * ```
41
+ *
42
+ * @param kind Generator kind to execute.
43
+ * @param name Resource name supplied by the caller before normalization.
44
+ * @param baseDirectory Source directory that should receive the generated domain folder.
45
+ * @param options Optional generation flags that control overwrites, request DTO feature placement, and sibling-aware templates.
46
+ * @returns Structured file and wiring metadata for the completed generation run.
47
+ * @throws {Error} When the resource name is invalid, the generator kind is unknown, or the target module source cannot be updated safely.
48
+ */
49
+ export declare function runGenerateCommand(kind: GeneratorKind, name: string, baseDirectory: string, options?: GenerateOptions): GenerateResult;
50
+ //# sourceMappingURL=generate-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-command.d.ts","sourceRoot":"","sources":["../src/generate-command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAkB,MAAM,0BAA0B,CAAC;AAIvF,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAWjE,8EAA8E;AAC9E,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,eAAe,GAAG,kBAAkB,GAAG,eAAe,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,CAAC;AAExI,0FAA0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,oCAAoC;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAyIF;;;;;;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,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,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,CAgE1I"}