@nimbus-sh/worker 0.8.0 → 0.9.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/dist/facets/compose.d.ts +3 -0
- package/dist/facets/compose.d.ts.map +1 -1
- package/dist/facets/compose.js +1 -1
- package/dist/facets/manager.d.ts +26 -15
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +138 -107
- package/dist/hosted/commands.d.ts.map +1 -1
- package/dist/hosted/commands.js +27 -38
- package/dist/hosted/runtime.d.ts +22 -1
- package/dist/hosted/runtime.d.ts.map +1 -1
- package/dist/hosted/runtime.js +69 -13
- package/dist/hosted/services.d.ts +6 -2
- package/dist/hosted/services.d.ts.map +1 -1
- package/dist/hosted/services.js +12 -2
- package/dist/loaders/generated-workers.d.ts +1 -1
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +2 -2
- package/dist/node-shims-artifact.generated.js +3 -3
- package/dist/runtime/bash-repl.d.ts +5 -3
- package/dist/runtime/bash-repl.d.ts.map +1 -1
- package/dist/runtime/bash-repl.js +8 -3
- package/dist/runtime/facet-loader-host.d.ts.map +1 -1
- package/dist/runtime/facet-loader-host.js +1 -3
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +14 -1
- package/dist/runtime/python-repl.d.ts +4 -3
- package/dist/runtime/python-repl.d.ts.map +1 -1
- package/dist/runtime/python-repl.js +10 -8
- package/dist/runtime/ruby-repl.d.ts +3 -2
- package/dist/runtime/ruby-repl.d.ts.map +1 -1
- package/dist/runtime/ruby-repl.js +7 -6
- package/dist/runtime/runtime-catalog.js +1 -1
- package/dist/runtime/wasi/preamble.d.ts.map +1 -1
- package/dist/runtime/wasi/preamble.js +74 -56
- package/dist/runtime-catalog.generated.js +1 -1
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +1 -0
- package/dist/session/nimbus-session.d.ts +6 -21
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +12 -35
- package/dist/session/port-capability.d.ts.map +1 -1
- package/dist/session/port-capability.js +11 -1
- package/dist/session/programmatic.js +2 -2
- package/dist/session/rpc.d.ts +3 -43
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +7 -65
- package/dist/session/supervisor-op.d.ts +8 -3
- package/dist/session/supervisor-op.d.ts.map +1 -1
- package/dist/session/supervisor-op.js +10 -45
- package/dist/session/supervisor-rpc.d.ts +33 -16
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +49 -4
- package/dist/vfs/facet-resident-store.d.ts.map +1 -1
- package/dist/vfs/facet-resident-store.js +4 -2
- package/dist/workspace-host.d.ts +2 -1
- package/dist/workspace-host.d.ts.map +1 -1
- package/package.json +4 -4
- package/public/_assets/runtime/{node-shims-8f8c64edd2a1f0f5.js → node-shims-acaa037e6d26ec0d.js} +14 -1
- package/scripts/bundle-runtime.mjs +35 -8
package/dist/facets/manager.js
CHANGED
|
@@ -21,6 +21,7 @@ import { getRealNodeImportsCode } from '@nimbus-sh/core/_shared/real-node-import
|
|
|
21
21
|
import { FACET_RESIDENT_STORE_SOURCE } from '../vfs/facet-resident-store.js';
|
|
22
22
|
import { VFS_CURSOR_SEED_SOURCE, serializeFacetVfsCursor, } from '@nimbus-sh/core/_shared/facet-vfs-cursor.js';
|
|
23
23
|
import { VFS_WRITE_LEDGER_SOURCE } from '@nimbus-sh/core/_shared/vfs-write-ledger.js';
|
|
24
|
+
import { ExecutionFs } from '@nimbus-sh/core/shell/execution-fs.js';
|
|
24
25
|
import { stripLeadingSlashes, vfsPathExtension } from '@nimbus-sh/core/vfs/path.js';
|
|
25
26
|
import { clearPortCapability, listPortReservations, readPortReservation, readPortReservationByOwner, releasePortReservation, restoreReservedPortCapability, } from '../session/port-capability.js';
|
|
26
27
|
import { deriveResidentOwner } from './resident-identity.js';
|
|
@@ -1148,8 +1149,8 @@ async function facetVfsBundleSourceFor(vfsState, pacer) {
|
|
|
1148
1149
|
* Throws on read errors (caller wraps in try/catch — matches the
|
|
1149
1150
|
* pre-fix readFileString contract).
|
|
1150
1151
|
*/
|
|
1151
|
-
function _readBundleCell(vfs, path) {
|
|
1152
|
-
const bytes = vfs.readFile(path);
|
|
1152
|
+
async function _readBundleCell(vfs, path) {
|
|
1153
|
+
const bytes = await vfs.readFile(path);
|
|
1153
1154
|
try {
|
|
1154
1155
|
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
1155
1156
|
}
|
|
@@ -1176,10 +1177,10 @@ function _bundleCellLength(cell) {
|
|
|
1176
1177
|
* `perFileCeiling` is the pass's own cap (e.g. CWD_SNAPSHOT_MAX_FILE_BYTES,
|
|
1177
1178
|
* BIN_PACKAGE_SPECULATIVE_MAX_FILE_BYTES); omit it for budget-only checks.
|
|
1178
1179
|
*/
|
|
1179
|
-
function _bundleAdmits(vfs, path, budgetState, perFileCeiling) {
|
|
1180
|
+
async function _bundleAdmits(vfs, path, budgetState, perFileCeiling) {
|
|
1180
1181
|
let size;
|
|
1181
1182
|
try {
|
|
1182
|
-
size = vfs.lstat(path).size;
|
|
1183
|
+
size = (await vfs.lstat(path)).size;
|
|
1183
1184
|
}
|
|
1184
1185
|
catch {
|
|
1185
1186
|
return false;
|
|
@@ -1650,9 +1651,9 @@ const MANIFEST_MAX_DEPTH = 12;
|
|
|
1650
1651
|
* fs.readdirSync / fs.statSync honest regardless of which subset of
|
|
1651
1652
|
* file CONTENT we ship.
|
|
1652
1653
|
*/
|
|
1653
|
-
function buildManifest(vfs, cwd, scriptPath) {
|
|
1654
|
+
async function buildManifest(vfs, cwd, scriptPath) {
|
|
1654
1655
|
const manifest = {};
|
|
1655
|
-
function walk(dirPath, depth = 0) {
|
|
1656
|
+
async function walk(dirPath, depth = 0) {
|
|
1656
1657
|
if (depth > MANIFEST_MAX_DEPTH)
|
|
1657
1658
|
return;
|
|
1658
1659
|
const stripped = dirPath.replace(/^\/+/, '');
|
|
@@ -1660,7 +1661,7 @@ function buildManifest(vfs, cwd, scriptPath) {
|
|
|
1660
1661
|
return;
|
|
1661
1662
|
let entries;
|
|
1662
1663
|
try {
|
|
1663
|
-
entries = vfs.readdir(stripped);
|
|
1664
|
+
entries = (await vfs.readdir(stripped));
|
|
1664
1665
|
}
|
|
1665
1666
|
catch {
|
|
1666
1667
|
return;
|
|
@@ -1669,7 +1670,7 @@ function buildManifest(vfs, cwd, scriptPath) {
|
|
|
1669
1670
|
for (const entry of entries) {
|
|
1670
1671
|
if (entry.type === 'directory') {
|
|
1671
1672
|
const childPath = stripped ? stripped + '/' + entry.name : entry.name;
|
|
1672
|
-
walk(childPath, depth + 1);
|
|
1673
|
+
(await walk(childPath, depth + 1));
|
|
1673
1674
|
}
|
|
1674
1675
|
}
|
|
1675
1676
|
}
|
|
@@ -1690,13 +1691,13 @@ function buildManifest(vfs, cwd, scriptPath) {
|
|
|
1690
1691
|
{
|
|
1691
1692
|
const segments = cwdStripped ? cwdStripped.split('/') : [];
|
|
1692
1693
|
for (let i = 0; i < segments.length; i++) {
|
|
1693
|
-
walk(segments.slice(0, i).join('/'), MANIFEST_MAX_DEPTH);
|
|
1694
|
+
(await walk(segments.slice(0, i).join('/'), MANIFEST_MAX_DEPTH));
|
|
1694
1695
|
}
|
|
1695
1696
|
}
|
|
1696
|
-
walk(cwdStripped, 0);
|
|
1697
|
+
(await walk(cwdStripped, 0));
|
|
1697
1698
|
const nmDir = cwdStripped + '/node_modules';
|
|
1698
|
-
if (vfs.exists(nmDir) && vfs.isDirectory(nmDir)) {
|
|
1699
|
-
walk(nmDir, 0);
|
|
1699
|
+
if ((await vfs.exists(nmDir)) && (await vfs.isDirectory(nmDir))) {
|
|
1700
|
+
(await walk(nmDir, 0));
|
|
1700
1701
|
}
|
|
1701
1702
|
// ── Bin-target package + hoisted dependency roots ─────────────────────
|
|
1702
1703
|
//
|
|
@@ -1728,19 +1729,19 @@ function buildManifest(vfs, cwd, scriptPath) {
|
|
|
1728
1729
|
const pkgEnd = isScoped ? nmIdx + 3 : nmIdx + 2;
|
|
1729
1730
|
if (pkgEnd <= segs.length) {
|
|
1730
1731
|
const pkgRoot = segs.slice(0, pkgEnd).join('/');
|
|
1731
|
-
if (vfs.exists(pkgRoot) && vfs.isDirectory(pkgRoot)) {
|
|
1732
|
-
walk(pkgRoot, 0);
|
|
1732
|
+
if ((await vfs.exists(pkgRoot)) && (await vfs.isDirectory(pkgRoot))) {
|
|
1733
|
+
(await walk(pkgRoot, 0));
|
|
1733
1734
|
}
|
|
1734
1735
|
}
|
|
1735
1736
|
const nodeModulesRoot = segs.slice(0, nmIdx + 1).join('/');
|
|
1736
|
-
if (vfs.exists(nodeModulesRoot) && vfs.isDirectory(nodeModulesRoot)) {
|
|
1737
|
-
walk(nodeModulesRoot, 0);
|
|
1737
|
+
if ((await vfs.exists(nodeModulesRoot)) && (await vfs.isDirectory(nodeModulesRoot))) {
|
|
1738
|
+
(await walk(nodeModulesRoot, 0));
|
|
1738
1739
|
}
|
|
1739
1740
|
}
|
|
1740
1741
|
}
|
|
1741
1742
|
return manifest;
|
|
1742
1743
|
}
|
|
1743
|
-
function buildVfsMetadata(vfs, manifest, bundle) {
|
|
1744
|
+
async function buildVfsMetadata(vfs, manifest, bundle) {
|
|
1744
1745
|
const paths = new Set(Object.keys(bundle).filter((path) => compiledCellPath(path) === null));
|
|
1745
1746
|
for (const [directory, children] of Object.entries(manifest)) {
|
|
1746
1747
|
paths.add(directory);
|
|
@@ -1751,7 +1752,9 @@ function buildVfsMetadata(vfs, manifest, bundle) {
|
|
|
1751
1752
|
const metadata = {};
|
|
1752
1753
|
for (const path of paths) {
|
|
1753
1754
|
try {
|
|
1754
|
-
const stat = vfs.
|
|
1755
|
+
const stat = await vfs.authority.stat(path, { followSymlinks: false });
|
|
1756
|
+
if (!stat)
|
|
1757
|
+
continue;
|
|
1755
1758
|
metadata[path] = {
|
|
1756
1759
|
type: stat.type,
|
|
1757
1760
|
size: stat.size,
|
|
@@ -1767,12 +1770,12 @@ function buildVfsMetadata(vfs, manifest, bundle) {
|
|
|
1767
1770
|
}
|
|
1768
1771
|
return metadata;
|
|
1769
1772
|
}
|
|
1770
|
-
function addUnreadableDenialCells(vfs, bundle, metadata) {
|
|
1773
|
+
async function addUnreadableDenialCells(vfs, bundle, metadata) {
|
|
1771
1774
|
for (const [path, stat] of Object.entries(metadata)) {
|
|
1772
1775
|
if (stat.type === 'directory' || path in bundle)
|
|
1773
1776
|
continue;
|
|
1774
1777
|
try {
|
|
1775
|
-
vfs.access(path, 0o4);
|
|
1778
|
+
(await vfs.access(path, 0o4));
|
|
1776
1779
|
}
|
|
1777
1780
|
catch (error) {
|
|
1778
1781
|
if (typeof error === 'object'
|
|
@@ -1801,14 +1804,14 @@ function addUnreadableDenialCells(vfs, bundle, metadata) {
|
|
|
1801
1804
|
// was a file-local helper. Adding the named export is a pure surface
|
|
1802
1805
|
// addition — no callers other than buildPrefetchBundle (same file) and
|
|
1803
1806
|
// the new probe.
|
|
1804
|
-
export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPaths = new Set()) {
|
|
1807
|
+
export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPaths = new Set()) {
|
|
1805
1808
|
let added = 0;
|
|
1806
1809
|
const cwdStripped = cwd.replace(/^\/+/, '');
|
|
1807
1810
|
const nmDir = cwdStripped + '/node_modules';
|
|
1808
|
-
if (!(vfs.exists(nmDir) && vfs.isDirectory(nmDir)))
|
|
1811
|
+
if (!((await vfs.exists(nmDir)) && (await vfs.isDirectory(nmDir))))
|
|
1809
1812
|
return { added };
|
|
1810
1813
|
const exts = ['', '.js', '.cjs', '.mjs', '/index.js', '/index.cjs'];
|
|
1811
|
-
function addOne(path) {
|
|
1814
|
+
async function addOne(path) {
|
|
1812
1815
|
const stripped = path.replace(/^\/+/, '');
|
|
1813
1816
|
if (stripped in bundle)
|
|
1814
1817
|
return false;
|
|
@@ -1817,7 +1820,7 @@ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPath
|
|
|
1817
1820
|
if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
|
|
1818
1821
|
return false;
|
|
1819
1822
|
try {
|
|
1820
|
-
if (!vfs.exists(stripped) || vfs.isDirectory(stripped))
|
|
1823
|
+
if (!(await vfs.exists(stripped)) || (await vfs.isDirectory(stripped)))
|
|
1821
1824
|
return false;
|
|
1822
1825
|
// This is a guess at what a program might require, and the same
|
|
1823
1826
|
// per-file ceiling the entry-package walk applies bounds it: a
|
|
@@ -1827,7 +1830,7 @@ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPath
|
|
|
1827
1830
|
// supervisor's headroom — on every invocation that never reads it.
|
|
1828
1831
|
// Anything the program really requires arrives through the closure,
|
|
1829
1832
|
// which is uncapped.
|
|
1830
|
-
if (!_bundleAdmits(vfs, stripped, budgetState, BIN_PACKAGE_SPECULATIVE_MAX_FILE_BYTES))
|
|
1833
|
+
if (!(await _bundleAdmits(vfs, stripped, budgetState, BIN_PACKAGE_SPECULATIVE_MAX_FILE_BYTES)))
|
|
1831
1834
|
return false;
|
|
1832
1835
|
// A guess must not be a native binary. Nothing in a Workers isolate can
|
|
1833
1836
|
// load a `.node` addon or a `.exe` — the ABI policy classifies them
|
|
@@ -1839,7 +1842,7 @@ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPath
|
|
|
1839
1842
|
if (stripped.endsWith('.node') || stripped.endsWith('.exe'))
|
|
1840
1843
|
return false;
|
|
1841
1844
|
// hardening-r5: preserve binary content as Uint8Array.
|
|
1842
|
-
const content = _readBundleCell(vfs, stripped);
|
|
1845
|
+
const content = (await _readBundleCell(vfs, stripped));
|
|
1843
1846
|
const cellLen = _bundleCellLength(content);
|
|
1844
1847
|
if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
|
|
1845
1848
|
return false;
|
|
@@ -1903,15 +1906,15 @@ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPath
|
|
|
1903
1906
|
}
|
|
1904
1907
|
return !reached;
|
|
1905
1908
|
}
|
|
1906
|
-
function addPkgEntry(pkgDir) {
|
|
1907
|
-
addOne(pkgDir + '/package.json');
|
|
1909
|
+
async function addPkgEntry(pkgDir) {
|
|
1910
|
+
(await addOne(pkgDir + '/package.json'));
|
|
1908
1911
|
// A package the closure reached keeps exactly what it reached; only an
|
|
1909
1912
|
// unreached one gets the guess below.
|
|
1910
1913
|
if (!mainIsSpeculative(pkgDir))
|
|
1911
1914
|
return;
|
|
1912
1915
|
let meta;
|
|
1913
1916
|
try {
|
|
1914
|
-
meta = JSON.parse(vfs.readFileString(pkgDir + '/package.json'));
|
|
1917
|
+
meta = JSON.parse((await vfs.readFileString(pkgDir + '/package.json')));
|
|
1915
1918
|
}
|
|
1916
1919
|
catch {
|
|
1917
1920
|
meta = null;
|
|
@@ -1943,9 +1946,9 @@ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPath
|
|
|
1943
1946
|
let landed = false;
|
|
1944
1947
|
const tries = /\.[a-z]+$/.test(norm) ? [base] : exts.map((e) => base + e);
|
|
1945
1948
|
for (const candidate of tries) {
|
|
1946
|
-
if (vfs.exists(candidate.replace(/^\/+/, '')) &&
|
|
1947
|
-
!vfs.isDirectory(candidate.replace(/^\/+/, ''))) {
|
|
1948
|
-
if (addOne(candidate)) {
|
|
1949
|
+
if ((await vfs.exists(candidate.replace(/^\/+/, ''))) &&
|
|
1950
|
+
!(await vfs.isDirectory(candidate.replace(/^\/+/, '')))) {
|
|
1951
|
+
if ((await addOne(candidate))) {
|
|
1949
1952
|
landed = true;
|
|
1950
1953
|
break;
|
|
1951
1954
|
}
|
|
@@ -1963,7 +1966,7 @@ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPath
|
|
|
1963
1966
|
// the greedy oversample is the safety net for their reachability.
|
|
1964
1967
|
const entryDir = base.replace(/\/[^/]+$/, '');
|
|
1965
1968
|
try {
|
|
1966
|
-
const sibs = vfs.readdir(entryDir);
|
|
1969
|
+
const sibs = (await vfs.readdir(entryDir));
|
|
1967
1970
|
for (const sib of sibs) {
|
|
1968
1971
|
if (sib.type !== 'file')
|
|
1969
1972
|
continue;
|
|
@@ -1983,7 +1986,7 @@ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPath
|
|
|
1983
1986
|
const hasMixedCase = /[A-Z]/.test(seg) && /[a-z]/.test(seg);
|
|
1984
1987
|
if (!hasDigitOrDash && !hasMixedCase)
|
|
1985
1988
|
continue;
|
|
1986
|
-
addOne(entryDir + '/' + sib.name);
|
|
1989
|
+
(await addOne(entryDir + '/' + sib.name));
|
|
1987
1990
|
}
|
|
1988
1991
|
// Walk one level into `shared/` — unconditionally, since the
|
|
1989
1992
|
// pattern is well-known across unbuild/rolldown/rollup chunked
|
|
@@ -1991,13 +1994,13 @@ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPath
|
|
|
1991
1994
|
// typical shared/ dir returns 1-5 files.
|
|
1992
1995
|
const sharedDir = entryDir + '/shared';
|
|
1993
1996
|
const sharedStripped = sharedDir.replace(/^\/+/, '');
|
|
1994
|
-
if (vfs.exists(sharedStripped) && vfs.isDirectory(sharedStripped)) {
|
|
1995
|
-
for (const sh of vfs.readdir(sharedDir)) {
|
|
1997
|
+
if ((await vfs.exists(sharedStripped)) && (await vfs.isDirectory(sharedStripped))) {
|
|
1998
|
+
for (const sh of (await vfs.readdir(sharedDir))) {
|
|
1996
1999
|
if (sh.type !== 'file')
|
|
1997
2000
|
continue;
|
|
1998
2001
|
if (!/\.(cjs|mjs|js)$/.test(sh.name))
|
|
1999
2002
|
continue;
|
|
2000
|
-
addOne(sharedDir + '/' + sh.name);
|
|
2003
|
+
(await addOne(sharedDir + '/' + sh.name));
|
|
2001
2004
|
}
|
|
2002
2005
|
}
|
|
2003
2006
|
}
|
|
@@ -2008,8 +2011,8 @@ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPath
|
|
|
2008
2011
|
}
|
|
2009
2012
|
}
|
|
2010
2013
|
}
|
|
2011
|
-
for (const pkgDir of speculativePackageDirs(vfs, cwdStripped, bundle))
|
|
2012
|
-
addPkgEntry(pkgDir);
|
|
2014
|
+
for (const pkgDir of (await speculativePackageDirs(vfs, cwdStripped, bundle)))
|
|
2015
|
+
(await addPkgEntry(pkgDir));
|
|
2013
2016
|
return { added };
|
|
2014
2017
|
}
|
|
2015
2018
|
/**
|
|
@@ -2029,10 +2032,10 @@ export function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPath
|
|
|
2029
2032
|
* dependency of the package doing the requiring, so the bound is one hop
|
|
2030
2033
|
* over `dependencies` only. Directories, sorted for a stable bundle.
|
|
2031
2034
|
*/
|
|
2032
|
-
export function speculativePackageDirs(vfs, cwdStripped, bundle) {
|
|
2033
|
-
const runtimeDeps = (pkgJsonPath) => {
|
|
2035
|
+
export async function speculativePackageDirs(vfs, cwdStripped, bundle) {
|
|
2036
|
+
const runtimeDeps = async (pkgJsonPath) => {
|
|
2034
2037
|
try {
|
|
2035
|
-
const meta = JSON.parse(vfs.readFileString(pkgJsonPath));
|
|
2038
|
+
const meta = JSON.parse((await vfs.readFileString(pkgJsonPath)));
|
|
2036
2039
|
const names = new Set();
|
|
2037
2040
|
for (const field of ['dependencies', 'optionalDependencies']) {
|
|
2038
2041
|
const deps = meta?.[field];
|
|
@@ -2057,11 +2060,11 @@ export function speculativePackageDirs(vfs, cwdStripped, bundle) {
|
|
|
2057
2060
|
};
|
|
2058
2061
|
// Resolve a bare name the way require does from `fromDir`: the nearest
|
|
2059
2062
|
// node_modules up the tree that has it.
|
|
2060
|
-
const resolveDir = (name, fromDir) => {
|
|
2063
|
+
const resolveDir = async (name, fromDir) => {
|
|
2061
2064
|
let dir = fromDir;
|
|
2062
2065
|
for (;;) {
|
|
2063
2066
|
const candidate = dir + '/node_modules/' + name;
|
|
2064
|
-
if (vfs.exists(candidate + '/package.json'))
|
|
2067
|
+
if ((await vfs.exists(candidate + '/package.json')))
|
|
2065
2068
|
return candidate;
|
|
2066
2069
|
const idx = dir.lastIndexOf('/');
|
|
2067
2070
|
if (idx <= 0)
|
|
@@ -2070,14 +2073,14 @@ export function speculativePackageDirs(vfs, cwdStripped, bundle) {
|
|
|
2070
2073
|
}
|
|
2071
2074
|
};
|
|
2072
2075
|
const reached = new Set();
|
|
2073
|
-
const hop = (fromDir) => {
|
|
2074
|
-
for (const name of runtimeDeps(fromDir + '/package.json')) {
|
|
2075
|
-
const dir = resolveDir(name, fromDir);
|
|
2076
|
+
const hop = async (fromDir) => {
|
|
2077
|
+
for (const name of (await runtimeDeps(fromDir + '/package.json'))) {
|
|
2078
|
+
const dir = (await resolveDir(name, fromDir));
|
|
2076
2079
|
if (dir !== null)
|
|
2077
2080
|
reached.add(dir);
|
|
2078
2081
|
}
|
|
2079
2082
|
};
|
|
2080
|
-
hop(cwdStripped);
|
|
2083
|
+
(await hop(cwdStripped));
|
|
2081
2084
|
const owners = new Set();
|
|
2082
2085
|
for (const path of Object.keys(bundle)) {
|
|
2083
2086
|
const owner = ownerOf(path);
|
|
@@ -2086,7 +2089,7 @@ export function speculativePackageDirs(vfs, cwdStripped, bundle) {
|
|
|
2086
2089
|
}
|
|
2087
2090
|
for (const owner of owners) {
|
|
2088
2091
|
reached.add(owner);
|
|
2089
|
-
hop(owner);
|
|
2092
|
+
(await hop(owner));
|
|
2090
2093
|
}
|
|
2091
2094
|
return [...reached].sort();
|
|
2092
2095
|
}
|
|
@@ -2129,7 +2132,7 @@ export function speculativePackageDirs(vfs, cwdStripped, bundle) {
|
|
|
2129
2132
|
* VFS_BUNDLE_MAX_FILES / VFS_BUNDLE_MAX_BYTES caps via the
|
|
2130
2133
|
* `budgetState` counter.
|
|
2131
2134
|
*/
|
|
2132
|
-
export function addStaticReadFileAssets(vfs, cwd, bundle, budgetState) {
|
|
2135
|
+
export async function addStaticReadFileAssets(vfs, cwd, bundle, budgetState) {
|
|
2133
2136
|
let added = 0;
|
|
2134
2137
|
// Asset extensions covered. Conservative whitelist — txt/json are
|
|
2135
2138
|
// also legit runtime-loaded assets (e.g. mime-db json, license.txt).
|
|
@@ -2147,7 +2150,7 @@ export function addStaticReadFileAssets(vfs, cwd, bundle, budgetState) {
|
|
|
2147
2150
|
// there's no `${` in the captured body (template-literal interpolation
|
|
2148
2151
|
// is rejected).
|
|
2149
2152
|
const RX = /(?:\bfs\s*\.)?readFileSync\s*\(\s*(?:[\w$.]+\s*\.\s*)?resolve\s*\(\s*__dirname\s*,\s*(['"`])([^'"`]+)\1\s*[\),]/g;
|
|
2150
|
-
function addOneAsset(absPath) {
|
|
2153
|
+
async function addOneAsset(absPath) {
|
|
2151
2154
|
const stripped = absPath.replace(/^\/+/, '');
|
|
2152
2155
|
if (stripped in bundle)
|
|
2153
2156
|
return false;
|
|
@@ -2156,12 +2159,12 @@ export function addStaticReadFileAssets(vfs, cwd, bundle, budgetState) {
|
|
|
2156
2159
|
if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
|
|
2157
2160
|
return false;
|
|
2158
2161
|
try {
|
|
2159
|
-
if (!vfs.exists(stripped) || vfs.isDirectory(stripped))
|
|
2162
|
+
if (!(await vfs.exists(stripped)) || (await vfs.isDirectory(stripped)))
|
|
2160
2163
|
return false;
|
|
2161
|
-
if (!_bundleAdmits(vfs, stripped, budgetState))
|
|
2164
|
+
if (!(await _bundleAdmits(vfs, stripped, budgetState)))
|
|
2162
2165
|
return false;
|
|
2163
2166
|
// hardening-r5: preserve binary content as Uint8Array.
|
|
2164
|
-
const content = _readBundleCell(vfs, stripped);
|
|
2167
|
+
const content = (await _readBundleCell(vfs, stripped));
|
|
2165
2168
|
const cellLen = _bundleCellLength(content);
|
|
2166
2169
|
if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
|
|
2167
2170
|
return false;
|
|
@@ -2231,7 +2234,7 @@ export function addStaticReadFileAssets(vfs, cwd, bundle, budgetState) {
|
|
|
2231
2234
|
}
|
|
2232
2235
|
resolved = out.join('/');
|
|
2233
2236
|
}
|
|
2234
|
-
addOneAsset(resolved);
|
|
2237
|
+
(await addOneAsset(resolved));
|
|
2235
2238
|
}
|
|
2236
2239
|
}
|
|
2237
2240
|
return { added };
|
|
@@ -2283,7 +2286,7 @@ export function addStaticReadFileAssets(vfs, cwd, bundle, budgetState) {
|
|
|
2283
2286
|
* Errors are swallowed: missing assets, unreadable VFS, and
|
|
2284
2287
|
* non-string readFile inputs are silent skips — matches Z3 posture.
|
|
2285
2288
|
*/
|
|
2286
|
-
export function addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, budgetState) {
|
|
2289
|
+
export async function addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, budgetState) {
|
|
2287
2290
|
let added = 0;
|
|
2288
2291
|
// The heuristic gate. Filenames matching either branch are eligible.
|
|
2289
2292
|
// - Leading `.` covers `.ts-jest-digest`, `.cache-marker`, `.lintstagedrc`-ish
|
|
@@ -2322,7 +2325,7 @@ export function addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, budgetSta
|
|
|
2322
2325
|
'\\s*__dirname\\s*,\\s*' +
|
|
2323
2326
|
// literal: ' " ` (no ${ for backtick)
|
|
2324
2327
|
'([\'"`])([^\'"`]+)\\1', 'g');
|
|
2325
|
-
function addOneAsset(absPath) {
|
|
2328
|
+
async function addOneAsset(absPath) {
|
|
2326
2329
|
const stripped = absPath.replace(/^\/+/, '');
|
|
2327
2330
|
if (stripped in bundle)
|
|
2328
2331
|
return false;
|
|
@@ -2331,12 +2334,12 @@ export function addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, budgetSta
|
|
|
2331
2334
|
if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
|
|
2332
2335
|
return false;
|
|
2333
2336
|
try {
|
|
2334
|
-
if (!vfs.exists(stripped) || vfs.isDirectory(stripped))
|
|
2337
|
+
if (!(await vfs.exists(stripped)) || (await vfs.isDirectory(stripped)))
|
|
2335
2338
|
return false;
|
|
2336
|
-
if (!_bundleAdmits(vfs, stripped, budgetState))
|
|
2339
|
+
if (!(await _bundleAdmits(vfs, stripped, budgetState)))
|
|
2337
2340
|
return false;
|
|
2338
2341
|
// hardening-r5: preserve binary content as Uint8Array.
|
|
2339
|
-
const content = _readBundleCell(vfs, stripped);
|
|
2342
|
+
const content = (await _readBundleCell(vfs, stripped));
|
|
2340
2343
|
const cellLen = _bundleCellLength(content);
|
|
2341
2344
|
if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
|
|
2342
2345
|
return false;
|
|
@@ -2405,7 +2408,7 @@ export function addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, budgetSta
|
|
|
2405
2408
|
const basename = slash >= 0 ? resolved.slice(slash + 1) : resolved;
|
|
2406
2409
|
if (!FILENAME_GATE.test(basename))
|
|
2407
2410
|
continue;
|
|
2408
|
-
addOneAsset(resolved);
|
|
2411
|
+
(await addOneAsset(resolved));
|
|
2409
2412
|
}
|
|
2410
2413
|
}
|
|
2411
2414
|
return { added };
|
|
@@ -2438,7 +2441,7 @@ export function addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, budgetSta
|
|
|
2438
2441
|
* (user scripts, npx-cache files outside node_modules, eval) is
|
|
2439
2442
|
* a no-op.
|
|
2440
2443
|
*/
|
|
2441
|
-
export function addBinTargetSiblings(vfs, scriptPath, bundle, budgetState, bundleProfile) {
|
|
2444
|
+
export async function addBinTargetSiblings(vfs, scriptPath, bundle, budgetState, bundleProfile) {
|
|
2442
2445
|
if (!scriptPath)
|
|
2443
2446
|
return { added: 0, wasmPaths: [] };
|
|
2444
2447
|
const stripped = scriptPath.replace(/^\/+/, '');
|
|
@@ -2492,7 +2495,7 @@ export function addBinTargetSiblings(vfs, scriptPath, bundle, budgetState, bundl
|
|
|
2492
2495
|
const dir = queue.shift();
|
|
2493
2496
|
let entries;
|
|
2494
2497
|
try {
|
|
2495
|
-
entries = vfs.readdir(dir);
|
|
2498
|
+
entries = (await vfs.readdir(dir));
|
|
2496
2499
|
}
|
|
2497
2500
|
catch {
|
|
2498
2501
|
continue;
|
|
@@ -2522,7 +2525,7 @@ export function addBinTargetSiblings(vfs, scriptPath, bundle, budgetState, bundl
|
|
|
2522
2525
|
continue;
|
|
2523
2526
|
let size;
|
|
2524
2527
|
try {
|
|
2525
|
-
size = vfs.lstat(child).size;
|
|
2528
|
+
size = (await vfs.lstat(child)).size;
|
|
2526
2529
|
}
|
|
2527
2530
|
catch {
|
|
2528
2531
|
continue;
|
|
@@ -2553,7 +2556,7 @@ export function addBinTargetSiblings(vfs, scriptPath, bundle, budgetState, bundl
|
|
|
2553
2556
|
// hardening-r5: preserve binary content as Uint8Array.
|
|
2554
2557
|
let content;
|
|
2555
2558
|
try {
|
|
2556
|
-
content = _readBundleCell(vfs, candidate.path);
|
|
2559
|
+
content = (await _readBundleCell(vfs, candidate.path));
|
|
2557
2560
|
}
|
|
2558
2561
|
catch {
|
|
2559
2562
|
continue;
|
|
@@ -2582,7 +2585,7 @@ export function addBinTargetSiblings(vfs, scriptPath, bundle, budgetState, bundl
|
|
|
2582
2585
|
* map whether it read the bytes by path or carried them inline. A file that
|
|
2583
2586
|
* cannot be read is left out; the seam's refusal names the module later.
|
|
2584
2587
|
*/
|
|
2585
|
-
export function collectClosureWasmImages(vfs, bundle, unstagedPaths) {
|
|
2588
|
+
export async function collectClosureWasmImages(vfs, bundle, unstagedPaths) {
|
|
2586
2589
|
const byPath = new Map();
|
|
2587
2590
|
for (const [path, cell] of Object.entries(bundle)) {
|
|
2588
2591
|
if (!path.endsWith('.wasm') || !(cell instanceof Uint8Array))
|
|
@@ -2594,7 +2597,7 @@ export function collectClosureWasmImages(vfs, bundle, unstagedPaths) {
|
|
|
2594
2597
|
continue;
|
|
2595
2598
|
let bytes;
|
|
2596
2599
|
try {
|
|
2597
|
-
bytes = vfs.readFile(path);
|
|
2600
|
+
bytes = await vfs.readFile(path);
|
|
2598
2601
|
}
|
|
2599
2602
|
catch {
|
|
2600
2603
|
continue;
|
|
@@ -2619,7 +2622,7 @@ export function collectClosureWasmImages(vfs, bundle, unstagedPaths) {
|
|
|
2619
2622
|
* budget is shared, so ordering by size maximizes the number of misses a
|
|
2620
2623
|
* fixed number of bytes repairs.
|
|
2621
2624
|
*/
|
|
2622
|
-
export function addObservedReads(vfs, observed, bundle, requiredPaths, budgetState) {
|
|
2625
|
+
export async function addObservedReads(vfs, observed, bundle, requiredPaths, budgetState) {
|
|
2623
2626
|
if (!observed || observed.size === 0)
|
|
2624
2627
|
return { added: 0 };
|
|
2625
2628
|
const candidates = [];
|
|
@@ -2628,7 +2631,7 @@ export function addObservedReads(vfs, observed, bundle, requiredPaths, budgetSta
|
|
|
2628
2631
|
continue;
|
|
2629
2632
|
let stat;
|
|
2630
2633
|
try {
|
|
2631
|
-
stat = vfs.lstat(path);
|
|
2634
|
+
stat = (await vfs.lstat(path));
|
|
2632
2635
|
}
|
|
2633
2636
|
catch {
|
|
2634
2637
|
continue;
|
|
@@ -2646,7 +2649,7 @@ export function addObservedReads(vfs, observed, bundle, requiredPaths, budgetSta
|
|
|
2646
2649
|
continue;
|
|
2647
2650
|
let content;
|
|
2648
2651
|
try {
|
|
2649
|
-
content = _readBundleCell(vfs, candidate.path);
|
|
2652
|
+
content = (await _readBundleCell(vfs, candidate.path));
|
|
2650
2653
|
}
|
|
2651
2654
|
catch {
|
|
2652
2655
|
continue;
|
|
@@ -2747,7 +2750,7 @@ function binPackageRootSegment(pkgRoot, path) {
|
|
|
2747
2750
|
function binPackageRelativePath(pkgRoot, path) {
|
|
2748
2751
|
return path.startsWith(pkgRoot + '/') ? path.slice(pkgRoot.length + 1) : path;
|
|
2749
2752
|
}
|
|
2750
|
-
function addCwdProjectFiles(vfs, cwd, bundle, budgetState) {
|
|
2753
|
+
async function addCwdProjectFiles(vfs, cwd, bundle, budgetState) {
|
|
2751
2754
|
const root = (cwd || '/home/user').replace(/^\/+/, '').replace(/\/+$/, '') || 'home/user';
|
|
2752
2755
|
const MAX_PROJECT_FILES = 512;
|
|
2753
2756
|
const SKIP_DIRS = new Set(['node_modules', '.git', '.nimbus']);
|
|
@@ -2758,7 +2761,7 @@ function addCwdProjectFiles(vfs, cwd, bundle, budgetState) {
|
|
|
2758
2761
|
const dir = queue.shift();
|
|
2759
2762
|
let entries;
|
|
2760
2763
|
try {
|
|
2761
|
-
entries = vfs.readdir(dir);
|
|
2764
|
+
entries = (await vfs.readdir(dir));
|
|
2762
2765
|
}
|
|
2763
2766
|
catch {
|
|
2764
2767
|
continue;
|
|
@@ -2785,11 +2788,11 @@ function addCwdProjectFiles(vfs, cwd, bundle, budgetState) {
|
|
|
2785
2788
|
// Skip an oversized file before reading it: this walk guesses at what
|
|
2786
2789
|
// the program might read, and one guess must not spend the budget (or
|
|
2787
2790
|
// the supervisor's headroom) that every later invocation then carries.
|
|
2788
|
-
if (!_bundleAdmits(vfs, child, budgetState, CWD_SNAPSHOT_MAX_FILE_BYTES))
|
|
2791
|
+
if (!(await _bundleAdmits(vfs, child, budgetState, CWD_SNAPSHOT_MAX_FILE_BYTES)))
|
|
2789
2792
|
continue;
|
|
2790
2793
|
let content;
|
|
2791
2794
|
try {
|
|
2792
|
-
content = _readBundleCell(vfs, child);
|
|
2795
|
+
content = (await _readBundleCell(vfs, child));
|
|
2793
2796
|
}
|
|
2794
2797
|
catch {
|
|
2795
2798
|
continue;
|
|
@@ -2832,7 +2835,7 @@ function addCwdProjectFiles(vfs, cwd, bundle, budgetState) {
|
|
|
2832
2835
|
* / openSync / readFile in source are scanned. Entry code is always
|
|
2833
2836
|
* scanned (it's the user's intent).
|
|
2834
2837
|
*/
|
|
2835
|
-
function addEntryAbsPathReads(vfs, entryCode, bundle, budgetState) {
|
|
2838
|
+
async function addEntryAbsPathReads(vfs, entryCode, bundle, budgetState) {
|
|
2836
2839
|
let added = 0;
|
|
2837
2840
|
// Capture absolute-path string literals. The path may not contain
|
|
2838
2841
|
// the quote char; the surrounding regex strips line/block comments
|
|
@@ -2841,7 +2844,7 @@ function addEntryAbsPathReads(vfs, entryCode, bundle, budgetState) {
|
|
|
2841
2844
|
// excludes spaces, glob chars, template syntax — all dynamic.
|
|
2842
2845
|
const RX = /(['"`])(\/[A-Za-z0-9._\-\/]{1,510})\1/g;
|
|
2843
2846
|
const REJECT_PREFIX = /^\/(proc|sys|dev|lib|lib64|boot|root)(\/|$)/;
|
|
2844
|
-
function tryAdd(absPath) {
|
|
2847
|
+
async function tryAdd(absPath) {
|
|
2845
2848
|
if (!absPath || absPath.length < 2 || absPath.length > 512)
|
|
2846
2849
|
return;
|
|
2847
2850
|
if (REJECT_PREFIX.test(absPath))
|
|
@@ -2854,12 +2857,12 @@ function addEntryAbsPathReads(vfs, entryCode, bundle, budgetState) {
|
|
|
2854
2857
|
if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
|
|
2855
2858
|
return;
|
|
2856
2859
|
try {
|
|
2857
|
-
if (!vfs.exists(stripped) || vfs.isDirectory(stripped))
|
|
2860
|
+
if (!(await vfs.exists(stripped)) || (await vfs.isDirectory(stripped)))
|
|
2858
2861
|
return;
|
|
2859
|
-
if (!_bundleAdmits(vfs, stripped, budgetState))
|
|
2862
|
+
if (!(await _bundleAdmits(vfs, stripped, budgetState)))
|
|
2860
2863
|
return;
|
|
2861
2864
|
// hardening-r5: preserve binary content as Uint8Array.
|
|
2862
|
-
const content = _readBundleCell(vfs, stripped);
|
|
2865
|
+
const content = (await _readBundleCell(vfs, stripped));
|
|
2863
2866
|
const cellLen = _bundleCellLength(content);
|
|
2864
2867
|
if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
|
|
2865
2868
|
return;
|
|
@@ -2870,7 +2873,7 @@ function addEntryAbsPathReads(vfs, entryCode, bundle, budgetState) {
|
|
|
2870
2873
|
}
|
|
2871
2874
|
catch { /* swallow — file may be binary, race-deleted, etc. */ }
|
|
2872
2875
|
}
|
|
2873
|
-
function scanOne(src) {
|
|
2876
|
+
async function scanOne(src) {
|
|
2874
2877
|
if (!src)
|
|
2875
2878
|
return;
|
|
2876
2879
|
// Strip line + block comments so we don't match commented-out reads.
|
|
@@ -2883,11 +2886,11 @@ function addEntryAbsPathReads(vfs, entryCode, bundle, budgetState) {
|
|
|
2883
2886
|
// forbids most). The check on the captured group is cheap.
|
|
2884
2887
|
if (/[\?\*\[\]\{\}]/.test(literal))
|
|
2885
2888
|
continue;
|
|
2886
|
-
tryAdd(literal);
|
|
2889
|
+
(await tryAdd(literal));
|
|
2887
2890
|
}
|
|
2888
2891
|
}
|
|
2889
2892
|
// Always scan entry code.
|
|
2890
|
-
scanOne(entryCode);
|
|
2893
|
+
(await scanOne(entryCode));
|
|
2891
2894
|
// Optionally scan bundled JS sources too — useful for transitive cases
|
|
2892
2895
|
// where a require'd module hardcodes an absolute path. Use the same
|
|
2893
2896
|
// budget-state so we don't blow caps.
|
|
@@ -2903,7 +2906,7 @@ function addEntryAbsPathReads(vfs, entryCode, bundle, budgetState) {
|
|
|
2903
2906
|
const cell = bundle[k];
|
|
2904
2907
|
if (typeof cell !== 'string')
|
|
2905
2908
|
continue;
|
|
2906
|
-
scanOne(cell);
|
|
2909
|
+
(await scanOne(cell));
|
|
2907
2910
|
}
|
|
2908
2911
|
return { added };
|
|
2909
2912
|
}
|
|
@@ -3231,9 +3234,10 @@ export async function buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbui
|
|
|
3231
3234
|
async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bundleProfile = DEFAULT_FACET_BUNDLE_PROFILE, observedReads, pacer, isolatedTransform, maxBundleBytes = VFS_BUNDLE_MAX_BYTES) {
|
|
3232
3235
|
// Read the cursor BEFORE the walk: a mutation that lands while the bundle
|
|
3233
3236
|
// is being assembled must be reported as invalidated, not silently missed.
|
|
3234
|
-
const
|
|
3237
|
+
const admitted = await vfs.authority.acquire(null, 0);
|
|
3238
|
+
const cursor = { epoch: admitted.epoch, rev: admitted.rev };
|
|
3235
3239
|
// 1. Static reachable-set walk from entry.
|
|
3236
|
-
const prefetch = prefetchForRequire(vfs, entryCode || '', cwd, scriptPath, maxBundleBytes);
|
|
3240
|
+
const prefetch = (await prefetchForRequire(vfs, entryCode || '', cwd, scriptPath, maxBundleBytes));
|
|
3237
3241
|
if ('kind' in prefetch) {
|
|
3238
3242
|
// A required closure larger than the bound can never launch as a
|
|
3239
3243
|
// snapshot. Surface it as the process's own failure rather than a
|
|
@@ -3263,14 +3267,14 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
|
|
|
3263
3267
|
// admitted first, ahead of every guess, and they join the required set
|
|
3264
3268
|
// rather than the evictable one, because a file evicted here misses
|
|
3265
3269
|
// again on the next run and the loop never closes.
|
|
3266
|
-
const observedAdd = addObservedReads(vfs, observedReads, bundle, requiredPaths, budgetState);
|
|
3270
|
+
const observedAdd = (await addObservedReads(vfs, observedReads, bundle, requiredPaths, budgetState));
|
|
3267
3271
|
void observedAdd;
|
|
3268
3272
|
await paceAfterPass();
|
|
3269
3273
|
// 2. Greedy oversample — every installed pkg's pkg.json + main.
|
|
3270
3274
|
// Catches dynamic-require / `bindings()` / plugin-loader cases the
|
|
3271
3275
|
// regex prefetch misses. Its budget is independent from the complete
|
|
3272
3276
|
// static require closure, which is correctness-critical.
|
|
3273
|
-
const greedy = greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPaths);
|
|
3277
|
+
const greedy = (await greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPaths));
|
|
3274
3278
|
await paceAfterPass();
|
|
3275
3279
|
// 2.25 X.5-Z3: static-readFileSync asset prefetch. Scans every
|
|
3276
3280
|
// bundle .js/.mjs/.cjs source for the canonical jsdom shape:
|
|
@@ -3281,7 +3285,7 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
|
|
|
3281
3285
|
// `default-stylesheet.css` (and similar runtime asset reads in
|
|
3282
3286
|
// tldts, parse5, lookup-table packages, mime-db, etc.) ENOENT
|
|
3283
3287
|
// at facet runtime even though the file is on VFS-disk + in
|
|
3284
|
-
const assetAdd = addStaticReadFileAssets(vfs, cwd, bundle, budgetState);
|
|
3288
|
+
const assetAdd = (await addStaticReadFileAssets(vfs, cwd, bundle, budgetState));
|
|
3285
3289
|
void assetAdd;
|
|
3286
3290
|
await paceAfterPass();
|
|
3287
3291
|
// 2.27 X.5-U: dotfile + SWC-shape readFileSync sentinel prefetch.
|
|
@@ -3291,7 +3295,7 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
|
|
|
3291
3295
|
// pattern AND filenames outside the Z3 ASSET_EXT whitelist
|
|
3292
3296
|
// (dotfiles, no-extension sentinels, "digest/hash/version/sha/md5"
|
|
3293
3297
|
// shapes). Motivating case: ts-jest's `.ts-jest-digest`. See
|
|
3294
|
-
const dotAdd = addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, budgetState);
|
|
3298
|
+
const dotAdd = (await addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, budgetState));
|
|
3295
3299
|
void dotAdd;
|
|
3296
3300
|
await paceAfterPass();
|
|
3297
3301
|
// 2.30 G3 (runtime-pkg wave): bin-target sibling oversample. Pulls
|
|
@@ -3302,14 +3306,14 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
|
|
|
3302
3306
|
// catches custom extensions (.cow, .pem, .ttf, .wasm bundled
|
|
3303
3307
|
// as data, etc.) without needing a per-pkg whitelist.
|
|
3304
3308
|
// No-op when entry isn't inside node_modules.
|
|
3305
|
-
const binSiblingAdd = addBinTargetSiblings(vfs, scriptPath, bundle, budgetState, bundleProfile);
|
|
3309
|
+
const binSiblingAdd = (await addBinTargetSiblings(vfs, scriptPath, bundle, budgetState, bundleProfile));
|
|
3306
3310
|
await paceAfterPass();
|
|
3307
3311
|
// 2.34 project-data snapshot: sync Node fs cannot await the
|
|
3308
3312
|
// supervisor. Include a bounded snapshot of the current working tree
|
|
3309
3313
|
// for common relative project-file reads while skipping dependency
|
|
3310
3314
|
// and Nimbus cache directories. Async fs still uses live supervisor
|
|
3311
3315
|
// reads and child-process staleness fallback.
|
|
3312
|
-
const cwdProjectAdd = addCwdProjectFiles(vfs, cwd, bundle, budgetState);
|
|
3316
|
+
const cwdProjectAdd = (await addCwdProjectFiles(vfs, cwd, bundle, budgetState));
|
|
3313
3317
|
void cwdProjectAdd;
|
|
3314
3318
|
await paceAfterPass();
|
|
3315
3319
|
// 2.35 shell compatibility: absolute-path readFileSync scanner.
|
|
@@ -3336,7 +3340,7 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
|
|
|
3336
3340
|
// - Byte/file budget guards identical to other passes.
|
|
3337
3341
|
// - Skip paths under known-untrusted prefixes (`/proc`, `/sys`,
|
|
3338
3342
|
// `/dev` — we don't have these mounts; they'd never resolve).
|
|
3339
|
-
const absScanAdd = addEntryAbsPathReads(vfs, entryCode || '', bundle, budgetState);
|
|
3343
|
+
const absScanAdd = (await addEntryAbsPathReads(vfs, entryCode || '', bundle, budgetState));
|
|
3340
3344
|
void absScanAdd;
|
|
3341
3345
|
await paceAfterPass();
|
|
3342
3346
|
// 2.5 W3.5 Fix B: ESM→CJS transform pass. Walks `bundle`, sniffs each
|
|
@@ -3385,7 +3389,7 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
|
|
|
3385
3389
|
// from content cap so fs.readdirSync remains honest even if the
|
|
3386
3390
|
// content for a given file was capped out.
|
|
3387
3391
|
await paceAfterPass();
|
|
3388
|
-
const manifest = buildManifest(vfs, cwd, scriptPath);
|
|
3392
|
+
const manifest = (await buildManifest(vfs, cwd, scriptPath));
|
|
3389
3393
|
await paceAfterPass();
|
|
3390
3394
|
// 4. JSON-encoded-size guard, measured in UTF-8 bytes (not UTF-16 code
|
|
3391
3395
|
// units) because that is what workerd charges against the per-module
|
|
@@ -3418,8 +3422,8 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
|
|
|
3418
3422
|
}
|
|
3419
3423
|
}
|
|
3420
3424
|
const fileCount = Object.keys(bundle).length;
|
|
3421
|
-
const metadata = buildVfsMetadata(vfs, manifest, bundle);
|
|
3422
|
-
addUnreadableDenialCells(vfs, bundle, metadata);
|
|
3425
|
+
const metadata = (await buildVfsMetadata(vfs, manifest, bundle));
|
|
3426
|
+
(await addUnreadableDenialCells(vfs, bundle, metadata));
|
|
3423
3427
|
await paceAfterPass();
|
|
3424
3428
|
// Denial cells land after the eviction pass, so account for them before
|
|
3425
3429
|
// deciding where the bundle has to live.
|
|
@@ -3428,7 +3432,7 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
|
|
|
3428
3432
|
const bundleSideModulesRequired = size.bytes > BUNDLE_MAX_ENCODED_BYTES;
|
|
3429
3433
|
// Suppress lint: `greedy.added` is observed only via diagnostics.
|
|
3430
3434
|
void greedy;
|
|
3431
|
-
const wasmImages = collectClosureWasmImages(vfs, bundle, binSiblingAdd.wasmPaths);
|
|
3435
|
+
const wasmImages = (await collectClosureWasmImages(vfs, bundle, binSiblingAdd.wasmPaths));
|
|
3432
3436
|
return {
|
|
3433
3437
|
bundle,
|
|
3434
3438
|
manifest,
|
|
@@ -3471,6 +3475,7 @@ export class FacetManager {
|
|
|
3471
3475
|
processes;
|
|
3472
3476
|
portRegistry;
|
|
3473
3477
|
vfs = null;
|
|
3478
|
+
filesystem = null;
|
|
3474
3479
|
hooks;
|
|
3475
3480
|
/**
|
|
3476
3481
|
* The resident-process scheduler (loaders/process-fabric.ts). Every
|
|
@@ -3507,6 +3512,8 @@ export class FacetManager {
|
|
|
3507
3512
|
* journal recovery rides the first pump.
|
|
3508
3513
|
*/
|
|
3509
3514
|
launchPump;
|
|
3515
|
+
launchTasks = new Set();
|
|
3516
|
+
launchesClosed = false;
|
|
3510
3517
|
// attach-pid → serve-pid: the resident serve facet a bare-`opencode` dual
|
|
3511
3518
|
// spawn created as an OS-child of the attach TUI. When the attach process
|
|
3512
3519
|
// exits (reported / killed), its serve facet is torn down with it.
|
|
@@ -3610,7 +3617,7 @@ export class FacetManager {
|
|
|
3610
3617
|
// Rooted on waitUntil: the hook fires synchronously inside whatever turn
|
|
3611
3618
|
// ended the process, and the delete must not be a floating promise there.
|
|
3612
3619
|
this.processes.setOnTerminal((pid) => {
|
|
3613
|
-
this.ctx.waitUntil(this._onResidentTerminal(pid));
|
|
3620
|
+
this.ctx.waitUntil(this.trackLaunchTask(this._onResidentTerminal(pid)));
|
|
3614
3621
|
});
|
|
3615
3622
|
}
|
|
3616
3623
|
/**
|
|
@@ -3630,7 +3637,7 @@ export class FacetManager {
|
|
|
3630
3637
|
const key = `${FENCED_WORK_KEY_PREFIX}${pid}`;
|
|
3631
3638
|
const row = (await this.launchJournal.rows()).get(key);
|
|
3632
3639
|
const crashed = entry?.state === 'exited' && (entry.exitCode ?? 0) !== 0;
|
|
3633
|
-
if (row === undefined || row.restart !== 'on-failure' || !crashed) {
|
|
3640
|
+
if (this.launchesClosed || row === undefined || row.restart !== 'on-failure' || !crashed) {
|
|
3634
3641
|
await this.launchJournal.release(pid);
|
|
3635
3642
|
return;
|
|
3636
3643
|
}
|
|
@@ -3704,7 +3711,10 @@ export class FacetManager {
|
|
|
3704
3711
|
});
|
|
3705
3712
|
return next;
|
|
3706
3713
|
}
|
|
3707
|
-
|
|
3714
|
+
/** The authority is the caller's: a session composes exactly one, and the
|
|
3715
|
+
* manager credentials its processes through that one rather than a second
|
|
3716
|
+
* authority over the same disk. */
|
|
3717
|
+
setVfs(vfs, filesystem) { this.vfs = vfs; this.filesystem = filesystem; }
|
|
3708
3718
|
/**
|
|
3709
3719
|
* The env/ctx pair every loader-backed runtime builds its facet pools
|
|
3710
3720
|
* from. A pool is constructed from exactly these two, so the manager
|
|
@@ -3804,15 +3814,17 @@ export class FacetManager {
|
|
|
3804
3814
|
* be read is left out; the program's compile then meets the seam's own
|
|
3805
3815
|
* refusal, which names the module.
|
|
3806
3816
|
*/
|
|
3807
|
-
_wasmModulesByValue(entry, wasmImports) {
|
|
3817
|
+
async _wasmModulesByValue(entry, wasmImports) {
|
|
3808
3818
|
const modules = {};
|
|
3809
3819
|
if (!this.vfs || wasmImports.length === 0)
|
|
3810
3820
|
return modules;
|
|
3811
|
-
|
|
3821
|
+
if (!this.filesystem)
|
|
3822
|
+
throw new Error('Process filesystem authority is not initialized');
|
|
3823
|
+
const vfs = new ExecutionFs(this.filesystem.bind({ pid: entry.pid, cred: entry.cred }));
|
|
3812
3824
|
for (const image of wasmImports) {
|
|
3813
3825
|
let bytes;
|
|
3814
3826
|
try {
|
|
3815
|
-
bytes = vfs.readFile(stripLeadingSlashes(image.vfsPath));
|
|
3827
|
+
bytes = (await vfs.readFile(stripLeadingSlashes(image.vfsPath)));
|
|
3816
3828
|
}
|
|
3817
3829
|
catch {
|
|
3818
3830
|
continue;
|
|
@@ -3835,12 +3847,14 @@ export class FacetManager {
|
|
|
3835
3847
|
if (!this.esbuild)
|
|
3836
3848
|
this.esbuild = new EsbuildService(this.vfs.as(CRED_KERNEL));
|
|
3837
3849
|
this.imageStore.ensureDir();
|
|
3838
|
-
|
|
3850
|
+
if (!this.filesystem)
|
|
3851
|
+
throw new Error('Process filesystem authority is not initialized');
|
|
3852
|
+
const vfs = new ExecutionFs(this.filesystem.bind({ pid: entry.pid, cred: entry.cred }));
|
|
3839
3853
|
const { cred } = entry;
|
|
3840
3854
|
const profile = spec.bundleProfile ?? DEFAULT_FACET_BUNDLE_PROFILE;
|
|
3841
3855
|
const credKey = `${cred.uid}:${cred.gid}:${cred.groups.join(',')}`;
|
|
3842
3856
|
const key = `${profile}\x00${credKey}\x00${spec.cwd}\x00${spec.scriptPath ?? ''}\x00${_fnv1a(spec.entryCode)}`;
|
|
3843
|
-
const revision = vfs.revision();
|
|
3857
|
+
const revision = (await vfs.revision());
|
|
3844
3858
|
// An entry built at an older revision can never be SERVED again — the
|
|
3845
3859
|
// lookup below requires an exact match — so from the first write after it
|
|
3846
3860
|
// was admitted it is retained garbage, held in the isolate that is
|
|
@@ -4255,7 +4269,7 @@ export class FacetManager {
|
|
|
4255
4269
|
// images ride by value: read here, inside the scope that holds
|
|
4256
4270
|
// the map, and compiled by the loader like the sqlite sidecar.
|
|
4257
4271
|
const wasmImports = facetWasmImports([], vfsState.wasmImages ?? []);
|
|
4258
|
-
const wasmModules = this._wasmModulesByValue(entry, wasmImports);
|
|
4272
|
+
const wasmModules = (await this._wasmModulesByValue(entry, wasmImports));
|
|
4259
4273
|
const generatedWorker = await generateEntrypointCode(code, vfsState, usesSqlite, shims, wasmImports);
|
|
4260
4274
|
if (diagSink) {
|
|
4261
4275
|
diagSink.moduleMapBytes = _encodedSourceBytes(generatedWorker.code);
|
|
@@ -4420,7 +4434,7 @@ export class FacetManager {
|
|
|
4420
4434
|
// other writes flush live through the SUPERVISOR RPC bridge.
|
|
4421
4435
|
if (this.vfs)
|
|
4422
4436
|
this.imageStore.ensureDir();
|
|
4423
|
-
const processVfs = this.
|
|
4437
|
+
const processVfs = this.filesystem ? new ExecutionFs(this.filesystem.bind({ pid: entry.pid, cred: entry.cred })) : null;
|
|
4424
4438
|
const vfsState = processVfs
|
|
4425
4439
|
? await buildPrefetchBundle(processVfs, undefined, opts.cwd, '', this.esbuild || undefined, DEFAULT_FACET_BUNDLE_PROFILE, undefined, undefined, this.hooks.transformLargeEsm)
|
|
4426
4440
|
: { bundle: {}, manifest: {}, metadata: {}, reachableCount: 0, truncated: false };
|
|
@@ -4729,6 +4743,18 @@ export class FacetManager {
|
|
|
4729
4743
|
pumpResidentLaunches() {
|
|
4730
4744
|
return this.launchPump.pump();
|
|
4731
4745
|
}
|
|
4746
|
+
trackLaunchTask(task) {
|
|
4747
|
+
this.launchTasks.add(task);
|
|
4748
|
+
void task.then(() => this.launchTasks.delete(task), () => this.launchTasks.delete(task));
|
|
4749
|
+
return task;
|
|
4750
|
+
}
|
|
4751
|
+
async closeLaunches() {
|
|
4752
|
+
this.launchesClosed = true;
|
|
4753
|
+
this.launchPump.close();
|
|
4754
|
+
while (this.launchTasks.size > 0)
|
|
4755
|
+
await Promise.allSettled([...this.launchTasks]);
|
|
4756
|
+
await Promise.all(this.rowAmendments.values());
|
|
4757
|
+
}
|
|
4732
4758
|
/** Whether any launch is suspended waiting for a turn. */
|
|
4733
4759
|
get hasPendingLaunchTurns() {
|
|
4734
4760
|
return this.launchPump.hasPending;
|
|
@@ -4998,7 +5024,12 @@ export class FacetManager {
|
|
|
4998
5024
|
* terminal WebSocket needs to survive a launch, and what no amount of making
|
|
4999
5025
|
* the launch faster would have given it.
|
|
5000
5026
|
*/
|
|
5001
|
-
|
|
5027
|
+
_runResidentLaunch(entry, code, command, opts, attempt) {
|
|
5028
|
+
if (this.launchesClosed)
|
|
5029
|
+
return Promise.reject(new Error('Launch scheduler is closed'));
|
|
5030
|
+
return this.trackLaunchTask(this._runResidentLaunchBody(entry, code, command, opts, attempt));
|
|
5031
|
+
}
|
|
5032
|
+
async _runResidentLaunchBody(entry, code, command, opts, attempt) {
|
|
5002
5033
|
const cwd = opts.cwd || '/home/user';
|
|
5003
5034
|
const pacer = this._launchPacer(entry.pid);
|
|
5004
5035
|
// Journalled before the first byte of work. A launch that FAILS deletes
|