@zaaxch/tailframe 4.0.7 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/new.mjs +36 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaaxch/tailframe",
3
- "version": "4.0.7",
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
@@ -269,10 +269,6 @@ add(`${svc}/.env.example`, `NODE_ENV=development\nPORT=3000\nCORS_ORIGIN=https:/
269
269
  add(`${svc}/.env.infrastructure.example`, `ECR_IMAGE=<account>.dkr.ecr.<region>.amazonaws.com/${options.name}
270
270
  IMAGE_TAG=<product-sha>
271
271
  APP_ENV_FILE=/opt/${options.name}/shared/.env.production
272
- CADDY_ORIGIN_CERTIFICATE=/opt/${options.name}/shared/caddy/origin.pem
273
- CADDY_ORIGIN_KEY=/opt/${options.name}/shared/caddy/origin-key.pem
274
- CADDY_DATA_DIR=/opt/${options.name}/shared/caddy/data
275
- CADDY_CONFIG_DIR=/opt/${options.name}/shared/caddy/config
276
272
  ${postgres ? `POSTGRES_DATA_DIR=/opt/${options.name}/shared/postgres/data
277
273
  POSTGRES_ROOT_PASSWORD=<uri-safe-random-value>
278
274
  POSTGRES_APP_PASSWORD=<different-uri-safe-random-value>
@@ -286,7 +282,11 @@ MONGODB_BACKUP_PASSWORD=<different-uri-safe-random-value>
286
282
  ${options.auth === "firebase" ? `SECRETS_DIR=/opt/${options.name}/shared/secrets
287
283
  ` : ""}${options.redis ? `REDIS_DATA_DIR=/opt/${options.name}/shared/redis/data
288
284
  REDIS_PASSWORD=<different-uri-safe-random-value>
289
- ` : ""}`);
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
+ `);
290
290
  add(`${svc}/src/platform/config/env.ts`, `import "@dotenvx/dotenvx/config";
291
291
  export const env = {
292
292
  nodeEnv: process.env.NODE_ENV ?? "development",
@@ -588,7 +588,7 @@ ${options.ui ? "- In production, serve the compiled UI from `dist/public` with a
588
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.` : ""}
589
589
 
590
590
  ## Production deployment
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\`. 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\`.
592
592
 
593
593
  ## Tests and validation
594
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.
@@ -1103,6 +1103,7 @@ jobs:
1103
1103
  "install -m 755 -d /opt/${options.name}/current/deploy/${deploymentArtifactDirectory}"
1104
1104
  scp -i ~/.ssh/${options.name}-deploy \\
1105
1105
  apps/service/docker-compose.yml \\
1106
+ apps/service/Caddyfile \\
1106
1107
  apps/service/scripts/deploy_remote.sh \\
1107
1108
  "\${DEPLOY_USER}@\${DEPLOY_HOST}:/opt/${options.name}/current/"
1108
1109
  scp -i ~/.ssh/${options.name}-deploy \\
@@ -1158,6 +1159,9 @@ trap - EXIT
1158
1159
  docker compose --env-file "\${INFRA_ENV}" --file "\${COMPOSE_FILE}" config --quiet
1159
1160
  docker compose --env-file "\${INFRA_ENV}" --file "\${COMPOSE_FILE}" pull
1160
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
1161
1165
  `);
1162
1166
 
1163
1167
  if (postgres) add(`${svc}/deploy/postgres/10-create-application-user.sh`, `#!/usr/bin/env bash
@@ -1194,14 +1198,16 @@ configured. No push or merge deploys production automatically.
1194
1198
  Configure the ECR repository with immutable image tags. The release-only role may publish; the distinct deployment
1195
1199
  role is pull-only. Record the full commit SHA and ECR digest; deployment and product migrations use that release image.
1196
1200
 
1197
- Production ingress is a required Caddy reverse proxy in every generated service deployment. The application container remains
1198
- internal; Caddy is the only edge surface and must own 80/443 exposure.
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.
1199
1204
 
1200
1205
  The host separates replaceable artifacts from persistent state:
1201
1206
 
1202
1207
  \`\`\`text
1203
1208
  /opt/${options.name}/
1204
1209
  ├── current/
1210
+ │ ├── Caddyfile
1205
1211
  │ ├── docker-compose.yml
1206
1212
  │ ├── deploy_remote.sh
1207
1213
  │ └── deploy/${deploymentArtifactDirectory}/${deploymentArtifactFile}
@@ -1228,8 +1234,7 @@ Actions may replace \`current/\` and must never replace \`shared/\`. The server
1228
1234
  - \`.env.production\` contains Node application behavior and provider credentials.
1229
1235
  - \`.env.infrastructure\` contains Compose paths, image identity, and infrastructure passwords. Start from
1230
1236
  \`.env.infrastructure.example\` and replace every placeholder.
1231
- - \`.env.infrastructure\` is also the host-owned control plane for Caddy certificate paths and runtime directories; keep TLS
1232
- key material under shared storage and rotate it independently from application images.
1237
+ - \`.env.infrastructure\` also owns Caddy certificate/runtime ownership boundaries.
1233
1238
  - Compose \`environment:\` owns fixed production wiring: \`NODE_ENV\`, internal database/cache URLs, and mounted
1234
1239
  credential paths.
1235
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.
@@ -1243,14 +1248,16 @@ ${postgres
1243
1248
  ? `2. Create \`/opt/${options.name}/current/deploy/postgres\`, \`/opt/${options.name}/shared/postgres/data\`,
1244
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\`` : ""}.`
1245
1250
  : `2. Create \`/opt/${options.name}/current/deploy/mongo\`, \`/opt/${options.name}/shared/mongo/config\`, and
1246
- \`/opt/${options.name}/shared/mongo/data\`, \`/opt/${options.name}/shared/caddy/certs\`,
1247
- \`/opt/${options.name}/shared/caddy/data\`, \`/opt/${options.name}/shared/caddy/config\`${options.redis ? `, plus \`/opt/${options.name}/shared/redis/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\`` : ""}.`}
1248
1253
  3. Install \`.env.production\` and a completed \`.env.infrastructure\` under \`shared/\`.
1249
- ${postgres ? "" : `4. Generate the MongoDB replica key with \`openssl rand -base64 756\`, make it owned by UID/GID \`999:999\`, and
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
1250
1257
  mode \`0400\`.
1251
- `}${options.auth === "firebase" ? `${postgres ? "4" : "5"}. Install the Firebase service account at \`shared/secrets/firebase-service-account.json\`, owned by \`root:1000\` and mode \`0640\`.
1252
- ` : ""}${postgres ? (options.auth === "firebase" ? "5" : "4") : (options.auth === "firebase" ? "6" : "5")}. Give the deployment user Docker access and write access to \`current/\`; keep \`shared/\` and its files protected.
1253
- ${postgres ? (options.auth === "firebase" ? "6" : "5") : (options.auth === "firebase" ? "7" : "6")}. Authorize the dedicated deployment SSH key and record the host key for GitHub Actions.
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.
1254
1261
 
1255
1262
  Use a fresh ${postgres ? "PostgreSQL" : "MongoDB"} data directory. Migrating an existing unauthenticated database requires a separately reviewed plan.
1256
1263
 
@@ -1297,6 +1304,10 @@ The remote script requires the provisioned infrastructure file, atomically repla
1297
1304
  full-SHA release and running the same workflow. Never use \`docker compose down -v\` in production; image rollback
1298
1305
  does not restore database state.
1299
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
+
1300
1311
  ## Product data-migration handoff
1301
1312
 
1302
1313
  Tailframe does not generate product-specific data-migration orchestration. Before a production data migration,
@@ -1305,8 +1316,8 @@ explicitly configure the production datastore target. Never inherit a rehearsal
1305
1316
 
1306
1317
  ${postgres ? "PostgreSQL" : "MongoDB"} and optional Redis expose no host ports and require authentication. Verify API readiness, container health,
1307
1318
  database/cache authentication, file permissions, and off-host backup/restore before treating the deployment as
1308
- production-ready. Product-specific TLS, capacity limits, migrations, and scheduled-work grace periods must be added
1309
- 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.
1310
1321
  `);
