@elpapi42/pi-fleet 0.1.0-beta.2 → 0.1.0-beta.3
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/CHANGELOG.md +15 -0
- package/README.md +2 -2
- package/dist/cli-meta.json +6 -6
- package/dist/cli.mjs +159 -54
- package/dist/cli.mjs.map +3 -3
- package/dist/installer-meta.json +6 -6
- package/dist/installer.mjs +158 -53
- package/dist/installer.mjs.map +3 -3
- package/dist/runtime-manifest.json +22 -25
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,21 @@
|
|
|
12
12
|
- Added a nightly reliability workflow and an isolated soak suite covering 500 concurrent ordered sends and 100 name lifecycle cycles.
|
|
13
13
|
- Reject invalid UTF-8 in stdin and Pi RPC stdout instead of silently replacing bytes at either protocol boundary.
|
|
14
14
|
|
|
15
|
+
## 0.1.0-beta.3 — 2026-07-20
|
|
16
|
+
|
|
17
|
+
Reliability fix for fresh global npm installations. No intentional CLI or agent-lifecycle contract change.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Replaced the invalid assumption that npm recreates CI's byte-identical `node_modules` tree. Schema-3 runtime manifests retain exact hashes for Fleet-owned files, validate direct dependency name/version, derive the observed dependency-closure hash at materialization, and prove source-before, staged, and source-after closure equality before activation.
|
|
22
|
+
- Made immutable runtime release paths closure-specific, so different legitimate npm dependency layouts do not collide.
|
|
23
|
+
- Strengthened package testing to mutate a fresh installed dependency tree, require an operational `list`, remove the npm installation, stop the runtime, and restart through the materialized release.
|
|
24
|
+
|
|
25
|
+
### Installation scope
|
|
26
|
+
|
|
27
|
+
- The documented global npm installation layout, where dependencies are package-local, is supported. Arbitrarily hoisted local-prefix, pnpm, and unusual `npx` dependency layouts are not yet supported.
|
|
28
|
+
- The tag workflow now performs a fresh registry-install operational `list` smoke after publishing. That smoke is a release gate and has not yet run for beta.3.
|
|
29
|
+
|
|
15
30
|
## 0.1.0-beta.2 — 2026-07-20
|
|
16
31
|
|
|
17
32
|
Reliability-focused beta maintenance release. No intentional product-contract changes.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ create · send · receive · status · list · watch · destroy
|
|
|
8
8
|
|
|
9
9
|
Fleet keeps a Pi process resident when possible, restores it from the same native Pi session when absent, accepts repeated steering input, and returns the latest assistant message after Pi becomes idle. Pi sessions remain under your control: Fleet references them but never copies or deletes them.
|
|
10
10
|
|
|
11
|
-
> **Beta:** `0.1.0-beta.
|
|
11
|
+
> **Beta:** `0.1.0-beta.3` has passed deterministic Linux x64 fault, recovery, package, compatibility, and resource-stability tests with Pi `0.80.10`. Its tag workflow additionally requires a fresh registry-install operational smoke after publication; that external check has not yet run for beta.3. Actual logout/reboot recovery, macOS launchd/containment, automatic upgrades, and public service-management UX are not yet release-validated.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -173,7 +173,7 @@ Do not include API keys, message contents, session contents, or private paths un
|
|
|
173
173
|
|
|
174
174
|
## Beta limitations
|
|
175
175
|
|
|
176
|
-
- Current deterministic reliability and package validation
|
|
176
|
+
- Current deterministic reliability and documented global-install package validation cover Linux x64 only. Arbitrarily hoisted local-prefix, pnpm, and unusual `npx` dependency layouts are not yet supported.
|
|
177
177
|
- Actual logout and host reboot recovery remain unvalidated.
|
|
178
178
|
- launchd and macOS descendant containment remain unvalidated.
|
|
179
179
|
- Real disk-exhaustion behavior and multi-hour resource growth remain unvalidated; bounded SQLite and in-process resource tests are covered.
|
package/dist/cli-meta.json
CHANGED
|
@@ -423,7 +423,7 @@
|
|
|
423
423
|
"format": "esm"
|
|
424
424
|
},
|
|
425
425
|
"src/platform/install/tree-integrity.ts": {
|
|
426
|
-
"bytes":
|
|
426
|
+
"bytes": 2529,
|
|
427
427
|
"imports": [
|
|
428
428
|
{
|
|
429
429
|
"path": "node:crypto",
|
|
@@ -444,7 +444,7 @@
|
|
|
444
444
|
"format": "esm"
|
|
445
445
|
},
|
|
446
446
|
"src/platform/install/runtime-release.ts": {
|
|
447
|
-
"bytes":
|
|
447
|
+
"bytes": 15742,
|
|
448
448
|
"imports": [
|
|
449
449
|
{
|
|
450
450
|
"path": "node:crypto",
|
|
@@ -593,7 +593,7 @@
|
|
|
593
593
|
"imports": [],
|
|
594
594
|
"exports": [],
|
|
595
595
|
"inputs": {},
|
|
596
|
-
"bytes":
|
|
596
|
+
"bytes": 270001
|
|
597
597
|
},
|
|
598
598
|
"dist/cli.mjs": {
|
|
599
599
|
"imports": [
|
|
@@ -828,16 +828,16 @@
|
|
|
828
828
|
"bytesInOutput": 4658
|
|
829
829
|
},
|
|
830
830
|
"src/platform/install/runtime-release.ts": {
|
|
831
|
-
"bytesInOutput":
|
|
831
|
+
"bytesInOutput": 13651
|
|
832
832
|
},
|
|
833
833
|
"src/platform/install/tree-integrity.ts": {
|
|
834
|
-
"bytesInOutput":
|
|
834
|
+
"bytesInOutput": 2182
|
|
835
835
|
},
|
|
836
836
|
"src/platform/shared/paths.ts": {
|
|
837
837
|
"bytesInOutput": 1191
|
|
838
838
|
}
|
|
839
839
|
},
|
|
840
|
-
"bytes":
|
|
840
|
+
"bytes": 150369
|
|
841
841
|
}
|
|
842
842
|
}
|
|
843
843
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -3385,7 +3385,7 @@ function splitArgv(argv) {
|
|
|
3385
3385
|
|
|
3386
3386
|
// src/shared/product-identity.ts
|
|
3387
3387
|
var PRODUCT_BINARY = "pifleet";
|
|
3388
|
-
var PRODUCT_VERSION = "0.1.0-beta.
|
|
3388
|
+
var PRODUCT_VERSION = "0.1.0-beta.3";
|
|
3389
3389
|
|
|
3390
3390
|
// src/cli/commands/create.ts
|
|
3391
3391
|
import { resolve } from "node:path";
|
|
@@ -3907,12 +3907,12 @@ import { promisify } from "node:util";
|
|
|
3907
3907
|
|
|
3908
3908
|
// src/platform/install/runtime-release.ts
|
|
3909
3909
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
3910
|
-
import { chmod, cp, lstat as lstat2, mkdir, readFile as readFile2, rename, rm } from "node:fs/promises";
|
|
3910
|
+
import { chmod, cp, lstat as lstat2, mkdir, readFile as readFile2, rename, rm, writeFile } from "node:fs/promises";
|
|
3911
3911
|
import { join as join2, posix, resolve as resolve3, sep as sep2 } from "node:path";
|
|
3912
3912
|
|
|
3913
3913
|
// src/platform/install/tree-integrity.ts
|
|
3914
3914
|
import { createHash } from "node:crypto";
|
|
3915
|
-
import { lstat, readFile, readdir, stat } from "node:fs/promises";
|
|
3915
|
+
import { lstat, readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
3916
3916
|
import { join, relative, resolve as resolve2, sep } from "node:path";
|
|
3917
3917
|
async function hashDirectoryTree(root, manifestPath) {
|
|
3918
3918
|
const resolvedRoot = resolve2(root);
|
|
@@ -3952,6 +3952,14 @@ async function collectFiles(root, directory) {
|
|
|
3952
3952
|
if (!info.isFile()) {
|
|
3953
3953
|
throw new Error(`Runtime dependency tree contains an unsupported entry: ${absolutePath}`);
|
|
3954
3954
|
}
|
|
3955
|
+
if (linkInfo.isSymbolicLink()) {
|
|
3956
|
+
const target = await realpath(absolutePath);
|
|
3957
|
+
if (target !== root && !target.startsWith(`${root}${sep}`)) {
|
|
3958
|
+
throw new Error(
|
|
3959
|
+
`Runtime dependency tree contains an external file symlink: ${absolutePath}`
|
|
3960
|
+
);
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3955
3963
|
const relativePath = relative(root, absolutePath).split(sep).join("/");
|
|
3956
3964
|
if (relativePath.length === 0 || relativePath.startsWith("../")) {
|
|
3957
3965
|
throw new Error(`Runtime dependency path escapes its root: ${absolutePath}`);
|
|
@@ -3970,19 +3978,33 @@ var requiredRuntimeArtifacts = /* @__PURE__ */ new Set([
|
|
|
3970
3978
|
"dist/runtime.mjs",
|
|
3971
3979
|
"dist/sqlite-worker.mjs"
|
|
3972
3980
|
]);
|
|
3981
|
+
var dependencyTreePath = "node_modules";
|
|
3973
3982
|
async function materializeRuntime(options) {
|
|
3974
3983
|
const sourceRoot = resolve3(options.sourceRoot);
|
|
3975
3984
|
const manifestBytes = await readFile2(join2(sourceRoot, "dist", "runtime-manifest.json"));
|
|
3976
|
-
const
|
|
3977
|
-
|
|
3985
|
+
const sourceManifest = await parseRuntimeManifest(manifestBytes, sourceRoot);
|
|
3986
|
+
if (sourceManifest.closure !== void 0) {
|
|
3987
|
+
await verifyRuntime(sourceRoot);
|
|
3988
|
+
return sourceRoot;
|
|
3989
|
+
}
|
|
3990
|
+
await verifyRuntimeFiles(sourceRoot, sourceManifest);
|
|
3991
|
+
await verifyDependencyIdentities(sourceRoot, sourceManifest.dependencies);
|
|
3992
|
+
const sourceTreeRoot = resolveInside(sourceRoot, dependencyTreePath);
|
|
3993
|
+
const sourceTreeBefore = await hashDirectoryTree(sourceTreeRoot, dependencyTreePath);
|
|
3994
|
+
const sourceManifestSha256 = createHash2("sha256").update(manifestBytes).digest("hex");
|
|
3995
|
+
const materializedManifest = {
|
|
3996
|
+
...sourceManifest,
|
|
3997
|
+
closure: { sourceManifestSha256, tree: sourceTreeBefore }
|
|
3998
|
+
};
|
|
3999
|
+
const materializedManifestBytes = serializeManifest(materializedManifest);
|
|
4000
|
+
const closureHash = createHash2("sha256").update(sourceManifestSha256).update("\0").update(JSON.stringify(sourceTreeBefore)).digest("hex").slice(0, 16);
|
|
3978
4001
|
const applicationRoot = resolve3(options.applicationRoot);
|
|
3979
4002
|
await ensurePrivateDirectory(applicationRoot);
|
|
3980
|
-
const manifestHash = createHash2("sha256").update(manifestBytes).digest("hex").slice(0, 16);
|
|
3981
4003
|
const releasesRoot = join2(applicationRoot, "releases");
|
|
3982
4004
|
await ensurePrivateDirectory(releasesRoot);
|
|
3983
|
-
const destination = join2(releasesRoot, `${
|
|
4005
|
+
const destination = join2(releasesRoot, `${sourceManifest.package.version}-${closureHash}`);
|
|
3984
4006
|
if (await pathExists(destination)) {
|
|
3985
|
-
await
|
|
4007
|
+
await verifyExpectedRuntime(destination, materializedManifest, materializedManifestBytes);
|
|
3986
4008
|
return destination;
|
|
3987
4009
|
}
|
|
3988
4010
|
const staging = join2(releasesRoot, `.staging-${randomUUID2()}`);
|
|
@@ -3992,36 +4014,73 @@ async function materializeRuntime(options) {
|
|
|
3992
4014
|
recursive: true,
|
|
3993
4015
|
dereference: true
|
|
3994
4016
|
});
|
|
3995
|
-
await cp(join2(sourceRoot, "bin"), join2(staging, "bin"), {
|
|
4017
|
+
await cp(join2(sourceRoot, "bin"), join2(staging, "bin"), {
|
|
4018
|
+
recursive: true,
|
|
4019
|
+
dereference: true
|
|
4020
|
+
});
|
|
3996
4021
|
await cp(join2(sourceRoot, "package.json"), join2(staging, "package.json"));
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4022
|
+
await cp(sourceTreeRoot, join2(staging, dependencyTreePath), {
|
|
4023
|
+
recursive: true,
|
|
4024
|
+
dereference: true
|
|
4025
|
+
});
|
|
4026
|
+
await options.hooks?.afterDependencyCopy?.();
|
|
4027
|
+
const stagedTree = await hashDirectoryTree(
|
|
4028
|
+
join2(staging, dependencyTreePath),
|
|
4029
|
+
dependencyTreePath
|
|
4030
|
+
);
|
|
4031
|
+
assertSameTree(sourceTreeBefore, stagedTree, "copied dependency closure");
|
|
4032
|
+
const sourceTreeAfter = await hashDirectoryTree(sourceTreeRoot, dependencyTreePath);
|
|
4033
|
+
assertSameTree(sourceTreeBefore, sourceTreeAfter, "source dependency closure");
|
|
4034
|
+
await writeFile(join2(staging, "dist", "runtime-manifest.json"), materializedManifestBytes);
|
|
4035
|
+
await verifyExpectedRuntime(staging, materializedManifest, materializedManifestBytes);
|
|
4004
4036
|
await chmod(staging, 448);
|
|
4005
4037
|
try {
|
|
4006
4038
|
await rename(staging, destination);
|
|
4007
4039
|
} catch (error) {
|
|
4008
4040
|
if (!isDestinationRace(error) || !await pathExists(destination)) throw error;
|
|
4009
|
-
await
|
|
4041
|
+
await verifyExpectedRuntime(destination, materializedManifest, materializedManifestBytes);
|
|
4010
4042
|
}
|
|
4011
4043
|
return destination;
|
|
4012
4044
|
} finally {
|
|
4013
4045
|
await rm(staging, { recursive: true, force: true });
|
|
4014
4046
|
}
|
|
4015
4047
|
}
|
|
4016
|
-
async function verifyRuntime(root
|
|
4048
|
+
async function verifyRuntime(root) {
|
|
4017
4049
|
const resolvedRoot = resolve3(root);
|
|
4018
|
-
const
|
|
4019
|
-
|
|
4020
|
-
|
|
4050
|
+
const manifestBytes = await readFile2(join2(resolvedRoot, "dist", "runtime-manifest.json"));
|
|
4051
|
+
const manifest = await parseRuntimeManifest(manifestBytes, resolvedRoot);
|
|
4052
|
+
if (manifest.closure === void 0) {
|
|
4053
|
+
throw new Error("Runtime release manifest is missing its materialized closure");
|
|
4054
|
+
}
|
|
4055
|
+
await verifyRuntimeFiles(resolvedRoot, manifest);
|
|
4056
|
+
await verifyDependencyIdentities(resolvedRoot, manifest.dependencies);
|
|
4057
|
+
const actualTree = await hashDirectoryTree(
|
|
4058
|
+
resolveInside(resolvedRoot, dependencyTreePath),
|
|
4059
|
+
dependencyTreePath
|
|
4060
|
+
);
|
|
4061
|
+
assertSameTree(manifest.closure.tree, actualTree, "materialized dependency closure");
|
|
4062
|
+
}
|
|
4063
|
+
async function verifyExpectedRuntime(root, expected, expectedBytes) {
|
|
4064
|
+
const manifestPath = join2(root, "dist", "runtime-manifest.json");
|
|
4065
|
+
const actualBytes = await readFile2(manifestPath);
|
|
4066
|
+
if (!actualBytes.equals(expectedBytes)) {
|
|
4067
|
+
throw new Error("Materialized runtime manifest does not match the expected closure");
|
|
4068
|
+
}
|
|
4069
|
+
const actual = await parseRuntimeManifest(actualBytes, root);
|
|
4070
|
+
if (actual.closure === void 0 || expected.closure === void 0) {
|
|
4071
|
+
throw new Error("Materialized runtime manifest is missing its closure");
|
|
4072
|
+
}
|
|
4073
|
+
await verifyRuntimeFiles(root, actual);
|
|
4074
|
+
await verifyDependencyIdentities(root, actual.dependencies);
|
|
4075
|
+
const actualTree = await hashDirectoryTree(
|
|
4076
|
+
resolveInside(root, dependencyTreePath),
|
|
4077
|
+
dependencyTreePath
|
|
4021
4078
|
);
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4079
|
+
assertSameTree(expected.closure.tree, actualTree, "materialized dependency closure");
|
|
4080
|
+
}
|
|
4081
|
+
async function verifyRuntimeFiles(root, manifest) {
|
|
4082
|
+
for (const file of manifest.files) {
|
|
4083
|
+
const path2 = resolveInside(root, file.path);
|
|
4025
4084
|
const info = await lstat2(path2);
|
|
4026
4085
|
if (info.isSymbolicLink()) {
|
|
4027
4086
|
throw new Error(`Runtime artifact ${file.path} must not be a symbolic link`);
|
|
@@ -4034,11 +4093,27 @@ async function verifyRuntime(root, manifest) {
|
|
|
4034
4093
|
throw new Error(`Runtime artifact ${file.path} failed verification`);
|
|
4035
4094
|
}
|
|
4036
4095
|
}
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4096
|
+
}
|
|
4097
|
+
async function verifyDependencyIdentities(root, dependencies) {
|
|
4098
|
+
const nodeModules = resolveInside(root, dependencyTreePath);
|
|
4099
|
+
const modulesInfo = await lstat2(nodeModules);
|
|
4100
|
+
if (!modulesInfo.isDirectory() || modulesInfo.isSymbolicLink()) {
|
|
4101
|
+
throw new Error("Runtime dependency closure must be a regular directory");
|
|
4102
|
+
}
|
|
4103
|
+
for (const dependency of dependencies) {
|
|
4104
|
+
const packageRoot = resolveInside(root, dependency.path);
|
|
4105
|
+
const packageInfo = await lstat2(packageRoot);
|
|
4106
|
+
if (!packageInfo.isDirectory() || packageInfo.isSymbolicLink()) {
|
|
4107
|
+
throw new Error(`Runtime dependency ${dependency.name} must be a regular directory`);
|
|
4108
|
+
}
|
|
4109
|
+
const packageJsonPath = join2(packageRoot, "package.json");
|
|
4110
|
+
const packageJsonInfo = await lstat2(packageJsonPath);
|
|
4111
|
+
if (!packageJsonInfo.isFile() || packageJsonInfo.isSymbolicLink()) {
|
|
4112
|
+
throw new Error(`Runtime dependency ${dependency.name} has an unsafe package.json`);
|
|
4113
|
+
}
|
|
4114
|
+
const identity = await readPackageMetadata(packageRoot);
|
|
4115
|
+
if (identity.name !== dependency.name || identity.version !== dependency.version) {
|
|
4116
|
+
throw new Error(`Runtime dependency ${dependency.name} has an unexpected identity`);
|
|
4042
4117
|
}
|
|
4043
4118
|
}
|
|
4044
4119
|
}
|
|
@@ -4053,17 +4128,17 @@ async function parseRuntimeManifest(bytes, root) {
|
|
|
4053
4128
|
return candidate;
|
|
4054
4129
|
}
|
|
4055
4130
|
async function validateRuntimeManifest(candidate, root) {
|
|
4056
|
-
if (!isRecord2(candidate) || candidate.schemaVersion !==
|
|
4131
|
+
if (!isRecord2(candidate) || candidate.schemaVersion !== 3) {
|
|
4057
4132
|
throw new Error("Runtime manifest has an unsupported schema version");
|
|
4058
4133
|
}
|
|
4059
|
-
if (!isRecord2(candidate.package) || typeof candidate.package.name !== "string" || typeof candidate.package.version !== "string") {
|
|
4134
|
+
if (!isRecord2(candidate.package) || typeof candidate.package.name !== "string" || typeof candidate.package.version !== "string" || typeof candidate.managedPi !== "string") {
|
|
4060
4135
|
throw new Error("Runtime manifest has an invalid package identity");
|
|
4061
4136
|
}
|
|
4062
|
-
const
|
|
4063
|
-
if (candidate.package.name !==
|
|
4137
|
+
const packageMetadata = await readPackageMetadata(root);
|
|
4138
|
+
if (candidate.package.name !== packageMetadata.name || candidate.package.version !== packageMetadata.version) {
|
|
4064
4139
|
throw new Error("Runtime manifest package identity does not match package.json");
|
|
4065
4140
|
}
|
|
4066
|
-
if (!Array.isArray(candidate.files) || !Array.isArray(candidate.
|
|
4141
|
+
if (!Array.isArray(candidate.files) || !Array.isArray(candidate.dependencies)) {
|
|
4067
4142
|
throw new Error("Runtime manifest has invalid artifact lists");
|
|
4068
4143
|
}
|
|
4069
4144
|
const filePaths = /* @__PURE__ */ new Set();
|
|
@@ -4071,8 +4146,9 @@ async function validateRuntimeManifest(candidate, root) {
|
|
|
4071
4146
|
if (!isRecord2(file) || !isManifestPath(file.path) || !isValidSize(file.bytes) || !isSha256(file.sha256)) {
|
|
4072
4147
|
throw new Error("Runtime manifest contains an invalid artifact");
|
|
4073
4148
|
}
|
|
4074
|
-
if (filePaths.has(file.path))
|
|
4149
|
+
if (filePaths.has(file.path)) {
|
|
4075
4150
|
throw new Error(`Runtime manifest has duplicate path ${file.path}`);
|
|
4151
|
+
}
|
|
4076
4152
|
filePaths.add(file.path);
|
|
4077
4153
|
}
|
|
4078
4154
|
for (const required of requiredRuntimeArtifacts) {
|
|
@@ -4080,37 +4156,63 @@ async function validateRuntimeManifest(candidate, root) {
|
|
|
4080
4156
|
throw new Error(`Runtime manifest is missing required artifact ${required}`);
|
|
4081
4157
|
}
|
|
4082
4158
|
}
|
|
4083
|
-
const
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4159
|
+
const dependencyPaths = /* @__PURE__ */ new Set();
|
|
4160
|
+
const dependencyNames = /* @__PURE__ */ new Set();
|
|
4161
|
+
for (const dependency of candidate.dependencies) {
|
|
4162
|
+
if (!isRecord2(dependency) || !isManifestPath(dependency.path) || typeof dependency.name !== "string" || dependency.name.length === 0 || typeof dependency.version !== "string" || dependency.version.length === 0 || dependency.path !== `node_modules/${dependency.name}`) {
|
|
4163
|
+
throw new Error("Runtime manifest contains an invalid dependency declaration");
|
|
4087
4164
|
}
|
|
4088
|
-
if (
|
|
4089
|
-
throw new Error(`Runtime manifest has duplicate
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4165
|
+
if (dependencyPaths.has(dependency.path) || dependencyNames.has(dependency.name)) {
|
|
4166
|
+
throw new Error(`Runtime manifest has duplicate dependency ${dependency.name}`);
|
|
4167
|
+
}
|
|
4168
|
+
dependencyPaths.add(dependency.path);
|
|
4169
|
+
dependencyNames.add(dependency.name);
|
|
4170
|
+
}
|
|
4171
|
+
const expectedDependencies = packageMetadata.dependencies;
|
|
4172
|
+
if (dependencyNames.size !== Object.keys(expectedDependencies).length || [...dependencyNames].some(
|
|
4173
|
+
(name) => expectedDependencies[name] !== candidate.dependencies.find(
|
|
4174
|
+
(dependency) => dependency.name === name
|
|
4175
|
+
)?.version
|
|
4176
|
+
)) {
|
|
4177
|
+
throw new Error("Runtime manifest dependencies do not match package.json");
|
|
4178
|
+
}
|
|
4179
|
+
for (const filePath of filePaths) {
|
|
4180
|
+
for (const dependencyPath of dependencyPaths) {
|
|
4181
|
+
if (filePath === dependencyPath || filePath.startsWith(`${dependencyPath}/`) || dependencyPath.startsWith(`${filePath}/`)) {
|
|
4182
|
+
throw new Error(`Runtime manifest has overlapping paths ${filePath} and ${dependencyPath}`);
|
|
4099
4183
|
}
|
|
4100
4184
|
}
|
|
4101
4185
|
}
|
|
4186
|
+
if (candidate.closure !== void 0) {
|
|
4187
|
+
if (!isRecord2(candidate.closure) || !isSha256(candidate.closure.sourceManifestSha256) || !isTreeIntegrity(candidate.closure.tree) || candidate.closure.tree.path !== dependencyTreePath) {
|
|
4188
|
+
throw new Error("Runtime manifest contains an invalid materialized closure");
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4102
4191
|
}
|
|
4103
|
-
async function
|
|
4192
|
+
async function readPackageMetadata(root) {
|
|
4104
4193
|
let candidate;
|
|
4105
4194
|
try {
|
|
4106
4195
|
candidate = JSON.parse(await readFile2(join2(root, "package.json"), "utf8"));
|
|
4107
4196
|
} catch {
|
|
4108
4197
|
throw new Error("Runtime package.json is not valid JSON");
|
|
4109
4198
|
}
|
|
4110
|
-
if (!isRecord2(candidate) || typeof candidate.name !== "string" || typeof candidate.version !== "string") {
|
|
4111
|
-
throw new Error("Runtime package.json has
|
|
4199
|
+
if (!isRecord2(candidate) || typeof candidate.name !== "string" || typeof candidate.version !== "string" || candidate.dependencies !== void 0 && !isRecord2(candidate.dependencies) || isRecord2(candidate.dependencies) && Object.values(candidate.dependencies).some((version) => typeof version !== "string")) {
|
|
4200
|
+
throw new Error("Runtime package.json has invalid package metadata");
|
|
4201
|
+
}
|
|
4202
|
+
return {
|
|
4203
|
+
name: candidate.name,
|
|
4204
|
+
version: candidate.version,
|
|
4205
|
+
dependencies: isRecord2(candidate.dependencies) ? candidate.dependencies : {}
|
|
4206
|
+
};
|
|
4207
|
+
}
|
|
4208
|
+
function serializeManifest(manifest) {
|
|
4209
|
+
return Buffer.from(`${JSON.stringify(manifest, null, 2)}
|
|
4210
|
+
`);
|
|
4211
|
+
}
|
|
4212
|
+
function assertSameTree(expected, actual, label) {
|
|
4213
|
+
if (expected.path !== actual.path || expected.files !== actual.files || expected.bytes !== actual.bytes || expected.sha256 !== actual.sha256) {
|
|
4214
|
+
throw new Error(`Runtime ${label} changed during materialization`);
|
|
4112
4215
|
}
|
|
4113
|
-
return { name: candidate.name, version: candidate.version };
|
|
4114
4216
|
}
|
|
4115
4217
|
function resolveInside(root, path2) {
|
|
4116
4218
|
if (!isManifestPath(path2)) throw new Error(`Runtime manifest contains an unsafe path ${path2}`);
|
|
@@ -4132,6 +4234,9 @@ function isValidSize(value) {
|
|
|
4132
4234
|
function isSha256(value) {
|
|
4133
4235
|
return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
4134
4236
|
}
|
|
4237
|
+
function isTreeIntegrity(value) {
|
|
4238
|
+
return isRecord2(value) && isManifestPath(value.path) && isValidSize(value.files) && isValidSize(value.bytes) && isSha256(value.sha256);
|
|
4239
|
+
}
|
|
4135
4240
|
function isDestinationRace(error) {
|
|
4136
4241
|
const code = error.code;
|
|
4137
4242
|
return code === "EEXIST" || code === "ENOTEMPTY";
|