@moinulmoin/eden 0.2.1 → 0.3.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.2.1
15
+ npm install --global @moinulmoin/eden@0.3.0
16
16
  ```
17
17
 
18
18
  ```sh
19
- pnpm add --global @moinulmoin/eden@0.2.1
19
+ pnpm add --global @moinulmoin/eden@0.3.0
20
20
  ```
21
21
 
22
22
  ```sh
23
- bun add --global @moinulmoin/eden@0.2.1
23
+ bun add --global @moinulmoin/eden@0.3.0
24
24
  ```
25
25
 
26
26
  Then confirm the CLI is available:
@@ -31,12 +31,18 @@ export interface EveDeploymentMetadata {
31
31
  readonly runtimeVariableNames: readonly string[];
32
32
  readonly evidenceRetained: true;
33
33
  }
34
+ export interface EvePreflightWarning {
35
+ readonly id: string;
36
+ readonly message: string;
37
+ readonly remediation?: string;
38
+ }
34
39
  export interface EvePreflightResult {
35
40
  readonly command: "eve preflight" | "eve deploy";
36
41
  readonly ok: boolean;
37
42
  readonly environment: EveCliEnvironment;
38
43
  readonly name: string;
39
44
  readonly checks: readonly EvePreflightCheck[];
45
+ readonly warnings: readonly EvePreflightWarning[];
40
46
  readonly candidate: EvePreflightCandidate | null;
41
47
  readonly deployment?: EveDeploymentMetadata;
42
48
  }
@@ -77,6 +83,7 @@ export interface EvePreflightRuntimeRunnerRequest {
77
83
  readonly publicOrigin?: string;
78
84
  readonly runtimeInjection?: EveRuntimeInjection;
79
85
  readonly retainImage?: boolean;
86
+ readonly workflowWorld?: string;
80
87
  }
81
88
  export type EvePreflightRuntimeRunner = (request: EvePreflightRuntimeRunnerRequest) => EvePreflightRuntimeEvidence | Promise<EvePreflightRuntimeEvidence>;
82
89
  export type EveRuntimeImageDiscardRunner = (request: EveRuntimeImageDiscardRequest) => boolean | Promise<boolean>;
@@ -237,6 +244,15 @@ export interface EveDestroyCloudflareReadRequest {
237
244
  /** Exact `repository:tag` image refs recorded for this target. */
238
245
  readonly registryImages?: readonly string[];
239
246
  }
247
+ /**
248
+ * Container applications in the account that belong to this exact target.
249
+ * Eden names a target's Container application `<name>-container`; containers
250
+ * owned by anything else in the account never count.
251
+ */
252
+ export declare function exactTargetContainerEntries(value: unknown, name: string): readonly {
253
+ readonly id: string;
254
+ readonly name: string;
255
+ }[] | undefined;
240
256
  export declare function runEveControlPlane(request: EveCliExecutionRequest, options?: EvePreflightOptions): Promise<void>;
241
257
  export interface EveDestroyOutcome {
242
258
  readonly ok: boolean;
@@ -75,6 +75,171 @@ function blockedAfterFailure(id, message) {
75
75
  function allChecksClear(checks) {
76
76
  return checks.every((value) => value.status === "passed" || value.status === "skipped");
77
77
  }
78
+ /**
79
+ * Path, inside the built snapshot, of the manifest `eve build` writes with the
80
+ * resolved agent configuration. `config.experimental.workflow.world` is the
81
+ * field Eve itself resolves at build time; absent/`local`/
82
+ * `@workflow/world-local` mean Eve's container-local default World.
83
+ */
84
+ const EVE_COMPILED_MANIFEST_PATH = ".output/.eve/compile/compiled-agent-manifest.json";
85
+ async function readCompiledWorldSpecifier(candidate) {
86
+ try {
87
+ const manifest = JSON.parse(await readFile(join(candidate.snapshotRoot, EVE_COMPILED_MANIFEST_PATH), "utf8"));
88
+ const world = manifest?.config?.experimental?.workflow?.world;
89
+ return typeof world === "string" ? world : undefined;
90
+ }
91
+ catch {
92
+ return undefined;
93
+ }
94
+ }
95
+ /**
96
+ * Reads authored schedules out of the compiled manifest `eve build` writes.
97
+ * Root `schedules` covers file and extension-mounted schedules; declared
98
+ * subagents cannot carry schedules, so only the root array is consulted.
99
+ */
100
+ async function readCompiledSchedules(candidate) {
101
+ try {
102
+ const manifest = JSON.parse(await readFile(join(candidate.snapshotRoot, EVE_COMPILED_MANIFEST_PATH), "utf8"));
103
+ const schedules = manifest?.schedules;
104
+ if (!Array.isArray(schedules))
105
+ return [];
106
+ return schedules.flatMap((schedule) => typeof schedule.name === "string" && typeof schedule.cron === "string"
107
+ ? [{ name: schedule.name, cron: schedule.cron }]
108
+ : []);
109
+ }
110
+ catch {
111
+ return [];
112
+ }
113
+ }
114
+ /**
115
+ * Mirrors Eve's own `assertWorkflowWorldCompatibility`
116
+ * (dist/src/internal/workflow/world-compatibility.js): Eve derives the
117
+ * expected Workflow line from its installed manifest's `@workflow/core`
118
+ * dependency, reads the selected World's manifest for a `@workflow/core` or
119
+ * `@workflow/world` dependency, and fails only on a definite line mismatch —
120
+ * different major, or both prerelease tags present and different.
121
+ */
122
+ function parseWorkflowVersionLine(range) {
123
+ const match = /(\d+)\.(?:\d+|x|\*)(?:\.(?:\d+|x|\*))?(?:-([0-9A-Za-z.-]+))?/u.exec(range.trim());
124
+ if (match === null)
125
+ return undefined;
126
+ const major = Number(match[1]);
127
+ if (!Number.isInteger(major))
128
+ return undefined;
129
+ return {
130
+ major,
131
+ ...(match[2] === undefined
132
+ ? {}
133
+ : { prereleaseTag: match[2].split(".")[0] }),
134
+ };
135
+ }
136
+ async function readInstalledManifest(snapshotRoot, packageName) {
137
+ try {
138
+ return JSON.parse(await readFile(join(snapshotRoot, "node_modules", packageName, "package.json"), "utf8"));
139
+ }
140
+ catch {
141
+ return undefined;
142
+ }
143
+ }
144
+ /**
145
+ * Fails deploy/preflight when the selected `@workflow/world-postgres` would
146
+ * trip Eve's runtime World-compatibility rejection. Unreadable manifests
147
+ * mirror Eve's own behavior (skip); Eve is the runtime authority.
148
+ */
149
+ async function checkEveWorldPairing(candidate, world) {
150
+ if (world !== "@workflow/world-postgres")
151
+ return undefined;
152
+ const eveManifest = await readInstalledManifest(candidate.snapshotRoot, "eve");
153
+ const expectedRange = eveManifest === undefined
154
+ ? undefined
155
+ : [
156
+ eveManifest.devDependencies,
157
+ eveManifest.dependencies,
158
+ eveManifest.peerDependencies,
159
+ ]
160
+ .map((deps) => deps?.["@workflow/core"])
161
+ .find((value) => typeof value === "string" && value.trim().length > 0);
162
+ if (expectedRange === undefined)
163
+ return undefined;
164
+ const worldManifest = await readInstalledManifest(candidate.snapshotRoot, world);
165
+ const declared = worldManifest === undefined
166
+ ? undefined
167
+ : ["@workflow/core", "@workflow/world"]
168
+ .map((name) => ({
169
+ packageName: name,
170
+ range: worldManifest.dependencies?.[name] ??
171
+ worldManifest.peerDependencies?.[name],
172
+ }))
173
+ .find((entry) => typeof entry.range === "string" && entry.range.trim().length > 0);
174
+ if (declared === undefined)
175
+ return undefined;
176
+ const declaredLine = parseWorkflowVersionLine(declared.range);
177
+ const expectedLine = parseWorkflowVersionLine(expectedRange);
178
+ if (declaredLine === undefined || expectedLine === undefined) {
179
+ return undefined;
180
+ }
181
+ const mismatch = declaredLine.major !== expectedLine.major ||
182
+ (declaredLine.prereleaseTag !== undefined &&
183
+ expectedLine.prereleaseTag !== undefined &&
184
+ declaredLine.prereleaseTag !== expectedLine.prereleaseTag);
185
+ if (mismatch) {
186
+ return check("EVE_WORLD_PAIRING", "failed", `The installed @workflow/world-postgres targets ${declared.packageName} ${declared.range}, but the project's Eve bundles @workflow/core ${expectedRange}; Eve rejects this World at runtime.`, `Install the @workflow/world-postgres release whose ${declared.packageName} dependency matches the Eve-bundled @workflow/core ${expectedRange} line (e.g. \`pnpm add @workflow/world-postgres@<matching-version>\`); see docs/deploy.md#durable-state-postgres-world.`);
187
+ }
188
+ return check("EVE_WORLD_PAIRING", "passed", `The installed @workflow/world-postgres ${declared.packageName} dependency ${declared.range} pairs with the Eve-bundled @workflow/core ${expectedRange}.`);
189
+ }
190
+ /**
191
+ * Returns a generic reason when the URL targets a transaction-mode pooler.
192
+ * Never returns URL content; only a safe classification of the host/port.
193
+ */
194
+ function pooledPostgresReason(connectionString) {
195
+ if (connectionString === undefined)
196
+ return undefined;
197
+ let url;
198
+ try {
199
+ url = new URL(connectionString);
200
+ }
201
+ catch {
202
+ return undefined;
203
+ }
204
+ if (url.hostname.includes("-pooler")) {
205
+ return "a transaction-mode pooler host";
206
+ }
207
+ if (url.port === "6543") {
208
+ return "a transaction-mode pooler port";
209
+ }
210
+ return undefined;
211
+ }
212
+ /**
213
+ * Non-blocking World warnings. The container wipes its disk on sleep, so the
214
+ * default local World loses pending approvals and sessions; and a pooled
215
+ * Postgres URL breaks the World streamer, which relies on LISTEN/NOTIFY.
216
+ */
217
+ async function collectEveWorldWarnings(candidate, runtimeConfig) {
218
+ const warnings = [];
219
+ if (candidate !== undefined) {
220
+ const world = await readCompiledWorldSpecifier(candidate);
221
+ if (world === undefined ||
222
+ world === "local" ||
223
+ world === "@workflow/world-local") {
224
+ warnings.push({
225
+ id: "EVE_WORLD_LOCAL",
226
+ message: "This project uses Eve's default local Workflow World; workflow state lives on the container disk and is lost when the container sleeps.",
227
+ remediation: "Configure a durable Workflow World such as @workflow/world-postgres; see docs/deploy.md#durable-state-postgres-world.",
228
+ });
229
+ }
230
+ }
231
+ if (runtimeConfig !== undefined) {
232
+ const pooledReason = runtimeConfig.withProtectedValues((values) => pooledPostgresReason(values.WORKFLOW_POSTGRES_URL ?? values.DATABASE_URL));
233
+ if (pooledReason !== undefined) {
234
+ warnings.push({
235
+ id: "EVE_WORLD_POSTGRES_POOLED",
236
+ message: `The configured Postgres connection uses ${pooledReason}; transaction-mode poolers accept LISTEN but never deliver notifications, so live session streaming hangs.`,
237
+ remediation: "Use the direct, unpooled Postgres URL for WORKFLOW_POSTGRES_URL (on Neon, the unpooled host without -pooler); see docs/deploy.md#durable-state-postgres-world.",
238
+ });
239
+ }
240
+ }
241
+ return warnings;
242
+ }
78
243
  /** True when the caller asked for the human progress/summary output. */
