@zaaxch/tailframe 2.0.0 → 2.1.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/new.mjs +27 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaaxch/tailframe",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
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
@@ -77,9 +77,7 @@ const prettierConfig = {
77
77
  endOfLine: "lf",
78
78
  singleQuote: false
79
79
  };
80
-
81
- add(".prettierrc.json", JSON.stringify(prettierConfig, null, "\t"));
82
- add(".gitattributes", `* text=auto eol=lf
80
+ const gitAttributes = `* text=auto eol=lf
83
81
 
84
82
  *.png binary
85
83
  *.jpg binary
@@ -87,7 +85,10 @@ add(".gitattributes", `* text=auto eol=lf
87
85
  *.gif binary
88
86
  *.ico binary
89
87
  *.pdf binary
90
- `);
88
+ `;
89
+
90
+ add(".prettierrc.json", JSON.stringify(prettierConfig, null, "\t"));
91
+ add(".gitattributes", gitAttributes);
91
92
 
92
93
  const repos = [[svc, "Express/TypeScript API and background runtime."]];
93
94
  if (options.ui) repos.push([ui, "Vue/Vite customer interface."]);
@@ -203,7 +204,7 @@ add(`${svc}/package.json`, JSON.stringify({
203
204
  ...(options.worker ? { "dev:worker": "nodemon --legacy-watch --exec ts-node -r tsconfig-paths/register src/worker.ts" } : {}),
204
205
  build: "tsc && tsc-alias", start: "node dist/server.js",
205
206
  ...(options.worker ? { worker: "node dist/worker.js" } : {}),
206
- test: "npm run test:unit && npm run test:integration", "test:unit": "jest --selectProjects unit --runInBand", "test:integration": "jest --selectProjects integration --runInBand", "validate:architecture": "tailframe validate --kind service .", format: "prettier --write src/",
207
+ test: "npm run test:unit && npm run test:integration", "test:unit": "jest --selectProjects unit --runInBand", "test:integration": "jest --selectProjects integration --runInBand", "validate:architecture": "tailframe validate --kind service .", format: "prettier --write src/", "format:check": "prettier --check src/",
207
208
  "test:db:up": "docker compose -f docker-compose.test.yml up -d", "test:db:down": "docker compose -f docker-compose.test.yml down -v",
208
209
  "docker:dev": "docker compose -f docker-compose.dev.yml up",
209
210
  "docker:prod": "docker compose -f docker-compose.yml up -d",
@@ -220,6 +221,7 @@ module.exports = { projects: [
220
221
  { ...base, displayName: "integration", testMatch: ["**/*.integration.test.ts"] }
221
222
  ] };`);
222
223
  add(`${svc}/.prettierrc.json`, JSON.stringify(prettierConfig, null, "\t"));
224
+ add(`${svc}/.gitattributes`, gitAttributes);
223
225
  add(`${svc}/.gitignore`, `node_modules/\ndist/\n.env*\n!.env.example\n*.log\nfirebase-service-account*.json\n`);
224
226
  add(`${svc}/.dockerignore`, `node_modules\ndist\n.git\n.env*\n!.env.example\n*.log\ndata\nfirebase-service-account*.json\n`);
225
227
  if (options.ui) add(".dockerignore", `**/node_modules\n**/dist\n**/.git\n**/.env*\n**/*.log\n**/data\n**/firebase-service-account*.json\n`);
@@ -444,15 +446,19 @@ This service uses MongoDB. Module-owned MongoDB adapters own collection access,
444
446
  - Register routes, workers, and jobs explicitly. Shutdown paths are awaitable and close resources without forcing \`process.exit\`.
445
447
  ${options.ui ? "- In production, serve the compiled UI from `dist/public` with a non-API SPA fallback. Never return the SPA for `/api` requests." : ""}
446
448
 
449
+ ## Production image
450
+ The ECR build targets \`linux/amd64\`. ${options.ui ? "Its default immutable tag is `<service-sha>_<ui-sha>` and production Compose requires that exact tag." : "Its default immutable tag is `<service-sha>` and production Compose requires that exact tag."}${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.` : ""}
451
+
447
452
  ## Tests and validation
448
- Run \`npm run validate:architecture\` after changing files or imports. The generated service also includes unit tests and Mongo-backed integration tests. Run \`npm run test:db:up\`, \`npm test\`, and \`npm run test:db:down\` for the full service check. Test use cases, policies, 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 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 \`npm run docker:push\`, must run outside the sandbox from the first attempt.
453
+ Run \`npm run validate:architecture\` and \`npm run format:check\` after changing files or imports. The generated service also includes unit tests and Mongo-backed integration tests. Run \`npm run test:db:up\`, \`npm test\`, and \`npm run test:db:down\` for the full service check. Test use cases, policies, 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 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 \`npm run docker:push\`, must run outside the sandbox from the first attempt.
449
454
  `);
