@treeseed/sdk 0.10.5 → 0.10.7
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 +58 -0
- package/dist/operations/repository-operations.d.ts +129 -0
- package/dist/operations/repository-operations.js +634 -0
- package/dist/operations/services/config-runtime.d.ts +7 -6
- package/dist/operations/services/config-runtime.js +45 -25
- package/dist/operations/services/deploy.d.ts +42 -0
- package/dist/operations/services/deploy.js +1 -1
- package/dist/operations/services/project-platform.d.ts +41 -1
- package/dist/operations/services/project-platform.js +13 -0
- package/dist/operations/services/railway-api.d.ts +35 -1
- package/dist/operations/services/railway-api.js +240 -35
- package/dist/operations/services/railway-deploy.d.ts +16 -234
- package/dist/operations/services/railway-deploy.js +177 -62
- package/dist/operations/services/release-candidate.js +1 -2
- package/dist/operations/services/runtime-tools.d.ts +14 -0
- package/dist/operations/services/runtime-tools.js +15 -1
- package/dist/operations/services/workspace-save.d.ts +24 -0
- package/dist/operations/services/workspace-save.js +143 -3
- package/dist/operations/services/workspace-tools.js +1 -1
- package/dist/platform/env.yaml +163 -2
- package/dist/platform/environment.d.ts +1 -0
- package/dist/platform/environment.js +9 -0
- package/dist/platform-operation-store.d.ts +90 -0
- package/dist/platform-operation-store.js +505 -0
- package/dist/platform-operations.d.ts +265 -0
- package/dist/platform-operations.js +421 -0
- package/dist/reconcile/bootstrap-systems.js +3 -3
- package/dist/reconcile/builtin-adapters.js +225 -29
- package/dist/reconcile/contracts.d.ts +1 -1
- package/dist/reconcile/desired-state.d.ts +14 -0
- package/dist/reconcile/desired-state.js +4 -0
- package/dist/reconcile/engine.d.ts +28 -0
- package/dist/reconcile/state.js +3 -0
- package/dist/reconcile/units.js +2 -0
- package/dist/workflow/operations.d.ts +13 -5
- package/dist/workflow/operations.js +69 -12
- package/dist/workflow-state.d.ts +2 -0
- package/dist/workflow-state.js +7 -2
- package/dist/workflow.d.ts +2 -0
- package/package.json +15 -2
|
@@ -5,7 +5,7 @@ const SYSTEM_DEPENDENCIES = {
|
|
|
5
5
|
data: [],
|
|
6
6
|
web: ["data"],
|
|
7
7
|
api: ["data"],
|
|
8
|
-
agents: [
|
|
8
|
+
agents: []
|
|
9
9
|
};
|
|
10
10
|
function uniqueSystems(values) {
|
|
11
11
|
return [...new Set(values)];
|
|
@@ -55,8 +55,8 @@ function agentsSystemDisabled(config) {
|
|
|
55
55
|
if (config.runtime?.mode === "none") {
|
|
56
56
|
return "runtime.mode is none.";
|
|
57
57
|
}
|
|
58
|
-
const enabled = ["workdayManager", "workerRunner"].some((serviceKey) => serviceEnabled(config, serviceKey));
|
|
59
|
-
return enabled ? null : "No agent Railway services are enabled.";
|
|
58
|
+
const enabled = ["marketOperationsRunner", "workdayManager", "workerRunner"].some((serviceKey) => serviceEnabled(config, serviceKey));
|
|
59
|
+
return enabled ? null : "No agent or market operations runner Railway services are enabled.";
|
|
60
60
|
}
|
|
61
61
|
function hasValue(env, key) {
|
|
62
62
|
return typeof env[key] === "string" && String(env[key]).trim().length > 0;
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from "../operations/services/deploy.js";
|
|
31
31
|
import {
|
|
32
32
|
configuredRailwayServices,
|
|
33
|
+
deriveRailwayMarketOperationsRunnerVolumeName,
|
|
33
34
|
ensureRailwayProjectContext,
|
|
34
35
|
runRailway,
|
|
35
36
|
validateRailwayDeployPrerequisites
|
|
@@ -37,15 +38,19 @@ import {
|
|
|
37
38
|
import { shouldExposeManagedHostRuntimeSecret } from "../operations/services/managed-host-security.js";
|
|
38
39
|
import {
|
|
39
40
|
ensureRailwayEnvironment,
|
|
41
|
+
ensureRailwayCustomDomain as ensureRailwayCustomDomainViaApi,
|
|
40
42
|
ensureRailwayProject,
|
|
41
43
|
ensureRailwayService,
|
|
42
44
|
ensureRailwayServiceInstanceConfiguration,
|
|
45
|
+
ensureRailwayServiceVolume,
|
|
43
46
|
getRailwayServiceInstance,
|
|
44
47
|
getRailwayProject,
|
|
45
48
|
listRailwayCustomDomains,
|
|
46
49
|
listRailwayProjects,
|
|
50
|
+
listRailwayVolumes,
|
|
47
51
|
listRailwayVariables,
|
|
48
52
|
resolveRailwayWorkspaceContext,
|
|
53
|
+
updateRailwayServiceName,
|
|
49
54
|
upsertRailwayVariables
|
|
50
55
|
} from "../operations/services/railway-api.js";
|
|
51
56
|
import { loadTreeseedReconcileState } from "./state.js";
|
|
@@ -93,6 +98,28 @@ function noopDiff() {
|
|
|
93
98
|
after: {}
|
|
94
99
|
};
|
|
95
100
|
}
|
|
101
|
+
function parseRailwayJsonPayload(output) {
|
|
102
|
+
const text = typeof output === "string" ? output.trim() : "";
|
|
103
|
+
if (!text) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(text);
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
const lines = text.split(/\r?\n/u);
|
|
111
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
112
|
+
const candidate = lines.slice(index).join("\n").trim();
|
|
113
|
+
if (!candidate.startsWith("{") && !candidate.startsWith("[")) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(candidate);
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
96
123
|
function buildCompositeAdapter(unitType) {
|
|
97
124
|
return {
|
|
98
125
|
providerId: "treeseed",
|
|
@@ -169,10 +196,17 @@ function resolveReconcileEnvironmentValues(input, scope) {
|
|
|
169
196
|
if (scope === "local") {
|
|
170
197
|
return resolveTreeseedMachineEnvironmentValues(input.context.tenantRoot, scope);
|
|
171
198
|
}
|
|
172
|
-
const
|
|
199
|
+
const hostedRuntimeValues = {
|
|
173
200
|
...normalizeEnvironmentValues(process.env),
|
|
174
201
|
...normalizeEnvironmentValues(input.context.launchEnv)
|
|
175
202
|
};
|
|
203
|
+
if (hostedRuntimeValues.CLOUDFLARE_API_TOKEN || hostedRuntimeValues.RAILWAY_API_TOKEN || hostedRuntimeValues.GH_TOKEN) {
|
|
204
|
+
return hostedRuntimeValues;
|
|
205
|
+
}
|
|
206
|
+
const values = {
|
|
207
|
+
...resolveTreeseedMachineEnvironmentValues(input.context.tenantRoot, scope),
|
|
208
|
+
...hostedRuntimeValues
|
|
209
|
+
};
|
|
176
210
|
return values;
|
|
177
211
|
}
|
|
178
212
|
function buildCloudflareEnv(input) {
|
|
@@ -494,15 +528,15 @@ function normalizeRailwayDomainPayload(value) {
|
|
|
494
528
|
}
|
|
495
529
|
async function ensureRailwayCustomDomain(input, service, domain, env, identifiers) {
|
|
496
530
|
if (identifiers?.projectId && identifiers?.environmentId && identifiers?.serviceId) {
|
|
497
|
-
const
|
|
531
|
+
const ensured = await ensureRailwayCustomDomainViaApi({
|
|
498
532
|
projectId: identifiers.projectId,
|
|
499
533
|
environmentId: identifiers.environmentId,
|
|
500
534
|
serviceId: identifiers.serviceId,
|
|
535
|
+
domain,
|
|
501
536
|
env
|
|
502
537
|
});
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
return matched;
|
|
538
|
+
if (ensured.domain) {
|
|
539
|
+
return ensured.domain;
|
|
506
540
|
}
|
|
507
541
|
}
|
|
508
542
|
ensureRailwayProjectContext(service, { env, capture: true });
|
|
@@ -1399,6 +1433,9 @@ async function resolveRailwayTopologyForScope(input, scope, {
|
|
|
1399
1433
|
if (!project) {
|
|
1400
1434
|
continue;
|
|
1401
1435
|
}
|
|
1436
|
+
if (typeof project.deletedAt === "string" && project.deletedAt.trim()) {
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1402
1439
|
projectsByKey.set(project.id, project);
|
|
1403
1440
|
projectsByKey.set(project.name, project);
|
|
1404
1441
|
}
|
|
@@ -1512,31 +1549,58 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false } = {}) {
|
|
|
1512
1549
|
includeInstances: !dryRun,
|
|
1513
1550
|
includeVariables: false
|
|
1514
1551
|
});
|
|
1515
|
-
const workerEntry = topology.services.get("workerRunner") ?? topology.services.get("worker") ?? null;
|
|
1516
|
-
const railwayRuntimeVariables = Object.fromEntries(
|
|
1517
|
-
[
|
|
1518
|
-
["TREESEED_RAILWAY_PROJECT_ID", workerEntry?.project?.id],
|
|
1519
|
-
["TREESEED_RAILWAY_ENVIRONMENT_ID", workerEntry?.environment?.id],
|
|
1520
|
-
["TREESEED_RAILWAY_WORKER_SERVICE_ID", workerEntry?.service?.id]
|
|
1521
|
-
].filter((entry) => typeof entry[1] === "string" && entry[1].length > 0)
|
|
1522
|
-
);
|
|
1523
1552
|
if (!dryRun) {
|
|
1524
|
-
|
|
1525
|
-
...sync.variables,
|
|
1526
|
-
...railwayRuntimeVariables,
|
|
1527
|
-
...sync.secrets
|
|
1528
|
-
};
|
|
1553
|
+
await ensureRailwayMarketDatabaseForScope(input, topology);
|
|
1529
1554
|
for (const entry of topology.services.values()) {
|
|
1530
1555
|
if (!entry.project || !entry.environment || !entry.service) {
|
|
1531
1556
|
continue;
|
|
1532
1557
|
}
|
|
1558
|
+
const serviceSync = sync.forService(entry.configuredService.key);
|
|
1533
1559
|
await upsertRailwayVariables({
|
|
1534
1560
|
projectId: entry.project.id,
|
|
1535
1561
|
environmentId: entry.environment.id,
|
|
1536
1562
|
serviceId: entry.service.id,
|
|
1537
|
-
variables:
|
|
1563
|
+
variables: {
|
|
1564
|
+
...serviceSync.variables,
|
|
1565
|
+
...serviceSync.secrets
|
|
1566
|
+
},
|
|
1538
1567
|
env: topology.env
|
|
1539
1568
|
});
|
|
1569
|
+
if (entry.configuredService.volumeMountPath) {
|
|
1570
|
+
const volumeName = entry.configuredService.key === "marketOperationsRunner" ? deriveRailwayMarketOperationsRunnerVolumeName(entry.service.name, entry.environment.name) : `${entry.service.name}-volume`;
|
|
1571
|
+
const volume = await ensureRailwayServiceVolume({
|
|
1572
|
+
projectId: entry.project.id,
|
|
1573
|
+
environmentId: entry.environment.id,
|
|
1574
|
+
serviceId: entry.service.id,
|
|
1575
|
+
name: volumeName,
|
|
1576
|
+
mountPath: entry.configuredService.volumeMountPath,
|
|
1577
|
+
env: topology.env
|
|
1578
|
+
});
|
|
1579
|
+
if (!volume.instance?.serviceId) {
|
|
1580
|
+
ensureRailwayProjectContext(entry.configuredService, { env: topology.env, capture: true });
|
|
1581
|
+
const attachResult = runRailway([
|
|
1582
|
+
"volume",
|
|
1583
|
+
"--service",
|
|
1584
|
+
entry.service.id,
|
|
1585
|
+
"attach",
|
|
1586
|
+
"--volume",
|
|
1587
|
+
volume.volume.id,
|
|
1588
|
+
"--yes",
|
|
1589
|
+
"--json"
|
|
1590
|
+
], {
|
|
1591
|
+
cwd: entry.configuredService.rootDir,
|
|
1592
|
+
capture: true,
|
|
1593
|
+
allowFailure: true,
|
|
1594
|
+
env: topology.env
|
|
1595
|
+
});
|
|
1596
|
+
if ((attachResult.status ?? 1) !== 0) {
|
|
1597
|
+
const attachMessage = attachResult.stderr?.trim() || attachResult.stdout?.trim() || "";
|
|
1598
|
+
if (!/already mounted/iu.test(attachMessage)) {
|
|
1599
|
+
throw new Error(attachMessage || `Railway volume attach failed for ${entry.service.name}.`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1540
1604
|
}
|
|
1541
1605
|
}
|
|
1542
1606
|
return {
|
|
@@ -1548,6 +1612,62 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false } = {}) {
|
|
|
1548
1612
|
workspace: topology.workspace.name
|
|
1549
1613
|
};
|
|
1550
1614
|
}
|
|
1615
|
+
async function ensureRailwayMarketDatabaseForScope(input, topology) {
|
|
1616
|
+
const marketDatabaseService = input.context.deployConfig.services?.marketDatabase;
|
|
1617
|
+
if (!marketDatabaseService || marketDatabaseService.enabled === false || marketDatabaseService.provider !== "railway" || marketDatabaseService.railway?.resourceType !== "postgres") {
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
const firstService = [...topology.services.values()].find((entry) => entry.project && entry.environment);
|
|
1621
|
+
if (!firstService?.project || !firstService.environment) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const baseName = typeof marketDatabaseService.railway?.serviceName === "string" && marketDatabaseService.railway.serviceName.trim() ? marketDatabaseService.railway.serviceName.trim() : `${input.context.deployConfig.slug ?? "treeseed-market"}-postgres`;
|
|
1625
|
+
const serviceName = `${baseName.replace(/-(staging|prod|production)$/u, "")}-${topology.scope === "prod" ? "prod" : topology.scope}`;
|
|
1626
|
+
let postgresService = firstService.project.services.find((entry) => entry.name === serviceName || entry.id === serviceName) ?? null;
|
|
1627
|
+
if (!postgresService) {
|
|
1628
|
+
ensureRailwayProjectContext(firstService.configuredService, { env: topology.env, capture: true });
|
|
1629
|
+
const addResult = runRailway(["add", "--database", "postgres", "--json"], {
|
|
1630
|
+
cwd: firstService.configuredService.rootDir,
|
|
1631
|
+
capture: true,
|
|
1632
|
+
allowFailure: true,
|
|
1633
|
+
env: topology.env
|
|
1634
|
+
});
|
|
1635
|
+
if ((addResult.status ?? 1) !== 0) {
|
|
1636
|
+
throw new Error(addResult.stderr?.trim() || addResult.stdout?.trim() || `Railway database provisioning failed for ${serviceName}.`);
|
|
1637
|
+
}
|
|
1638
|
+
const payload = parseRailwayJsonPayload(addResult.stdout);
|
|
1639
|
+
const createdServiceId = typeof payload?.serviceId === "string" && payload.serviceId.trim() ? payload.serviceId.trim() : "";
|
|
1640
|
+
if (!createdServiceId) {
|
|
1641
|
+
throw new Error(`Railway database provisioning did not return a service id for ${serviceName}.`);
|
|
1642
|
+
}
|
|
1643
|
+
postgresService = await updateRailwayServiceName({
|
|
1644
|
+
serviceId: createdServiceId,
|
|
1645
|
+
name: serviceName,
|
|
1646
|
+
env: topology.env
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
1650
|
+
const existingVolumes = await listRailwayVolumes({
|
|
1651
|
+
projectId: firstService.project.id,
|
|
1652
|
+
env: topology.env
|
|
1653
|
+
});
|
|
1654
|
+
const attached = existingVolumes.some((volume) => volume.instances.some(
|
|
1655
|
+
(instance) => instance.serviceId === postgresService.id && instance.environmentId === firstService.environment?.id && instance.mountPath === "/var/lib/postgresql/data"
|
|
1656
|
+
));
|
|
1657
|
+
if (attached) {
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1660
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1500));
|
|
1661
|
+
}
|
|
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
|
+
}
|
|
1551
1671
|
async function observeRailwayUnit(input, { refresh = false } = {}) {
|
|
1552
1672
|
let attempt = 0;
|
|
1553
1673
|
for (; ; ) {
|
|
@@ -1600,17 +1720,27 @@ function collectRailwayEnvironmentSync(input) {
|
|
|
1600
1720
|
const values = resolveReconcileEnvironmentValues(input, scope);
|
|
1601
1721
|
const registry = collectTreeseedEnvironmentContext(input.context.tenantRoot);
|
|
1602
1722
|
const state = loadDeployState(input.context.tenantRoot, input.context.deployConfig, { target: toDeployTarget(input.context.target) });
|
|
1603
|
-
const
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
);
|
|
1723
|
+
const serviceTargetsEntry = (entry, serviceKey) => {
|
|
1724
|
+
const targets = Array.isArray(entry.serviceTargets) ? entry.serviceTargets.map((value) => String(value).trim()).filter(Boolean) : [];
|
|
1725
|
+
return targets.length === 0 || targets.includes(serviceKey);
|
|
1726
|
+
};
|
|
1727
|
+
const entriesForService = (target, serviceKey) => registry.entries.filter((entry) => entry.scopes.includes(scope) && entry.targets.includes(target) && serviceTargetsEntry(entry, serviceKey) && (target !== "railway-secret" || shouldExposeManagedHostRuntimeSecret(input.context.deployConfig, entry.id))).map((entry) => [entry.id, values[entry.id]]).filter(([, value]) => typeof value === "string" && value.length > 0);
|
|
1728
|
+
const aggregateEntries = (target) => registry.entries.filter((entry) => entry.scopes.includes(scope) && entry.targets.includes(target) && (target !== "railway-secret" || shouldExposeManagedHostRuntimeSecret(input.context.deployConfig, entry.id))).map((entry) => [entry.id, values[entry.id]]).filter(([, value]) => typeof value === "string" && value.length > 0);
|
|
1729
|
+
const secrets = Object.fromEntries(aggregateEntries("railway-secret"));
|
|
1730
|
+
const variables = Object.fromEntries(aggregateEntries("railway-var"));
|
|
1731
|
+
const apiOnlySecrets = {};
|
|
1732
|
+
const apiOnlyVariables = {};
|
|
1733
|
+
const marketDatabaseService = input.context.deployConfig.services?.marketDatabase;
|
|
1734
|
+
const marketDatabaseBaseName = typeof marketDatabaseService?.railway?.serviceName === "string" && marketDatabaseService.railway.serviceName.trim() ? marketDatabaseService.railway.serviceName.trim() : `${input.context.deployConfig.slug ?? "treeseed-market"}-postgres`;
|
|
1735
|
+
const marketDatabaseServiceName = `${marketDatabaseBaseName.replace(/-(staging|prod|production)$/u, "")}-${scope === "prod" ? "prod" : scope}`;
|
|
1736
|
+
const marketDatabaseUrl = typeof values.TREESEED_MARKET_DATABASE_URL === "string" && values.TREESEED_MARKET_DATABASE_URL.length > 0 ? values.TREESEED_MARKET_DATABASE_URL : marketDatabaseService?.enabled !== false && marketDatabaseService?.provider === "railway" ? `\${{${marketDatabaseServiceName}.DATABASE_URL}}` : "";
|
|
1609
1737
|
if (typeof values.CLOUDFLARE_API_TOKEN === "string" && values.CLOUDFLARE_API_TOKEN.length > 0 && shouldExposeManagedHostRuntimeSecret(input.context.deployConfig, "CLOUDFLARE_API_TOKEN")) {
|
|
1610
1738
|
secrets.CLOUDFLARE_API_TOKEN = values.CLOUDFLARE_API_TOKEN;
|
|
1739
|
+
apiOnlySecrets.CLOUDFLARE_API_TOKEN = values.CLOUDFLARE_API_TOKEN;
|
|
1611
1740
|
}
|
|
1612
1741
|
if (typeof values.CLOUDFLARE_ACCOUNT_ID === "string" && values.CLOUDFLARE_ACCOUNT_ID.length > 0) {
|
|
1613
1742
|
variables.CLOUDFLARE_ACCOUNT_ID = values.CLOUDFLARE_ACCOUNT_ID;
|
|
1743
|
+
apiOnlyVariables.CLOUDFLARE_ACCOUNT_ID = values.CLOUDFLARE_ACCOUNT_ID;
|
|
1614
1744
|
}
|
|
1615
1745
|
const siteDataDb = state.d1Databases?.SITE_DATA_DB;
|
|
1616
1746
|
let apiD1DatabaseId = siteDataDb?.databaseId;
|
|
@@ -1620,8 +1750,29 @@ function collectRailwayEnvironmentSync(input) {
|
|
|
1620
1750
|
}
|
|
1621
1751
|
if (hasLiveResourceId(apiD1DatabaseId)) {
|
|
1622
1752
|
variables.TREESEED_API_D1_DATABASE_ID = apiD1DatabaseId;
|
|
1753
|
+
apiOnlyVariables.TREESEED_API_D1_DATABASE_ID = apiD1DatabaseId;
|
|
1623
1754
|
}
|
|
1624
|
-
|
|
1755
|
+
if (marketDatabaseUrl) {
|
|
1756
|
+
secrets.TREESEED_MARKET_DATABASE_URL = marketDatabaseUrl;
|
|
1757
|
+
}
|
|
1758
|
+
return {
|
|
1759
|
+
scope,
|
|
1760
|
+
secrets,
|
|
1761
|
+
variables,
|
|
1762
|
+
forService(serviceKey) {
|
|
1763
|
+
return {
|
|
1764
|
+
secrets: {
|
|
1765
|
+
...Object.fromEntries(entriesForService("railway-secret", serviceKey)),
|
|
1766
|
+
...marketDatabaseUrl && ["api", "marketOperationsRunner"].includes(serviceKey) ? { TREESEED_MARKET_DATABASE_URL: marketDatabaseUrl } : {},
|
|
1767
|
+
...serviceKey === "api" ? apiOnlySecrets : {}
|
|
1768
|
+
},
|
|
1769
|
+
variables: {
|
|
1770
|
+
...Object.fromEntries(entriesForService("railway-var", serviceKey)),
|
|
1771
|
+
...serviceKey === "api" ? apiOnlyVariables : {}
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1625
1776
|
}
|
|
1626
1777
|
function buildAttachmentDiff(input, observed) {
|
|
1627
1778
|
if (!observed.exists) {
|
|
@@ -1943,7 +2094,7 @@ async function verifyRailwayUnit(input) {
|
|
|
1943
2094
|
})
|
|
1944
2095
|
]);
|
|
1945
2096
|
}
|
|
1946
|
-
const sync = collectRailwayEnvironmentSync(input);
|
|
2097
|
+
const sync = collectRailwayEnvironmentSync(input).forService(serviceKey);
|
|
1947
2098
|
const checks = [
|
|
1948
2099
|
verificationCheck("railway.workspace", "Railway workspace is resolved", "api", {
|
|
1949
2100
|
exists: Boolean(topology.workspace.id),
|
|
@@ -1977,12 +2128,13 @@ async function verifyRailwayUnit(input) {
|
|
|
1977
2128
|
})
|
|
1978
2129
|
];
|
|
1979
2130
|
if (service.startCommand) {
|
|
2131
|
+
const startCommandMatches = railwayStartCommandMatches(serviceKey, entry.instance?.startCommand, service.startCommand);
|
|
1980
2132
|
checks.push(verificationCheck("railway.instance.start-command", "Railway start command matches desired config", "api", {
|
|
1981
2133
|
exists: Boolean(entry.instance?.id),
|
|
1982
|
-
configured:
|
|
2134
|
+
configured: startCommandMatches,
|
|
1983
2135
|
expected: service.startCommand,
|
|
1984
2136
|
observed: entry.instance?.startCommand ?? null,
|
|
1985
|
-
issues:
|
|
2137
|
+
issues: startCommandMatches ? [] : ["Railway start command does not match the desired value."]
|
|
1986
2138
|
}));
|
|
1987
2139
|
}
|
|
1988
2140
|
const desiredRootDirectory = relativeRailwayRootDir(input.context.tenantRoot, service.rootDir);
|
|
@@ -2050,6 +2202,24 @@ async function verifyRailwayUnit(input) {
|
|
|
2050
2202
|
}));
|
|
2051
2203
|
}
|
|
2052
2204
|
}
|
|
2205
|
+
if (service.volumeMountPath) {
|
|
2206
|
+
const volumes = entry.project?.id ? await listRailwayVolumes({ projectId: entry.project.id, env: topology.env }) : [];
|
|
2207
|
+
const expectedServiceId = entry.service?.id ?? null;
|
|
2208
|
+
const expectedEnvironmentId = entry.environment?.id ?? null;
|
|
2209
|
+
const mountedVolume = volumes.find((volume) => volume.instances.some(
|
|
2210
|
+
(instance) => instance.serviceId === expectedServiceId && instance.environmentId === expectedEnvironmentId && instance.mountPath === service.volumeMountPath
|
|
2211
|
+
)) ?? null;
|
|
2212
|
+
checks.push(verificationCheck("railway.volume:data", "Railway service has persistent data volume mounted", "api", {
|
|
2213
|
+
exists: Boolean(mountedVolume),
|
|
2214
|
+
configured: Boolean(mountedVolume),
|
|
2215
|
+
expected: service.volumeMountPath,
|
|
2216
|
+
observed: mountedVolume ? {
|
|
2217
|
+
name: mountedVolume.name,
|
|
2218
|
+
mountPath: service.volumeMountPath
|
|
2219
|
+
} : null,
|
|
2220
|
+
issues: mountedVolume ? [] : [`Railway service ${service.serviceName ?? service.key} is missing a persistent volume mounted at ${service.volumeMountPath}.`]
|
|
2221
|
+
}));
|
|
2222
|
+
}
|
|
2053
2223
|
for (const [key, value] of Object.entries(sync.variables)) {
|
|
2054
2224
|
checks.push(verificationCheck(`railway.var:${key}`, `Railway variable ${key} exists with the expected value`, "api", {
|
|
2055
2225
|
exists: Object.hasOwn(entry.currentVariables, key),
|
|
@@ -2077,6 +2247,30 @@ async function verifyRailwayUnit(input) {
|
|
|
2077
2247
|
}
|
|
2078
2248
|
}
|
|
2079
2249
|
}
|
|
2250
|
+
function railwayStartCommandMatches(serviceKey, observed, expected) {
|
|
2251
|
+
if (observed === expected) {
|
|
2252
|
+
return true;
|
|
2253
|
+
}
|
|
2254
|
+
if (serviceKey !== "marketOperationsRunner") {
|
|
2255
|
+
return false;
|
|
2256
|
+
}
|
|
2257
|
+
const normalizedObserved = String(observed ?? "").trim().replace(/\s+/gu, " ");
|
|
2258
|
+
const normalizedExpected = String(expected ?? "").trim().replace(/\s+/gu, " ");
|
|
2259
|
+
if (normalizedObserved === normalizedExpected) {
|
|
2260
|
+
return true;
|
|
2261
|
+
}
|
|
2262
|
+
const allowedInlineEnv = [
|
|
2263
|
+
"TREESEED_MARKET_ID",
|
|
2264
|
+
"TREESEED_PLATFORM_RUNNER_ID",
|
|
2265
|
+
"TREESEED_PLATFORM_RUNNER_DATA_DIR",
|
|
2266
|
+
"TREESEED_PLATFORM_RUNNER_ENVIRONMENT"
|
|
2267
|
+
];
|
|
2268
|
+
let remainder = normalizedObserved;
|
|
2269
|
+
for (const key of allowedInlineEnv) {
|
|
2270
|
+
remainder = remainder.replace(new RegExp(`^${key}=[^\\s]+\\s+`, "u"), "");
|
|
2271
|
+
}
|
|
2272
|
+
return remainder === normalizedExpected;
|
|
2273
|
+
}
|
|
2080
2274
|
function buildRailwayDiff(input, observed) {
|
|
2081
2275
|
if (!observed.exists) {
|
|
2082
2276
|
return {
|
|
@@ -2230,10 +2424,12 @@ function createCloudflareReconcileAdapters() {
|
|
|
2230
2424
|
function createRailwayReconcileAdapters() {
|
|
2231
2425
|
return [
|
|
2232
2426
|
buildRailwayAdapter("railway-service:api"),
|
|
2427
|
+
buildRailwayAdapter("railway-service:market-operations-runner"),
|
|
2233
2428
|
buildRailwayAdapter("railway-service:workday-manager"),
|
|
2234
2429
|
buildRailwayAdapter("railway-service:worker-runner"),
|
|
2235
2430
|
buildCustomDomainAdapter("custom-domain:api", "railway"),
|
|
2236
2431
|
buildCompositeAdapter("api-runtime"),
|
|
2432
|
+
buildCompositeAdapter("market-operations-runner-runtime"),
|
|
2237
2433
|
buildCompositeAdapter("workday-manager-runtime"),
|
|
2238
2434
|
buildCompositeAdapter("worker-runner-runtime")
|
|
2239
2435
|
];
|
|
@@ -3,7 +3,7 @@ export type TreeseedReconcileProviderId = string;
|
|
|
3
3
|
export type TreeseedReconcileActionKind = 'noop' | 'create' | 'update' | 'reuse' | 'drift_correct' | 'destroy';
|
|
4
4
|
export type TreeseedReconcileStatusKind = 'pending' | 'ready' | 'drifted' | 'error';
|
|
5
5
|
export type TreeseedReconcileVerificationSource = 'cli' | 'api' | 'sdk' | 'derived';
|
|
6
|
-
export type TreeseedReconcileUnitType = 'web-ui' | 'api-runtime' | 'workday-manager-runtime' | 'worker-runner-runtime' | 'edge-worker' | 'content-store' | 'queue' | 'database' | 'kv-form-guard' | 'pages-project' | 'custom-domain:web' | 'custom-domain:api' | 'dns-record' | 'railway-service:api' | 'railway-service:workday-manager' | 'railway-service:worker-runner';
|
|
6
|
+
export type TreeseedReconcileUnitType = 'web-ui' | 'api-runtime' | 'market-operations-runner-runtime' | 'workday-manager-runtime' | 'worker-runner-runtime' | 'edge-worker' | 'content-store' | 'queue' | 'database' | 'kv-form-guard' | 'pages-project' | 'custom-domain:web' | 'custom-domain:api' | 'dns-record' | 'railway-service:api' | 'railway-service:market-operations-runner' | 'railway-service:workday-manager' | 'railway-service:worker-runner';
|
|
7
7
|
export type TreeseedReconcileTarget = {
|
|
8
8
|
kind: 'persistent';
|
|
9
9
|
scope: 'local' | 'staging' | 'prod';
|
|
@@ -149,9 +149,23 @@ export declare function deriveTreeseedDesiredUnits({ tenantRoot, target, }: {
|
|
|
149
149
|
projectName: string | undefined;
|
|
150
150
|
serviceId: string | undefined;
|
|
151
151
|
serviceName: string | undefined;
|
|
152
|
+
resourceType: string | undefined;
|
|
153
|
+
environmentVariable: string | undefined;
|
|
154
|
+
serviceTargets: any;
|
|
152
155
|
rootDir: string | undefined;
|
|
153
156
|
buildCommand: string | undefined;
|
|
154
157
|
startCommand: string | undefined;
|
|
158
|
+
healthcheckPath: string | undefined;
|
|
159
|
+
healthcheckTimeoutSeconds: number | undefined;
|
|
160
|
+
healthcheckIntervalSeconds: number | undefined;
|
|
161
|
+
restartPolicy: string | undefined;
|
|
162
|
+
runtimeMode: string | undefined;
|
|
163
|
+
volumeMountPath: string | undefined;
|
|
164
|
+
runnerPool: {
|
|
165
|
+
bootstrapCount: number | undefined;
|
|
166
|
+
maxRunners: number | undefined;
|
|
167
|
+
volumeMountPath: string | undefined;
|
|
168
|
+
} | undefined;
|
|
155
169
|
schedule: any;
|
|
156
170
|
};
|
|
157
171
|
environments: {
|
|
@@ -11,6 +11,8 @@ function railwayConcreteUnitTypeForServiceKey(serviceKey) {
|
|
|
11
11
|
switch (serviceKey) {
|
|
12
12
|
case "api":
|
|
13
13
|
return "railway-service:api";
|
|
14
|
+
case "marketOperationsRunner":
|
|
15
|
+
return "railway-service:market-operations-runner";
|
|
14
16
|
case "workdayManager":
|
|
15
17
|
return "railway-service:workday-manager";
|
|
16
18
|
case "workerRunner":
|
|
@@ -266,6 +268,8 @@ function deriveTreeseedDesiredUnits({
|
|
|
266
268
|
switch (serviceKey) {
|
|
267
269
|
case "api":
|
|
268
270
|
return "api-runtime";
|
|
271
|
+
case "marketOperationsRunner":
|
|
272
|
+
return "market-operations-runner-runtime";
|
|
269
273
|
case "workdayManager":
|
|
270
274
|
return "workday-manager-runtime";
|
|
271
275
|
case "workerRunner":
|
|
@@ -156,9 +156,23 @@ export declare function observeTreeseedUnits({ tenantRoot, target, env, systems,
|
|
|
156
156
|
projectName: string | undefined;
|
|
157
157
|
serviceId: string | undefined;
|
|
158
158
|
serviceName: string | undefined;
|
|
159
|
+
resourceType: string | undefined;
|
|
160
|
+
environmentVariable: string | undefined;
|
|
161
|
+
serviceTargets: any;
|
|
159
162
|
rootDir: string | undefined;
|
|
160
163
|
buildCommand: string | undefined;
|
|
161
164
|
startCommand: string | undefined;
|
|
165
|
+
healthcheckPath: string | undefined;
|
|
166
|
+
healthcheckTimeoutSeconds: number | undefined;
|
|
167
|
+
healthcheckIntervalSeconds: number | undefined;
|
|
168
|
+
restartPolicy: string | undefined;
|
|
169
|
+
runtimeMode: string | undefined;
|
|
170
|
+
volumeMountPath: string | undefined;
|
|
171
|
+
runnerPool: {
|
|
172
|
+
bootstrapCount: number | undefined;
|
|
173
|
+
maxRunners: number | undefined;
|
|
174
|
+
volumeMountPath: string | undefined;
|
|
175
|
+
} | undefined;
|
|
162
176
|
schedule: any;
|
|
163
177
|
};
|
|
164
178
|
environments: {
|
|
@@ -345,9 +359,23 @@ export declare function planTreeseedReconciliation({ tenantRoot, target, env, sy
|
|
|
345
359
|
projectName: string | undefined;
|
|
346
360
|
serviceId: string | undefined;
|
|
347
361
|
serviceName: string | undefined;
|
|
362
|
+
resourceType: string | undefined;
|
|
363
|
+
environmentVariable: string | undefined;
|
|
364
|
+
serviceTargets: any;
|
|
348
365
|
rootDir: string | undefined;
|
|
349
366
|
buildCommand: string | undefined;
|
|
350
367
|
startCommand: string | undefined;
|
|
368
|
+
healthcheckPath: string | undefined;
|
|
369
|
+
healthcheckTimeoutSeconds: number | undefined;
|
|
370
|
+
healthcheckIntervalSeconds: number | undefined;
|
|
371
|
+
restartPolicy: string | undefined;
|
|
372
|
+
runtimeMode: string | undefined;
|
|
373
|
+
volumeMountPath: string | undefined;
|
|
374
|
+
runnerPool: {
|
|
375
|
+
bootstrapCount: number | undefined;
|
|
376
|
+
maxRunners: number | undefined;
|
|
377
|
+
volumeMountPath: string | undefined;
|
|
378
|
+
} | undefined;
|
|
351
379
|
schedule: any;
|
|
352
380
|
};
|
|
353
381
|
environments: {
|
package/dist/reconcile/state.js
CHANGED
|
@@ -6,6 +6,9 @@ function stableHash(value) {
|
|
|
6
6
|
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
7
7
|
}
|
|
8
8
|
function railwayUnitTypeForServiceKey(serviceKey) {
|
|
9
|
+
if (serviceKey === "marketOperationsRunner") {
|
|
10
|
+
return "railway-service:market-operations-runner";
|
|
11
|
+
}
|
|
9
12
|
if (serviceKey === "workdayManager") {
|
|
10
13
|
return "railway-service:workday-manager";
|
|
11
14
|
}
|
package/dist/reconcile/units.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const TRESEED_RECONCILE_UNIT_TYPES = [
|
|
2
2
|
"web-ui",
|
|
3
3
|
"api-runtime",
|
|
4
|
+
"market-operations-runner-runtime",
|
|
4
5
|
"workday-manager-runtime",
|
|
5
6
|
"worker-runner-runtime",
|
|
6
7
|
"edge-worker",
|
|
@@ -13,6 +14,7 @@ const TRESEED_RECONCILE_UNIT_TYPES = [
|
|
|
13
14
|
"custom-domain:api",
|
|
14
15
|
"dns-record",
|
|
15
16
|
"railway-service:api",
|
|
17
|
+
"railway-service:market-operations-runner",
|
|
16
18
|
"railway-service:workday-manager",
|
|
17
19
|
"railway-service:worker-runner"
|
|
18
20
|
];
|
|
@@ -871,16 +871,24 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
871
871
|
mode: TreeseedWorkflowMode;
|
|
872
872
|
mergeStrategy: string;
|
|
873
873
|
level: string;
|
|
874
|
+
releaseLine: {
|
|
875
|
+
group: string[];
|
|
876
|
+
repair: boolean;
|
|
877
|
+
targetLine: string;
|
|
878
|
+
highestCurrentLine: string;
|
|
879
|
+
alignedBefore: boolean;
|
|
880
|
+
};
|
|
874
881
|
rootVersion: string;
|
|
875
882
|
releaseTag: string;
|
|
876
883
|
stagingBranch: string;
|
|
877
884
|
productionBranch: string;
|
|
878
885
|
packageSelection: {
|
|
879
|
-
changed:
|
|
880
|
-
dependents:
|
|
881
|
-
selected:
|
|
886
|
+
changed: any[];
|
|
887
|
+
dependents: any[];
|
|
888
|
+
selected: any[];
|
|
882
889
|
};
|
|
883
890
|
plannedVersions: any;
|
|
891
|
+
stableDependencyVersions: Record<string, string>;
|
|
884
892
|
plannedDevReferenceRewrites: {
|
|
885
893
|
repoName: string;
|
|
886
894
|
filePath: string;
|
|
@@ -890,12 +898,12 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
|
|
|
890
898
|
reason: string;
|
|
891
899
|
}[];
|
|
892
900
|
plannedPublishWaits: {
|
|
893
|
-
name:
|
|
901
|
+
name: any;
|
|
894
902
|
workflow: string;
|
|
895
903
|
branch: string;
|
|
896
904
|
status: string;
|
|
897
905
|
}[];
|
|
898
|
-
touchedPackages:
|
|
906
|
+
touchedPackages: any[];
|
|
899
907
|
repos: WorkflowRepoReport[];
|
|
900
908
|
rootRepo: WorkflowRepoReport;
|
|
901
909
|
finalBranch: string;
|