79
244
  function humanOutput(options) {
80
245
  return options.outputFormat === "human";
@@ -113,6 +278,12 @@ function formatEveElapsed(startedAt) {
113
278
  */
114
279
  function humanEveResultLines(result, startedAt, eveVersion) {
115
280
  const lines = [...eveCheckLines(result.checks)];
281
+ for (const warning of result.warnings) {
282
+ lines.push(`! ${warning.id} — ${safeText(warning.message)}`);
283
+ if (warning.remediation !== undefined) {
284
+ lines.push(` → ${safeText(warning.remediation)}`);
285
+ }
286
+ }
116
287
  const elapsed = formatEveElapsed(startedAt);
117
288
  const commandLabel = result.command === "eve deploy" ? "deploy" : "preflight";
118
289
  if (result.ok) {
@@ -353,6 +524,14 @@ async function readWorkersDevSubdomain(accountId) {
353
524
  return undefined;
354
525
  }
355
526
  }
527
+ /**
528
+ * Container applications in the account that belong to this exact target.
529
+ * Eden names a target's Container application `<name>-container`; containers
530
+ * owned by anything else in the account never count.
531
+ */
532
+ export function exactTargetContainerEntries(value, name) {
533
+ return exactContainerEntries(value, boundedResourceName(name, "container"));
534
+ }
356
535
  async function defaultCloudflareRead(request) {
357
536
  const account = await readWranglerJson(["whoami", "--json"]);
358
537
  const containers = await readWranglerJson([
@@ -362,7 +541,6 @@ async function defaultCloudflareRead(request) {
362
541
  "--per-page",
363
542
  "100",
364
543
  ]);
365
- const containerCollection = jsonCollection(containers.value);
366
544
  const deployments = await readWranglerJson([
367
545
  "deployments",
368
546
  "list",
@@ -374,7 +552,7 @@ async function defaultCloudflareRead(request) {
374
552
  const accountAccess = account.failed || accountId === undefined
375
553
  ? "unavailable"
376
554
  : "available";
377
- const containerEntries = exactContainerEntries(containers.value, request.name);
555
+ const containerEntries = exactTargetContainerEntries(containers.value, request.name);
378
556
  const containerAccess = containers.failed || containerEntries === undefined
379
557
  ? "unavailable"
380
558
  : "available";
@@ -403,7 +581,7 @@ async function defaultCloudflareRead(request) {
403
581
  };
404
582
  }
405
583
  if (deploymentEntries.length === 0 &&
406
- containerCollection?.length === 0) {
584
+ containerEntries?.length === 0) {
407
585
  return {
408
586
  accountAccess,
409
587
  containerAccess,
@@ -490,6 +668,9 @@ async function defaultRuntimeRunner(request) {
490
668
  ...(request.runtimeInjection === undefined
491
669
  ? {}
492
670
  : { runtimeInjection: request.runtimeInjection }),
671
+ ...(request.workflowWorld === undefined
672
+ ? {}
673
+ : { workflowWorld: request.workflowWorld }),
493
674
  retainImage: request.retainImage ?? false,
494
675
  };
495
676
  result = await buildEveRuntimeImage(imageRequest);
@@ -1235,6 +1416,10 @@ async function runEveDeployment(request, options, collected) {
1235
1416
  ...(identity.runtimeRevisionHandle === undefined
1236
1417
  ? {}
1237
1418
  : { runtimeRevisionHandle: identity.runtimeRevisionHandle }),
1419
+ schedules: collected.schedules ?? [],
1420
+ ...(process.env.EDEN_EVE_CONTAINER_SLEEP_AFTER === undefined
1421
+ ? {}
1422
+ : { containerSleepAfter: process.env.EDEN_EVE_CONTAINER_SLEEP_AFTER }),
1238
1423
  });
1239
1424
  }
1240
1425
  catch (error) {
@@ -1488,6 +1673,7 @@ async function runEveDeployment(request, options, collected) {
1488
1673
  }
1489
1674
  async function collectEvePreflight(request, options) {
1490
1675
  let runtimeConfig;
1676
+ const warnings = [];
1491
1677
  const checks = [];
1492
1678
  if (request.envFile !== undefined) {
1493
1679
  eveProgress(options, "validating environment file");
@@ -1502,6 +1688,7 @@ async function collectEvePreflight(request, options) {
1502
1688
  else {
1503
1689
  runtimeConfig = await options.runtimeConfigLoader(request.envFile, request.cwd);
1504
1690
  checks.push(check("VAL-SEC-001", "passed", "The explicit environment file passed the deployment-safety opaque UTF-8 KEY=VALUE grammar."), check("VAL-SEC-002", "passed", "The explicit environment file contains no reserved Eve host-variable collision."));
1691
+ warnings.push(...(await collectEveWorldWarnings(undefined, runtimeConfig)));
1505
1692
  }
1506
1693
  }
1507
1694
  catch (error) {
@@ -1515,6 +1702,7 @@ async function collectEvePreflight(request, options) {
1515
1702
  environment: request.environment,
1516
1703
  name: request.name,
1517
1704
  checks,
1705
+ warnings,
1518
1706
  candidate: null,
1519
1707
  },
1520
1708
  runtimeConfig,
@@ -1608,12 +1796,49 @@ async function collectEvePreflight(request, options) {
1608
1796
  environment: request.environment,
1609
1797
  name: request.name,
1610
1798
  checks,
1799
+ warnings,
1611
1800
  candidate: null,
1612
1801
  },
1613
1802
  runtimeConfig,
1614
1803
  };
1615
1804
  }
1616
1805
  const candidate = packaging.candidate;
1806
+ const workflowWorld = await readCompiledWorldSpecifier(candidate);
1807
+ warnings.push(...(await collectEveWorldWarnings(candidate, undefined)));
1808
+ const schedules = await readCompiledSchedules(candidate);
1809
+ // The runtime package is imported lazily so `eden` commands that never
1810
+ // reach deploy checks stay fast; the module specifier is fixed.
1811
+ const { parseEveScheduleCron } = await import("@moinulmoin/eden-runtime-cloudflare");
1812
+ const expressible = schedules.filter((schedule) => parseEveScheduleCron(schedule.cron) !== undefined);
1813
+ const unexpressible = schedules.filter((schedule) => parseEveScheduleCron(schedule.cron) === undefined);
1814
+ if (unexpressible.length > 0) {
1815
+ warnings.push({
1816
+ id: "EVE_SCHEDULE_UNSUPPORTED",
1817
+ message: `Schedule ${unexpressible.map((schedule) => schedule.name).join(", ")} uses a cron expression Cloudflare wake triggers cannot evaluate, so it only fires while the Container is already awake.`,
1818
+ remediation: "Use a standard 5-field cron expression (minute hour day-of-month month day-of-week) for schedules that must fire while the Container sleeps.",
1819
+ });
1820
+ }
1821
+ if (expressible.length > 0) {
1822
+ checks.push(check("EVE-SCHEDULES", "passed", `schedules: ${expressible.length} (wake via Cloudflare Cron)`));
1823
+ }
1824
+ const worldPairing = await checkEveWorldPairing(candidate, workflowWorld);
1825
+ if (worldPairing !== undefined)
1826
+ checks.push(worldPairing);
1827
+ if (!allChecksClear(checks)) {
1828
+ await discardPackagingImage();
1829
+ return {
1830
+ result: {
1831
+ command: request.command === "deploy" ? "eve deploy" : "eve preflight",
1832
+ ok: false,
1833
+ environment: request.environment,
1834
+ name: request.name,
1835
+ checks,
1836
+ warnings,
1837
+ candidate: candidateMetadata(candidate, undefined),
1838
+ },
1839
+ runtimeConfig,
1840
+ };
1841
+ }
1617
1842
  let cloudflare;
1618
1843
  let publicOrigin;
1619
1844
  if (request.command === "deploy") {
@@ -1636,6 +1861,7 @@ async function collectEvePreflight(request, options) {
1636
1861
  environment: request.environment,
1637
1862
  name: request.name,
1638
1863
  checks,
1864
+ warnings,
1639
1865
  candidate: candidateMetadata(candidate, undefined),
1640
1866
  },
1641
1867
  runtimeConfig,
@@ -1655,6 +1881,7 @@ async function collectEvePreflight(request, options) {
1655
1881
  environment: request.environment,
1656
1882
  name: request.name,
1657
1883
  checks,
1884
+ warnings,
1658
1885
  candidate: candidateMetadata(candidate, undefined),
1659
1886
  },
1660
1887
  runtimeConfig,
@@ -1693,7 +1920,7 @@ async function collectEvePreflight(request, options) {
1693
1920
  if (hostRequirements !== undefined && hostRequirementsValid) {
1694
1921
  eveProgress(options, "building linux/amd64 runtime image, booting eve, checking health");
1695
1922
  try {
1696
- if (runtimeConfig !== undefined && request.command === "preflight") {
1923
+ if (runtimeConfig !== undefined) {
1697
1924
  runtimeInjection = await prepareEveRuntimeInjection(runtimeConfig, {
1698
1925
  mode: "preflight",
1699
1926
  });
@@ -1707,6 +1934,7 @@ async function collectEvePreflight(request, options) {
1707
1934
  ...(publicOrigin === undefined ? {} : { publicOrigin }),
1708
1935
  retainImage: request.command === "deploy",
1709
1936
  ...(runtimeInjection === undefined ? {} : { runtimeInjection }),
1937
+ ...(workflowWorld === undefined ? {} : { workflowWorld }),
1710
1938
  });
1711
1939
  checks.push(...runtimeEvidence.checks);
1712
1940
  const requiredRuntimeChecks = [
@@ -1759,6 +1987,7 @@ async function collectEvePreflight(request, options) {
1759
1987
  environment: request.environment,
1760
1988
  name: request.name,
1761
1989
  checks,
1990
+ warnings,
1762
1991
  candidate: candidateResult,
1763
1992
  },
1764
1993
  runtimeConfig,
@@ -1784,6 +2013,7 @@ async function collectEvePreflight(request, options) {
1784
2013
  environment: request.environment,
1785
2014
  name: request.name,
1786
2015
  checks,
2016
+ warnings,
1787
2017
  candidate: candidateResult,
1788
2018
  },
1789
2019
  runtimeConfig,
@@ -1799,6 +2029,7 @@ async function collectEvePreflight(request, options) {
1799
2029
  environment: request.environment,
1800
2030
  name: request.name,
1801
2031
  checks,
2032
+ warnings,
1802
2033
  candidate: candidateResult,
1803
2034
  },
1804
2035
  runtimeConfig,
@@ -1807,6 +2038,9 @@ async function collectEvePreflight(request, options) {
1807
2038
  ? {}
1808
2039
  : { projectId: packaging.project.projectId }),
1809
2040
  ...(runtimeEvidence === undefined ? {} : { runtimeEvidence }),
2041
+ // Only expressible schedules are wired into wake triggers; the rest were
2042
+ // reported as EVE_SCHEDULE_UNSUPPORTED and fire only while awake.
2043
+ schedules: expressible,
1810
2044
  cloudflare,
1811
2045
  };
1812
2046
  }
@@ -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" | "CLEANUP_UNVERIFIED" | "PACKAGE_VERIFICATION_FAILED";
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" | "PACKAGE_VERIFICATION_FAILED";
3
3
  export interface EveRuntimeConfigExclusion {
4
4
  /**
5
5
  * This path is opaque. The packaging worker only uses its lexical identity
@@ -16,6 +16,17 @@ export class EvePackagingError extends Error {
16
16
  this.remediation = options.remediation;
17
17
  }
18
18
  }
19
+ // The Postgres World's `cbor-x` dependency declares `cbor-extract` as an
20
+ // optional native accelerator whose prebuilt binary cannot load in the
21
+ // runtime image. pnpm's CLI cannot exempt a single package from
22
+ // strict-dep-builds, so the Dockerfile passes
23
+ // `--config.strict-dep-builds=false` and then runs
24
+ // IGNORED_BUILDS_GUARD, which restores the strict failure for every ignored
25
+ // build script except cbor-extract. Eden never runs dependency build scripts
26
+ // either way, `--frozen-lockfile` stays honest, cbor-extract's .node binary is
27
+ // exempt from the ldd check, and the runtime stage sets
28
+ // CBOR_NATIVE_ACCELERATION_DISABLED.
29
+ const IGNORED_BUILDS_GUARD = `node -e 'const m=JSON.parse(require("fs").readFileSync("node_modules/.modules.yaml","utf8"));const b=(m.ignoredBuilds||[]).filter((p)=>!p.startsWith("cbor-extract@"));if(b.length){console.error("ERR_PNPM_IGNORED_BUILDS Ignored build scripts: "+b.join(", "));process.exit(1)}'`;
19
30
  const INSTALL_COMMAND = [
20
31
  "corepack",
21
32
  "pnpm",
@@ -943,7 +954,8 @@ RUN corepack enable \\
943
954
  && corepack prepare pnpm@${options.packageManagerVersion} --activate \\
944
955
  && test "$(corepack pnpm --version)" = "${options.packageManagerVersion}" \\
945
956
  && test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f1)" = "${options.lockfileSha256}" \\
946
- && corepack pnpm install --frozen-lockfile --config.node-linker=hoisted
957
+ && corepack pnpm install --frozen-lockfile --config.node-linker=hoisted --config.strict-dep-builds=false \\
958
+ && ${IGNORED_BUILDS_GUARD}
947
959
  RUN test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f1)" = "${options.lockfileSha256}"
948
960
  COPY . ./
949
961
  RUN test -x ./node_modules/.bin/eve \\
@@ -951,7 +963,8 @@ RUN test -x ./node_modules/.bin/eve \\
951
963
 
952
964
  FROM builder AS runtime-deps
953
965
  RUN rm -rf node_modules \\
954
- && corepack pnpm install --frozen-lockfile --prod --config.node-linker=hoisted \\
966
+ && corepack pnpm install --frozen-lockfile --prod --config.node-linker=hoisted --config.strict-dep-builds=false \\
967
+ && ${IGNORED_BUILDS_GUARD} \\
955
968
  && 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
956
969
 
957
970
  FROM --platform=linux/amd64 ${image} AS runtime
@@ -1339,7 +1352,7 @@ function classifyPnpmInstallFailure(stderr) {
1339
1352
  return new EvePackagingError({
1340
1353
  code: "DEPENDENCY_AMBIGUITY",
1341
1354
  subject: "ERR_PNPM_IGNORED_BUILDS",
1342
- reason: "The isolated pinned pnpm install refused dependency build scripts that the project pnpm policy neither allows nor ignores.",
1355
+ reason: "The isolated pinned pnpm install refused dependency build scripts that the project pnpm policy neither allows nor ignores (the known-optional cbor-extract addon is already exempted by Eden).",
1343
1356
  remediation: "Name the dependency under allowBuilds or ignoredBuiltDependencies in the project pnpm-workspace.yaml and retry.",
1344
1357
  });
1345
1358
  }
@@ -86,6 +86,14 @@ export interface EveRuntimeImageRequest {
86
86
  readonly hostRequirements?: EveHostRequirements;
87
87
  readonly publicOrigin?: string;
88
88
  readonly runtimeInjection?: EveRuntimeInjection;
89
+ /**
90
+ * The Workflow World specifier resolved by `eve build`
91
+ * (`config.experimental.workflow.world`). When it selects a known durable
92
+ * World with a one-time schema setup, Eden runs that setup inside the boot
93
+ * probe — idempotently, before health — so the deployed container finds the
94
+ * schema already present.
95
+ */
96
+ readonly workflowWorld?: string;
89
97
  /**
90
98
  * Preflight must remove its disposable runtime image after health proof.
91
99
  * Deploy may retain the exact image for the publication handoff.
@@ -1,5 +1,5 @@
1
1
  import { createHash, } from "node:crypto";
2
- import { execFile, spawn, } from "node:child_process";
2
+ import { execFile, } from "node:child_process";
3
3
  import { cp, lstat, mkdir, readFile, readlink, readdir, realpath, rm, symlink, writeFile, } from "node:fs/promises";
4
4
  import { dirname, join, relative, resolve, } from "node:path";
5
5
  import { promisify, } from "node:util";
@@ -448,7 +448,8 @@ ENV HOST=0.0.0.0 \\
448
448
  NITRO_HOST=0.0.0.0 \\
449
449
  PORT=8080 \\
450
450
  NITRO_PORT=8080 \\
451
- NODE_ENV=production
451
+ NODE_ENV=production \
452
+ CBOR_NATIVE_ACCELERATION_DISABLED=true
452
453
  COPY --from=candidate /candidate/.output /workspace/.output
453
454
  COPY --from=candidate /candidate/node_modules /workspace/node_modules
454
455
  ${sourceCopyLines}
@@ -541,42 +542,22 @@ function safeDockerEnvironment() {
541
542
  DOCKER_CONTEXT: process.env.DOCKER_CONTEXT,
542
543
  };
543
544
  }
545
+ /**
546
+ * Runs the Docker CLI. `environment` values reach the CLI process only, so
547
+ * `--env NAME` arguments (names, never values) can forward runtime secrets
548
+ * into a container without them touching argv, logs, or files. `--env-file
549
+ * /dev/stdin` is not used: on Linux, Node's spawned stdin is a socket that
550
+ * `/dev/stdin` cannot open.
551
+ */
544
552
  async function docker(state, args, options = {}) {
545
553
  return execFileAsync(state.command, [...args], {
546
- env: state.env,
554
+ env: { ...state.env, ...options.environment },
547
555
  cwd: state.generationRoot,
548
556
  maxBuffer: options.maxBuffer ?? 4 * 1024 * 1024,
549
557
  });
550
558
  }
551
- async function dockerWithInput(state, args, input) {
552
- return new Promise((resolveResult, rejectResult) => {
553
- const child = spawn(state.command, [...args], {
554
- cwd: state.generationRoot,
555
- env: state.env,
556
- stdio: ["pipe", "pipe", "pipe"],
557
- });
558
- let stdout = "";
559
- let stderr = "";
560
- child.stdout.on("data", (chunk) => {
561
- stdout += chunk.toString();
562
- });
563
- child.stderr.on("data", (chunk) => {
564
- stderr += chunk.toString();
565
- });
566
- child.once("error", rejectResult);
567
- child.once("close", (code) => {
568
- if (code === 0) {
569
- resolveResult({ stdout, stderr });
570
- }
571
- else {
572
- const error = new Error(stderr || "The Docker operation failed.");
573
- Object.assign(error, { stdout, stderr, code });
574
- rejectResult(error);
575
- }
576
- });
577
- child.stdin.end(input);
578
- });
579
- }
559
+ /** Names that must keep the local Docker CLI's own values. */
560
+ const DOCKER_CLI_OWNED_VARIABLES = ["PATH", "HOME", "DOCKER_HOST", "DOCKER_CONTEXT"];
580
561
  function runtimeContainerEnvironment(request) {
581
562
  return {
582
563
  ...HOST_ENVIRONMENT,
@@ -936,6 +917,12 @@ export async function buildEveRuntimeImage(request) {
936
917
  hostEnvironment: runtimeContainerEnvironment(request),
937
918
  run: (startRequest) => startRequest.env,
938
919
  });
920
+ if (request.runtimeInjection !== undefined) {
921
+ const collision = Object.keys(runtimeEnvironment).find((name) => DOCKER_CLI_OWNED_VARIABLES.includes(name));
922
+ if (collision !== undefined) {
923
+ throw packagingError("DOCKER_PLATFORM_BLOCKED", "runtime variable name", `The runtime variable ${collision} would replace the local Docker CLI's own ${collision}.`, `Rename ${collision} in --env-file; ${DOCKER_CLI_OWNED_VARIABLES.join(", ")} are reserved for the local Docker CLI.`);
924
+ }
925
+ }
939
926
  const bootArgs = [
940
927
  "run",
941
928
  "--detach",
@@ -950,14 +937,12 @@ export async function buildEveRuntimeImage(request) {
950
937
  "--env",
951
938
  `${name}=${value}`,
952
939
  ])
953
- : ["--env-file", "/dev/stdin"]),
940
+ : Object.keys(runtimeEnvironment).flatMap((name) => ["--env", name])),
954
941
  state.imageId,
955
942
  ];
956
- const boot = request.runtimeInjection === undefined
957
- ? await docker(state, bootArgs)
958
- : await dockerWithInput(state, bootArgs, `${Object.entries(runtimeEnvironment)
959
- .map(([name, value]) => `${name}=${value}`)
960
- .join("\n")}\n`);
943
+ const boot = await docker(state, bootArgs, request.runtimeInjection === undefined
944
+ ? {}
945
+ : { environment: runtimeEnvironment });
961
946
  state.containerId = boot.stdout.trim();
962
947
  bootContainerId = state.containerId;
963
948
  if (state.containerId.length === 0) {
@@ -984,8 +969,35 @@ export async function buildEveRuntimeImage(request) {
984
969
  state.containerId,
985
970
  "sh",
986
971
  "-ceu",
987
- "set -o noglob; status=0; for module in $(find /workspace/node_modules -type f -name '*.node'); do if ldd \"$module\" 2>&1 | grep -q 'not found'; then echo \"unloadable native module: $module\" >&2; status=1; fi; done; exit $status",
972
+ "set -o noglob; status=0; for module in $(find /workspace/node_modules -type f -name '*.node' -not -path '*cbor-extract*'); do if ldd \"$module\" 2>&1 | grep -q 'not found'; then echo \"unloadable native module: $module\" >&2; status=1; fi; done; exit $status",
988
973
  ]);
974
+ if (request.workflowWorld === "@workflow/world-postgres") {
975
+ // The Postgres World ships an idempotent setup binary (drizzle
976
+ // `migrate()` plus graphile-worker `workerUtils.migrate()`; both skip
977
+ // already-applied work) that must run once per database before Eve
978
+ // consumes the schema. Running it inside the disposable boot
979
+ // container, before health and publish, exercises the exact deployed
980
+ // node_modules against the env-file URL. `docker exec` inherits the
981
+ // environment the container booted with, so the URL never appears in
982
+ // argv, logs, or the image.
983
+ try {
984
+ await docker(state, [
985
+ "exec",
986
+ state.containerId,
987
+ "./node_modules/.bin/bootstrap",
988
+ ]);
989
+ await docker(state, [
990
+ "exec",
991
+ state.containerId,
992
+ "sh",
993
+ "-ceu",
994
+ "cd node_modules/@workflow/world-postgres && node -e \"const {encode,decode}=require('cbor-x');if(decode(encode({ok:true})).ok!==true)process.exit(1)\"",
995
+ ]);
996
+ }
997
+ catch {
998
+ throw packagingError("WORLD_MIGRATION_FAILED", "@workflow/world-postgres setup", "The Postgres World schema migration or cbor-x codec check failed inside the disposable Eve container.", "Confirm WORKFLOW_POSTGRES_URL in --env-file is the direct, unpooled database URL reachable from this machine, then retry; the migration is idempotent.");
999
+ }
1000
+ }
989
1001
  await pollEveHealth(healthPort, timeoutMs, pollIntervalMs, request.fetchHealth ?? fetch);
990
1002
  await docker(state, [
991
1003
  "exec",
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export type { EveCliCommand, EveCliEnvironment, EveCliExecutionRequest, EveCliHe
10
10
  export type { EveNodeImage, EvePackagingCheck, EvePackagingCode, EveProjectBuilder, EveProjectBuilderRequest, EveProjectBuilderResult, EveProjectBuildCandidate, EveProjectFile, EveProjectImage, EveProjectInputManifest, EveProjectOutput, EveProjectPackagingResult, EveProjectRuntime, EveProjectSnapshot, EveProjectSnapshotOptions, EveProjectToolchain, EveRuntimeConfigExclusion, } from "./eve-packaging.js";
11
11
  export type { EveHostRequirements, EveRuntimeClosure, EveRuntimeClosureFile, EveRuntimeCleanup, EveRuntimeImage, EveRuntimeImageDiscardRequest, EveRuntimeImageMetadata, EveRuntimeImageRequest, EveRuntimeImageResult, EveRuntimeNativeModule, EveRuntimeStatus, } from "./eve-runtime-image.js";
12
12
  export { DEFAULT_EVE_HOST_REQUIREMENTS, DEFAULT_EVE_NODE_IMAGE, runEveControlPlane, runEveDestroy, } from "./eve-control-plane.js";
13
- export type { EveCloudflareReadRequest, EveCloudflareReadResult, EveCloudflareReadRunner, EveCloudflareTargetState, EveContainerDeleteRunner, EveDeploymentCompensationRequest, EveDeploymentCompensationRunner, EveDeploymentHealthRequest, EveDeploymentHealthResult, EveDeploymentHealthRunner, EveDeploymentIdentity, EveDeploymentIdentityProof, EveDeploymentMetadata, EveDeploymentPublicationRequest, EveDeploymentPublicationResult, EveDeploymentPublicationRunner, EveDeploymentStatus, EveDestroyCloudflareReadRequest, EveDestroyCloudflareReadRunner, EveDestroyOutcome, EveDestroyTargetRead, EveImagePublicationRequest, EveImagePublicationResult, EveImagePublicationRunner, EvePreflightCandidate, EvePreflightCheck, EvePreflightCheckStatus, EvePreflightOptions, EvePreflightResult, EvePreflightRuntimeEvidence, EvePreflightRuntimeRunner, EvePreflightRuntimeRunnerRequest, EveRuntimeConfigLoader, EveRuntimeImageDiscardRunner, EveWorkerDeleteRunner, } from "./eve-control-plane.js";
13
+ export type { EveCloudflareReadRequest, EveCloudflareReadResult, EveCloudflareReadRunner, EveCloudflareTargetState, EveContainerDeleteRunner, EveDeploymentCompensationRequest, EveDeploymentCompensationRunner, EveDeploymentHealthRequest, EveDeploymentHealthResult, EveDeploymentHealthRunner, EveDeploymentIdentity, EveDeploymentIdentityProof, EveDeploymentMetadata, EveDeploymentPublicationRequest, EveDeploymentPublicationResult, EveDeploymentPublicationRunner, EveDeploymentStatus, EveDestroyCloudflareReadRequest, EveDestroyCloudflareReadRunner, EveDestroyOutcome, EveDestroyTargetRead, EveImagePublicationRequest, EveImagePublicationResult, EveImagePublicationRunner, EvePreflightCandidate, EvePreflightResult, EvePreflightRuntimeEvidence, EvePreflightWarning, EvePreflightRuntimeRunner, EvePreflightRuntimeRunnerRequest, EveRuntimeConfigLoader, EveRuntimeImageDiscardRunner, EveWorkerDeleteRunner, } from "./eve-control-plane.js";
14
14
  export declare const EDEN_CLI_COMMANDS: readonly ["preflight", "deploy", "destroy"];
15
15
  export type EdenCliCommand = (typeof EDEN_CLI_COMMANDS)[number];
16
16
  export interface EdenCliRunOptions {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moinulmoin/eden",
3
- "version": "0.2.1",
3
+ "version": "0.3.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.2.1",
34
+ "@moinulmoin/eden-runtime-cloudflare": "0.3.0",
35
35
  "wrangler": "4.120.0"
36
36
  },
37
37
  "devDependencies": {