@moinulmoin/eden 0.7.4 → 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/README.md +3 -3
- package/dist/eve-control-plane.js +99 -5
- package/dist/eve-packaging.d.ts +34 -27
- package/dist/eve-packaging.js +338 -44
- package/dist/eve-runtime-config.js +8 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,15 +12,15 @@ If you already have an Eve project, start with `eden deploy`.
|
|
|
12
12
|
Use any one of these package managers:
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
npm install --global @moinulmoin/eden@0.
|
|
15
|
+
npm install --global @moinulmoin/eden@0.9.0
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
pnpm add --global @moinulmoin/eden@0.
|
|
19
|
+
pnpm add --global @moinulmoin/eden@0.9.0
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
|
-
bun add --global @moinulmoin/eden@0.
|
|
23
|
+
bun add --global @moinulmoin/eden@0.9.0
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Then confirm the CLI is available:
|
|
@@ -8,7 +8,7 @@ import { execFile, spawn, } from "node:child_process";
|
|
|
8
8
|
import { fileURLToPath, } from "node:url";
|
|
9
9
|
import { promisify, } from "node:util";
|
|
10
10
|
import { EveCliError, EVE_NAME_PATTERN, } from "./eve.js";
|
|
11
|
-
import { buildEveProjectSnapshot, createDockerEveProjectBuilder, revalidateEveProjectCandidateInputs, EvePackagingError, IMAGE_ID_PATTERN, } from "./eve-packaging.js";
|
|
11
|
+
import { buildEveProjectSnapshot, checkEveSandboxProvider, createDockerEveProjectBuilder, revalidateEveProjectCandidateInputs, EvePackagingError, IMAGE_ID_PATTERN, } from "./eve-packaging.js";
|
|
12
12
|
import { buildEveRuntimeImage, discardEveRuntimeImage, revalidateEveRuntimeCandidate, validateEveHostRequirements, } from "./eve-runtime-image.js";
|
|
13
13
|
import { EveRuntimeConfigError, prepareEveRuntimeInjection, redactEveRuntimeOutput, } from "./eve-runtime-config.js";
|
|
14
14
|
import { createEveHostConfig, generateEveHostWorkerSource, parseEveScheduleCron, resolveStableWorkersDevOrigin, } from "@moinulmoin/eden-runtime-cloudflare";
|
|
@@ -65,6 +65,7 @@ function mapPackagingCheck(value) {
|
|
|
65
65
|
case "DOCKER_PLATFORM_BLOCKED":
|
|
66
66
|
return "VAL-BUILD-005";
|
|
67
67
|
case "EVE_SANDBOX_RESUME_PATCH":
|
|
68
|
+
case "EVE_SANDBOX_PROVIDER":
|
|
68
69
|
return value.id;
|
|
69
70
|
default:
|
|
70
71
|
return `EVE-${value.id}`;
|
|
@@ -214,10 +215,82 @@ function pooledPostgresReason(connectionString) {
|
|
|
214
215
|
return undefined;
|
|
215
216
|
}
|
|
216
217
|
/**
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* Postgres URL breaks the World streamer, which relies on LISTEN/NOTIFY.
|
|
218
|
+
* Recognizes the generated channel, not arbitrary user authentication policies.
|
|
219
|
+
* The calls below match the Eve 0.82 initializer's agent/channels/eve.ts.
|
|
220
220
|
*/
|
|
221
|
+
async function collectEveSetupWarnings(projectRoot) {
|
|
222
|
+
const warnings = [];
|
|
223
|
+
const source = async (path) => {
|
|
224
|
+
try {
|
|
225
|
+
return await readFile(join(projectRoot, path), "utf8");
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return "";
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const channel = (await source("agent/channels/eve.ts"))
|
|
232
|
+
.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/gu, "")
|
|
233
|
+
.replace(/\s+/gu, "");
|
|
234
|
+
if (/exportdefaulteveChannel\(\{auth:\[vercelOidc\(\),localDev\(\),placeholderAuth\(\),?\],?\}\);?$/u
|
|
235
|
+
.test(channel)) {
|
|
236
|
+
warnings.push({
|
|
237
|
+
id: "EVE_AUTH_SCAFFOLD",
|
|
238
|
+
message: "Eve's generated channel auth does not grant Cloudflare callers access.",
|
|
239
|
+
remediation: "Replace agent/channels/eve.ts auth with your app's authenticator; put credentials in --env-file.",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
const manifest = JSON.parse(await source("package.json"));
|
|
244
|
+
const agent = await source("agent/agent.ts");
|
|
245
|
+
if (/export\s+default\s+defineAgent\s*\(\s*\{\s*model\s*:\s*["'][^"']+["']\s*,\s*reasoning\s*:\s*["'][^"']+["']\s*,?\s*\}\s*\)/u.test(agent)) {
|
|
246
|
+
warnings.push({
|
|
247
|
+
id: "EVE_WORLD_LOCAL",
|
|
248
|
+
message: "Local World stores memory on container disk; updates or a lost sleep snapshot reset sessions and approvals.",
|
|
249
|
+
remediation: "Configure @workflow/world-postgres; see docs/deploy.md#durable-state-postgres-world.",
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (manifest.dependencies?.["just-bash"] === undefined &&
|
|
253
|
+
agent.length > 0 &&
|
|
254
|
+
!/\bsandbox\s*:/u.test(agent)) {
|
|
255
|
+
const command = manifest.packageManager?.startsWith("pnpm@")
|
|
256
|
+
? "pnpm add just-bash"
|
|
257
|
+
: "npm install just-bash";
|
|
258
|
+
warnings.push({
|
|
259
|
+
id: "EVE_JUST_BASH_MISSING",
|
|
260
|
+
message: "Eve's default sandbox needs the just-bash production dependency in Eden's builder.",
|
|
261
|
+
remediation: `Run ${command}, commit the updated manifest and lockfile, then retry.`,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (!manifest.packageManager?.startsWith("pnpm@")) {
|
|
265
|
+
const lockText = await source("package-lock.json");
|
|
266
|
+
// Same trust level as the manifest above: npm wrote it; packaging validates it.
|
|
267
|
+
const lock = JSON.parse(lockText === "" ? "{}" : lockText);
|
|
268
|
+
const entries = lock.packages ?? {};
|
|
269
|
+
const skipped = new Set();
|
|
270
|
+
for (const [path, entry] of Object.entries(entries)) {
|
|
271
|
+
const at = path.lastIndexOf("node_modules/");
|
|
272
|
+
if (at !== -1 &&
|
|
273
|
+
entry.hasInstallScript === true &&
|
|
274
|
+
entry.dev !== true &&
|
|
275
|
+
entry.optional !== true &&
|
|
276
|
+
entry.devOptional !== true) {
|
|
277
|
+
skipped.add(path.slice(at + "node_modules/".length));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (skipped.size > 0) {
|
|
281
|
+
warnings.push({
|
|
282
|
+
id: "EVE_NPM_INSTALL_SCRIPTS_SKIPPED",
|
|
283
|
+
message: `Eden's npm install skips dependency install scripts; these production dependencies have one and may fail at runtime: ${[...skipped].sort().join(", ")}.`,
|
|
284
|
+
remediation: "Use packages that need no install script, or switch to exact-pinned pnpm and list them under allowBuilds in pnpm-workspace.yaml.",
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Packaging reports invalid manifests; setup hints must not mask that result.
|
|
291
|
+
}
|
|
292
|
+
return warnings;
|
|
293
|
+
}
|
|
221
294
|
async function collectEveWorldWarnings(candidate, runtimeConfig) {
|
|
222
295
|
const warnings = [];
|
|
223
296
|
if (candidate !== undefined) {
|
|
@@ -227,7 +300,7 @@ async function collectEveWorldWarnings(candidate, runtimeConfig) {
|
|
|
227
300
|
world === "@workflow/world-local") {
|
|
228
301
|
warnings.push({
|
|
229
302
|
id: "EVE_WORLD_LOCAL",
|
|
230
|
-
message: "
|
|
303
|
+
message: "Local World stores memory on container disk; updates or a lost sleep snapshot reset sessions and approvals.",
|
|
231
304
|
remediation: "Configure a durable Workflow World such as @workflow/world-postgres; see docs/deploy.md#durable-state-postgres-world.",
|
|
232
305
|
});
|
|
233
306
|
}
|
|
@@ -1981,6 +2054,23 @@ async function collectEvePreflight(request, options) {
|
|
|
1981
2054
|
runtimeConfig,
|
|
1982
2055
|
};
|
|
1983
2056
|
}
|
|
2057
|
+
const sandboxProvider = await checkEveSandboxProvider(request.projectRoot);
|
|
2058
|
+
if (sandboxProvider !== undefined) {
|
|
2059
|
+
checks.push(check(sandboxProvider.code, "failed", sandboxProvider.message, sandboxProvider.remediation));
|
|
2060
|
+
return {
|
|
2061
|
+
result: {
|
|
2062
|
+
command: request.command === "deploy" ? "eve deploy" : "eve preflight",
|
|
2063
|
+
ok: false,
|
|
2064
|
+
environment: request.environment,
|
|
2065
|
+
name: request.name,
|
|
2066
|
+
checks,
|
|
2067
|
+
warnings,
|
|
2068
|
+
candidate: null,
|
|
2069
|
+
},
|
|
2070
|
+
runtimeConfig,
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
warnings.push(...await collectEveSetupWarnings(request.projectRoot));
|
|
1984
2074
|
eveProgress(options, "packaging project — snapshot, frozen install, eve build");
|
|
1985
2075
|
const nodeImage = options.nodeImage ?? DEFAULT_EVE_NODE_IMAGE;
|
|
1986
2076
|
const artifactRoot = options.artifactRoot ??
|
|
@@ -2087,6 +2177,10 @@ async function collectEvePreflight(request, options) {
|
|
|
2087
2177
|
if (workflowWorld === "@moinulmoin/eden-world-cloudflare") {
|
|
2088
2178
|
checks.push(check("EVE-WORLD", "passed", "world: Cloudflare Durable Object"));
|
|
2089
2179
|
}
|
|
2180
|
+
// Compiled configuration supersedes the early generated-source hint.
|
|
2181
|
+
const localHint = warnings.findIndex((warning) => warning.id === "EVE_WORLD_LOCAL");
|
|
2182
|
+
if (localHint !== -1)
|
|
2183
|
+
warnings.splice(localHint, 1);
|
|
2090
2184
|
warnings.push(...(await collectEveWorldWarnings(candidate, undefined)));
|
|
2091
2185
|
const schedules = await readCompiledSchedules(candidate);
|
|
2092
2186
|
const expressible = schedules.filter((schedule) => parseEveScheduleCron(schedule.cron) !== undefined);
|
package/dist/eve-packaging.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type EveRuntimeInputIdentity } from "./eve-runtime-config.js";
|
|
2
|
-
export type EvePackagingCode = "ROOT_INVALID" | "UNSUPPORTED_TOOLCHAIN" | "DEPENDENCY_AMBIGUITY" | "SOURCE_RACE" | "EVE_BUILD_FAILED" | "UNSUPPORTED_EVE_OUTPUT" | "RUNTIME_CLOSURE_INCOMPLETE" | "SECRET_EXCLUSION_FAILED" | "DOCKER_PLATFORM_BLOCKED" | "UNSUPPORTED_HOST_REQUIREMENT" | "EVE_HEALTH_FAILED" | "WORLD_MIGRATION_FAILED" | "CLEANUP_UNVERIFIED";
|
|
2
|
+
export type EvePackagingCode = "ROOT_INVALID" | "UNSUPPORTED_TOOLCHAIN" | "DEPENDENCY_AMBIGUITY" | "SOURCE_RACE" | "EVE_BUILD_FAILED" | "EVE_SANDBOX_PROVIDER" | "UNSUPPORTED_EVE_OUTPUT" | "RUNTIME_CLOSURE_INCOMPLETE" | "SECRET_EXCLUSION_FAILED" | "DOCKER_PLATFORM_BLOCKED" | "UNSUPPORTED_HOST_REQUIREMENT" | "EVE_HEALTH_FAILED" | "WORLD_MIGRATION_FAILED" | "CLEANUP_UNVERIFIED";
|
|
3
3
|
export interface EveRuntimeConfigExclusion {
|
|
4
4
|
/**
|
|
5
5
|
* This path is opaque. The packaging worker only uses its lexical identity
|
|
@@ -19,18 +19,15 @@ export interface EveNodeImage {
|
|
|
19
19
|
readonly reference: string;
|
|
20
20
|
readonly digest: string;
|
|
21
21
|
}
|
|
22
|
+
export type EvePackageManager = "pnpm" | "npm";
|
|
23
|
+
export type EveInstallCommand = readonly ["corepack", "pnpm", "install", "--frozen-lockfile"] | readonly ["npm", "ci", "--ignore-scripts", "--no-audit", "--no-fund"];
|
|
22
24
|
export interface EveProjectBuilderRequest {
|
|
23
25
|
readonly generationRoot: string;
|
|
24
26
|
readonly snapshotRoot: string;
|
|
25
27
|
readonly inputManifestPath: string;
|
|
26
28
|
readonly dockerfilePath: string | undefined;
|
|
27
29
|
readonly packageManagerVersion: string;
|
|
28
|
-
readonly installCommand:
|
|
29
|
-
"corepack",
|
|
30
|
-
"pnpm",
|
|
31
|
-
"install",
|
|
32
|
-
"--frozen-lockfile"
|
|
33
|
-
];
|
|
30
|
+
readonly installCommand: EveInstallCommand;
|
|
34
31
|
readonly buildCommand: readonly ["./node_modules/.bin/eve", "build"];
|
|
35
32
|
readonly platform: "linux/amd64";
|
|
36
33
|
readonly sourceDigest: string;
|
|
@@ -75,7 +72,7 @@ export interface EveProjectInputManifest {
|
|
|
75
72
|
readonly requestedRoot: string;
|
|
76
73
|
readonly canonicalRoot: string;
|
|
77
74
|
readonly projectId: string;
|
|
78
|
-
readonly packageManager:
|
|
75
|
+
readonly packageManager: EvePackageManager;
|
|
79
76
|
readonly packageManagerVersion: string;
|
|
80
77
|
readonly packageJsonSha256: string;
|
|
81
78
|
readonly lockfileSha256: string;
|
|
@@ -123,14 +120,9 @@ export interface EveProjectBuildCandidate {
|
|
|
123
120
|
readonly generationRoot: string;
|
|
124
121
|
readonly snapshotRoot: string;
|
|
125
122
|
readonly inputManifestPath: string;
|
|
126
|
-
readonly packageManager:
|
|
123
|
+
readonly packageManager: EvePackageManager;
|
|
127
124
|
readonly packageManagerVersion: string;
|
|
128
|
-
readonly installCommand:
|
|
129
|
-
"corepack",
|
|
130
|
-
"pnpm",
|
|
131
|
-
"install",
|
|
132
|
-
"--frozen-lockfile"
|
|
133
|
-
];
|
|
125
|
+
readonly installCommand: EveInstallCommand;
|
|
134
126
|
readonly buildCommand: readonly ["./node_modules/.bin/eve", "build"];
|
|
135
127
|
readonly eveExecutable: "node_modules/.bin/eve";
|
|
136
128
|
readonly eveVersion: string;
|
|
@@ -145,14 +137,9 @@ export interface EveProjectBuildCandidate {
|
|
|
145
137
|
}
|
|
146
138
|
export interface EveProjectToolchain {
|
|
147
139
|
readonly nodeVersion: "24.17.0";
|
|
148
|
-
readonly packageManager:
|
|
140
|
+
readonly packageManager: EvePackageManager;
|
|
149
141
|
readonly packageManagerVersion: string;
|
|
150
|
-
readonly installCommand:
|
|
151
|
-
"corepack",
|
|
152
|
-
"pnpm",
|
|
153
|
-
"install",
|
|
154
|
-
"--frozen-lockfile"
|
|
155
|
-
];
|
|
142
|
+
readonly installCommand: EveInstallCommand;
|
|
156
143
|
readonly buildCommand: readonly ["./node_modules/.bin/eve", "build"];
|
|
157
144
|
readonly startCommand: readonly [
|
|
158
145
|
"./node_modules/.bin/eve",
|
|
@@ -227,7 +214,7 @@ export interface EveProjectPackagingResult {
|
|
|
227
214
|
readonly sha256: string;
|
|
228
215
|
};
|
|
229
216
|
readonly lockfile: {
|
|
230
|
-
readonly path: "pnpm-lock.yaml";
|
|
217
|
+
readonly path: "pnpm-lock.yaml" | "package-lock.json";
|
|
231
218
|
readonly sha256: string;
|
|
232
219
|
};
|
|
233
220
|
readonly sourceDigest: string;
|
|
@@ -241,9 +228,10 @@ export interface EveProjectPackagingResult {
|
|
|
241
228
|
readonly checks: readonly EvePackagingCheck[];
|
|
242
229
|
/**
|
|
243
230
|
* Outcome of the builder's temporary eve#4440 sandbox-resume patch:
|
|
244
|
-
* `applied` when the shipped just-bash binding carries it, `
|
|
245
|
-
*
|
|
246
|
-
*
|
|
231
|
+
* `applied` when the shipped just-bash binding carries it, `fixed` when the
|
|
232
|
+
* installed Eve already includes the upstream fix, `skipped` when the
|
|
233
|
+
* binding is a version Eden does not know, `absent` when this Eve build has
|
|
234
|
+
* no just-bash binding or packaging never reached the image.
|
|
247
235
|
*/
|
|
248
236
|
readonly sandboxResumePatch: EveSandboxResumePatchStatus;
|
|
249
237
|
readonly candidateImageId: string | null;
|
|
@@ -285,6 +273,25 @@ export declare const EVE_SANDBOX_RESUME_PATCH_KNOWN_SHA256 = "0ba0ac3a237c99af2f
|
|
|
285
273
|
export declare const EVE_SANDBOX_RESUME_PATCH_LATEST_SHA256 = "ab8b1ad7398af8f6fd2a1e91eb0d1ff8d2b9b9afe1836234f36ce0e896791c45";
|
|
286
274
|
/** Marker the patched binding always contains; proves the patch shipped. */
|
|
287
275
|
export declare const EVE_SANDBOX_RESUME_PATCH_SIGNATURE = "edenRefreshJustBashSessionSkills";
|
|
276
|
+
/**
|
|
277
|
+
* First Eve release whose just-bash provider keeps a session's files across
|
|
278
|
+
* template changes (vercel/eve#5132), so the patch is not needed from here on.
|
|
279
|
+
*/
|
|
280
|
+
export declare const EVE_SANDBOX_RESUME_FIXED_VERSION = "0.83.0";
|
|
281
|
+
/**
|
|
282
|
+
* True when an installed Eve version already ships the eve#4440 fix. A
|
|
283
|
+
* prerelease of the fixed version sorts before it (semver), so it may predate
|
|
284
|
+
* the fix and keeps the patch.
|
|
285
|
+
*/
|
|
286
|
+
export declare function eveIncludesSandboxResumeFix(version: unknown): boolean;
|
|
287
|
+
/**
|
|
288
|
+
* Early, source-level rejection of sandbox setups that cannot work in Eden's
|
|
289
|
+
* isolated builder: a native provider named in the sandbox module, or, on Eve
|
|
290
|
+
* 0.83.0 and later, DefaultSandbox (no module, or one that names it without
|
|
291
|
+
* JustBash). Anything it cannot recognize is left to
|
|
292
|
+
* checkPreparedJustBashTemplate, which inspects what `eve build` produced.
|
|
293
|
+
*/
|
|
294
|
+
export declare function checkEveSandboxProvider(projectRoot: string, resolvedVersion?: string): Promise<EvePackagingError | undefined>;
|
|
288
295
|
/** The exact `resume()` implementation in Eve 0.68.0, 0.71.2, 0.72.1, and 0.79.1. */
|
|
289
296
|
export declare const EVE_JUST_BASH_RESUME_ORIGINAL = "async resume(e,n,r){let i=requirePreparedJustBashArtifact(n),a=requireJustBashSessionState(r),o=createSandboxProviderIdentity({artifact:i,version:1}),s=sessionRootPath(e,i);if(a.generation!==o||a.rootPath!==s)throw Error(`just-bash session state is incompatible with this environment.`);if(!await pathExists(a.rootPath))throw Error(`just-bash session root \"${a.rootPath}\" no longer exists.`);return await openHandle(e,a.rootPath,t)}";
|
|
290
297
|
/**
|
|
@@ -318,7 +325,7 @@ export declare const EVE_SANDBOX_RESUME_PATCH_HELPERS = "async function edenRefr
|
|
|
318
325
|
*/
|
|
319
326
|
export declare const EVE_SANDBOX_RESUME_PATCH_SCRIPT: string;
|
|
320
327
|
/** Outcome of the builder's sandbox-resume patch for the packaged image. */
|
|
321
|
-
export type EveSandboxResumePatchStatus = "applied" | "skipped" | "absent";
|
|
328
|
+
export type EveSandboxResumePatchStatus = "applied" | "fixed" | "skipped" | "absent";
|
|
322
329
|
export declare function sha256(value: string | Buffer): string;
|
|
323
330
|
export declare function jsonBytes(value: unknown): Buffer;
|
|
324
331
|
export declare function isWithin(root: string, candidate: string): boolean;
|
package/dist/eve-packaging.js
CHANGED
|
@@ -48,6 +48,150 @@ export const EVE_SANDBOX_RESUME_PATCH_KNOWN_SHA256 = "0ba0ac3a237c99af2f67a6028f
|
|
|
48
48
|
export const EVE_SANDBOX_RESUME_PATCH_LATEST_SHA256 = "ab8b1ad7398af8f6fd2a1e91eb0d1ff8d2b9b9afe1836234f36ce0e896791c45";
|
|
49
49
|
/** Marker the patched binding always contains; proves the patch shipped. */
|
|
50
50
|
export const EVE_SANDBOX_RESUME_PATCH_SIGNATURE = "edenRefreshJustBashSessionSkills";
|
|
51
|
+
/**
|
|
52
|
+
* First Eve release whose just-bash provider keeps a session's files across
|
|
53
|
+
* template changes (vercel/eve#5132), so the patch is not needed from here on.
|
|
54
|
+
*/
|
|
55
|
+
export const EVE_SANDBOX_RESUME_FIXED_VERSION = "0.83.0";
|
|
56
|
+
/** `major.minor.patch` of a version string; prerelease and build suffixes are dropped. */
|
|
57
|
+
function parseEveRelease(version) {
|
|
58
|
+
const match = typeof version === "string"
|
|
59
|
+
? /^(\d+)\.(\d+)\.(\d+)/u.exec(version)
|
|
60
|
+
: null;
|
|
61
|
+
return match === null
|
|
62
|
+
? null
|
|
63
|
+
: [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* True when an installed Eve version already ships the eve#4440 fix. A
|
|
67
|
+
* prerelease of the fixed version sorts before it (semver), so it may predate
|
|
68
|
+
* the fix and keeps the patch.
|
|
69
|
+
*/
|
|
70
|
+
export function eveIncludesSandboxResumeFix(version) {
|
|
71
|
+
const installed = parseEveRelease(version);
|
|
72
|
+
const fixed = parseEveRelease(EVE_SANDBOX_RESUME_FIXED_VERSION);
|
|
73
|
+
if (installed === null || fixed === null)
|
|
74
|
+
return false;
|
|
75
|
+
for (let index = 0; index < 3; index += 1) {
|
|
76
|
+
if (installed[index] !== fixed[index]) {
|
|
77
|
+
return (installed[index] ?? 0) > (fixed[index] ?? 0);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return typeof version === "string" && !/^\d+\.\d+\.\d+-/u.test(version);
|
|
81
|
+
}
|
|
82
|
+
/** Eve 0.83.0 and later no longer fall back to just-bash in production builds. */
|
|
83
|
+
function eveRequiresExplicitJustBash(version) {
|
|
84
|
+
const release = parseEveRelease(version);
|
|
85
|
+
return release !== null && (release[0] > 0 || release[1] >= 83);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Eve reads its sandbox module from either path, with any source extension.
|
|
89
|
+
* When `agent/sandbox/` exists only the nested module counts: Eve 0.83–0.86
|
|
90
|
+
* silently ignores `agent/sandbox.ts` and 0.87+ fails the build.
|
|
91
|
+
*/
|
|
92
|
+
const EVE_SANDBOX_MODULE_PATHS = ["agent/sandbox", "agent/sandbox/sandbox"].flatMap((base) => ["ts", "mts", "cts", "tsx", "js", "mjs", "cjs", "jsx"].map((extension) => `${base}.${extension}`));
|
|
93
|
+
function justBashRemediation(command, path) {
|
|
94
|
+
return `Run ${command} (production dependency), then create or replace ${path} with:\nimport { defineSandbox } from 'eve/sandbox';\nimport { JustBashSandbox } from 'eve/sandbox/just-bash';\nexport const environment = JustBashSandbox.environment();\nexport default defineSandbox(() => environment.open());\nCommit the module, package.json, and updated lockfile, then retry eden preflight. See docs/deploy.md. Do not set EVE_DEV.`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Early, source-level rejection of sandbox setups that cannot work in Eden's
|
|
98
|
+
* isolated builder: a native provider named in the sandbox module, or, on Eve
|
|
99
|
+
* 0.83.0 and later, DefaultSandbox (no module, or one that names it without
|
|
100
|
+
* JustBash). Anything it cannot recognize is left to
|
|
101
|
+
* checkPreparedJustBashTemplate, which inspects what `eve build` produced.
|
|
102
|
+
*/
|
|
103
|
+
export async function checkEveSandboxProvider(projectRoot, resolvedVersion) {
|
|
104
|
+
const source = async (path) => readFile(join(projectRoot, path), "utf8").catch(() => "");
|
|
105
|
+
const json = async (path) => {
|
|
106
|
+
try {
|
|
107
|
+
const value = JSON.parse(await source(path));
|
|
108
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
109
|
+
? value
|
|
110
|
+
: {};
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const manifest = await json("package.json");
|
|
117
|
+
const dependencies = manifest.dependencies;
|
|
118
|
+
const lock = await json("package-lock.json");
|
|
119
|
+
const packages = lock.packages;
|
|
120
|
+
const pnpmLock = await source("pnpm-lock.yaml");
|
|
121
|
+
const pnpmVersion = /^\s{6}eve:\s*\r?\n\s{8}specifier:[^\n]*\r?\n\s{8}version:\s*['"]?(\d+\.\d+\.\d+(?:-[^('"\s]+)?)/mu
|
|
122
|
+
.exec(pnpmLock)?.[1];
|
|
123
|
+
const installed = await json("node_modules/eve/package.json");
|
|
124
|
+
const declared = typeof dependencies?.eve === "string"
|
|
125
|
+
? /^[~^]?(\d+\.\d+\.\d+)/u.exec(dependencies.eve)?.[1]
|
|
126
|
+
: undefined;
|
|
127
|
+
const version = resolvedVersion ?? packages?.["node_modules/eve"]?.version ??
|
|
128
|
+
pnpmVersion ?? installed.version ?? declared;
|
|
129
|
+
const requiresExplicit = eveRequiresExplicitJustBash(version);
|
|
130
|
+
const sandboxDirectory = (await lstat(join(projectRoot, "agent/sandbox")).catch(() => undefined))
|
|
131
|
+
?.isDirectory() === true;
|
|
132
|
+
let path;
|
|
133
|
+
for (const candidate of EVE_SANDBOX_MODULE_PATHS) {
|
|
134
|
+
if ((await lstat(join(projectRoot, candidate)).catch(() => undefined))?.isFile() === true) {
|
|
135
|
+
path = candidate;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const module = path === undefined
|
|
140
|
+
? ""
|
|
141
|
+
: (await source(path)).replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/gu, "");
|
|
142
|
+
const otherProvider = /\b(DefaultSandbox|DockerSandbox|VercelSandbox|VmSandbox|VMSandbox)\b/u
|
|
143
|
+
.exec(module)?.[1];
|
|
144
|
+
const justBash = /import\s*\{[^}]*?\bJustBashSandbox(?:\s+as\s+(\w+))?\s*(?:,|\})[^;]*?from\s*["']eve\/sandbox\/just-bash["']/u
|
|
145
|
+
.exec(module);
|
|
146
|
+
const binding = justBash?.[1] ?? "JustBashSandbox";
|
|
147
|
+
const explicit = justBash !== null &&
|
|
148
|
+
new RegExp(`\\b${binding}\\s*\\.\\s*environment\\s*\\(`, "u").test(module);
|
|
149
|
+
const native = otherProvider !== undefined && otherProvider !== "DefaultSandbox";
|
|
150
|
+
const usesDefault = path === undefined || (otherProvider === "DefaultSandbox" && !explicit);
|
|
151
|
+
const shadowed = requiresExplicit && sandboxDirectory && path?.startsWith("agent/sandbox.") === true;
|
|
152
|
+
if (!native && !shadowed && !(requiresExplicit && usesDefault))
|
|
153
|
+
return undefined;
|
|
154
|
+
const target = shadowed || (path === undefined && sandboxDirectory)
|
|
155
|
+
? "agent/sandbox/sandbox.ts"
|
|
156
|
+
: path ?? "agent/sandbox.ts";
|
|
157
|
+
const command = typeof manifest.packageManager === "string" &&
|
|
158
|
+
manifest.packageManager.startsWith("pnpm@")
|
|
159
|
+
? "pnpm add just-bash"
|
|
160
|
+
: "npm install just-bash";
|
|
161
|
+
return new EvePackagingError({
|
|
162
|
+
code: "EVE_SANDBOX_PROVIDER",
|
|
163
|
+
subject: target,
|
|
164
|
+
reason: shadowed
|
|
165
|
+
? `${String(path)} sits beside the agent/sandbox/ directory, where Eve ${String(version)} reads only agent/sandbox/sandbox.ts: Eve 0.83–0.86 ignores ${String(path)} and falls back to DefaultSandbox, and 0.87 and later fail the build.`
|
|
166
|
+
: native
|
|
167
|
+
? `${target} selects ${otherProvider}, which Eden does not support; use explicit JustBash instead.`
|
|
168
|
+
: path === undefined
|
|
169
|
+
? `Eve ${String(version)} has no sandbox module, so it uses DefaultSandbox, which has no provider in Eden's isolated builder: production builds no longer fall back to just-bash.`
|
|
170
|
+
: `${target} selects DefaultSandbox, which has no provider in Eden's isolated builder on Eve ${String(version)}: production builds no longer fall back to just-bash.`,
|
|
171
|
+
remediation: shadowed
|
|
172
|
+
? `Move ${String(path)} to agent/sandbox/sandbox.ts (delete it instead if agent/sandbox/sandbox.ts already exists), then retry eden preflight.`
|
|
173
|
+
: justBashRemediation(command, target),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Ground truth after `eve build`: on Eve 0.83.0 and later an agent whose
|
|
178
|
+
* sandbox resolved to no provider prepares no template and would fail to open
|
|
179
|
+
* its sandbox on Cloudflare, however its sandbox module is written.
|
|
180
|
+
*/
|
|
181
|
+
async function checkPreparedJustBashTemplate(snapshotRoot, version) {
|
|
182
|
+
if (!eveRequiresExplicitJustBash(version))
|
|
183
|
+
return undefined;
|
|
184
|
+
const templatesPath = `${EVE_SANDBOX_CACHE_ROOT}/just-bash/templates`;
|
|
185
|
+
const templates = await readdir(join(snapshotRoot, templatesPath)).catch(() => []);
|
|
186
|
+
if (templates.length > 0)
|
|
187
|
+
return undefined;
|
|
188
|
+
return new EvePackagingError({
|
|
189
|
+
code: "EVE_SANDBOX_PROVIDER",
|
|
190
|
+
subject: templatesPath,
|
|
191
|
+
reason: `Eve ${version} built no just-bash sandbox template, so the agent's sandbox would fail to open on Cloudflare; its sandbox module does not resolve to JustBash.`,
|
|
192
|
+
remediation: justBashRemediation("npm install just-bash (or pnpm add just-bash)", "agent/sandbox.ts (agent/sandbox/sandbox.ts when agent/sandbox/ exists)"),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
51
195
|
/** Path of the just-bash binding inside the resolved Eve package root. */
|
|
52
196
|
const EVE_JUST_BASH_BINDING_PATH = "dist/src/execution/sandbox/bindings/just-bash.js";
|
|
53
197
|
/** The exact `resume()` implementation in Eve 0.68.0, 0.71.2, 0.72.1, and 0.79.1. */
|
|
@@ -107,9 +251,23 @@ const KNOWN_PATCHES = ${JSON.stringify({
|
|
|
107
251
|
const SIGNATURE = ${JSON.stringify(EVE_SANDBOX_RESUME_PATCH_SIGNATURE)};
|
|
108
252
|
const BINDING_PATH = ${JSON.stringify(EVE_JUST_BASH_BINDING_PATH)};
|
|
109
253
|
const HELPERS = ${JSON.stringify(EVE_SANDBOX_RESUME_PATCH_HELPERS)};
|
|
254
|
+
const FIXED_VERSION = ${JSON.stringify(EVE_SANDBOX_RESUME_FIXED_VERSION)};
|
|
110
255
|
function finish(outcome, detail) {
|
|
111
256
|
console.log("eden-eve-sandbox-resume-patch: " + outcome + " (" + detail + ")");
|
|
112
257
|
}
|
|
258
|
+
function parseRelease(version) {
|
|
259
|
+
const match = typeof version === "string" ? /^(\\d+)\\.(\\d+)\\.(\\d+)/.exec(version) : null;
|
|
260
|
+
return match === null ? null : [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
261
|
+
}
|
|
262
|
+
function includesFix(version) {
|
|
263
|
+
const installed = parseRelease(version);
|
|
264
|
+
const fixed = parseRelease(FIXED_VERSION);
|
|
265
|
+
if (installed === null || fixed === null) return false;
|
|
266
|
+
for (let index = 0; index < 3; index += 1) {
|
|
267
|
+
if (installed[index] !== fixed[index]) return installed[index] > fixed[index];
|
|
268
|
+
}
|
|
269
|
+
return !/^\\d+\\.\\d+\\.\\d+-/.test(version);
|
|
270
|
+
}
|
|
113
271
|
let packageJsonPath;
|
|
114
272
|
try {
|
|
115
273
|
packageJsonPath = fs.realpathSync("node_modules/eve/package.json");
|
|
@@ -125,6 +283,16 @@ try {
|
|
|
125
283
|
finish("absent", "this eve build has no just-bash binding");
|
|
126
284
|
process.exit(0);
|
|
127
285
|
}
|
|
286
|
+
let eveVersion;
|
|
287
|
+
try {
|
|
288
|
+
eveVersion = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")).version;
|
|
289
|
+
} catch {
|
|
290
|
+
eveVersion = undefined;
|
|
291
|
+
}
|
|
292
|
+
if (!source.includes(SIGNATURE) && includesFix(eveVersion)) {
|
|
293
|
+
finish("fixed", "eve " + eveVersion + " includes the upstream fix for eve#4440");
|
|
294
|
+
process.exit(0);
|
|
295
|
+
}
|
|
128
296
|
if (source.includes(SIGNATURE)) {
|
|
129
297
|
finish("applied", "already patched");
|
|
130
298
|
process.exit(0);
|
|
@@ -167,6 +335,9 @@ const INSTALL_COMMAND = [
|
|
|
167
335
|
"install",
|
|
168
336
|
"--frozen-lockfile",
|
|
169
337
|
];
|
|
338
|
+
const NPM_INSTALL_COMMAND = ["npm", "ci", "--ignore-scripts", "--no-audit", "--no-fund"];
|
|
339
|
+
// Install this exact isolated manager without consulting project npm configuration.
|
|
340
|
+
const NPM_VERSION = "11.13.0";
|
|
170
341
|
const BUILD_COMMAND = ["./node_modules/.bin/eve", "build"];
|
|
171
342
|
const EVE_ENTRYPOINT = ".output/server/index.mjs";
|
|
172
343
|
const EVE_EXCLUDED_DIRECTORY_NAMES = new Set([
|
|
@@ -201,6 +372,7 @@ const EVE_COMPETING_LOCKFILES = new Set([
|
|
|
201
372
|
"bun.lock",
|
|
202
373
|
"bun.lockb",
|
|
203
374
|
"npm-shrinkwrap.json",
|
|
375
|
+
"pnpm-lock.yaml",
|
|
204
376
|
"package-lock.json",
|
|
205
377
|
"yarn.lock",
|
|
206
378
|
]);
|
|
@@ -548,37 +720,76 @@ function validateProjectContract(inputs) {
|
|
|
548
720
|
remediation: "Add a non-empty package.json name and retry.",
|
|
549
721
|
});
|
|
550
722
|
}
|
|
551
|
-
const
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
723
|
+
const npmLock = inputs.files.find((file) => file.relativePath === "package-lock.json");
|
|
724
|
+
const packageManager = npmLock === undefined ? "pnpm" : "npm";
|
|
725
|
+
const lockfilePath = packageManager === "npm" ? "package-lock.json" : "pnpm-lock.yaml";
|
|
726
|
+
const npmPin = typeof packageValue.packageManager === "string"
|
|
727
|
+
? /^npm@((?:[7-9]|[1-9][0-9]+)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*))$/u.exec(packageValue.packageManager)?.[1]
|
|
728
|
+
: undefined;
|
|
729
|
+
const packageManagerVersion = packageManager === "pnpm"
|
|
730
|
+
? parsePnpmVersion(packageValue.packageManager)
|
|
731
|
+
: npmPin ?? NPM_VERSION;
|
|
732
|
+
const lockFiles = inputs.files.filter((file) => file.relativePath === lockfilePath || file.relativePath.endsWith(`/${lockfilePath}`));
|
|
733
|
+
if (lockFiles.length !== 1 || lockFiles[0]?.relativePath !== lockfilePath) {
|
|
555
734
|
throw new EvePackagingError({
|
|
556
735
|
code: "DEPENDENCY_AMBIGUITY",
|
|
557
|
-
subject:
|
|
558
|
-
reason: "The selected Eve root must contain exactly one regular root
|
|
736
|
+
subject: lockfilePath,
|
|
737
|
+
reason: "The selected Eve root must contain exactly one regular root lockfile.",
|
|
559
738
|
remediation: "Remove competing or nested lockfiles and retry with the matching root lockfile.",
|
|
560
739
|
});
|
|
561
740
|
}
|
|
562
|
-
const competing = inputs.files.find((file) => EVE_COMPETING_LOCKFILES.has(basename(file.relativePath)));
|
|
741
|
+
const competing = inputs.files.find((file) => EVE_COMPETING_LOCKFILES.has(basename(file.relativePath)) && file.relativePath !== lockfilePath);
|
|
563
742
|
if (competing !== undefined) {
|
|
564
743
|
throw new EvePackagingError({
|
|
565
744
|
code: "DEPENDENCY_AMBIGUITY",
|
|
566
745
|
subject: competing.relativePath,
|
|
567
746
|
reason: "A competing package-manager lockfile was found at the selected Eve root.",
|
|
568
|
-
remediation: "Keep only
|
|
747
|
+
remediation: "Keep only one supported package manager and its matching root lockfile.",
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
if (packageManager === "npm" && packageValue.packageManager !== undefined &&
|
|
751
|
+
npmPin === undefined) {
|
|
752
|
+
throw new EvePackagingError({
|
|
753
|
+
code: "UNSUPPORTED_TOOLCHAIN",
|
|
754
|
+
subject: "package.json.packageManager",
|
|
755
|
+
reason: "npm projects may omit packageManager or declare an exact npm version (major 7 or newer).",
|
|
756
|
+
remediation: `Remove the optional packageManager field to use npm@${NPM_VERSION}, or declare an exact npm@<major>.<minor>.<patch> pin.`,
|
|
569
757
|
});
|
|
570
758
|
}
|
|
571
759
|
const lockfile = lockFiles[0];
|
|
572
|
-
|
|
760
|
+
let npmLockValid = false;
|
|
761
|
+
if (packageManager === "npm" && lockfile !== undefined) {
|
|
762
|
+
try {
|
|
763
|
+
const parsed = JSON.parse(lockfile.bytes.toString("utf8"));
|
|
764
|
+
npmLockValid = (parsed.lockfileVersion === 2 || parsed.lockfileVersion === 3) &&
|
|
765
|
+
parsed.packages !== null && typeof parsed.packages === "object" &&
|
|
766
|
+
!Array.isArray(parsed.packages) && parsed.packages[""] !== undefined;
|
|
767
|
+
for (const entry of Object.values(parsed.packages ?? {})) {
|
|
768
|
+
const dependency = entry;
|
|
769
|
+
if (typeof dependency.resolved === "string" &&
|
|
770
|
+
!dependency.resolved.startsWith("https://registry.npmjs.org/"))
|
|
771
|
+
npmLockValid = false;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
catch {
|
|
775
|
+
npmLockValid = false;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (lockfile === undefined || (packageManager === "pnpm"
|
|
779
|
+
? !lockfileMatchesPnpm(packageManagerVersion, lockfile.bytes)
|
|
780
|
+
: !npmLockValid)) {
|
|
573
781
|
throw new EvePackagingError({
|
|
574
782
|
code: "DEPENDENCY_AMBIGUITY",
|
|
575
|
-
subject:
|
|
576
|
-
reason: "The root
|
|
577
|
-
remediation: "Regenerate the lockfile with the
|
|
783
|
+
subject: lockfilePath,
|
|
784
|
+
reason: "The root lockfile is malformed or incompatible with the selected package manager or public registry.",
|
|
785
|
+
remediation: "Regenerate the root lockfile with the selected manager and public npm registry; private registries and project .npmrc configuration are not supported.",
|
|
578
786
|
});
|
|
579
787
|
}
|
|
580
788
|
return {
|
|
581
789
|
projectId,
|
|
790
|
+
packageManager,
|
|
791
|
+
lockfilePath,
|
|
792
|
+
installCommand: packageManager === "npm" ? NPM_INSTALL_COMMAND : INSTALL_COMMAND,
|
|
582
793
|
packageManagerVersion,
|
|
583
794
|
packageJsonSha256: packageFile.sha256,
|
|
584
795
|
lockfileSha256: lockfile.sha256,
|
|
@@ -1077,7 +1288,7 @@ export async function eveSnapshotSourceEntries(snapshotRoot) {
|
|
|
1077
1288
|
}
|
|
1078
1289
|
async function writeDockerBuildFiles(options) {
|
|
1079
1290
|
const image = imageReference(options.nodeImage, "The Linux/amd64 builder requires an immutable Node 24 image digest.", "Supply a verified Node 24 image reference and sha256 digest.");
|
|
1080
|
-
const installSources = ["package.json",
|
|
1291
|
+
const installSources = ["package.json", options.lockfilePath];
|
|
1081
1292
|
if ((await lstat(join(options.snapshotRoot, "pnpm-workspace.yaml"))
|
|
1082
1293
|
.catch(() => undefined))?.isFile() === true) {
|
|
1083
1294
|
installSources.push("pnpm-workspace.yaml");
|
|
@@ -1085,17 +1296,27 @@ async function writeDockerBuildFiles(options) {
|
|
|
1085
1296
|
const runtimeSourceCopies = (await eveSnapshotSourceEntries(options.snapshotRoot))
|
|
1086
1297
|
.map((name) => `COPY --from=builder ${JSON.stringify([`/workspace/${name}`, `/app/${name}`])}`)
|
|
1087
1298
|
.join("\n");
|
|
1299
|
+
const npm = options.packageManager === "npm";
|
|
1300
|
+
const lockCheck = `test "$(sha256sum ${options.lockfilePath} | cut -d ' ' -f1)" = "${options.lockfileSha256}"`;
|
|
1301
|
+
const managerSetup = npm
|
|
1302
|
+
? `npm install --global npm@${options.packageManagerVersion} --ignore-scripts --no-audit --no-fund --registry=https://registry.npmjs.org/ && test "$(npm --version)" = "${options.packageManagerVersion}"`
|
|
1303
|
+
: `corepack enable && corepack prepare pnpm@${options.packageManagerVersion} --activate && test "$(corepack pnpm --version)" = "${options.packageManagerVersion}"`;
|
|
1304
|
+
const install = npm
|
|
1305
|
+
? "npm ci --ignore-scripts --no-audit --no-fund --registry=https://registry.npmjs.org/"
|
|
1306
|
+
: "corepack pnpm install --frozen-lockfile --config.node-linker=hoisted --config.strict-dep-builds=false";
|
|
1307
|
+
const productionInstall = npm
|
|
1308
|
+
? `${install} --omit=dev`
|
|
1309
|
+
: "corepack pnpm install --frozen-lockfile --prod --config.node-linker=hoisted --config.strict-dep-builds=false";
|
|
1310
|
+
const policy = npm ? "" : ` && ${IGNORED_BUILDS_GUARD}`;
|
|
1088
1311
|
const dockerfile = `# syntax=docker/dockerfile:1
|
|
1089
1312
|
FROM --platform=linux/amd64 ${image} AS builder
|
|
1090
1313
|
WORKDIR /workspace
|
|
1091
1314
|
COPY ${JSON.stringify([...installSources, "./"])}
|
|
1092
|
-
RUN
|
|
1093
|
-
&&
|
|
1094
|
-
&&
|
|
1095
|
-
&&
|
|
1096
|
-
|
|
1097
|
-
&& ${IGNORED_BUILDS_GUARD}
|
|
1098
|
-
${EVE_SANDBOX_RESUME_PATCH_DOCKERFILE_STEP}RUN test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f1)" = "${options.lockfileSha256}"
|
|
1315
|
+
RUN ${managerSetup} \\
|
|
1316
|
+
&& ${lockCheck} \\
|
|
1317
|
+
&& ${install}${policy} \\
|
|
1318
|
+
&& ${lockCheck}
|
|
1319
|
+
${EVE_SANDBOX_RESUME_PATCH_DOCKERFILE_STEP}RUN ${lockCheck}
|
|
1099
1320
|
COPY . ./
|
|
1100
1321
|
RUN test -x ./node_modules/.bin/eve \\
|
|
1101
1322
|
&& ./node_modules/.bin/eve build \\
|
|
@@ -1103,10 +1324,10 @@ RUN test -x ./node_modules/.bin/eve \\
|
|
|
1103
1324
|
|
|
1104
1325
|
FROM builder AS runtime-deps
|
|
1105
1326
|
RUN rm -rf node_modules \\
|
|
1106
|
-
&&
|
|
1107
|
-
&& ${
|
|
1327
|
+
&& ${productionInstall}${policy} \\
|
|
1328
|
+
&& ${lockCheck} \\
|
|
1108
1329
|
&& for l in node_modules/.bin/*; do t=$(readlink "$l") || continue; case "$t" in /*) ln -sfn "$(realpath --relative-to=node_modules/.bin "$t")" "$l" ;; esac; done
|
|
1109
|
-
${EVE_SANDBOX_RESUME_PATCH_DOCKERFILE_STEP}
|
|
1330
|
+
${EVE_SANDBOX_RESUME_PATCH_DOCKERFILE_STEP}RUN ${lockCheck}
|
|
1110
1331
|
|
|
1111
1332
|
FROM --platform=linux/amd64 ${image} AS runtime
|
|
1112
1333
|
WORKDIR /app
|
|
@@ -1422,6 +1643,14 @@ async function scanGeneratedSandboxCache(snapshotRoot) {
|
|
|
1422
1643
|
const relativePath = relativeDirectory.length === 0
|
|
1423
1644
|
? entry.name
|
|
1424
1645
|
: `${relativeDirectory}/${entry.name}`;
|
|
1646
|
+
if (relativeDirectory === "" && entry.name !== "just-bash") {
|
|
1647
|
+
throw new EvePackagingError({
|
|
1648
|
+
code: "EVE_SANDBOX_PROVIDER",
|
|
1649
|
+
subject: `${EVE_SANDBOX_CACHE_ROOT}/${entry.name}`,
|
|
1650
|
+
reason: `The Eve build prepared a ${entry.name} sandbox template; Eden supports only just-bash templates.`,
|
|
1651
|
+
remediation: "Replace the sandbox module with the explicit JustBash environment in docs/deploy.md, run npm install just-bash (or pnpm add just-bash), commit the module and lockfile, then retry eden preflight without EVE_DEV.",
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1425
1654
|
const candidate = join(directory, entry.name);
|
|
1426
1655
|
if (entry.isSymbolicLink()) {
|
|
1427
1656
|
throw new EvePackagingError({
|
|
@@ -1627,20 +1856,50 @@ function classifyPnpmInstallFailure(stderr) {
|
|
|
1627
1856
|
}
|
|
1628
1857
|
return undefined;
|
|
1629
1858
|
}
|
|
1859
|
+
function classifyNpmInstallFailure(stderr) {
|
|
1860
|
+
if (!/\bnpm (?:ci|install|error|ERR!)/u.test(stderr))
|
|
1861
|
+
return undefined;
|
|
1862
|
+
const code = /npm (?:error|ERR!) code ([A-Z0-9_]+)/u.exec(stderr)?.[1];
|
|
1863
|
+
return new EvePackagingError({
|
|
1864
|
+
code: "DEPENDENCY_AMBIGUITY",
|
|
1865
|
+
subject: code === undefined ? "frozen npm install" : `npm ${code}`,
|
|
1866
|
+
reason: "The isolated pinned npm install could not complete with the captured lockfile and disabled lifecycle scripts.",
|
|
1867
|
+
remediation: "Fix the reported npm failure; regenerate package-lock.json with the selected exact npm version if it is out of sync. Use public registry dependencies without .npmrc credentials or dependency build scripts.",
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* The failing BuildKit step's own output (`#<step> <seconds> <line>`), last 20
|
|
1872
|
+
* lines, so Eve's error reaches the user instead of a generic build failure.
|
|
1873
|
+
*/
|
|
1874
|
+
function failingDockerStepOutput(stderr) {
|
|
1875
|
+
const step = [...stderr.matchAll(/^#(\d+) ERROR:/gmu)].at(-1)?.[1];
|
|
1876
|
+
if (step === undefined)
|
|
1877
|
+
return undefined;
|
|
1878
|
+
const lines = [...stderr.matchAll(new RegExp(`^#${step} \\d+\\.\\d+ (.*)$`, "gmu"))]
|
|
1879
|
+
.map((match) => (match[1] ?? "").trimEnd())
|
|
1880
|
+
.filter((line) => line.length > 0)
|
|
1881
|
+
.slice(-20);
|
|
1882
|
+
if (lines.length === 0)
|
|
1883
|
+
return undefined;
|
|
1884
|
+
const output = lines.join("\n");
|
|
1885
|
+
return output.length > 2000 ? `…${output.slice(-2000)}` : output;
|
|
1886
|
+
}
|
|
1630
1887
|
function classifyEveBuildFailure(stderr) {
|
|
1888
|
+
const output = failingDockerStepOutput(stderr);
|
|
1889
|
+
const reported = output === undefined ? "" : `\nEve reported:\n${output}`;
|
|
1631
1890
|
if (/\bjust-bash\b/u.test(stderr)) {
|
|
1632
1891
|
return new EvePackagingError({
|
|
1633
1892
|
code: "EVE_BUILD_FAILED",
|
|
1634
1893
|
subject: "eve build",
|
|
1635
|
-
reason:
|
|
1636
|
-
remediation: "Declare just-bash as a production dependency of the Eve project, regenerate
|
|
1894
|
+
reason: `The project-local Eve build failed inside the isolated Linux/amd64 builder because Eve's default sandbox needs just-bash where no Docker daemon or /dev/kvm exists.${reported}`,
|
|
1895
|
+
remediation: "Declare just-bash as a production dependency of the Eve project, regenerate its package-manager lockfile, and retry.",
|
|
1637
1896
|
});
|
|
1638
1897
|
}
|
|
1639
1898
|
return new EvePackagingError({
|
|
1640
1899
|
code: "EVE_BUILD_FAILED",
|
|
1641
1900
|
subject: "eve build",
|
|
1642
|
-
reason:
|
|
1643
|
-
remediation: "Fix the
|
|
1901
|
+
reason: `The project-local Eve build failed inside the isolated Linux/amd64 builder.${reported}`,
|
|
1902
|
+
remediation: "Fix the error Eve reported (run eve build locally to reproduce it) and retry.",
|
|
1644
1903
|
});
|
|
1645
1904
|
}
|
|
1646
1905
|
function classifyDockerBuildFailure(error) {
|
|
@@ -1655,6 +1914,9 @@ function classifyDockerBuildFailure(error) {
|
|
|
1655
1914
|
if (/\beve\b/u.test(step) && /\bbuild\b/u.test(step)) {
|
|
1656
1915
|
return classifyEveBuildFailure(stderr);
|
|
1657
1916
|
}
|
|
1917
|
+
if (/\bnpm\b/u.test(step)) {
|
|
1918
|
+
return classifyNpmInstallFailure(`npm ci\n${stderr}`);
|
|
1919
|
+
}
|
|
1658
1920
|
if (/pnpm|corepack/u.test(step)) {
|
|
1659
1921
|
const pnpmFailure = classifyPnpmInstallFailure(stderr);
|
|
1660
1922
|
if (pnpmFailure !== undefined)
|
|
@@ -1670,6 +1932,9 @@ function classifyDockerBuildFailure(error) {
|
|
|
1670
1932
|
const pnpmFailure = classifyPnpmInstallFailure(stderr);
|
|
1671
1933
|
if (pnpmFailure !== undefined)
|
|
1672
1934
|
return pnpmFailure;
|
|
1935
|
+
const npmFailure = classifyNpmInstallFailure(stderr);
|
|
1936
|
+
if (npmFailure !== undefined)
|
|
1937
|
+
return npmFailure;
|
|
1673
1938
|
if (/\bjust-bash\b|Cannot find package/u.test(stderr)) {
|
|
1674
1939
|
return classifyEveBuildFailure(stderr);
|
|
1675
1940
|
}
|
|
@@ -1748,9 +2013,18 @@ async function detectEveSandboxResumePatch(snapshotRoot) {
|
|
|
1748
2013
|
try {
|
|
1749
2014
|
const packageJsonPath = await realpath(join(snapshotRoot, "node_modules/eve/package.json"));
|
|
1750
2015
|
const binding = await readFile(join(dirname(packageJsonPath), EVE_JUST_BASH_BINDING_PATH), "utf8");
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
2016
|
+
if (binding.includes(EVE_SANDBOX_RESUME_PATCH_SIGNATURE))
|
|
2017
|
+
return "applied";
|
|
2018
|
+
const version = await readFile(packageJsonPath, "utf8")
|
|
2019
|
+
.then((text) => {
|
|
2020
|
+
const manifest = JSON.parse(text);
|
|
2021
|
+
return typeof manifest === "object" && manifest !== null &&
|
|
2022
|
+
"version" in manifest
|
|
2023
|
+
? manifest.version
|
|
2024
|
+
: undefined;
|
|
2025
|
+
})
|
|
2026
|
+
.catch(() => undefined);
|
|
2027
|
+
return eveIncludesSandboxResumeFix(version) ? "fixed" : "skipped";
|
|
1754
2028
|
}
|
|
1755
2029
|
catch {
|
|
1756
2030
|
return "absent";
|
|
@@ -1915,7 +2189,7 @@ export async function buildEveProjectSnapshot(options) {
|
|
|
1915
2189
|
requestedRoot: roots.requestedRoot,
|
|
1916
2190
|
canonicalRoot: roots.canonicalRoot,
|
|
1917
2191
|
projectId: contract.projectId,
|
|
1918
|
-
packageManager:
|
|
2192
|
+
packageManager: contract.packageManager,
|
|
1919
2193
|
packageManagerVersion: contract.packageManagerVersion,
|
|
1920
2194
|
packageJsonSha256: contract.packageJsonSha256,
|
|
1921
2195
|
lockfileSha256: contract.lockfileSha256,
|
|
@@ -1958,6 +2232,9 @@ export async function buildEveProjectSnapshot(options) {
|
|
|
1958
2232
|
const afterCopyRace = await sourceRaceErrorIfChanged(roots.canonicalRoot, initialInputs, initialRuntimeInputIdentity, options.runtimeConfig, resolve(options.artifactRoot), roots.requestedRoot);
|
|
1959
2233
|
if (afterCopyRace !== undefined)
|
|
1960
2234
|
return blockedResult(afterCopyRace, { writtenPaths });
|
|
2235
|
+
const sandboxProvider = await checkEveSandboxProvider(snapshotRoot);
|
|
2236
|
+
if (sandboxProvider !== undefined)
|
|
2237
|
+
throw sandboxProvider;
|
|
1961
2238
|
const nodeImage = options.nodeImage ?? options.builder.nodeImage;
|
|
1962
2239
|
let actualDockerfilePath;
|
|
1963
2240
|
if (nodeImage !== undefined) {
|
|
@@ -1965,6 +2242,8 @@ export async function buildEveProjectSnapshot(options) {
|
|
|
1965
2242
|
snapshotRoot,
|
|
1966
2243
|
dockerfilePath,
|
|
1967
2244
|
nodeImage,
|
|
2245
|
+
packageManager: contract.packageManager,
|
|
2246
|
+
lockfilePath: contract.lockfilePath,
|
|
1968
2247
|
packageManagerVersion: contract.packageManagerVersion,
|
|
1969
2248
|
lockfileSha256: contract.lockfileSha256,
|
|
1970
2249
|
});
|
|
@@ -1977,7 +2256,7 @@ export async function buildEveProjectSnapshot(options) {
|
|
|
1977
2256
|
inputManifestPath,
|
|
1978
2257
|
dockerfilePath: actualDockerfilePath,
|
|
1979
2258
|
packageManagerVersion: contract.packageManagerVersion,
|
|
1980
|
-
installCommand:
|
|
2259
|
+
installCommand: contract.installCommand,
|
|
1981
2260
|
buildCommand: BUILD_COMMAND,
|
|
1982
2261
|
platform: "linux/amd64",
|
|
1983
2262
|
sourceDigest: initialInputs.sourceDigest,
|
|
@@ -2009,31 +2288,37 @@ export async function buildEveProjectSnapshot(options) {
|
|
|
2009
2288
|
const cleanupError = await discardBuiltCandidate();
|
|
2010
2289
|
return blockedResult(cleanupError ?? afterBuildRace, { writtenPaths });
|
|
2011
2290
|
}
|
|
2012
|
-
const snapshotLockfile = await readStableFile(join(snapshotRoot,
|
|
2291
|
+
const snapshotLockfile = await readStableFile(join(snapshotRoot, contract.lockfilePath), snapshotRoot);
|
|
2013
2292
|
if (snapshotLockfile.sha256 !== contract.lockfileSha256 ||
|
|
2014
2293
|
!snapshotLockfile.bytes.equals(contract.lockfileBytes)) {
|
|
2015
2294
|
const dependencyError = new EvePackagingError({
|
|
2016
2295
|
code: "DEPENDENCY_AMBIGUITY",
|
|
2017
|
-
subject:
|
|
2296
|
+
subject: contract.lockfilePath,
|
|
2018
2297
|
reason: "The frozen install changed the captured lockfile bytes.",
|
|
2019
|
-
remediation: "Use the exact
|
|
2298
|
+
remediation: "Use the selected exact package manager with its frozen install command.",
|
|
2020
2299
|
});
|
|
2021
2300
|
const cleanupError = await discardBuiltCandidate();
|
|
2022
2301
|
return blockedResult(cleanupError ?? dependencyError, { writtenPaths });
|
|
2023
2302
|
}
|
|
2024
2303
|
const eve = await resolveProjectLocalEve(snapshotRoot);
|
|
2304
|
+
const sandboxProviderAfterBuild = await checkEveSandboxProvider(snapshotRoot, eve.version);
|
|
2305
|
+
if (sandboxProviderAfterBuild !== undefined)
|
|
2306
|
+
throw sandboxProviderAfterBuild;
|
|
2025
2307
|
const sandboxResumePatch = await detectEveSandboxResumePatch(snapshotRoot);
|
|
2026
2308
|
const output = await scanGeneratedOutput(snapshotRoot);
|
|
2027
2309
|
const sandboxCache = await scanGeneratedSandboxCache(snapshotRoot);
|
|
2310
|
+
const preparedTemplate = await checkPreparedJustBashTemplate(snapshotRoot, eve.version);
|
|
2311
|
+
if (preparedTemplate !== undefined)
|
|
2312
|
+
throw preparedTemplate;
|
|
2028
2313
|
await assertGeneratedTreesExcludeSecrets(snapshotRoot);
|
|
2029
2314
|
const snapshotSourceDigest = await snapshotDigest(snapshotRoot);
|
|
2030
2315
|
const runtimeManifestPath = join(generationRoot, "runtime-manifest.json");
|
|
2031
2316
|
await writeJson(runtimeManifestPath, {
|
|
2032
2317
|
version: 1,
|
|
2033
2318
|
generationId: basename(generationRoot),
|
|
2034
|
-
packageManager:
|
|
2319
|
+
packageManager: contract.packageManager,
|
|
2035
2320
|
packageManagerVersion: contract.packageManagerVersion,
|
|
2036
|
-
installCommand:
|
|
2321
|
+
installCommand: contract.installCommand,
|
|
2037
2322
|
buildCommand: BUILD_COMMAND,
|
|
2038
2323
|
startCommand: START_COMMAND,
|
|
2039
2324
|
sourceDigest: initialInputs.sourceDigest,
|
|
@@ -2093,7 +2378,7 @@ export async function buildEveProjectSnapshot(options) {
|
|
|
2093
2378
|
sha256: contract.packageJsonSha256,
|
|
2094
2379
|
},
|
|
2095
2380
|
lockfile: {
|
|
2096
|
-
path:
|
|
2381
|
+
path: contract.lockfilePath,
|
|
2097
2382
|
sha256: contract.lockfileSha256,
|
|
2098
2383
|
},
|
|
2099
2384
|
sourceDigest: initialInputs.sourceDigest,
|
|
@@ -2104,9 +2389,9 @@ export async function buildEveProjectSnapshot(options) {
|
|
|
2104
2389
|
generationRoot,
|
|
2105
2390
|
snapshotRoot,
|
|
2106
2391
|
inputManifestPath,
|
|
2107
|
-
packageManager:
|
|
2392
|
+
packageManager: contract.packageManager,
|
|
2108
2393
|
packageManagerVersion: contract.packageManagerVersion,
|
|
2109
|
-
installCommand:
|
|
2394
|
+
installCommand: contract.installCommand,
|
|
2110
2395
|
buildCommand: BUILD_COMMAND,
|
|
2111
2396
|
eveExecutable: "node_modules/.bin/eve",
|
|
2112
2397
|
eveVersion: eve.version,
|
|
@@ -2137,9 +2422,9 @@ export async function buildEveProjectSnapshot(options) {
|
|
|
2137
2422
|
snapshot,
|
|
2138
2423
|
toolchain: {
|
|
2139
2424
|
nodeVersion: "24.17.0",
|
|
2140
|
-
packageManager:
|
|
2425
|
+
packageManager: contract.packageManager,
|
|
2141
2426
|
packageManagerVersion: contract.packageManagerVersion,
|
|
2142
|
-
installCommand:
|
|
2427
|
+
installCommand: contract.installCommand,
|
|
2143
2428
|
buildCommand: BUILD_COMMAND,
|
|
2144
2429
|
startCommand: START_COMMAND,
|
|
2145
2430
|
eveExecutable: eve.path,
|
|
@@ -2172,7 +2457,7 @@ export async function buildEveProjectSnapshot(options) {
|
|
|
2172
2457
|
id: "VAL-BUILD-001",
|
|
2173
2458
|
status: "pass",
|
|
2174
2459
|
subject: "package-manager",
|
|
2175
|
-
reason: "The
|
|
2460
|
+
reason: "The selected package manager and one matching regular root lockfile were verified.",
|
|
2176
2461
|
remediation: null,
|
|
2177
2462
|
},
|
|
2178
2463
|
{
|
|
@@ -2205,6 +2490,15 @@ export async function buildEveProjectSnapshot(options) {
|
|
|
2205
2490
|
remediation: null,
|
|
2206
2491
|
}]
|
|
2207
2492
|
: []),
|
|
2493
|
+
...(sandboxResumePatch === "fixed"
|
|
2494
|
+
? [{
|
|
2495
|
+
id: "EVE_SANDBOX_RESUME_PATCH",
|
|
2496
|
+
status: "pass",
|
|
2497
|
+
subject: "eve-sandbox-resume",
|
|
2498
|
+
reason: `not needed: Eve ${EVE_SANDBOX_RESUME_FIXED_VERSION} and later include the fix for eve#4440`,
|
|
2499
|
+
remediation: null,
|
|
2500
|
+
}]
|
|
2501
|
+
: []),
|
|
2208
2502
|
],
|
|
2209
2503
|
sandboxResumePatch,
|
|
2210
2504
|
candidateImageId: buildResult.imageId ?? null,
|
|
@@ -2287,7 +2581,7 @@ export async function revalidateEveProjectCandidateInputs(candidate, runtimeConf
|
|
|
2287
2581
|
}
|
|
2288
2582
|
const observedLatest = latest ?? fail();
|
|
2289
2583
|
const packageFile = observedLatest.files.find((file) => file.relativePath === "package.json");
|
|
2290
|
-
const lockfile = observedLatest.files.find((file) => file.relativePath === "pnpm-lock.yaml");
|
|
2584
|
+
const lockfile = observedLatest.files.find((file) => file.relativePath === (candidate.packageManager === "npm" ? "package-lock.json" : "pnpm-lock.yaml"));
|
|
2291
2585
|
if (packageFile === undefined ||
|
|
2292
2586
|
lockfile === undefined ||
|
|
2293
2587
|
packageFile.sha256 !== candidate.packageJsonSha256 ||
|
|
@@ -196,11 +196,18 @@ function parseEnvironmentText(text) {
|
|
|
196
196
|
}
|
|
197
197
|
return Object.freeze(values);
|
|
198
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Values shorter than this are not masked: substring masking of `1`, `8`, or
|
|
201
|
+
* `eve` garbled every message and matched Eden's own random revision IDs,
|
|
202
|
+
* failing deploys. Credentials are longer; short values are still delivered
|
|
203
|
+
* only through the protected channels, just not masked if a tool echoes them.
|
|
204
|
+
*/
|
|
205
|
+
const MIN_REDACTED_RUNTIME_VALUE_LENGTH = 8;
|
|
199
206
|
class EveRuntimeRedactionRegistry {
|
|
200
207
|
handle = `eve-runtime-redaction-${randomUUID()}`;
|
|
201
208
|
values = new Set();
|
|
202
209
|
register(value) {
|
|
203
|
-
if (value.length
|
|
210
|
+
if (value.length >= MIN_REDACTED_RUNTIME_VALUE_LENGTH)
|
|
204
211
|
this.values.add(value);
|
|
205
212
|
}
|
|
206
213
|
redact(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moinulmoin/eden",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Deploy existing Eve projects to your own Cloudflare account.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"NOTICE"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@moinulmoin/eden-runtime-cloudflare": "0.
|
|
34
|
+
"@moinulmoin/eden-runtime-cloudflare": "0.9.0",
|
|
35
35
|
"wrangler": "4.147.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|