@nimbus-sh/core 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +47 -26
  2. package/dist/_shared/byte-stream.d.ts +5 -4
  3. package/dist/_shared/byte-stream.d.ts.map +1 -1
  4. package/dist/_shared/cache-stats.d.ts +23 -1
  5. package/dist/_shared/cache-stats.d.ts.map +1 -1
  6. package/dist/_shared/cache-stats.js +13 -1
  7. package/dist/_shared/exports-resolver.d.ts +1 -1
  8. package/dist/_shared/exports-resolver.js +1 -1
  9. package/dist/_shared/real-node-imports.d.ts +7 -0
  10. package/dist/_shared/real-node-imports.d.ts.map +1 -1
  11. package/dist/_shared/real-node-imports.js +8 -0
  12. package/dist/_shared/retry.js +1 -1
  13. package/dist/_shared/tarball.d.ts +23 -5
  14. package/dist/_shared/tarball.d.ts.map +1 -1
  15. package/dist/_shared/tarball.js +38 -5
  16. package/dist/_shared/vfs-write-ledger.d.ts.map +1 -1
  17. package/dist/_shared/vfs-write-ledger.js +22 -16
  18. package/dist/constants.d.ts +5 -11
  19. package/dist/constants.d.ts.map +1 -1
  20. package/dist/constants.js +16 -56
  21. package/dist/runtime/assets-loader.js +1 -1
  22. package/dist/runtime/comment-strip.d.ts +0 -35
  23. package/dist/runtime/comment-strip.d.ts.map +1 -1
  24. package/dist/runtime/comment-strip.js +48 -20
  25. package/dist/runtime/esbuild-service.d.ts +38 -42
  26. package/dist/runtime/esbuild-service.d.ts.map +1 -1
  27. package/dist/runtime/esbuild-service.js +494 -197
  28. package/dist/runtime/javascript-ast.d.ts.map +1 -1
  29. package/dist/runtime/javascript-ast.js +45 -7
  30. package/dist/runtime/opentui-wasm-backend.d.ts.map +1 -1
  31. package/dist/runtime/opentui-wasm-backend.js +18 -16
  32. package/dist/runtime/port-registry.d.ts +4 -2
  33. package/dist/runtime/port-registry.d.ts.map +1 -1
  34. package/dist/runtime/port-registry.js +60 -8
  35. package/dist/runtime/virtual-socket-kernel.generated.d.ts +1 -1
  36. package/dist/runtime/virtual-socket-kernel.generated.js +1 -1
  37. package/dist/shell/stdin-adapter.d.ts +11 -0
  38. package/dist/shell/stdin-adapter.d.ts.map +1 -0
  39. package/dist/shell/stdin-adapter.js +59 -0
  40. package/dist/shell/unix-commands.d.ts +2 -1
  41. package/dist/shell/unix-commands.d.ts.map +1 -1
  42. package/dist/shell/unix-commands.js +1078 -50
  43. package/dist/substrate/lifo/commands/io/dd.js +1 -1
  44. package/dist/substrate/lifo/commands/net/curl.d.ts.map +1 -1
  45. package/dist/substrate/lifo/commands/net/curl.js +270 -54
  46. package/dist/substrate/lifo/commands/system/npm.d.ts +18 -1
  47. package/dist/substrate/lifo/commands/system/npm.d.ts.map +1 -1
  48. package/dist/substrate/lifo/commands/system/npm.js +26 -43
  49. package/dist/substrate/lifo/commands/text/head.d.ts.map +1 -1
  50. package/dist/substrate/lifo/commands/text/head.js +2 -1
  51. package/dist/substrate/lifo/commands/text/sed.d.ts.map +1 -1
  52. package/dist/substrate/lifo/commands/text/sed.js +499 -117
  53. package/dist/substrate/lifo/commands/types.d.ts +8 -4
  54. package/dist/substrate/lifo/commands/types.d.ts.map +1 -1
  55. package/dist/substrate/lifo/node-compat/zlib.d.ts +37 -18
  56. package/dist/substrate/lifo/node-compat/zlib.d.ts.map +1 -1
  57. package/dist/substrate/lifo/node-compat/zlib.js +122 -23
  58. package/dist/substrate/lifo/shell/interpreter.d.ts +9 -1
  59. package/dist/substrate/lifo/shell/interpreter.d.ts.map +1 -1
  60. package/dist/substrate/lifo/shell/interpreter.js +79 -29
  61. package/dist/substrate/lifo/shell/pipe.d.ts +16 -0
  62. package/dist/substrate/lifo/shell/pipe.d.ts.map +1 -1
  63. package/dist/substrate/lifo/shell/pipe.js +62 -18
  64. package/dist/substrate/lifo/shell/terminal-stdin.d.ts +20 -3
  65. package/dist/substrate/lifo/shell/terminal-stdin.d.ts.map +1 -1
  66. package/dist/substrate/lifo/shell/terminal-stdin.js +60 -24
  67. package/dist/vfs/seed-project.d.ts.map +1 -1
  68. package/dist/vfs/seed-project.js +1 -1
  69. package/dist/vfs/sqlite-vfs.d.ts +21 -30
  70. package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
  71. package/dist/vfs/sqlite-vfs.js +113 -29
  72. package/dist/workspace/nimbus-workspace.d.ts +15 -0
  73. package/dist/workspace/nimbus-workspace.d.ts.map +1 -1
  74. package/dist/workspace/nimbus-workspace.js +20 -3
  75. package/dist/workspace/supervisor-op.d.ts +23 -0
  76. package/dist/workspace/supervisor-op.d.ts.map +1 -0
  77. package/dist/workspace/supervisor-op.js +92 -0
  78. package/package.json +3 -2
  79. package/src/_shared/byte-stream.ts +5 -4
  80. package/src/_shared/cache-stats.ts +27 -1
  81. package/src/_shared/exports-resolver.ts +1 -1
  82. package/src/_shared/real-node-imports.ts +8 -0
  83. package/src/_shared/retry.ts +1 -1
  84. package/src/_shared/tarball.ts +56 -5
  85. package/src/_shared/vfs-write-ledger.ts +22 -16
  86. package/src/constants.ts +30 -60
  87. package/src/runtime/assets-loader.ts +1 -1
  88. package/src/runtime/comment-strip.ts +42 -23
  89. package/src/runtime/esbuild-service.ts +566 -217
  90. package/src/runtime/javascript-ast.ts +40 -9
  91. package/src/runtime/opentui-wasm-backend.ts +19 -17
  92. package/src/runtime/port-registry.ts +66 -8
  93. package/src/runtime/virtual-socket-kernel.generated.ts +1 -1
  94. package/src/shell/stdin-adapter.ts +58 -0
  95. package/src/shell/unix-commands.ts +1054 -50
  96. package/src/substrate/lifo/commands/io/dd.ts +2 -2
  97. package/src/substrate/lifo/commands/net/curl.ts +310 -58
  98. package/src/substrate/lifo/commands/system/npm.ts +58 -38
  99. package/src/substrate/lifo/commands/text/head.ts +2 -1
  100. package/src/substrate/lifo/commands/text/sed.ts +519 -109
  101. package/src/substrate/lifo/commands/types.ts +8 -5
  102. package/src/substrate/lifo/node-compat/zlib.ts +155 -26
  103. package/src/substrate/lifo/shell/interpreter.ts +74 -27
  104. package/src/substrate/lifo/shell/pipe.ts +65 -24
  105. package/src/substrate/lifo/shell/terminal-stdin.ts +66 -30
  106. package/src/vfs/seed-project.ts +3 -3
  107. package/src/vfs/sqlite-vfs.ts +128 -64
  108. package/src/workspace/nimbus-workspace.ts +26 -2
  109. package/src/workspace/supervisor-op.ts +117 -0
  110. package/dist/_shared/install-phase.d.ts +0 -47
  111. package/dist/_shared/install-phase.d.ts.map +0 -1
  112. package/dist/_shared/install-phase.js +0 -1
  113. package/dist/_shared/rpc-dispose.d.ts +0 -4
  114. package/dist/_shared/rpc-dispose.d.ts.map +0 -1
  115. package/dist/_shared/rpc-dispose.js +0 -30
  116. package/dist/_shared/w7-frame.d.ts +0 -79
  117. package/dist/_shared/w7-frame.d.ts.map +0 -1
  118. package/dist/_shared/w7-frame.js +0 -840
  119. package/dist/_shared/weighted-credit-pool.d.ts +0 -61
  120. package/dist/_shared/weighted-credit-pool.d.ts.map +0 -1
  121. package/dist/_shared/weighted-credit-pool.js +0 -196
  122. package/dist/observability/diag-counters.d.ts +0 -236
  123. package/dist/observability/diag-counters.d.ts.map +0 -1
  124. package/dist/observability/diag-counters.js +0 -195
  125. package/dist/observability/heap-estimate.d.ts +0 -225
  126. package/dist/observability/heap-estimate.d.ts.map +0 -1
  127. package/dist/observability/heap-estimate.js +0 -185
  128. package/dist/observability/heavy-alloc-coord.d.ts +0 -93
  129. package/dist/observability/heavy-alloc-coord.d.ts.map +0 -1
  130. package/dist/observability/heavy-alloc-coord.js +0 -181
  131. package/dist/observability/oom-classify.d.ts +0 -138
  132. package/dist/observability/oom-classify.d.ts.map +0 -1
  133. package/dist/observability/oom-classify.js +0 -268
  134. package/dist/observability/oom-discriminator.d.ts +0 -176
  135. package/dist/observability/oom-discriminator.d.ts.map +0 -1
  136. package/dist/observability/oom-discriminator.js +0 -272
  137. package/src/_shared/install-phase.ts +0 -56
  138. package/src/_shared/rpc-dispose.ts +0 -31
  139. package/src/_shared/w7-frame.ts +0 -1013
  140. package/src/_shared/weighted-credit-pool.ts +0 -244
  141. package/src/observability/diag-counters.ts +0 -343
  142. package/src/observability/heap-estimate.ts +0 -331
  143. package/src/observability/heavy-alloc-coord.ts +0 -241
  144. package/src/observability/oom-classify.ts +0 -260
  145. package/src/observability/oom-discriminator.ts +0 -405
