@treeseed/sdk 0.12.26 → 0.12.28

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 (47) hide show
  1. package/README.md +1 -1
  2. package/dist/guarantees/index.js +15 -1
  3. package/dist/hosting/builtins.js +4 -4
  4. package/dist/hosting/contracts.d.ts +3 -3
  5. package/dist/hosting/graph.d.ts +4 -4
  6. package/dist/hosting/graph.js +17 -17
  7. package/dist/operations/agent-tools.d.ts +1 -1
  8. package/dist/operations/agent-tools.js +1 -1
  9. package/dist/operations/providers/default.js +2 -2
  10. package/dist/operations/services/config-runtime.d.ts +9 -9
  11. package/dist/operations/services/config-runtime.js +10 -10
  12. package/dist/operations/services/deploy.d.ts +8 -8
  13. package/dist/operations/services/deploy.js +151 -151
  14. package/dist/operations/services/github-automation.d.ts +6 -6
  15. package/dist/operations/services/github-automation.js +8 -8
  16. package/dist/operations/services/hub-launch.d.ts +1 -1
  17. package/dist/operations/services/hub-launch.js +1 -1
  18. package/dist/operations/services/package-adapters.d.ts +2 -2
  19. package/dist/operations/services/package-adapters.js +2 -2
  20. package/dist/operations/services/project-host-operations.d.ts +1 -1
  21. package/dist/operations/services/project-host-operations.js +1 -1
  22. package/dist/operations/services/project-platform.d.ts +8 -8
  23. package/dist/operations/services/project-platform.js +31 -31
  24. package/dist/operations/services/project-web-monitor.d.ts +1 -1
  25. package/dist/operations/services/project-web-monitor.js +4 -4
  26. package/dist/operations/services/railway-deploy.d.ts +4 -4
  27. package/dist/operations/services/railway-deploy.js +7 -7
  28. package/dist/operations/services/repository-save-orchestrator.js +3 -3
  29. package/dist/operations/services/template-secret-sync.d.ts +1 -1
  30. package/dist/operations/services/template-secret-sync.js +3 -3
  31. package/dist/reconcile/builtin-adapters.js +86 -40
  32. package/dist/reconcile/contracts.d.ts +1 -1
  33. package/dist/reconcile/engine.d.ts +4 -4
  34. package/dist/reconcile/engine.js +12 -12
  35. package/dist/reconcile/providers/release-private.d.ts +1 -1
  36. package/dist/scripts/tenant-deploy.js +9 -9
  37. package/dist/scripts/tenant-destroy.js +7 -7
  38. package/dist/scripts/tenant-workflow-action.js +4 -4
  39. package/dist/scripts/test-scaffold.js +2 -2
  40. package/dist/scripts/workspace-command-e2e.js +4 -4
  41. package/dist/sdk-types.d.ts +2 -2
  42. package/dist/treedx/generated/openapi-types.d.ts +11 -11
  43. package/dist/treedx/types.d.ts +15 -15
  44. package/dist/workflow/operations.d.ts +6 -4
  45. package/dist/workflow/operations.js +46 -23
  46. package/dist/workflow.d.ts +0 -9
  47. package/package.json +1 -1
