@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.6.4-develop.4382.1.6b4f656b77",
3
+ "version": "0.6.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -55,25 +55,25 @@
55
55
  "node": "24.x"
56
56
  },
57
57
  "dependencies": {
58
- "@mikro-orm/core": "^7.1.3",
59
- "@mikro-orm/decorators": "^7.1.3",
60
- "@mikro-orm/migrations": "^7.1.3",
61
- "@mikro-orm/postgresql": "^7.1.3",
62
- "@open-mercato/queue": "0.6.4-develop.4382.1.6b4f656b77",
63
- "@open-mercato/shared": "0.6.4-develop.4382.1.6b4f656b77",
58
+ "@mikro-orm/core": "^7.1.4",
59
+ "@mikro-orm/decorators": "^7.1.4",
60
+ "@mikro-orm/migrations": "^7.1.4",
61
+ "@mikro-orm/postgresql": "^7.1.4",
62
+ "@open-mercato/queue": "0.6.4",
63
+ "@open-mercato/shared": "0.6.4",
64
64
  "cross-spawn": "^7.0.6",
65
65
  "pg": "8.21.0",
66
- "semver": "^7.8.1",
66
+ "semver": "^7.8.2",
67
67
  "testcontainers": "^12.0.1",
68
68
  "ts-morph": "^28.0.0",
69
69
  "tsx": "^4.22.4",
70
70
  "typescript": "^6.0.3"
71
71
  },
72
72
  "peerDependencies": {
73
- "@open-mercato/shared": "0.6.4-develop.4382.1.6b4f656b77"
73
+ "@open-mercato/shared": "0.6.4"
74
74
  },
75
75
  "devDependencies": {
76
- "@open-mercato/shared": "0.6.4-develop.4382.1.6b4f656b77",
76
+ "@open-mercato/shared": "0.6.4",
77
77
  "@types/jest": "^30.0.0",
78
78
  "jest": "^30.4.2",
79
79
  "ts-jest": "^29.4.11"
@@ -85,6 +85,5 @@
85
85
  "type": "git",
86
86
  "url": "https://github.com/open-mercato/open-mercato",
87
87
  "directory": "packages/cli"
88
- },
89
- "stableVersion": "0.6.3"
88
+ }
90
89
  }
