@open-mercato/cli 0.6.4-develop.4382.1.6b4f656b77 → 0.6.4

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 (109) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/agentic/guides/core.customers.md +1 -1
  3. package/dist/agentic/shared/AGENTS.md.template +56 -33
  4. package/dist/agentic/shared/ai/skills/{auto-continue-pr → om-auto-continue-pr}/SKILL.md +19 -19
  5. package/dist/agentic/shared/ai/skills/{auto-create-pr → om-auto-continue-pr}/STANDALONE.md +1 -1
  6. package/dist/agentic/shared/ai/skills/{auto-continue-pr-loop → om-auto-continue-pr-loop}/SKILL.md +32 -32
  7. package/dist/agentic/shared/ai/skills/{auto-create-pr → om-auto-create-pr}/SKILL.md +24 -24
  8. package/dist/agentic/shared/ai/skills/{auto-fix-github → om-auto-create-pr}/STANDALONE.md +1 -1
  9. package/dist/agentic/shared/ai/skills/{auto-create-pr-loop → om-auto-create-pr-loop}/SKILL.md +40 -40
  10. package/dist/agentic/shared/ai/skills/{auto-fix-github → om-auto-fix-github}/SKILL.md +3 -3
  11. package/dist/agentic/shared/ai/skills/{auto-continue-pr → om-auto-fix-github}/STANDALONE.md +1 -1
  12. package/dist/agentic/shared/ai/skills/{auto-review-pr → om-auto-review-pr}/SKILL.md +5 -5
  13. package/dist/agentic/shared/ai/skills/{auto-review-pr → om-auto-review-pr}/STANDALONE.md +1 -1
  14. package/dist/agentic/shared/ai/skills/{auto-upgrade-0.4.10-to-0.5.0 → om-auto-upgrade-0.4.10-to-0.5.0}/SKILL.md +1 -1
  15. package/dist/agentic/shared/ai/skills/{backend-ui-design → om-backend-ui-design}/SKILL.md +2 -2
  16. package/dist/agentic/shared/ai/skills/{code-review → om-code-review}/SKILL.md +1 -1
  17. package/dist/agentic/shared/ai/skills/{code-review → om-code-review}/references/review-checklist.md +3 -0
  18. package/dist/agentic/shared/ai/skills/{data-model-design → om-data-model-design}/SKILL.md +4 -2
  19. package/dist/agentic/shared/ai/skills/{eject-and-customize → om-eject-and-customize}/SKILL.md +2 -2
  20. package/dist/agentic/shared/ai/skills/{implement-spec → om-implement-spec}/SKILL.md +6 -6
  21. package/dist/agentic/shared/ai/skills/{integration-builder → om-integration-builder}/SKILL.md +1 -1
  22. package/dist/agentic/shared/ai/skills/{integration-tests → om-integration-tests}/SKILL.md +1 -1
  23. package/dist/agentic/shared/ai/skills/{module-scaffold → om-module-scaffold}/SKILL.md +5 -3
  24. package/dist/agentic/shared/ai/skills/{module-scaffold → om-module-scaffold}/references/naming-conventions.md +7 -8
  25. package/dist/agentic/shared/ai/skills/om-prepare-issue/SKILL.md +202 -0
  26. package/dist/agentic/shared/ai/skills/{spec-writing → om-spec-writing}/SKILL.md +6 -6
  27. package/dist/agentic/shared/ai/skills/{spec-writing → om-spec-writing}/references/spec-checklist.md +3 -3
  28. package/dist/agentic/shared/ai/skills/{system-extension → om-system-extension}/SKILL.md +1 -1
  29. package/dist/agentic/shared/ai/skills/{trim-unused-modules → om-trim-unused-modules}/SKILL.md +1 -1
  30. package/dist/agentic/shared/ai/skills/{troubleshooter → om-troubleshooter}/SKILL.md +1 -1
  31. package/dist/bin.js +1 -0
  32. package/dist/bin.js.map +2 -2
  33. package/dist/lib/__integration__/TC-INT-008.spec.js +1 -1
  34. package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
  35. package/dist/lib/agentic-setup.js +52 -51
  36. package/dist/lib/agentic-setup.js.map +2 -2
  37. package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.meta.js +7 -0
  38. package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.meta.js.map +7 -0
  39. package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.spec.js +63 -0
  40. package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.spec.js.map +7 -0
  41. package/dist/lib/deploy/railway/env.js +98 -0
  42. package/dist/lib/deploy/railway/env.js.map +7 -0
  43. package/dist/lib/deploy/railway/graphql-client.js +99 -0
  44. package/dist/lib/deploy/railway/graphql-client.js.map +7 -0
  45. package/dist/lib/deploy/railway/index.js +1115 -0
  46. package/dist/lib/deploy/railway/index.js.map +7 -0
  47. package/dist/lib/deploy/railway/operations.js +249 -0
  48. package/dist/lib/deploy/railway/operations.js.map +7 -0
  49. package/dist/lib/deploy/railway/options.js +155 -0
  50. package/dist/lib/deploy/railway/options.js.map +7 -0
  51. package/dist/lib/deploy/railway/railway-cli.js +61 -0
  52. package/dist/lib/deploy/railway/railway-cli.js.map +7 -0
  53. package/dist/lib/deploy/railway/redaction.js +88 -0
  54. package/dist/lib/deploy/railway/redaction.js.map +7 -0
  55. package/dist/lib/deploy/railway/source.js +128 -0
  56. package/dist/lib/deploy/railway/source.js.map +7 -0
  57. package/dist/lib/deploy/railway/state.js +41 -0
  58. package/dist/lib/deploy/railway/state.js.map +7 -0
  59. package/dist/lib/deploy/railway/token.js +46 -0
  60. package/dist/lib/deploy/railway/token.js.map +7 -0
  61. package/dist/lib/deploy/railway/types.js +1 -0
  62. package/dist/lib/deploy/railway/types.js.map +7 -0
  63. package/dist/lib/generators/openapi.js +4 -0
  64. package/dist/lib/generators/openapi.js.map +2 -2
  65. package/dist/lib/testing/integration.js +57 -26
  66. package/dist/lib/testing/integration.js.map +2 -2
  67. package/dist/lib/testing/runtime-utils.js +16 -8
  68. package/dist/lib/testing/runtime-utils.js.map +2 -2
  69. package/dist/mercato.js +23 -8
  70. package/dist/mercato.js.map +2 -2
  71. package/package.json +11 -12
  72. package/src/bin.ts +1 -0
  73. package/src/lib/__integration__/TC-INT-008.spec.ts +1 -1
  74. package/src/lib/agentic-setup.ts +52 -51
  75. package/src/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.meta.ts +3 -0
  76. package/src/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.spec.ts +71 -0
  77. package/src/lib/deploy/railway/__tests__/dry-run.test.ts +61 -0
  78. package/src/lib/deploy/railway/__tests__/env.test.ts +55 -0
  79. package/src/lib/deploy/railway/__tests__/graphql-client.test.ts +80 -0
  80. package/src/lib/deploy/railway/__tests__/operations.test.ts +21 -0
  81. package/src/lib/deploy/railway/__tests__/options.test.ts +78 -0
  82. package/src/lib/deploy/railway/__tests__/redaction.test.ts +62 -0
  83. package/src/lib/deploy/railway/__tests__/source.test.ts +130 -0
  84. package/src/lib/deploy/railway/__tests__/state.test.ts +24 -0
  85. package/src/lib/deploy/railway/__tests__/token.test.ts +39 -0
  86. package/src/lib/deploy/railway/env.ts +112 -0
  87. package/src/lib/deploy/railway/graphql-client.ts +128 -0
  88. package/src/lib/deploy/railway/index.ts +1454 -0
  89. package/src/lib/deploy/railway/operations.ts +248 -0
  90. package/src/lib/deploy/railway/options.ts +167 -0
  91. package/src/lib/deploy/railway/railway-cli.ts +65 -0
  92. package/src/lib/deploy/railway/redaction.ts +91 -0
  93. package/src/lib/deploy/railway/source.ts +143 -0
  94. package/src/lib/deploy/railway/state.ts +45 -0
  95. package/src/lib/deploy/railway/token.ts +49 -0
  96. package/src/lib/deploy/railway/types.ts +87 -0
  97. package/src/lib/generators/openapi.ts +3 -0
  98. package/src/lib/testing/__tests__/integration.test.ts +79 -0
  99. package/src/lib/testing/__tests__/runtime-utils.test.ts +45 -1
  100. package/src/lib/testing/integration.ts +74 -28
  101. package/src/lib/testing/runtime-utils.ts +17 -8
  102. package/src/mercato.ts +33 -8
  103. /package/dist/agentic/shared/ai/skills/{backend-ui-design → om-backend-ui-design}/references/ui-components.md +0 -0
  104. /package/dist/agentic/shared/ai/skills/{data-model-design → om-data-model-design}/references/mikro-orm-cheatsheet.md +0 -0
  105. /package/dist/agentic/shared/ai/skills/{integration-builder → om-integration-builder}/references/adapter-contracts.md +0 -0
  106. /package/dist/agentic/shared/ai/skills/{module-scaffold → om-module-scaffold}/references/navigation-patterns.md +0 -0
  107. /package/dist/agentic/shared/ai/skills/{spec-writing → om-spec-writing}/references/spec-template.md +0 -0
  108. /package/dist/agentic/shared/ai/skills/{system-extension → om-system-extension}/references/extension-contracts.md +0 -0
  109. /package/dist/agentic/shared/ai/skills/{troubleshooter → om-troubleshooter}/references/diagnostic-commands.md +0 -0
