@treeseed/sdk 0.6.38 → 0.6.40
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/capacity.d.ts +53 -0
- package/dist/capacity.js +100 -0
- package/dist/control-plane-client.d.ts +41 -1
- package/dist/control-plane-client.js +154 -0
- package/dist/control-plane.d.ts +6 -1
- package/dist/control-plane.js +39 -2
- package/dist/d1-store.d.ts +63 -1
- package/dist/d1-store.js +190 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +12 -0
- package/dist/operations/services/config-runtime.js +5 -3
- package/dist/operations/services/deploy.js +3 -2
- package/dist/operations/services/github-api.d.ts +2 -1
- package/dist/operations/services/github-api.js +35 -26
- package/dist/operations/services/knowledge-coop-launch.js +5 -28
- package/dist/operations/services/package-reference-policy.d.ts +68 -0
- package/dist/operations/services/package-reference-policy.js +135 -0
- package/dist/operations/services/project-platform.d.ts +14 -0
- package/dist/operations/services/project-platform.js +3 -2
- package/dist/operations/services/railway-api.d.ts +33 -0
- package/dist/operations/services/railway-api.js +273 -0
- package/dist/operations/services/railway-deploy.d.ts +22 -0
- package/dist/operations/services/railway-deploy.js +81 -19
- package/dist/operations/services/release-candidate.d.ts +2 -0
- package/dist/operations/services/release-candidate.js +28 -0
- package/dist/operations/services/runtime-tools.js +1 -1
- package/dist/operations-registry.js +1 -0
- package/dist/reconcile/bootstrap-systems.js +1 -1
- package/dist/reconcile/builtin-adapters.js +5 -9
- package/dist/reconcile/contracts.d.ts +1 -1
- package/dist/reconcile/desired-state.js +9 -17
- package/dist/reconcile/state.js +4 -4
- package/dist/reconcile/units.js +4 -8
- package/dist/sdk-types.d.ts +566 -3
- package/dist/sdk.d.ts +12 -1
- package/dist/sdk.js +44 -0
- package/dist/stores/operational-store.d.ts +12 -1
- package/dist/stores/operational-store.js +283 -5
- package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +5 -24
- package/dist/types/agents.d.ts +27 -0
- package/dist/workflow/operations.d.ts +94 -2
- package/dist/workflow/operations.js +90 -32
- package/dist/workflow-state.js +3 -5
- package/dist/workflow.d.ts +8 -1
- package/dist/workflow.js +6 -0
- package/package.json +5 -1
|
@@ -20,6 +20,19 @@ function isPrereleaseVersion(version) {
|
|
|
20
20
|
function isStableVersion(version) {
|
|
21
21
|
return /^\d+\.\d+\.\d+$/u.test(String(version).trim());
|
|
22
22
|
}
|
|
23
|
+
function stableVersionBase(version) {
|
|
24
|
+
const match = String(version).trim().match(/^(\d+\.\d+\.\d+)(?:-|$)/u);
|
|
25
|
+
return match?.[1] ?? null;
|
|
26
|
+
}
|
|
27
|
+
function compareStableVersions(left, right) {
|
|
28
|
+
const leftParts = left.split(".").map((part) => Number(part));
|
|
29
|
+
const rightParts = right.split(".").map((part) => Number(part));
|
|
30
|
+
for (let index = 0; index < 3; index += 1) {
|
|
31
|
+
const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
|
32
|
+
if (difference !== 0) return difference;
|
|
33
|
+
}
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
23
36
|
function isGitDependencySpec(spec) {
|
|
24
37
|
return /^(?:git\+|github:|gitlab:|bitbucket:|ssh:\/\/|https:\/\/|file:)/u.test(String(spec).trim()) && String(spec).includes("#");
|
|
25
38
|
}
|
|
@@ -279,9 +292,68 @@ function gitTagMessage(repoDir, tagName) {
|
|
|
279
292
|
return "";
|
|
280
293
|
}
|
|
281
294
|
}
|
|
295
|
+
function gitRemoteTagMessage(repoDir, tagName) {
|
|
296
|
+
try {
|
|
297
|
+
run("git", ["fetch", "--no-tags", "origin", `refs/tags/${tagName}`], { cwd: repoDir, capture: true });
|
|
298
|
+
return run("git", ["cat-file", "-p", "FETCH_HEAD"], { cwd: repoDir, capture: true });
|
|
299
|
+
} catch {
|
|
300
|
+
return "";
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function gitDevTagMessage(repoDir, tagName) {
|
|
304
|
+
const local = gitTagMessage(repoDir, tagName);
|
|
305
|
+
if (local.trim()) return local;
|
|
306
|
+
return gitRemoteTagMessage(repoDir, tagName);
|
|
307
|
+
}
|
|
282
308
|
function tagHasTreeseedDevMetadata(repoDir, tagName) {
|
|
283
309
|
return gitTagMessage(repoDir, tagName).includes("treeseed-dev-tag: true");
|
|
284
310
|
}
|
|
311
|
+
function parseTreeseedDevTag(tagName, message) {
|
|
312
|
+
const match = String(tagName).match(/^(\d+\.\d+\.\d+)-dev\.([0-9A-Za-z.-]+)\.(\d{8}T\d{6}Z)$/u);
|
|
313
|
+
if (!match) return null;
|
|
314
|
+
const metadata = Object.fromEntries(
|
|
315
|
+
String(message).split(/\r?\n/u).map((line) => line.match(/^([^:]+):\s*(.*)$/u)).filter((lineMatch) => Boolean(lineMatch)).map((lineMatch) => [String(lineMatch[1]).trim(), String(lineMatch[2] ?? "").trim()])
|
|
316
|
+
);
|
|
317
|
+
if (metadata["treeseed-dev-tag"] !== "true") return null;
|
|
318
|
+
return {
|
|
319
|
+
tagName,
|
|
320
|
+
stableVersion: match[1],
|
|
321
|
+
branchSlug: match[2],
|
|
322
|
+
timestamp: match[3],
|
|
323
|
+
metadata,
|
|
324
|
+
branch: metadata.branch || null,
|
|
325
|
+
packageName: metadata.package || null,
|
|
326
|
+
version: metadata.version || null
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function devTagScope(info) {
|
|
330
|
+
const branch = info.branch ?? info.branchSlug;
|
|
331
|
+
if (branch === "staging" || info.branchSlug === "staging") return "staging";
|
|
332
|
+
if (branch === "main" || branch === "prod" || branch === "production" || info.branchSlug === "main" || info.branchSlug === "prod" || info.branchSlug === "production") return "main";
|
|
333
|
+
return "preview";
|
|
334
|
+
}
|
|
335
|
+
function classifyStaleTreeseedDevTag(input) {
|
|
336
|
+
const tagName = String(input.tagName).trim();
|
|
337
|
+
if (!tagName.includes("-dev.")) return { tagName, action: "skip", reason: "not-dev-tag", info: null };
|
|
338
|
+
if (tagName.startsWith("deprecated/")) return { tagName, action: "skip", reason: "deprecated-tag", info: null };
|
|
339
|
+
const info = parseTreeseedDevTag(tagName, input.message);
|
|
340
|
+
if (!info) return { tagName, action: "skip", reason: input.message.includes("treeseed-dev-tag: true") ? "malformed-dev-tag" : "missing-treeseed-metadata", info: null };
|
|
341
|
+
if (input.expectedPackageName && info.packageName && info.packageName !== input.expectedPackageName) {
|
|
342
|
+
return { tagName, action: "skip", reason: "package-mismatch", info };
|
|
343
|
+
}
|
|
344
|
+
if ((input.activeReferences ?? []).includes(tagName)) return { tagName, action: "skip", reason: "still-referenced", info };
|
|
345
|
+
const scope = devTagScope(info);
|
|
346
|
+
if (scope === "main") return { tagName, action: "skip", reason: "main-or-production-tag", info };
|
|
347
|
+
const branchScope = input.branchScope ?? "all";
|
|
348
|
+
if (branchScope !== "all" && branchScope !== scope) return { tagName, action: "skip", reason: `scope-${scope}`, info };
|
|
349
|
+
const currentStableVersion = stableVersionBase(input.currentVersion);
|
|
350
|
+
if (!currentStableVersion) return { tagName, action: "skip", reason: "invalid-current-version", info };
|
|
351
|
+
const comparison = compareStableVersions(info.stableVersion, currentStableVersion);
|
|
352
|
+
if (comparison >= 0) {
|
|
353
|
+
return { tagName, action: "skip", reason: comparison === 0 ? "current-version" : "newer-version", info };
|
|
354
|
+
}
|
|
355
|
+
return { tagName, action: "delete", reason: "stale-before-current-version", info };
|
|
356
|
+
}
|
|
285
357
|
function cleanupDevTags(repoDir, tagNames, activeReferences = []) {
|
|
286
358
|
const active = new Set(activeReferences.filter(Boolean));
|
|
287
359
|
const cleaned = [];
|
|
@@ -309,10 +381,72 @@ function cleanupDevTags(repoDir, tagNames, activeReferences = []) {
|
|
|
309
381
|
}
|
|
310
382
|
return { cleaned, skipped };
|
|
311
383
|
}
|
|
384
|
+
function localDevTags(repoDir) {
|
|
385
|
+
return run("git", ["tag", "-l", "*-dev.*"], { cwd: repoDir, capture: true }).split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
|
|
386
|
+
}
|
|
387
|
+
function remoteDevTags(repoDir) {
|
|
388
|
+
try {
|
|
389
|
+
return run("git", ["ls-remote", "--tags", "origin", "*-dev.*"], { cwd: repoDir, capture: true }).split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).filter((line) => !line.endsWith("^{}")).map((line) => line.split(/\s+/u)[1] ?? "").filter((ref) => ref.startsWith("refs/tags/")).map((ref) => ref.slice("refs/tags/".length));
|
|
390
|
+
} catch {
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function collectTreeseedDevTagCleanupPlan(input) {
|
|
395
|
+
const tagNames = [.../* @__PURE__ */ new Set([...localDevTags(input.repoDir), ...remoteDevTags(input.repoDir)])].sort();
|
|
396
|
+
const classifications = tagNames.map((tagName) => classifyStaleTreeseedDevTag({
|
|
397
|
+
tagName,
|
|
398
|
+
message: gitDevTagMessage(input.repoDir, tagName),
|
|
399
|
+
currentVersion: input.currentVersion,
|
|
400
|
+
activeReferences: input.activeReferences ?? [],
|
|
401
|
+
branchScope: input.branchScope ?? "all",
|
|
402
|
+
expectedPackageName: input.packageName
|
|
403
|
+
}));
|
|
404
|
+
return {
|
|
405
|
+
packageName: input.packageName,
|
|
406
|
+
currentVersion: input.currentVersion,
|
|
407
|
+
branchScope: input.branchScope ?? "all",
|
|
408
|
+
candidates: classifications.filter((classification) => classification.action === "delete"),
|
|
409
|
+
skipped: classifications.filter((classification) => classification.action === "skip"),
|
|
410
|
+
tags: classifications
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function cleanupStaleTreeseedDevTags(input) {
|
|
414
|
+
const plan = collectTreeseedDevTagCleanupPlan(input);
|
|
415
|
+
const cleaned = [];
|
|
416
|
+
const skipped = [...plan.skipped.map((entry) => ({ tagName: entry.tagName, reason: entry.reason }))];
|
|
417
|
+
for (const candidate of plan.candidates) {
|
|
418
|
+
if (input.dryRun) continue;
|
|
419
|
+
try {
|
|
420
|
+
run("git", ["tag", "-d", candidate.tagName], { cwd: input.repoDir });
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
run("git", ["push", "origin", `:refs/tags/${candidate.tagName}`], { cwd: input.repoDir });
|
|
425
|
+
cleaned.push(candidate.tagName);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
skipped.push({ tagName: candidate.tagName, reason: error instanceof Error ? error.message : String(error) });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
status: input.dryRun ? "planned" : "completed",
|
|
432
|
+
packageName: input.packageName,
|
|
433
|
+
currentVersion: input.currentVersion,
|
|
434
|
+
branchScope: input.branchScope ?? "all",
|
|
435
|
+
candidates: plan.candidates.map((entry) => ({ tagName: entry.tagName, reason: entry.reason, branch: entry.info?.branch, branchSlug: entry.info?.branchSlug, version: entry.info?.version })),
|
|
436
|
+
candidateCount: plan.candidates.length,
|
|
437
|
+
cleaned,
|
|
438
|
+
cleanedCount: cleaned.length,
|
|
439
|
+
skipped,
|
|
440
|
+
skippedCount: skipped.length
|
|
441
|
+
};
|
|
442
|
+
}
|
|
312
443
|
export {
|
|
313
444
|
assertNoInternalDevReferences,
|
|
445
|
+
classifyStaleTreeseedDevTag,
|
|
314
446
|
cleanupDevTags,
|
|
447
|
+
cleanupStaleTreeseedDevTags,
|
|
315
448
|
collectInternalDevReferenceIssues,
|
|
449
|
+
collectTreeseedDevTagCleanupPlan,
|
|
316
450
|
createDevTagMessage,
|
|
317
451
|
createPackageDependencyReference,
|
|
318
452
|
devTagFromDependencySpec,
|
|
@@ -323,6 +457,7 @@ export {
|
|
|
323
457
|
isStableVersion,
|
|
324
458
|
normalizeGitRemoteForDependency,
|
|
325
459
|
normalizeGitRemoteForManifest,
|
|
460
|
+
parseTreeseedDevTag,
|
|
326
461
|
rewriteInternalDependenciesToStableVersions,
|
|
327
462
|
rewriteProjectInternalDependenciesToStableVersions,
|
|
328
463
|
tagHasTreeseedDevMetadata,
|
|
@@ -244,6 +244,13 @@ export declare function deployProjectPlatform(options: ProjectPlatformActionOpti
|
|
|
244
244
|
healthcheckPath: string | null;
|
|
245
245
|
healthcheckTimeoutSeconds: number | null;
|
|
246
246
|
runtimeMode: string | null;
|
|
247
|
+
volume: {
|
|
248
|
+
id: string;
|
|
249
|
+
name: string;
|
|
250
|
+
mountPath: any;
|
|
251
|
+
created: boolean;
|
|
252
|
+
updated: boolean;
|
|
253
|
+
} | null;
|
|
247
254
|
} | null;
|
|
248
255
|
} | undefined)[];
|
|
249
256
|
}>;
|
|
@@ -737,6 +744,13 @@ export declare function runProjectPlatformAction(action: ProjectPlatformAction,
|
|
|
737
744
|
healthcheckPath: string | null;
|
|
738
745
|
healthcheckTimeoutSeconds: number | null;
|
|
739
746
|
runtimeMode: string | null;
|
|
747
|
+
volume: {
|
|
748
|
+
id: string;
|
|
749
|
+
name: string;
|
|
750
|
+
mountPath: any;
|
|
751
|
+
created: boolean;
|
|
752
|
+
updated: boolean;
|
|
753
|
+
} | null;
|
|
740
754
|
} | null;
|
|
741
755
|
} | undefined)[];
|
|
742
756
|
}>;
|
|
@@ -631,9 +631,10 @@ function probeR2(tenantRoot, siteConfig, state, target) {
|
|
|
631
631
|
}
|
|
632
632
|
}
|
|
633
633
|
function probeScaleConfiguration(siteConfig, state) {
|
|
634
|
-
const worker = state.services?.worker ?? {};
|
|
634
|
+
const worker = state.services?.workerRunner ?? state.services?.worker ?? {};
|
|
635
|
+
const workerConfig = siteConfig.services?.workerRunner ?? siteConfig.services?.worker ?? {};
|
|
635
636
|
const scalerKind = String(process.env.TREESEED_WORKER_POOL_SCALER ?? "").trim();
|
|
636
|
-
if (scalerKind !== "railway" &&
|
|
637
|
+
if (scalerKind !== "railway" && workerConfig.provider !== "railway") {
|
|
637
638
|
return {
|
|
638
639
|
ok: true,
|
|
639
640
|
skipped: true,
|
|
@@ -53,6 +53,20 @@ export type RailwayCustomDomainSummary = {
|
|
|
53
53
|
verificationToken: string | null;
|
|
54
54
|
dnsRecords: RailwayCustomDomainDnsRecord[];
|
|
55
55
|
};
|
|
56
|
+
export type RailwayVolumeInstanceSummary = {
|
|
57
|
+
id: string;
|
|
58
|
+
serviceId: string | null;
|
|
59
|
+
environmentId: string | null;
|
|
60
|
+
mountPath: string | null;
|
|
61
|
+
sizeGb: number | null;
|
|
62
|
+
usedGb: number | null;
|
|
63
|
+
};
|
|
64
|
+
export type RailwayVolumeSummary = {
|
|
65
|
+
id: string;
|
|
66
|
+
name: string;
|
|
67
|
+
projectId: string | null;
|
|
68
|
+
instances: RailwayVolumeInstanceSummary[];
|
|
69
|
+
};
|
|
56
70
|
export declare function isUsableRailwayToken(value: string | undefined | null): boolean;
|
|
57
71
|
export declare function resolveRailwayApiToken(env?: NodeJS.ProcessEnv | Record<string, string | undefined>): string;
|
|
58
72
|
export declare function resolveRailwayApiUrl(env?: NodeJS.ProcessEnv | Record<string, string | undefined>): string;
|
|
@@ -235,6 +249,25 @@ export declare function upsertRailwayVariables({ projectId, environmentId, servi
|
|
|
235
249
|
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
236
250
|
fetchImpl?: typeof fetch;
|
|
237
251
|
}): Promise<void>;
|
|
252
|
+
export declare function listRailwayVolumes({ projectId, env, fetchImpl, }: {
|
|
253
|
+
projectId: string;
|
|
254
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
255
|
+
fetchImpl?: typeof fetch;
|
|
256
|
+
}): Promise<RailwayVolumeSummary[]>;
|
|
257
|
+
export declare function ensureRailwayServiceVolume({ projectId, environmentId, serviceId, name, mountPath, env, fetchImpl, }: {
|
|
258
|
+
projectId: string;
|
|
259
|
+
environmentId: string;
|
|
260
|
+
serviceId: string;
|
|
261
|
+
name: string;
|
|
262
|
+
mountPath: string;
|
|
263
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
264
|
+
fetchImpl?: typeof fetch;
|
|
265
|
+
}): Promise<{
|
|
266
|
+
volume: RailwayVolumeSummary;
|
|
267
|
+
instance: RailwayVolumeInstanceSummary | null;
|
|
268
|
+
created: boolean;
|
|
269
|
+
updated: boolean;
|
|
270
|
+
}>;
|
|
238
271
|
export declare function listRailwayCustomDomains({ projectId, environmentId, serviceId, env, fetchImpl, }: {
|
|
239
272
|
projectId: string;
|
|
240
273
|
environmentId: string;
|
|
@@ -191,6 +191,83 @@ function normalizeRailwayCustomDomain(node) {
|
|
|
191
191
|
dnsRecords
|
|
192
192
|
};
|
|
193
193
|
}
|
|
194
|
+
function normalizeRailwayVolumeInstance(node) {
|
|
195
|
+
const id = railwayConnectionLabel(node.id);
|
|
196
|
+
if (!id) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const sizeGb = normalizeRailwayNumber(node.sizeGb ?? node.sizeGB ?? node.size_gb ?? node.capacityGb ?? node.capacityGB);
|
|
200
|
+
const usedGb = normalizeRailwayNumber(node.usedGb ?? node.usedGB ?? node.used_gb ?? node.currentUsageGb ?? node.currentUsageGB);
|
|
201
|
+
return {
|
|
202
|
+
id,
|
|
203
|
+
serviceId: railwayConnectionLabel(node.serviceId) || railwayConnectionLabel(node.service?.id) || null,
|
|
204
|
+
environmentId: railwayConnectionLabel(node.environmentId) || railwayConnectionLabel(node.environment?.id) || null,
|
|
205
|
+
mountPath: railwayConnectionLabel(node.mountPath) || railwayConnectionLabel(node.mount_path) || null,
|
|
206
|
+
sizeGb,
|
|
207
|
+
usedGb
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function normalizeVolumeInstances(value) {
|
|
211
|
+
const direct = Array.isArray(value) ? value : null;
|
|
212
|
+
if (direct) {
|
|
213
|
+
return direct.map((entry) => entry && typeof entry === "object" ? normalizeRailwayVolumeInstance(entry) : null).filter(Boolean);
|
|
214
|
+
}
|
|
215
|
+
return normalizeConnectionNodes(value, normalizeRailwayVolumeInstance);
|
|
216
|
+
}
|
|
217
|
+
function normalizeRailwayVolume(node) {
|
|
218
|
+
const id = railwayConnectionLabel(node.id);
|
|
219
|
+
if (!id) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
id,
|
|
224
|
+
name: railwayConnectionLabel(node.name),
|
|
225
|
+
projectId: railwayConnectionLabel(node.projectId) || null,
|
|
226
|
+
instances: [
|
|
227
|
+
...normalizeVolumeInstances(node.instances),
|
|
228
|
+
...normalizeVolumeInstances(node.volumeInstances),
|
|
229
|
+
...normalizeVolumeInstances(node.volume_instances)
|
|
230
|
+
]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function collectRailwayVolumes(value, seen = /* @__PURE__ */ new Set()) {
|
|
234
|
+
const volumes = [];
|
|
235
|
+
const visit = (entry) => {
|
|
236
|
+
if (!entry || typeof entry !== "object") {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (seen.has(entry)) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
seen.add(entry);
|
|
243
|
+
if (Array.isArray(entry)) {
|
|
244
|
+
for (const item of entry) {
|
|
245
|
+
visit(item);
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const record = entry;
|
|
250
|
+
const volume = normalizeRailwayVolume(record);
|
|
251
|
+
if (volume && (record.volumeInstances !== void 0 || record.instances !== void 0 || record.projectId !== void 0 || record.name !== void 0)) {
|
|
252
|
+
volumes.push(volume);
|
|
253
|
+
}
|
|
254
|
+
for (const child of Object.values(record)) {
|
|
255
|
+
visit(child);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
visit(value);
|
|
259
|
+
const byId = /* @__PURE__ */ new Map();
|
|
260
|
+
for (const volume of volumes) {
|
|
261
|
+
const existing = byId.get(volume.id);
|
|
262
|
+
byId.set(volume.id, existing ? {
|
|
263
|
+
...existing,
|
|
264
|
+
name: existing.name || volume.name,
|
|
265
|
+
projectId: existing.projectId || volume.projectId,
|
|
266
|
+
instances: [...existing.instances, ...volume.instances]
|
|
267
|
+
} : volume);
|
|
268
|
+
}
|
|
269
|
+
return [...byId.values()];
|
|
270
|
+
}
|
|
194
271
|
async function railwayGraphqlRequest({
|
|
195
272
|
query,
|
|
196
273
|
variables,
|
|
@@ -811,6 +888,200 @@ mutation TreeseedRailwayVariableCollectionUpsert($input: VariableCollectionUpser
|
|
|
811
888
|
fetchImpl
|
|
812
889
|
});
|
|
813
890
|
}
|
|
891
|
+
async function listRailwayVolumes({
|
|
892
|
+
projectId,
|
|
893
|
+
env = process.env,
|
|
894
|
+
fetchImpl = fetch
|
|
895
|
+
}) {
|
|
896
|
+
const query = configuredEnvValue(env, "TREESEED_RAILWAY_VOLUME_LIST_QUERY") || `
|
|
897
|
+
query TreeseedRailwayVolumeList($projectId: String!) {
|
|
898
|
+
project(id: $projectId) {
|
|
899
|
+
id
|
|
900
|
+
volumes {
|
|
901
|
+
edges {
|
|
902
|
+
node {
|
|
903
|
+
id
|
|
904
|
+
name
|
|
905
|
+
projectId
|
|
906
|
+
volumeInstances {
|
|
907
|
+
edges {
|
|
908
|
+
node {
|
|
909
|
+
id
|
|
910
|
+
serviceId
|
|
911
|
+
environmentId
|
|
912
|
+
mountPath
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
`.trim();
|
|
922
|
+
const payload = await railwayGraphqlRequest({
|
|
923
|
+
query,
|
|
924
|
+
variables: { projectId },
|
|
925
|
+
env,
|
|
926
|
+
fetchImpl
|
|
927
|
+
});
|
|
928
|
+
return collectRailwayVolumes(payload.data);
|
|
929
|
+
}
|
|
930
|
+
async function createRailwayVolume({
|
|
931
|
+
projectId,
|
|
932
|
+
environmentId,
|
|
933
|
+
serviceId,
|
|
934
|
+
name,
|
|
935
|
+
mountPath,
|
|
936
|
+
env = process.env,
|
|
937
|
+
fetchImpl = fetch
|
|
938
|
+
}) {
|
|
939
|
+
const mutation = configuredEnvValue(env, "TREESEED_RAILWAY_VOLUME_CREATE_MUTATION") || `
|
|
940
|
+
mutation TreeseedRailwayVolumeCreate($input: VolumeCreateInput!) {
|
|
941
|
+
volumeCreate(input: $input) {
|
|
942
|
+
id
|
|
943
|
+
name
|
|
944
|
+
projectId
|
|
945
|
+
volumeInstances {
|
|
946
|
+
edges {
|
|
947
|
+
node {
|
|
948
|
+
id
|
|
949
|
+
serviceId
|
|
950
|
+
environmentId
|
|
951
|
+
mountPath
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
`.trim();
|
|
958
|
+
const payload = await railwayGraphqlRequest({
|
|
959
|
+
query: mutation,
|
|
960
|
+
variables: {
|
|
961
|
+
input: {
|
|
962
|
+
projectId,
|
|
963
|
+
environmentId,
|
|
964
|
+
serviceId,
|
|
965
|
+
name,
|
|
966
|
+
mountPath
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
env,
|
|
970
|
+
fetchImpl
|
|
971
|
+
});
|
|
972
|
+
const volume = collectRailwayVolumes(payload.data)[0] ?? null;
|
|
973
|
+
if (!volume) {
|
|
974
|
+
throw new Error(`Railway volume create did not return a usable volume for ${name}.`);
|
|
975
|
+
}
|
|
976
|
+
return volume;
|
|
977
|
+
}
|
|
978
|
+
async function updateRailwayVolumeName({
|
|
979
|
+
volumeId,
|
|
980
|
+
name,
|
|
981
|
+
env = process.env,
|
|
982
|
+
fetchImpl = fetch
|
|
983
|
+
}) {
|
|
984
|
+
const mutation = configuredEnvValue(env, "TREESEED_RAILWAY_VOLUME_UPDATE_MUTATION") || `
|
|
985
|
+
mutation TreeseedRailwayVolumeUpdate($id: String!, $input: VolumeUpdateInput!) {
|
|
986
|
+
volumeUpdate(id: $id, input: $input) {
|
|
987
|
+
id
|
|
988
|
+
name
|
|
989
|
+
projectId
|
|
990
|
+
volumeInstances {
|
|
991
|
+
edges {
|
|
992
|
+
node {
|
|
993
|
+
id
|
|
994
|
+
serviceId
|
|
995
|
+
environmentId
|
|
996
|
+
mountPath
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
`.trim();
|
|
1003
|
+
const payload = await railwayGraphqlRequest({
|
|
1004
|
+
query: mutation,
|
|
1005
|
+
variables: {
|
|
1006
|
+
id: volumeId,
|
|
1007
|
+
input: { name }
|
|
1008
|
+
},
|
|
1009
|
+
env,
|
|
1010
|
+
fetchImpl
|
|
1011
|
+
});
|
|
1012
|
+
return collectRailwayVolumes(payload.data)[0] ?? null;
|
|
1013
|
+
}
|
|
1014
|
+
async function updateRailwayVolumeInstanceMountPath({
|
|
1015
|
+
instanceId,
|
|
1016
|
+
mountPath,
|
|
1017
|
+
env = process.env,
|
|
1018
|
+
fetchImpl = fetch
|
|
1019
|
+
}) {
|
|
1020
|
+
const mutation = configuredEnvValue(env, "TREESEED_RAILWAY_VOLUME_INSTANCE_UPDATE_MUTATION") || `
|
|
1021
|
+
mutation TreeseedRailwayVolumeInstanceUpdate($id: String!, $input: VolumeInstanceUpdateInput!) {
|
|
1022
|
+
volumeInstanceUpdate(id: $id, input: $input) {
|
|
1023
|
+
id
|
|
1024
|
+
serviceId
|
|
1025
|
+
environmentId
|
|
1026
|
+
mountPath
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
`.trim();
|
|
1030
|
+
await railwayGraphqlRequest({
|
|
1031
|
+
query: mutation,
|
|
1032
|
+
variables: {
|
|
1033
|
+
id: instanceId,
|
|
1034
|
+
input: { mountPath }
|
|
1035
|
+
},
|
|
1036
|
+
env,
|
|
1037
|
+
fetchImpl
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
async function ensureRailwayServiceVolume({
|
|
1041
|
+
projectId,
|
|
1042
|
+
environmentId,
|
|
1043
|
+
serviceId,
|
|
1044
|
+
name,
|
|
1045
|
+
mountPath,
|
|
1046
|
+
env = process.env,
|
|
1047
|
+
fetchImpl = fetch
|
|
1048
|
+
}) {
|
|
1049
|
+
if (!mountPath.startsWith("/")) {
|
|
1050
|
+
throw new Error(`Railway volume mount path must be absolute: ${mountPath}`);
|
|
1051
|
+
}
|
|
1052
|
+
const volumes = await listRailwayVolumes({ projectId, env, fetchImpl });
|
|
1053
|
+
let volume = volumes.find(
|
|
1054
|
+
(candidate) => candidate.instances.some((instance2) => instance2.serviceId === serviceId && instance2.environmentId === environmentId)
|
|
1055
|
+
) ?? volumes.find((candidate) => candidate.name === name) ?? null;
|
|
1056
|
+
let created = false;
|
|
1057
|
+
let updated = false;
|
|
1058
|
+
if (!volume) {
|
|
1059
|
+
volume = await createRailwayVolume({
|
|
1060
|
+
projectId,
|
|
1061
|
+
environmentId,
|
|
1062
|
+
serviceId,
|
|
1063
|
+
name,
|
|
1064
|
+
mountPath,
|
|
1065
|
+
env,
|
|
1066
|
+
fetchImpl
|
|
1067
|
+
});
|
|
1068
|
+
created = true;
|
|
1069
|
+
}
|
|
1070
|
+
if (volume.name && volume.name !== name) {
|
|
1071
|
+
volume = await updateRailwayVolumeName({ volumeId: volume.id, name, env, fetchImpl }) ?? { ...volume, name };
|
|
1072
|
+
updated = true;
|
|
1073
|
+
}
|
|
1074
|
+
const instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? null;
|
|
1075
|
+
if (instance && instance.mountPath !== mountPath) {
|
|
1076
|
+
await updateRailwayVolumeInstanceMountPath({ instanceId: instance.id, mountPath, env, fetchImpl });
|
|
1077
|
+
volume = {
|
|
1078
|
+
...volume,
|
|
1079
|
+
instances: volume.instances.map((entry) => entry.id === instance.id ? { ...entry, mountPath } : entry)
|
|
1080
|
+
};
|
|
1081
|
+
updated = true;
|
|
1082
|
+
}
|
|
1083
|
+
return { volume, instance, created, updated };
|
|
1084
|
+
}
|
|
814
1085
|
async function listRailwayCustomDomains({
|
|
815
1086
|
projectId,
|
|
816
1087
|
environmentId,
|
|
@@ -863,6 +1134,7 @@ export {
|
|
|
863
1134
|
ensureRailwayProject,
|
|
864
1135
|
ensureRailwayService,
|
|
865
1136
|
ensureRailwayServiceInstanceConfiguration,
|
|
1137
|
+
ensureRailwayServiceVolume,
|
|
866
1138
|
getRailwayAuthProfile,
|
|
867
1139
|
getRailwayProject,
|
|
868
1140
|
getRailwayServiceInstance,
|
|
@@ -872,6 +1144,7 @@ export {
|
|
|
872
1144
|
listRailwayProjects,
|
|
873
1145
|
listRailwayServices,
|
|
874
1146
|
listRailwayVariables,
|
|
1147
|
+
listRailwayVolumes,
|
|
875
1148
|
normalizeRailwayEnvironmentName,
|
|
876
1149
|
railwayGraphqlRequest,
|
|
877
1150
|
resolveRailwayApiToken,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { type TreeseedBootstrapTaskPrefix, type TreeseedBootstrapWriter } from './bootstrap-runner.ts';
|
|
2
|
+
export declare function deriveRailwayWorkerRunnerServiceName(projectSlug: any, index?: number): string;
|
|
3
|
+
export declare function deriveRailwayWorkerRunnerVolumeName(serviceName: any): string;
|
|
4
|
+
export declare function railwayServiceRuntimeStartCommand(service: any): any;
|
|
2
5
|
export declare function isUsableRailwayToken(value: any): boolean;
|
|
3
6
|
export declare function resolveRailwayAuthToken(env?: NodeJS.ProcessEnv): string;
|
|
4
7
|
export declare function buildRailwayCommandEnv(env?: NodeJS.ProcessEnv): {
|
|
@@ -55,6 +58,10 @@ export declare function configuredRailwayServices(tenantRoot: any, scope: any):
|
|
|
55
58
|
runtimeMode: any;
|
|
56
59
|
schedule: string[];
|
|
57
60
|
hostingKind: string;
|
|
61
|
+
runnerPool: {
|
|
62
|
+
bootstrapIndex: number;
|
|
63
|
+
volumeMountPath: string;
|
|
64
|
+
} | null;
|
|
58
65
|
} | null)[];
|
|
59
66
|
export declare function configuredRailwayScheduledJobs(tenantRoot: any, scope: any, { phase }?: {
|
|
60
67
|
phase?: string | undefined;
|
|
@@ -95,6 +102,10 @@ export declare function validateRailwayServiceConfiguration(tenantRoot: any, sco
|
|
|
95
102
|
runtimeMode: any;
|
|
96
103
|
schedule: string[];
|
|
97
104
|
hostingKind: string;
|
|
105
|
+
runnerPool: {
|
|
106
|
+
bootstrapIndex: number;
|
|
107
|
+
volumeMountPath: string;
|
|
108
|
+
} | null;
|
|
98
109
|
} | null)[];
|
|
99
110
|
schedules: {
|
|
100
111
|
service: string;
|
|
@@ -134,6 +145,10 @@ export declare function validateRailwayDeployPrerequisites(tenantRoot: any, scop
|
|
|
134
145
|
runtimeMode: any;
|
|
135
146
|
schedule: string[];
|
|
136
147
|
hostingKind: string;
|
|
148
|
+
runnerPool: {
|
|
149
|
+
bootstrapIndex: number;
|
|
150
|
+
volumeMountPath: string;
|
|
151
|
+
} | null;
|
|
137
152
|
} | null)[];
|
|
138
153
|
schedules: {
|
|
139
154
|
service: string;
|
|
@@ -296,5 +311,12 @@ export declare function deployRailwayService(tenantRoot: any, service: any, { dr
|
|
|
296
311
|
healthcheckPath: string | null;
|
|
297
312
|
healthcheckTimeoutSeconds: number | null;
|
|
298
313
|
runtimeMode: string | null;
|
|
314
|
+
volume: {
|
|
315
|
+
id: string;
|
|
316
|
+
name: string;
|
|
317
|
+
mountPath: any;
|
|
318
|
+
created: boolean;
|
|
319
|
+
updated: boolean;
|
|
320
|
+
} | null;
|
|
299
321
|
} | null;
|
|
300
322
|
}>;
|