@@ -66,7 +66,7 @@ async function copyFromStdin(ctx, options) {
66
66
  skipRemaining -= chunk.length;
67
67
  continue;
68
68
  }
69
- const bytes = encode(chunk);
69
+ const bytes = typeof chunk === 'string' ? encode(chunk) : chunk;
70
70
  sink.write(bytes);
71
71
  copied += bytes.length;
72
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"curl.d.ts","sourceRoot":"","sources":["../../../../../src/substrate/lifo/commands/net/curl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAytBpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD;AAGD,QAAA,MAAM,OAAO,EAAE,OAA0B,CAAC;AAE1C,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"curl.d.ts","sourceRoot":"","sources":["../../../../../src/substrate/lifo/commands/net/curl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAq9BpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD;AAGD,QAAA,MAAM,OAAO,EAAE,OAA0B,CAAC;AAE1C,eAAe,OAAO,CAAC"}
@@ -3,15 +3,96 @@ import { isLoopbackHost } from '../../kernel/index.js';
3
3
  import { waitForSignalOrTimeout } from '../signal.js';
4
4
  import { NIMBUS_AI_TOKEN_ENV, requestCarriesSessionAiToken } from '../../../../_shared/ai-egress.js';
5
5
  import { NIMBUS_AI_GATEWAY_PORT } from '../../../../constants.js';
6
- const SHORT_VALUE_OPTIONS = new Set(['X', 'H', 'd', 'o', 'w']);
6
+ const SHORT_VALUE_OPTIONS = new Set(['X', 'H', 'd', 'o', 'w', 'D']);
7
7
  const MAX_REDIRECTS = 20;
8
+ /** curl's exit code for local write failures (-o/-D targets). */
9
+ const CURL_WRITE_ERROR_EXIT = 23;
10
+ /**
11
+ * A dump-target failure is a local write problem, not a transport error: it
12
+ * must surface as exit 23 with the precise cause, never as a rejected
13
+ * virtual call or a generic connection-failure exit 7.
14
+ */
15
+ class CurlHeaderWriteError extends Error {
16
+ constructor(action, target, cause) {
17
+ const detail = cause instanceof Error ? cause.message : String(cause);
18
+ super(`${action} dump-header file '${target}': ${detail}`);
19
+ }
20
+ }
21
+ /**
22
+ * Transfer-scoped sink for -D/--dump-header, opened once per command before
23
+ * the request. '-' streams straight to stdout with no init; a VFS path is
24
+ * truncated into existence up front — an empty file is the truthful record
25
+ * of a response that never arrived — and every received header block is
26
+ * persisted on arrival, before any body drain. With no -D flag at all the
27
+ * sink is inert; an empty-string target is still a target — it resolves to
28
+ * the cwd, the write is attempted up front, and failing it exits 23.
29
+ */
30
+ class CurlHeaderSink {
31
+ ctx;
32
+ target;
33
+ contents = [];
34
+ constructor(ctx, target) {
35
+ this.ctx = ctx;
36
+ this.target = target;
37
+ }
38
+ /** True when no -D was given: every sink method is a no-op. */
39
+ get inert() {
40
+ return this.target === null;
41
+ }
42
+ /** Truncate/create the file target up front; '-' needs no init, no -D skips. */
43
+ async open() {
44
+ if (this.target === null || this.target === '-')
45
+ return;
46
+ try {
47
+ this.ctx.vfs.writeFile(resolve(this.ctx.cwd, this.target), '');
48
+ }
49
+ catch (error) {
50
+ throw new CurlHeaderWriteError('create', this.target, error);
51
+ }
52
+ }
53
+ /** Persist one received response's header block, in arrival order. */
54
+ async writeBlock(response) {
55
+ if (this.target === null)
56
+ return;
57
+ const block = curlHeaderBlock(response);
58
+ try {
59
+ if (this.target === '-') {
60
+ this.ctx.stdout.write(block);
61
+ return;
62
+ }
63
+ this.contents.push(block);
64
+ this.ctx.vfs.writeFile(resolve(this.ctx.cwd, this.target), this.contents.join(''));
65
+ }
66
+ catch (error) {
67
+ throw new CurlHeaderWriteError('write', this.target, error);
68
+ }
69
+ }
70
+ }
71
+ function reportCurlWriteError(ctx, error) {
72
+ ctx.stderr.write(`curl: (${CURL_WRITE_ERROR_EXIT}) Failed ${error.message}\n`);
73
+ return CURL_WRITE_ERROR_EXIT;
74
+ }
75
+ /**
76
+ * Persist one arrived response block; a failing dump target cancels the
77
+ * undrained body before the write error surfaces, so the abandoned
78
+ * transfer leaks no stream on its way to exit 23.
79
+ */
80
+ async function writeHeaderDump(sink, response) {
81
+ try {
82
+ await sink.writeBlock(response);
83
+ }
84
+ catch (error) {
85
+ cancelStreamBody(response.body);
86
+ throw error;
87
+ }
88
+ }
8
89
  function createCurlImpl(kernel) {
9
90
  return async (ctx) => {
10
91
  const options = parseCurlArgs(ctx.args);
11
92
  let url = options.url;
12
93
  if (!url) {
13
94
  ctx.stderr.write('curl: no URL specified\n');
14
- ctx.stderr.write('Usage: curl [-fsSLI#] [-X method] [-H header] [-d data] [-o file] [-w format] url\n');
95
+ ctx.stderr.write('Usage: curl [-fsSLI#] [-X method] [-H header] [-d data] [-o file] [-D file] [-w format] url\n');
15
96
  return 1;
16
97
  }
17
98
  // Ensure URL has protocol
@@ -25,49 +106,49 @@ function createCurlImpl(kernel) {
25
106
  ctx.stderr.write(`curl: ${error instanceof Error ? error.message : String(error)}\n`);
26
107
  return 26;
27
108
  }
28
- if (kernel?.portRegistry) {
29
- const virtual = await resolveVirtualCurlResponse(kernel, ctx, options, url);
30
- if (virtual?.kind === 'response') {
31
- return handleCurlResponse(ctx, options, virtual.response);
32
- }
33
- if (virtual?.kind === 'external') {
34
- url = virtual.url;
35
- }
36
- if (virtual?.kind === 'error') {
37
- return virtual.exitCode;
38
- }
39
- }
109
+ const headers = new CurlHeaderSink(ctx, options.dumpHeaders ?? null);
40
110
  try {
41
111
  const requestSignal = createRequestSignal(ctx.signal, options.maxTimeSeconds);
42
- const fetchOptions = {
43
- method: options.method,
44
- headers: Object.keys(options.headers).length > 0 ? options.headers : undefined,
45
- body: options.data,
46
- redirect: options.followRedirects ? 'follow' : 'manual',
47
- signal: requestSignal.signal,
48
- };
49
112
  try {
50
- const response = await fetch(url, fetchOptions);
51
- const effectiveUrl = response.url || url;
52
- // Body streams to stdout as it arrives (SSE/chunked responses flow
53
- // live); an -o file needs the whole payload for a single VFS write.
54
- const body = options.outputFile
55
- ? new Uint8Array(await response.arrayBuffer())
56
- : response.body ?? '';
57
- return await handleCurlResponse(ctx, options, {
58
- status: response.status,
59
- statusText: response.statusText,
60
- headers: headersToRecord(response.headers),
61
- body,
62
- url: effectiveUrl,
113
+ await headers.open();
114
+ if (kernel?.portRegistry) {
115
+ const virtual = await resolveVirtualCurlResponse(kernel, ctx, options, url, headers);
116
+ if (virtual?.kind === 'response') {
117
+ return handleCurlResponse(ctx, options, virtual.response);
118
+ }
119
+ if (virtual?.kind === 'external') {
120
+ url = virtual.url;
121
+ }
122
+ if (virtual?.kind === 'error') {
123
+ return virtual.exitCode;
124
+ }
125
+ }
126
+ // -L with a dump target needs every hop's headers, so redirects are
127
+ // followed manually here; without -D the fetch-native follow keeps
128
+ // its proven semantics.
129
+ if (options.followRedirects && !headers.inert) {
130
+ return await followExternalWithDump(ctx, options, url, headers, requestSignal.signal);
131
+ }
132
+ const response = await fetch(url, {
133
+ method: options.method,
134
+ headers: Object.keys(options.headers).length > 0 ? options.headers : undefined,
135
+ body: options.data,
136
+ redirect: options.followRedirects ? 'follow' : 'manual',
137
+ signal: requestSignal.signal,
63
138
  });
139
+ // Headers are dumped on arrival — before any arrayBuffer()/body drain.
140
+ await writeHeaderDump(headers, response);
141
+ return await handleCurlResponse(ctx, options, await drainCurlResponse(options, response, response.url || url));
64
142
  }
65
143
  finally {
66
144
  requestSignal.cleanup();
67
145
  }
68
146
  }
69
- catch (e) {
70
- const msg = e instanceof Error ? e.message : String(e);
147
+ catch (error) {
148
+ if (error instanceof CurlHeaderWriteError) {
149
+ return reportCurlWriteError(ctx, error);
150
+ }
151
+ const msg = error instanceof Error ? error.message : String(error);
71
152
  if (msg === CURL_TIMEOUT_ERROR) {
72
153
  ctx.stderr.write(`curl: operation timed out after ${options.maxTimeSeconds} seconds\n`);
73
154
  }
@@ -82,6 +163,96 @@ function createCurlImpl(kernel) {
82
163
  }
83
164
  };
84
165
  }
166
+ /** Request headers fetch-follow strips when a redirect crosses origins. */
167
+ const CREDENTIAL_HEADERS = ['authorization', 'proxy-authorization', 'cookie', 'cookie2'];
168
+ /** Content headers that only make sense on a request that carries a body. */
169
+ const CONTENT_HEADERS = ['content-type', 'content-length', 'transfer-encoding', 'expect'];
170
+ /**
171
+ * Bounded manual redirect walk for external -L with a dump target: every
172
+ * hop's header block is written on arrival, and the final response goes
173
+ * through normal handling; exceeding the cap reports curl's redirect error.
174
+ * Request mutation mirrors fetch-follow: 301/302 collapse only POST to a
175
+ * bodyless GET, 303 collapses everything but HEAD, 307/308 preserve method
176
+ * and body; content headers go with the body, and credential headers are
177
+ * stripped when a hop crosses origins.
178
+ */
179
+ async function followExternalWithDump(ctx, options, startUrl, headers, signal) {
180
+ let current = new URL(startUrl);
181
+ let method = options.method;
182
+ let data = options.data;
183
+ let requestHeaders = new Headers(Object.keys(options.headers).length > 0 ? options.headers : {});
184
+ for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects++) {
185
+ const response = await fetch(current, {
186
+ method,
187
+ headers: [...requestHeaders.keys()].length > 0 ? requestHeaders : undefined,
188
+ body: data,
189
+ redirect: 'manual',
190
+ signal,
191
+ });
192
+ // Dump on arrival — before any drain of this hop's body.
193
+ await writeHeaderDump(headers, response);
194
+ const record = headersToRecord(response.headers);
195
+ if (!isRedirectStatus(response.status)) {
196
+ return await handleCurlResponse(ctx, options, await drainCurlResponse(options, response, response.url || current.toString()));
197
+ }
198
+ const location = getHeader(record, 'location');
199
+ if (!location) {
200
+ // Redirect status without a target: nothing to follow, handle as final.
201
+ return await handleCurlResponse(ctx, options, await drainCurlResponse(options, response, response.url || current.toString()));
202
+ }
203
+ if (redirects === MAX_REDIRECTS) {
204
+ cancelStreamBody(response.body);
205
+ break;
206
+ }
207
+ let next;
208
+ try {
209
+ next = new URL(location, current);
210
+ }
211
+ catch {
212
+ // Unusable Location: this hop is as final as it gets — the body must
213
+ // still be drainable, so it is only released once we decide to follow.
214
+ return await handleCurlResponse(ctx, options, await drainCurlResponse(options, response, current.toString()));
215
+ }
216
+ // Follow decision made: this hop's body is no longer needed.
217
+ cancelStreamBody(response.body);
218
+ if ([301, 302].includes(response.status) && method === 'POST') {
219
+ method = 'GET';
220
+ data = undefined;
221
+ }
222
+ else if (response.status === 303 && method !== 'HEAD') {
223
+ method = 'GET';
224
+ data = undefined;
225
+ }
226
+ if (data === undefined) {
227
+ for (const name of CONTENT_HEADERS)
228
+ requestHeaders.delete(name);
229
+ }
230
+ if (next.origin !== current.origin) {
231
+ for (const name of CREDENTIAL_HEADERS)
232
+ requestHeaders.delete(name);
233
+ }
234
+ current = next;
235
+ }
236
+ ctx.stderr.write(`curl: (47) Maximum (${MAX_REDIRECTS}) redirects followed\n`);
237
+ return 47;
238
+ }
239
+ /**
240
+ * Shape a native Response for response handling. Body streams to stdout as
241
+ * they arrive (SSE/chunked responses flow live); an -o file needs the whole
242
+ * payload for a single VFS write.
243
+ */
244
+ async function drainCurlResponse(options, response, url) {
245
+ const body = options.outputFile
246
+ ? new Uint8Array(await response.arrayBuffer())
247
+ : response.body ?? '';
248
+ return {
249
+ status: response.status,
250
+ statusText: response.statusText,
251
+ headers: headersToRecord(response.headers),
252
+ body,
253
+ url,
254
+ };
255
+ }
85
256
  const CURL_TIMEOUT_ERROR = 'curl request timeout';
86
257
  function parseCurlArgs(args) {
87
258
  const options = {
@@ -152,6 +323,11 @@ function parseLongOption(options, arg, args, index) {
152
323
  options.outputFile = value;
153
324
  return consumed;
154
325
  }
326
+ case '--dump-header': {
327
+ const { value, consumed } = readValue();
328
+ options.dumpHeaders = value;
329
+ return consumed;
330
+ }
155
331
  case '--write-out': {
156
332
  const { value, consumed } = readValue();
157
333
  options.writeOut = value;
@@ -247,6 +423,9 @@ function applyShortValueOption(options, flag, value) {
247
423
  case 'w':
248
424
  options.writeOut = value;
249
425
  break;
426
+ case 'D':
427
+ options.dumpHeaders = value;
428
+ break;
250
429
  }
251
430
  }
252
431
  /**
@@ -293,10 +472,10 @@ function addHeader(options, header) {
293
472
  return;
294
473
  options.headers[header.slice(0, colonIdx).trim()] = header.slice(colonIdx + 1).trim();
295
474
  }
296
- async function resolveVirtualCurlResponse(kernel, ctx, options, startUrl) {
475
+ async function resolveVirtualCurlResponse(kernel, ctx, options, startUrl, headers) {
297
476
  let currentUrl = startUrl;
298
477
  for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects++) {
299
- const response = await fetchVirtualCurlResponse(kernel, ctx, options, currentUrl);
478
+ const response = await fetchVirtualCurlResponse(kernel, ctx, options, currentUrl, headers);
300
479
  if (!response) {
301
480
  return redirects === 0 ? null : { kind: 'external', url: currentUrl };
302
481
  }
@@ -325,7 +504,7 @@ async function resolveVirtualCurlResponse(kernel, ctx, options, startUrl) {
325
504
  * Hand one curl request to the supervisor's loopback router and shape the
326
505
  * answer as curl sees it. Null when nothing is listening on `port`.
327
506
  */
328
- async function routeCurlOverLoopback(kernel, ctx, options, requestUrl, url, port) {
507
+ async function routeCurlOverLoopback(kernel, ctx, options, requestUrl, url, port, headers) {
329
508
  if (!kernel.routeLoopback)
330
509
  return null;
331
510
  const hasBody = options.method !== 'GET' && options.method !== 'HEAD' && options.data !== undefined;
@@ -337,6 +516,8 @@ async function routeCurlOverLoopback(kernel, ctx, options, requestUrl, url, port
337
516
  }));
338
517
  if (!response)
339
518
  return null;
519
+ // Dump on arrival — before the optional arrayBuffer() drain below.
520
+ await writeHeaderDump(headers, response);
340
521
  return {
341
522
  status: response.status,
342
523
  statusText: response.statusText,
@@ -349,7 +530,7 @@ async function routeCurlOverLoopback(kernel, ctx, options, requestUrl, url, port
349
530
  url: response.url || url,
350
531
  };
351
532
  }
352
- async function fetchVirtualCurlResponse(kernel, ctx, options, url) {
533
+ async function fetchVirtualCurlResponse(kernel, ctx, options, url, headers) {
353
534
  let requestUrl;
354
535
  try {
355
536
  requestUrl = new URL(url);
@@ -372,7 +553,7 @@ async function fetchVirtualCurlResponse(kernel, ctx, options, url) {
372
553
  if (!requestCarriesSessionAiToken(new Headers(options.headers), ctx.env[NIMBUS_AI_TOKEN_ENV] || '')) {
373
554
  return null;
374
555
  }
375
- return routeCurlOverLoopback(kernel, ctx, options, requestUrl, url, NIMBUS_AI_GATEWAY_PORT);
556
+ return routeCurlOverLoopback(kernel, ctx, options, requestUrl, url, NIMBUS_AI_GATEWAY_PORT, headers);
376
557
  }
377
558
  const handler = kernel.portRegistry.get(port);
378
559
  if (handler) {
@@ -398,6 +579,11 @@ async function fetchVirtualCurlResponse(kernel, ctx, options, url) {
398
579
  return { exitCode: 7 };
399
580
  }
400
581
  }
582
+ await headers.writeBlock({
583
+ status: vRes.statusCode,
584
+ statusText: statusText(vRes.statusCode),
585
+ headers: vRes.headers,
586
+ });
401
587
  return {
402
588
  status: vRes.statusCode,
403
589
  statusText: statusText(vRes.statusCode),
@@ -406,7 +592,7 @@ async function fetchVirtualCurlResponse(kernel, ctx, options, url) {
406
592
  url,
407
593
  };
408
594
  }
409
- const routed = await routeCurlOverLoopback(kernel, ctx, options, requestUrl, url, port);
595
+ const routed = await routeCurlOverLoopback(kernel, ctx, options, requestUrl, url, port, headers);
410
596
  if (routed)
411
597
  return routed;
412
598
  ctx.stderr.write(`curl: (7) Failed to connect to ${requestUrl.hostname} port ${port}\n`);
@@ -417,7 +603,7 @@ async function handleCurlResponse(ctx, options, response) {
417
603
  if (options.headOnly) {
418
604
  cancelStreamBody(response.body);
419
605
  if (!failed) {
420
- await writeCurlOutput(ctx, options, curlHeaders(response));
606
+ await writeCurlOutput(ctx, options, curlHeaderBlock(response));
421
607
  }
422
608
  writeCurlFailure(ctx, options, response);
423
609
  writeCurlWriteOut(ctx, options, response);
@@ -443,11 +629,21 @@ function cancelStreamBody(body) {
443
629
  body.cancel().catch(() => { });
444
630
  }
445
631
  }
632
+ /**
633
+ * --fail diagnostic: curl's own wording pairs its exit code with the
634
+ * offending HTTP status.
635
+ */
446
636
  function writeCurlFailure(ctx, options, response) {
447
637
  if (options.fail && options.showError && response.status >= 400) {
448
- ctx.stderr.write(`curl: (${response.status}) The requested URL returned error: ${response.status}\n`);
638
+ ctx.stderr.write(`curl: (22) The requested URL returned error: ${response.status}\n`);
449
639
  }
450
640
  }
641
+ function curlExitCode(options, status) {
642
+ return options.fail && status >= 400 ? 22 : 0;
643
+ }
644
+ function isRedirectStatus(status) {
645
+ return status >= 300 && status < 400;
646
+ }
451
647
  async function writeCurlOutput(ctx, options, body) {
452
648
  if (!options.outputFile) {
453
649
  if (typeof body === 'string') {
@@ -533,18 +729,38 @@ function headersToRecord(headers) {
533
729
  });
534
730
  return record;
535
731
  }
536
- function curlHeaders(response) {
537
- const lines = [`HTTP/${response.status} ${response.statusText}`];
538
- for (const [key, value] of Object.entries(response.headers)) {
539
- lines.push(`${key}: ${value}`);
732
+ function curlHeaderEntries(headers) {
733
+ if (!(headers instanceof Headers))
734
+ return Object.entries(headers);
735
+ // TS's Headers lib type predates getSetCookie; the runtime has it.
736
+ const getSetCookie = headers.getSetCookie;
737
+ const pairs = [];
738
+ for (const [name, value] of headers.entries()) {
739
+ if (typeof getSetCookie === 'function' && name.toLowerCase() === 'set-cookie')
740
+ continue;
741
+ pairs.push([name, value]);
540
742
  }
541
- return lines.join('\n') + '\n';
542
- }
543
- function curlExitCode(options, status) {
544
- return options.fail && status >= 400 ? 22 : 0;
743
+ if (typeof getSetCookie === 'function') {
744
+ for (const value of getSetCookie.call(headers))
745
+ pairs.push(['set-cookie', value]);
746
+ }
747
+ return pairs;
545
748
  }
546
- function isRedirectStatus(status) {
547
- return status >= 300 && status < 400;
749
+ /**
750
+ * The received response as an HTTP/1.1 header block: status line plus each
751
+ * header terminated CRLF, closed by the blank CRLF line that ends every
752
+ * header section. fetch exposes no negotiated wire version, so HTTP/1.1 is
753
+ * the explicit compatibility representation — a bare `HTTP/<code>` is not
754
+ * valid status-line syntax. Both -I's stdout display and -D's dump share
755
+ * this one serializer so the two surfaces cannot drift.
756
+ */
757
+ function curlHeaderBlock(response) {
758
+ const reason = response.statusText;
759
+ const statusLine = reason
760
+ ? `HTTP/1.1 ${response.status} ${reason}`
761
+ : `HTTP/1.1 ${response.status}`;
762
+ const lines = [statusLine, ...curlHeaderEntries(response.headers).map(([n, v]) => `${n}: ${v}`)];
763
+ return lines.map((line) => `${line}\r\n`).join('') + '\r\n';
548
764
  }
549
765
  function getHeader(headers, name) {
550
766
  const wanted = name.toLowerCase();
@@ -17,9 +17,26 @@ interface PackageJson {
17
17
  };
18
18
  }
19
19
  export type ShellExecuteFn = (cmd: string, ctx: CommandContext) => Promise<number>;
20
+ /** A host may supply a facet-based installer instead of in-process extraction. */
21
+ export interface NpmInstallPort {
22
+ install(projectDir: string, options: {
23
+ packages?: string[];
24
+ production?: boolean;
25
+ pid?: number;
26
+ }): Promise<{
27
+ installed: string[];
28
+ failed: string[];
29
+ totalFiles: number;
30
+ elapsed: number;
31
+ cachedHits?: number;
32
+ }>;
33
+ }
34
+ export interface NpmCommandDeps {
35
+ readonly installer?: NpmInstallPort;
36
+ }
20
37
  export declare function getBinEntries(pkg: PackageJson): Record<string, string>;
21
38
  export declare function registerBinCommand(registry: CommandRegistry, binName: string, scriptPath: string, kernel?: Kernel): void;
22
- export declare function createNpmCommand(registry: CommandRegistry, shellExecute?: ShellExecuteFn, kernel?: Kernel): Command;
39
+ export declare function createNpmCommand(registry: CommandRegistry, shellExecute?: ShellExecuteFn, kernel?: Kernel, deps?: NpmCommandDeps): Command;
23
40
  export declare function createNpxCommand(registry: CommandRegistry, shellExecute?: ShellExecuteFn): Command;
24
41
  export declare function npmInstallGlobal(packageName: string, ctx: CommandContext, registry: CommandRegistry, kernel?: Kernel): Promise<number>;
25
42
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"npm.d.ts","sourceRoot":"","sources":["../../../../../src/substrate/lifo/commands/system/npm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAuB,MAAM,aAAa,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAapD,eAAO,MAAM,WAAW,WAAW,CAAC;AAIpC,UAAU,WAAW;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACnC;AAED,MAAM,MAAM,cAAc,GAAG,CAC5B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,cAAc,KACf,OAAO,CAAC,MAAM,CAAC,CAAC;AAoNrB,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMtE;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAaxH;AA0kBD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,EAAE,YAAY,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CA+CnH;AAiCD,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,eAAe,EACzB,YAAY,CAAC,EAAE,cAAc,GAC3B,OAAO,CAyGT;AAED,wBAAsB,gBAAgB,CACrC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,eAAe,EACzB,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CA2BjB"}
1
+ {"version":3,"file":"npm.d.ts","sourceRoot":"","sources":["../../../../../src/substrate/lifo/commands/system/npm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAuB,MAAM,aAAa,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAapD,eAAO,MAAM,WAAW,WAAW,CAAC;AAIpC,UAAU,WAAW;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACnC;AAED,MAAM,MAAM,cAAc,GAAG,CAC5B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,cAAc,KACf,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC9B,OAAO,CACN,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAClE,OAAO,CAAC;QACV,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC;CACpC;AAuLD,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMtE;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAaxH;AAomBD,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,eAAe,EACzB,YAAY,CAAC,EAAE,cAAc,EAC7B,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,cAAc,GACnB,OAAO,CA+CT;AAiCD,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,eAAe,EACzB,YAAY,CAAC,EAAE,cAAc,GAC3B,OAAO,CAyGT;AAED,wBAAsB,gBAAgB,CACrC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,eAAe,EACzB,MAAM,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CA2BjB"}
@@ -1,5 +1,5 @@
1
1
  import { resolve, join } from '../../utils/path.js';
2
- import { decompressGzip, parseTar } from '../../utils/archive.js';
2
+ import { writeTarballStream } from '../../../../_shared/tarball.js';
3
3
  import { RegistryPackumentSchema, RegistrySearchResponseSchema, RegistryVersionInfoSchema, } from './registry-schemas.js';
4
4
  const GLOBAL_MODULES = '/usr/lib/node_modules';
5
5
  const GLOBAL_BIN = '/usr/bin';
@@ -132,48 +132,14 @@ async function fetchWithRange(registry, name, range, signal) {
132
132
  }
133
133
  return data.versions[matching[0].version];
134
134
  }
135
- async function downloadAndExtract(tarballUrl, targetDir, vfs, signal) {
135
+ async function fetchAndStreamPackage(tarballUrl, targetDir, vfs, signal) {
136
136
  const response = await fetch(tarballUrl, { signal });
137
137
  if (!response.ok) {
138
138
  throw new Error(`Failed to download tarball: ${response.status}`);
139
139
  }
140
- const arrayBuffer = await response.arrayBuffer();
141
- const compressed = new Uint8Array(arrayBuffer);
142
- // Decompress gzip → parse tar
143
- const decompressed = await decompressGzip(compressed);
144
- const entries = parseTar(decompressed);
145
- // Ensure target directory
146
- try {
147
- vfs.mkdir(targetDir, { recursive: true });
148
- }
149
- catch { /* exists */ }
150
- // Extract, stripping the first path component (npm tarballs use "package/")
151
- for (const entry of entries) {
152
- const slashIdx = entry.path.indexOf('/');
153
- if (slashIdx === -1)
154
- continue;
155
- const relativePath = entry.path.slice(slashIdx + 1);
156
- if (!relativePath)
157
- continue;
158
- const fullPath = join(targetDir, relativePath);
159
- if (entry.type === 'directory') {
160
- try {
161
- vfs.mkdir(fullPath, { recursive: true });
162
- }
163
- catch { /* exists */ }
164
- }
165
- else {
166
- // Ensure parent exists
167
- const parent = fullPath.slice(0, fullPath.lastIndexOf('/'));
168
- if (parent) {
169
- try {
170
- vfs.mkdir(parent, { recursive: true });
171
- }
172
- catch { /* exists */ }
173
- }
174
- vfs.writeFile(fullPath, entry.data);
175
- }
176
- }
140
+ if (!response.body)
141
+ throw new Error(`Registry served no body for ${tarballUrl}`);
142
+ return writeTarballStream(response.body, targetDir, vfs);
177
143
  }
178
144
  function readProjectPackageJson(vfs, cwd) {
179
145
  const pkgPath = join(cwd, 'package.json');
@@ -220,7 +186,10 @@ async function installSinglePackage(name, version, targetBase, vfs, npmRegistry,
220
186
  }
221
187
  stdout.write(` ${name}${version ? '@' + version : ''}...\n`);
222
188
  const info = await fetchPackageInfo(npmRegistry, name, version, signal);
223
- await downloadAndExtract(info.dist.tarball, targetDir, vfs, signal);
189
+ const written = await fetchAndStreamPackage(info.dist.tarball, targetDir, vfs, signal);
190
+ if (written.files === 0 || !vfs.exists(join(targetDir, 'package.json'))) {
191
+ throw new Error(`${name}: extraction wrote ${written.files} files and left no package.json in ${targetDir}`);
192
+ }
224
193
  let installed = 1;
225
194
  // Global install: link binaries
226
195
  if (isGlobal) {
@@ -285,7 +254,7 @@ async function npmInit(ctx) {
285
254
  ctx.stdout.write(JSON.stringify(pkg, null, 2) + '\n');
286
255
  return 0;
287
256
  }
288
- async function npmInstall(ctx, registry, kernel) {
257
+ async function npmInstall(ctx, registry, kernel, deps) {
289
258
  const args = ctx.args.slice(1);
290
259
  let isGlobal = false;
291
260
  let saveDev = false;
@@ -319,6 +288,20 @@ async function npmInstall(ctx, registry, kernel) {
319
288
  }
320
289
  catch { /* exists */ }
321
290
  }
291
+ if (deps?.installer) {
292
+ const projectDir = isGlobal ? GLOBAL_MODULES : ctx.cwd;
293
+ const result = await deps.installer.install(projectDir, {
294
+ packages: packages.length > 0 ? packages : undefined,
295
+ production: saveDev ? false : undefined,
296
+ pid: ctx.pid,
297
+ });
298
+ for (const failure of result.failed)
299
+ ctx.stderr.write(`npm ERR! ${failure}\n`);
300
+ ctx.stdout.write(`\nadded ${result.installed.length} package${result.installed.length === 1 ? '' : 's'}`
301
+ + ` (${result.totalFiles} files) in ${(result.elapsed / 1000).toFixed(1)}s\n`);
302
+ registerLocalBins(ctx.vfs, projectDir, registry, kernel);
303
+ return result.failed.length > 0 ? 1 : 0;
304
+ }
322
305
  if (packages.length === 0) {
323
306
  // Install from package.json
324
307
  if (isGlobal) {
@@ -734,7 +717,7 @@ async function npmSearch(ctx) {
734
717
  return 0;
735
718
  }
736
719
  // ─── Factory ───
737
- export function createNpmCommand(registry, shellExecute, kernel) {
720
+ export function createNpmCommand(registry, shellExecute, kernel, deps) {
738
721
  return async (ctx) => {
739
722
  const subcommand = ctx.args[0];
740
723
  if (!subcommand || subcommand === '--help' || subcommand === '-h') {
@@ -747,7 +730,7 @@ export function createNpmCommand(registry, shellExecute, kernel) {
747
730
  case 'install':
748
731
  case 'i':
749
732
  case 'add':
750
- return npmInstall(ctx, registry, kernel);
733
+ return npmInstall(ctx, registry, kernel, deps);
751
734
  case 'uninstall':
752
735
  case 'remove':
753
736
  case 'rm':
@@ -1 +1 @@
1
- {"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../../../../../src/substrate/lifo/commands/text/head.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAM3C,QAAA,MAAM,OAAO,EAAE,OAwEd,CAAC;AAgBF,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../../../../../src/substrate/lifo/commands/text/head.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAM3C,QAAA,MAAM,OAAO,EAAE,OAyEd,CAAC;AAgBF,eAAe,OAAO,CAAC"}
@@ -55,7 +55,8 @@ const command = async (ctx) => {
55
55
  const chunk = ctx.stdin.readBytes ? await ctx.stdin.readBytes(want) : await ctx.stdin.read();
56
56
  if (chunk === null)
57
57
  break;
58
- const encoded = encode(chunk).subarray(0, want);
58
+ const raw = typeof chunk === 'string' ? encode(chunk) : chunk;
59
+ const encoded = raw.subarray(0, want);
59
60
  writer.write(encoded);
60
61
  copied += encoded.length;
61
62
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sed.d.ts","sourceRoot":"","sources":["../../../../../src/substrate/lifo/commands/text/sed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAKhE,KAAK,MAAM,GAAG;IACZ,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACrC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;CAC7D,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB,CAAC;AA0FF,wBAAsB,MAAM,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwFtE;AA8FD,QAAA,MAAM,OAAO,EAAE,OAAoC,CAAC;AAEpD,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"sed.d.ts","sourceRoot":"","sources":["../../../../../src/substrate/lifo/commands/text/sed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAKhE,KAAK,MAAM,GAAG;IACZ,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACrC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;CAC7D,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB,CAAC;AAoaF,wBAAsB,MAAM,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkHtE;AAoJD,QAAA,MAAM,OAAO,EAAE,OAAoC,CAAC;AAEpD,eAAe,OAAO,CAAC"}