@polymorphism-tech/morph-spec 8.29.0 → 8.31.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.
- package/CLAUDE.md +6 -1
- package/README.md +13 -2
- package/bin/morph-spec.js +48 -0
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -0
- package/framework/MORPH.md +2 -1
- package/framework/agents.json +6 -6
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +18 -0
- package/framework/evals/rubrics/code-quality.md +17 -0
- package/framework/evals/rubrics/condensed.md +3 -1
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/phase-utils.js +2 -0
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/mcp/registry.json +39 -4
- package/framework/rubrics/backend.json +2 -2
- package/framework/rubrics/default.json +2 -2
- package/framework/rubrics/frontend.json +2 -2
- package/framework/schemas/tasks.schema.json +25 -0
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +12 -3
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +12 -2
- package/framework/skills/core/morph-plan/SKILL.md +45 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +11 -3
- package/framework/skills/core/morph-stack-scan/SKILL.md +13 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +231 -231
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/context/ARCHITECTURE.md +20 -0
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/graph/affected.js +89 -0
- package/src/commands/graph/build.js +94 -0
- package/src/commands/graph/explain.js +96 -0
- package/src/commands/graph/status.js +93 -0
- package/src/commands/index.js +13 -13
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +11 -0
- package/src/core/paths/output-schema.js +7 -0
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +45 -4
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/graph/god-nodes.js +75 -0
- package/src/lib/graph/graph-health.js +61 -0
- package/src/lib/graph/graph-loader.js +86 -0
- package/src/lib/graph/graphify-runtime.js +100 -0
- package/src/lib/graph/graphifyignore.js +86 -0
- package/src/lib/graph/sanitize.js +280 -0
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/tasks-json-parser.js +26 -0
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/reuse/index.js +10 -0
- package/src/lib/validators/reuse/reuse-validator.js +324 -0
- package/src/lib/validators/reuse/symbol-scanner.js +144 -0
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +40 -1
- package/src/scripts/setup-infra.js +60 -1
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +40 -0
- package/src/utils/skills-installer.js +187 -187
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
# Architecture Decision Records - {{titleCase FEATURE_NAME}}
|
|
2
|
-
|
|
3
|
-
## ADR-001: {Decision Title}
|
|
4
|
-
|
|
5
|
-
**Date:** {{DATE}}
|
|
6
|
-
**Status:** Proposed | Accepted | Deprecated | Superseded
|
|
7
|
-
**Deciders:** MORPH Agents, Developer
|
|
8
|
-
|
|
9
|
-
### Context
|
|
10
|
-
|
|
11
|
-
{Describe the context and problem that led to this decision}
|
|
12
|
-
|
|
13
|
-
### Decision
|
|
14
|
-
|
|
15
|
-
{Describe the decision made}
|
|
16
|
-
|
|
17
|
-
### Consequences
|
|
18
|
-
|
|
19
|
-
**Pros:**
|
|
20
|
-
- {Positive consequence 1}
|
|
21
|
-
- {Positive consequence 2}
|
|
22
|
-
|
|
23
|
-
**Cons:**
|
|
24
|
-
- {Negative consequence 1}
|
|
25
|
-
|
|
26
|
-
**Risks:**
|
|
27
|
-
- {Identified risk}
|
|
28
|
-
|
|
29
|
-
### Alternatives Considered
|
|
30
|
-
|
|
31
|
-
#### Option A: {Name}
|
|
32
|
-
- Pros: {advantages}
|
|
33
|
-
- Cons: {disadvantages}
|
|
34
|
-
- **Rejected because:** {reason}
|
|
35
|
-
|
|
36
|
-
#### Option B: {Name}
|
|
37
|
-
- Pros: {advantages}
|
|
38
|
-
- Cons: {disadvantages}
|
|
39
|
-
- **Rejected because:** {reason}
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
{{#if (eq STACK "nextjs-neon")}}
|
|
44
|
-
## ADR-002: Database Access Strategy
|
|
45
|
-
|
|
46
|
-
**Date:** {{DATE}}
|
|
47
|
-
**Status:** Accepted
|
|
48
|
-
|
|
49
|
-
### Context
|
|
50
|
-
|
|
51
|
-
Need to decide how the .NET API accesses Neon PostgreSQL.
|
|
52
|
-
|
|
53
|
-
### Decision
|
|
54
|
-
|
|
55
|
-
Use Dapper with Npgsql for direct SQL queries. No EF Core.
|
|
56
|
-
|
|
57
|
-
### Consequences
|
|
58
|
-
|
|
59
|
-
**Pros:**
|
|
60
|
-
- Full control over SQL queries
|
|
61
|
-
- Better performance (no ORM overhead)
|
|
62
|
-
- Direct access to PostgreSQL features (jsonb, pgvector, functions)
|
|
63
|
-
- RLS policies apply naturally through connection
|
|
64
|
-
|
|
65
|
-
**Cons:**
|
|
66
|
-
- No automatic migrations (use Neon CLI / dotnet ef instead)
|
|
67
|
-
- Manual SQL query writing
|
|
68
|
-
- No change tracking
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## ADR-003: Auth Strategy
|
|
73
|
-
|
|
74
|
-
**Date:** {{DATE}}
|
|
75
|
-
**Status:** Accepted
|
|
76
|
-
|
|
77
|
-
### Context
|
|
78
|
-
|
|
79
|
-
Need authentication for both Next.js frontend and .NET API.
|
|
80
|
-
|
|
81
|
-
### Decision
|
|
82
|
-
|
|
83
|
-
Use Neon Auth (Better Auth) for authentication. Auth data lives in the Neon database. JWT validated by .NET API middleware.
|
|
84
|
-
|
|
85
|
-
### Consequences
|
|
86
|
-
|
|
87
|
-
**Pros:**
|
|
88
|
-
- Auth data in same database — no external provider, no sync delays
|
|
89
|
-
- RLS policies work with Neon Auth JWT via auth.user_id() / pg_session_jwt
|
|
90
|
-
- Built-in social login, email/password via @neondatabase/auth SDK
|
|
91
|
-
- Branch isolation — each Neon branch gets isolated auth data
|
|
92
|
-
- No webhooks needed — query neon_auth schema directly
|
|
93
|
-
|
|
94
|
-
**Cons:**
|
|
95
|
-
- Neon Auth is currently in Beta
|
|
96
|
-
- JWT must be forwarded to .NET API for backend auth
|
|
97
|
-
{{/if}}
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Template for New ADRs
|
|
102
|
-
|
|
103
|
-
```markdown
|
|
104
|
-
## ADR-XXX: {Title}
|
|
105
|
-
|
|
106
|
-
**Date:**
|
|
107
|
-
**Status:** Proposed | Accepted | Deprecated | Superseded
|
|
108
|
-
|
|
109
|
-
### Context
|
|
110
|
-
{Why is this decision needed?}
|
|
111
|
-
|
|
112
|
-
### Decision
|
|
113
|
-
{What was decided?}
|
|
114
|
-
|
|
115
|
-
### Consequences
|
|
116
|
-
**Pros:** {list}
|
|
117
|
-
**Cons:** {list}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
*Generated by MORPH Framework*
|
|
1
|
+
# Architecture Decision Records - {{titleCase FEATURE_NAME}}
|
|
2
|
+
|
|
3
|
+
## ADR-001: {Decision Title}
|
|
4
|
+
|
|
5
|
+
**Date:** {{DATE}}
|
|
6
|
+
**Status:** Proposed | Accepted | Deprecated | Superseded
|
|
7
|
+
**Deciders:** MORPH Agents, Developer
|
|
8
|
+
|
|
9
|
+
### Context
|
|
10
|
+
|
|
11
|
+
{Describe the context and problem that led to this decision}
|
|
12
|
+
|
|
13
|
+
### Decision
|
|
14
|
+
|
|
15
|
+
{Describe the decision made}
|
|
16
|
+
|
|
17
|
+
### Consequences
|
|
18
|
+
|
|
19
|
+
**Pros:**
|
|
20
|
+
- {Positive consequence 1}
|
|
21
|
+
- {Positive consequence 2}
|
|
22
|
+
|
|
23
|
+
**Cons:**
|
|
24
|
+
- {Negative consequence 1}
|
|
25
|
+
|
|
26
|
+
**Risks:**
|
|
27
|
+
- {Identified risk}
|
|
28
|
+
|
|
29
|
+
### Alternatives Considered
|
|
30
|
+
|
|
31
|
+
#### Option A: {Name}
|
|
32
|
+
- Pros: {advantages}
|
|
33
|
+
- Cons: {disadvantages}
|
|
34
|
+
- **Rejected because:** {reason}
|
|
35
|
+
|
|
36
|
+
#### Option B: {Name}
|
|
37
|
+
- Pros: {advantages}
|
|
38
|
+
- Cons: {disadvantages}
|
|
39
|
+
- **Rejected because:** {reason}
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
{{#if (eq STACK "nextjs-neon")}}
|
|
44
|
+
## ADR-002: Database Access Strategy
|
|
45
|
+
|
|
46
|
+
**Date:** {{DATE}}
|
|
47
|
+
**Status:** Accepted
|
|
48
|
+
|
|
49
|
+
### Context
|
|
50
|
+
|
|
51
|
+
Need to decide how the .NET API accesses Neon PostgreSQL.
|
|
52
|
+
|
|
53
|
+
### Decision
|
|
54
|
+
|
|
55
|
+
Use Dapper with Npgsql for direct SQL queries. No EF Core.
|
|
56
|
+
|
|
57
|
+
### Consequences
|
|
58
|
+
|
|
59
|
+
**Pros:**
|
|
60
|
+
- Full control over SQL queries
|
|
61
|
+
- Better performance (no ORM overhead)
|
|
62
|
+
- Direct access to PostgreSQL features (jsonb, pgvector, functions)
|
|
63
|
+
- RLS policies apply naturally through connection
|
|
64
|
+
|
|
65
|
+
**Cons:**
|
|
66
|
+
- No automatic migrations (use Neon CLI / dotnet ef instead)
|
|
67
|
+
- Manual SQL query writing
|
|
68
|
+
- No change tracking
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## ADR-003: Auth Strategy
|
|
73
|
+
|
|
74
|
+
**Date:** {{DATE}}
|
|
75
|
+
**Status:** Accepted
|
|
76
|
+
|
|
77
|
+
### Context
|
|
78
|
+
|
|
79
|
+
Need authentication for both Next.js frontend and .NET API.
|
|
80
|
+
|
|
81
|
+
### Decision
|
|
82
|
+
|
|
83
|
+
Use Neon Auth (Better Auth) for authentication. Auth data lives in the Neon database. JWT validated by .NET API middleware.
|
|
84
|
+
|
|
85
|
+
### Consequences
|
|
86
|
+
|
|
87
|
+
**Pros:**
|
|
88
|
+
- Auth data in same database — no external provider, no sync delays
|
|
89
|
+
- RLS policies work with Neon Auth JWT via auth.user_id() / pg_session_jwt
|
|
90
|
+
- Built-in social login, email/password via @neondatabase/auth SDK
|
|
91
|
+
- Branch isolation — each Neon branch gets isolated auth data
|
|
92
|
+
- No webhooks needed — query neon_auth schema directly
|
|
93
|
+
|
|
94
|
+
**Cons:**
|
|
95
|
+
- Neon Auth is currently in Beta
|
|
96
|
+
- JWT must be forwarded to .NET API for backend auth
|
|
97
|
+
{{/if}}
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Template for New ADRs
|
|
102
|
+
|
|
103
|
+
```markdown
|
|
104
|
+
## ADR-XXX: {Title}
|
|
105
|
+
|
|
106
|
+
**Date:**
|
|
107
|
+
**Status:** Proposed | Accepted | Deprecated | Superseded
|
|
108
|
+
|
|
109
|
+
### Context
|
|
110
|
+
{Why is this decision needed?}
|
|
111
|
+
|
|
112
|
+
### Decision
|
|
113
|
+
{What was decided?}
|
|
114
|
+
|
|
115
|
+
### Consequences
|
|
116
|
+
**Pros:** {list}
|
|
117
|
+
**Cons:** {list}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
*Generated by MORPH Framework*
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
# Multi-stage Next.js production build for Coolify
|
|
2
|
-
# Stage 1: Dependencies
|
|
3
|
-
FROM node:20-alpine AS deps
|
|
4
|
-
RUN apk add --no-cache libc6-compat
|
|
5
|
-
WORKDIR /app
|
|
6
|
-
|
|
7
|
-
COPY package.json package-lock.json* ./
|
|
8
|
-
RUN npm ci --only=production && npm cache clean --force
|
|
9
|
-
|
|
10
|
-
# Stage 2: Build
|
|
11
|
-
FROM node:20-alpine AS builder
|
|
12
|
-
WORKDIR /app
|
|
13
|
-
|
|
14
|
-
COPY --from=deps /app/node_modules ./node_modules
|
|
15
|
-
COPY . .
|
|
16
|
-
|
|
17
|
-
# Build args for NEXT_PUBLIC_ variables (must be available at build time)
|
|
18
|
-
ARG NEXT_PUBLIC_APP_URL
|
|
19
|
-
ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
|
|
20
|
-
|
|
21
|
-
RUN npm run build
|
|
22
|
-
|
|
23
|
-
# Stage 3: Production runner
|
|
24
|
-
FROM node:20-alpine AS runner
|
|
25
|
-
WORKDIR /app
|
|
26
|
-
|
|
27
|
-
ENV NODE_ENV=production
|
|
28
|
-
ENV NEXT_TELEMETRY_DISABLED=1
|
|
29
|
-
|
|
30
|
-
RUN addgroup --system --gid 1001 nodejs
|
|
31
|
-
RUN adduser --system --uid 1001 nextjs
|
|
32
|
-
|
|
33
|
-
COPY --from=builder /app/public ./public
|
|
34
|
-
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
35
|
-
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
36
|
-
|
|
37
|
-
USER nextjs
|
|
38
|
-
|
|
39
|
-
EXPOSE 3000
|
|
40
|
-
ENV PORT=3000
|
|
41
|
-
ENV HOSTNAME="0.0.0.0"
|
|
42
|
-
|
|
43
|
-
CMD ["node", "server.js"]
|
|
1
|
+
# Multi-stage Next.js production build for Coolify
|
|
2
|
+
# Stage 1: Dependencies
|
|
3
|
+
FROM node:20-alpine AS deps
|
|
4
|
+
RUN apk add --no-cache libc6-compat
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
COPY package.json package-lock.json* ./
|
|
8
|
+
RUN npm ci --only=production && npm cache clean --force
|
|
9
|
+
|
|
10
|
+
# Stage 2: Build
|
|
11
|
+
FROM node:20-alpine AS builder
|
|
12
|
+
WORKDIR /app
|
|
13
|
+
|
|
14
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
15
|
+
COPY . .
|
|
16
|
+
|
|
17
|
+
# Build args for NEXT_PUBLIC_ variables (must be available at build time)
|
|
18
|
+
ARG NEXT_PUBLIC_APP_URL
|
|
19
|
+
ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
|
|
20
|
+
|
|
21
|
+
RUN npm run build
|
|
22
|
+
|
|
23
|
+
# Stage 3: Production runner
|
|
24
|
+
FROM node:20-alpine AS runner
|
|
25
|
+
WORKDIR /app
|
|
26
|
+
|
|
27
|
+
ENV NODE_ENV=production
|
|
28
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
29
|
+
|
|
30
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
31
|
+
RUN adduser --system --uid 1001 nextjs
|
|
32
|
+
|
|
33
|
+
COPY --from=builder /app/public ./public
|
|
34
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
35
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
36
|
+
|
|
37
|
+
USER nextjs
|
|
38
|
+
|
|
39
|
+
EXPOSE 3000
|
|
40
|
+
ENV PORT=3000
|
|
41
|
+
ENV HOSTNAME="0.0.0.0"
|
|
42
|
+
|
|
43
|
+
CMD ["node", "server.js"]
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
// features/{{kebabCase featureName}}/components/{{kebabCase featureName}}-list.tsx
|
|
2
|
-
'use client';
|
|
3
|
-
|
|
4
|
-
import { use{{pascalCase featureName}}s } from '@/features/{{kebabCase featureName}}/hooks/use-{{kebabCase featureName}}s';
|
|
5
|
-
import { DataTable } from '@/components/data-table';
|
|
6
|
-
import type { {{pascalCase featureName}} } from '@/features/{{kebabCase featureName}}/types/{{kebabCase featureName}}.types';
|
|
7
|
-
import type { ColumnDef } from '@tanstack/react-table';
|
|
8
|
-
|
|
9
|
-
const columns: ColumnDef<{{pascalCase featureName}}>[] = [
|
|
10
|
-
{ accessorKey: 'id', header: 'ID' },
|
|
11
|
-
{ accessorKey: 'name', header: 'Name' },
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
interface {{pascalCase featureName}}ListProps {
|
|
15
|
-
initialData?: {{pascalCase featureName}}[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function {{pascalCase featureName}}List({ initialData }: {{pascalCase featureName}}ListProps) {
|
|
19
|
-
const { data = initialData ?? [], isLoading } = use{{pascalCase featureName}}s();
|
|
20
|
-
|
|
21
|
-
if (isLoading) {
|
|
22
|
-
return <div className="flex items-center justify-center h-32">Loading...</div>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return <DataTable columns={columns} data={data} />;
|
|
26
|
-
}
|
|
1
|
+
// features/{{kebabCase featureName}}/components/{{kebabCase featureName}}-list.tsx
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
import { use{{pascalCase featureName}}s } from '@/features/{{kebabCase featureName}}/hooks/use-{{kebabCase featureName}}s';
|
|
5
|
+
import { DataTable } from '@/components/data-table';
|
|
6
|
+
import type { {{pascalCase featureName}} } from '@/features/{{kebabCase featureName}}/types/{{kebabCase featureName}}.types';
|
|
7
|
+
import type { ColumnDef } from '@tanstack/react-table';
|
|
8
|
+
|
|
9
|
+
const columns: ColumnDef<{{pascalCase featureName}}>[] = [
|
|
10
|
+
{ accessorKey: 'id', header: 'ID' },
|
|
11
|
+
{ accessorKey: 'name', header: 'Name' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
interface {{pascalCase featureName}}ListProps {
|
|
15
|
+
initialData?: {{pascalCase featureName}}[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function {{pascalCase featureName}}List({ initialData }: {{pascalCase featureName}}ListProps) {
|
|
19
|
+
const { data = initialData ?? [], isLoading } = use{{pascalCase featureName}}s();
|
|
20
|
+
|
|
21
|
+
if (isLoading) {
|
|
22
|
+
return <div className="flex items-center justify-center h-32">Loading...</div>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return <DataTable columns={columns} data={data} />;
|
|
26
|
+
}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
// env.mjs — Zod-validated environment variables
|
|
2
|
-
// Crashes at startup if required vars are missing — no silent undefined
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
const serverSchema = z.object({
|
|
6
|
-
API_URL: z.string().url('API_URL must be a valid URL'),
|
|
7
|
-
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
const clientSchema = z.object({
|
|
11
|
-
NEXT_PUBLIC_APP_URL: z.string().url().optional(),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// Validate server-side env (only available on server)
|
|
15
|
-
const serverEnv = serverSchema.safeParse(process.env);
|
|
16
|
-
if (!serverEnv.success) {
|
|
17
|
-
console.error('❌ Invalid server environment variables:');
|
|
18
|
-
console.error(serverEnv.error.flatten().fieldErrors);
|
|
19
|
-
throw new Error('Invalid environment variables');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Validate client-side env
|
|
23
|
-
const clientEnv = clientSchema.safeParse({
|
|
24
|
-
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
|
25
|
-
});
|
|
26
|
-
if (!clientEnv.success) {
|
|
27
|
-
console.error('❌ Invalid client environment variables:');
|
|
28
|
-
console.error(clientEnv.error.flatten().fieldErrors);
|
|
29
|
-
throw new Error('Invalid client environment variables');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const env = { ...serverEnv.data, ...clientEnv.data };
|
|
1
|
+
// env.mjs — Zod-validated environment variables
|
|
2
|
+
// Crashes at startup if required vars are missing — no silent undefined
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
const serverSchema = z.object({
|
|
6
|
+
API_URL: z.string().url('API_URL must be a valid URL'),
|
|
7
|
+
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const clientSchema = z.object({
|
|
11
|
+
NEXT_PUBLIC_APP_URL: z.string().url().optional(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Validate server-side env (only available on server)
|
|
15
|
+
const serverEnv = serverSchema.safeParse(process.env);
|
|
16
|
+
if (!serverEnv.success) {
|
|
17
|
+
console.error('❌ Invalid server environment variables:');
|
|
18
|
+
console.error(serverEnv.error.flatten().fieldErrors);
|
|
19
|
+
throw new Error('Invalid environment variables');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Validate client-side env
|
|
23
|
+
const clientEnv = clientSchema.safeParse({
|
|
24
|
+
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
|
25
|
+
});
|
|
26
|
+
if (!clientEnv.success) {
|
|
27
|
+
console.error('❌ Invalid client environment variables:');
|
|
28
|
+
console.error(clientEnv.error.flatten().fieldErrors);
|
|
29
|
+
throw new Error('Invalid client environment variables');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const env = { ...serverEnv.data, ...clientEnv.data };
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
// features/{{kebabCase featureName}}/components/create-{{kebabCase featureName}}-form.tsx
|
|
2
|
-
'use client';
|
|
3
|
-
|
|
4
|
-
import { useForm } from 'react-hook-form';
|
|
5
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
-
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
|
|
7
|
-
import { Input } from '@/components/ui/input';
|
|
8
|
-
import { Button } from '@/components/ui/button';
|
|
9
|
-
import { useCreate{{pascalCase featureName}} } from '@/features/{{kebabCase featureName}}/hooks/use-create-{{kebabCase featureName}}';
|
|
10
|
-
import { create{{pascalCase featureName}}Schema, type Create{{pascalCase featureName}}Input } from '@/features/{{kebabCase featureName}}/types/{{kebabCase featureName}}.schemas';
|
|
11
|
-
|
|
12
|
-
interface Create{{pascalCase featureName}}FormProps {
|
|
13
|
-
onSuccess?: () => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function Create{{pascalCase featureName}}Form({ onSuccess }: Create{{pascalCase featureName}}FormProps) {
|
|
17
|
-
const form = useForm<Create{{pascalCase featureName}}Input>({
|
|
18
|
-
resolver: zodResolver(create{{pascalCase featureName}}Schema),
|
|
19
|
-
defaultValues: { name: '' },
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const { mutate: create{{pascalCase featureName}}, isPending } = useCreate{{pascalCase featureName}}();
|
|
23
|
-
|
|
24
|
-
function onSubmit(values: Create{{pascalCase featureName}}Input) {
|
|
25
|
-
create{{pascalCase featureName}}(values, {
|
|
26
|
-
onSuccess: () => { form.reset(); onSuccess?.(); },
|
|
27
|
-
onError: (error) => { form.setError('root', { message: error.message }); },
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<Form {...form}>
|
|
33
|
-
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
34
|
-
<FormField
|
|
35
|
-
control={form.control}
|
|
36
|
-
name="name"
|
|
37
|
-
render={({ field }) => (
|
|
38
|
-
<FormItem>
|
|
39
|
-
<FormLabel>Name</FormLabel>
|
|
40
|
-
<FormControl>
|
|
41
|
-
<Input placeholder="Enter name" {...field} />
|
|
42
|
-
</FormControl>
|
|
43
|
-
<FormMessage />
|
|
44
|
-
</FormItem>
|
|
45
|
-
)}
|
|
46
|
-
/>
|
|
47
|
-
{form.formState.errors.root && (
|
|
48
|
-
<p className="text-sm text-destructive">{form.formState.errors.root.message}</p>
|
|
49
|
-
)}
|
|
50
|
-
<Button type="submit" disabled={isPending}>
|
|
51
|
-
{isPending ? 'Creating...' : 'Create {{pascalCase featureName}}'}
|
|
52
|
-
</Button>
|
|
53
|
-
</form>
|
|
54
|
-
</Form>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
1
|
+
// features/{{kebabCase featureName}}/components/create-{{kebabCase featureName}}-form.tsx
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
import { useForm } from 'react-hook-form';
|
|
5
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
+
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
|
|
7
|
+
import { Input } from '@/components/ui/input';
|
|
8
|
+
import { Button } from '@/components/ui/button';
|
|
9
|
+
import { useCreate{{pascalCase featureName}} } from '@/features/{{kebabCase featureName}}/hooks/use-create-{{kebabCase featureName}}';
|
|
10
|
+
import { create{{pascalCase featureName}}Schema, type Create{{pascalCase featureName}}Input } from '@/features/{{kebabCase featureName}}/types/{{kebabCase featureName}}.schemas';
|
|
11
|
+
|
|
12
|
+
interface Create{{pascalCase featureName}}FormProps {
|
|
13
|
+
onSuccess?: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Create{{pascalCase featureName}}Form({ onSuccess }: Create{{pascalCase featureName}}FormProps) {
|
|
17
|
+
const form = useForm<Create{{pascalCase featureName}}Input>({
|
|
18
|
+
resolver: zodResolver(create{{pascalCase featureName}}Schema),
|
|
19
|
+
defaultValues: { name: '' },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const { mutate: create{{pascalCase featureName}}, isPending } = useCreate{{pascalCase featureName}}();
|
|
23
|
+
|
|
24
|
+
function onSubmit(values: Create{{pascalCase featureName}}Input) {
|
|
25
|
+
create{{pascalCase featureName}}(values, {
|
|
26
|
+
onSuccess: () => { form.reset(); onSuccess?.(); },
|
|
27
|
+
onError: (error) => { form.setError('root', { message: error.message }); },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Form {...form}>
|
|
33
|
+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
34
|
+
<FormField
|
|
35
|
+
control={form.control}
|
|
36
|
+
name="name"
|
|
37
|
+
render={({ field }) => (
|
|
38
|
+
<FormItem>
|
|
39
|
+
<FormLabel>Name</FormLabel>
|
|
40
|
+
<FormControl>
|
|
41
|
+
<Input placeholder="Enter name" {...field} />
|
|
42
|
+
</FormControl>
|
|
43
|
+
<FormMessage />
|
|
44
|
+
</FormItem>
|
|
45
|
+
)}
|
|
46
|
+
/>
|
|
47
|
+
{form.formState.errors.root && (
|
|
48
|
+
<p className="text-sm text-destructive">{form.formState.errors.root.message}</p>
|
|
49
|
+
)}
|
|
50
|
+
<Button type="submit" disabled={isPending}>
|
|
51
|
+
{isPending ? 'Creating...' : 'Create {{pascalCase featureName}}'}
|
|
52
|
+
</Button>
|
|
53
|
+
</form>
|
|
54
|
+
</Form>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
// app/(dashboard)/{{kebabCase featureName}}/page.tsx
|
|
2
|
-
// Server Component — fetches initial data, renders feature component
|
|
3
|
-
import { {{pascalCase featureName}}List } from '@/features/{{kebabCase featureName}}';
|
|
4
|
-
|
|
5
|
-
async function get{{pascalCase featureName}}List() {
|
|
6
|
-
const res = await fetch(`${process.env.API_URL}/api/{{kebabCase featureName}}`, {
|
|
7
|
-
next: { revalidate: 30 },
|
|
8
|
-
headers: { 'Content-Type': 'application/json' },
|
|
9
|
-
});
|
|
10
|
-
if (!res.ok) throw new Error('Failed to fetch {{featureName}}');
|
|
11
|
-
return res.json();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default async function {{pascalCase featureName}}Page() {
|
|
15
|
-
const data = await get{{pascalCase featureName}}List();
|
|
16
|
-
return (
|
|
17
|
-
<div className="container py-6">
|
|
18
|
-
<h1 className="text-2xl font-bold mb-6">{{pascalCase featureName}}</h1>
|
|
19
|
-
<{{pascalCase featureName}}List initialData={data} />
|
|
20
|
-
</div>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
1
|
+
// app/(dashboard)/{{kebabCase featureName}}/page.tsx
|
|
2
|
+
// Server Component — fetches initial data, renders feature component
|
|
3
|
+
import { {{pascalCase featureName}}List } from '@/features/{{kebabCase featureName}}';
|
|
4
|
+
|
|
5
|
+
async function get{{pascalCase featureName}}List() {
|
|
6
|
+
const res = await fetch(`${process.env.API_URL}/api/{{kebabCase featureName}}`, {
|
|
7
|
+
next: { revalidate: 30 },
|
|
8
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
if (!res.ok) throw new Error('Failed to fetch {{featureName}}');
|
|
11
|
+
return res.json();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default async function {{pascalCase featureName}}Page() {
|
|
15
|
+
const data = await get{{pascalCase featureName}}List();
|
|
16
|
+
return (
|
|
17
|
+
<div className="container py-6">
|
|
18
|
+
<h1 className="text-2xl font-bold mb-6">{{pascalCase featureName}}</h1>
|
|
19
|
+
<{{pascalCase featureName}}List initialData={data} />
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
-
"allowJs": false,
|
|
6
|
-
"skipLibCheck": true,
|
|
7
|
-
"strict": true,
|
|
8
|
-
"noUncheckedIndexedAccess": true,
|
|
9
|
-
"noImplicitOverride": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"noEmit": true,
|
|
12
|
-
"esModuleInterop": true,
|
|
13
|
-
"module": "esnext",
|
|
14
|
-
"moduleResolution": "bundler",
|
|
15
|
-
"resolveJsonModule": true,
|
|
16
|
-
"isolatedModules": true,
|
|
17
|
-
"jsx": "preserve",
|
|
18
|
-
"incremental": true,
|
|
19
|
-
"plugins": [{ "name": "next" }],
|
|
20
|
-
"paths": {
|
|
21
|
-
"@/*": ["./src/*"]
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
25
|
-
"exclude": ["node_modules"]
|
|
26
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": false,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noUncheckedIndexedAccess": true,
|
|
9
|
+
"noImplicitOverride": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"module": "esnext",
|
|
14
|
+
"moduleResolution": "bundler",
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"jsx": "preserve",
|
|
18
|
+
"incremental": true,
|
|
19
|
+
"plugins": [{ "name": "next" }],
|
|
20
|
+
"paths": {
|
|
21
|
+
"@/*": ["./src/*"]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
25
|
+
"exclude": ["node_modules"]
|
|
26
|
+
}
|