@treeseed/sdk 0.10.19 → 0.10.21

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.
@@ -613,9 +613,7 @@ function setRailwaySecretVariable({ cwd, service, environment, key, value, env =
613
613
  }
614
614
  function ensureRailwayProjectExists(service, { env = process.env } = {}) {
615
615
  const projectName = typeof service?.projectName === "string" ? service.projectName.trim() : "";
616
- if (!projectName) {
617
- throw new Error(`Railway service ${service?.key ?? service?.serviceName ?? service?.serviceId ?? "(unknown)"} is missing a projectName.`);
618
- }
616
+ const projectId = typeof service?.projectId === "string" ? service.projectId.trim() : "";
619
617
  const listed = runRailway(["list", "--json"], {
620
618
  cwd: service.rootDir,
621
619
  capture: true,
@@ -623,11 +621,17 @@ function ensureRailwayProjectExists(service, { env = process.env } = {}) {
623
621
  env
624
622
  });
625
623
  if (listed.status === 0) {
626
- const match = normalizeRailwayProjectList(listed.stdout ?? "").find((entry) => entry.name === projectName || entry.id === projectName);
624
+ const match = normalizeRailwayProjectList(listed.stdout ?? "").find((entry) => entry.name === projectName || entry.id === projectName || entry.id === projectId);
627
625
  if (match) {
628
626
  return match;
629
627
  }
630
628
  }
629
+ if (!projectName) {
630
+ if (projectId) {
631
+ return { id: projectId, name: "" };
632
+ }
633
+ throw new Error(`Railway service ${service?.key ?? service?.serviceName ?? service?.serviceId ?? "(unknown)"} is missing a projectName.`);
634
+ }
631
635
  const args = ["init", "--name", projectName, "--json"];
632
636
  const workspace = resolveRailwayWorkspace(env);
633
637
  if (workspace) {
@@ -1758,6 +1762,16 @@ async function ensureRailwayServiceVolumeWithCliFallback({
1758
1762
  capture: true,
1759
1763
  env
1760
1764
  };
1765
+ ensureRailwayProjectContext({
1766
+ key: serviceName,
1767
+ projectId,
1768
+ serviceName,
1769
+ rootDir: tenantRoot,
1770
+ railwayEnvironment: environmentName
1771
+ }, {
1772
+ env,
1773
+ capture: true
1774
+ });
1761
1775
  const volumeArgs = ["volume", "--service", serviceId, "--environment", environmentId];
1762
1776
  const listResult = runRailway([...volumeArgs, "list", "--json"], cliOptions);
1763
1777
  const existingVolumes = normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
@@ -47,6 +47,7 @@ import {
47
47
  getRailwayServiceInstance,
48
48
  getRailwayProject,
49
49
  listRailwayCustomDomains,
50
+ listRailwayEnvironments,
50
51
  listRailwayProjects,
51
52
  listRailwayVolumes,
52
53
  listRailwayVariables,
@@ -1395,6 +1396,44 @@ function relativeRailwayRootDir(tenantRoot, serviceRoot) {
1395
1396
  const resolved = relative(tenantRoot, serviceRoot).replace(/\\/gu, "/");
1396
1397
  return !resolved || resolved === "" ? "." : resolved;
1397
1398
  }
1399
+ async function ensureRailwayEnvironmentForService({
1400
+ service,
1401
+ project,
1402
+ environmentName,
1403
+ env
1404
+ }) {
1405
+ try {
1406
+ return (await ensureRailwayEnvironment({
1407
+ projectId: project.id,
1408
+ environmentName,
1409
+ env
1410
+ })).environment;
1411
+ } catch (error) {
1412
+ const message = error instanceof Error ? error.message : String(error ?? "");
1413
+ if (!/Problem processing request/iu.test(message)) {
1414
+ throw error;
1415
+ }
1416
+ }
1417
+ ensureRailwayProjectContext({
1418
+ ...service,
1419
+ projectId: project.id,
1420
+ projectName: project.name ?? service.projectName,
1421
+ railwayEnvironment: environmentName
1422
+ }, {
1423
+ env,
1424
+ allowFailure: true,
1425
+ capture: true
1426
+ });
1427
+ for (let attempt = 0; attempt < 12; attempt += 1) {
1428
+ const environments = await listRailwayEnvironments({ projectId: project.id, env });
1429
+ const existing = environments.find((environment) => environment.name === environmentName || environment.id === environmentName) ?? null;
1430
+ if (existing) {
1431
+ return existing;
1432
+ }
1433
+ await new Promise((resolve2) => setTimeout(resolve2, 2500));
1434
+ }
1435
+ throw new Error(`Railway environment ${environmentName} was created through the CLI fallback but was not visible through the Railway API.`);
1436
+ }
1398
1437
  async function resolveRailwayTopologyForScope(input, scope, {
1399
1438
  ensure = false,
1400
1439
  refresh = false,
@@ -1465,11 +1504,12 @@ async function resolveRailwayTopologyForScope(input, scope, {
1465
1504
  }
1466
1505
  let environment = project?.environments.find((entry) => entry.name === service.railwayEnvironment || entry.id === service.railwayEnvironment) ?? null;
1467
1506
  if (project && !environment && ensure) {
1468
- environment = (await ensureRailwayEnvironment({
1469
- projectId: project.id,
1507
+ environment = await ensureRailwayEnvironmentForService({
1508
+ service,
1509
+ project,
1470
1510
  environmentName: service.railwayEnvironment,
1471
1511
  env
1472
- })).environment;
1512
+ });
1473
1513
  project = {
1474
1514
  ...project,
1475
1515
  environments: [...project.environments.filter((entry) => entry.id !== environment?.id), environment]
@@ -2093,7 +2133,8 @@ async function verifyRailwayUnit(input) {
2093
2133
  const topology = await resolveRailwayTopologyForScope(input, scope, {
2094
2134
  serviceKeys: [serviceKey],
2095
2135
  includeInstances: true,
2096
- includeVariables: true
2136
+ includeVariables: true,
2137
+ refresh: true
2097
2138
  });
2098
2139
  const entry = topology.services.get(serviceKey) ?? null;
2099
2140
  const service = entry?.configuredService ?? null;
@@ -408,7 +408,7 @@ export declare function workflowSave(helpers: WorkflowOperationHelpers, input: T
408
408
  };
409
409
  ciMode: "hosted" | "off";
410
410
  verifyMode: "action-first" | "local-only" | import("../workflow.ts").TreeseedWorkflowVerifyMode;
411
- workflowGates: Record<string, unknown>[] | {
411
+ workflowGates: (Record<string, unknown> | {
412
412
  name: string;
413
413
  repository: string | null;
414
414
  workflow: string;
@@ -423,7 +423,7 @@ export declare function workflowSave(helpers: WorkflowOperationHelpers, input: T
423
423
  updatedAt: null;
424
424
  timeoutSeconds: number | null;
425
425
  cached: boolean;
426
- }[];
426
+ })[];
427
427
  releaseCandidate: ReleaseCandidateReport | null;
428
428
  hostingAudit: import("../workflow-support.ts").TreeseedHostingAuditReport | null;
429
429
  } & {
@@ -551,7 +551,7 @@ export declare function workflowClose(helpers: WorkflowOperationHelpers, input:
551
551
  };
552
552
  ciMode: "hosted" | "off";
553
553
  verifyMode: "action-first" | "local-only" | import("../workflow.ts").TreeseedWorkflowVerifyMode;
554
- workflowGates: Record<string, unknown>[] | {
554
+ workflowGates: (Record<string, unknown> | {
555
555
  name: string;
556
556
  repository: string | null;
557
557
  workflow: string;
@@ -566,7 +566,7 @@ export declare function workflowClose(helpers: WorkflowOperationHelpers, input:
566
566
  updatedAt: null;
567
567
  timeoutSeconds: number | null;
568
568
  cached: boolean;
569
- }[];
569
+ })[];
570
570
  releaseCandidate: ReleaseCandidateReport | null;
571
571
  hostingAudit: import("../workflow-support.ts").TreeseedHostingAuditReport | null;
572
572
  } & {
@@ -735,7 +735,7 @@ export declare function workflowStage(helpers: WorkflowOperationHelpers, input:
735
735
  };
736
736
  ciMode: "hosted" | "off";
737
737
  verifyMode: "action-first" | "local-only" | import("../workflow.ts").TreeseedWorkflowVerifyMode;
738
- workflowGates: Record<string, unknown>[] | {
738
+ workflowGates: (Record<string, unknown> | {
739
739
  name: string;
740
740
  repository: string | null;
741
741
  workflow: string;
@@ -750,7 +750,7 @@ export declare function workflowStage(helpers: WorkflowOperationHelpers, input:
750
750
  updatedAt: null;
751
751
  timeoutSeconds: number | null;
752
752
  cached: boolean;
753
- }[];
753
+ })[];
754
754
  releaseCandidate: ReleaseCandidateReport | null;
755
755
  hostingAudit: import("../workflow-support.ts").TreeseedHostingAuditReport | null;
756
756
  } & {
@@ -69,6 +69,7 @@ import {
69
69
  syncBranchWithOrigin
70
70
  } from "../operations/services/git-workflow.js";
71
71
  import { resolveGitHubRepositorySlug } from "../operations/services/github-automation.js";
72
+ import { dispatchGitHubWorkflowRun } from "../operations/services/github-api.js";
72
73
  import {
73
74
  formatGitHubActionsGateFailure,
74
75
  inspectGitHubActionsVerification,
@@ -1168,6 +1169,9 @@ function workflowSessionSnapshot(session) {
1168
1169
  }))
1169
1170
  };
1170
1171
  }
1172
+ function sleep(ms) {
1173
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1174
+ }
1171
1175
  function nextPendingJournalStep(journal) {
1172
1176
  return journal.steps.find((step) => step.status === "pending") ?? null;
1173
1177
  }
@@ -3127,9 +3131,44 @@ async function workflowSave(helpers, input) {
3127
3131
  lockfileValidation: repo.lockfileValidation
3128
3132
  }))
3129
3133
  };
3130
- const saveWorkflowGates = shouldUseHostedSaveCi(effectiveInput, branch) ? await executeJournalStep(root, workflowRun.runId, "hosted-ci", () => {
3134
+ const saveWorkflowGates = shouldUseHostedSaveCi(effectiveInput, branch) ? await executeJournalStep(root, workflowRun.runId, "hosted-ci", async () => {
3131
3135
  if (branch === STAGING_BRANCH) {
3132
- return { workflowGates: saveResult?.workflowGates ?? [] };
3136
+ const workflowGates = saveResult?.workflowGates ?? [];
3137
+ if (effectiveInput.verifyDeployedResources !== true || scope === "local" || !savedRootRepo.commitSha) {
3138
+ return { workflowGates };
3139
+ }
3140
+ helpers.write("[save][workflow] Dispatching hosted market deploy gate for deployed resource verification.");
3141
+ const repository = resolveGitHubRepositorySlug(savedRootRepo.path);
3142
+ await dispatchGitHubWorkflowRun(repository, {
3143
+ workflow: "deploy.yml",
3144
+ branch,
3145
+ inputs: {
3146
+ environment: "staging",
3147
+ action_kind: "deploy_web"
3148
+ }
3149
+ });
3150
+ await sleep(5e3);
3151
+ helpers.write("[save][workflow] Waiting for hosted market deploy gate.");
3152
+ const dispatchedGates = await waitForWorkflowGates("save", [
3153
+ hostedDeployGate({
3154
+ name: savedRootRepo.name,
3155
+ repoPath: savedRootRepo.path,
3156
+ repository,
3157
+ workflow: "deploy.yml",
3158
+ branch,
3159
+ headSha: savedRootRepo.commitSha
3160
+ })
3161
+ ], "hosted", {
3162
+ root,
3163
+ runId: workflowRun.runId,
3164
+ onProgress: (line, stream) => helpers.write(line, stream)
3165
+ });
3166
+ return {
3167
+ workflowGates: [
3168
+ ...workflowGates.filter((gate) => !(gate.repository === repository && gate.workflow === "deploy.yml")),
3169
+ ...dispatchedGates
3170
+ ]
3171
+ };
3133
3172
  }
3134
3173
  helpers.write("[save][workflow] Waiting for hosted save workflow gates.");
3135
3174
  return waitForWorkflowGates("save", [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.10.19",
3
+ "version": "0.10.21",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {