@zaaxch/tailframe 4.0.6 → 4.0.8
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/package.json +1 -1
- package/src/new.mjs +68 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zaaxch/tailframe",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.8",
|
|
4
4
|
"description": "Tailframe architecture toolkit: validates the Tailframe structure, import-boundary, and file-convention contracts. The package version is the contract version.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/new.mjs
CHANGED
|
@@ -282,7 +282,11 @@ MONGODB_BACKUP_PASSWORD=<different-uri-safe-random-value>
|
|
|
282
282
|
${options.auth === "firebase" ? `SECRETS_DIR=/opt/${options.name}/shared/secrets
|
|
283
283
|
` : ""}${options.redis ? `REDIS_DATA_DIR=/opt/${options.name}/shared/redis/data
|
|
284
284
|
REDIS_PASSWORD=<different-uri-safe-random-value>
|
|
285
|
-
` : ""}
|
|
285
|
+
` : ""}CADDY_ORIGIN_CERTIFICATE=/opt/${options.name}/shared/caddy/certs/origin.pem
|
|
286
|
+
CADDY_ORIGIN_KEY=/opt/${options.name}/shared/caddy/certs/origin-key.pem
|
|
287
|
+
CADDY_DATA_DIR=/opt/${options.name}/shared/caddy/data
|
|
288
|
+
CADDY_CONFIG_DIR=/opt/${options.name}/shared/caddy/config
|
|
289
|
+
`);
|
|
286
290
|
add(`${svc}/src/platform/config/env.ts`, `import "@dotenvx/dotenvx/config";
|
|
287
291
|
export const env = {
|
|
288
292
|
nodeEnv: process.env.NODE_ENV ?? "development",
|
|
@@ -584,7 +588,7 @@ ${options.ui ? "- In production, serve the compiled UI from `dist/public` with a
|
|
|
584
588
|
The ECR build targets \`linux/amd64\`. ${options.ui ? "Its default immutable tag is `<product-sha>` and production Compose requires that exact tag." : "Its default immutable tag is `<product-sha>` and production Compose requires that exact tag."} The runtime image contains production dependencies only and runs as the Node user.${options.ui && options.auth === "firebase" ? ` The build requires \`${ui}/.env.production\` and mounts it as a BuildKit secret only while Vite compiles the browser bundle; it is not copied into the final image.` : ""}
|
|
585
589
|
|
|
586
590
|
## Production deployment
|
|
587
|
-
Pull requests into product \`main\` validate release candidates. The resulting \`main\` push validates again before GitHub Actions may publish an immutable image tagged with the full product commit SHA. Image publication and production deployment are separate workflows with distinct AWS roles: the release role publishes, while the pull-only deployment role manually deploys from \`main\` using an already-published full SHA from \`main\` history and that commit's deployment files. Build both role-trust subjects from GitHub's API-reported \`sub_claim_prefix\`; never infer the effective prefix from \`use_immutable_subject\`. Product data migrations must authenticate to the registry, pull and digest-verify that exact release image, and explicitly select the production datastore; never inherit rehearsal image or target defaults. GitHub Actions may replace files under \`/opt/${options.name}/current\` and MUST NOT replace persistent configuration, credentials, keyfiles, or data under \`/opt/${options.name}/shared\`. Keep Node runtime behavior in the shared service \`.env.production\`, Compose interpolation and infrastructure credentials in \`.env.infrastructure\`, and fixed topology wiring in Compose \`environment:\`. The optional UI build-time \`.env.production\` is a separate ephemeral file. Do not run deployment until the host and GitHub environments have been bootstrapped according to \`docs/production-deployment.md\`.
|
|
591
|
+
Pull requests into product \`main\` validate release candidates. The resulting \`main\` push validates again before GitHub Actions may publish an immutable image tagged with the full product commit SHA. Image publication and production deployment are separate workflows with distinct AWS roles: the release role publishes, while the pull-only deployment role manually deploys from \`main\` using an already-published full SHA from \`main\` history and that commit's deployment files. Build both role-trust subjects from GitHub's API-reported \`sub_claim_prefix\`; never infer the effective prefix from \`use_immutable_subject\`. Product data migrations must authenticate to the registry, pull and digest-verify that exact release image, and explicitly select the production datastore; never inherit rehearsal image or target defaults. GitHub Actions may replace files under \`/opt/${options.name}/current\` and MUST NOT replace persistent configuration, credentials, keyfiles, or data under \`/opt/${options.name}/shared\`. Caddy is the only production edge surface: deploy the matching commit's \`Caddyfile\`, keep its certificate and runtime state under \`shared\`, and force-recreate only the proxy after rollout so bind-mounted configuration changes take effect. Keep Node runtime behavior in the shared service \`.env.production\`, Compose interpolation and infrastructure credentials in \`.env.infrastructure\`, and fixed topology wiring in Compose \`environment:\`. The optional UI build-time \`.env.production\` is a separate ephemeral file. Do not run deployment until the host and GitHub environments have been bootstrapped according to \`docs/production-deployment.md\`.
|
|
588
592
|
|
|
589
593
|
## Tests and validation
|
|
590
594
|
Run \`pnpm validate:architecture\` and \`pnpm format:check\` after changing files or imports. The generated service also includes unit tests and ${postgres ? "PostgreSQL-backed" : "Mongo-backed"} integration tests. Run focused service database tests through pnpm for the full service check. Test use cases, policies, ${postgres ? "PostgreSQL" : "Mongo"} repository adapters, and HTTP boundaries where behavior lives. Every public operation needs success and error-envelope coverage; authenticated operations need trusted-identity coverage; persisted capabilities need ${postgres ? "PostgreSQL" : "Mongo"} ownership/query coverage; UI-enabled services must verify that \`/api\` paths never fall through to the SPA. All AWS/ECR and Docker commands, including the release image script, must run outside the sandbox from the first attempt.
|
|
@@ -1099,6 +1103,7 @@ jobs:
|
|
|
1099
1103
|
"install -m 755 -d /opt/${options.name}/current/deploy/${deploymentArtifactDirectory}"
|
|
1100
1104
|
scp -i ~/.ssh/${options.name}-deploy \\
|
|
1101
1105
|
apps/service/docker-compose.yml \\
|
|
1106
|
+
apps/service/Caddyfile \\
|
|
1102
1107
|
apps/service/scripts/deploy_remote.sh \\
|
|
1103
1108
|
"\${DEPLOY_USER}@\${DEPLOY_HOST}:/opt/${options.name}/current/"
|
|
1104
1109
|
scp -i ~/.ssh/${options.name}-deploy \\
|
|
@@ -1154,6 +1159,9 @@ trap - EXIT
|
|
|
1154
1159
|
docker compose --env-file "\${INFRA_ENV}" --file "\${COMPOSE_FILE}" config --quiet
|
|
1155
1160
|
docker compose --env-file "\${INFRA_ENV}" --file "\${COMPOSE_FILE}" pull
|
|
1156
1161
|
docker compose --env-file "\${INFRA_ENV}" --file "\${COMPOSE_FILE}" up -d --wait
|
|
1162
|
+
# Compose does not detect changes inside the bind-mounted Caddyfile, so recreate only the proxy
|
|
1163
|
+
# after every deployment to make the files installed under current/ take effect.
|
|
1164
|
+
docker compose --env-file "\${INFRA_ENV}" --file "\${COMPOSE_FILE}" up -d --wait --no-deps --force-recreate caddy
|
|
1157
1165
|
`);
|
|
1158
1166
|
|
|
1159
1167
|
if (postgres) add(`${svc}/deploy/postgres/10-create-application-user.sh`, `#!/usr/bin/env bash
|
|
@@ -1190,17 +1198,28 @@ configured. No push or merge deploys production automatically.
|
|
|
1190
1198
|
Configure the ECR repository with immutable image tags. The release-only role may publish; the distinct deployment
|
|
1191
1199
|
role is pull-only. Record the full commit SHA and ECR digest; deployment and product migrations use that release image.
|
|
1192
1200
|
|
|
1201
|
+
Production ingress is a required Caddy reverse proxy for every generated Tailframe service. The app container is internal-only;
|
|
1202
|
+
Caddy is the only edge surface and must remain the only internet-facing host for production traffic.
|
|
1203
|
+
This document assumes 80/443 exposure through Caddy only.
|
|
1204
|
+
|
|
1193
1205
|
The host separates replaceable artifacts from persistent state:
|
|
1194
1206
|
|
|
1195
1207
|
\`\`\`text
|
|
1196
1208
|
/opt/${options.name}/
|
|
1197
1209
|
├── current/
|
|
1210
|
+
│ ├── Caddyfile
|
|
1198
1211
|
│ ├── docker-compose.yml
|
|
1199
1212
|
│ ├── deploy_remote.sh
|
|
1200
1213
|
│ └── deploy/${deploymentArtifactDirectory}/${deploymentArtifactFile}
|
|
1201
1214
|
└── shared/
|
|
1202
1215
|
├── .env.production
|
|
1203
1216
|
├── .env.infrastructure
|
|
1217
|
+
├── caddy/
|
|
1218
|
+
│ ├── certs/
|
|
1219
|
+
│ │ ├── origin.pem
|
|
1220
|
+
│ │ └── origin-key.pem
|
|
1221
|
+
│ ├── config/
|
|
1222
|
+
│ └── data/
|
|
1204
1223
|
${options.auth === "firebase" ? ` ├── secrets/firebase-service-account.json
|
|
1205
1224
|
` : ""}${postgres ? ` ├── postgres/data/
|
|
1206
1225
|
` : ` ├── mongo/config/keyfile
|
|
@@ -1215,6 +1234,7 @@ Actions may replace \`current/\` and must never replace \`shared/\`. The server
|
|
|
1215
1234
|
- \`.env.production\` contains Node application behavior and provider credentials.
|
|
1216
1235
|
- \`.env.infrastructure\` contains Compose paths, image identity, and infrastructure passwords. Start from
|
|
1217
1236
|
\`.env.infrastructure.example\` and replace every placeholder.
|
|
1237
|
+
- \`.env.infrastructure\` also owns Caddy certificate/runtime ownership boundaries.
|
|
1218
1238
|
- Compose \`environment:\` owns fixed production wiring: \`NODE_ENV\`, internal database/cache URLs, and mounted
|
|
1219
1239
|
credential paths.
|
|
1220
1240
|
${options.ui && options.auth === "firebase" ? `- The UI application's build-time \`.env.production\` comes from the GitHub \`UI_ENV_PRODUCTION\` secret and is unrelated to the service runtime file above.
|
|
@@ -1225,15 +1245,19 @@ Do not inject \`.env.infrastructure\` into Node. Make both host environment file
|
|
|
1225
1245
|
|
|
1226
1246
|
1. Install Docker Engine and Docker Compose.
|
|
1227
1247
|
${postgres
|
|
1228
|
-
? `2. Create \`/opt/${options.name}/current/deploy/postgres
|
|
1248
|
+
? `2. Create \`/opt/${options.name}/current/deploy/postgres\`, \`/opt/${options.name}/shared/postgres/data\`,
|
|
1249
|
+
\`/opt/${options.name}/shared/caddy/certs\`, \`/opt/${options.name}/shared/caddy/data\`, \`/opt/${options.name}/shared/caddy/config\`${options.redis ? `, plus \`/opt/${options.name}/shared/redis/data\`` : ""}.`
|
|
1229
1250
|
: `2. Create \`/opt/${options.name}/current/deploy/mongo\`, \`/opt/${options.name}/shared/mongo/config\`, and
|
|
1230
|
-
\`/opt/${options.name}/shared/mongo/data
|
|
1251
|
+
\`/opt/${options.name}/shared/mongo/data\`, \`/opt/${options.name}/shared/caddy/certs\`, \`/opt/${options.name}/shared/caddy/data\`,
|
|
1252
|
+
\`/opt/${options.name}/shared/caddy/config\`${options.redis ? `, plus \`/opt/${options.name}/shared/redis/data\`` : ""}.`}
|
|
1231
1253
|
3. Install \`.env.production\` and a completed \`.env.infrastructure\` under \`shared/\`.
|
|
1232
|
-
|
|
1254
|
+
4. Install the origin certificate at \`shared/caddy/certs/origin.pem\` and its private key at
|
|
1255
|
+
\`shared/caddy/certs/origin-key.pem\`; make both root-owned and mode \`0600\`.
|
|
1256
|
+
${postgres ? "" : `5. Generate the MongoDB replica key with \`openssl rand -base64 756\`, make it owned by UID/GID \`999:999\`, and
|
|
1233
1257
|
mode \`0400\`.
|
|
1234
|
-
`}${options.auth === "firebase" ? `${postgres ? "
|
|
1235
|
-
` : ""}${postgres ? (options.auth === "firebase" ? "
|
|
1236
|
-
${postgres ? (options.auth === "firebase" ? "
|
|
1258
|
+
`}${options.auth === "firebase" ? `${postgres ? "5" : "6"}. Install the Firebase service account at \`shared/secrets/firebase-service-account.json\`, owned by \`root:1000\` and mode \`0640\`.
|
|
1259
|
+
` : ""}${postgres ? (options.auth === "firebase" ? "6" : "5") : (options.auth === "firebase" ? "7" : "6")}. Give the deployment user Docker access and write access to \`current/\`; keep \`shared/\` and its files protected.
|
|
1260
|
+
${postgres ? (options.auth === "firebase" ? "7" : "6") : (options.auth === "firebase" ? "8" : "7")}. Authorize the dedicated deployment SSH key and record the host key for GitHub Actions.
|
|
1237
1261
|
|
|
1238
1262
|
Use a fresh ${postgres ? "PostgreSQL" : "MongoDB"} data directory. Migrating an existing unauthenticated database requires a separately reviewed plan.
|
|
1239
1263
|
|
|
@@ -1280,6 +1304,10 @@ The remote script requires the provisioned infrastructure file, atomically repla
|
|
|
1280
1304
|
full-SHA release and running the same workflow. Never use \`docker compose down -v\` in production; image rollback
|
|
1281
1305
|
does not restore database state.
|
|
1282
1306
|
|
|
1307
|
+
The deployment installs the selected commit's \`Caddyfile\` and force-recreates only the proxy after the general
|
|
1308
|
+
rollout so bind-mounted configuration changes take effect. Preserve product-specific hostname restrictions when
|
|
1309
|
+
customizing the domain-neutral generated listener.
|
|
1310
|
+
|
|
1283
1311
|
## Product data-migration handoff
|
|
1284
1312
|
|
|
1285
1313
|
Tailframe does not generate product-specific data-migration orchestration. Before a production data migration,
|
|
@@ -1288,8 +1316,8 @@ explicitly configure the production datastore target. Never inherit a rehearsal
|
|
|
1288
1316
|
|
|
1289
1317
|
${postgres ? "PostgreSQL" : "MongoDB"} and optional Redis expose no host ports and require authentication. Verify API readiness, container health,
|
|
1290
1318
|
database/cache authentication, file permissions, and off-host backup/restore before treating the deployment as
|
|
1291
|
-
production-ready. Product-specific
|
|
1292
|
-
from measured application requirements rather than copied from another product.
|
|
1319
|
+
production-ready. Product-specific hostnames, certificate-provider procedures, capacity limits, migrations, and
|
|
1320
|
+
scheduled-work grace periods must be added from measured application requirements rather than copied from another product.
|
|
1293
1321
|
`);
|
|
1294
1322
|
|
|
1295
1323
|
const serviceVolumes = [
|
|
@@ -1331,6 +1359,34 @@ const developmentEnvironment = [
|
|
|
1331
1359
|
];
|
|
1332
1360
|
const developmentEnvironmentBlock = `\n environment:\n${developmentEnvironment.join("\n")}`;
|
|
1333
1361
|
const volumeBlock = serviceVolumes.length ? `\n volumes:\n${serviceVolumes.join("\n")}` : "";
|
|
1362
|
+
const caddyService = `
|
|
1363
|
+
caddy:
|
|
1364
|
+
image: caddy:2.11.4-alpine
|
|
1365
|
+
ports:
|
|
1366
|
+
- "80:80"
|
|
1367
|
+
- "443:443"
|
|
1368
|
+
volumes:
|
|
1369
|
+
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
1370
|
+
- \${CADDY_ORIGIN_CERTIFICATE:?Set CADDY_ORIGIN_CERTIFICATE}:/etc/caddy/certs/origin.pem:ro
|
|
1371
|
+
- \${CADDY_ORIGIN_KEY:?Set CADDY_ORIGIN_KEY}:/etc/caddy/certs/origin-key.pem:ro
|
|
1372
|
+
- \${CADDY_DATA_DIR:?Set CADDY_DATA_DIR}:/data
|
|
1373
|
+
- \${CADDY_CONFIG_DIR:?Set CADDY_CONFIG_DIR}:/config
|
|
1374
|
+
depends_on:
|
|
1375
|
+
${options.name}:
|
|
1376
|
+
condition: service_healthy
|
|
1377
|
+
networks:
|
|
1378
|
+
- backend
|
|
1379
|
+
restart: unless-stopped`;
|
|
1380
|
+
add(`${svc}/Caddyfile`, `{
|
|
1381
|
+
auto_https off
|
|
1382
|
+
}
|
|
1383
|
+
:80 {
|
|
1384
|
+
redir https://{host}{uri} 308
|
|
1385
|
+
}
|
|
1386
|
+
:443 {
|
|
1387
|
+
tls /etc/caddy/certs/origin.pem /etc/caddy/certs/origin-key.pem
|
|
1388
|
+
reverse_proxy ${options.name}:3000
|
|
1389
|
+
}`);
|
|
1334
1390
|
const workerService = options.worker ? `\n worker:
|
|
1335
1391
|
image: \${ECR_IMAGE:?Set ECR_IMAGE}:\${IMAGE_TAG:?Set IMAGE_TAG}
|
|
1336
1392
|
env_file:
|
|
@@ -1472,9 +1528,7 @@ services:
|
|
|
1472
1528
|
${options.name}:
|
|
1473
1529
|
image: \${ECR_IMAGE:?Set ECR_IMAGE}:\${IMAGE_TAG:?Set IMAGE_TAG}
|
|
1474
1530
|
env_file:
|
|
1475
|
-
- \${APP_ENV_FILE:-.env.production}
|
|
1476
|
-
ports:
|
|
1477
|
-
- "3000:3000"${productionEnvironmentBlock}${volumeBlock}${dependsBlock}
|
|
1531
|
+
- \${APP_ENV_FILE:-.env.production}${productionEnvironmentBlock}${volumeBlock}${dependsBlock}
|
|
1478
1532
|
healthcheck:
|
|
1479
1533
|
test:
|
|
1480
1534
|
- CMD-SHELL
|
|
@@ -1490,7 +1544,7 @@ services:
|
|
|
1490
1544
|
stop_grace_period: 2m
|
|
1491
1545
|
networks:
|
|
1492
1546
|
- backend
|
|
1493
|
-
restart: unless-stopped${workerService}${schemaInitService}${databaseService}${redisService}
|
|
1547
|
+
restart: unless-stopped${caddyService}${workerService}${schemaInitService}${databaseService}${redisService}
|
|
1494
1548
|
networks:
|
|
1495
1549
|
backend:
|
|
1496
1550
|
driver: bridge
|