create-stitchkit 0.3.3 → 0.4.1
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/CHANGELOG.md +347 -0
- package/README.md +3 -1
- package/UPGRADING.md +342 -0
- package/dist/cli.js +238 -42
- package/examples/repository/_env.example.append +21 -0
- package/examples/repository/packages/backend/src/domain/repository/github-cache.ts +2 -2
- package/examples/repository/packages/backend/src/surface.ts +1 -1
- package/examples/repository/packages/config/src/features.ts +17 -0
- package/examples/repository/packages/frontend/src/app/[locale]/page.tsx +4 -4
- package/examples/repository/packages/frontend/src/app/[locale]/starter-page.tsx +2 -2
- package/examples/repository/packages/frontend/src/app/api/[...path]/route.ts +66 -0
- package/examples/repository/packages/frontend/src/lib/api/client.ts +17 -12
- package/examples/repository/packages/frontend/src/lib/api/cross-origin.ts +87 -0
- package/examples/repository/packages/frontend/src/lib/api/place.ts +26 -0
- package/examples/repository/packages/frontend/src/lib/api/queries.ts +3 -0
- package/examples/repository/packages/frontend/src/lib/api/server-client.ts +11 -0
- package/examples/repository/packages/frontend/src/lib/realtime/repository.ts +44 -12
- package/examples/repository/packages/frontend/src/providers/client-providers.tsx +30 -0
- package/examples/repository/packages/frontend/src/providers/index.tsx +17 -12
- package/examples/repository/packages/frontend/src/providers/realtime.tsx +6 -4
- package/examples/repository/project.json +189 -0
- package/examples/repository/scripts/runtime-smoke.ts +38 -6
- package/package.json +12 -2
- package/template/AGENTS.md +23 -3
- package/template/README.md +83 -8
- package/template/_env.example +16 -4
- package/template/_gitignore +1 -0
- package/template/biome.json +6 -2
- package/template/bun.lock +115 -98
- package/template/e2e/starter.spec.ts +5 -7
- package/template/ecosystem.config.cjs +42 -19
- package/template/ecosystem.dev.config.cjs +41 -21
- package/template/package.json +12 -10
- package/template/packages/backend/package.json +2 -2
- package/template/packages/backend/src/cleanup.ts +121 -0
- package/template/packages/backend/src/cli.ts +6 -2
- package/template/packages/backend/src/index.ts +33 -8
- package/template/packages/backend/src/surface.ts +6 -1
- package/template/packages/backend/src/transport/errors.ts +4 -2
- package/template/packages/config/package.json +6 -2
- package/template/packages/config/src/app-identity.generated.ts +20 -0
- package/template/packages/config/src/declaration.ts +30 -0
- package/template/packages/config/src/server.ts +8 -17
- package/template/packages/config/src/shutdown.ts +20 -0
- package/template/packages/config/src/variables.ts +89 -0
- package/template/packages/db/package.json +2 -2
- package/template/packages/frontend/next.config.ts +3 -2
- package/template/packages/frontend/package.json +13 -13
- package/template/packages/frontend/scripts/serve.ts +70 -0
- package/template/packages/frontend/src/app/[locale]/layout.tsx +9 -8
- package/template/packages/frontend/src/app/[locale]/page.tsx +2 -2
- package/template/packages/frontend/src/app/[locale]/starter-page.tsx +2 -2
- package/template/packages/frontend/src/app/[locale]/ui/[story]/page.tsx +1 -1
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/landing-showcase.tsx +1 -1
- package/template/packages/frontend/src/app/robots.ts +4 -2
- package/template/packages/frontend/src/app/sitemap.ts +7 -19
- package/template/packages/frontend/src/components/ui/toaster.tsx +4 -1
- package/template/packages/frontend/src/env.ts +27 -8
- package/template/packages/frontend/src/lib/seo/cache-by-origin.test.ts +68 -0
- package/template/packages/frontend/src/lib/seo/cache-by-origin.ts +40 -0
- package/template/packages/frontend/src/lib/seo/metadata.ts +68 -11
- package/template/packages/frontend/src/lib/seo/pages.ts +1 -1
- package/template/packages/frontend/src/lib/seo/request-origin.ts +89 -0
- package/template/packages/frontend/src/theme/config.ts +1 -1
- package/template/packages/frontend/tsconfig.json +10 -3
- package/template/packages/shared/package.json +1 -1
- package/template/playwright.config.ts +1 -1
- package/template/project.json +169 -0
- package/template/scripts/acceptance-database.test.ts +73 -0
- package/template/scripts/acceptance-database.ts +92 -0
- package/template/scripts/acceptance-local.ts +144 -0
- package/template/scripts/build-inputs.test.ts +69 -0
- package/template/scripts/build-inputs.ts +58 -0
- package/template/scripts/build-stamp.test.ts +151 -0
- package/template/scripts/build-stamp.ts +169 -0
- package/template/scripts/check-authored.ts +18 -2
- package/template/scripts/client-boundary.test.ts +117 -0
- package/template/scripts/client-boundary.ts +148 -0
- package/template/scripts/declaration.test.ts +206 -0
- package/template/scripts/declaration.ts +271 -0
- package/template/scripts/deployment-preflight.ts +41 -0
- package/template/scripts/dev.ts +43 -20
- package/template/scripts/local-env.test.ts +2 -2
- package/template/scripts/local-env.ts +9 -3
- package/template/scripts/readiness.ts +92 -0
- package/template/scripts/release-steps.test.ts +87 -0
- package/template/scripts/release-steps.ts +112 -0
- package/template/scripts/release.ts +38 -0
- package/template/scripts/runtime-smoke.test.ts +178 -0
- package/template/scripts/runtime-smoke.ts +21 -5
- package/template/scripts/serve-mode.test.ts +36 -0
- package/template/scripts/shutdown-budget.fixture.ts +29 -0
- package/template/scripts/shutdown-budget.test.ts +164 -0
- package/template/scripts/supervision-signal.test.ts +94 -0
- package/template/scripts/surface-conformance.ts +8 -1
- package/template/scripts/tooling-env.ts +35 -3
- package/template/scripts/web-surface-smoke.ts +183 -2
- package/template/app.config.json +0 -9
- package/template/packages/config/src/identity.ts +0 -18
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "application",
|
|
4
|
+
"identity": {
|
|
5
|
+
"slug": "stitchkit-starter",
|
|
6
|
+
"name": "Stitchkit Starter",
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"description": {
|
|
9
|
+
"en": "Stitchkit Starter is a production application built with Stitchkit.",
|
|
10
|
+
"ru": "Stitchkit Starter — production-приложение на Stitchkit."
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"roles": [
|
|
14
|
+
{
|
|
15
|
+
"name": "api",
|
|
16
|
+
"workingDirectory": "packages/backend",
|
|
17
|
+
"commands": {
|
|
18
|
+
"development": {
|
|
19
|
+
"executable": "bun",
|
|
20
|
+
"args": [
|
|
21
|
+
"--watch",
|
|
22
|
+
"src/index.ts"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"production": {
|
|
26
|
+
"executable": "bun",
|
|
27
|
+
"args": [
|
|
28
|
+
"dist/index.js"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"listener": {
|
|
33
|
+
"portVariable": "API_PORT",
|
|
34
|
+
"bindVariable": "BIND_HOST",
|
|
35
|
+
"readinessPath": "/health"
|
|
36
|
+
},
|
|
37
|
+
"drainFloorMs": 15000
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "web",
|
|
41
|
+
"workingDirectory": "packages/frontend",
|
|
42
|
+
"commands": {
|
|
43
|
+
"development": {
|
|
44
|
+
"executable": "bun",
|
|
45
|
+
"args": [
|
|
46
|
+
"scripts/serve.ts",
|
|
47
|
+
"development"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"production": {
|
|
51
|
+
"executable": "bun",
|
|
52
|
+
"args": [
|
|
53
|
+
"scripts/serve.ts",
|
|
54
|
+
"production"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"listener": {
|
|
59
|
+
"portVariable": "WEB_PORT",
|
|
60
|
+
"bindVariable": "BIND_HOST",
|
|
61
|
+
"readinessPath": "/"
|
|
62
|
+
},
|
|
63
|
+
"drainFloorMs": 5000
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"build": {
|
|
67
|
+
"command": {
|
|
68
|
+
"executable": "bun",
|
|
69
|
+
"args": [
|
|
70
|
+
"run",
|
|
71
|
+
"build"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"artifacts": [
|
|
75
|
+
"packages/backend/dist",
|
|
76
|
+
"packages/frontend/.next",
|
|
77
|
+
"packages/db/src/generated"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"requires": [
|
|
81
|
+
{
|
|
82
|
+
"name": "postgres",
|
|
83
|
+
"phases": [
|
|
84
|
+
"release",
|
|
85
|
+
"start"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"release": {
|
|
90
|
+
"migrations": {
|
|
91
|
+
"engine": "prisma",
|
|
92
|
+
"root": "packages/db/migrations",
|
|
93
|
+
"lockfile": "packages/db/migrations/migration_lock.toml"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"env": {
|
|
97
|
+
"variables": [
|
|
98
|
+
{
|
|
99
|
+
"name": "API_PORT",
|
|
100
|
+
"shape": "integer",
|
|
101
|
+
"required": true
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "BIND_HOST",
|
|
105
|
+
"shape": "string",
|
|
106
|
+
"required": false
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "CORS_ORIGIN",
|
|
110
|
+
"shape": "url",
|
|
111
|
+
"required": false
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "DATABASE_URL",
|
|
115
|
+
"shape": "url",
|
|
116
|
+
"required": true
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "GITHUB_API_URL",
|
|
120
|
+
"shape": "url",
|
|
121
|
+
"required": false
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "GITHUB_CACHE_TTL_SECONDS",
|
|
125
|
+
"shape": "integer",
|
|
126
|
+
"required": false
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "GITHUB_REPOSITORY",
|
|
130
|
+
"shape": "string",
|
|
131
|
+
"required": true
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"name": "GITHUB_TOKEN",
|
|
135
|
+
"shape": "string",
|
|
136
|
+
"required": false
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "INTERNAL_API_URL",
|
|
140
|
+
"shape": "url",
|
|
141
|
+
"required": true
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"name": "LOG_FORMAT",
|
|
145
|
+
"shape": "enum",
|
|
146
|
+
"required": false,
|
|
147
|
+
"members": [
|
|
148
|
+
"pretty",
|
|
149
|
+
"json"
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"name": "NODE_ENV",
|
|
154
|
+
"shape": "enum",
|
|
155
|
+
"required": false,
|
|
156
|
+
"members": [
|
|
157
|
+
"development",
|
|
158
|
+
"test",
|
|
159
|
+
"production"
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "PUBLIC_API_ORIGIN",
|
|
164
|
+
"shape": "url",
|
|
165
|
+
"required": false
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "PUBLIC_REALTIME_ORIGIN",
|
|
169
|
+
"shape": "url",
|
|
170
|
+
"required": false
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "PUBLIC_WEB_HOSTS",
|
|
174
|
+
"shape": "string",
|
|
175
|
+
"required": false
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "PUBLIC_WEB_ORIGIN",
|
|
179
|
+
"shape": "url",
|
|
180
|
+
"required": false
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "WEB_PORT",
|
|
184
|
+
"shape": "integer",
|
|
185
|
+
"required": true
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { RepositorySnapshotSchema, repositoryRealtimeContract } from '@app/shared';
|
|
2
2
|
import { createRealtimeClient, defineRealtimeContract } from 'stitchkit';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import { assertDeploymentIsAnswering } from './deployment-preflight';
|
|
4
5
|
import { defineSurfaceProbe, runSurfaceConformance } from './surface-conformance';
|
|
5
6
|
import { loadToolingEnv } from './tooling-env';
|
|
6
|
-
import { assertPublicWebSurface } from './web-surface-smoke';
|
|
7
|
+
import { assertArtifactIsPlacementFree, assertPublicWebSurface } from './web-surface-smoke';
|
|
7
8
|
|
|
8
9
|
const toolingEnv = loadToolingEnv();
|
|
9
|
-
const apiOrigin = toolingEnv.
|
|
10
|
+
const apiOrigin = toolingEnv.SMOKE_API_ORIGIN;
|
|
11
|
+
|
|
12
|
+
await assertDeploymentIsAnswering({
|
|
13
|
+
'the API role': apiOrigin,
|
|
14
|
+
'the web role': toolingEnv.SMOKE_WEB_ORIGIN,
|
|
15
|
+
});
|
|
10
16
|
|
|
11
17
|
async function json(path: string, init?: RequestInit): Promise<unknown> {
|
|
12
|
-
const response = await fetch(`${apiOrigin}${path}`,
|
|
18
|
+
const response = await fetch(`${apiOrigin}${path}`, {
|
|
19
|
+
...init,
|
|
20
|
+
signal: AbortSignal.timeout(30_000),
|
|
21
|
+
});
|
|
13
22
|
if (!response.ok)
|
|
14
23
|
throw new Error(`${init?.method ?? 'GET'} ${path} returned ${response.status}`);
|
|
15
24
|
return response.json();
|
|
@@ -155,7 +164,7 @@ await runSurfaceConformance({
|
|
|
155
164
|
});
|
|
156
165
|
if (
|
|
157
166
|
corsResponse.headers.get('access-control-allow-origin') !==
|
|
158
|
-
new URL(toolingEnv.
|
|
167
|
+
new URL(toolingEnv.SMOKE_WEB_ORIGIN).origin
|
|
159
168
|
) {
|
|
160
169
|
throw new Error('API CORS origin differs from the configured web origin');
|
|
161
170
|
}
|
|
@@ -167,6 +176,29 @@ await runSurfaceConformance({
|
|
|
167
176
|
}
|
|
168
177
|
}
|
|
169
178
|
|
|
170
|
-
|
|
179
|
+
{
|
|
180
|
+
// THE DEFAULT PATH, end to end: the browser's own origin answers `/api/…`
|
|
181
|
+
// because the web role forwards it. This is what makes the example's client a
|
|
182
|
+
// module constant, so it is checked rather than described.
|
|
183
|
+
const proxied = await fetch(`${toolingEnv.SMOKE_WEB_ORIGIN}/api/repository`);
|
|
184
|
+
if (!proxied.ok) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`The web role did not forward /api/repository (${proxied.status}) — the same-origin default is broken`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const sameOrigin = RepositorySnapshotSchema.parse(await proxied.json());
|
|
190
|
+
const direct = RepositorySnapshotSchema.parse(await json('/api/repository/'));
|
|
191
|
+
if (sameOrigin.fullName !== direct.fullName) {
|
|
192
|
+
throw new Error("the forwarded answer differs from the API role's own");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
await assertPublicWebSurface(toolingEnv.SMOKE_WEB_ORIGIN);
|
|
197
|
+
await assertArtifactIsPlacementFree(toolingEnv.SMOKE_WEB_ORIGIN, {
|
|
198
|
+
origin: toolingEnv.PUBLIC_WEB_ORIGIN,
|
|
199
|
+
hosts: toolingEnv.PUBLIC_WEB_HOSTS,
|
|
200
|
+
});
|
|
171
201
|
|
|
172
|
-
console.log(
|
|
202
|
+
console.log(
|
|
203
|
+
'Runtime HTTP (same-origin and direct), OpenAPI, Socket.IO, MCP and public web smoke passed',
|
|
204
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-stitchkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Create a production-shaped Stitchkit application",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Max Listov <maxlistov@gmail.com>",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"template/**/*",
|
|
19
19
|
"examples/**/*",
|
|
20
20
|
"!template/**/.env",
|
|
21
|
+
"!template/**/.build-stamp.json",
|
|
21
22
|
"!template/**/node_modules/**",
|
|
22
23
|
"!template/**/.next/**",
|
|
23
24
|
"!template/**/dist/**",
|
|
@@ -27,13 +28,22 @@
|
|
|
27
28
|
"!template/**/src/generated/**",
|
|
28
29
|
"!template/**/*.log",
|
|
29
30
|
"!template/**/*.tsbuildinfo",
|
|
31
|
+
"!template/**/coverage/**",
|
|
32
|
+
"!examples/**/.env",
|
|
33
|
+
"!examples/**/.build-stamp.json",
|
|
30
34
|
"!examples/**/node_modules/**",
|
|
31
35
|
"!examples/**/.next/**",
|
|
32
36
|
"!examples/**/dist/**",
|
|
37
|
+
"!examples/**/coverage/**",
|
|
38
|
+
"!examples/**/playwright-report/**",
|
|
39
|
+
"!examples/**/test-results/**",
|
|
40
|
+
"!examples/**/next-env.d.ts",
|
|
41
|
+
"!examples/**/src/generated/**",
|
|
33
42
|
"!examples/**/*.log",
|
|
34
43
|
"!examples/**/*.tsbuildinfo",
|
|
35
44
|
"README.md",
|
|
36
45
|
"CHANGELOG.md",
|
|
46
|
+
"UPGRADING.md",
|
|
37
47
|
"LICENSE"
|
|
38
48
|
],
|
|
39
49
|
"publishConfig": {
|
|
@@ -49,7 +59,7 @@
|
|
|
49
59
|
"zod": "^4.4.3"
|
|
50
60
|
},
|
|
51
61
|
"devDependencies": {
|
|
52
|
-
"@types/bun": "^1.
|
|
62
|
+
"@types/bun": "^1.4.0",
|
|
53
63
|
"typescript": "^7.0.2"
|
|
54
64
|
},
|
|
55
65
|
"engines": {
|
package/template/AGENTS.md
CHANGED
|
@@ -12,8 +12,19 @@ framework source repository.
|
|
|
12
12
|
owns application policy. Routes remain thin transport boundaries.
|
|
13
13
|
- `packages/frontend` owns Next.js pages, typed clients, query/mutation hooks and
|
|
14
14
|
cache reactions.
|
|
15
|
-
- `packages/config/src/
|
|
16
|
-
`
|
|
15
|
+
- `packages/config/src/variables.ts` is the only declaration of an environment
|
|
16
|
+
variable. `server.ts` and `frontend/src/env.ts` are projections of it, and
|
|
17
|
+
the declaration's `env.variables` is derived from it — never restated.
|
|
18
|
+
- `project.json` is the only place this project describes itself. It is written
|
|
19
|
+
by machine — the scaffolder stamps the identity, `bun run gen:declaration`
|
|
20
|
+
derives `env.variables` — so the formatter leaves it alone and
|
|
21
|
+
`scripts/declaration.test.ts` is what checks it.
|
|
22
|
+
- Four things are generated from it and must not be hand-edited:
|
|
23
|
+
`ecosystem.config.cjs`, `ecosystem.dev.config.cjs`,
|
|
24
|
+
`packages/config/src/app-identity.generated.ts` and the `env.variables` block
|
|
25
|
+
of `project.json`. Run `bun run gen:declaration` after changing a role. It
|
|
26
|
+
holds nothing that differs between two deployments; those are named there by
|
|
27
|
+
variable and supplied by the place.
|
|
17
28
|
|
|
18
29
|
Dependencies point inward: frontend/backend → shared; backend → db/config.
|
|
19
30
|
Shared never imports an application runtime package.
|
|
@@ -48,6 +59,15 @@ vertical path. Before handing work off, run:
|
|
|
48
59
|
bun run check
|
|
49
60
|
bun run test
|
|
50
61
|
bun run build
|
|
51
|
-
bun run
|
|
62
|
+
bun run acceptance:local
|
|
52
63
|
```
|
|
53
64
|
|
|
65
|
+
`acceptance:local` is part of the list because `runtime:smoke` and `e2e` check a
|
|
66
|
+
running deployment: it creates one of its own — separate PM2 home, ephemeral
|
|
67
|
+
ports, and its own database from `ACCEPTANCE_DATABASE_URL` — runs both against
|
|
68
|
+
it, and destroys it. The separate database is not tidiness: the gates write, so
|
|
69
|
+
one borrowing `DATABASE_URL` writes rows wherever `.env` points. **Never put
|
|
70
|
+
`pm2:prod` in this list.** It applies the declared migrations to *your* database
|
|
71
|
+
and reloads the running deployment; deploying is its own command, asked for on
|
|
72
|
+
purpose, and no gate performs it.
|
|
73
|
+
|
package/template/README.md
CHANGED
|
@@ -2,9 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Production-shaped application generated by `create-stitchkit`.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
This project describes itself in [`project.json`](project.json) — its
|
|
6
|
+
**declaration**: identity, the roles it runs, what it builds, what it needs
|
|
7
|
+
before it starts, and the environment variables a deployment must supply.
|
|
8
|
+
|
|
9
|
+
The declaration is true **with no machine in existence**. A field you cannot
|
|
10
|
+
fill in without knowing where the code will run is a *binding*, not a
|
|
11
|
+
declaration: ports, hosts, addresses, machine paths and supervision policy are
|
|
12
|
+
named there by variable and never by value. The schema has no field that asks
|
|
13
|
+
for one, so nothing in it ever requires a value of the place; where a value
|
|
14
|
+
could still be written into a free-text field, a filter refuses the known
|
|
15
|
+
shapes of a machine name. Change the slug, display name, version or description there and package
|
|
16
|
+
names, process names, MCP/OpenAPI identity, UI copy and SEO follow.
|
|
17
|
+
|
|
18
|
+
Four things are **generated** from it — `ecosystem.config.cjs`,
|
|
19
|
+
`ecosystem.dev.config.cjs`, `packages/config/src/app-identity.generated.ts` and
|
|
20
|
+
the `env.variables` block of the declaration itself. Run
|
|
21
|
+
`bun run gen:declaration` after changing a role; the test suite refuses a stale
|
|
22
|
+
copy.
|
|
8
23
|
|
|
9
24
|
## Start
|
|
10
25
|
|
|
@@ -35,6 +50,27 @@ Configure it with `GITHUB_REPOSITORY`; `GITHUB_TOKEN` is optional.
|
|
|
35
50
|
Repository visibility then demonstrates the canonical Prisma enum → shared Zod schema
|
|
36
51
|
→ HTTP/UI path without duplicating its allowed values.
|
|
37
52
|
|
|
53
|
+
**How the browser reaches the API, and what each shape costs.** By default it
|
|
54
|
+
does not reach it at all: the browser calls its own origin (`/api/…`), the web
|
|
55
|
+
role forwards to the API role (`packages/frontend/src/app/api/[...path]/route.ts`),
|
|
56
|
+
and the client is a plain module constant — no address to wait for, no lazy
|
|
57
|
+
accessor, nothing to order relative to `<Providers>`. A single-origin deployment
|
|
58
|
+
supplies only `INTERNAL_API_URL`.
|
|
59
|
+
|
|
60
|
+
Two things can pull a deployment out of that, and they are separate questions,
|
|
61
|
+
so they have separate variables. **`PUBLIC_REALTIME_ORIGIN`** is the socket: a
|
|
62
|
+
WebSocket upgrade does not survive the route handler that forwards `/api`, so
|
|
63
|
+
two roles on two ports with nothing in front of them must name the socket's
|
|
64
|
+
origin even though their HTTP is already same-origin. Behind one routing layer
|
|
65
|
+
that forwards `/socket.io`, leave it unset. **`PUBLIC_API_ORIGIN`** is HTTP, for
|
|
66
|
+
a frontend that genuinely dials the API role itself — and setting it changes
|
|
67
|
+
nothing on its own: switching is one import in
|
|
68
|
+
`packages/frontend/src/lib/api/queries.ts`, documented in
|
|
69
|
+
`packages/frontend/src/lib/api/cross-origin.ts`. That variant costs a
|
|
70
|
+
server-delivered address, a client built on first use (hence the parentheses),
|
|
71
|
+
an ordering requirement relative to `<Providers>`, and `CORS_ORIGIN` on the API
|
|
72
|
+
role.
|
|
73
|
+
|
|
38
74
|
The `/en/ui` catalogue is isolated under
|
|
39
75
|
`packages/frontend/src/app/[locale]/ui` and can be removed as one directory;
|
|
40
76
|
reusable primitives remain in `components/ui`. It renders the complete
|
|
@@ -54,22 +90,61 @@ Unsupported browsers and users requesting reduced motion switch immediately.
|
|
|
54
90
|
bun run check
|
|
55
91
|
bun run test
|
|
56
92
|
bun run build
|
|
57
|
-
bun run
|
|
58
|
-
bun run e2e
|
|
93
|
+
bun run acceptance:local
|
|
59
94
|
```
|
|
60
95
|
|
|
96
|
+
Top to bottom, in one terminal, and none of it touches a deployment. They
|
|
97
|
+
assume your development database is already set up — see [Start](#start).
|
|
98
|
+
|
|
99
|
+
`acceptance:local` is the last one because `runtime:smoke` and `e2e` check a
|
|
100
|
+
**running** deployment rather than a source tree — so it creates one and
|
|
101
|
+
destroys it: its own PM2 home, ephemeral ports, its own public-host allowlist,
|
|
102
|
+
and a stop that names the roles the declaration declares. It reloads nothing
|
|
103
|
+
you are running.
|
|
104
|
+
|
|
105
|
+
It also brings its own database, `ACCEPTANCE_DATABASE_URL`, and applies this
|
|
106
|
+
project's migrations to that — not to yours. The gates WRITE (the repository
|
|
107
|
+
example's smoke posts a refresh, and that upserts), so borrowing `DATABASE_URL`
|
|
108
|
+
would make a gate a writer in whatever database your `.env` names. It refuses to
|
|
109
|
+
start when the variable is unset or names the same database, and says what to
|
|
110
|
+
add. It needs `pm2` (see [Requirements](#requirements)).
|
|
111
|
+
|
|
112
|
+
Deploying is a separate, deliberate command — `bun run pm2:prod` under
|
|
113
|
+
[Production](#production) — and it is not a gate.
|
|
114
|
+
|
|
115
|
+
To run the two runtime gates against a deployment that already exists somewhere,
|
|
116
|
+
point `SMOKE_API_ORIGIN` / `SMOKE_WEB_ORIGIN` at it and call `bun run
|
|
117
|
+
runtime:smoke` / `bun run e2e` directly. `runtime:smoke` asks the web role to
|
|
118
|
+
answer as two of the addresses `PUBLIC_WEB_HOSTS` claims, to prove one artifact
|
|
119
|
+
serves many; a deployment claiming fewer than two addresses besides the one
|
|
120
|
+
being dialled is told exactly what to add rather than failing on a refused host.
|
|
121
|
+
|
|
122
|
+
## Requirements
|
|
123
|
+
|
|
124
|
+
- **Bun** and **Node ≥ 22**.
|
|
125
|
+
- **PostgreSQL** — external infrastructure, in development and production
|
|
126
|
+
alike. This project owns its schema and migrations, never the database
|
|
127
|
+
process.
|
|
128
|
+
- **PM2** on `PATH` (`bun add --global pm2`) for `bun run dev`,
|
|
129
|
+
`bun run acceptance:local` and `bun run pm2:prod`.
|
|
130
|
+
- **Playwright browsers** (`bunx playwright install`) for `bun run e2e`.
|
|
131
|
+
|
|
61
132
|
## Production
|
|
62
133
|
|
|
63
134
|
Provide a production `.env`, then:
|
|
64
135
|
|
|
65
136
|
```bash
|
|
66
|
-
bun run db:deploy
|
|
67
137
|
bun run build
|
|
68
138
|
bun run pm2:prod
|
|
69
139
|
```
|
|
70
140
|
|
|
71
|
-
|
|
72
|
-
|
|
141
|
+
`pm2:prod` is the release: it checks every artifact `build.artifacts` declares,
|
|
142
|
+
applies the migrations `release.migrations` declares, and only then starts the
|
|
143
|
+
roles from the generated supervision file. There is no separate `db:deploy`
|
|
144
|
+
step to remember — the declaration already says the migrations exist, and one
|
|
145
|
+
place saying it is the point.
|
|
146
|
+
|
|
147
|
+
The roles are separate processes and can also be deployed independently.
|
|
73
148
|
|
|
74
149
|
PostgreSQL is external infrastructure in development and production. The
|
|
75
150
|
application owns its schema and migrations; the environment owns the database
|
package/template/_env.example
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
NODE_ENV=development
|
|
2
2
|
DATABASE_URL=postgresql://USER:PASSWORD@127.0.0.1:5432/stitchkit_starter
|
|
3
|
+
# The throwaway database `bun run acceptance:local` creates and writes to. The
|
|
4
|
+
# runtime gates WRITE, so they get one of their own: the harness refuses to
|
|
5
|
+
# start if this is unset or names the database above.
|
|
6
|
+
ACCEPTANCE_DATABASE_URL=postgresql://USER:PASSWORD@127.0.0.1:5432/stitchkit_starter_acceptance
|
|
3
7
|
# 0.0.0.0 exposes the app to every network interface — opt in consciously.
|
|
4
8
|
BIND_HOST=127.0.0.1
|
|
5
9
|
API_PORT=3211
|
|
6
10
|
WEB_PORT=3210
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
SMOKE_API_ORIGIN=http://127.0.0.1:3211
|
|
12
|
+
SMOKE_WEB_ORIGIN=http://127.0.0.1:3210
|
|
13
|
+
# Hosts this deployment answers for, comma-separated. One built artifact can
|
|
14
|
+
# serve several addresses; a forwarded host outside this list is refused rather
|
|
15
|
+
# than believed. Leave unset and set PUBLIC_WEB_ORIGIN instead for a single one.
|
|
16
|
+
#
|
|
17
|
+
# `bun run acceptance:local` supplies its own list to the deployment it creates,
|
|
18
|
+
# so the addresses the portability check needs are not policy this project
|
|
19
|
+
# carries. List here only the hosts this deployment really answers for.
|
|
20
|
+
PUBLIC_WEB_HOSTS=127.0.0.1:3210
|
|
10
21
|
LOG_FORMAT=pretty
|
|
11
|
-
CORS_ORIGIN
|
|
22
|
+
# CORS_ORIGIN is only needed for a genuinely cross-origin browser.
|
|
23
|
+
# CORS_ORIGIN=http://127.0.0.1:3210
|
package/template/_gitignore
CHANGED
package/template/biome.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://biomejs.dev/schemas/2.5.
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.5.10/schema.json",
|
|
3
3
|
"files": {
|
|
4
4
|
"includes": [
|
|
5
5
|
"**",
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
"!!playwright-report",
|
|
10
10
|
"!!test-results",
|
|
11
11
|
"!!packages/frontend/next-env.d.ts",
|
|
12
|
-
"!!packages/frontend/public"
|
|
12
|
+
"!!packages/frontend/public",
|
|
13
|
+
"!!project.json",
|
|
14
|
+
"!!ecosystem.config.cjs",
|
|
15
|
+
"!!ecosystem.dev.config.cjs",
|
|
16
|
+
"!!packages/config/src/app-identity.generated.ts"
|
|
13
17
|
]
|
|
14
18
|
},
|
|
15
19
|
"formatter": {
|