fix template, remove okr, use casan.*
This commit is contained in:
+31
@@ -0,0 +1,31 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
- run: npm run build
|
||||
- name: CASAN governance gate
|
||||
env:
|
||||
CASAN_PROJECT_MANIFEST: apps/__PROJECT_SLUG__/domain/project.manifest.json
|
||||
CASAN_PROJECT_GATE_RUN_BUILD: "0"
|
||||
CASAN_PROJECT_GATE_RUN_TEST: "0"
|
||||
run: bin/casan gate
|
||||
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
dist/
|
||||
coverage/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.DS_Store
|
||||
.specify/logs/
|
||||
@@ -0,0 +1,16 @@
|
||||
# __PROJECT_NAME__
|
||||
|
||||
Production-ready CASAN-governed NestJS + React project shell.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm test
|
||||
npm run build
|
||||
CASAN_PROJECT_MANIFEST=apps/__PROJECT_SLUG__/domain/project.manifest.json bin/casan gate
|
||||
```
|
||||
|
||||
The shell intentionally contains only health/bootstrap functionality. Product behavior must be
|
||||
implemented from `apps/__PROJECT_SLUG__/domain/input/requirement.md` and may not bypass the
|
||||
manifest build, test, verification, security, traceability, or approval gates.
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
COPY apps/__PROJECT_SLUG__/backend/package.json apps/__PROJECT_SLUG__/backend/package.json
|
||||
COPY apps/__PROJECT_SLUG__/frontend/package.json apps/__PROJECT_SLUG__/frontend/package.json
|
||||
RUN npm ci
|
||||
COPY apps/__PROJECT_SLUG__/backend apps/__PROJECT_SLUG__/backend
|
||||
RUN npm run build -w @__PROJECT_SLUG__/backend
|
||||
|
||||
FROM node:20-alpine AS runtime
|
||||
ENV NODE_ENV=production
|
||||
USER node
|
||||
WORKDIR /app
|
||||
COPY --chown=node:node package*.json ./
|
||||
COPY --chown=node:node apps/__PROJECT_SLUG__/backend/package.json apps/__PROJECT_SLUG__/backend/package.json
|
||||
COPY --chown=node:node apps/__PROJECT_SLUG__/frontend/package.json apps/__PROJECT_SLUG__/frontend/package.json
|
||||
RUN npm ci --omit=dev --workspace @__PROJECT_SLUG__/backend --include-workspace-root=false && npm cache clean --force
|
||||
COPY --from=build --chown=node:node /app/apps/__PROJECT_SLUG__/backend/dist ./apps/__PROJECT_SLUG__/backend/dist
|
||||
EXPOSE 3000
|
||||
CMD ["node", "apps/__PROJECT_SLUG__/backend/dist/main.js"]
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@__PROJECT_SLUG__/backend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"dev": "tsx watch src/main.ts",
|
||||
"start": "node dist/main.js",
|
||||
"test": "node --import tsx --test test/**/*.test.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^10.4.20",
|
||||
"@nestjs/core": "^10.4.20",
|
||||
"@nestjs/platform-express": "^10.4.20",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.8",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { HealthController } from './health.controller.js';
|
||||
|
||||
@Module({ controllers: [HealthController] })
|
||||
export class AppModule {}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
|
||||
export interface HealthResponse {
|
||||
status: 'ok';
|
||||
service: string;
|
||||
}
|
||||
|
||||
@Controller('health')
|
||||
export class HealthController {
|
||||
@Get()
|
||||
health(): HealthResponse {
|
||||
return { status: 'ok', service: '__PROJECT_SLUG__-backend' };
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import 'reflect-metadata';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module.js';
|
||||
|
||||
async function bootstrap(): Promise<void> {
|
||||
const app = await NestFactory.create(AppModule, { bufferLogs: true });
|
||||
app.setGlobalPrefix('api/v1', { exclude: ['health'] });
|
||||
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }));
|
||||
app.enableCors({ origin: process.env.CORS_ORIGIN?.split(',') ?? ['http://localhost:5173'], credentials: true });
|
||||
const port = Number(process.env.PORT ?? 3000);
|
||||
await app.listen(port, '0.0.0.0');
|
||||
}
|
||||
|
||||
void bootstrap();
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { HealthController } from '../src/health.controller.js';
|
||||
|
||||
test('health contract is deterministic', () => {
|
||||
assert.deepEqual(new HealthController().health(), { status: 'ok', service: '__PROJECT_SLUG__-backend' });
|
||||
});
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["test", "dist", "node_modules"]
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"strict": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"project_id": "__PROJECT_SLUG__",
|
||||
"display_name": "__PROJECT_NAME__",
|
||||
"domain_root": "apps/__PROJECT_SLUG__/domain",
|
||||
"requirements": "apps/__PROJECT_SLUG__/domain/input/requirement.md",
|
||||
"architecture": "apps/__PROJECT_SLUG__/domain/input/architecture.md",
|
||||
"quality_profile": "config/casan/quality-profiles/enterprise-web-v1.json",
|
||||
"feature": {
|
||||
"id": "__FEATURE_ID__",
|
||||
"module_id": "__MODULE_ID__",
|
||||
"slug": "__PROJECT_SLUG__-core",
|
||||
"title": "__PROJECT_NAME__ Core"
|
||||
},
|
||||
"source_roots": [
|
||||
"apps/__PROJECT_SLUG__/backend",
|
||||
"apps/__PROJECT_SLUG__/frontend"
|
||||
],
|
||||
"commands": {
|
||||
"build": [["npm", "run", "build"]],
|
||||
"test": [["npm", "test"]]
|
||||
},
|
||||
"verification": [
|
||||
{
|
||||
"path_prefix": "apps/__PROJECT_SLUG__/backend/",
|
||||
"commands": [
|
||||
["npm", "run", "build", "-w", "@__PROJECT_SLUG__/backend"],
|
||||
["npm", "test", "-w", "@__PROJECT_SLUG__/backend"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"path_prefix": "apps/__PROJECT_SLUG__/frontend/",
|
||||
"commands": [
|
||||
["npm", "run", "build", "-w", "@__PROJECT_SLUG__/frontend"],
|
||||
["npm", "test", "-w", "@__PROJECT_SLUG__/frontend"]
|
||||
]
|
||||
}
|
||||
],
|
||||
"artifacts_root": "docs/output",
|
||||
"implementation_evidence": [
|
||||
"apps/__PROJECT_SLUG__/backend/src/main.ts",
|
||||
"apps/__PROJECT_SLUG__/backend/test/health.test.ts",
|
||||
"apps/__PROJECT_SLUG__/frontend/src/App.tsx",
|
||||
"apps/__PROJECT_SLUG__/frontend/src/__tests__/App.test.tsx"
|
||||
],
|
||||
"tech_stack": "NestJS 10, React 18, Vite 5, Tailwind CSS 3, strict TypeScript"
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
COPY apps/__PROJECT_SLUG__/backend/package.json apps/__PROJECT_SLUG__/backend/package.json
|
||||
COPY apps/__PROJECT_SLUG__/frontend/package.json apps/__PROJECT_SLUG__/frontend/package.json
|
||||
RUN npm ci
|
||||
COPY apps/__PROJECT_SLUG__/frontend apps/__PROJECT_SLUG__/frontend
|
||||
RUN npm run build -w @__PROJECT_SLUG__/frontend
|
||||
|
||||
FROM nginx:1.27-alpine
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/apps/__PROJECT_SLUG__/frontend/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>__PROJECT_NAME__</title></head>
|
||||
<body><div id="root"></div><script type="module" src="/src/main.tsx"></script></body>
|
||||
</html>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
location / { try_files $uri $uri/ /index.html; }
|
||||
location /api/ { proxy_pass http://backend:3000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@__PROJECT_SLUG__/frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.81.5",
|
||||
"axios": "^1.10.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.59.0",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"zod": "^3.25.67"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/node": "^24.0.8",
|
||||
"@types/react": "^18.3.23",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"jsdom": "^26.1.0",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^5.4.19",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export default { plugins: { tailwindcss: {}, autoprefixer: {} } };
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
export function App() {
|
||||
return (
|
||||
<main className="min-h-screen bg-gray-50 p-6 text-gray-800">
|
||||
<section className="mx-auto max-w-4xl rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
||||
<p className="text-sm font-medium text-blue-600">CASAN-governed project</p>
|
||||
<h1 className="mt-2 text-2xl font-semibold">__PROJECT_NAME__</h1>
|
||||
<p className="mt-3 text-gray-500">The production shell is ready. Implement product screens from the approved requirement and architecture.</p>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { App } from '../App';
|
||||
|
||||
describe('App', () => {
|
||||
it('renders the project identity', () => {
|
||||
render(<App />);
|
||||
expect(screen.getByRole('heading', { name: '__PROJECT_NAME__' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+1
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body { margin: 0; min-width: 320px; min-height: 100vh; }
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { App } from './App';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(<React.StrictMode><App /></React.StrictMode>);
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
theme: { extend: {} },
|
||||
plugins: [],
|
||||
} satisfies Config;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2020"],
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "vite.config.ts"]
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: { port: 5173 },
|
||||
test: { environment: 'jsdom', setupFiles: ['./src/__tests__/setup.ts'] },
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/__PROJECT_SLUG__/backend/Dockerfile
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
ports: ["3000:3000"]
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/__PROJECT_SLUG__/frontend/Dockerfile
|
||||
ports: ["8080:80"]
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "__PROJECT_SLUG__",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"workspaces": ["apps/__PROJECT_SLUG__/backend", "apps/__PROJECT_SLUG__/frontend"],
|
||||
"scripts": {
|
||||
"build": "npm run build -w @__PROJECT_SLUG__/backend && npm run build -w @__PROJECT_SLUG__/frontend",
|
||||
"test": "npm test -w @__PROJECT_SLUG__/backend && npm test -w @__PROJECT_SLUG__/frontend",
|
||||
"dev:backend": "npm run dev -w @__PROJECT_SLUG__/backend",
|
||||
"dev:frontend": "npm run dev -w @__PROJECT_SLUG__/frontend"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user