@treeseed/sdk 0.10.15 → 0.10.17
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/dist/api/config.js +10 -3
- package/dist/operations/services/config-runtime.js +6 -1
- package/dist/operations/services/deploy.d.ts +107 -27
- package/dist/operations/services/deploy.js +852 -28
- package/dist/operations/services/project-platform.d.ts +4 -2
- package/dist/operations/services/project-platform.js +56 -27
- package/dist/operations/services/railway-api.d.ts +20 -0
- package/dist/operations/services/railway-api.js +153 -11
- package/dist/operations/services/railway-deploy.d.ts +7 -4
- package/dist/operations/services/railway-deploy.js +12 -4
- package/dist/platform/environment.js +3 -0
- package/dist/reconcile/builtin-adapters.js +34 -20
- package/dist/scripts/tenant-destroy.js +8 -2
- package/dist/workflow/operations.d.ts +92 -0
- package/dist/workflow/operations.js +11 -3
- package/dist/workflow.d.ts +1 -0
- package/package.json +1 -1
- package/templates/github/deploy-web.workflow.yml +8 -1
|
@@ -3,6 +3,7 @@ import type { TreeseedRunnableBootstrapSystem } from '../../reconcile/index.ts';
|
|
|
3
3
|
import { type TreeseedBootstrapExecution, type TreeseedBootstrapWriter } from './bootstrap-runner.ts';
|
|
4
4
|
export type ProjectPlatformScope = 'local' | 'staging' | 'prod';
|
|
5
5
|
export type ProjectPlatformAction = 'deploy_web' | 'publish_content' | 'monitor';
|
|
6
|
+
type ProjectPlatformContentPublishMode = 'production' | 'editorial_overlay';
|
|
6
7
|
export interface ProjectPlatformActionOptions {
|
|
7
8
|
tenantRoot: string;
|
|
8
9
|
scope: ProjectPlatformScope;
|
|
@@ -292,7 +293,7 @@ export declare function resolveRailwayServiceDeployDependencies({ includeDataDep
|
|
|
292
293
|
export declare function publishProjectContent(options: ProjectPlatformActionOptions): Promise<{
|
|
293
294
|
ok: boolean;
|
|
294
295
|
scope: ProjectPlatformScope;
|
|
295
|
-
mode:
|
|
296
|
+
mode: ProjectPlatformContentPublishMode;
|
|
296
297
|
revision: string;
|
|
297
298
|
previewId: string | null;
|
|
298
299
|
previewUrl: string | null;
|
|
@@ -454,7 +455,7 @@ export declare function syncControlPlaneState(options: ProjectPlatformActionOpti
|
|
|
454
455
|
export declare function runProjectPlatformAction(action: ProjectPlatformAction, options: ProjectPlatformActionOptions): Promise<{
|
|
455
456
|
ok: boolean;
|
|
456
457
|
scope: ProjectPlatformScope;
|
|
457
|
-
mode:
|
|
458
|
+
mode: ProjectPlatformContentPublishMode;
|
|
458
459
|
revision: string;
|
|
459
460
|
previewId: string | null;
|
|
460
461
|
previewUrl: string | null;
|
|
@@ -809,3 +810,4 @@ export declare function runProjectPlatformAction(action: ProjectPlatformAction,
|
|
|
809
810
|
} | null;
|
|
810
811
|
} | undefined)[];
|
|
811
812
|
}>;
|
|
813
|
+
export {};
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
purgePublishedContentCaches,
|
|
28
28
|
resolveConfiguredCloudflareAccountId,
|
|
29
29
|
resolveConfiguredSurfaceBaseUrl,
|
|
30
|
+
resolveTreeseedResourceIdentity,
|
|
30
31
|
syncCloudflareSecrets,
|
|
31
32
|
writeDeployState
|
|
32
33
|
} from "./deploy.js";
|
|
@@ -220,7 +221,8 @@ function prepareTenantCloudflareDeploy({
|
|
|
220
221
|
env: {
|
|
221
222
|
...process.env,
|
|
222
223
|
...env,
|
|
223
|
-
CLOUDFLARE_ACCOUNT_ID: resolveConfiguredCloudflareAccountId(deployConfig)
|
|
224
|
+
CLOUDFLARE_ACCOUNT_ID: resolveConfiguredCloudflareAccountId(deployConfig),
|
|
225
|
+
...target.kind === "persistent" && target.scope !== "local" ? { TREESEED_CONTENT_SERVING_MODE: "published_runtime" } : {}
|
|
224
226
|
},
|
|
225
227
|
write
|
|
226
228
|
};
|
|
@@ -464,8 +466,8 @@ function resolveReporter(tenantRoot, explicit) {
|
|
|
464
466
|
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
465
467
|
return createControlPlaneReporter({ deployConfig });
|
|
466
468
|
}
|
|
467
|
-
function uploadObject(tenantRoot, wranglerPath, wranglerEnv, bucketName, pointer, filePath) {
|
|
468
|
-
|
|
469
|
+
async function uploadObject(tenantRoot, wranglerPath, wranglerEnv, bucketName, pointer, filePath, options) {
|
|
470
|
+
await runPrefixedWranglerWithRetry(tenantRoot, [
|
|
469
471
|
"r2",
|
|
470
472
|
"object",
|
|
471
473
|
"put",
|
|
@@ -478,7 +480,11 @@ function uploadObject(tenantRoot, wranglerPath, wranglerEnv, bucketName, pointer
|
|
|
478
480
|
filePath,
|
|
479
481
|
"--content-type",
|
|
480
482
|
pointer.contentType ?? inferContentType(filePath)
|
|
481
|
-
],
|
|
483
|
+
], {
|
|
484
|
+
env: wranglerEnv,
|
|
485
|
+
write: options.write,
|
|
486
|
+
prefix: options.prefix
|
|
487
|
+
});
|
|
482
488
|
}
|
|
483
489
|
function deleteObject(tenantRoot, wranglerPath, wranglerEnv, bucketName, objectKey) {
|
|
484
490
|
runWrangler(tenantRoot, [
|
|
@@ -794,11 +800,11 @@ function probeScaleConfiguration(siteConfig, state) {
|
|
|
794
800
|
serviceName: worker.serviceName ?? null
|
|
795
801
|
};
|
|
796
802
|
}
|
|
797
|
-
async function publishContent(options, reporter) {
|
|
803
|
+
async function publishContent(options, reporter, publishOptions = {}) {
|
|
798
804
|
const target = runTenantPublishContentPreflight(options);
|
|
799
805
|
const siteConfig = loadCliDeployConfig(options.tenantRoot);
|
|
800
806
|
const tenantConfig = loadTreeseedManifest(resolve(options.tenantRoot, "src", "manifest.yaml"));
|
|
801
|
-
const teamId =
|
|
807
|
+
const teamId = resolveTreeseedResourceIdentity(siteConfig, target).teamId;
|
|
802
808
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
803
809
|
const commitSha = currentCommit(options.tenantRoot);
|
|
804
810
|
const branchName = currentRef(options.tenantRoot);
|
|
@@ -827,7 +833,8 @@ async function publishContent(options, reporter) {
|
|
|
827
833
|
sourceRef: branchName,
|
|
828
834
|
previewId
|
|
829
835
|
});
|
|
830
|
-
const
|
|
836
|
+
const publishMode = publishOptions.mode ?? (options.scope === "staging" ? "editorial_overlay" : "production");
|
|
837
|
+
const built = publishMode === "editorial_overlay" ? await pipeline.buildEditorialOverlay({ previousManifest, previewId }) : await pipeline.buildProductionRevision({ previousManifest });
|
|
831
838
|
const changedEntrySet = "manifest" in built ? changedEntries(previousManifest, built.manifest.entries) : [];
|
|
832
839
|
const changedArtifactSet = "manifest" in built ? changedArtifacts(previousManifest, built.manifest.artifacts ?? []) : [];
|
|
833
840
|
const tombstones = "manifest" in built ? built.manifest.tombstones ?? [] : [];
|
|
@@ -910,20 +917,29 @@ async function publishContent(options, reporter) {
|
|
|
910
917
|
const tempRoot = mkdtempSync(join(tmpdir(), "treeseed-content-publish-"));
|
|
911
918
|
try {
|
|
912
919
|
if (!options.dryRun) {
|
|
920
|
+
const uploadOptions = {
|
|
921
|
+
write: options.write,
|
|
922
|
+
prefix: {
|
|
923
|
+
scope: options.scope,
|
|
924
|
+
system: "content",
|
|
925
|
+
task: "publish",
|
|
926
|
+
stage: "upload"
|
|
927
|
+
}
|
|
928
|
+
};
|
|
913
929
|
for (const object of built.objects) {
|
|
914
930
|
const filePath = writeTempFile(tempRoot, objectFileName(object.pointer), toBuffer(object.body));
|
|
915
|
-
uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, object.pointer, filePath);
|
|
931
|
+
await uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, object.pointer, filePath, uploadOptions);
|
|
916
932
|
}
|
|
917
933
|
for (const alias of stableObjectUploads) {
|
|
918
934
|
const filePath = writeTempFile(tempRoot, objectFileName(alias.pointer), alias.body);
|
|
919
|
-
uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, alias.pointer, filePath);
|
|
935
|
+
await uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, alias.pointer, filePath, uploadOptions);
|
|
920
936
|
}
|
|
921
937
|
for (const objectKey of deletedObjectKeys) {
|
|
922
938
|
deleteObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, objectKey);
|
|
923
939
|
}
|
|
924
940
|
if ("overlay" in built) {
|
|
925
941
|
const overlayFile = writeTempFile(tempRoot, "overlay.json", Buffer.from(JSON.stringify(built.overlay, null, 2)));
|
|
926
|
-
uploadObject(
|
|
942
|
+
await uploadObject(
|
|
927
943
|
options.tenantRoot,
|
|
928
944
|
wranglerPath,
|
|
929
945
|
wranglerEnv,
|
|
@@ -934,23 +950,24 @@ async function publishContent(options, reporter) {
|
|
|
934
950
|
size: statSync(overlayFile).size,
|
|
935
951
|
contentType: "application/json"
|
|
936
952
|
},
|
|
937
|
-
overlayFile
|
|
953
|
+
overlayFile,
|
|
954
|
+
uploadOptions
|
|
938
955
|
);
|
|
939
956
|
} else {
|
|
940
957
|
const manifestFile = writeTempFile(tempRoot, "manifest.json", Buffer.from(JSON.stringify(built.manifest, null, 2)));
|
|
941
958
|
const snapshotKey = locator.manifestKey.replace(/\/common\.json$/u, `/manifests/${built.manifest.revision}.json`);
|
|
942
|
-
uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, {
|
|
959
|
+
await uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, {
|
|
943
960
|
objectKey: snapshotKey,
|
|
944
961
|
sha256: stableHash(readFileSync(manifestFile)),
|
|
945
962
|
size: statSync(manifestFile).size,
|
|
946
963
|
contentType: "application/json"
|
|
947
|
-
}, manifestFile);
|
|
948
|
-
uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, {
|
|
964
|
+
}, manifestFile, uploadOptions);
|
|
965
|
+
await uploadObject(options.tenantRoot, wranglerPath, wranglerEnv, bucketName, {
|
|
949
966
|
objectKey: locator.manifestKey,
|
|
950
967
|
sha256: stableHash(readFileSync(manifestFile)),
|
|
951
968
|
size: statSync(manifestFile).size,
|
|
952
969
|
contentType: "application/json"
|
|
953
|
-
}, manifestFile);
|
|
970
|
+
}, manifestFile, uploadOptions);
|
|
954
971
|
if (contentPurgeUrls.size > 0) {
|
|
955
972
|
try {
|
|
956
973
|
purgePublishedContentCaches(options.tenantRoot, [...contentPurgeUrls].filter(Boolean), { target });
|
|
@@ -960,12 +977,14 @@ async function publishContent(options, reporter) {
|
|
|
960
977
|
}
|
|
961
978
|
}
|
|
962
979
|
const state = loadDeployState(options.tenantRoot, siteConfig, { target });
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
980
|
+
if (!options.dryRun) {
|
|
981
|
+
state.content.lastPublishedManifestRevision = "overlay" in built ? built.overlay.previewId : built.manifest.revision;
|
|
982
|
+
state.content.lastPublishedManifestSha256 = stableHash(
|
|
983
|
+
JSON.stringify("overlay" in built ? built.overlay : built.manifest)
|
|
984
|
+
);
|
|
985
|
+
writeDeployState(options.tenantRoot, state, { target });
|
|
986
|
+
}
|
|
987
|
+
const previewToken = publishMode === "editorial_overlay" && process.env.TREESEED_EDITORIAL_PREVIEW_SECRET ? signEditorialPreviewToken({
|
|
969
988
|
teamId,
|
|
970
989
|
previewId,
|
|
971
990
|
expiresAt: "overlay" in built ? built.overlay.expiresAt ?? new Date(Date.now() + resolvePublishedContentPreviewTtlHours(siteConfig) * 60 * 60 * 1e3).toISOString() : new Date(Date.now() + resolvePublishedContentPreviewTtlHours(siteConfig) * 60 * 60 * 1e3).toISOString()
|
|
@@ -980,9 +999,9 @@ async function publishContent(options, reporter) {
|
|
|
980
999
|
commitSha,
|
|
981
1000
|
triggeredByType: "project_runner",
|
|
982
1001
|
metadata: {
|
|
983
|
-
mode:
|
|
1002
|
+
mode: publishMode,
|
|
984
1003
|
revision: "overlay" in built ? built.overlay.previewId : built.manifest.revision,
|
|
985
|
-
previewId:
|
|
1004
|
+
previewId: publishMode === "editorial_overlay" ? previewId : null,
|
|
986
1005
|
previewUrl,
|
|
987
1006
|
entries: ("overlay" in built ? built.overlay.entries : built.manifest.entries).length,
|
|
988
1007
|
artifacts: ("overlay" in built ? built.overlay.artifacts : built.manifest.artifacts)?.length ?? 0,
|
|
@@ -994,9 +1013,9 @@ async function publishContent(options, reporter) {
|
|
|
994
1013
|
return {
|
|
995
1014
|
ok: true,
|
|
996
1015
|
scope: options.scope,
|
|
997
|
-
mode:
|
|
1016
|
+
mode: publishMode,
|
|
998
1017
|
revision: "overlay" in built ? built.overlay.previewId : built.manifest.revision,
|
|
999
|
-
previewId:
|
|
1018
|
+
previewId: publishMode === "editorial_overlay" ? previewId : null,
|
|
1000
1019
|
previewUrl,
|
|
1001
1020
|
target: deployTargetLabel(target)
|
|
1002
1021
|
};
|
|
@@ -1214,13 +1233,23 @@ async function deployProjectPlatform(options) {
|
|
|
1214
1233
|
}
|
|
1215
1234
|
if (cloudflareContext && selectedSystems.has("web")) {
|
|
1216
1235
|
const context = cloudflareContext;
|
|
1236
|
+
const contentNodeId = "content:publish-runtime";
|
|
1237
|
+
nodes.push({
|
|
1238
|
+
id: contentNodeId,
|
|
1239
|
+
dependencies: selectedSystems.has("data") ? ["data:d1-migrate"] : [],
|
|
1240
|
+
run: () => publishContent({
|
|
1241
|
+
...options,
|
|
1242
|
+
reporter,
|
|
1243
|
+
bootstrapSystems: ["web"]
|
|
1244
|
+
}, reporter, { mode: "production" })
|
|
1245
|
+
});
|
|
1217
1246
|
nodes.push({
|
|
1218
1247
|
id: "web:build",
|
|
1219
1248
|
run: () => runTenantWebBuild(context)
|
|
1220
1249
|
});
|
|
1221
1250
|
nodes.push({
|
|
1222
1251
|
id: "web:publish",
|
|
1223
|
-
dependencies: ["web:build", ...selectedSystems.has("data") ? ["data:d1-migrate"] : []],
|
|
1252
|
+
dependencies: ["web:build", contentNodeId, ...selectedSystems.has("data") ? ["data:d1-migrate"] : []],
|
|
1224
1253
|
run: () => runTenantWebPublish(context)
|
|
1225
1254
|
});
|
|
1226
1255
|
}
|
|
@@ -1452,7 +1481,7 @@ async function runProjectPlatformAction(action, options) {
|
|
|
1452
1481
|
const previousWorkflowAction = process.env.TREESEED_WORKFLOW_ACTION;
|
|
1453
1482
|
const previousWorkflowPlane = process.env.TREESEED_WORKFLOW_PLANE;
|
|
1454
1483
|
process.env.TREESEED_WORKFLOW_ACTION = action;
|
|
1455
|
-
process.env.TREESEED_WORKFLOW_PLANE = previousWorkflowPlane ?? "
|
|
1484
|
+
process.env.TREESEED_WORKFLOW_PLANE = previousWorkflowPlane ?? "all";
|
|
1456
1485
|
applyTreeseedEnvironmentToProcess({ tenantRoot: options.tenantRoot, scope: options.scope, override: true });
|
|
1457
1486
|
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
1458
1487
|
try {
|
|
@@ -316,3 +316,23 @@ export declare function ensureRailwayCustomDomain({ projectId, environmentId, se
|
|
|
316
316
|
domain: RailwayCustomDomainSummary;
|
|
317
317
|
created: boolean;
|
|
318
318
|
}>;
|
|
319
|
+
export declare function deleteRailwayCustomDomain({ domainId, env, fetchImpl, }: {
|
|
320
|
+
domainId: string;
|
|
321
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
322
|
+
fetchImpl?: typeof fetch;
|
|
323
|
+
}): Promise<Record<string, unknown>>;
|
|
324
|
+
export declare function deleteRailwayVolume({ volumeId, env, fetchImpl, }: {
|
|
325
|
+
volumeId: string;
|
|
326
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
327
|
+
fetchImpl?: typeof fetch;
|
|
328
|
+
}): Promise<Record<string, unknown>>;
|
|
329
|
+
export declare function deleteRailwayEnvironment({ environmentId, env, fetchImpl, }: {
|
|
330
|
+
environmentId: string;
|
|
331
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
332
|
+
fetchImpl?: typeof fetch;
|
|
333
|
+
}): Promise<Record<string, unknown>>;
|
|
334
|
+
export declare function deleteRailwayProject({ projectId, env, fetchImpl, }: {
|
|
335
|
+
projectId: string;
|
|
336
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
337
|
+
fetchImpl?: typeof fetch;
|
|
338
|
+
}): Promise<Record<string, unknown>>;
|
|
@@ -1195,11 +1195,13 @@ async function ensureRailwayServiceVolume({
|
|
|
1195
1195
|
})).filter((candidate) => candidate.instances.length > 0);
|
|
1196
1196
|
let volume = activeVolumes.find(
|
|
1197
1197
|
(candidate) => candidate.instances.some((instance2) => instance2.serviceId === serviceId && instance2.environmentId === environmentId)
|
|
1198
|
-
) ?? activeVolumes.find(
|
|
1198
|
+
) ?? activeVolumes.find(
|
|
1199
|
+
(candidate) => candidate.name === name && candidate.instances.some((instance2) => instance2.environmentId === environmentId)
|
|
1200
|
+
) ?? null;
|
|
1199
1201
|
let created = false;
|
|
1200
1202
|
let updated = false;
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
+
const createReplacementVolume = async () => {
|
|
1204
|
+
const replacement = await createRailwayVolume({
|
|
1203
1205
|
projectId,
|
|
1204
1206
|
environmentId,
|
|
1205
1207
|
serviceId,
|
|
@@ -1209,24 +1211,50 @@ async function ensureRailwayServiceVolume({
|
|
|
1209
1211
|
fetchImpl
|
|
1210
1212
|
});
|
|
1211
1213
|
created = true;
|
|
1214
|
+
return replacement;
|
|
1215
|
+
};
|
|
1216
|
+
if (!volume) {
|
|
1217
|
+
volume = await createReplacementVolume();
|
|
1212
1218
|
}
|
|
1213
1219
|
if (volume.name && volume.name !== name) {
|
|
1214
|
-
|
|
1220
|
+
try {
|
|
1221
|
+
volume = await updateRailwayVolumeName({ volumeId: volume.id, name, env, fetchImpl }) ?? { ...volume, name };
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
if (!looksLikeRailwayMissingResource(error)) {
|
|
1224
|
+
throw error;
|
|
1225
|
+
}
|
|
1226
|
+
volume = await createReplacementVolume();
|
|
1227
|
+
}
|
|
1215
1228
|
updated = true;
|
|
1216
1229
|
}
|
|
1217
1230
|
let instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? null;
|
|
1218
1231
|
if (!instance && volume.instances.some((entry) => entry.environmentId === environmentId)) {
|
|
1219
|
-
|
|
1220
|
-
|
|
1232
|
+
try {
|
|
1233
|
+
await updateRailwayVolumeInstanceMountPath({ volumeId: volume.id, serviceId, mountPath, env, fetchImpl });
|
|
1234
|
+
volume = await listRailwayVolumes({ projectId, env, fetchImpl }).then((refreshed) => refreshed.find((candidate) => candidate.id === volume?.id) ?? volume);
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
if (!looksLikeRailwayMissingResource(error)) {
|
|
1237
|
+
throw error;
|
|
1238
|
+
}
|
|
1239
|
+
volume = await createReplacementVolume();
|
|
1240
|
+
}
|
|
1221
1241
|
instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? null;
|
|
1222
1242
|
updated = true;
|
|
1223
1243
|
}
|
|
1224
1244
|
if (instance && instance.mountPath !== mountPath) {
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1245
|
+
try {
|
|
1246
|
+
await updateRailwayVolumeInstanceMountPath({ volumeId: volume.id, mountPath, env, fetchImpl });
|
|
1247
|
+
volume = {
|
|
1248
|
+
...volume,
|
|
1249
|
+
instances: volume.instances.map((entry) => entry.id === instance.id ? { ...entry, mountPath } : entry)
|
|
1250
|
+
};
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
if (!looksLikeRailwayMissingResource(error)) {
|
|
1253
|
+
throw error;
|
|
1254
|
+
}
|
|
1255
|
+
volume = await createReplacementVolume();
|
|
1256
|
+
instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? null;
|
|
1257
|
+
}
|
|
1230
1258
|
updated = true;
|
|
1231
1259
|
}
|
|
1232
1260
|
return { volume, instance, created, updated };
|
|
@@ -1340,7 +1368,121 @@ mutation TreeseedRailwayCustomDomainCreate($input: CustomDomainCreateInput!) {
|
|
|
1340
1368
|
}
|
|
1341
1369
|
return { domain: created, created: true };
|
|
1342
1370
|
}
|
|
1371
|
+
function looksLikeRailwayMissingResource(error) {
|
|
1372
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
1373
|
+
return /not found|does not exist|could not find|unknown|invalid .*id/iu.test(message);
|
|
1374
|
+
}
|
|
1375
|
+
async function railwayDeleteMutation({
|
|
1376
|
+
query,
|
|
1377
|
+
variables,
|
|
1378
|
+
env,
|
|
1379
|
+
fetchImpl,
|
|
1380
|
+
missingResult
|
|
1381
|
+
}) {
|
|
1382
|
+
try {
|
|
1383
|
+
await railwayGraphqlRequest({
|
|
1384
|
+
query,
|
|
1385
|
+
variables,
|
|
1386
|
+
env,
|
|
1387
|
+
fetchImpl
|
|
1388
|
+
});
|
|
1389
|
+
return { status: "deleted" };
|
|
1390
|
+
} catch (error) {
|
|
1391
|
+
if (looksLikeRailwayMissingResource(error)) {
|
|
1392
|
+
return missingResult;
|
|
1393
|
+
}
|
|
1394
|
+
throw error;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
async function deleteRailwayCustomDomain({
|
|
1398
|
+
domainId,
|
|
1399
|
+
env = process.env,
|
|
1400
|
+
fetchImpl = fetch
|
|
1401
|
+
}) {
|
|
1402
|
+
if (!railwayConnectionLabel(domainId)) {
|
|
1403
|
+
return { status: "missing", id: domainId };
|
|
1404
|
+
}
|
|
1405
|
+
const mutation = configuredEnvValue(env, "TREESEED_RAILWAY_CUSTOM_DOMAIN_DELETE_MUTATION") || `
|
|
1406
|
+
mutation TreeseedRailwayCustomDomainDelete($id: String!) {
|
|
1407
|
+
customDomainDelete(id: $id)
|
|
1408
|
+
}
|
|
1409
|
+
`.trim();
|
|
1410
|
+
return railwayDeleteMutation({
|
|
1411
|
+
query: mutation,
|
|
1412
|
+
variables: { id: domainId },
|
|
1413
|
+
env,
|
|
1414
|
+
fetchImpl,
|
|
1415
|
+
missingResult: { status: "missing", id: domainId }
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
async function deleteRailwayVolume({
|
|
1419
|
+
volumeId,
|
|
1420
|
+
env = process.env,
|
|
1421
|
+
fetchImpl = fetch
|
|
1422
|
+
}) {
|
|
1423
|
+
if (!railwayConnectionLabel(volumeId)) {
|
|
1424
|
+
return { status: "missing", id: volumeId };
|
|
1425
|
+
}
|
|
1426
|
+
const mutation = configuredEnvValue(env, "TREESEED_RAILWAY_VOLUME_DELETE_MUTATION") || `
|
|
1427
|
+
mutation TreeseedRailwayVolumeDelete($volumeId: String!) {
|
|
1428
|
+
volumeDelete(volumeId: $volumeId)
|
|
1429
|
+
}
|
|
1430
|
+
`.trim();
|
|
1431
|
+
return railwayDeleteMutation({
|
|
1432
|
+
query: mutation,
|
|
1433
|
+
variables: { volumeId },
|
|
1434
|
+
env,
|
|
1435
|
+
fetchImpl,
|
|
1436
|
+
missingResult: { status: "missing", id: volumeId }
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
async function deleteRailwayEnvironment({
|
|
1440
|
+
environmentId,
|
|
1441
|
+
env = process.env,
|
|
1442
|
+
fetchImpl = fetch
|
|
1443
|
+
}) {
|
|
1444
|
+
if (!railwayConnectionLabel(environmentId)) {
|
|
1445
|
+
return { status: "missing", id: environmentId };
|
|
1446
|
+
}
|
|
1447
|
+
const mutation = configuredEnvValue(env, "TREESEED_RAILWAY_ENVIRONMENT_DELETE_MUTATION") || `
|
|
1448
|
+
mutation TreeseedRailwayEnvironmentDelete($id: String!) {
|
|
1449
|
+
environmentDelete(id: $id)
|
|
1450
|
+
}
|
|
1451
|
+
`.trim();
|
|
1452
|
+
return railwayDeleteMutation({
|
|
1453
|
+
query: mutation,
|
|
1454
|
+
variables: { id: environmentId },
|
|
1455
|
+
env,
|
|
1456
|
+
fetchImpl,
|
|
1457
|
+
missingResult: { status: "missing", id: environmentId }
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
async function deleteRailwayProject({
|
|
1461
|
+
projectId,
|
|
1462
|
+
env = process.env,
|
|
1463
|
+
fetchImpl = fetch
|
|
1464
|
+
}) {
|
|
1465
|
+
if (!railwayConnectionLabel(projectId)) {
|
|
1466
|
+
return { status: "missing", id: projectId };
|
|
1467
|
+
}
|
|
1468
|
+
const mutation = configuredEnvValue(env, "TREESEED_RAILWAY_PROJECT_DELETE_MUTATION") || `
|
|
1469
|
+
mutation TreeseedRailwayProjectDelete($id: String!) {
|
|
1470
|
+
projectDelete(id: $id)
|
|
1471
|
+
}
|
|
1472
|
+
`.trim();
|
|
1473
|
+
return railwayDeleteMutation({
|
|
1474
|
+
query: mutation,
|
|
1475
|
+
variables: { id: projectId },
|
|
1476
|
+
env,
|
|
1477
|
+
fetchImpl,
|
|
1478
|
+
missingResult: { status: "missing", id: projectId }
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1343
1481
|
export {
|
|
1482
|
+
deleteRailwayCustomDomain,
|
|
1483
|
+
deleteRailwayEnvironment,
|
|
1484
|
+
deleteRailwayProject,
|
|
1485
|
+
deleteRailwayVolume,
|
|
1344
1486
|
ensureRailwayCustomDomain,
|
|
1345
1487
|
ensureRailwayEnvironment,
|
|
1346
1488
|
ensureRailwayPostgresService,
|
|
@@ -14,10 +14,13 @@ export declare function buildRailwayDeployCommandEnv(env?: NodeJS.ProcessEnv): {
|
|
|
14
14
|
[key: string]: string | undefined;
|
|
15
15
|
};
|
|
16
16
|
export declare function isRailwayTransientFailure(result: any): boolean;
|
|
17
|
-
export declare function runRailway(args: any, { cwd, capture, allowFailure, input, env }?: {
|
|
17
|
+
export declare function runRailway(args: any, { cwd, capture, allowFailure, input, env, retryTransient, retryAttempts, retryDelayMs }?: {
|
|
18
18
|
capture?: boolean | undefined;
|
|
19
19
|
allowFailure?: boolean | undefined;
|
|
20
|
-
|
|
20
|
+
retryTransient?: boolean | undefined;
|
|
21
|
+
retryAttempts?: number | undefined;
|
|
22
|
+
retryDelayMs?: number | undefined;
|
|
23
|
+
}): import("child_process").SpawnSyncReturns<string> | null;
|
|
21
24
|
export declare function waitForRailwayManagedDeploymentsSettled(tenantRoot: any, scope: any, { services, env, timeoutMs, pollMs, onProgress, }?: {
|
|
22
25
|
services?: unknown[] | undefined;
|
|
23
26
|
env?: NodeJS.ProcessEnv | undefined;
|
|
@@ -53,11 +56,11 @@ export declare function ensureRailwayEnvironmentExists(service: any, { env }?: {
|
|
|
53
56
|
}): import("child_process").SpawnSyncReturns<string> | null;
|
|
54
57
|
export declare function ensureRailwayServiceExists(service: any, { env }?: {
|
|
55
58
|
env?: NodeJS.ProcessEnv | undefined;
|
|
56
|
-
}): import("child_process").SpawnSyncReturns<string
|
|
59
|
+
}): import("child_process").SpawnSyncReturns<string> | null;
|
|
57
60
|
export declare function ensureRailwayDatabaseServiceExists(service: any, { database, env, }?: {
|
|
58
61
|
database?: string | undefined;
|
|
59
62
|
env?: NodeJS.ProcessEnv | undefined;
|
|
60
|
-
}): import("child_process").SpawnSyncReturns<string
|
|
63
|
+
}): import("child_process").SpawnSyncReturns<string> | null;
|
|
61
64
|
export declare function ensureRailwayProjectContext(service: any, { env, allowFailure, capture }?: {
|
|
62
65
|
env?: NodeJS.ProcessEnv | undefined;
|
|
63
66
|
allowFailure?: boolean | undefined;
|
|
@@ -332,7 +332,7 @@ function isRailwayTransientFailure(result) {
|
|
|
332
332
|
if (!message.trim() && result?.status === 1) {
|
|
333
333
|
return true;
|
|
334
334
|
}
|
|
335
|
-
return /timed out|failed to fetch|temporarily unavailable|econnreset|etimedout|failed to stream build logs|failed to retrieve build log/iu.test(message);
|
|
335
|
+
return /timed out|failed to fetch|error decoding response body|expected value at line 1 column 1|temporarily unavailable|econnreset|etimedout|failed to stream build logs|failed to retrieve build log/iu.test(message);
|
|
336
336
|
}
|
|
337
337
|
function sleepSync(milliseconds) {
|
|
338
338
|
if (!Number.isFinite(milliseconds) || milliseconds <= 0) {
|
|
@@ -412,7 +412,7 @@ mutation TreeseedScheduleUpdate($id: String!, $name: String!, $schedule: String!
|
|
|
412
412
|
`.trim()
|
|
413
413
|
};
|
|
414
414
|
}
|
|
415
|
-
function runRailway(args, { cwd, capture = false, allowFailure = false, input, env } = {}) {
|
|
415
|
+
function runRailway(args, { cwd, capture = false, allowFailure = false, input, env, retryTransient = true, retryAttempts = 3, retryDelayMs = 2e3 } = {}) {
|
|
416
416
|
const effectiveEnv = buildRailwayCommandEnv({ ...process.env, ...env ?? {} });
|
|
417
417
|
const railway = resolveTreeseedToolCommand("railway", { env: effectiveEnv });
|
|
418
418
|
if (!railway) {
|
|
@@ -425,8 +425,16 @@ function runRailway(args, { cwd, capture = false, allowFailure = false, input, e
|
|
|
425
425
|
env: spawnEnv,
|
|
426
426
|
input
|
|
427
427
|
});
|
|
428
|
-
|
|
429
|
-
|
|
428
|
+
let result = null;
|
|
429
|
+
const maxAttempts = retryTransient && !allowFailure ? Math.max(1, Number(retryAttempts) || 1) : 1;
|
|
430
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
431
|
+
result = runWithEnv(effectiveEnv);
|
|
432
|
+
if (result.status === 0 || allowFailure || !isRailwayTransientFailure(result) || attempt === maxAttempts) {
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
sleepSync((Number(retryDelayMs) || 0) * attempt);
|
|
436
|
+
}
|
|
437
|
+
if (result?.status !== 0 && !allowFailure) {
|
|
430
438
|
throw new Error(result.stderr?.trim() || result.stdout?.trim() || `railway ${args.join(" ")} failed`);
|
|
431
439
|
}
|
|
432
440
|
return result;
|
|
@@ -68,6 +68,9 @@ function platformSurfaceEnabled(context, surface) {
|
|
|
68
68
|
}
|
|
69
69
|
function activeWorkflowPlane() {
|
|
70
70
|
const plane = process.env.TREESEED_WORKFLOW_PLANE;
|
|
71
|
+
if (plane === "all") {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
71
74
|
return plane === "web" || plane === "processing" ? plane : null;
|
|
72
75
|
}
|
|
73
76
|
function workflowPlaneAllows(plane) {
|
|
@@ -587,7 +587,7 @@ function collectCloudflareEnvironmentSync(input) {
|
|
|
587
587
|
const registry = collectTreeseedEnvironmentContext(input.context.tenantRoot);
|
|
588
588
|
const state = loadDeployState(input.context.tenantRoot, input.context.deployConfig, { target });
|
|
589
589
|
const generatedSecrets = buildSecretMap(input.context.deployConfig, state);
|
|
590
|
-
const publicVars = buildPublicVars(input.context.deployConfig);
|
|
590
|
+
const publicVars = buildPublicVars(input.context.deployConfig, { target });
|
|
591
591
|
const secrets = {};
|
|
592
592
|
const vars = { ...publicVars };
|
|
593
593
|
const secretNames = /* @__PURE__ */ new Set();
|
|
@@ -1578,26 +1578,40 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false } = {}) {
|
|
|
1578
1578
|
});
|
|
1579
1579
|
if (!volume.instance?.serviceId) {
|
|
1580
1580
|
ensureRailwayProjectContext(entry.configuredService, { env: topology.env, capture: true });
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1581
|
+
let attachMessage = "";
|
|
1582
|
+
let attached = false;
|
|
1583
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
1584
|
+
const attachResult = runRailway([
|
|
1585
|
+
"volume",
|
|
1586
|
+
"--service",
|
|
1587
|
+
entry.service.id,
|
|
1588
|
+
"attach",
|
|
1589
|
+
"--volume",
|
|
1590
|
+
volume.volume.id,
|
|
1591
|
+
"--yes",
|
|
1592
|
+
"--json"
|
|
1593
|
+
], {
|
|
1594
|
+
cwd: entry.configuredService.rootDir,
|
|
1595
|
+
capture: true,
|
|
1596
|
+
allowFailure: true,
|
|
1597
|
+
env: topology.env
|
|
1598
|
+
});
|
|
1599
|
+
if ((attachResult.status ?? 1) === 0) {
|
|
1600
|
+
attached = true;
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
attachMessage = attachResult.stderr?.trim() || attachResult.stdout?.trim() || "";
|
|
1604
|
+
if (/already mounted/iu.test(attachMessage)) {
|
|
1605
|
+
attached = true;
|
|
1606
|
+
break;
|
|
1600
1607
|
}
|
|
1608
|
+
if (!/volume .*not found|not found|does not exist/iu.test(attachMessage)) {
|
|
1609
|
+
break;
|
|
1610
|
+
}
|
|
1611
|
+
sleepMs(2e3 * (attempt + 1));
|
|
1612
|
+
}
|
|
1613
|
+
if (!attached) {
|
|
1614
|
+
throw new Error(attachMessage || `Railway volume attach failed for ${entry.service.name}.`);
|
|
1601
1615
|
}
|
|
1602
1616
|
}
|
|
1603
1617
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import readline from 'node:readline/promises';
|
|
3
3
|
import { stdin as input, stdout as output } from 'node:process';
|
|
4
4
|
import { applyTreeseedEnvironmentToProcess, assertTreeseedCommandEnvironment } from '../operations/services/config-runtime.js';
|
|
5
|
-
import { cleanupDestroyedState, createPersistentDeployTarget,
|
|
5
|
+
import { cleanupDestroyedState, createPersistentDeployTarget, destroyTreeseedEnvironmentResources, loadDeployState, printDestroySummary, validateDestroyPrerequisites, } from '../operations/services/deploy.js';
|
|
6
6
|
import { deriveCloudflareWorkerName } from '../platform/deploy-config.js';
|
|
7
7
|
const tenantRoot = process.cwd();
|
|
8
8
|
function parseArgs(argv) {
|
|
@@ -11,6 +11,7 @@ function parseArgs(argv) {
|
|
|
11
11
|
force: false,
|
|
12
12
|
skipConfirmation: false,
|
|
13
13
|
confirm: null,
|
|
14
|
+
deleteData: false,
|
|
14
15
|
removeBuildArtifacts: false,
|
|
15
16
|
environment: null,
|
|
16
17
|
};
|
|
@@ -27,6 +28,10 @@ function parseArgs(argv) {
|
|
|
27
28
|
parsed.force = true;
|
|
28
29
|
continue;
|
|
29
30
|
}
|
|
31
|
+
if (current === '--delete-data') {
|
|
32
|
+
parsed.deleteData = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
30
35
|
if (current === '--skip-confirmation') {
|
|
31
36
|
parsed.skipConfirmation = true;
|
|
32
37
|
continue;
|
|
@@ -92,9 +97,10 @@ if (!options.skipConfirmation) {
|
|
|
92
97
|
process.exit(1);
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
|
-
const result =
|
|
100
|
+
const result = await destroyTreeseedEnvironmentResources(tenantRoot, {
|
|
96
101
|
dryRun: options.dryRun,
|
|
97
102
|
force: options.force,
|
|
103
|
+
deleteData: options.deleteData,
|
|
98
104
|
target,
|
|
99
105
|
});
|
|
100
106
|
printDestroySummary(result);
|