450
455
 
451
456
  if (options.ui) {
452
457
  const uiDeps = { "@tailwindcss/vite": "^4.1.18", "@vueuse/core": "^14.3.0", axios: "^1.9.0", pinia: "^3.0.1", primevue: "^4.2.5", vue: "^3.5.13", "vue-router": "^4.5.0", ...(options.auth === "firebase" ? { firebase: "^11.0.0" } : {}) };
453
458
  const uiDevDeps = { "@tsconfig/node22": "^22.0.1", "@zaaxch/tailframe": contractVersion, "@types/node": "^22.13.14", "@vitejs/plugin-vue": "^5.2.3", "@vue/test-utils": "^2.4.10", "@vue/tsconfig": "^0.7.0", jsdom: "^29.1.1", "npm-run-all2": "^7.0.2", prettier: "3.5.3", typescript: "~5.8.0", vite: "^6.2.4", vitest: "^4.1.7", "vue-tsc": "^2.2.8" };
454
- add(`${ui}/package.json`, JSON.stringify({ name: ui, version: "0.1.0", private: true, type: "module", scripts: { dev: "vite --host 0.0.0.0", build: "run-p type-check \"build-only {@}\" --", "build-only": "vite build", "type-check": "vue-tsc --build", test: "vitest", "validate:architecture": "tailframe validate --kind ui .", format: "prettier --write src/" }, dependencies: uiDeps, devDependencies: uiDevDeps }, null, "\t"));
459
+ add(`${ui}/package.json`, JSON.stringify({ name: ui, version: "0.1.0", private: true, type: "module", scripts: { dev: "vite --host 0.0.0.0", build: "run-p type-check \"build-only {@}\" --", "build-only": "vite build", "type-check": "vue-tsc --build", test: "vitest run", "test:watch": "vitest", "validate:architecture": "tailframe validate --kind ui .", format: "prettier --write src/", "format:check": "prettier --check src/" }, dependencies: uiDeps, devDependencies: uiDevDeps }, null, "\t"));
455
460
  add(`${ui}/.prettierrc.json`, JSON.stringify(prettierConfig, null, "\t"));
461
+ add(`${ui}/.gitattributes`, gitAttributes);
456
462
  add(`${ui}/tsconfig.json`, JSON.stringify({ files: [], references: [{ path: "./tsconfig.app.json" }, { path: "./tsconfig.node.json" }] }, null, "\t"));
457
463
  add(`${ui}/tsconfig.app.json`, JSON.stringify({ extends: "@vue/tsconfig/tsconfig.dom.json", include: ["env.d.ts", "src/**/*", "src/**/*.vue"], compilerOptions: { composite: true, baseUrl: ".", paths: { "@/*": ["./src/*"] } } }, null, "\t"));
458
464
  add(`${ui}/tsconfig.node.json`, JSON.stringify({ extends: "@tsconfig/node22/tsconfig.json", include: ["vite.config.*"], compilerOptions: { composite: true, types: ["node"] } }, null, "\t"));
@@ -587,13 +593,13 @@ Reuse Vue, PrimeVue, Tailwind, loading, error, and accessibility conventions. Pr
587
593
  - Keep \`src/app/styles.css\` limited to Tailwind imports, theme tokens, and true global base behavior.
588
594
 
589
595
  ## Production packaging
590
- The UI runs through Vite in development but has no production container. The service's multi-stage Dockerfile builds this repository and copies its output into \`dist/public\`; Express serves it with SPA fallback. AWS ECR stores the combined production image.
596
+ The UI runs through Vite in development but has no production container. The service's multi-stage Dockerfile builds this repository 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 \`<service-sha>_<ui-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.` : ""}
591
597
 
592
598
  ## Product language
593
599
  The product domain is undefined. Do not invent entities, workflows, roles, claims, navigation, or customer-facing promises.
594
600
 
595
601
  ## Validation
596
- Run \`npm run validate:architecture\`, then add focused Vitest coverage and run type-check, build, and tests as applicable.
602
+ Run \`npm run validate:architecture\` and \`npm run format:check\`, then add focused Vitest coverage and run type-check, build, and \`npm test\` as applicable. Use \`npm run test:watch\` only for interactive development.
597
603
  `);
598
604
  }
599
605
 
@@ -609,7 +615,7 @@ WORKDIR /app/ui
609
615
  COPY ${ui}/package*.json ./
