@treeseed/sdk 0.6.40 → 0.6.42

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.
@@ -245,8 +245,8 @@ export declare function deployProjectPlatform(options: ProjectPlatformActionOpti
245
245
  healthcheckTimeoutSeconds: number | null;
246
246
  runtimeMode: string | null;
247
247
  volume: {
248
- id: string;
249
- name: string;
248
+ id: any;
249
+ name: any;
250
250
  mountPath: any;
251
251
  created: boolean;
252
252
  updated: boolean;
@@ -745,8 +745,8 @@ export declare function runProjectPlatformAction(action: ProjectPlatformAction,
745
745
  healthcheckTimeoutSeconds: number | null;
746
746
  runtimeMode: string | null;
747
747
  volume: {
748
- id: string;
749
- name: string;
748
+ id: any;
749
+ name: any;
750
750
  mountPath: any;
751
751
  created: boolean;
752
752
  updated: boolean;
@@ -1120,9 +1120,10 @@ async function deployProjectPlatform(options) {
1120
1120
  previousRailwayDeployNodeId = nodeId;
1121
1121
  }
1122
1122
  }
1123
+ const managesRailwaySchedules = options.scope === "staging" || options.scope === "prod";
1123
1124
  let railwaySchedules = [];
1124
- let railwayScheduleVerification = { ok: true, checks: [], skipped: true, reason: !selectedSystems.has("agents") ? "agents_not_selected" : options.scope !== "prod" ? "prod_only" : "dry_run" };
1125
- if (options.scope === "prod" && selectedSystems.has("agents")) {
1125
+ let railwayScheduleVerification = { ok: true, checks: [], skipped: true, reason: !selectedSystems.has("agents") ? "agents_not_selected" : !managesRailwaySchedules ? "scope_not_scheduled" : "dry_run" };
1126
+ if (managesRailwaySchedules && selectedSystems.has("agents")) {
1126
1127
  const agentDeployNodeIds = nodes.filter((node) => node.id.startsWith("agents:") && node.id.endsWith("-railway-deploy")).map((node) => node.id);
1127
1128
  nodes.push({
1128
1129
  id: "agents:schedules",
@@ -1156,9 +1157,9 @@ async function deployProjectPlatform(options) {
1156
1157
  serviceResults
1157
1158
  });
1158
1159
  }
1159
- if (options.scope !== "prod" || !selectedSystems.has("agents")) {
1160
+ if (!managesRailwaySchedules || !selectedSystems.has("agents")) {
1160
1161
  railwaySchedules = [];
1161
- railwayScheduleVerification = { ok: true, checks: railwaySchedules, skipped: true, reason: !selectedSystems.has("agents") ? "agents_not_selected" : options.scope !== "prod" ? "prod_only" : "dry_run" };
1162
+ railwayScheduleVerification = { ok: true, checks: railwaySchedules, skipped: true, reason: !selectedSystems.has("agents") ? "agents_not_selected" : !managesRailwaySchedules ? "scope_not_scheduled" : "dry_run" };
1162
1163
  }
1163
1164
  if (selectedSystems.has("agents")) {
1164
1165
  serviceResults.push({
@@ -22,6 +22,7 @@ export type RailwayServiceInstanceSummary = {
22
22
  id: string | null;
23
23
  buildCommand: string | null;
24
24
  startCommand: string | null;
25
+ cronSchedule: string | null;
25
26
  rootDirectory: string | null;
26
27
  healthcheckPath: string | null;
27
28
  healthcheckTimeoutSeconds: number | null;
@@ -157,6 +158,7 @@ export declare function getRailwayServiceInstance({ serviceId, environmentId, en
157
158
  id: string | null;
158
159
  buildCommand: string | null;
159
160
  startCommand: string | null;
161
+ cronSchedule: string | null;
160
162
  rootDirectory: string | null;
161
163
  healthcheckPath: string | null;
162
164
  healthcheckTimeoutSeconds: number | null;
@@ -169,6 +171,7 @@ export declare function getRailwayServiceInstance({ serviceId, environmentId, en
169
171
  id: string | null;
170
172
  buildCommand: string | null;
171
173
  startCommand: string | null;
174
+ cronSchedule: string | null;
172
175
  rootDirectory: string | null;
173
176
  healthcheckPath: null;
174
177
  healthcheckTimeoutSeconds: null;
@@ -178,11 +181,12 @@ export declare function getRailwayServiceInstance({ serviceId, environmentId, en
178
181
  sleepApplication: null;
179
182
  runtimeConfigSupported: false;
180
183
  }>;
181
- export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, environmentId, buildCommand, startCommand, rootDirectory, healthcheckPath, healthcheckTimeoutSeconds, healthcheckIntervalSeconds, restartPolicy, runtimeMode, env, fetchImpl, }: {
184
+ export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, environmentId, buildCommand, startCommand, cronSchedule, rootDirectory, healthcheckPath, healthcheckTimeoutSeconds, healthcheckIntervalSeconds, restartPolicy, runtimeMode, env, fetchImpl, }: {
182
185
  serviceId: string;
183
186
  environmentId: string;
184
187
  buildCommand?: string | null;
185
188
  startCommand?: string | null;
189
+ cronSchedule?: string | null;
186
190
  rootDirectory?: string | null;
187
191
  healthcheckPath?: string | null;
188
192
  healthcheckTimeoutSeconds?: number | null;
@@ -196,6 +200,7 @@ export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, e
196
200
  id: string | null;
197
201
  buildCommand: string | null;
198
202
  startCommand: string | null;
203
+ cronSchedule: string | null;
199
204
  rootDirectory: string | null;
200
205
  healthcheckPath: string | null;
201
206
  healthcheckTimeoutSeconds: number | null;
@@ -208,6 +213,7 @@ export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, e
208
213
  id: string | null;
209
214
  buildCommand: string | null;
210
215
  startCommand: string | null;
216
+ cronSchedule: string | null;
211
217
  rootDirectory: string | null;
212
218
  healthcheckPath: null;
213
219
  healthcheckTimeoutSeconds: null;
@@ -223,6 +229,7 @@ export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, e
223
229
  id: string;
224
230
  buildCommand: string | null;
225
231
  startCommand: string | null;
232
+ cronSchedule: string | null;
226
233
  rootDirectory: string | null;
227
234
  healthcheckPath: string | null;
228
235
  healthcheckTimeoutSeconds: number | null;
@@ -659,6 +659,7 @@ async function getRailwayServiceInstance({
659
659
  id: null,
660
660
  buildCommand: null,
661
661
  startCommand: null,
662
+ cronSchedule: null,
662
663
  rootDirectory: null,
663
664
  healthcheckPath: null,
664
665
  healthcheckTimeoutSeconds: null,
@@ -676,6 +677,7 @@ query TreeseedRailwayServiceInstance($serviceId: String!, $environmentId: String
676
677
  id
677
678
  buildCommand
678
679
  startCommand
680
+ cronSchedule
679
681
  rootDirectory
680
682
  healthcheckPath
681
683
  healthcheckTimeout
@@ -692,6 +694,7 @@ query TreeseedRailwayServiceInstance($serviceId: String!, $environmentId: String
692
694
  id: railwayConnectionLabel(instance?.id) || null,
693
695
  buildCommand: railwayConnectionLabel(instance?.buildCommand) || null,
694
696
  startCommand: railwayConnectionLabel(instance?.startCommand) || null,
697
+ cronSchedule: railwayConnectionLabel(instance?.cronSchedule) || null,
695
698
  rootDirectory: railwayConnectionLabel(instance?.rootDirectory) || null,
696
699
  healthcheckPath: railwayConnectionLabel(instance?.healthcheckPath) || null,
697
700
  healthcheckTimeoutSeconds: normalizeRailwayNumber(instance?.healthcheckTimeout),
@@ -711,6 +714,7 @@ query TreeseedRailwayServiceInstanceLegacy($serviceId: String!, $environmentId:
711
714
  id
712
715
  buildCommand
713
716
  startCommand
717
+ cronSchedule
714
718
  rootDirectory
715
719
  }
716
720
  }
@@ -725,6 +729,7 @@ query TreeseedRailwayServiceInstanceLegacy($serviceId: String!, $environmentId:
725
729
  id: railwayConnectionLabel(instance?.id) || null,
726
730
  buildCommand: railwayConnectionLabel(instance?.buildCommand) || null,
727
731
  startCommand: railwayConnectionLabel(instance?.startCommand) || null,
732
+ cronSchedule: railwayConnectionLabel(instance?.cronSchedule) || null,
728
733
  rootDirectory: railwayConnectionLabel(instance?.rootDirectory) || null
729
734
  };
730
735
  }
@@ -739,6 +744,7 @@ async function ensureRailwayServiceInstanceConfiguration({
739
744
  environmentId,
740
745
  buildCommand,
741
746
  startCommand,
747
+ cronSchedule,
742
748
  rootDirectory,
743
749
  healthcheckPath,
744
750
  healthcheckTimeoutSeconds,
@@ -755,6 +761,7 @@ async function ensureRailwayServiceInstanceConfiguration({
755
761
  const desired = {
756
762
  buildCommand: railwayConnectionLabel(buildCommand) || null,
757
763
  startCommand: railwayConnectionLabel(startCommand) || null,
764
+ cronSchedule: railwayConnectionLabel(cronSchedule) || null,
758
765
  rootDirectory: railwayConnectionLabel(rootDirectory) || null,
759
766
  healthcheckPath: railwayConnectionLabel(healthcheckPath) || null,
760
767
  healthcheckTimeoutSeconds: normalizeRailwayNumber(healthcheckTimeoutSeconds),
@@ -773,7 +780,7 @@ async function ensureRailwayServiceInstanceConfiguration({
773
780
  if (desired.restartPolicy !== null) {
774
781
  throw new Error("Railway service instance restart policies are unsupported by the current Railway API schema.");
775
782
  }
776
- const drifted = desired.buildCommand !== null && desired.buildCommand !== current.buildCommand || desired.startCommand !== null && desired.startCommand !== current.startCommand || desired.rootDirectory !== null && desired.rootDirectory !== current.rootDirectory || desired.healthcheckPath !== null && desired.healthcheckPath !== current.healthcheckPath || desired.healthcheckTimeoutSeconds !== null && desired.healthcheckTimeoutSeconds !== current.healthcheckTimeoutSeconds || desired.runtimeMode !== null && desired.runtimeMode !== current.runtimeMode;
783
+ const drifted = desired.buildCommand !== null && desired.buildCommand !== current.buildCommand || desired.startCommand !== null && desired.startCommand !== current.startCommand || desired.cronSchedule !== null && desired.cronSchedule !== current.cronSchedule || desired.rootDirectory !== null && desired.rootDirectory !== current.rootDirectory || desired.healthcheckPath !== null && desired.healthcheckPath !== current.healthcheckPath || desired.healthcheckTimeoutSeconds !== null && desired.healthcheckTimeoutSeconds !== current.healthcheckTimeoutSeconds || desired.runtimeMode !== null && desired.runtimeMode !== current.runtimeMode;
777
784
  if (!drifted) {
778
785
  return { instance: current, updated: false };
779
786
  }
@@ -795,6 +802,7 @@ mutation TreeseedRailwayServiceInstanceUpdateLegacy($serviceId: String!, $enviro
795
802
  input: {
796
803
  ...desired.buildCommand !== null ? { buildCommand: desired.buildCommand } : {},
797
804
  ...desired.startCommand !== null ? { startCommand: desired.startCommand } : {},
805
+ ...desired.cronSchedule !== null ? { cronSchedule: desired.cronSchedule } : {},
798
806
  ...desired.rootDirectory !== null ? { rootDirectory: desired.rootDirectory } : {},
799
807
  ...desired.healthcheckPath !== null ? { healthcheckPath: desired.healthcheckPath } : {},
800
808
  ...desired.healthcheckTimeoutSeconds !== null ? { healthcheckTimeout: desired.healthcheckTimeoutSeconds } : {},
@@ -822,6 +830,7 @@ mutation TreeseedRailwayServiceInstanceUpdateLegacy($serviceId: String!, $enviro
822
830
  id: instance.id || current.id,
823
831
  buildCommand: instance.buildCommand ?? desired.buildCommand,
824
832
  startCommand: instance.startCommand ?? desired.startCommand,
833
+ cronSchedule: instance.cronSchedule ?? desired.cronSchedule,
825
834
  rootDirectory: instance.rootDirectory ?? desired.rootDirectory,
826
835
  healthcheckPath: instance.healthcheckPath ?? desired.healthcheckPath,
827
836
  healthcheckTimeoutSeconds: instance.healthcheckTimeoutSeconds ?? desired.healthcheckTimeoutSeconds,
@@ -1,6 +1,6 @@
1
1
  import { type TreeseedBootstrapTaskPrefix, type TreeseedBootstrapWriter } from './bootstrap-runner.ts';
2
2
  export declare function deriveRailwayWorkerRunnerServiceName(projectSlug: any, index?: number): string;
3
- export declare function deriveRailwayWorkerRunnerVolumeName(serviceName: any): string;
3
+ export declare function deriveRailwayWorkerRunnerVolumeName(serviceName: any, environmentName?: string): string;
4
4
  export declare function railwayServiceRuntimeStartCommand(service: any): any;
5
5
  export declare function isUsableRailwayToken(value: any): boolean;
6
6
  export declare function resolveRailwayAuthToken(env?: NodeJS.ProcessEnv): string;
@@ -201,10 +201,10 @@ export declare function ensureRailwayScheduledJobs(tenantRoot: any, scope: any,
201
201
  logicalName: string;
202
202
  } | {
203
203
  projectId: string;
204
- id: any;
204
+ id: string | null;
205
205
  status: string;
206
- enabled: any;
207
- command: any;
206
+ enabled: boolean;
207
+ command: string | null;
208
208
  serviceId: string;
209
209
  environmentId: string;
210
210
  service: string;
@@ -257,16 +257,16 @@ export declare function verifyRailwayScheduledJobs(tenantRoot: any, scope: any,
257
257
  enabled: boolean;
258
258
  logicalName: string;
259
259
  } | {
260
- id: any;
260
+ id: string | null;
261
261
  projectId: string;
262
262
  serviceId: string;
263
263
  environmentId: string;
264
264
  ok: boolean;
265
265
  status: string;
266
266
  observed: {
267
- expression: any;
268
- command: any;
269
- enabled: any;
267
+ expression: string | null;
268
+ command: string | null;
269
+ enabled: boolean;
270
270
  } | null;
271
271
  message: string | undefined;
272
272
  service: string;
@@ -312,8 +312,8 @@ export declare function deployRailwayService(tenantRoot: any, service: any, { dr
312
312
  healthcheckTimeoutSeconds: number | null;
313
313
  runtimeMode: string | null;
314
314
  volume: {
315
- id: string;
316
- name: string;
315
+ id: any;
316
+ name: any;
317
317
  mountPath: any;
318
318
  created: boolean;
319
319
  updated: boolean;
@@ -11,11 +11,11 @@ import {
11
11
  ensureRailwayService,
12
12
  ensureRailwayServiceInstanceConfiguration,
13
13
  ensureRailwayServiceVolume,
14
+ getRailwayServiceInstance,
14
15
  listRailwayEnvironments,
15
16
  listRailwayProjects,
16
17
  listRailwayServices,
17
18
  normalizeRailwayEnvironmentName,
18
- railwayGraphqlRequest,
19
19
  resolveRailwayApiToken,
20
20
  resolveRailwayApiUrl,
21
21
  resolveRailwayWorkspace,
@@ -43,13 +43,12 @@ function deriveRailwayWorkerRunnerServiceName(projectSlug, index = WORKER_RUNNER
43
43
  const normalizedIndex = Math.max(1, Number.parseInt(String(index), 10) || WORKER_RUNNER_BOOTSTRAP_INDEX);
44
44
  return `${projectSlug}-worker-runner-${String(normalizedIndex).padStart(2, "0")}`;
45
45
  }
46
- function deriveRailwayWorkerRunnerVolumeName(serviceName) {
47
- return `${serviceName}-data`;
46
+ function deriveRailwayWorkerRunnerVolumeName(serviceName, environmentName = "") {
47
+ const environment = normalizeRailwayEnvironmentName(environmentName);
48
+ const environmentSuffix = environment && environment !== "production" ? `-${environment}` : "";
49
+ return `${serviceName}${environmentSuffix}-data`;
48
50
  }
49
51
  function railwayServiceRuntimeStartCommand(service) {
50
- if (service.key === "workdayManager" && Array.isArray(service.schedule) && service.schedule.length > 0) {
51
- return `node -e "console.log('workday-manager scheduled-only service idle')"`;
52
- }
53
52
  return service.startCommand;
54
53
  }
55
54
  function normalizeScheduleExpressions(value) {
@@ -73,6 +72,61 @@ function configuredEnvValue(env, name) {
73
72
  const value = env?.[name];
74
73
  return typeof value === "string" && value.trim() ? value.trim() : "";
75
74
  }
75
+ function parseRailwayJsonOutput(output) {
76
+ const trimmed = typeof output === "string" ? output.trim() : "";
77
+ if (!trimmed) {
78
+ return null;
79
+ }
80
+ try {
81
+ return JSON.parse(trimmed);
82
+ } catch {
83
+ }
84
+ const lines = trimmed.split(/\r?\n/u);
85
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
86
+ const candidate = lines.slice(index).join("\n").trim();
87
+ if (!candidate.startsWith("{") && !candidate.startsWith("[")) {
88
+ continue;
89
+ }
90
+ try {
91
+ return JSON.parse(candidate);
92
+ } catch {
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+ function normalizeRailwayCliVolume(value, { serviceId, environmentId, fallbackName, fallbackMountPath }) {
98
+ if (!value || typeof value !== "object") {
99
+ return null;
100
+ }
101
+ const record = value;
102
+ const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : "";
103
+ if (!id) {
104
+ return null;
105
+ }
106
+ const name = typeof record.name === "string" && record.name.trim() ? record.name.trim() : fallbackName;
107
+ const mountPath = typeof record.mountPath === "string" && record.mountPath.trim() ? record.mountPath.trim() : fallbackMountPath;
108
+ const sizeMb = typeof record.sizeMB === "number" ? record.sizeMB : null;
109
+ const currentSizeMb = typeof record.currentSizeMB === "number" ? record.currentSizeMB : null;
110
+ return {
111
+ id,
112
+ name,
113
+ projectId: null,
114
+ instances: [{
115
+ id,
116
+ serviceId,
117
+ environmentId,
118
+ mountPath,
119
+ sizeGb: sizeMb === null ? null : sizeMb / 1e3,
120
+ usedGb: currentSizeMb === null ? null : currentSizeMb / 1e3
121
+ }]
122
+ };
123
+ }
124
+ function normalizeRailwayCliVolumeList(value, options) {
125
+ if (!value || typeof value !== "object" || !Array.isArray(value.volumes)) {
126
+ return [];
127
+ }
128
+ return value.volumes.map((entry) => normalizeRailwayCliVolume(entry, options)).filter(Boolean);
129
+ }
76
130
  function isUsableRailwayToken(value) {
77
131
  return typeof value === "string" && value.trim().length >= 8;
78
132
  }
@@ -636,7 +690,6 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
636
690
  if (typeof effectiveApiToken !== "string" || effectiveApiToken.trim().length === 0) {
637
691
  throw new Error("Configure RAILWAY_API_TOKEN before deploying Railway-managed services.");
638
692
  }
639
- const queries = defaultRailwayScheduleQueries();
640
693
  const results = [];
641
694
  try {
642
695
  for (const schedule of schedules) {
@@ -658,21 +711,12 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
658
711
  });
659
712
  continue;
660
713
  }
661
- const variables = {
662
- projectId: target.project.id,
714
+ const current = await getRailwayServiceInstance({
663
715
  serviceId: target.service.id,
664
- environmentId: target.environment.id
665
- };
666
- const listed = await railwayGraphqlRequest({
667
- query: queries.listQuery,
668
- variables,
669
- apiToken: effectiveApiToken,
670
- apiUrl: effectiveApiUrl,
716
+ environmentId: target.environment.id,
717
+ env: { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
671
718
  fetchImpl
672
719
  });
673
- const existing = collectRailwaySchedules(listed?.data).find(
674
- (entry) => entry.id && entry.id === schedule.id || entry.name && entry.name === schedule.logicalName || entry.expression === schedule.expression && entry.serviceId === schedule.serviceId && (!schedule.environmentId || entry.environmentId === schedule.environmentId)
675
- );
676
720
  const desired = {
677
721
  name: schedule.logicalName,
678
722
  schedule: schedule.expression,
@@ -680,90 +724,38 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
680
724
  enabled: schedule.enabled !== false
681
725
  };
682
726
  const drifted = Boolean(
683
- existing && (existing.expression !== desired.schedule || (existing.command ?? null) !== (desired.command ?? null) || existing.enabled !== desired.enabled)
727
+ current.id && (current.cronSchedule !== desired.schedule || (current.startCommand ?? null) !== (desired.command ?? null))
684
728
  );
685
729
  if (dryRun) {
686
730
  results.push({
687
731
  ...schedule,
688
- projectId: variables.projectId,
689
- id: existing?.id ?? null,
690
- status: existing ? drifted ? "planned_update" : "planned_noop" : "planned_create",
732
+ projectId: target.project.id,
733
+ id: current.id,
734
+ status: current.id ? drifted ? "planned_update" : "planned_noop" : "planned_create",
691
735
  enabled: desired.enabled,
692
736
  command: desired.command,
693
- serviceId: variables.serviceId,
694
- environmentId: variables.environmentId
695
- });
696
- continue;
697
- }
698
- if (!existing) {
699
- const created = await railwayGraphqlRequest({
700
- query: queries.createMutation,
701
- variables: {
702
- ...variables,
703
- name: desired.name,
704
- schedule: desired.schedule,
705
- command: desired.command,
706
- enabled: desired.enabled
707
- },
708
- apiToken: effectiveApiToken,
709
- apiUrl: effectiveApiUrl,
710
- fetchImpl
711
- });
712
- const createdSchedule = collectRailwaySchedules(created?.data)[0];
713
- if (!createdSchedule?.id) {
714
- throw new Error(`Railway schedule create did not return an id for ${schedule.logicalName}.`);
715
- }
716
- results.push({
717
- ...schedule,
718
- projectId: variables.projectId,
719
- id: createdSchedule.id,
720
- status: "created",
721
- enabled: createdSchedule.enabled,
722
- command: createdSchedule.command ?? desired.command,
723
- serviceId: variables.serviceId,
724
- environmentId: variables.environmentId
725
- });
726
- continue;
727
- }
728
- if (drifted) {
729
- const updated = await railwayGraphqlRequest({
730
- query: queries.updateMutation,
731
- variables: {
732
- id: existing.id,
733
- name: desired.name,
734
- schedule: desired.schedule,
735
- command: desired.command,
736
- enabled: desired.enabled
737
- },
738
- apiToken: effectiveApiToken,
739
- apiUrl: effectiveApiUrl,
740
- fetchImpl
741
- });
742
- const updatedSchedule = collectRailwaySchedules(updated?.data)[0];
743
- if (!updatedSchedule?.id) {
744
- throw new Error(`Railway schedule update did not return an id for ${schedule.logicalName}.`);
745
- }
746
- results.push({
747
- ...schedule,
748
- projectId: variables.projectId,
749
- id: updatedSchedule.id,
750
- status: "updated",
751
- enabled: updatedSchedule.enabled,
752
- command: updatedSchedule.command ?? desired.command,
753
- serviceId: variables.serviceId,
754
- environmentId: variables.environmentId
737
+ serviceId: target.service.id,
738
+ environmentId: target.environment.id
755
739
  });
756
740
  continue;
757
741
  }
742
+ const updated = await ensureRailwayServiceInstanceConfiguration({
743
+ serviceId: target.service.id,
744
+ environmentId: target.environment.id,
745
+ startCommand: desired.command,
746
+ cronSchedule: desired.schedule,
747
+ env: { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
748
+ fetchImpl
749
+ });
758
750
  results.push({
759
751
  ...schedule,
760
- projectId: variables.projectId,
761
- id: existing.id,
762
- status: "noop",
763
- enabled: existing.enabled,
764
- command: existing.command ?? desired.command,
765
- serviceId: variables.serviceId,
766
- environmentId: variables.environmentId
752
+ projectId: target.project.id,
753
+ id: updated.instance.id,
754
+ status: updated.updated ? "updated" : "noop",
755
+ enabled: desired.enabled,
756
+ command: desired.command,
757
+ serviceId: target.service.id,
758
+ environmentId: target.environment.id
767
759
  });
768
760
  }
769
761
  } catch (error) {
@@ -785,7 +777,6 @@ async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch
785
777
  const effectiveApiToken = apiToken || env?.RAILWAY_API_TOKEN;
786
778
  const effectiveApiUrl = apiUrl || resolveRailwayApiUrl(env);
787
779
  const configured = configuredRailwayScheduledJobs(tenantRoot, scope);
788
- const queries = defaultRailwayScheduleQueries();
789
780
  const checks = [];
790
781
  try {
791
782
  for (const schedule of configured) {
@@ -804,36 +795,28 @@ async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch
804
795
  });
805
796
  continue;
806
797
  }
807
- const listed = await railwayGraphqlRequest({
808
- query: queries.listQuery,
809
- variables: {
810
- projectId: target.project.id,
811
- serviceId: target.service.id,
812
- environmentId: target.environment.id
813
- },
814
- apiToken: effectiveApiToken,
815
- apiUrl: effectiveApiUrl,
798
+ const existing = await getRailwayServiceInstance({
799
+ serviceId: target.service.id,
800
+ environmentId: target.environment.id,
801
+ env: { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
816
802
  fetchImpl
817
803
  });
818
- const existing = collectRailwaySchedules(listed?.data).find(
819
- (entry) => entry.name && entry.name === schedule.logicalName || entry.expression === schedule.expression && entry.serviceId === schedule.serviceId && (!schedule.environmentId || entry.environmentId === schedule.environmentId)
820
- );
821
804
  checks.push({
822
805
  ...schedule,
823
- id: existing?.id ?? null,
806
+ id: existing.id,
824
807
  projectId: target.project.id,
825
808
  serviceId: target.service.id,
826
809
  environmentId: target.environment.id,
827
810
  ok: Boolean(
828
- existing && existing.expression === schedule.expression && (existing.command ?? null) === (schedule.command ?? null) && existing.enabled !== false
811
+ existing.id && existing.cronSchedule === schedule.expression && (existing.startCommand ?? null) === (schedule.command ?? null)
829
812
  ),
830
- status: existing ? "checked" : "missing",
831
- observed: existing ? {
832
- expression: existing.expression,
833
- command: existing.command ?? null,
834
- enabled: existing.enabled
813
+ status: existing.id ? "checked" : "missing",
814
+ observed: existing.id ? {
815
+ expression: existing.cronSchedule,
816
+ command: existing.startCommand ?? null,
817
+ enabled: true
835
818
  } : null,
836
- message: existing ? void 0 : `Railway schedule ${schedule.logicalName} is missing for ${target.service.name} in ${target.environment.name}.`
819
+ message: existing.id ? void 0 : `Railway schedule ${schedule.logicalName} is missing for ${target.service.name} in ${target.environment.name}.`
837
820
  });
838
821
  }
839
822
  } catch (error) {
@@ -950,6 +933,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
950
933
  environmentId: environment.id,
951
934
  buildCommand: service.buildCommand,
952
935
  startCommand: railwayServiceRuntimeStartCommand(service),
936
+ cronSchedule: service.schedule?.[0] ?? null,
953
937
  rootDirectory: relativeRailwayRootDir(tenantRoot, service.rootDir),
954
938
  healthcheckPath: service.healthcheckPath,
955
939
  healthcheckTimeoutSeconds: service.healthcheckTimeoutSeconds,
@@ -959,11 +943,14 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
959
943
  env
960
944
  }) : null;
961
945
  const volumeMountPath = service.runnerPool?.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH;
962
- const volumeConfiguration = wantsRunnerVolume ? await ensureRailwayServiceVolume({
946
+ const volumeConfiguration = wantsRunnerVolume ? await ensureRailwayServiceVolumeWithCliFallback({
947
+ tenantRoot,
963
948
  projectId: project.id,
964
949
  environmentId: environment.id,
950
+ environmentName: environment.name,
965
951
  serviceId: railwayService.id,
966
- name: deriveRailwayWorkerRunnerVolumeName(railwayService.name),
952
+ serviceName: railwayService.name,
953
+ name: deriveRailwayWorkerRunnerVolumeName(railwayService.name, environment.name),
967
954
  mountPath: volumeMountPath,
968
955
  env
969
956
  }) : null;
@@ -975,7 +962,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
975
962
  variables: {
976
963
  TREESEED_RUNNER_SERVICE_NAME: railwayService.name,
977
964
  TREESEED_RUNNER_VOLUME_ROOT: volumeMountPath,
978
- TREESEED_RUNNER_VOLUME_NAME: volumeConfiguration?.volume.name ?? deriveRailwayWorkerRunnerVolumeName(railwayService.name),
965
+ TREESEED_RUNNER_VOLUME_NAME: volumeConfiguration?.volume.name ?? deriveRailwayWorkerRunnerVolumeName(railwayService.name, environment.name),
979
966
  TREESEED_WORKER_IDLE_EXIT_MS: configuredEnvValue(env, "TREESEED_WORKER_IDLE_EXIT_MS") || "60000",
980
967
  ...volumeConfiguration?.volume.id ? { TREESEED_RUNNER_VOLUME_ID: volumeConfiguration.volume.id } : {}
981
968
  },
@@ -994,6 +981,84 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
994
981
  } : null
995
982
  };
996
983
  }
984
+ async function ensureRailwayServiceVolumeWithCliFallback({
985
+ tenantRoot,
986
+ projectId,
987
+ environmentId,
988
+ environmentName,
989
+ serviceId,
990
+ serviceName,
991
+ name,
992
+ mountPath,
993
+ env = process.env
994
+ }) {
995
+ try {
996
+ return await ensureRailwayServiceVolume({
997
+ projectId,
998
+ environmentId,
999
+ serviceId,
1000
+ name,
1001
+ mountPath,
1002
+ env
1003
+ });
1004
+ } catch (error) {
1005
+ const message = error instanceof Error ? error.message : String(error);
1006
+ if (!message.includes("Problem processing request")) {
1007
+ throw error;
1008
+ }
1009
+ }
1010
+ const cliOptions = {
1011
+ cwd: tenantRoot,
1012
+ capture: true,
1013
+ env
1014
+ };
1015
+ const volumeArgs = ["volume", "--service", serviceId, "--environment", environmentId];
1016
+ const listResult = runRailway([...volumeArgs, "list", "--json"], cliOptions);
1017
+ const existingVolumes = normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
1018
+ serviceId,
1019
+ environmentId,
1020
+ fallbackName: name,
1021
+ fallbackMountPath: mountPath
1022
+ });
1023
+ let volume = existingVolumes.find((entry) => entry.name === name) ?? existingVolumes.find((entry) => entry.instances.some((instance2) => instance2.mountPath === mountPath)) ?? existingVolumes[0] ?? null;
1024
+ let created = false;
1025
+ let updated = false;
1026
+ if (!volume) {
1027
+ const createResult = runRailway([...volumeArgs, "add", "--mount-path", mountPath, "--json"], cliOptions);
1028
+ volume = normalizeRailwayCliVolume(parseRailwayJsonOutput(createResult.stdout ?? ""), {
1029
+ serviceId,
1030
+ environmentId,
1031
+ fallbackName: name,
1032
+ fallbackMountPath: mountPath
1033
+ });
1034
+ if (!volume) {
1035
+ throw new Error(`Railway CLI volume add did not return a usable volume for ${serviceName} in ${environmentName}.`);
1036
+ }
1037
+ created = true;
1038
+ }
1039
+ const instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
1040
+ if (volume.name !== name || instance?.mountPath !== mountPath) {
1041
+ const updateResult = runRailway([...volumeArgs, "update", "--volume", volume.id, "--name", name, "--mount-path", mountPath, "--json"], cliOptions);
1042
+ const updatedVolume = normalizeRailwayCliVolume(parseRailwayJsonOutput(updateResult.stdout ?? ""), {
1043
+ serviceId,
1044
+ environmentId,
1045
+ fallbackName: name,
1046
+ fallbackMountPath: mountPath
1047
+ });
1048
+ volume = updatedVolume ?? {
1049
+ ...volume,
1050
+ name,
1051
+ instances: volume.instances.map((entry) => ({ ...entry, mountPath }))
1052
+ };
1053
+ updated = true;
1054
+ }
1055
+ return {
1056
+ volume,
1057
+ instance: volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null,
1058
+ created,
1059
+ updated
1060
+ };
1061
+ }
997
1062
  async function deployRailwayService(tenantRoot, service, {
998
1063
  dryRun = false,
999
1064
  write,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.6.40",
3
+ "version": "0.6.42",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {