@next-friday/create-app 9.7.0 → 9.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,7 +24,7 @@ Without a terminal, as in CI or an agent, the name and `--stack` are required. P
24
24
  | `payload` | Payload CMS on Next.js with PostgreSQL |
25
25
  | `turborepo` | A Turborepo workspace with a Next.js app and a Payload backoffice |
26
26
 
27
- Every stack is built from [`templates/base`](templates/base), which holds the shared company setup, plus its own directory under [`templates/`](templates), which holds the stack-specific files.
27
+ Every stack is built from [`templates/base`](templates/base), which holds the shared company setup, plus its own directory under [`templates/`](templates), which holds the stack-specific files. With `--deploy railway`, the files of [`templates/deploy-railway`](templates/deploy-railway) are copied last.
28
28
 
29
29
  ## What every project includes
30
30
 
package/dist/main.mjs CHANGED
@@ -285,10 +285,11 @@ function resolveDeploySections(content, target) {
285
285
  const kept = [];
286
286
  let section;
287
287
  let isAfterMarker = false;
288
- for (const line of content.split("\n")) {
288
+ const lines = content.split("\n");
289
+ for (const [index, line] of lines.entries()) {
289
290
  const name = marker.exec(line)?.groups?.name;
290
291
  if (name === void 0) {
291
- if (!(isAfterMarker && line.trim() === "") && (section ?? target) === target) kept.push(line);
292
+ if (!(isAfterMarker && line.trim() === "" && index < lines.length - 1) && (section ?? target) === target) kept.push(line);
292
293
  } else section = sectionAfterMarker(name, section);
293
294
  isAfterMarker = name !== void 0;
294
295
  }
@@ -306,6 +307,7 @@ function sectionAfterMarker(name, section) {
306
307
  if (section === void 0) throw new Error("A deploy:end marker has no deploy target section to close.");
307
308
  return;
308
309
  }
310
+ if (!isDeployTarget(name)) throw new Error(`A deploy:${name} marker names no deploy target. Use one of: ${deployTargets.join(", ")}.`);
309
311
  if (section !== void 0) throw new Error(`A deploy:${name} marker starts inside the deploy:${section} section.`);
310
312
  return name;
311
313
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next-friday/create-app",
3
- "version": "9.7.0",
3
+ "version": "9.7.2",
4
4
  "description": "Create a Next Friday project from a template.",
5
5
  "keywords": [
6
6
  "create",
@@ -45,9 +45,9 @@ release_gate="A release manager runs prod deploys, commands against prod, and re
45
45
 
46
46
  # The gate reads the command line as text, joining continued lines and dropping quotes,
47
47
  # backslashes, and fd redirections, so wrappers such as bash -c, eval, or npx and split words such
48
- # as pr''od do not hide the common forms. A dispatch input or Railway environment the gate cannot
49
- # read (a variable, a command substitution, a file, or stdin) counts as prod. It is a convenience,
50
- # not a security boundary: the prod environment's required reviewers and Railway credentials are.
48
+ # as pr''od do not hide the common forms. A dispatch input the gate cannot read (a variable, a
49
+ # command substitution, a file, or stdin) counts as prod. It is a convenience, not a security
50
+ # boundary: the prod environment's required reviewers and the deploy credentials are.
51
51
  text=${command//'\\'/}
52
52
  text=${text//$'\\\n'/}
53
53
  text=$(sed -E 's/([0-9]*[<>]&[0-9-]*|&>>?)//g' <<< "${text//[\"\'\\]/}")
@@ -56,16 +56,21 @@ prod="(prod([^[:alnum:]_-]|$)|[\$\`@])"
56
56
  dispatch="(workflow[[:space:]]+run|/dispatches)"
57
57
  environment_field="environment[]]*[=:][[:space:]]*$prod"
58
58
  unreadable_inputs="--(json|input)([[:space:]=]|$)"
59
+ # deploy:railway
60
+ # A Railway environment the gate cannot read counts as prod too.
59
61
  railway="(^|[^[:alnum:]_.-])(railway|@railway/cli(@[^[:space:]]*)?)([[:space:]]$simple)?"
62
+ # deploy:end
60
63
  git_push="(^|[^[:alnum:]_.-])git([[:space:]]+(-[Cc][[:space:]]+[^[:space:]]+|--(git-dir|work-tree|namespace|config-env)[[:space:]]+[^[:space:]]+|--?[^[:space:]]+))*[[:space:]]+push([[:space:]]$simple)?"
61
64
  tag_ref="([[:space:]:+]((refs/)?tags/|v[0-9])|[[:space:]]--((follow-)?tags|mirror)([[:space:]=]|$))"
62
65
 
63
66
  if [[ $text =~ $dispatch$simple($environment_field|$unreadable_inputs) || $text =~ $environment_field$simple$dispatch ]]; then
64
67
  block "$release_gate"
65
68
  fi
69
+ # deploy:railway
66
70
  if [[ $text =~ $railway[[:space:]](-[[:alpha:]]*e([[:space:]]+|=)?|--environment([[:space:]]+|=))$prod || $text =~ $railway[[:space:]](env|environment)([[:space:]]+[[:alpha:]]+)?[[:space:]]+$prod ]]; then
67
71
  block "$release_gate"
68
72
  fi
73
+ # deploy:end
69
74
  if [[ $text =~ $git_push$tag_ref || $(tr '[:upper:]' '[:lower:]' <<< "$text") == *push.followtags* ]]; then
70
75
  block "$release_gate"
71
76
  fi
@@ -137,7 +137,7 @@ const allowedCommands = [
137
137
  'gh api repos/next-friday/example/issues/1/comments -f body="APPROVED by QA"',
138
138
  ];
139
139
 
140
- const railwayProductionCommand = "railway up -e prod";
140
+ const sampleProductionCommand = "gh workflow run deploy.yml -f environment=prod";
141
141
 
142
142
  const productionCommands = [
143
143
  "gh workflow run deploy.yml -f environment=prod -f commit_sha=abc",
@@ -158,8 +158,30 @@ const productionCommands = [
158
158
  "gh api repos/o/r/actions/workflows/1/dispatches --input body.json",
159
159
  "gh api repos/o/r/actions/workflows/1/dispatches -f inputs[environment]=$E",
160
160
  "bash -c 'gh workflow run deploy.yml -f environment=prod'",
161
+ sampleProductionCommand,
162
+ "git push origin v1.0.0",
163
+ "git push origin HEAD:refs/tags/v1.0.0",
164
+ "git push --tags",
165
+ "git push --follow-tags origin main",
166
+ "git -C . push origin v2.3.4",
167
+ "gh workflow run deploy.yml -f environment=pr''od",
168
+ "gh workflow run deploy.yml -f environment=p\\rod",
169
+ 'gh workflow run deploy.yml -f "envi"ronment=prod',
170
+ "git push origin 'refs/tags/*'",
171
+ 'git push origin "refs/tags/v*"',
172
+ "git push --mirror",
173
+ "git -c push.followTags=true push origin main",
174
+ "git config push.followTags true && git push",
175
+ "gh workflow run deploy.yml 2>&1 -f environment=prod",
176
+ "git push origin tags/v1.0.0",
177
+ "git --git-dir .git push origin v1.0.0",
178
+ "gh workflow run deploy.yml \\\n -f environment=prod \\\n -f commit_sha=abc",
179
+ "git push \\\n origin v1.0.0",
180
+ "git push &>/dev/null origin v1.0.0",
181
+ "cd a\\\\\ngit push origin v1.0.0",
182
+ // deploy:railway
161
183
  "railway up --ci --environment prod",
162
- railwayProductionCommand,
184
+ "railway up -e prod",
163
185
  "railway up -e=prod",
164
186
  "railway up -eprod",
165
187
  "railway up -ce prod",
@@ -181,32 +203,13 @@ const productionCommands = [
181
203
  "/usr/local/bin/railway up -e prod",
182
204
  "timeout 60 railway up -e prod",
183
205
  "eval railway up -e prod",
184
- "git push origin v1.0.0",
185
- "git push origin HEAD:refs/tags/v1.0.0",
186
- "git push --tags",
187
- "git push --follow-tags origin main",
188
- "git -C . push origin v2.3.4",
189
206
  "npx @railway/cli@5.71.0 up -e prod",
190
207
  "npx @railway/cli@latest up --environment prod",
191
208
  "railway up -e pr''od",
192
- "gh workflow run deploy.yml -f environment=pr''od",
193
- "gh workflow run deploy.yml -f environment=p\\rod",
194
- 'gh workflow run deploy.yml -f "envi"ronment=prod',
195
- "git push origin 'refs/tags/*'",
196
- 'git push origin "refs/tags/v*"',
197
- "git push --mirror",
198
- "git -c push.followTags=true push origin main",
199
- "git config push.followTags true && git push",
200
- "gh workflow run deploy.yml 2>&1 -f environment=prod",
201
209
  "railway up 2>&1 -e prod",
202
- "git push origin tags/v1.0.0",
203
- "git --git-dir .git push origin v1.0.0",
204
- "gh workflow run deploy.yml \\\n -f environment=prod \\\n -f commit_sha=abc",
205
210
  "railway up \\\n --environment prod",
206
- "git push \\\n origin v1.0.0",
207
211
  "railway up &>/dev/null -e prod",
208
- "git push &>/dev/null origin v1.0.0",
209
- "cd a\\\\\ngit push origin v1.0.0",
212
+ // deploy:end
210
213
  ];
211
214
 
212
215
  const approvalLiftsGate = ["CHG-123", "CHG-1"];
@@ -229,10 +232,6 @@ const approvalKeepsGate = [
229
232
  const nonProductionCommands = [
230
233
  "gh workflow run deploy.yml -f environment=uat -f commit_sha=abc",
231
234
  "gh api repos/o/r/actions/workflows/deploy.yml/dispatches -f 'inputs[environment]=uat'",
232
- "railway up -e uat",
233
- "railway up -e prod-eu",
234
- "railway environment uat",
235
- "railway status",
236
235
  "gh workflow list",
237
236
  "gh run list --workflow deploy.yml",
238
237
  "gh api repos/o/r/environments/prod",
@@ -241,14 +240,20 @@ const nonProductionCommands = [
241
240
  'git commit -m "release v1.0" && git push',
242
241
  'git commit -m "docs(x): deploy to production"',
243
242
  'gh pr create --title "ci(x): promote to prod" --body "run gh workflow run later"',
244
- "echo railway",
245
243
  "git tag v1.0.0",
246
244
  'git stash push -m "wip v2"',
247
245
  'git commit -m "fix(ci): push v2 images"',
248
- 'git commit -m "fix: railway env uat and prod split"',
249
246
  "git push -u origin HEAD",
250
247
  "git push origin feature/push-v2",
251
248
  "pnpm verify 2>&1 | tail && git push",
249
+ // deploy:railway
250
+ "railway up -e uat",
251
+ "railway up -e prod-eu",
252
+ "railway environment uat",
253
+ "railway status",
254
+ "echo railway",
255
+ 'git commit -m "fix: railway env uat and prod split"',
256
+ // deploy:end
252
257
  ];
253
258
 
254
259
  const mcpTools = {
@@ -461,11 +466,11 @@ const cases: GateCase[] = [
461
466
  expectedApproval: releaseApproval,
462
467
  input: {
463
468
  tool_input: {
464
- command: railwayProductionCommand,
469
+ command: sampleProductionCommand,
465
470
  },
466
471
  },
467
472
  expectedPermission: "allow" as const,
468
- name: `production with RELEASE_APPROVAL="${releaseApproval}": ${railwayProductionCommand}`,
473
+ name: `production with RELEASE_APPROVAL="${releaseApproval}": ${sampleProductionCommand}`,
469
474
  expectedExit: 0 as const,
470
475
  })),
471
476
  ...approvalKeepsGate.map(releaseApproval => ({
@@ -473,10 +478,10 @@ const cases: GateCase[] = [
473
478
  releaseApproval,
474
479
  input: {
475
480
  tool_input: {
476
- command: railwayProductionCommand,
481
+ command: sampleProductionCommand,
477
482
  },
478
483
  },
479
- name: `production with RELEASE_APPROVAL="${releaseApproval}": ${railwayProductionCommand}`,
484
+ name: `production with RELEASE_APPROVAL="${releaseApproval}": ${sampleProductionCommand}`,
480
485
  expectedExit: 2 as const,
481
486
  })),
482
487
  ...nonProductionCommands.map(command => ({
@@ -590,7 +595,7 @@ const cases: GateCase[] = [
590
595
  name: "record: the conversation of a Cursor hook input",
591
596
  expectedExit: 2,
592
597
  input: {
593
- command: railwayProductionCommand,
598
+ command: sampleProductionCommand,
594
599
  conversation_id: "cursor-conversation",
595
600
  },
596
601
  },
@@ -617,7 +622,7 @@ const cases: GateCase[] = [
617
622
  releaseApproval: "CHG-123",
618
623
  expectedExit: 2,
619
624
  input: {
620
- command: railwayProductionCommand,
625
+ command: sampleProductionCommand,
621
626
  },
622
627
  },
623
628
  {
@@ -636,7 +641,7 @@ const cases: GateCase[] = [
636
641
  expectedExit: 2,
637
642
  isRecordUnwritable: true,
638
643
  input: {
639
- command: railwayProductionCommand,
644
+ command: sampleProductionCommand,
640
645
  },
641
646
  },
642
647
  ];
@@ -118,7 +118,7 @@ jobs:
118
118
  image:
119
119
  name: Container Image
120
120
  needs: changes
121
- if: needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule'
121
+ if: (needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule') && vars.CONTAINER_IMAGE != 'false'
122
122
  runs-on: ubuntu-latest
123
123
  timeout-minutes: 15
124
124
  env:
@@ -157,6 +157,7 @@ jobs:
157
157
  tar --extract --gzip --file "$archive" trivy
158
158
  ./trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --no-progress app:ci
159
159
 
160
+ # deploy:railway
160
161
  deploy-dev:
161
162
  name: Deploy to dev
162
163
  needs:
@@ -171,6 +172,7 @@ jobs:
171
172
  environment: dev
172
173
  commit_sha: ${{ github.sha }}
173
174
 
175
+ # deploy:end
174
176
  triage:
175
177
  name: Triage failed build
176
178
  needs:
@@ -34,8 +34,18 @@ CI also runs the build, the end-to-end tests, the container image scan, and the
34
34
 
35
35
  ## Container
36
36
 
37
+ <!-- deploy:railway -->
38
+
39
+ The `Dockerfile` builds a production image that runs as a non-root user (UID 1000). The platform must set `PORT`; Railway sets it automatically. The build needs `pnpm-lock.yaml`, so run `pnpm install` once before the first `docker build`.
40
+
41
+ <!-- deploy:end -->
42
+
43
+ <!-- deploy:none -->
44
+
37
45
  The `Dockerfile` builds a production image that runs as a non-root user (UID 1000). The platform must set `PORT`. The build needs `pnpm-lock.yaml`, so run `pnpm install` once before the first `docker build`.
38
46
 
47
+ <!-- deploy:end -->
48
+
39
49
  ```sh
40
50
  docker build -t app .
41
51
  docker run --env PORT=3000 --publish 3000:3000 app
@@ -58,6 +68,8 @@ docker run --env PORT=3000 --publish 3000:3000 app
58
68
  3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
59
69
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health`.
60
70
 
71
+ Remove the workflow, `railway.json`, and the `deploy-dev` job in `ci.yml` if the project deploys elsewhere.
72
+
61
73
  <!-- deploy:end -->
62
74
 
63
75
  <!-- deploy:none -->
@@ -124,7 +124,7 @@ jobs:
124
124
  image:
125
125
  name: Container Image
126
126
  needs: changes
127
- if: needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule'
127
+ if: (needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule') && vars.CONTAINER_IMAGE != 'false'
128
128
  runs-on: ubuntu-latest
129
129
  timeout-minutes: 15
130
130
  env:
@@ -163,6 +163,7 @@ jobs:
163
163
  tar --extract --gzip --file "$archive" trivy
164
164
  ./trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --no-progress app:ci
165
165
 
166
+ # deploy:railway
166
167
  deploy-dev:
167
168
  name: Deploy to dev
168
169
  needs:
@@ -177,6 +178,7 @@ jobs:
177
178
  environment: dev
178
179
  commit_sha: ${{ github.sha }}
179
180
 
181
+ # deploy:end
180
182
  triage:
181
183
  name: Triage failed build
182
184
  needs:
@@ -32,8 +32,18 @@ CI also runs the build, the end-to-end tests, the container image scan, and the
32
32
 
33
33
  ## Container
34
34
 
35
+ <!-- deploy:railway -->
36
+
37
+ The `Dockerfile` builds a standalone production image that runs as the `node` user, for platforms such as Railway. The server listens on `PORT`, or 3000 when it is unset.
38
+
39
+ <!-- deploy:end -->
40
+
41
+ <!-- deploy:none -->
42
+
35
43
  The `Dockerfile` builds a standalone production image that runs as the `node` user for a container platform. The server listens on `PORT`, or 3000 when it is unset.
36
44
 
45
+ <!-- deploy:end -->
46
+
37
47
  ```sh
38
48
  docker build -t app .
39
49
  docker run --publish 3000:3000 app
@@ -58,6 +68,8 @@ On Vercel, deploy without the image; the Next.js preset takes precedence over th
58
68
  3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
59
69
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health`.
60
70
 
71
+ Remove the workflow, `railway.json`, and the `deploy-dev` job in `ci.yml` if the project deploys elsewhere.
72
+
61
73
  <!-- deploy:end -->
62
74
 
63
75
  <!-- deploy:none -->
@@ -116,7 +116,7 @@ jobs:
116
116
  image:
117
117
  name: Container Image
118
118
  needs: changes
119
- if: needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule'
119
+ if: (needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule') && vars.CONTAINER_IMAGE != 'false'
120
120
  runs-on: ubuntu-latest
121
121
  timeout-minutes: 15
122
122
  env:
@@ -155,6 +155,7 @@ jobs:
155
155
  tar --extract --gzip --file "$archive" trivy
156
156
  ./trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --no-progress app:ci
157
157
 
158
+ # deploy:railway
158
159
  deploy-dev:
159
160
  name: Deploy to dev
160
161
  needs:
@@ -169,6 +170,7 @@ jobs:
169
170
  environment: dev
170
171
  commit_sha: ${{ github.sha }}
171
172
 
173
+ # deploy:end
172
174
  triage:
173
175
  name: Triage failed build
174
176
  needs:
@@ -32,8 +32,18 @@ CI also runs the build, the container image scan, and the security scans, includ
32
32
 
33
33
  ## Container
34
34
 
35
+ <!-- deploy:railway -->
36
+
37
+ The `Dockerfile` builds a production image that runs as a non-root user (UID 1000). The platform must set `PORT`; Railway sets it automatically. The build needs `pnpm-lock.yaml`, so run `pnpm install` once before the first `docker build`.
38
+
39
+ <!-- deploy:end -->
40
+
41
+ <!-- deploy:none -->
42
+
35
43
  The `Dockerfile` builds a production image that runs as a non-root user (UID 1000). The platform must set `PORT`. The build needs `pnpm-lock.yaml`, so run `pnpm install` once before the first `docker build`.
36
44
 
45
+ <!-- deploy:end -->
46
+
37
47
  ```sh
38
48
  docker build -t app .
39
49
  docker run --env PORT=3000 --publish 3000:3000 app
@@ -56,6 +66,8 @@ docker run --env PORT=3000 --publish 3000:3000 app
56
66
  3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
57
67
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health`.
58
68
 
69
+ Remove the workflow, `railway.json`, and the `deploy-dev` job in `ci.yml` if the project deploys elsewhere.
70
+
59
71
  <!-- deploy:end -->
60
72
 
61
73
  <!-- deploy:none -->
@@ -141,7 +141,7 @@ jobs:
141
141
  image:
142
142
  name: Container Image
143
143
  needs: changes
144
- if: needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule'
144
+ if: (needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule') && vars.CONTAINER_IMAGE != 'false'
145
145
  runs-on: ubuntu-latest
146
146
  timeout-minutes: 15
147
147
  services:
@@ -196,6 +196,7 @@ jobs:
196
196
  tar --extract --gzip --file "$archive" trivy
197
197
  ./trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --no-progress app:ci
198
198
 
199
+ # deploy:railway
199
200
  deploy-dev:
200
201
  name: Deploy to dev
201
202
  needs:
@@ -210,6 +211,7 @@ jobs:
210
211
  environment: dev
211
212
  commit_sha: ${{ github.sha }}
212
213
 
214
+ # deploy:end
213
215
  triage:
214
216
  name: Triage failed build
215
217
  needs:
@@ -48,8 +48,18 @@ CI also runs the build, the end-to-end tests, the container image scan, and the
48
48
 
49
49
  ## Container
50
50
 
51
+ <!-- deploy:railway -->
52
+
53
+ The `Dockerfile` builds a standalone production image that runs as the `node` user, for platforms such as Railway. The server listens on `PORT`, or 3000 when it is unset, and needs `DATABASE_URL`, `PAYLOAD_SECRET`, and `APP_URL`, the public URL that Payload accepts cookie-authenticated and cross-origin requests from.
54
+
55
+ <!-- deploy:end -->
56
+
57
+ <!-- deploy:none -->
58
+
51
59
  The `Dockerfile` builds a standalone production image that runs as the `node` user for a container platform. The server listens on `PORT`, or 3000 when it is unset, and needs `DATABASE_URL`, `PAYLOAD_SECRET`, and `APP_URL`, the public URL that Payload accepts cookie-authenticated and cross-origin requests from.
52
60
 
61
+ <!-- deploy:end -->
62
+
53
63
  ```sh
54
64
  docker build -t app .
55
65
  docker run --publish 3000:3000 --env DATABASE_URL --env PAYLOAD_SECRET --env APP_URL app
@@ -84,6 +94,8 @@ On Vercel, deploy without the image; the Next.js preset takes precedence over th
84
94
  3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
85
95
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health`.
86
96
 
97
+ Remove the workflow, `railway.json`, and the `deploy-dev` job in `ci.yml` if the project deploys elsewhere.
98
+
87
99
  <!-- deploy:end -->
88
100
 
89
101
  <!-- deploy:none -->
@@ -212,7 +212,7 @@ jobs:
212
212
  needs:
213
213
  - changes
214
214
  - affected-apps
215
- if: (needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule') && needs.affected-apps.outputs.apps != '[]'
215
+ if: (needs.changes.outputs.full_verification == 'true' || github.event_name == 'schedule') && needs.affected-apps.outputs.apps != '[]' && vars.CONTAINER_IMAGE != 'false'
216
216
  runs-on: ubuntu-latest
217
217
  timeout-minutes: 20
218
218
  strategy:
@@ -272,6 +272,7 @@ jobs:
272
272
  tar --extract --gzip --file "$archive" trivy
273
273
  ./trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --no-progress "$APP:ci"
274
274
 
275
+ # deploy:railway
275
276
  deploy-dev:
276
277
  name: Deploy to dev
277
278
  needs:
@@ -286,6 +287,7 @@ jobs:
286
287
  environment: dev
287
288
  commit_sha: ${{ github.sha }}
288
289
 
290
+ # deploy:end
289
291
  triage:
290
292
  name: Triage failed build
291
293
  needs:
@@ -86,6 +86,8 @@ On Vercel, deploy an app without its image. The Next.js preset takes precedence
86
86
  3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`. The workflow compares the commit with the commit of the last successful deploy to that environment, found from the successful `deploy.yml` and `ci.yml` runs on `main`, and runs `railway up` only for services whose packages changed. A promotion through `uat` or `prod` uses the same comparison against the commit last deployed there, so several commits still ship every changed service. The first deploy to an environment ships both services.
87
87
  4. Promote with **Actions → Deploy → Run workflow**, giving the environment and the full commit SHA. The workflow requires a successful CI run on `main` for that commit and a successful deployment to the previous environment, then waits for `/health` on each service it uploaded.
88
88
 
89
+ Remove the workflow, `railway.json`, and the `deploy-dev` job in `ci.yml` if the project deploys elsewhere.
90
+
89
91
  <!-- deploy:end -->
90
92
 
91
93
  <!-- deploy:none -->
@@ -108,6 +110,16 @@ To add an Expo app, create it under `apps/mobile`:
108
110
  2. Use `@expo/fingerprint` with `expo-github-action/fingerprint` so a pull request requests a native build only when the native layer changed ([Expo fingerprint](https://docs.expo.dev/versions/latest/sdk/fingerprint/)).
109
111
  3. Keep a single `react-native` version in the workspace. Duplicate React Native versions in one monorepo are not supported ([Expo: Monorepos](https://docs.expo.dev/guides/monorepos/)).
110
112
 
113
+ <!-- deploy:railway -->
114
+
115
+ EAS Build and Submit replace Railway for that app. CI `--affected` already includes `apps/*` and `packages/*`, so an Expo app is skipped when it did not change.
116
+
117
+ <!-- deploy:end -->
118
+
119
+ <!-- deploy:none -->
120
+
111
121
  EAS Build and Submit deploy that app, in place of the project's deploy step. CI `--affected` already includes `apps/*` and `packages/*`, so an Expo app is skipped when it did not change.
112
122
 
123
+ <!-- deploy:end -->
124
+
113
125
  See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow.