@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 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.7.4
15
+ npm install --global @moinulmoin/eden@0.9.0
16
16
  ```
17
17
 
18
18
  ```sh
19
- pnpm add --global @moinulmoin/eden@0.7.4
19
+ pnpm add --global @moinulmoin/eden@0.9.0
20
20
  ```
21
21
 
22
22
  ```sh
23
- bun add --global @moinulmoin/eden@0.7.4
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
- * Non-blocking World warnings. The container wipes its disk on sleep, so the
218
- * default local World loses pending approvals and sessions; and a pooled
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: "This project uses Eve's default local Workflow World; workflow state lives on the container disk, survives sleep, and resets when you update the agent.",
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);
@@ -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: readonly [
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: "pnpm";
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: "pnpm";
123
+ readonly packageManager: EvePackageManager;
127
124
  readonly packageManagerVersion: string;
128
- readonly installCommand: readonly [
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: "pnpm";
140
+ readonly packageManager: EvePackageManager;
149
141
  readonly packageManagerVersion: string;
150
- readonly installCommand: readonly [
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, `skipped` when
245
- * the binding is a version Eden does not know, `absent` when this Eve
246
- * build has no just-bash binding or packaging never reached the image.
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;
@@ -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 packageManagerVersion = parsePnpmVersion(packageValue.packageManager);
552
- const lockFiles = inputs.files.filter((file) => file.relativePath === "pnpm-lock.yaml" ||
553
- file.relativePath.endsWith("/pnpm-lock.yaml"));
554
- if (lockFiles.length !== 1 || lockFiles[0]?.relativePath !== "pnpm-lock.yaml") {
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: "pnpm-lock.yaml",
558
- reason: "The selected Eve root must contain exactly one regular root pnpm-lock.yaml.",
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 the pinned pnpm toolchain and its matching root lockfile.",
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
- if (lockfile === undefined || !lockfileMatchesPnpm(packageManagerVersion, lockfile.bytes)) {
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: "pnpm-lock.yaml",
576
- reason: "The root pnpm-lock.yaml is malformed or does not match the exact packageManager pin.",
577
- remediation: "Regenerate the lockfile with the declared pnpm version, without changing it during packaging.",
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", "pnpm-lock.yaml"];
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 corepack enable \\
1093
- && corepack prepare pnpm@${options.packageManagerVersion} --activate \\
1094
- && test "$(corepack pnpm --version)" = "${options.packageManagerVersion}" \\
1095
- && test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f1)" = "${options.lockfileSha256}" \\
1096
- && corepack pnpm install --frozen-lockfile --config.node-linker=hoisted --config.strict-dep-builds=false \\
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
- && corepack pnpm install --frozen-lockfile --prod --config.node-linker=hoisted --config.strict-dep-builds=false \\
1107
- && ${IGNORED_BUILDS_GUARD} \\
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: "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.",
1636
- remediation: "Declare just-bash as a production dependency of the Eve project, regenerate pnpm-lock.yaml, and retry.",
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: "The project-local Eve build failed inside the isolated Linux/amd64 builder.",
1643
- remediation: "Fix the project-local Eve build and retry without changing the authored project during packaging.",
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
- return binding.includes(EVE_SANDBOX_RESUME_PATCH_SIGNATURE)
1752
- ? "applied"
1753
- : "skipped";
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: "pnpm",
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: INSTALL_COMMAND,
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, "pnpm-lock.yaml"), 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: "pnpm-lock.yaml",
2296
+ subject: contract.lockfilePath,
2018
2297
  reason: "The frozen install changed the captured lockfile bytes.",
2019
- remediation: "Use the exact declared pnpm version with pnpm install --frozen-lockfile.",
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: "pnpm",
2319
+ packageManager: contract.packageManager,
2035
2320
  packageManagerVersion: contract.packageManagerVersion,
2036
- installCommand: INSTALL_COMMAND,
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: "pnpm-lock.yaml",
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: "pnpm",
2392
+ packageManager: contract.packageManager,
2108
2393
  packageManagerVersion: contract.packageManagerVersion,
2109
- installCommand: INSTALL_COMMAND,
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: "pnpm",
2425
+ packageManager: contract.packageManager,
2141
2426
  packageManagerVersion: contract.packageManagerVersion,
2142
- installCommand: INSTALL_COMMAND,
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 exact pnpm pin and one matching regular root lockfile were verified.",
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 > 0)
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.7.4",
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.7.4",
34
+ "@moinulmoin/eden-runtime-cloudflare": "0.9.0",
35
35
  "wrangler": "4.147.0"
36
36
  },
37
37
  "devDependencies": {