@open-mercato/cli 0.6.5-develop.5116.1.f0af9e5080 → 0.6.5-develop.5155.1.148d10a46d

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 (56) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/bin.js +1 -0
  3. package/dist/bin.js.map +2 -2
  4. package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.meta.js +7 -0
  5. package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.meta.js.map +7 -0
  6. package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.spec.js +63 -0
  7. package/dist/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.spec.js.map +7 -0
  8. package/dist/lib/deploy/railway/env.js +98 -0
  9. package/dist/lib/deploy/railway/env.js.map +7 -0
  10. package/dist/lib/deploy/railway/graphql-client.js +99 -0
  11. package/dist/lib/deploy/railway/graphql-client.js.map +7 -0
  12. package/dist/lib/deploy/railway/index.js +1115 -0
  13. package/dist/lib/deploy/railway/index.js.map +7 -0
  14. package/dist/lib/deploy/railway/operations.js +249 -0
  15. package/dist/lib/deploy/railway/operations.js.map +7 -0
  16. package/dist/lib/deploy/railway/options.js +155 -0
  17. package/dist/lib/deploy/railway/options.js.map +7 -0
  18. package/dist/lib/deploy/railway/railway-cli.js +61 -0
  19. package/dist/lib/deploy/railway/railway-cli.js.map +7 -0
  20. package/dist/lib/deploy/railway/redaction.js +88 -0
  21. package/dist/lib/deploy/railway/redaction.js.map +7 -0
  22. package/dist/lib/deploy/railway/source.js +128 -0
  23. package/dist/lib/deploy/railway/source.js.map +7 -0
  24. package/dist/lib/deploy/railway/state.js +41 -0
  25. package/dist/lib/deploy/railway/state.js.map +7 -0
  26. package/dist/lib/deploy/railway/token.js +46 -0
  27. package/dist/lib/deploy/railway/token.js.map +7 -0
  28. package/dist/lib/deploy/railway/types.js +1 -0
  29. package/dist/lib/deploy/railway/types.js.map +7 -0
  30. package/dist/mercato.js +23 -8
  31. package/dist/mercato.js.map +2 -2
  32. package/package.json +10 -10
  33. package/src/bin.ts +1 -0
  34. package/src/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.meta.ts +3 -0
  35. package/src/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.spec.ts +71 -0
  36. package/src/lib/deploy/railway/__tests__/dry-run.test.ts +61 -0
  37. package/src/lib/deploy/railway/__tests__/env.test.ts +55 -0
  38. package/src/lib/deploy/railway/__tests__/graphql-client.test.ts +80 -0
  39. package/src/lib/deploy/railway/__tests__/operations.test.ts +21 -0
  40. package/src/lib/deploy/railway/__tests__/options.test.ts +78 -0
  41. package/src/lib/deploy/railway/__tests__/redaction.test.ts +62 -0
  42. package/src/lib/deploy/railway/__tests__/source.test.ts +130 -0
  43. package/src/lib/deploy/railway/__tests__/state.test.ts +24 -0
  44. package/src/lib/deploy/railway/__tests__/token.test.ts +39 -0
  45. package/src/lib/deploy/railway/env.ts +112 -0
  46. package/src/lib/deploy/railway/graphql-client.ts +128 -0
  47. package/src/lib/deploy/railway/index.ts +1454 -0
  48. package/src/lib/deploy/railway/operations.ts +248 -0
  49. package/src/lib/deploy/railway/options.ts +167 -0
  50. package/src/lib/deploy/railway/railway-cli.ts +65 -0
  51. package/src/lib/deploy/railway/redaction.ts +91 -0
  52. package/src/lib/deploy/railway/source.ts +143 -0
  53. package/src/lib/deploy/railway/state.ts +45 -0
  54. package/src/lib/deploy/railway/token.ts +49 -0
  55. package/src/lib/deploy/railway/types.ts +87 -0
  56. package/src/mercato.ts +33 -8
@@ -1,4 +1,4 @@
1
- [build:cli] found 74 entry points
1
+ [build:cli] found 87 entry points
2
2
  Copied create-app/agentic/ → dist/agentic/
3
3
  Discovered 16 standalone guides → dist/agentic/guides/
4
4
  [build:cli] built successfully
