@fluojs/cli 2.0.1 → 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 (118) hide show
  1. package/README.ko.md +169 -21
  2. package/README.md +172 -21
  3. package/dist/cli.d.ts +5 -4
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +127 -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/inspect.d.ts +5 -1
  10. package/dist/commands/inspect.d.ts.map +1 -1
  11. package/dist/commands/inspect.js +84 -17
  12. package/dist/commands/migrate.d.ts.map +1 -1
  13. package/dist/commands/migrate.js +27 -15
  14. package/dist/commands/migration-transform-tokens.d.ts +15 -0
  15. package/dist/commands/migration-transform-tokens.d.ts.map +1 -0
  16. package/dist/commands/migration-transform-tokens.js +41 -0
  17. package/dist/commands/new.d.ts.map +1 -1
  18. package/dist/commands/new.js +29 -9
  19. package/dist/commands/output-path-safety.d.ts +14 -0
  20. package/dist/commands/output-path-safety.d.ts.map +1 -0
  21. package/dist/commands/output-path-safety.js +34 -0
  22. package/dist/commands/scripts.js +2 -2
  23. package/dist/commands/typegen-artifact.d.ts +54 -0
  24. package/dist/commands/typegen-artifact.d.ts.map +1 -0
  25. package/dist/commands/typegen-artifact.js +117 -0
  26. package/dist/commands/typegen-generation-child.d.ts +2 -0
  27. package/dist/commands/typegen-generation-child.d.ts.map +1 -0
  28. package/dist/commands/typegen-generation-child.js +59 -0
  29. package/dist/commands/typegen-generation-process.d.ts +46 -0
  30. package/dist/commands/typegen-generation-process.d.ts.map +1 -0
  31. package/dist/commands/typegen-generation-process.js +131 -0
  32. package/dist/commands/typegen-generation-protocol.d.ts +16 -0
  33. package/dist/commands/typegen-generation-protocol.d.ts.map +1 -0
  34. package/dist/commands/typegen-generation-protocol.js +35 -0
  35. package/dist/commands/typegen-isolated-source.d.ts +10 -0
  36. package/dist/commands/typegen-isolated-source.d.ts.map +1 -0
  37. package/dist/commands/typegen-isolated-source.js +75 -0
  38. package/dist/commands/typegen-options.d.ts +20 -0
  39. package/dist/commands/typegen-options.d.ts.map +1 -0
  40. package/dist/commands/typegen-options.js +71 -0
  41. package/dist/commands/typegen-source.d.ts +59 -0
  42. package/dist/commands/typegen-source.d.ts.map +1 -0
  43. package/dist/commands/typegen-source.js +183 -0
  44. package/dist/commands/typegen-watch.d.ts +46 -0
  45. package/dist/commands/typegen-watch.d.ts.map +1 -0
  46. package/dist/commands/typegen-watch.js +212 -0
  47. package/dist/commands/typegen.d.ts +29 -0
  48. package/dist/commands/typegen.d.ts.map +1 -0
  49. package/dist/commands/typegen.js +119 -0
  50. package/dist/dev-runner/node-restart-runner.d.ts.map +1 -1
  51. package/dist/dev-runner/node-restart-runner.js +54 -7
  52. package/dist/fixtures/inspect-react-app.module.d.ts +4 -0
  53. package/dist/fixtures/inspect-react-app.module.d.ts.map +1 -0
  54. package/dist/fixtures/inspect-react-app.module.js +32 -0
  55. package/dist/fixtures/typegen-react-app.module.d.ts +4 -0
  56. package/dist/fixtures/typegen-react-app.module.d.ts.map +1 -0
  57. package/dist/fixtures/typegen-react-app.module.js +33 -0
  58. package/dist/index.d.ts +6 -5
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +3 -2
  61. package/dist/new/install.js +1 -1
  62. package/dist/new/package-spec-resolver.d.ts.map +1 -1
  63. package/dist/new/package-spec-resolver.js +50 -59
  64. package/dist/new/prompt.d.ts +1 -0
  65. package/dist/new/prompt.d.ts.map +1 -1
  66. package/dist/new/prompt.js +40 -12
  67. package/dist/new/published-internal-dependencies.d.ts +20 -0
  68. package/dist/new/published-internal-dependencies.d.ts.map +1 -0
  69. package/dist/new/published-internal-dependencies.js +20 -0
  70. package/dist/new/react-vite-ssr-scaffold.d.ts +19 -0
  71. package/dist/new/react-vite-ssr-scaffold.d.ts.map +1 -0
  72. package/dist/new/react-vite-ssr-scaffold.js +108 -0
  73. package/dist/new/resolver.d.ts +2 -2
  74. package/dist/new/resolver.d.ts.map +1 -1
  75. package/dist/new/resolver.js +5 -1
  76. package/dist/new/scaffold.d.ts.map +1 -1
  77. package/dist/new/scaffold.js +62 -31
  78. package/dist/new/starter-profiles.d.ts +8 -4
  79. package/dist/new/starter-profiles.d.ts.map +1 -1
  80. package/dist/new/starter-profiles.js +52 -18
  81. package/dist/new/templates/react-vite-ssr/README.md.ejs +74 -0
  82. package/dist/new/templates/react-vite-ssr/playwright.config.ts.ejs +26 -0
  83. package/dist/new/templates/react-vite-ssr/src/app.test.ts.ejs +82 -0
  84. package/dist/new/templates/react-vite-ssr/src/app.ts.ejs +91 -0
  85. package/dist/new/templates/react-vite-ssr/src/entry-client.tsx.ejs +29 -0
  86. package/dist/new/templates/react-vite-ssr/src/entry-server.tsx.ejs +52 -0
  87. package/dist/new/templates/react-vite-ssr/src/load-manifest.test.ts.ejs +55 -0
  88. package/dist/new/templates/react-vite-ssr/src/load-manifest.ts.ejs +53 -0
  89. package/dist/new/templates/react-vite-ssr/src/main.ts.ejs +18 -0
  90. package/dist/new/templates/react-vite-ssr/src/page.tsx.ejs +43 -0
  91. package/dist/new/templates/react-vite-ssr/src/react-app.test.tsx.ejs +67 -0
  92. package/dist/new/templates/react-vite-ssr/src/react-app.tsx.ejs +46 -0
  93. package/dist/new/templates/react-vite-ssr/src/styles.css.ejs +25 -0
  94. package/dist/new/templates/react-vite-ssr/src/styles.d.ts.ejs +1 -0
  95. package/dist/new/templates/react-vite-ssr/tests/production-hydration.spec.ts.ejs +54 -0
  96. package/dist/new/templates/react-vite-ssr/tsconfig.json.ejs +16 -0
  97. package/dist/new/templates/react-vite-ssr/vite.client.config.ts.ejs +24 -0
  98. package/dist/new/templates/react-vite-ssr/vite.server.config.ts.ejs +17 -0
  99. package/dist/new/templates/react-vite-ssr/vitest.config.ts.ejs +10 -0
  100. package/dist/new/types.d.ts +4 -0
  101. package/dist/new/types.d.ts.map +1 -1
  102. package/dist/public-typegen.d.ts +14 -0
  103. package/dist/public-typegen.d.ts.map +1 -0
  104. package/dist/public-typegen.js +17 -0
  105. package/dist/run-cli.d.ts +2 -1
  106. package/dist/run-cli.d.ts.map +1 -1
  107. package/dist/studio/sidecar.d.ts.map +1 -1
  108. package/dist/studio/sidecar.js +35 -9
  109. package/dist/transforms/nestjs-migrate.d.ts +4 -1
  110. package/dist/transforms/nestjs-migrate.d.ts.map +1 -1
  111. package/dist/transforms/nestjs-migrate.js +625 -65
  112. package/dist/typegen-contract.d.ts +10 -0
  113. package/dist/typegen-contract.d.ts.map +1 -0
  114. package/dist/typegen-contract.js +9 -0
  115. package/dist/usage.d.ts +6 -0
  116. package/dist/usage.d.ts.map +1 -1
  117. package/dist/usage.js +47 -0
  118. package/package.json +19 -10