@@ -56,6 +56,7 @@ import {
56
56
  deleteRailwayService,
57
57
  deleteRailwayVolume,
58
58
  deployRailwayServiceInstance,
59
+ inspectRailwayServiceDeploymentHealth,
59
60
  listRailwayEnvironmentServices,
60
61
  getRailwayServiceInstance,
61
62
  getRailwayProject,
@@ -298,7 +299,7 @@ function buildPackageWorkflowAdapter() {
298
299
  },
299
300
  apply(input) {
300
301
  const packageId = typeof input.unit.spec.packageId === "string" ? input.unit.spec.packageId : typeof input.unit.metadata.packageId === "string" ? input.unit.metadata.packageId : null;
301
- if (packageId && input.context.dryRun !== true && input.diff.action !== "noop") {
302
+ if (packageId && input.context.planOnly !== true && input.diff.action !== "noop") {
302
303
  const sync = syncTreeseedPackageWorkflows({
303
304
  root: input.context.tenantRoot,
304
305
  packageId,
@@ -836,7 +837,7 @@ function buildLocalDockerComposeAdapter() {
836
837
  const composeFiles = observedOrResolvedComposeFiles(input);
837
838
  const cwd = String(input.observed.live.cwd ?? input.context.tenantRoot);
838
839
  const projectName = String(input.unit.spec.projectName ?? "treeseed-capacity-provider");
839
- if (input.context.dryRun !== true && composeFiles.length > 0 && composeFiles.every((composeFile) => existsSync(composeFile))) {
840
+ if (input.context.planOnly !== true && composeFiles.length > 0 && composeFiles.every((composeFile) => existsSync(composeFile))) {
840
841
  const result = runDockerCompose({
841
842
  composeFiles,
842
843
  projectName,
@@ -1270,7 +1271,7 @@ function buildLocalProcessAdapter() {
1270
1271
  },
1271
1272
  async destroy(input) {
1272
1273
  const surfaces = Array.isArray(input.unit.spec.surfaces) ? input.unit.spec.surfaces.filter((entry) => typeof entry === "string") : [String(input.unit.spec.processId ?? input.unit.logicalName)];
1273
- if (input.context.dryRun !== true) {
1274
+ if (input.context.planOnly !== true) {
1274
1275
  await runManagedDevAction({
1275
1276
  tenantRoot: input.context.tenantRoot,
1276
1277
  action: "stop",
@@ -1797,7 +1798,7 @@ function buildReleaseGateAdapter() {
1797
1798
  parentContext: input.context,
1798
1799
  selector,
1799
1800
  target: { kind: "persistent", scope: environment },
1800
- dryRun: input.context.dryRun === true
1801
+ planOnly: input.context.planOnly === true
1801
1802
  });
1802
1803
  return genericResult(input, { ...input.observed.live, nested, fingerprint: input.unit.spec.fingerprint });
1803
1804
  }
@@ -1906,7 +1907,7 @@ function buildWorkflowMetaAdapter() {
1906
1907
  parentContext: input.context,
1907
1908
  selector,
1908
1909
  target: input.context.target,
1909
- dryRun: input.context.dryRun === true
1910
+ planOnly: input.context.planOnly === true
1910
1911
  });
1911
1912
  return genericResult(input, { ...input.observed.live, nested, fingerprint });
1912
1913
  }
@@ -2000,7 +2001,7 @@ function buildCloudflareEnv(input) {
2000
2001
  };
2001
2002
  }
2002
2003
  function hasLiveResourceId(value) {
2003
- return typeof value === "string" && value.length > 0 && !value.startsWith("dryrun-") && !value.startsWith("local-") && !value.endsWith("-id") && !value.endsWith("-preview-id");
2004
+ return typeof value === "string" && value.length > 0 && !value.startsWith("plan-") && !value.startsWith("local-") && !value.endsWith("-id") && !value.endsWith("-preview-id");
2004
2005
  }
2005
2006
  function buildRailwayEnv(input, scope) {
2006
2007
  const values = resolveReconcileEnvironmentValues(input, scope);
@@ -2421,6 +2422,12 @@ async function observeRailwayCustomDomainLive(input, domain) {
2421
2422
  });
2422
2423
  return normalized?.domain ? normalized : customDomain;
2423
2424
  }
2425
+ function railwayCustomDomainHasDnsRequirements(live) {
2426
+ if (!live) {
2427
+ return false;
2428
+ }
2429
+ return Array.isArray(live.dnsRecords) && live.dnsRecords.length > 0 || typeof live.serviceDomain === "string" && live.serviceDomain.trim().length > 0 || typeof live.verificationDnsHost === "string" && live.verificationDnsHost.trim().length > 0 && typeof live.verificationToken === "string" && live.verificationToken.trim().length > 0;
2430
+ }
2424
2431
  function collectCloudflareEnvironmentSync(input) {
2425
2432
  const target = toDeployTarget(input.context.target);
2426
2433
  const scope = scopeFromTarget(target);
@@ -2481,7 +2488,7 @@ function verificationCheck(key, description, source, options) {
2481
2488
  };
2482
2489
  }
2483
2490
  function summarizeVerification(unitId, checks, warnings = []) {
2484
- const missing = checks.flatMap((check) => !check.exists ? [`${check.key}: ${check.description}`] : []);
2491
+ const missing = checks.flatMap((check) => !check.exists ? [`${check.key}: ${check.issues.join("; ") || check.description}`] : []);
2485
2492
  const drifted = checks.flatMap(
2486
2493
  (check) => check.exists && (!check.configured || !check.ready || !check.verified || check.issues.length > 0) ? [`${check.key}: ${check.issues.join("; ") || "verification failed"}`] : []
2487
2494
  );
@@ -2518,7 +2525,7 @@ function unsupportedVerification(unitId, message) {
2518
2525
  warnings: []
2519
2526
  };
2520
2527
  }
2521
- function syncPagesEnvironmentVariablesForTarget(input, { dryRun = false } = {}) {
2528
+ function syncPagesEnvironmentVariablesForTarget(input, { planOnly = false } = {}) {
2522
2529
  const target = toDeployTarget(input.context.target);
2523
2530
  if (target.kind !== "persistent") {
2524
2531
  return { vars: [], secrets: [] };
@@ -2540,7 +2547,7 @@ function syncPagesEnvironmentVariablesForTarget(input, { dryRun = false } = {})
2540
2547
  ...plainVars,
2541
2548
  ...secretVars
2542
2549
  };
2543
- if (dryRun || Object.keys(envVars).length === 0) {
2550
+ if (planOnly || Object.keys(envVars).length === 0) {
2544
2551
  return {
2545
2552
  vars: Object.keys(plainVars),
2546
2553
  secrets: Object.keys(secretVars)
@@ -2572,17 +2579,17 @@ function syncPagesEnvironmentVariablesForTarget(input, { dryRun = false } = {})
2572
2579
  secrets: Object.keys(secretVars)
2573
2580
  };
2574
2581
  }
2575
- function reconcileCloudflareTarget(input, { dryRun = false } = {}) {
2582
+ function reconcileCloudflareTarget(input, { planOnly = false } = {}) {
2576
2583
  const target = toDeployTarget(input.context.target);
2577
2584
  const deployConfig = input.context.deployConfig;
2578
2585
  const state = loadDeployState(input.context.tenantRoot, deployConfig, { target });
2579
2586
  const env = buildCloudflareEnv(input);
2580
- const kvNamespaces = dryRun ? [] : listKvNamespaces(input.context.tenantRoot, env);
2581
- const d1Databases = dryRun ? [] : listD1Databases(input.context.tenantRoot, env);
2582
- const queues = dryRun ? [] : listQueues(input.context.tenantRoot, env);
2583
- const buckets = dryRun ? [] : listR2Buckets(input.context.tenantRoot, env);
2584
- const pagesProjects = dryRun ? [] : listPagesProjects(input.context.tenantRoot, env);
2585
- const turnstileWidgets = dryRun ? [] : listTurnstileWidgets(input.context.tenantRoot, env);
2587
+ const kvNamespaces = planOnly ? [] : listKvNamespaces(input.context.tenantRoot, env);
2588
+ const d1Databases = planOnly ? [] : listD1Databases(input.context.tenantRoot, env);
2589
+ const queues = planOnly ? [] : listQueues(input.context.tenantRoot, env);
2590
+ const buckets = planOnly ? [] : listR2Buckets(input.context.tenantRoot, env);
2591
+ const pagesProjects = planOnly ? [] : listPagesProjects(input.context.tenantRoot, env);
2592
+ const turnstileWidgets = planOnly ? [] : listTurnstileWidgets(input.context.tenantRoot, env);
2586
2593
  const runStep = (label, fn) => {
2587
2594
  try {
2588
2595
  return fn();
@@ -2606,9 +2613,9 @@ function reconcileCloudflareTarget(input, { dryRun = false } = {}) {
2606
2613
  state.kvNamespaces[binding].previewId = existing.id;
2607
2614
  return;
2608
2615
  }
2609
- if (dryRun) {
2610
- state.kvNamespaces[binding].id = `dryrun-${current.name}`;
2611
- state.kvNamespaces[binding].previewId = `dryrun-${current.name}`;
2616
+ if (planOnly) {
2617
+ state.kvNamespaces[binding].id = `plan-${current.name}`;
2618
+ state.kvNamespaces[binding].previewId = `plan-${current.name}`;
2612
2619
  return;
2613
2620
  }
2614
2621
  runWrangler(["kv", "namespace", "create", current.name], {
@@ -2638,9 +2645,9 @@ function reconcileCloudflareTarget(input, { dryRun = false } = {}) {
2638
2645
  current.previewDatabaseId = existing.previewDatabaseUuid ?? existing.uuid;
2639
2646
  return;
2640
2647
  }
2641
- if (dryRun) {
2642
- current.databaseId = `dryrun-${current.databaseName}`;
2643
- current.previewDatabaseId = `dryrun-${current.databaseName}`;
2648
+ if (planOnly) {
2649
+ current.databaseId = `plan-${current.databaseName}`;
2650
+ current.previewDatabaseId = `plan-${current.databaseName}`;
2644
2651
  return;
2645
2652
  }
2646
2653
  try {
@@ -2676,7 +2683,7 @@ function reconcileCloudflareTarget(input, { dryRun = false } = {}) {
2676
2683
  }
2677
2684
  let refreshedBuckets = buckets;
2678
2685
  const existing = refreshedBuckets.find((entry) => entry?.name === bucketName);
2679
- if (existing || dryRun) {
2686
+ if (existing || planOnly) {
2680
2687
  return;
2681
2688
  }
2682
2689
  try {
@@ -2699,7 +2706,7 @@ function reconcileCloudflareTarget(input, { dryRun = false } = {}) {
2699
2706
  }
2700
2707
  const existing = pagesProjects.find((entry) => entry?.name === current.projectName);
2701
2708
  if (existing) {
2702
- if (!dryRun && (existing.production_branch ?? "main") !== (current.productionBranch ?? "main")) {
2709
+ if (!planOnly && (existing.production_branch ?? "main") !== (current.productionBranch ?? "main")) {
2703
2710
  cloudflareApiRequest(
2704
2711
  `/accounts/${encodeURIComponent(env.CLOUDFLARE_ACCOUNT_ID)}/pages/projects/${encodeURIComponent(current.projectName)}`,
2705
2712
  {
@@ -2714,7 +2721,7 @@ function reconcileCloudflareTarget(input, { dryRun = false } = {}) {
2714
2721
  current.url = existing.subdomain ? `https://${existing.subdomain}` : current.url ?? `https://${current.projectName}.pages.dev`;
2715
2722
  return;
2716
2723
  }
2717
- if (dryRun) {
2724
+ if (planOnly) {
2718
2725
  current.url = `https://${current.projectName}.pages.dev`;
2719
2726
  return;
2720
2727
  }
@@ -2755,9 +2762,9 @@ function reconcileCloudflareTarget(input, { dryRun = false } = {}) {
2755
2762
  current.mode = "managed";
2756
2763
  current.managed = true;
2757
2764
  const existing = findTurnstileWidget(turnstileWidgets, current, current.name);
2758
- if (dryRun) {
2759
- current.sitekey = current.sitekey ?? `dryrun-${current.name}-sitekey`;
2760
- current.secret = current.secret ?? `dryrun-${current.name}-secret`;
2765
+ if (planOnly) {
2766
+ current.sitekey = current.sitekey ?? `plan-${current.name}-sitekey`;
2767
+ current.secret = current.secret ?? `plan-${current.name}-secret`;
2761
2768
  current.lastSyncedAt = nowIso();
2762
2769
  return;
2763
2770
  }
@@ -2790,7 +2797,7 @@ function reconcileCloudflareTarget(input, { dryRun = false } = {}) {
2790
2797
  runStep("r2", ensureR2Bucket);
2791
2798
  runStep("pages", ensurePagesProject);
2792
2799
  runStep("turnstile-widget", ensureTurnstileWidget);
2793
- runStep("web-cache", () => reconcileCloudflareWebCacheRules(input.context.tenantRoot, deployConfig, state, target, { dryRun, env }));
2800
+ runStep("web-cache", () => reconcileCloudflareWebCacheRules(input.context.tenantRoot, deployConfig, state, target, { planOnly, env }));
2794
2801
  state.readiness.configured = true;
2795
2802
  state.readiness.provisioned = hasProvisionedCloudflareResources(state);
2796
2803
  state.readiness.deployable = state.readiness.provisioned === true;
@@ -2807,7 +2814,7 @@ function reconcileCloudflareTarget(input, { dryRun = false } = {}) {
2807
2814
  writeDeployState(input.context.tenantRoot, state, { target });
2808
2815
  return { state, summary: buildProvisioningSummary(deployConfig, state, target) };
2809
2816
  }
2810
- function syncCloudflareSecretsForTarget(input, { dryRun = false } = {}) {
2817
+ function syncCloudflareSecretsForTarget(input, { planOnly = false } = {}) {
2811
2818
  const target = toDeployTarget(input.context.target);
2812
2819
  const deployConfig = input.context.deployConfig;
2813
2820
  const state = loadDeployState(input.context.tenantRoot, deployConfig, { target });
@@ -3818,11 +3825,11 @@ async function reconcileStaleOperationsRunnerResourcesForProject(input, {
3818
3825
  });
3819
3826
  }
3820
3827
  }
3821
- async function syncRailwayEnvironmentForScope(input, { dryRun = false, serviceKeys } = {}) {
3828
+ async function syncRailwayEnvironmentForScope(input, { planOnly = false, serviceKeys } = {}) {
3822
3829
  const scope = input.context.target.kind === "persistent" ? input.context.target.scope : "staging";
3823
3830
  const sync = collectRailwayEnvironmentSync(input);
3824
3831
  const selectedServiceKeys = Array.isArray(serviceKeys) && serviceKeys.length > 0 ? [...new Set(serviceKeys.map((value) => String(value).trim()).filter(Boolean))] : void 0;
3825
- traceRailwayReconcile(input.context.env, "sync:start", `scope=${scope} dryRun=${dryRun ? "yes" : "no"}`);
3832
+ traceRailwayReconcile(input.context.env, "sync:start", `scope=${scope} planOnly=${planOnly ? "yes" : "no"}`);
3826
3833
  const projectGroups = configuredRailwayProjectSyncGroups(input, scope, selectedServiceKeys);
3827
3834
  if (projectGroups.length === 0) {
3828
3835
  return {
@@ -3830,7 +3837,7 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false, serviceKe
3830
3837
  services: [],
3831
3838
  secrets: Object.keys(sync.secrets),
3832
3839
  variables: Object.keys(sync.variables),
3833
- dryRun,
3840
+ planOnly,
3834
3841
  workspace: ""
3835
3842
  };
3836
3843
  }
@@ -3839,7 +3846,7 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false, serviceKe
3839
3846
  for (const projectServices of projectGroups) {
3840
3847
  const serviceKeySet = [...new Set(projectServices.map((service) => service.key))];
3841
3848
  const topology = await resolveRailwayTopologyForScope(input, scope, {
3842
- ensure: !dryRun,
3849
+ ensure: !planOnly,
3843
3850
  ensureServices: false,
3844
3851
  serviceKeys: serviceKeySet,
3845
3852
  includeInstances: false,
@@ -3854,7 +3861,7 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false, serviceKe
3854
3861
  for (const service of projectServices) {
3855
3862
  railwayIacServiceInput(input, sync, service, scope);
3856
3863
  }
3857
- if (dryRun) {
3864
+ if (planOnly) {
3858
3865
  syncedServices.push(...projectServices);
3859
3866
  continue;
3860
3867
  }
@@ -3983,7 +3990,7 @@ async function syncRailwayEnvironmentForScope(input, { dryRun = false, serviceKe
3983
3990
  services: syncedServices,
3984
3991
  secrets: Object.keys(sync.secrets),
3985
3992
  variables: Object.keys(sync.variables),
3986
- dryRun,
3993
+ planOnly,
3987
3994
  workspace
3988
3995
  };
3989
3996
  }
@@ -4750,7 +4757,8 @@ async function observeCustomDomainUnit(input) {
4750
4757
  }
4751
4758
  case "custom-domain:api": {
4752
4759
  const domain = String(input.unit.spec.domain ?? "").trim();
4753
- const live = getCustomDomainState(input, "railway", domain) ?? await observeRailwayCustomDomainLive(input, domain) ?? input.persistedState?.lastObservedState ?? input.persistedState?.lastReconciledState ?? null;
4760
+ const cached = getCustomDomainState(input, "railway", domain);
4761
+ const live = (cached && railwayCustomDomainHasDnsRequirements(cached) ? cached : null) ?? await observeRailwayCustomDomainLive(input, domain) ?? cached ?? input.persistedState?.lastObservedState ?? input.persistedState?.lastReconciledState ?? null;
4754
4762
  if (live?.domain) {
4755
4763
  storeCustomDomainState(input, "railway", domain, live);
4756
4764
  }
@@ -4811,7 +4819,8 @@ async function verifyCustomDomainUnit(input) {
4811
4819
  }
4812
4820
  case "custom-domain:api": {
4813
4821
  const domain = String(input.unit.spec.domain ?? "").trim();
4814
- const live = getCustomDomainState(input, "railway", domain) ?? await observeRailwayCustomDomainLive(input, domain) ?? getPersistedCustomDomainState(input, "railway", domain) ?? input.persistedState?.lastObservedState ?? input.persistedState?.lastReconciledState ?? null;
4822
+ const cached = getCustomDomainState(input, "railway", domain);
4823
+ const live = (cached && railwayCustomDomainHasDnsRequirements(cached) ? cached : null) ?? await observeRailwayCustomDomainLive(input, domain) ?? cached ?? getPersistedCustomDomainState(input, "railway", domain) ?? input.persistedState?.lastObservedState ?? input.persistedState?.lastReconciledState ?? null;
4815
4824
  const dnsRecords = Array.isArray(live?.dnsRecords) ? live.dnsRecords : [];
4816
4825
  const certificateStatus = typeof live?.certificateStatus === "string" ? live.certificateStatus.trim().toUpperCase() : null;
4817
4826
  const verified = live?.verified === true;
@@ -5081,13 +5090,50 @@ async function verifyRailwayUnit(input) {
5081
5090
  }));
5082
5091
  }
5083
5092
  const desiredRootDirectory = service.imageRef || service.sourceMode === "image" ? null : railwayServiceRootDirectory(input.context.tenantRoot, service);
5093
+ const normalizeRailwayRootDirectory = (value) => {
5094
+ const trimmed = String(value ?? "").trim();
5095
+ return trimmed || ".";
5096
+ };
5084
5097
  if (desiredRootDirectory) {
5098
+ const observedRootDirectory = normalizeRailwayRootDirectory(entry.instance?.rootDirectory);
5099
+ const desiredNormalizedRootDirectory = normalizeRailwayRootDirectory(desiredRootDirectory);
5085
5100
  checks.push(verificationCheck("railway.instance.root-directory", "Railway root directory matches desired config", "api", {
5086
5101
  exists: Boolean(entry.instance?.id),
5087
- configured: entry.instance?.rootDirectory === desiredRootDirectory,
5102
+ configured: observedRootDirectory === desiredNormalizedRootDirectory,
5088
5103
  expected: desiredRootDirectory,
5089
5104
  observed: entry.instance?.rootDirectory ?? null,
5090
- issues: entry.instance?.rootDirectory === desiredRootDirectory ? [] : ["Railway root directory does not match the desired value."]
5105
+ issues: observedRootDirectory === desiredNormalizedRootDirectory ? [] : ["Railway root directory does not match the desired value."]
5106
+ }));
5107
+ }
5108
+ if (service.sourceMode === "git" && entry.service?.id && entry.environment?.id) {
5109
+ const deployment = await inspectRailwayServiceDeploymentHealth({
5110
+ serviceId: entry.service.id,
5111
+ environmentId: entry.environment.id,
5112
+ env: topology.env
5113
+ }).catch((error) => ({
5114
+ ok: false,
5115
+ repo: null,
5116
+ branch: null,
5117
+ rootDirectory: null,
5118
+ message: error instanceof Error ? error.message : String(error)
5119
+ }));
5120
+ const repoMatches = service.sourceRepo ? deployment.repo === service.sourceRepo : true;
5121
+ const branchMatches = service.sourceBranch ? deployment.branch === service.sourceBranch : true;
5122
+ const rootMatches = desiredRootDirectory ? normalizeRailwayRootDirectory(deployment.rootDirectory) === normalizeRailwayRootDirectory(desiredRootDirectory) : true;
5123
+ const deploymentIssues = [
5124
+ repoMatches ? null : `Expected Railway Git deployment repo ${service.sourceRepo}, observed ${deployment.repo ?? "(unset)"}.`,
5125
+ branchMatches ? null : `Expected Railway Git deployment branch ${service.sourceBranch}, observed ${deployment.branch ?? "(unset)"}.`,
5126
+ rootMatches ? null : `Expected Railway deployment root ${desiredRootDirectory}, observed ${deployment.rootDirectory ?? "(unset)"}.`,
5127
+ deployment.repo || deployment.branch ? null : "Railway latest deployment does not expose a Git repository/branch; staging may still be using a Docker image source."
5128
+ ].filter((issue) => Boolean(issue));
5129
+ checks.push(verificationCheck("railway.service.source-mode", "Railway staging service deploys from Git source", "api", {
5130
+ exists: Boolean(entry.service?.id),
5131
+ configured: deploymentIssues.length === 0,
5132
+ ready: deploymentIssues.length === 0,
5133
+ verified: deploymentIssues.length === 0,
5134
+ expected: { sourceMode: "git", repo: service.sourceRepo ?? null, branch: service.sourceBranch ?? null, rootDirectory: desiredRootDirectory ?? null },
5135
+ observed: { repo: deployment.repo, branch: deployment.branch, rootDirectory: deployment.rootDirectory, message: deployment.message ?? null },
5136
+ issues: deploymentIssues
5091
5137
  }));
5092
5138
  }
5093
5139
  if (service.key === "api") {
@@ -128,7 +128,7 @@ export interface TreeseedReconcileRunContext {
128
128
  target: TreeseedReconcileTarget;
129
129
  deployConfig: TreeseedDeployConfig;
130
130
  launchEnv: NodeJS.ProcessEnv;
131
- dryRun?: boolean;
131
+ planOnly?: boolean;
132
132
  write?: (line: string) => void;
133
133
  session: Map<string, unknown>;
134
134
  }
@@ -30,7 +30,7 @@ export declare function planTreeseedReconciliation({ tenantRoot, target, env, sy
30
30
  state: TreeseedReconcileStateRecord;
31
31
  deployConfig: import("../platform/contracts.js").TreeseedDeployConfig;
32
32
  }>;
33
- export declare function reconcileTreeseedTarget({ tenantRoot, target, env, systems, selector, units, write, dryRun, session, }: {
33
+ export declare function reconcileTreeseedTarget({ tenantRoot, target, env, systems, selector, units, write, planOnly, session, }: {
34
34
  tenantRoot: string;
35
35
  target: TreeseedReconcileTarget;
36
36
  env?: NodeJS.ProcessEnv;
@@ -38,7 +38,7 @@ export declare function reconcileTreeseedTarget({ tenantRoot, target, env, syste
38
38
  selector?: TreeseedReconcileSelector;
39
39
  units?: TreeseedDesiredUnit[];
40
40
  write?: (line: string) => void;
41
- dryRun?: boolean;
41
+ planOnly?: boolean;
42
42
  session?: Map<string, unknown>;
43
43
  }): Promise<{
44
44
  target: TreeseedReconcileTarget;
@@ -83,11 +83,11 @@ export declare function collectTreeseedReconcileStatus({ tenantRoot, target, env
83
83
  verification: TreeseedUnitVerificationResult | null;
84
84
  }[];
85
85
  }>;
86
- export declare function reconcileTreeseedNestedTarget({ parentContext, selector, target, dryRun, }: {
86
+ export declare function reconcileTreeseedNestedTarget({ parentContext, selector, target, planOnly, }: {
87
87
  parentContext: TreeseedReconcileRunContext;
88
88
  selector: TreeseedReconcileSelector;
89
89
  target: TreeseedReconcileTarget;
90
- dryRun: boolean;
90
+ planOnly: boolean;
91
91
  }): Promise<{
92
92
  target: TreeseedReconcileTarget;
93
93
  units: TreeseedDesiredUnit[];
@@ -26,14 +26,14 @@ function formatVerificationFailure(verification) {
26
26
  ].filter(Boolean);
27
27
  return details.length > 0 ? `Verification failed (${details.join(" | ")})` : "Verification failed.";
28
28
  }
29
- function createRunContext(tenantRoot, target, launchEnv, write, dryRun = false, session) {
29
+ function createRunContext(tenantRoot, target, launchEnv, write, planOnly = false, session) {
30
30
  const { deployConfig } = deriveTreeseedDesiredUnits({ tenantRoot, target, env: launchEnv });
31
31
  return {
32
32
  tenantRoot,
33
33
  target,
34
34
  deployConfig,
35
35
  launchEnv,
36
- dryRun,
36
+ planOnly,
37
37
  write,
38
38
  session: session ?? /* @__PURE__ */ new Map()
39
39
  };
@@ -278,12 +278,12 @@ async function reconcileTreeseedTarget({
278
278
  selector,
279
279
  units,
280
280
  write,
281
- dryRun = false,
281
+ planOnly = false,
282
282
  session
283
283
  }) {
284
284
  const planned = await planTreeseedReconciliation({ tenantRoot, target, env, systems, selector, units, write });
285
285
  const registry = createTreeseedReconcileRegistry(planned.deployConfig);
286
- const context = createRunContext(tenantRoot, target, env, write, dryRun, session);
286
+ const context = createRunContext(tenantRoot, target, env, write, planOnly, session);
287
287
  const results = [];
288
288
  const verificationMap = /* @__PURE__ */ new Map();
289
289
  const timingEntries = [];
@@ -337,7 +337,7 @@ async function reconcileTreeseedTarget({
337
337
  let result;
338
338
  try {
339
339
  const stageStartMs = performance.now();
340
- if (dryRun) {
340
+ if (planOnly) {
341
341
  result = {
342
342
  unit: plan.unit,
343
343
  observed: plan.observed,
@@ -360,7 +360,7 @@ async function reconcileTreeseedTarget({
360
360
  unitTiming.children?.push({
361
361
  name: `${unitTiming.name}:apply`,
362
362
  durationMs: elapsedMs(stageStartMs),
363
- status: dryRun ? "skipped" : "success"
363
+ status: planOnly ? "skipped" : "success"
364
364
  });
365
365
  } catch (error) {
366
366
  unitTiming.children?.push({
@@ -373,7 +373,7 @@ async function reconcileTreeseedTarget({
373
373
  wrapAdapterFailure("apply", plan.unit.provider, plan.unit.unitType, plan.unit.unitId, error);
374
374
  }
375
375
  let refreshedObserved = result.observed;
376
- if (!dryRun) {
376
+ if (!planOnly) {
377
377
  try {
378
378
  const stageStartMs = performance.now();
379
379
  refreshedObserved = await Promise.resolve(adapter.refresh({
@@ -447,17 +447,17 @@ async function reconcileTreeseedTarget({
447
447
  unitTiming.status = verification.verified ? "success" : "failed";
448
448
  write?.(`Finished ${plan.unit.provider}:${plan.unit.unitType} (${plan.unit.logicalName}) in ${formatDurationMs(unitTiming.durationMs)}.`);
449
449
  if (!verification.verified) {
450
- if (!dryRun) {
450
+ if (!planOnly) {
451
451
  await persistVerifiedResult(persisted, verifiedResult);
452
452
  }
453
453
  throw new Error(`Treeseed reconcile verification failed for ${plan.unit.provider}:${plan.unit.unitType} (${plan.unit.unitId}): ${formatVerificationFailure(verification)}`);
454
454
  }
455
- if (!dryRun) {
455
+ if (!planOnly) {
456
456
  await persistVerifiedResult(persisted, verifiedResult);
457
457
  }
458
458
  results.push(verifiedResult);
459
459
  });
460
- if (!dryRun) {
460
+ if (!planOnly) {
461
461
  writeTreeseedReconcileState(tenantRoot, planned.state, env);
462
462
  }
463
463
  return {
@@ -579,7 +579,7 @@ async function reconcileTreeseedNestedTarget({
579
579
  parentContext,
580
580
  selector,
581
581
  target,
582
- dryRun
582
+ planOnly
583
583
  }) {
584
584
  const previousVerificationResults = parentContext.session.get("treeseed:verification-results");
585
585
  const previousTimings = parentContext.session.get("treeseed:timings");
@@ -590,7 +590,7 @@ async function reconcileTreeseedNestedTarget({
590
590
  env: parentContext.launchEnv,
591
591
  selector,
592
592
  write: parentContext.write,
593
- dryRun,
593
+ planOnly,
594
594
  session: parentContext.session
595
595
  });
596
596
  } finally {
@@ -61,7 +61,7 @@ export declare function runHostedReconcileGate(input: {
61
61
  parentContext: TreeseedReconcileRunContext;
62
62
  selector: TreeseedReconcileSelector;
63
63
  target: TreeseedReconcileTarget;
64
- dryRun: boolean;
64
+ planOnly: boolean;
65
65
  }): Promise<{
66
66
  target: TreeseedReconcileTarget;
67
67
  units: import("../contracts.js").TreeseedDesiredUnit[];
@@ -21,7 +21,7 @@ function writeDeployReport(payload) {
21
21
  }
22
22
  function parseArgs(argv) {
23
23
  const parsed = {
24
- dryRun: false,
24
+ planOnly: false,
25
25
  only: null,
26
26
  name: null,
27
27
  environment: null,
@@ -32,8 +32,8 @@ function parseArgs(argv) {
32
32
  const current = rest.shift();
33
33
  if (!current)
34
34
  continue;
35
- if (current === '--dry-run') {
36
- parsed.dryRun = true;
35
+ if (current === '--plan') {
36
+ parsed.planOnly = true;
37
37
  continue;
38
38
  }
39
39
  if (current === '--only') {
@@ -166,8 +166,8 @@ async function main() {
166
166
  }
167
167
  if (scope === 'local') {
168
168
  runTenantDeployPreflight({ cwd: tenantRoot, scope: 'local' });
169
- await runTenantWebBuild({ tenantRoot, scope: 'local', dryRun: options.dryRun, env: process.env });
170
- writeDeployReport({ ok: true, kind: 'success', scope, dryRun: options.dryRun, target: deployTargetLabel(target) });
169
+ await runTenantWebBuild({ tenantRoot, scope: 'local', planOnly: options.planOnly, env: process.env });
170
+ writeDeployReport({ ok: true, kind: 'success', scope, planOnly: options.planOnly, target: deployTargetLabel(target) });
171
171
  console.log('Treeseed local deploy completed as a build-only publish target.');
172
172
  return;
173
173
  }
@@ -177,7 +177,7 @@ async function main() {
177
177
  tenantRoot,
178
178
  scope,
179
179
  target,
180
- dryRun: options.dryRun,
180
+ planOnly: options.planOnly,
181
181
  env: process.env,
182
182
  });
183
183
  }
@@ -192,21 +192,21 @@ async function main() {
192
192
  }
193
193
  if (shouldRun('migrate')) {
194
194
  const result = await runTenantDataMigration(context);
195
- console.log(`${options.dryRun ? 'Planned' : 'Applied'} remote migrations for ${result.databaseName}.`);
195
+ console.log(`${options.planOnly ? 'Planned' : 'Applied'} remote migrations for ${result.databaseName}.`);
196
196
  }
197
197
  if (shouldRun('build')) {
198
198
  await runTenantWebBuild(context);
199
199
  }
200
200
  if (shouldRun('publish')) {
201
201
  await runTenantWebPublish(context);
202
- if (!options.dryRun) {
202
+ if (!options.planOnly) {
203
203
  finalizeDeploymentState(tenantRoot, { target });
204
204
  }
205
205
  }
206
206
  writeDeployReport({
207
207
  ok: true,
208
208
  kind: 'success',
209
- dryRun: options.dryRun,
209
+ planOnly: options.planOnly,
210
210
  only: options.only,
211
211
  target: deployTargetLabel(target),
212
212
  });
@@ -7,7 +7,7 @@ import { deriveCloudflareWorkerName } from '../platform/deploy-config.js';
7
7
  const tenantRoot = process.cwd();
8
8
  function parseArgs(argv) {
9
9
  const parsed = {
10
- dryRun: false,
10
+ planOnly: false,
11
11
  force: false,
12
12
  skipConfirmation: false,
13
13
  confirm: null,
@@ -20,8 +20,8 @@ function parseArgs(argv) {
20
20
  const current = rest.shift();
21
21
  if (!current)
22
22
  continue;
23
- if (current === '--dry-run') {
24
- parsed.dryRun = true;
23
+ if (current === '--plan') {
24
+ parsed.planOnly = true;
25
25
  continue;
26
26
  }
27
27
  if (current === '--force') {
@@ -86,7 +86,7 @@ const scope = options.environment ?? 'prod';
86
86
  const target = createPersistentDeployTarget(scope);
87
87
  applyTreeseedEnvironmentToProcess({ tenantRoot, scope, override: true });
88
88
  assertTreeseedCommandEnvironment({ tenantRoot, scope, purpose: 'destroy' });
89
- const deployConfig = validateDestroyPrerequisites(tenantRoot, { requireRemote: !options.dryRun });
89
+ const deployConfig = validateDestroyPrerequisites(tenantRoot, { requireRemote: !options.planOnly });
90
90
  const state = loadDeployState(tenantRoot, deployConfig, { target });
91
91
  const expectedConfirmation = getExpectedConfirmation(deployConfig);
92
92
  if (!options.skipConfirmation) {
@@ -98,14 +98,14 @@ if (!options.skipConfirmation) {
98
98
  }
99
99
  }
100
100
  const result = await destroyTreeseedEnvironmentResources(tenantRoot, {
101
- dryRun: options.dryRun,
101
+ planOnly: options.planOnly,
102
102
  force: options.force,
103
103
  deleteData: options.deleteData,
104
104
  target,
105
105
  });
106
106
  printDestroySummary(result);
107
- if (options.dryRun) {
108
- console.log('Dry run: no remote resources were deleted.');
107
+ if (options.planOnly) {
108
+ console.log('Plan: no remote resources were deleted.');
109
109
  process.exit(0);
110
110
  }
111
111
  cleanupDestroyedState(tenantRoot, { target, removeBuildArtifacts: options.removeBuildArtifacts });
@@ -10,7 +10,7 @@ function parseArgs(argv) {
10
10
  environment: null,
11
11
  projectId: null,
12
12
  previewId: null,
13
- dryRun: false,
13
+ planOnly: false,
14
14
  };
15
15
  const rest = [...argv];
16
16
  while (rest.length) {
@@ -49,8 +49,8 @@ function parseArgs(argv) {
49
49
  parsed.previewId = current.split('=', 2)[1] ?? null;
50
50
  continue;
51
51
  }
52
- if (current === '--dry-run') {
53
- parsed.dryRun = true;
52
+ if (current === '--plan') {
53
+ parsed.planOnly = true;
54
54
  continue;
55
55
  }
56
56
  throw new Error(`Unknown workflow action argument: ${current}`);
@@ -78,7 +78,7 @@ async function main() {
78
78
  scope,
79
79
  projectId: options.projectId ?? process.env.TREESEED_PROJECT_ID ?? null,
80
80
  previewId: options.previewId,
81
- dryRun: options.dryRun,
81
+ planOnly: options.planOnly,
82
82
  write: (line) => writeStatus(line),
83
83
  });
84
84
  if (result !== undefined) {
@@ -233,8 +233,8 @@ function runScaffoldChecks(siteRoot) {
233
233
  });
234
234
  }
235
235
  if (scaffoldChecks.has('deploy')) {
236
- withTiming('scaffold deploy dry-run', () => {
237
- runStep('npm', ['run', 'deploy', '--', '--dry-run'], { cwd: siteRoot });
236
+ withTiming('scaffold deploy plan', () => {
237
+ runStep('npm', ['run', 'deploy', '--', '--plan'], { cwd: siteRoot });
238
238
  });
239
239
  }
240
240
  }
@@ -491,15 +491,15 @@ async function runLocalSuite() {
491
491
  env: cacheEnv(),
492
492
  });
493
493
  });
494
- await withStep('scaffold tenant deploy dry-run', async () => {
495
- return runCommand('scaffold-deploy-dry-run', process.execPath, [packageScriptPath('treeseed'), 'deploy', '--dry-run'], {
494
+ await withStep('scaffold tenant deploy plan', async () => {
495
+ return runCommand('scaffold-deploy-plan', process.execPath, [packageScriptPath('treeseed'), 'deploy', '--plan'], {
496
496
  cwd: siteRoot,
497
497
  env: cacheEnv(),
498
498
  });
499
499
  });
500
- await withStep('scaffold tenant destroy dry-run', async () => {
500
+ await withStep('scaffold tenant destroy plan', async () => {
501
501
  const deployConfig = readTenantConfig(siteRoot);
502
- return runCommand('scaffold-destroy-dry-run', process.execPath, [packageScriptPath('treeseed'), 'destroy', '--dry-run', '--skip-confirmation', '--confirm', String(deployConfig.slug)], {
502
+ return runCommand('scaffold-destroy-plan', process.execPath, [packageScriptPath('treeseed'), 'destroy', '--plan', '--skip-confirmation', '--confirm', String(deployConfig.slug)], {
503
503
  cwd: siteRoot,
504
504
  env: cacheEnv(),
505
505
  });