package/src/bin.ts CHANGED
@@ -14,6 +14,7 @@ import { run } from './mercato.js'
14
14
  const BOOTSTRAP_FREE_COMMANDS = [
15
15
  'generate',
16
16
  'module',
17
+ 'deploy',
17
18
  'db',
18
19
  'init',
19
20
  'agentic:init',
@@ -237,7 +237,7 @@ test.describe('TC-INT-008: CLI agentic init mirrors standalone scaffolding asset
237
237
  expect(cursorRulesSource).toContain('sample-store')
238
238
 
239
239
  const specWritingSkillSource = fs.readFileSync(
240
- path.join(appDir, '.ai', 'skills', 'spec-writing', 'SKILL.md'),
240
+ path.join(appDir, '.ai', 'skills', 'om-spec-writing', 'SKILL.md'),
241
241
  'utf8',
242
242
  )
243
243
  expect(specWritingSkillSource).toContain('sample-store')
@@ -69,127 +69,128 @@ function generateShared(config: AgenticConfig): void {
69
69
  // .ai/skills/
70
70
  writeTemplate(
71
71
  srcDir,
72
- 'ai/skills/spec-writing/SKILL.md',
73
- join(targetDir, '.ai', 'skills', 'spec-writing', 'SKILL.md'),
72
+ 'ai/skills/om-spec-writing/SKILL.md',
73
+ join(targetDir, '.ai', 'skills', 'om-spec-writing', 'SKILL.md'),
74
74
  config,
75
75
  )
76
76
  copyFile(
77
77
  srcDir,
78
- 'ai/skills/spec-writing/references/spec-template.md',
79
- join(targetDir, '.ai', 'skills', 'spec-writing', 'references', 'spec-template.md'),
78
+ 'ai/skills/om-spec-writing/references/spec-template.md',
79
+ join(targetDir, '.ai', 'skills', 'om-spec-writing', 'references', 'spec-template.md'),
80
80
  )
81
81
  copyFile(
82
82
  srcDir,
83
- 'ai/skills/spec-writing/references/spec-checklist.md',
84
- join(targetDir, '.ai', 'skills', 'spec-writing', 'references', 'spec-checklist.md'),
83
+ 'ai/skills/om-spec-writing/references/spec-checklist.md',
84
+ join(targetDir, '.ai', 'skills', 'om-spec-writing', 'references', 'spec-checklist.md'),
85
85
  )
86
86
  copyFile(
87
87
  srcDir,
88
- 'ai/skills/backend-ui-design/SKILL.md',
89
- join(targetDir, '.ai', 'skills', 'backend-ui-design', 'SKILL.md'),
88
+ 'ai/skills/om-backend-ui-design/SKILL.md',
89
+ join(targetDir, '.ai', 'skills', 'om-backend-ui-design', 'SKILL.md'),
90
90
  )
91
91
  copyFile(
92
92
  srcDir,
93
- 'ai/skills/backend-ui-design/references/ui-components.md',
94
- join(targetDir, '.ai', 'skills', 'backend-ui-design', 'references', 'ui-components.md'),
93
+ 'ai/skills/om-backend-ui-design/references/ui-components.md',
94
+ join(targetDir, '.ai', 'skills', 'om-backend-ui-design', 'references', 'ui-components.md'),
95
95
  )
96
96
  copyFile(
97
97
  srcDir,
98
- 'ai/skills/code-review/SKILL.md',
99
- join(targetDir, '.ai', 'skills', 'code-review', 'SKILL.md'),
98
+ 'ai/skills/om-code-review/SKILL.md',
99
+ join(targetDir, '.ai', 'skills', 'om-code-review', 'SKILL.md'),
100
100
  )
101
101
  copyFile(
102
102
  srcDir,
103
- 'ai/skills/code-review/references/review-checklist.md',
104
- join(targetDir, '.ai', 'skills', 'code-review', 'references', 'review-checklist.md'),
103
+ 'ai/skills/om-code-review/references/review-checklist.md',
104
+ join(targetDir, '.ai', 'skills', 'om-code-review', 'references', 'review-checklist.md'),
105
105
  )
106
- copyFile(srcDir, 'ai/skills/integration-builder/SKILL.md', join(targetDir, '.ai', 'skills', 'integration-builder', 'SKILL.md'))
106
+ copyFile(srcDir, 'ai/skills/om-integration-builder/SKILL.md', join(targetDir, '.ai', 'skills', 'om-integration-builder', 'SKILL.md'))
107
107
  copyFile(
108
108
  srcDir,
109
- 'ai/skills/integration-builder/references/adapter-contracts.md',
110
- join(targetDir, '.ai', 'skills', 'integration-builder', 'references', 'adapter-contracts.md'),
109
+ 'ai/skills/om-integration-builder/references/adapter-contracts.md',
110
+ join(targetDir, '.ai', 'skills', 'om-integration-builder', 'references', 'adapter-contracts.md'),
111
111
  )
112
112
 
113
113
  copyFile(
114
114
  srcDir,
115
- 'ai/skills/system-extension/SKILL.md',
116
- join(targetDir, '.ai', 'skills', 'system-extension', 'SKILL.md'),
115
+ 'ai/skills/om-system-extension/SKILL.md',
116
+ join(targetDir, '.ai', 'skills', 'om-system-extension', 'SKILL.md'),
117
117
  )
118
118
  copyFile(
119
119
  srcDir,
120
- 'ai/skills/system-extension/references/extension-contracts.md',
121
- join(targetDir, '.ai', 'skills', 'system-extension', 'references', 'extension-contracts.md'),
120
+ 'ai/skills/om-system-extension/references/extension-contracts.md',
121
+ join(targetDir, '.ai', 'skills', 'om-system-extension', 'references', 'extension-contracts.md'),
122
122
  )
123
123
 
124
124
  copyFile(
125
125
  srcDir,
126
- 'ai/skills/module-scaffold/SKILL.md',
127
- join(targetDir, '.ai', 'skills', 'module-scaffold', 'SKILL.md'),
126
+ 'ai/skills/om-module-scaffold/SKILL.md',
127
+ join(targetDir, '.ai', 'skills', 'om-module-scaffold', 'SKILL.md'),
128
128
  )
129
129
  copyFile(
130
130
  srcDir,
131
- 'ai/skills/module-scaffold/references/naming-conventions.md',
132
- join(targetDir, '.ai', 'skills', 'module-scaffold', 'references', 'naming-conventions.md'),
131
+ 'ai/skills/om-module-scaffold/references/naming-conventions.md',
132
+ join(targetDir, '.ai', 'skills', 'om-module-scaffold', 'references', 'naming-conventions.md'),
133
133
  )
134
134
  copyFile(
135
135
  srcDir,
136
- 'ai/skills/module-scaffold/references/navigation-patterns.md',
137
- join(targetDir, '.ai', 'skills', 'module-scaffold', 'references', 'navigation-patterns.md'),
136
+ 'ai/skills/om-module-scaffold/references/navigation-patterns.md',
137
+ join(targetDir, '.ai', 'skills', 'om-module-scaffold', 'references', 'navigation-patterns.md'),
138
138
  )
139
139
 
140
140
  copyFile(
141
141
  srcDir,
142
- 'ai/skills/troubleshooter/SKILL.md',
143
- join(targetDir, '.ai', 'skills', 'troubleshooter', 'SKILL.md'),
142
+ 'ai/skills/om-troubleshooter/SKILL.md',
143
+ join(targetDir, '.ai', 'skills', 'om-troubleshooter', 'SKILL.md'),
144
144
  )
145
145
  copyFile(
146
146
  srcDir,
147
- 'ai/skills/troubleshooter/references/diagnostic-commands.md',
148
- join(targetDir, '.ai', 'skills', 'troubleshooter', 'references', 'diagnostic-commands.md'),
147
+ 'ai/skills/om-troubleshooter/references/diagnostic-commands.md',
148
+ join(targetDir, '.ai', 'skills', 'om-troubleshooter', 'references', 'diagnostic-commands.md'),
149
149
  )
150
150
 
151
151
  copyFile(
152
152
  srcDir,
153
- 'ai/skills/eject-and-customize/SKILL.md',
154
- join(targetDir, '.ai', 'skills', 'eject-and-customize', 'SKILL.md'),
153
+ 'ai/skills/om-eject-and-customize/SKILL.md',
154
+ join(targetDir, '.ai', 'skills', 'om-eject-and-customize', 'SKILL.md'),
155
155
  )
156
156
 
157
157
  copyFile(
158
158
  srcDir,
159
- 'ai/skills/data-model-design/SKILL.md',
160
- join(targetDir, '.ai', 'skills', 'data-model-design', 'SKILL.md'),
159
+ 'ai/skills/om-data-model-design/SKILL.md',
160
+ join(targetDir, '.ai', 'skills', 'om-data-model-design', 'SKILL.md'),
161
161
  )
162
162
  copyFile(
163
163
  srcDir,
164
- 'ai/skills/data-model-design/references/mikro-orm-cheatsheet.md',
165
- join(targetDir, '.ai', 'skills', 'data-model-design', 'references', 'mikro-orm-cheatsheet.md'),
164
+ 'ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md',
165
+ join(targetDir, '.ai', 'skills', 'om-data-model-design', 'references', 'mikro-orm-cheatsheet.md'),
166
166
  )
167
167
 
168
168
  copyFile(
169
169
  srcDir,
170
- 'ai/skills/implement-spec/SKILL.md',
171
- join(targetDir, '.ai', 'skills', 'implement-spec', 'SKILL.md'),
170
+ 'ai/skills/om-implement-spec/SKILL.md',
171
+ join(targetDir, '.ai', 'skills', 'om-implement-spec', 'SKILL.md'),
172
172
  )
173
173
 
174
174
  copyFile(
175
175
  srcDir,
176
- 'ai/skills/integration-tests/SKILL.md',
177
- join(targetDir, '.ai', 'skills', 'integration-tests', 'SKILL.md'),
176
+ 'ai/skills/om-integration-tests/SKILL.md',
177
+ join(targetDir, '.ai', 'skills', 'om-integration-tests', 'SKILL.md'),
178
178
  )
179
179
 
180
180
  copyFile(
181
181
  srcDir,
182
- 'ai/skills/auto-upgrade-0.4.10-to-0.5.0/SKILL.md',
183
- join(targetDir, '.ai', 'skills', 'auto-upgrade-0.4.10-to-0.5.0', 'SKILL.md'),
182
+ 'ai/skills/om-auto-upgrade-0.4.10-to-0.5.0/SKILL.md',
183
+ join(targetDir, '.ai', 'skills', 'om-auto-upgrade-0.4.10-to-0.5.0', 'SKILL.md'),
184
184
  )
185
185
 
186
186
  for (const autoSkill of [
187
- 'auto-create-pr',
188
- 'auto-continue-pr',
189
- 'auto-create-pr-loop',
190
- 'auto-continue-pr-loop',
191
- 'auto-review-pr',
192
- 'auto-fix-github',
187
+ 'om-auto-create-pr',
188
+ 'om-auto-continue-pr',
189
+ 'om-auto-create-pr-loop',
190
+ 'om-auto-continue-pr-loop',
191
+ 'om-auto-review-pr',
192
+ 'om-auto-fix-github',
193
+ 'om-prepare-issue',
193
194
  ]) {
194
195
  copyFile(
195
196
  srcDir,
@@ -207,8 +208,8 @@ function generateShared(config: AgenticConfig): void {
207
208
 
208
209
  copyFile(
209
210
  srcDir,
210
- 'ai/skills/trim-unused-modules/SKILL.md',
211
- join(targetDir, '.ai', 'skills', 'trim-unused-modules', 'SKILL.md'),
211
+ 'ai/skills/om-trim-unused-modules/SKILL.md',
212
+ join(targetDir, '.ai', 'skills', 'om-trim-unused-modules', 'SKILL.md'),
212
213
  )
213
214
 
214
215
  copyFile(srcDir, 'ai/qa/tests/playwright.config.ts', join(targetDir, '.ai', 'qa', 'tests', 'playwright.config.ts'))
@@ -0,0 +1,3 @@
1
+ export const integrationMeta = {
2
+ requiredEnvVars: ['RAILWAY_INTEGRATION_TOKEN', 'RAILWAY_INTEGRATION_APP_ROOT'],
3
+ }
@@ -0,0 +1,71 @@
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
+
6
+ const token = process.env.RAILWAY_INTEGRATION_TOKEN
7
+ const appRoot = process.env.RAILWAY_INTEGRATION_APP_ROOT
8
+ const cliBin = resolve(process.cwd(), 'packages/cli/dist/bin.js')
9
+
10
+ function runCli(args: string[]): string {
11
+ if (!appRoot || !token) throw new Error('Railway integration environment is incomplete.')
12
+ return execFileSync(process.execPath, [cliBin, ...args], {
13
+ cwd: appRoot,
14
+ encoding: 'utf8',
15
+ env: {
16
+ ...process.env,
17
+ RAILWAY_API_TOKEN: token,
18
+ CI: 'true',
19
+ },
20
+ timeout: 15 * 60 * 1000,
21
+ })
22
+ }
23
+
24
+ test.describe('mercato deploy railway live integration', () => {
25
+ test.skip(!token || !appRoot, 'RAILWAY_INTEGRATION_TOKEN and RAILWAY_INTEGRATION_APP_ROOT are required')
26
+
27
+ test('deploys, verifies health, resumes, and cleans up', async ({ request }) => {
28
+ expect(existsSync(cliBin)).toBe(true)
29
+ const projectName = `mercato-integration-${Date.now()}`
30
+
31
+ try {
32
+ const output = runCli([
33
+ 'deploy',
34
+ 'railway',
35
+ '--project',
36
+ projectName,
37
+ '--source',
38
+ 'local',
39
+ '--no-worker',
40
+ '--non-interactive',
41
+ ])
42
+ const outputLines = output.trim().split(/\r?\n/)
43
+ const deployUrl = outputLines
44
+ .find((line) => line.startsWith('DEPLOY_URL='))
45
+ ?.slice('DEPLOY_URL='.length)
46
+ expect(deployUrl).toBeTruthy()
47
+ expect(outputLines.at(-1)).toBe(`DEPLOY_URL=${deployUrl}`)
48
+
49
+ const health = await request.get(`${deployUrl}/api/healthz`)
50
+ expect(health.status()).toBe(200)
51
+ expect(await health.json()).toEqual({
52
+ status: 'ok',
53
+ ts: expect.any(String),
54
+ })
55
+
56
+ const resumeOutput = runCli([
57
+ 'deploy',
58
+ 'railway',
59
+ '--source',
60
+ 'local',
61
+ '--no-worker',
62
+ '--non-interactive',
63
+ ])
64
+ expect(resumeOutput).toContain(`Project: https://railway.com/project/`)
65
+ } finally {
66
+ if (appRoot && existsSync(resolve(appRoot, '.mercato', 'railway.json'))) {
67
+ runCli(['deploy', 'railway', '--cleanup', '--non-interactive', '--yes'])
68
+ }
69
+ }
70
+ })
71
+ })
@@ -0,0 +1,61 @@
1
+ import { existsSync, mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+ import { runRailwayDeploy } from '../index'
5
+
6
+ describe('mercato deploy railway --dry-run', () => {
7
+ it('prints a deterministic local-source plan without a token or state mutation', async () => {
8
+ const cwd = mkdtempSync(join(tmpdir(), 'mercato-railway-dry-run-'))
9
+ mkdirSync(join(cwd, 'node_modules'), { recursive: true })
10
+ writeFileSync(join(cwd, 'package.json'), JSON.stringify({
11
+ name: 'My Shop',
12
+ dependencies: { '@open-mercato/core': '0.6.4' },
13
+ }))
14
+ writeFileSync(join(cwd, '.env'), 'CUSTOM_SETTING=enabled\n')
15
+ writeFileSync(
16
+ join(cwd, '.railwayignore'),
17
+ '.env*\n*.pem\n*.key\nid_*\n.git\n.railway\nnode_modules\n.yarn/cache\n.next\n.turbo\n*.db\n*.sqlite\n*.sqlite3\n.mercato/railway.json*\n',
18
+ )
19
+ const log = jest.spyOn(console, 'log').mockImplementation(() => undefined)
20
+
21
+ await runRailwayDeploy(['--dry-run', '--source', 'local'], {
22
+ cwd,
23
+ resolveSource: () => ({ mode: 'local', reason: 'test local source' }),
24
+ })
25
+
26
+ const output = log.mock.calls.map((call) => call.join(' ')).join('\n')
27
+ expect(output).toContain('Railway deployment plan (dry run)')
28
+ expect(output).toContain('Project: my-shop')
29
+ expect(output).toContain('Source: local')
30
+ expect(output).toContain('AUTH_SECRET=<redacted>')
31
+ expect(output).not.toContain('generated-at-deploy')
32
+ log.mockRestore()
33
+ })
34
+
35
+ it('prints a cleanup plan without deleting tracked state', async () => {
36
+ const cwd = mkdtempSync(join(tmpdir(), 'mercato-railway-cleanup-dry-run-'))
37
+ mkdirSync(join(cwd, '.mercato'), { recursive: true })
38
+ writeFileSync(join(cwd, 'package.json'), JSON.stringify({
39
+ name: 'My Shop',
40
+ dependencies: { '@open-mercato/core': '0.6.4' },
41
+ }))
42
+ const statePath = join(cwd, '.mercato', 'railway.json')
43
+ writeFileSync(statePath, JSON.stringify({
44
+ schemaVersion: 1,
45
+ provider: 'railway',
46
+ projectId: 'project-1',
47
+ projectName: 'my-shop',
48
+ environments: {},
49
+ writtenBy: { cliVersion: 'test' },
50
+ }))
51
+ const log = jest.spyOn(console, 'log').mockImplementation(() => undefined)
52
+
53
+ await runRailwayDeploy(['--cleanup', '--dry-run'], { cwd })
54
+
55
+ const output = log.mock.calls.map((call) => call.join(' ')).join('\n')
56
+ expect(output).toContain('Railway cleanup plan (dry run)')
57
+ expect(output).toContain('projectDelete')
58
+ expect(existsSync(statePath)).toBe(true)
59
+ log.mockRestore()
60
+ })
61
+ })
@@ -0,0 +1,55 @@
1
+ import {
2
+ computeRailwayVariables,
3
+ generateProtectedSecrets,
4
+ parseEnvFile,
5
+ } from '../env'
6
+
7
+ describe('Railway environment variables', () => {
8
+ it('parses common dotenv syntax', () => {
9
+ expect(parseEnvFile(`
10
+ # comment
11
+ PLAIN=value
12
+ export QUOTED="hello world"
13
+ SINGLE='x'
14
+ `)).toEqual({
15
+ PLAIN: 'value',
16
+ QUOTED: 'hello world',
17
+ SINGLE: 'x',
18
+ })
19
+ })
20
+
21
+ it('generates missing protected secrets and preserves existing ones', () => {
22
+ const secrets = generateProtectedSecrets({ AUTH_SECRET: 'existing' })
23
+ expect(secrets.AUTH_SECRET).toBe('existing')
24
+ expect(secrets.JWT_SECRET).toHaveLength(128)
25
+ expect(secrets.TENANT_DATA_ENCRYPTION_FALLBACK_KEY.length).toBeGreaterThan(30)
26
+ })
27
+
28
+ it('computes app and worker settings without leaking Railway tokens', () => {
29
+ const variables = computeRailwayVariables({
30
+ env: {
31
+ RAILWAY_API_TOKEN: 'never-upload',
32
+ CUSTOM_SETTING: 'enabled',
33
+ },
34
+ role: 'app',
35
+ workerEnabled: true,
36
+ appUrl: 'https://example.up.railway.app',
37
+ protectedSecrets: {
38
+ AUTH_SECRET: 'auth',
39
+ JWT_SECRET: 'jwt',
40
+ TENANT_DATA_ENCRYPTION_FALLBACK_KEY: 'encryption',
41
+ },
42
+ })
43
+ expect(variables).toMatchObject({
44
+ DATABASE_URL: '${{Postgres.DATABASE_URL}}',
45
+ REDIS_URL: '${{Redis.REDIS_URL}}',
46
+ CACHE_STRATEGY: 'redis',
47
+ QUEUE_STRATEGY: 'async',
48
+ AUTO_SPAWN_WORKERS: 'false',
49
+ PORT: '3000',
50
+ APP_URL: 'https://example.up.railway.app',
51
+ CUSTOM_SETTING: 'enabled',
52
+ })
53
+ expect(variables.RAILWAY_API_TOKEN).toBeUndefined()
54
+ })
55
+ })
@@ -0,0 +1,80 @@
1
+ import {
2
+ createRailwayGraphqlClient,
3
+ RailwayGraphqlError,
4
+ } from '../graphql-client'
5
+
6
+ const query = { name: 'TestQuery', query: 'query TestQuery { me { id } }' }
7
+ const mutation = { ...query, name: 'TestMutation', mutation: true }
8
+
9
+ function response(body: unknown, status = 200): Response {
10
+ return new Response(JSON.stringify(body), { status })
11
+ }
12
+
13
+ describe('Railway GraphQL client', () => {
14
+ it('sends operationName, query, variables, and bearer auth', async () => {
15
+ const fetchImpl = jest.fn(async () => response({ data: { me: { id: 'user' } } }))
16
+ const client = createRailwayGraphqlClient({
17
+ token: 'account-token',
18
+ fetchImpl: fetchImpl as typeof fetch,
19
+ })
20
+
21
+ await expect(client.request(query, { input: 'value' })).resolves.toEqual({ me: { id: 'user' } })
22
+ expect(fetchImpl).toHaveBeenCalledWith(
23
+ 'https://backboard.railway.com/graphql/v2',
24
+ expect.objectContaining({
25
+ method: 'POST',
26
+ headers: expect.objectContaining({ Authorization: 'Bearer account-token' }),
27
+ body: JSON.stringify({
28
+ operationName: 'TestQuery',
29
+ query: query.query,
30
+ variables: { input: 'value' },
31
+ }),
32
+ }),
33
+ )
34
+ })
35
+
36
+ it('retries idempotent reads once on a server error', async () => {
37
+ const fetchImpl = jest.fn()
38
+ .mockResolvedValueOnce(response({ errors: [{ message: 'temporary' }] }, 500))
39
+ .mockResolvedValueOnce(response({ data: { ok: true } }))
40
+ const client = createRailwayGraphqlClient({
41
+ token: 'token',
42
+ fetchImpl: fetchImpl as typeof fetch,
43
+ })
44
+ await expect(client.request(query)).resolves.toEqual({ ok: true })
45
+ expect(fetchImpl).toHaveBeenCalledTimes(2)
46
+ })
47
+
48
+ it('never blindly retries mutations', async () => {
49
+ const fetchImpl = jest.fn(async () => response({ errors: [{ message: 'ambiguous' }] }, 500))
50
+ const client = createRailwayGraphqlClient({
51
+ token: 'token',
52
+ fetchImpl: fetchImpl as typeof fetch,
53
+ })
54
+ await expect(client.request(mutation)).rejects.toBeInstanceOf(RailwayGraphqlError)
55
+ expect(fetchImpl).toHaveBeenCalledTimes(1)
56
+ })
57
+
58
+ it('redacts sensitive request and response fields in verbose logs', async () => {
59
+ const logger = { log: jest.fn() }
60
+ const fetchImpl = jest.fn(async () => response({
61
+ data: { variables: { AUTH_SECRET: 'response-secret', SAFE_VALUE: 'visible' } },
62
+ }))
63
+ const client = createRailwayGraphqlClient({
64
+ token: 'account-token',
65
+ verbose: true,
66
+ fetchImpl: fetchImpl as typeof fetch,
67
+ logger,
68
+ })
69
+
70
+ await client.request(query, {
71
+ input: { JWT_SECRET: 'request-secret', SAFE_VALUE: 'visible' },
72
+ })
73
+
74
+ const output = logger.log.mock.calls.flat().join('\n')
75
+ expect(output).not.toContain('request-secret')
76
+ expect(output).not.toContain('response-secret')
77
+ expect(output).toContain('<redacted>')
78
+ expect(output).toContain('visible')
79
+ })
80
+ })
@@ -0,0 +1,21 @@
1
+ import { createHash } from 'node:crypto'
2
+ import {
3
+ railwayOperations,
4
+ railwaySchemaFingerprintSource,
5
+ } from '../operations'
6
+
7
+ describe('Railway GraphQL operation contract', () => {
8
+ it('matches the verified operation fingerprint', () => {
9
+ const fingerprint = createHash('sha256')
10
+ .update(railwaySchemaFingerprintSource())
11
+ .digest('hex')
12
+ expect(fingerprint).toBe('01e3c5feaa163ebe626a1cb9ae5f22b5e7ea316162fed9353375f34c25ebc522')
13
+ })
14
+
15
+ it('uses template-backed databases and singular service variable upserts', () => {
16
+ const source = railwaySchemaFingerprintSource()
17
+ expect(source).toContain('templateDeployV2')
18
+ expect(source).not.toContain('pluginCreate')
19
+ expect(railwayOperations.variableUpsert.query).toContain('VariableCollectionUpsertInput')
20
+ })
21
+ })
@@ -0,0 +1,78 @@
1
+ import { parseRailwayDeployOptions, redactRailwayCliArgs } from '../options'
2
+
3
+ describe('parseRailwayDeployOptions', () => {
4
+ it('parses the complete command surface', () => {
5
+ expect(parseRailwayDeployOptions([
6
+ '--project=my-shop',
7
+ '--env', 'staging',
8
+ '--service', 'web',
9
+ '--no-worker',
10
+ '--source', 'local',
11
+ '--region', 'europe-west4',
12
+ '--env-file', '.env.railway',
13
+ '--domain', 'shop.example.com',
14
+ '--no-wait-domain',
15
+ '--volume', '/app/storage',
16
+ '--token', 'token',
17
+ '--non-interactive',
18
+ '--dry-run',
19
+ '--yes',
20
+ '--write-env',
21
+ '--no-track',
22
+ '--force-rename',
23
+ '--timeout', '120',
24
+ '--allow-secret-passthrough', 'OPENAI_API_KEY',
25
+ '--allow-secret-passthrough=RESEND_API_KEY',
26
+ '--verbose',
27
+ ], {})).toMatchObject({
28
+ project: 'my-shop',
29
+ environment: 'staging',
30
+ service: 'web',
31
+ worker: false,
32
+ source: 'local',
33
+ region: 'europe-west4',
34
+ envFile: '.env.railway',
35
+ domain: 'shop.example.com',
36
+ waitDomain: false,
37
+ volume: '/app/storage',
38
+ token: 'token',
39
+ nonInteractive: true,
40
+ dryRun: true,
41
+ yes: true,
42
+ writeEnv: true,
43
+ track: false,
44
+ forceRename: true,
45
+ timeoutSeconds: 120,
46
+ allowedSecretKeys: ['OPENAI_API_KEY', 'RESEND_API_KEY'],
47
+ verbose: true,
48
+ })
49
+ })
50
+
51
+ it('enables non-interactive mode in CI', () => {
52
+ expect(parseRailwayDeployOptions([], { CI: 'true' }).nonInteractive).toBe(true)
53
+ })
54
+
55
+ it('allows cleanup dry-runs in non-interactive environments without --yes', () => {
56
+ expect(parseRailwayDeployOptions(['--cleanup', '--dry-run'], { CI: 'true' }))
57
+ .toMatchObject({
58
+ cleanup: true,
59
+ dryRun: true,
60
+ nonInteractive: true,
61
+ yes: false,
62
+ })
63
+ })
64
+
65
+ it.each([
66
+ [['--source', 'invalid'], 'Invalid --source'],
67
+ [['--timeout', '0'], '--timeout must be'],
68
+ [['--unknown'], 'Unknown Railway deploy option'],
69
+ [['--cleanup', '--non-interactive'], 'requires --yes'],
70
+ ])('rejects invalid arguments', (args, expectedMessage) => {
71
+ expect(() => parseRailwayDeployOptions(args, {})).toThrow(expectedMessage)
72
+ })
73
+
74
+ it('redacts both token flag syntaxes in dispatcher output', () => {
75
+ expect(redactRailwayCliArgs(['--token', 'secret', '--token=other', '--dry-run']))
76
+ .toEqual(['--token', '****', '--token=****', '--dry-run'])
77
+ })
78
+ })