@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.
Files changed (41) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +58 -0
  3. package/dist/operations/repository-operations.d.ts +129 -0
  4. package/dist/operations/repository-operations.js +634 -0
  5. package/dist/operations/services/config-runtime.d.ts +7 -6
  6. package/dist/operations/services/config-runtime.js +45 -25
  7. package/dist/operations/services/deploy.d.ts +42 -0
  8. package/dist/operations/services/deploy.js +1 -1
  9. package/dist/operations/services/project-platform.d.ts +41 -1
  10. package/dist/operations/services/project-platform.js +13 -0
  11. package/dist/operations/services/railway-api.d.ts +35 -1
  12. package/dist/operations/services/railway-api.js +240 -35
  13. package/dist/operations/services/railway-deploy.d.ts +16 -234
  14. package/dist/operations/services/railway-deploy.js +177 -62
  15. package/dist/operations/services/release-candidate.js +1 -2
  16. package/dist/operations/services/runtime-tools.d.ts +14 -0
  17. package/dist/operations/services/runtime-tools.js +15 -1
  18. package/dist/operations/services/workspace-save.d.ts +24 -0
  19. package/dist/operations/services/workspace-save.js +143 -3
  20. package/dist/operations/services/workspace-tools.js +1 -1
  21. package/dist/platform/env.yaml +163 -2
  22. package/dist/platform/environment.d.ts +1 -0
  23. package/dist/platform/environment.js +9 -0
  24. package/dist/platform-operation-store.d.ts +90 -0
  25. package/dist/platform-operation-store.js +505 -0
  26. package/dist/platform-operations.d.ts +265 -0
  27. package/dist/platform-operations.js +421 -0
  28. package/dist/reconcile/bootstrap-systems.js +3 -3
  29. package/dist/reconcile/builtin-adapters.js +225 -29
  30. package/dist/reconcile/contracts.d.ts +1 -1
  31. package/dist/reconcile/desired-state.d.ts +14 -0
  32. package/dist/reconcile/desired-state.js +4 -0
  33. package/dist/reconcile/engine.d.ts +28 -0
  34. package/dist/reconcile/state.js +3 -0
  35. package/dist/reconcile/units.js +2 -0
  36. package/dist/workflow/operations.d.ts +13 -5
  37. package/dist/workflow/operations.js +69 -12
  38. package/dist/workflow-state.d.ts +2 -0
  39. package/dist/workflow-state.js +7 -2
  40. package/dist/workflow.d.ts +2 -0
  41. 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: ["data"]
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 values = {
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 existing = await listRailwayCustomDomains({
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
- const matched = existing.find((entry) => entry.domain === domain) ?? null;
504
- if (matched) {
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
- const combinedVariables = {
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: combinedVariables,
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 secrets = Object.fromEntries(
1604
- registry.entries.filter((entry) => entry.scopes.includes(scope) && entry.targets.includes("railway-secret") && shouldExposeManagedHostRuntimeSecret(input.context.deployConfig, entry.id)).map((entry) => [entry.id, values[entry.id]]).filter(([, value]) => typeof value === "string" && value.length > 0)
1605
- );
1606
- const variables = Object.fromEntries(
1607
- registry.entries.filter((entry) => entry.scopes.includes(scope) && entry.targets.includes("railway-var")).map((entry) => [entry.id, values[entry.id]]).filter(([, value]) => typeof value === "string" && value.length > 0)
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
- return { scope, secrets, variables };
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: entry.instance?.startCommand === service.startCommand,
2134
+ configured: startCommandMatches,
1983
2135
  expected: service.startCommand,
1984
2136
  observed: entry.instance?.startCommand ?? null,
1985
- issues: entry.instance?.startCommand === service.startCommand ? [] : ["Railway start command does not match the desired value."]
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: {
@@ -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
  }
@@ -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: string[];
880
- dependents: string[];
881
- selected: string[];
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: string;
901
+ name: any;
894
902
  workflow: string;
895
903
  branch: string;
896
904
  status: string;
897
905
  }[];
898
- touchedPackages: string[];
906
+ touchedPackages: any[];
899
907
  repos: WorkflowRepoReport[];
900
908
  rootRepo: WorkflowRepoReport;
901
909
  finalBranch: string;