@fluojs/cli 1.1.0 → 3.0.0

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 (137) hide show
  1. package/README.ko.md +183 -22
  2. package/README.md +186 -22
  3. package/dist/cli.d.ts +5 -4
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +128 -35
  6. package/dist/commands/diagnostics.d.ts +35 -0
  7. package/dist/commands/diagnostics.d.ts.map +1 -1
  8. package/dist/commands/diagnostics.js +60 -0
  9. package/dist/commands/generate.d.ts +1 -49
  10. package/dist/commands/generate.d.ts.map +1 -1
  11. package/dist/commands/generate.js +1 -214
  12. package/dist/commands/inspect.d.ts +5 -7
  13. package/dist/commands/inspect.d.ts.map +1 -1
  14. package/dist/commands/inspect.js +85 -65
  15. package/dist/commands/migrate.d.ts.map +1 -1
  16. package/dist/commands/migrate.js +27 -15
  17. package/dist/commands/migration-transform-tokens.d.ts +15 -0
  18. package/dist/commands/migration-transform-tokens.d.ts.map +1 -0
  19. package/dist/commands/migration-transform-tokens.js +41 -0
  20. package/dist/commands/new.d.ts +0 -6
  21. package/dist/commands/new.d.ts.map +1 -1
  22. package/dist/commands/new.js +42 -96
  23. package/dist/commands/output-path-safety.d.ts +14 -0
  24. package/dist/commands/output-path-safety.d.ts.map +1 -0
  25. package/dist/commands/output-path-safety.js +34 -0
  26. package/dist/commands/scripts.d.ts +1 -1
  27. package/dist/commands/scripts.d.ts.map +1 -1
  28. package/dist/commands/scripts.js +13 -7
  29. package/dist/commands/typegen-artifact.d.ts +54 -0
  30. package/dist/commands/typegen-artifact.d.ts.map +1 -0
  31. package/dist/commands/typegen-artifact.js +117 -0
  32. package/dist/commands/typegen-generation-child.d.ts +2 -0
  33. package/dist/commands/typegen-generation-child.d.ts.map +1 -0
  34. package/dist/commands/typegen-generation-child.js +59 -0
  35. package/dist/commands/typegen-generation-process.d.ts +46 -0
  36. package/dist/commands/typegen-generation-process.d.ts.map +1 -0
  37. package/dist/commands/typegen-generation-process.js +131 -0
  38. package/dist/commands/typegen-generation-protocol.d.ts +16 -0
  39. package/dist/commands/typegen-generation-protocol.d.ts.map +1 -0
  40. package/dist/commands/typegen-generation-protocol.js +35 -0
  41. package/dist/commands/typegen-isolated-source.d.ts +10 -0
  42. package/dist/commands/typegen-isolated-source.d.ts.map +1 -0
  43. package/dist/commands/typegen-isolated-source.js +75 -0
  44. package/dist/commands/typegen-options.d.ts +20 -0
  45. package/dist/commands/typegen-options.d.ts.map +1 -0
  46. package/dist/commands/typegen-options.js +71 -0
  47. package/dist/commands/typegen-source.d.ts +59 -0
  48. package/dist/commands/typegen-source.d.ts.map +1 -0
  49. package/dist/commands/typegen-source.js +183 -0
  50. package/dist/commands/typegen-watch.d.ts +46 -0
  51. package/dist/commands/typegen-watch.d.ts.map +1 -0
  52. package/dist/commands/typegen-watch.js +212 -0
  53. package/dist/commands/typegen.d.ts +29 -0
  54. package/dist/commands/typegen.d.ts.map +1 -0
  55. package/dist/commands/typegen.js +119 -0
  56. package/dist/dev-runner/node-restart-runner.d.ts +6 -0
  57. package/dist/dev-runner/node-restart-runner.d.ts.map +1 -1
  58. package/dist/dev-runner/node-restart-runner.js +64 -13
  59. package/dist/fixtures/inspect-react-app.module.d.ts +4 -0
  60. package/dist/fixtures/inspect-react-app.module.d.ts.map +1 -0
  61. package/dist/fixtures/inspect-react-app.module.js +32 -0
  62. package/dist/fixtures/typegen-react-app.module.d.ts +4 -0
  63. package/dist/fixtures/typegen-react-app.module.d.ts.map +1 -0
  64. package/dist/fixtures/typegen-react-app.module.js +33 -0
  65. package/dist/generate-command.d.ts +50 -0
  66. package/dist/generate-command.d.ts.map +1 -0
  67. package/dist/generate-command.js +214 -0
  68. package/dist/index.d.ts +6 -5
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +6 -5
  71. package/dist/new/install.js +1 -1
  72. package/dist/new/package-spec-resolver.d.ts.map +1 -1
  73. package/dist/new/package-spec-resolver.js +50 -59
  74. package/dist/new/prompt.d.ts +1 -0
  75. package/dist/new/prompt.d.ts.map +1 -1
  76. package/dist/new/prompt.js +40 -12
  77. package/dist/new/published-internal-dependencies.d.ts +20 -0
  78. package/dist/new/published-internal-dependencies.d.ts.map +1 -0
  79. package/dist/new/published-internal-dependencies.js +20 -0
  80. package/dist/new/react-vite-ssr-scaffold.d.ts +19 -0
  81. package/dist/new/react-vite-ssr-scaffold.d.ts.map +1 -0
  82. package/dist/new/react-vite-ssr-scaffold.js +108 -0
  83. package/dist/new/resolver.d.ts +2 -2
  84. package/dist/new/resolver.d.ts.map +1 -1
  85. package/dist/new/resolver.js +5 -1
  86. package/dist/new/scaffold.d.ts.map +1 -1
  87. package/dist/new/scaffold.js +177 -54
  88. package/dist/new/starter-profiles.d.ts +8 -4
  89. package/dist/new/starter-profiles.d.ts.map +1 -1
  90. package/dist/new/starter-profiles.js +52 -18
  91. package/dist/new/templates/react-vite-ssr/README.md.ejs +74 -0
  92. package/dist/new/templates/react-vite-ssr/playwright.config.ts.ejs +26 -0
  93. package/dist/new/templates/react-vite-ssr/src/app.test.ts.ejs +82 -0
  94. package/dist/new/templates/react-vite-ssr/src/app.ts.ejs +91 -0
  95. package/dist/new/templates/react-vite-ssr/src/entry-client.tsx.ejs +29 -0
  96. package/dist/new/templates/react-vite-ssr/src/entry-server.tsx.ejs +52 -0
  97. package/dist/new/templates/react-vite-ssr/src/load-manifest.test.ts.ejs +55 -0
  98. package/dist/new/templates/react-vite-ssr/src/load-manifest.ts.ejs +53 -0
  99. package/dist/new/templates/react-vite-ssr/src/main.ts.ejs +18 -0
  100. package/dist/new/templates/react-vite-ssr/src/page.tsx.ejs +43 -0
  101. package/dist/new/templates/react-vite-ssr/src/react-app.test.tsx.ejs +67 -0
  102. package/dist/new/templates/react-vite-ssr/src/react-app.tsx.ejs +46 -0
  103. package/dist/new/templates/react-vite-ssr/src/styles.css.ejs +25 -0
  104. package/dist/new/templates/react-vite-ssr/src/styles.d.ts.ejs +1 -0
  105. package/dist/new/templates/react-vite-ssr/tests/production-hydration.spec.ts.ejs +54 -0
  106. package/dist/new/templates/react-vite-ssr/tsconfig.json.ejs +16 -0
  107. package/dist/new/templates/react-vite-ssr/vite.client.config.ts.ejs +24 -0
  108. package/dist/new/templates/react-vite-ssr/vite.server.config.ts.ejs +17 -0
  109. package/dist/new/templates/react-vite-ssr/vitest.config.ts.ejs +10 -0
  110. package/dist/new/types.d.ts +4 -2
  111. package/dist/new/types.d.ts.map +1 -1
  112. package/dist/public-generate.d.ts +2 -0
  113. package/dist/public-generate.d.ts.map +1 -0
  114. package/dist/public-generate.js +1 -0
  115. package/dist/public-inspect.d.ts +13 -0
  116. package/dist/public-inspect.d.ts.map +1 -0
  117. package/dist/public-inspect.js +16 -0
  118. package/dist/public-new.d.ts +13 -0
  119. package/dist/public-new.d.ts.map +1 -0
  120. package/dist/public-new.js +16 -0
  121. package/dist/public-typegen.d.ts +14 -0
  122. package/dist/public-typegen.d.ts.map +1 -0
  123. package/dist/public-typegen.js +17 -0
  124. package/dist/run-cli.d.ts +2 -1
  125. package/dist/run-cli.d.ts.map +1 -1
  126. package/dist/studio/sidecar.d.ts.map +1 -1
  127. package/dist/studio/sidecar.js +106 -14
  128. package/dist/transforms/nestjs-migrate.d.ts +4 -1
  129. package/dist/transforms/nestjs-migrate.d.ts.map +1 -1
  130. package/dist/transforms/nestjs-migrate.js +625 -65
  131. package/dist/typegen-contract.d.ts +10 -0
  132. package/dist/typegen-contract.d.ts.map +1 -0
  133. package/dist/typegen-contract.js +9 -0
  134. package/dist/usage.d.ts +19 -0
  135. package/dist/usage.d.ts.map +1 -0
  136. package/dist/usage.js +178 -0
  137. package/package.json +19 -10
