@treeseed/sdk 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +46 -0
- package/dist/operations/providers/default.js +1 -1
- package/dist/operations/services/config-runtime.d.ts +49 -42
- package/dist/operations/services/config-runtime.js +465 -142
- package/dist/operations/services/deploy.d.ts +298 -0
- package/dist/operations/services/deploy.js +381 -137
- package/dist/operations/services/git-workflow.d.ts +9 -0
- package/dist/operations/services/git-workflow.js +32 -0
- package/dist/operations/services/github-api.d.ts +115 -0
- package/dist/operations/services/github-api.js +455 -0
- package/dist/operations/services/github-automation.d.ts +19 -33
- package/dist/operations/services/github-automation.js +44 -131
- package/dist/operations/services/key-agent.d.ts +20 -1
- package/dist/operations/services/key-agent.js +267 -102
- package/dist/operations/services/knowledge-coop-launch.d.ts +2 -3
- package/dist/operations/services/knowledge-coop-launch.js +26 -12
- package/dist/operations/services/project-platform.d.ts +157 -150
- package/dist/operations/services/project-platform.js +129 -26
- package/dist/operations/services/railway-api.d.ts +244 -0
- package/dist/operations/services/railway-api.js +882 -0
- package/dist/operations/services/railway-deploy.d.ts +171 -27
- package/dist/operations/services/railway-deploy.js +672 -172
- package/dist/operations/services/runtime-tools.d.ts +18 -0
- package/dist/operations/services/runtime-tools.js +19 -6
- package/dist/operations/services/workspace-preflight.js +2 -2
- package/dist/platform/contracts.d.ts +7 -0
- package/dist/platform/deploy-config.js +23 -0
- package/dist/platform/deploy-runtime.d.ts +1 -0
- package/dist/platform/deploy-runtime.js +7 -9
- package/dist/platform/env.yaml +10 -9
- package/dist/platform/environment.js +4 -0
- package/dist/platform/plugin.d.ts +6 -0
- package/dist/platform/plugins/constants.d.ts +1 -0
- package/dist/platform/plugins/constants.js +1 -0
- package/dist/platform/plugins/runtime.d.ts +4 -0
- package/dist/platform/plugins/runtime.js +8 -1
- package/dist/platform/published-content.js +27 -4
- package/dist/platform/tenant/runtime-config.js +33 -24
- package/dist/plugin-default.d.ts +1 -0
- package/dist/plugin-default.js +1 -0
- package/dist/reconcile/builtin-adapters.d.ts +3 -0
- package/dist/reconcile/builtin-adapters.js +2093 -0
- package/dist/reconcile/contracts.d.ts +155 -0
- package/dist/reconcile/contracts.js +0 -0
- package/dist/reconcile/desired-state.d.ts +179 -0
- package/dist/reconcile/desired-state.js +319 -0
- package/dist/reconcile/engine.d.ts +405 -0
- package/dist/reconcile/engine.js +356 -0
- package/dist/reconcile/errors.d.ts +5 -0
- package/dist/reconcile/errors.js +13 -0
- package/dist/reconcile/index.d.ts +7 -0
- package/dist/reconcile/index.js +7 -0
- package/dist/reconcile/registry.d.ts +7 -0
- package/dist/reconcile/registry.js +64 -0
- package/dist/reconcile/state.d.ts +7 -0
- package/dist/reconcile/state.js +303 -0
- package/dist/reconcile/units.d.ts +6 -0
- package/dist/reconcile/units.js +68 -0
- package/dist/scripts/config-treeseed.js +27 -19
- package/dist/scripts/tenant-deploy.js +35 -14
- package/dist/workflow/operations.js +127 -22
- package/dist/workflow-support.d.ts +3 -1
- package/dist/workflow-support.js +50 -0
- package/dist/workflow.d.ts +2 -0
- package/package.json +7 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { loadCliDeployConfig } from "../operations/services/runtime-tools.js";
|
|
3
|
+
import { loadDeployState, resolveTreeseedResourceIdentity, writeDeployState } from "../operations/services/deploy.js";
|
|
4
|
+
import { targetKey } from "./units.js";
|
|
5
|
+
function stableHash(value) {
|
|
6
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
7
|
+
}
|
|
8
|
+
function railwayUnitTypeForServiceKey(serviceKey) {
|
|
9
|
+
if (serviceKey === "workdayStart") {
|
|
10
|
+
return "railway-service:workday-start";
|
|
11
|
+
}
|
|
12
|
+
if (serviceKey === "workdayReport") {
|
|
13
|
+
return "railway-service:workday-report";
|
|
14
|
+
}
|
|
15
|
+
return `railway-service:${serviceKey}`;
|
|
16
|
+
}
|
|
17
|
+
function emptyPersistedUnitState(unit) {
|
|
18
|
+
return {
|
|
19
|
+
unitId: unit.unitId,
|
|
20
|
+
unitType: unit.unitType,
|
|
21
|
+
provider: unit.provider,
|
|
22
|
+
identity: unit.identity,
|
|
23
|
+
target: unit.target,
|
|
24
|
+
logicalName: unit.logicalName,
|
|
25
|
+
desiredSpecHash: stableHash(unit.spec),
|
|
26
|
+
lastObservedAt: null,
|
|
27
|
+
lastReconciledAt: null,
|
|
28
|
+
lastVerifiedAt: null,
|
|
29
|
+
lastStatus: "pending",
|
|
30
|
+
lastObservedState: {},
|
|
31
|
+
lastReconciledState: {},
|
|
32
|
+
lastDiff: null,
|
|
33
|
+
lastVerification: null,
|
|
34
|
+
lastAction: null,
|
|
35
|
+
resourceLocators: {},
|
|
36
|
+
warnings: [],
|
|
37
|
+
error: null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function migrateLegacyDeployStateUnits(legacyState, target) {
|
|
41
|
+
const identity = legacyState.identity ?? resolveTreeseedResourceIdentity({
|
|
42
|
+
slug: legacyState.hosting?.projectId ?? legacyState.runtime?.projectId ?? "project",
|
|
43
|
+
hosting: legacyState.hosting ?? {},
|
|
44
|
+
runtime: legacyState.runtime ?? {},
|
|
45
|
+
cloudflare: {}
|
|
46
|
+
}, target);
|
|
47
|
+
const units = {};
|
|
48
|
+
const queue = legacyState.queues?.agentWork;
|
|
49
|
+
if (queue?.name) {
|
|
50
|
+
units[`queue:${queue.name}`] = {
|
|
51
|
+
unitId: `queue:${queue.name}`,
|
|
52
|
+
unitType: "queue",
|
|
53
|
+
provider: "cloudflare",
|
|
54
|
+
identity,
|
|
55
|
+
target,
|
|
56
|
+
logicalName: queue.name,
|
|
57
|
+
desiredSpecHash: "",
|
|
58
|
+
lastObservedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
59
|
+
lastReconciledAt: legacyState.lastDeploymentTimestamp ?? null,
|
|
60
|
+
lastVerifiedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
61
|
+
lastStatus: queue.queueId ? "ready" : "pending",
|
|
62
|
+
lastObservedState: {
|
|
63
|
+
name: queue.name,
|
|
64
|
+
dlqName: queue.dlqName ?? null,
|
|
65
|
+
queueId: queue.queueId ?? null,
|
|
66
|
+
dlqId: queue.dlqId ?? null
|
|
67
|
+
},
|
|
68
|
+
lastReconciledState: {
|
|
69
|
+
name: queue.name,
|
|
70
|
+
dlqName: queue.dlqName ?? null,
|
|
71
|
+
queueId: queue.queueId ?? null,
|
|
72
|
+
dlqId: queue.dlqId ?? null
|
|
73
|
+
},
|
|
74
|
+
lastDiff: null,
|
|
75
|
+
lastVerification: null,
|
|
76
|
+
lastAction: null,
|
|
77
|
+
resourceLocators: {
|
|
78
|
+
queueId: queue.queueId ?? null,
|
|
79
|
+
dlqId: queue.dlqId ?? null
|
|
80
|
+
},
|
|
81
|
+
warnings: [],
|
|
82
|
+
error: null
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const db = legacyState.d1Databases?.SITE_DATA_DB;
|
|
86
|
+
if (db?.databaseName) {
|
|
87
|
+
units[`database:${db.databaseName}`] = {
|
|
88
|
+
unitId: `database:${db.databaseName}`,
|
|
89
|
+
unitType: "database",
|
|
90
|
+
provider: "cloudflare",
|
|
91
|
+
identity,
|
|
92
|
+
target,
|
|
93
|
+
logicalName: db.databaseName,
|
|
94
|
+
desiredSpecHash: "",
|
|
95
|
+
lastObservedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
96
|
+
lastReconciledAt: legacyState.lastDeploymentTimestamp ?? null,
|
|
97
|
+
lastVerifiedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
98
|
+
lastStatus: db.databaseId ? "ready" : "pending",
|
|
99
|
+
lastObservedState: {
|
|
100
|
+
databaseName: db.databaseName,
|
|
101
|
+
databaseId: db.databaseId ?? null,
|
|
102
|
+
previewDatabaseId: db.previewDatabaseId ?? null
|
|
103
|
+
},
|
|
104
|
+
lastReconciledState: {
|
|
105
|
+
databaseName: db.databaseName,
|
|
106
|
+
databaseId: db.databaseId ?? null,
|
|
107
|
+
previewDatabaseId: db.previewDatabaseId ?? null
|
|
108
|
+
},
|
|
109
|
+
lastDiff: null,
|
|
110
|
+
lastVerification: null,
|
|
111
|
+
lastAction: null,
|
|
112
|
+
resourceLocators: {
|
|
113
|
+
databaseId: db.databaseId ?? null,
|
|
114
|
+
previewDatabaseId: db.previewDatabaseId ?? null
|
|
115
|
+
},
|
|
116
|
+
warnings: [],
|
|
117
|
+
error: null
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
for (const [binding, namespace] of Object.entries(legacyState.kvNamespaces ?? {})) {
|
|
121
|
+
const record = namespace;
|
|
122
|
+
if (!record?.name) continue;
|
|
123
|
+
const unitType = binding === "FORM_GUARD_KV" ? "kv-form-guard" : "kv-session";
|
|
124
|
+
units[`${unitType}:${record.name}`] = {
|
|
125
|
+
unitId: `${unitType}:${record.name}`,
|
|
126
|
+
unitType,
|
|
127
|
+
provider: "cloudflare",
|
|
128
|
+
identity,
|
|
129
|
+
target,
|
|
130
|
+
logicalName: record.name,
|
|
131
|
+
desiredSpecHash: "",
|
|
132
|
+
lastObservedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
133
|
+
lastReconciledAt: legacyState.lastDeploymentTimestamp ?? null,
|
|
134
|
+
lastVerifiedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
135
|
+
lastStatus: record.id ? "ready" : "pending",
|
|
136
|
+
lastObservedState: { ...record },
|
|
137
|
+
lastReconciledState: { ...record },
|
|
138
|
+
lastDiff: null,
|
|
139
|
+
lastVerification: null,
|
|
140
|
+
lastAction: null,
|
|
141
|
+
resourceLocators: {
|
|
142
|
+
id: record.id ?? null,
|
|
143
|
+
previewId: record.previewId ?? null
|
|
144
|
+
},
|
|
145
|
+
warnings: [],
|
|
146
|
+
error: null
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (legacyState.content?.bucketName) {
|
|
150
|
+
units[`content-store:${legacyState.content.bucketName}`] = {
|
|
151
|
+
unitId: `content-store:${legacyState.content.bucketName}`,
|
|
152
|
+
unitType: "content-store",
|
|
153
|
+
provider: "cloudflare",
|
|
154
|
+
identity,
|
|
155
|
+
target,
|
|
156
|
+
logicalName: legacyState.content.bucketName,
|
|
157
|
+
desiredSpecHash: "",
|
|
158
|
+
lastObservedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
159
|
+
lastReconciledAt: legacyState.lastDeploymentTimestamp ?? null,
|
|
160
|
+
lastVerifiedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
161
|
+
lastStatus: "ready",
|
|
162
|
+
lastObservedState: { ...legacyState.content },
|
|
163
|
+
lastReconciledState: { ...legacyState.content },
|
|
164
|
+
lastDiff: null,
|
|
165
|
+
lastVerification: null,
|
|
166
|
+
lastAction: null,
|
|
167
|
+
resourceLocators: {
|
|
168
|
+
bucketName: legacyState.content.bucketName,
|
|
169
|
+
r2Binding: legacyState.content.r2Binding ?? null
|
|
170
|
+
},
|
|
171
|
+
warnings: [],
|
|
172
|
+
error: null
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (legacyState.pages?.projectName) {
|
|
176
|
+
units[`pages-project:${legacyState.pages.projectName}`] = {
|
|
177
|
+
unitId: `pages-project:${legacyState.pages.projectName}`,
|
|
178
|
+
unitType: "pages-project",
|
|
179
|
+
provider: "cloudflare",
|
|
180
|
+
identity,
|
|
181
|
+
target,
|
|
182
|
+
logicalName: legacyState.pages.projectName,
|
|
183
|
+
desiredSpecHash: "",
|
|
184
|
+
lastObservedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
185
|
+
lastReconciledAt: legacyState.lastDeploymentTimestamp ?? null,
|
|
186
|
+
lastVerifiedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
187
|
+
lastStatus: "ready",
|
|
188
|
+
lastObservedState: { ...legacyState.pages },
|
|
189
|
+
lastReconciledState: { ...legacyState.pages },
|
|
190
|
+
lastDiff: null,
|
|
191
|
+
lastVerification: null,
|
|
192
|
+
lastAction: null,
|
|
193
|
+
resourceLocators: {
|
|
194
|
+
projectName: legacyState.pages.projectName,
|
|
195
|
+
url: legacyState.pages.url ?? null
|
|
196
|
+
},
|
|
197
|
+
warnings: [],
|
|
198
|
+
error: null
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (legacyState.workerName) {
|
|
202
|
+
units[`edge-worker:${legacyState.workerName}`] = {
|
|
203
|
+
unitId: `edge-worker:${legacyState.workerName}`,
|
|
204
|
+
unitType: "edge-worker",
|
|
205
|
+
provider: "cloudflare",
|
|
206
|
+
identity,
|
|
207
|
+
target,
|
|
208
|
+
logicalName: legacyState.workerName,
|
|
209
|
+
desiredSpecHash: "",
|
|
210
|
+
lastObservedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
211
|
+
lastReconciledAt: legacyState.lastDeploymentTimestamp ?? null,
|
|
212
|
+
lastVerifiedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
213
|
+
lastStatus: legacyState.workerName ? "ready" : "pending",
|
|
214
|
+
lastObservedState: { workerName: legacyState.workerName, lastDeployedUrl: legacyState.lastDeployedUrl ?? null },
|
|
215
|
+
lastReconciledState: { workerName: legacyState.workerName, lastDeployedUrl: legacyState.lastDeployedUrl ?? null },
|
|
216
|
+
lastDiff: null,
|
|
217
|
+
lastVerification: null,
|
|
218
|
+
lastAction: null,
|
|
219
|
+
resourceLocators: {
|
|
220
|
+
workerName: legacyState.workerName,
|
|
221
|
+
url: legacyState.lastDeployedUrl ?? null
|
|
222
|
+
},
|
|
223
|
+
warnings: [],
|
|
224
|
+
error: null
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
for (const [serviceKey, service] of Object.entries(legacyState.services ?? {})) {
|
|
228
|
+
const record = service;
|
|
229
|
+
if (!record?.enabled || record.provider !== "railway") {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const unitType = railwayUnitTypeForServiceKey(serviceKey);
|
|
233
|
+
const logicalName = record.serviceName ?? record.serviceId ?? serviceKey;
|
|
234
|
+
units[`${unitType}:${logicalName}`] = {
|
|
235
|
+
unitId: `${unitType}:${logicalName}`,
|
|
236
|
+
unitType,
|
|
237
|
+
provider: "railway",
|
|
238
|
+
identity,
|
|
239
|
+
target,
|
|
240
|
+
logicalName,
|
|
241
|
+
desiredSpecHash: "",
|
|
242
|
+
lastObservedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
243
|
+
lastReconciledAt: record.lastDeploymentTimestamp ?? null,
|
|
244
|
+
lastVerifiedAt: legacyState.readiness?.lastValidatedAt ?? null,
|
|
245
|
+
lastStatus: record.initialized ? "ready" : "pending",
|
|
246
|
+
lastObservedState: { ...record },
|
|
247
|
+
lastReconciledState: { ...record },
|
|
248
|
+
lastDiff: null,
|
|
249
|
+
lastVerification: null,
|
|
250
|
+
lastAction: null,
|
|
251
|
+
resourceLocators: {
|
|
252
|
+
projectId: record.projectId ?? null,
|
|
253
|
+
serviceId: record.serviceId ?? null,
|
|
254
|
+
serviceName: record.serviceName ?? null,
|
|
255
|
+
publicBaseUrl: record.publicBaseUrl ?? null
|
|
256
|
+
},
|
|
257
|
+
warnings: [],
|
|
258
|
+
error: null
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return units;
|
|
262
|
+
}
|
|
263
|
+
function loadTreeseedReconcileState(tenantRoot, target) {
|
|
264
|
+
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
265
|
+
const legacyState = loadDeployState(tenantRoot, deployConfig, { target });
|
|
266
|
+
const persistedUnits = legacyState.units && typeof legacyState.units === "object" ? legacyState.units : migrateLegacyDeployStateUnits(legacyState, target);
|
|
267
|
+
return {
|
|
268
|
+
version: 1,
|
|
269
|
+
target,
|
|
270
|
+
dependencyGraphVersion: legacyState.reconcile?.dependencyGraphVersion ?? 1,
|
|
271
|
+
units: { ...persistedUnits }
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function writeTreeseedReconcileState(tenantRoot, reconcileState) {
|
|
275
|
+
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
276
|
+
const legacyState = loadDeployState(tenantRoot, deployConfig, { target: reconcileState.target });
|
|
277
|
+
writeDeployState(tenantRoot, {
|
|
278
|
+
...legacyState,
|
|
279
|
+
reconcile: {
|
|
280
|
+
version: reconcileState.version,
|
|
281
|
+
dependencyGraphVersion: reconcileState.dependencyGraphVersion,
|
|
282
|
+
targetKey: targetKey(reconcileState.target)
|
|
283
|
+
},
|
|
284
|
+
units: reconcileState.units
|
|
285
|
+
}, { target: reconcileState.target });
|
|
286
|
+
}
|
|
287
|
+
function ensureTreeseedPersistedUnitState(reconcileState, unit) {
|
|
288
|
+
return reconcileState.units[unit.unitId] ?? emptyPersistedUnitState(unit);
|
|
289
|
+
}
|
|
290
|
+
function updateTreeseedPersistedUnitState(reconcileState, state) {
|
|
291
|
+
reconcileState.units[state.unitId] = state;
|
|
292
|
+
}
|
|
293
|
+
function desiredUnitSpecHash(unit) {
|
|
294
|
+
return stableHash(unit.spec);
|
|
295
|
+
}
|
|
296
|
+
export {
|
|
297
|
+
desiredUnitSpecHash,
|
|
298
|
+
ensureTreeseedPersistedUnitState,
|
|
299
|
+
loadTreeseedReconcileState,
|
|
300
|
+
migrateLegacyDeployStateUnits,
|
|
301
|
+
updateTreeseedPersistedUnitState,
|
|
302
|
+
writeTreeseedReconcileState
|
|
303
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TreeseedDesiredUnit, TreeseedReconcileTarget, TreeseedReconcileUnitType } from './contracts.ts';
|
|
2
|
+
export declare const TRESEED_RECONCILE_UNIT_TYPES: TreeseedReconcileUnitType[];
|
|
3
|
+
export declare function targetKey(target: TreeseedReconcileTarget): string;
|
|
4
|
+
export declare function createTreeseedReconcileUnitId(unitType: TreeseedReconcileUnitType, logicalName: string): string;
|
|
5
|
+
export declare function topologicallySortDesiredUnits(units: TreeseedDesiredUnit[]): TreeseedDesiredUnit[];
|
|
6
|
+
export declare function reverseTopologicallySortedUnits(units: TreeseedDesiredUnit[]): TreeseedDesiredUnit[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const TRESEED_RECONCILE_UNIT_TYPES = [
|
|
2
|
+
"web-ui",
|
|
3
|
+
"api-runtime",
|
|
4
|
+
"manager-runtime",
|
|
5
|
+
"worker-runtime",
|
|
6
|
+
"workday-start-runtime",
|
|
7
|
+
"workday-report-runtime",
|
|
8
|
+
"edge-worker",
|
|
9
|
+
"content-store",
|
|
10
|
+
"queue",
|
|
11
|
+
"database",
|
|
12
|
+
"kv-form-guard",
|
|
13
|
+
"kv-session",
|
|
14
|
+
"pages-project",
|
|
15
|
+
"custom-domain:web",
|
|
16
|
+
"custom-domain:api",
|
|
17
|
+
"dns-record",
|
|
18
|
+
"railway-service:api",
|
|
19
|
+
"railway-service:manager",
|
|
20
|
+
"railway-service:worker",
|
|
21
|
+
"railway-service:workday-start",
|
|
22
|
+
"railway-service:workday-report"
|
|
23
|
+
];
|
|
24
|
+
function targetKey(target) {
|
|
25
|
+
return target.kind === "persistent" ? target.scope : `branch:${target.branchName}`;
|
|
26
|
+
}
|
|
27
|
+
function createTreeseedReconcileUnitId(unitType, logicalName) {
|
|
28
|
+
return `${unitType}:${logicalName}`;
|
|
29
|
+
}
|
|
30
|
+
function topologicallySortDesiredUnits(units) {
|
|
31
|
+
const byId = new Map(units.map((unit) => [unit.unitId, unit]));
|
|
32
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
33
|
+
const visited = /* @__PURE__ */ new Set();
|
|
34
|
+
const output = [];
|
|
35
|
+
const visit = (unit) => {
|
|
36
|
+
if (visited.has(unit.unitId)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (visiting.has(unit.unitId)) {
|
|
40
|
+
throw new Error(`Treeseed reconcile dependency cycle detected at ${unit.unitId}.`);
|
|
41
|
+
}
|
|
42
|
+
visiting.add(unit.unitId);
|
|
43
|
+
for (const dependencyId of unit.dependencies) {
|
|
44
|
+
const dependency = byId.get(dependencyId);
|
|
45
|
+
if (!dependency) {
|
|
46
|
+
throw new Error(`Treeseed reconcile dependency ${dependencyId} referenced by ${unit.unitId} is missing.`);
|
|
47
|
+
}
|
|
48
|
+
visit(dependency);
|
|
49
|
+
}
|
|
50
|
+
visiting.delete(unit.unitId);
|
|
51
|
+
visited.add(unit.unitId);
|
|
52
|
+
output.push(unit);
|
|
53
|
+
};
|
|
54
|
+
for (const unit of units) {
|
|
55
|
+
visit(unit);
|
|
56
|
+
}
|
|
57
|
+
return output;
|
|
58
|
+
}
|
|
59
|
+
function reverseTopologicallySortedUnits(units) {
|
|
60
|
+
return [...topologicallySortDesiredUnits(units)].reverse();
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
TRESEED_RECONCILE_UNIT_TYPES,
|
|
64
|
+
createTreeseedReconcileUnitId,
|
|
65
|
+
reverseTopologicallySortedUnits,
|
|
66
|
+
targetKey,
|
|
67
|
+
topologicallySortDesiredUnits
|
|
68
|
+
};
|
|
@@ -5,6 +5,7 @@ function parseArgs(argv) {
|
|
|
5
5
|
const parsed = {
|
|
6
6
|
scopes: [],
|
|
7
7
|
sync: 'all',
|
|
8
|
+
bootstrap: false,
|
|
8
9
|
rotateMachineKey: false,
|
|
9
10
|
};
|
|
10
11
|
const rest = [...argv];
|
|
@@ -29,6 +30,10 @@ function parseArgs(argv) {
|
|
|
29
30
|
parsed.sync = current.split('=', 2)[1] ?? 'all';
|
|
30
31
|
continue;
|
|
31
32
|
}
|
|
33
|
+
if (current === '--bootstrap') {
|
|
34
|
+
parsed.bootstrap = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
32
37
|
if (current === '--rotate-machine-key') {
|
|
33
38
|
parsed.rotateMachineKey = true;
|
|
34
39
|
continue;
|
|
@@ -50,30 +55,34 @@ try {
|
|
|
50
55
|
}
|
|
51
56
|
else {
|
|
52
57
|
applyTreeseedSafeRepairs(tenantRoot);
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
const applyResult = options.bootstrap
|
|
59
|
+
? { updated: [], sharedStorageMigrations: [] }
|
|
60
|
+
: (() => {
|
|
61
|
+
const context = collectTreeseedConfigContext({
|
|
62
|
+
tenantRoot,
|
|
63
|
+
scopes,
|
|
64
|
+
env: process.env,
|
|
65
|
+
});
|
|
66
|
+
const updates = scopes.flatMap((scope) => context.entriesByScope[scope].map((entry) => ({
|
|
67
|
+
scope,
|
|
68
|
+
entryId: entry.id,
|
|
69
|
+
value: entry.effectiveValue,
|
|
70
|
+
reused: entry.currentValue.length > 0 || entry.suggestedValue.length > 0,
|
|
71
|
+
})));
|
|
72
|
+
return applyTreeseedConfigValues({ tenantRoot, updates });
|
|
73
|
+
})();
|
|
74
|
+
const result = await finalizeTreeseedConfig({
|
|
66
75
|
tenantRoot,
|
|
67
76
|
scopes,
|
|
68
77
|
sync: options.sync,
|
|
69
78
|
env: process.env,
|
|
70
79
|
});
|
|
71
80
|
const { configPath, keyPath } = getTreeseedMachineConfigPaths(tenantRoot);
|
|
72
|
-
console.log('Treeseed config completed.');
|
|
81
|
+
console.log(options.bootstrap ? 'Treeseed bootstrap completed.' : 'Treeseed config completed.');
|
|
73
82
|
console.log(`Machine config: ${configPath}`);
|
|
74
83
|
console.log(`Machine key: ${keyPath}`);
|
|
75
84
|
console.log(`Updated values: ${applyResult.updated.length}`);
|
|
76
|
-
console.log(`
|
|
85
|
+
console.log(`Reconciled environments: ${result.reconciled.length}`);
|
|
77
86
|
for (const scope of scopes) {
|
|
78
87
|
const readiness = result.readinessByScope?.[scope];
|
|
79
88
|
if (!readiness)
|
|
@@ -84,10 +93,9 @@ try {
|
|
|
84
93
|
if (result.synced.github) {
|
|
85
94
|
console.log(`GitHub sync: ${result.synced.github.secrets.length} secrets, ${result.synced.github.variables.length} variables (${result.synced.github.repository})`);
|
|
86
95
|
}
|
|
87
|
-
if (result.synced.cloudflare) {
|
|
88
|
-
console.log(`Cloudflare sync: ${result.synced.cloudflare.secrets.length} secrets, ${result.synced.cloudflare.varsManagedByWranglerConfig.length} vars via Wrangler config`);
|
|
89
|
-
}
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
|
-
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
100
|
+
process.exitCode = 1;
|
|
93
101
|
}
|
|
@@ -99,14 +99,29 @@ function runNodeScript(scriptPath, scriptArgs = [], env = {}) {
|
|
|
99
99
|
process.exit(result.status ?? 1);
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
+
function isTransientWranglerFailure(result) {
|
|
103
|
+
const output = [result.stderr, result.stdout]
|
|
104
|
+
.filter((value) => typeof value === 'string' && value.trim().length > 0)
|
|
105
|
+
.join('\n');
|
|
106
|
+
return /fetch failed|timed out|etimedout|econnreset|enetunreach|temporarily unavailable|connectivity issue|internal error/i.test(output);
|
|
107
|
+
}
|
|
108
|
+
function sleepSync(milliseconds) {
|
|
109
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
110
|
+
}
|
|
102
111
|
function runWranglerDeploy(configPath) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
113
|
+
const result = spawnSync(process.execPath, [resolveWranglerBin(), 'deploy', '--config', configPath], {
|
|
114
|
+
stdio: 'inherit',
|
|
115
|
+
cwd: tenantRoot,
|
|
116
|
+
env: { ...process.env },
|
|
117
|
+
});
|
|
118
|
+
if (result.status === 0) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (attempt === 3 || !isTransientWranglerFailure(result)) {
|
|
122
|
+
process.exit(result.status ?? 1);
|
|
123
|
+
}
|
|
124
|
+
sleepSync(2000 * attempt);
|
|
110
125
|
}
|
|
111
126
|
}
|
|
112
127
|
function runWranglerPagesDeploy(projectName, branchName, outputDir = 'dist') {
|
|
@@ -121,13 +136,19 @@ function runWranglerPagesDeploy(projectName, branchName, outputDir = 'dist') {
|
|
|
121
136
|
if (branchName) {
|
|
122
137
|
args.push('--branch', branchName);
|
|
123
138
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
139
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
140
|
+
const result = spawnSync(process.execPath, args, {
|
|
141
|
+
stdio: 'inherit',
|
|
142
|
+
cwd: tenantRoot,
|
|
143
|
+
env: { ...process.env },
|
|
144
|
+
});
|
|
145
|
+
if (result.status === 0) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (attempt === 3 || !isTransientWranglerFailure(result)) {
|
|
149
|
+
process.exit(result.status ?? 1);
|
|
150
|
+
}
|
|
151
|
+
sleepSync(2000 * attempt);
|
|
131
152
|
}
|
|
132
153
|
}
|
|
133
154
|
async function main() {
|