1311
1322
 
1312
1323
  const serviceVolumes = [
@@ -1350,7 +1361,7 @@ const developmentEnvironmentBlock = `\n environment:\n${developmentEnvironmen
1350
1361
  const volumeBlock = serviceVolumes.length ? `\n volumes:\n${serviceVolumes.join("\n")}` : "";
1351
1362
  const caddyService = `
1352
1363
  caddy:
1353
- image: caddy:2-alpine
1364
+ image: caddy:2.11.4-alpine
1354
1365
  ports:
1355
1366
  - "80:80"
1356
1367
  - "443:443"
@@ -1367,14 +1378,14 @@ const caddyService = `
1367
1378
  - backend
1368
1379
  restart: unless-stopped`;
1369
1380
  add(`${svc}/Caddyfile`, `{
1370
- auto_https off
1381
+ auto_https off
1371
1382
  }
1372
1383
  :80 {
1373
- redir https://{host}{uri} 308
1384
+ redir https://{host}{uri} 308
1374
1385
  }
1375
1386
  :443 {
1376
- tls /etc/caddy/certs/origin.pem /etc/caddy/certs/origin-key.pem
1377
- reverse_proxy ${options.name}:3000
1387
+ tls /etc/caddy/certs/origin.pem /etc/caddy/certs/origin-key.pem
1388
+ reverse_proxy ${options.name}:3000
1378
1389
  }`);
1379
1390
  const workerService = options.worker ? `\n worker:
1380
1391
  image: \${ECR_IMAGE:?Set ECR_IMAGE}:\${IMAGE_TAG:?Set IMAGE_TAG}
@@ -1517,8 +1528,7 @@ services:
1517
1528
  ${options.name}:
1518
1529
  image: \${ECR_IMAGE:?Set ECR_IMAGE}:\${IMAGE_TAG:?Set IMAGE_TAG}
1519
1530
  env_file:
1520
- - \${APP_ENV_FILE:-.env.production}
1521
- ${productionEnvironmentBlock}${volumeBlock}${dependsBlock}
1531
+ - \${APP_ENV_FILE:-.env.production}${productionEnvironmentBlock}${volumeBlock}${dependsBlock}
1522
1532
  healthcheck:
1523
1533
  test:
1524
1534
  - CMD-SHELL
@@ -1534,7 +1544,7 @@ ${productionEnvironmentBlock}${volumeBlock}${dependsBlock}
1534
1544
  stop_grace_period: 2m
1535
1545
  networks:
1536
1546
  - backend
1537
- restart: unless-stopped${workerService}${schemaInitService}${databaseService}${redisService}${caddyService}
1547
+ restart: unless-stopped${caddyService}${workerService}${schemaInitService}${databaseService}${redisService}
1538
1548
  networks:
1539
1549
  backend:
1540
1550
  driver: bridge