@moinulmoin/eden 0.5.0 → 0.6.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/NOTICE +0 -9
- package/README.md +4 -4
- package/dist/eve-control-plane.d.ts +21 -18
- package/dist/eve-control-plane.js +241 -167
- package/dist/index.d.ts +1 -1
- package/package.json +3 -3
package/NOTICE
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
Eden
|
|
2
2
|
Copyright 2026 Eden contributors
|
|
3
3
|
|
|
4
|
-
Cloudflare Containers
|
|
5
|
-
@cloudflare/containers, version 0.3.7
|
|
6
|
-
https://github.com/cloudflare/containers
|
|
7
|
-
|
|
8
|
-
Eden's generated Worker host bundle includes software from
|
|
9
|
-
`@cloudflare/containers`, distributed under MIT OR Apache-2.0. Eden
|
|
10
|
-
redistributes that bundled software under the Apache-2.0 option included in
|
|
11
|
-
LICENSE.
|
|
12
|
-
|
|
13
4
|
Workflow SDK
|
|
14
5
|
@workflow/world-postgres, version 5.0.0-beta.47
|
|
15
6
|
https://github.com/vercel/workflow
|
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.6.0
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
pnpm add --global @moinulmoin/eden@0.
|
|
19
|
+
pnpm add --global @moinulmoin/eden@0.6.0
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
|
-
bun add --global @moinulmoin/eden@0.
|
|
23
|
+
bun add --global @moinulmoin/eden@0.6.0
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Then confirm the CLI is available:
|
|
@@ -39,7 +39,7 @@ Bun can install Eden, but Eden still runs on Node. Do not use `bunx --bun`.
|
|
|
39
39
|
- Wrangler authentication:
|
|
40
40
|
|
|
41
41
|
```sh
|
|
42
|
-
npx wrangler@4.
|
|
42
|
+
npx wrangler@4.147.0 login
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
- Docker or OrbStack with Linux/amd64 container support
|
|
@@ -2,7 +2,7 @@ import { type EveCliEnvironment, type EveCliExecutionRequest } from "./eve.js";
|
|
|
2
2
|
import { type EveNodeImage, type EveProjectBuildCandidate, type EveProjectBuilder } from "./eve-packaging.js";
|
|
3
3
|
import { type EveHostRequirements, type EveRuntimeCleanup, type EveRuntimeImageDiscardRequest } from "./eve-runtime-image.js";
|
|
4
4
|
import { type EveRuntimeConfig, type EveRuntimeInjection, type EveRuntimeProtectedStore } from "./eve-runtime-config.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type EveHostConfig } from "@moinulmoin/eden-runtime-cloudflare";
|
|
6
6
|
export declare const DEFAULT_EVE_HOST_REQUIREMENTS: EveHostRequirements;
|
|
7
7
|
export declare const DEFAULT_EVE_NODE_IMAGE: EveNodeImage;
|
|
8
8
|
export type EvePreflightCheckStatus = "passed" | "failed" | "blocked" | "skipped";
|
|
@@ -87,15 +87,18 @@ export interface EveCloudflareReadResult {
|
|
|
87
87
|
/**
|
|
88
88
|
* Provider-backed facts about the live target, read through the
|
|
89
89
|
* Cloudflare API (not self-asserted response headers, which any Worker
|
|
90
|
-
* can forge): the Container application's
|
|
91
|
-
* `EDEN_EVE_DEPLOYMENT_ID` plain-text var Eden stamps into every
|
|
92
|
-
* it publishes. `settingsReadable` tells the ownership resolver
|
|
93
|
-
* a missing var is evidence of a foreign Worker or just an
|
|
94
|
-
* settings call.
|
|
90
|
+
* can forge): the Container application's Durable Object namespace and
|
|
91
|
+
* the `EDEN_EVE_DEPLOYMENT_ID` plain-text var Eden stamps into every
|
|
92
|
+
* Worker it publishes. `settingsReadable` tells the ownership resolver
|
|
93
|
+
* whether a missing var is evidence of a foreign Worker or just an
|
|
94
|
+
* unreadable settings call.
|
|
95
95
|
*/
|
|
96
96
|
readonly providerEvidence?: {
|
|
97
|
-
readonly
|
|
97
|
+
readonly containerApplicationId?: string;
|
|
98
|
+
readonly containerSchedulingPolicy?: string;
|
|
99
|
+
readonly containerNamespaceId?: string;
|
|
98
100
|
readonly workerDeploymentId?: string;
|
|
101
|
+
readonly workerContainerNamespaceId?: string;
|
|
99
102
|
readonly settingsReadable?: boolean;
|
|
100
103
|
};
|
|
101
104
|
};
|
|
@@ -249,19 +252,20 @@ export interface EvePreflightOptions {
|
|
|
249
252
|
readonly destroyCloudflareRead?: EveDestroyCloudflareReadRunner;
|
|
250
253
|
readonly deleteWorker?: EveWorkerDeleteRunner;
|
|
251
254
|
readonly deleteContainer?: EveContainerDeleteRunner;
|
|
252
|
-
readonly
|
|
255
|
+
readonly deleteRegistryRepository?: EveRegistryRepositoryDeleteRunner;
|
|
253
256
|
}
|
|
254
257
|
export interface EveDestroyTargetRead {
|
|
255
258
|
readonly workerExists: boolean;
|
|
256
259
|
readonly containerApplicationId?: string;
|
|
257
260
|
readonly accountId?: string;
|
|
258
261
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
262
|
+
* Every `repository:tag` ref still present in the managed registry whose
|
|
263
|
+
* repository is one of the request's owned repositories — including the
|
|
264
|
+
* `rootfs-snapshot-*`/`rootfs-set-*` tags container snapshots create.
|
|
265
|
+
* `undefined` means the reader could not enumerate the registry; destroy
|
|
266
|
+
* then relies on the exact delete outcomes only.
|
|
263
267
|
*/
|
|
264
|
-
readonly
|
|
268
|
+
readonly registryTagsPresent?: readonly string[];
|
|
265
269
|
}
|
|
266
270
|
export type EveDestroyCloudflareReadRunner = (request: EveDestroyCloudflareReadRequest) => EveDestroyTargetRead | Promise<EveDestroyTargetRead>;
|
|
267
271
|
export type EveWorkerDeleteRunner = (request: {
|
|
@@ -270,15 +274,15 @@ export type EveWorkerDeleteRunner = (request: {
|
|
|
270
274
|
export type EveContainerDeleteRunner = (request: {
|
|
271
275
|
readonly applicationId: string;
|
|
272
276
|
}) => "deleted" | "absent" | "indeterminate" | Promise<"deleted" | "absent" | "indeterminate">;
|
|
273
|
-
export type
|
|
274
|
-
readonly
|
|
277
|
+
export type EveRegistryRepositoryDeleteRunner = (request: {
|
|
278
|
+
readonly repository: string;
|
|
275
279
|
}) => "deleted" | "absent" | "indeterminate" | Promise<"deleted" | "absent" | "indeterminate">;
|
|
276
280
|
export type EveRuntimeConfigLoader = (path: string, cwd: string) => EveRuntimeConfig | Promise<EveRuntimeConfig>;
|
|
277
281
|
export interface EveDestroyCloudflareReadRequest {
|
|
278
282
|
readonly workerName: string;
|
|
279
283
|
readonly containerApplicationName: string;
|
|
280
|
-
/**
|
|
281
|
-
readonly
|
|
284
|
+
/** Registry repository names this target provably owns. */
|
|
285
|
+
readonly registryRepositories?: readonly string[];
|
|
282
286
|
}
|
|
283
287
|
/**
|
|
284
288
|
* Container applications in the account that belong to this exact target.
|
|
@@ -288,7 +292,6 @@ export interface EveDestroyCloudflareReadRequest {
|
|
|
288
292
|
export declare function exactTargetContainerEntries(value: unknown, name: string): readonly {
|
|
289
293
|
readonly id: string;
|
|
290
294
|
readonly name: string;
|
|
291
|
-
readonly image?: string;
|
|
292
295
|
}[] | undefined;
|
|
293
296
|
export declare function runEveControlPlane(request: EveCliExecutionRequest, options?: EvePreflightOptions): Promise<void>;
|
|
294
297
|
export interface EveDestroyOutcome {
|
|
@@ -11,6 +11,7 @@ import { EveCliError, } from "./eve.js";
|
|
|
11
11
|
import { buildEveProjectSnapshot, createDockerEveProjectBuilder, revalidateEveProjectCandidateInputs, EvePackagingError, } 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
|
+
import { createEveHostConfig, generateEveHostWorkerSource, parseEveScheduleCron, resolveStableWorkersDevOrigin, } from "@moinulmoin/eden-runtime-cloudflare";
|
|
14
15
|
const execFileAsync = promisify(execFile);
|
|
15
16
|
const require = createRequire(import.meta.url);
|
|
16
17
|
const wranglerEntrypoint = require.resolve("wrangler");
|
|
@@ -440,21 +441,11 @@ function exactContainerEntries(value, name) {
|
|
|
440
441
|
typeof entry.name !== "string") {
|
|
441
442
|
return undefined;
|
|
442
443
|
}
|
|
443
|
-
const id = entry.id;
|
|
444
444
|
const entryName = entry.name;
|
|
445
445
|
if (entryName === name) {
|
|
446
|
-
const configuration = entry.configuration;
|
|
447
|
-
const image = typeof entry.image === "string"
|
|
448
|
-
? entry.image
|
|
449
|
-
: typeof configuration === "object" &&
|
|
450
|
-
configuration !== null &&
|
|
451
|
-
typeof configuration.image === "string"
|
|
452
|
-
? configuration.image
|
|
453
|
-
: undefined;
|
|
454
446
|
entries.push({
|
|
455
|
-
id,
|
|
447
|
+
id: entry.id,
|
|
456
448
|
name: entryName,
|
|
457
|
-
...(image === undefined ? {} : { image }),
|
|
458
449
|
});
|
|
459
450
|
}
|
|
460
451
|
}
|
|
@@ -610,7 +601,7 @@ async function defaultCloudflareRead(request) {
|
|
|
610
601
|
}
|
|
611
602
|
const workerPresent = deploymentEntries.length > 0;
|
|
612
603
|
const containerPresent = (containerEntries?.length ?? 0) > 0;
|
|
613
|
-
const providerEvidence = await readProviderEvidence(request.name, accountId, workerPresent, containerPresent ? containerEntries?.[0]
|
|
604
|
+
const providerEvidence = await readProviderEvidence(request.name, accountId, workerPresent, containerPresent ? containerEntries?.[0] : undefined);
|
|
614
605
|
return {
|
|
615
606
|
accountAccess,
|
|
616
607
|
containerAccess,
|
|
@@ -634,16 +625,40 @@ async function defaultCloudflareRead(request) {
|
|
|
634
625
|
}
|
|
635
626
|
/**
|
|
636
627
|
* Reads provider-backed facts about the live target: the Container
|
|
637
|
-
* application's
|
|
638
|
-
* `
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
*
|
|
628
|
+
* application's identity from `wrangler containers list` plus `containers
|
|
629
|
+
* info` (scheduling policy and Durable Object namespace), and the
|
|
630
|
+
* `EDEN_EVE_DEPLOYMENT_ID` plain-text var plus the `EVE_CONTAINER` Durable
|
|
631
|
+
* Object namespace Eden stamps into every Worker it publishes (from the
|
|
632
|
+
* script settings API). All are provider state — a foreign Worker can forge
|
|
633
|
+
* `x-eden-*` response headers but cannot set vars or namespaces Eden
|
|
634
|
+
* recorded at publish time. The ownership proof is the Container
|
|
635
|
+
* application's Durable Object namespace matching the Worker's binding
|
|
636
|
+
* namespace.
|
|
642
637
|
*/
|
|
643
|
-
async function readProviderEvidence(workerName, accountId, workerPresent,
|
|
638
|
+
async function readProviderEvidence(workerName, accountId, workerPresent, containerEntry) {
|
|
644
639
|
const evidence = {
|
|
645
|
-
...(
|
|
640
|
+
...(containerEntry === undefined
|
|
641
|
+
? {}
|
|
642
|
+
: { containerApplicationId: containerEntry.id }),
|
|
646
643
|
};
|
|
644
|
+
if (containerEntry !== undefined) {
|
|
645
|
+
const info = await readWranglerJson([
|
|
646
|
+
"containers",
|
|
647
|
+
"info",
|
|
648
|
+
containerEntry.id,
|
|
649
|
+
"--json",
|
|
650
|
+
]);
|
|
651
|
+
if (!info.failed && typeof info.value === "object" && info.value !== null) {
|
|
652
|
+
const infoRecord = info.value;
|
|
653
|
+
if (typeof infoRecord.scheduling_policy === "string") {
|
|
654
|
+
evidence.containerSchedulingPolicy = infoRecord.scheduling_policy;
|
|
655
|
+
}
|
|
656
|
+
const namespaceId = infoRecord.durable_objects?.namespace_id;
|
|
657
|
+
if (typeof namespaceId === "string") {
|
|
658
|
+
evidence.containerNamespaceId = namespaceId;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
647
662
|
if (workerPresent && accountId !== undefined) {
|
|
648
663
|
const token = process.env.CLOUDFLARE_API_TOKEN ??
|
|
649
664
|
(await readWranglerOAuthToken());
|
|
@@ -656,10 +671,16 @@ async function readProviderEvidence(workerName, accountId, workerPresent, contai
|
|
|
656
671
|
if (response.ok) {
|
|
657
672
|
evidence.settingsReadable = true;
|
|
658
673
|
const parsed = (await response.json());
|
|
659
|
-
const
|
|
674
|
+
const bindings = parsed.result?.bindings ?? [];
|
|
675
|
+
const deploymentBinding = bindings.find((value) => value.type === "plain_text" &&
|
|
660
676
|
value.name === "EDEN_EVE_DEPLOYMENT_ID");
|
|
661
|
-
if (typeof
|
|
662
|
-
evidence.workerDeploymentId =
|
|
677
|
+
if (typeof deploymentBinding?.text === "string") {
|
|
678
|
+
evidence.workerDeploymentId = deploymentBinding.text;
|
|
679
|
+
}
|
|
680
|
+
const containerBinding = bindings.find((value) => value.type === "durable_object_namespace" &&
|
|
681
|
+
value.name === "EVE_CONTAINER");
|
|
682
|
+
if (typeof containerBinding?.namespace_id === "string") {
|
|
683
|
+
evidence.workerContainerNamespaceId = containerBinding.namespace_id;
|
|
663
684
|
}
|
|
664
685
|
}
|
|
665
686
|
else if (response.status !== 404) {
|
|
@@ -698,19 +719,23 @@ async function resolveEveTargetOwnership(request, result) {
|
|
|
698
719
|
}
|
|
699
720
|
// Remote proof must come from provider state. A foreign Worker can echo
|
|
700
721
|
// `x-eden-*` response headers, but it cannot plant the plain-text var Eden
|
|
701
|
-
// publishes into script settings
|
|
702
|
-
//
|
|
703
|
-
//
|
|
704
|
-
// unreadable settings fail closed.
|
|
722
|
+
// publishes into script settings or claim the Durable Object namespace the
|
|
723
|
+
// Worker's EVE_CONTAINER binding resolves to. The Worker is proven only by
|
|
724
|
+
// its own settings: unreadable settings fail closed.
|
|
705
725
|
const evidence = result.target.providerEvidence;
|
|
706
726
|
const recordedDeploymentIds = new Set(record.deploymentIds);
|
|
707
727
|
const workerProven = result.target.observed?.worker !== true ||
|
|
708
728
|
(evidence?.settingsReadable === true &&
|
|
709
729
|
evidence.workerDeploymentId !== undefined &&
|
|
710
730
|
recordedDeploymentIds.has(evidence.workerDeploymentId));
|
|
731
|
+
// The Container proof is the application running the durable_object
|
|
732
|
+
// scheduling policy with a namespace that matches the Worker binding's
|
|
733
|
+
// namespace; anything else fails closed.
|
|
711
734
|
const containerProven = result.target.observed?.container !== true ||
|
|
712
|
-
(evidence?.
|
|
713
|
-
|
|
735
|
+
(evidence?.containerSchedulingPolicy === "durable_object" &&
|
|
736
|
+
evidence.containerNamespaceId !== undefined &&
|
|
737
|
+
evidence.workerContainerNamespaceId !== undefined &&
|
|
738
|
+
evidence.containerNamespaceId === evidence.workerContainerNamespaceId);
|
|
714
739
|
const remoteProven = evidence !== undefined && workerProven && containerProven;
|
|
715
740
|
if (!remoteProven) {
|
|
716
741
|
return {
|
|
@@ -763,7 +788,6 @@ async function resolvedCloudflareOrigin(result, workerName) {
|
|
|
763
788
|
return undefined;
|
|
764
789
|
}
|
|
765
790
|
try {
|
|
766
|
-
const { resolveStableWorkersDevOrigin } = await import("@moinulmoin/eden-runtime-cloudflare");
|
|
767
791
|
const origin = resolveStableWorkersDevOrigin({
|
|
768
792
|
workerName,
|
|
769
793
|
workersDevSubdomain: result.workersDevSubdomain,
|
|
@@ -1130,8 +1154,6 @@ function defaultPublicationRunner() {
|
|
|
1130
1154
|
"--env",
|
|
1131
1155
|
request.identity.environment,
|
|
1132
1156
|
"--strict",
|
|
1133
|
-
"--containers-rollout",
|
|
1134
|
-
"immediate",
|
|
1135
1157
|
], dirname(request.wranglerConfigPath));
|
|
1136
1158
|
if (result.exitCode !== 0) {
|
|
1137
1159
|
return {
|
|
@@ -1490,7 +1512,6 @@ async function runEveDeployment(request, options, collected) {
|
|
|
1490
1512
|
}
|
|
1491
1513
|
let stableWorkersDevOrigin;
|
|
1492
1514
|
try {
|
|
1493
|
-
const { resolveStableWorkersDevOrigin } = await import("@moinulmoin/eden-runtime-cloudflare");
|
|
1494
1515
|
stableWorkersDevOrigin = resolveStableWorkersDevOrigin({
|
|
1495
1516
|
workerName: request.name,
|
|
1496
1517
|
workersDevSubdomain,
|
|
@@ -1607,38 +1628,39 @@ async function runEveDeployment(request, options, collected) {
|
|
|
1607
1628
|
? {}
|
|
1608
1629
|
: { runtimeRevisionHandle: runtimeInjection.protectedHandle }),
|
|
1609
1630
|
};
|
|
1631
|
+
const baseConfigRequest = {
|
|
1632
|
+
accountId,
|
|
1633
|
+
workerName: identity.workerName,
|
|
1634
|
+
containerApplicationName: identity.containerApplicationName,
|
|
1635
|
+
containerClassName: "EveHostDurableContainer",
|
|
1636
|
+
containerBindingName: "EVE_CONTAINER",
|
|
1637
|
+
stableContainerInstanceName: identity.stableContainerInstanceName,
|
|
1638
|
+
deploymentId: identity.deploymentId,
|
|
1639
|
+
generationId: identity.generationId,
|
|
1640
|
+
stableWorkersDevOrigin: identity.stableWorkersDevOrigin,
|
|
1641
|
+
containerImage: identity.containerImage,
|
|
1642
|
+
runtimeVariableNames: identity.runtimeVariableNames,
|
|
1643
|
+
...(identity.runtimeRevisionHandle === undefined
|
|
1644
|
+
? {}
|
|
1645
|
+
: { runtimeRevisionHandle: identity.runtimeRevisionHandle }),
|
|
1646
|
+
schedules: collected.schedules ?? [],
|
|
1647
|
+
...(collected.workflowWorld === undefined
|
|
1648
|
+
? {}
|
|
1649
|
+
: { workflowWorld: collected.workflowWorld }),
|
|
1650
|
+
// In-place updates carry the prior Durable Object bindings and
|
|
1651
|
+
// migration history forward verbatim: classes are append-only and are
|
|
1652
|
+
// never silently re-declared or removed.
|
|
1653
|
+
...(isUpdate && priorDurableObjects !== undefined
|
|
1654
|
+
? { durableObjects: priorDurableObjects }
|
|
1655
|
+
: {}),
|
|
1656
|
+
...(process.env.EDEN_EVE_CONTAINER_SLEEP_AFTER === undefined
|
|
1657
|
+
? {}
|
|
1658
|
+
: { containerSleepAfter: process.env.EDEN_EVE_CONTAINER_SLEEP_AFTER }),
|
|
1659
|
+
};
|
|
1610
1660
|
let hostConfig;
|
|
1611
1661
|
try {
|
|
1612
|
-
const { createEveHostConfig, } = await import("@moinulmoin/eden-runtime-cloudflare");
|
|
1613
1662
|
hostConfig = createEveHostConfig({
|
|
1614
|
-
|
|
1615
|
-
workerName: identity.workerName,
|
|
1616
|
-
containerApplicationName: identity.containerApplicationName,
|
|
1617
|
-
containerClassName: "EveHostContainer",
|
|
1618
|
-
containerBindingName: "EVE_CONTAINER",
|
|
1619
|
-
stableContainerInstanceName: identity.stableContainerInstanceName,
|
|
1620
|
-
deploymentId: identity.deploymentId,
|
|
1621
|
-
generationId: identity.generationId,
|
|
1622
|
-
stableWorkersDevOrigin: identity.stableWorkersDevOrigin,
|
|
1623
|
-
containerImage: identity.containerImage,
|
|
1624
|
-
runtimeVariableNames: identity.runtimeVariableNames,
|
|
1625
|
-
...(identity.runtimeRevisionHandle === undefined
|
|
1626
|
-
? {}
|
|
1627
|
-
: { runtimeRevisionHandle: identity.runtimeRevisionHandle }),
|
|
1628
|
-
schedules: collected.schedules ?? [],
|
|
1629
|
-
...(collected.workflowWorld === undefined
|
|
1630
|
-
? {}
|
|
1631
|
-
: { workflowWorld: collected.workflowWorld }),
|
|
1632
|
-
// In-place updates carry the prior Durable Object bindings and
|
|
1633
|
-
// migration history forward verbatim: classes are append-only, never
|
|
1634
|
-
// re-declared, and never deleted (a deleted_classes migration destroys
|
|
1635
|
-
// the data).
|
|
1636
|
-
...(isUpdate && priorDurableObjects !== undefined
|
|
1637
|
-
? { durableObjects: priorDurableObjects }
|
|
1638
|
-
: {}),
|
|
1639
|
-
...(process.env.EDEN_EVE_CONTAINER_SLEEP_AFTER === undefined
|
|
1640
|
-
? {}
|
|
1641
|
-
: { containerSleepAfter: process.env.EDEN_EVE_CONTAINER_SLEEP_AFTER }),
|
|
1663
|
+
...baseConfigRequest,
|
|
1642
1664
|
});
|
|
1643
1665
|
}
|
|
1644
1666
|
catch (error) {
|
|
@@ -1646,7 +1668,6 @@ async function runEveDeployment(request, options, collected) {
|
|
|
1646
1668
|
? error.message
|
|
1647
1669
|
: "The exact Eve Worker/Container host configuration was invalid.");
|
|
1648
1670
|
}
|
|
1649
|
-
const { generateEveHostWorkerSource } = await import("@moinulmoin/eden-runtime-cloudflare");
|
|
1650
1671
|
const workerSource = generateEveHostWorkerSource({ config: hostConfig });
|
|
1651
1672
|
const paths = await writeEveDeploymentArtifacts(candidate, hostConfig, workerSource);
|
|
1652
1673
|
const publicationRequest = {
|
|
@@ -1656,13 +1677,26 @@ async function runEveDeployment(request, options, collected) {
|
|
|
1656
1677
|
...paths,
|
|
1657
1678
|
...(runtimeInjection === undefined ? {} : { runtimeInjection }),
|
|
1658
1679
|
};
|
|
1680
|
+
/** Publishes one wrangler config; returns its terminal result. */
|
|
1681
|
+
const publishOne = async (request) => (options.publish ?? defaultPublicationRunner())(request);
|
|
1659
1682
|
let publication;
|
|
1683
|
+
const writeFailureRecord = async (status, evidence) => {
|
|
1684
|
+
try {
|
|
1685
|
+
await writeEveDeploymentRecord(candidate, identity, hostConfig, status, {
|
|
1686
|
+
evidence,
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
catch (error) {
|
|
1690
|
+
if (error.code !== "EEXIST")
|
|
1691
|
+
throw error;
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1660
1694
|
try {
|
|
1661
1695
|
eveProgress(options, "publishing Worker and Container application");
|
|
1662
|
-
publication = await (
|
|
1696
|
+
publication = await publishOne(publicationRequest);
|
|
1663
1697
|
}
|
|
1664
1698
|
catch {
|
|
1665
|
-
await
|
|
1699
|
+
await writeFailureRecord("indeterminate", "publication-threw-before-terminal-result");
|
|
1666
1700
|
const deployment = deploymentMetadata(identity, targetKey, "indeterminate", operation, superseded);
|
|
1667
1701
|
return {
|
|
1668
1702
|
...collected.result,
|
|
@@ -1671,7 +1705,7 @@ async function runEveDeployment(request, options, collected) {
|
|
|
1671
1705
|
};
|
|
1672
1706
|
}
|
|
1673
1707
|
if (publication.status === "indeterminate") {
|
|
1674
|
-
await
|
|
1708
|
+
await writeFailureRecord("indeterminate", safeText(publication.reason));
|
|
1675
1709
|
return {
|
|
1676
1710
|
...collected.result,
|
|
1677
1711
|
ok: false,
|
|
@@ -1862,25 +1896,26 @@ async function runEveDeployment(request, options, collected) {
|
|
|
1862
1896
|
if (isUpdate) {
|
|
1863
1897
|
// The superseded generation's registry repository is generation-scoped
|
|
1864
1898
|
// and no longer referenced once CURRENT promotes this generation; only
|
|
1865
|
-
// then is it safe to delete.
|
|
1866
|
-
|
|
1899
|
+
// then is it safe to delete. Every tag in the repository goes with it —
|
|
1900
|
+
// including the snapshot tags Cloudflare created under it.
|
|
1901
|
+
const previousRepository = priorIdentity === undefined
|
|
1867
1902
|
? undefined
|
|
1868
|
-
:
|
|
1869
|
-
if (
|
|
1870
|
-
eveProgress(options, `deleting superseded registry
|
|
1903
|
+
: ownedRegistryRepository(priorIdentity, request.environment, request.name, accountId);
|
|
1904
|
+
if (previousRepository !== undefined) {
|
|
1905
|
+
eveProgress(options, `deleting superseded registry repository ${previousRepository}`);
|
|
1871
1906
|
let deleted = "indeterminate";
|
|
1872
1907
|
try {
|
|
1873
|
-
deleted = await (options.
|
|
1874
|
-
|
|
1908
|
+
deleted = await (options.deleteRegistryRepository ??
|
|
1909
|
+
defaultRegistryRepositoryDelete)({ repository: previousRepository });
|
|
1875
1910
|
}
|
|
1876
1911
|
catch {
|
|
1877
1912
|
deleted = "indeterminate";
|
|
1878
1913
|
}
|
|
1879
1914
|
supersededImageCleanup = deleted === "indeterminate"
|
|
1880
|
-
? check("VAL-LIFE-007", "failed", `The update is healthy and promoted, but the superseded registry
|
|
1915
|
+
? check("VAL-LIFE-007", "failed", `The update is healthy and promoted, but the superseded registry repository ${previousRepository} could not be proven deleted.`, `Remove only the recorded repository's tags ${previousRepository}; do not retry or run broad registry cleanup.`)
|
|
1881
1916
|
: check("VAL-LIFE-007", "passed", deleted === "deleted"
|
|
1882
|
-
? "The superseded generation's registry
|
|
1883
|
-
: "The superseded generation's registry
|
|
1917
|
+
? "The superseded generation's registry repository (all tags, including snapshots) was deleted after promotion."
|
|
1918
|
+
: "The superseded generation's registry repository was already absent.");
|
|
1884
1919
|
}
|
|
1885
1920
|
}
|
|
1886
1921
|
let runtimeImageCleanup;
|
|
@@ -2058,9 +2093,6 @@ async function collectEvePreflight(request, options) {
|
|
|
2058
2093
|
}
|
|
2059
2094
|
warnings.push(...(await collectEveWorldWarnings(candidate, undefined)));
|
|
2060
2095
|
const schedules = await readCompiledSchedules(candidate);
|
|
2061
|
-
// The runtime package is imported lazily so `eden` commands that never
|
|
2062
|
-
// reach deploy checks stay fast; the module specifier is fixed.
|
|
2063
|
-
const { parseEveScheduleCron } = await import("@moinulmoin/eden-runtime-cloudflare");
|
|
2064
2096
|
const expressible = schedules.filter((schedule) => parseEveScheduleCron(schedule.cron) !== undefined);
|
|
2065
2097
|
const unexpressible = schedules.filter((schedule) => parseEveScheduleCron(schedule.cron) === undefined);
|
|
2066
2098
|
if (unexpressible.length > 0) {
|
|
@@ -2496,7 +2528,6 @@ async function readEveDeploymentRecord(projectRoot, environment, name, accountId
|
|
|
2496
2528
|
const entries = await readdir(generationsRoot).catch(() => []);
|
|
2497
2529
|
const scanned = [];
|
|
2498
2530
|
const deploymentIds = new Set();
|
|
2499
|
-
const images = new Set();
|
|
2500
2531
|
for (const entry of entries) {
|
|
2501
2532
|
const generationRoot = join(generationsRoot, entry);
|
|
2502
2533
|
const recordPath = join(generationRoot, "deployment.json");
|
|
@@ -2519,9 +2550,6 @@ async function readEveDeploymentRecord(projectRoot, environment, name, accountId
|
|
|
2519
2550
|
const deploymentId = parsed.deploymentId;
|
|
2520
2551
|
if (deploymentId !== undefined)
|
|
2521
2552
|
deploymentIds.add(deploymentId);
|
|
2522
|
-
if (typeof parsed.identity.containerImage === "string") {
|
|
2523
|
-
images.add(parsed.identity.containerImage);
|
|
2524
|
-
}
|
|
2525
2553
|
}
|
|
2526
2554
|
}
|
|
2527
2555
|
scanned.sort((left, right) => right.modifiedAt - left.modifiedAt);
|
|
@@ -2557,7 +2585,6 @@ async function readEveDeploymentRecord(projectRoot, environment, name, accountId
|
|
|
2557
2585
|
identity: live.record.identity,
|
|
2558
2586
|
status: live.record.status,
|
|
2559
2587
|
deploymentIds: [...deploymentIds],
|
|
2560
|
-
images: [...images],
|
|
2561
2588
|
worldCloudflare: live.record.worldCloudflare,
|
|
2562
2589
|
durableObjects: historySource?.record.durableObjects,
|
|
2563
2590
|
};
|
|
@@ -2607,19 +2634,20 @@ async function parseEveDeploymentRecord(recordPath, environment, name) {
|
|
|
2607
2634
|
const migrations = [];
|
|
2608
2635
|
const durableObjectsValid = Array.isArray(rawBindings) &&
|
|
2609
2636
|
Array.isArray(rawMigrations);
|
|
2637
|
+
const invalidHistory = () => ({
|
|
2638
|
+
recordPath,
|
|
2639
|
+
identity: base.identity,
|
|
2640
|
+
status,
|
|
2641
|
+
deploymentId,
|
|
2642
|
+
worldCloudflare: rawVars?.EDEN_EVE_WORLD_CLOUDFLARE === true,
|
|
2643
|
+
durableObjects: undefined,
|
|
2644
|
+
});
|
|
2610
2645
|
if (durableObjectsValid) {
|
|
2611
2646
|
for (const binding of rawBindings) {
|
|
2612
2647
|
if (typeof binding !== "object" || binding === null ||
|
|
2613
2648
|
typeof binding.name !== "string" ||
|
|
2614
2649
|
typeof binding.class_name !== "string") {
|
|
2615
|
-
return
|
|
2616
|
-
recordPath,
|
|
2617
|
-
identity: base.identity,
|
|
2618
|
-
status,
|
|
2619
|
-
deploymentId,
|
|
2620
|
-
worldCloudflare: rawVars?.EDEN_EVE_WORLD_CLOUDFLARE === true,
|
|
2621
|
-
durableObjects: undefined,
|
|
2622
|
-
};
|
|
2650
|
+
return invalidHistory();
|
|
2623
2651
|
}
|
|
2624
2652
|
bindings.push(binding);
|
|
2625
2653
|
}
|
|
@@ -2627,15 +2655,8 @@ async function parseEveDeploymentRecord(recordPath, environment, name) {
|
|
|
2627
2655
|
if (typeof migration !== "object" || migration === null ||
|
|
2628
2656
|
typeof migration.tag !== "string" ||
|
|
2629
2657
|
!Array.isArray(migration.new_sqlite_classes) ||
|
|
2630
|
-
migration.new_sqlite_classes.some((className) => typeof className !== "string")) {
|
|
2631
|
-
return
|
|
2632
|
-
recordPath,
|
|
2633
|
-
identity: base.identity,
|
|
2634
|
-
status,
|
|
2635
|
-
deploymentId,
|
|
2636
|
-
worldCloudflare: rawVars?.EDEN_EVE_WORLD_CLOUDFLARE === true,
|
|
2637
|
-
durableObjects: undefined,
|
|
2638
|
-
};
|
|
2658
|
+
(migration.new_sqlite_classes ?? []).some((className) => typeof className !== "string")) {
|
|
2659
|
+
return invalidHistory();
|
|
2639
2660
|
}
|
|
2640
2661
|
migrations.push(migration);
|
|
2641
2662
|
}
|
|
@@ -2670,18 +2691,25 @@ async function defaultDestroyTargetRead(request) {
|
|
|
2670
2691
|
(exactDeploymentEntries(deployments.value)?.length ?? 0) > 0;
|
|
2671
2692
|
const entries = exactContainerEntries(containers.value, request.containerApplicationName);
|
|
2672
2693
|
const firstContainer = entries === undefined ? undefined : entries[0];
|
|
2673
|
-
const
|
|
2674
|
-
let
|
|
2675
|
-
if (
|
|
2694
|
+
const ownedRepositories = request.registryRepositories;
|
|
2695
|
+
let registryTagsPresent;
|
|
2696
|
+
if (ownedRepositories !== undefined && ownedRepositories.length > 0) {
|
|
2676
2697
|
const images = await readWranglerJson([
|
|
2677
2698
|
"containers",
|
|
2678
2699
|
"images",
|
|
2679
2700
|
"list",
|
|
2680
2701
|
"--json",
|
|
2681
2702
|
]);
|
|
2682
|
-
const
|
|
2683
|
-
if (!images.failed &&
|
|
2684
|
-
|
|
2703
|
+
const tagsByRepository = registryTagsByRepository(images.value);
|
|
2704
|
+
if (!images.failed && tagsByRepository !== undefined) {
|
|
2705
|
+
const present = [];
|
|
2706
|
+
for (const [repository, tags] of tagsByRepository) {
|
|
2707
|
+
if (!ownedRepositories.includes(repository))
|
|
2708
|
+
continue;
|
|
2709
|
+
for (const tag of tags)
|
|
2710
|
+
present.push(`${repository}:${tag}`);
|
|
2711
|
+
}
|
|
2712
|
+
registryTagsPresent = present;
|
|
2685
2713
|
}
|
|
2686
2714
|
}
|
|
2687
2715
|
return {
|
|
@@ -2690,21 +2718,22 @@ async function defaultDestroyTargetRead(request) {
|
|
|
2690
2718
|
? {}
|
|
2691
2719
|
: { containerApplicationId: firstContainer.id }),
|
|
2692
2720
|
...(accountId === undefined ? {} : { accountId }),
|
|
2693
|
-
...(
|
|
2721
|
+
...(registryTagsPresent === undefined
|
|
2694
2722
|
? {}
|
|
2695
|
-
: {
|
|
2723
|
+
: { registryTagsPresent }),
|
|
2696
2724
|
};
|
|
2697
2725
|
}
|
|
2698
2726
|
/**
|
|
2699
2727
|
* `wrangler containers images list --json` reports `[{name, tags}]` entries
|
|
2700
|
-
* with the account prefix already stripped from the repository name. The
|
|
2701
|
-
* carries
|
|
2728
|
+
* with the account prefix already stripped from the repository name. The map
|
|
2729
|
+
* carries every tag per repository so owned-repository cleanup can delete
|
|
2730
|
+
* snapshot tags alongside the pushed `candidate` tag.
|
|
2702
2731
|
*/
|
|
2703
|
-
function
|
|
2732
|
+
function registryTagsByRepository(value) {
|
|
2704
2733
|
const collection = jsonCollection(value);
|
|
2705
2734
|
if (collection === undefined)
|
|
2706
2735
|
return undefined;
|
|
2707
|
-
const
|
|
2736
|
+
const tagsByRepository = new Map();
|
|
2708
2737
|
for (const entry of collection) {
|
|
2709
2738
|
if (typeof entry !== "object" || entry === null || !("name" in entry)) {
|
|
2710
2739
|
return undefined;
|
|
@@ -2713,13 +2742,15 @@ function registryImageNames(value) {
|
|
|
2713
2742
|
const tags = "tags" in entry ? entry.tags : undefined;
|
|
2714
2743
|
if (typeof name !== "string" || !Array.isArray(tags))
|
|
2715
2744
|
return undefined;
|
|
2745
|
+
const validTags = [];
|
|
2716
2746
|
for (const tag of tags) {
|
|
2717
2747
|
if (typeof tag !== "string")
|
|
2718
2748
|
return undefined;
|
|
2719
|
-
|
|
2749
|
+
validTags.push(tag);
|
|
2720
2750
|
}
|
|
2751
|
+
tagsByRepository.set(name, validTags);
|
|
2721
2752
|
}
|
|
2722
|
-
return
|
|
2753
|
+
return tagsByRepository;
|
|
2723
2754
|
}
|
|
2724
2755
|
async function defaultWorkerDelete(request) {
|
|
2725
2756
|
const result = await runWranglerWithInput(["delete", "--name", request.name, "--force"], process.cwd(), "y\n");
|
|
@@ -2739,23 +2770,59 @@ async function defaultContainerDelete(request) {
|
|
|
2739
2770
|
}
|
|
2740
2771
|
return "indeterminate";
|
|
2741
2772
|
}
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2773
|
+
/**
|
|
2774
|
+
* Deletes every tag in an owned registry repository — the `candidate` tag
|
|
2775
|
+
* Eden pushed plus any `rootfs-snapshot-*`/`rootfs-set-*` tags container
|
|
2776
|
+
* snapshots created under the source image repository. Deleting only
|
|
2777
|
+
* `candidate` would leave snapshot storage behind, so each tag in the
|
|
2778
|
+
* repository is deleted individually.
|
|
2779
|
+
*/
|
|
2780
|
+
async function defaultRegistryRepositoryDelete(request) {
|
|
2781
|
+
const images = await readWranglerJson([
|
|
2782
|
+
"containers",
|
|
2783
|
+
"images",
|
|
2784
|
+
"list",
|
|
2785
|
+
"--json",
|
|
2786
|
+
]);
|
|
2787
|
+
const collection = jsonCollection(images.value);
|
|
2788
|
+
if (images.failed || collection === undefined)
|
|
2789
|
+
return "indeterminate";
|
|
2790
|
+
const entry = collection.find((candidate) => typeof candidate === "object" &&
|
|
2791
|
+
candidate !== null &&
|
|
2792
|
+
candidate.name === request.repository);
|
|
2793
|
+
if (entry === undefined)
|
|
2747
2794
|
return "absent";
|
|
2795
|
+
const tags = entry.tags;
|
|
2796
|
+
if (!Array.isArray(tags) || tags.some((tag) => typeof tag !== "string")) {
|
|
2797
|
+
return "indeterminate";
|
|
2748
2798
|
}
|
|
2749
|
-
|
|
2799
|
+
let deletedAny = false;
|
|
2800
|
+
for (const tag of tags) {
|
|
2801
|
+
const result = await runWranglerWithInput([
|
|
2802
|
+
"containers",
|
|
2803
|
+
"images",
|
|
2804
|
+
"delete",
|
|
2805
|
+
`${request.repository}:${tag}`,
|
|
2806
|
+
"--skip-confirmation",
|
|
2807
|
+
], process.cwd());
|
|
2808
|
+
if (result.exitCode === 0) {
|
|
2809
|
+
deletedAny = true;
|
|
2810
|
+
continue;
|
|
2811
|
+
}
|
|
2812
|
+
if (!/(?:does not exist|not found|404)/iu.test(result.stderr)) {
|
|
2813
|
+
return "indeterminate";
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
return deletedAny ? "deleted" : "absent";
|
|
2750
2817
|
}
|
|
2751
2818
|
/**
|
|
2752
|
-
* The
|
|
2753
|
-
*
|
|
2754
|
-
*
|
|
2755
|
-
*
|
|
2756
|
-
*
|
|
2819
|
+
* The registry repository this generation's deployment record proves Eden
|
|
2820
|
+
* pushed to, or undefined. Ownership holds only when the recorded digest
|
|
2821
|
+
* reference points at the repository derived from the same identity that
|
|
2822
|
+
* produced it; a digest-bearing reference to any other repository was
|
|
2823
|
+
* published outside Eden's push and is never owned here.
|
|
2757
2824
|
*/
|
|
2758
|
-
function
|
|
2825
|
+
function ownedRegistryRepository(identity, environment, name, accountId) {
|
|
2759
2826
|
if (identity.projectId === undefined ||
|
|
2760
2827
|
identity.generationId === undefined ||
|
|
2761
2828
|
identity.containerImage === undefined ||
|
|
@@ -2768,14 +2835,14 @@ function ownedRegistryImageRef(identity, environment, name, accountId) {
|
|
|
2768
2835
|
imageDigestFromReference(identity.containerImage) === undefined) {
|
|
2769
2836
|
return undefined;
|
|
2770
2837
|
}
|
|
2771
|
-
return
|
|
2838
|
+
return repository;
|
|
2772
2839
|
}
|
|
2773
2840
|
/**
|
|
2774
|
-
* The
|
|
2775
|
-
* retained attempt evidence names the target's repository; the account
|
|
2776
|
-
* target key must match the verified destroy record.
|
|
2841
|
+
* The registry repository an indeterminate deploy attempt provably pushed.
|
|
2842
|
+
* The retained attempt evidence names the target's repository; the account
|
|
2843
|
+
* and target key must match the verified destroy record.
|
|
2777
2844
|
*/
|
|
2778
|
-
async function
|
|
2845
|
+
async function attemptRegistryRepository(attemptPath, accountId, targetKey) {
|
|
2779
2846
|
const raw = await readFile(attemptPath, "utf8").catch(() => undefined);
|
|
2780
2847
|
if (raw === undefined)
|
|
2781
2848
|
return undefined;
|
|
@@ -2795,7 +2862,7 @@ async function attemptRegistryImageRef(attemptPath, accountId, targetKey) {
|
|
|
2795
2862
|
(!("targetKey" in parsed) || parsed.targetKey === targetKey) &&
|
|
2796
2863
|
"accountId" in parsed &&
|
|
2797
2864
|
parsed.accountId === accountId) {
|
|
2798
|
-
return
|
|
2865
|
+
return parsed.imageRepository;
|
|
2799
2866
|
}
|
|
2800
2867
|
if ("imageReference" in parsed &&
|
|
2801
2868
|
typeof parsed.imageReference === "string" &&
|
|
@@ -2805,39 +2872,40 @@ async function attemptRegistryImageRef(attemptPath, accountId, targetKey) {
|
|
|
2805
2872
|
if (repository !== undefined &&
|
|
2806
2873
|
repository.startsWith(expected) &&
|
|
2807
2874
|
(!("targetKey" in parsed) || parsed.targetKey === targetKey)) {
|
|
2808
|
-
return
|
|
2875
|
+
return repository.slice(`registry.cloudflare.com/${accountId}/`.length);
|
|
2809
2876
|
}
|
|
2810
2877
|
}
|
|
2811
2878
|
return undefined;
|
|
2812
2879
|
}
|
|
2813
2880
|
/**
|
|
2814
|
-
* Every registry
|
|
2881
|
+
* Every registry repository this exact target provably owns: the recorded
|
|
2815
2882
|
* digest references in matching deployment records plus the repository
|
|
2816
2883
|
* evidence retained by indeterminate pushes. Never derived by prefix-matching
|
|
2817
|
-
* the registry; each
|
|
2884
|
+
* the registry; each repository is computed from the immutable generation
|
|
2885
|
+
* records.
|
|
2818
2886
|
*/
|
|
2819
|
-
async function
|
|
2887
|
+
async function collectEveDestroyRegistryRepositories(projectRoot, environment, name, record) {
|
|
2820
2888
|
const generationsRoot = join(projectRoot, ".eden", "eve-deploy", "generations");
|
|
2821
2889
|
const entries = await readdir(generationsRoot).catch(() => []);
|
|
2822
|
-
const
|
|
2890
|
+
const repositories = new Set();
|
|
2823
2891
|
const targetKey = record.projectId === undefined
|
|
2824
2892
|
? undefined
|
|
2825
2893
|
: stableDeploymentKey(record.projectId, record.accountId, environment, name);
|
|
2826
2894
|
for (const entry of entries) {
|
|
2827
2895
|
const generationRoot = join(generationsRoot, entry);
|
|
2828
2896
|
const parsed = await parseEveDestroyRecord(join(generationRoot, "deployment.json"), environment, name);
|
|
2829
|
-
const
|
|
2897
|
+
const deployedRepository = parsed === undefined
|
|
2830
2898
|
? undefined
|
|
2831
|
-
:
|
|
2832
|
-
if (
|
|
2833
|
-
|
|
2899
|
+
: ownedRegistryRepository(parsed.identity, environment, name, record.accountId);
|
|
2900
|
+
if (deployedRepository !== undefined)
|
|
2901
|
+
repositories.add(deployedRepository);
|
|
2834
2902
|
if (targetKey === undefined)
|
|
2835
2903
|
continue;
|
|
2836
|
-
const
|
|
2837
|
-
if (
|
|
2838
|
-
|
|
2904
|
+
const attemptRepository = await attemptRegistryRepository(join(generationRoot, "deployment-attempt.json"), record.accountId, targetKey);
|
|
2905
|
+
if (attemptRepository !== undefined)
|
|
2906
|
+
repositories.add(attemptRepository);
|
|
2839
2907
|
}
|
|
2840
|
-
return [...
|
|
2908
|
+
return [...repositories];
|
|
2841
2909
|
}
|
|
2842
2910
|
export async function runEveDestroy(request, options = {}) {
|
|
2843
2911
|
const startedAt = Date.now();
|
|
@@ -2890,11 +2958,11 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
|
|
|
2890
2958
|
}
|
|
2891
2959
|
checks.push(check("VAL-LIFE-006-RECORD", "passed", "The immutable deployment record matched the exact target selectors."));
|
|
2892
2960
|
eveProgress(options, "verifying the immutable ownership record");
|
|
2893
|
-
const
|
|
2961
|
+
const ownedRegistryRepositories = await collectEveDestroyRegistryRepositories(request.projectRoot, request.environment, request.name, record.identity);
|
|
2894
2962
|
const readTarget = () => Promise.resolve(reader({
|
|
2895
2963
|
workerName: expectedWorker,
|
|
2896
2964
|
containerApplicationName: expectedContainer,
|
|
2897
|
-
|
|
2965
|
+
registryRepositories: ownedRegistryRepositories,
|
|
2898
2966
|
}));
|
|
2899
2967
|
const reader = options.destroyCloudflareRead ?? defaultDestroyTargetRead;
|
|
2900
2968
|
eveProgress(options, "reading the exact remote inventory");
|
|
@@ -2904,9 +2972,9 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
|
|
|
2904
2972
|
}
|
|
2905
2973
|
if (!before.workerExists &&
|
|
2906
2974
|
before.containerApplicationId === undefined &&
|
|
2907
|
-
(
|
|
2908
|
-
(before.
|
|
2909
|
-
before.
|
|
2975
|
+
(ownedRegistryRepositories.length === 0 ||
|
|
2976
|
+
(before.registryTagsPresent !== undefined &&
|
|
2977
|
+
before.registryTagsPresent.length === 0))) {
|
|
2910
2978
|
emit({
|
|
2911
2979
|
ok: true,
|
|
2912
2980
|
status: "absent",
|
|
@@ -2963,17 +3031,22 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
|
|
|
2963
3031
|
? "The exact owned Container application was deleted."
|
|
2964
3032
|
: "The exact Container application was already absent."));
|
|
2965
3033
|
}
|
|
2966
|
-
const
|
|
2967
|
-
if (
|
|
2968
|
-
const
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
3034
|
+
const repositoryDeletes = new Map();
|
|
3035
|
+
if (ownedRegistryRepositories.length > 0) {
|
|
3036
|
+
const deleteRegistryRepository = options.deleteRegistryRepository ?? defaultRegistryRepositoryDelete;
|
|
3037
|
+
// Every tag inside an owned repository is deleted (candidate plus
|
|
3038
|
+
// snapshot tags). When the registry could not be enumerated, still
|
|
3039
|
+
// attempt the exact owned repositories; absence verification below
|
|
3040
|
+
// re-reads the registry when it becomes readable.
|
|
3041
|
+
const presentRepositories = new Set((before.registryTagsPresent ?? []).map((ref) => ref.split(":")[0] ?? ""));
|
|
3042
|
+
const deletable = before.registryTagsPresent === undefined
|
|
3043
|
+
? ownedRegistryRepositories
|
|
3044
|
+
: ownedRegistryRepositories.filter((repository) => presentRepositories.has(repository));
|
|
2972
3045
|
if (deletable.length > 0) {
|
|
2973
|
-
eveProgress(options, `deleting ${deletable.length} registry
|
|
3046
|
+
eveProgress(options, `deleting ${deletable.length} registry repositor${deletable.length === 1 ? "y" : "ies"}`);
|
|
2974
3047
|
}
|
|
2975
|
-
for (const
|
|
2976
|
-
|
|
3048
|
+
for (const repository of deletable) {
|
|
3049
|
+
repositoryDeletes.set(repository, await deleteRegistryRepository({ repository }));
|
|
2977
3050
|
}
|
|
2978
3051
|
}
|
|
2979
3052
|
eveProgress(options, "verifying the exact target is absent");
|
|
@@ -2991,15 +3064,16 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
|
|
|
2991
3064
|
});
|
|
2992
3065
|
fail("EVE_DESTROY_ABSENCE_UNPROVEN", "The exact target could not be proven absent after deletion.");
|
|
2993
3066
|
}
|
|
2994
|
-
const
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
3067
|
+
const presentTagsAfter = after.registryTagsPresent ?? [];
|
|
3068
|
+
const retainedRepositories = ownedRegistryRepositories.filter((repository) => presentTagsAfter.some((ref) => ref.startsWith(`${repository}:`)) ||
|
|
3069
|
+
(after.registryTagsPresent === undefined &&
|
|
3070
|
+
repositoryDeletes.get(repository) === "indeterminate"));
|
|
3071
|
+
if (ownedRegistryRepositories.length > 0) {
|
|
3072
|
+
checks.push(retainedRepositories.length === 0
|
|
2999
3073
|
? check("VAL-LIFE-006-REGISTRY", "passed", "Every registry image tag recorded for this exact target was deleted or proven absent.")
|
|
3000
|
-
: check("VAL-LIFE-006-REGISTRY", "failed", `The exact target's registry image(s) remain after destroy: ${
|
|
3074
|
+
: check("VAL-LIFE-006-REGISTRY", "failed", `The exact target's registry image tag(s) remain after destroy: ${retainedRepositories.join(", ")}.`, "Retry eden destroy with the same selectors, or remove only the listed repositories' tags with wrangler containers images delete."));
|
|
3001
3075
|
}
|
|
3002
|
-
if (
|
|
3076
|
+
if (retainedRepositories.length > 0) {
|
|
3003
3077
|
emit({
|
|
3004
3078
|
ok: false,
|
|
3005
3079
|
status: "failed",
|
|
@@ -3007,7 +3081,7 @@ async function runEveDestroyLocked(request, options, checks, emit, fail) {
|
|
|
3007
3081
|
name: request.name,
|
|
3008
3082
|
checks,
|
|
3009
3083
|
});
|
|
3010
|
-
fail("EVE_DESTROY_IMAGES_RETAINED", `The exact Worker and Container are absent, but registry image(s) remain: ${
|
|
3084
|
+
fail("EVE_DESTROY_IMAGES_RETAINED", `The exact Worker and Container are absent, but registry image tag(s) remain in: ${retainedRepositories.join(", ")}.`);
|
|
3011
3085
|
}
|
|
3012
3086
|
checks.push(check("VAL-LIFE-006", "passed", "Exact absence was verified; clearing this target's CURRENT pointer only."));
|
|
3013
3087
|
const currentPath = join(request.projectRoot, ".eden", "eve-deploy", "targets", `${request.environment}-${request.name}`, "CURRENT");
|
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, EvePreflightResult, EvePreflightRuntimeEvidence, EvePreflightWarning, EvePreflightRuntimeRunner, EvePreflightRuntimeRunnerRequest,
|
|
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, EveRegistryRepositoryDeleteRunner, 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.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Deploy existing Eve projects to your own Cloudflare account.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"NOTICE"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@moinulmoin/eden-runtime-cloudflare": "0.
|
|
35
|
-
"wrangler": "4.
|
|
34
|
+
"@moinulmoin/eden-runtime-cloudflare": "0.6.0",
|
|
35
|
+
"wrangler": "4.147.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "24.0.0"
|