@fluojs/cli 1.0.0-beta.4 → 1.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +97 -3
- package/README.md +97 -3
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +201 -4
- package/dist/commands/diagnostics.d.ts +15 -0
- package/dist/commands/diagnostics.d.ts.map +1 -0
- package/dist/commands/diagnostics.js +163 -0
- package/dist/commands/new.js +2 -2
- package/dist/commands/package-manager.d.ts +9 -0
- package/dist/commands/package-manager.d.ts.map +1 -0
- package/dist/commands/package-manager.js +63 -0
- package/dist/commands/package-workflow.d.ts +20 -0
- package/dist/commands/package-workflow.d.ts.map +1 -0
- package/dist/commands/package-workflow.js +137 -0
- package/dist/commands/scripts.d.ts +38 -0
- package/dist/commands/scripts.d.ts.map +1 -0
- package/dist/commands/scripts.js +570 -0
- package/dist/dev-runner/node-restart-runner.d.ts +55 -0
- package/dist/dev-runner/node-restart-runner.d.ts.map +1 -0
- package/dist/dev-runner/node-restart-runner.js +317 -0
- package/dist/dev-runner/preserve-color-tty.d.ts +2 -0
- package/dist/dev-runner/preserve-color-tty.d.ts.map +1 -0
- package/dist/dev-runner/preserve-color-tty.js +12 -0
- package/dist/generators/manifest.d.ts +24 -0
- package/dist/generators/manifest.d.ts.map +1 -1
- package/dist/generators/manifest.js +9 -0
- package/dist/generators/resource.d.ts +10 -0
- package/dist/generators/resource.d.ts.map +1 -0
- package/dist/generators/resource.js +23 -0
- package/dist/generators/templates/controller.ts.ejs +5 -1
- package/dist/generators/templates/request-dto.ts.ejs +3 -0
- package/dist/new/scaffold.d.ts.map +1 -1
- package/dist/new/scaffold.js +193 -148
- package/dist/new/starter-profiles.d.ts.map +1 -1
- package/dist/new/starter-profiles.js +13 -13
- package/dist/update-check.d.ts +1 -0
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +7 -5
- package/package.json +2 -2
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { delimiter, dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { SUPPORTED_PACKAGE_MANAGERS } from './package-manager.js';
|
|
6
|
+
const EMPTY_ENV = {};
|
|
7
|
+
const FAILURE_STDOUT_BUFFER_LIMIT = 16_384;
|
|
8
|
+
const PRETTY_TTY_COLOR_ENV = 'FLUO_DEV_PRETTY_TTY_COLOR';
|
|
9
|
+
const PRETTY_CHILD_OUTPUT_PREFIX = 'app │ ';
|
|
10
|
+
const SHOW_NODE_RESTART_NOTICE_ENV = 'FLUO_DEV_SHOW_RESTART_NOTICE';
|
|
11
|
+
function getCliSourceRoot() {
|
|
12
|
+
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
13
|
+
}
|
|
14
|
+
function getCliEntryPoint() {
|
|
15
|
+
return join(getCliSourceRoot(), 'cli.js');
|
|
16
|
+
}
|
|
17
|
+
function getPreserveColorTtyImport() {
|
|
18
|
+
return join(getCliSourceRoot(), 'dev-runner', 'preserve-color-tty.js');
|
|
19
|
+
}
|
|
20
|
+
function isRecord(value) {
|
|
21
|
+
return typeof value === 'object' && value !== null;
|
|
22
|
+
}
|
|
23
|
+
function readJsonFile(filePath) {
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
26
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
27
|
+
} catch (_error) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function findProjectManifest(startDirectory) {
|
|
32
|
+
let current = resolve(startDirectory);
|
|
33
|
+
while (true) {
|
|
34
|
+
const candidate = join(current, 'package.json');
|
|
35
|
+
if (existsSync(candidate)) {
|
|
36
|
+
const manifest = readJsonFile(candidate);
|
|
37
|
+
if (manifest) {
|
|
38
|
+
return {
|
|
39
|
+
directory: current,
|
|
40
|
+
manifest,
|
|
41
|
+
path: candidate
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const parent = dirname(current);
|
|
46
|
+
if (parent === current) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
current = parent;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function hasManifestDependency(manifest, packageName) {
|
|
53
|
+
for (const field of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
|
54
|
+
const entries = manifest[field];
|
|
55
|
+
if (isRecord(entries) && typeof entries[packageName] === 'string') {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
function detectProjectRuntime(manifest) {
|
|
62
|
+
if (hasManifestDependency(manifest, '@fluojs/platform-bun')) {
|
|
63
|
+
return 'bun';
|
|
64
|
+
}
|
|
65
|
+
if (hasManifestDependency(manifest, '@fluojs/platform-deno')) {
|
|
66
|
+
return 'deno';
|
|
67
|
+
}
|
|
68
|
+
if (hasManifestDependency(manifest, '@fluojs/platform-cloudflare-workers')) {
|
|
69
|
+
return 'cloudflare-workers';
|
|
70
|
+
}
|
|
71
|
+
return 'node';
|
|
72
|
+
}
|
|
73
|
+
function withDefaultNodeEnv(env, defaultNodeEnv) {
|
|
74
|
+
if (env.NODE_ENV) {
|
|
75
|
+
return {
|
|
76
|
+
...env
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
...env,
|
|
81
|
+
NODE_ENV: defaultNodeEnv
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function findPathEnvKey(env) {
|
|
85
|
+
return Object.keys(env).find(key => key.toLowerCase() === 'path') ?? 'PATH';
|
|
86
|
+
}
|
|
87
|
+
function withProjectLocalBin(env, projectDirectory) {
|
|
88
|
+
const pathKey = findPathEnvKey(env);
|
|
89
|
+
const existingPath = env[pathKey];
|
|
90
|
+
const localBin = join(projectDirectory, 'node_modules', '.bin');
|
|
91
|
+
return {
|
|
92
|
+
...env,
|
|
93
|
+
[pathKey]: existingPath ? `${localBin}${delimiter}${existingPath}` : localBin
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function withPipedReporterColorEnv(env, mode, stdout, stderr) {
|
|
97
|
+
if (!(mode === 'app' || mode === 'pretty') || env.NO_COLOR !== undefined) {
|
|
98
|
+
return env;
|
|
99
|
+
}
|
|
100
|
+
if (!stdout.isTTY || !stderr.isTTY) {
|
|
101
|
+
return env;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
...env,
|
|
105
|
+
FORCE_COLOR: env.FORCE_COLOR ?? '1',
|
|
106
|
+
[PRETTY_TTY_COLOR_ENV]: '1'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function defaultSpawnCommand(command, args, options) {
|
|
110
|
+
return new Promise((resolveExitCode, reject) => {
|
|
111
|
+
const child = spawn(command, args, options);
|
|
112
|
+
if (options.stdio === 'pipe') {
|
|
113
|
+
child.stdout?.on('data', chunk => options.stdout?.write(String(chunk)));
|
|
114
|
+
child.stderr?.on('data', chunk => options.stderr?.write(String(chunk)));
|
|
115
|
+
}
|
|
116
|
+
child.on('error', reject);
|
|
117
|
+
child.on('close', code => resolveExitCode(code ?? 1));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function buildNativeNodeWatchStep(passThrough) {
|
|
121
|
+
return {
|
|
122
|
+
command: 'node',
|
|
123
|
+
args: ['--env-file=.env', '--watch', '--watch-preserve-output', '--import', 'tsx', 'src/main.ts', ...passThrough],
|
|
124
|
+
mode: 'native-watch'
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function buildFluoDevRunnerStep(runtime, passThrough) {
|
|
128
|
+
return {
|
|
129
|
+
command: 'node',
|
|
130
|
+
args: ['--import', 'tsx', getCliEntryPoint(), '__dev-runner', '--runtime', runtime, '--', ...passThrough],
|
|
131
|
+
mode: 'fluo-restart'
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function buildNativeRuntimeDevStep(runtime, passThrough) {
|
|
135
|
+
switch (runtime) {
|
|
136
|
+
case 'node':
|
|
137
|
+
return buildNativeNodeWatchStep(passThrough);
|
|
138
|
+
case 'bun':
|
|
139
|
+
return {
|
|
140
|
+
command: 'bun',
|
|
141
|
+
args: ['--watch', 'src/main.ts', ...passThrough],
|
|
142
|
+
mode: 'runtime-native-watch'
|
|
143
|
+
};
|
|
144
|
+
case 'deno':
|
|
145
|
+
return {
|
|
146
|
+
command: 'deno',
|
|
147
|
+
args: ['run', '--watch', '--allow-env', '--allow-net', 'src/main.ts', ...passThrough],
|
|
148
|
+
mode: 'runtime-native-watch'
|
|
149
|
+
};
|
|
150
|
+
case 'cloudflare-workers':
|
|
151
|
+
return {
|
|
152
|
+
command: 'wrangler',
|
|
153
|
+
args: ['dev', '--show-interactive-dev-session=false', ...passThrough],
|
|
154
|
+
mode: 'runtime-native-watch'
|
|
155
|
+
};
|
|
156
|
+
default:
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function buildProjectRunner(command, runtime, passThrough, options) {
|
|
161
|
+
if (command === 'build') {
|
|
162
|
+
switch (runtime) {
|
|
163
|
+
case 'bun':
|
|
164
|
+
return [{
|
|
165
|
+
command: 'bun',
|
|
166
|
+
args: ['build', './src/main.ts', '--outdir', './dist', '--target', 'bun', ...passThrough]
|
|
167
|
+
}];
|
|
168
|
+
case 'deno':
|
|
169
|
+
return [{
|
|
170
|
+
command: 'deno',
|
|
171
|
+
args: ['compile', '--allow-env', '--allow-net', '--output', join('dist', 'app'), 'src/main.ts', ...passThrough]
|
|
172
|
+
}];
|
|
173
|
+
case 'cloudflare-workers':
|
|
174
|
+
return [{
|
|
175
|
+
command: 'wrangler',
|
|
176
|
+
args: ['deploy', '--dry-run', ...passThrough]
|
|
177
|
+
}];
|
|
178
|
+
default:
|
|
179
|
+
return [{
|
|
180
|
+
command: 'vite',
|
|
181
|
+
args: ['build', ...passThrough],
|
|
182
|
+
mode: 'single-run'
|
|
183
|
+
}, {
|
|
184
|
+
command: 'tsc',
|
|
185
|
+
args: ['-p', 'tsconfig.build.json'],
|
|
186
|
+
mode: 'single-run'
|
|
187
|
+
}];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (command === 'dev') {
|
|
191
|
+
if (options.devRunner === 'native') {
|
|
192
|
+
const nativeStep = buildNativeRuntimeDevStep(runtime, passThrough);
|
|
193
|
+
if (nativeStep) {
|
|
194
|
+
return [nativeStep];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
switch (runtime) {
|
|
198
|
+
case 'bun':
|
|
199
|
+
return [buildFluoDevRunnerStep(runtime, passThrough)];
|
|
200
|
+
case 'deno':
|
|
201
|
+
return [buildFluoDevRunnerStep(runtime, passThrough)];
|
|
202
|
+
case 'cloudflare-workers':
|
|
203
|
+
return [buildFluoDevRunnerStep(runtime, passThrough)];
|
|
204
|
+
default:
|
|
205
|
+
if (options.rawWatch) {
|
|
206
|
+
return [buildNativeNodeWatchStep(passThrough)];
|
|
207
|
+
}
|
|
208
|
+
return [buildFluoDevRunnerStep(runtime, passThrough)];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
switch (runtime) {
|
|
212
|
+
case 'bun':
|
|
213
|
+
return [{
|
|
214
|
+
command: 'bun',
|
|
215
|
+
args: ['dist/main.js', ...passThrough]
|
|
216
|
+
}];
|
|
217
|
+
case 'deno':
|
|
218
|
+
return [{
|
|
219
|
+
command: join('dist', 'app'),
|
|
220
|
+
args: [...passThrough]
|
|
221
|
+
}];
|
|
222
|
+
case 'cloudflare-workers':
|
|
223
|
+
return [{
|
|
224
|
+
command: 'wrangler',
|
|
225
|
+
args: ['dev', '--remote', '--show-interactive-dev-session=false', ...passThrough]
|
|
226
|
+
}];
|
|
227
|
+
default:
|
|
228
|
+
return [{
|
|
229
|
+
command: 'node',
|
|
230
|
+
args: ['dist/main.js', ...passThrough],
|
|
231
|
+
mode: 'single-run'
|
|
232
|
+
}];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function runProjectRunnerSteps(steps, runtime, options) {
|
|
236
|
+
for (const step of steps) {
|
|
237
|
+
const exitCode = await runtime.spawnCommand(step.command, step.args, options);
|
|
238
|
+
if (exitCode !== 0) {
|
|
239
|
+
return exitCode;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
function withPipedAppColorTtyBootstrap(steps, env) {
|
|
245
|
+
if (env[PRETTY_TTY_COLOR_ENV] !== '1') {
|
|
246
|
+
return steps;
|
|
247
|
+
}
|
|
248
|
+
return steps.map(step => {
|
|
249
|
+
const preserveColorTtyImport = getPreserveColorTtyImport();
|
|
250
|
+
if (step.command === 'node' && step.mode !== 'fluo-restart') {
|
|
251
|
+
return {
|
|
252
|
+
...step,
|
|
253
|
+
args: ['--import', preserveColorTtyImport, ...step.args]
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (step.command === 'bun' && (step.mode === 'runtime-native-watch' || step.args[0] === 'dist/main.js')) {
|
|
257
|
+
return {
|
|
258
|
+
...step,
|
|
259
|
+
args: ['--preload', preserveColorTtyImport, ...step.args]
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return step;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function parseScriptArgs(argv) {
|
|
266
|
+
let devRunner;
|
|
267
|
+
let dryRun = false;
|
|
268
|
+
let packageManager;
|
|
269
|
+
let rawWatch = false;
|
|
270
|
+
let reporter = 'auto';
|
|
271
|
+
let verbose = false;
|
|
272
|
+
const passThrough = [];
|
|
273
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
274
|
+
const arg = argv[index];
|
|
275
|
+
if (arg === '--dry-run') {
|
|
276
|
+
dryRun = true;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (arg === '--verbose') {
|
|
280
|
+
verbose = true;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (arg === '--raw-watch') {
|
|
284
|
+
rawWatch = true;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (arg === '--runner') {
|
|
288
|
+
const value = argv[index + 1];
|
|
289
|
+
if (!value || value.startsWith('-')) {
|
|
290
|
+
throw new Error('Expected --runner to have a value.');
|
|
291
|
+
}
|
|
292
|
+
if (!(value === 'fluo' || value === 'native')) {
|
|
293
|
+
throw new Error(`Invalid --runner value "${value}". Use one of: fluo, native.`);
|
|
294
|
+
}
|
|
295
|
+
devRunner = value;
|
|
296
|
+
index += 1;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (arg === '--reporter') {
|
|
300
|
+
const value = argv[index + 1];
|
|
301
|
+
if (!value || value.startsWith('-')) {
|
|
302
|
+
throw new Error('Expected --reporter to have a value.');
|
|
303
|
+
}
|
|
304
|
+
if (!(value === 'auto' || value === 'pretty' || value === 'stream' || value === 'silent')) {
|
|
305
|
+
throw new Error(`Invalid --reporter value "${value}". Use one of: auto, pretty, stream, silent.`);
|
|
306
|
+
}
|
|
307
|
+
reporter = value;
|
|
308
|
+
index += 1;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (arg === '--package-manager') {
|
|
312
|
+
const value = argv[index + 1];
|
|
313
|
+
if (!value || value.startsWith('-')) {
|
|
314
|
+
throw new Error('Expected --package-manager to have a value.');
|
|
315
|
+
}
|
|
316
|
+
if (!SUPPORTED_PACKAGE_MANAGERS.has(value)) {
|
|
317
|
+
throw new Error(`Invalid --package-manager value "${value}". Use one of: pnpm, npm, yarn, bun.`);
|
|
318
|
+
}
|
|
319
|
+
packageManager = value;
|
|
320
|
+
index += 1;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (arg === '--') {
|
|
324
|
+
passThrough.push(...argv.slice(index + 1));
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
passThrough.push(arg);
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
devRunner,
|
|
331
|
+
dryRun,
|
|
332
|
+
packageManager,
|
|
333
|
+
passThrough,
|
|
334
|
+
rawWatch,
|
|
335
|
+
reporter,
|
|
336
|
+
verbose
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function isEnabledEnvironmentFlag(value) {
|
|
340
|
+
return value === '1' || value === 'true' || value === 'yes' || value === 'on';
|
|
341
|
+
}
|
|
342
|
+
function resolveReporterMode(parsed, runtime) {
|
|
343
|
+
if (parsed.reporter !== 'auto') {
|
|
344
|
+
return parsed.reporter;
|
|
345
|
+
}
|
|
346
|
+
if (parsed.verbose || isEnabledEnvironmentFlag(runtime.env?.FLUO_VERBOSE)) {
|
|
347
|
+
return 'stream';
|
|
348
|
+
}
|
|
349
|
+
return 'app';
|
|
350
|
+
}
|
|
351
|
+
function resolveDevRunnerPreference(parsed, env, runtime) {
|
|
352
|
+
const configured = parsed.devRunner ?? env.FLUO_DEV_RUNNER;
|
|
353
|
+
if (configured === undefined || configured === '') {
|
|
354
|
+
return runtime === 'node' ? 'fluo' : 'native';
|
|
355
|
+
}
|
|
356
|
+
if (configured === 'fluo' || configured === 'native') {
|
|
357
|
+
return configured;
|
|
358
|
+
}
|
|
359
|
+
throw new Error(`Invalid FLUO_DEV_RUNNER value "${configured}". Use one of: fluo, native.`);
|
|
360
|
+
}
|
|
361
|
+
function renderStep(step) {
|
|
362
|
+
return `${step.command} ${step.args.join(' ')}`.trim();
|
|
363
|
+
}
|
|
364
|
+
function createBoundedBufferStream(limit) {
|
|
365
|
+
let buffer = '';
|
|
366
|
+
return {
|
|
367
|
+
flush(target) {
|
|
368
|
+
if (buffer.length > 0) {
|
|
369
|
+
target.write(buffer);
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
hasContent() {
|
|
373
|
+
return buffer.length > 0;
|
|
374
|
+
},
|
|
375
|
+
write(message) {
|
|
376
|
+
buffer += message;
|
|
377
|
+
if (buffer.length > limit) {
|
|
378
|
+
buffer = buffer.slice(buffer.length - limit);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function createLinePrefixedStream(target, prefix) {
|
|
384
|
+
let atLineStart = true;
|
|
385
|
+
return {
|
|
386
|
+
finalizeLine() {
|
|
387
|
+
if (!atLineStart) {
|
|
388
|
+
target.write('\n');
|
|
389
|
+
atLineStart = true;
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
write(message) {
|
|
393
|
+
for (const character of message) {
|
|
394
|
+
if (atLineStart && character !== '\n') {
|
|
395
|
+
target.write(prefix);
|
|
396
|
+
atLineStart = false;
|
|
397
|
+
}
|
|
398
|
+
target.write(character);
|
|
399
|
+
if (character === '\n') {
|
|
400
|
+
atLineStart = true;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function createReporterStreams(mode, verbose, stdout, stderr) {
|
|
407
|
+
if (mode === 'stream') {
|
|
408
|
+
return {
|
|
409
|
+
finalizeChildOutputBeforeStatus() {},
|
|
410
|
+
flushBufferedStdoutOnFailure() {},
|
|
411
|
+
stdio: 'inherit'
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
if (mode === 'app') {
|
|
415
|
+
return {
|
|
416
|
+
finalizeChildOutputBeforeStatus() {},
|
|
417
|
+
flushBufferedStdoutOnFailure() {},
|
|
418
|
+
stderr,
|
|
419
|
+
stdio: 'pipe',
|
|
420
|
+
stdout
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
if (mode === 'pretty') {
|
|
424
|
+
if (verbose) {
|
|
425
|
+
return {
|
|
426
|
+
finalizeChildOutputBeforeStatus() {},
|
|
427
|
+
flushBufferedStdoutOnFailure() {},
|
|
428
|
+
stderr,
|
|
429
|
+
stdio: 'pipe',
|
|
430
|
+
stdout
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const prefixedStdout = createLinePrefixedStream(stdout, PRETTY_CHILD_OUTPUT_PREFIX);
|
|
434
|
+
const prefixedStderr = createLinePrefixedStream(stderr, PRETTY_CHILD_OUTPUT_PREFIX);
|
|
435
|
+
return {
|
|
436
|
+
finalizeChildOutputBeforeStatus() {
|
|
437
|
+
prefixedStdout.finalizeLine();
|
|
438
|
+
prefixedStderr.finalizeLine();
|
|
439
|
+
},
|
|
440
|
+
flushBufferedStdoutOnFailure() {},
|
|
441
|
+
stderr: prefixedStderr,
|
|
442
|
+
stdio: 'pipe',
|
|
443
|
+
stdout: prefixedStdout
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
if (mode === 'silent') {
|
|
447
|
+
if (verbose) {
|
|
448
|
+
return {
|
|
449
|
+
finalizeChildOutputBeforeStatus() {},
|
|
450
|
+
flushBufferedStdoutOnFailure() {},
|
|
451
|
+
stderr,
|
|
452
|
+
stdio: 'pipe',
|
|
453
|
+
stdout
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
const bufferedStdout = createBoundedBufferStream(FAILURE_STDOUT_BUFFER_LIMIT);
|
|
457
|
+
return {
|
|
458
|
+
finalizeChildOutputBeforeStatus() {},
|
|
459
|
+
flushBufferedStdoutOnFailure() {
|
|
460
|
+
if (bufferedStdout.hasContent()) {
|
|
461
|
+
stderr.write('[fluo] child stdout before failure:\n');
|
|
462
|
+
bufferedStdout.flush(stderr);
|
|
463
|
+
stderr.write('\n');
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
stderr,
|
|
467
|
+
stdio: 'pipe',
|
|
468
|
+
stdout: bufferedStdout
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
finalizeChildOutputBeforeStatus() {},
|
|
473
|
+
flushBufferedStdoutOnFailure() {},
|
|
474
|
+
stdio: 'inherit'
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Renders lifecycle command help text.
|
|
480
|
+
*
|
|
481
|
+
* @param command Lifecycle command whose help text should be rendered.
|
|
482
|
+
* @returns Human-readable lifecycle command usage text.
|
|
483
|
+
*/
|
|
484
|
+
export function scriptUsage(command) {
|
|
485
|
+
const nodeEnv = command === 'dev' ? 'development' : 'production';
|
|
486
|
+
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, ' --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');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Runs one generated-project lifecycle command through the CLI-owned runtime command matrix.
|
|
491
|
+
*
|
|
492
|
+
* @param command Lifecycle command to run.
|
|
493
|
+
* @param argv Command-specific arguments after the lifecycle command name.
|
|
494
|
+
* @param runtime Runtime dependencies used by tests, sandboxes, and embedders.
|
|
495
|
+
* @returns Process-style exit code from the lifecycle command.
|
|
496
|
+
*/
|
|
497
|
+
export async function runScriptCommand(command, argv, runtime = {}) {
|
|
498
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
499
|
+
(runtime.stdout ?? process.stdout).write(`${scriptUsage(command)}\n`);
|
|
500
|
+
return 0;
|
|
501
|
+
}
|
|
502
|
+
const env = runtime.env ?? EMPTY_ENV;
|
|
503
|
+
const stdout = runtime.stdout ?? process.stdout;
|
|
504
|
+
const stderr = runtime.stderr ?? process.stderr;
|
|
505
|
+
const project = findProjectManifest(runtime.cwd ?? process.cwd());
|
|
506
|
+
if (!project) {
|
|
507
|
+
throw new Error(`Unable to find package.json for fluo ${command}.`);
|
|
508
|
+
}
|
|
509
|
+
const parsed = parseScriptArgs(argv);
|
|
510
|
+
const projectRuntime = detectProjectRuntime(project.manifest);
|
|
511
|
+
const defaultNodeEnv = command === 'dev' ? 'development' : 'production';
|
|
512
|
+
const rawWatch = parsed.rawWatch || isEnabledEnvironmentFlag(env.FLUO_DEV_RAW_WATCH);
|
|
513
|
+
if (command !== 'dev' && parsed.devRunner) {
|
|
514
|
+
throw new Error('--runner is only supported for fluo dev. Use -- to forward --runner to the child command.');
|
|
515
|
+
}
|
|
516
|
+
const devRunner = command === 'dev' ? resolveDevRunnerPreference(parsed, env, projectRuntime) : 'fluo';
|
|
517
|
+
const runnerSteps = buildProjectRunner(command, projectRuntime, parsed.passThrough, {
|
|
518
|
+
devRunner,
|
|
519
|
+
rawWatch
|
|
520
|
+
});
|
|
521
|
+
const reporterMode = resolveReporterMode(parsed, {
|
|
522
|
+
...runtime,
|
|
523
|
+
env,
|
|
524
|
+
stdout
|
|
525
|
+
});
|
|
526
|
+
const verbose = parsed.verbose || isEnabledEnvironmentFlag(env.FLUO_VERBOSE);
|
|
527
|
+
const childEnv = withPipedReporterColorEnv(withProjectLocalBin(withDefaultNodeEnv(env, defaultNodeEnv), project.directory), reporterMode, stdout, stderr);
|
|
528
|
+
const colorAwareRunnerSteps = withPipedAppColorTtyBootstrap(runnerSteps, childEnv);
|
|
529
|
+
if (command === 'dev' && (reporterMode === 'pretty' || verbose)) {
|
|
530
|
+
childEnv[SHOW_NODE_RESTART_NOTICE_ENV] = '1';
|
|
531
|
+
}
|
|
532
|
+
if (parsed.dryRun) {
|
|
533
|
+
for (const step of colorAwareRunnerSteps) {
|
|
534
|
+
stdout.write(`Would run: ${step.command} ${step.args.join(' ')}\n`);
|
|
535
|
+
}
|
|
536
|
+
stdout.write(`Project: ${project.path}\n`);
|
|
537
|
+
stdout.write(`Runtime: ${projectRuntime}\n`);
|
|
538
|
+
stdout.write(`NODE_ENV: ${childEnv.NODE_ENV ?? ''}\n`);
|
|
539
|
+
stdout.write(`Reporter: ${reporterMode}\n`);
|
|
540
|
+
if (command === 'dev') {
|
|
541
|
+
stdout.write(`Watch mode: ${colorAwareRunnerSteps.map(step => step.mode ?? 'single-run').join(', ')}\n`);
|
|
542
|
+
}
|
|
543
|
+
return 0;
|
|
544
|
+
}
|
|
545
|
+
if (reporterMode === 'pretty') {
|
|
546
|
+
stdout.write(`[fluo] ${command} ${projectRuntime} lifecycle starting\n`);
|
|
547
|
+
stdout.write(`[fluo] ${colorAwareRunnerSteps.map(renderStep).join(' && ')}\n`);
|
|
548
|
+
}
|
|
549
|
+
const reporterStreams = createReporterStreams(reporterMode, verbose, stdout, stderr);
|
|
550
|
+
const exitCode = await runProjectRunnerSteps(colorAwareRunnerSteps, {
|
|
551
|
+
spawnCommand: runtime.spawnCommand ?? defaultSpawnCommand
|
|
552
|
+
}, {
|
|
553
|
+
cwd: project.directory,
|
|
554
|
+
env: childEnv,
|
|
555
|
+
...reporterStreams
|
|
556
|
+
});
|
|
557
|
+
if (reporterMode === 'pretty') {
|
|
558
|
+
reporterStreams.finalizeChildOutputBeforeStatus();
|
|
559
|
+
if (exitCode === 0) {
|
|
560
|
+
stdout.write(`[fluo] ${command} lifecycle completed\n`);
|
|
561
|
+
} else {
|
|
562
|
+
reporterStreams.flushBufferedStdoutOnFailure();
|
|
563
|
+
stderr.write(`[fluo] ${command} lifecycle failed with exit code ${exitCode}\n`);
|
|
564
|
+
}
|
|
565
|
+
} else if (reporterMode === 'silent' && exitCode !== 0) {
|
|
566
|
+
reporterStreams.flushBufferedStdoutOnFailure();
|
|
567
|
+
stderr.write(`[fluo] ${command} lifecycle failed with exit code ${exitCode}\n`);
|
|
568
|
+
}
|
|
569
|
+
return exitCode;
|
|
570
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type ChildProcess } from 'node:child_process';
|
|
2
|
+
import { type FSWatcher } from 'node:fs';
|
|
3
|
+
type RestartRunnerStream = {
|
|
4
|
+
isTTY?: boolean;
|
|
5
|
+
write(message: string): unknown;
|
|
6
|
+
};
|
|
7
|
+
type RestartChildSpawner = (command: string, args: string[], options: {
|
|
8
|
+
cwd: string;
|
|
9
|
+
env: NodeJS.ProcessEnv;
|
|
10
|
+
stdio: 'inherit';
|
|
11
|
+
}) => ChildProcess;
|
|
12
|
+
/** Runtime target handled by the fluo-owned development restart runner. */
|
|
13
|
+
export type DevRunnerRuntime = 'bun' | 'cloudflare-workers' | 'deno' | 'node';
|
|
14
|
+
type RestartSignal = 'SIGINT' | 'SIGTERM';
|
|
15
|
+
type RestartSignalTarget = {
|
|
16
|
+
off(signal: RestartSignal, listener: () => void): unknown;
|
|
17
|
+
once(signal: RestartSignal, listener: () => void): unknown;
|
|
18
|
+
};
|
|
19
|
+
type RestartWatcherFactory = (target: string, optionsOrListener: {
|
|
20
|
+
recursive: boolean;
|
|
21
|
+
} | ((event: string, filename: string | Buffer | null) => void), listener?: (event: string, filename: string | Buffer | null) => void) => FSWatcher;
|
|
22
|
+
type ContentChangeGate = {
|
|
23
|
+
commitBaseline(paths: Iterable<string>): void;
|
|
24
|
+
hasMeaningfulChange(paths: Iterable<string>): boolean;
|
|
25
|
+
};
|
|
26
|
+
/** Options used to configure the fluo-owned Node restart-on-watch process boundary. */
|
|
27
|
+
export type NodeRestartRunnerOptions = {
|
|
28
|
+
appArgs?: string[];
|
|
29
|
+
debounceMs?: number;
|
|
30
|
+
env: NodeJS.ProcessEnv;
|
|
31
|
+
projectDirectory?: string;
|
|
32
|
+
runtime?: DevRunnerRuntime;
|
|
33
|
+
signalTarget?: RestartSignalTarget;
|
|
34
|
+
spawnChild?: RestartChildSpawner;
|
|
35
|
+
stderr?: RestartRunnerStream;
|
|
36
|
+
stdout?: RestartRunnerStream;
|
|
37
|
+
watchTarget?: RestartWatcherFactory;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Creates a content-diff gate for fluo-owned dev restarts.
|
|
41
|
+
*
|
|
42
|
+
* @param projectDirectory Project root used for ignore matching.
|
|
43
|
+
* @param ignorePatterns Additional or default ignore patterns to apply before hashing.
|
|
44
|
+
* @returns A gate that reports whether watched paths changed by content rather than by filesystem event alone.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createContentChangeGate(projectDirectory: string, ignorePatterns?: string[]): ContentChangeGate;
|
|
47
|
+
/**
|
|
48
|
+
* Runs the Node.js development lifecycle through fluo-owned restart supervision.
|
|
49
|
+
*
|
|
50
|
+
* @param options Runner dependencies and project settings.
|
|
51
|
+
* @returns A promise that resolves with the final child exit code when the runner stops.
|
|
52
|
+
*/
|
|
53
|
+
export declare function runNodeRestartRunner(options: NodeRestartRunnerOptions): Promise<number>;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=node-restart-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-restart-runner.d.ts","sourceRoot":"","sources":["../../src/dev-runner/node-restart-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,EAA0D,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAIjG,KAAK,mBAAmB,GAAG;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC,CAAC;AAEF,KAAK,mBAAmB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,KAAK,YAAY,CAAC;AACjJ,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9E,KAAK,aAAa,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE1C,KAAK,mBAAmB,GAAG;IACzB,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;IAC1D,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;CAC5D,CAAC;AAEF,KAAK,qBAAqB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,KAAK,IAAI,KAAK,SAAS,CAAC;AAE1O,KAAK,iBAAiB,GAAG;IACvB,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC9C,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;CACvD,CAAC;AAEF,uFAAuF;AACvF,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC,CAAC;AAgGF;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,gBAAgB,EAAE,MAAM,EAAE,cAAc,GAAE,MAAM,EAAoB,GAAG,iBAAiB,CAuB/H;AAqED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2J7F"}
|