@@ -0,0 +1,1115 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { existsSync, readFileSync, unlinkSync } from "node:fs";
3
+ import { basename, resolve } from "node:path";
4
+ import { stdin as input, stdout as output } from "node:process";
5
+ import { satisfies } from "semver";
6
+ import {
7
+ computeRailwayVariables,
8
+ formatVariablePlan,
9
+ generateProtectedSecrets,
10
+ parseEnvFile,
11
+ resolveEnvFile,
12
+ writeGeneratedSecrets
13
+ } from "./env.js";
14
+ import { createRailwayGraphqlClient, RailwayGraphqlError } from "./graphql-client.js";
15
+ import { railwayOperations } from "./operations.js";
16
+ import { parseRailwayDeployOptions, railwayDeployHelp } from "./options.js";
17
+ import { collectSensitiveStructuredValues, redactText } from "./redaction.js";
18
+ import { uploadLocalSource } from "./railway-cli.js";
19
+ import {
20
+ assertLocalUploadSafe,
21
+ resolveRailwaySource
22
+ } from "./source.js";
23
+ import {
24
+ createRailwayState,
25
+ loadRailwayState,
26
+ railwayStatePath,
27
+ saveRailwayState
28
+ } from "./state.js";
29
+ import {
30
+ railwayTokenConfigPath,
31
+ resolveRailwayToken,
32
+ writeCachedRailwayToken
33
+ } from "./token.js";
34
+ const SUCCESS_STATUSES = /* @__PURE__ */ new Set(["SUCCESS", "COMPLETED"]);
35
+ const FAILURE_STATUSES = /* @__PURE__ */ new Set(["FAILED", "CRASHED", "REMOVED"]);
36
+ function isNotFoundError(error) {
37
+ return error instanceof RailwayGraphqlError && (error.status === 404 || error.code === "NOT_FOUND" || error.code?.endsWith("_NOT_FOUND") === true);
38
+ }
39
+ function isAmbiguousMutationError(error) {
40
+ if (!(error instanceof RailwayGraphqlError)) return true;
41
+ return error.status === void 0 || error.status >= 500 || error.code === "INTERNAL_ERROR";
42
+ }
43
+ function createPrompt() {
44
+ const readline = createInterface({ input, output });
45
+ return {
46
+ ask: (question) => readline.question(question),
47
+ close: () => readline.close()
48
+ };
49
+ }
50
+ function nodes(connection) {
51
+ return (connection?.edges ?? []).map((edge) => edge?.node).filter((node) => node !== null && node !== void 0);
52
+ }
53
+ function wait(milliseconds) {
54
+ return new Promise((resolveWait) => setTimeout(resolveWait, milliseconds));
55
+ }
56
+ function normalizeProjectName(value) {
57
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-").slice(0, 48).replace(/-+$/g, "");
58
+ if (!normalized) throw new Error("Unable to derive a valid Railway project name.");
59
+ return normalized;
60
+ }
61
+ function readAppPackage(cwd) {
62
+ const packagePath = resolve(cwd, "package.json");
63
+ if (!existsSync(packagePath)) throw new Error("Run this command from an Open Mercato app root.");
64
+ const parsed = JSON.parse(readFileSync(packagePath, "utf8"));
65
+ if (!parsed || typeof parsed !== "object") throw new Error("Invalid package.json.");
66
+ const record = parsed;
67
+ const dependencies = {
68
+ ...record.dependencies && typeof record.dependencies === "object" ? record.dependencies : {},
69
+ ...record.devDependencies && typeof record.devDependencies === "object" ? record.devDependencies : {}
70
+ };
71
+ if (!Object.keys(dependencies).some((key) => key.startsWith("@open-mercato/"))) {
72
+ throw new Error("The current package does not declare Open Mercato dependencies.");
73
+ }
74
+ return {
75
+ name: typeof record.name === "string" && record.name.trim() ? record.name : basename(cwd),
76
+ dependencies,
77
+ nodeEngine: record.engines && typeof record.engines === "object" && typeof record.engines.node === "string" ? record.engines.node : void 0
78
+ };
79
+ }
80
+ function assertSupportedNodeVersion(nodeEngine) {
81
+ if (!nodeEngine) return;
82
+ if (!satisfies(process.version, nodeEngine)) {
83
+ throw new Error(
84
+ `Node ${process.version} does not satisfy this app's required version "${nodeEngine}".`
85
+ );
86
+ }
87
+ }
88
+ function ensureEnvironmentState(state, name) {
89
+ state.environments[name] ??= {};
90
+ return state.environments[name];
91
+ }
92
+ function persistState(path, state) {
93
+ saveRailwayState(path, state);
94
+ }
95
+ async function resolveTokenInteractively(options, prompt) {
96
+ const configPath = railwayTokenConfigPath();
97
+ const resolved = resolveRailwayToken({ flagToken: options.token, configPath });
98
+ if (resolved.token) return { token: resolved.token, cached: resolved.source === "cache" };
99
+ if (options.nonInteractive || !prompt) {
100
+ const compatibilityHint = process.env.RAILWAY_TOKEN ? " RAILWAY_TOKEN is project-scoped; use RAILWAY_API_TOKEN for account/workspace operations." : "";
101
+ throw new Error(`Railway Account token is required.${compatibilityHint}`);
102
+ }
103
+ console.log("Create a token at https://railway.com/account/tokens (Account Settings -> Tokens).");
104
+ const token = (await prompt.ask("Railway Account token: ")).trim();
105
+ if (!token) throw new Error("Railway Account token is required.");
106
+ const persist = (await prompt.ask("Cache this token in ~/.config/open-mercato/railway.json? [y/N] ")).trim().toLowerCase();
107
+ if (persist === "y" || persist === "yes") {
108
+ writeCachedRailwayToken(configPath, token);
109
+ return { token, cached: true };
110
+ }
111
+ return { token, cached: false };
112
+ }
113
+ async function resolveWorkspace(client, preferredWorkspaceId) {
114
+ let data;
115
+ try {
116
+ data = await client.request(railwayOperations.me);
117
+ } catch (error) {
118
+ if (error instanceof RailwayGraphqlError && (error.status === 401 || error.status === 403 || error.code === "UNAUTHORIZED")) {
119
+ throw new Error(
120
+ "Token rejected. Generate a Railway Account token at https://railway.com/account/tokens."
121
+ );
122
+ }
123
+ throw error;
124
+ }
125
+ const workspaces = data.me.workspaces ?? [];
126
+ const workspace = preferredWorkspaceId ? workspaces.find((candidate) => candidate.id === preferredWorkspaceId) : workspaces[0];
127
+ if (preferredWorkspaceId && !workspace) {
128
+ throw new Error("The recorded Railway workspace is no longer accessible to this Account token.");
129
+ }
130
+ if (!workspace) throw new Error("The Railway account has no accessible workspace.");
131
+ return { userId: data.me.id, workspaceId: workspace.id };
132
+ }
133
+ async function findProjectByName(client, workspaceId, projectName) {
134
+ const data = await client.request(railwayOperations.projects, { workspaceId });
135
+ return nodes(data.projects).find((project) => project.name === projectName) ?? null;
136
+ }
137
+ async function ensureProject(input2) {
138
+ if (input2.state.projectId) {
139
+ try {
140
+ const data = await input2.client.request(
141
+ railwayOperations.project,
142
+ { id: input2.state.projectId }
143
+ );
144
+ if (data.project.workspaceId && data.project.workspaceId !== input2.workspaceId) {
145
+ throw new Error("Recorded Railway project belongs to a different workspace.");
146
+ }
147
+ if (data.project.name !== input2.state.projectName) {
148
+ if (!input2.options.forceRename) {
149
+ throw new Error(
150
+ `Recorded Railway project is named "${data.project.name}". Use --force-rename to rename it.`
151
+ );
152
+ }
153
+ await input2.client.request(railwayOperations.projectUpdate, {
154
+ id: data.project.id,
155
+ input: { name: input2.state.projectName }
156
+ });
157
+ }
158
+ return data.project.id;
159
+ } catch (error) {
160
+ if (!isNotFoundError(error)) throw error;
161
+ input2.state.projectId = void 0;
162
+ }
163
+ }
164
+ const existing = await findProjectByName(
165
+ input2.client,
166
+ input2.workspaceId,
167
+ input2.state.projectName
168
+ );
169
+ if (existing) {
170
+ input2.state.projectId = existing.id;
171
+ persistState(input2.statePath, input2.state);
172
+ return existing.id;
173
+ }
174
+ try {
175
+ const data = await input2.client.request(railwayOperations.projectCreate, {
176
+ input: {
177
+ name: input2.state.projectName,
178
+ description: "Open Mercato deployment managed by mercato deploy railway",
179
+ workspaceId: input2.workspaceId
180
+ }
181
+ });
182
+ input2.state.projectId = data.projectCreate.id;
183
+ } catch (error) {
184
+ const discovered = await findProjectByName(
185
+ input2.client,
186
+ input2.workspaceId,
187
+ input2.state.projectName
188
+ );
189
+ if (!discovered) throw error;
190
+ input2.state.projectId = discovered.id;
191
+ }
192
+ persistState(input2.statePath, input2.state);
193
+ return input2.state.projectId;
194
+ }
195
+ async function ensureEnvironment(input2) {
196
+ if (input2.environmentState.environmentId) {
197
+ try {
198
+ const data2 = await input2.client.request(
199
+ railwayOperations.environment,
200
+ { id: input2.environmentState.environmentId, projectId: input2.projectId }
201
+ );
202
+ return data2.environment.id;
203
+ } catch (error) {
204
+ if (!isNotFoundError(error)) throw error;
205
+ input2.environmentState.environmentId = void 0;
206
+ }
207
+ }
208
+ const data = await input2.client.request(railwayOperations.environments, { projectId: input2.projectId });
209
+ const existing = nodes(data.environments).find((environment) => environment.name === input2.name);
210
+ if (existing) {
211
+ input2.environmentState.environmentId = existing.id;
212
+ input2.persist();
213
+ return existing.id;
214
+ }
215
+ try {
216
+ const created = await input2.client.request(
217
+ railwayOperations.environmentCreate,
218
+ { input: { projectId: input2.projectId, name: input2.name } }
219
+ );
220
+ input2.environmentState.environmentId = created.environmentCreate.id;
221
+ } catch (error) {
222
+ const retryLookup = await input2.client.request(railwayOperations.environments, { projectId: input2.projectId });
223
+ const discovered = nodes(retryLookup.environments).find((environment) => environment.name === input2.name);
224
+ if (!discovered) throw error;
225
+ input2.environmentState.environmentId = discovered.id;
226
+ }
227
+ input2.persist();
228
+ return input2.environmentState.environmentId;
229
+ }
230
+ async function listServices(client, projectId) {
231
+ const data = await client.request(railwayOperations.services, { projectId });
232
+ return nodes(data.project.services);
233
+ }
234
+ async function waitForService(client, projectId, name, timeoutMs = 18e4) {
235
+ const deadline = Date.now() + timeoutMs;
236
+ while (Date.now() < deadline) {
237
+ const service = (await listServices(client, projectId)).find((candidate) => candidate.name === name);
238
+ if (service) return service;
239
+ await wait(5e3);
240
+ }
241
+ throw new Error(`Timed out waiting for Railway service "${name}". Re-run the command to resume.`);
242
+ }
243
+ async function ensureDatabaseReady(input2) {
244
+ const previous = await latestDeployment(
245
+ input2.client,
246
+ input2.projectId,
247
+ input2.environmentId,
248
+ input2.serviceId
249
+ );
250
+ if (previous && SUCCESS_STATUSES.has(previous.status)) return;
251
+ if (previous && !FAILURE_STATUSES.has(previous.status)) {
252
+ await waitForDeployment({
253
+ client: input2.client,
254
+ deploymentId: previous.id,
255
+ timeoutSeconds: 180,
256
+ prefix: input2.name,
257
+ secrets: []
258
+ });
259
+ return;
260
+ }
261
+ let triggerError;
262
+ try {
263
+ await input2.client.request(railwayOperations.deploy, {
264
+ environmentId: input2.environmentId,
265
+ serviceId: input2.serviceId,
266
+ commitSha: null
267
+ });
268
+ } catch (error) {
269
+ if (!isAmbiguousMutationError(error)) throw error;
270
+ triggerError = error;
271
+ }
272
+ const deadline = Date.now() + 3e4;
273
+ while (Date.now() < deadline) {
274
+ const current = await latestDeployment(
275
+ input2.client,
276
+ input2.projectId,
277
+ input2.environmentId,
278
+ input2.serviceId
279
+ );
280
+ if (current && current.id !== previous?.id) {
281
+ await waitForDeployment({
282
+ client: input2.client,
283
+ deploymentId: current.id,
284
+ timeoutSeconds: 180,
285
+ prefix: input2.name,
286
+ secrets: []
287
+ });
288
+ return;
289
+ }
290
+ await wait(2e3);
291
+ }
292
+ if (triggerError) throw triggerError;
293
+ throw new Error(`Railway did not expose a ${input2.name} deployment in this environment.`);
294
+ }
295
+ async function ensureDatabase(input2) {
296
+ if (input2.currentId) {
297
+ try {
298
+ const data = await input2.client.request(
299
+ railwayOperations.service,
300
+ { id: input2.currentId }
301
+ );
302
+ if (data.service.projectId !== input2.projectId) {
303
+ throw new Error(`Recorded ${input2.name} service belongs to a different Railway project.`);
304
+ }
305
+ await ensureDatabaseReady({
306
+ client: input2.client,
307
+ projectId: input2.projectId,
308
+ environmentId: input2.environmentId,
309
+ serviceId: data.service.id,
310
+ name: input2.name
311
+ });
312
+ return data.service.id;
313
+ } catch (error) {
314
+ if (!isNotFoundError(error)) throw error;
315
+ }
316
+ }
317
+ const existing = (await listServices(input2.client, input2.projectId)).find((service) => service.name === input2.name);
318
+ if (existing) {
319
+ await ensureDatabaseReady({
320
+ client: input2.client,
321
+ projectId: input2.projectId,
322
+ environmentId: input2.environmentId,
323
+ serviceId: existing.id,
324
+ name: input2.name
325
+ });
326
+ input2.onResolved(existing.id);
327
+ return existing.id;
328
+ }
329
+ const templateData = await input2.client.request(railwayOperations.template, { code: input2.code });
330
+ if (!templateData.template.serializedConfig) {
331
+ throw new Error(`Railway template "${input2.code}" does not expose a deployable configuration.`);
332
+ }
333
+ try {
334
+ await input2.client.request(railwayOperations.templateDeploy, {
335
+ input: {
336
+ projectId: input2.projectId,
337
+ environmentId: input2.environmentId,
338
+ workspaceId: input2.workspaceId,
339
+ templateId: templateData.template.id,
340
+ serializedConfig: templateData.template.serializedConfig
341
+ }
342
+ });
343
+ } catch (error) {
344
+ const discovered = (await listServices(input2.client, input2.projectId)).find((service) => service.name === input2.name);
345
+ if (!discovered) throw error;
346
+ }
347
+ const created = await waitForService(input2.client, input2.projectId, input2.name);
348
+ const readinessDeadline = Date.now() + 18e4;
349
+ let ready = false;
350
+ while (Date.now() < readinessDeadline) {
351
+ const deployment = await latestDeployment(
352
+ input2.client,
353
+ input2.projectId,
354
+ input2.environmentId,
355
+ created.id
356
+ );
357
+ if (deployment && SUCCESS_STATUSES.has(deployment.status)) {
358
+ ready = true;
359
+ break;
360
+ }
361
+ if (deployment && FAILURE_STATUSES.has(deployment.status)) {
362
+ throw new Error(`${input2.name} provisioning failed with status ${deployment.status}.`);
363
+ }
364
+ await wait(5e3);
365
+ }
366
+ if (!ready) {
367
+ throw new Error(`Timed out waiting for ${input2.name} to become ready. Re-run the command to resume.`);
368
+ }
369
+ input2.onResolved(created.id);
370
+ return created.id;
371
+ }
372
+ async function ensureService(input2) {
373
+ let serviceId;
374
+ let createdService = false;
375
+ if (input2.currentId) {
376
+ try {
377
+ const data = await input2.client.request(
378
+ railwayOperations.service,
379
+ { id: input2.currentId }
380
+ );
381
+ if (data.service.projectId !== input2.projectId) {
382
+ throw new Error(`Recorded ${input2.name} service belongs to a different Railway project.`);
383
+ }
384
+ serviceId = data.service.id;
385
+ } catch (error) {
386
+ if (!isNotFoundError(error)) throw error;
387
+ }
388
+ }
389
+ if (!serviceId) {
390
+ const existing = (await listServices(input2.client, input2.projectId)).find((service) => service.name === input2.name);
391
+ serviceId = existing?.id;
392
+ }
393
+ if (!serviceId) {
394
+ const serviceInput = {
395
+ projectId: input2.projectId,
396
+ name: input2.name
397
+ };
398
+ if (input2.source.mode === "git") {
399
+ serviceInput.source = { repo: input2.source.repo };
400
+ serviceInput.branch = input2.source.branch;
401
+ }
402
+ try {
403
+ const created = await input2.client.request(
404
+ railwayOperations.serviceCreate,
405
+ { input: serviceInput }
406
+ );
407
+ serviceId = created.serviceCreate.id;
408
+ createdService = true;
409
+ } catch (error) {
410
+ const discovered = (await listServices(input2.client, input2.projectId)).find((service) => service.name === input2.name);
411
+ if (!discovered) throw error;
412
+ serviceId = discovered.id;
413
+ }
414
+ }
415
+ if (input2.source.mode === "git" && !createdService) {
416
+ await input2.client.request(railwayOperations.serviceConnect, {
417
+ id: serviceId,
418
+ input: {
419
+ repo: input2.source.repo,
420
+ branch: input2.source.branch
421
+ }
422
+ });
423
+ } else if (input2.source.mode === "local" && input2.previousSource?.mode === "git") {
424
+ await input2.client.request(railwayOperations.serviceDisconnect, { id: serviceId });
425
+ }
426
+ if (input2.startCommand || input2.region || input2.railwayConfigFile) {
427
+ await input2.client.request(railwayOperations.serviceInstanceUpdate, {
428
+ environmentId: input2.environmentId,
429
+ serviceId,
430
+ input: {
431
+ ...input2.startCommand ? { startCommand: input2.startCommand } : {},
432
+ ...input2.region ? { region: input2.region } : {},
433
+ ...input2.railwayConfigFile ? { railwayConfigFile: input2.railwayConfigFile } : {}
434
+ }
435
+ });
436
+ }
437
+ input2.onResolved(serviceId);
438
+ return serviceId;
439
+ }
440
+ async function removeDisabledWorker(input2) {
441
+ if (!input2.workerServiceId) return;
442
+ try {
443
+ const data = await input2.client.request(railwayOperations.service, { id: input2.workerServiceId });
444
+ if (data.service.projectId !== input2.projectId || data.service.name !== "mercato-worker") {
445
+ throw new Error("Recorded worker service does not match the managed Railway project.");
446
+ }
447
+ } catch (error) {
448
+ if (isNotFoundError(error)) return;
449
+ throw error;
450
+ }
451
+ try {
452
+ await input2.client.request(railwayOperations.serviceDelete, {
453
+ id: input2.workerServiceId,
454
+ environmentId: input2.environmentId
455
+ });
456
+ } catch (error) {
457
+ try {
458
+ await input2.client.request(railwayOperations.service, { id: input2.workerServiceId });
459
+ } catch (lookupError) {
460
+ if (isNotFoundError(lookupError)) return;
461
+ throw lookupError;
462
+ }
463
+ throw error;
464
+ }
465
+ }
466
+ async function upsertVariables(input2) {
467
+ await input2.client.request(railwayOperations.variableUpsert, {
468
+ input: {
469
+ projectId: input2.projectId,
470
+ environmentId: input2.environmentId,
471
+ serviceId: input2.serviceId,
472
+ variables: input2.variables,
473
+ replace: false,
474
+ skipDeploys: true
475
+ }
476
+ });
477
+ }
478
+ async function loadVariables(input2) {
479
+ const data = await input2.client.request(
480
+ railwayOperations.variables,
481
+ {
482
+ projectId: input2.projectId,
483
+ environmentId: input2.environmentId,
484
+ serviceId: input2.serviceId
485
+ }
486
+ );
487
+ if (!data.variables || typeof data.variables !== "object" || Array.isArray(data.variables)) return {};
488
+ return Object.fromEntries(
489
+ Object.entries(data.variables).filter((entry) => typeof entry[1] === "string")
490
+ );
491
+ }
492
+ async function latestDeployment(client, projectId, environmentId, serviceId) {
493
+ const data = await client.request(railwayOperations.deployments, { projectId, environmentId, serviceId });
494
+ return nodes(data.deployments)[0] ?? null;
495
+ }
496
+ async function printDeploymentLogs(client, deploymentId, prefix, secrets) {
497
+ for (const operation of [railwayOperations.buildLogs, railwayOperations.deploymentLogs]) {
498
+ try {
499
+ const data = await client.request(
500
+ operation,
501
+ { deploymentId, limit: 100 }
502
+ );
503
+ const logKey = operation === railwayOperations.buildLogs ? "buildLogs" : "deploymentLogs";
504
+ for (const entry of data[logKey] ?? []) {
505
+ if (entry.message) console.log(`[${prefix}] ${redactText(entry.message, secrets)}`);
506
+ }
507
+ } catch {
508
+ }
509
+ }
510
+ }
511
+ async function waitForDeployment(input2) {
512
+ const deadline = Date.now() + input2.timeoutSeconds * 1e3;
513
+ let lastStatus = "UNKNOWN";
514
+ while (Date.now() < deadline) {
515
+ const data = await input2.client.request(
516
+ railwayOperations.deployment,
517
+ { id: input2.deploymentId }
518
+ );
519
+ lastStatus = data.deployment.status;
520
+ if (SUCCESS_STATUSES.has(lastStatus)) return data.deployment;
521
+ if (FAILURE_STATUSES.has(lastStatus)) {
522
+ await printDeploymentLogs(
523
+ input2.client,
524
+ input2.deploymentId,
525
+ input2.prefix,
526
+ input2.secrets
527
+ );
528
+ throw new Error(`${input2.prefix} deployment failed with status ${lastStatus}.`);
529
+ }
530
+ await wait(5e3);
531
+ }
532
+ throw new Error(
533
+ `Timed out waiting for ${input2.prefix} deployment. Last status: ${lastStatus}. Re-run the command to resume.`
534
+ );
535
+ }
536
+ async function deployService(input2) {
537
+ const previous = await latestDeployment(
538
+ input2.client,
539
+ input2.projectId,
540
+ input2.environmentId,
541
+ input2.serviceId
542
+ );
543
+ let deploymentId = null;
544
+ let triggerError;
545
+ try {
546
+ if (input2.source.mode === "git") {
547
+ await input2.client.request(railwayOperations.deploy, {
548
+ environmentId: input2.environmentId,
549
+ serviceId: input2.serviceId,
550
+ commitSha: input2.source.commitSha
551
+ });
552
+ } else {
553
+ deploymentId = uploadLocalSource({
554
+ cwd: input2.cwd,
555
+ projectId: input2.projectId,
556
+ environmentId: input2.environmentId,
557
+ serviceId: input2.serviceId,
558
+ serviceName: input2.serviceName,
559
+ token: input2.token
560
+ });
561
+ }
562
+ } catch (error) {
563
+ if (!isAmbiguousMutationError(error)) throw error;
564
+ triggerError = error;
565
+ }
566
+ if (!deploymentId) {
567
+ const deadline = Date.now() + 3e4;
568
+ while (Date.now() < deadline) {
569
+ const current = await latestDeployment(
570
+ input2.client,
571
+ input2.projectId,
572
+ input2.environmentId,
573
+ input2.serviceId
574
+ );
575
+ if (current && current.id !== previous?.id) {
576
+ deploymentId = current.id;
577
+ break;
578
+ }
579
+ await wait(2e3);
580
+ }
581
+ }
582
+ if (!deploymentId) {
583
+ if (triggerError) throw triggerError;
584
+ throw new Error(`Railway did not expose a new deployment for ${input2.serviceName}.`);
585
+ }
586
+ return waitForDeployment({
587
+ client: input2.client,
588
+ deploymentId,
589
+ timeoutSeconds: input2.timeoutSeconds,
590
+ prefix: input2.serviceName,
591
+ secrets: [input2.token, ...input2.secrets]
592
+ });
593
+ }
594
+ function collectSensitiveValues(variables) {
595
+ return collectSensitiveStructuredValues(variables);
596
+ }
597
+ async function ensureDomain(input2) {
598
+ const existingData = await input2.client.request(railwayOperations.domains, {
599
+ projectId: input2.projectId,
600
+ environmentId: input2.environmentId,
601
+ serviceId: input2.serviceId
602
+ });
603
+ if (!input2.customDomain && input2.currentDomainId) {
604
+ const existingCustomDomain = existingData.domains.customDomains?.find(
605
+ (candidate) => candidate.id === input2.currentDomainId
606
+ );
607
+ if (existingCustomDomain) {
608
+ return {
609
+ id: existingCustomDomain.id,
610
+ url: `https://${existingCustomDomain.domain}`
611
+ };
612
+ }
613
+ const existingRecordedServiceDomain = existingData.domains.serviceDomains?.find(
614
+ (candidate) => candidate.id === input2.currentDomainId
615
+ );
616
+ if (existingRecordedServiceDomain) {
617
+ return {
618
+ id: existingRecordedServiceDomain.id,
619
+ url: `https://${existingRecordedServiceDomain.domain}`
620
+ };
621
+ }
622
+ }
623
+ if (input2.customDomain) {
624
+ const existingDomain = existingData.domains.customDomains?.find(
625
+ (candidate) => candidate.domain === input2.customDomain
626
+ );
627
+ let domain;
628
+ if (existingDomain) {
629
+ domain = existingDomain;
630
+ } else {
631
+ try {
632
+ const data = await input2.client.request(
633
+ railwayOperations.customDomainCreate,
634
+ {
635
+ input: {
636
+ projectId: input2.projectId,
637
+ environmentId: input2.environmentId,
638
+ serviceId: input2.serviceId,
639
+ domain: input2.customDomain,
640
+ targetPort: 3e3
641
+ }
642
+ }
643
+ );
644
+ domain = data.customDomainCreate;
645
+ } catch (error) {
646
+ const discovered = await input2.client.request(railwayOperations.domains, {
647
+ projectId: input2.projectId,
648
+ environmentId: input2.environmentId,
649
+ serviceId: input2.serviceId
650
+ });
651
+ const created = discovered.domains.customDomains?.find(
652
+ (candidate) => candidate.domain === input2.customDomain
653
+ );
654
+ if (!created) throw error;
655
+ domain = created;
656
+ }
657
+ }
658
+ const records = domain.status?.dnsRecords ?? [];
659
+ if (records.length > 0) {
660
+ console.log("Configure these DNS records:");
661
+ for (const record of records) {
662
+ console.log(` ${record.recordType ?? "DNS"} ${record.hostlabel ?? "@"} -> ${record.requiredValue ?? ""}`);
663
+ }
664
+ }
665
+ if (input2.waitForVerification && !domain.status?.verified) {
666
+ const deadline = Date.now() + 3e5;
667
+ while (Date.now() < deadline) {
668
+ await wait(1e4);
669
+ const refreshed = await input2.client.request(
670
+ railwayOperations.customDomain,
671
+ { id: domain.id, projectId: input2.projectId }
672
+ );
673
+ domain = refreshed.customDomain;
674
+ if (domain.status?.verified) break;
675
+ }
676
+ if (!domain.status?.verified) {
677
+ console.warn("DNS not yet propagated. The deployment succeeded; re-run later to verify the domain.");
678
+ }
679
+ }
680
+ return {
681
+ id: domain.id,
682
+ url: `https://${domain.domain}`
683
+ };
684
+ }
685
+ const existingServiceDomain = existingData.domains.serviceDomains?.[0];
686
+ if (existingServiceDomain) {
687
+ return {
688
+ id: existingServiceDomain.id,
689
+ url: `https://${existingServiceDomain.domain}`
690
+ };
691
+ }
692
+ let createdDomain;
693
+ try {
694
+ const data = await input2.client.request(railwayOperations.serviceDomainCreate, {
695
+ input: {
696
+ environmentId: input2.environmentId,
697
+ serviceId: input2.serviceId,
698
+ targetPort: 3e3
699
+ }
700
+ });
701
+ createdDomain = data.serviceDomainCreate;
702
+ } catch (error) {
703
+ const discovered = await input2.client.request(railwayOperations.domains, {
704
+ projectId: input2.projectId,
705
+ environmentId: input2.environmentId,
706
+ serviceId: input2.serviceId
707
+ });
708
+ const created = discovered.domains.serviceDomains?.[0];
709
+ if (!created) throw error;
710
+ createdDomain = created;
711
+ }
712
+ return {
713
+ id: createdDomain.id,
714
+ url: `https://${createdDomain.domain}`
715
+ };
716
+ }
717
+ async function cleanupProject(input2) {
718
+ if (!input2.state.projectId) throw new Error("The Railway state file does not contain a project ID.");
719
+ if (input2.state.workspaceId && input2.state.workspaceId !== input2.workspaceId) {
720
+ throw new Error("Recorded Railway project belongs to a different workspace.");
721
+ }
722
+ const projectData = await input2.client.request(railwayOperations.project, { id: input2.state.projectId });
723
+ if (projectData.project.name !== input2.state.projectName || projectData.project.workspaceId && projectData.project.workspaceId !== input2.workspaceId) {
724
+ throw new Error("Recorded Railway project identity does not match the cleanup target.");
725
+ }
726
+ if (!input2.options.yes) {
727
+ if (input2.options.nonInteractive || !input2.prompt) {
728
+ throw new Error("--cleanup requires --yes in non-interactive mode.");
729
+ }
730
+ console.log(`Project: ${input2.state.projectName} (${input2.state.projectId})`);
731
+ for (const [name, environment] of Object.entries(input2.state.environments)) {
732
+ console.log(` ${name}: ${environment.appUrl ?? "no recorded URL"}`);
733
+ }
734
+ const confirmation = await input2.prompt.ask(`Type "${input2.state.projectName}" to delete this project: `);
735
+ if (confirmation.trim() !== input2.state.projectName) throw new Error("Cleanup cancelled.");
736
+ }
737
+ await input2.client.request(railwayOperations.projectDelete, { id: input2.state.projectId });
738
+ if (existsSync(input2.statePath)) unlinkSync(input2.statePath);
739
+ console.log(`Deleted Railway project ${input2.state.projectName}.`);
740
+ }
741
+ function printDryRun(input2) {
742
+ console.log("Railway deployment plan (dry run)");
743
+ console.log(` Project: ${input2.state.projectName}`);
744
+ console.log(` Environment: ${input2.options.environment}`);
745
+ console.log(` Source: ${input2.source.mode} (${input2.source.reason})`);
746
+ console.log(` Env file: ${input2.envFile}`);
747
+ console.log(" Operations:");
748
+ console.log(" - validate Railway account and workspace");
749
+ console.log(" - create or reuse project and environment");
750
+ console.log(" - provision Postgres and Redis from Railway templates");
751
+ console.log(` - create or reuse ${input2.options.service}`);
752
+ if (input2.options.worker) console.log(" - create or reuse mercato-worker");
753
+ if (!input2.options.worker && input2.state.environments[input2.options.environment]?.workerServiceId) {
754
+ console.log(" - delete the recorded managed mercato-worker service");
755
+ }
756
+ console.log(" - upsert service variables");
757
+ console.log(` - deploy via ${input2.source.mode === "git" ? "Railway Git source" : "railway up"}`);
758
+ console.log(" - create a Railway or custom domain and redeploy the app");
759
+ if (input2.options.volume) console.log(` - create volume mounted at ${input2.options.volume}`);
760
+ console.log(" App variables:");
761
+ for (const line of formatVariablePlan(input2.appVariables, input2.options.token)) console.log(` ${line}`);
762
+ if (input2.workerVariables) {
763
+ console.log(" Worker variables:");
764
+ for (const line of formatVariablePlan(input2.workerVariables, input2.options.token)) console.log(` ${line}`);
765
+ }
766
+ }
767
+ async function runRailwayDeploy(args, dependencies = {}) {
768
+ const options = parseRailwayDeployOptions(args);
769
+ if (options.help) {
770
+ console.log(railwayDeployHelp());
771
+ return;
772
+ }
773
+ const cwd = dependencies.cwd ?? process.cwd();
774
+ const appPackage = readAppPackage(cwd);
775
+ assertSupportedNodeVersion(appPackage.nodeEngine);
776
+ const statePath = railwayStatePath(cwd, options.track);
777
+ const loadedState = loadRailwayState(statePath);
778
+ const requestedName = normalizeProjectName(options.project || appPackage.name);
779
+ const state = loadedState ?? createRailwayState(requestedName, process.env.npm_package_version || "unknown");
780
+ if (options.cleanup && !state.projectId) {
781
+ throw new Error("The Railway state file does not contain a project ID.");
782
+ }
783
+ if (loadedState && options.project && requestedName !== loadedState.projectName) {
784
+ if (!options.forceRename) {
785
+ throw new Error(
786
+ `State records project "${loadedState.projectName}". Pass --force-rename to use "${requestedName}".`
787
+ );
788
+ }
789
+ state.projectName = requestedName;
790
+ }
791
+ const environmentState = ensureEnvironmentState(state, options.environment);
792
+ const source = options.cleanup ? { mode: "local", reason: "cleanup does not deploy source" } : (dependencies.resolveSource ?? resolveRailwaySource)(options.source, cwd);
793
+ if (options.cleanup && options.dryRun) {
794
+ console.log("Railway cleanup plan (dry run)");
795
+ console.log(` Project: ${state.projectName} (${state.projectId})`);
796
+ console.log(` State: ${statePath}`);
797
+ console.log(" Operation: projectDelete");
798
+ return;
799
+ }
800
+ if (!options.cleanup && !options.dryRun) {
801
+ console.log(`Source: ${source.mode} (${source.reason})`);
802
+ }
803
+ if (!options.cleanup && source.mode === "local") assertLocalUploadSafe(cwd);
804
+ const envFile = options.cleanup ? "" : resolveEnvFile(cwd, options.envFile);
805
+ const localEnv = options.cleanup ? {} : parseEnvFile(readFileSync(envFile, "utf8"));
806
+ let protectedSecrets = options.dryRun ? {
807
+ AUTH_SECRET: "generated-at-deploy",
808
+ JWT_SECRET: "generated-at-deploy",
809
+ TENANT_DATA_ENCRYPTION_FALLBACK_KEY: "generated-at-deploy"
810
+ } : generateProtectedSecrets(localEnv);
811
+ let appVariables = computeRailwayVariables({
812
+ env: localEnv,
813
+ role: "app",
814
+ workerEnabled: options.worker,
815
+ protectedSecrets,
816
+ railwayToken: options.token || process.env.RAILWAY_API_TOKEN,
817
+ allowedSecretKeys: options.allowedSecretKeys
818
+ });
819
+ let workerVariables = options.worker ? computeRailwayVariables({
820
+ env: localEnv,
821
+ role: "worker",
822
+ workerEnabled: true,
823
+ protectedSecrets,
824
+ railwayToken: options.token || process.env.RAILWAY_API_TOKEN,
825
+ allowedSecretKeys: options.allowedSecretKeys
826
+ }) : void 0;
827
+ if (options.dryRun) {
828
+ printDryRun({ state, options, source, envFile, appVariables, workerVariables });
829
+ return;
830
+ }
831
+ const ownsPrompt = dependencies.prompt === void 0;
832
+ const prompt = dependencies.prompt === void 0 ? !options.nonInteractive && process.stdin.isTTY ? createPrompt() : null : dependencies.prompt;
833
+ try {
834
+ const tokenResult = await resolveTokenInteractively(options, prompt);
835
+ const client = createRailwayGraphqlClient({
836
+ token: tokenResult.token,
837
+ verbose: options.verbose
838
+ });
839
+ console.log("Step 1: Authenticate with Railway");
840
+ const { workspaceId } = await resolveWorkspace(client, state.workspaceId);
841
+ if (options.cleanup) {
842
+ await cleanupProject({ client, state, statePath, options, prompt, workspaceId });
843
+ return;
844
+ }
845
+ state.workspaceId = workspaceId;
846
+ persistState(statePath, state);
847
+ console.log("Step 2: Resolve workspace");
848
+ console.log(`Step 3: Create or look up project "${state.projectName}"`);
849
+ const projectId = await ensureProject({ client, state, statePath, workspaceId, options });
850
+ console.log(`Step 4: Create or look up environment "${options.environment}"`);
851
+ const environmentId = await ensureEnvironment({
852
+ client,
853
+ projectId,
854
+ name: options.environment,
855
+ environmentState,
856
+ persist: () => persistState(statePath, state)
857
+ });
858
+ if (options.region) {
859
+ const regionData = await client.request(
860
+ railwayOperations.regions,
861
+ { projectId }
862
+ );
863
+ if (!regionData.regions.some(
864
+ (region) => region.id === options.region || region.region === options.region
865
+ )) {
866
+ throw new Error(`Unknown Railway region "${options.region}".`);
867
+ }
868
+ }
869
+ console.log("Step 5: Provision managed Postgres and Redis");
870
+ await ensureDatabase({
871
+ client,
872
+ projectId,
873
+ environmentId,
874
+ workspaceId,
875
+ name: "Postgres",
876
+ code: "postgres",
877
+ currentId: environmentState.postgresServiceId,
878
+ onResolved: (id) => {
879
+ environmentState.postgresServiceId = id;
880
+ persistState(statePath, state);
881
+ }
882
+ });
883
+ await ensureDatabase({
884
+ client,
885
+ projectId,
886
+ environmentId,
887
+ workspaceId,
888
+ name: "Redis",
889
+ code: "redis",
890
+ currentId: environmentState.redisServiceId,
891
+ onResolved: (id) => {
892
+ environmentState.redisServiceId = id;
893
+ persistState(statePath, state);
894
+ }
895
+ });
896
+ console.log("Step 6: Create app and worker services");
897
+ const previousSource = environmentState.source;
898
+ const appServiceId = await ensureService({
899
+ client,
900
+ projectId,
901
+ environmentId,
902
+ name: options.service,
903
+ source,
904
+ currentId: environmentState.appServiceId,
905
+ previousSource,
906
+ startCommand: "sh ./scripts/railway-start.sh",
907
+ region: options.region,
908
+ railwayConfigFile: "railway.toml",
909
+ onResolved: (id) => {
910
+ environmentState.appServiceId = id;
911
+ environmentState.source = source;
912
+ persistState(statePath, state);
913
+ }
914
+ });
915
+ const workerServiceId = options.worker ? await ensureService({
916
+ client,
917
+ projectId,
918
+ environmentId,
919
+ name: "mercato-worker",
920
+ source,
921
+ currentId: environmentState.workerServiceId,
922
+ previousSource,
923
+ startCommand: "sh ./scripts/railway-worker.sh",
924
+ region: options.region,
925
+ railwayConfigFile: "railway.worker.toml",
926
+ onResolved: (id) => {
927
+ environmentState.workerServiceId = id;
928
+ persistState(statePath, state);
929
+ }
930
+ }) : void 0;
931
+ if (!options.worker && environmentState.workerServiceId) {
932
+ await removeDisabledWorker({
933
+ client,
934
+ projectId,
935
+ environmentId,
936
+ workerServiceId: environmentState.workerServiceId
937
+ });
938
+ environmentState.workerServiceId = void 0;
939
+ if (environmentState.lastDeployIds) {
940
+ delete environmentState.lastDeployIds.worker;
941
+ }
942
+ persistState(statePath, state);
943
+ }
944
+ if (options.volume) {
945
+ if (environmentState.volumeId) {
946
+ const volumeData = await client.request(railwayOperations.volumes, { projectId });
947
+ const recordedVolume = nodes(volumeData.project.volumes).find(
948
+ (volume) => volume.id === environmentState.volumeId && nodes(volume.volumeInstances).some(
949
+ (instance) => instance.environmentId === environmentId && instance.serviceId === appServiceId && instance.mountPath === options.volume
950
+ )
951
+ );
952
+ if (!recordedVolume) environmentState.volumeId = void 0;
953
+ }
954
+ }
955
+ if (options.volume && !environmentState.volumeId) {
956
+ try {
957
+ const volumeData = await client.request(
958
+ railwayOperations.volumeCreate,
959
+ {
960
+ input: {
961
+ projectId,
962
+ environmentId,
963
+ serviceId: appServiceId,
964
+ mountPath: options.volume
965
+ }
966
+ }
967
+ );
968
+ environmentState.volumeId = volumeData.volumeCreate.id;
969
+ } catch (error) {
970
+ const volumeData = await client.request(railwayOperations.volumes, { projectId });
971
+ const discovered = nodes(volumeData.project.volumes).find(
972
+ (volume) => nodes(volume.volumeInstances).some(
973
+ (instance) => instance.environmentId === environmentId && instance.serviceId === appServiceId && instance.mountPath === options.volume
974
+ )
975
+ );
976
+ if (!discovered) throw error;
977
+ environmentState.volumeId = discovered.id;
978
+ }
979
+ persistState(statePath, state);
980
+ }
981
+ if (!options.volume) {
982
+ console.warn(
983
+ "Attachments uploaded to this deployment will be lost on redeploy. Re-run with --volume /app/storage to enable persistent storage."
984
+ );
985
+ }
986
+ console.log("Step 7: Compute and upload environment variables");
987
+ const existingVariables = await loadVariables({
988
+ client,
989
+ projectId,
990
+ environmentId,
991
+ serviceId: appServiceId
992
+ });
993
+ protectedSecrets = generateProtectedSecrets({ ...localEnv, ...existingVariables });
994
+ appVariables = computeRailwayVariables({
995
+ env: localEnv,
996
+ role: "app",
997
+ workerEnabled: options.worker,
998
+ protectedSecrets,
999
+ railwayToken: tokenResult.token,
1000
+ allowedSecretKeys: options.allowedSecretKeys
1001
+ });
1002
+ workerVariables = options.worker ? computeRailwayVariables({
1003
+ env: localEnv,
1004
+ role: "worker",
1005
+ workerEnabled: true,
1006
+ protectedSecrets,
1007
+ railwayToken: tokenResult.token,
1008
+ allowedSecretKeys: options.allowedSecretKeys
1009
+ }) : void 0;
1010
+ await upsertVariables({ client, projectId, environmentId, serviceId: appServiceId, variables: appVariables });
1011
+ if (workerServiceId && workerVariables) {
1012
+ await upsertVariables({
1013
+ client,
1014
+ projectId,
1015
+ environmentId,
1016
+ serviceId: workerServiceId,
1017
+ variables: workerVariables
1018
+ });
1019
+ }
1020
+ if (options.writeEnv) writeGeneratedSecrets(envFile, protectedSecrets);
1021
+ console.log("Step 8: Trigger deployment and monitor status");
1022
+ const appDeployment = await deployService({
1023
+ client,
1024
+ cwd,
1025
+ source,
1026
+ projectId,
1027
+ environmentId,
1028
+ serviceId: appServiceId,
1029
+ serviceName: options.service,
1030
+ timeoutSeconds: options.timeoutSeconds,
1031
+ token: tokenResult.token,
1032
+ secrets: collectSensitiveValues(appVariables)
1033
+ });
1034
+ environmentState.lastDeployIds ??= {};
1035
+ environmentState.lastDeployIds.app = appDeployment.id;
1036
+ persistState(statePath, state);
1037
+ if (workerServiceId) {
1038
+ const workerDeployment = await deployService({
1039
+ client,
1040
+ cwd,
1041
+ source,
1042
+ projectId,
1043
+ environmentId,
1044
+ serviceId: workerServiceId,
1045
+ serviceName: "mercato-worker",
1046
+ timeoutSeconds: options.timeoutSeconds,
1047
+ token: tokenResult.token,
1048
+ secrets: collectSensitiveValues(workerVariables ?? {})
1049
+ });
1050
+ environmentState.lastDeployIds.worker = workerDeployment.id;
1051
+ persistState(statePath, state);
1052
+ }
1053
+ console.log("Step 9: Provision public domain");
1054
+ const domain = await ensureDomain({
1055
+ client,
1056
+ projectId,
1057
+ environmentId,
1058
+ serviceId: appServiceId,
1059
+ customDomain: options.domain,
1060
+ waitForVerification: options.waitDomain,
1061
+ currentDomainId: environmentState.domainId
1062
+ });
1063
+ environmentState.domainId = domain.id;
1064
+ environmentState.appUrl = domain.url;
1065
+ persistState(statePath, state);
1066
+ const finalAppVariables = { ...appVariables, APP_URL: domain.url, NEXT_PUBLIC_APP_URL: domain.url };
1067
+ await upsertVariables({
1068
+ client,
1069
+ projectId,
1070
+ environmentId,
1071
+ serviceId: appServiceId,
1072
+ variables: finalAppVariables
1073
+ });
1074
+ const finalDeployment = await deployService({
1075
+ client,
1076
+ cwd,
1077
+ source,
1078
+ projectId,
1079
+ environmentId,
1080
+ serviceId: appServiceId,
1081
+ serviceName: options.service,
1082
+ timeoutSeconds: options.timeoutSeconds,
1083
+ token: tokenResult.token,
1084
+ secrets: collectSensitiveValues(finalAppVariables)
1085
+ });
1086
+ environmentState.lastDeployIds.app = finalDeployment.id;
1087
+ persistState(statePath, state);
1088
+ console.log("Step 10: Deployment complete");
1089
+ console.log("");
1090
+ console.log("Open Mercato deployed to Railway");
1091
+ console.log(` Project: https://railway.com/project/${projectId}`);
1092
+ console.log(` Environment: ${options.environment}`);
1093
+ console.log(` Source: ${source.mode}`);
1094
+ console.log(` App URL: ${domain.url}`);
1095
+ console.log(` Health: ${domain.url}/api/healthz`);
1096
+ if (workerServiceId) console.log(" Worker: mercato-worker (running)");
1097
+ console.log(` State: ${statePath}`);
1098
+ if (tokenResult.cached) console.log(` Token cache: ${railwayTokenConfigPath()}`);
1099
+ console.log("");
1100
+ console.log(`DEPLOY_URL=${domain.url}`);
1101
+ } finally {
1102
+ if (ownsPrompt) prompt?.close();
1103
+ }
1104
+ }
1105
+ export {
1106
+ computeRailwayVariables,
1107
+ formatVariablePlan,
1108
+ generateProtectedSecrets,
1109
+ parseEnvFile,
1110
+ parseRailwayDeployOptions,
1111
+ railwayDeployHelp,
1112
+ resolveEnvFile,
1113
+ runRailwayDeploy
1114
+ };
1115
+ //# sourceMappingURL=index.js.map