@nimbus-sh/core 0.4.0 → 0.5.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.md +35 -7
- package/dist/_shared/error-text.d.ts +15 -0
- package/dist/_shared/error-text.d.ts.map +1 -0
- package/dist/_shared/error-text.js +21 -0
- package/dist/_shared/exports-resolver.d.ts +19 -7
- package/dist/_shared/exports-resolver.d.ts.map +1 -1
- package/dist/_shared/exports-resolver.js +1 -1
- package/dist/_shared/retry.d.ts.map +1 -1
- package/dist/_shared/retry.js +3 -1
- package/dist/_shared/w7-frame.js +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +30 -16
- package/dist/observability/oom-classify.d.ts +1 -1
- package/dist/observability/oom-classify.d.ts.map +1 -1
- package/dist/observability/oom-classify.js +7 -0
- package/dist/observability/oom-discriminator.d.ts +1 -1
- package/dist/runtime/esbuild-service.d.ts +4 -3
- package/dist/runtime/esbuild-service.d.ts.map +1 -1
- package/dist/runtime/esbuild-service.js +9 -4
- package/dist/runtime/installed-runtimes.d.ts +4 -3
- package/dist/runtime/installed-runtimes.d.ts.map +1 -1
- package/dist/runtime/long-running-handle.d.ts +3 -3
- package/dist/runtime/long-running-handle.d.ts.map +1 -1
- package/dist/runtime/os-contracts.d.ts +7 -5
- package/dist/runtime/os-contracts.d.ts.map +1 -1
- package/dist/runtime/port-registry.d.ts +28 -0
- package/dist/runtime/port-registry.d.ts.map +1 -1
- package/dist/runtime/port-registry.js +79 -9
- package/dist/runtime/process-logs.d.ts +9 -1
- package/dist/runtime/process-logs.d.ts.map +1 -1
- package/dist/runtime/project-detect.d.ts +6 -1
- package/dist/runtime/project-detect.d.ts.map +1 -1
- package/dist/runtime/require-resolver.d.ts.map +1 -1
- package/dist/runtime/require-resolver.js +3 -4
- package/dist/runtime/runtime-registry.d.ts +4 -3
- package/dist/runtime/runtime-registry.d.ts.map +1 -1
- package/dist/runtime/runtime-registry.js +2 -1
- package/dist/runtime/sqlite-runtime-fs-bridge.js +1 -5
- package/dist/runtime/virtual-socket-kernel.d.ts.map +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.d.ts +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.js +2 -2
- package/dist/runtime/virtual-socket-kernel.js +10 -5
- package/dist/runtime/wasm-runner.d.ts.map +1 -1
- package/dist/runtime/wasm-runner.js +22 -14
- package/dist/shell/features.d.ts.map +1 -1
- package/dist/shell/features.js +5 -4
- package/dist/shell/unix-commands.d.ts +14 -1
- package/dist/shell/unix-commands.d.ts.map +1 -1
- package/dist/shell/unix-commands.js +115 -69
- package/dist/substrate/lifo/commands/net/ip.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/system/ps.d.ts +2 -1
- package/dist/substrate/lifo/commands/system/ps.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/ServiceManager.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/ServiceManager.js +0 -2
- package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts +22 -11
- package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/network/NetworkStack.js +0 -2
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts +2 -1
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.js +3 -4
- package/dist/substrate/lifo/kernel/vfs/VFS.d.ts +17 -0
- package/dist/substrate/lifo/kernel/vfs/VFS.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/vfs/VFS.js +33 -0
- package/dist/substrate/lifo/node-compat/esbuild.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/esbuild.js +2 -4
- package/dist/substrate/lifo/node-compat/index.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/index.js +3 -2
- package/dist/substrate/lifo/node-compat/loose-equality.d.ts +19 -0
- package/dist/substrate/lifo/node-compat/loose-equality.d.ts.map +1 -0
- package/dist/substrate/lifo/node-compat/loose-equality.js +91 -0
- package/dist/substrate/lifo/node-compat/process.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/process.js +6 -3
- package/dist/substrate/lifo/shell/Shell.d.ts +19 -0
- package/dist/substrate/lifo/shell/Shell.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/Shell.js +86 -75
- package/dist/substrate/lifo/shell/lexer.d.ts +9 -0
- package/dist/substrate/lifo/shell/lexer.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/lexer.js +40 -0
- package/dist/vfs/seed-project.d.ts.map +1 -1
- package/dist/vfs/seed-project.js +8 -2
- package/dist/vfs/sqlite-vfs.d.ts +61 -1
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +199 -35
- package/package.json +1 -1
- package/src/_shared/error-text.ts +21 -0
- package/src/_shared/exports-resolver.ts +24 -4
- package/src/_shared/retry.ts +4 -2
- package/src/_shared/w7-frame.ts +2 -1
- package/src/constants.ts +30 -16
- package/src/observability/oom-classify.ts +7 -0
- package/src/observability/oom-discriminator.ts +1 -1
- package/src/runtime/esbuild-service.ts +16 -11
- package/src/runtime/installed-runtimes.ts +4 -3
- package/src/runtime/long-running-handle.ts +3 -3
- package/src/runtime/os-contracts.ts +7 -5
- package/src/runtime/port-registry.ts +97 -9
- package/src/runtime/process-logs.ts +12 -3
- package/src/runtime/project-detect.ts +7 -1
- package/src/runtime/require-resolver.ts +7 -7
- package/src/runtime/runtime-registry.ts +9 -7
- package/src/runtime/sqlite-runtime-fs-bridge.ts +9 -6
- package/src/runtime/virtual-socket-kernel.generated.ts +2 -2
- package/src/runtime/virtual-socket-kernel.ts +10 -5
- package/src/runtime/wasm-runner.ts +55 -29
- package/src/shell/features.ts +5 -4
- package/src/shell/unix-commands.ts +273 -128
- package/src/substrate/lifo/commands/net/ip.ts +15 -15
- package/src/substrate/lifo/commands/system/ps.ts +2 -1
- package/src/substrate/lifo/kernel/ServiceManager.ts +0 -2
- package/src/substrate/lifo/kernel/network/NetworkStack.ts +24 -17
- package/src/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.ts +37 -12
- package/src/substrate/lifo/kernel/vfs/VFS.ts +34 -0
- package/src/substrate/lifo/node-compat/esbuild.ts +16 -8
- package/src/substrate/lifo/node-compat/index.ts +3 -2
- package/src/substrate/lifo/node-compat/loose-equality.ts +81 -0
- package/src/substrate/lifo/node-compat/process.ts +5 -2
- package/src/substrate/lifo/shell/Shell.ts +92 -70
- package/src/substrate/lifo/shell/lexer.ts +38 -0
- package/src/vfs/seed-project.ts +10 -3
- package/src/vfs/sqlite-vfs.ts +209 -36
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
type TerminalFdState,
|
|
19
19
|
assignScalar,
|
|
20
20
|
} from './interpreter.js';
|
|
21
|
-
import { lex } from './lexer.js';
|
|
21
|
+
import { continuationState, lex } from './lexer.js';
|
|
22
22
|
import { TokenKind } from './types.js';
|
|
23
23
|
import { HistoryManager } from './history.js';
|
|
24
24
|
import { JobTable } from './jobs.js';
|
|
@@ -54,6 +54,9 @@ export function formatShellPrompt(env: Record<string, string>, cwd: string): str
|
|
|
54
54
|
return `${BOLD}${GREEN}${user}@${host}${RESET}:${BOLD}${BLUE}${displayPath}${RESET}$ `;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/** PS2 — shown while an accepted line has not closed into a command yet. */
|
|
58
|
+
const CONTINUATION_PROMPT = '> ';
|
|
59
|
+
|
|
57
60
|
export interface ExecuteOptions {
|
|
58
61
|
cwd?: string;
|
|
59
62
|
env?: Record<string, string>;
|
|
@@ -136,6 +139,13 @@ export class Shell {
|
|
|
136
139
|
// Paste queue for multiline paste support
|
|
137
140
|
pasteQueue: string[] = [];
|
|
138
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Accepted lines that do not form a complete command yet: an unclosed
|
|
144
|
+
* quote or a trailing `\` keeps the shell reading under PS2, as bash
|
|
145
|
+
* does, instead of executing a truncated command.
|
|
146
|
+
*/
|
|
147
|
+
private pendingLine: string | null = null;
|
|
148
|
+
|
|
139
149
|
/**
|
|
140
150
|
* Keystrokes that arrived while a foreground command owned the terminal and
|
|
141
151
|
* nothing was reading stdin. A tty buffers type-ahead and hands it to the
|
|
@@ -267,6 +277,15 @@ export class Shell {
|
|
|
267
277
|
return this.vfs;
|
|
268
278
|
}
|
|
269
279
|
|
|
280
|
+
/**
|
|
281
|
+
* The `runAs` host this shell re-credentials through. A caller building a
|
|
282
|
+
* second Shell over the same kernel needs it, or its commands lose the
|
|
283
|
+
* identity-transition path `sudo` and `su` are dispatched on.
|
|
284
|
+
*/
|
|
285
|
+
getRunAsHost(): CommandRunAsHost | undefined {
|
|
286
|
+
return this.commandIdentity.runAs;
|
|
287
|
+
}
|
|
288
|
+
|
|
270
289
|
getRegistry(): CommandRegistry {
|
|
271
290
|
return this.registry;
|
|
272
291
|
}
|
|
@@ -442,6 +461,11 @@ export class Shell {
|
|
|
442
461
|
}
|
|
443
462
|
|
|
444
463
|
printPrompt(): void {
|
|
464
|
+
if (this.pendingLine !== null) {
|
|
465
|
+
this.terminal.write(CONTINUATION_PROMPT);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
445
469
|
// Report finished background jobs from JobTable (legacy)
|
|
446
470
|
const doneJobs = this.jobTable.collectDone();
|
|
447
471
|
for (const job of doneJobs) {
|
|
@@ -462,57 +486,32 @@ export class Shell {
|
|
|
462
486
|
return;
|
|
463
487
|
}
|
|
464
488
|
|
|
465
|
-
// Multiline paste detection: if data contains newlines and has multiple
|
|
466
|
-
// split into lines and
|
|
489
|
+
// Multiline paste detection: if data contains newlines and has multiple
|
|
490
|
+
// chars, split into lines and accept them sequentially via the paste
|
|
491
|
+
// queue. Middle segments are queued even when empty — a blank pasted
|
|
492
|
+
// line is a newline inside an open quote; only the tail after the final
|
|
493
|
+
// newline is dropped when empty, because no Enter followed it.
|
|
467
494
|
if (!this.running && data.length > 1 && /[\r\n]/.test(data)) {
|
|
468
495
|
const lines = data.split(/\r\n|\r|\n/);
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
this.lineBuffer = '';
|
|
483
|
-
this.cursorPos = 0;
|
|
484
|
-
this.historyIndex = -1;
|
|
485
|
-
if (line) {
|
|
486
|
-
this.history.push(line);
|
|
487
|
-
// Queue remaining lines before executing
|
|
488
|
-
for (let j = 1; j < lines.length - 1; j++) {
|
|
489
|
-
if (lines[j]) this.pasteQueue.push(lines[j]);
|
|
490
|
-
}
|
|
491
|
-
// Last segment (after final newline) goes into lineBuffer without executing
|
|
492
|
-
const lastSegment = lines[lines.length - 1];
|
|
493
|
-
if (lastSegment) {
|
|
494
|
-
this.pasteQueue.push(lastSegment);
|
|
495
|
-
}
|
|
496
|
-
this.executeLine(line);
|
|
497
|
-
return;
|
|
498
|
-
} else {
|
|
499
|
-
// Empty first line, queue the rest
|
|
500
|
-
for (let j = 1; j < lines.length - 1; j++) {
|
|
501
|
-
if (lines[j]) this.pasteQueue.push(lines[j]);
|
|
502
|
-
}
|
|
503
|
-
const lastSegment = lines[lines.length - 1];
|
|
504
|
-
if (lastSegment) {
|
|
505
|
-
this.pasteQueue.push(lastSegment);
|
|
506
|
-
}
|
|
507
|
-
this.drainPasteQueue();
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
496
|
+
const first = lines[0];
|
|
497
|
+
if (first) {
|
|
498
|
+
this.lineBuffer = this.lineBuffer.slice(0, this.cursorPos) + first + this.lineBuffer.slice(this.cursorPos);
|
|
499
|
+
this.cursorPos += first.length;
|
|
500
|
+
}
|
|
501
|
+
this.redrawLine();
|
|
502
|
+
this.terminal.write('\r\n');
|
|
503
|
+
const line = this.lineBuffer;
|
|
504
|
+
this.lineBuffer = '';
|
|
505
|
+
this.cursorPos = 0;
|
|
506
|
+
this.historyIndex = -1;
|
|
507
|
+
for (let j = 1; j < lines.length - 1; j++) {
|
|
508
|
+
this.pasteQueue.push(lines[j]);
|
|
512
509
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
510
|
+
const lastSegment = lines[lines.length - 1];
|
|
511
|
+
if (lastSegment) {
|
|
512
|
+
this.pasteQueue.push(lastSegment);
|
|
513
|
+
}
|
|
514
|
+
this.acceptLine(line);
|
|
516
515
|
return;
|
|
517
516
|
}
|
|
518
517
|
|
|
@@ -541,6 +540,7 @@ export class Shell {
|
|
|
541
540
|
this.abortController.abort(signalAbortReason(signal));
|
|
542
541
|
} else if (signal === 'INT') {
|
|
543
542
|
this.terminal.write('^C\r\n');
|
|
543
|
+
this.pendingLine = null;
|
|
544
544
|
this.lineBuffer = '';
|
|
545
545
|
this.cursorPos = 0;
|
|
546
546
|
this.screenCursorRow = 0;
|
|
@@ -606,18 +606,12 @@ export class Shell {
|
|
|
606
606
|
// Enter
|
|
607
607
|
if (data === '\r') {
|
|
608
608
|
this.terminal.write('\r\n');
|
|
609
|
-
const line = this.lineBuffer
|
|
609
|
+
const line = this.lineBuffer;
|
|
610
610
|
this.lineBuffer = '';
|
|
611
611
|
this.cursorPos = 0;
|
|
612
612
|
this.screenCursorRow = 0;
|
|
613
613
|
this.historyIndex = -1;
|
|
614
|
-
|
|
615
|
-
if (line) {
|
|
616
|
-
this.history.push(line);
|
|
617
|
-
this.executeLine(line);
|
|
618
|
-
} else {
|
|
619
|
-
this.printPrompt();
|
|
620
|
-
}
|
|
614
|
+
this.acceptLine(line);
|
|
621
615
|
return;
|
|
622
616
|
}
|
|
623
617
|
|
|
@@ -802,6 +796,7 @@ export class Shell {
|
|
|
802
796
|
}
|
|
803
797
|
|
|
804
798
|
private getPromptWidth(): number {
|
|
799
|
+
if (this.pendingLine !== null) return CONTINUATION_PROMPT.length;
|
|
805
800
|
const { displayPath, user, host } = shellPromptParts(this.env, this.cwd);
|
|
806
801
|
// "user@host:path$ " — count visible chars only (no ANSI codes)
|
|
807
802
|
return user.length + 1 + host.length + 1 + displayPath.length + 2;
|
|
@@ -822,7 +817,9 @@ export class Shell {
|
|
|
822
817
|
this.terminal.write('\x1b[J');
|
|
823
818
|
|
|
824
819
|
// Rewrite prompt + buffer
|
|
825
|
-
this.terminal.write(
|
|
820
|
+
this.terminal.write(
|
|
821
|
+
this.pendingLine === null ? formatShellPrompt(this.env, this.cwd) : CONTINUATION_PROMPT,
|
|
822
|
+
);
|
|
826
823
|
this.terminal.write(this.lineBuffer);
|
|
827
824
|
|
|
828
825
|
// After writing all content, figure out which row the cursor is on.
|
|
@@ -868,18 +865,10 @@ export class Shell {
|
|
|
868
865
|
|
|
869
866
|
drainPasteQueue(): void {
|
|
870
867
|
const next = this.pasteQueue.shift();
|
|
871
|
-
if (next
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
this.terminal.write('\r\n');
|
|
876
|
-
this.history.push(line);
|
|
877
|
-
this.executeLine(line);
|
|
878
|
-
} else {
|
|
879
|
-
// Skip empty lines, try next
|
|
880
|
-
this.drainPasteQueue();
|
|
881
|
-
}
|
|
882
|
-
}
|
|
868
|
+
if (next === undefined) return;
|
|
869
|
+
this.terminal.write(next);
|
|
870
|
+
this.terminal.write('\r\n');
|
|
871
|
+
this.acceptLine(next);
|
|
883
872
|
}
|
|
884
873
|
|
|
885
874
|
private moveCursorLeft(): void {
|
|
@@ -943,6 +932,39 @@ export class Shell {
|
|
|
943
932
|
this.redrawLine();
|
|
944
933
|
}
|
|
945
934
|
|
|
935
|
+
/**
|
|
936
|
+
* A line the user finished with Enter. If it leaves a quote open or ends
|
|
937
|
+
* in a line continuation it is not a command yet: bash buffers it, shows
|
|
938
|
+
* PS2 and keeps reading, and so does this shell. A `\<newline>` join drops
|
|
939
|
+
* both characters; a quoted join keeps the newline in the string.
|
|
940
|
+
*/
|
|
941
|
+
private acceptLine(rawLine: string): void {
|
|
942
|
+
let command: string;
|
|
943
|
+
if (this.pendingLine === null) {
|
|
944
|
+
command = rawLine.trim();
|
|
945
|
+
if (!command) {
|
|
946
|
+
this.printPrompt();
|
|
947
|
+
this.drainPasteQueue();
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
} else {
|
|
951
|
+
command = continuationState(this.pendingLine) === 'backslash'
|
|
952
|
+
? this.pendingLine.slice(0, -1) + rawLine
|
|
953
|
+
: this.pendingLine + '\n' + rawLine;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (continuationState(command) !== null) {
|
|
957
|
+
this.pendingLine = command;
|
|
958
|
+
this.printPrompt();
|
|
959
|
+
this.drainPasteQueue();
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
this.pendingLine = null;
|
|
964
|
+
this.history.push(command);
|
|
965
|
+
this.executeLine(command);
|
|
966
|
+
}
|
|
967
|
+
|
|
946
968
|
async executeLine(line: string): Promise<void> {
|
|
947
969
|
// History expansion
|
|
948
970
|
const expanded = this.historyManager.expand(line);
|
|
@@ -69,6 +69,44 @@ export function lex(input: string): Token[] {
|
|
|
69
69
|
return tokens;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
export type ContinuationState = 'single' | 'double' | 'backslash';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* How `input` leaves the lexer at end of input: inside an unclosed single or
|
|
76
|
+
* double quote, or ending in a `\` line continuation (which bash also honors
|
|
77
|
+
* inside double quotes, where `\<newline>` is removed). The interactive shell
|
|
78
|
+
* uses this to keep reading (PS2) instead of executing a truncated command.
|
|
79
|
+
* Comments are skipped exactly where lex() treats `#` as one.
|
|
80
|
+
*/
|
|
81
|
+
export function continuationState(input: string): ContinuationState | null {
|
|
82
|
+
let quote: '"' | "'" | null = null;
|
|
83
|
+
for (let i = 0; i < input.length; i++) {
|
|
84
|
+
const ch = input[i];
|
|
85
|
+
if (quote === "'") {
|
|
86
|
+
if (ch === "'") quote = null;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (ch === '\\') {
|
|
90
|
+
if (i === input.length - 1) return 'backslash';
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (quote === '"') {
|
|
95
|
+
if (ch === '"') quote = null;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (ch === "'" || ch === '"') {
|
|
99
|
+
quote = ch;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (ch === '#') {
|
|
103
|
+
while (i + 1 < input.length && input[i + 1] !== '\n') i++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (quote === null) return null;
|
|
107
|
+
return quote === '"' ? 'double' : 'single';
|
|
108
|
+
}
|
|
109
|
+
|
|
72
110
|
function tryOperator(input: string, pos: number): { token: Token; end: number } | null {
|
|
73
111
|
const ch = input[pos];
|
|
74
112
|
const next = input[pos + 1];
|
package/src/vfs/seed-project.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
} from './sqlite-vfs.js';
|
|
26
26
|
import { CHUNK_SIZE } from '../constants.js';
|
|
27
27
|
import { enc } from '../_shared/bytes.js';
|
|
28
|
+
import { errorText } from '../_shared/error-text.js';
|
|
28
29
|
import { CRED_KERNEL } from '../runtime/os-contracts.js';
|
|
29
30
|
|
|
30
31
|
/** Sentinel: if present, seed never runs again (until user deletes it). */
|
|
@@ -1085,9 +1086,15 @@ export function seedProject(
|
|
|
1085
1086
|
chunks: [{ path: SEED_SENTINEL_PATH, chunkId: 0, data: sentinelData }],
|
|
1086
1087
|
});
|
|
1087
1088
|
log?.(`[seed] sentinel written → ${SEED_SENTINEL_PATH}`);
|
|
1088
|
-
} catch (e
|
|
1089
|
-
log?.(`[seed] failed: ${e
|
|
1090
|
-
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
log?.(`[seed] failed: ${errorText(e)}`);
|
|
1091
|
+
// `reason` keeps its own fallback: a caught value carrying no message
|
|
1092
|
+
// must still report 'write-failed', not the value's text.
|
|
1093
|
+
const reason =
|
|
1094
|
+
typeof e === 'object' && e !== null && 'message' in e && e.message
|
|
1095
|
+
? String(e.message)
|
|
1096
|
+
: 'write-failed';
|
|
1097
|
+
return { seeded: false, files: fileCount, reason };
|
|
1091
1098
|
}
|
|
1092
1099
|
|
|
1093
1100
|
return { seeded: true, files: fileCount };
|