@zaaxch/tailframe 4.0.8 → 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 +60 -19
- 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
|
@@ -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
|
|
|
@@ -1552,17 +1581,17 @@ networks:
|
|
|
1552
1581
|
|
|
1553
1582
|
const devWorker = options.worker ? `\n worker:
|
|
1554
1583
|
build:
|
|
1555
|
-
context:
|
|
1556
|
-
dockerfile: Dockerfile.dev
|
|
1584
|
+
context: ../..
|
|
1585
|
+
dockerfile: apps/service/Dockerfile.dev
|
|
1557
1586
|
env_file:
|
|
1558
1587
|
- .env.development
|
|
1559
|
-
command: ["
|
|
1588
|
+
command: ["pnpm", "dev:worker"]${developmentEnvironmentBlock}
|
|
1560
1589
|
volumes:
|
|
1561
1590
|
${devVolumes.join("\n")}${devDependsBlock}` : "";
|
|
1562
1591
|
const devDatabase = postgres ? `\n postgres:
|
|
1563
1592
|
image: postgres:17
|
|
1564
|
-
|
|
1565
|
-
- "5432
|
|
1593
|
+
expose:
|
|
1594
|
+
- "5432"
|
|
1566
1595
|
environment:
|
|
1567
1596
|
POSTGRES_DB: ${databaseName}
|
|
1568
1597
|
POSTGRES_USER: ${options.name}-app
|
|
@@ -1576,8 +1605,8 @@ const devDatabase = postgres ? `\n postgres:
|
|
|
1576
1605
|
retries: 30` : `\n mongodb:
|
|
1577
1606
|
image: mongo:8
|
|
1578
1607
|
command: mongod --replSet rs0 --bind_ip_all
|
|
1579
|
-
|
|
1580
|
-
- "27017
|
|
1608
|
+
expose:
|
|
1609
|
+
- "27017"
|
|
1581
1610
|
volumes:
|
|
1582
1611
|
- mongodb-dev-data:/data/db
|
|
1583
1612
|
healthcheck:
|
|
@@ -1604,10 +1633,22 @@ const devDatabase = postgres ? `\n postgres:
|
|
|
1604
1633
|
'`;
|
|
1605
1634
|
const devRedis = options.redis ? `\n redis:
|
|
1606
1635
|
image: redis:7-alpine
|
|
1607
|
-
|
|
1608
|
-
- "6379
|
|
1636
|
+
expose:
|
|
1637
|
+
- "6379"
|
|
1609
1638
|
volumes:
|
|
1610
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"` : "";
|
|
1611
1652
|
const devNamedVolumes = [
|
|
1612
1653
|
postgres ? " postgres-dev-data:" : " mongodb-dev-data:",
|
|
1613
1654
|
...(options.redis ? [" redis-dev-data:"] : [])
|
|
@@ -1616,11 +1657,11 @@ add(`${svc}/docker-compose.dev.yml`, `name: ${options.name}-dev
|
|
|
1616
1657
|
services:
|
|
1617
1658
|
schema-init:
|
|
1618
1659
|
build:
|
|
1619
|
-
context:
|
|
1620
|
-
dockerfile: Dockerfile.dev
|
|
1660
|
+
context: ../..
|
|
1661
|
+
dockerfile: apps/service/Dockerfile.dev
|
|
1621
1662
|
env_file:
|
|
1622
1663
|
- .env.development
|
|
1623
|
-
command: ["
|
|
1664
|
+
command: ["pnpm", "schema:apply:dev"]${developmentEnvironmentBlock}
|
|
1624
1665
|
volumes:
|
|
1625
1666
|
${devVolumes.join("\n")}
|
|
1626
1667
|
depends_on:
|
|
@@ -1628,14 +1669,14 @@ ${devVolumes.join("\n")}
|
|
|
1628
1669
|
condition: ${postgres ? "service_healthy" : "service_completed_successfully"}
|
|
1629
1670
|
${options.name}:
|
|
1630
1671
|
build:
|
|
1631
|
-
context:
|
|
1632
|
-
dockerfile: Dockerfile.dev
|
|
1672
|
+
context: ../..
|
|
1673
|
+
dockerfile: apps/service/Dockerfile.dev
|
|
1633
1674
|
env_file:
|
|
1634
1675
|
- .env.development
|
|
1635
|
-
|
|
1636
|
-
- "3000
|
|
1676
|
+
expose:
|
|
1677
|
+
- "3000"${developmentEnvironmentBlock}
|
|
1637
1678
|
volumes:
|
|
1638
|
-
${devVolumes.join("\n")}${devDependsBlock}${devWorker}${devDatabase}${devRedis}
|
|
1679
|
+
${devVolumes.join("\n")}${devDependsBlock}${devUi}${devWorker}${devDatabase}${devRedis}
|
|
1639
1680
|
volumes:
|
|
1640
1681
|
${devNamedVolumes.join("\n")}
|
|
1641
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) {
|