@zaaxch/tailframe 4.0.7 → 4.0.9
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 +96 -45
- package/src/validate.mjs +12 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zaaxch/tailframe",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.9",
|
|
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.
|
|
@@ -604,7 +604,24 @@ add(`${ui}/.gitattributes`, gitAttributes);
|
|
|
604
604
|
add(`${ui}/tsconfig.json`, JSON.stringify({ files: [], references: [{ path: "./tsconfig.app.json" }, { path: "./tsconfig.node.json" }] }, null, "\t"));
|
|
605
605
|
add(`${ui}/tsconfig.app.json`, JSON.stringify({ extends: "@vue/tsconfig/tsconfig.dom.json", include: ["env.d.ts", "src/**/*", "src/**/*.vue"], compilerOptions: { composite: true, noEmit: true, tsBuildInfoFile: "./.tmp/tsconfig.app.tsbuildinfo", types: ["vitest/globals"], baseUrl: ".", paths: { "@/*": ["./src/*"] } } }, null, "\t"));
|
|
606
606
|
add(`${ui}/tsconfig.node.json`, JSON.stringify({ extends: "@tsconfig/node22/tsconfig.json", include: ["vite.config.ts"], compilerOptions: { composite: true, noEmit: true, tsBuildInfoFile: "./.tmp/tsconfig.node.tsbuildinfo", types: ["node"] } }, null, "\t"));
|
|
607
|
-
add(`${ui}/vite.config.ts`, `import { fileURLToPath, URL } from "node:url"
|
|
607
|
+
add(`${ui}/vite.config.ts`, `import { fileURLToPath, URL } from "node:url";
|
|
608
|
+
import { defineConfig } from "vitest/config";
|
|
609
|
+
import vue from "@vitejs/plugin-vue";
|
|
610
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
611
|
+
|
|
612
|
+
export default defineConfig({
|
|
613
|
+
plugins: [vue(), tailwindcss()],
|
|
614
|
+
resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) } },
|
|
615
|
+
server: {
|
|
616
|
+
proxy: {
|
|
617
|
+
"/api": {
|
|
618
|
+
target: process.env.VITE_API_PROXY_TARGET ?? "http://localhost:3000",
|
|
619
|
+
changeOrigin: true
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
test: { environment: "jsdom", globals: true }
|
|
624
|
+
});`);
|
|
608
625
|
add(`${ui}/env.d.ts`, `/// <reference types="vite/client" />`);
|
|
609
626
|
add(`${ui}/index.html`, `<!doctype html>
|
|
610
627
|
<html lang="en">
|
|
@@ -760,7 +777,7 @@ Keep all product state in the owning module. The auth/notification/theme stores,
|
|
|
760
777
|
- Keep \`src/app/styles.css\` limited to Tailwind imports, theme tokens, and true global base behavior.
|
|
761
778
|
|
|
762
779
|
## Production packaging
|
|
763
|
-
|
|
780
|
+
Development Compose runs Vite from the UI's development-only container on the product's private network; its proxy target is configurable and all development ports remain internal-only so multiple products can run concurrently. The UI has no independent production container. The service's multi-stage Dockerfile builds this application and copies its output into \`dist/public\`; Express serves it with SPA fallback. AWS ECR stores the combined \`linux/amd64\` production image under the default immutable \`<product-sha>\` tag, and production Compose requires that exact tag.${options.auth === "firebase" ? ` Provide \`${ui}/.env.production\` before a production build; the service build mounts it as a required BuildKit secret only while Vite compiles the browser bundle and does not copy it into the final image.` : ""}
|
|
764
781
|
|
|
765
782
|
## Product language
|
|
766
783
|
The product domain is undefined. Do not invent entities, workflows, roles, claims, navigation, or customer-facing promises.
|
|
@@ -799,6 +816,18 @@ WORKDIR /workspace/apps/service
|
|
|
799
816
|
EXPOSE 3000
|
|
800
817
|
CMD ["pnpm", "dev"]
|
|
801
818
|
`);
|
|
819
|
+
if (options.ui) add(`${ui}/Dockerfile.dev`, `FROM node:22.13-alpine
|
|
820
|
+
RUN npm install --global corepack@0.34.5 && corepack enable
|
|
821
|
+
WORKDIR /workspace
|
|
822
|
+
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
|
|
823
|
+
COPY apps/service/package.json apps/service/package.json
|
|
824
|
+
COPY apps/ui/package.json apps/ui/package.json
|
|
825
|
+
RUN pnpm install --frozen-lockfile --filter ${uiPackage}...
|
|
826
|
+
COPY apps/ui apps/ui
|
|
827
|
+
WORKDIR /workspace/apps/ui
|
|
828
|
+
EXPOSE 5173
|
|
829
|
+
CMD ["pnpm", "dev"]
|
|
830
|
+
`);
|
|
802
831
|
add(`${svc}/scripts/build_and_push.sh`, `#!/usr/bin/env bash
|
|
803
832
|
set -euo pipefail
|
|
804
833
|
|
|
@@ -1103,6 +1132,7 @@ jobs:
|
|
|
1103
1132
|
"install -m 755 -d /opt/${options.name}/current/deploy/${deploymentArtifactDirectory}"
|
|
1104
1133
|
scp -i ~/.ssh/${options.name}-deploy \\
|
|
1105
1134
|
apps/service/docker-compose.yml \\
|
|
1135
|
+
apps/service/Caddyfile \\
|
|
1106
1136
|
apps/service/scripts/deploy_remote.sh \\
|
|
1107
1137
|
"\${DEPLOY_USER}@\${DEPLOY_HOST}:/opt/${options.name}/current/"
|
|
1108
1138
|
scp -i ~/.ssh/${options.name}-deploy \\
|
|
@@ -1158,6 +1188,9 @@ trap - EXIT
|
|
|
1158
1188
|
docker compose --env-file "\${INFRA_ENV}" --file "\${COMPOSE_FILE}" config --quiet
|
|
1159
1189
|
docker compose --env-file "\${INFRA_ENV}" --file "\${COMPOSE_FILE}" pull
|
|
1160
1190
|
docker compose --env-file "\${INFRA_ENV}" --file "\${COMPOSE_FILE}" up -d --wait
|
|
1191
|
+
# Compose does not detect changes inside the bind-mounted Caddyfile, so recreate only the proxy
|
|
1192
|
+
# after every deployment to make the files installed under current/ take effect.
|
|
1193
|
+
docker compose --env-file "\${INFRA_ENV}" --file "\${COMPOSE_FILE}" up -d --wait --no-deps --force-recreate caddy
|
|
1161
1194
|
`);
|
|
1162
1195
|
|
|
1163
1196
|
if (postgres) add(`${svc}/deploy/postgres/10-create-application-user.sh`, `#!/usr/bin/env bash
|
|
@@ -1194,14 +1227,16 @@ configured. No push or merge deploys production automatically.
|
|
|
1194
1227
|
Configure the ECR repository with immutable image tags. The release-only role may publish; the distinct deployment
|
|
1195
1228
|
role is pull-only. Record the full commit SHA and ECR digest; deployment and product migrations use that release image.
|
|
1196
1229
|
|
|
1197
|
-
Production ingress is a required Caddy reverse proxy
|
|
1198
|
-
|
|
1230
|
+
Production ingress is a required Caddy reverse proxy for every generated Tailframe service. The app container is internal-only;
|
|
1231
|
+
Caddy is the only edge surface and must remain the only internet-facing host for production traffic.
|
|
1232
|
+
This document assumes 80/443 exposure through Caddy only.
|
|
1199
1233
|
|
|
1200
1234
|
The host separates replaceable artifacts from persistent state:
|
|
1201
1235
|
|
|
1202
1236
|
\`\`\`text
|
|
1203
1237
|
/opt/${options.name}/
|
|
1204
1238
|
├── current/
|
|
1239
|
+
│ ├── Caddyfile
|
|
1205
1240
|
│ ├── docker-compose.yml
|
|
1206
1241
|
│ ├── deploy_remote.sh
|
|
1207
1242
|
│ └── deploy/${deploymentArtifactDirectory}/${deploymentArtifactFile}
|
|
@@ -1228,8 +1263,7 @@ Actions may replace \`current/\` and must never replace \`shared/\`. The server
|
|
|
1228
1263
|
- \`.env.production\` contains Node application behavior and provider credentials.
|
|
1229
1264
|
- \`.env.infrastructure\` contains Compose paths, image identity, and infrastructure passwords. Start from
|
|
1230
1265
|
\`.env.infrastructure.example\` and replace every placeholder.
|
|
1231
|
-
- \`.env.infrastructure\`
|
|
1232
|
-
key material under shared storage and rotate it independently from application images.
|
|
1266
|
+
- \`.env.infrastructure\` also owns Caddy certificate/runtime ownership boundaries.
|
|
1233
1267
|
- Compose \`environment:\` owns fixed production wiring: \`NODE_ENV\`, internal database/cache URLs, and mounted
|
|
1234
1268
|
credential paths.
|
|
1235
1269
|
${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 +1277,16 @@ ${postgres
|
|
|
1243
1277
|
? `2. Create \`/opt/${options.name}/current/deploy/postgres\`, \`/opt/${options.name}/shared/postgres/data\`,
|
|
1244
1278
|
\`/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
1279
|
: `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/
|
|
1280
|
+
\`/opt/${options.name}/shared/mongo/data\`, \`/opt/${options.name}/shared/caddy/certs\`, \`/opt/${options.name}/shared/caddy/data\`,
|
|
1281
|
+
\`/opt/${options.name}/shared/caddy/config\`${options.redis ? `, plus \`/opt/${options.name}/shared/redis/data\`` : ""}.`}
|
|
1248
1282
|
3. Install \`.env.production\` and a completed \`.env.infrastructure\` under \`shared/\`.
|
|
1249
|
-
|
|
1283
|
+
4. Install the origin certificate at \`shared/caddy/certs/origin.pem\` and its private key at
|
|
1284
|
+
\`shared/caddy/certs/origin-key.pem\`; make both root-owned and mode \`0600\`.
|
|
1285
|
+
${postgres ? "" : `5. Generate the MongoDB replica key with \`openssl rand -base64 756\`, make it owned by UID/GID \`999:999\`, and
|
|
1250
1286
|
mode \`0400\`.
|
|
1251
|
-
`}${options.auth === "firebase" ? `${postgres ? "
|
|
1252
|
-
` : ""}${postgres ? (options.auth === "firebase" ? "
|
|
1253
|
-
${postgres ? (options.auth === "firebase" ? "
|
|
1287
|
+
`}${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\`.
|
|
1288
|
+
` : ""}${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.
|
|
1289
|
+
${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
1290
|
|
|
1255
1291
|
Use a fresh ${postgres ? "PostgreSQL" : "MongoDB"} data directory. Migrating an existing unauthenticated database requires a separately reviewed plan.
|
|
1256
1292
|
|
|
@@ -1297,6 +1333,10 @@ The remote script requires the provisioned infrastructure file, atomically repla
|
|
|
1297
1333
|
full-SHA release and running the same workflow. Never use \`docker compose down -v\` in production; image rollback
|
|
1298
1334
|
does not restore database state.
|
|
1299
1335
|
|
|
1336
|
+
The deployment installs the selected commit's \`Caddyfile\` and force-recreates only the proxy after the general
|
|
1337
|
+
rollout so bind-mounted configuration changes take effect. Preserve product-specific hostname restrictions when
|
|
1338
|
+
customizing the domain-neutral generated listener.
|
|
1339
|
+
|
|
1300
1340
|
## Product data-migration handoff
|
|
1301
1341
|
|
|
1302
1342
|
Tailframe does not generate product-specific data-migration orchestration. Before a production data migration,
|
|
@@ -1305,8 +1345,8 @@ explicitly configure the production datastore target. Never inherit a rehearsal
|
|
|
1305
1345
|
|
|
1306
1346
|
${postgres ? "PostgreSQL" : "MongoDB"} and optional Redis expose no host ports and require authentication. Verify API readiness, container health,
|
|
1307
1347
|
database/cache authentication, file permissions, and off-host backup/restore before treating the deployment as
|
|
1308
|
-
production-ready. Product-specific
|
|
1309
|
-
from measured application requirements rather than copied from another product.
|
|
1348
|
+
production-ready. Product-specific hostnames, certificate-provider procedures, capacity limits, migrations, and
|
|
1349
|
+
scheduled-work grace periods must be added from measured application requirements rather than copied from another product.
|
|
1310
1350
|
`);
|
|
1311
1351
|
|
|
1312
1352
|
const serviceVolumes = [
|
|
@@ -1350,7 +1390,7 @@ const developmentEnvironmentBlock = `\n environment:\n${developmentEnvironmen
|
|
|
1350
1390
|
const volumeBlock = serviceVolumes.length ? `\n volumes:\n${serviceVolumes.join("\n")}` : "";
|
|
1351
1391
|
const caddyService = `
|
|
1352
1392
|
caddy:
|
|
1353
|
-
image: caddy:2-alpine
|
|
1393
|
+
image: caddy:2.11.4-alpine
|
|
1354
1394
|
ports:
|
|
1355
1395
|
- "80:80"
|
|
1356
1396
|
- "443:443"
|
|
@@ -1367,14 +1407,14 @@ const caddyService = `
|
|
|
1367
1407
|
- backend
|
|
1368
1408
|
restart: unless-stopped`;
|
|
1369
1409
|
add(`${svc}/Caddyfile`, `{
|
|
1370
|
-
|
|
1410
|
+
auto_https off
|
|
1371
1411
|
}
|
|
1372
1412
|
:80 {
|
|
1373
|
-
|
|
1413
|
+
redir https://{host}{uri} 308
|
|
1374
1414
|
}
|
|
1375
1415
|
:443 {
|
|
1376
|
-
|
|
1377
|
-
|
|
1416
|
+
tls /etc/caddy/certs/origin.pem /etc/caddy/certs/origin-key.pem
|
|
1417
|
+
reverse_proxy ${options.name}:3000
|
|
1378
1418
|
}`);
|
|
1379
1419
|
const workerService = options.worker ? `\n worker:
|
|
1380
1420
|
image: \${ECR_IMAGE:?Set ECR_IMAGE}:\${IMAGE_TAG:?Set IMAGE_TAG}
|
|
@@ -1517,8 +1557,7 @@ services:
|
|
|
1517
1557
|
${options.name}:
|
|
1518
1558
|
image: \${ECR_IMAGE:?Set ECR_IMAGE}:\${IMAGE_TAG:?Set IMAGE_TAG}
|
|
1519
1559
|
env_file:
|
|
1520
|
-
- \${APP_ENV_FILE:-.env.production}
|
|
1521
|
-
${productionEnvironmentBlock}${volumeBlock}${dependsBlock}
|
|
1560
|
+
- \${APP_ENV_FILE:-.env.production}${productionEnvironmentBlock}${volumeBlock}${dependsBlock}
|
|
1522
1561
|
healthcheck:
|
|
1523
1562
|
test:
|
|
1524
1563
|
- CMD-SHELL
|
|
@@ -1534,7 +1573,7 @@ ${productionEnvironmentBlock}${volumeBlock}${dependsBlock}
|
|
|
1534
1573
|
stop_grace_period: 2m
|
|
1535
1574
|
networks:
|
|
1536
1575
|
- backend
|
|
1537
|
-
restart: unless-stopped${workerService}${schemaInitService}${databaseService}${redisService}
|
|
1576
|
+
restart: unless-stopped${caddyService}${workerService}${schemaInitService}${databaseService}${redisService}
|
|
1538
1577
|
networks:
|
|
1539
1578
|
backend:
|
|
1540
1579
|
driver: bridge
|
|
@@ -1542,17 +1581,17 @@ networks:
|
|
|
1542
1581
|
|
|
1543
1582
|
const devWorker = options.worker ? `\n worker:
|
|
1544
1583
|
build:
|
|
1545
|
-
context:
|
|
1546
|
-
dockerfile: Dockerfile.dev
|
|
1584
|
+
context: ../..
|
|
1585
|
+
dockerfile: apps/service/Dockerfile.dev
|
|
1547
1586
|
env_file:
|
|
1548
1587
|
- .env.development
|
|
1549
|
-
command: ["
|
|
1588
|
+
command: ["pnpm", "dev:worker"]${developmentEnvironmentBlock}
|
|
1550
1589
|
volumes:
|
|
1551
1590
|
${devVolumes.join("\n")}${devDependsBlock}` : "";
|
|
1552
1591
|
const devDatabase = postgres ? `\n postgres:
|
|
1553
1592
|
image: postgres:17
|
|
1554
|
-
|
|
1555
|
-
- "5432
|
|
1593
|
+
expose:
|
|
1594
|
+
- "5432"
|
|
1556
1595
|
environment:
|
|
1557
1596
|
POSTGRES_DB: ${databaseName}
|
|
1558
1597
|
POSTGRES_USER: ${options.name}-app
|
|
@@ -1566,8 +1605,8 @@ const devDatabase = postgres ? `\n postgres:
|
|
|
1566
1605
|
retries: 30` : `\n mongodb:
|
|
1567
1606
|
image: mongo:8
|
|
1568
1607
|
command: mongod --replSet rs0 --bind_ip_all
|
|
1569
|
-
|
|
1570
|
-
- "27017
|
|
1608
|
+
expose:
|
|
1609
|
+
- "27017"
|
|
1571
1610
|
volumes:
|
|
1572
1611
|
- mongodb-dev-data:/data/db
|
|
1573
1612
|
healthcheck:
|
|
@@ -1594,10 +1633,22 @@ const devDatabase = postgres ? `\n postgres:
|
|
|
1594
1633
|
'`;
|
|
1595
1634
|
const devRedis = options.redis ? `\n redis:
|
|
1596
1635
|
image: redis:7-alpine
|
|
1597
|
-
|
|
1598
|
-
- "6379
|
|
1636
|
+
expose:
|
|
1637
|
+
- "6379"
|
|
1599
1638
|
volumes:
|
|
1600
1639
|
- redis-dev-data:/data` : "";
|
|
1640
|
+
const devUi = options.ui ? `\n ui:
|
|
1641
|
+
build:
|
|
1642
|
+
context: ../..
|
|
1643
|
+
dockerfile: apps/ui/Dockerfile.dev
|
|
1644
|
+
${options.auth === "firebase" ? " env_file:\n - ../ui/.env.development\n" : ""} environment:
|
|
1645
|
+
VITE_API_PROXY_TARGET: http://${options.name}:3000
|
|
1646
|
+
volumes:
|
|
1647
|
+
- ../ui:/workspace/apps/ui
|
|
1648
|
+
- /workspace/node_modules
|
|
1649
|
+
- /workspace/apps/ui/node_modules
|
|
1650
|
+
expose:
|
|
1651
|
+
- "5173"` : "";
|
|
1601
1652
|
const devNamedVolumes = [
|
|
1602
1653
|
postgres ? " postgres-dev-data:" : " mongodb-dev-data:",
|
|
1603
1654
|
...(options.redis ? [" redis-dev-data:"] : [])
|
|
@@ -1606,11 +1657,11 @@ add(`${svc}/docker-compose.dev.yml`, `name: ${options.name}-dev
|
|
|
1606
1657
|
services:
|
|
1607
1658
|
schema-init:
|
|
1608
1659
|
build:
|
|
1609
|
-
context:
|
|
1610
|
-
dockerfile: Dockerfile.dev
|
|
1660
|
+
context: ../..
|
|
1661
|
+
dockerfile: apps/service/Dockerfile.dev
|
|
1611
1662
|
env_file:
|
|
1612
1663
|
- .env.development
|
|
1613
|
-
command: ["
|
|
1664
|
+
command: ["pnpm", "schema:apply:dev"]${developmentEnvironmentBlock}
|
|
1614
1665
|
volumes:
|
|
1615
1666
|
${devVolumes.join("\n")}
|
|
1616
1667
|
depends_on:
|
|
@@ -1618,14 +1669,14 @@ ${devVolumes.join("\n")}
|
|
|
1618
1669
|
condition: ${postgres ? "service_healthy" : "service_completed_successfully"}
|
|
1619
1670
|
${options.name}:
|
|
1620
1671
|
build:
|
|
1621
|
-
context:
|
|
1622
|
-
dockerfile: Dockerfile.dev
|
|
1672
|
+
context: ../..
|
|
1673
|
+
dockerfile: apps/service/Dockerfile.dev
|
|
1623
1674
|
env_file:
|
|
1624
1675
|
- .env.development
|
|
1625
|
-
|
|
1626
|
-
- "3000
|
|
1676
|
+
expose:
|
|
1677
|
+
- "3000"${developmentEnvironmentBlock}
|
|
1627
1678
|
volumes:
|
|
1628
|
-
${devVolumes.join("\n")}${devDependsBlock}${devWorker}${devDatabase}${devRedis}
|
|
1679
|
+
${devVolumes.join("\n")}${devDependsBlock}${devUi}${devWorker}${devDatabase}${devRedis}
|
|
1629
1680
|
volumes:
|
|
1630
1681
|
${devNamedVolumes.join("\n")}
|
|
1631
1682
|
`);
|
package/src/validate.mjs
CHANGED
|
@@ -61,7 +61,7 @@ function validateVersionMetadata(root, runningVersion) {
|
|
|
61
61
|
return errors;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function validateAppMetadata(productRoot, app) {
|
|
64
|
+
function validateAppMetadata(productRoot, app, productConfig) {
|
|
65
65
|
const errors = [];
|
|
66
66
|
const root = appRoot(productRoot, app);
|
|
67
67
|
if (!fs.existsSync(root)) return [`Missing configured application root: ${app.path}`];
|
|
@@ -71,6 +71,7 @@ function validateAppMetadata(productRoot, app) {
|
|
|
71
71
|
else {
|
|
72
72
|
const compose = fs.readFileSync(composeFile, "utf8");
|
|
73
73
|
const generatedProcessCount = app.profiles.includes("worker") ? 3 : 2;
|
|
74
|
+
const hasUi = productConfig.apps.some((candidate) => candidate.kind === "ui");
|
|
74
75
|
const count = (term) => compose.split(term).length - 1;
|
|
75
76
|
for (const term of [
|
|
76
77
|
" - .:/workspace/apps/service",
|
|
@@ -84,6 +85,14 @@ function validateAppMetadata(productRoot, app) {
|
|
|
84
85
|
if (compose.includes(" - .:/app\n")) {
|
|
85
86
|
errors.push(`${app.path}/docker-compose.dev.yml uses the obsolete /app service source mount`);
|
|
86
87
|
}
|
|
88
|
+
if (compose.includes(" ports:")) {
|
|
89
|
+
errors.push(`${app.path}/docker-compose.dev.yml must keep generated development ports internal-only`);
|
|
90
|
+
}
|
|
91
|
+
if (hasUi) {
|
|
92
|
+
for (const term of [" ui:", "dockerfile: apps/ui/Dockerfile.dev", "VITE_API_PROXY_TARGET:", " - ../ui:/workspace/apps/ui", " - /workspace/apps/ui/node_modules"]) {
|
|
93
|
+
if (!compose.includes(term)) errors.push(`${app.path}/docker-compose.dev.yml is missing the development UI container contract: ${term.trim()}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
87
96
|
if (app.profiles.includes("firebase")) {
|
|
88
97
|
for (const term of [
|
|
89
98
|
" FIREBASE_SERVICE_ACCOUNT_PATH: /app/firebase-service-account.development.json",
|
|
@@ -97,6 +106,7 @@ function validateAppMetadata(productRoot, app) {
|
|
|
97
106
|
}
|
|
98
107
|
}
|
|
99
108
|
if (app.kind === "ui") {
|
|
109
|
+
if (!fs.existsSync(path.join(root, "Dockerfile.dev"))) errors.push(`${app.path}/Dockerfile.dev is required`);
|
|
100
110
|
const generatedArtifacts = fs.readdirSync(root, { withFileTypes: true })
|
|
101
111
|
.filter(
|
|
102
112
|
(entry) =>
|
|
@@ -140,7 +150,7 @@ export function runConfiguredValidate(rootArgument, runningVersion, selectedKind
|
|
|
140
150
|
errors.push(`tailframe.json does not declare a ${selectedKind} app`);
|
|
141
151
|
}
|
|
142
152
|
errors.push(...validateVersionMetadata(loaded.root, runningVersion));
|
|
143
|
-
for (const app of loaded.config.apps) errors.push(...validateAppMetadata(loaded.root, app));
|
|
153
|
+
for (const app of loaded.config.apps) errors.push(...validateAppMetadata(loaded.root, app, loaded.config));
|
|
144
154
|
if (errors.length) return errors;
|
|
145
155
|
const apps = selectedKind ? loaded.config.apps.filter((app) => app.kind === selectedKind) : loaded.config.apps;
|
|
146
156
|
for (const app of apps) {
|