@@ -1,20 +1,24 @@
1
+ import { existsSync } from 'node:fs';
1
2
  import { mkdir, writeFile } from 'node:fs/promises';
2
3
  import { createRequire } from 'node:module';
3
4
  import { dirname, extname, resolve } from 'node:path';
4
5
  import { pathToFileURL } from 'node:url';
5
- import * as clack from '@clack/prompts';
6
- import { FluoFactory, PLATFORM_SHELL } from '@fluojs/runtime';
7
6
  import { tsImport } from 'tsx/esm/api';
8
7
  import { CliPromptCancelledError, isCliPromptCancelledError } from '../prompt-cancel.js';
9
8
  import { inspectUsage } from '../usage.js';
9
+ import { createCliDiagnosticsLogger } from './diagnostics.js';
10
+ import { assertOutputDoesNotAliasModule } from './output-path-safety.js';
10
11
 
11
12
  /**
12
13
  * Runtime options for the inspect command when used programmatically.
13
14
  */
14
15
 
15
16
  const STUDIO_CONTRACT_ENTRYPOINT = '@fluojs/studio/contracts';
17
+ const RUNTIME_ENTRYPOINT = '@fluojs/runtime';
16
18
  const TYPESCRIPT_MODULE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts']);
19
+ const RUNTIME_MISSING_MESSAGE = ['Runtime inspection requires @fluojs/runtime, but it is not resolvable from this project.', 'Install a version whose Node.js engine supports your current Node.js version (for example: pnpm add -D @fluojs/runtime) and rerun fluo inspect.'].join('\n');
17
20
  const STUDIO_MISSING_MESSAGE = ['Mermaid graph rendering is owned by @fluojs/studio, but @fluojs/studio is not resolvable from this project.', 'Install @fluojs/studio explicitly (for example: pnpm add -D @fluojs/studio) and rerun fluo inspect --mermaid.'].join('\n');
21
+ const CLACK_MISSING_MESSAGE = ['Interactive inspect guidance requires @clack/prompts, but it is not resolvable from this installation.', 'Install a version whose Node.js engine supports your current Node.js version and rerun fluo inspect.'].join('\n');
18
22
  function isHelpFlag(value) {
19
23
  return value === '--help' || value === '-h';
20
24
  }
@@ -35,6 +39,18 @@ function parseInspectArgs(argv) {
35
39
  json = true;
36
40
  continue;
37
41
  }
42
+ if (option === '--format') {
43
+ const format = argv[index + 1];
44
+ if (!format || format.startsWith('-')) {
45
+ throw new Error('Expected --format to have a value of "json".');
46
+ }
47
+ if (format !== 'json') {
48
+ throw new Error(`Invalid --format value "${format}". Use "json".`);
49
+ }
50
+ json = true;
51
+ index += 1;
52
+ continue;
53
+ }
38
54
  if (option === '--mermaid') {
39
55
  mermaid = true;
40
56
  continue;
@@ -105,6 +121,13 @@ function resolveRootModule(exportedValue, exportName) {
105
121
  async function importInspectModule(modulePath) {
106
122
  const moduleUrl = pathToFileURL(modulePath).href;
107
123
  if (TYPESCRIPT_MODULE_EXTENSIONS.has(extname(modulePath))) {
124
+ const adjacentTsconfigPath = resolve(dirname(modulePath), 'tsconfig.json');
125
+ if (existsSync(adjacentTsconfigPath)) {
126
+ return tsImport(moduleUrl, {
127
+ parentURL: import.meta.url,
128
+ tsconfig: adjacentTsconfigPath
129
+ });
130
+ }
108
131
  return tsImport(moduleUrl, import.meta.url);
109
132
  }
110
133
  return import(moduleUrl);
@@ -145,18 +168,26 @@ function stringifySnapshotWithTiming(snapshot, timing) {
145
168
  timing: timing ?? createEmptyTimingDiagnostics()
146
169
  }, null, 2);
147
170
  }
148
- async function emitInspectPayload(payload, parsed, cwd, stdout) {
149
- if (!parsed.outputPath) {
171
+ async function emitInspectPayload(payload, outputPath, stdout) {
172
+ if (!outputPath) {
150
173
  stdout.write(`${payload}\n`);
151
174
  return;
152
175
  }
153
- const outputPath = resolve(cwd, parsed.outputPath);
154
176
  await mkdir(dirname(outputPath), {
155
177
  recursive: true
156
178
  });
157
179
  await writeFile(outputPath, `${payload}\n`, 'utf8');
158
180
  }
159
- function createInspectPrompter() {
181
+ async function createInspectPrompter() {
182
+ let clack;
183
+ try {
184
+ clack = await import('@clack/prompts');
185
+ } catch (error) {
186
+ if (isModuleNotFoundError(error)) {
187
+ throw new Error(CLACK_MISSING_MESSAGE);
188
+ }
189
+ throw error;
190
+ }
160
191
  return {
161
192
  async confirm(message, defaultValue) {
162
193
  const result = await clack.confirm({
@@ -180,6 +211,29 @@ function shouldPromptForStudio(runtime) {
180
211
  }
181
212
  return runtime.stdout === undefined && runtime.stderr === undefined && (runtime.interactive ?? true) && Boolean(runtime.stdin?.isTTY ?? process.stdin.isTTY);
182
213
  }
214
+ function isModuleNotFoundError(error) {
215
+ const code = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;
216
+ return code === 'MODULE_NOT_FOUND' || code === 'ERR_MODULE_NOT_FOUND';
217
+ }
218
+ async function loadRuntimeInspectionModule(cwd) {
219
+ const resolver = createRequire(resolve(cwd, 'package.json'));
220
+ try {
221
+ const resolvedEntrypoint = resolver.resolve(RUNTIME_ENTRYPOINT);
222
+ return await import(pathToFileURL(resolvedEntrypoint).href);
223
+ } catch (error) {
224
+ if (isModuleNotFoundError(error)) {
225
+ return undefined;
226
+ }
227
+ throw error;
228
+ }
229
+ }
230
+ async function resolveRuntimeInspectionModule(cwd, runtime) {
231
+ const runtimeModule = await (runtime.loadRuntimeInspectionModule ?? loadRuntimeInspectionModule)(cwd);
232
+ if (!runtimeModule) {
233
+ throw new Error(RUNTIME_MISSING_MESSAGE);
234
+ }
235
+ return runtimeModule;
236
+ }
183
237
  async function loadStudioMermaidRenderer(cwd) {
184
238
  const resolvers = [createRequire(resolve(cwd, 'package.json')), createRequire(import.meta.url)];
185
239
  for (const resolver of resolvers) {
@@ -191,8 +245,7 @@ async function loadStudioMermaidRenderer(cwd) {
191
245
  }
192
246
  return importedContract.renderMermaid;
193
247
  } catch (error) {
194
- const code = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;
195
- if (code !== 'MODULE_NOT_FOUND' && code !== 'ERR_MODULE_NOT_FOUND') {
248
+ if (!isModuleNotFoundError(error)) {
196
249
  throw error;
197
250
  }
198
251
  }
@@ -207,7 +260,7 @@ async function resolveStudioMermaidRenderer(cwd, runtime) {
207
260
  if (!shouldPromptForStudio(runtime)) {
208
261
  throw new Error(STUDIO_MISSING_MESSAGE);
209
262
  }
210
- const prompt = runtime.prompt ?? createInspectPrompter();
263
+ const prompt = runtime.prompt ?? (await createInspectPrompter());
211
264
  try {
212
265
  const approvedInstall = await prompt.confirm('Install @fluojs/studio before rendering Mermaid output?', false);
213
266
  if (!approvedInstall) {
@@ -237,28 +290,42 @@ export async function runInspectCommand(argv, runtime = {}) {
237
290
  }
238
291
  const parsed = parseInspectArgs(argv);
239
292
  const modulePath = resolve(cwd, parsed.modulePath);
293
+ const outputPath = parsed.outputPath === undefined ? undefined : resolve(cwd, parsed.outputPath);
294
+ if (outputPath !== undefined) {
295
+ await assertOutputDoesNotAliasModule(modulePath, outputPath);
296
+ }
297
+ const {
298
+ createRuntimeInspectionSnapshot,
299
+ FluoFactory,
300
+ PLATFORM_SHELL
301
+ } = await resolveRuntimeInspectionModule(cwd, runtime);
240
302
  const importedModule = await importInspectModule(modulePath);
241
303
  const rootModule = resolveRootModule(importedModule[parsed.exportName], parsed.exportName);
242
- const context = await FluoFactory.createApplicationContext(rootModule, {
304
+ const application = await FluoFactory.create(rootModule, {
243
305
  diagnostics: parsed.timing || parsed.report ? {
244
306
  timing: true
245
- } : undefined
307
+ } : undefined,
308
+ ...(parsed.json || parsed.report ? {
309
+ logger: createCliDiagnosticsLogger(stderr)
310
+ } : {})
246
311
  });
247
312
  try {
248
- const platformShell = await context.get(PLATFORM_SHELL);
249
- const snapshot = await platformShell.snapshot();
313
+ const platformShell = await application.get(PLATFORM_SHELL);
314
+ const platformSnapshot = await platformShell.snapshot();
315
+ const routes = application.dispatcher.describeRoutes?.() ?? [];
316
+ const snapshot = createRuntimeInspectionSnapshot(platformSnapshot, routes);
250
317
  if (parsed.json) {
251
- await emitInspectPayload(parsed.timing ? stringifySnapshotWithTiming(snapshot, context.bootstrapTiming) : stringifySnapshot(snapshot), parsed, cwd, stdout);
318
+ await emitInspectPayload(parsed.timing ? stringifySnapshotWithTiming(snapshot, application.bootstrapTiming) : stringifySnapshot(snapshot), outputPath, stdout);
252
319
  }
253
320
  if (parsed.report) {
254
- await emitInspectPayload(JSON.stringify(createInspectReport(snapshot, context.bootstrapTiming), null, 2), parsed, cwd, stdout);
321
+ await emitInspectPayload(JSON.stringify(createInspectReport(snapshot, application.bootstrapTiming), null, 2), outputPath, stdout);
255
322
  }
256
323
  if (parsed.mermaid) {
257
324
  const renderMermaid = await resolveStudioMermaidRenderer(cwd, runtime);
258
- await emitInspectPayload(renderMermaid(snapshot), parsed, cwd, stdout);
325
+ await emitInspectPayload(renderMermaid(snapshot), outputPath, stdout);
259
326
  }
260
327
  } finally {
261
- await context.close();
328
+ await application.close();
262
329
  }
263
330
  return 0;
264
331
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAaA,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,yCAAyC;IACzC,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AA4KD;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAgBrC;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,MAAM,CAAC,CAwEnH"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAgBA,KAAK,SAAS,GAAG;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,yCAAyC;IACzC,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAqLD;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAgBrC;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,MAAM,CAAC,CAyEnH"}
@@ -1,6 +1,7 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { renderAliasList, renderHelpTable } from '../help.js';
3
- import { MIGRATION_TRANSFORMS, getWarningCategoryLabel, groupWarningsByCategory, renderTransformList, runNestJsMigration } from '../transforms/nestjs-migrate.js';
3
+ import { getWarningCategoryLabel, groupWarningsByCategory, MIGRATION_TRANSFORMS, renderTransformList, runNestJsMigration } from '../transforms/nestjs-migrate.js';
4
+ import { MIGRATION_TRANSFORM_CLI_TOKENS, parseMigrationTransformList } from './migration-transform-tokens.js';
4
5
 
5
6
  /**
6
7
  * Runtime configuration for the migrate command.
@@ -16,12 +17,16 @@ const MIGRATE_OPTION_HELP = [{
16
17
  option: '--json'
17
18
  }, {
18
19
  aliases: [],
19
- description: `Run only selected transforms. Available: ${MIGRATION_TRANSFORMS.join(', ')}.`,
20
+ description: `Run only selected transforms. Available: ${MIGRATION_TRANSFORM_CLI_TOKENS.join(', ')}.`,
20
21
  option: '--only <comma-list>'
21
22
  }, {
22
23
  aliases: [],
23
- description: `Skip selected transforms. Available: ${MIGRATION_TRANSFORMS.join(', ')}.`,
24
+ description: `Skip selected transforms. Available: ${MIGRATION_TRANSFORM_CLI_TOKENS.join(', ')}.`,
24
25
  option: '--skip <comma-list>'
26
+ }, {
27
+ aliases: [],
28
+ description: 'Select the HTTP platform for bootstrap rewrites. Required for automatic bootstrap migration; supported: express.',
29
+ option: '--platform <name>'
25
30
  }, {
26
31
  aliases: ['-h'],
27
32
  description: 'Show help for the migrate command.',
@@ -30,22 +35,12 @@ const MIGRATE_OPTION_HELP = [{
30
35
  function isHelpFlag(value) {
31
36
  return value === '--help' || value === '-h';
32
37
  }
33
- function parseTransformList(rawValue, optionName) {
34
- const values = rawValue.split(',').map(value => value.trim()).filter(value => value.length > 0);
35
- if (values.length === 0) {
36
- throw new Error(`${optionName} requires a non-empty comma-separated transform list.`);
37
- }
38
- const invalid = values.filter(value => !MIGRATION_TRANSFORMS.includes(value));
39
- if (invalid.length > 0) {
40
- throw new Error(`Unknown transform(s): ${invalid.join(', ')}. Available transforms: ${MIGRATION_TRANSFORMS.join(', ')}.`);
41
- }
42
- return values;
43
- }
44
38
  function parseArgs(argv) {
45
39
  let pathArgument;
46
40
  let apply = false;
47
41
  let json = false;
48
42
  let onlyTransforms;
43
+ let platform;
49
44
  let skipTransforms = [];
50
45
  for (let index = 0; index < argv.length; index += 1) {
51
46
  const arg = argv[index];
@@ -65,7 +60,7 @@ function parseArgs(argv) {
65
60
  if (!rawValue || rawValue.startsWith('-')) {
66
61
  throw new Error(`Expected ${arg} to have a comma-separated value.`);
67
62
  }
68
- const parsed = parseTransformList(rawValue, arg);
63
+ const parsed = parseMigrationTransformList(rawValue, arg);
69
64
  if (arg === '--only') {
70
65
  if (onlyTransforms) {
71
66
  throw new Error('Duplicate --only option.');
@@ -77,6 +72,21 @@ function parseArgs(argv) {
77
72
  index += 1;
78
73
  continue;
79
74
  }
75
+ if (arg === '--platform') {
76
+ const rawValue = argv[index + 1];
77
+ if (!rawValue || rawValue.startsWith('-')) {
78
+ throw new Error('Expected --platform to have a platform name.');
79
+ }
80
+ if (platform) {
81
+ throw new Error('Duplicate --platform option.');
82
+ }
83
+ if (rawValue !== 'express') {
84
+ throw new Error(`Unsupported bootstrap platform: ${rawValue}. Supported platform: express.`);
85
+ }
86
+ platform = rawValue;
87
+ index += 1;
88
+ continue;
89
+ }
80
90
  if (arg.startsWith('-')) {
81
91
  throw new Error(`Unknown option for migrate command: ${arg}`);
82
92
  }
@@ -99,6 +109,7 @@ function parseArgs(argv) {
99
109
  apply,
100
110
  json,
101
111
  path: pathArgument,
112
+ platform,
102
113
  transforms: enabled
103
114
  };
104
115
  }
@@ -166,6 +177,7 @@ export async function runMigrateCommand(argv, runtime = {}) {
166
177
  const transforms = [...parsed.transforms];
167
178
  const report = runNestJsMigration({
168
179
  apply: parsed.apply,
180
+ bootstrapPlatform: parsed.platform,
169
181
  enabledTransforms: parsed.transforms,
170
182
  targetPath
171
183
  });
@@ -0,0 +1,15 @@
1
+ import { type MigrationTransformKind } from '../transforms/nestjs-migrate.js';
2
+ /**
3
+ * Provides canonical migration transform tokens accepted by CLI options.
4
+ */
5
+ export declare const MIGRATION_TRANSFORM_CLI_TOKENS: readonly ["imports", "inject-params", "scope", "bootstrap", "tests", "tsconfig"];
6
+ /**
7
+ * Parse canonical or legacy migration transform tokens from one CLI option.
8
+ *
9
+ * @param rawValue Comma-separated transform tokens passed to the CLI.
10
+ * @param optionName CLI option that supplied the transform tokens.
11
+ * @returns Canonical migration transform kinds.
12
+ * @throws When the option has no tokens or includes an unsupported token.
13
+ */
14
+ export declare function parseMigrationTransformList(rawValue: string, optionName: '--only' | '--skip'): MigrationTransformKind[];
15
+ //# sourceMappingURL=migration-transform-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-transform-tokens.d.ts","sourceRoot":"","sources":["../../src/commands/migration-transform-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAEpG;;GAEG;AACH,eAAO,MAAM,8BAA8B,kFAAmF,CAAC;AAS/H;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAC9B,sBAAsB,EAAE,CA2B1B"}
@@ -0,0 +1,41 @@
1
+ import { MIGRATION_TRANSFORMS } from '../transforms/nestjs-migrate.js';
2
+
3
+ /**
4
+ * Provides canonical migration transform tokens accepted by CLI options.
5
+ */
6
+ export const MIGRATION_TRANSFORM_CLI_TOKENS = ['imports', 'inject-params', 'scope', 'bootstrap', 'tests', 'tsconfig'];
7
+ const MIGRATION_TRANSFORM_ALIASES = {
8
+ 'inject-params': 'injectable',
9
+ injectable: 'injectable',
10
+ tests: 'testing',
11
+ testing: 'testing'
12
+ };
13
+
14
+ /**
15
+ * Parse canonical or legacy migration transform tokens from one CLI option.
16
+ *
17
+ * @param rawValue Comma-separated transform tokens passed to the CLI.
18
+ * @param optionName CLI option that supplied the transform tokens.
19
+ * @returns Canonical migration transform kinds.
20
+ * @throws When the option has no tokens or includes an unsupported token.
21
+ */
22
+ export function parseMigrationTransformList(rawValue, optionName) {
23
+ const values = rawValue.split(',').map(value => value.trim()).filter(value => value.length > 0);
24
+ if (values.length === 0) {
25
+ throw new Error(`${optionName} requires a non-empty comma-separated transform list.`);
26
+ }
27
+ const transforms = [];
28
+ const invalid = [];
29
+ for (const value of values) {
30
+ const transform = MIGRATION_TRANSFORMS.find(candidate => candidate === value) ?? (Object.hasOwn(MIGRATION_TRANSFORM_ALIASES, value) ? MIGRATION_TRANSFORM_ALIASES[value] : undefined);
31
+ if (transform) {
32
+ transforms.push(transform);
33
+ } else {
34
+ invalid.push(value);
35
+ }
36
+ }
37
+ if (invalid.length > 0) {
38
+ throw new Error(`Unknown transform(s): ${invalid.join(', ')}. Available transforms: ${MIGRATION_TRANSFORM_CLI_TOKENS.join(', ')}.`);
39
+ }
40
+ return transforms;
41
+ }
@@ -1 +1 @@
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
+ {"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,10 +1,9 @@
1
1
  import { resolve } from 'node:path';
2
- import { log as clackLog, spinner as clackSpinner } from '@clack/prompts';
3
2
  import { installDependencies } from '../new/install.js';
4
3
  import { collectBootstrapAnswers } from '../new/prompt.js';
5
4
  import { resolveBootstrapPlan } from '../new/resolver.js';
6
5
  import { scaffoldBootstrapApp } from '../new/scaffold.js';
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';
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';
8
7
  import { isCliPromptCancelledError } from '../prompt-cancel.js';
9
8
  import { newUsage } from '../usage.js';
10
9
  function shouldUseInteractiveShell(runtime) {
@@ -35,6 +34,7 @@ function readInternalScaffoldOverrides(runtime) {
35
34
 
36
35
  const SUPPORTED_PACKAGE_MANAGERS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
37
36
  const SUPPORTED_SHAPES = new Set(SUPPORTED_BOOTSTRAP_SHAPES);
37
+ const SUPPORTED_STARTERS = new Set(SUPPORTED_BOOTSTRAP_STARTERS);
38
38
  const SUPPORTED_TRANSPORTS = new Set(SUPPORTED_BOOTSTRAP_TRANSPORTS);
39
39
  const SUPPORTED_RUNTIMES = new Set(SUPPORTED_BOOTSTRAP_RUNTIMES);
40
40
  const SUPPORTED_PLATFORMS = new Set(SUPPORTED_BOOTSTRAP_PLATFORMS);
@@ -86,6 +86,16 @@ function parseArgs(argv) {
86
86
  }
87
87
  index += 1;
88
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;
89
99
  case '--transport':
90
100
  if (parsed.transport) {
91
101
  throw new Error('Duplicate --transport option.');
@@ -189,7 +199,7 @@ function renderDependencyList(dependencies) {
189
199
  }
190
200
  function renderScaffoldPlanPreview(answers, resolvedTargetDirectory) {
191
201
  const bootstrapPlan = resolveBootstrapPlan(answers);
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');
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');
193
203
  }
194
204
 
195
205
  /**
@@ -226,9 +236,13 @@ export async function runNewCommand(argv, runtime = {}) {
226
236
  if (!partialAnswers.projectName && !(runtime.interactive ?? runtime.prompt ?? runtime.stdin?.isTTY ?? process.stdin.isTTY)) {
227
237
  throw new Error(newUsage());
228
238
  }
239
+ let usedInteractivePrompt = false;
229
240
  const answers = await collectBootstrapAnswers(partialAnswers, runtime.cwd ?? process.cwd(), runtime.userAgent, {
230
241
  interactive: runtime.interactive,
231
242
  completionMessage: parsed.printPlan ? 'Scaffold plan resolved. No files were written.' : undefined,
243
+ onInteractivePrompt: () => {
244
+ usedInteractivePrompt = true;
245
+ },
232
246
  prompt: runtime.prompt,
233
247
  stdin: runtime.stdin,
234
248
  stdout
@@ -249,14 +263,15 @@ export async function runNewCommand(argv, runtime = {}) {
249
263
  skipInstall: true,
250
264
  targetDirectory
251
265
  };
252
- const isInteractiveShell = shouldUseInteractiveShell(runtime);
266
+ const isInteractiveShell = usedInteractivePrompt && shouldUseInteractiveShell(runtime);
267
+ const clack = isInteractiveShell ? await import('@clack/prompts') : undefined;
253
268
  let scaffoldSpinner;
254
269
  if (!answers.installDependencies && !isInteractiveShell) {
255
270
  stdout.write('Skipping dependency installation.\n');
256
271
  }
257
272
  if (isInteractiveShell) {
258
- scaffoldSpinner = clackSpinner();
259
- scaffoldSpinner.start('Scaffolding project files');
273
+ scaffoldSpinner = clack?.spinner();
274
+ scaffoldSpinner?.start('Scaffolding project files');
260
275
  }
261
276
  await scaffoldBootstrapApp(options);
262
277
  if (scaffoldSpinner) {
@@ -269,7 +284,10 @@ export async function runNewCommand(argv, runtime = {}) {
269
284
  stderr
270
285
  });
271
286
  } else {
272
- const installSpinner = clackSpinner();
287
+ const installSpinner = clack?.spinner();
288
+ if (!installSpinner) {
289
+ throw new Error('Interactive dependency installation requires @clack/prompts.');
290
+ }
273
291
  installSpinner.start(`Installing dependencies with ${answers.packageManager}`);
274
292
  try {
275
293
  await installDependencies(targetDirectory, answers.packageManager, {
@@ -287,10 +305,12 @@ export async function runNewCommand(argv, runtime = {}) {
287
305
  }
288
306
  }
289
307
  } else if (isInteractiveShell) {
290
- clackLog.step('Dependency installation skipped');
308
+ clack?.log.step('Dependency installation skipped');
291
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';
292
312
  stdout.write('Done.\n');
293
- 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`);
294
314
  return 0;
295
315
  } catch (error) {
296
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
+ }
@@ -403,7 +403,7 @@ function assertStudioSupport(command, studio, projectRuntime, devRunner, rawWatc
403
403
  throw new Error('--studio is only supported for fluo dev.');
404
404
  }
405
405
  if (projectRuntime !== 'node') {
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.`);
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>.`);
407
407
  }
408
408
  if (devRunner !== 'fluo' || rawWatch) {
409
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.');
@@ -597,7 +597,7 @@ async function runScriptWithStudioSidecar(command, projectDirectory, projectRunt
597
597
  */
598
598
  export function scriptUsage(command) {
599
599
  const nodeEnv = command === 'dev' ? 'development' : 'production';
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 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');
601
601
  }
602
602
 
603
603
  /**
@@ -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"}