@@ -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":"AAGA,OAAO,EAAE,KAAK,iBAAiB,EAA2B,MAAM,kBAAkB,CAAC;AAYnF,OAAO,KAAK,EAAsC,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAI7F,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAmCF;;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;AA+PD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC,CA6H3G"}
@@ -1,12 +1,11 @@
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';
5
2
  import { installDependencies } from '../new/install.js';
6
3
  import { collectBootstrapAnswers } from '../new/prompt.js';
7
4
  import { resolveBootstrapPlan } from '../new/resolver.js';
8
5
  import { scaffoldBootstrapApp } from '../new/scaffold.js';
9
- 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';
6
+ import { SUPPORTED_BOOTSTRAP_PLATFORMS, SUPPORTED_BOOTSTRAP_RUNTIMES, SUPPORTED_BOOTSTRAP_SHAPES, SUPPORTED_BOOTSTRAP_STARTERS, SUPPORTED_BOOTSTRAP_TOOLING_PRESETS, SUPPORTED_BOOTSTRAP_TOPOLOGY_MODES, SUPPORTED_BOOTSTRAP_TRANSPORTS } from '../new/starter-profiles.js';
7
+ import { isCliPromptCancelledError } from '../prompt-cancel.js';
8
+ import { newUsage } from '../usage.js';
10
9
  function shouldUseInteractiveShell(runtime) {
11
10
  return runtime.prompt === undefined && runtime.stdout === undefined && runtime.stderr === undefined && (runtime.interactive ?? true) && Boolean(runtime.stdin?.isTTY ?? process.stdin.isTTY);
12
11
  }
