@nimbus-sh/core 0.6.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.
- package/README.md +47 -26
- package/dist/_shared/byte-stream.d.ts +5 -4
- package/dist/_shared/byte-stream.d.ts.map +1 -1
- package/dist/_shared/real-node-imports.d.ts +7 -0
- package/dist/_shared/real-node-imports.d.ts.map +1 -1
- package/dist/_shared/real-node-imports.js +8 -0
- package/dist/_shared/tarball.d.ts +23 -5
- package/dist/_shared/tarball.d.ts.map +1 -1
- package/dist/_shared/tarball.js +38 -5
- package/dist/_shared/vfs-write-ledger.d.ts.map +1 -1
- package/dist/_shared/vfs-write-ledger.js +22 -16
- package/dist/runtime/comment-strip.d.ts +0 -35
- package/dist/runtime/comment-strip.d.ts.map +1 -1
- package/dist/runtime/comment-strip.js +48 -20
- package/dist/runtime/esbuild-service.d.ts +38 -42
- package/dist/runtime/esbuild-service.d.ts.map +1 -1
- package/dist/runtime/esbuild-service.js +494 -197
- package/dist/runtime/javascript-ast.d.ts.map +1 -1
- package/dist/runtime/javascript-ast.js +45 -7
- package/dist/runtime/opentui-wasm-backend.d.ts.map +1 -1
- package/dist/runtime/opentui-wasm-backend.js +18 -16
- package/dist/runtime/port-registry.d.ts +4 -2
- package/dist/runtime/port-registry.d.ts.map +1 -1
- package/dist/runtime/port-registry.js +60 -8
- package/dist/shell/stdin-adapter.d.ts +11 -0
- package/dist/shell/stdin-adapter.d.ts.map +1 -0
- package/dist/shell/stdin-adapter.js +59 -0
- package/dist/shell/unix-commands.d.ts +2 -1
- package/dist/shell/unix-commands.d.ts.map +1 -1
- package/dist/shell/unix-commands.js +1078 -50
- package/dist/substrate/lifo/commands/io/dd.js +1 -1
- package/dist/substrate/lifo/commands/net/curl.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/net/curl.js +270 -54
- package/dist/substrate/lifo/commands/system/npm.d.ts +18 -1
- package/dist/substrate/lifo/commands/system/npm.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/system/npm.js +26 -43
- package/dist/substrate/lifo/commands/text/head.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/text/head.js +2 -1
- package/dist/substrate/lifo/commands/text/sed.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/text/sed.js +499 -117
- package/dist/substrate/lifo/commands/types.d.ts +8 -4
- package/dist/substrate/lifo/commands/types.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/zlib.d.ts +37 -18
- package/dist/substrate/lifo/node-compat/zlib.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/zlib.js +122 -23
- package/dist/substrate/lifo/shell/interpreter.d.ts +9 -1
- package/dist/substrate/lifo/shell/interpreter.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/interpreter.js +79 -29
- package/dist/substrate/lifo/shell/pipe.d.ts +16 -0
- package/dist/substrate/lifo/shell/pipe.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/pipe.js +62 -18
- package/dist/substrate/lifo/shell/terminal-stdin.d.ts +20 -3
- package/dist/substrate/lifo/shell/terminal-stdin.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/terminal-stdin.js +60 -24
- package/dist/vfs/sqlite-vfs.d.ts +19 -0
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +106 -23
- package/dist/workspace/nimbus-workspace.d.ts +15 -0
- package/dist/workspace/nimbus-workspace.d.ts.map +1 -1
- package/dist/workspace/nimbus-workspace.js +20 -3
- package/dist/workspace/supervisor-op.d.ts +23 -0
- package/dist/workspace/supervisor-op.d.ts.map +1 -0
- package/dist/workspace/supervisor-op.js +92 -0
- package/package.json +2 -2
- package/src/_shared/byte-stream.ts +5 -4
- package/src/_shared/real-node-imports.ts +8 -0
- package/src/_shared/tarball.ts +56 -5
- package/src/_shared/vfs-write-ledger.ts +22 -16
- package/src/runtime/comment-strip.ts +42 -23
- package/src/runtime/esbuild-service.ts +566 -217
- package/src/runtime/javascript-ast.ts +40 -9
- package/src/runtime/opentui-wasm-backend.ts +19 -17
- package/src/runtime/port-registry.ts +66 -8
- package/src/shell/stdin-adapter.ts +58 -0
- package/src/shell/unix-commands.ts +1054 -50
- package/src/substrate/lifo/commands/io/dd.ts +2 -2
- package/src/substrate/lifo/commands/net/curl.ts +310 -58
- package/src/substrate/lifo/commands/system/npm.ts +58 -38
- package/src/substrate/lifo/commands/text/head.ts +2 -1
- package/src/substrate/lifo/commands/text/sed.ts +519 -109
- package/src/substrate/lifo/commands/types.ts +8 -5
- package/src/substrate/lifo/node-compat/zlib.ts +155 -26
- package/src/substrate/lifo/shell/interpreter.ts +74 -27
- package/src/substrate/lifo/shell/pipe.ts +65 -24
- package/src/substrate/lifo/shell/terminal-stdin.ts +66 -30
- package/src/vfs/sqlite-vfs.ts +104 -23
- package/src/workspace/nimbus-workspace.ts +26 -2
- package/src/workspace/supervisor-op.ts +117 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parse, type AnyNode } from 'acorn';
|
|
1
|
+
import { parse, tokenizer, tokTypes, type AnyNode, type TokenType } from 'acorn';
|
|
2
2
|
|
|
3
3
|
export type AstNode = AnyNode & Record<string, unknown>;
|
|
4
4
|
|
|
@@ -11,18 +11,49 @@ export function parseJavaScriptModule(source: string): AstNode {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function hasTopLevelModuleSyntax(source: string): boolean {
|
|
14
|
-
let ast: AstNode;
|
|
15
14
|
try {
|
|
16
|
-
|
|
15
|
+
const tokens = tokenizer(source, {
|
|
16
|
+
ecmaVersion: 'latest',
|
|
17
|
+
sourceType: 'module',
|
|
18
|
+
allowHashBang: true,
|
|
19
|
+
});
|
|
20
|
+
let braceDepth = 0;
|
|
21
|
+
let parenDepth = 0;
|
|
22
|
+
let bracketDepth = 0;
|
|
23
|
+
let previous: TokenType | undefined;
|
|
24
|
+
|
|
25
|
+
const updateDepth = (type: TokenType): void => {
|
|
26
|
+
if (type === tokTypes.braceL || type === tokTypes.dollarBraceL) braceDepth++;
|
|
27
|
+
else if (type === tokTypes.braceR) braceDepth = Math.max(0, braceDepth - 1);
|
|
28
|
+
else if (type === tokTypes.parenL) parenDepth++;
|
|
29
|
+
else if (type === tokTypes.parenR) parenDepth = Math.max(0, parenDepth - 1);
|
|
30
|
+
else if (type === tokTypes.bracketL) bracketDepth++;
|
|
31
|
+
else if (type === tokTypes.bracketR) bracketDepth = Math.max(0, bracketDepth - 1);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
while (true) {
|
|
35
|
+
const token = tokens.getToken();
|
|
36
|
+
const type = token.type;
|
|
37
|
+
if (type === tokTypes.eof) return false;
|
|
38
|
+
const topLevel = braceDepth === 0 && parenDepth === 0 && bracketDepth === 0;
|
|
39
|
+
|
|
40
|
+
if (topLevel && previous !== tokTypes.dot) {
|
|
41
|
+
if (type === tokTypes._export) return true;
|
|
42
|
+
if (type === tokTypes._import) {
|
|
43
|
+
const next = tokens.getToken();
|
|
44
|
+
if (next.type !== tokTypes.parenL && next.type !== tokTypes.dot) return true;
|
|
45
|
+
updateDepth(next.type);
|
|
46
|
+
previous = next.type;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
updateDepth(type);
|
|
52
|
+
previous = type;
|
|
53
|
+
}
|
|
17
54
|
} catch {
|
|
18
55
|
return false;
|
|
19
56
|
}
|
|
20
|
-
return nodeList(ast, 'body').some((node) =>
|
|
21
|
-
node.type === 'ImportDeclaration' ||
|
|
22
|
-
node.type === 'ExportNamedDeclaration' ||
|
|
23
|
-
node.type === 'ExportDefaultDeclaration' ||
|
|
24
|
-
node.type === 'ExportAllDeclaration'
|
|
25
|
-
);
|
|
26
57
|
}
|
|
27
58
|
|
|
28
59
|
export function nodeList(node: AstNode, key: string): AstNode[] {
|
|
@@ -211,18 +211,17 @@ export class OpenTUIWasmBackend {
|
|
|
211
211
|
* semantics), so each `dlopen`'d symbol call frees what was allocated before
|
|
212
212
|
* it. Without this a per-frame `rgbaPtr(color)` would leak linear memory.
|
|
213
213
|
*
|
|
214
|
-
* `view` is
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* `
|
|
219
|
-
*
|
|
220
|
-
* so the claimed-scratch release copies these back before freeing.
|
|
214
|
+
* `view` is a writable byte view over the caller's original storage.
|
|
215
|
+
* Native `ptr(value)` exposes live memory for both ArrayBuffer and typed-array
|
|
216
|
+
* inputs, so every transient allocation copies back after the symbol call.
|
|
217
|
+
* OpenTUI's struct getters use bare ArrayBuffers (`LogicalCursorStruct`,
|
|
218
|
+
* `VisualCursorStruct`, stats, terminal capabilities), while span-feed drains
|
|
219
|
+
* use typed arrays. Both forms must observe Zig's writes.
|
|
221
220
|
*/
|
|
222
221
|
readonly #pendingPtrScratch: Array<{
|
|
223
222
|
offset: number;
|
|
224
223
|
size: number;
|
|
225
|
-
view: ArrayBufferView
|
|
224
|
+
view: ArrayBufferView;
|
|
226
225
|
}> = [];
|
|
227
226
|
|
|
228
227
|
/**
|
|
@@ -336,12 +335,10 @@ export class OpenTUIWasmBackend {
|
|
|
336
335
|
// allocation as transient scratch that the next symbol call frees (see
|
|
337
336
|
// `#bindSymbol`). This keeps a per-frame `rgbaPtr(color)` loop leak-free.
|
|
338
337
|
//
|
|
339
|
-
//
|
|
340
|
-
// a live pointer into the
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
// and every FFIRenderLib `ptr(outBuffer)`/`ptr(cursorBuffer)`/`ptr(statsBuffer)`
|
|
344
|
-
// getter depend on this). Read-only ArrayBuffers carry a null view (copy-in only).
|
|
338
|
+
// Every ArrayBuffer and ArrayBufferView is potentially an OUT-buffer:
|
|
339
|
+
// native `ptr(value)` is a live pointer into the same storage. Copy the
|
|
340
|
+
// arena bytes back after the call so bare struct buffers and typed-array
|
|
341
|
+
// windows have identical semantics.
|
|
345
342
|
ptr(value: ArrayBuffer | ArrayBufferView): OpenTUIPointer {
|
|
346
343
|
const { bytes, byteOffset, byteLength } = viewBytes(value);
|
|
347
344
|
const size = byteLength === 0 ? ARENA_ALIGN : byteLength;
|
|
@@ -352,7 +349,7 @@ export class OpenTUIWasmBackend {
|
|
|
352
349
|
this.#pendingPtrScratch.push({
|
|
353
350
|
offset,
|
|
354
351
|
size,
|
|
355
|
-
view: ArrayBuffer.isView(value) ? value :
|
|
352
|
+
view: ArrayBuffer.isView(value) ? value : new Uint8Array(value),
|
|
356
353
|
});
|
|
357
354
|
return offset;
|
|
358
355
|
}
|
|
@@ -513,8 +510,13 @@ export class OpenTUIWasmBackend {
|
|
|
513
510
|
for (const s of scratch) scratchBytes += s.size;
|
|
514
511
|
diag.rec(name, this.#exports.memory.buffer.byteLength - memBefore, marshaled, scratchBytes);
|
|
515
512
|
}
|
|
516
|
-
if (returns === 'u64'
|
|
517
|
-
|
|
513
|
+
if (returns === 'u64') {
|
|
514
|
+
const value = typeof result === 'bigint' ? result : BigInt(result as number);
|
|
515
|
+
return BigInt.asUintN(64, value);
|
|
516
|
+
}
|
|
517
|
+
if (returns === 'i64') {
|
|
518
|
+
const value = typeof result === 'bigint' ? result : BigInt(result as number);
|
|
519
|
+
return BigInt.asIntN(64, value);
|
|
518
520
|
}
|
|
519
521
|
return result;
|
|
520
522
|
};
|
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
* `request.method` and application headers mirror the outer request,
|
|
24
24
|
* Nimbus credentials/internal headers are removed, and
|
|
25
25
|
* `request.body` is a ReadableStream (or null for GET/HEAD) that
|
|
26
|
-
* the facet can consume once. The returned Response is
|
|
27
|
-
*
|
|
26
|
+
* the facet can consume once. The returned Response is streamed
|
|
27
|
+
* straight back, with one normalization: the hop speaks identity,
|
|
28
|
+
* so a compressed body is decoded here. See `decodeContentCoding`.
|
|
28
29
|
*/
|
|
29
30
|
|
|
30
31
|
import { sanitizeUntrustedHeaders } from '../_shared/untrusted-request.js';
|
|
@@ -48,7 +49,60 @@ export interface PortEntry {
|
|
|
48
49
|
capability: string;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Content codings this hop can undo. `DecompressionStream` decodes exactly
|
|
54
|
+
* these; brotli and zstd have no decoder in the runtime, so a body in one of
|
|
55
|
+
* those cannot be repaired here.
|
|
56
|
+
*/
|
|
57
|
+
const DECODABLE_CONTENT_CODINGS = new Map<string, 'gzip' | 'deflate'>([
|
|
58
|
+
['gzip', 'gzip'],
|
|
59
|
+
['x-gzip', 'gzip'],
|
|
60
|
+
['deflate', 'deflate'],
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Hand back a port target's response with its bytes and its headers in
|
|
65
|
+
* agreement.
|
|
66
|
+
*
|
|
67
|
+
* A `Response` a facet builds always holds an identity body. The runtime
|
|
68
|
+
* treats the bytes a `Response` is constructed from as unencoded and runs its
|
|
69
|
+
* own content negotiation on the way out: it drops a `Content-Encoding` the
|
|
70
|
+
* client did not ask for, and compresses again when the client did. Either
|
|
71
|
+
* way a guest server's `Content-Encoding` is a label with nothing behind it,
|
|
72
|
+
* and the browser renders compressed bytes as text.
|
|
73
|
+
*
|
|
74
|
+
* The forwarded request asks the target for `identity`, so this decode covers
|
|
75
|
+
* the server that compresses whatever the client said. A coding with no
|
|
76
|
+
* decoder answers 502: an honest failure beats a page of mojibake.
|
|
77
|
+
*/
|
|
78
|
+
function decodeContentCoding(response: Response, port: number): Response {
|
|
79
|
+
const coding = response.headers.get('Content-Encoding')?.trim().toLowerCase();
|
|
80
|
+
if (!coding || coding === 'identity' || response.body === null) return response;
|
|
81
|
+
|
|
82
|
+
const format = DECODABLE_CONTENT_CODINGS.get(coding);
|
|
83
|
+
if (!format) {
|
|
84
|
+
void response.body.cancel().catch(() => {});
|
|
85
|
+
return new Response(
|
|
86
|
+
JSON.stringify({
|
|
87
|
+
error: `port proxy: target answered Content-Encoding "${coding}", which this hop cannot decode`,
|
|
88
|
+
port,
|
|
89
|
+
}),
|
|
90
|
+
{ status: 502, headers: { 'Content-Type': 'application/json' } },
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const headers = new Headers(response.headers);
|
|
95
|
+
headers.delete('Content-Encoding');
|
|
96
|
+
// The decoded body has a different length; the Response re-derives it.
|
|
97
|
+
headers.delete('Content-Length');
|
|
98
|
+
return new Response(response.body.pipeThrough(new DecompressionStream(format)), {
|
|
99
|
+
status: response.status,
|
|
100
|
+
statusText: response.statusText,
|
|
101
|
+
headers,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function createPortCapability(): string {
|
|
52
106
|
const bytes = crypto.getRandomValues(new Uint8Array(12));
|
|
53
107
|
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
54
108
|
}
|
|
@@ -262,6 +316,10 @@ export class PortRegistry {
|
|
|
262
316
|
if (authorization) headers.set('authorization', authorization);
|
|
263
317
|
|
|
264
318
|
headers.set('X-Nimbus-Port', String(port));
|
|
319
|
+
// A Response cannot carry an encoded body across this hop, so the
|
|
320
|
+
// target is asked for the one coding that survives it. See
|
|
321
|
+
// `decodeContentCoding` for the server that compresses anyway.
|
|
322
|
+
headers.set('Accept-Encoding', 'identity');
|
|
265
323
|
// `duplex: 'half'` is required by workerd when body is a
|
|
266
324
|
// ReadableStream — otherwise `new Request(…)` throws. It's not
|
|
267
325
|
// part of the published @cloudflare/workers-types RequestInit,
|
|
@@ -305,12 +363,12 @@ export class PortRegistry {
|
|
|
305
363
|
);
|
|
306
364
|
}
|
|
307
365
|
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
// inject Access-Control-Allow-Origin — a port proxy
|
|
311
|
-
// whatever CORS policy the user's HTTP server chose (audit C3
|
|
366
|
+
// Stream the facet's Response back. Status, status-text, and every
|
|
367
|
+
// header pass through; only a content coding the hop cannot carry is
|
|
368
|
+
// undone. We do NOT inject Access-Control-Allow-Origin — a port proxy
|
|
369
|
+
// forwards whatever CORS policy the user's HTTP server chose (audit C3
|
|
312
370
|
// discourages gratuitous wildcards on non-static routes).
|
|
313
|
-
return response;
|
|
371
|
+
return decodeContentCoding(response, port);
|
|
314
372
|
} catch (error: unknown) {
|
|
315
373
|
// Server-side triage — users see only the 502 body, operators
|
|
316
374
|
// see the full error + stack in Worker logs.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { CommandInputStream } from '../substrate/lifo/commands/types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One consuming source behind the full reader contract, byte-accurate:
|
|
5
|
+
* the text is encoded once and every offset is a byte offset, so
|
|
6
|
+
* readBytes(1) over `é` yields exactly one UTF-8 byte per call and mixed
|
|
7
|
+
* text/byte consumption never diverges. Worker pure-builtin adapters hold
|
|
8
|
+
* stdin as a plain string; without this they can only offer readAll, and
|
|
9
|
+
* streaming commands see an empty pipe.
|
|
10
|
+
*/
|
|
11
|
+
export function staticStdinReader(text: string): CommandInputStream {
|
|
12
|
+
const bytes = new TextEncoder().encode(text);
|
|
13
|
+
const decoder = new TextDecoder('utf-8');
|
|
14
|
+
let offset = 0;
|
|
15
|
+
|
|
16
|
+
const pull = (max: number): Uint8Array | null => {
|
|
17
|
+
if (offset >= bytes.length) return null;
|
|
18
|
+
const end = Math.min(offset + max, bytes.length);
|
|
19
|
+
const chunk = bytes.subarray(offset, end);
|
|
20
|
+
offset = end;
|
|
21
|
+
return chunk;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
readBytes: async (maxLength: number) => {
|
|
26
|
+
if (maxLength <= 0) return new Uint8Array(0);
|
|
27
|
+
return pull(maxLength);
|
|
28
|
+
},
|
|
29
|
+
read: async () => {
|
|
30
|
+
while (offset < bytes.length) {
|
|
31
|
+
const end = Math.min(offset + 65536, bytes.length);
|
|
32
|
+
const text2 = decoder.decode(bytes.subarray(offset, end), { stream: true });
|
|
33
|
+
offset = end;
|
|
34
|
+
if (text2.length > 0) return text2;
|
|
35
|
+
}
|
|
36
|
+
const tail = decoder.decode();
|
|
37
|
+
return tail.length > 0 ? tail : null;
|
|
38
|
+
},
|
|
39
|
+
readAll: async () => {
|
|
40
|
+
if (offset >= bytes.length) return '';
|
|
41
|
+
const out = decoder.decode(bytes.subarray(offset));
|
|
42
|
+
offset = bytes.length;
|
|
43
|
+
return out;
|
|
44
|
+
},
|
|
45
|
+
readLine: async () => {
|
|
46
|
+
if (offset >= bytes.length) return null;
|
|
47
|
+
const newline = bytes.indexOf(0x0a, offset);
|
|
48
|
+
if (newline === -1) {
|
|
49
|
+
const line = decoder.decode(bytes.subarray(offset));
|
|
50
|
+
offset = bytes.length;
|
|
51
|
+
return line;
|
|
52
|
+
}
|
|
53
|
+
const line = decoder.decode(bytes.subarray(offset, newline));
|
|
54
|
+
offset = newline + 1;
|
|
55
|
+
return line;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|