package/dist/bin.js CHANGED
@@ -3,6 +3,7 @@ import { run } from "./mercato.js";
3
3
  const BOOTSTRAP_FREE_COMMANDS = [
4
4
  "generate",
5
5
  "module",
6
+ "deploy",
6
7
  "db",
7
8
  "init",
8
9
  "agentic:init",
package/dist/bin.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/bin.ts"],
4
- "sourcesContent": ["/**\n * CLI binary entry point for @open-mercato/cli package.\n *\n * Called from within a Next.js app directory as: yarn mercato <command>\n * Uses dynamic app resolution to find generated files at .mercato/generated/\n */\nimport { run } from './mercato.js'\n\n// Commands that can run without bootstrap (without generated files)\n// - generate: creates the generated files\n// - db: uses resolver directly to find modules and migrations\n// - init: runs yarn commands to set up the app\n// - help: just shows help text\nconst BOOTSTRAP_FREE_COMMANDS = [\n 'generate',\n 'module',\n 'db',\n 'init',\n 'agentic:init',\n 'eject',\n 'test',\n 'test:integration',\n 'test:integration:coverage',\n 'test:integration:spec-coverage',\n 'test:ephemeral',\n 'test:integration:interactive',\n 'umes:list',\n 'umes:inspect',\n 'umes:check',\n 'help',\n '--help',\n '-h',\n]\n\nfunction assertNode24Runtime(): void {\n const detectedNodeVersion = process.versions.node\n const majorVersion = Number.parseInt(detectedNodeVersion.split('.')[0] ?? '0', 10)\n if (majorVersion >= 24) {\n return\n }\n throw new Error(\n [\n 'Unsupported Node.js runtime.',\n `Cause: Detected Node ${detectedNodeVersion}, but Open Mercato requires Node 24.x.`,\n 'What to do: switch your shell to Node 24 (for example `nvm use 24`), run `yarn install`, then retry.',\n ].join(' '),\n )\n}\n\nfunction needsBootstrap(argv: string[]): boolean {\n const [, , first] = argv\n if (!first) return false // help screen\n return !BOOTSTRAP_FREE_COMMANDS.includes(first)\n}\n\nasync function tryBootstrap(): Promise<boolean> {\n try {\n const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n const { registerCliModules } = await import('./mercato.js')\n // Use the CLI resolver to find the app directory (handles monorepo detection)\n const { createResolver } = await import('./lib/resolver.js')\n const resolver = createResolver()\n const appDir = resolver.getAppDir()\n const data = await bootstrapFromAppRoot(appDir)\n // Register CLI modules directly to avoid module resolution issues\n registerCliModules(data.modules)\n return true\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n // Check if the error is about missing generated files\n if (\n message.includes('Cannot find module') &&\n (message.includes('/generated/') || message.includes('.generated') || message.includes('.mercato'))\n ) {\n return false\n }\n // Re-throw other errors\n throw err\n }\n}\n\nasync function main(): Promise<void> {\n assertNode24Runtime()\n const requiresBootstrap = needsBootstrap(process.argv)\n\n if (requiresBootstrap) {\n const bootstrapSucceeded = await tryBootstrap()\n if (!bootstrapSucceeded) {\n console.error('\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557')\n console.error('\u2551 Generated files not found! \u2551')\n console.error('\u2551 \u2551')\n console.error('\u2551 The CLI requires generated files to discover modules. \u2551')\n console.error('\u2551 Please run the following command first: \u2551')\n console.error('\u2551 \u2551')\n console.error('\u2551 yarn mercato generate \u2551')\n console.error('\u2551 \u2551')\n console.error('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D')\n process.exit(1)\n }\n }\n\n const code = await run(process.argv)\n process.exit(code ?? 0)\n}\n\nmain().catch((error: unknown) => {\n if (error instanceof Error) {\n console.error(error.message)\n } else {\n console.error(error)\n }\n process.exit(1)\n})\n"],
5
- "mappings": "AAMA,SAAS,WAAW;AAOpB,MAAM,0BAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,sBAA4B;AACnC,QAAM,sBAAsB,QAAQ,SAAS;AAC7C,QAAM,eAAe,OAAO,SAAS,oBAAoB,MAAM,GAAG,EAAE,CAAC,KAAK,KAAK,EAAE;AACjF,MAAI,gBAAgB,IAAI;AACtB;AAAA,EACF;AACA,QAAM,IAAI;AAAA,IACR;AAAA,MACE;AAAA,MACA,wBAAwB,mBAAmB;AAAA,MAC3C;AAAA,IACF,EAAE,KAAK,GAAG;AAAA,EACZ;AACF;AAEA,SAAS,eAAe,MAAyB;AAC/C,QAAM,CAAC,EAAE,EAAE,KAAK,IAAI;AACpB,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,CAAC,wBAAwB,SAAS,KAAK;AAChD;AAEA,eAAe,eAAiC;AAC9C,MAAI;AACF,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,kDAAkD;AAChG,UAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,cAAc;AAE1D,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,mBAAmB;AAC3D,UAAM,WAAW,eAAe;AAChC,UAAM,SAAS,SAAS,UAAU;AAClC,UAAM,OAAO,MAAM,qBAAqB,MAAM;AAE9C,uBAAmB,KAAK,OAAO;AAC/B,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAE/D,QACE,QAAQ,SAAS,oBAAoB,MACpC,QAAQ,SAAS,aAAa,KAAK,QAAQ,SAAS,YAAY,KAAK,QAAQ,SAAS,UAAU,IACjG;AACA,aAAO;AAAA,IACT;AAEA,UAAM;AAAA,EACR;AACF;AAEA,eAAe,OAAsB;AACnC,sBAAoB;AACpB,QAAM,oBAAoB,eAAe,QAAQ,IAAI;AAErD,MAAI,mBAAmB;AACrB,UAAM,qBAAqB,MAAM,aAAa;AAC9C,QAAI,CAAC,oBAAoB;AACvB,cAAQ,MAAM,gaAAuE;AACrF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gaAAuE;AACrF,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,IAAI,QAAQ,IAAI;AACnC,UAAQ,KAAK,QAAQ,CAAC;AACxB;AAEA,KAAK,EAAE,MAAM,CAAC,UAAmB;AAC/B,MAAI,iBAAiB,OAAO;AAC1B,YAAQ,MAAM,MAAM,OAAO;AAAA,EAC7B,OAAO;AACL,YAAQ,MAAM,KAAK;AAAA,EACrB;AACA,UAAQ,KAAK,CAAC;AAChB,CAAC;",
4
+ "sourcesContent": ["/**\n * CLI binary entry point for @open-mercato/cli package.\n *\n * Called from within a Next.js app directory as: yarn mercato <command>\n * Uses dynamic app resolution to find generated files at .mercato/generated/\n */\nimport { run } from './mercato.js'\n\n// Commands that can run without bootstrap (without generated files)\n// - generate: creates the generated files\n// - db: uses resolver directly to find modules and migrations\n// - init: runs yarn commands to set up the app\n// - help: just shows help text\nconst BOOTSTRAP_FREE_COMMANDS = [\n 'generate',\n 'module',\n 'deploy',\n 'db',\n 'init',\n 'agentic:init',\n 'eject',\n 'test',\n 'test:integration',\n 'test:integration:coverage',\n 'test:integration:spec-coverage',\n 'test:ephemeral',\n 'test:integration:interactive',\n 'umes:list',\n 'umes:inspect',\n 'umes:check',\n 'help',\n '--help',\n '-h',\n]\n\nfunction assertNode24Runtime(): void {\n const detectedNodeVersion = process.versions.node\n const majorVersion = Number.parseInt(detectedNodeVersion.split('.')[0] ?? '0', 10)\n if (majorVersion >= 24) {\n return\n }\n throw new Error(\n [\n 'Unsupported Node.js runtime.',\n `Cause: Detected Node ${detectedNodeVersion}, but Open Mercato requires Node 24.x.`,\n 'What to do: switch your shell to Node 24 (for example `nvm use 24`), run `yarn install`, then retry.',\n ].join(' '),\n )\n}\n\nfunction needsBootstrap(argv: string[]): boolean {\n const [, , first] = argv\n if (!first) return false // help screen\n return !BOOTSTRAP_FREE_COMMANDS.includes(first)\n}\n\nasync function tryBootstrap(): Promise<boolean> {\n try {\n const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n const { registerCliModules } = await import('./mercato.js')\n // Use the CLI resolver to find the app directory (handles monorepo detection)\n const { createResolver } = await import('./lib/resolver.js')\n const resolver = createResolver()\n const appDir = resolver.getAppDir()\n const data = await bootstrapFromAppRoot(appDir)\n // Register CLI modules directly to avoid module resolution issues\n registerCliModules(data.modules)\n return true\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n // Check if the error is about missing generated files\n if (\n message.includes('Cannot find module') &&\n (message.includes('/generated/') || message.includes('.generated') || message.includes('.mercato'))\n ) {\n return false\n }\n // Re-throw other errors\n throw err\n }\n}\n\nasync function main(): Promise<void> {\n assertNode24Runtime()\n const requiresBootstrap = needsBootstrap(process.argv)\n\n if (requiresBootstrap) {\n const bootstrapSucceeded = await tryBootstrap()\n if (!bootstrapSucceeded) {\n console.error('\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557')\n console.error('\u2551 Generated files not found! \u2551')\n console.error('\u2551 \u2551')\n console.error('\u2551 The CLI requires generated files to discover modules. \u2551')\n console.error('\u2551 Please run the following command first: \u2551')\n console.error('\u2551 \u2551')\n console.error('\u2551 yarn mercato generate \u2551')\n console.error('\u2551 \u2551')\n console.error('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D')\n process.exit(1)\n }\n }\n\n const code = await run(process.argv)\n process.exit(code ?? 0)\n}\n\nmain().catch((error: unknown) => {\n if (error instanceof Error) {\n console.error(error.message)\n } else {\n console.error(error)\n }\n process.exit(1)\n})\n"],
5
+ "mappings": "AAMA,SAAS,WAAW;AAOpB,MAAM,0BAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,sBAA4B;AACnC,QAAM,sBAAsB,QAAQ,SAAS;AAC7C,QAAM,eAAe,OAAO,SAAS,oBAAoB,MAAM,GAAG,EAAE,CAAC,KAAK,KAAK,EAAE;AACjF,MAAI,gBAAgB,IAAI;AACtB;AAAA,EACF;AACA,QAAM,IAAI;AAAA,IACR;AAAA,MACE;AAAA,MACA,wBAAwB,mBAAmB;AAAA,MAC3C;AAAA,IACF,EAAE,KAAK,GAAG;AAAA,EACZ;AACF;AAEA,SAAS,eAAe,MAAyB;AAC/C,QAAM,CAAC,EAAE,EAAE,KAAK,IAAI;AACpB,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,CAAC,wBAAwB,SAAS,KAAK;AAChD;AAEA,eAAe,eAAiC;AAC9C,MAAI;AACF,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,kDAAkD;AAChG,UAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,cAAc;AAE1D,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,mBAAmB;AAC3D,UAAM,WAAW,eAAe;AAChC,UAAM,SAAS,SAAS,UAAU;AAClC,UAAM,OAAO,MAAM,qBAAqB,MAAM;AAE9C,uBAAmB,KAAK,OAAO;AAC/B,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAE/D,QACE,QAAQ,SAAS,oBAAoB,MACpC,QAAQ,SAAS,aAAa,KAAK,QAAQ,SAAS,YAAY,KAAK,QAAQ,SAAS,UAAU,IACjG;AACA,aAAO;AAAA,IACT;AAEA,UAAM;AAAA,EACR;AACF;AAEA,eAAe,OAAsB;AACnC,sBAAoB;AACpB,QAAM,oBAAoB,eAAe,QAAQ,IAAI;AAErD,MAAI,mBAAmB;AACrB,UAAM,qBAAqB,MAAM,aAAa;AAC9C,QAAI,CAAC,oBAAoB;AACvB,cAAQ,MAAM,gaAAuE;AACrF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gaAAuE;AACrF,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,IAAI,QAAQ,IAAI;AACnC,UAAQ,KAAK,QAAQ,CAAC;AACxB;AAEA,KAAK,EAAE,MAAM,CAAC,UAAmB;AAC/B,MAAI,iBAAiB,OAAO;AAC1B,YAAQ,MAAM,MAAM,OAAO;AAAA,EAC7B,OAAO;AACL,YAAQ,MAAM,KAAK;AAAA,EACrB;AACA,UAAQ,KAAK,CAAC;AAChB,CAAC;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,7 @@
1
+ const integrationMeta = {
2
+ requiredEnvVars: ["RAILWAY_INTEGRATION_TOKEN", "RAILWAY_INTEGRATION_APP_ROOT"]
3
+ };
4
+ export {
5
+ integrationMeta
6
+ };
7
+ //# sourceMappingURL=TC-CLI-RAILWAY-001.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.meta.ts"],
4
+ "sourcesContent": ["export const integrationMeta = {\n requiredEnvVars: ['RAILWAY_INTEGRATION_TOKEN', 'RAILWAY_INTEGRATION_APP_ROOT'],\n}\n"],
5
+ "mappings": "AAAO,MAAM,kBAAkB;AAAA,EAC7B,iBAAiB,CAAC,6BAA6B,8BAA8B;AAC/E;",
6
+ "names": []
7
+ }
@@ -0,0 +1,63 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { expect, test } from "@playwright/test";
5
+ const token = process.env.RAILWAY_INTEGRATION_TOKEN;
6
+ const appRoot = process.env.RAILWAY_INTEGRATION_APP_ROOT;
7
+ const cliBin = resolve(process.cwd(), "packages/cli/dist/bin.js");
8
+ function runCli(args) {
9
+ if (!appRoot || !token) throw new Error("Railway integration environment is incomplete.");
10
+ return execFileSync(process.execPath, [cliBin, ...args], {
11
+ cwd: appRoot,
12
+ encoding: "utf8",
13
+ env: {
14
+ ...process.env,
15
+ RAILWAY_API_TOKEN: token,
16
+ CI: "true"
17
+ },
18
+ timeout: 15 * 60 * 1e3
19
+ });
20
+ }
21
+ test.describe("mercato deploy railway live integration", () => {
22
+ test.skip(!token || !appRoot, "RAILWAY_INTEGRATION_TOKEN and RAILWAY_INTEGRATION_APP_ROOT are required");
23
+ test("deploys, verifies health, resumes, and cleans up", async ({ request }) => {
24
+ expect(existsSync(cliBin)).toBe(true);
25
+ const projectName = `mercato-integration-${Date.now()}`;
26
+ try {
27
+ const output = runCli([
28
+ "deploy",
29
+ "railway",
30
+ "--project",
31
+ projectName,
32
+ "--source",
33
+ "local",
34
+ "--no-worker",
35
+ "--non-interactive"
36
+ ]);
37
+ const outputLines = output.trim().split(/\r?\n/);
38
+ const deployUrl = outputLines.find((line) => line.startsWith("DEPLOY_URL="))?.slice("DEPLOY_URL=".length);
39
+ expect(deployUrl).toBeTruthy();
40
+ expect(outputLines.at(-1)).toBe(`DEPLOY_URL=${deployUrl}`);
41
+ const health = await request.get(`${deployUrl}/api/healthz`);
42
+ expect(health.status()).toBe(200);
43
+ expect(await health.json()).toEqual({
44
+ status: "ok",
45
+ ts: expect.any(String)
46
+ });
47
+ const resumeOutput = runCli([
48
+ "deploy",
49
+ "railway",
50
+ "--source",
51
+ "local",
52
+ "--no-worker",
53
+ "--non-interactive"
54
+ ]);
55
+ expect(resumeOutput).toContain(`Project: https://railway.com/project/`);
56
+ } finally {
57
+ if (appRoot && existsSync(resolve(appRoot, ".mercato", "railway.json"))) {
58
+ runCli(["deploy", "railway", "--cleanup", "--non-interactive", "--yes"]);
59
+ }
60
+ }
61
+ });
62
+ });
63
+ //# sourceMappingURL=TC-CLI-RAILWAY-001.spec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/lib/deploy/railway/__integration__/TC-CLI-RAILWAY-001.spec.ts"],
4
+ "sourcesContent": ["import { execFileSync } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { expect, test } from '@playwright/test'\n\nconst token = process.env.RAILWAY_INTEGRATION_TOKEN\nconst appRoot = process.env.RAILWAY_INTEGRATION_APP_ROOT\nconst cliBin = resolve(process.cwd(), 'packages/cli/dist/bin.js')\n\nfunction runCli(args: string[]): string {\n if (!appRoot || !token) throw new Error('Railway integration environment is incomplete.')\n return execFileSync(process.execPath, [cliBin, ...args], {\n cwd: appRoot,\n encoding: 'utf8',\n env: {\n ...process.env,\n RAILWAY_API_TOKEN: token,\n CI: 'true',\n },\n timeout: 15 * 60 * 1000,\n })\n}\n\ntest.describe('mercato deploy railway live integration', () => {\n test.skip(!token || !appRoot, 'RAILWAY_INTEGRATION_TOKEN and RAILWAY_INTEGRATION_APP_ROOT are required')\n\n test('deploys, verifies health, resumes, and cleans up', async ({ request }) => {\n expect(existsSync(cliBin)).toBe(true)\n const projectName = `mercato-integration-${Date.now()}`\n\n try {\n const output = runCli([\n 'deploy',\n 'railway',\n '--project',\n projectName,\n '--source',\n 'local',\n '--no-worker',\n '--non-interactive',\n ])\n const outputLines = output.trim().split(/\\r?\\n/)\n const deployUrl = outputLines\n .find((line) => line.startsWith('DEPLOY_URL='))\n ?.slice('DEPLOY_URL='.length)\n expect(deployUrl).toBeTruthy()\n expect(outputLines.at(-1)).toBe(`DEPLOY_URL=${deployUrl}`)\n\n const health = await request.get(`${deployUrl}/api/healthz`)\n expect(health.status()).toBe(200)\n expect(await health.json()).toEqual({\n status: 'ok',\n ts: expect.any(String),\n })\n\n const resumeOutput = runCli([\n 'deploy',\n 'railway',\n '--source',\n 'local',\n '--no-worker',\n '--non-interactive',\n ])\n expect(resumeOutput).toContain(`Project: https://railway.com/project/`)\n } finally {\n if (appRoot && existsSync(resolve(appRoot, '.mercato', 'railway.json'))) {\n runCli(['deploy', 'railway', '--cleanup', '--non-interactive', '--yes'])\n }\n }\n })\n})\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,QAAQ,YAAY;AAE7B,MAAM,QAAQ,QAAQ,IAAI;AAC1B,MAAM,UAAU,QAAQ,IAAI;AAC5B,MAAM,SAAS,QAAQ,QAAQ,IAAI,GAAG,0BAA0B;AAEhE,SAAS,OAAO,MAAwB;AACtC,MAAI,CAAC,WAAW,CAAC,MAAO,OAAM,IAAI,MAAM,gDAAgD;AACxF,SAAO,aAAa,QAAQ,UAAU,CAAC,QAAQ,GAAG,IAAI,GAAG;AAAA,IACvD,KAAK;AAAA,IACL,UAAU;AAAA,IACV,KAAK;AAAA,MACH,GAAG,QAAQ;AAAA,MACX,mBAAmB;AAAA,MACnB,IAAI;AAAA,IACN;AAAA,IACA,SAAS,KAAK,KAAK;AAAA,EACrB,CAAC;AACH;AAEA,KAAK,SAAS,2CAA2C,MAAM;AAC7D,OAAK,KAAK,CAAC,SAAS,CAAC,SAAS,yEAAyE;AAEvG,OAAK,oDAAoD,OAAO,EAAE,QAAQ,MAAM;AAC9E,WAAO,WAAW,MAAM,CAAC,EAAE,KAAK,IAAI;AACpC,UAAM,cAAc,uBAAuB,KAAK,IAAI,CAAC;AAErD,QAAI;AACF,YAAM,SAAS,OAAO;AAAA,QACpB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,YAAM,cAAc,OAAO,KAAK,EAAE,MAAM,OAAO;AAC/C,YAAM,YAAY,YACf,KAAK,CAAC,SAAS,KAAK,WAAW,aAAa,CAAC,GAC5C,MAAM,cAAc,MAAM;AAC9B,aAAO,SAAS,EAAE,WAAW;AAC7B,aAAO,YAAY,GAAG,EAAE,CAAC,EAAE,KAAK,cAAc,SAAS,EAAE;AAEzD,YAAM,SAAS,MAAM,QAAQ,IAAI,GAAG,SAAS,cAAc;AAC3D,aAAO,OAAO,OAAO,CAAC,EAAE,KAAK,GAAG;AAChC,aAAO,MAAM,OAAO,KAAK,CAAC,EAAE,QAAQ;AAAA,QAClC,QAAQ;AAAA,QACR,IAAI,OAAO,IAAI,MAAM;AAAA,MACvB,CAAC;AAED,YAAM,eAAe,OAAO;AAAA,QAC1B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAO,YAAY,EAAE,UAAU,2CAA2C;AAAA,IAC5E,UAAE;AACA,UAAI,WAAW,WAAW,QAAQ,SAAS,YAAY,cAAc,CAAC,GAAG;AACvE,eAAO,CAAC,UAAU,WAAW,aAAa,qBAAqB,OAAO,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF,CAAC;AACH,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,98 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { assertSafeVariables, formatVariableValue } from "./redaction.js";
6
+ function resolveEnvFile(cwd, requested) {
7
+ const candidates = requested ? [requested] : [".env.production", ".env"];
8
+ for (const candidate of candidates) {
9
+ const path = resolve(cwd, candidate);
10
+ if (existsSync(path)) return path;
11
+ }
12
+ throw new Error(`Environment file not found. Checked: ${candidates.join(", ")}.`);
13
+ }
14
+ function parseEnvFile(contents) {
15
+ const result = {};
16
+ for (const rawLine of contents.split(/\r?\n/)) {
17
+ const line = rawLine.trim();
18
+ if (!line || line.startsWith("#")) continue;
19
+ const normalized = line.startsWith("export ") ? line.slice(7).trim() : line;
20
+ const separator = normalized.indexOf("=");
21
+ if (separator <= 0) continue;
22
+ const key = normalized.slice(0, separator).trim();
23
+ let value = normalized.slice(separator + 1).trim();
24
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
25
+ value = value.slice(1, -1);
26
+ }
27
+ result[key] = value.replace(/\\n/g, "\n");
28
+ }
29
+ return result;
30
+ }
31
+ function generateProtectedSecrets(existing) {
32
+ return {
33
+ AUTH_SECRET: existing.AUTH_SECRET || randomBytes(64).toString("hex"),
34
+ JWT_SECRET: existing.JWT_SECRET || randomBytes(64).toString("hex"),
35
+ TENANT_DATA_ENCRYPTION_FALLBACK_KEY: existing.TENANT_DATA_ENCRYPTION_FALLBACK_KEY || randomBytes(32).toString("base64url")
36
+ };
37
+ }
38
+ function computeRailwayVariables(input) {
39
+ const variables = {
40
+ ...input.env,
41
+ ...input.protectedSecrets,
42
+ DATABASE_URL: "${{Postgres.DATABASE_URL}}",
43
+ REDIS_URL: "${{Redis.REDIS_URL}}",
44
+ CACHE_STRATEGY: "redis",
45
+ NODE_ENV: "production",
46
+ NEXT_TELEMETRY_DISABLED: "1",
47
+ PORT: "3000",
48
+ OM_AI_PROVIDER: input.env.OM_AI_PROVIDER || "openai",
49
+ OM_AI_MODEL: input.env.OM_AI_MODEL || "gpt-5-mini",
50
+ QUEUE_STRATEGY: "async",
51
+ NEXT_PUBLIC_QUEUE_STRATEGY: "async",
52
+ AUTO_SPAWN_WORKERS: input.role === "app" && !input.workerEnabled ? "true" : "false",
53
+ OM_AUTO_SPAWN_WORKERS: input.role === "app" && !input.workerEnabled ? "true" : "false"
54
+ };
55
+ if (input.appUrl) {
56
+ variables.APP_URL = input.appUrl;
57
+ variables.NEXT_PUBLIC_APP_URL = input.appUrl;
58
+ }
59
+ delete variables.RAILWAY_API_TOKEN;
60
+ delete variables.RAILWAY_TOKEN;
61
+ assertSafeVariables(variables, {
62
+ railwayToken: input.railwayToken,
63
+ allowedKeys: input.allowedSecretKeys
64
+ });
65
+ return variables;
66
+ }
67
+ function formatVariablePlan(variables, railwayToken) {
68
+ return Object.keys(variables).sort().map((key) => `+ ADD ${key}=${formatVariableValue(key, variables[key] ?? "", railwayToken)}`);
69
+ }
70
+ function writeGeneratedSecrets(envFile, secrets) {
71
+ const ignoreCheck = spawnSync("git", ["check-ignore", "--quiet", envFile], {
72
+ cwd: dirname(envFile),
73
+ stdio: "ignore"
74
+ });
75
+ if (ignoreCheck.status !== 0) {
76
+ throw new Error(`Refusing --write-env because ${envFile} is not ignored by Git.`);
77
+ }
78
+ const existing = readFileSync(envFile, "utf8");
79
+ const missing = Object.entries(secrets).filter(([key]) => !new RegExp(`^${key}=`, "m").test(existing));
80
+ if (missing.length === 0) return;
81
+ const suffix = missing.map(([key, value]) => `${key}=${value}`).join("\n");
82
+ writeFileSync(envFile, `${existing.trimEnd()}
83
+
84
+ # DO NOT COMMIT: generated for Railway deployment
85
+ ${suffix}
86
+ `, {
87
+ mode: 384
88
+ });
89
+ }
90
+ export {
91
+ computeRailwayVariables,
92
+ formatVariablePlan,
93
+ generateProtectedSecrets,
94
+ parseEnvFile,
95
+ resolveEnvFile,
96
+ writeGeneratedSecrets
97
+ };
98
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/lib/deploy/railway/env.ts"],
4
+ "sourcesContent": ["import { randomBytes } from 'node:crypto'\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { assertSafeVariables, formatVariableValue } from './redaction'\n\nexport type RailwayServiceRole = 'app' | 'worker'\n\nexport function resolveEnvFile(cwd: string, requested?: string): string {\n const candidates = requested ? [requested] : ['.env.production', '.env']\n for (const candidate of candidates) {\n const path = resolve(cwd, candidate)\n if (existsSync(path)) return path\n }\n throw new Error(`Environment file not found. Checked: ${candidates.join(', ')}.`)\n}\n\nexport function parseEnvFile(contents: string): Record<string, string> {\n const result: Record<string, string> = {}\n for (const rawLine of contents.split(/\\r?\\n/)) {\n const line = rawLine.trim()\n if (!line || line.startsWith('#')) continue\n const normalized = line.startsWith('export ') ? line.slice(7).trim() : line\n const separator = normalized.indexOf('=')\n if (separator <= 0) continue\n const key = normalized.slice(0, separator).trim()\n let value = normalized.slice(separator + 1).trim()\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1)\n }\n result[key] = value.replace(/\\\\n/g, '\\n')\n }\n return result\n}\n\nexport function generateProtectedSecrets(existing: Record<string, string>): Record<string, string> {\n return {\n AUTH_SECRET: existing.AUTH_SECRET || randomBytes(64).toString('hex'),\n JWT_SECRET: existing.JWT_SECRET || randomBytes(64).toString('hex'),\n TENANT_DATA_ENCRYPTION_FALLBACK_KEY:\n existing.TENANT_DATA_ENCRYPTION_FALLBACK_KEY || randomBytes(32).toString('base64url'),\n }\n}\n\nexport function computeRailwayVariables(input: {\n env: Record<string, string>\n role: RailwayServiceRole\n workerEnabled: boolean\n appUrl?: string\n protectedSecrets: Record<string, string>\n railwayToken?: string\n allowedSecretKeys?: string[]\n}): Record<string, string> {\n const variables: Record<string, string> = {\n ...input.env,\n ...input.protectedSecrets,\n DATABASE_URL: '${{Postgres.DATABASE_URL}}',\n REDIS_URL: '${{Redis.REDIS_URL}}',\n CACHE_STRATEGY: 'redis',\n NODE_ENV: 'production',\n NEXT_TELEMETRY_DISABLED: '1',\n PORT: '3000',\n OM_AI_PROVIDER: input.env.OM_AI_PROVIDER || 'openai',\n OM_AI_MODEL: input.env.OM_AI_MODEL || 'gpt-5-mini',\n QUEUE_STRATEGY: 'async',\n NEXT_PUBLIC_QUEUE_STRATEGY: 'async',\n AUTO_SPAWN_WORKERS: input.role === 'app' && !input.workerEnabled ? 'true' : 'false',\n OM_AUTO_SPAWN_WORKERS: input.role === 'app' && !input.workerEnabled ? 'true' : 'false',\n }\n\n if (input.appUrl) {\n variables.APP_URL = input.appUrl\n variables.NEXT_PUBLIC_APP_URL = input.appUrl\n }\n\n delete variables.RAILWAY_API_TOKEN\n delete variables.RAILWAY_TOKEN\n assertSafeVariables(variables, {\n railwayToken: input.railwayToken,\n allowedKeys: input.allowedSecretKeys,\n })\n return variables\n}\n\nexport function formatVariablePlan(\n variables: Record<string, string>,\n railwayToken?: string,\n): string[] {\n return Object.keys(variables)\n .sort()\n .map((key) => `+ ADD ${key}=${formatVariableValue(key, variables[key] ?? '', railwayToken)}`)\n}\n\nexport function writeGeneratedSecrets(envFile: string, secrets: Record<string, string>): void {\n const ignoreCheck = spawnSync('git', ['check-ignore', '--quiet', envFile], {\n cwd: dirname(envFile),\n stdio: 'ignore',\n })\n if (ignoreCheck.status !== 0) {\n throw new Error(`Refusing --write-env because ${envFile} is not ignored by Git.`)\n }\n const existing = readFileSync(envFile, 'utf8')\n const missing = Object.entries(secrets).filter(([key]) => !new RegExp(`^${key}=`, 'm').test(existing))\n if (missing.length === 0) return\n const suffix = missing.map(([key, value]) => `${key}=${value}`).join('\\n')\n writeFileSync(envFile, `${existing.trimEnd()}\\n\\n# DO NOT COMMIT: generated for Railway deployment\\n${suffix}\\n`, {\n mode: 0o600,\n })\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,99 @@
1
+ import {
2
+ collectSensitiveStructuredValues,
3
+ redactStructuredValue,
4
+ redactText
5
+ } from "./redaction.js";
6
+ class RailwayGraphqlError extends Error {
7
+ constructor(message, status, code) {
8
+ super(message);
9
+ this.status = status;
10
+ this.code = code;
11
+ this.name = "RailwayGraphqlError";
12
+ }
13
+ }
14
+ function shouldRetry(error) {
15
+ return error instanceof RailwayGraphqlError && (error.status !== void 0 && error.status >= 500 || error.code === "INTERNAL_ERROR");
16
+ }
17
+ function wait(milliseconds) {
18
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
19
+ }
20
+ function createRailwayGraphqlClient(input) {
21
+ const endpoint = input.endpoint ?? "https://backboard.railway.com/graphql/v2";
22
+ if (!endpoint.startsWith("https://")) {
23
+ throw new Error("Railway GraphQL endpoint must use HTTPS.");
24
+ }
25
+ const fetchImpl = input.fetchImpl ?? fetch;
26
+ const logger = input.logger ?? console;
27
+ async function execute(operation, variables) {
28
+ const requestSecrets = [input.token, ...collectSensitiveStructuredValues(variables)];
29
+ if (input.verbose) {
30
+ logger.log(
31
+ `[railway:gql] ${operation.name} ${JSON.stringify(redactStructuredValue(variables, requestSecrets))}`
32
+ );
33
+ }
34
+ const response = await fetchImpl(endpoint, {
35
+ method: "POST",
36
+ headers: {
37
+ Authorization: `Bearer ${input.token}`,
38
+ "Content-Type": "application/json"
39
+ },
40
+ body: JSON.stringify({
41
+ operationName: operation.name,
42
+ query: operation.query,
43
+ variables
44
+ }),
45
+ signal: AbortSignal.timeout(operation.mutation ? 3e4 : 1e4)
46
+ });
47
+ const bodyText = await response.text();
48
+ let body;
49
+ try {
50
+ body = JSON.parse(bodyText);
51
+ } catch {
52
+ throw new RailwayGraphqlError(
53
+ `Railway returned a non-JSON response (${response.status}).`,
54
+ response.status
55
+ );
56
+ }
57
+ const firstError = body.errors?.[0];
58
+ if (!response.ok || firstError) {
59
+ throw new RailwayGraphqlError(
60
+ redactText(
61
+ firstError?.message || `Railway request failed (${response.status}).`,
62
+ requestSecrets
63
+ ),
64
+ response.status,
65
+ firstError?.extensions?.code
66
+ );
67
+ }
68
+ if (body.data === void 0) {
69
+ throw new RailwayGraphqlError("Railway response did not include data.", response.status);
70
+ }
71
+ if (input.verbose) {
72
+ logger.log(
73
+ `[railway:gql] ${operation.name} response ${JSON.stringify(redactStructuredValue(body.data, requestSecrets))}`
74
+ );
75
+ }
76
+ return body.data;
77
+ }
78
+ return {
79
+ async request(operation, variables = {}) {
80
+ const attempts = operation.mutation ? 1 : 2;
81
+ let lastError;
82
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
83
+ try {
84
+ return await execute(operation, variables);
85
+ } catch (error) {
86
+ lastError = error;
87
+ if (attempt + 1 >= attempts || !shouldRetry(error)) throw error;
88
+ await wait(250 * 2 ** attempt);
89
+ }
90
+ }
91
+ throw lastError;
92
+ }
93
+ };
94
+ }
95
+ export {
96
+ RailwayGraphqlError,
97
+ createRailwayGraphqlClient
98
+ };
99
+ //# sourceMappingURL=graphql-client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/lib/deploy/railway/graphql-client.ts"],
4
+ "sourcesContent": ["import {\n collectSensitiveStructuredValues,\n redactStructuredValue,\n redactText,\n} from './redaction'\nimport type { RailwayGraphqlClient, RailwayGraphqlOperation } from './types'\n\ntype FetchLike = typeof fetch\n\ntype GraphqlResponse<T> = {\n data?: T\n errors?: Array<{\n message?: string\n extensions?: { code?: string }\n }>\n}\n\nexport class RailwayGraphqlError extends Error {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly code?: string,\n ) {\n super(message)\n this.name = 'RailwayGraphqlError'\n }\n}\n\nfunction shouldRetry(error: unknown): boolean {\n return (\n error instanceof RailwayGraphqlError &&\n ((error.status !== undefined && error.status >= 500) || error.code === 'INTERNAL_ERROR')\n )\n}\n\nfunction wait(milliseconds: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, milliseconds))\n}\n\nexport function createRailwayGraphqlClient(input: {\n token: string\n verbose?: boolean\n endpoint?: string\n fetchImpl?: FetchLike\n logger?: Pick<Console, 'log'>\n}): RailwayGraphqlClient {\n const endpoint = input.endpoint ?? 'https://backboard.railway.com/graphql/v2'\n if (!endpoint.startsWith('https://')) {\n throw new Error('Railway GraphQL endpoint must use HTTPS.')\n }\n const fetchImpl = input.fetchImpl ?? fetch\n const logger = input.logger ?? console\n\n async function execute<T>(\n operation: RailwayGraphqlOperation,\n variables: Record<string, unknown>,\n ): Promise<T> {\n const requestSecrets = [input.token, ...collectSensitiveStructuredValues(variables)]\n if (input.verbose) {\n logger.log(\n `[railway:gql] ${operation.name} ${JSON.stringify(redactStructuredValue(variables, requestSecrets))}`,\n )\n }\n const response = await fetchImpl(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${input.token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n operationName: operation.name,\n query: operation.query,\n variables,\n }),\n signal: AbortSignal.timeout(operation.mutation ? 30_000 : 10_000),\n })\n const bodyText = await response.text()\n let body: GraphqlResponse<T>\n try {\n body = JSON.parse(bodyText) as GraphqlResponse<T>\n } catch {\n throw new RailwayGraphqlError(\n `Railway returned a non-JSON response (${response.status}).`,\n response.status,\n )\n }\n const firstError = body.errors?.[0]\n if (!response.ok || firstError) {\n throw new RailwayGraphqlError(\n redactText(\n firstError?.message || `Railway request failed (${response.status}).`,\n requestSecrets,\n ),\n response.status,\n firstError?.extensions?.code,\n )\n }\n if (body.data === undefined) {\n throw new RailwayGraphqlError('Railway response did not include data.', response.status)\n }\n if (input.verbose) {\n logger.log(\n `[railway:gql] ${operation.name} response ${JSON.stringify(redactStructuredValue(body.data, requestSecrets))}`,\n )\n }\n return body.data\n }\n\n return {\n async request<T>(\n operation: RailwayGraphqlOperation,\n variables: Record<string, unknown> = {},\n ): Promise<T> {\n const attempts = operation.mutation ? 1 : 2\n let lastError: unknown\n for (let attempt = 0; attempt < attempts; attempt += 1) {\n try {\n return await execute<T>(operation, variables)\n } catch (error) {\n lastError = error\n if (attempt + 1 >= attempts || !shouldRetry(error)) throw error\n await wait(250 * (2 ** attempt))\n }\n }\n throw lastError\n },\n }\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAaA,MAAM,4BAA4B,MAAM;AAAA,EAC7C,YACE,SACgB,QACA,MAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAEA,SAAS,YAAY,OAAyB;AAC5C,SACE,iBAAiB,wBACf,MAAM,WAAW,UAAa,MAAM,UAAU,OAAQ,MAAM,SAAS;AAE3E;AAEA,SAAS,KAAK,cAAqC;AACjD,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,YAAY,CAAC;AACnE;AAEO,SAAS,2BAA2B,OAMlB;AACvB,QAAM,WAAW,MAAM,YAAY;AACnC,MAAI,CAAC,SAAS,WAAW,UAAU,GAAG;AACpC,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,QAAM,YAAY,MAAM,aAAa;AACrC,QAAM,SAAS,MAAM,UAAU;AAE/B,iBAAe,QACb,WACA,WACY;AACZ,UAAM,iBAAiB,CAAC,MAAM,OAAO,GAAG,iCAAiC,SAAS,CAAC;AACnF,QAAI,MAAM,SAAS;AACjB,aAAO;AAAA,QACL,iBAAiB,UAAU,IAAI,IAAI,KAAK,UAAU,sBAAsB,WAAW,cAAc,CAAC,CAAC;AAAA,MACrG;AAAA,IACF;AACA,UAAM,WAAW,MAAM,UAAU,UAAU;AAAA,MACzC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,MAAM,KAAK;AAAA,QACpC,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,eAAe,UAAU;AAAA,QACzB,OAAO,UAAU;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,MACD,QAAQ,YAAY,QAAQ,UAAU,WAAW,MAAS,GAAM;AAAA,IAClE,CAAC;AACD,UAAM,WAAW,MAAM,SAAS,KAAK;AACrC,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,MAAM,QAAQ;AAAA,IAC5B,QAAQ;AACN,YAAM,IAAI;AAAA,QACR,yCAAyC,SAAS,MAAM;AAAA,QACxD,SAAS;AAAA,MACX;AAAA,IACF;AACA,UAAM,aAAa,KAAK,SAAS,CAAC;AAClC,QAAI,CAAC,SAAS,MAAM,YAAY;AAC9B,YAAM,IAAI;AAAA,QACR;AAAA,UACE,YAAY,WAAW,2BAA2B,SAAS,MAAM;AAAA,UACjE;AAAA,QACF;AAAA,QACA,SAAS;AAAA,QACT,YAAY,YAAY;AAAA,MAC1B;AAAA,IACF;AACA,QAAI,KAAK,SAAS,QAAW;AAC3B,YAAM,IAAI,oBAAoB,0CAA0C,SAAS,MAAM;AAAA,IACzF;AACA,QAAI,MAAM,SAAS;AACjB,aAAO;AAAA,QACL,iBAAiB,UAAU,IAAI,aAAa,KAAK,UAAU,sBAAsB,KAAK,MAAM,cAAc,CAAC,CAAC;AAAA,MAC9G;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAEA,SAAO;AAAA,IACL,MAAM,QACJ,WACA,YAAqC,CAAC,GAC1B;AACZ,YAAM,WAAW,UAAU,WAAW,IAAI;AAC1C,UAAI;AACJ,eAAS,UAAU,GAAG,UAAU,UAAU,WAAW,GAAG;AACtD,YAAI;AACF,iBAAO,MAAM,QAAW,WAAW,SAAS;AAAA,QAC9C,SAAS,OAAO;AACd,sBAAY;AACZ,cAAI,UAAU,KAAK,YAAY,CAAC,YAAY,KAAK,EAAG,OAAM;AAC1D,gBAAM,KAAK,MAAO,KAAK,OAAQ;AAAA,QACjC;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }