create-stitchkit 0.3.2 → 0.4.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/CHANGELOG.md +243 -0
- package/README.md +3 -1
- package/UPGRADING.md +225 -0
- package/dist/cli.js +233 -41
- 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 +25 -5
- package/package.json +9 -1
- package/template/AGENTS.md +15 -2
- package/template/README.md +51 -6
- package/template/_env.example +10 -4
- package/template/biome.json +5 -1
- package/template/bun.lock +2 -2
- package/template/e2e/starter.spec.ts +5 -7
- package/template/ecosystem.config.cjs +42 -13
- package/template/ecosystem.dev.config.cjs +41 -15
- package/template/package.json +5 -4
- package/template/packages/backend/package.json +1 -1
- package/template/packages/backend/scripts/ensure-built.ts +7 -0
- package/template/packages/backend/src/cli.ts +6 -2
- package/template/packages/backend/src/index.ts +23 -7
- package/template/packages/backend/src/surface.ts +6 -1
- package/template/packages/backend/src/transport/errors.ts +4 -2
- package/template/packages/backend/tsconfig.json +1 -1
- package/template/packages/config/package.json +3 -1
- 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/project-declaration.generated.ts +611 -0
- package/template/packages/config/src/server.ts +8 -14
- package/template/packages/config/src/variables.ts +89 -0
- package/template/packages/frontend/next.config.ts +3 -2
- package/template/packages/frontend/package.json +2 -2
- 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/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 +2 -2
- 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/playwright.config.ts +1 -1
- package/template/project.json +169 -0
- package/template/scripts/build-inputs.test.ts +69 -0
- package/template/scripts/build-inputs.ts +57 -0
- package/template/scripts/check-authored.ts +18 -2
- package/template/scripts/declaration.test.ts +206 -0
- package/template/scripts/declaration.ts +268 -0
- package/template/scripts/dev.ts +84 -15
- package/template/scripts/local-env.test.ts +2 -2
- package/template/scripts/local-env.ts +3 -3
- package/template/scripts/release-steps.test.ts +87 -0
- package/template/scripts/release-steps.ts +108 -0
- package/template/scripts/release.ts +30 -0
- package/template/scripts/runtime-smoke.ts +7 -4
- package/template/scripts/serve-mode.test.ts +36 -0
- package/template/scripts/supervision-signal.test.ts +94 -0
- package/template/scripts/tooling-env.ts +5 -2
- package/template/scripts/web-surface-smoke.ts +70 -0
- package/template/app.config.json +0 -9
- package/template/packages/config/src/identity.ts +0 -18
|
@@ -1,28 +1,57 @@
|
|
|
1
|
+
// GENERATED FILE — do not edit.
|
|
2
|
+
//
|
|
3
|
+
// Rendered from `project.json` by `scripts/declaration.ts`; run
|
|
4
|
+
// `bun run gen:declaration` after changing a role. Roles, commands and the
|
|
5
|
+
// drain floor come from the declaration because they are true of the code;
|
|
6
|
+
// restart policy and the kill timeout are this machine's, and the generator
|
|
7
|
+
// refuses a timeout shorter than any role's full shutdown budget.
|
|
1
8
|
const path = require('node:path');
|
|
2
9
|
const { config } = require('dotenv');
|
|
3
|
-
const
|
|
10
|
+
const declaration = require('./project.json');
|
|
4
11
|
|
|
5
|
-
|
|
12
|
+
// NOT `override`: an environment a deployment injected into this process must
|
|
13
|
+
// win over a file in the repository. The file fills gaps; it does not overrule
|
|
14
|
+
// the place.
|
|
15
|
+
config({ path: path.join(__dirname, '.env'), quiet: true });
|
|
6
16
|
|
|
7
17
|
module.exports = {
|
|
8
18
|
apps: [
|
|
9
19
|
{
|
|
10
|
-
name: `${identity.slug}-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
name: `${declaration.identity.slug}-api`,
|
|
21
|
+
// The role's OWN process, in its OWN directory — no launcher in between.
|
|
22
|
+
// Measured: a launcher makes the role see the stop signal twice (once from
|
|
23
|
+
// the supervisor, once forwarded), the second press forces the shutdown,
|
|
24
|
+
// and a declared drain of seconds collapses to milliseconds. A workspace
|
|
25
|
+
// filter is worse: the signal never arrives at all.
|
|
26
|
+
cwd: path.join(__dirname, "packages/backend"),
|
|
27
|
+
script: "bun",
|
|
28
|
+
// No argv invented here: the deployment injects `API_PORT` and the command
|
|
29
|
+
// reads it. Serialised rather than concatenated — an argument with a space
|
|
30
|
+
// or a quote has to survive this file intact.
|
|
31
|
+
args: ["dist/index.js"],
|
|
32
|
+
interpreter: 'none',
|
|
14
33
|
autorestart: true,
|
|
15
|
-
|
|
34
|
+
// >= this role's full shutdown budget of 25000ms.
|
|
35
|
+
kill_timeout: 30000,
|
|
16
36
|
env: { NODE_ENV: 'production' },
|
|
17
37
|
},
|
|
18
38
|
{
|
|
19
|
-
name: `${identity.slug}-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
name: `${declaration.identity.slug}-web`,
|
|
40
|
+
// The role's OWN process, in its OWN directory — no launcher in between.
|
|
41
|
+
// Measured: a launcher makes the role see the stop signal twice (once from
|
|
42
|
+
// the supervisor, once forwarded), the second press forces the shutdown,
|
|
43
|
+
// and a declared drain of seconds collapses to milliseconds. A workspace
|
|
44
|
+
// filter is worse: the signal never arrives at all.
|
|
45
|
+
cwd: path.join(__dirname, "packages/frontend"),
|
|
46
|
+
script: "bun",
|
|
47
|
+
// No argv invented here: the deployment injects `WEB_PORT` and the command
|
|
48
|
+
// reads it. Serialised rather than concatenated — an argument with a space
|
|
49
|
+
// or a quote has to survive this file intact.
|
|
50
|
+
args: ["scripts/serve.ts","production"],
|
|
51
|
+
interpreter: 'none',
|
|
24
52
|
autorestart: true,
|
|
25
|
-
|
|
53
|
+
// >= this role's full shutdown budget of 15000ms.
|
|
54
|
+
kill_timeout: 30000,
|
|
26
55
|
env: { NODE_ENV: 'production' },
|
|
27
56
|
},
|
|
28
57
|
],
|
|
@@ -1,31 +1,57 @@
|
|
|
1
|
+
// GENERATED FILE — do not edit.
|
|
2
|
+
//
|
|
3
|
+
// Rendered from `project.json` by `scripts/declaration.ts`; run
|
|
4
|
+
// `bun run gen:declaration` after changing a role. Roles, commands and the
|
|
5
|
+
// drain floor come from the declaration because they are true of the code;
|
|
6
|
+
// restart policy and the kill timeout are this machine's, and the generator
|
|
7
|
+
// refuses a timeout shorter than any role's full shutdown budget.
|
|
1
8
|
const path = require('node:path');
|
|
2
9
|
const { config } = require('dotenv');
|
|
3
|
-
const
|
|
10
|
+
const declaration = require('./project.json');
|
|
4
11
|
|
|
12
|
+
// NOT `override`: an environment a deployment injected into this process must
|
|
13
|
+
// win over a file in the repository. The file fills gaps; it does not overrule
|
|
14
|
+
// the place.
|
|
5
15
|
config({ path: path.join(__dirname, '.env'), quiet: true });
|
|
6
16
|
|
|
7
|
-
const frontendArgs = ['dev', '--port', process.env.WEB_PORT, '--hostname', '0.0.0.0'];
|
|
8
|
-
|
|
9
17
|
module.exports = {
|
|
10
18
|
apps: [
|
|
11
19
|
{
|
|
12
|
-
name: `${identity.slug}-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
name: `${declaration.identity.slug}-api-dev`,
|
|
21
|
+
// The role's OWN process, in its OWN directory — no launcher in between.
|
|
22
|
+
// Measured: a launcher makes the role see the stop signal twice (once from
|
|
23
|
+
// the supervisor, once forwarded), the second press forces the shutdown,
|
|
24
|
+
// and a declared drain of seconds collapses to milliseconds. A workspace
|
|
25
|
+
// filter is worse: the signal never arrives at all.
|
|
26
|
+
cwd: path.join(__dirname, "packages/backend"),
|
|
27
|
+
script: "bun",
|
|
28
|
+
// No argv invented here: the deployment injects `API_PORT` and the command
|
|
29
|
+
// reads it. Serialised rather than concatenated — an argument with a space
|
|
30
|
+
// or a quote has to survive this file intact.
|
|
31
|
+
args: ["--watch","src/index.ts"],
|
|
32
|
+
interpreter: 'none',
|
|
17
33
|
autorestart: true,
|
|
18
|
-
|
|
34
|
+
// >= this role's full shutdown budget of 25000ms.
|
|
35
|
+
kill_timeout: 30000,
|
|
19
36
|
env: { NODE_ENV: 'development' },
|
|
20
37
|
},
|
|
21
38
|
{
|
|
22
|
-
name: `${identity.slug}-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
name: `${declaration.identity.slug}-web-dev`,
|
|
40
|
+
// The role's OWN process, in its OWN directory — no launcher in between.
|
|
41
|
+
// Measured: a launcher makes the role see the stop signal twice (once from
|
|
42
|
+
// the supervisor, once forwarded), the second press forces the shutdown,
|
|
43
|
+
// and a declared drain of seconds collapses to milliseconds. A workspace
|
|
44
|
+
// filter is worse: the signal never arrives at all.
|
|
45
|
+
cwd: path.join(__dirname, "packages/frontend"),
|
|
46
|
+
script: "bun",
|
|
47
|
+
// No argv invented here: the deployment injects `WEB_PORT` and the command
|
|
48
|
+
// reads it. Serialised rather than concatenated — an argument with a space
|
|
49
|
+
// or a quote has to survive this file intact.
|
|
50
|
+
args: ["scripts/serve.ts","development"],
|
|
51
|
+
interpreter: 'none',
|
|
27
52
|
autorestart: true,
|
|
28
|
-
|
|
53
|
+
// >= this role's full shutdown budget of 15000ms.
|
|
54
|
+
kill_timeout: 30000,
|
|
29
55
|
env: { NODE_ENV: 'development' },
|
|
30
56
|
},
|
|
31
57
|
],
|
package/template/package.json
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
"packages/*"
|
|
8
8
|
],
|
|
9
9
|
"catalog": {
|
|
10
|
-
"stitchkit": "^0.
|
|
10
|
+
"stitchkit": "^0.60.0"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"dev": "bun scripts/dev.ts",
|
|
14
14
|
"check": "bun run db:generate && bun run check:authored && bun x tsc -p tsconfig.json --noEmit && bun run --filter '*' check",
|
|
15
15
|
"check:authored": "bun scripts/check-authored.ts",
|
|
16
|
-
"test": "bun run --filter '*' test",
|
|
17
|
-
"build": "bun run db:generate && bun --filter @app/backend build && bun --filter @app/frontend build",
|
|
16
|
+
"test": "bun test scripts && bun run --filter '*' test",
|
|
17
|
+
"build": "bun scripts/build-inputs.ts && bun run db:generate && bun --filter @app/backend build && bun --filter @app/frontend build",
|
|
18
18
|
"start:api": "bun --filter @app/backend start",
|
|
19
19
|
"start:web": "bun --filter @app/frontend start",
|
|
20
20
|
"env:ensure": "bun scripts/local-env.ts",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"lint": "biome check --error-on-warnings .",
|
|
32
32
|
"lint:fix": "biome check --write .",
|
|
33
33
|
"pm2:dev": "bun scripts/dev.ts",
|
|
34
|
-
"pm2:prod": "bun
|
|
34
|
+
"pm2:prod": "bun scripts/release.ts",
|
|
35
|
+
"gen:declaration": "bun scripts/declaration.ts"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"dotenv": "^17.4.2"
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "bun --watch src/index.ts",
|
|
8
8
|
"build": "rm -rf dist && bun build src/index.ts src/cli.ts src/tools.ts --outdir dist --target bun --packages external",
|
|
9
|
-
"start": "bun dist/index.js",
|
|
9
|
+
"start": "bun scripts/ensure-built.ts && bun dist/index.js",
|
|
10
10
|
"check": "bun x tsc --noEmit",
|
|
11
11
|
"test": "bun test --pass-with-no-tests",
|
|
12
12
|
"cli": "bun src/cli.ts",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Preflight for `start` / `pm2:prod`: a missing build otherwise surfaces as a
|
|
2
|
+
// bare `Module not found "dist/index.js"` with no hint at the obvious fix.
|
|
3
|
+
const entry = new URL('../dist/index.js', import.meta.url);
|
|
4
|
+
if (!(await Bun.file(entry).exists())) {
|
|
5
|
+
console.error('dist/index.js not found — run `bun run build` first.');
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { appDeclaration } from '@app/config/declaration';
|
|
4
4
|
import { createCli } from 'stitchkit/cli';
|
|
5
5
|
import { createSurface } from './surface';
|
|
6
6
|
|
|
7
7
|
const { services, socket } = await createSurface();
|
|
8
8
|
|
|
9
9
|
try {
|
|
10
|
-
await createCli({
|
|
10
|
+
await createCli({
|
|
11
|
+
name: appDeclaration.identity.slug,
|
|
12
|
+
version: appDeclaration.identity.version,
|
|
13
|
+
services,
|
|
14
|
+
});
|
|
11
15
|
} finally {
|
|
12
16
|
await socket.close();
|
|
13
17
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { env } from '@app/config';
|
|
2
|
-
import {
|
|
2
|
+
import { apiRole, appDeclaration } from '@app/config/declaration';
|
|
3
3
|
import { wrapInRequestContext } from 'stitchkit/observability';
|
|
4
4
|
import {
|
|
5
5
|
bindProcessSignals,
|
|
@@ -15,20 +15,26 @@ import { onError } from './transport/errors';
|
|
|
15
15
|
async function main(): Promise<void> {
|
|
16
16
|
const { services, socket } = await createSurface();
|
|
17
17
|
const mcp = createMcpHandler({
|
|
18
|
-
serverInfo: {
|
|
18
|
+
serverInfo: {
|
|
19
|
+
name: appDeclaration.identity.slug,
|
|
20
|
+
version: appDeclaration.identity.version,
|
|
21
|
+
},
|
|
19
22
|
auth: () => ({ scope: 'public' }),
|
|
20
23
|
services,
|
|
21
24
|
});
|
|
22
25
|
const openApi = generateOpenApiDocument({
|
|
23
|
-
info: {
|
|
26
|
+
info: {
|
|
27
|
+
title: `${appDeclaration.identity.name} API`,
|
|
28
|
+
version: appDeclaration.identity.version,
|
|
29
|
+
},
|
|
24
30
|
groups: [{ pathPrefix: '/api', services }],
|
|
25
31
|
});
|
|
26
32
|
|
|
27
33
|
const server = createServer({
|
|
28
34
|
groups: [{ pathPrefix: '/api', services }],
|
|
29
35
|
port: env.API_PORT,
|
|
30
|
-
hostname:
|
|
31
|
-
cors: { origin: env.CORS_ORIGIN },
|
|
36
|
+
hostname: env.BIND_HOST,
|
|
37
|
+
cors: env.CORS_ORIGIN ? { origin: env.CORS_ORIGIN } : undefined,
|
|
32
38
|
hooks: { onError },
|
|
33
39
|
logging: { format: env.LOG_FORMAT },
|
|
34
40
|
socket,
|
|
@@ -48,10 +54,20 @@ async function main(): Promise<void> {
|
|
|
48
54
|
// application's and close after the drain. A second signal forces this same
|
|
49
55
|
// shutdown, a third hands the signal back to its default disposition.
|
|
50
56
|
bindProcessSignals(server, {
|
|
51
|
-
|
|
57
|
+
// The FLOOR comes from the declaration, which is where a supervisor reads
|
|
58
|
+
// it too — one number, not two that can disagree. It is a property of the
|
|
59
|
+
// code: whatever supervises this process must allow at least this much
|
|
60
|
+
// before sending SIGKILL, or the drain never finishes.
|
|
61
|
+
shutdown: { gracePeriodMs: apiRole.drainFloorMs },
|
|
52
62
|
onComplete: async (result) => {
|
|
53
63
|
await mcp.close();
|
|
54
64
|
await prisma.$disconnect();
|
|
65
|
+
// Say how the drain ended. Without this an operator sees a process that
|
|
66
|
+
// vanished and an exit code, and cannot tell a clean drain from one the
|
|
67
|
+
// deadline or a second signal cut short.
|
|
68
|
+
console.log(
|
|
69
|
+
`Shutdown ${result.outcome}${result.reason ? ` (${result.reason})` : ''} in ${result.durationMs}ms — ${result.completedRequests} requests completed, ${result.abortedRequests} aborted, ${result.forcedWebSockets} sockets forced`,
|
|
70
|
+
);
|
|
55
71
|
process.exitCode = result.outcome === 'clean' ? 0 : 1;
|
|
56
72
|
},
|
|
57
73
|
onError: (phase, error) => {
|
|
@@ -60,7 +76,7 @@ async function main(): Promise<void> {
|
|
|
60
76
|
},
|
|
61
77
|
});
|
|
62
78
|
|
|
63
|
-
console.log(`API listening on http
|
|
79
|
+
console.log(`API listening on http://${env.BIND_HOST}:${env.API_PORT}`);
|
|
64
80
|
}
|
|
65
81
|
|
|
66
82
|
main().catch((error: unknown) => {
|
|
@@ -3,6 +3,11 @@ import { createSocketIOServer } from 'stitchkit/server';
|
|
|
3
3
|
import { createSystemService } from './transport/system-service';
|
|
4
4
|
|
|
5
5
|
export async function createSurface() {
|
|
6
|
-
|
|
6
|
+
// An EMPTY allow-list is same-origin: no origin is permitted to open a
|
|
7
|
+
// cross-origin socket, and no browser on this app's own origin needs one.
|
|
8
|
+
// `CORS_ORIGIN` is set only when the browser genuinely lives elsewhere.
|
|
9
|
+
// (Once the workspace targets a Stitchkit release where `cors` itself is
|
|
10
|
+
// optional, this becomes `undefined` and the empty array goes away.)
|
|
11
|
+
const socket = await createSocketIOServer({ cors: { origin: env.CORS_ORIGIN ?? [] } });
|
|
7
12
|
return { socket, services: [createSystemService()] };
|
|
8
13
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { StitchErrorCode } from 'stitchkit';
|
|
2
1
|
import { createErrorHook } from 'stitchkit/server';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
// Deliberately not annotated as an exhaustive `Record<StitchErrorCode, …>`:
|
|
4
|
+
// this template compiles against both its pinned Stitchkit target and HEAD, and
|
|
5
|
+
// the code union differs between them. Unlisted codes travel as themselves.
|
|
6
|
+
const codeMap = {
|
|
5
7
|
BAD_REQUEST: 'bad_request',
|
|
6
8
|
VALIDATION_ERROR: 'validation_error',
|
|
7
9
|
UNAUTHORIZED: 'unauthorized',
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/server.ts",
|
|
8
|
-
"./
|
|
8
|
+
"./variables": "./src/variables.ts",
|
|
9
|
+
"./declaration": "./src/declaration.ts",
|
|
10
|
+
"./app-identity": "./src/app-identity.generated.ts"
|
|
9
11
|
},
|
|
10
12
|
"scripts": {
|
|
11
13
|
"check": "bun x tsc --noEmit",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// GENERATED FILE — do not edit.
|
|
2
|
+
//
|
|
3
|
+
// Rendered from `project.json` by `scripts/declaration.ts`.
|
|
4
|
+
//
|
|
5
|
+
// Identity ONLY, inlined rather than imported, because this is the part of the
|
|
6
|
+
// declaration a browser may know. Importing the whole declaration from a client
|
|
7
|
+
// component would put role commands, working directories, build artifact paths,
|
|
8
|
+
// the migration lockfile and every environment variable name into the browser
|
|
9
|
+
// bundle — the same mistake as publishing internal topology from a status
|
|
10
|
+
// endpoint, made from the other side.
|
|
11
|
+
|
|
12
|
+
export const appIdentity = {
|
|
13
|
+
"slug": "stitchkit-starter",
|
|
14
|
+
"name": "Stitchkit Starter",
|
|
15
|
+
"version": "0.1.0",
|
|
16
|
+
"description": {
|
|
17
|
+
"en": "Stitchkit Starter is a production application built with Stitchkit.",
|
|
18
|
+
"ru": "Stitchkit Starter — production-приложение на Stitchkit."
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import source from '../../../project.json' with { type: 'json' };
|
|
2
|
+
import { findProjectRole, parseProjectDeclaration } from './project-declaration.generated';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What this repository says about itself — the one machine-readable statement
|
|
6
|
+
* that is true with no machine in existence.
|
|
7
|
+
*
|
|
8
|
+
* Ports, hosts, addresses, machine paths and supervision policy are NOT here by
|
|
9
|
+
* construction: the schema has nowhere to put them. A deployment supplies those
|
|
10
|
+
* under the variable names the declaration lists.
|
|
11
|
+
*/
|
|
12
|
+
export const appDeclaration = parseProjectDeclaration(source);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* This application's API role.
|
|
16
|
+
*
|
|
17
|
+
* Resolved once, here, so the role's own code can read what the declaration
|
|
18
|
+
* says about it — the drain floor above all — instead of restating it. A
|
|
19
|
+
* declaration without the role is a broken declaration, and saying so at
|
|
20
|
+
* startup beats a silent `undefined` deep inside a shutdown path.
|
|
21
|
+
*/
|
|
22
|
+
const API_ROLE_NAME = 'api';
|
|
23
|
+
|
|
24
|
+
export const apiRole = (() => {
|
|
25
|
+
const role = findProjectRole(appDeclaration, API_ROLE_NAME);
|
|
26
|
+
if (!role) {
|
|
27
|
+
throw new Error(`project.json declares no "${API_ROLE_NAME}" role.`);
|
|
28
|
+
}
|
|
29
|
+
return role;
|
|
30
|
+
})();
|