610
616
  RUN npm ci
611
617
  COPY ${ui}/ ./
612
- RUN npm run build
618
+ ${options.auth === "firebase" ? "RUN --mount=type=secret,id=ui_env,target=/app/ui/.env.production,required=true npm run build" : "RUN npm run build"}
613
619
 
614
620
  FROM node:22.13-bookworm-slim
615
621
  WORKDIR /app
@@ -650,18 +656,23 @@ set -euo pipefail
650
656
  : "\${AWS_ACCOUNT_ID:?Set AWS_ACCOUNT_ID}"
651
657
  : "\${AWS_REGION:?Set AWS_REGION}"
652
658
 
659
+ SVC_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
660
+ BUILD_CONTEXT="${options.ui ? `$(cd "\${SVC_DIR}/.." && pwd)` : `\${SVC_DIR}`}"
661
+ ${options.ui ? `UI_DIR="\${BUILD_CONTEXT}/${ui}"
662
+ SVC_SHA="$(git -C "\${SVC_DIR}" rev-parse --short HEAD)"
663
+ UI_SHA="$(git -C "\${UI_DIR}" rev-parse --short HEAD)"` : `SVC_SHA="$(git -C "\${SVC_DIR}" rev-parse --short HEAD)"`}
664
+
653
665
  ECR_REPOSITORY="\${ECR_REPOSITORY:-${options.name}}"
654
- IMAGE_TAG="\${IMAGE_TAG:-latest}"
666
+ IMAGE_TAG="\${IMAGE_TAG:-${options.ui ? `\${SVC_SHA}_\${UI_SHA}` : `\${SVC_SHA}`}}"
655
667
  REGISTRY="\${AWS_ACCOUNT_ID}.dkr.ecr.\${AWS_REGION}.amazonaws.com"
656
668
  IMAGE="\${REGISTRY}/\${ECR_REPOSITORY}:\${IMAGE_TAG}"
657
- SVC_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
658
- BUILD_CONTEXT="${options.ui ? `$(cd "\${SVC_DIR}/.." && pwd)` : `\${SVC_DIR}`}"
659
669
  DOCKERFILE="\${SVC_DIR}/Dockerfile"
670
+ ${options.ui && options.auth === "firebase" ? `UI_ENV_FILE="\${UI_ENV_FILE:-\${UI_DIR}/.env.production}"` : ""}
660
671
 
661
672
  aws ecr get-login-password --region "\${AWS_REGION}" |
662
673
  docker login --username AWS --password-stdin "\${REGISTRY}"
663
674
 
664
- docker build --file "\${DOCKERFILE}" --tag "\${IMAGE}" "\${BUILD_CONTEXT}"
675
+ docker build --platform linux/amd64${options.ui && options.auth === "firebase" ? ` --secret "id=ui_env,src=\${UI_ENV_FILE}"` : ""} --file "\${DOCKERFILE}" --tag "\${IMAGE}" "\${BUILD_CONTEXT}"
665
676
  docker push "\${IMAGE}"
666
677
 
667
678
  echo "Pushed \${IMAGE}"
@@ -683,7 +694,7 @@ const dependsBlock = `\n depends_on:\n${dependencies.map(([dependency, condit
683
694
  const databaseEnvironmentBlock = `\n environment:\n MONGODB_URI: "mongodb://mongodb:27017/?replicaSet=rs0&directConnection=true"`;
684
695
  const volumeBlock = serviceVolumes.length ? `\n volumes:\n${serviceVolumes.join("\n")}` : "";
685
696
  const workerService = options.worker ? `\n worker:
686
- image: \${ECR_IMAGE:?Set ECR_IMAGE}:\${IMAGE_TAG:-latest}
697
+ image: \${ECR_IMAGE:?Set ECR_IMAGE}:\${IMAGE_TAG:?Set IMAGE_TAG}
687
698
  env_file:
688
699
  - .env.production
689
700
  command: ["node", "dist/worker.js"]${databaseEnvironmentBlock}${volumeBlock}${dependsBlock}
@@ -728,7 +739,7 @@ const namedVolumes = [
728
739
  add(`${svc}/docker-compose.yml`, `name: ${options.name}-prod
729
740
  services:
730
741
  ${options.name}:
731
- image: \${ECR_IMAGE:?Set ECR_IMAGE}:\${IMAGE_TAG:-latest}
742
+ image: \${ECR_IMAGE:?Set ECR_IMAGE}:\${IMAGE_TAG:?Set IMAGE_TAG}
732
743
  env_file:
733
744
  - .env.production
734
745
  ports: