@treeseed/sdk 0.10.19 → 0.10.20

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]
@@ -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,39 @@ 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 (workflowGates.length > 0 || 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 { workflowGates: dispatchedGates };
3133
3167
  }
3134
3168
  helpers.write("[save][workflow] Waiting for hosted save workflow gates.");
3135
3169
  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.20",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {