@treeseed/sdk 0.5.2 → 0.6.0
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.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +46 -0
- package/dist/operations/providers/default.js +1 -1
- package/dist/operations/services/config-runtime.d.ts +49 -42
- package/dist/operations/services/config-runtime.js +465 -142
- package/dist/operations/services/deploy.d.ts +298 -0
- package/dist/operations/services/deploy.js +381 -137
- package/dist/operations/services/git-workflow.d.ts +9 -0
- package/dist/operations/services/git-workflow.js +32 -0
- package/dist/operations/services/github-api.d.ts +115 -0
- package/dist/operations/services/github-api.js +455 -0
- package/dist/operations/services/github-automation.d.ts +19 -33
- package/dist/operations/services/github-automation.js +44 -131
- package/dist/operations/services/key-agent.d.ts +20 -1
- package/dist/operations/services/key-agent.js +267 -102
- package/dist/operations/services/knowledge-coop-launch.d.ts +2 -3
- package/dist/operations/services/knowledge-coop-launch.js +26 -12
- package/dist/operations/services/project-platform.d.ts +157 -150
- package/dist/operations/services/project-platform.js +129 -26
- package/dist/operations/services/railway-api.d.ts +244 -0
- package/dist/operations/services/railway-api.js +882 -0
- package/dist/operations/services/railway-deploy.d.ts +171 -27
- package/dist/operations/services/railway-deploy.js +672 -172
- package/dist/operations/services/runtime-tools.d.ts +18 -0
- package/dist/operations/services/runtime-tools.js +19 -6
- package/dist/operations/services/workspace-preflight.js +2 -2
- package/dist/platform/contracts.d.ts +7 -0
- package/dist/platform/deploy-config.js +23 -0
- package/dist/platform/deploy-runtime.d.ts +1 -0
- package/dist/platform/deploy-runtime.js +7 -9
- package/dist/platform/env.yaml +10 -9
- package/dist/platform/environment.js +4 -0
- package/dist/platform/plugin.d.ts +6 -0
- package/dist/platform/plugins/constants.d.ts +1 -0
- package/dist/platform/plugins/constants.js +1 -0
- package/dist/platform/plugins/runtime.d.ts +4 -0
- package/dist/platform/plugins/runtime.js +8 -1
- package/dist/platform/published-content.js +27 -4
- package/dist/platform/tenant/runtime-config.js +33 -24
- package/dist/plugin-default.d.ts +1 -0
- package/dist/plugin-default.js +1 -0
- package/dist/reconcile/builtin-adapters.d.ts +3 -0
- package/dist/reconcile/builtin-adapters.js +2093 -0
- package/dist/reconcile/contracts.d.ts +155 -0
- package/dist/reconcile/contracts.js +0 -0
- package/dist/reconcile/desired-state.d.ts +179 -0
- package/dist/reconcile/desired-state.js +319 -0
- package/dist/reconcile/engine.d.ts +405 -0
- package/dist/reconcile/engine.js +356 -0
- package/dist/reconcile/errors.d.ts +5 -0
- package/dist/reconcile/errors.js +13 -0
- package/dist/reconcile/index.d.ts +7 -0
- package/dist/reconcile/index.js +7 -0
- package/dist/reconcile/registry.d.ts +7 -0
- package/dist/reconcile/registry.js +64 -0
- package/dist/reconcile/state.d.ts +7 -0
- package/dist/reconcile/state.js +303 -0
- package/dist/reconcile/units.d.ts +6 -0
- package/dist/reconcile/units.js +68 -0
- package/dist/scripts/config-treeseed.js +27 -19
- package/dist/scripts/tenant-deploy.js +35 -14
- package/dist/workflow/operations.js +127 -22
- package/dist/workflow-support.d.ts +3 -1
- package/dist/workflow-support.js +50 -0
- package/dist/workflow.d.ts +2 -0
- package/package.json +7 -1
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
const DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
|
|
2
|
+
const DEFAULT_RAILWAY_WORKSPACE = "knowledge-coop";
|
|
3
|
+
function normalizeRailwayEnvironmentName(value) {
|
|
4
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
5
|
+
if (!normalized) {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
return normalized === "prod" ? "production" : normalized;
|
|
9
|
+
}
|
|
10
|
+
function configuredEnvValue(env, name) {
|
|
11
|
+
const value = env?.[name];
|
|
12
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
13
|
+
}
|
|
14
|
+
function isUsableRailwayToken(value) {
|
|
15
|
+
return typeof value === "string" && value.trim().length >= 8;
|
|
16
|
+
}
|
|
17
|
+
function resolveRailwayApiToken(env = process.env) {
|
|
18
|
+
const token = configuredEnvValue(env, "RAILWAY_API_TOKEN");
|
|
19
|
+
return isUsableRailwayToken(token) ? token : "";
|
|
20
|
+
}
|
|
21
|
+
function resolveRailwayApiUrl(env = process.env) {
|
|
22
|
+
return configuredEnvValue(env, "TREESEED_RAILWAY_API_URL") || DEFAULT_RAILWAY_API_URL;
|
|
23
|
+
}
|
|
24
|
+
function resolveRailwayWorkspace(env = process.env) {
|
|
25
|
+
return configuredEnvValue(env, "TREESEED_RAILWAY_WORKSPACE") || DEFAULT_RAILWAY_WORKSPACE;
|
|
26
|
+
}
|
|
27
|
+
function normalizeRailwayErrorMessage(payload, fallbackStatus) {
|
|
28
|
+
if (payload && typeof payload === "object" && Array.isArray(payload.errors) && payload.errors.length > 0) {
|
|
29
|
+
const first = payload.errors[0];
|
|
30
|
+
if (first && typeof first === "object" && typeof first.message === "string") {
|
|
31
|
+
return first.message;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return typeof fallbackStatus === "number" ? `Railway API request failed with ${fallbackStatus}.` : "Railway API request failed.";
|
|
35
|
+
}
|
|
36
|
+
function isRetryableRailwayStatus(status) {
|
|
37
|
+
return status === 408 || status === 429 || status >= 500;
|
|
38
|
+
}
|
|
39
|
+
function parseRetryAfterMs(value) {
|
|
40
|
+
if (!value) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const seconds = Number(value);
|
|
44
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
45
|
+
return Math.round(seconds * 1e3);
|
|
46
|
+
}
|
|
47
|
+
const absoluteTime = Date.parse(value);
|
|
48
|
+
if (Number.isFinite(absoluteTime)) {
|
|
49
|
+
return Math.max(0, absoluteTime - Date.now());
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function markRailwayTransientError(error, options = {}) {
|
|
54
|
+
const tagged = error;
|
|
55
|
+
tagged.treeseedTransient = true;
|
|
56
|
+
if (typeof options.retryAfterMs === "number" && Number.isFinite(options.retryAfterMs) && options.retryAfterMs >= 0) {
|
|
57
|
+
tagged.treeseedRetryAfterMs = options.retryAfterMs;
|
|
58
|
+
}
|
|
59
|
+
return tagged;
|
|
60
|
+
}
|
|
61
|
+
function isTransientRailwayRequestError(error) {
|
|
62
|
+
if (error && typeof error === "object" && error.treeseedTransient === true) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
66
|
+
return /fetch failed|timed out|etimedout|econnreset|enetunreach|temporarily unavailable|aborted|rate limit|too many requests|429/iu.test(message);
|
|
67
|
+
}
|
|
68
|
+
function railwayConnectionLabel(value) {
|
|
69
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
70
|
+
}
|
|
71
|
+
function normalizeConnectionNodes(connection, mapper) {
|
|
72
|
+
if (!connection || typeof connection !== "object" || !Array.isArray(connection.edges)) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
return connection.edges.map((edge) => {
|
|
76
|
+
if (!edge || typeof edge !== "object") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const node = edge.node;
|
|
80
|
+
return node && typeof node === "object" ? mapper(node) : null;
|
|
81
|
+
}).filter(Boolean);
|
|
82
|
+
}
|
|
83
|
+
function normalizeWorkspace(node) {
|
|
84
|
+
const id = railwayConnectionLabel(node.id);
|
|
85
|
+
const name = railwayConnectionLabel(node.name);
|
|
86
|
+
if (!id || !name) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return { id, name };
|
|
90
|
+
}
|
|
91
|
+
function normalizeEnvironment(node) {
|
|
92
|
+
const id = railwayConnectionLabel(node.id);
|
|
93
|
+
const name = railwayConnectionLabel(node.name);
|
|
94
|
+
if (!id || !name) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return { id, name };
|
|
98
|
+
}
|
|
99
|
+
function normalizeService(node) {
|
|
100
|
+
const id = railwayConnectionLabel(node.id);
|
|
101
|
+
const name = railwayConnectionLabel(node.name);
|
|
102
|
+
if (!id || !name) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return { id, name };
|
|
106
|
+
}
|
|
107
|
+
function normalizeProject(node) {
|
|
108
|
+
const id = railwayConnectionLabel(node.id);
|
|
109
|
+
const name = railwayConnectionLabel(node.name);
|
|
110
|
+
if (!id || !name) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
id,
|
|
115
|
+
name,
|
|
116
|
+
workspaceId: railwayConnectionLabel(node.workspaceId) || null,
|
|
117
|
+
environments: normalizeConnectionNodes(node.environments, normalizeEnvironment),
|
|
118
|
+
services: normalizeConnectionNodes(node.services, normalizeService)
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function normalizeVariableMap(value) {
|
|
122
|
+
if (!value) {
|
|
123
|
+
return {};
|
|
124
|
+
}
|
|
125
|
+
if (typeof value === "string") {
|
|
126
|
+
try {
|
|
127
|
+
return normalizeVariableMap(JSON.parse(value));
|
|
128
|
+
} catch {
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
return Object.fromEntries(
|
|
136
|
+
Object.entries(value).map(([key, entryValue]) => {
|
|
137
|
+
if (typeof entryValue === "string") {
|
|
138
|
+
return [key, entryValue];
|
|
139
|
+
}
|
|
140
|
+
if (entryValue && typeof entryValue === "object" && typeof entryValue.value === "string") {
|
|
141
|
+
return [key, entryValue.value];
|
|
142
|
+
}
|
|
143
|
+
return [key, null];
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
function normalizeRailwayNumber(value) {
|
|
148
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
if (typeof value === "string" && value.trim()) {
|
|
152
|
+
const parsed = Number(value);
|
|
153
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
function normalizeRailwayCustomDomainDnsRecord(node) {
|
|
158
|
+
const fqdn = railwayConnectionLabel(node.fqdn);
|
|
159
|
+
if (!fqdn) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
fqdn,
|
|
164
|
+
hostlabel: railwayConnectionLabel(node.hostlabel),
|
|
165
|
+
recordType: railwayConnectionLabel(node.recordType),
|
|
166
|
+
requiredValue: railwayConnectionLabel(node.requiredValue),
|
|
167
|
+
currentValue: railwayConnectionLabel(node.currentValue),
|
|
168
|
+
status: railwayConnectionLabel(node.status),
|
|
169
|
+
zone: railwayConnectionLabel(node.zone),
|
|
170
|
+
purpose: railwayConnectionLabel(node.purpose)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function normalizeRailwayCustomDomain(node) {
|
|
174
|
+
const id = railwayConnectionLabel(node.id);
|
|
175
|
+
const domain = railwayConnectionLabel(node.domain);
|
|
176
|
+
if (!id || !domain) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const status = node.status && typeof node.status === "object" ? node.status : {};
|
|
180
|
+
const dnsRecords = Array.isArray(status.dnsRecords) ? status.dnsRecords.map((entry) => entry && typeof entry === "object" ? normalizeRailwayCustomDomainDnsRecord(entry) : null).filter(Boolean) : [];
|
|
181
|
+
return {
|
|
182
|
+
id,
|
|
183
|
+
domain,
|
|
184
|
+
environmentId: railwayConnectionLabel(node.environmentId),
|
|
185
|
+
serviceId: railwayConnectionLabel(node.serviceId),
|
|
186
|
+
targetPort: typeof node.targetPort === "number" && Number.isFinite(node.targetPort) ? node.targetPort : null,
|
|
187
|
+
verified: status.verified === true,
|
|
188
|
+
certificateStatus: railwayConnectionLabel(status.certificateStatus) || null,
|
|
189
|
+
verificationDnsHost: railwayConnectionLabel(status.verificationDnsHost) || null,
|
|
190
|
+
verificationToken: railwayConnectionLabel(status.verificationToken) || null,
|
|
191
|
+
dnsRecords
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
async function railwayGraphqlRequest({
|
|
195
|
+
query,
|
|
196
|
+
variables,
|
|
197
|
+
env = process.env,
|
|
198
|
+
apiToken,
|
|
199
|
+
apiUrl,
|
|
200
|
+
fetchImpl = fetch,
|
|
201
|
+
timeoutMs = 15e3,
|
|
202
|
+
retries = 5
|
|
203
|
+
}) {
|
|
204
|
+
const token = apiToken || resolveRailwayApiToken(env);
|
|
205
|
+
if (!token) {
|
|
206
|
+
throw new Error("Configure RAILWAY_API_TOKEN before invoking Railway APIs.");
|
|
207
|
+
}
|
|
208
|
+
let attempt = 0;
|
|
209
|
+
for (; ; ) {
|
|
210
|
+
const controller = new AbortController();
|
|
211
|
+
let timer = null;
|
|
212
|
+
try {
|
|
213
|
+
const response = await Promise.race([
|
|
214
|
+
fetchImpl(apiUrl || resolveRailwayApiUrl(env), {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: {
|
|
217
|
+
authorization: `Bearer ${token}`,
|
|
218
|
+
"content-type": "application/json"
|
|
219
|
+
},
|
|
220
|
+
body: JSON.stringify({ query, variables }),
|
|
221
|
+
signal: controller.signal
|
|
222
|
+
}),
|
|
223
|
+
new Promise((_, reject) => {
|
|
224
|
+
timer = setTimeout(() => {
|
|
225
|
+
controller.abort();
|
|
226
|
+
reject(markRailwayTransientError(new Error(`Railway API request timed out after ${timeoutMs}ms.`)));
|
|
227
|
+
}, timeoutMs);
|
|
228
|
+
})
|
|
229
|
+
]);
|
|
230
|
+
const payload = await response.json().catch(() => ({}));
|
|
231
|
+
if (!response.ok || Array.isArray(payload.errors) && payload.errors.length > 0) {
|
|
232
|
+
const message = normalizeRailwayErrorMessage(payload, response.status);
|
|
233
|
+
const hasGraphqlErrors = Array.isArray(payload.errors) && payload.errors.length > 0;
|
|
234
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
|
|
235
|
+
const shouldRetry = isRetryableRailwayStatus(response.status) || /rate limit|too many requests/iu.test(message);
|
|
236
|
+
const error = new Error(message);
|
|
237
|
+
if (shouldRetry || hasGraphqlErrors && /rate limit|too many requests/iu.test(message)) {
|
|
238
|
+
throw markRailwayTransientError(error, { retryAfterMs });
|
|
239
|
+
}
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
return payload;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (attempt >= retries || !isTransientRailwayRequestError(error)) {
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
attempt += 1;
|
|
248
|
+
const retryAfterMs = error && typeof error === "object" && typeof error.treeseedRetryAfterMs === "number" ? Math.max(0, Number(error.treeseedRetryAfterMs)) : null;
|
|
249
|
+
const backoffMs = retryAfterMs ?? Math.min(500 * 2 ** (attempt - 1), 4e3);
|
|
250
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
251
|
+
} finally {
|
|
252
|
+
if (timer) {
|
|
253
|
+
clearTimeout(timer);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function getRailwayAuthProfile({
|
|
259
|
+
env = process.env,
|
|
260
|
+
fetchImpl = fetch
|
|
261
|
+
}) {
|
|
262
|
+
const payload = await railwayGraphqlRequest({
|
|
263
|
+
query: `
|
|
264
|
+
query TreeseedRailwayAuthProfile {
|
|
265
|
+
me {
|
|
266
|
+
id
|
|
267
|
+
name
|
|
268
|
+
email
|
|
269
|
+
workspaces {
|
|
270
|
+
id
|
|
271
|
+
name
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
`.trim(),
|
|
276
|
+
env,
|
|
277
|
+
fetchImpl
|
|
278
|
+
});
|
|
279
|
+
const me = payload.data?.me;
|
|
280
|
+
return {
|
|
281
|
+
id: railwayConnectionLabel(me?.id) || null,
|
|
282
|
+
name: railwayConnectionLabel(me?.name) || null,
|
|
283
|
+
email: railwayConnectionLabel(me?.email) || null,
|
|
284
|
+
workspaces: Array.isArray(me?.workspaces) ? me.workspaces.map((workspace) => workspace && typeof workspace === "object" ? normalizeWorkspace(workspace) : null).filter(Boolean) : []
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async function resolveRailwayWorkspaceContext({
|
|
288
|
+
env = process.env,
|
|
289
|
+
workspace,
|
|
290
|
+
fetchImpl = fetch
|
|
291
|
+
}) {
|
|
292
|
+
const desired = (workspace || resolveRailwayWorkspace(env)).trim();
|
|
293
|
+
const profile = await getRailwayAuthProfile({ env, fetchImpl });
|
|
294
|
+
const match = profile.workspaces.find((candidate) => candidate.id === desired || candidate.name === desired) ?? null;
|
|
295
|
+
if (!match) {
|
|
296
|
+
const available = profile.workspaces.map((candidate) => candidate.name).join(", ") || "(none)";
|
|
297
|
+
throw new Error(`Railway workspace ${desired} is not visible to the current token. Available workspaces: ${available}.`);
|
|
298
|
+
}
|
|
299
|
+
return match;
|
|
300
|
+
}
|
|
301
|
+
async function listRailwayProjects({
|
|
302
|
+
env = process.env,
|
|
303
|
+
workspaceId,
|
|
304
|
+
fetchImpl = fetch
|
|
305
|
+
}) {
|
|
306
|
+
const payload = await railwayGraphqlRequest({
|
|
307
|
+
query: `
|
|
308
|
+
query TreeseedRailwayProjects($workspaceId: String!, $first: Int!) {
|
|
309
|
+
projects(workspaceId: $workspaceId, first: $first) {
|
|
310
|
+
edges {
|
|
311
|
+
node {
|
|
312
|
+
id
|
|
313
|
+
name
|
|
314
|
+
workspaceId
|
|
315
|
+
environments(first: 50) {
|
|
316
|
+
edges {
|
|
317
|
+
node {
|
|
318
|
+
id
|
|
319
|
+
name
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
services(first: 50) {
|
|
324
|
+
edges {
|
|
325
|
+
node {
|
|
326
|
+
id
|
|
327
|
+
name
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
`.trim(),
|
|
336
|
+
variables: { workspaceId, first: 100 },
|
|
337
|
+
env,
|
|
338
|
+
fetchImpl
|
|
339
|
+
});
|
|
340
|
+
return normalizeConnectionNodes(payload.data?.projects, normalizeProject);
|
|
341
|
+
}
|
|
342
|
+
async function getRailwayProject({
|
|
343
|
+
projectId,
|
|
344
|
+
env = process.env,
|
|
345
|
+
fetchImpl = fetch
|
|
346
|
+
}) {
|
|
347
|
+
const payload = await railwayGraphqlRequest({
|
|
348
|
+
query: `
|
|
349
|
+
query TreeseedRailwayProject($projectId: String!) {
|
|
350
|
+
project(id: $projectId) {
|
|
351
|
+
id
|
|
352
|
+
name
|
|
353
|
+
workspaceId
|
|
354
|
+
environments(first: 50) {
|
|
355
|
+
edges {
|
|
356
|
+
node {
|
|
357
|
+
id
|
|
358
|
+
name
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
services(first: 50) {
|
|
363
|
+
edges {
|
|
364
|
+
node {
|
|
365
|
+
id
|
|
366
|
+
name
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
`.trim(),
|
|
373
|
+
variables: { projectId },
|
|
374
|
+
env,
|
|
375
|
+
fetchImpl
|
|
376
|
+
});
|
|
377
|
+
return payload.data?.project ? normalizeProject(payload.data.project) : null;
|
|
378
|
+
}
|
|
379
|
+
async function ensureRailwayProject({
|
|
380
|
+
projectName,
|
|
381
|
+
projectId,
|
|
382
|
+
defaultEnvironmentName = "staging",
|
|
383
|
+
env = process.env,
|
|
384
|
+
workspace,
|
|
385
|
+
fetchImpl = fetch
|
|
386
|
+
}) {
|
|
387
|
+
const workspaceContext = await resolveRailwayWorkspaceContext({ env, workspace, fetchImpl });
|
|
388
|
+
const projects = await listRailwayProjects({ env, workspaceId: workspaceContext.id, fetchImpl });
|
|
389
|
+
const desiredProjectName = railwayConnectionLabel(projectName);
|
|
390
|
+
const desiredProjectId = railwayConnectionLabel(projectId);
|
|
391
|
+
const existing = projects.find(
|
|
392
|
+
(project2) => desiredProjectId && project2.id === desiredProjectId || desiredProjectName && project2.name === desiredProjectName
|
|
393
|
+
) ?? null;
|
|
394
|
+
if (existing) {
|
|
395
|
+
return { workspace: workspaceContext, project: existing, created: false };
|
|
396
|
+
}
|
|
397
|
+
if (!desiredProjectName) {
|
|
398
|
+
throw new Error("Railway project creation requires a project name.");
|
|
399
|
+
}
|
|
400
|
+
const created = await railwayGraphqlRequest({
|
|
401
|
+
query: `
|
|
402
|
+
mutation TreeseedRailwayProjectCreate($input: ProjectCreateInput!) {
|
|
403
|
+
projectCreate(input: $input) {
|
|
404
|
+
id
|
|
405
|
+
name
|
|
406
|
+
workspaceId
|
|
407
|
+
environments(first: 50) {
|
|
408
|
+
edges {
|
|
409
|
+
node {
|
|
410
|
+
id
|
|
411
|
+
name
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
services(first: 50) {
|
|
416
|
+
edges {
|
|
417
|
+
node {
|
|
418
|
+
id
|
|
419
|
+
name
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
`.trim(),
|
|
426
|
+
variables: {
|
|
427
|
+
input: {
|
|
428
|
+
name: desiredProjectName,
|
|
429
|
+
workspaceId: workspaceContext.id,
|
|
430
|
+
defaultEnvironmentName
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
env,
|
|
434
|
+
fetchImpl
|
|
435
|
+
});
|
|
436
|
+
const project = created.data?.projectCreate ? normalizeProject(created.data.projectCreate) : null;
|
|
437
|
+
if (!project) {
|
|
438
|
+
throw new Error(`Railway project create did not return a usable project for ${desiredProjectName}.`);
|
|
439
|
+
}
|
|
440
|
+
return { workspace: workspaceContext, project, created: true };
|
|
441
|
+
}
|
|
442
|
+
async function ensureRailwayEnvironment({
|
|
443
|
+
projectId,
|
|
444
|
+
environmentName,
|
|
445
|
+
env = process.env,
|
|
446
|
+
fetchImpl = fetch
|
|
447
|
+
}) {
|
|
448
|
+
const environments = await listRailwayEnvironments({ projectId, env, fetchImpl });
|
|
449
|
+
const existing = environments.find((environment2) => environment2.name === environmentName || environment2.id === environmentName) ?? null;
|
|
450
|
+
if (existing) {
|
|
451
|
+
return { environment: existing, created: false };
|
|
452
|
+
}
|
|
453
|
+
const created = await railwayGraphqlRequest({
|
|
454
|
+
query: `
|
|
455
|
+
mutation TreeseedRailwayEnvironmentCreate($input: EnvironmentCreateInput!) {
|
|
456
|
+
environmentCreate(input: $input) {
|
|
457
|
+
id
|
|
458
|
+
name
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
`.trim(),
|
|
462
|
+
variables: {
|
|
463
|
+
input: {
|
|
464
|
+
projectId,
|
|
465
|
+
name: environmentName,
|
|
466
|
+
skipInitialDeploys: true
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
env,
|
|
470
|
+
fetchImpl
|
|
471
|
+
});
|
|
472
|
+
const environment = created.data?.environmentCreate ? normalizeEnvironment(created.data.environmentCreate) : null;
|
|
473
|
+
if (!environment) {
|
|
474
|
+
throw new Error(`Railway environment create did not return a usable environment for ${environmentName}.`);
|
|
475
|
+
}
|
|
476
|
+
return { environment, created: true };
|
|
477
|
+
}
|
|
478
|
+
async function listRailwayEnvironments({
|
|
479
|
+
projectId,
|
|
480
|
+
env = process.env,
|
|
481
|
+
fetchImpl = fetch
|
|
482
|
+
}) {
|
|
483
|
+
const payload = await railwayGraphqlRequest({
|
|
484
|
+
query: `
|
|
485
|
+
query TreeseedRailwayProjectEnvironments($projectId: String!) {
|
|
486
|
+
project(id: $projectId) {
|
|
487
|
+
id
|
|
488
|
+
environments(first: 50) {
|
|
489
|
+
edges {
|
|
490
|
+
node {
|
|
491
|
+
id
|
|
492
|
+
name
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
`.trim(),
|
|
499
|
+
variables: { projectId },
|
|
500
|
+
env,
|
|
501
|
+
fetchImpl
|
|
502
|
+
});
|
|
503
|
+
return normalizeConnectionNodes(payload.data?.project ? payload.data.project.environments : null, normalizeEnvironment);
|
|
504
|
+
}
|
|
505
|
+
async function ensureRailwayService({
|
|
506
|
+
projectId,
|
|
507
|
+
serviceName,
|
|
508
|
+
serviceId,
|
|
509
|
+
env = process.env,
|
|
510
|
+
fetchImpl = fetch
|
|
511
|
+
}) {
|
|
512
|
+
const services = await listRailwayServices({ projectId, env, fetchImpl });
|
|
513
|
+
const desiredServiceName = railwayConnectionLabel(serviceName);
|
|
514
|
+
const desiredServiceId = railwayConnectionLabel(serviceId);
|
|
515
|
+
const existing = services.find(
|
|
516
|
+
(service2) => desiredServiceId && service2.id === desiredServiceId || desiredServiceName && service2.name === desiredServiceName
|
|
517
|
+
) ?? null;
|
|
518
|
+
if (existing) {
|
|
519
|
+
return { service: existing, created: false };
|
|
520
|
+
}
|
|
521
|
+
if (!desiredServiceName) {
|
|
522
|
+
throw new Error("Railway service creation requires a service name.");
|
|
523
|
+
}
|
|
524
|
+
const created = await railwayGraphqlRequest({
|
|
525
|
+
query: `
|
|
526
|
+
mutation TreeseedRailwayServiceCreate($input: ServiceCreateInput!) {
|
|
527
|
+
serviceCreate(input: $input) {
|
|
528
|
+
id
|
|
529
|
+
name
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
`.trim(),
|
|
533
|
+
variables: {
|
|
534
|
+
input: {
|
|
535
|
+
projectId,
|
|
536
|
+
name: desiredServiceName
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
env,
|
|
540
|
+
fetchImpl
|
|
541
|
+
});
|
|
542
|
+
const service = created.data?.serviceCreate ? normalizeService(created.data.serviceCreate) : null;
|
|
543
|
+
if (!service) {
|
|
544
|
+
throw new Error(`Railway service create did not return a usable service for ${desiredServiceName}.`);
|
|
545
|
+
}
|
|
546
|
+
return { service, created: true };
|
|
547
|
+
}
|
|
548
|
+
async function listRailwayServices({
|
|
549
|
+
projectId,
|
|
550
|
+
env = process.env,
|
|
551
|
+
fetchImpl = fetch
|
|
552
|
+
}) {
|
|
553
|
+
const payload = await railwayGraphqlRequest({
|
|
554
|
+
query: `
|
|
555
|
+
query TreeseedRailwayProjectServices($projectId: String!) {
|
|
556
|
+
project(id: $projectId) {
|
|
557
|
+
id
|
|
558
|
+
services(first: 50) {
|
|
559
|
+
edges {
|
|
560
|
+
node {
|
|
561
|
+
id
|
|
562
|
+
name
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
`.trim(),
|
|
569
|
+
variables: { projectId },
|
|
570
|
+
env,
|
|
571
|
+
fetchImpl
|
|
572
|
+
});
|
|
573
|
+
return normalizeConnectionNodes(payload.data?.project ? payload.data.project.services : null, normalizeService);
|
|
574
|
+
}
|
|
575
|
+
async function getRailwayServiceInstance({
|
|
576
|
+
serviceId,
|
|
577
|
+
environmentId,
|
|
578
|
+
env = process.env,
|
|
579
|
+
fetchImpl = fetch
|
|
580
|
+
}) {
|
|
581
|
+
const legacySummary = {
|
|
582
|
+
id: null,
|
|
583
|
+
buildCommand: null,
|
|
584
|
+
startCommand: null,
|
|
585
|
+
rootDirectory: null,
|
|
586
|
+
healthcheckPath: null,
|
|
587
|
+
healthcheckTimeoutSeconds: null,
|
|
588
|
+
healthcheckIntervalSeconds: null,
|
|
589
|
+
restartPolicy: null,
|
|
590
|
+
runtimeMode: null,
|
|
591
|
+
sleepApplication: null,
|
|
592
|
+
runtimeConfigSupported: false
|
|
593
|
+
};
|
|
594
|
+
try {
|
|
595
|
+
const payload = await railwayGraphqlRequest({
|
|
596
|
+
query: `
|
|
597
|
+
query TreeseedRailwayServiceInstance($serviceId: String!, $environmentId: String!) {
|
|
598
|
+
serviceInstance(serviceId: $serviceId, environmentId: $environmentId) {
|
|
599
|
+
id
|
|
600
|
+
buildCommand
|
|
601
|
+
startCommand
|
|
602
|
+
rootDirectory
|
|
603
|
+
healthcheckPath
|
|
604
|
+
healthcheckTimeout
|
|
605
|
+
sleepApplication
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
`.trim(),
|
|
609
|
+
variables: { serviceId, environmentId },
|
|
610
|
+
env,
|
|
611
|
+
fetchImpl
|
|
612
|
+
});
|
|
613
|
+
const instance = payload.data?.serviceInstance;
|
|
614
|
+
return {
|
|
615
|
+
id: railwayConnectionLabel(instance?.id) || null,
|
|
616
|
+
buildCommand: railwayConnectionLabel(instance?.buildCommand) || null,
|
|
617
|
+
startCommand: railwayConnectionLabel(instance?.startCommand) || null,
|
|
618
|
+
rootDirectory: railwayConnectionLabel(instance?.rootDirectory) || null,
|
|
619
|
+
healthcheckPath: railwayConnectionLabel(instance?.healthcheckPath) || null,
|
|
620
|
+
healthcheckTimeoutSeconds: normalizeRailwayNumber(instance?.healthcheckTimeout),
|
|
621
|
+
healthcheckIntervalSeconds: null,
|
|
622
|
+
restartPolicy: null,
|
|
623
|
+
runtimeMode: instance?.sleepApplication === true ? "serverless" : "replicated",
|
|
624
|
+
sleepApplication: typeof instance?.sleepApplication === "boolean" ? instance.sleepApplication : null,
|
|
625
|
+
runtimeConfigSupported: true
|
|
626
|
+
};
|
|
627
|
+
} catch (error) {
|
|
628
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
629
|
+
if (/Cannot query field .*healthcheckPath|Cannot query field .*healthcheckTimeout|Cannot query field .*sleepApplication/iu.test(message)) {
|
|
630
|
+
const payload = await railwayGraphqlRequest({
|
|
631
|
+
query: `
|
|
632
|
+
query TreeseedRailwayServiceInstanceLegacy($serviceId: String!, $environmentId: String!) {
|
|
633
|
+
serviceInstance(serviceId: $serviceId, environmentId: $environmentId) {
|
|
634
|
+
id
|
|
635
|
+
buildCommand
|
|
636
|
+
startCommand
|
|
637
|
+
rootDirectory
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
`.trim(),
|
|
641
|
+
variables: { serviceId, environmentId },
|
|
642
|
+
env,
|
|
643
|
+
fetchImpl
|
|
644
|
+
});
|
|
645
|
+
const instance = payload.data?.serviceInstance;
|
|
646
|
+
return {
|
|
647
|
+
...legacySummary,
|
|
648
|
+
id: railwayConnectionLabel(instance?.id) || null,
|
|
649
|
+
buildCommand: railwayConnectionLabel(instance?.buildCommand) || null,
|
|
650
|
+
startCommand: railwayConnectionLabel(instance?.startCommand) || null,
|
|
651
|
+
rootDirectory: railwayConnectionLabel(instance?.rootDirectory) || null
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
if (!/ServiceInstance not found/iu.test(message)) {
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
return legacySummary;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
async function ensureRailwayServiceInstanceConfiguration({
|
|
661
|
+
serviceId,
|
|
662
|
+
environmentId,
|
|
663
|
+
buildCommand,
|
|
664
|
+
startCommand,
|
|
665
|
+
rootDirectory,
|
|
666
|
+
healthcheckPath,
|
|
667
|
+
healthcheckTimeoutSeconds,
|
|
668
|
+
healthcheckIntervalSeconds,
|
|
669
|
+
restartPolicy,
|
|
670
|
+
runtimeMode,
|
|
671
|
+
env = process.env,
|
|
672
|
+
fetchImpl = fetch
|
|
673
|
+
}) {
|
|
674
|
+
const current = await getRailwayServiceInstance({ serviceId, environmentId, env, fetchImpl });
|
|
675
|
+
if (!current.id) {
|
|
676
|
+
return { instance: current, updated: false };
|
|
677
|
+
}
|
|
678
|
+
const desired = {
|
|
679
|
+
buildCommand: railwayConnectionLabel(buildCommand) || null,
|
|
680
|
+
startCommand: railwayConnectionLabel(startCommand) || null,
|
|
681
|
+
rootDirectory: railwayConnectionLabel(rootDirectory) || null,
|
|
682
|
+
healthcheckPath: railwayConnectionLabel(healthcheckPath) || null,
|
|
683
|
+
healthcheckTimeoutSeconds: normalizeRailwayNumber(healthcheckTimeoutSeconds),
|
|
684
|
+
healthcheckIntervalSeconds: normalizeRailwayNumber(healthcheckIntervalSeconds),
|
|
685
|
+
restartPolicy: railwayConnectionLabel(restartPolicy) || null,
|
|
686
|
+
runtimeMode: railwayConnectionLabel(runtimeMode) || null,
|
|
687
|
+
sleepApplication: railwayConnectionLabel(runtimeMode) === "serverless" ? true : railwayConnectionLabel(runtimeMode) === "replicated" ? false : null
|
|
688
|
+
};
|
|
689
|
+
const needsRuntimeConfig = desired.healthcheckPath !== null || desired.healthcheckTimeoutSeconds !== null || desired.runtimeMode !== null;
|
|
690
|
+
if (needsRuntimeConfig && current.runtimeConfigSupported !== true) {
|
|
691
|
+
throw new Error("Railway service instance runtime settings are unsupported by the current Railway API schema.");
|
|
692
|
+
}
|
|
693
|
+
if (desired.healthcheckIntervalSeconds !== null) {
|
|
694
|
+
throw new Error("Railway service instance healthcheck intervals are unsupported by the current Railway API schema.");
|
|
695
|
+
}
|
|
696
|
+
if (desired.restartPolicy !== null) {
|
|
697
|
+
throw new Error("Railway service instance restart policies are unsupported by the current Railway API schema.");
|
|
698
|
+
}
|
|
699
|
+
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;
|
|
700
|
+
if (!drifted) {
|
|
701
|
+
return { instance: current, updated: false };
|
|
702
|
+
}
|
|
703
|
+
const mutationQuery = needsRuntimeConfig ? `
|
|
704
|
+
mutation TreeseedRailwayServiceInstanceUpdate($serviceId: String!, $environmentId: String!, $input: ServiceInstanceUpdateInput!) {
|
|
705
|
+
serviceInstanceUpdate(serviceId: $serviceId, environmentId: $environmentId, input: $input)
|
|
706
|
+
}
|
|
707
|
+
`.trim() : `
|
|
708
|
+
mutation TreeseedRailwayServiceInstanceUpdateLegacy($serviceId: String!, $environmentId: String!, $input: ServiceInstanceUpdateInput!) {
|
|
709
|
+
serviceInstanceUpdate(serviceId: $serviceId, environmentId: $environmentId, input: $input)
|
|
710
|
+
}
|
|
711
|
+
`.trim();
|
|
712
|
+
try {
|
|
713
|
+
await railwayGraphqlRequest({
|
|
714
|
+
query: mutationQuery,
|
|
715
|
+
variables: {
|
|
716
|
+
serviceId,
|
|
717
|
+
environmentId,
|
|
718
|
+
input: {
|
|
719
|
+
...desired.buildCommand !== null ? { buildCommand: desired.buildCommand } : {},
|
|
720
|
+
...desired.startCommand !== null ? { startCommand: desired.startCommand } : {},
|
|
721
|
+
...desired.rootDirectory !== null ? { rootDirectory: desired.rootDirectory } : {},
|
|
722
|
+
...desired.healthcheckPath !== null ? { healthcheckPath: desired.healthcheckPath } : {},
|
|
723
|
+
...desired.healthcheckTimeoutSeconds !== null ? { healthcheckTimeout: desired.healthcheckTimeoutSeconds } : {},
|
|
724
|
+
...desired.sleepApplication !== null ? { sleepApplication: desired.sleepApplication } : {}
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
env,
|
|
728
|
+
fetchImpl
|
|
729
|
+
});
|
|
730
|
+
} catch (error) {
|
|
731
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
732
|
+
if (needsRuntimeConfig && /Field .* is not defined by type .*ServiceInstanceUpdateInput|Unknown argument|Cannot query field/iu.test(message)) {
|
|
733
|
+
throw new Error("Railway service instance runtime settings are unsupported by the current Railway API schema.");
|
|
734
|
+
}
|
|
735
|
+
throw error;
|
|
736
|
+
}
|
|
737
|
+
const instance = await getRailwayServiceInstance({
|
|
738
|
+
serviceId,
|
|
739
|
+
environmentId,
|
|
740
|
+
env,
|
|
741
|
+
fetchImpl
|
|
742
|
+
});
|
|
743
|
+
return {
|
|
744
|
+
instance: {
|
|
745
|
+
id: instance.id || current.id,
|
|
746
|
+
buildCommand: instance.buildCommand ?? desired.buildCommand,
|
|
747
|
+
startCommand: instance.startCommand ?? desired.startCommand,
|
|
748
|
+
rootDirectory: instance.rootDirectory ?? desired.rootDirectory,
|
|
749
|
+
healthcheckPath: instance.healthcheckPath ?? desired.healthcheckPath,
|
|
750
|
+
healthcheckTimeoutSeconds: instance.healthcheckTimeoutSeconds ?? desired.healthcheckTimeoutSeconds,
|
|
751
|
+
healthcheckIntervalSeconds: instance.healthcheckIntervalSeconds ?? desired.healthcheckIntervalSeconds,
|
|
752
|
+
restartPolicy: instance.restartPolicy ?? desired.restartPolicy,
|
|
753
|
+
runtimeMode: instance.runtimeMode ?? desired.runtimeMode,
|
|
754
|
+
sleepApplication: instance.sleepApplication ?? desired.sleepApplication,
|
|
755
|
+
runtimeConfigSupported: instance.runtimeConfigSupported
|
|
756
|
+
},
|
|
757
|
+
updated: true
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
async function listRailwayVariables({
|
|
761
|
+
projectId,
|
|
762
|
+
environmentId,
|
|
763
|
+
serviceId,
|
|
764
|
+
env = process.env,
|
|
765
|
+
fetchImpl = fetch
|
|
766
|
+
}) {
|
|
767
|
+
const payload = await railwayGraphqlRequest({
|
|
768
|
+
query: `
|
|
769
|
+
query TreeseedRailwayVariables($projectId: String!, $environmentId: String!, $serviceId: String) {
|
|
770
|
+
variables(projectId: $projectId, environmentId: $environmentId, serviceId: $serviceId, unrendered: true)
|
|
771
|
+
}
|
|
772
|
+
`.trim(),
|
|
773
|
+
variables: {
|
|
774
|
+
projectId,
|
|
775
|
+
environmentId,
|
|
776
|
+
serviceId: serviceId || null
|
|
777
|
+
},
|
|
778
|
+
env,
|
|
779
|
+
fetchImpl
|
|
780
|
+
});
|
|
781
|
+
return normalizeVariableMap(payload.data?.variables);
|
|
782
|
+
}
|
|
783
|
+
async function upsertRailwayVariables({
|
|
784
|
+
projectId,
|
|
785
|
+
environmentId,
|
|
786
|
+
serviceId,
|
|
787
|
+
variables,
|
|
788
|
+
env = process.env,
|
|
789
|
+
fetchImpl = fetch
|
|
790
|
+
}) {
|
|
791
|
+
if (Object.keys(variables).length === 0) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
await railwayGraphqlRequest({
|
|
795
|
+
query: `
|
|
796
|
+
mutation TreeseedRailwayVariableCollectionUpsert($input: VariableCollectionUpsertInput!) {
|
|
797
|
+
variableCollectionUpsert(input: $input)
|
|
798
|
+
}
|
|
799
|
+
`.trim(),
|
|
800
|
+
variables: {
|
|
801
|
+
input: {
|
|
802
|
+
projectId,
|
|
803
|
+
environmentId,
|
|
804
|
+
serviceId: serviceId || null,
|
|
805
|
+
variables,
|
|
806
|
+
replace: false,
|
|
807
|
+
skipDeploys: true
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
env,
|
|
811
|
+
fetchImpl
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
async function listRailwayCustomDomains({
|
|
815
|
+
projectId,
|
|
816
|
+
environmentId,
|
|
817
|
+
serviceId,
|
|
818
|
+
env = process.env,
|
|
819
|
+
fetchImpl = fetch
|
|
820
|
+
}) {
|
|
821
|
+
const payload = await railwayGraphqlRequest({
|
|
822
|
+
query: `
|
|
823
|
+
query TreeseedRailwayCustomDomains($projectId: String!, $environmentId: String!, $serviceId: String!) {
|
|
824
|
+
domains(projectId: $projectId, environmentId: $environmentId, serviceId: $serviceId) {
|
|
825
|
+
customDomains {
|
|
826
|
+
id
|
|
827
|
+
domain
|
|
828
|
+
environmentId
|
|
829
|
+
serviceId
|
|
830
|
+
targetPort
|
|
831
|
+
status {
|
|
832
|
+
verified
|
|
833
|
+
certificateStatus
|
|
834
|
+
verificationDnsHost
|
|
835
|
+
verificationToken
|
|
836
|
+
dnsRecords {
|
|
837
|
+
fqdn
|
|
838
|
+
hostlabel
|
|
839
|
+
recordType
|
|
840
|
+
requiredValue
|
|
841
|
+
currentValue
|
|
842
|
+
status
|
|
843
|
+
zone
|
|
844
|
+
purpose
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
`.trim(),
|
|
851
|
+
variables: {
|
|
852
|
+
projectId,
|
|
853
|
+
environmentId,
|
|
854
|
+
serviceId
|
|
855
|
+
},
|
|
856
|
+
env,
|
|
857
|
+
fetchImpl
|
|
858
|
+
});
|
|
859
|
+
return Array.isArray(payload.data?.domains?.customDomains) ? payload.data.domains.customDomains.map((entry) => entry && typeof entry === "object" ? normalizeRailwayCustomDomain(entry) : null).filter(Boolean) : [];
|
|
860
|
+
}
|
|
861
|
+
export {
|
|
862
|
+
ensureRailwayEnvironment,
|
|
863
|
+
ensureRailwayProject,
|
|
864
|
+
ensureRailwayService,
|
|
865
|
+
ensureRailwayServiceInstanceConfiguration,
|
|
866
|
+
getRailwayAuthProfile,
|
|
867
|
+
getRailwayProject,
|
|
868
|
+
getRailwayServiceInstance,
|
|
869
|
+
isUsableRailwayToken,
|
|
870
|
+
listRailwayCustomDomains,
|
|
871
|
+
listRailwayEnvironments,
|
|
872
|
+
listRailwayProjects,
|
|
873
|
+
listRailwayServices,
|
|
874
|
+
listRailwayVariables,
|
|
875
|
+
normalizeRailwayEnvironmentName,
|
|
876
|
+
railwayGraphqlRequest,
|
|
877
|
+
resolveRailwayApiToken,
|
|
878
|
+
resolveRailwayApiUrl,
|
|
879
|
+
resolveRailwayWorkspace,
|
|
880
|
+
resolveRailwayWorkspaceContext,
|
|
881
|
+
upsertRailwayVariables
|
|
882
|
+
};
|