@volter/tabnode 0.5.5 → 0.5.7
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/dist/assets/{runtime-worker-iInsmIAS.js → runtime-worker-CZQ5Vxoj.js} +40 -12
- package/dist/index.mjs +54 -16
- package/dist/native-stream-owner.d.ts +8 -1
- package/package.json +1 -1
- package/src/index.ts +4 -1
- package/src/native-stream-owner.ts +15 -2
- package/src/node-lib/binding/pipe_wrap.ts +5 -4
- package/src/runtime.ts +33 -10
- package/src/shims/child_process.ts +23 -2
|
@@ -7817,7 +7817,7 @@ class Pipe extends LibuvStreamWrap {
|
|
|
7817
7817
|
if (native) return native.write(req, [bytes2], handle);
|
|
7818
7818
|
if (handle === void 0 || handle === null) return super.dispatchWrite(req, bytes2);
|
|
7819
7819
|
const sent = handle;
|
|
7820
|
-
const given = sent.
|
|
7820
|
+
const given = sent.listening === true ? sent : sent.duplicate();
|
|
7821
7821
|
__adoptHandle(given, ownerOf(this.peer ?? this));
|
|
7822
7822
|
const status = super.dispatchWrite(req, bytes2, given);
|
|
7823
7823
|
if (status !== 0 && given !== sent) given.close();
|
|
@@ -70769,12 +70769,27 @@ function programExists(file, cwd, env) {
|
|
|
70769
70769
|
const name = __substrateProgramName(file);
|
|
70770
70770
|
const bare = name.includes("/") ? name.slice(name.lastIndexOf("/") + 1) : name;
|
|
70771
70771
|
if (shellCommandNames().has(bare)) return true;
|
|
70772
|
-
if (name.includes("/"))
|
|
70772
|
+
if (name.includes("/")) {
|
|
70773
|
+
const path2 = resolve(cwd ?? "/", name);
|
|
70774
|
+
return existsInTree(path2) && !isRegisteredProgramStub(path2);
|
|
70775
|
+
}
|
|
70773
70776
|
for (const dir of (env.PATH ?? "/usr/local/bin:/usr/bin:/bin:/node_modules/.bin").split(":")) {
|
|
70774
|
-
|
|
70777
|
+
const path2 = `${dir}/${name}`.replace(/\/+/gu, "/");
|
|
70778
|
+
if (dir.length > 0 && existsInTree(path2) && !isRegisteredProgramStub(path2)) return true;
|
|
70775
70779
|
}
|
|
70776
70780
|
return false;
|
|
70777
70781
|
}
|
|
70782
|
+
const REGISTERED_PROGRAM_STUB = /^#!\/bin\/sh\n# [\w.+-]+: runtime-registered program\n/u;
|
|
70783
|
+
function isRegisteredProgramStub(path2) {
|
|
70784
|
+
if (!currentVfs$1) return false;
|
|
70785
|
+
try {
|
|
70786
|
+
const stat = currentVfs$1.statSync(path2);
|
|
70787
|
+
if (!stat.isFile() || stat.size > 256) return false;
|
|
70788
|
+
return REGISTERED_PROGRAM_STUB.test(String(currentVfs$1.readFileSync(path2, "utf8")));
|
|
70789
|
+
} catch {
|
|
70790
|
+
return false;
|
|
70791
|
+
}
|
|
70792
|
+
}
|
|
70778
70793
|
let __nextChildRun = 1;
|
|
70779
70794
|
function startChildRun(request) {
|
|
70780
70795
|
var _a3;
|
|
@@ -74067,7 +74082,7 @@ function transformDynamicImportsRegex(code2) {
|
|
|
74067
74082
|
return code2.replace(new RegExp("(?<![.$\\w#])import\\s*\\(", "g"), "__dynamicImport(");
|
|
74068
74083
|
}
|
|
74069
74084
|
function __substrateRewriteDynamicImportsInScript(code2) {
|
|
74070
|
-
if (!new RegExp("(?<![.$\\w#])import\\s*\\(").test(code2)) return code2;
|
|
74085
|
+
if (!new RegExp("(?<![.$\\w#])(?:import|eval)\\s*\\(").test(code2)) return code2;
|
|
74071
74086
|
let ast;
|
|
74072
74087
|
try {
|
|
74073
74088
|
ast = parse3(code2, { ecmaVersion: "latest", sourceType: "script", allowReturnOutsideFunction: true, allowHashBang: true, allowAwaitOutsideFunction: true });
|
|
@@ -74076,14 +74091,30 @@ function __substrateRewriteDynamicImportsInScript(code2) {
|
|
|
74076
74091
|
}
|
|
74077
74092
|
const replacements = [];
|
|
74078
74093
|
walkAst(ast, (node) => {
|
|
74079
|
-
if (node.type === "ImportExpression") replacements.push([node.start, node.start + 6]);
|
|
74094
|
+
if (node.type === "ImportExpression") replacements.push([node.start, node.start + 6, "__dynamicImport"]);
|
|
74095
|
+
collectDirectEval(node, replacements);
|
|
74080
74096
|
});
|
|
74081
74097
|
if (replacements.length === 0) return code2;
|
|
74082
|
-
|
|
74098
|
+
return applyReplacements(code2, replacements);
|
|
74099
|
+
}
|
|
74100
|
+
function collectDirectEval(node, replacements) {
|
|
74101
|
+
var _a3, _b3;
|
|
74102
|
+
if (node.type !== "CallExpression" || node.optional || ((_a3 = node.callee) == null ? void 0 : _a3.type) !== "Identifier" || node.callee.name !== "eval") return;
|
|
74103
|
+
const source = (_b3 = node.arguments) == null ? void 0 : _b3[0];
|
|
74104
|
+
if (!source || source.type === "SpreadElement") return;
|
|
74105
|
+
replacements.push([source.start, source.start, "__substrateEvalSource("]);
|
|
74106
|
+
replacements.push([source.end, source.end, ")"]);
|
|
74107
|
+
}
|
|
74108
|
+
function applyReplacements(code2, replacements) {
|
|
74109
|
+
replacements.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
|
|
74083
74110
|
let out = code2;
|
|
74084
|
-
for (const [start, end] of replacements) out = out.slice(0, start) +
|
|
74111
|
+
for (const [start, end, text] of replacements) out = out.slice(0, start) + text + out.slice(end);
|
|
74085
74112
|
return out;
|
|
74086
74113
|
}
|
|
74114
|
+
Object.defineProperty(globalThis, "__substrateEvalSource", {
|
|
74115
|
+
configurable: true,
|
|
74116
|
+
value: (source) => typeof source === "string" ? __substrateRewriteDynamicImportsInScript(source) : source
|
|
74117
|
+
});
|
|
74087
74118
|
function transformEsmToCjs(code2, filename) {
|
|
74088
74119
|
const maybeEsm = __substrateHasEsmSyntax(code2);
|
|
74089
74120
|
if (!maybeEsm) return __substrateRewriteDynamicImportsInScript(code2);
|
|
@@ -74107,17 +74138,14 @@ function transformEsmToCjsAst(code2, filename) {
|
|
|
74107
74138
|
if (node.type === "ImportExpression") {
|
|
74108
74139
|
deepReplacements.push([node.start, node.start + 6, "__dynamicImport"]);
|
|
74109
74140
|
}
|
|
74141
|
+
collectDirectEval(node, deepReplacements);
|
|
74110
74142
|
});
|
|
74111
74143
|
const hasImportDecl = ast.body.some((n8) => n8.type === "ImportDeclaration");
|
|
74112
74144
|
const hasExportDecl = ast.body.some((n8) => {
|
|
74113
74145
|
var _a3;
|
|
74114
74146
|
return (_a3 = n8.type) == null ? void 0 : _a3.startsWith("Export");
|
|
74115
74147
|
});
|
|
74116
|
-
let transformed = code2;
|
|
74117
|
-
deepReplacements.sort((a, b) => b[0] - a[0]);
|
|
74118
|
-
for (const [start, end, replacement] of deepReplacements) {
|
|
74119
|
-
transformed = transformed.slice(0, start) + replacement + transformed.slice(end);
|
|
74120
|
-
}
|
|
74148
|
+
let transformed = applyReplacements(code2, deepReplacements);
|
|
74121
74149
|
if (hasImportDecl || hasExportDecl) {
|
|
74122
74150
|
transformed = transformEsmToCjsSimple(transformed);
|
|
74123
74151
|
if (hasExportDecl) {
|
package/dist/index.mjs
CHANGED
|
@@ -7980,7 +7980,7 @@ class Pipe extends LibuvStreamWrap {
|
|
|
7980
7980
|
if (native) return native.write(req, [bytes2], handle);
|
|
7981
7981
|
if (handle === void 0 || handle === null) return super.dispatchWrite(req, bytes2);
|
|
7982
7982
|
const sent = handle;
|
|
7983
|
-
const given = sent.
|
|
7983
|
+
const given = sent.listening === true ? sent : sent.duplicate();
|
|
7984
7984
|
__adoptHandle(given, ownerOf(this.peer ?? this));
|
|
7985
7985
|
const status = super.dispatchWrite(req, bytes2, given);
|
|
7986
7986
|
if (status !== 0 && given !== sent) given.close();
|
|
@@ -30461,12 +30461,27 @@ function programExists(file, cwd, env) {
|
|
|
30461
30461
|
const name = __substrateProgramName(file);
|
|
30462
30462
|
const bare = name.includes("/") ? name.slice(name.lastIndexOf("/") + 1) : name;
|
|
30463
30463
|
if (shellCommandNames().has(bare)) return true;
|
|
30464
|
-
if (name.includes("/"))
|
|
30464
|
+
if (name.includes("/")) {
|
|
30465
|
+
const path2 = resolve(cwd ?? "/", name);
|
|
30466
|
+
return existsInTree(path2) && !isRegisteredProgramStub(path2);
|
|
30467
|
+
}
|
|
30465
30468
|
for (const dir of (env.PATH ?? "/usr/local/bin:/usr/bin:/bin:/node_modules/.bin").split(":")) {
|
|
30466
|
-
|
|
30469
|
+
const path2 = `${dir}/${name}`.replace(/\/+/gu, "/");
|
|
30470
|
+
if (dir.length > 0 && existsInTree(path2) && !isRegisteredProgramStub(path2)) return true;
|
|
30467
30471
|
}
|
|
30468
30472
|
return false;
|
|
30469
30473
|
}
|
|
30474
|
+
const REGISTERED_PROGRAM_STUB = /^#!\/bin\/sh\n# [\w.+-]+: runtime-registered program\n/u;
|
|
30475
|
+
function isRegisteredProgramStub(path2) {
|
|
30476
|
+
if (!currentVfs$1) return false;
|
|
30477
|
+
try {
|
|
30478
|
+
const stat = currentVfs$1.statSync(path2);
|
|
30479
|
+
if (!stat.isFile() || stat.size > 256) return false;
|
|
30480
|
+
return REGISTERED_PROGRAM_STUB.test(String(currentVfs$1.readFileSync(path2, "utf8")));
|
|
30481
|
+
} catch {
|
|
30482
|
+
return false;
|
|
30483
|
+
}
|
|
30484
|
+
}
|
|
30470
30485
|
let __nextChildRun = 1;
|
|
30471
30486
|
function startChildRun(request) {
|
|
30472
30487
|
var _a2;
|
|
@@ -33760,7 +33775,7 @@ function transformDynamicImportsRegex(code2) {
|
|
|
33760
33775
|
return code2.replace(new RegExp("(?<![.$\\w#])import\\s*\\(", "g"), "__dynamicImport(");
|
|
33761
33776
|
}
|
|
33762
33777
|
function __substrateRewriteDynamicImportsInScript(code2) {
|
|
33763
|
-
if (!new RegExp("(?<![.$\\w#])import\\s*\\(").test(code2)) return code2;
|
|
33778
|
+
if (!new RegExp("(?<![.$\\w#])(?:import|eval)\\s*\\(").test(code2)) return code2;
|
|
33764
33779
|
let ast;
|
|
33765
33780
|
try {
|
|
33766
33781
|
ast = parse3(code2, { ecmaVersion: "latest", sourceType: "script", allowReturnOutsideFunction: true, allowHashBang: true, allowAwaitOutsideFunction: true });
|
|
@@ -33769,14 +33784,30 @@ function __substrateRewriteDynamicImportsInScript(code2) {
|
|
|
33769
33784
|
}
|
|
33770
33785
|
const replacements = [];
|
|
33771
33786
|
walkAst(ast, (node) => {
|
|
33772
|
-
if (node.type === "ImportExpression") replacements.push([node.start, node.start + 6]);
|
|
33787
|
+
if (node.type === "ImportExpression") replacements.push([node.start, node.start + 6, "__dynamicImport"]);
|
|
33788
|
+
collectDirectEval(node, replacements);
|
|
33773
33789
|
});
|
|
33774
33790
|
if (replacements.length === 0) return code2;
|
|
33775
|
-
|
|
33791
|
+
return applyReplacements(code2, replacements);
|
|
33792
|
+
}
|
|
33793
|
+
function collectDirectEval(node, replacements) {
|
|
33794
|
+
var _a2, _b2;
|
|
33795
|
+
if (node.type !== "CallExpression" || node.optional || ((_a2 = node.callee) == null ? void 0 : _a2.type) !== "Identifier" || node.callee.name !== "eval") return;
|
|
33796
|
+
const source = (_b2 = node.arguments) == null ? void 0 : _b2[0];
|
|
33797
|
+
if (!source || source.type === "SpreadElement") return;
|
|
33798
|
+
replacements.push([source.start, source.start, "__substrateEvalSource("]);
|
|
33799
|
+
replacements.push([source.end, source.end, ")"]);
|
|
33800
|
+
}
|
|
33801
|
+
function applyReplacements(code2, replacements) {
|
|
33802
|
+
replacements.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
|
|
33776
33803
|
let out = code2;
|
|
33777
|
-
for (const [start, end] of replacements) out = out.slice(0, start) +
|
|
33804
|
+
for (const [start, end, text] of replacements) out = out.slice(0, start) + text + out.slice(end);
|
|
33778
33805
|
return out;
|
|
33779
33806
|
}
|
|
33807
|
+
Object.defineProperty(globalThis, "__substrateEvalSource", {
|
|
33808
|
+
configurable: true,
|
|
33809
|
+
value: (source) => typeof source === "string" ? __substrateRewriteDynamicImportsInScript(source) : source
|
|
33810
|
+
});
|
|
33780
33811
|
function transformEsmToCjs(code2, filename) {
|
|
33781
33812
|
const maybeEsm = __substrateHasEsmSyntax(code2);
|
|
33782
33813
|
if (!maybeEsm) return __substrateRewriteDynamicImportsInScript(code2);
|
|
@@ -33800,17 +33831,14 @@ function transformEsmToCjsAst(code2, filename) {
|
|
|
33800
33831
|
if (node.type === "ImportExpression") {
|
|
33801
33832
|
deepReplacements.push([node.start, node.start + 6, "__dynamicImport"]);
|
|
33802
33833
|
}
|
|
33834
|
+
collectDirectEval(node, deepReplacements);
|
|
33803
33835
|
});
|
|
33804
33836
|
const hasImportDecl = ast.body.some((n) => n.type === "ImportDeclaration");
|
|
33805
33837
|
const hasExportDecl = ast.body.some((n) => {
|
|
33806
33838
|
var _a2;
|
|
33807
33839
|
return (_a2 = n.type) == null ? void 0 : _a2.startsWith("Export");
|
|
33808
33840
|
});
|
|
33809
|
-
let transformed = code2;
|
|
33810
|
-
deepReplacements.sort((a, b) => b[0] - a[0]);
|
|
33811
|
-
for (const [start, end, replacement] of deepReplacements) {
|
|
33812
|
-
transformed = transformed.slice(0, start) + replacement + transformed.slice(end);
|
|
33813
|
-
}
|
|
33841
|
+
let transformed = applyReplacements(code2, deepReplacements);
|
|
33814
33842
|
if (hasImportDecl || hasExportDecl) {
|
|
33815
33843
|
transformed = transformEsmToCjsSimple(transformed);
|
|
33816
33844
|
if (hasExportDecl) {
|
|
@@ -35655,7 +35683,7 @@ class WorkerRuntime {
|
|
|
35655
35683
|
this.worker = new Worker(
|
|
35656
35684
|
new URL(
|
|
35657
35685
|
/* @vite-ignore */
|
|
35658
|
-
"/assets/runtime-worker-
|
|
35686
|
+
"/assets/runtime-worker-CZQ5Vxoj.js",
|
|
35659
35687
|
import.meta.url
|
|
35660
35688
|
),
|
|
35661
35689
|
{ type: "module" }
|
|
@@ -38023,7 +38051,11 @@ function resetServerBridge() {
|
|
|
38023
38051
|
}
|
|
38024
38052
|
const scopes = /* @__PURE__ */ new WeakMap();
|
|
38025
38053
|
class NativeStreamScope {
|
|
38026
|
-
|
|
38054
|
+
/**
|
|
38055
|
+
* `ownerPid` names the process whose worker this scope serves: a listener
|
|
38056
|
+
* it opens is that process's socket, as the container's /proc shows it.
|
|
38057
|
+
*/
|
|
38058
|
+
constructor(limits, emit2, ownerPid) {
|
|
38027
38059
|
__publicField(this, "entries", /* @__PURE__ */ new Map());
|
|
38028
38060
|
__publicField(this, "descriptors", /* @__PURE__ */ new Map());
|
|
38029
38061
|
__publicField(this, "writes", /* @__PURE__ */ new Map());
|
|
@@ -38033,6 +38065,7 @@ class NativeStreamScope {
|
|
|
38033
38065
|
__publicField(this, "disposed", false);
|
|
38034
38066
|
__publicField(this, "limits");
|
|
38035
38067
|
this.emit = emit2;
|
|
38068
|
+
this.ownerPid = ownerPid;
|
|
38036
38069
|
if ([limits.maxHandles, limits.maxReadChunkBytes, limits.maxQueuedBytes, limits.maxQueuedHandles, limits.maxPendingWriteBytes].some((value) => !Number.isSafeInteger(value) || value < 1) || limits.maxReadChunkBytes > 2147483647 || limits.maxReadChunkBytes > limits.maxQueuedBytes || limits.maxReadChunkBytes > limits.maxPendingWriteBytes) throw new Error("Invalid native stream resource limits.");
|
|
38037
38070
|
this.limits = Object.freeze({ ...limits });
|
|
38038
38071
|
}
|
|
@@ -38222,7 +38255,7 @@ class NativeStreamScope {
|
|
|
38222
38255
|
if (bytes2.byteLength > this.limits.maxReadChunkBytes || this.pendingWriteBytes + bytes2.byteLength > this.limits.maxPendingWriteBytes) return Promise.resolve(UV_ENOBUFS);
|
|
38223
38256
|
const sent = sentId === void 0 ? void 0 : (_b2 = this.entry(sentId)) == null ? void 0 : _b2.handle;
|
|
38224
38257
|
if (sentId !== void 0 && !sent) return Promise.resolve(UV_EBADF);
|
|
38225
|
-
if (sent && (!(handle instanceof Pipe) ||
|
|
38258
|
+
if (sent && (!(handle instanceof Pipe) || sent.listening || bytes2.byteLength === 0)) return Promise.resolve(UV_ENOTSUP);
|
|
38226
38259
|
const owned = new Uint8Array(bytes2.byteLength);
|
|
38227
38260
|
owned.set(bytes2);
|
|
38228
38261
|
const retained = sent == null ? void 0 : sent.duplicate();
|
|
@@ -38317,6 +38350,10 @@ class NativeStreamScope {
|
|
|
38317
38350
|
if (failures.length) throw new AggregateError(failures, "Native stream scope cleanup failed.");
|
|
38318
38351
|
}
|
|
38319
38352
|
}
|
|
38353
|
+
function nativeStreamOwnerPid(handle) {
|
|
38354
|
+
var _a2, _b2;
|
|
38355
|
+
return (_b2 = (_a2 = scopes.get(handle)) == null ? void 0 : _a2.ownerPid) == null ? void 0 : _b2.call(_a2);
|
|
38356
|
+
}
|
|
38320
38357
|
let __nextRunName = 1;
|
|
38321
38358
|
function createContainer(options) {
|
|
38322
38359
|
const vfs2 = options && options.vfs || new VirtualFS();
|
|
@@ -38374,7 +38411,8 @@ function createContainer(options) {
|
|
|
38374
38411
|
portPid: (port) => {
|
|
38375
38412
|
var _a2;
|
|
38376
38413
|
const listener = listenerOnPort(port);
|
|
38377
|
-
|
|
38414
|
+
if (!listener) return void 0;
|
|
38415
|
+
return ((_a2 = runPid(ownerOf(listener))) == null ? void 0 : _a2.pid) ?? nativeStreamOwnerPid(listener);
|
|
38378
38416
|
},
|
|
38379
38417
|
/**
|
|
38380
38418
|
* Deliver a signal to the named run as another process's `kill(pid)` does:
|
|
@@ -94,6 +94,7 @@ export interface NativeStreamTransport {
|
|
|
94
94
|
*/
|
|
95
95
|
export declare class NativeStreamScope {
|
|
96
96
|
private readonly emit;
|
|
97
|
+
readonly ownerPid?: (() => number | undefined) | undefined;
|
|
97
98
|
private readonly entries;
|
|
98
99
|
private readonly descriptors;
|
|
99
100
|
private readonly writes;
|
|
@@ -102,7 +103,11 @@ export declare class NativeStreamScope {
|
|
|
102
103
|
private pendingWriteBytes;
|
|
103
104
|
private disposed;
|
|
104
105
|
private readonly limits;
|
|
105
|
-
|
|
106
|
+
/**
|
|
107
|
+
* `ownerPid` names the process whose worker this scope serves: a listener
|
|
108
|
+
* it opens is that process's socket, as the container's /proc shows it.
|
|
109
|
+
*/
|
|
110
|
+
constructor(limits: NativeStreamLimits, emit: (event: NativeStreamEvent) => void, ownerPid?: (() => number | undefined) | undefined);
|
|
106
111
|
private entry;
|
|
107
112
|
private publish;
|
|
108
113
|
private claim;
|
|
@@ -119,3 +124,5 @@ export declare class NativeStreamScope {
|
|
|
119
124
|
private close;
|
|
120
125
|
dispose(): void;
|
|
121
126
|
}
|
|
127
|
+
/** The process a handle opened for another worker's process belongs to, where a scope holds it. */
|
|
128
|
+
export declare function nativeStreamOwnerPid(handle: object): number | undefined;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ import { runCommand, registerRunStreams, releaseRunStreams, sendStdin } from './
|
|
|
56
56
|
import { Server as NetServer, __releaseOwnedHandles, type Socket as NetSocket } from './node-lib/net-module';
|
|
57
57
|
import { __adoptHandle, ownerOf, type OwnedHandle } from './node-lib/binding/handles';
|
|
58
58
|
import { listenerOnPort } from './node-lib/binding/tcp_wrap';
|
|
59
|
+
import { nativeStreamOwnerPid } from './native-stream-owner';
|
|
59
60
|
|
|
60
61
|
import { __currentProcessToken, __runFor, __signalOwnedProcess, __stopOwnedProcess, runPid, processByPid } from './process-tokens';
|
|
61
62
|
export { runPid, processByPid };
|
|
@@ -215,7 +216,9 @@ export function createContainer(options?: ContainerOptions): {
|
|
|
215
216
|
processPorts: (token: string): number[] => __ownedServerPorts(token),
|
|
216
217
|
portPid: (port: number): number | undefined => {
|
|
217
218
|
const listener = listenerOnPort(port);
|
|
218
|
-
|
|
219
|
+
if (!listener) return undefined;
|
|
220
|
+
// a guest's own listener, or one this engine holds for another worker's process
|
|
221
|
+
return runPid(ownerOf(listener as unknown as OwnedHandle))?.pid ?? nativeStreamOwnerPid(listener);
|
|
219
222
|
},
|
|
220
223
|
/**
|
|
221
224
|
* Deliver a signal to the named run as another process's `kill(pid)` does:
|
|
@@ -56,7 +56,11 @@ export class NativeStreamScope {
|
|
|
56
56
|
private disposed = false;
|
|
57
57
|
private readonly limits: Readonly<NativeStreamLimits>;
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* `ownerPid` names the process whose worker this scope serves: a listener
|
|
61
|
+
* it opens is that process's socket, as the container's /proc shows it.
|
|
62
|
+
*/
|
|
63
|
+
constructor(limits: NativeStreamLimits, private readonly emit: (event: NativeStreamEvent) => void, readonly ownerPid?: () => number | undefined) {
|
|
60
64
|
if ([limits.maxHandles, limits.maxReadChunkBytes, limits.maxQueuedBytes, limits.maxQueuedHandles, limits.maxPendingWriteBytes].some(value => !Number.isSafeInteger(value) || value < 1)
|
|
61
65
|
|| limits.maxReadChunkBytes > 0x7fffffff
|
|
62
66
|
|| limits.maxReadChunkBytes > limits.maxQueuedBytes
|
|
@@ -236,7 +240,11 @@ export class NativeStreamScope {
|
|
|
236
240
|
|| this.pendingWriteBytes + bytes.byteLength > this.limits.maxPendingWriteBytes) return Promise.resolve(UV_ENOBUFS);
|
|
237
241
|
const sent = sentId === undefined ? undefined : this.entry(sentId)?.handle;
|
|
238
242
|
if (sentId !== undefined && !sent) return Promise.resolve(UV_EBADF);
|
|
239
|
-
|
|
243
|
+
// A connection crosses whether or not its other end is still there: Node
|
|
244
|
+
// passes a socket whose peer has closed, and the receiver reads its EOF.
|
|
245
|
+
// Refusing it failed the whole channel write (`write ENOTSUP`), which ended
|
|
246
|
+
// VS Code's extension host when a reconnecting client had already gone.
|
|
247
|
+
if (sent && (!(handle instanceof Pipe) || sent.listening || bytes.byteLength === 0)) return Promise.resolve(UV_ENOTSUP);
|
|
240
248
|
// Retain the descriptor while capacity is unavailable; the sender can close
|
|
241
249
|
// its original after submitting the write without invalidating this copy.
|
|
242
250
|
const owned = new Uint8Array(bytes.byteLength);
|
|
@@ -315,3 +323,8 @@ export class NativeStreamScope {
|
|
|
315
323
|
if (failures.length) throw new AggregateError(failures, 'Native stream scope cleanup failed.');
|
|
316
324
|
}
|
|
317
325
|
}
|
|
326
|
+
|
|
327
|
+
/** The process a handle opened for another worker's process belongs to, where a scope holds it. */
|
|
328
|
+
export function nativeStreamOwnerPid(handle: object): number | undefined {
|
|
329
|
+
return scopes.get(handle as NativeHandle)?.ownerPid?.();
|
|
330
|
+
}
|
|
@@ -165,10 +165,11 @@ export class Pipe extends LibuvStreamWrap {
|
|
|
165
165
|
// A descriptor crosses as a duplicate, as `SCM_RIGHTS` hands one over: the
|
|
166
166
|
// receiver gets its own handle on the same connection, so the sender's
|
|
167
167
|
// close on `NODE_HANDLE_ACK` -- which Node's `child_process.js` does the
|
|
168
|
-
// moment the receiver confirms -- ends nothing. A
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
const
|
|
168
|
+
// moment the receiver confirms -- ends nothing. A connection is duplicated
|
|
169
|
+
// whether or not its other end is still there; a listening server has no
|
|
170
|
+
// connection to duplicate and crosses as it is.
|
|
171
|
+
const sent = handle as LibuvStreamWrap & { listening?: boolean };
|
|
172
|
+
const given = sent.listening === true ? sent : sent.duplicate();
|
|
172
173
|
// The receiving run owns this reference; the sender retains its own until
|
|
173
174
|
// Node closes it on acknowledgement, or continues using it with keepOpen.
|
|
174
175
|
__adoptHandle(given, ownerOf(this.peer ?? this));
|
package/src/runtime.ts
CHANGED
|
@@ -343,23 +343,49 @@ function transformDynamicImportsRegex(code: string): string {
|
|
|
343
343
|
* saw no import at all.
|
|
344
344
|
*/
|
|
345
345
|
function __substrateRewriteDynamicImportsInScript(code: string): string {
|
|
346
|
-
if (!/(?<![.$\w#])import\s*\(/.test(code)) return code;
|
|
346
|
+
if (!/(?<![.$\w#])(?:import|eval)\s*\(/.test(code)) return code;
|
|
347
347
|
let ast;
|
|
348
348
|
try {
|
|
349
349
|
ast = acorn.parse(code, { ecmaVersion: "latest", sourceType: "script", allowReturnOutsideFunction: true, allowHashBang: true, allowAwaitOutsideFunction: true });
|
|
350
350
|
} catch {
|
|
351
351
|
return code;
|
|
352
352
|
}
|
|
353
|
-
const replacements: Array<[number, number]> = [];
|
|
353
|
+
const replacements: Array<[number, number, string]> = [];
|
|
354
354
|
walkAst(ast, (node: any) => {
|
|
355
|
-
if (node.type === "ImportExpression") replacements.push([node.start, node.start + 6]);
|
|
355
|
+
if (node.type === "ImportExpression") replacements.push([node.start, node.start + 6, "__dynamicImport"]);
|
|
356
|
+
collectDirectEval(node, replacements);
|
|
356
357
|
});
|
|
357
358
|
if (replacements.length === 0) return code;
|
|
358
|
-
|
|
359
|
+
return applyReplacements(code, replacements);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* A direct `eval` runs source the module builds at run time, and Node runs an
|
|
364
|
+
* `import()` in it through the module's loader: Playwright loads an ES config
|
|
365
|
+
* with ``eval(`import(${url})`)``. The call stays a direct eval, so the
|
|
366
|
+
* source keeps the module's scope, `__dynamicImport` included; only the
|
|
367
|
+
* source it is given is rewritten the way the module's own was.
|
|
368
|
+
*/
|
|
369
|
+
function collectDirectEval(node: any, replacements: Array<[number, number, string]>): void {
|
|
370
|
+
if (node.type !== "CallExpression" || node.optional || node.callee?.type !== "Identifier" || node.callee.name !== "eval") return;
|
|
371
|
+
const source = node.arguments?.[0];
|
|
372
|
+
if (!source || source.type === "SpreadElement") return;
|
|
373
|
+
replacements.push([source.start, source.start, "__substrateEvalSource("]);
|
|
374
|
+
replacements.push([source.end, source.end, ")"]);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Applies insertions and replacements from the end, so earlier offsets hold. */
|
|
378
|
+
function applyReplacements(code: string, replacements: Array<[number, number, string]>): string {
|
|
379
|
+
replacements.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
|
|
359
380
|
let out = code;
|
|
360
|
-
for (const [start, end] of replacements) out = out.slice(0, start) +
|
|
381
|
+
for (const [start, end, text] of replacements) out = out.slice(0, start) + text + out.slice(end);
|
|
361
382
|
return out;
|
|
362
383
|
}
|
|
384
|
+
|
|
385
|
+
Object.defineProperty(globalThis, "__substrateEvalSource", {
|
|
386
|
+
configurable: true,
|
|
387
|
+
value: (source: unknown): unknown => typeof source === "string" ? __substrateRewriteDynamicImportsInScript(source) : source,
|
|
388
|
+
});
|
|
363
389
|
function transformEsmToCjs(code: string, filename: string): string {
|
|
364
390
|
// Quick check: does the code have any ESM-like patterns?
|
|
365
391
|
const maybeEsm = __substrateHasEsmSyntax(code);
|
|
@@ -401,6 +427,7 @@ function transformEsmToCjsAst(code: string, filename: string): string {
|
|
|
401
427
|
// Replace just the 'import' keyword, preserving the (...) part
|
|
402
428
|
deepReplacements.push([node.start, node.start + 6, '__dynamicImport']);
|
|
403
429
|
}
|
|
430
|
+
collectDirectEval(node, deepReplacements);
|
|
404
431
|
});
|
|
405
432
|
|
|
406
433
|
// Check for actual import/export declarations
|
|
@@ -408,11 +435,7 @@ function transformEsmToCjsAst(code: string, filename: string): string {
|
|
|
408
435
|
const hasExportDecl = ast.body.some((n: any) => n.type?.startsWith('Export'));
|
|
409
436
|
|
|
410
437
|
// Apply deep replacements from end to start (preserves earlier positions)
|
|
411
|
-
let transformed = code;
|
|
412
|
-
deepReplacements.sort((a, b) => b[0] - a[0]);
|
|
413
|
-
for (const [start, end, replacement] of deepReplacements) {
|
|
414
|
-
transformed = transformed.slice(0, start) + replacement + transformed.slice(end);
|
|
415
|
-
}
|
|
438
|
+
let transformed = applyReplacements(code, deepReplacements);
|
|
416
439
|
|
|
417
440
|
// Transform import/export declarations (re-parses the modified code)
|
|
418
441
|
if (hasImportDecl || hasExportDecl) {
|
|
@@ -1650,13 +1650,34 @@ function programExists(file: string, cwd: string | undefined, env: Record<string
|
|
|
1650
1650
|
// what `spawn('/usr/bin/env', …)` means and what a Dockerfile's shebang
|
|
1651
1651
|
// reaches. A file in the tree is a program too, and so is one on PATH.
|
|
1652
1652
|
if (shellCommandNames().has(bare)) return true;
|
|
1653
|
-
if (name.includes('/'))
|
|
1653
|
+
if (name.includes('/')) {
|
|
1654
|
+
const path = __resolvePath(cwd ?? '/', name);
|
|
1655
|
+
return existsInTree(path) && !isRegisteredProgramStub(path);
|
|
1656
|
+
}
|
|
1654
1657
|
for (const dir of (env.PATH ?? '/usr/local/bin:/usr/bin:/bin:/node_modules/.bin').split(':')) {
|
|
1655
|
-
|
|
1658
|
+
const path = `${dir}/${name}`.replace(/\/+/gu, '/');
|
|
1659
|
+
if (dir.length > 0 && existsInTree(path) && !isRegisteredProgramStub(path)) return true;
|
|
1656
1660
|
}
|
|
1657
1661
|
return false;
|
|
1658
1662
|
}
|
|
1659
1663
|
|
|
1664
|
+
/**
|
|
1665
|
+
* A program the page registered is not a file, so a host places a stub where
|
|
1666
|
+
* a loader looks for it (`#!/bin/sh` then `# <name>: runtime-registered
|
|
1667
|
+
* program`), for a caller that checks the path exists or walks PATH. The stub
|
|
1668
|
+
* is the page's program, not a script of this engine's: it is relayed to the
|
|
1669
|
+
* host, whose process host maps the pathname to the program by its basename.
|
|
1670
|
+
*/
|
|
1671
|
+
const REGISTERED_PROGRAM_STUB = /^#!\/bin\/sh\n# [\w.+-]+: runtime-registered program\n/u;
|
|
1672
|
+
function isRegisteredProgramStub(path: string): boolean {
|
|
1673
|
+
if (!currentVfs) return false;
|
|
1674
|
+
try {
|
|
1675
|
+
const stat = currentVfs.statSync(path);
|
|
1676
|
+
if (!stat.isFile() || stat.size > 256) return false;
|
|
1677
|
+
return REGISTERED_PROGRAM_STUB.test(String(currentVfs.readFileSync(path, 'utf8')));
|
|
1678
|
+
} catch { return false; }
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1660
1681
|
/** The runs a `Process` handle started, so a later name never lands on a live one. */
|
|
1661
1682
|
let __nextChildRun = 1;
|
|
1662
1683
|
|