sandboxedjs 0.1.94 → 0.1.95
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/bin/sandboxedjs-pack.mjs +363 -0
- package/dist/agent.d.cts +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/{container-WEYRUg6Q.d.ts → container-CFieZcbe.d.ts} +9 -0
- package/dist/{container-C3cgcnXZ.d.cts → container-CPwyHoUy.d.cts} +9 -0
- package/dist/index.cjs +764 -60
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +764 -60
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -7003,8 +7003,8 @@ var WasiHost = class {
|
|
|
7003
7003
|
view.setUint16(at + 8, WASI_ESUCCESS, true);
|
|
7004
7004
|
view.setUint8(at + 10, subscription.type);
|
|
7005
7005
|
if (subscription.type === EVENTTYPE_FD_READ) {
|
|
7006
|
-
const
|
|
7007
|
-
view.setBigUint64(at + 16, BigInt(
|
|
7006
|
+
const available2 = subscription.fd === 0 ? this.opts.stdin.available : this.bytesLeft(subscription.fd);
|
|
7007
|
+
view.setBigUint64(at + 16, BigInt(available2), true);
|
|
7008
7008
|
} else if (subscription.type === EVENTTYPE_FD_WRITE) {
|
|
7009
7009
|
view.setBigUint64(at + 16, BigInt(64 * 1024), true);
|
|
7010
7010
|
}
|
|
@@ -7241,13 +7241,13 @@ var BinaryExecutionRegistry = class {
|
|
|
7241
7241
|
}
|
|
7242
7242
|
async run(ctx, path) {
|
|
7243
7243
|
const bytes2 = ctx.vfs.readFile(path, ctx.cred);
|
|
7244
|
-
let
|
|
7244
|
+
let info2;
|
|
7245
7245
|
try {
|
|
7246
|
-
|
|
7246
|
+
info2 = inspectElf(bytes2);
|
|
7247
7247
|
} catch (error) {
|
|
7248
7248
|
return ctx.fail(String(error), 126);
|
|
7249
7249
|
}
|
|
7250
|
-
const request = { path, bytes: bytes2, info, sha256: binaryDigest(bytes2) };
|
|
7250
|
+
const request = { path, bytes: bytes2, info: info2, sha256: binaryDigest(bytes2) };
|
|
7251
7251
|
const reasons = [];
|
|
7252
7252
|
const backends = [...this.backends];
|
|
7253
7253
|
for (const tier of ["compatibility", "translation", "emulation"]) {
|
|
@@ -7266,7 +7266,7 @@ var BinaryExecutionRegistry = class {
|
|
|
7266
7266
|
reasons.push(`${backend.id}: ${result.reason}`);
|
|
7267
7267
|
}
|
|
7268
7268
|
}
|
|
7269
|
-
return ctx.fail(`cannot execute ${
|
|
7269
|
+
return ctx.fail(`cannot execute ${info2.architecture} ELF: ${reasons.join("; ") || "no binary backends registered"}`, 126);
|
|
7270
7270
|
}
|
|
7271
7271
|
};
|
|
7272
7272
|
function checkedWasm(artifact) {
|
|
@@ -8530,8 +8530,8 @@ var NetworkStack = class {
|
|
|
8530
8530
|
return this.ifaces.some((i) => i.ipv4 === addr);
|
|
8531
8531
|
}
|
|
8532
8532
|
// ── listening ports ──────────────────────────────────────────────────────
|
|
8533
|
-
registerListener(port,
|
|
8534
|
-
this.listeners.set(port, { port, since: Date.now(), ...
|
|
8533
|
+
registerListener(port, info2) {
|
|
8534
|
+
this.listeners.set(port, { port, since: Date.now(), ...info2 });
|
|
8535
8535
|
}
|
|
8536
8536
|
unregisterListener(port) {
|
|
8537
8537
|
this.listeners.delete(port);
|
|
@@ -12124,21 +12124,21 @@ var uname = defineCommand({
|
|
|
12124
12124
|
{ short: "i", long: "hardware-platform" },
|
|
12125
12125
|
{ short: "o", long: "operating-system" }
|
|
12126
12126
|
]);
|
|
12127
|
-
const
|
|
12127
|
+
const info2 = unameInfo(ctx.kernel.hostname);
|
|
12128
12128
|
if (args.has("all")) {
|
|
12129
|
-
ctx.line(`${
|
|
12129
|
+
ctx.line(`${info2.sysname} ${info2.nodename} ${info2.release} ${info2.version} ${info2.machine} ${info2.operatingSystem}`);
|
|
12130
12130
|
return 0;
|
|
12131
12131
|
}
|
|
12132
12132
|
const parts = [];
|
|
12133
|
-
if (args.has("kernel-name")) parts.push(
|
|
12134
|
-
if (args.has("nodename")) parts.push(
|
|
12135
|
-
if (args.has("kernel-release")) parts.push(
|
|
12136
|
-
if (args.has("kernel-version")) parts.push(
|
|
12137
|
-
if (args.has("machine")) parts.push(
|
|
12138
|
-
if (args.has("processor")) parts.push(
|
|
12139
|
-
if (args.has("hardware-platform")) parts.push(
|
|
12140
|
-
if (args.has("operating-system")) parts.push(
|
|
12141
|
-
ctx.line(parts.length ? parts.join(" ") :
|
|
12133
|
+
if (args.has("kernel-name")) parts.push(info2.sysname);
|
|
12134
|
+
if (args.has("nodename")) parts.push(info2.nodename);
|
|
12135
|
+
if (args.has("kernel-release")) parts.push(info2.release);
|
|
12136
|
+
if (args.has("kernel-version")) parts.push(info2.version);
|
|
12137
|
+
if (args.has("machine")) parts.push(info2.machine);
|
|
12138
|
+
if (args.has("processor")) parts.push(info2.processor);
|
|
12139
|
+
if (args.has("hardware-platform")) parts.push(info2.hardwarePlatform);
|
|
12140
|
+
if (args.has("operating-system")) parts.push(info2.operatingSystem);
|
|
12141
|
+
ctx.line(parts.length ? parts.join(" ") : info2.sysname);
|
|
12142
12142
|
return 0;
|
|
12143
12143
|
}
|
|
12144
12144
|
});
|
|
@@ -15122,15 +15122,15 @@ var Interpreter = class {
|
|
|
15122
15122
|
return this.exitCode;
|
|
15123
15123
|
}
|
|
15124
15124
|
};
|
|
15125
|
-
function splitRecord(
|
|
15126
|
-
if (
|
|
15125
|
+
function splitRecord(record2, fs) {
|
|
15126
|
+
if (record2 === "") return [];
|
|
15127
15127
|
if (fs === " ") {
|
|
15128
|
-
const trimmed =
|
|
15128
|
+
const trimmed = record2.replace(/^[ \t\n]+|[ \t\n]+$/g, "");
|
|
15129
15129
|
return trimmed === "" ? [] : trimmed.split(/[ \t\n]+/);
|
|
15130
15130
|
}
|
|
15131
|
-
if (fs === "") return [...
|
|
15132
|
-
if (fs.length === 1 && !"\\^$.[]|()*+?{}".includes(fs)) return
|
|
15133
|
-
return
|
|
15131
|
+
if (fs === "") return [...record2];
|
|
15132
|
+
if (fs.length === 1 && !"\\^$.[]|()*+?{}".includes(fs)) return record2.split(fs);
|
|
15133
|
+
return record2.split(new RegExp(translateEre(fs)));
|
|
15134
15134
|
}
|
|
15135
15135
|
var ERE_CLASSES = {
|
|
15136
15136
|
alpha: "A-Za-z",
|
|
@@ -20958,7 +20958,7 @@ var wheels_default = {
|
|
|
20958
20958
|
|
|
20959
20959
|
// package.json
|
|
20960
20960
|
var package_default = {
|
|
20961
|
-
version: "0.1.
|
|
20961
|
+
version: "0.1.95"};
|
|
20962
20962
|
|
|
20963
20963
|
// src/python/extension-abi.ts
|
|
20964
20964
|
var EXTENSION_ABI = {
|
|
@@ -23686,7 +23686,7 @@ async function resolve2(options) {
|
|
|
23686
23686
|
const clauses = (state.constraints.get(name) ?? []).flatMap((entry) => entry.spec.map((s) => `${s.op}${s.version}`)).sort();
|
|
23687
23687
|
return `${name}|${[...new Set(clauses)].join(",")}`;
|
|
23688
23688
|
};
|
|
23689
|
-
const
|
|
23689
|
+
const search2 = async (queue, state) => {
|
|
23690
23690
|
checkBudget();
|
|
23691
23691
|
if (queue.length === 0) return state;
|
|
23692
23692
|
const [edge, ...rest] = queue;
|
|
@@ -23694,7 +23694,7 @@ async function resolve2(options) {
|
|
|
23694
23694
|
const name = requirement.name;
|
|
23695
23695
|
if (!markerApplies(requirement.marker, environment, extras)) {
|
|
23696
23696
|
if (requirement.marker) skipped.push({ name, marker: requirement.marker });
|
|
23697
|
-
return
|
|
23697
|
+
return search2(rest, state);
|
|
23698
23698
|
}
|
|
23699
23699
|
const previous = state.constraints.get(name) ?? [];
|
|
23700
23700
|
const constraints = new Map(state.constraints);
|
|
@@ -23711,7 +23711,7 @@ async function resolve2(options) {
|
|
|
23711
23711
|
explain(name, next.constraints, already.version)
|
|
23712
23712
|
);
|
|
23713
23713
|
}
|
|
23714
|
-
return
|
|
23714
|
+
return search2(rest, next);
|
|
23715
23715
|
}
|
|
23716
23716
|
const signature = signatureOf(name, next);
|
|
23717
23717
|
const learned = nogoods.get(signature);
|
|
@@ -23774,7 +23774,7 @@ async function resolve2(options) {
|
|
|
23774
23774
|
const chosen = new Map(next.chosen);
|
|
23775
23775
|
chosen.set(name, { ...candidate, requires });
|
|
23776
23776
|
try {
|
|
23777
|
-
return await
|
|
23777
|
+
return await search2([...edges, ...rest], { chosen, constraints: next.constraints });
|
|
23778
23778
|
} catch (error) {
|
|
23779
23779
|
if (!(error instanceof ResolutionError)) throw error;
|
|
23780
23780
|
if (/cancelled|budget/.test(error.message)) throw error;
|
|
@@ -23789,12 +23789,12 @@ async function resolve2(options) {
|
|
|
23789
23789
|
if (specifiers.length === 0 || !markerApplies(marker, environment, root.extras)) continue;
|
|
23790
23790
|
seeded.set(name, [...seeded.get(name) ?? [], { spec: specifiers, origin: root.origin }]);
|
|
23791
23791
|
}
|
|
23792
|
-
const solved = await
|
|
23792
|
+
const solved = await search2(roots, { chosen: /* @__PURE__ */ new Map(), constraints: seeded });
|
|
23793
23793
|
const distributions = [...solved.chosen.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
23794
23794
|
const reported = new Map(skipped.map((s) => [`${s.name}\0${s.marker}`, s]));
|
|
23795
23795
|
return { distributions, skipped: [...reported.values()] };
|
|
23796
23796
|
}
|
|
23797
|
-
function explain(name, constraints, chosenVersion,
|
|
23797
|
+
function explain(name, constraints, chosenVersion, available2 = []) {
|
|
23798
23798
|
const lines = [];
|
|
23799
23799
|
for (const entry of constraints.get(name) ?? []) {
|
|
23800
23800
|
if (entry.spec.length === 0) continue;
|
|
@@ -23802,9 +23802,9 @@ function explain(name, constraints, chosenVersion, available = []) {
|
|
|
23802
23802
|
lines.push(`${entry.origin} requires ${name} ${text2}`);
|
|
23803
23803
|
}
|
|
23804
23804
|
if (chosenVersion) lines.push(`but ${name} ${chosenVersion} is already selected`);
|
|
23805
|
-
if (
|
|
23806
|
-
const versions = [...new Set(
|
|
23807
|
-
lines.push(`usable versions: ${versions.join(", ")}${
|
|
23805
|
+
if (available2.length > 0) {
|
|
23806
|
+
const versions = [...new Set(available2.map((c) => c.version))].slice(0, 8);
|
|
23807
|
+
lines.push(`usable versions: ${versions.join(", ")}${available2.length > 8 ? ", ..." : ""}`);
|
|
23808
23808
|
}
|
|
23809
23809
|
return lines;
|
|
23810
23810
|
}
|
|
@@ -26401,9 +26401,9 @@ function parsePax(data) {
|
|
|
26401
26401
|
if (space < 0) break;
|
|
26402
26402
|
const length = Number(value.slice(offset, space));
|
|
26403
26403
|
if (!Number.isFinite(length) || length <= 0) break;
|
|
26404
|
-
const
|
|
26405
|
-
const equal2 =
|
|
26406
|
-
if (equal2 > 0) result[
|
|
26404
|
+
const record2 = value.slice(space + 1, offset + length - 1);
|
|
26405
|
+
const equal2 = record2.indexOf("=");
|
|
26406
|
+
if (equal2 > 0) result[record2.slice(0, equal2)] = record2.slice(equal2 + 1);
|
|
26407
26407
|
offset += length;
|
|
26408
26408
|
}
|
|
26409
26409
|
return result;
|
|
@@ -27183,27 +27183,27 @@ var apt = defineCommand({
|
|
|
27183
27183
|
switch (subcommand) {
|
|
27184
27184
|
case "list": {
|
|
27185
27185
|
const installedOnly = rest.includes("--installed");
|
|
27186
|
-
for (const [name,
|
|
27187
|
-
const
|
|
27188
|
-
if (installedOnly && !
|
|
27189
|
-
ctx.line(`${name}/stable ${
|
|
27186
|
+
for (const [name, info2] of Object.entries(provided)) {
|
|
27187
|
+
const available2 = ctx.kernel.which(name === "python3-pip" ? "pip" : name === "gawk" ? "awk" : name, ctx.cwd, ctx.env, ctx.cred) !== null;
|
|
27188
|
+
if (installedOnly && !available2) continue;
|
|
27189
|
+
ctx.line(`${name}/stable ${info2.version} amd64${available2 ? " [installed]" : ""}`);
|
|
27190
27190
|
}
|
|
27191
27191
|
return 0;
|
|
27192
27192
|
}
|
|
27193
27193
|
case "show": {
|
|
27194
27194
|
let status = 0;
|
|
27195
27195
|
for (const name of rest) {
|
|
27196
|
-
const
|
|
27197
|
-
if (!
|
|
27196
|
+
const info2 = provided[name];
|
|
27197
|
+
if (!info2) {
|
|
27198
27198
|
ctx.warn(`E: No packages found matching ${name}`);
|
|
27199
27199
|
status = 1;
|
|
27200
27200
|
continue;
|
|
27201
27201
|
}
|
|
27202
27202
|
ctx.line(`Package: ${name}`);
|
|
27203
|
-
ctx.line(`Version: ${
|
|
27203
|
+
ctx.line(`Version: ${info2.version}`);
|
|
27204
27204
|
ctx.line(`Priority: optional`);
|
|
27205
27205
|
ctx.line(`Architecture: amd64`);
|
|
27206
|
-
ctx.line(`Description: ${
|
|
27206
|
+
ctx.line(`Description: ${info2.description}`);
|
|
27207
27207
|
ctx.line("");
|
|
27208
27208
|
}
|
|
27209
27209
|
return status;
|
|
@@ -27234,10 +27234,10 @@ var apt = defineCommand({
|
|
|
27234
27234
|
return 100;
|
|
27235
27235
|
case "search": {
|
|
27236
27236
|
const pattern = new RegExp(rest[0] ?? "", "i");
|
|
27237
|
-
for (const [name,
|
|
27238
|
-
if (pattern.test(name) || pattern.test(
|
|
27239
|
-
ctx.line(`${name}/stable ${
|
|
27240
|
-
ctx.line(` ${
|
|
27237
|
+
for (const [name, info2] of Object.entries(provided)) {
|
|
27238
|
+
if (pattern.test(name) || pattern.test(info2.description)) {
|
|
27239
|
+
ctx.line(`${name}/stable ${info2.version} amd64`);
|
|
27240
|
+
ctx.line(` ${info2.description}`);
|
|
27241
27241
|
ctx.line("");
|
|
27242
27242
|
}
|
|
27243
27243
|
}
|
|
@@ -27702,6 +27702,701 @@ function browserCommands() {
|
|
|
27702
27702
|
];
|
|
27703
27703
|
}
|
|
27704
27704
|
|
|
27705
|
+
// src/apps/pm.ts
|
|
27706
|
+
init_binary();
|
|
27707
|
+
init_zip();
|
|
27708
|
+
|
|
27709
|
+
// src/apps/registry.ts
|
|
27710
|
+
var APPS_ROOT = "/opt";
|
|
27711
|
+
var APP_BIN_DIR = "/usr/local/bin";
|
|
27712
|
+
var APP_STATE = "/var/lib/pm/installed.json";
|
|
27713
|
+
var DEFAULT_REGISTRY = "https://sandboxedjswheels.pages.dev/apps";
|
|
27714
|
+
var BUILT_IN_PUBLISHERS = [
|
|
27715
|
+
{
|
|
27716
|
+
publisher: "SandboxedJs",
|
|
27717
|
+
publicKey: "MCowBQYDK2VwAyEAuyxKMeyeO/05mr5Qu8vc+Dd9yMG2/oKDwt7KA+C9Mj4="
|
|
27718
|
+
}
|
|
27719
|
+
];
|
|
27720
|
+
var AppRegistryError = class extends Error {
|
|
27721
|
+
};
|
|
27722
|
+
function parseIndex(text2, source) {
|
|
27723
|
+
let parsed;
|
|
27724
|
+
try {
|
|
27725
|
+
parsed = JSON.parse(text2);
|
|
27726
|
+
} catch (error) {
|
|
27727
|
+
throw new AppRegistryError(`${source} is not JSON: ${error.message}`);
|
|
27728
|
+
}
|
|
27729
|
+
const index = parsed;
|
|
27730
|
+
if (index?.format !== "sandboxedjs-app-index") {
|
|
27731
|
+
throw new AppRegistryError(
|
|
27732
|
+
`${source} is not an application index (its format is ${JSON.stringify(index?.format ?? null)})`
|
|
27733
|
+
);
|
|
27734
|
+
}
|
|
27735
|
+
if (!Array.isArray(index.apps)) {
|
|
27736
|
+
throw new AppRegistryError(`${source} has no apps`);
|
|
27737
|
+
}
|
|
27738
|
+
for (const app of index.apps) {
|
|
27739
|
+
for (const field of ["name", "version", "filename", "sha256"]) {
|
|
27740
|
+
if (typeof app[field] !== "string" || !app[field]) {
|
|
27741
|
+
throw new AppRegistryError(`${source}: an entry is missing ${field}`);
|
|
27742
|
+
}
|
|
27743
|
+
}
|
|
27744
|
+
}
|
|
27745
|
+
return index;
|
|
27746
|
+
}
|
|
27747
|
+
function selectRelease(index, name, version) {
|
|
27748
|
+
const matches3 = index.apps.filter((app) => app.name === name);
|
|
27749
|
+
if (matches3.length === 0) {
|
|
27750
|
+
const names = index.apps.map((app) => app.name).sort();
|
|
27751
|
+
throw new AppRegistryError(
|
|
27752
|
+
`no application named ${name}. The registry has: ${names.join(", ") || "nothing"}`
|
|
27753
|
+
);
|
|
27754
|
+
}
|
|
27755
|
+
if (version) {
|
|
27756
|
+
const exact = matches3.find((app) => app.version === version);
|
|
27757
|
+
if (!exact) {
|
|
27758
|
+
const versions = matches3.map((app) => app.version).join(", ");
|
|
27759
|
+
throw new AppRegistryError(`${name} has no version ${version}; it has ${versions}`);
|
|
27760
|
+
}
|
|
27761
|
+
return exact;
|
|
27762
|
+
}
|
|
27763
|
+
return matches3.slice().sort((a, b) => compareVersions2(b.version, a.version))[0];
|
|
27764
|
+
}
|
|
27765
|
+
function compareVersions2(a, b) {
|
|
27766
|
+
const left = a.split(/[.-]/);
|
|
27767
|
+
const right = b.split(/[.-]/);
|
|
27768
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
27769
|
+
const x = left[index] ?? "";
|
|
27770
|
+
const y = right[index] ?? "";
|
|
27771
|
+
const nx = Number(x);
|
|
27772
|
+
const ny = Number(y);
|
|
27773
|
+
if (Number.isFinite(nx) && Number.isFinite(ny) && x !== "" && y !== "") {
|
|
27774
|
+
if (nx !== ny) return nx - ny;
|
|
27775
|
+
continue;
|
|
27776
|
+
}
|
|
27777
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
27778
|
+
}
|
|
27779
|
+
return 0;
|
|
27780
|
+
}
|
|
27781
|
+
function parseSpecifier(specifier) {
|
|
27782
|
+
const at = specifier.lastIndexOf("@");
|
|
27783
|
+
if (at <= 0) return { name: specifier };
|
|
27784
|
+
return { name: specifier.slice(0, at), version: specifier.slice(at + 1) };
|
|
27785
|
+
}
|
|
27786
|
+
function validateManifest2(manifest, source) {
|
|
27787
|
+
const app = manifest;
|
|
27788
|
+
if (typeof app?.name !== "string" || typeof app?.version !== "string") {
|
|
27789
|
+
throw new AppRegistryError(`${source} is not an application manifest`);
|
|
27790
|
+
}
|
|
27791
|
+
for (const binary of app.bins ?? []) {
|
|
27792
|
+
if (typeof binary?.name !== "string" || typeof binary?.path !== "string") {
|
|
27793
|
+
throw new AppRegistryError(`${source}: a bins entry needs a name and a path`);
|
|
27794
|
+
}
|
|
27795
|
+
if (binary.name.includes("/")) {
|
|
27796
|
+
throw new AppRegistryError(`${source}: a command name cannot contain a slash`);
|
|
27797
|
+
}
|
|
27798
|
+
}
|
|
27799
|
+
for (const service of app.services ?? []) {
|
|
27800
|
+
if (typeof service?.port !== "number" || !Number.isInteger(service.port)) {
|
|
27801
|
+
throw new AppRegistryError(`${source}: a service needs an integer port`);
|
|
27802
|
+
}
|
|
27803
|
+
}
|
|
27804
|
+
return app;
|
|
27805
|
+
}
|
|
27806
|
+
|
|
27807
|
+
// src/apps/sbjs.ts
|
|
27808
|
+
init_binary();
|
|
27809
|
+
init_zip();
|
|
27810
|
+
var MANIFEST_PATH = "META/manifest.json";
|
|
27811
|
+
var SIGNATURE_PATH = "META/signature.json";
|
|
27812
|
+
var TRUSTED_KEYS_PATH = "/etc/pm/trusted-keys.json";
|
|
27813
|
+
var PackageError = class extends Error {
|
|
27814
|
+
};
|
|
27815
|
+
async function openPackage(bytes2, trusted) {
|
|
27816
|
+
const entries = readZip(bytes2);
|
|
27817
|
+
const byName = new Map(entries.map((entry) => [entry.name, entry]));
|
|
27818
|
+
const manifestEntry = byName.get(MANIFEST_PATH);
|
|
27819
|
+
if (!manifestEntry) {
|
|
27820
|
+
throw new PackageError(
|
|
27821
|
+
`not a SandboxedJs package: it has no ${MANIFEST_PATH}. A plain zip can be installed with --allow-unsigned, but it carries no publisher.`
|
|
27822
|
+
);
|
|
27823
|
+
}
|
|
27824
|
+
const manifestBytes = manifestEntry.data();
|
|
27825
|
+
const manifest = parseManifest(manifestBytes);
|
|
27826
|
+
validateManifest2(manifest.app, `${MANIFEST_PATH}'s app`);
|
|
27827
|
+
for (const entry of entries) {
|
|
27828
|
+
if (entry.isDirectory || entry.name.startsWith("META/")) continue;
|
|
27829
|
+
const expected = manifest.files[entry.name];
|
|
27830
|
+
if (!expected) {
|
|
27831
|
+
throw new PackageError(`${entry.name} is in the package but not in its manifest`);
|
|
27832
|
+
}
|
|
27833
|
+
const actual = await digestHex("sha256", entry.data());
|
|
27834
|
+
if (actual !== expected) {
|
|
27835
|
+
throw new PackageError(
|
|
27836
|
+
`${entry.name} does not match the manifest (expected ${expected}, found ${actual})`
|
|
27837
|
+
);
|
|
27838
|
+
}
|
|
27839
|
+
}
|
|
27840
|
+
for (const name of Object.keys(manifest.files)) {
|
|
27841
|
+
if (!byName.has(name)) {
|
|
27842
|
+
throw new PackageError(`${name} is in the manifest but missing from the package`);
|
|
27843
|
+
}
|
|
27844
|
+
}
|
|
27845
|
+
const signatureEntry = byName.get(SIGNATURE_PATH);
|
|
27846
|
+
if (!signatureEntry) {
|
|
27847
|
+
return { manifest, signature: null, entries, trustedBy: null };
|
|
27848
|
+
}
|
|
27849
|
+
const signature = parseSignature(signatureEntry.data());
|
|
27850
|
+
const valid = await verifySignature(manifestBytes, signature);
|
|
27851
|
+
if (!valid) {
|
|
27852
|
+
throw new PackageError(
|
|
27853
|
+
`the signature on ${manifest.app.name} does not match its manifest; the package has been altered since it was signed`
|
|
27854
|
+
);
|
|
27855
|
+
}
|
|
27856
|
+
const trustedBy = trusted.find((key) => key.publicKey === signature.publicKey) ?? null;
|
|
27857
|
+
return { manifest, signature, entries, trustedBy };
|
|
27858
|
+
}
|
|
27859
|
+
function parseManifest(bytes2) {
|
|
27860
|
+
let parsed;
|
|
27861
|
+
try {
|
|
27862
|
+
parsed = JSON.parse(new TextDecoder().decode(bytes2));
|
|
27863
|
+
} catch (error) {
|
|
27864
|
+
throw new PackageError(`${MANIFEST_PATH} is not JSON: ${error.message}`);
|
|
27865
|
+
}
|
|
27866
|
+
const manifest = parsed;
|
|
27867
|
+
if (manifest?.format !== "sandboxedjs-package") {
|
|
27868
|
+
throw new PackageError(`${MANIFEST_PATH} is not a package manifest`);
|
|
27869
|
+
}
|
|
27870
|
+
if (!manifest.files || typeof manifest.files !== "object") {
|
|
27871
|
+
throw new PackageError(`${MANIFEST_PATH} lists no files`);
|
|
27872
|
+
}
|
|
27873
|
+
if (!manifest.app) {
|
|
27874
|
+
throw new PackageError(`${MANIFEST_PATH} carries no application manifest`);
|
|
27875
|
+
}
|
|
27876
|
+
return manifest;
|
|
27877
|
+
}
|
|
27878
|
+
function parseSignature(bytes2) {
|
|
27879
|
+
let parsed;
|
|
27880
|
+
try {
|
|
27881
|
+
parsed = JSON.parse(new TextDecoder().decode(bytes2));
|
|
27882
|
+
} catch (error) {
|
|
27883
|
+
throw new PackageError(`${SIGNATURE_PATH} is not JSON: ${error.message}`);
|
|
27884
|
+
}
|
|
27885
|
+
const signature = parsed;
|
|
27886
|
+
if (signature?.format !== "sandboxedjs-signature") {
|
|
27887
|
+
throw new PackageError(`${SIGNATURE_PATH} is not a signature`);
|
|
27888
|
+
}
|
|
27889
|
+
if (signature.algorithm !== "ed25519") {
|
|
27890
|
+
throw new PackageError(
|
|
27891
|
+
`${signature.algorithm} signatures are not supported by this runtime; only ed25519 is`
|
|
27892
|
+
);
|
|
27893
|
+
}
|
|
27894
|
+
for (const field of ["publicKey", "signature"]) {
|
|
27895
|
+
if (typeof signature[field] !== "string" || !signature[field]) {
|
|
27896
|
+
throw new PackageError(`${SIGNATURE_PATH} has no ${field}`);
|
|
27897
|
+
}
|
|
27898
|
+
}
|
|
27899
|
+
return signature;
|
|
27900
|
+
}
|
|
27901
|
+
async function verifySignature(manifestBytes, signature) {
|
|
27902
|
+
const subtle = globalThis.crypto?.subtle;
|
|
27903
|
+
if (!subtle) {
|
|
27904
|
+
throw new PackageError(
|
|
27905
|
+
"this runtime has no WebCrypto, so a package signature cannot be checked"
|
|
27906
|
+
);
|
|
27907
|
+
}
|
|
27908
|
+
let key;
|
|
27909
|
+
try {
|
|
27910
|
+
key = await subtle.importKey(
|
|
27911
|
+
"spki",
|
|
27912
|
+
intoBuffer(base64ToBytes(signature.publicKey)),
|
|
27913
|
+
{ name: "Ed25519" },
|
|
27914
|
+
false,
|
|
27915
|
+
["verify"]
|
|
27916
|
+
);
|
|
27917
|
+
} catch (error) {
|
|
27918
|
+
throw new PackageError(
|
|
27919
|
+
`cannot use the package's public key: ${error.message}. Ed25519 needs Node 20 or a browser that implements it.`
|
|
27920
|
+
);
|
|
27921
|
+
}
|
|
27922
|
+
return subtle.verify(
|
|
27923
|
+
{ name: "Ed25519" },
|
|
27924
|
+
key,
|
|
27925
|
+
intoBuffer(base64ToBytes(signature.signature)),
|
|
27926
|
+
intoBuffer(manifestBytes)
|
|
27927
|
+
);
|
|
27928
|
+
}
|
|
27929
|
+
function intoBuffer(bytes2) {
|
|
27930
|
+
return bytes2.slice().buffer;
|
|
27931
|
+
}
|
|
27932
|
+
function base64ToBytes(text2) {
|
|
27933
|
+
const binary = atob(text2);
|
|
27934
|
+
const bytes2 = new Uint8Array(binary.length);
|
|
27935
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
27936
|
+
bytes2[index] = binary.charCodeAt(index);
|
|
27937
|
+
}
|
|
27938
|
+
return bytes2;
|
|
27939
|
+
}
|
|
27940
|
+
async function fingerprint(publicKey) {
|
|
27941
|
+
const digest2 = await digestHex("sha256", base64ToBytes(publicKey));
|
|
27942
|
+
return digest2.slice(0, 32).replace(/(.{4})(?=.)/g, "$1-");
|
|
27943
|
+
}
|
|
27944
|
+
function asRegistryError(error) {
|
|
27945
|
+
return new AppRegistryError(error.message);
|
|
27946
|
+
}
|
|
27947
|
+
|
|
27948
|
+
// src/apps/pm.ts
|
|
27949
|
+
function registryBase(ctx) {
|
|
27950
|
+
const configured = ctx.env.SBX_PM_REGISTRY ?? ctx.env.PM_REGISTRY;
|
|
27951
|
+
return configured && configured.trim() || DEFAULT_REGISTRY;
|
|
27952
|
+
}
|
|
27953
|
+
var pm = defineCommand({
|
|
27954
|
+
name: "pm",
|
|
27955
|
+
path: "/usr/bin/pm",
|
|
27956
|
+
summary: "install and manage applications",
|
|
27957
|
+
usage: "pm <install|remove|list|info|search|available> [name[@version]]",
|
|
27958
|
+
manual: `Applications are programs the container runs, packaged with what
|
|
27959
|
+
they need. They are not Python or Node packages: use pip or npm for those.
|
|
27960
|
+
|
|
27961
|
+
pm install NAME[@VERSION] download, verify and install
|
|
27962
|
+
pm remove NAME remove it and the commands it installed
|
|
27963
|
+
pm list what is installed here
|
|
27964
|
+
pm info NAME what a package provides
|
|
27965
|
+
pm search TEXT search the registry
|
|
27966
|
+
pm available everything the registry offers
|
|
27967
|
+
pm trust list|add|remove publisher keys this container trusts
|
|
27968
|
+
|
|
27969
|
+
Packages come from ${DEFAULT_REGISTRY} unless SBX_PM_REGISTRY names another.
|
|
27970
|
+
|
|
27971
|
+
A package is a .sbjs file: a zip carrying META/manifest.json, which lists a
|
|
27972
|
+
sha256 for every file in it, and META/signature.json, an Ed25519 signature
|
|
27973
|
+
over that manifest. Installing checks both, and refuses a package signed by a
|
|
27974
|
+
key this container does not trust. --allow-untrusted and --allow-unsigned
|
|
27975
|
+
override that, one install at a time.
|
|
27976
|
+
|
|
27977
|
+
A signature says who published a package and that nothing in it has changed
|
|
27978
|
+
since. It does not say the program is safe to run.`,
|
|
27979
|
+
async run(ctx) {
|
|
27980
|
+
const [subcommand, ...rest] = ctx.args;
|
|
27981
|
+
try {
|
|
27982
|
+
switch (subcommand) {
|
|
27983
|
+
case "install":
|
|
27984
|
+
return await install2(ctx, rest);
|
|
27985
|
+
case "remove":
|
|
27986
|
+
case "uninstall":
|
|
27987
|
+
return await remove(ctx, rest);
|
|
27988
|
+
case "list":
|
|
27989
|
+
return listInstalled2(ctx);
|
|
27990
|
+
case "info":
|
|
27991
|
+
return await info(ctx, rest);
|
|
27992
|
+
case "search":
|
|
27993
|
+
return await search(ctx, rest);
|
|
27994
|
+
case "available":
|
|
27995
|
+
return await available(ctx);
|
|
27996
|
+
case "trust":
|
|
27997
|
+
return await trust(ctx, rest);
|
|
27998
|
+
case void 0:
|
|
27999
|
+
case "--help":
|
|
28000
|
+
case "-h":
|
|
28001
|
+
ctx.line(pm.usage);
|
|
28002
|
+
return 0;
|
|
28003
|
+
default:
|
|
28004
|
+
ctx.warn(`pm: unknown command ${subcommand}`);
|
|
28005
|
+
return 2;
|
|
28006
|
+
}
|
|
28007
|
+
} catch (error) {
|
|
28008
|
+
ctx.warn(`pm: ${error.message}`);
|
|
28009
|
+
return error instanceof AppRegistryError ? 2 : 1;
|
|
28010
|
+
}
|
|
28011
|
+
}
|
|
28012
|
+
});
|
|
28013
|
+
async function install2(ctx, args) {
|
|
28014
|
+
if (args.length === 0) {
|
|
28015
|
+
ctx.warn("pm install: name it, as `pm install officesuite`");
|
|
28016
|
+
return 2;
|
|
28017
|
+
}
|
|
28018
|
+
const options = {
|
|
28019
|
+
allowUnsigned: args.includes("--allow-unsigned"),
|
|
28020
|
+
allowUntrusted: args.includes("--allow-untrusted") || args.includes("--allow-unsigned")
|
|
28021
|
+
};
|
|
28022
|
+
const names = args.filter((argument) => !argument.startsWith("--"));
|
|
28023
|
+
if (names.length === 0) {
|
|
28024
|
+
ctx.warn("pm install: name it, as `pm install officesuite`");
|
|
28025
|
+
return 2;
|
|
28026
|
+
}
|
|
28027
|
+
const index = await fetchIndex(ctx);
|
|
28028
|
+
for (const specifier of names) {
|
|
28029
|
+
const { name, version } = parseSpecifier(specifier);
|
|
28030
|
+
const release = selectRelease(index, name, version);
|
|
28031
|
+
await installRelease(ctx, index, release, /* @__PURE__ */ new Set(), options);
|
|
28032
|
+
}
|
|
28033
|
+
return 0;
|
|
28034
|
+
}
|
|
28035
|
+
async function installRelease(ctx, index, release, seen, options) {
|
|
28036
|
+
if (seen.has(release.name)) return;
|
|
28037
|
+
seen.add(release.name);
|
|
28038
|
+
for (const required of release.requires ?? []) {
|
|
28039
|
+
const { name, version } = parseSpecifier(required);
|
|
28040
|
+
if (installedApps(ctx).some((app) => app.name === name)) continue;
|
|
28041
|
+
await installRelease(ctx, index, selectRelease(index, name, version), seen, options);
|
|
28042
|
+
}
|
|
28043
|
+
const base2 = registryBase(ctx).replace(/\/$/, "");
|
|
28044
|
+
const url = `${base2}/${release.filename}`;
|
|
28045
|
+
ctx.line(`Fetching ${release.name} ${release.version}`);
|
|
28046
|
+
const archive = await download(url);
|
|
28047
|
+
const digest2 = await digestHex("sha256", archive);
|
|
28048
|
+
if (digest2 !== release.sha256) {
|
|
28049
|
+
throw new AppRegistryError(
|
|
28050
|
+
`${release.filename} failed verification: the registry publishes sha256 ${release.sha256} but the download hashes to ${digest2}`
|
|
28051
|
+
);
|
|
28052
|
+
}
|
|
28053
|
+
const verified = await verify(ctx, archive, release, options);
|
|
28054
|
+
const root = `${APPS_ROOT}/${release.name}`;
|
|
28055
|
+
removeInstalled(ctx, release.name, { quiet: true });
|
|
28056
|
+
const manifest = unpack(ctx, archive, root, release, verified);
|
|
28057
|
+
const bins = linkBinaries(ctx, manifest, root);
|
|
28058
|
+
record(ctx, {
|
|
28059
|
+
...manifest,
|
|
28060
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28061
|
+
root,
|
|
28062
|
+
installedBins: bins,
|
|
28063
|
+
sha256: release.sha256,
|
|
28064
|
+
...verified?.signature ? {
|
|
28065
|
+
publisher: verified.signature.publisher ?? verified.trustedBy?.publisher ?? "unknown",
|
|
28066
|
+
publicKey: verified.signature.publicKey
|
|
28067
|
+
} : {}
|
|
28068
|
+
});
|
|
28069
|
+
ctx.line(`Installed ${manifest.name} ${manifest.version} in ${root}`);
|
|
28070
|
+
if (bins.length > 0) ctx.line(` commands: ${bins.join(", ")}`);
|
|
28071
|
+
for (const service of manifest.services ?? []) {
|
|
28072
|
+
ctx.line(
|
|
28073
|
+
` service: ${service.name} on port ${service.port}` + (service.path ? ` at ${service.path}` : "") + ` (start it with \`${service.command}\`)`
|
|
28074
|
+
);
|
|
28075
|
+
}
|
|
28076
|
+
if (manifest.docs) ctx.line(` docs: ${root}/${manifest.docs}`);
|
|
28077
|
+
}
|
|
28078
|
+
async function download(url) {
|
|
28079
|
+
const response = await fetch(url);
|
|
28080
|
+
if (!response.ok) {
|
|
28081
|
+
throw new AppRegistryError(`${url} answered ${response.status} ${response.statusText}`);
|
|
28082
|
+
}
|
|
28083
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
28084
|
+
}
|
|
28085
|
+
async function verify(ctx, archive, release, options) {
|
|
28086
|
+
let opened;
|
|
28087
|
+
try {
|
|
28088
|
+
opened = await openPackage(archive, trustedKeys(ctx));
|
|
28089
|
+
} catch (error) {
|
|
28090
|
+
if (options.allowUnsigned && /has no META\/manifest\.json/.test(error.message)) {
|
|
28091
|
+
ctx.warn(`pm: ${release.name} is unsigned and its publisher is unknown`);
|
|
28092
|
+
return null;
|
|
28093
|
+
}
|
|
28094
|
+
throw asRegistryError(error);
|
|
28095
|
+
}
|
|
28096
|
+
if (!opened.signature) {
|
|
28097
|
+
if (!options.allowUnsigned) {
|
|
28098
|
+
throw new AppRegistryError(
|
|
28099
|
+
`${release.name} carries no signature, so its publisher cannot be established. Install it with --allow-unsigned if you got it from somewhere you trust.`
|
|
28100
|
+
);
|
|
28101
|
+
}
|
|
28102
|
+
ctx.warn(`pm: ${release.name} is unsigned and its publisher is unknown`);
|
|
28103
|
+
return opened;
|
|
28104
|
+
}
|
|
28105
|
+
const print = await fingerprint(opened.signature.publicKey);
|
|
28106
|
+
if (!opened.trustedBy && !options.allowUntrusted) {
|
|
28107
|
+
throw new AppRegistryError(
|
|
28108
|
+
`${release.name} is signed by a key this container does not trust (${opened.signature.publisher ?? "unnamed"}, ${print}). Add it with \`pm trust add ${print}\` after checking it, or install with --allow-untrusted.`
|
|
28109
|
+
);
|
|
28110
|
+
}
|
|
28111
|
+
ctx.line(
|
|
28112
|
+
opened.trustedBy ? ` signed by ${opened.trustedBy.publisher} (${print})` : ` signed by an untrusted key (${opened.signature.publisher ?? "unnamed"}, ${print})`
|
|
28113
|
+
);
|
|
28114
|
+
return opened;
|
|
28115
|
+
}
|
|
28116
|
+
function trustedKeys(ctx) {
|
|
28117
|
+
let stored = [];
|
|
28118
|
+
try {
|
|
28119
|
+
const parsed = JSON.parse(ctx.vfs.readText(TRUSTED_KEYS_PATH, ctx.cred));
|
|
28120
|
+
stored = Array.isArray(parsed.keys) ? parsed.keys : [];
|
|
28121
|
+
} catch {
|
|
28122
|
+
}
|
|
28123
|
+
const known = new Set(stored.map((key) => key.publicKey));
|
|
28124
|
+
return [...stored, ...BUILT_IN_PUBLISHERS.filter((key) => !known.has(key.publicKey))];
|
|
28125
|
+
}
|
|
28126
|
+
function writeTrustedKeys(ctx, keys) {
|
|
28127
|
+
ctx.vfs.mkdir("/etc/pm", { cred: ctx.cred, recursive: true });
|
|
28128
|
+
ctx.vfs.writeFile(
|
|
28129
|
+
TRUSTED_KEYS_PATH,
|
|
28130
|
+
`${JSON.stringify({ format: "sandboxedjs-trusted-keys", keys }, null, 2)}
|
|
28131
|
+
`,
|
|
28132
|
+
{ cred: ctx.cred, mode: 420 }
|
|
28133
|
+
);
|
|
28134
|
+
}
|
|
28135
|
+
async function trust(ctx, args) {
|
|
28136
|
+
const [action, ...rest] = args;
|
|
28137
|
+
const keys = trustedKeys(ctx);
|
|
28138
|
+
switch (action) {
|
|
28139
|
+
case "list":
|
|
28140
|
+
case void 0: {
|
|
28141
|
+
if (keys.length === 0) {
|
|
28142
|
+
ctx.line("No publisher keys are trusted; only unsigned installs are possible.");
|
|
28143
|
+
return 0;
|
|
28144
|
+
}
|
|
28145
|
+
for (const key of keys) {
|
|
28146
|
+
ctx.line(`${key.publisher} ${await fingerprint(key.publicKey)}`);
|
|
28147
|
+
}
|
|
28148
|
+
return 0;
|
|
28149
|
+
}
|
|
28150
|
+
case "add": {
|
|
28151
|
+
const [publisher, publicKey] = rest;
|
|
28152
|
+
if (!publisher || !publicKey) {
|
|
28153
|
+
ctx.warn("pm trust add: give a publisher name and its base64 public key");
|
|
28154
|
+
return 2;
|
|
28155
|
+
}
|
|
28156
|
+
const next = keys.filter((key) => key.publicKey !== publicKey);
|
|
28157
|
+
next.push({ publisher, publicKey, addedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
28158
|
+
writeTrustedKeys(ctx, next);
|
|
28159
|
+
ctx.line(`Trusting ${publisher} (${await fingerprint(publicKey)})`);
|
|
28160
|
+
return 0;
|
|
28161
|
+
}
|
|
28162
|
+
case "remove": {
|
|
28163
|
+
const [publisher] = rest;
|
|
28164
|
+
const next = keys.filter((key) => key.publisher !== publisher);
|
|
28165
|
+
if (next.length === keys.length) {
|
|
28166
|
+
ctx.warn(`pm: no trusted key named ${publisher}`);
|
|
28167
|
+
return 1;
|
|
28168
|
+
}
|
|
28169
|
+
writeTrustedKeys(ctx, next);
|
|
28170
|
+
ctx.line(`Removed ${publisher}`);
|
|
28171
|
+
return 0;
|
|
28172
|
+
}
|
|
28173
|
+
default:
|
|
28174
|
+
ctx.warn(`pm trust: unknown action ${action}`);
|
|
28175
|
+
return 2;
|
|
28176
|
+
}
|
|
28177
|
+
}
|
|
28178
|
+
function unpack(ctx, archive, root, release, verified) {
|
|
28179
|
+
const entries = verified?.entries ?? readZip(archive);
|
|
28180
|
+
const manifest = verified?.manifest.app ?? readAppManifest(entries, release);
|
|
28181
|
+
if (manifest.name !== release.name || manifest.version !== release.version) {
|
|
28182
|
+
throw new AppRegistryError(
|
|
28183
|
+
`${release.filename} contains ${manifest.name} ${manifest.version}, but the registry lists it as ${release.name} ${release.version}`
|
|
28184
|
+
);
|
|
28185
|
+
}
|
|
28186
|
+
ctx.vfs.mkdir(root, { cred: ctx.cred, recursive: true });
|
|
28187
|
+
for (const entry of entries) {
|
|
28188
|
+
if (entry.name.startsWith("META/")) continue;
|
|
28189
|
+
const target = resolveInside(root, entry.name);
|
|
28190
|
+
if (entry.isDirectory) {
|
|
28191
|
+
ctx.vfs.mkdir(target, { cred: ctx.cred, recursive: true });
|
|
28192
|
+
continue;
|
|
28193
|
+
}
|
|
28194
|
+
const parent = target.slice(0, target.lastIndexOf("/"));
|
|
28195
|
+
if (parent) ctx.vfs.mkdir(parent, { cred: ctx.cred, recursive: true });
|
|
28196
|
+
const executable = (entry.mode & 73) !== 0 || (manifest.bins ?? []).some((binary) => binary.path === entry.name);
|
|
28197
|
+
ctx.vfs.writeFile(target, entry.data(), {
|
|
28198
|
+
cred: ctx.cred,
|
|
28199
|
+
mode: executable ? 493 : 420
|
|
28200
|
+
});
|
|
28201
|
+
}
|
|
28202
|
+
return manifest;
|
|
28203
|
+
}
|
|
28204
|
+
function readAppManifest(entries, release) {
|
|
28205
|
+
const entry = entries.find((candidate) => candidate.name === "app.json");
|
|
28206
|
+
if (!entry) throw new AppRegistryError(`${release.filename} has no app.json`);
|
|
28207
|
+
return validateManifest2(
|
|
28208
|
+
JSON.parse(new TextDecoder().decode(entry.data())),
|
|
28209
|
+
`${release.name}'s app.json`
|
|
28210
|
+
);
|
|
28211
|
+
}
|
|
28212
|
+
function resolveInside(root, name) {
|
|
28213
|
+
const clean2 = name.replace(/\\/g, "/");
|
|
28214
|
+
if (clean2.startsWith("/")) {
|
|
28215
|
+
throw new AppRegistryError(`archive entry ${name} is an absolute path`);
|
|
28216
|
+
}
|
|
28217
|
+
const parts = [];
|
|
28218
|
+
for (const part of clean2.split("/")) {
|
|
28219
|
+
if (part === "" || part === ".") continue;
|
|
28220
|
+
if (part === "..") {
|
|
28221
|
+
throw new AppRegistryError(`archive entry ${name} escapes the package directory`);
|
|
28222
|
+
}
|
|
28223
|
+
parts.push(part);
|
|
28224
|
+
}
|
|
28225
|
+
return `${root}/${parts.join("/")}`;
|
|
28226
|
+
}
|
|
28227
|
+
function linkBinaries(ctx, manifest, root) {
|
|
28228
|
+
const installed3 = [];
|
|
28229
|
+
if ((manifest.bins ?? []).length === 0) return installed3;
|
|
28230
|
+
ctx.vfs.mkdir(APP_BIN_DIR, { cred: ctx.cred, recursive: true });
|
|
28231
|
+
for (const binary of manifest.bins ?? []) {
|
|
28232
|
+
const target = resolveInside(root, binary.path);
|
|
28233
|
+
const wrapper = `${APP_BIN_DIR}/${binary.name}`;
|
|
28234
|
+
ctx.vfs.writeFile(
|
|
28235
|
+
wrapper,
|
|
28236
|
+
`#!/bin/sh
|
|
28237
|
+
# installed by pm for ${manifest.name} ${manifest.version}
|
|
28238
|
+
exec ${target} "$@"
|
|
28239
|
+
`,
|
|
28240
|
+
{ cred: ctx.cred, mode: 493 }
|
|
28241
|
+
);
|
|
28242
|
+
installed3.push(binary.name);
|
|
28243
|
+
}
|
|
28244
|
+
return installed3;
|
|
28245
|
+
}
|
|
28246
|
+
async function remove(ctx, args) {
|
|
28247
|
+
if (args.length === 0) {
|
|
28248
|
+
ctx.warn("pm remove: name it");
|
|
28249
|
+
return 2;
|
|
28250
|
+
}
|
|
28251
|
+
let code = 0;
|
|
28252
|
+
for (const specifier of args) {
|
|
28253
|
+
const { name } = parseSpecifier(specifier);
|
|
28254
|
+
if (!removeInstalled(ctx, name, { quiet: false })) {
|
|
28255
|
+
ctx.warn(`pm: ${name} is not installed`);
|
|
28256
|
+
code = 1;
|
|
28257
|
+
} else {
|
|
28258
|
+
ctx.line(`Removed ${name}`);
|
|
28259
|
+
}
|
|
28260
|
+
}
|
|
28261
|
+
return code;
|
|
28262
|
+
}
|
|
28263
|
+
function removeInstalled(ctx, name, options) {
|
|
28264
|
+
const apps = installedApps(ctx);
|
|
28265
|
+
const app = apps.find((entry) => entry.name === name);
|
|
28266
|
+
if (!app) return false;
|
|
28267
|
+
for (const binary of app.installedBins) {
|
|
28268
|
+
try {
|
|
28269
|
+
ctx.vfs.unlink(`${APP_BIN_DIR}/${binary}`, ctx.cred);
|
|
28270
|
+
} catch {
|
|
28271
|
+
}
|
|
28272
|
+
}
|
|
28273
|
+
try {
|
|
28274
|
+
removeTree(ctx, app.root);
|
|
28275
|
+
} catch (error) {
|
|
28276
|
+
if (!options.quiet) ctx.warn(`pm: could not remove ${app.root}: ${error.message}`);
|
|
28277
|
+
}
|
|
28278
|
+
writeState(ctx, apps.filter((entry) => entry.name !== name));
|
|
28279
|
+
return true;
|
|
28280
|
+
}
|
|
28281
|
+
function removeTree(ctx, path) {
|
|
28282
|
+
let stats;
|
|
28283
|
+
try {
|
|
28284
|
+
stats = ctx.vfs.stat(path, { cred: ctx.cred });
|
|
28285
|
+
} catch {
|
|
28286
|
+
return;
|
|
28287
|
+
}
|
|
28288
|
+
if (stats.isDirectory()) {
|
|
28289
|
+
for (const entry of ctx.vfs.readdir(path, ctx.cred)) {
|
|
28290
|
+
removeTree(ctx, `${path}/${entry}`);
|
|
28291
|
+
}
|
|
28292
|
+
ctx.vfs.rmdir(path, ctx.cred);
|
|
28293
|
+
return;
|
|
28294
|
+
}
|
|
28295
|
+
ctx.vfs.unlink(path, ctx.cred);
|
|
28296
|
+
}
|
|
28297
|
+
function listInstalled2(ctx) {
|
|
28298
|
+
const apps = installedApps(ctx);
|
|
28299
|
+
if (apps.length === 0) {
|
|
28300
|
+
ctx.line("No applications installed. `pm available` lists what there is.");
|
|
28301
|
+
return 0;
|
|
28302
|
+
}
|
|
28303
|
+
for (const app of apps.slice().sort((a, b) => a.name.localeCompare(b.name))) {
|
|
28304
|
+
const commands12 = app.installedBins.length > 0 ? ` (${app.installedBins.join(", ")})` : "";
|
|
28305
|
+
ctx.line(`${app.name} ${app.version}${commands12}`);
|
|
28306
|
+
}
|
|
28307
|
+
return 0;
|
|
28308
|
+
}
|
|
28309
|
+
async function info(ctx, args) {
|
|
28310
|
+
const name = args[0];
|
|
28311
|
+
if (!name) {
|
|
28312
|
+
ctx.warn("pm info: name it");
|
|
28313
|
+
return 2;
|
|
28314
|
+
}
|
|
28315
|
+
const local = installedApps(ctx).find((app) => app.name === name);
|
|
28316
|
+
if (local) {
|
|
28317
|
+
ctx.line(`${local.name} ${local.version} (installed ${local.installedAt})`);
|
|
28318
|
+
if (local.summary) ctx.line(` ${local.summary}`);
|
|
28319
|
+
ctx.line(` root: ${local.root}`);
|
|
28320
|
+
if (local.installedBins.length > 0) ctx.line(` commands: ${local.installedBins.join(", ")}`);
|
|
28321
|
+
for (const service of local.services ?? []) {
|
|
28322
|
+
ctx.line(` service: ${service.name} on port ${service.port}${service.path ?? ""}`);
|
|
28323
|
+
}
|
|
28324
|
+
if (local.docs) ctx.line(` docs: ${local.root}/${local.docs}`);
|
|
28325
|
+
return 0;
|
|
28326
|
+
}
|
|
28327
|
+
const release = selectRelease(await fetchIndex(ctx), name);
|
|
28328
|
+
ctx.line(`${release.name} ${release.version} (not installed)`);
|
|
28329
|
+
if (release.summary) ctx.line(` ${release.summary}`);
|
|
28330
|
+
ctx.line(` size: ${Math.round(release.size / 1024)} kB`);
|
|
28331
|
+
if ((release.bins ?? []).length > 0) {
|
|
28332
|
+
ctx.line(` commands: ${(release.bins ?? []).map((b) => b.name).join(", ")}`);
|
|
28333
|
+
}
|
|
28334
|
+
return 0;
|
|
28335
|
+
}
|
|
28336
|
+
async function search(ctx, args) {
|
|
28337
|
+
const term = (args[0] ?? "").toLowerCase();
|
|
28338
|
+
const index = await fetchIndex(ctx);
|
|
28339
|
+
const matches3 = index.apps.filter(
|
|
28340
|
+
(app) => app.name.toLowerCase().includes(term) || (app.summary ?? "").toLowerCase().includes(term)
|
|
28341
|
+
);
|
|
28342
|
+
if (matches3.length === 0) {
|
|
28343
|
+
ctx.line(`nothing matches ${JSON.stringify(args[0] ?? "")}`);
|
|
28344
|
+
return 1;
|
|
28345
|
+
}
|
|
28346
|
+
for (const app of matches3) ctx.line(`${app.name} ${app.version} ${app.summary ?? ""}`);
|
|
28347
|
+
return 0;
|
|
28348
|
+
}
|
|
28349
|
+
async function available(ctx) {
|
|
28350
|
+
const index = await fetchIndex(ctx);
|
|
28351
|
+
for (const app of index.apps.slice().sort((a, b) => a.name.localeCompare(b.name))) {
|
|
28352
|
+
ctx.line(`${app.name} ${app.version} ${app.summary ?? ""}`);
|
|
28353
|
+
}
|
|
28354
|
+
return 0;
|
|
28355
|
+
}
|
|
28356
|
+
async function fetchIndex(ctx) {
|
|
28357
|
+
const base2 = registryBase(ctx).replace(/\/$/, "");
|
|
28358
|
+
const url = `${base2}/index.json`;
|
|
28359
|
+
let response;
|
|
28360
|
+
try {
|
|
28361
|
+
response = await fetch(url);
|
|
28362
|
+
} catch (error) {
|
|
28363
|
+
throw new AppRegistryError(
|
|
28364
|
+
`cannot reach the application registry at ${url}: ${error.message}`
|
|
28365
|
+
);
|
|
28366
|
+
}
|
|
28367
|
+
if (!response.ok) {
|
|
28368
|
+
throw new AppRegistryError(`${url} answered ${response.status} ${response.statusText}`);
|
|
28369
|
+
}
|
|
28370
|
+
return parseIndex(await response.text(), url);
|
|
28371
|
+
}
|
|
28372
|
+
function installedApps(ctx) {
|
|
28373
|
+
try {
|
|
28374
|
+
const text2 = ctx.vfs.readText(APP_STATE, ctx.cred);
|
|
28375
|
+
const parsed = JSON.parse(text2);
|
|
28376
|
+
return Array.isArray(parsed.apps) ? parsed.apps : [];
|
|
28377
|
+
} catch {
|
|
28378
|
+
return [];
|
|
28379
|
+
}
|
|
28380
|
+
}
|
|
28381
|
+
function record(ctx, app) {
|
|
28382
|
+
const apps = installedApps(ctx).filter((entry) => entry.name !== app.name);
|
|
28383
|
+
apps.push(app);
|
|
28384
|
+
writeState(ctx, apps);
|
|
28385
|
+
}
|
|
28386
|
+
function writeState(ctx, apps) {
|
|
28387
|
+
const directory2 = APP_STATE.slice(0, APP_STATE.lastIndexOf("/"));
|
|
28388
|
+
ctx.vfs.mkdir(directory2, { cred: ctx.cred, recursive: true });
|
|
28389
|
+
ctx.vfs.writeFile(
|
|
28390
|
+
APP_STATE,
|
|
28391
|
+
`${JSON.stringify({ format: "sandboxedjs-installed-apps", apps }, null, 2)}
|
|
28392
|
+
`,
|
|
28393
|
+
{ cred: ctx.cred, mode: 420 }
|
|
28394
|
+
);
|
|
28395
|
+
}
|
|
28396
|
+
function appCommands() {
|
|
28397
|
+
return [pm];
|
|
28398
|
+
}
|
|
28399
|
+
|
|
27705
28400
|
// src/bin/index.ts
|
|
27706
28401
|
function allCommands() {
|
|
27707
28402
|
return [
|
|
@@ -27721,7 +28416,8 @@ function allCommands() {
|
|
|
27721
28416
|
...ffmpegCommands(),
|
|
27722
28417
|
...wasiCommands(),
|
|
27723
28418
|
...packageCommands(),
|
|
27724
|
-
...browserCommands()
|
|
28419
|
+
...browserCommands(),
|
|
28420
|
+
...appCommands()
|
|
27725
28421
|
];
|
|
27726
28422
|
}
|
|
27727
28423
|
function installUserland(kernel) {
|
|
@@ -31440,7 +32136,7 @@ ${indent(detail, 4)}
|
|
|
31440
32136
|
if (detail) host2.write(`${indent(detail, depth * 2 + 2)}
|
|
31441
32137
|
`);
|
|
31442
32138
|
};
|
|
31443
|
-
const
|
|
32139
|
+
const record2 = (outcome, name, error) => {
|
|
31444
32140
|
counts.tests += 1;
|
|
31445
32141
|
if (outcome === "pass") counts.pass += 1;
|
|
31446
32142
|
else if (outcome === "fail") {
|
|
@@ -31462,13 +32158,13 @@ ${indent(detail, 4)}
|
|
|
31462
32158
|
if (!matchesPattern2(display)) return true;
|
|
31463
32159
|
if (runOnly && !options.only) return true;
|
|
31464
32160
|
if (options.skip) {
|
|
31465
|
-
|
|
32161
|
+
record2("skip", display);
|
|
31466
32162
|
report(depth, "skip", name, started);
|
|
31467
32163
|
return true;
|
|
31468
32164
|
}
|
|
31469
32165
|
if (!fn) {
|
|
31470
32166
|
const outcome = options.todo ? "todo" : "skip";
|
|
31471
|
-
|
|
32167
|
+
record2(outcome, display);
|
|
31472
32168
|
report(depth, outcome, name, started);
|
|
31473
32169
|
return true;
|
|
31474
32170
|
}
|
|
@@ -31532,21 +32228,21 @@ ${indent(detail, 4)}
|
|
|
31532
32228
|
controller.abort();
|
|
31533
32229
|
}
|
|
31534
32230
|
if (skipped) {
|
|
31535
|
-
|
|
32231
|
+
record2("skip", display);
|
|
31536
32232
|
report(depth, "skip", name, started);
|
|
31537
32233
|
return true;
|
|
31538
32234
|
}
|
|
31539
32235
|
if (error !== void 0) {
|
|
31540
32236
|
if (todo) {
|
|
31541
|
-
|
|
32237
|
+
record2("todo", display);
|
|
31542
32238
|
report(depth, "todo", name, started);
|
|
31543
32239
|
return true;
|
|
31544
32240
|
}
|
|
31545
|
-
|
|
32241
|
+
record2("fail", display, error);
|
|
31546
32242
|
report(depth, "fail", name, started, errorText(error));
|
|
31547
32243
|
return false;
|
|
31548
32244
|
}
|
|
31549
|
-
|
|
32245
|
+
record2(todo ? "todo" : "pass", display);
|
|
31550
32246
|
report(depth, todo ? "todo" : "pass", name, started);
|
|
31551
32247
|
return true;
|
|
31552
32248
|
};
|
|
@@ -31561,7 +32257,7 @@ ${indent(detail, 4)}
|
|
|
31561
32257
|
else host2.write(`${" ".repeat(depth)}\u25B6 ${suite.name}
|
|
31562
32258
|
`);
|
|
31563
32259
|
if (suite.options.skip) {
|
|
31564
|
-
for (const child of suite.children) if (child.kind === "test")
|
|
32260
|
+
for (const child of suite.children) if (child.kind === "test") record2("skip", fullName(child));
|
|
31565
32261
|
report(depth, "skip", suite.name, started);
|
|
31566
32262
|
return true;
|
|
31567
32263
|
}
|
|
@@ -31573,7 +32269,7 @@ ${indent(detail, 4)}
|
|
|
31573
32269
|
} catch (error) {
|
|
31574
32270
|
for (const child of suite.children) {
|
|
31575
32271
|
if (child.kind === "test") {
|
|
31576
|
-
|
|
32272
|
+
record2("cancelled", fullName(child), error);
|
|
31577
32273
|
report(depth + (isRoot ? 0 : 1), "cancelled", child.name, started, errorText(error));
|
|
31578
32274
|
}
|
|
31579
32275
|
}
|
|
@@ -37031,6 +37727,14 @@ var Container = class _Container {
|
|
|
37031
37727
|
installUserland(kernel);
|
|
37032
37728
|
const container = new _Container(kernel, pod, opts);
|
|
37033
37729
|
if (opts.files) container.mountSync(opts.files, { cwd: opts.cwd ?? "/" });
|
|
37730
|
+
if (opts.modules && opts.modules.length > 0) {
|
|
37731
|
+
const result = await container.exec(`pm install ${opts.modules.join(" ")}`);
|
|
37732
|
+
if (result.exitCode !== 0) {
|
|
37733
|
+
throw new Error(
|
|
37734
|
+
`could not install ${opts.modules.join(", ")}: ${(result.stderr || result.stdout).trim() || `pm exited ${result.exitCode}`}`
|
|
37735
|
+
);
|
|
37736
|
+
}
|
|
37737
|
+
}
|
|
37034
37738
|
return container;
|
|
37035
37739
|
}
|
|
37036
37740
|
// ── file seeding ──────────────────────────────────────────────────────────
|