@treeseed/sdk 0.10.16 → 0.10.18
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 +21 -6
- package/dist/operations/services/railway-api.js +208 -23
- package/dist/operations/services/railway-deploy.d.ts +24 -4
- package/dist/operations/services/railway-deploy.js +13 -4
- package/dist/platform/environment.js +3 -0
- package/dist/reconcile/builtin-adapters.js +43 -33
- 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
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
configuredRailwayServices,
|
|
33
33
|
deriveRailwayMarketOperationsRunnerVolumeName,
|
|
34
34
|
ensureRailwayProjectContext,
|
|
35
|
+
ensureRailwayServiceVolumeWithCliFallback,
|
|
35
36
|
runRailway,
|
|
36
37
|
validateRailwayDeployPrerequisites
|
|
37
38
|
} from "../operations/services/railway-deploy.js";
|
|
@@ -42,7 +43,6 @@ import {
|
|
|
42
43
|
ensureRailwayProject,
|
|
43
44
|
ensureRailwayService,
|
|
44
45
|
ensureRailwayServiceInstanceConfiguration,
|
|
45
|
-
ensureRailwayServiceVolume,
|
|
46
46
|
getRailwayServiceInstance,
|
|
47
47
|
getRailwayProject,
|
|
48
48
|
listRailwayCustomDomains,
|
|
@@ -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();
|
|
@@ -1568,36 +1568,54 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false } = {}) {
|
|
|
1568
1568
|
});
|
|
1569
1569
|
if (entry.configuredService.volumeMountPath) {
|
|
1570
1570
|
const volumeName = entry.configuredService.key === "marketOperationsRunner" ? deriveRailwayMarketOperationsRunnerVolumeName(entry.service.name, entry.environment.name) : `${entry.service.name}-volume`;
|
|
1571
|
-
const volume = await
|
|
1571
|
+
const volume = await ensureRailwayServiceVolumeWithCliFallback({
|
|
1572
|
+
tenantRoot: input.context.tenantRoot,
|
|
1572
1573
|
projectId: entry.project.id,
|
|
1573
1574
|
environmentId: entry.environment.id,
|
|
1575
|
+
environmentName: entry.environment.name,
|
|
1574
1576
|
serviceId: entry.service.id,
|
|
1577
|
+
serviceName: entry.service.name,
|
|
1575
1578
|
name: volumeName,
|
|
1576
1579
|
mountPath: entry.configuredService.volumeMountPath,
|
|
1580
|
+
preferCli: entry.configuredService.key === "marketOperationsRunner",
|
|
1577
1581
|
env: topology.env
|
|
1578
1582
|
});
|
|
1579
1583
|
if (!volume.instance?.serviceId) {
|
|
1580
1584
|
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
|
-
|
|
1585
|
+
let attachMessage = "";
|
|
1586
|
+
let attached = false;
|
|
1587
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
1588
|
+
const attachResult = runRailway([
|
|
1589
|
+
"volume",
|
|
1590
|
+
"--service",
|
|
1591
|
+
entry.service.id,
|
|
1592
|
+
"attach",
|
|
1593
|
+
"--volume",
|
|
1594
|
+
volume.volume.id,
|
|
1595
|
+
"--yes",
|
|
1596
|
+
"--json"
|
|
1597
|
+
], {
|
|
1598
|
+
cwd: entry.configuredService.rootDir,
|
|
1599
|
+
capture: true,
|
|
1600
|
+
allowFailure: true,
|
|
1601
|
+
env: topology.env
|
|
1602
|
+
});
|
|
1603
|
+
if ((attachResult.status ?? 1) === 0) {
|
|
1604
|
+
attached = true;
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
attachMessage = attachResult.stderr?.trim() || attachResult.stdout?.trim() || "";
|
|
1608
|
+
if (/already mounted/iu.test(attachMessage)) {
|
|
1609
|
+
attached = true;
|
|
1610
|
+
break;
|
|
1611
|
+
}
|
|
1612
|
+
if (!/volume .*not found|not found|does not exist/iu.test(attachMessage)) {
|
|
1613
|
+
break;
|
|
1600
1614
|
}
|
|
1615
|
+
sleepMs(2e3 * (attempt + 1));
|
|
1616
|
+
}
|
|
1617
|
+
if (!attached) {
|
|
1618
|
+
throw new Error(attachMessage || `Railway volume attach failed for ${entry.service.name}.`);
|
|
1601
1619
|
}
|
|
1602
1620
|
}
|
|
1603
1621
|
}
|
|
@@ -1646,7 +1664,7 @@ async function ensureRailwayMarketDatabaseForScope(input, topology) {
|
|
|
1646
1664
|
env: topology.env
|
|
1647
1665
|
});
|
|
1648
1666
|
}
|
|
1649
|
-
for (let attempt = 0; attempt <
|
|
1667
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
1650
1668
|
const existingVolumes = await listRailwayVolumes({
|
|
1651
1669
|
projectId: firstService.project.id,
|
|
1652
1670
|
env: topology.env
|
|
@@ -1655,18 +1673,10 @@ async function ensureRailwayMarketDatabaseForScope(input, topology) {
|
|
|
1655
1673
|
(instance) => instance.serviceId === postgresService.id && instance.environmentId === firstService.environment?.id && instance.mountPath === "/var/lib/postgresql/data"
|
|
1656
1674
|
));
|
|
1657
1675
|
if (attached) {
|
|
1658
|
-
|
|
1676
|
+
return;
|
|
1659
1677
|
}
|
|
1660
|
-
await new Promise((resolve2) => setTimeout(resolve2,
|
|
1678
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
1661
1679
|
}
|
|
1662
|
-
await ensureRailwayServiceVolume({
|
|
1663
|
-
projectId: firstService.project.id,
|
|
1664
|
-
environmentId: firstService.environment.id,
|
|
1665
|
-
serviceId: postgresService.id,
|
|
1666
|
-
name: `postgres-${topology.scope === "prod" ? "prod" : topology.scope}-data`,
|
|
1667
|
-
mountPath: "/var/lib/postgresql/data",
|
|
1668
|
-
env: topology.env
|
|
1669
|
-
});
|
|
1670
1680
|
}
|
|
1671
1681
|
async function observeRailwayUnit(input, { refresh = false } = {}) {
|
|
1672
1682
|
let attempt = 0;
|
|
@@ -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);
|
|
@@ -1227,10 +1227,102 @@ export declare function workflowRecover(helpers: WorkflowOperationHelpers, input
|
|
|
1227
1227
|
finalState?: WorkflowStatePayload;
|
|
1228
1228
|
}>>;
|
|
1229
1229
|
export declare function workflowDestroy(helpers: WorkflowOperationHelpers, input: TreeseedDestroyInput): Promise<TreeseedWorkflowResult<{
|
|
1230
|
+
remoteResult: {
|
|
1231
|
+
target: {
|
|
1232
|
+
kind: string;
|
|
1233
|
+
scope: string;
|
|
1234
|
+
} | {
|
|
1235
|
+
kind: string;
|
|
1236
|
+
branchName: string;
|
|
1237
|
+
};
|
|
1238
|
+
deleteData: boolean;
|
|
1239
|
+
summary: {
|
|
1240
|
+
target: any;
|
|
1241
|
+
identity: any;
|
|
1242
|
+
workerName: any;
|
|
1243
|
+
siteUrl: any;
|
|
1244
|
+
accountId: any;
|
|
1245
|
+
pages: any;
|
|
1246
|
+
formGuardKv: any;
|
|
1247
|
+
sessionKv: any;
|
|
1248
|
+
siteDataDb: any;
|
|
1249
|
+
queue: any;
|
|
1250
|
+
content: any;
|
|
1251
|
+
resources: {
|
|
1252
|
+
pagesProject: any;
|
|
1253
|
+
contentBucket: any;
|
|
1254
|
+
queue: any;
|
|
1255
|
+
dlq: any;
|
|
1256
|
+
database: any;
|
|
1257
|
+
formGuardKv: any;
|
|
1258
|
+
railwayProject: any;
|
|
1259
|
+
webDomain: any;
|
|
1260
|
+
apiDomain: any;
|
|
1261
|
+
railwayServices: {
|
|
1262
|
+
[k: string]: any;
|
|
1263
|
+
};
|
|
1264
|
+
};
|
|
1265
|
+
webCache: {
|
|
1266
|
+
webHost: any;
|
|
1267
|
+
contentHost: any;
|
|
1268
|
+
rulesManaged: boolean;
|
|
1269
|
+
lastSyncedAt: any;
|
|
1270
|
+
lastError: any;
|
|
1271
|
+
policy: {
|
|
1272
|
+
sourcePages: {
|
|
1273
|
+
paths: string[];
|
|
1274
|
+
browserTtlSeconds: number;
|
|
1275
|
+
edgeTtlSeconds: number;
|
|
1276
|
+
staleWhileRevalidateSeconds: number;
|
|
1277
|
+
staleIfErrorSeconds: number;
|
|
1278
|
+
};
|
|
1279
|
+
contentPages: Required<import("../platform/contracts.ts").TreeseedWebCachePolicyConfig>;
|
|
1280
|
+
r2PublishedObjects: Required<import("../platform/contracts.ts").TreeseedWebCachePolicyConfig>;
|
|
1281
|
+
};
|
|
1282
|
+
deployPurge: any;
|
|
1283
|
+
contentPurge: any;
|
|
1284
|
+
};
|
|
1285
|
+
};
|
|
1286
|
+
operations: {
|
|
1287
|
+
cloudflare: any[];
|
|
1288
|
+
railway: {
|
|
1289
|
+
provider: any;
|
|
1290
|
+
type: any;
|
|
1291
|
+
name: any;
|
|
1292
|
+
status: any;
|
|
1293
|
+
}[];
|
|
1294
|
+
local: {
|
|
1295
|
+
provider: any;
|
|
1296
|
+
type: any;
|
|
1297
|
+
name: any;
|
|
1298
|
+
status: any;
|
|
1299
|
+
}[];
|
|
1300
|
+
};
|
|
1301
|
+
} | null;
|
|
1302
|
+
scope: string;
|
|
1303
|
+
dryRun: boolean;
|
|
1304
|
+
force: boolean;
|
|
1305
|
+
deleteData: boolean;
|
|
1306
|
+
destroyRemote: boolean;
|
|
1307
|
+
destroyLocal: boolean;
|
|
1308
|
+
removeBuildArtifacts: boolean;
|
|
1309
|
+
expectedConfirmation: string;
|
|
1310
|
+
stateSummary: {
|
|
1311
|
+
workerName: any;
|
|
1312
|
+
lastDeploymentTimestamp: any;
|
|
1313
|
+
};
|
|
1314
|
+
plannedSteps: {
|
|
1315
|
+
id: string;
|
|
1316
|
+
description: string;
|
|
1317
|
+
}[];
|
|
1318
|
+
} & {
|
|
1319
|
+
finalState?: WorkflowStatePayload;
|
|
1320
|
+
}> | TreeseedWorkflowResult<{
|
|
1230
1321
|
dryRun: boolean;
|
|
1231
1322
|
remoteResult: Record<string, unknown> | null;
|
|
1232
1323
|
scope: string;
|
|
1233
1324
|
force: boolean;
|
|
1325
|
+
deleteData: boolean;
|
|
1234
1326
|
destroyRemote: boolean;
|
|
1235
1327
|
destroyLocal: boolean;
|
|
1236
1328
|
removeBuildArtifacts: boolean;
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
createBranchPreviewDeployTarget,
|
|
34
34
|
createPersistentDeployTarget,
|
|
35
35
|
destroyCloudflareResources,
|
|
36
|
+
destroyTreeseedEnvironmentResources,
|
|
36
37
|
ensureGeneratedWranglerConfig,
|
|
37
38
|
finalizeDeploymentState,
|
|
38
39
|
loadDeployState,
|
|
@@ -4679,6 +4680,7 @@ async function workflowDestroy(helpers, input) {
|
|
|
4679
4680
|
const target = createPersistentDeployTarget(scope);
|
|
4680
4681
|
const dryRun = executionMode === "plan";
|
|
4681
4682
|
const force = input.force === true;
|
|
4683
|
+
const deleteData = input.deleteData === true;
|
|
4682
4684
|
const destroyRemote = input.destroyRemote !== false;
|
|
4683
4685
|
const destroyLocal = input.destroyLocal !== false;
|
|
4684
4686
|
const removeBuildArtifacts = input.removeBuildArtifacts === true;
|
|
@@ -4691,6 +4693,7 @@ async function workflowDestroy(helpers, input) {
|
|
|
4691
4693
|
scope,
|
|
4692
4694
|
dryRun,
|
|
4693
4695
|
force,
|
|
4696
|
+
deleteData,
|
|
4694
4697
|
destroyRemote,
|
|
4695
4698
|
destroyLocal,
|
|
4696
4699
|
removeBuildArtifacts,
|
|
@@ -4706,14 +4709,18 @@ async function workflowDestroy(helpers, input) {
|
|
|
4706
4709
|
remoteResult: null
|
|
4707
4710
|
};
|
|
4708
4711
|
if (executionMode === "plan") {
|
|
4712
|
+
const plannedRemoteResult = destroyRemote ? await destroyTreeseedEnvironmentResources(tenantRoot, { dryRun: true, force, deleteData, target }) : null;
|
|
4709
4713
|
return buildWorkflowResult(
|
|
4710
4714
|
"destroy",
|
|
4711
4715
|
tenantRoot,
|
|
4712
|
-
|
|
4716
|
+
{
|
|
4717
|
+
...payload,
|
|
4718
|
+
remoteResult: plannedRemoteResult
|
|
4719
|
+
},
|
|
4713
4720
|
{
|
|
4714
4721
|
executionMode,
|
|
4715
4722
|
nextSteps: createNextSteps([
|
|
4716
|
-
{ operation: "destroy", reason: "Run without --plan to destroy the selected environment.", input: { environment: scope, force, removeBuildArtifacts } },
|
|
4723
|
+
{ operation: "destroy", reason: "Run without --plan to destroy the selected environment.", input: { environment: scope, force, deleteData, removeBuildArtifacts } },
|
|
4717
4724
|
{ operation: "status", reason: "Confirm the current environment state before making destructive changes." }
|
|
4718
4725
|
])
|
|
4719
4726
|
}
|
|
@@ -4725,6 +4732,7 @@ async function workflowDestroy(helpers, input) {
|
|
|
4725
4732
|
{
|
|
4726
4733
|
environment: scope,
|
|
4727
4734
|
force,
|
|
4735
|
+
deleteData,
|
|
4728
4736
|
destroyRemote,
|
|
4729
4737
|
destroyLocal,
|
|
4730
4738
|
removeBuildArtifacts
|
|
@@ -4754,7 +4762,7 @@ async function workflowDestroy(helpers, input) {
|
|
|
4754
4762
|
if (!confirmed) {
|
|
4755
4763
|
workflowError("destroy", "confirmation_required", `Destroy confirmation required. Re-run with confirm="${expectedConfirmation}".`);
|
|
4756
4764
|
}
|
|
4757
|
-
const remoteResult = destroyRemote ? await executeJournalStep(root, workflowRun.runId, "destroy-remote", () =>
|
|
4765
|
+
const remoteResult = destroyRemote ? await executeJournalStep(root, workflowRun.runId, "destroy-remote", () => destroyTreeseedEnvironmentResources(tenantRoot, { dryRun: false, force, deleteData, target })) : null;
|
|
4758
4766
|
if (!destroyRemote) {
|
|
4759
4767
|
skipJournalStep(root, workflowRun.runId, "destroy-remote", { skippedReason: "destroyRemote=false" });
|
|
4760
4768
|
}
|
package/dist/workflow.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -69,6 +69,13 @@ __WORKING_DIRECTORY_BLOCK__ web:
|
|
|
69
69
|
TREESEED_WEB_SERVICE_SECRET: ${{ secrets.TREESEED_WEB_SERVICE_SECRET }}
|
|
70
70
|
TREESEED_WEB_ASSERTION_SECRET: ${{ secrets.TREESEED_WEB_ASSERTION_SECRET }}
|
|
71
71
|
TREESEED_WEB_CSRF_SECRET: ${{ secrets.TREESEED_WEB_CSRF_SECRET }}
|
|
72
|
+
TREESEED_API_WEB_SERVICE_ID: ${{ vars.TREESEED_API_WEB_SERVICE_ID || vars.TREESEED_WEB_SERVICE_ID || 'web' }}
|
|
73
|
+
TREESEED_API_WEB_SERVICE_SECRET: ${{ secrets.TREESEED_API_WEB_SERVICE_SECRET || secrets.TREESEED_WEB_SERVICE_SECRET }}
|
|
74
|
+
TREESEED_API_WEB_ASSERTION_SECRET: ${{ secrets.TREESEED_API_WEB_ASSERTION_SECRET || secrets.TREESEED_WEB_ASSERTION_SECRET }}
|
|
75
|
+
TREESEED_API_AUTH_SECRET: ${{ secrets.TREESEED_API_AUTH_SECRET || secrets.TREESEED_BETTER_AUTH_SECRET }}
|
|
76
|
+
TREESEED_PLATFORM_RUNNER_SECRET: ${{ secrets.TREESEED_PLATFORM_RUNNER_SECRET }}
|
|
77
|
+
TREESEED_HOSTED_HUBS_GITHUB_OWNER: ${{ vars.TREESEED_HOSTED_HUBS_GITHUB_OWNER }}
|
|
78
|
+
TREESEED_HOSTED_HUBS_GITHUB_TOKEN: ${{ secrets.TREESEED_HOSTED_HUBS_GITHUB_TOKEN }}
|
|
72
79
|
TREESEED_SITE_URL: ${{ vars.TREESEED_SITE_URL }}
|
|
73
80
|
BETTER_AUTH_URL: ${{ vars.BETTER_AUTH_URL || vars.TREESEED_SITE_URL }}
|
|
74
81
|
TREESEED_CENTRAL_MARKET_API_BASE_URL: ${{ vars.TREESEED_CENTRAL_MARKET_API_BASE_URL || 'https://api.treeseed.ai' }}
|
|
@@ -80,7 +87,7 @@ __WORKING_DIRECTORY_BLOCK__ web:
|
|
|
80
87
|
TREESEED_PROJECT_ID: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID }}
|
|
81
88
|
TREESEED_WORKFLOW_ACTION: ${{ inputs.action_kind }}
|
|
82
89
|
TREESEED_WORKFLOW_ENVIRONMENT: ${{ inputs.environment }}
|
|
83
|
-
TREESEED_WORKFLOW_PLANE:
|
|
90
|
+
TREESEED_WORKFLOW_PLANE: all
|
|
84
91
|
TREESEED_WORKFLOW_PROJECT: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID }}
|
|
85
92
|
TREESEED_WORKFLOW_PREVIEW_ID: ${{ inputs.preview_id }}
|
|
86
93
|
steps:
|