@fluojs/cli 1.0.0-beta.5 → 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 +40 -9
- package/README.md +40 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +34 -4
- package/dist/commands/scripts.d.ts.map +1 -1
- package/dist/commands/scripts.js +187 -35
- package/dist/dev-runner/node-restart-runner.d.ts +5 -0
- package/dist/dev-runner/node-restart-runner.d.ts.map +1 -1
- package/dist/dev-runner/node-restart-runner.js +72 -3
- 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/new/scaffold.d.ts.map +1 -1
- package/dist/new/scaffold.js +173 -177
- package/dist/new/starter-profiles.d.ts.map +1 -1
- package/dist/new/starter-profiles.js +13 -13
- package/package.json +2 -2
package/dist/commands/scripts.js
CHANGED
|
@@ -5,12 +5,18 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { SUPPORTED_PACKAGE_MANAGERS } from './package-manager.js';
|
|
6
6
|
const EMPTY_ENV = {};
|
|
7
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';
|
|
8
11
|
function getCliSourceRoot() {
|
|
9
12
|
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
10
13
|
}
|
|
11
14
|
function getCliEntryPoint() {
|
|
12
15
|
return join(getCliSourceRoot(), 'cli.js');
|
|
13
16
|
}
|
|
17
|
+
function getPreserveColorTtyImport() {
|
|
18
|
+
return join(getCliSourceRoot(), 'dev-runner', 'preserve-color-tty.js');
|
|
19
|
+
}
|
|
14
20
|
function isRecord(value) {
|
|
15
21
|
return typeof value === 'object' && value !== null;
|
|
16
22
|
}
|
|
@@ -87,6 +93,19 @@ function withProjectLocalBin(env, projectDirectory) {
|
|
|
87
93
|
[pathKey]: existingPath ? `${localBin}${delimiter}${existingPath}` : localBin
|
|
88
94
|
};
|
|
89
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
|
+
}
|
|
90
109
|
function defaultSpawnCommand(command, args, options) {
|
|
91
110
|
return new Promise((resolveExitCode, reject) => {
|
|
92
111
|
const child = spawn(command, args, options);
|
|
@@ -105,6 +124,39 @@ function buildNativeNodeWatchStep(passThrough) {
|
|
|
105
124
|
mode: 'native-watch'
|
|
106
125
|
};
|
|
107
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
|
+
}
|
|
108
160
|
function buildProjectRunner(command, runtime, passThrough, options) {
|
|
109
161
|
if (command === 'build') {
|
|
110
162
|
switch (runtime) {
|
|
@@ -136,32 +188,24 @@ function buildProjectRunner(command, runtime, passThrough, options) {
|
|
|
136
188
|
}
|
|
137
189
|
}
|
|
138
190
|
if (command === 'dev') {
|
|
191
|
+
if (options.devRunner === 'native') {
|
|
192
|
+
const nativeStep = buildNativeRuntimeDevStep(runtime, passThrough);
|
|
193
|
+
if (nativeStep) {
|
|
194
|
+
return [nativeStep];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
139
197
|
switch (runtime) {
|
|
140
198
|
case 'bun':
|
|
141
|
-
return [
|
|
142
|
-
command: 'bun',
|
|
143
|
-
args: ['--watch', 'src/main.ts', ...passThrough]
|
|
144
|
-
}];
|
|
199
|
+
return [buildFluoDevRunnerStep(runtime, passThrough)];
|
|
145
200
|
case 'deno':
|
|
146
|
-
return [
|
|
147
|
-
command: 'deno',
|
|
148
|
-
args: ['run', '--allow-env', '--allow-net', '--watch', 'src/main.ts', ...passThrough]
|
|
149
|
-
}];
|
|
201
|
+
return [buildFluoDevRunnerStep(runtime, passThrough)];
|
|
150
202
|
case 'cloudflare-workers':
|
|
151
|
-
return [
|
|
152
|
-
command: 'wrangler',
|
|
153
|
-
args: ['dev', ...passThrough],
|
|
154
|
-
mode: 'native-watch'
|
|
155
|
-
}];
|
|
203
|
+
return [buildFluoDevRunnerStep(runtime, passThrough)];
|
|
156
204
|
default:
|
|
157
205
|
if (options.rawWatch) {
|
|
158
206
|
return [buildNativeNodeWatchStep(passThrough)];
|
|
159
207
|
}
|
|
160
|
-
return [
|
|
161
|
-
command: 'node',
|
|
162
|
-
args: ['--import', 'tsx', getCliEntryPoint(), '__node-dev-runner', '--', ...passThrough],
|
|
163
|
-
mode: 'fluo-node-restart'
|
|
164
|
-
}];
|
|
208
|
+
return [buildFluoDevRunnerStep(runtime, passThrough)];
|
|
165
209
|
}
|
|
166
210
|
}
|
|
167
211
|
switch (runtime) {
|
|
@@ -178,7 +222,7 @@ function buildProjectRunner(command, runtime, passThrough, options) {
|
|
|
178
222
|
case 'cloudflare-workers':
|
|
179
223
|
return [{
|
|
180
224
|
command: 'wrangler',
|
|
181
|
-
args: ['dev', '--remote', ...passThrough]
|
|
225
|
+
args: ['dev', '--remote', '--show-interactive-dev-session=false', ...passThrough]
|
|
182
226
|
}];
|
|
183
227
|
default:
|
|
184
228
|
return [{
|
|
@@ -197,7 +241,29 @@ async function runProjectRunnerSteps(steps, runtime, options) {
|
|
|
197
241
|
}
|
|
198
242
|
return 0;
|
|
199
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
|
+
}
|
|
200
265
|
function parseScriptArgs(argv) {
|
|
266
|
+
let devRunner;
|
|
201
267
|
let dryRun = false;
|
|
202
268
|
let packageManager;
|
|
203
269
|
let rawWatch = false;
|
|
@@ -218,13 +284,25 @@ function parseScriptArgs(argv) {
|
|
|
218
284
|
rawWatch = true;
|
|
219
285
|
continue;
|
|
220
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
|
+
}
|
|
221
299
|
if (arg === '--reporter') {
|
|
222
300
|
const value = argv[index + 1];
|
|
223
301
|
if (!value || value.startsWith('-')) {
|
|
224
302
|
throw new Error('Expected --reporter to have a value.');
|
|
225
303
|
}
|
|
226
|
-
if (!(value === 'auto' || value === 'stream' || value === 'silent')) {
|
|
227
|
-
throw new Error(`Invalid --reporter value "${value}". Use one of: auto, stream, silent.`);
|
|
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.`);
|
|
228
306
|
}
|
|
229
307
|
reporter = value;
|
|
230
308
|
index += 1;
|
|
@@ -249,6 +327,7 @@ function parseScriptArgs(argv) {
|
|
|
249
327
|
passThrough.push(arg);
|
|
250
328
|
}
|
|
251
329
|
return {
|
|
330
|
+
devRunner,
|
|
252
331
|
dryRun,
|
|
253
332
|
packageManager,
|
|
254
333
|
passThrough,
|
|
@@ -260,20 +339,24 @@ function parseScriptArgs(argv) {
|
|
|
260
339
|
function isEnabledEnvironmentFlag(value) {
|
|
261
340
|
return value === '1' || value === 'true' || value === 'yes' || value === 'on';
|
|
262
341
|
}
|
|
263
|
-
function resolveReporterMode(
|
|
342
|
+
function resolveReporterMode(parsed, runtime) {
|
|
264
343
|
if (parsed.reporter !== 'auto') {
|
|
265
344
|
return parsed.reporter;
|
|
266
345
|
}
|
|
267
346
|
if (parsed.verbose || isEnabledEnvironmentFlag(runtime.env?.FLUO_VERBOSE)) {
|
|
268
347
|
return 'stream';
|
|
269
348
|
}
|
|
270
|
-
|
|
271
|
-
|
|
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';
|
|
272
355
|
}
|
|
273
|
-
if (
|
|
274
|
-
return
|
|
356
|
+
if (configured === 'fluo' || configured === 'native') {
|
|
357
|
+
return configured;
|
|
275
358
|
}
|
|
276
|
-
|
|
359
|
+
throw new Error(`Invalid FLUO_DEV_RUNNER value "${configured}". Use one of: fluo, native.`);
|
|
277
360
|
}
|
|
278
361
|
function renderStep(step) {
|
|
279
362
|
return `${step.command} ${step.args.join(' ')}`.trim();
|
|
@@ -297,16 +380,73 @@ function createBoundedBufferStream(limit) {
|
|
|
297
380
|
}
|
|
298
381
|
};
|
|
299
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
|
+
}
|
|
300
406
|
function createReporterStreams(mode, verbose, stdout, stderr) {
|
|
301
407
|
if (mode === 'stream') {
|
|
302
408
|
return {
|
|
409
|
+
finalizeChildOutputBeforeStatus() {},
|
|
303
410
|
flushBufferedStdoutOnFailure() {},
|
|
304
411
|
stdio: 'inherit'
|
|
305
412
|
};
|
|
306
413
|
}
|
|
307
|
-
if (mode === '
|
|
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') {
|
|
308
447
|
if (verbose) {
|
|
309
448
|
return {
|
|
449
|
+
finalizeChildOutputBeforeStatus() {},
|
|
310
450
|
flushBufferedStdoutOnFailure() {},
|
|
311
451
|
stderr,
|
|
312
452
|
stdio: 'pipe',
|
|
@@ -315,6 +455,7 @@ function createReporterStreams(mode, verbose, stdout, stderr) {
|
|
|
315
455
|
}
|
|
316
456
|
const bufferedStdout = createBoundedBufferStream(FAILURE_STDOUT_BUFFER_LIMIT);
|
|
317
457
|
return {
|
|
458
|
+
finalizeChildOutputBeforeStatus() {},
|
|
318
459
|
flushBufferedStdoutOnFailure() {
|
|
319
460
|
if (bufferedStdout.hasContent()) {
|
|
320
461
|
stderr.write('[fluo] child stdout before failure:\n');
|
|
@@ -328,6 +469,7 @@ function createReporterStreams(mode, verbose, stdout, stderr) {
|
|
|
328
469
|
};
|
|
329
470
|
}
|
|
330
471
|
return {
|
|
472
|
+
finalizeChildOutputBeforeStatus() {},
|
|
331
473
|
flushBufferedStdoutOnFailure() {},
|
|
332
474
|
stdio: 'inherit'
|
|
333
475
|
};
|
|
@@ -341,7 +483,7 @@ function createReporterStreams(mode, verbose, stdout, stderr) {
|
|
|
341
483
|
*/
|
|
342
484
|
export function scriptUsage(command) {
|
|
343
485
|
const nodeEnv = command === 'dev' ? 'development' : 'production';
|
|
344
|
-
return [`Usage: fluo ${command} [options] [-- <args>]`, '', `Run the generated fluo project ${command} lifecycle with NODE_ENV defaulting to ${nodeEnv} when unset.`, '', 'Options', ' --dry-run Print the command without running it.', command === 'dev' ? ' --raw-watch Use the runtime-native watcher instead of the fluo
|
|
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');
|
|
345
487
|
}
|
|
346
488
|
|
|
347
489
|
/**
|
|
@@ -367,19 +509,28 @@ export async function runScriptCommand(command, argv, runtime = {}) {
|
|
|
367
509
|
const parsed = parseScriptArgs(argv);
|
|
368
510
|
const projectRuntime = detectProjectRuntime(project.manifest);
|
|
369
511
|
const defaultNodeEnv = command === 'dev' ? 'development' : 'production';
|
|
370
|
-
const childEnv = withProjectLocalBin(withDefaultNodeEnv(env, defaultNodeEnv), project.directory);
|
|
371
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';
|
|
372
517
|
const runnerSteps = buildProjectRunner(command, projectRuntime, parsed.passThrough, {
|
|
518
|
+
devRunner,
|
|
373
519
|
rawWatch
|
|
374
520
|
});
|
|
375
|
-
const reporterMode = resolveReporterMode(
|
|
521
|
+
const reporterMode = resolveReporterMode(parsed, {
|
|
376
522
|
...runtime,
|
|
377
523
|
env,
|
|
378
524
|
stdout
|
|
379
525
|
});
|
|
380
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
|
+
}
|
|
381
532
|
if (parsed.dryRun) {
|
|
382
|
-
for (const step of
|
|
533
|
+
for (const step of colorAwareRunnerSteps) {
|
|
383
534
|
stdout.write(`Would run: ${step.command} ${step.args.join(' ')}\n`);
|
|
384
535
|
}
|
|
385
536
|
stdout.write(`Project: ${project.path}\n`);
|
|
@@ -387,16 +538,16 @@ export async function runScriptCommand(command, argv, runtime = {}) {
|
|
|
387
538
|
stdout.write(`NODE_ENV: ${childEnv.NODE_ENV ?? ''}\n`);
|
|
388
539
|
stdout.write(`Reporter: ${reporterMode}\n`);
|
|
389
540
|
if (command === 'dev') {
|
|
390
|
-
stdout.write(`Watch mode: ${
|
|
541
|
+
stdout.write(`Watch mode: ${colorAwareRunnerSteps.map(step => step.mode ?? 'single-run').join(', ')}\n`);
|
|
391
542
|
}
|
|
392
543
|
return 0;
|
|
393
544
|
}
|
|
394
545
|
if (reporterMode === 'pretty') {
|
|
395
546
|
stdout.write(`[fluo] ${command} ${projectRuntime} lifecycle starting\n`);
|
|
396
|
-
stdout.write(`[fluo] ${
|
|
547
|
+
stdout.write(`[fluo] ${colorAwareRunnerSteps.map(renderStep).join(' && ')}\n`);
|
|
397
548
|
}
|
|
398
549
|
const reporterStreams = createReporterStreams(reporterMode, verbose, stdout, stderr);
|
|
399
|
-
const exitCode = await runProjectRunnerSteps(
|
|
550
|
+
const exitCode = await runProjectRunnerSteps(colorAwareRunnerSteps, {
|
|
400
551
|
spawnCommand: runtime.spawnCommand ?? defaultSpawnCommand
|
|
401
552
|
}, {
|
|
402
553
|
cwd: project.directory,
|
|
@@ -404,6 +555,7 @@ export async function runScriptCommand(command, argv, runtime = {}) {
|
|
|
404
555
|
...reporterStreams
|
|
405
556
|
});
|
|
406
557
|
if (reporterMode === 'pretty') {
|
|
558
|
+
reporterStreams.finalizeChildOutputBeforeStatus();
|
|
407
559
|
if (exitCode === 0) {
|
|
408
560
|
stdout.write(`[fluo] ${command} lifecycle completed\n`);
|
|
409
561
|
} else {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ChildProcess } from 'node:child_process';
|
|
2
2
|
import { type FSWatcher } from 'node:fs';
|
|
3
3
|
type RestartRunnerStream = {
|
|
4
|
+
isTTY?: boolean;
|
|
4
5
|
write(message: string): unknown;
|
|
5
6
|
};
|
|
6
7
|
type RestartChildSpawner = (command: string, args: string[], options: {
|
|
@@ -8,6 +9,8 @@ type RestartChildSpawner = (command: string, args: string[], options: {
|
|
|
8
9
|
env: NodeJS.ProcessEnv;
|
|
9
10
|
stdio: 'inherit';
|
|
10
11
|
}) => ChildProcess;
|
|
12
|
+
/** Runtime target handled by the fluo-owned development restart runner. */
|
|
13
|
+
export type DevRunnerRuntime = 'bun' | 'cloudflare-workers' | 'deno' | 'node';
|
|
11
14
|
type RestartSignal = 'SIGINT' | 'SIGTERM';
|
|
12
15
|
type RestartSignalTarget = {
|
|
13
16
|
off(signal: RestartSignal, listener: () => void): unknown;
|
|
@@ -20,11 +23,13 @@ type ContentChangeGate = {
|
|
|
20
23
|
commitBaseline(paths: Iterable<string>): void;
|
|
21
24
|
hasMeaningfulChange(paths: Iterable<string>): boolean;
|
|
22
25
|
};
|
|
26
|
+
/** Options used to configure the fluo-owned Node restart-on-watch process boundary. */
|
|
23
27
|
export type NodeRestartRunnerOptions = {
|
|
24
28
|
appArgs?: string[];
|
|
25
29
|
debounceMs?: number;
|
|
26
30
|
env: NodeJS.ProcessEnv;
|
|
27
31
|
projectDirectory?: string;
|
|
32
|
+
runtime?: DevRunnerRuntime;
|
|
28
33
|
signalTarget?: RestartSignalTarget;
|
|
29
34
|
spawnChild?: RestartChildSpawner;
|
|
30
35
|
stderr?: RestartRunnerStream;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-restart-runner.d.ts","sourceRoot":"","sources":["../../src/dev-runner/node-restart-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,EAA0D,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;
|
|
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"}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { existsSync, readdirSync, readFileSync, statSync, watch } from 'node:fs';
|
|
4
|
-
import { basename, join, relative, sep } from 'node:path';
|
|
4
|
+
import { basename, dirname, join, relative, sep } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
/** Runtime target handled by the fluo-owned development restart runner. */
|
|
8
|
+
|
|
9
|
+
/** Options used to configure the fluo-owned Node restart-on-watch process boundary. */
|
|
10
|
+
|
|
5
11
|
const DEFAULT_DEBOUNCE_MS = 100;
|
|
12
|
+
const PRETTY_TTY_COLOR_ENV = 'FLUO_DEV_PRETTY_TTY_COLOR';
|
|
6
13
|
const DEFAULT_IGNORES = ['.cache', '.fluo', '.git', '.turbo', 'coverage', 'dist', 'node_modules', '*.swp', '*.swo', '*~', '.#*'];
|
|
7
14
|
const WATCH_FILES = ['.env', 'package.json', 'tsconfig.json', 'tsconfig.build.json'];
|
|
15
|
+
const SHOW_NODE_RESTART_NOTICE_ENV = 'FLUO_DEV_SHOW_RESTART_NOTICE';
|
|
16
|
+
const CLEAR_SCREEN = '\u001B[2J\u001B[3J\u001B[H';
|
|
8
17
|
function normalizeIgnorePatterns(patterns) {
|
|
9
18
|
return patterns.map(pattern => pattern.trim()).filter(pattern => pattern.length > 0);
|
|
10
19
|
}
|
|
@@ -103,6 +112,60 @@ function stopChild(child) {
|
|
|
103
112
|
child.kill('SIGTERM');
|
|
104
113
|
}
|
|
105
114
|
}
|
|
115
|
+
function getPreserveColorTtyImport() {
|
|
116
|
+
return join(dirname(dirname(fileURLToPath(import.meta.url))), 'dev-runner', 'preserve-color-tty.js');
|
|
117
|
+
}
|
|
118
|
+
function buildNodeAppArgs(env, appArgs) {
|
|
119
|
+
const colorTtyImport = env[PRETTY_TTY_COLOR_ENV] === '1' ? ['--import', getPreserveColorTtyImport()] : [];
|
|
120
|
+
return ['--env-file=.env', ...colorTtyImport, '--import', 'tsx', 'src/main.ts', ...appArgs];
|
|
121
|
+
}
|
|
122
|
+
function buildBunAppArgs(env, appArgs) {
|
|
123
|
+
const colorTtyPreload = env[PRETTY_TTY_COLOR_ENV] === '1' ? ['--preload', getPreserveColorTtyImport()] : [];
|
|
124
|
+
return [...colorTtyPreload, 'src/main.ts', ...appArgs];
|
|
125
|
+
}
|
|
126
|
+
function buildAppCommand(runtime, env, appArgs) {
|
|
127
|
+
switch (runtime) {
|
|
128
|
+
case 'bun':
|
|
129
|
+
return {
|
|
130
|
+
command: 'bun',
|
|
131
|
+
args: buildBunAppArgs(env, appArgs)
|
|
132
|
+
};
|
|
133
|
+
case 'cloudflare-workers':
|
|
134
|
+
return {
|
|
135
|
+
command: 'wrangler',
|
|
136
|
+
args: ['dev', '--show-interactive-dev-session=false', ...appArgs]
|
|
137
|
+
};
|
|
138
|
+
case 'deno':
|
|
139
|
+
return {
|
|
140
|
+
command: 'deno',
|
|
141
|
+
args: ['run', '--allow-env', '--allow-net', 'src/main.ts', ...appArgs]
|
|
142
|
+
};
|
|
143
|
+
default:
|
|
144
|
+
return {
|
|
145
|
+
command: process.execPath,
|
|
146
|
+
args: buildNodeAppArgs(env, appArgs)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function readDevScriptHeader(projectDirectory) {
|
|
151
|
+
const fallbackName = basename(projectDirectory);
|
|
152
|
+
try {
|
|
153
|
+
const manifest = JSON.parse(readFileSync(join(projectDirectory, 'package.json'), 'utf8'));
|
|
154
|
+
const name = typeof manifest.name === 'string' && manifest.name.length > 0 ? manifest.name : fallbackName;
|
|
155
|
+
const version = typeof manifest.version === 'string' && manifest.version.length > 0 ? `@${manifest.version}` : '';
|
|
156
|
+
const devScript = typeof manifest.scripts?.dev === 'string' && manifest.scripts.dev.length > 0 ? manifest.scripts.dev : 'fluo dev';
|
|
157
|
+
return `> ${name}${version} dev ${projectDirectory}\n> ${devScript}\n\n`;
|
|
158
|
+
} catch (_error) {
|
|
159
|
+
return `> ${fallbackName} dev ${projectDirectory}\n> fluo dev\n\n`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function redrawDevScriptHeader(stdout, projectDirectory, env) {
|
|
163
|
+
if (env[SHOW_NODE_RESTART_NOTICE_ENV] !== '1') {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
stdout.write(CLEAR_SCREEN);
|
|
167
|
+
stdout.write(readDevScriptHeader(projectDirectory));
|
|
168
|
+
}
|
|
106
169
|
|
|
107
170
|
/**
|
|
108
171
|
* Runs the Node.js development lifecycle through fluo-owned restart supervision.
|
|
@@ -113,6 +176,7 @@ function stopChild(child) {
|
|
|
113
176
|
export async function runNodeRestartRunner(options) {
|
|
114
177
|
const projectDirectory = options.projectDirectory ?? process.cwd();
|
|
115
178
|
const env = options.env;
|
|
179
|
+
const runnerRuntime = options.runtime ?? 'node';
|
|
116
180
|
const signalTarget = options.signalTarget ?? process;
|
|
117
181
|
const spawnChild = options.spawnChild ?? spawn;
|
|
118
182
|
const stdout = options.stdout ?? process.stdout;
|
|
@@ -129,7 +193,8 @@ export async function runNodeRestartRunner(options) {
|
|
|
129
193
|
let restarting = false;
|
|
130
194
|
let stopping = false;
|
|
131
195
|
const startChild = (resolveExitCode, cleanup) => {
|
|
132
|
-
|
|
196
|
+
const appCommand = buildAppCommand(runnerRuntime, env, appArgs);
|
|
197
|
+
child = spawnChild(appCommand.command, appCommand.args, {
|
|
133
198
|
cwd: projectDirectory,
|
|
134
199
|
env,
|
|
135
200
|
stdio: 'inherit'
|
|
@@ -159,9 +224,12 @@ export async function runNodeRestartRunner(options) {
|
|
|
159
224
|
if (!gate.hasMeaningfulChange(restartPaths)) {
|
|
160
225
|
return;
|
|
161
226
|
}
|
|
162
|
-
|
|
227
|
+
if (env[SHOW_NODE_RESTART_NOTICE_ENV] === '1') {
|
|
228
|
+
stdout.write(`[fluo] restarting after content change: ${relative(projectDirectory, restartPaths[restartPaths.length - 1] ?? projectDirectory)}\n`);
|
|
229
|
+
}
|
|
163
230
|
const previousChild = child;
|
|
164
231
|
const startReplacementChild = () => {
|
|
232
|
+
redrawDevScriptHeader(stdout, projectDirectory, env);
|
|
165
233
|
startChild(resolveExitCode, cleanup);
|
|
166
234
|
gate.commitBaseline(restartPaths);
|
|
167
235
|
};
|
|
@@ -180,6 +248,7 @@ export async function runNodeRestartRunner(options) {
|
|
|
180
248
|
if (stopping) {
|
|
181
249
|
return;
|
|
182
250
|
}
|
|
251
|
+
redrawDevScriptHeader(stdout, projectDirectory, env);
|
|
183
252
|
startChild(resolveExitCode, cleanup);
|
|
184
253
|
gate.commitBaseline(committedRestartPaths);
|
|
185
254
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preserve-color-tty.d.ts","sourceRoot":"","sources":["../../src/dev-runner/preserve-color-tty.ts"],"names":[],"mappings":"AAAA,iBAAS,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,GAAG,IAAI,CASnE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function preserveTtyColorDetection(stream) {
|
|
2
|
+
try {
|
|
3
|
+
Object.defineProperty(stream, 'isTTY', {
|
|
4
|
+
configurable: true,
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
} catch (_error) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
preserveTtyColorDetection(process.stdout);
|
|
12
|
+
preserveTtyColorDetection(process.stderr);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/new/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/new/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;AAkoEnE;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,gBAAgB,EACzB,aAAa,SAAkB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,6BAAuB,CAAC"}
|