@treeseed/sdk 0.10.27 → 0.11.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 +207 -6
- package/dist/capacity-provider.d.ts +3 -1
- package/dist/capacity-provider.js +25 -5
- package/dist/control-plane.d.ts +1 -0
- package/dist/control-plane.js +38 -13
- package/dist/db/market-schema.d.ts +8860 -6172
- package/dist/db/market-schema.js +108 -0
- package/dist/db/node-sqlite.js +7 -2
- package/dist/hosting/apps.d.ts +12 -0
- package/dist/hosting/apps.js +107 -0
- package/dist/hosting/builtins.d.ts +25 -0
- package/dist/hosting/builtins.js +791 -0
- package/dist/hosting/contracts.d.ts +207 -0
- package/dist/hosting/contracts.js +0 -0
- package/dist/hosting/graph.d.ts +192 -0
- package/dist/hosting/graph.js +1106 -0
- package/dist/hosting/index.d.ts +4 -0
- package/dist/hosting/index.js +4 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +71 -7
- package/dist/managed-dependencies.js +1 -2
- package/dist/market-client.d.ts +63 -3
- package/dist/market-client.js +83 -11
- package/dist/operations/services/bootstrap-runner.d.ts +3 -1
- package/dist/operations/services/bootstrap-runner.js +22 -2
- package/dist/operations/services/config-runtime.d.ts +10 -5
- package/dist/operations/services/config-runtime.js +209 -66
- package/dist/operations/services/deploy.d.ts +70 -7
- package/dist/operations/services/deploy.js +579 -64
- package/dist/operations/services/deployment-readiness.d.ts +30 -0
- package/dist/operations/services/deployment-readiness.js +175 -0
- package/dist/operations/services/git-workflow.d.ts +2 -1
- package/dist/operations/services/git-workflow.js +9 -3
- package/dist/operations/services/github-actions-verification.d.ts +1 -0
- package/dist/operations/services/github-actions-verification.js +1 -0
- package/dist/operations/services/github-api.js +1 -1
- package/dist/operations/services/github-automation.d.ts +1 -1
- package/dist/operations/services/github-automation.js +4 -3
- package/dist/operations/services/github-credentials.d.ts +13 -0
- package/dist/operations/services/github-credentials.js +58 -0
- package/dist/operations/services/hosted-service-checks.d.ts +63 -0
- package/dist/operations/services/hosted-service-checks.js +327 -0
- package/dist/operations/services/hub-provider-launch.js +3 -3
- package/dist/operations/services/live-hosted-service-checks.d.ts +25 -0
- package/dist/operations/services/live-hosted-service-checks.js +350 -0
- package/dist/operations/services/managed-host-security.js +1 -1
- package/dist/operations/services/operations-runner-smoke.d.ts +30 -0
- package/dist/operations/services/operations-runner-smoke.js +180 -0
- package/dist/operations/services/package-adapters.d.ts +95 -0
- package/dist/operations/services/package-adapters.js +288 -0
- package/dist/operations/services/package-reference-policy.d.ts +1 -0
- package/dist/operations/services/package-reference-policy.js +15 -2
- package/dist/operations/services/project-platform.d.ts +80 -22
- package/dist/operations/services/project-platform.js +49 -8
- package/dist/operations/services/project-web-monitor.js +26 -4
- package/dist/operations/services/railway-api.d.ts +88 -5
- package/dist/operations/services/railway-api.js +626 -35
- package/dist/operations/services/railway-deploy.d.ts +46 -40
- package/dist/operations/services/railway-deploy.js +261 -293
- package/dist/operations/services/release-candidate.d.ts +19 -0
- package/dist/operations/services/release-candidate.js +375 -38
- package/dist/operations/services/repository-save-orchestrator.d.ts +3 -1
- package/dist/operations/services/repository-save-orchestrator.js +279 -66
- package/dist/operations/services/runtime-tools.d.ts +1 -0
- package/dist/operations/services/runtime-tools.js +10 -9
- package/dist/operations/services/template-registry.js +14 -7
- package/dist/operations/services/verification-cache.d.ts +25 -0
- package/dist/operations/services/verification-cache.js +71 -0
- package/dist/operations/services/workspace-dependency-mode.js +9 -1
- package/dist/operations/services/workspace-save.js +1 -1
- package/dist/operations/services/workspace-tools.js +2 -1
- package/dist/platform/contracts.d.ts +32 -1
- package/dist/platform/deploy-config.js +73 -8
- package/dist/platform/env.yaml +163 -35
- package/dist/platform/environment.d.ts +1 -0
- package/dist/platform/environment.js +74 -5
- package/dist/platform/plugin.d.ts +9 -0
- package/dist/platform-operation-store.js +2 -2
- package/dist/platform-operations.js +1 -1
- package/dist/reconcile/bootstrap-systems.js +2 -2
- package/dist/reconcile/builtin-adapters.js +372 -189
- package/dist/reconcile/contracts.d.ts +9 -5
- package/dist/reconcile/desired-state.d.ts +1 -0
- package/dist/reconcile/desired-state.js +5 -5
- package/dist/reconcile/engine.d.ts +5 -2
- package/dist/reconcile/engine.js +53 -32
- package/dist/reconcile/index.d.ts +2 -0
- package/dist/reconcile/index.js +2 -0
- package/dist/reconcile/live-acceptance.d.ts +79 -0
- package/dist/reconcile/live-acceptance.js +1615 -0
- package/dist/reconcile/platform.d.ts +104 -0
- package/dist/reconcile/platform.js +100 -0
- package/dist/reconcile/state.js +4 -4
- package/dist/reconcile/units.js +2 -2
- package/dist/scripts/deployment-readiness.js +20 -0
- package/dist/scripts/generate-treedx-openapi-types.js +186 -0
- package/dist/scripts/operations-runner-smoke.js +16 -0
- package/dist/scripts/release-verify.js +4 -1
- package/dist/scripts/template-catalog.test.js +7 -7
- package/dist/scripts/tenant-workflow-action.js +10 -1
- package/dist/sdk-types.d.ts +172 -5
- package/dist/sdk-types.js +28 -3
- package/dist/sdk.d.ts +35 -24
- package/dist/sdk.js +186 -17
- package/dist/template-launch-requirements.js +9 -0
- package/dist/treedx/adapters.d.ts +6 -0
- package/dist/treedx/adapters.js +36 -0
- package/dist/treedx/client.d.ts +222 -0
- package/dist/treedx/client.js +871 -0
- package/dist/treedx/errors.d.ts +13 -0
- package/dist/treedx/errors.js +17 -0
- package/dist/treedx/federated-client.d.ts +27 -0
- package/dist/treedx/federated-client.js +158 -0
- package/dist/treedx/generated/openapi-types.d.ts +3558 -0
- package/dist/treedx/generated/openapi-types.js +0 -0
- package/dist/treedx/graph-adapter.d.ts +33 -0
- package/dist/treedx/graph-adapter.js +156 -0
- package/dist/treedx/index.d.ts +14 -0
- package/dist/treedx/index.js +48 -0
- package/dist/treedx/market-integration.d.ts +27 -0
- package/dist/treedx/market-integration.js +131 -0
- package/dist/treedx/ports.d.ts +166 -0
- package/dist/treedx/ports.js +231 -0
- package/dist/treedx/query-adapter.d.ts +19 -0
- package/dist/treedx/query-adapter.js +62 -0
- package/dist/treedx/registry-client.d.ts +11 -0
- package/dist/treedx/registry-client.js +19 -0
- package/dist/treedx/repository-adapter.d.ts +45 -0
- package/dist/treedx/repository-adapter.js +308 -0
- package/dist/treedx/sdk-integration.d.ts +27 -0
- package/dist/treedx/sdk-integration.js +63 -0
- package/dist/treedx/types.d.ts +1084 -0
- package/dist/treedx/types.js +8 -0
- package/dist/treedx/workspace-adapter.d.ts +27 -0
- package/dist/treedx/workspace-adapter.js +65 -0
- package/dist/treedx-backends.d.ts +218 -0
- package/dist/treedx-backends.js +632 -0
- package/dist/treedx-client.d.ts +86 -0
- package/dist/treedx-client.js +175 -0
- package/dist/treeseed/template-catalog/catalog.fixture.json +497 -138
- package/dist/workflow/operations.d.ts +119 -13
- package/dist/workflow/operations.js +309 -53
- package/dist/workflow-state.d.ts +13 -0
- package/dist/workflow-state.js +43 -26
- package/dist/workflow-support.d.ts +11 -3
- package/dist/workflow-support.js +67 -3
- package/dist/workflow.d.ts +5 -0
- package/drizzle/market/0004_treedx_market_integration.sql +99 -0
- package/package.json +34 -3
- package/templates/github/deploy-web.workflow.yml +39 -6
- package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.d.ts +0 -3
- package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.ts +0 -6
- package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +0 -35
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/api/server.js +0 -4
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/config.yaml +0 -65
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/decisions/adopt-initial-proposal-loop.mdx +0 -22
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/empty/.gitkeep +0 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/knowledge/handbook/index.mdx +0 -11
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/pages/welcome.mdx +0 -11
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/people/starter-steward.mdx +0 -11
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/proposals/establish-initial-proposal-loop.mdx +0 -17
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.d.ts +0 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.ts +0 -3
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/env.yaml +0 -1
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/manifest.yaml +0 -26
- package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +0 -74
- package/dist/treeseed/template-catalog/templates/starter-basic/template/tsconfig.json +0 -9
- package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +0 -103
|
@@ -319,7 +319,7 @@ function buildPublicVars(deployConfig, options = {}) {
|
|
|
319
319
|
TREESEED_RUNTIME_MODE: deployConfig.runtime?.mode ?? "none",
|
|
320
320
|
TREESEED_RUNTIME_REGISTRATION: deployConfig.runtime?.registration ?? "none",
|
|
321
321
|
TREESEED_CENTRAL_MARKET_API_BASE_URL: envOrNull("TREESEED_CENTRAL_MARKET_API_BASE_URL") ?? DEFAULT_TREESEED_MARKET_BASE_URL,
|
|
322
|
-
|
|
322
|
+
TREESEED_API_BASE_URL: resolveConfiguredMarketBaseUrl(deployConfig),
|
|
323
323
|
TREESEED_CATALOG_MARKET_API_BASE_URLS: envOrNull("TREESEED_CATALOG_MARKET_API_BASE_URLS") ?? resolveConfiguredMarketBaseUrl(deployConfig),
|
|
324
324
|
TREESEED_HOSTING_TEAM_ID: contentDefaultTeamId,
|
|
325
325
|
TREESEED_PROJECT_ID: identity.projectId,
|
|
@@ -906,7 +906,7 @@ function runWrangler(args, { cwd, allowFailure = false, json = false, capture =
|
|
|
906
906
|
output || `Wrangler command failed: ${args.join(" ")}`,
|
|
907
907
|
"",
|
|
908
908
|
"Treeseed Cloudflare authentication failed. Check that CLOUDFLARE_API_TOKEN is an account-level token scoped to the target account and domain.",
|
|
909
|
-
"Required Cloudflare permissions:
|
|
909
|
+
"Required Cloudflare permissions: account Pages Write, Workers Scripts Write, Workers KV Storage Write, Workers R2 Storage Write, D1 Write, Queues Write, Turnstile Sites Write, Account Rulesets Write, and Account Rule Lists Write; target zone Zone Read, DNS Write, Cache Settings Write, and SSL and Certificates Write."
|
|
910
910
|
].join("\n"));
|
|
911
911
|
}
|
|
912
912
|
throw new Error(output || `Wrangler command failed: ${args.join(" ")}`);
|
|
@@ -990,6 +990,48 @@ function listTurnstileWidgets(tenantRoot, env) {
|
|
|
990
990
|
});
|
|
991
991
|
return Array.isArray(payload?.result) ? payload.result : [];
|
|
992
992
|
}
|
|
993
|
+
function listWorkers(tenantRoot, env) {
|
|
994
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
995
|
+
if (!accountId) {
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
const payload = cloudflareApiRequest(`/accounts/${encodeURIComponent(accountId)}/workers/services?per_page=100`, {
|
|
999
|
+
env,
|
|
1000
|
+
allowFailure: true
|
|
1001
|
+
});
|
|
1002
|
+
return Array.isArray(payload?.result) ? payload.result : [];
|
|
1003
|
+
}
|
|
1004
|
+
function listDnsZones(env) {
|
|
1005
|
+
const payload = cloudflareApiRequest("/zones?per_page=100", {
|
|
1006
|
+
env,
|
|
1007
|
+
allowFailure: true
|
|
1008
|
+
});
|
|
1009
|
+
return Array.isArray(payload?.result) ? payload.result : [];
|
|
1010
|
+
}
|
|
1011
|
+
function listDnsRecords(zoneId, env) {
|
|
1012
|
+
if (!zoneId) {
|
|
1013
|
+
return [];
|
|
1014
|
+
}
|
|
1015
|
+
const records = [];
|
|
1016
|
+
let page = 1;
|
|
1017
|
+
let totalPages = 1;
|
|
1018
|
+
while (page <= totalPages && page <= 50) {
|
|
1019
|
+
const payload = cloudflareApiRequest(
|
|
1020
|
+
`/zones/${encodeURIComponent(zoneId)}/dns_records?per_page=100&page=${page}`,
|
|
1021
|
+
{ env, allowFailure: true }
|
|
1022
|
+
);
|
|
1023
|
+
if (payload?.success === false) {
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
if (Array.isArray(payload?.result)) {
|
|
1027
|
+
records.push(...payload.result);
|
|
1028
|
+
}
|
|
1029
|
+
const reportedTotal = Number(payload?.result_info?.total_pages);
|
|
1030
|
+
totalPages = Number.isFinite(reportedTotal) && reportedTotal > 0 ? reportedTotal : page;
|
|
1031
|
+
page += 1;
|
|
1032
|
+
}
|
|
1033
|
+
return records;
|
|
1034
|
+
}
|
|
993
1035
|
function getTurnstileWidget(env, sitekey) {
|
|
994
1036
|
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
995
1037
|
if (!accountId || !sitekey) {
|
|
@@ -1199,6 +1241,13 @@ function shouldManageCloudflareWebCacheRules(deployConfig, target) {
|
|
|
1199
1241
|
return Boolean(webTarget?.host && !webTarget.host.endsWith(".workers.dev") && !webTarget.host.endsWith(".pages.dev"));
|
|
1200
1242
|
}
|
|
1201
1243
|
function cloudflareApiRequest(path, { method = "GET", body, env, allowFailure = false } = {}) {
|
|
1244
|
+
const token = env?.CLOUDFLARE_API_TOKEN ?? process.env.CLOUDFLARE_API_TOKEN ?? "";
|
|
1245
|
+
if (!token) {
|
|
1246
|
+
if (allowFailure) {
|
|
1247
|
+
return null;
|
|
1248
|
+
}
|
|
1249
|
+
throw new Error(`Cloudflare API token is required: ${method} ${path}`);
|
|
1250
|
+
}
|
|
1202
1251
|
const requestScript = `import { readFileSync } from 'node:fs';
|
|
1203
1252
|
import { request } from 'node:https';
|
|
1204
1253
|
const input = JSON.parse(readFileSync(0, 'utf8') || '{}');
|
|
@@ -1262,9 +1311,13 @@ try {
|
|
|
1262
1311
|
method,
|
|
1263
1312
|
body,
|
|
1264
1313
|
timeoutMs: 12e3,
|
|
1265
|
-
token
|
|
1314
|
+
token
|
|
1266
1315
|
});
|
|
1267
|
-
const isTransient = (text) => /fetch failed|timed out|etimedout|econnreset|enetunreach|temporarily unavailable|aborted/iu.test(text || "");
|
|
1316
|
+
const isTransient = (text) => /fetch failed|timed out|etimedout|econnreset|enetunreach|temporarily unavailable|aborted|rate limit|too many requests|throttl|please wait/iu.test(text || "");
|
|
1317
|
+
const retryDelay = (text, currentAttempt) => {
|
|
1318
|
+
const base = /rate limit|too many requests|throttl|please wait/iu.test(text || "") ? 2500 : 500;
|
|
1319
|
+
return base * (currentAttempt + 1);
|
|
1320
|
+
};
|
|
1268
1321
|
const formatPayloadErrors = (payload) => Array.isArray(payload?.errors) ? payload.errors.map((entry) => entry?.message ?? JSON.stringify(entry)).join("; ") : "";
|
|
1269
1322
|
const summarizeChildError = (text) => {
|
|
1270
1323
|
const lines = String(text || "").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
@@ -1288,9 +1341,9 @@ try {
|
|
|
1288
1341
|
}
|
|
1289
1342
|
);
|
|
1290
1343
|
if (response.error?.code === "ETIMEDOUT") {
|
|
1291
|
-
if (attempt <
|
|
1344
|
+
if (attempt < 7) {
|
|
1292
1345
|
attempt += 1;
|
|
1293
|
-
sleepSync(
|
|
1346
|
+
sleepSync(retryDelay("timed out", attempt));
|
|
1294
1347
|
continue;
|
|
1295
1348
|
}
|
|
1296
1349
|
if (!allowFailure) {
|
|
@@ -1300,9 +1353,9 @@ try {
|
|
|
1300
1353
|
}
|
|
1301
1354
|
const stderr = response.stderr?.trim() || "";
|
|
1302
1355
|
if (response.status !== 0) {
|
|
1303
|
-
if (attempt <
|
|
1356
|
+
if (attempt < 7 && isTransient(stderr)) {
|
|
1304
1357
|
attempt += 1;
|
|
1305
|
-
sleepSync(
|
|
1358
|
+
sleepSync(retryDelay(stderr, attempt));
|
|
1306
1359
|
continue;
|
|
1307
1360
|
}
|
|
1308
1361
|
if (!allowFailure) {
|
|
@@ -1323,9 +1376,9 @@ try {
|
|
|
1323
1376
|
};
|
|
1324
1377
|
}
|
|
1325
1378
|
const details = formatPayloadErrors(parsed.payload);
|
|
1326
|
-
if (!parsed.ok &&
|
|
1379
|
+
if (!parsed.ok && isTransient(details) && attempt < 7) {
|
|
1327
1380
|
attempt += 1;
|
|
1328
|
-
sleepSync(
|
|
1381
|
+
sleepSync(retryDelay(details, attempt));
|
|
1329
1382
|
continue;
|
|
1330
1383
|
}
|
|
1331
1384
|
if (!parsed.ok && !allowFailure) {
|
|
@@ -1635,7 +1688,7 @@ function resolveConfiguredCloudflareAccountId(deployConfig) {
|
|
|
1635
1688
|
return envOrNull("CLOUDFLARE_ACCOUNT_ID") ?? deployConfig.cloudflare.accountId;
|
|
1636
1689
|
}
|
|
1637
1690
|
function resolveConfiguredMarketBaseUrl(deployConfig) {
|
|
1638
|
-
return envOrNull("
|
|
1691
|
+
return envOrNull("TREESEED_API_BASE_URL") ?? envOrNull("TREESEED_CENTRAL_MARKET_API_BASE_URL") ?? deployConfig.runtime?.marketBaseUrl ?? deployConfig.hosting?.marketBaseUrl ?? DEFAULT_TREESEED_MARKET_BASE_URL;
|
|
1639
1692
|
}
|
|
1640
1693
|
function resolveConfiguredPagesProjectName(deployConfig) {
|
|
1641
1694
|
return sharedDeploymentName(resolveTreeseedResourceIdentity(deployConfig, createPersistentDeployTarget("prod")));
|
|
@@ -1803,7 +1856,7 @@ function resolveExistingD1ByName(d1Databases, expectedName, current) {
|
|
|
1803
1856
|
};
|
|
1804
1857
|
}
|
|
1805
1858
|
function looksLikeMissingResource(output) {
|
|
1806
|
-
return /not found|does not exist|could(?: not|n't) find|couldnt find/i.test(output);
|
|
1859
|
+
return /not found|does not exist|could(?: not|n't) find|couldnt find|already deleted|deleted widget|access a deleted/i.test(output);
|
|
1807
1860
|
}
|
|
1808
1861
|
function deleteKvNamespace(tenantRoot, namespaceId, { env, dryRun, preview = false }) {
|
|
1809
1862
|
if (!namespaceId || isPlaceholderResourceId(namespaceId)) {
|
|
@@ -1957,9 +2010,9 @@ function listR2Objects(bucketName, { env }) {
|
|
|
1957
2010
|
}
|
|
1958
2011
|
const objects = [];
|
|
1959
2012
|
let cursor = "";
|
|
1960
|
-
for (let page = 0; page <
|
|
2013
|
+
for (let page = 0; page < 20; page += 1) {
|
|
1961
2014
|
const payload = cloudflareApiRequest(
|
|
1962
|
-
`/accounts/${encodeURIComponent(accountId)}/r2/buckets/${encodeURIComponent(bucketName)}/objects?per_page=
|
|
2015
|
+
`/accounts/${encodeURIComponent(accountId)}/r2/buckets/${encodeURIComponent(bucketName)}/objects?per_page=200${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`,
|
|
1963
2016
|
{ env, allowFailure: true }
|
|
1964
2017
|
);
|
|
1965
2018
|
if (payload?.success === false) {
|
|
@@ -1972,45 +2025,156 @@ function listR2Objects(bucketName, { env }) {
|
|
|
1972
2025
|
break;
|
|
1973
2026
|
}
|
|
1974
2027
|
cursor = nextCursor;
|
|
2028
|
+
if (objects.length >= 200) {
|
|
2029
|
+
break;
|
|
2030
|
+
}
|
|
1975
2031
|
}
|
|
1976
2032
|
return objects;
|
|
1977
2033
|
}
|
|
1978
2034
|
function drainR2Bucket(bucketName, { env }) {
|
|
1979
2035
|
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
1980
2036
|
if (!accountId || !bucketName) {
|
|
1981
|
-
return { objectsDeleted: 0 };
|
|
2037
|
+
return { objectsDeleted: 0, objectsMissing: 0, objectsDeferred: 0 };
|
|
1982
2038
|
}
|
|
1983
2039
|
let objectsDeleted = 0;
|
|
2040
|
+
let objectsMissing = 0;
|
|
2041
|
+
let objectsDeferred = 0;
|
|
1984
2042
|
for (let batch = 0; batch < 100; batch += 1) {
|
|
1985
2043
|
const objects = listR2Objects(bucketName, { env });
|
|
1986
2044
|
if (objects.length === 0) {
|
|
1987
2045
|
break;
|
|
1988
2046
|
}
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
2047
|
+
const keys = objects.map((object) => r2ObjectKey(object)).filter(Boolean);
|
|
2048
|
+
const deleted = deleteR2ObjectsBatch(bucketName, keys, { env });
|
|
2049
|
+
objectsDeleted += deleted.objectsDeleted;
|
|
2050
|
+
objectsMissing += deleted.objectsMissing;
|
|
2051
|
+
objectsDeferred += deleted.objectsDeferred;
|
|
2052
|
+
const batchDeleted = deleted.objectsDeleted + deleted.objectsMissing;
|
|
2053
|
+
if (batchDeleted === 0) {
|
|
2054
|
+
if (deleted.objectsDeferred > 0) {
|
|
2055
|
+
sleepSync(3e3);
|
|
1993
2056
|
continue;
|
|
1994
2057
|
}
|
|
1995
|
-
const result = cloudflareApiRequest(
|
|
1996
|
-
`/accounts/${encodeURIComponent(accountId)}/r2/buckets/${encodeURIComponent(bucketName)}/objects/${encodeURIComponent(key)}`,
|
|
1997
|
-
{ method: "DELETE", env, allowFailure: true }
|
|
1998
|
-
);
|
|
1999
|
-
if (result?.success === false) {
|
|
2000
|
-
const message = formatCloudflareErrors(result);
|
|
2001
|
-
if (looksLikeMissingResource(message)) {
|
|
2002
|
-
continue;
|
|
2003
|
-
}
|
|
2004
|
-
throw new Error(message || `Failed to delete R2 object ${key}.`);
|
|
2005
|
-
}
|
|
2006
|
-
objectsDeleted += 1;
|
|
2007
|
-
batchDeleted += 1;
|
|
2008
|
-
}
|
|
2009
|
-
if (batchDeleted === 0) {
|
|
2010
2058
|
break;
|
|
2011
2059
|
}
|
|
2060
|
+
if (deleted.objectsDeferred > 0) {
|
|
2061
|
+
sleepSync(1500);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
return { objectsDeleted, objectsMissing, objectsDeferred };
|
|
2065
|
+
}
|
|
2066
|
+
function deleteR2ObjectsBatch(bucketName, keys, { env }) {
|
|
2067
|
+
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
2068
|
+
const token = env?.CLOUDFLARE_API_TOKEN ?? process.env.CLOUDFLARE_API_TOKEN ?? "";
|
|
2069
|
+
const uniqueKeys = [...new Set((keys ?? []).filter(Boolean))];
|
|
2070
|
+
if (!accountId || !bucketName || uniqueKeys.length === 0) {
|
|
2071
|
+
return { objectsDeleted: 0, objectsMissing: 0, objectsDeferred: 0 };
|
|
2072
|
+
}
|
|
2073
|
+
const script = `
|
|
2074
|
+
const input = JSON.parse(await new Promise((resolve) => {
|
|
2075
|
+
let body = '';
|
|
2076
|
+
process.stdin.setEncoding('utf8');
|
|
2077
|
+
process.stdin.on('data', (chunk) => { body += chunk; });
|
|
2078
|
+
process.stdin.on('end', () => resolve(body || '{}'));
|
|
2079
|
+
}));
|
|
2080
|
+
let index = 0;
|
|
2081
|
+
let deleted = 0;
|
|
2082
|
+
let missing = 0;
|
|
2083
|
+
let deferred = 0;
|
|
2084
|
+
const failed = [];
|
|
2085
|
+
async function removeKey(key) {
|
|
2086
|
+
function encodeObjectKey(value) {
|
|
2087
|
+
return String(value).split('/').map((part) => encodeURIComponent(part)).join('/');
|
|
2088
|
+
}
|
|
2089
|
+
const url = 'https://api.cloudflare.com/client/v4/accounts/'
|
|
2090
|
+
+ encodeURIComponent(input.accountId)
|
|
2091
|
+
+ '/r2/buckets/'
|
|
2092
|
+
+ encodeURIComponent(input.bucketName)
|
|
2093
|
+
+ '/objects/'
|
|
2094
|
+
+ encodeObjectKey(key);
|
|
2095
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
2096
|
+
const controller = new AbortController();
|
|
2097
|
+
const timeout = setTimeout(() => controller.abort(), input.timeoutMs || 15000);
|
|
2098
|
+
try {
|
|
2099
|
+
const response = await fetch(url, {
|
|
2100
|
+
method: 'DELETE',
|
|
2101
|
+
headers: { authorization: 'Bearer ' + input.token },
|
|
2102
|
+
signal: controller.signal,
|
|
2103
|
+
});
|
|
2104
|
+
const text = await response.text();
|
|
2105
|
+
let payload = {};
|
|
2106
|
+
try { payload = text ? JSON.parse(text) : {}; } catch { payload = { errors: [{ message: text }] }; }
|
|
2107
|
+
if (response.ok && payload.success !== false) {
|
|
2108
|
+
deleted += 1;
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
const message = Array.isArray(payload.errors) ? payload.errors.map((entry) => entry?.message || JSON.stringify(entry)).join('; ') : text;
|
|
2112
|
+
if (/not found|does not exist|deleted|missing/i.test(message || '')) {
|
|
2113
|
+
missing += 1;
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
if (response.status === 429 || /rate limit|too many requests/i.test(message || '')) {
|
|
2117
|
+
await new Promise((resolve) => setTimeout(resolve, 750 * (attempt + 1)));
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
2120
|
+
failed.push({ key, message: message || \`delete failed with status \${response.status}\` });
|
|
2121
|
+
return;
|
|
2122
|
+
} catch (error) {
|
|
2123
|
+
if (attempt < 5 && /aborted|timed out|fetch failed|econnreset/i.test(error instanceof Error ? error.message : String(error))) {
|
|
2124
|
+
await new Promise((resolve) => setTimeout(resolve, 750 * (attempt + 1)));
|
|
2125
|
+
continue;
|
|
2126
|
+
}
|
|
2127
|
+
failed.push({ key, message: error instanceof Error ? error.message : String(error) });
|
|
2128
|
+
return;
|
|
2129
|
+
} finally {
|
|
2130
|
+
clearTimeout(timeout);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
deferred += 1;
|
|
2134
|
+
}
|
|
2135
|
+
async function worker() {
|
|
2136
|
+
for (;;) {
|
|
2137
|
+
const current = index;
|
|
2138
|
+
index += 1;
|
|
2139
|
+
if (current >= input.keys.length) return;
|
|
2140
|
+
await removeKey(input.keys[current]);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
await Promise.all(Array.from({ length: Math.min(input.concurrency || 4, input.keys.length) }, () => worker()));
|
|
2144
|
+
process.stdout.write(JSON.stringify({ deleted, missing, deferred, failed }));
|
|
2145
|
+
`.trim();
|
|
2146
|
+
const result = spawnSync(process.execPath, ["--input-type=module", "-e", script], {
|
|
2147
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2148
|
+
encoding: "utf8",
|
|
2149
|
+
env: { ...process.env, ...env ?? {} },
|
|
2150
|
+
input: JSON.stringify({
|
|
2151
|
+
accountId,
|
|
2152
|
+
bucketName,
|
|
2153
|
+
keys: uniqueKeys,
|
|
2154
|
+
token,
|
|
2155
|
+
concurrency: 4,
|
|
2156
|
+
timeoutMs: 12e3
|
|
2157
|
+
}),
|
|
2158
|
+
timeout: 12e4
|
|
2159
|
+
});
|
|
2160
|
+
if (result.status !== 0 || result.error) {
|
|
2161
|
+
throw new Error(result.stderr?.trim() || result.error?.message || `Failed to delete R2 object batch for ${bucketName}.`);
|
|
2012
2162
|
}
|
|
2013
|
-
|
|
2163
|
+
let parsed;
|
|
2164
|
+
try {
|
|
2165
|
+
parsed = JSON.parse(result.stdout || "{}");
|
|
2166
|
+
} catch {
|
|
2167
|
+
throw new Error(`R2 object batch delete returned invalid JSON for ${bucketName}.`);
|
|
2168
|
+
}
|
|
2169
|
+
if (Array.isArray(parsed.failed) && parsed.failed.length > 0) {
|
|
2170
|
+
const first = parsed.failed[0];
|
|
2171
|
+
throw new Error(`Failed to delete ${parsed.failed.length} R2 objects from ${bucketName}: ${first?.message ?? first?.key ?? "unknown error"}`);
|
|
2172
|
+
}
|
|
2173
|
+
return {
|
|
2174
|
+
objectsDeleted: Number(parsed.deleted) || 0,
|
|
2175
|
+
objectsMissing: Number(parsed.missing) || 0,
|
|
2176
|
+
objectsDeferred: Number(parsed.deferred) || 0
|
|
2177
|
+
};
|
|
2014
2178
|
}
|
|
2015
2179
|
function deleteD1DatabaseForDestroy(tenantRoot, databaseName, { env, dryRun, deleteData }) {
|
|
2016
2180
|
if (!deleteData) {
|
|
@@ -2066,20 +2230,20 @@ function listPagesCustomDomainsWithWrangler(tenantRoot, projectName, { env }) {
|
|
|
2066
2230
|
return [];
|
|
2067
2231
|
}
|
|
2068
2232
|
}
|
|
2069
|
-
function deletePagesCustomDomains(tenantRoot, projectName, knownNames, { env, dryRun }) {
|
|
2233
|
+
function deletePagesCustomDomains(tenantRoot, projectName, knownNames, { env, dryRun, knownOnly = false }) {
|
|
2070
2234
|
if (!projectName) {
|
|
2071
2235
|
return [resourceOperation("cloudflare", "pages-custom-domain", projectName, "missing")];
|
|
2072
2236
|
}
|
|
2073
2237
|
const desiredNames = [...new Set((knownNames ?? []).filter(Boolean))];
|
|
2074
2238
|
if (dryRun) {
|
|
2075
|
-
return desiredNames.length > 0 ? desiredNames.map((name) => resourceOperation("cloudflare", "pages-custom-domain", name, "planned", { projectName })) : [resourceOperation("cloudflare", "pages-custom-domain", projectName, "planned", { reason: "project_delete_prerequisite" })];
|
|
2239
|
+
return desiredNames.length > 0 ? desiredNames.map((name) => resourceOperation("cloudflare", "pages-custom-domain", name, "planned", { projectName, knownOnly })) : [resourceOperation("cloudflare", "pages-custom-domain", projectName, knownOnly ? "skipped" : "planned", { reason: knownOnly ? "no_target_scoped_domain" : "project_delete_prerequisite" })];
|
|
2076
2240
|
}
|
|
2077
2241
|
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
2078
2242
|
if (!accountId) {
|
|
2079
2243
|
return desiredNames.length > 0 ? desiredNames.map((name) => resourceOperation("cloudflare", "pages-custom-domain", name, "blocked", { projectName, reason: "missing_cloudflare_account_id" })) : [resourceOperation("cloudflare", "pages-custom-domain", projectName, "blocked", { reason: "missing_cloudflare_account_id" })];
|
|
2080
2244
|
}
|
|
2081
|
-
const listedNames = listPagesCustomDomains(projectName, { env }).map(pagesDomainName).filter(Boolean);
|
|
2082
|
-
const wranglerNames = listPagesCustomDomainsWithWrangler(tenantRoot, projectName, { env });
|
|
2245
|
+
const listedNames = knownOnly ? [] : listPagesCustomDomains(projectName, { env }).map(pagesDomainName).filter(Boolean);
|
|
2246
|
+
const wranglerNames = knownOnly ? [] : listPagesCustomDomainsWithWrangler(tenantRoot, projectName, { env });
|
|
2083
2247
|
const domainNames = [.../* @__PURE__ */ new Set([...desiredNames, ...listedNames, ...wranglerNames])];
|
|
2084
2248
|
if (domainNames.length === 0) {
|
|
2085
2249
|
return [resourceOperation("cloudflare", "pages-custom-domain", projectName, "missing", { projectName })];
|
|
@@ -2095,13 +2259,16 @@ function normalizePagesDeploymentId(deployment) {
|
|
|
2095
2259
|
function normalizePagesDeployments(value) {
|
|
2096
2260
|
return (Array.isArray(value) ? value : Array.isArray(value?.result) ? value.result : []).filter((entry) => normalizePagesDeploymentId(entry));
|
|
2097
2261
|
}
|
|
2098
|
-
function
|
|
2262
|
+
function pagesDeploymentEnvironments(environment = "all") {
|
|
2263
|
+
return environment === "preview" ? ["preview"] : environment === "production" ? ["production"] : ["preview", "production"];
|
|
2264
|
+
}
|
|
2265
|
+
function listPagesDeploymentsWithApi(projectName, { env, environment = "all" }) {
|
|
2099
2266
|
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
2100
2267
|
if (!projectName || !accountId) {
|
|
2101
2268
|
return [];
|
|
2102
2269
|
}
|
|
2103
2270
|
const deployments = [];
|
|
2104
|
-
for (const pagesEnvironment of
|
|
2271
|
+
for (const pagesEnvironment of pagesDeploymentEnvironments(environment)) {
|
|
2105
2272
|
let page = 1;
|
|
2106
2273
|
let totalPages = 1;
|
|
2107
2274
|
while (page <= totalPages && page <= 50) {
|
|
@@ -2120,9 +2287,9 @@ function listPagesDeploymentsWithApi(projectName, { env }) {
|
|
|
2120
2287
|
}
|
|
2121
2288
|
return deployments;
|
|
2122
2289
|
}
|
|
2123
|
-
function listPagesDeployments(tenantRoot, projectName, { env }) {
|
|
2290
|
+
function listPagesDeployments(tenantRoot, projectName, { env, environment = "all" }) {
|
|
2124
2291
|
const deployments = [];
|
|
2125
|
-
for (const pagesEnvironment of
|
|
2292
|
+
for (const pagesEnvironment of pagesDeploymentEnvironments(environment)) {
|
|
2126
2293
|
const result = runWrangler(["pages", "deployment", "list", "--project-name", projectName, "--environment", pagesEnvironment, "--json"], {
|
|
2127
2294
|
cwd: tenantRoot,
|
|
2128
2295
|
allowFailure: true,
|
|
@@ -2141,14 +2308,14 @@ function listPagesDeployments(tenantRoot, projectName, { env }) {
|
|
|
2141
2308
|
const byId = new Map(deployments.map((deployment) => [normalizePagesDeploymentId(deployment), deployment]));
|
|
2142
2309
|
return [...byId.values()];
|
|
2143
2310
|
}
|
|
2144
|
-
return listPagesDeploymentsWithApi(projectName, { env });
|
|
2311
|
+
return listPagesDeploymentsWithApi(projectName, { env, environment });
|
|
2145
2312
|
}
|
|
2146
|
-
function deletePagesDeployments(tenantRoot, projectName, { env, dryRun }) {
|
|
2313
|
+
function deletePagesDeployments(tenantRoot, projectName, { env, dryRun, environment = "all" }) {
|
|
2147
2314
|
if (!projectName) {
|
|
2148
2315
|
return resourceOperation("cloudflare", "pages-deployments", projectName, "missing");
|
|
2149
2316
|
}
|
|
2150
2317
|
if (dryRun) {
|
|
2151
|
-
return resourceOperation("cloudflare", "pages-deployments", projectName, "planned", { reason: "project_delete_prerequisite" });
|
|
2318
|
+
return resourceOperation("cloudflare", "pages-deployments", projectName, "planned", { reason: "project_delete_prerequisite", environment });
|
|
2152
2319
|
}
|
|
2153
2320
|
const accountId = env?.CLOUDFLARE_ACCOUNT_ID ?? process.env.CLOUDFLARE_ACCOUNT_ID ?? "";
|
|
2154
2321
|
if (!accountId) {
|
|
@@ -2158,7 +2325,7 @@ function deletePagesDeployments(tenantRoot, projectName, { env, dryRun }) {
|
|
|
2158
2325
|
let skipped = 0;
|
|
2159
2326
|
let total = 0;
|
|
2160
2327
|
for (let batch = 0; batch < 100; batch += 1) {
|
|
2161
|
-
const deployments = listPagesDeployments(tenantRoot, projectName, { env });
|
|
2328
|
+
const deployments = listPagesDeployments(tenantRoot, projectName, { env, environment });
|
|
2162
2329
|
if (deployments.length === 0) {
|
|
2163
2330
|
return resourceOperation("cloudflare", "pages-deployments", projectName, deleted > 0 ? "deleted" : "missing", {
|
|
2164
2331
|
deleted,
|
|
@@ -2286,31 +2453,31 @@ function configuredRailwayDestroyTargets(tenantRoot, deployConfig, scope) {
|
|
|
2286
2453
|
identity = { deploymentKey: deployConfig.slug ?? deployConfig.name ?? "treeseed" };
|
|
2287
2454
|
}
|
|
2288
2455
|
const services = [];
|
|
2289
|
-
for (const serviceKey of ["api", "
|
|
2456
|
+
for (const serviceKey of ["api", "operationsRunner"]) {
|
|
2290
2457
|
const service = deployConfig.services?.[serviceKey];
|
|
2291
2458
|
if (!service || service.enabled === false || (service.provider ?? "railway") !== "railway") {
|
|
2292
2459
|
continue;
|
|
2293
2460
|
}
|
|
2294
|
-
const baseServiceName = service.railway?.serviceName ?? `${identity.deploymentKey}-${serviceKey === "
|
|
2295
|
-
const runnerPool = serviceKey === "
|
|
2296
|
-
const count = serviceKey === "
|
|
2461
|
+
const baseServiceName = service.railway?.serviceName ?? `${identity.deploymentKey}-${serviceKey === "operationsRunner" ? "operations-runner" : serviceKey}`;
|
|
2462
|
+
const runnerPool = serviceKey === "operationsRunner" && service.railway?.runnerPool && typeof service.railway.runnerPool === "object" ? service.railway.runnerPool : null;
|
|
2463
|
+
const count = serviceKey === "operationsRunner" ? Math.max(1, Number.parseInt(String(runnerPool?.bootstrapCount ?? 1), 10) || 1) : 1;
|
|
2297
2464
|
for (let index = 1; index <= count; index += 1) {
|
|
2298
|
-
const serviceName = serviceKey === "
|
|
2465
|
+
const serviceName = serviceKey === "operationsRunner" ? `${String(baseServiceName).replace(/-\d+$/u, "").replace(/-\d{2}$/u, "")}-${String(index).padStart(2, "0")}` : baseServiceName;
|
|
2299
2466
|
services.push({
|
|
2300
2467
|
key: serviceKey,
|
|
2301
2468
|
projectName: service.railway?.projectName ?? identity.deploymentKey,
|
|
2302
2469
|
serviceName,
|
|
2303
2470
|
railwayEnvironment: normalizeRailwayEnvironmentName(service.environments?.[normalizedScope]?.railwayEnvironment ?? normalizedScope),
|
|
2304
2471
|
domain: service.environments?.[normalizedScope]?.domain ?? null,
|
|
2305
|
-
volumeMountPath: serviceKey === "
|
|
2472
|
+
volumeMountPath: serviceKey === "operationsRunner" ? service.railway?.volumeMountPath ?? runnerPool?.volumeMountPath ?? "/data" : null
|
|
2306
2473
|
});
|
|
2307
2474
|
}
|
|
2308
2475
|
}
|
|
2309
|
-
const
|
|
2310
|
-
if (
|
|
2311
|
-
const baseName = typeof
|
|
2476
|
+
const treeseedDatabase = deployConfig.services?.treeseedDatabase;
|
|
2477
|
+
if (treeseedDatabase?.enabled !== false && treeseedDatabase?.provider === "railway" && treeseedDatabase?.railway?.resourceType === "postgres") {
|
|
2478
|
+
const baseName = typeof treeseedDatabase.railway?.serviceName === "string" && treeseedDatabase.railway.serviceName.trim() ? treeseedDatabase.railway.serviceName.trim() : `${deployConfig.slug ?? "treeseed-market"}-postgres`;
|
|
2312
2479
|
services.push({
|
|
2313
|
-
key: "
|
|
2480
|
+
key: "treeseedDatabase",
|
|
2314
2481
|
projectName: deployConfig.services?.api?.railway?.projectName ?? identity.deploymentKey,
|
|
2315
2482
|
serviceName: `${baseName.replace(/-(staging|prod|production)$/u, "")}-${normalizedScope === "prod" ? "prod" : normalizedScope}`,
|
|
2316
2483
|
railwayEnvironment: normalizeRailwayEnvironmentName(normalizedScope),
|
|
@@ -2465,6 +2632,87 @@ function killPidFromFile(filePath, { dryRun }) {
|
|
|
2465
2632
|
}
|
|
2466
2633
|
return resourceOperation("local", "dev-process", String(pid), "deleted", { pidFile: filePath });
|
|
2467
2634
|
}
|
|
2635
|
+
const LOCAL_DOCKER_RESOURCE_PATTERN = /(?:^|[-_.])(?:treeseed|treedx|treedb)(?:[-_.]|$)|(?:treeseed|treedx|treedb)/iu;
|
|
2636
|
+
let destroyDockerRunnerForTests = null;
|
|
2637
|
+
function setDestroyDockerRunnerForTests(runner) {
|
|
2638
|
+
destroyDockerRunnerForTests = runner;
|
|
2639
|
+
}
|
|
2640
|
+
function runDestroyDocker(args) {
|
|
2641
|
+
if (destroyDockerRunnerForTests) {
|
|
2642
|
+
return destroyDockerRunnerForTests(args);
|
|
2643
|
+
}
|
|
2644
|
+
return spawnSync("docker", args, { encoding: "utf8", stdio: "pipe" });
|
|
2645
|
+
}
|
|
2646
|
+
function dockerAvailable() {
|
|
2647
|
+
const result = runDestroyDocker(["info"]);
|
|
2648
|
+
return result.status === 0;
|
|
2649
|
+
}
|
|
2650
|
+
function dockerList(formatArgs) {
|
|
2651
|
+
const result = runDestroyDocker(formatArgs);
|
|
2652
|
+
if (result.status !== 0) {
|
|
2653
|
+
return [];
|
|
2654
|
+
}
|
|
2655
|
+
return result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
2656
|
+
}
|
|
2657
|
+
function matchingDockerEntries(lines, parser) {
|
|
2658
|
+
return lines.map(parser).filter((entry) => entry && LOCAL_DOCKER_RESOURCE_PATTERN.test(`${entry.name} ${entry.image ?? ""}`));
|
|
2659
|
+
}
|
|
2660
|
+
function removeDockerResource(kind, id, name) {
|
|
2661
|
+
const args = kind === "container" ? ["rm", "-f", id] : kind === "volume" ? ["volume", "rm", "-f", id] : ["network", "rm", id];
|
|
2662
|
+
const result = runDestroyDocker(args);
|
|
2663
|
+
if (result.status === 0) {
|
|
2664
|
+
return resourceOperation("local", `docker-${kind}`, name, "deleted", { id });
|
|
2665
|
+
}
|
|
2666
|
+
return resourceOperation("local", `docker-${kind}`, name, "blocked", {
|
|
2667
|
+
id,
|
|
2668
|
+
reason: result.stderr?.trim() || result.stdout?.trim() || "docker_remove_failed"
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
function dockerLocalRuntimeResourceOperations({ dryRun = false } = {}) {
|
|
2672
|
+
if (!dockerAvailable()) {
|
|
2673
|
+
return [resourceOperation("local", "docker-cleanup", "docker", "skipped", { reason: "docker_unavailable" })];
|
|
2674
|
+
}
|
|
2675
|
+
const containers = matchingDockerEntries(
|
|
2676
|
+
dockerList(["ps", "-a", "--format", "{{.ID}} {{.Names}} {{.Image}}"]),
|
|
2677
|
+
(line) => {
|
|
2678
|
+
const [id, name, image] = line.split(" ");
|
|
2679
|
+
return id && name ? { id, name, image } : null;
|
|
2680
|
+
}
|
|
2681
|
+
);
|
|
2682
|
+
const volumes = matchingDockerEntries(
|
|
2683
|
+
dockerList(["volume", "ls", "--format", "{{.Name}}"]),
|
|
2684
|
+
(line) => ({ id: line, name: line })
|
|
2685
|
+
);
|
|
2686
|
+
const networks = matchingDockerEntries(
|
|
2687
|
+
dockerList(["network", "ls", "--format", "{{.ID}} {{.Name}}"]),
|
|
2688
|
+
(line) => {
|
|
2689
|
+
const [id, name] = line.split(" ");
|
|
2690
|
+
return id && name ? { id, name } : null;
|
|
2691
|
+
}
|
|
2692
|
+
).filter((entry) => !["bridge", "host", "none"].includes(entry.name));
|
|
2693
|
+
if (dryRun) {
|
|
2694
|
+
return [
|
|
2695
|
+
...containers.map((entry) => resourceOperation("local", "docker-container", entry.name, "planned", { id: entry.id })),
|
|
2696
|
+
...volumes.map((entry) => resourceOperation("local", "docker-volume", entry.name, "planned", { id: entry.id })),
|
|
2697
|
+
...networks.map((entry) => resourceOperation("local", "docker-network", entry.name, "planned", { id: entry.id })),
|
|
2698
|
+
...containers.length || volumes.length || networks.length ? [] : [resourceOperation("local", "docker-cleanup", "docker", "missing", { reason: "no_matching_resources" })]
|
|
2699
|
+
];
|
|
2700
|
+
}
|
|
2701
|
+
const operations = [];
|
|
2702
|
+
for (const entry of containers) {
|
|
2703
|
+
operations.push(removeDockerResource("container", entry.id, entry.name));
|
|
2704
|
+
}
|
|
2705
|
+
for (const entry of volumes) {
|
|
2706
|
+
operations.push(removeDockerResource("volume", entry.id, entry.name));
|
|
2707
|
+
}
|
|
2708
|
+
for (const entry of networks) {
|
|
2709
|
+
operations.push(removeDockerResource("network", entry.id, entry.name));
|
|
2710
|
+
}
|
|
2711
|
+
if (!operations.length) {
|
|
2712
|
+
operations.push(resourceOperation("local", "docker-cleanup", "docker", "missing", { reason: "no_matching_resources" }));
|
|
2713
|
+
}
|
|
2714
|
+
return operations;
|
|
2715
|
+
}
|
|
2468
2716
|
function destroyLocalRuntimeResources(tenantRoot, { dryRun = false, deleteData = false } = {}) {
|
|
2469
2717
|
const operations = [];
|
|
2470
2718
|
const pidDir = resolve(tenantRoot, ".treeseed/dev-pids");
|
|
@@ -2481,7 +2729,7 @@ function destroyLocalRuntimeResources(tenantRoot, { dryRun = false, deleteData =
|
|
|
2481
2729
|
for (const relativePath of [
|
|
2482
2730
|
".treeseed/generated/environments/local",
|
|
2483
2731
|
".treeseed/generated/dev",
|
|
2484
|
-
".treeseed/
|
|
2732
|
+
".treeseed/operations-runner",
|
|
2485
2733
|
".treeseed/local-capacity-provider/data"
|
|
2486
2734
|
]) {
|
|
2487
2735
|
const absolutePath = resolve(tenantRoot, relativePath);
|
|
@@ -2496,11 +2744,250 @@ function destroyLocalRuntimeResources(tenantRoot, { dryRun = false, deleteData =
|
|
|
2496
2744
|
rmSync(absolutePath, { recursive: true, force: true });
|
|
2497
2745
|
operations.push(resourceOperation("local", "data-path", relativePath, "deleted"));
|
|
2498
2746
|
}
|
|
2747
|
+
operations.push(...dockerLocalRuntimeResourceOperations({ dryRun }));
|
|
2499
2748
|
} else {
|
|
2500
2749
|
operations.push(resourceOperation("local", "data-path", ".treeseed/generated/environments/local", "skipped", { reason: "data_preserved" }));
|
|
2501
2750
|
}
|
|
2502
2751
|
return { operations };
|
|
2503
2752
|
}
|
|
2753
|
+
function treeSeedSweepTokens(deployConfig, state) {
|
|
2754
|
+
const configuredHosts = [
|
|
2755
|
+
deployConfig.siteUrl,
|
|
2756
|
+
deployConfig.surfaces?.web?.publicBaseUrl,
|
|
2757
|
+
deployConfig.surfaces?.web?.environments?.staging?.domain,
|
|
2758
|
+
deployConfig.surfaces?.web?.environments?.prod?.domain,
|
|
2759
|
+
deployConfig.surfaces?.api?.environments?.staging?.domain,
|
|
2760
|
+
deployConfig.surfaces?.api?.environments?.prod?.domain,
|
|
2761
|
+
deployConfig.services?.api?.environments?.staging?.domain,
|
|
2762
|
+
deployConfig.services?.api?.environments?.prod?.domain
|
|
2763
|
+
].map((value) => primaryHost(value) ?? value);
|
|
2764
|
+
return [...new Set([
|
|
2765
|
+
"treeseed",
|
|
2766
|
+
deployConfig.slug,
|
|
2767
|
+
deployConfig.name,
|
|
2768
|
+
state.identity?.deploymentKey,
|
|
2769
|
+
state.identity?.environmentKey,
|
|
2770
|
+
state.pages?.projectName,
|
|
2771
|
+
state.workerName,
|
|
2772
|
+
state.content?.bucketName,
|
|
2773
|
+
state.queues?.agentWork?.name,
|
|
2774
|
+
state.queues?.agentWork?.dlqName,
|
|
2775
|
+
state.kvNamespaces?.FORM_GUARD_KV?.name,
|
|
2776
|
+
state.kvNamespaces?.SESSION?.name,
|
|
2777
|
+
state.d1Databases?.SITE_DATA_DB?.databaseName,
|
|
2778
|
+
...configuredHosts
|
|
2779
|
+
].map((value) => String(value ?? "").trim().toLowerCase()).filter((value) => value.length >= 4))];
|
|
2780
|
+
}
|
|
2781
|
+
function isProtectedAiIntegrationResource(value) {
|
|
2782
|
+
return /(?:^|[-_.])(?:ai-gateway|workers-ai|ai-integration|openai|anthropic)(?:[-_.]|$)/iu.test(String(value ?? ""));
|
|
2783
|
+
}
|
|
2784
|
+
function matchesTreeSeedSweep(value, tokens) {
|
|
2785
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
2786
|
+
if (!normalized || isProtectedAiIntegrationResource(normalized)) {
|
|
2787
|
+
return false;
|
|
2788
|
+
}
|
|
2789
|
+
return tokens.some((token) => normalized === token || normalized.includes(token));
|
|
2790
|
+
}
|
|
2791
|
+
function cloudflareNameCandidates(entry) {
|
|
2792
|
+
return [
|
|
2793
|
+
entry?.name,
|
|
2794
|
+
entry?.title,
|
|
2795
|
+
entry?.id,
|
|
2796
|
+
entry?.queue_name,
|
|
2797
|
+
entry?.script,
|
|
2798
|
+
entry?.domain,
|
|
2799
|
+
entry?.hostname,
|
|
2800
|
+
entry?.content,
|
|
2801
|
+
entry?.comment,
|
|
2802
|
+
...Array.isArray(entry?.domains) ? entry.domains : [],
|
|
2803
|
+
...Array.isArray(entry?.tags) ? entry.tags : []
|
|
2804
|
+
].filter(Boolean);
|
|
2805
|
+
}
|
|
2806
|
+
function cloudflareEntryMatchesTreeSeed(entry, tokens) {
|
|
2807
|
+
return cloudflareNameCandidates(entry).some((candidate) => matchesTreeSeedSweep(candidate, tokens));
|
|
2808
|
+
}
|
|
2809
|
+
function deleteDnsRecord(zoneId, record, { env, dryRun }) {
|
|
2810
|
+
const name = record?.name ?? record?.content ?? record?.id ?? null;
|
|
2811
|
+
if (!zoneId || !record?.id) {
|
|
2812
|
+
return resourceOperation("cloudflare", "dns-record", name, "missing", { zoneId });
|
|
2813
|
+
}
|
|
2814
|
+
if (dryRun) {
|
|
2815
|
+
return resourceOperation("cloudflare", "dns-record", name, "planned", {
|
|
2816
|
+
zoneId,
|
|
2817
|
+
id: record.id,
|
|
2818
|
+
content: record.content ?? null,
|
|
2819
|
+
recordType: record.type ?? null
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2822
|
+
return deleteCloudflareApiResource(
|
|
2823
|
+
`/zones/${encodeURIComponent(zoneId)}/dns_records/${encodeURIComponent(record.id)}`,
|
|
2824
|
+
{ env, dryRun: false, name, type: "dns-record" }
|
|
2825
|
+
);
|
|
2826
|
+
}
|
|
2827
|
+
function sweepTreeSeedCloudflareResources(tenantRoot, deployConfig, state, { env, dryRun, deleteData }) {
|
|
2828
|
+
const tokens = treeSeedSweepTokens(deployConfig, state);
|
|
2829
|
+
const operations = [];
|
|
2830
|
+
const pagesProjects = listPagesProjects(tenantRoot, env).filter((entry) => cloudflareEntryMatchesTreeSeed(entry, tokens));
|
|
2831
|
+
for (const project of pagesProjects) {
|
|
2832
|
+
const projectName = project?.name ?? project?.id ?? null;
|
|
2833
|
+
operations.push(...deletePagesCustomDomains(tenantRoot, projectName, [], { env, dryRun, knownOnly: false }));
|
|
2834
|
+
operations.push(deletePagesDeployments(tenantRoot, projectName, { env, dryRun, environment: "all" }));
|
|
2835
|
+
operations.push(deletePagesProject(projectName, { env, dryRun }));
|
|
2836
|
+
}
|
|
2837
|
+
for (const worker of listWorkers(tenantRoot, env).filter((entry) => cloudflareEntryMatchesTreeSeed(entry, tokens))) {
|
|
2838
|
+
const name = worker?.id ?? worker?.name ?? worker?.script ?? null;
|
|
2839
|
+
const deleted = deleteWorker(tenantRoot, name, { env, dryRun, force: true });
|
|
2840
|
+
operations.push(resourceOperation("cloudflare", "worker", name, deleted.status, { ...deleted, sweep: true }));
|
|
2841
|
+
}
|
|
2842
|
+
for (const namespace of listKvNamespaces(tenantRoot, env).filter((entry) => cloudflareEntryMatchesTreeSeed(entry, tokens))) {
|
|
2843
|
+
const deleted = deleteKvNamespace(tenantRoot, namespace.id, { env, dryRun });
|
|
2844
|
+
operations.push(resourceOperation("cloudflare", "kv-namespace", namespace.title ?? namespace.id, deleted.status, { ...deleted, sweep: true }));
|
|
2845
|
+
}
|
|
2846
|
+
for (const queue of listQueues(tenantRoot, env).filter((entry) => cloudflareEntryMatchesTreeSeed(entry, tokens))) {
|
|
2847
|
+
operations.push({ ...deleteQueueByName(tenantRoot, queue, { env, dryRun }), sweep: true });
|
|
2848
|
+
}
|
|
2849
|
+
for (const database of listD1Databases(tenantRoot, env).filter((entry) => cloudflareEntryMatchesTreeSeed(entry, tokens))) {
|
|
2850
|
+
const name = database?.name ?? database?.uuid ?? database?.id ?? null;
|
|
2851
|
+
const deleted = deleteData ? deleteD1Database(tenantRoot, name, { env, dryRun }) : null;
|
|
2852
|
+
operations.push(resourceOperation("cloudflare", "d1-database", name, deleteData ? deleted?.status : "skipped", {
|
|
2853
|
+
...deleteData ? deleted : { reason: "data_preserved" },
|
|
2854
|
+
sweep: true
|
|
2855
|
+
}));
|
|
2856
|
+
}
|
|
2857
|
+
for (const bucket of listR2Buckets(tenantRoot, env).filter((entry) => cloudflareEntryMatchesTreeSeed(entry, tokens))) {
|
|
2858
|
+
operations.push({ ...deleteR2Bucket(tenantRoot, bucket.name, { env, dryRun, deleteData }), sweep: true });
|
|
2859
|
+
}
|
|
2860
|
+
for (const widget of listTurnstileWidgets(tenantRoot, env).filter((entry) => cloudflareEntryMatchesTreeSeed(entry, tokens))) {
|
|
2861
|
+
const deleted = deleteTurnstileWidget(widget.sitekey, { env, dryRun, name: widget.name });
|
|
2862
|
+
operations.push(resourceOperation("cloudflare", "turnstile-widget", widget.name ?? widget.sitekey, deleted.status, { ...deleted, sweep: true }));
|
|
2863
|
+
}
|
|
2864
|
+
for (const zone of listDnsZones(env)) {
|
|
2865
|
+
const zoneId = zone?.id ?? null;
|
|
2866
|
+
for (const record of listDnsRecords(zoneId, env)) {
|
|
2867
|
+
if (record?.type === "SOA" || record?.type === "NS") {
|
|
2868
|
+
continue;
|
|
2869
|
+
}
|
|
2870
|
+
if (!cloudflareEntryMatchesTreeSeed(record, tokens)) {
|
|
2871
|
+
continue;
|
|
2872
|
+
}
|
|
2873
|
+
operations.push({ ...deleteDnsRecord(zoneId, record, { env, dryRun }), zoneName: zone?.name ?? null, sweep: true });
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
return operations.length > 0 ? operations : [resourceOperation("cloudflare", "treeseed-sweep", "cloudflare", "missing", { reason: "no_matching_resources" })];
|
|
2877
|
+
}
|
|
2878
|
+
function countMatchingCloudflareEntries(entries, tokens) {
|
|
2879
|
+
return entries.filter((entry) => cloudflareEntryMatchesTreeSeed(entry, tokens)).length;
|
|
2880
|
+
}
|
|
2881
|
+
function cloudflareDestroyVerification(tenantRoot, deployConfig, state, env) {
|
|
2882
|
+
const tokens = treeSeedSweepTokens(deployConfig, state);
|
|
2883
|
+
const zoneIds = /* @__PURE__ */ new Set([
|
|
2884
|
+
deployConfig.cloudflare?.zoneId,
|
|
2885
|
+
state.webCache?.webZoneId,
|
|
2886
|
+
state.webCache?.contentZoneId
|
|
2887
|
+
]);
|
|
2888
|
+
for (const zone of listDnsZones(env)) {
|
|
2889
|
+
if (zone?.id) {
|
|
2890
|
+
zoneIds.add(zone.id);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
const dnsRecords = [];
|
|
2894
|
+
for (const zoneId of [...zoneIds].filter(Boolean)) {
|
|
2895
|
+
dnsRecords.push(...listDnsRecords(zoneId, env));
|
|
2896
|
+
}
|
|
2897
|
+
const remaining = {
|
|
2898
|
+
pages: countMatchingCloudflareEntries(listPagesProjects(tenantRoot, env), tokens),
|
|
2899
|
+
workers: countMatchingCloudflareEntries(listWorkers(tenantRoot, env), tokens),
|
|
2900
|
+
kvNamespaces: countMatchingCloudflareEntries(listKvNamespaces(tenantRoot, env), tokens),
|
|
2901
|
+
queues: countMatchingCloudflareEntries(listQueues(tenantRoot, env), tokens),
|
|
2902
|
+
d1Databases: countMatchingCloudflareEntries(listD1Databases(tenantRoot, env), tokens),
|
|
2903
|
+
r2Buckets: countMatchingCloudflareEntries(listR2Buckets(tenantRoot, env), tokens),
|
|
2904
|
+
turnstileWidgets: countMatchingCloudflareEntries(listTurnstileWidgets(tenantRoot, env), tokens),
|
|
2905
|
+
dnsRecords: countMatchingCloudflareEntries(
|
|
2906
|
+
dnsRecords.filter((record) => record?.type !== "SOA" && record?.type !== "NS"),
|
|
2907
|
+
tokens
|
|
2908
|
+
)
|
|
2909
|
+
};
|
|
2910
|
+
const totalRemaining = Object.values(remaining).reduce((sum, value) => sum + value, 0);
|
|
2911
|
+
return {
|
|
2912
|
+
provider: "cloudflare",
|
|
2913
|
+
method: "cloudflare-api",
|
|
2914
|
+
status: totalRemaining === 0 ? "clean" : "remaining",
|
|
2915
|
+
remaining,
|
|
2916
|
+
totalRemaining
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
function localDockerDestroyVerification() {
|
|
2920
|
+
if (!dockerAvailable()) {
|
|
2921
|
+
return {
|
|
2922
|
+
provider: "local-docker",
|
|
2923
|
+
method: "docker-cli",
|
|
2924
|
+
status: "skipped",
|
|
2925
|
+
reason: "docker_unavailable",
|
|
2926
|
+
remaining: {
|
|
2927
|
+
containers: 0,
|
|
2928
|
+
volumes: 0,
|
|
2929
|
+
networks: 0
|
|
2930
|
+
},
|
|
2931
|
+
totalRemaining: 0
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
const containers = matchingDockerEntries(
|
|
2935
|
+
dockerList(["ps", "-a", "--format", "{{.ID}} {{.Names}} {{.Image}}"]),
|
|
2936
|
+
(line) => {
|
|
2937
|
+
const [id, name, image] = line.split(" ");
|
|
2938
|
+
return id && name ? { id, name, image } : null;
|
|
2939
|
+
}
|
|
2940
|
+
).length;
|
|
2941
|
+
const volumes = matchingDockerEntries(
|
|
2942
|
+
dockerList(["volume", "ls", "--format", "{{.Name}}"]),
|
|
2943
|
+
(line) => ({ id: line, name: line })
|
|
2944
|
+
).length;
|
|
2945
|
+
const networks = matchingDockerEntries(
|
|
2946
|
+
dockerList(["network", "ls", "--format", "{{.ID}} {{.Name}}"]),
|
|
2947
|
+
(line) => {
|
|
2948
|
+
const [id, name] = line.split(" ");
|
|
2949
|
+
return id && name ? { id, name } : null;
|
|
2950
|
+
}
|
|
2951
|
+
).filter((entry) => !["bridge", "host", "none"].includes(entry.name)).length;
|
|
2952
|
+
const remaining = { containers, volumes, networks };
|
|
2953
|
+
const totalRemaining = containers + volumes + networks;
|
|
2954
|
+
return {
|
|
2955
|
+
provider: "local-docker",
|
|
2956
|
+
method: "docker-cli",
|
|
2957
|
+
status: totalRemaining === 0 ? "clean" : "remaining",
|
|
2958
|
+
remaining,
|
|
2959
|
+
totalRemaining
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
async function sweepTreeSeedRailwayResources(deployConfig, state, { env, dryRun }) {
|
|
2963
|
+
if (!resolveRailwayApiToken(env)) {
|
|
2964
|
+
return [resourceOperation("railway", "treeseed-sweep", "railway", "blocked", { reason: "missing_railway_api_token" })];
|
|
2965
|
+
}
|
|
2966
|
+
const tokens = treeSeedSweepTokens(deployConfig, state);
|
|
2967
|
+
const workspace = await resolveRailwayWorkspaceContext({ env, workspace: resolveRailwayWorkspace(env) });
|
|
2968
|
+
const projects = await listRailwayProjects({ env, workspaceId: workspace.id });
|
|
2969
|
+
const operations = [];
|
|
2970
|
+
for (const project of projects) {
|
|
2971
|
+
if (project.deletedAt || !matchesTreeSeedSweep(project.name, tokens)) {
|
|
2972
|
+
continue;
|
|
2973
|
+
}
|
|
2974
|
+
if (dryRun) {
|
|
2975
|
+
operations.push(resourceOperation("railway", "project", project.name, "planned", {
|
|
2976
|
+
id: project.id,
|
|
2977
|
+
workspaceId: workspace.id,
|
|
2978
|
+
sweep: true
|
|
2979
|
+
}));
|
|
2980
|
+
} else {
|
|
2981
|
+
const deleted = await deleteRailwayProject({ projectId: project.id, env });
|
|
2982
|
+
operations.push(resourceOperation("railway", "project", project.name, deleted.status, {
|
|
2983
|
+
id: project.id,
|
|
2984
|
+
workspaceId: workspace.id,
|
|
2985
|
+
sweep: true
|
|
2986
|
+
}));
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
return operations.length > 0 ? operations : [resourceOperation("railway", "treeseed-sweep", "railway", "missing", { reason: "no_matching_projects" })];
|
|
2990
|
+
}
|
|
2504
2991
|
async function destroyTreeseedEnvironmentResources(tenantRoot, options = {}) {
|
|
2505
2992
|
const target = normalizeTarget(options.scope ?? options.target ?? "prod");
|
|
2506
2993
|
const deployConfig = loadTenantDeployConfig(tenantRoot);
|
|
@@ -2512,6 +2999,8 @@ async function destroyTreeseedEnvironmentResources(tenantRoot, options = {}) {
|
|
|
2512
2999
|
const dryRun = options.dryRun ?? false;
|
|
2513
3000
|
const deleteData = options.deleteData === true;
|
|
2514
3001
|
const force = options.force ?? false;
|
|
3002
|
+
const sweepTreeseed = options.sweepTreeseed === true;
|
|
3003
|
+
const destroysSharedWebSurface = target.kind === "persistent" && target.scope === "prod" && deleteData;
|
|
2515
3004
|
const kvNamespaces = dryRun ? [] : listKvNamespaces(tenantRoot, env);
|
|
2516
3005
|
const d1Databases = dryRun ? [] : listD1Databases(tenantRoot, env);
|
|
2517
3006
|
const queues = dryRun ? [] : listQueues(tenantRoot, env);
|
|
@@ -2585,11 +3074,21 @@ async function destroyTreeseedEnvironmentResources(tenantRoot, options = {}) {
|
|
|
2585
3074
|
].filter(Boolean);
|
|
2586
3075
|
const dnsRecords = [.../* @__PURE__ */ new Set([...pageDnsNames, ...apiDnsNames])].flatMap((name) => deleteDnsRecordsForName(deployConfig, name, { env, dryRun }));
|
|
2587
3076
|
const cacheRules = deleteTreeseedCacheRules(deployConfig, state, { env, dryRun });
|
|
2588
|
-
const pageCustomDomains = pagesProject || dryRun ? deletePagesCustomDomains(tenantRoot, state.pages?.projectName, pageDnsNames, { env, dryRun }) : [resourceOperation("cloudflare", "pages-custom-domain", state.pages?.projectName, "missing")];
|
|
2589
|
-
const pageDeployments = pagesProject || dryRun ? deletePagesDeployments(tenantRoot, state.pages?.projectName, {
|
|
2590
|
-
|
|
3077
|
+
const pageCustomDomains = pagesProject || dryRun ? deletePagesCustomDomains(tenantRoot, state.pages?.projectName, pageDnsNames, { env, dryRun, knownOnly: !destroysSharedWebSurface }) : [resourceOperation("cloudflare", "pages-custom-domain", state.pages?.projectName, "missing")];
|
|
3078
|
+
const pageDeployments = pagesProject || dryRun ? deletePagesDeployments(tenantRoot, state.pages?.projectName, {
|
|
3079
|
+
env,
|
|
3080
|
+
dryRun,
|
|
3081
|
+
environment: destroysSharedWebSurface ? "all" : "preview"
|
|
3082
|
+
}) : resourceOperation("cloudflare", "pages-deployments", state.pages?.projectName, "missing");
|
|
3083
|
+
const pages = destroysSharedWebSurface && (pagesProject || dryRun) ? deletePagesProject(state.pages?.projectName, { env, dryRun }) : resourceOperation("cloudflare", "pages-project", state.pages?.projectName, "skipped", {
|
|
3084
|
+
reason: target.scope === "prod" ? "delete_data_required" : "shared_web_surface"
|
|
3085
|
+
});
|
|
2591
3086
|
const local = target.kind === "persistent" && target.scope === "local" ? destroyLocalRuntimeResources(tenantRoot, { dryRun, deleteData }) : { operations: [] };
|
|
2592
3087
|
const railway = await destroyRailwayResources(tenantRoot, deployConfig, target, { dryRun, deleteData, env: process.env });
|
|
3088
|
+
const sweep = sweepTreeseed ? {
|
|
3089
|
+
cloudflare: sweepTreeSeedCloudflareResources(tenantRoot, deployConfig, state, { env, dryRun, deleteData }),
|
|
3090
|
+
railway: await sweepTreeSeedRailwayResources(deployConfig, state, { env: process.env, dryRun })
|
|
3091
|
+
} : { cloudflare: [], railway: [] };
|
|
2593
3092
|
const operations = {
|
|
2594
3093
|
cloudflare: [
|
|
2595
3094
|
resourceOperation("cloudflare", "worker", state.workerName, workerResult.status, workerResult),
|
|
@@ -2608,16 +3107,26 @@ async function destroyTreeseedEnvironmentResources(tenantRoot, options = {}) {
|
|
|
2608
3107
|
pageDeployments,
|
|
2609
3108
|
pages,
|
|
2610
3109
|
...dnsRecords,
|
|
2611
|
-
...cacheRules
|
|
3110
|
+
...cacheRules,
|
|
3111
|
+
...sweep.cloudflare
|
|
3112
|
+
],
|
|
3113
|
+
railway: [
|
|
3114
|
+
...railway.operations,
|
|
3115
|
+
...sweep.railway
|
|
2612
3116
|
],
|
|
2613
|
-
railway: railway.operations,
|
|
2614
3117
|
local: local.operations
|
|
2615
3118
|
};
|
|
3119
|
+
const verification = dryRun ? null : {
|
|
3120
|
+
cloudflare: cloudflareDestroyVerification(tenantRoot, deployConfig, state, env),
|
|
3121
|
+
...target.kind === "persistent" && target.scope === "local" ? { localDocker: localDockerDestroyVerification() } : {}
|
|
3122
|
+
};
|
|
2616
3123
|
return {
|
|
2617
3124
|
target,
|
|
2618
3125
|
deleteData,
|
|
3126
|
+
sweepTreeseed,
|
|
2619
3127
|
summary: buildDestroySummary(deployConfig, state, target),
|
|
2620
|
-
operations
|
|
3128
|
+
operations,
|
|
3129
|
+
verification
|
|
2621
3130
|
};
|
|
2622
3131
|
}
|
|
2623
3132
|
function destroyCloudflareResources(tenantRoot, options = {}) {
|
|
@@ -3243,6 +3752,7 @@ export {
|
|
|
3243
3752
|
buildWranglerConfigContents,
|
|
3244
3753
|
cleanupDestroyedState,
|
|
3245
3754
|
cloudflareApiRequest,
|
|
3755
|
+
cloudflareDestroyVerification,
|
|
3246
3756
|
collectMissingDeployInputs,
|
|
3247
3757
|
createBranchPreviewDeployTarget,
|
|
3248
3758
|
createPersistentDeployTarget,
|
|
@@ -3251,17 +3761,21 @@ export {
|
|
|
3251
3761
|
deriveTreeseedStagingSurfaceDomain,
|
|
3252
3762
|
destroyCloudflareResources,
|
|
3253
3763
|
destroyTreeseedEnvironmentResources,
|
|
3764
|
+
dockerLocalRuntimeResourceOperations,
|
|
3254
3765
|
ensureGeneratedWranglerConfig,
|
|
3255
3766
|
finalizeDeploymentState,
|
|
3256
3767
|
getTurnstileWidget,
|
|
3257
3768
|
hasProvisionedCloudflareResources,
|
|
3258
3769
|
isWranglerAlreadyExistsError,
|
|
3259
3770
|
listD1Databases,
|
|
3771
|
+
listDnsRecords,
|
|
3772
|
+
listDnsZones,
|
|
3260
3773
|
listKvNamespaces,
|
|
3261
3774
|
listPagesProjects,
|
|
3262
3775
|
listQueues,
|
|
3263
3776
|
listR2Buckets,
|
|
3264
3777
|
listTurnstileWidgets,
|
|
3778
|
+
listWorkers,
|
|
3265
3779
|
loadDeployState,
|
|
3266
3780
|
markDeploymentInitialized,
|
|
3267
3781
|
markManagedServicesInitialized,
|
|
@@ -3286,6 +3800,7 @@ export {
|
|
|
3286
3800
|
runRemoteD1Migrations,
|
|
3287
3801
|
runWrangler,
|
|
3288
3802
|
scopeFromTarget,
|
|
3803
|
+
setDestroyDockerRunnerForTests,
|
|
3289
3804
|
shouldDeleteRailwayProjectAfterEnvironmentDestroy,
|
|
3290
3805
|
syncCloudflareSecrets,
|
|
3291
3806
|
updateTurnstileWidget,
|