@next-friday/create-app 9.6.0 → 9.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -10
- package/dist/main.mjs +109 -14
- package/package.json +1 -1
- package/templates/base/SETUP.md +9 -1
- package/templates/deploy-railway/turborepo/turbo.json +66 -0
- package/templates/nestjs/AGENTS.md +5 -0
- package/templates/nestjs/Dockerfile +1 -1
- package/templates/nestjs/README.md +16 -2
- package/templates/nextjs/AGENTS.md +5 -0
- package/templates/nextjs/Dockerfile +1 -1
- package/templates/nextjs/README.md +16 -2
- package/templates/nodejs/AGENTS.md +5 -0
- package/templates/nodejs/Dockerfile +1 -1
- package/templates/nodejs/README.md +16 -2
- package/templates/payload/AGENTS.md +5 -0
- package/templates/payload/Dockerfile +1 -1
- package/templates/payload/README.md +16 -2
- package/templates/turborepo/AGENTS.md +5 -0
- package/templates/turborepo/README.md +16 -2
- package/templates/turborepo/apps/app/Dockerfile +1 -1
- package/templates/turborepo/apps/backoffice/Dockerfile +1 -1
- package/templates/turborepo/turbo.json +1 -7
- /package/templates/{base → deploy-railway/base}/railway.json +0 -0
- /package/templates/{base → deploy-railway/base}/runbooks/rollback-deploy.md +0 -0
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Every stack is built from [`templates/base`](templates/base), which holds the sh
|
|
|
31
31
|
- **Code quality:** TypeScript, ESLint and Prettier with the Next Friday configs, Vitest with coverage, fast-check, Knip, dependency-cruiser, editorconfig-checker, and sort-package-json.
|
|
32
32
|
- **Git hooks:** Husky runs gitleaks, `pnpm rules:check`, and lint-staged before each commit, commitlint on each message, and `pnpm check` before each push. After the first commit, commits to `main` are blocked, and local merges into `main` always are; pushes to `main` are blocked once it exists on the remote.
|
|
33
33
|
- **CI:** GitHub Actions run the static checks, tests with patch coverage, the build, end-to-end tests for Next.js and Payload, a container build and vulnerability scan, Semgrep, OSV-Scanner, CodeQL, dependency review, OpenSSF Scorecard, gitleaks, TruffleHog, zizmor, and pull request title validation. Dependabot keeps dependencies current.
|
|
34
|
-
- **Deployment:** a `Dockerfile` that runs as the `node` user
|
|
34
|
+
- **Deployment:** a `Dockerfile` that runs as the `node` user. With `--deploy railway`, also a Railway workflow that promotes one commit through `dev`, `uat`, and `prod`.
|
|
35
35
|
- **Agent rules:** `CLAUDE.md` imports `AGENTS.md`. Shared rules live in `.agents/rules`, with hooks that enforce them in Claude Code and Cursor.
|
|
36
36
|
|
|
37
37
|
## Requirements
|
|
@@ -44,17 +44,18 @@ Every stack is built from [`templates/base`](templates/base), which holds the sh
|
|
|
44
44
|
## Options
|
|
45
45
|
|
|
46
46
|
```text
|
|
47
|
-
pnpm create @next-friday/app [name] [--stack nodejs|nestjs|nextjs|payload|turborepo]
|
|
47
|
+
pnpm create @next-friday/app [name] [--stack nodejs|nestjs|nextjs|payload|turborepo] [--deploy none|railway]
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
| Option | Description
|
|
51
|
-
| :--------------- |
|
|
52
|
-
| `name` | Project directory and package name. Asked when left out.
|
|
53
|
-
| `--stack` | `nodejs`, `nestjs`, `nextjs`, `payload`, or `turborepo`. Asked when left out.
|
|
54
|
-
| `--
|
|
55
|
-
| `--skip-
|
|
56
|
-
|
|
|
57
|
-
| `-
|
|
50
|
+
| Option | Description |
|
|
51
|
+
| :--------------- | :---------------------------------------------------------------------------------------- |
|
|
52
|
+
| `name` | Project directory and package name. Asked when left out. |
|
|
53
|
+
| `--stack` | `nodejs`, `nestjs`, `nextjs`, `payload`, or `turborepo`. Asked when left out. |
|
|
54
|
+
| `--deploy` | Deploy target: `none` or `railway`. The default is `none`, which adds no deploy workflow. |
|
|
55
|
+
| `--skip-git` | Do not run `git init`. |
|
|
56
|
+
| `--skip-install` | Do not run `pnpm install`. |
|
|
57
|
+
| `-v, --version` | Print the version. |
|
|
58
|
+
| `-h, --help` | Print the usage. |
|
|
58
59
|
|
|
59
60
|
The name must:
|
|
60
61
|
|
package/dist/main.mjs
CHANGED
|
@@ -117,6 +117,16 @@ function validateProjectName(name, cwd) {
|
|
|
117
117
|
function ownEntries(directory) {
|
|
118
118
|
return readdirSync(directory).filter((entry) => entry !== ".git");
|
|
119
119
|
}
|
|
120
|
+
// src/deploy-targets.ts
|
|
121
|
+
const deployTargets = ["none", "railway"];
|
|
122
|
+
/**
|
|
123
|
+
* Check whether a string names a supported deploy target.
|
|
124
|
+
* @param value - Deploy target from the command line.
|
|
125
|
+
* @returns Whether `value` is a supported deploy target.
|
|
126
|
+
*/
|
|
127
|
+
function isDeployTarget(value) {
|
|
128
|
+
return deployTargets.some((target) => target === value);
|
|
129
|
+
}
|
|
120
130
|
// src/stacks.ts
|
|
121
131
|
const stacks = [
|
|
122
132
|
{
|
|
@@ -173,10 +183,11 @@ function templateName(stack) {
|
|
|
173
183
|
return `next-friday-template-${stack}`;
|
|
174
184
|
}
|
|
175
185
|
// src/arguments.ts
|
|
176
|
-
const usage = `Usage: pnpm create @next-friday/app [name] [--stack ${stacks.map((stack) => stack.id).join("|")}]
|
|
186
|
+
const usage = `Usage: pnpm create @next-friday/app [name] [--stack ${stacks.map((stack) => stack.id).join("|")}] [--deploy ${deployTargets.join("|")}]
|
|
177
187
|
|
|
178
188
|
name Project directory and package name, or . for the current directory. Asked when left out.
|
|
179
189
|
--stack Template stack. Asked when left out.
|
|
190
|
+
--deploy Deploy target: ${deployTargets.join(" or ")}. The default is none.
|
|
180
191
|
--skip-git Do not run git init.
|
|
181
192
|
--skip-install Do not run pnpm install.
|
|
182
193
|
-v, --version Show the version.
|
|
@@ -192,6 +203,10 @@ function parseCliArguments(cliArguments) {
|
|
|
192
203
|
allowPositionals: true,
|
|
193
204
|
options: {
|
|
194
205
|
stack: { type: "string" },
|
|
206
|
+
deploy: {
|
|
207
|
+
type: "string",
|
|
208
|
+
default: "none"
|
|
209
|
+
},
|
|
195
210
|
"skip-git": {
|
|
196
211
|
type: "boolean",
|
|
197
212
|
default: false
|
|
@@ -212,23 +227,33 @@ function parseCliArguments(cliArguments) {
|
|
|
212
227
|
}
|
|
213
228
|
}
|
|
214
229
|
});
|
|
215
|
-
const { help, stack, version } = values;
|
|
230
|
+
const { deploy, help, stack, version } = values;
|
|
216
231
|
const isInformational = help || version;
|
|
217
232
|
if (!isInformational && positionals.length > 1) throw new Error(`Pass one project name, not ${positionals.length}: ${positionals.join(" ")}.`);
|
|
218
233
|
const [name] = positionals;
|
|
219
|
-
if (!isInformational
|
|
220
|
-
const ids = stacks.map((supported) => supported.id).join(", ");
|
|
221
|
-
throw new Error(`Unknown stack "${stack}". Use one of: ${ids}.`);
|
|
222
|
-
}
|
|
234
|
+
if (!isInformational) assertKnownChoices(stack, deploy);
|
|
223
235
|
return {
|
|
224
236
|
help,
|
|
225
237
|
name,
|
|
226
238
|
version,
|
|
239
|
+
deploy: isDeployTarget(deploy) ? deploy : "none",
|
|
227
240
|
stack: stack !== void 0 && isStackId(stack) ? stack : void 0,
|
|
228
241
|
skipGit: values["skip-git"],
|
|
229
242
|
skipInstall: values["skip-install"]
|
|
230
243
|
};
|
|
231
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Throw when the stack or the deploy target is not one the CLI supports.
|
|
247
|
+
* @param stack - Stack from the command line, or `undefined` when left out.
|
|
248
|
+
* @param deploy - Deploy target from the command line.
|
|
249
|
+
*/
|
|
250
|
+
function assertKnownChoices(stack, deploy) {
|
|
251
|
+
if (stack !== void 0 && !isStackId(stack)) {
|
|
252
|
+
const ids = stacks.map((supported) => supported.id).join(", ");
|
|
253
|
+
throw new Error(`Unknown stack "${stack}". Use one of: ${ids}.`);
|
|
254
|
+
}
|
|
255
|
+
if (!isDeployTarget(deploy)) throw new Error(`Unknown deploy target "${deploy}". Use one of: ${deployTargets.join(", ")}.`);
|
|
256
|
+
}
|
|
232
257
|
// src/rename.ts
|
|
233
258
|
/**
|
|
234
259
|
* Replace the template name with the project name in every text file.
|
|
@@ -245,6 +270,45 @@ async function renameProject(directory, from, to) {
|
|
|
245
270
|
await writeFile(file, text.replaceAll(from, () => to));
|
|
246
271
|
}));
|
|
247
272
|
}
|
|
273
|
+
// src/deploy-sections.ts
|
|
274
|
+
const marker = /^\s*(?:#|\/\/|<!--)\s*deploy:(?<name>[a-z]+)\s*(?:-->\s*)?$/u;
|
|
275
|
+
const endName = "end";
|
|
276
|
+
/**
|
|
277
|
+
* Keep the sections of a template file marked for the chosen deploy target and remove the others.
|
|
278
|
+
* A section starts at a comment line that holds `deploy:<target>` and ends at one that holds `deploy:end`.
|
|
279
|
+
* Every marker line is removed, with a blank line that directly follows it.
|
|
280
|
+
* @param content - Text of a copied template file.
|
|
281
|
+
* @param target - Chosen deploy target.
|
|
282
|
+
* @returns The text for the chosen deploy target.
|
|
283
|
+
*/
|
|
284
|
+
function resolveDeploySections(content, target) {
|
|
285
|
+
const kept = [];
|
|
286
|
+
let section;
|
|
287
|
+
let isAfterMarker = false;
|
|
288
|
+
for (const line of content.split("\n")) {
|
|
289
|
+
const name = marker.exec(line)?.groups?.name;
|
|
290
|
+
if (name === void 0) {
|
|
291
|
+
if (!(isAfterMarker && line.trim() === "") && (section ?? target) === target) kept.push(line);
|
|
292
|
+
} else section = sectionAfterMarker(name, section);
|
|
293
|
+
isAfterMarker = name !== void 0;
|
|
294
|
+
}
|
|
295
|
+
if (section !== void 0) throw new Error(`The deploy:${section} section has no deploy:end marker.`);
|
|
296
|
+
return kept.join("\n");
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Give the section a marker line leaves open, or throw when the marker is out of place.
|
|
300
|
+
* @param name - Deploy target the marker names, or `end`.
|
|
301
|
+
* @param section - Deploy target of the section the marker sits in, or `undefined` outside a section.
|
|
302
|
+
* @returns The deploy target of the section now open, or `undefined` after an end marker.
|
|
303
|
+
*/
|
|
304
|
+
function sectionAfterMarker(name, section) {
|
|
305
|
+
if (name === endName) {
|
|
306
|
+
if (section === void 0) throw new Error("A deploy:end marker has no deploy target section to close.");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (section !== void 0) throw new Error(`A deploy:${name} marker starts inside the deploy:${section} section.`);
|
|
310
|
+
return name;
|
|
311
|
+
}
|
|
248
312
|
// src/create.ts
|
|
249
313
|
const templatesDirectory = fileURLToPath(new URL("../templates", import.meta.url));
|
|
250
314
|
const interruptSignals = [
|
|
@@ -276,7 +340,7 @@ async function create(cliArguments) {
|
|
|
276
340
|
const target = path.resolve(cwd, name);
|
|
277
341
|
const directory = existsSync(target) ? realpathSync(target) : target;
|
|
278
342
|
log.step(`Copying the ${stack} template.`);
|
|
279
|
-
await writeProject(stack, directory, manifestName);
|
|
343
|
+
await writeProject(stack, options.deploy, directory, manifestName);
|
|
280
344
|
log.success(`Created ${manifestName} from the ${stack} template.`);
|
|
281
345
|
const { git, isInstalled } = setUpProject(directory, options);
|
|
282
346
|
outro(nextSteps(name, stack, git, isInstalled));
|
|
@@ -294,10 +358,11 @@ async function create(cliArguments) {
|
|
|
294
358
|
* Copy, rename, and write `.env`, removing what was written when a step fails or Ctrl+C, SIGTERM, or SIGHUP arrives.
|
|
295
359
|
* An interrupt waits for the running step, so the removal does not race the copy.
|
|
296
360
|
* @param stack - Supported stack id.
|
|
361
|
+
* @param deploy - Supported deploy target.
|
|
297
362
|
* @param directory - Project directory.
|
|
298
363
|
* @param manifestName - Package name for `package.json`.
|
|
299
364
|
*/
|
|
300
|
-
async function writeProject(stack, directory, manifestName) {
|
|
365
|
+
async function writeProject(stack, deploy, directory, manifestName) {
|
|
301
366
|
const kept = existsSync(directory) ? readdirSync(directory) : void 0;
|
|
302
367
|
let isInterrupted = false;
|
|
303
368
|
const interrupt = () => {
|
|
@@ -305,7 +370,7 @@ async function writeProject(stack, directory, manifestName) {
|
|
|
305
370
|
};
|
|
306
371
|
for (const signal of interruptSignals) process.on(signal, interrupt);
|
|
307
372
|
try {
|
|
308
|
-
await copyTemplate(stack, directory);
|
|
373
|
+
await copyTemplate(stack, deploy, directory);
|
|
309
374
|
await renameProject(directory, templateName(stack), manifestName);
|
|
310
375
|
await writeEnvironmentFile(directory);
|
|
311
376
|
if (isInterrupted) throw new CancelledError();
|
|
@@ -382,19 +447,49 @@ function existingGit(directory) {
|
|
|
382
447
|
return existsSync(path.join(directory, ".git")) ? "existing" : void 0;
|
|
383
448
|
}
|
|
384
449
|
/**
|
|
385
|
-
* Copy the shared template files,
|
|
450
|
+
* Copy the shared template files, the stack's files, and the deploy target's files into the project directory.
|
|
386
451
|
* npm leaves `.gitignore` out of published packages, so the template keeps it as `_gitignore`.
|
|
387
452
|
* @param stack - Supported stack id.
|
|
453
|
+
* @param deploy - Supported deploy target.
|
|
388
454
|
* @param directory - Project directory.
|
|
389
455
|
*/
|
|
390
|
-
async function copyTemplate(stack, directory) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
456
|
+
async function copyTemplate(stack, deploy, directory) {
|
|
457
|
+
const deployLayer = deploy === "none" ? void 0 : `deploy-${deploy}`;
|
|
458
|
+
const required = [
|
|
459
|
+
"base",
|
|
460
|
+
stack,
|
|
461
|
+
...deployLayer === void 0 ? [] : [`${deployLayer}/base`]
|
|
462
|
+
];
|
|
463
|
+
for (const template of required) if (!existsSync(path.join(templatesDirectory, template))) throw new Error(`The ${template} template is missing from ${templatesDirectory}. Reinstall @next-friday/create-app.`);
|
|
464
|
+
const copyLayer = async (layer) => {
|
|
465
|
+
await cp(path.join(templatesDirectory, layer), directory, { recursive: true });
|
|
466
|
+
};
|
|
467
|
+
await copyLayer("base");
|
|
468
|
+
await copyLayer(stack);
|
|
469
|
+
if (deployLayer !== void 0) {
|
|
470
|
+
await copyLayer(`${deployLayer}/base`);
|
|
471
|
+
if (existsSync(path.join(templatesDirectory, deployLayer, stack))) await copyLayer(`${deployLayer}/${stack}`);
|
|
472
|
+
}
|
|
394
473
|
await rename(path.join(directory, "_gitignore"), path.join(directory, ".gitignore"));
|
|
474
|
+
await resolveDeployTarget(directory, deploy);
|
|
395
475
|
await markScriptsExecutable(directory);
|
|
396
476
|
}
|
|
397
477
|
/**
|
|
478
|
+
* Resolve the deploy target sections of every copied file that holds a marker.
|
|
479
|
+
* @param directory - Project directory.
|
|
480
|
+
* @param deploy - Supported deploy target.
|
|
481
|
+
*/
|
|
482
|
+
async function resolveDeployTarget(directory, deploy) {
|
|
483
|
+
const files = await listProjectFiles(directory);
|
|
484
|
+
await Promise.all(files.map(async (file) => {
|
|
485
|
+
const content = await readFile(file);
|
|
486
|
+
if (!content.includes("deploy:")) return;
|
|
487
|
+
const template = content.toString();
|
|
488
|
+
const resolved = resolveDeploySections(template, deploy);
|
|
489
|
+
if (resolved !== template) await writeFile(file, resolved);
|
|
490
|
+
}));
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
398
493
|
* Make every file that starts with a shebang executable. `pnpm pack` drops the executable bit,
|
|
399
494
|
* and the Git and agent hooks run these scripts directly.
|
|
400
495
|
* @param directory - Project directory.
|
package/package.json
CHANGED
package/templates/base/SETUP.md
CHANGED
|
@@ -64,7 +64,10 @@ Replace `security@example.com` in `SECURITY.md` with the contact the security po
|
|
|
64
64
|
|
|
65
65
|
Rulesets in a private repository need GitHub Pro, Team, or Enterprise; on GitHub Free, make the repository public or upgrade.
|
|
66
66
|
|
|
67
|
-
- **Secrets and variables → Actions → Variables:** in a private repository, set `CODE_SCANNING` to `true` to run CodeQL, Scorecard, and Dependency Review, which need GitHub Code Security there; public repositories run them without it. Set `
|
|
67
|
+
- **Secrets and variables → Actions → Variables:** in a private repository, set `CODE_SCANNING` to `true` to run CodeQL, Scorecard, and Dependency Review, which need GitHub Code Security there; public repositories run them without it. Set `CONTAINER_IMAGE` to `false` to skip the job that builds and scans the container image, for a project that ships no image.
|
|
68
|
+
<!-- deploy:railway -->
|
|
69
|
+
- **Secrets and variables → Actions → Variables:** set `DEPLOY_ENABLED` to `true` when each push to `main` that passes CI should deploy to `dev`; the README's deploy section sets up the environments.
|
|
70
|
+
<!-- deploy:end -->
|
|
68
71
|
- Create the `needs triage` label, which the issue forms apply: `gh label create "needs triage"`.
|
|
69
72
|
|
|
70
73
|
**Check:** a test pull request shows the two required checks and asks for a code owner's review.
|
|
@@ -94,7 +97,12 @@ Replace `security@example.com` in `SECURITY.md` with the contact the security po
|
|
|
94
97
|
- **Sources of truth (engineering lead):** for each artifact, name one system as the source of truth: this repository, an existing tool such as Jira, or, as the minimum, a link both ways (the record id in the file and the commit SHA in the record).
|
|
95
98
|
- **Higher risk (engineering lead):** decide which changes count as higher risk. A tech lead or architect approves their specs and plans.
|
|
96
99
|
- **Policies (each policy owner, with an engineer):** the plugin ships a default for each area, `plugins/sdlc/claude-code/skills/<area>-policy/SKILL.md` (security: OWASP ASVS 5.0.0 Level 1; UX: WCAG 2.2 Level AA; compliance: the OECD privacy principles; brand: one declared source for every brand value). Review it. To replace a default, add the organization's own rules in `.agents/skills/<area>-policy/SKILL.md` in the project: that file wins over the plugin's default, and the `CODEOWNERS` line of step 2 for that area makes the policy owner its code owner. Do not edit the plugin's copy. Then run `pnpm rules:sync`, which renders the project's own policy skill for Claude Code and Cursor. Test that the skill triggers: ask for the relevant task in different ways and confirm the skill loads each time.
|
|
100
|
+
<!-- deploy:railway -->
|
|
97
101
|
- **Production gate (engineering leadership, with change management and compliance):** name the release managers and add them as required reviewers on the `prod` environment, as the README's deploy section describes. `.agents/hooks/agent-production-gate.sh` blocks an agent's shell commands that dispatch a `prod` deploy, run a Railway command with `prod` as its environment, or push a release tag. It does not read MCP tools. A release manager whose own agent session must do one of these starts the agent with `RELEASE_APPROVAL` set to the approval record, for example `RELEASE_APPROVAL=CHG-123 claude`. The value lifts the gate only when it matches the extended regular expression on the first line of `.agents/release-approval-form`, shipped as `^[A-Z]+-[0-9]+$`; set that line to the form of your organization's approval records. Each gate hook appends its decisions to `.agents/logs/gate-decisions.jsonl`, which git ignores: the time, the hook, the decision, the session, and the reason for a block, and, when the production gate is lifted, the approval value and the Git user name. A record holds no command text and no path.
|
|
102
|
+
<!-- deploy:end -->
|
|
103
|
+
<!-- deploy:none -->
|
|
104
|
+
- **Production gate (engineering leadership, with change management and compliance):** name the release managers and add them as required reviewers on the `prod` environment, as the README's deploy section describes. `.agents/hooks/agent-production-gate.sh` blocks an agent's shell commands that dispatch a `prod` deploy or push a release tag. It does not read MCP tools. A release manager whose own agent session must do one of these starts the agent with `RELEASE_APPROVAL` set to the approval record, for example `RELEASE_APPROVAL=CHG-123 claude`. The value lifts the gate only when it matches the extended regular expression on the first line of `.agents/release-approval-form`, shipped as `^[A-Z]+-[0-9]+$`; set that line to the form of your organization's approval records. Each gate hook appends its decisions to `.agents/logs/gate-decisions.jsonl`, which git ignores: the time, the hook, the decision, the session, and the reason for a block, and, when the production gate is lifted, the approval value and the Git user name. A record holds no command text and no path.
|
|
105
|
+
<!-- deploy:end -->
|
|
98
106
|
- **Review (tech lead):** `.agents/review.md` names this stack's generated files to skip; add a path when the project gains a generated file, then run `pnpm rules:sync`, which renders it to `REVIEW.md` for Claude Code Review. Once a month, rate the review findings and tune `.agents/review.md`.
|
|
99
107
|
- **People without Git experience:** connect GitHub to Claude (claude.ai or Cowork), so Claude commits `intent.md` for them.
|
|
100
108
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://turborepo.dev/schema.json",
|
|
3
|
+
"futureFlags": {
|
|
4
|
+
"affectedUsingTaskInputs": true
|
|
5
|
+
},
|
|
6
|
+
"globalDependencies": [
|
|
7
|
+
".dockerignore",
|
|
8
|
+
".env",
|
|
9
|
+
"package.json",
|
|
10
|
+
"pnpm-workspace.yaml",
|
|
11
|
+
"railway.json"
|
|
12
|
+
],
|
|
13
|
+
"globalPassThroughEnv": [
|
|
14
|
+
"APP_URL",
|
|
15
|
+
"CI",
|
|
16
|
+
"DATABASE_URL",
|
|
17
|
+
"NEXT_TELEMETRY_DISABLED",
|
|
18
|
+
"PAYLOAD_SECRET"
|
|
19
|
+
],
|
|
20
|
+
"tasks": {
|
|
21
|
+
"build": {
|
|
22
|
+
"dependsOn": ["^build"],
|
|
23
|
+
"inputs": ["$TURBO_DEFAULT$", "!README.md"],
|
|
24
|
+
"outputs": [".next/**", "!.next/cache/**", "!.next/dev/**"]
|
|
25
|
+
},
|
|
26
|
+
"depcruise": {
|
|
27
|
+
"inputs": ["$TURBO_DEFAULT$", "!README.md"]
|
|
28
|
+
},
|
|
29
|
+
"dev": {
|
|
30
|
+
"cache": false,
|
|
31
|
+
"persistent": true,
|
|
32
|
+
"inputs": ["$TURBO_DEFAULT$", "!README.md"]
|
|
33
|
+
},
|
|
34
|
+
"start": {
|
|
35
|
+
"cache": false,
|
|
36
|
+
"persistent": true,
|
|
37
|
+
"inputs": ["$TURBO_DEFAULT$", "!README.md"]
|
|
38
|
+
},
|
|
39
|
+
"test": {
|
|
40
|
+
"inputs": ["$TURBO_DEFAULT$", "!README.md"]
|
|
41
|
+
},
|
|
42
|
+
"test:changed": {
|
|
43
|
+
"cache": false,
|
|
44
|
+
"inputs": ["$TURBO_DEFAULT$", "!README.md"]
|
|
45
|
+
},
|
|
46
|
+
"test:coverage": {
|
|
47
|
+
"inputs": ["$TURBO_DEFAULT$", "!README.md"],
|
|
48
|
+
"outputs": ["coverage/**"]
|
|
49
|
+
},
|
|
50
|
+
"test:e2e": {
|
|
51
|
+
"cache": false,
|
|
52
|
+
"dependsOn": ["build"],
|
|
53
|
+
"inputs": ["$TURBO_DEFAULT$", "!README.md"]
|
|
54
|
+
},
|
|
55
|
+
"test:watch": {
|
|
56
|
+
"cache": false,
|
|
57
|
+
"persistent": true,
|
|
58
|
+
"inputs": ["$TURBO_DEFAULT$", "!README.md"]
|
|
59
|
+
},
|
|
60
|
+
"typecheck": {
|
|
61
|
+
"dependsOn": ["^typecheck"],
|
|
62
|
+
"inputs": ["$TURBO_DEFAULT$", "!README.md"],
|
|
63
|
+
"outputs": [".next/types/**", "next-env.d.ts"]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -45,7 +45,12 @@ The `verifier` subagent starts the app with `pnpm start:dev`. It exercises the c
|
|
|
45
45
|
|
|
46
46
|
When Claude makes a mistake twice, add the correction here.
|
|
47
47
|
|
|
48
|
+
<!-- deploy:railway -->
|
|
48
49
|
- **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod`, runs a Railway CLI command that names `prod`, or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it.
|
|
50
|
+
<!-- deploy:end -->
|
|
51
|
+
<!-- deploy:none -->
|
|
52
|
+
- **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod` or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool passes it.
|
|
53
|
+
<!-- deploy:end -->
|
|
49
54
|
- **Issues**: open an issue only to hand work to another person, never to give a pull request a number to close. A pull request closes an issue that already exists with `Closes #<issue>`.
|
|
50
55
|
- **What belongs in the repository**: files, commit messages, and pull requests hold the change, its reason, and the checks. Write no date, log, history, mistake count, token figure, or session note into a file.
|
|
51
56
|
- **Sourced requirements**: add a requirement to an intent, spec, or plan only when a person decided it or a named source says it. This includes a requirement a review bot suggests. When no person decided it and no source says it, leave it out and answer the thread with that.
|
|
@@ -34,13 +34,15 @@ CI also runs the build, the end-to-end tests, the container image scan, and the
|
|
|
34
34
|
|
|
35
35
|
## Container
|
|
36
36
|
|
|
37
|
-
The `Dockerfile` builds a production image that runs as a non-root user (UID 1000). The platform must set `PORT
|
|
37
|
+
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
38
|
|
|
39
39
|
```sh
|
|
40
40
|
docker build -t app .
|
|
41
41
|
docker run --env PORT=3000 --publish 3000:3000 app
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
<!-- deploy:railway -->
|
|
45
|
+
|
|
44
46
|
## Deploy
|
|
45
47
|
|
|
46
48
|
`.github/workflows/deploy.yml` deploys the `Dockerfile` to Railway and promotes one commit through `dev`, `uat`, and `prod`. `railway.json` sets the Railway health check to `/health`. The workflow stays off until you configure it:
|
|
@@ -56,6 +58,18 @@ docker run --env PORT=3000 --publish 3000:3000 app
|
|
|
56
58
|
3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
|
|
57
59
|
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
60
|
|
|
59
|
-
|
|
61
|
+
<!-- deploy:end -->
|
|
62
|
+
|
|
63
|
+
<!-- deploy:none -->
|
|
64
|
+
|
|
65
|
+
## Deploy
|
|
66
|
+
|
|
67
|
+
The project has no deploy step yet. A deploy step needs:
|
|
68
|
+
|
|
69
|
+
- a workflow under `.github/workflows/` that deploys;
|
|
70
|
+
- the `prod` environment under **Settings → Environments**, with the release managers as its required reviewers;
|
|
71
|
+
- the image the `Dockerfile` builds.
|
|
72
|
+
|
|
73
|
+
<!-- deploy:end -->
|
|
60
74
|
|
|
61
75
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow.
|
|
@@ -46,7 +46,12 @@ The `verifier` subagent starts the app with `pnpm dev`. It exercises the changed
|
|
|
46
46
|
When Claude makes a mistake twice, add the correction here.
|
|
47
47
|
|
|
48
48
|
- **First end-to-end run**: run `pnpm exec playwright install chromium` once. `pnpm test:e2e` starts the development server unless one is already running.
|
|
49
|
+
<!-- deploy:railway -->
|
|
49
50
|
- **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod`, runs a Railway CLI command that names `prod`, or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it.
|
|
51
|
+
<!-- deploy:end -->
|
|
52
|
+
<!-- deploy:none -->
|
|
53
|
+
- **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod` or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool passes it.
|
|
54
|
+
<!-- deploy:end -->
|
|
50
55
|
- **Issues**: open an issue only to hand work to another person, never to give a pull request a number to close. A pull request closes an issue that already exists with `Closes #<issue>`.
|
|
51
56
|
- **What belongs in the repository**: files, commit messages, and pull requests hold the change, its reason, and the checks. Write no date, log, history, mistake count, token figure, or session note into a file.
|
|
52
57
|
- **Sourced requirements**: add a requirement to an intent, spec, or plan only when a person decided it or a named source says it. This includes a requirement a review bot suggests. When no person decided it and no source says it, leave it out and answer the thread with that.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Runs on any OCI platform
|
|
1
|
+
# Runs on any OCI container platform. A platform that builds Next.js itself deploys without it. The platform sets `PORT`.
|
|
2
2
|
FROM \
|
|
3
3
|
node:24.22.1-alpine@sha256:06d7adf3e0ef61a13f1d28682726366f97a15aa1888c934d3ecec86c33918b53 \
|
|
4
4
|
AS build
|
|
@@ -32,7 +32,7 @@ CI also runs the build, the end-to-end tests, the container image scan, and the
|
|
|
32
32
|
|
|
33
33
|
## Container
|
|
34
34
|
|
|
35
|
-
The `Dockerfile` builds a standalone production image that runs as the `node` user
|
|
35
|
+
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
36
|
|
|
37
37
|
```sh
|
|
38
38
|
docker build -t app .
|
|
@@ -41,6 +41,8 @@ docker run --publish 3000:3000 app
|
|
|
41
41
|
|
|
42
42
|
On Vercel, deploy without the image; the Next.js preset takes precedence over the `Dockerfile`.
|
|
43
43
|
|
|
44
|
+
<!-- deploy:railway -->
|
|
45
|
+
|
|
44
46
|
## Deploy
|
|
45
47
|
|
|
46
48
|
`.github/workflows/deploy.yml` deploys the `Dockerfile` to Railway and promotes one commit through `dev`, `uat`, and `prod`. `railway.json` sets the Railway health check to `/health`. The workflow stays off until you configure it:
|
|
@@ -56,6 +58,18 @@ On Vercel, deploy without the image; the Next.js preset takes precedence over th
|
|
|
56
58
|
3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
|
|
57
59
|
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
60
|
|
|
59
|
-
|
|
61
|
+
<!-- deploy:end -->
|
|
62
|
+
|
|
63
|
+
<!-- deploy:none -->
|
|
64
|
+
|
|
65
|
+
## Deploy
|
|
66
|
+
|
|
67
|
+
The project has no deploy step yet. A deploy step needs:
|
|
68
|
+
|
|
69
|
+
- a workflow under `.github/workflows/` that deploys;
|
|
70
|
+
- the `prod` environment under **Settings → Environments**, with the release managers as its required reviewers;
|
|
71
|
+
- the image the `Dockerfile` builds.
|
|
72
|
+
|
|
73
|
+
<!-- deploy:end -->
|
|
60
74
|
|
|
61
75
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow.
|
|
@@ -44,7 +44,12 @@ The `verifier` subagent starts the app with `pnpm start:dev`. It exercises the c
|
|
|
44
44
|
|
|
45
45
|
When Claude makes a mistake twice, add the correction here.
|
|
46
46
|
|
|
47
|
+
<!-- deploy:railway -->
|
|
47
48
|
- **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod`, runs a Railway CLI command that names `prod`, or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it.
|
|
49
|
+
<!-- deploy:end -->
|
|
50
|
+
<!-- deploy:none -->
|
|
51
|
+
- **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod` or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool passes it.
|
|
52
|
+
<!-- deploy:end -->
|
|
48
53
|
- **Issues**: open an issue only to hand work to another person, never to give a pull request a number to close. A pull request closes an issue that already exists with `Closes #<issue>`.
|
|
49
54
|
- **What belongs in the repository**: files, commit messages, and pull requests hold the change, its reason, and the checks. Write no date, log, history, mistake count, token figure, or session note into a file.
|
|
50
55
|
- **Sourced requirements**: add a requirement to an intent, spec, or plan only when a person decided it or a named source says it. This includes a requirement a review bot suggests. When no person decided it and no source says it, leave it out and answer the thread with that.
|
|
@@ -32,13 +32,15 @@ CI also runs the build, the container image scan, and the security scans, includ
|
|
|
32
32
|
|
|
33
33
|
## Container
|
|
34
34
|
|
|
35
|
-
The `Dockerfile` builds a production image that runs as a non-root user (UID 1000). The platform must set `PORT
|
|
35
|
+
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
36
|
|
|
37
37
|
```sh
|
|
38
38
|
docker build -t app .
|
|
39
39
|
docker run --env PORT=3000 --publish 3000:3000 app
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
<!-- deploy:railway -->
|
|
43
|
+
|
|
42
44
|
## Deploy
|
|
43
45
|
|
|
44
46
|
`.github/workflows/deploy.yml` deploys the `Dockerfile` to Railway and promotes one commit through `dev`, `uat`, and `prod`. `railway.json` sets the Railway health check to `/health`. The workflow stays off until you configure it:
|
|
@@ -54,6 +56,18 @@ docker run --env PORT=3000 --publish 3000:3000 app
|
|
|
54
56
|
3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
|
|
55
57
|
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`.
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
<!-- deploy:end -->
|
|
60
|
+
|
|
61
|
+
<!-- deploy:none -->
|
|
62
|
+
|
|
63
|
+
## Deploy
|
|
64
|
+
|
|
65
|
+
The project has no deploy step yet. A deploy step needs:
|
|
66
|
+
|
|
67
|
+
- a workflow under `.github/workflows/` that deploys;
|
|
68
|
+
- the `prod` environment under **Settings → Environments**, with the release managers as its required reviewers;
|
|
69
|
+
- the image the `Dockerfile` builds.
|
|
70
|
+
|
|
71
|
+
<!-- deploy:end -->
|
|
58
72
|
|
|
59
73
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow.
|
|
@@ -49,7 +49,12 @@ The `verifier` subagent starts the app with `pnpm dev`, which needs a PostgreSQL
|
|
|
49
49
|
When Claude makes a mistake twice, add the correction here.
|
|
50
50
|
|
|
51
51
|
- **Database for `pnpm dev` and end-to-end tests**: both need a PostgreSQL database in `DATABASE_URL` and `PAYLOAD_SECRET` in `.env`; the README lists every variable. Run `pnpm exec playwright install chromium` once before the first `pnpm test:e2e`.
|
|
52
|
+
<!-- deploy:railway -->
|
|
52
53
|
- **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod`, runs a Railway CLI command that names `prod`, or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it.
|
|
54
|
+
<!-- deploy:end -->
|
|
55
|
+
<!-- deploy:none -->
|
|
56
|
+
- **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod` or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool passes it.
|
|
57
|
+
<!-- deploy:end -->
|
|
53
58
|
- **Issues**: open an issue only to hand work to another person, never to give a pull request a number to close. A pull request closes an issue that already exists with `Closes #<issue>`.
|
|
54
59
|
- **What belongs in the repository**: files, commit messages, and pull requests hold the change, its reason, and the checks. Write no date, log, history, mistake count, token figure, or session note into a file.
|
|
55
60
|
- **Sourced requirements**: add a requirement to an intent, spec, or plan only when a person decided it or a named source says it. This includes a requirement a review bot suggests. When no person decided it and no source says it, leave it out and answer the thread with that.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Runs on any OCI platform
|
|
1
|
+
# Runs on any OCI container platform. A platform that builds Next.js itself deploys without it. The platform sets `PORT`.
|
|
2
2
|
FROM \
|
|
3
3
|
node:24.22.1-alpine@sha256:06d7adf3e0ef61a13f1d28682726366f97a15aa1888c934d3ecec86c33918b53 \
|
|
4
4
|
AS build
|
|
@@ -48,7 +48,7 @@ CI also runs the build, the end-to-end tests, the container image scan, and the
|
|
|
48
48
|
|
|
49
49
|
## Container
|
|
50
50
|
|
|
51
|
-
The `Dockerfile` builds a standalone production image that runs as the `node` user
|
|
51
|
+
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
52
|
|
|
53
53
|
```sh
|
|
54
54
|
docker build -t app .
|
|
@@ -61,6 +61,8 @@ The config sets no `email` adapter, so Payload writes every email, including pas
|
|
|
61
61
|
|
|
62
62
|
On Vercel, deploy without the image; the Next.js preset takes precedence over the `Dockerfile`.
|
|
63
63
|
|
|
64
|
+
<!-- deploy:railway -->
|
|
65
|
+
|
|
64
66
|
## Deploy
|
|
65
67
|
|
|
66
68
|
`.github/workflows/deploy.yml` deploys the `Dockerfile` to Railway and promotes one commit through `dev`, `uat`, and `prod`. `railway.json` sets the Railway health check to `/health`. The workflow stays off until you configure it:
|
|
@@ -82,6 +84,18 @@ On Vercel, deploy without the image; the Next.js preset takes precedence over th
|
|
|
82
84
|
3. Set the repository variable `DEPLOY_ENABLED` to `true`. Each push to `main` that passes CI then deploys to `dev`.
|
|
83
85
|
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`.
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
<!-- deploy:end -->
|
|
88
|
+
|
|
89
|
+
<!-- deploy:none -->
|
|
90
|
+
|
|
91
|
+
## Deploy
|
|
92
|
+
|
|
93
|
+
The project has no deploy step yet. A deploy step needs:
|
|
94
|
+
|
|
95
|
+
- a workflow under `.github/workflows/` that deploys;
|
|
96
|
+
- the `prod` environment under **Settings → Environments**, with the release managers as its required reviewers;
|
|
97
|
+
- the image the `Dockerfile` builds.
|
|
98
|
+
|
|
99
|
+
<!-- deploy:end -->
|
|
86
100
|
|
|
87
101
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow.
|
|
@@ -52,7 +52,12 @@ When Claude makes a mistake twice, add the correction here.
|
|
|
52
52
|
|
|
53
53
|
- **Next.js docs live per app**: this Next.js version differs from your training data. Read the guide in `apps/app/node_modules/next/dist/docs/` or `apps/backoffice/node_modules/next/dist/docs/` before writing Next.js code; the workspace root has no `next` package.
|
|
54
54
|
- **Database for `pnpm dev` and end-to-end tests**: the backoffice needs a PostgreSQL database in `DATABASE_URL` and `PAYLOAD_SECRET` in `.env`; the README lists every variable. Run `pnpm --filter ./apps/app exec playwright install chromium` once before the first `pnpm test:e2e`.
|
|
55
|
+
<!-- deploy:railway -->
|
|
55
56
|
- **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod`, runs a Railway CLI command that names `prod`, or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool or a Railway command without `-e` or `--environment` passes it.
|
|
57
|
+
<!-- deploy:end -->
|
|
58
|
+
<!-- deploy:none -->
|
|
59
|
+
- **Production gate**: `.agents/hooks/agent-production-gate.sh` blocks a shell command that deploys to `prod` or pushes a release tag, unless a release manager started the session with `RELEASE_APPROVAL` set. Any value lifts the gate except an empty value, `0`, `false`, and `no`, in any letter case. Report what is ready to promote instead. When a gated command appears only inside a message, write the text to a file with your file-editing tool, not a shell heredoc, and pass it with `git commit -F <file>` or `gh pr create --body-file <file>`. The gate reads only shell commands, so an MCP tool passes it.
|
|
60
|
+
<!-- deploy:end -->
|
|
56
61
|
- **Issues**: open an issue only to hand work to another person, never to give a pull request a number to close. A pull request closes an issue that already exists with `Closes #<issue>`.
|
|
57
62
|
- **What belongs in the repository**: files, commit messages, and pull requests hold the change, its reason, and the checks. Write no date, log, history, mistake count, token figure, or session note into a file.
|
|
58
63
|
- **Sourced requirements**: add a requirement to an intent, spec, or plan only when a person decided it or a named source says it. This includes a requirement a review bot suggests. When no person decided it and no source says it, leave it out and answer the thread with that.
|
|
@@ -61,6 +61,8 @@ Uploaded images, up to 10 MB each, are written to the backoffice container files
|
|
|
61
61
|
|
|
62
62
|
On Vercel, deploy an app without its image. The Next.js preset takes precedence over the Dockerfile.
|
|
63
63
|
|
|
64
|
+
<!-- deploy:railway -->
|
|
65
|
+
|
|
64
66
|
## Deploy
|
|
65
67
|
|
|
66
68
|
`.github/workflows/deploy.yml` deploys both apps and promotes one commit through `dev`, `uat`, and `prod`. `railway.json` sets the Railway health check to `/health`. Create one Railway service per app, with that service's Dockerfile path set to `apps/app/Dockerfile` or `apps/backoffice/Dockerfile`. The workflow stays off until you configure it:
|
|
@@ -84,7 +86,19 @@ On Vercel, deploy an app without its image. The Next.js preset takes precedence
|
|
|
84
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.
|
|
85
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.
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
<!-- deploy:end -->
|
|
90
|
+
|
|
91
|
+
<!-- deploy:none -->
|
|
92
|
+
|
|
93
|
+
## Deploy
|
|
94
|
+
|
|
95
|
+
The project has no deploy step yet. A deploy step needs:
|
|
96
|
+
|
|
97
|
+
- a workflow under `.github/workflows/` that deploys;
|
|
98
|
+
- the `prod` environment under **Settings → Environments**, with the release managers as its required reviewers;
|
|
99
|
+
- the image the `Dockerfile` builds.
|
|
100
|
+
|
|
101
|
+
<!-- deploy:end -->
|
|
88
102
|
|
|
89
103
|
## Mobile app
|
|
90
104
|
|
|
@@ -94,6 +108,6 @@ To add an Expo app, create it under `apps/mobile`:
|
|
|
94
108
|
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/)).
|
|
95
109
|
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/)).
|
|
96
110
|
|
|
97
|
-
EAS Build and Submit
|
|
111
|
+
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.
|
|
98
112
|
|
|
99
113
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Runs on any OCI platform
|
|
1
|
+
# Runs on any OCI container platform. A platform that builds Next.js itself deploys without it. The platform sets `PORT`.
|
|
2
2
|
FROM \
|
|
3
3
|
node:24.22.1-alpine@sha256:06d7adf3e0ef61a13f1d28682726366f97a15aa1888c934d3ecec86c33918b53 \
|
|
4
4
|
AS build
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Runs on any OCI platform
|
|
1
|
+
# Runs on any OCI container platform. A platform that builds Next.js itself deploys without it. The platform sets `PORT`.
|
|
2
2
|
FROM \
|
|
3
3
|
node:24.22.1-alpine@sha256:06d7adf3e0ef61a13f1d28682726366f97a15aa1888c934d3ecec86c33918b53 \
|
|
4
4
|
AS build
|
|
@@ -3,13 +3,7 @@
|
|
|
3
3
|
"futureFlags": {
|
|
4
4
|
"affectedUsingTaskInputs": true
|
|
5
5
|
},
|
|
6
|
-
"globalDependencies": [
|
|
7
|
-
".dockerignore",
|
|
8
|
-
".env",
|
|
9
|
-
"package.json",
|
|
10
|
-
"pnpm-workspace.yaml",
|
|
11
|
-
"railway.json"
|
|
12
|
-
],
|
|
6
|
+
"globalDependencies": [".dockerignore", ".env", "package.json", "pnpm-workspace.yaml"],
|
|
13
7
|
"globalPassThroughEnv": [
|
|
14
8
|
"APP_URL",
|
|
15
9
|
"CI",
|
|
File without changes
|
|
File without changes
|