@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.
- package/README.ko.md +183 -22
- package/README.md +186 -22
- package/dist/cli.d.ts +5 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +128 -35
- package/dist/commands/diagnostics.d.ts +35 -0
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +60 -0
- package/dist/commands/generate.d.ts +1 -49
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +1 -214
- package/dist/commands/inspect.d.ts +5 -7
- package/dist/commands/inspect.d.ts.map +1 -1
- package/dist/commands/inspect.js +85 -65
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +27 -15
- package/dist/commands/migration-transform-tokens.d.ts +15 -0
- package/dist/commands/migration-transform-tokens.d.ts.map +1 -0
- package/dist/commands/migration-transform-tokens.js +41 -0
- package/dist/commands/new.d.ts +0 -6
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +42 -96
- package/dist/commands/output-path-safety.d.ts +14 -0
- package/dist/commands/output-path-safety.d.ts.map +1 -0
- package/dist/commands/output-path-safety.js +34 -0
- package/dist/commands/scripts.d.ts +1 -1
- package/dist/commands/scripts.d.ts.map +1 -1
- package/dist/commands/scripts.js +13 -7
- package/dist/commands/typegen-artifact.d.ts +54 -0
- package/dist/commands/typegen-artifact.d.ts.map +1 -0
- package/dist/commands/typegen-artifact.js +117 -0
- package/dist/commands/typegen-generation-child.d.ts +2 -0
- package/dist/commands/typegen-generation-child.d.ts.map +1 -0
- package/dist/commands/typegen-generation-child.js +59 -0
- package/dist/commands/typegen-generation-process.d.ts +46 -0
- package/dist/commands/typegen-generation-process.d.ts.map +1 -0
- package/dist/commands/typegen-generation-process.js +131 -0
- package/dist/commands/typegen-generation-protocol.d.ts +16 -0
- package/dist/commands/typegen-generation-protocol.d.ts.map +1 -0
- package/dist/commands/typegen-generation-protocol.js +35 -0
- package/dist/commands/typegen-isolated-source.d.ts +10 -0
- package/dist/commands/typegen-isolated-source.d.ts.map +1 -0
- package/dist/commands/typegen-isolated-source.js +75 -0
- package/dist/commands/typegen-options.d.ts +20 -0
- package/dist/commands/typegen-options.d.ts.map +1 -0
- package/dist/commands/typegen-options.js +71 -0
- package/dist/commands/typegen-source.d.ts +59 -0
- package/dist/commands/typegen-source.d.ts.map +1 -0
- package/dist/commands/typegen-source.js +183 -0
- package/dist/commands/typegen-watch.d.ts +46 -0
- package/dist/commands/typegen-watch.d.ts.map +1 -0
- package/dist/commands/typegen-watch.js +212 -0
- package/dist/commands/typegen.d.ts +29 -0
- package/dist/commands/typegen.d.ts.map +1 -0
- package/dist/commands/typegen.js +119 -0
- package/dist/dev-runner/node-restart-runner.d.ts +6 -0
- package/dist/dev-runner/node-restart-runner.d.ts.map +1 -1
- package/dist/dev-runner/node-restart-runner.js +64 -13
- package/dist/fixtures/inspect-react-app.module.d.ts +4 -0
- package/dist/fixtures/inspect-react-app.module.d.ts.map +1 -0
- package/dist/fixtures/inspect-react-app.module.js +32 -0
- package/dist/fixtures/typegen-react-app.module.d.ts +4 -0
- package/dist/fixtures/typegen-react-app.module.d.ts.map +1 -0
- package/dist/fixtures/typegen-react-app.module.js +33 -0
- package/dist/generate-command.d.ts +50 -0
- package/dist/generate-command.d.ts.map +1 -0
- package/dist/generate-command.js +214 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/dist/new/install.js +1 -1
- package/dist/new/package-spec-resolver.d.ts.map +1 -1
- package/dist/new/package-spec-resolver.js +50 -59
- package/dist/new/prompt.d.ts +1 -0
- package/dist/new/prompt.d.ts.map +1 -1
- package/dist/new/prompt.js +40 -12
- package/dist/new/published-internal-dependencies.d.ts +20 -0
- package/dist/new/published-internal-dependencies.d.ts.map +1 -0
- package/dist/new/published-internal-dependencies.js +20 -0
- package/dist/new/react-vite-ssr-scaffold.d.ts +19 -0
- package/dist/new/react-vite-ssr-scaffold.d.ts.map +1 -0
- package/dist/new/react-vite-ssr-scaffold.js +108 -0
- package/dist/new/resolver.d.ts +2 -2
- package/dist/new/resolver.d.ts.map +1 -1
- package/dist/new/resolver.js +5 -1
- package/dist/new/scaffold.d.ts.map +1 -1
- package/dist/new/scaffold.js +177 -54
- package/dist/new/starter-profiles.d.ts +8 -4
- package/dist/new/starter-profiles.d.ts.map +1 -1
- package/dist/new/starter-profiles.js +52 -18
- package/dist/new/templates/react-vite-ssr/README.md.ejs +74 -0
- package/dist/new/templates/react-vite-ssr/playwright.config.ts.ejs +26 -0
- package/dist/new/templates/react-vite-ssr/src/app.test.ts.ejs +82 -0
- package/dist/new/templates/react-vite-ssr/src/app.ts.ejs +91 -0
- package/dist/new/templates/react-vite-ssr/src/entry-client.tsx.ejs +29 -0
- package/dist/new/templates/react-vite-ssr/src/entry-server.tsx.ejs +52 -0
- package/dist/new/templates/react-vite-ssr/src/load-manifest.test.ts.ejs +55 -0
- package/dist/new/templates/react-vite-ssr/src/load-manifest.ts.ejs +53 -0
- package/dist/new/templates/react-vite-ssr/src/main.ts.ejs +18 -0
- package/dist/new/templates/react-vite-ssr/src/page.tsx.ejs +43 -0
- package/dist/new/templates/react-vite-ssr/src/react-app.test.tsx.ejs +67 -0
- package/dist/new/templates/react-vite-ssr/src/react-app.tsx.ejs +46 -0
- package/dist/new/templates/react-vite-ssr/src/styles.css.ejs +25 -0
- package/dist/new/templates/react-vite-ssr/src/styles.d.ts.ejs +1 -0
- package/dist/new/templates/react-vite-ssr/tests/production-hydration.spec.ts.ejs +54 -0
- package/dist/new/templates/react-vite-ssr/tsconfig.json.ejs +16 -0
- package/dist/new/templates/react-vite-ssr/vite.client.config.ts.ejs +24 -0
- package/dist/new/templates/react-vite-ssr/vite.server.config.ts.ejs +17 -0
- package/dist/new/templates/react-vite-ssr/vitest.config.ts.ejs +10 -0
- package/dist/new/types.d.ts +4 -2
- package/dist/new/types.d.ts.map +1 -1
- package/dist/public-generate.d.ts +2 -0
- package/dist/public-generate.d.ts.map +1 -0
- package/dist/public-generate.js +1 -0
- package/dist/public-inspect.d.ts +13 -0
- package/dist/public-inspect.d.ts.map +1 -0
- package/dist/public-inspect.js +16 -0
- package/dist/public-new.d.ts +13 -0
- package/dist/public-new.d.ts.map +1 -0
- package/dist/public-new.js +16 -0
- package/dist/public-typegen.d.ts +14 -0
- package/dist/public-typegen.d.ts.map +1 -0
- package/dist/public-typegen.js +17 -0
- package/dist/run-cli.d.ts +2 -1
- package/dist/run-cli.d.ts.map +1 -1
- package/dist/studio/sidecar.d.ts.map +1 -1
- package/dist/studio/sidecar.js +106 -14
- package/dist/transforms/nestjs-migrate.d.ts +4 -1
- package/dist/transforms/nestjs-migrate.d.ts.map +1 -1
- package/dist/transforms/nestjs-migrate.js +625 -65
- package/dist/typegen-contract.d.ts +10 -0
- package/dist/typegen-contract.d.ts.map +1 -0
- package/dist/typegen-contract.js +9 -0
- package/dist/usage.d.ts +19 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +178 -0
- package/package.json +19 -10
package/dist/commands/new.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/commands/new.js
CHANGED
|
@@ -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:
|
|
258
|
+
dependencySource: internalScaffoldOverrides.dependencySource,
|
|
319
259
|
force: parsed.force ?? runtime.force,
|
|
320
260
|
initializeGit: answers.initializeGit,
|
|
321
261
|
installDependencies: false,
|
|
322
|
-
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 =
|
|
333
|
-
scaffoldSpinner
|
|
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 =
|
|
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
|
-
|
|
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 ${
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scripts.d.ts","sourceRoot":"","sources":["../../src/commands/scripts.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,
|
|
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"}
|
package/dist/commands/scripts.js
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
|
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 ??
|
|
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 @@
|
|
|
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();
|