@@ -20,78 +19,22 @@ function extractDependencyInstallationOutput(error) {
20
19
  function isHelpFlag(value) {
21
20
  return value === '--help' || value === '-h';
22
21
  }
22
+ function readInternalScaffoldOverrides(runtime) {
23
+ const dependencySource = 'dependencySource' in runtime ? runtime.dependencySource : undefined;
24
+ const repoRoot = 'repoRoot' in runtime ? runtime.repoRoot : undefined;
25
+ return {
26
+ dependencySource: dependencySource === 'local' || dependencySource === 'published' ? dependencySource : undefined,
27
+ repoRoot: typeof repoRoot === 'string' ? repoRoot : undefined
28
+ };
29
+ }
23
30
 
24
31
  /**
25
32
  * Runtime dependency overrides for the programmatic `fluo new` entry point.
26
33
  */
27
34
 
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
35
  const SUPPORTED_PACKAGE_MANAGERS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
94
36
  const SUPPORTED_SHAPES = new Set(SUPPORTED_BOOTSTRAP_SHAPES);
37
+ const SUPPORTED_STARTERS = new Set(SUPPORTED_BOOTSTRAP_STARTERS);
95
38
  const SUPPORTED_TRANSPORTS = new Set(SUPPORTED_BOOTSTRAP_TRANSPORTS);
96
39
  const SUPPORTED_RUNTIMES = new Set(SUPPORTED_BOOTSTRAP_RUNTIMES);
97
40
  const SUPPORTED_PLATFORMS = new Set(SUPPORTED_BOOTSTRAP_PLATFORMS);
@@ -143,6 +86,16 @@ function parseArgs(argv) {
143
86
  }
144
87
  index += 1;
145
88
  break;
89
+ case '--starter':
90
+ if (parsed.starter) {
91
+ throw new Error('Duplicate --starter option.');
92
+ }
93
+ parsed.starter = readOptionValue(argv, index, '--starter');
94
+ if (!SUPPORTED_STARTERS.has(parsed.starter)) {
95
+ throw new Error(`Invalid --starter value "${parsed.starter}". Use one of: standard, react-vite-ssr.`);
96
+ }
97
+ index += 1;
98
+ break;
146
99
  case '--transport':
147
100
  if (parsed.transport) {
148
101
  throw new Error('Duplicate --transport option.');
@@ -246,25 +199,7 @@ function renderDependencyList(dependencies) {
246
199
  }
247
200
  function renderScaffoldPlanPreview(answers, resolvedTargetDirectory) {
248
201
  const bootstrapPlan = resolveBootstrapPlan(answers);
249
- 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
- }
251
-
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');
202
+ return ['fluo new scaffold plan', '', `Project name: ${answers.projectName}`, `Target directory: ${answers.targetDirectory}`, `Resolved target: ${resolvedTargetDirectory}`, `Starter: ${answers.starter}`, `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');
268
203
  }
269
204
 
270
205
  /**
@@ -301,9 +236,13 @@ export async function runNewCommand(argv, runtime = {}) {
301
236
  if (!partialAnswers.projectName && !(runtime.interactive ?? runtime.prompt ?? runtime.stdin?.isTTY ?? process.stdin.isTTY)) {
302
237
  throw new Error(newUsage());
303
238
  }
239
+ let usedInteractivePrompt = false;
304
240
  const answers = await collectBootstrapAnswers(partialAnswers, runtime.cwd ?? process.cwd(), runtime.userAgent, {
305
241
  interactive: runtime.interactive,
306
242
  completionMessage: parsed.printPlan ? 'Scaffold plan resolved. No files were written.' : undefined,
243
+ onInteractivePrompt: () => {
244
+ usedInteractivePrompt = true;
245
+ },
307
246
  prompt: runtime.prompt,
308
247
  stdin: runtime.stdin,
309
248
  stdout
@@ -313,24 +252,26 @@ export async function runNewCommand(argv, runtime = {}) {
313
252
  stdout.write(`${renderScaffoldPlanPreview(answers, targetDirectory)}\n`);
314
253
  return 0;
315
254
  }
255
+ const internalScaffoldOverrides = readInternalScaffoldOverrides(runtime);
316
256
  const options = {
317
257
  ...answers,
318
- dependencySource: runtime.dependencySource,
258
+ dependencySource: internalScaffoldOverrides.dependencySource,
319
259
  force: parsed.force ?? runtime.force,
320
260
  initializeGit: answers.initializeGit,
321
261
  installDependencies: false,
322
- repoRoot: runtime.repoRoot,
262
+ repoRoot: internalScaffoldOverrides.repoRoot,
323
263
  skipInstall: true,
324
264
  targetDirectory
325
265
  };
326
- const isInteractiveShell = shouldUseInteractiveShell(runtime);
266
+ const isInteractiveShell = usedInteractivePrompt && shouldUseInteractiveShell(runtime);
267
+ const clack = isInteractiveShell ? await import('@clack/prompts') : undefined;
327
268
  let scaffoldSpinner;
328
269
  if (!answers.installDependencies && !isInteractiveShell) {
329
270
  stdout.write('Skipping dependency installation.\n');
330
271
  }
331
272
  if (isInteractiveShell) {
332
- scaffoldSpinner = clackSpinner();
333
- scaffoldSpinner.start('Scaffolding project files');
273
+ scaffoldSpinner = clack?.spinner();
274
+ scaffoldSpinner?.start('Scaffolding project files');
334
275
  }
335
276
  await scaffoldBootstrapApp(options);
336
277
  if (scaffoldSpinner) {
@@ -343,7 +284,10 @@ export async function runNewCommand(argv, runtime = {}) {
343
284
  stderr
344
285
  });
345
286
  } else {
346
- const installSpinner = clackSpinner();
287
+ const installSpinner = clack?.spinner();
288
+ if (!installSpinner) {
289
+ throw new Error('Interactive dependency installation requires @clack/prompts.');
290
+ }
347
291
  installSpinner.start(`Installing dependencies with ${answers.packageManager}`);
348
292
  try {
349
293
  await installDependencies(targetDirectory, answers.packageManager, {
@@ -361,10 +305,12 @@ export async function runNewCommand(argv, runtime = {}) {
361
305
  }
362
306
  }
363
307
  } else if (isInteractiveShell) {
364
- clackLog.step('Dependency installation skipped');
308
+ clack?.log.step('Dependency installation skipped');
365
309
  }
310
+ const devCommand = answers.packageManager === 'npm' ? 'npm run dev' : answers.packageManager === 'bun' ? 'bun run dev' : `${answers.packageManager} dev`;
311
+ const devDescription = answers.starter === 'react-vite-ssr' ? 'builds and starts the React SSR app' : 'runs fluo dev';
366
312
  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`} # runs fluo dev\n`);
313
+ stdout.write(`Next steps:\n cd ${answers.targetDirectory}\n ${devCommand} # ${devDescription}\n`);
368
314
  return 0;
369
315
  } catch (error) {
370
316
  if (isCliPromptCancelledError(error)) {
@@ -0,0 +1,14 @@
1
+ /** Raised when an artifact output resolves to its input application module. */
2
+ export declare class OutputPathAliasesModuleError extends Error {
3
+ readonly name = "OutputPathAliasesModuleError";
4
+ }
5
+ /**
6
+ * Rejects an artifact path that directly or through a symlink resolves to the input module.
7
+ *
8
+ * @param modulePath Absolute application module path.
9
+ * @param outputPath Absolute artifact output path.
10
+ * @returns Resolves when the output path does not identify the input application module.
11
+ * @throws {OutputPathAliasesModuleError} When the paths identify the same file.
12
+ */
13
+ export declare function assertOutputDoesNotAliasModule(modulePath: string, outputPath: string): Promise<void>;
14
+ //# sourceMappingURL=output-path-safety.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-path-safety.d.ts","sourceRoot":"","sources":["../../src/commands/output-path-safety.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,QAAQ,CAAC,IAAI,kCAAkC;CAChD;AAMD;;;;;;;GAOG;AACH,wBAAsB,8BAA8B,CAClD,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAgBf"}
@@ -0,0 +1,34 @@
1
+ import { realpath } from 'node:fs/promises';
2
+
3
+ /** Raised when an artifact output resolves to its input application module. */
4
+ export class OutputPathAliasesModuleError extends Error {
5
+ name = 'OutputPathAliasesModuleError';
6
+ }
7
+ function isMissingPathError(error) {
8
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
9
+ }
10
+
11
+ /**
12
+ * Rejects an artifact path that directly or through a symlink resolves to the input module.
13
+ *
14
+ * @param modulePath Absolute application module path.
15
+ * @param outputPath Absolute artifact output path.
16
+ * @returns Resolves when the output path does not identify the input application module.
17
+ * @throws {OutputPathAliasesModuleError} When the paths identify the same file.
18
+ */
19
+ export async function assertOutputDoesNotAliasModule(modulePath, outputPath) {
20
+ if (modulePath === outputPath) {
21
+ throw new OutputPathAliasesModuleError('Output path must not identify the input application module.');
22
+ }
23
+ const resolvedModulePath = await realpath(modulePath);
24
+ try {
25
+ const resolvedOutputPath = await realpath(outputPath);
26
+ if (resolvedModulePath === resolvedOutputPath) {
27
+ throw new OutputPathAliasesModuleError('Output path must not identify the input application module.');
28
+ }
29
+ } catch (error) {
30
+ if (error instanceof OutputPathAliasesModuleError || !isMissingPathError(error)) {
31
+ throw error;
32
+ }
33
+ }
34
+ }
@@ -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;AA2lB/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',
@@ -398,13 +403,14 @@ function assertStudioSupport(command, studio, projectRuntime, devRunner, rawWatc
398
403
  throw new Error('--studio is only supported for fluo dev.');
399
404
  }
400
405
  if (projectRuntime !== 'node') {
401
- 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.`);
406
+ throw new Error(`fluo dev --studio supports Node dev runner projects only. ${projectRuntime} projects can generate Studio-compatible artifacts with fluo inspect <module-path> --json --output <path> or fluo inspect <module-path> --report --output <path>.`);
402
407
  }
403
408
  if (devRunner !== 'fluo' || rawWatch) {
404
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.');
405
410
  }
406
411
  }
407
- function withStudioDryRunEnv(env, project, projectRuntime) {
412
+ function withStudioDryRunEnv(env, project, projectRuntime, studioPort) {
413
+ const portLabel = studioPort === undefined ? '<auto>' : String(studioPort);
408
414
  return {
409
415
  ...env,
410
416
  FLUO_STUDIO: '1',
@@ -412,7 +418,7 @@ function withStudioDryRunEnv(env, project, projectRuntime) {
412
418
  FLUO_STUDIO_EPOCH: '<generated-at-runtime>',
413
419
  FLUO_STUDIO_RUNTIME: projectRuntimeToStudioRuntime(projectRuntime),
414
420
  FLUO_STUDIO_TOKEN: '<generated-at-runtime>',
415
- FLUO_STUDIO_URL: 'http://127.0.0.1:<auto>'
421
+ FLUO_STUDIO_URL: `http://127.0.0.1:${portLabel}`
416
422
  };
417
423
  }
418
424
  function renderStep(step) {
@@ -591,7 +597,7 @@ async function runScriptWithStudioSidecar(command, projectDirectory, projectRunt
591
597
  */
592
598
  export function scriptUsage(command) {
593
599
  const nodeEnv = command === 'dev' ? 'development' : 'production';
594
- return [`Usage: fluo ${command} [options] [-- <args>]`, '', `Run the generated fluo project ${command} lifecycle with NODE_ENV defaulting to ${nodeEnv} when unset.`, '', 'Default output forwards child stdout/stderr without fluo lifecycle UI.', 'Use --reporter pretty for fluo lifecycle status + app │ prefixes.', 'Use --verbose (or FLUO_VERBOSE=1) to expose raw runtime/tooling output.', '', 'Options', ' --dry-run Print the command without running it.', command === 'dev' ? ' --raw-watch Use the runtime-native Node watcher instead of the fluo restart runner.' : undefined, command === 'dev' ? ' --runner <fluo|native> Select fluo restart supervision or runtime-native watch (default: fluo for Node, native for non-Node runtimes).' : undefined, command === 'dev' ? ' --studio Start the local Fluo Studio sidecar and inject runtime devtool env.' : undefined, command === 'dev' ? ' --studio-port <port> Bind the Studio sidecar to a specific local port (default: 0).' : undefined, ' --reporter <auto|pretty|stream|silent> Choose lifecycle reporter output mode (default: auto).', ' --verbose Expose raw child process output; also honored by FLUO_VERBOSE=1.', ` --help Show help for the ${command} command.`].filter(line => typeof line === 'string').join('\n');
600
+ return [`Usage: fluo ${command} [options] [-- <args>]`, '', `Run the generated fluo project ${command} lifecycle with NODE_ENV defaulting to ${nodeEnv} when unset.`, '', 'Default output forwards child stdout/stderr without fluo lifecycle UI.', 'Use --reporter pretty for fluo lifecycle status + app │ prefixes.', 'Use --verbose (or FLUO_VERBOSE=1) to expose raw runtime/tooling output.', '', 'Options', ' --dry-run Print the command without running it.', command === 'dev' ? ' --raw-watch Use the runtime-native Node watcher instead of the fluo restart runner.' : undefined, command === 'dev' ? ' --runner <fluo|native> Select fluo restart supervision or runtime-native watch (default: fluo for Node, native for non-Node runtimes).' : undefined, command === 'dev' ? ' --studio Start the local Fluo Studio sidecar for Node dev runner projects only.' : undefined, command === 'dev' ? ' --studio-port <port> Bind the Studio sidecar to a specific local port (default: 0).' : undefined, ' --reporter <auto|pretty|stream|silent> Choose lifecycle reporter output mode (default: auto).', ' --verbose Expose raw child process output; also honored by FLUO_VERBOSE=1.', ` --help Show help for the ${command} command.`].filter(line => typeof line === 'string').join('\n');
595
601
  }
596
602
 
597
603
  /**
@@ -635,10 +641,10 @@ export async function runScriptCommand(command, argv, runtime = {}) {
635
641
  const verbose = parsed.verbose || isEnabledEnvironmentFlag(env.FLUO_VERBOSE);
636
642
  let childEnv = withPipedReporterColorEnv(withProjectLocalBin(withDefaultNodeEnv(env, defaultNodeEnv), project.directory), reporterMode, stdout, stderr);
637
643
  if (parsed.studio && parsed.dryRun) {
638
- childEnv = withStudioDryRunEnv(childEnv, project, projectRuntime);
644
+ childEnv = withStudioDryRunEnv(childEnv, project, projectRuntime, parsed.studioPort);
639
645
  }
640
646
  if (command === 'dev' && parsed.studio && !parsed.dryRun) {
641
- const studioSidecarFactory = runtime.startStudioSidecar ?? startStudioSidecar;
647
+ const studioSidecarFactory = runtime.startStudioSidecar ?? (await loadDefaultStudioSidecarFactory());
642
648
  const studioSidecar = await studioSidecarFactory({
643
649
  appId: projectDisplayName(project),
644
650
  port: parsed.studioPort,
@@ -0,0 +1,54 @@
1
+ /** Filesystem boundary used by deterministic typegen artifact commits. */
2
+ export type TypegenArtifactFileSystem = {
3
+ readonly mkdir: (path: string) => Promise<void>;
4
+ readonly readFile: (path: string) => Promise<string>;
5
+ readonly rename: (source: string, destination: string) => undefined;
6
+ readonly rm: (path: string) => Promise<void>;
7
+ readonly writeFile: (path: string, content: string) => Promise<void>;
8
+ };
9
+ type ArtifactInspection = {
10
+ readonly status: 'malformed';
11
+ } | {
12
+ readonly status: 'unsupported-version';
13
+ readonly version: number;
14
+ } | {
15
+ readonly status: 'valid';
16
+ readonly version: number;
17
+ };
18
+ /** Stable non-mutating comparison result for an existing typegen target. */
19
+ export type TypegenArtifactCheck = {
20
+ readonly status: 'MALFORMED';
21
+ } | {
22
+ readonly status: 'MISSING';
23
+ } | {
24
+ readonly status: 'STALE';
25
+ } | {
26
+ readonly status: 'UNCHANGED';
27
+ } | {
28
+ readonly status: 'UNSUPPORTED_VERSION';
29
+ readonly version: number;
30
+ };
31
+ /** Stable write action reported after an atomic typegen artifact commit. */
32
+ export type TypegenArtifactWriteAction = 'CREATE' | 'UNCHANGED' | 'UPDATE';
33
+ /**
34
+ * Compares an existing artifact with expected generated bytes without writing.
35
+ *
36
+ * @param outputPath Target artifact path.
37
+ * @param expectedSource Authoritative generated source.
38
+ * @param inspect Structural artifact classifier.
39
+ * @param fileSystem Filesystem boundary used to read the target.
40
+ * @returns A stable unchanged, missing, stale, malformed, or unsupported-version result.
41
+ */
42
+ export declare function checkTypegenArtifact(outputPath: string, expectedSource: string, inspect: (source: string) => ArtifactInspection, fileSystem?: TypegenArtifactFileSystem): Promise<TypegenArtifactCheck>;
43
+ /**
44
+ * Atomically publishes generated source while skipping byte-identical rewrites.
45
+ *
46
+ * @param outputPath Target artifact path.
47
+ * @param source Complete generated source to publish.
48
+ * @param fileSystem Filesystem boundary used for the atomic replacement.
49
+ * @param signal Optional owner cancellation signal checked before publication.
50
+ * @returns The stable create, update, or unchanged action.
51
+ */
52
+ export declare function writeTypegenArtifact(outputPath: string, source: string, fileSystem?: TypegenArtifactFileSystem, signal?: AbortSignal): Promise<TypegenArtifactWriteAction>;
53
+ export {};
54
+ //# sourceMappingURL=typegen-artifact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typegen-artifact.d.ts","sourceRoot":"","sources":["../../src/commands/typegen-artifact.ts"],"names":[],"mappings":"AAIA,0EAA0E;AAC1E,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,SAAS,CAAC;IACpE,QAAQ,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE,CAAC;AAEF,KAAK,kBAAkB,GACnB;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAChC;IAAE,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,4EAA4E;AAC5E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAChC;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAChC;IAAE,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE,4EAA4E;AAC5E,MAAM,MAAM,0BAA0B,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;AAuC3E;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,kBAAkB,EAC/C,UAAU,GAAE,yBAA4C,GACvD,OAAO,CAAC,oBAAoB,CAAC,CAiB/B;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,UAAU,GAAE,yBAA4C,EACxD,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,0BAA0B,CAAC,CA0BrC"}
@@ -0,0 +1,117 @@
1
+ import { renameSync } from 'node:fs';
2
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { basename, dirname, join } from 'node:path';
4
+
5
+ /** Filesystem boundary used by deterministic typegen artifact commits. */
6
+
7
+ /** Stable non-mutating comparison result for an existing typegen target. */
8
+
9
+ /** Stable write action reported after an atomic typegen artifact commit. */
10
+
11
+ const NODE_FILE_SYSTEM = {
12
+ async mkdir(path) {
13
+ await mkdir(path, {
14
+ recursive: true
15
+ });
16
+ },
17
+ readFile: path => readFile(path, 'utf8'),
18
+ rename(source, destination) {
19
+ renameSync(source, destination);
20
+ return undefined;
21
+ },
22
+ async rm(path) {
23
+ await rm(path, {
24
+ force: true
25
+ });
26
+ },
27
+ async writeFile(path, content) {
28
+ await writeFile(path, content, {
29
+ encoding: 'utf8',
30
+ flag: 'wx'
31
+ });
32
+ }
33
+ };
34
+ let temporaryFileSequence = 0;
35
+ function isMissingFileError(error) {
36
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
37
+ }
38
+ async function readExistingArtifact(outputPath, fileSystem) {
39
+ try {
40
+ return await fileSystem.readFile(outputPath);
41
+ } catch (error) {
42
+ if (isMissingFileError(error)) {
43
+ return undefined;
44
+ }
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Compares an existing artifact with expected generated bytes without writing.
51
+ *
52
+ * @param outputPath Target artifact path.
53
+ * @param expectedSource Authoritative generated source.
54
+ * @param inspect Structural artifact classifier.
55
+ * @param fileSystem Filesystem boundary used to read the target.
56
+ * @returns A stable unchanged, missing, stale, malformed, or unsupported-version result.
57
+ */
58
+ export async function checkTypegenArtifact(outputPath, expectedSource, inspect, fileSystem = NODE_FILE_SYSTEM) {
59
+ const existingSource = await readExistingArtifact(outputPath, fileSystem);
60
+ if (existingSource === undefined) {
61
+ return {
62
+ status: 'MISSING'
63
+ };
64
+ }
65
+ if (existingSource === expectedSource) {
66
+ return {
67
+ status: 'UNCHANGED'
68
+ };
69
+ }
70
+ const inspection = inspect(existingSource);
71
+ if (inspection.status === 'malformed') {
72
+ return {
73
+ status: 'MALFORMED'
74
+ };
75
+ }
76
+ if (inspection.status === 'unsupported-version') {
77
+ return {
78
+ status: 'UNSUPPORTED_VERSION',
79
+ version: inspection.version
80
+ };
81
+ }
82
+ return {
83
+ status: 'STALE'
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Atomically publishes generated source while skipping byte-identical rewrites.
89
+ *
90
+ * @param outputPath Target artifact path.
91
+ * @param source Complete generated source to publish.
92
+ * @param fileSystem Filesystem boundary used for the atomic replacement.
93
+ * @param signal Optional owner cancellation signal checked before publication.
94
+ * @returns The stable create, update, or unchanged action.
95
+ */
96
+ export async function writeTypegenArtifact(outputPath, source, fileSystem = NODE_FILE_SYSTEM, signal) {
97
+ signal?.throwIfAborted();
98
+ const existingSource = await readExistingArtifact(outputPath, fileSystem);
99
+ if (existingSource === source) {
100
+ return 'UNCHANGED';
101
+ }
102
+ await fileSystem.mkdir(dirname(outputPath));
103
+ temporaryFileSequence += 1;
104
+ const temporaryPath = join(dirname(outputPath), `.${basename(outputPath)}.${String(process.pid)}.${String(temporaryFileSequence)}.tmp`);
105
+ let committed = false;
106
+ try {
107
+ await fileSystem.writeFile(temporaryPath, source);
108
+ signal?.throwIfAborted();
109
+ fileSystem.rename(temporaryPath, outputPath);
110
+ committed = true;
111
+ } finally {
112
+ if (!committed) {
113
+ await fileSystem.rm(temporaryPath);
114
+ }
115
+ }
116
+ return existingSource === undefined ? 'CREATE' : 'UPDATE';
117
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=typegen-generation-child.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typegen-generation-child.d.ts","sourceRoot":"","sources":["../../src/commands/typegen-generation-child.ts"],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ import { createProcessIsolatedTypegenSource } from './typegen-isolated-source.js';
2
+ import { TypegenCommandError } from './typegen-options.js';
3
+ function readGenerationArgs() {
4
+ const [cwd, modulePath, exportName] = process.argv.slice(2);
5
+ if (cwd === undefined || modulePath === undefined || exportName === undefined) {
6
+ throw new TypegenCommandError('Typegen generation process received incomplete arguments.');
7
+ }
8
+ return {
9
+ cwd,
10
+ exportName,
11
+ modulePath
12
+ };
13
+ }
14
+ function sendGenerationMessage(message) {
15
+ if (process.send === undefined) {
16
+ throw new TypegenCommandError('Typegen generation process requires an IPC channel.');
17
+ }
18
+ const send = process.send.bind(process);
19
+ return new Promise((resolve, reject) => {
20
+ send(message, error => {
21
+ if (error === null) {
22
+ resolve();
23
+ return;
24
+ }
25
+ reject(error);
26
+ });
27
+ });
28
+ }
29
+ async function runGenerationChild() {
30
+ let message;
31
+ try {
32
+ const request = readGenerationArgs();
33
+ const parsed = {
34
+ check: false,
35
+ exportName: request.exportName,
36
+ modulePath: request.modulePath,
37
+ outputPath: '',
38
+ watch: false
39
+ };
40
+ message = {
41
+ kind: 'source',
42
+ source: await createProcessIsolatedTypegenSource(parsed, request.cwd)
43
+ };
44
+ } catch (error) {
45
+ process.exitCode = 1;
46
+ message = {
47
+ kind: 'error',
48
+ message: error instanceof Error ? error.message : String(error)
49
+ };
50
+ }
51
+ try {
52
+ await sendGenerationMessage(message);
53
+ } finally {
54
+ if (process.connected) {
55
+ process.disconnect();
56
+ }
57
+ }
58
+ }
59
+ await runGenerationChild();