@glxmart/boss-cli 1.0.0 → 1.2.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/README.md +0 -1
- package/assets/claude-md/docs/conductor.md +139 -0
- package/assets/claude-md/docs/initialization.md +2 -2
- package/assets/claude-md/docs/workers.md +33 -54
- package/assets/claude-md/docs/workflow.md +23 -59
- package/assets/claude-md/template.md +198 -756
- package/assets/git-hooks/commit-msg.sh +4 -8
- package/assets/git-hooks/pre-commit.sh +0 -4
- package/assets/git-hooks/pre-push.sh +21 -0
- package/assets/github-workflows/CODEOWNERS +21 -2
- package/assets/github-workflows/boss-ci.yml +163 -23
- package/assets/github-workflows/boss-gates.yml +100 -13
- package/assets/template-docs/nextjs-app-turbo.md +412 -61
- package/assets/template-loader/gitignore +14 -0
- package/assets/worker-configs/architect/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/architect/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/architect/CLAUDE.md +99 -38
- package/assets/worker-configs/clarifier/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/clarifier/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/clarifier/CLAUDE.md +81 -40
- package/assets/worker-configs/code-reviewer/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/code-reviewer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/code-reviewer/.claude/skills/test-first-methodology.md +745 -0
- package/assets/worker-configs/code-reviewer/CLAUDE.md +85 -79
- package/assets/worker-configs/consolidator/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/consolidator/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/consolidator/CLAUDE.md +94 -88
- package/assets/worker-configs/developer-backend/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/developer-backend/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/developer-backend/.claude/skills/test-first-methodology.md +745 -0
- package/assets/worker-configs/developer-backend/CLAUDE.md +156 -56
- package/assets/worker-configs/developer-frontend/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/developer-frontend/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/developer-frontend/.claude/skills/test-first-methodology.md +745 -0
- package/assets/worker-configs/developer-frontend/CLAUDE.md +152 -54
- package/assets/worker-configs/developer-fullstack/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/developer-fullstack/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/developer-fullstack/.claude/skills/test-first-methodology.md +745 -0
- package/assets/worker-configs/developer-fullstack/CLAUDE.md +155 -57
- package/assets/worker-configs/devops-engineer/.claude/skills/infrastructure-as-code.md +794 -0
- package/assets/worker-configs/devops-engineer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/devops-engineer/CLAUDE.md +92 -85
- package/assets/worker-configs/planner/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/planner/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/planner/CLAUDE.md +143 -46
- package/assets/worker-configs/product-owner/CLAUDE.md +72 -82
- package/assets/worker-configs/reviewer/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/reviewer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/reviewer/CLAUDE.md +108 -50
- package/assets/worker-configs/security-engineer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/security-engineer/CLAUDE.md +83 -83
- package/assets/worker-configs/spec-writer/.claude/skills/conductor-orchestration.md +635 -0
- package/assets/worker-configs/spec-writer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/spec-writer/CLAUDE.md +107 -48
- package/assets/worker-configs/technical-writer/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/technical-writer/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/technical-writer/CLAUDE.md +91 -81
- package/assets/worker-configs/tester/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/assets/worker-configs/tester/.claude/skills/spec-kit-workflow.md +827 -0
- package/assets/worker-configs/tester/.claude/skills/test-first-methodology.md +745 -0
- package/assets/worker-configs/tester/CLAUDE.md +141 -52
- package/dist/assets/claude-md/docs/conductor.md +139 -0
- package/dist/assets/claude-md/docs/initialization.md +2 -2
- package/dist/assets/claude-md/docs/workers.md +33 -54
- package/dist/assets/claude-md/docs/workflow.md +23 -59
- package/dist/assets/claude-md/template.md +198 -756
- package/dist/assets/git-hooks/commit-msg.sh +4 -8
- package/dist/assets/git-hooks/pre-commit.sh +0 -4
- package/dist/assets/git-hooks/pre-push.sh +21 -0
- package/dist/assets/github-workflows/CODEOWNERS +21 -2
- package/dist/assets/github-workflows/boss-ci.yml +163 -23
- package/dist/assets/github-workflows/boss-gates.yml +100 -13
- package/dist/assets/template-docs/nextjs-app-turbo.md +412 -61
- package/dist/assets/template-loader/gitignore +14 -0
- package/dist/assets/worker-configs/architect/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/architect/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/architect/CLAUDE.md +99 -38
- package/dist/assets/worker-configs/clarifier/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/clarifier/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/clarifier/CLAUDE.md +81 -40
- package/dist/assets/worker-configs/code-reviewer/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/code-reviewer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/code-reviewer/.claude/skills/test-first-methodology.md +745 -0
- package/dist/assets/worker-configs/code-reviewer/CLAUDE.md +85 -79
- package/dist/assets/worker-configs/consolidator/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/consolidator/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/consolidator/CLAUDE.md +94 -88
- package/dist/assets/worker-configs/developer-backend/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/developer-backend/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/developer-backend/.claude/skills/test-first-methodology.md +745 -0
- package/dist/assets/worker-configs/developer-backend/CLAUDE.md +156 -56
- package/dist/assets/worker-configs/developer-frontend/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/developer-frontend/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/developer-frontend/.claude/skills/test-first-methodology.md +745 -0
- package/dist/assets/worker-configs/developer-frontend/CLAUDE.md +152 -54
- package/dist/assets/worker-configs/developer-fullstack/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/developer-fullstack/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/developer-fullstack/.claude/skills/test-first-methodology.md +745 -0
- package/dist/assets/worker-configs/developer-fullstack/CLAUDE.md +155 -57
- package/dist/assets/worker-configs/devops-engineer/.claude/skills/infrastructure-as-code.md +794 -0
- package/dist/assets/worker-configs/devops-engineer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/devops-engineer/CLAUDE.md +92 -85
- package/dist/assets/worker-configs/planner/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/planner/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/planner/CLAUDE.md +143 -46
- package/dist/assets/worker-configs/product-owner/CLAUDE.md +72 -82
- package/dist/assets/worker-configs/reviewer/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/reviewer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/reviewer/CLAUDE.md +108 -50
- package/dist/assets/worker-configs/security-engineer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/security-engineer/CLAUDE.md +83 -83
- package/dist/assets/worker-configs/spec-writer/.claude/skills/conductor-orchestration.md +635 -0
- package/dist/assets/worker-configs/spec-writer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/spec-writer/CLAUDE.md +107 -48
- package/dist/assets/worker-configs/technical-writer/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/technical-writer/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/technical-writer/CLAUDE.md +91 -81
- package/dist/assets/worker-configs/tester/.claude/skills/nextjs-turbo-stack.md +1017 -0
- package/dist/assets/worker-configs/tester/.claude/skills/spec-kit-workflow.md +827 -0
- package/dist/assets/worker-configs/tester/.claude/skills/test-first-methodology.md +745 -0
- package/dist/assets/worker-configs/tester/CLAUDE.md +141 -52
- package/dist/commands/__tests__/bootstrap.test.js +51 -51
- package/dist/commands/__tests__/bootstrap.test.js.map +1 -1
- package/dist/commands/bootstrap.d.ts.map +1 -1
- package/dist/commands/bootstrap.js +23 -20
- package/dist/commands/bootstrap.js.map +1 -1
- package/dist/commands/doctor.js +33 -9
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/templates.d.ts +1 -1
- package/dist/commands/templates.d.ts.map +1 -1
- package/dist/commands/templates.js +1 -1
- package/dist/commands/templates.js.map +1 -1
- package/dist/constants.d.ts +2 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/generators/__tests__/boss-config.test.js +4 -4
- package/dist/generators/__tests__/boss-config.test.js.map +1 -1
- package/dist/generators/__tests__/claude-folder.test.js +8 -8
- package/dist/generators/__tests__/claude-folder.test.js.map +1 -1
- package/dist/generators/__tests__/claude-md.test.js +25 -16
- package/dist/generators/__tests__/claude-md.test.js.map +1 -1
- package/dist/generators/__tests__/container-use-config.test.js +2 -2
- package/dist/generators/__tests__/container-use-config.test.js.map +1 -1
- package/dist/generators/__tests__/git-hooks.test.js.map +1 -1
- package/dist/generators/__tests__/github-workflows.test.js +10 -6
- package/dist/generators/__tests__/github-workflows.test.js.map +1 -1
- package/dist/generators/__tests__/mcp-config.test.js +6 -6
- package/dist/generators/__tests__/project-structure.test.js +2 -2
- package/dist/generators/__tests__/quality-gates.test.js +11 -3
- package/dist/generators/__tests__/quality-gates.test.js.map +1 -1
- package/dist/generators/__tests__/specify-structure.test.js +1 -1
- package/dist/generators/__tests__/specify-structure.test.js.map +1 -1
- package/dist/generators/__tests__/template-docs.test.js +1 -1
- package/dist/generators/__tests__/template-docs.test.js.map +1 -1
- package/dist/generators/__tests__/template-loader.test.js +209 -59
- package/dist/generators/__tests__/template-loader.test.js.map +1 -1
- package/dist/generators/__tests__/worker-configs.test.js +7 -7
- package/dist/generators/boss-config.d.ts.map +1 -1
- package/dist/generators/boss-config.js +46 -51
- package/dist/generators/boss-config.js.map +1 -1
- package/dist/generators/claude-folder.d.ts +1 -1
- package/dist/generators/claude-folder.d.ts.map +1 -1
- package/dist/generators/claude-folder.js +5 -5
- package/dist/generators/claude-folder.js.map +1 -1
- package/dist/generators/claude-md.d.ts.map +1 -1
- package/dist/generators/claude-md.js +83 -42
- package/dist/generators/claude-md.js.map +1 -1
- package/dist/generators/container-use-config.d.ts.map +1 -1
- package/dist/generators/container-use-config.js +5 -7
- package/dist/generators/container-use-config.js.map +1 -1
- package/dist/generators/docker-compose.d.ts.map +1 -1
- package/dist/generators/docker-compose.js.map +1 -1
- package/dist/generators/git-hooks.d.ts.map +1 -1
- package/dist/generators/git-hooks.js +2 -2
- package/dist/generators/git-hooks.js.map +1 -1
- package/dist/generators/github-workflows.js +3 -3
- package/dist/generators/github-workflows.js.map +1 -1
- package/dist/generators/mcp-config.d.ts.map +1 -1
- package/dist/generators/mcp-config.js +18 -16
- package/dist/generators/mcp-config.js.map +1 -1
- package/dist/generators/project-structure.d.ts +1 -1
- package/dist/generators/project-structure.d.ts.map +1 -1
- package/dist/generators/project-structure.js +2 -2
- package/dist/generators/project-structure.js.map +1 -1
- package/dist/generators/quality-gates.d.ts.map +1 -1
- package/dist/generators/quality-gates.js +13 -5
- package/dist/generators/quality-gates.js.map +1 -1
- package/dist/generators/specify-structure.d.ts.map +1 -1
- package/dist/generators/specify-structure.js +1 -2
- package/dist/generators/specify-structure.js.map +1 -1
- package/dist/generators/template-docs.js +2 -2
- package/dist/generators/template-docs.js.map +1 -1
- package/dist/generators/template-loader.d.ts.map +1 -1
- package/dist/generators/template-loader.js +259 -143
- package/dist/generators/template-loader.js.map +1 -1
- package/dist/generators/worker-configs.d.ts.map +1 -1
- package/dist/generators/worker-configs.js +7 -5
- package/dist/generators/worker-configs.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/presets/__tests__/quality-presets.test.js +9 -5
- package/dist/presets/__tests__/quality-presets.test.js.map +1 -1
- package/dist/presets/quality-presets.d.ts.map +1 -1
- package/dist/presets/quality-presets.js +11 -11
- package/dist/presets/quality-presets.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/internal.d.ts +69 -0
- package/dist/types/internal.d.ts.map +1 -0
- package/dist/types/internal.js +4 -0
- package/dist/types/internal.js.map +1 -0
- package/dist/utils/__tests__/file-system.test.js +1 -1
- package/dist/utils/__tests__/file-system.test.js.map +1 -1
- package/dist/utils/__tests__/git.test.js.map +1 -1
- package/dist/utils/__tests__/template-loader.test.js.map +1 -1
- package/dist/utils/__tests__/validators.test.js +1 -1
- package/dist/utils/__tests__/validators.test.js.map +1 -1
- package/dist/utils/file-system.d.ts.map +1 -1
- package/dist/utils/file-system.js +1 -4
- package/dist/utils/file-system.js.map +1 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +17 -11
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/prompts.d.ts.map +1 -1
- package/dist/utils/prompts.js +51 -39
- package/dist/utils/prompts.js.map +1 -1
- package/dist/utils/template-loader.d.ts +2 -1
- package/dist/utils/template-loader.d.ts.map +1 -1
- package/dist/utils/template-loader.js +11 -5
- package/dist/utils/template-loader.js.map +1 -1
- package/dist/utils/validators.d.ts.map +1 -1
- package/dist/utils/validators.js +16 -4
- package/dist/utils/validators.js.map +1 -1
- package/package.json +2 -2
- package/templates/nextjs-turbo-monorepo/base/README.md +167 -0
- package/templates/nextjs-turbo-monorepo/base/_gitignore +71 -0
- package/templates/nextjs-turbo-monorepo/base/_npmrc +12 -0
- package/templates/nextjs-turbo-monorepo/base/apps/admin/app/layout.tsx +19 -0
- package/templates/nextjs-turbo-monorepo/base/apps/admin/app/page.tsx +34 -0
- package/templates/nextjs-turbo-monorepo/base/apps/admin/next.config.ts +20 -0
- package/templates/nextjs-turbo-monorepo/base/apps/admin/package.json +42 -0
- package/templates/nextjs-turbo-monorepo/base/apps/admin/tsconfig.json +16 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/app/globals.css +59 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/app/layout.tsx +20 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/app/page.tsx +17 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/components.json +18 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/env.ts +45 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/next.config.ts +38 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/package.json +45 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/tsconfig.json +19 -0
- package/templates/nextjs-turbo-monorepo/base/apps/web/vitest.config.ts +28 -0
- package/templates/nextjs-turbo-monorepo/base/docker/Dockerfile.admin +76 -0
- package/templates/nextjs-turbo-monorepo/base/docker/Dockerfile.web +76 -0
- package/templates/nextjs-turbo-monorepo/base/docker/_dockerignore +48 -0
- package/templates/nextjs-turbo-monorepo/base/docker/docker-compose.yml +39 -0
- package/templates/nextjs-turbo-monorepo/base/package.json +62 -0
- package/templates/nextjs-turbo-monorepo/base/packages/auth/package.json +26 -0
- package/templates/nextjs-turbo-monorepo/base/packages/auth/src/config.ts +88 -0
- package/templates/nextjs-turbo-monorepo/base/packages/auth/src/index.ts +11 -0
- package/templates/nextjs-turbo-monorepo/base/packages/auth/src/types.ts +28 -0
- package/templates/nextjs-turbo-monorepo/base/packages/auth/tsconfig.json +9 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/eslint/library.js +35 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/eslint/nextjs.js +51 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/eslint/react-library.js +45 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/package.json +19 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/tailwind/base.ts +50 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/typescript/base.json +23 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/typescript/nextjs.json +17 -0
- package/templates/nextjs-turbo-monorepo/base/packages/config/typescript/react-library.json +11 -0
- package/templates/nextjs-turbo-monorepo/base/packages/database/package.json +32 -0
- package/templates/nextjs-turbo-monorepo/base/packages/database/prisma/schema.prisma +84 -0
- package/templates/nextjs-turbo-monorepo/base/packages/database/src/client.ts +16 -0
- package/templates/nextjs-turbo-monorepo/base/packages/database/src/index.ts +2 -0
- package/templates/nextjs-turbo-monorepo/base/packages/database/tsconfig.json +9 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/package.json +32 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/src/context.ts +17 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/src/index.ts +3 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/src/init.ts +34 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/src/routers/_app.ts +8 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/src/routers/user.ts +36 -0
- package/templates/nextjs-turbo-monorepo/base/packages/trpc/tsconfig.json +9 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/.storybook/main.ts +19 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/.storybook/preview.ts +15 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/components.json +17 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/package.json +62 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/postcss.config.js +6 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/components/index.ts +2 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/components/ui/button.tsx +57 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/components/ui/card.tsx +76 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/hooks/index.ts +2 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/lib/utils.ts +6 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/src/styles/globals.css +59 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/tailwind.config.ts +15 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/tsconfig.json +12 -0
- package/templates/nextjs-turbo-monorepo/base/packages/ui/vitest.config.ts +28 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/package.json +24 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/src/date.ts +117 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/src/index.ts +2 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/src/string.ts +59 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/tsconfig.json +9 -0
- package/templates/nextjs-turbo-monorepo/base/packages/utils/vitest.config.ts +13 -0
- package/templates/nextjs-turbo-monorepo/base/pnpm-workspace.yaml +3 -0
- package/templates/nextjs-turbo-monorepo/base/tsconfig.json +19 -0
- package/templates/nextjs-turbo-monorepo/base/turbo.json +47 -0
- package/templates/nextjs-turbo-monorepo/extras/boss-cli/assets/claude-md/template.md +0 -0
- package/templates/nextjs-turbo-monorepo/extras/boss-cli/assets/github-workflows/CODEOWNERS +0 -0
- package/templates/nextjs-turbo-monorepo/extras/boss-cli/assets/github-workflows/boss-ci.yml +139 -0
- package/templates/nextjs-turbo-monorepo/extras/boss-cli/assets/github-workflows/boss-gates.yml +116 -0
- package/templates/nextjs-turbo-monorepo/extras/config/kamal/_env +18 -0
- package/templates/nextjs-turbo-monorepo/extras/config/kamal/deploy.yml +92 -0
- package/templates/nextjs-turbo-monorepo/extras/scripts/deploy.sh +38 -0
- package/templates/nextjs-turbo-monorepo/extras/scripts/setup-db.sh +29 -0
- package/assets/claude-md/docs/container-use.md +0 -140
- package/dist/assets/claude-md/docs/container-use.md +0 -140
|
@@ -0,0 +1,1017 @@
|
|
|
1
|
+
# Next.js Turbo Stack
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Create, modify, and review applications built with the Next.js Turbo monorepo stack. Use when working with Next.js 15, React 19, tRPC 11, Prisma 5.24, Tailwind CSS, Storybook, Vitest, Turbo, NextAuth v5, and pnpm workspaces.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The BOSS nextjs-turbo-monorepo template provides a production-ready stack optimized for rapid development with type safety, testing, and scalability.
|
|
10
|
+
|
|
11
|
+
**Core Stack**:
|
|
12
|
+
- **Next.js 15** - React framework with App Router, server components, server actions
|
|
13
|
+
- **React 19** - UI library with new use hook, server components, concurrent features
|
|
14
|
+
- **tRPC 11** - Type-safe APIs without code generation
|
|
15
|
+
- **Prisma 5.24** - Type-safe database ORM with migrations
|
|
16
|
+
- **Tailwind CSS v4** - Utility-first CSS with shadcn/ui components
|
|
17
|
+
- **Storybook** - Component development and documentation
|
|
18
|
+
- **Vitest** - Fast unit testing with React Testing Library
|
|
19
|
+
- **Turbo** - High-performance monorepo build system
|
|
20
|
+
- **NextAuth v5** - Authentication with providers and sessions
|
|
21
|
+
- **pnpm** - Fast, disk-efficient package manager
|
|
22
|
+
|
|
23
|
+
## Monorepo Structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
project/
|
|
27
|
+
├── apps/
|
|
28
|
+
│ ├── web/ # Main Next.js app
|
|
29
|
+
│ │ ├── app/ # App Router (Next.js 15)
|
|
30
|
+
│ │ ├── components/ # React components
|
|
31
|
+
│ │ ├── lib/ # Utilities
|
|
32
|
+
│ │ └── public/ # Static assets
|
|
33
|
+
│ └── admin/ # Admin dashboard (optional)
|
|
34
|
+
├── packages/
|
|
35
|
+
│ ├── ui/ # Shared React component library
|
|
36
|
+
│ │ ├── src/ # Component source
|
|
37
|
+
│ │ ├── .storybook/ # Storybook config
|
|
38
|
+
│ │ └── stories/ # Component stories
|
|
39
|
+
│ ├── database/ # Prisma ORM
|
|
40
|
+
│ │ ├── prisma/ # Schema and migrations
|
|
41
|
+
│ │ └── src/ # Prisma Client exports
|
|
42
|
+
│ ├── auth/ # NextAuth configuration
|
|
43
|
+
│ │ └── src/ # Auth helpers and config
|
|
44
|
+
│ ├── trpc/ # tRPC routers
|
|
45
|
+
│ │ └── src/ # API routers and procedures
|
|
46
|
+
│ ├── utils/ # Shared utilities
|
|
47
|
+
│ └── config/ # Shared configs
|
|
48
|
+
│ ├── eslint/ # ESLint config
|
|
49
|
+
│ ├── typescript/ # TypeScript config
|
|
50
|
+
│ └── tailwind/ # Tailwind config
|
|
51
|
+
├── turbo.json # Turbo pipeline config
|
|
52
|
+
├── pnpm-workspace.yaml # pnpm workspace config
|
|
53
|
+
└── package.json # Root package.json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Next.js 15 (App Router)
|
|
57
|
+
|
|
58
|
+
### File-Based Routing
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
app/
|
|
62
|
+
├── layout.tsx # Root layout (wraps all pages)
|
|
63
|
+
├── page.tsx # Home page (/)
|
|
64
|
+
├── loading.tsx # Loading UI
|
|
65
|
+
├── error.tsx # Error boundary
|
|
66
|
+
├── not-found.tsx # 404 page
|
|
67
|
+
├── dashboard/
|
|
68
|
+
│ ├── layout.tsx # Dashboard layout
|
|
69
|
+
│ ├── page.tsx # /dashboard
|
|
70
|
+
│ └── settings/
|
|
71
|
+
│ └── page.tsx # /dashboard/settings
|
|
72
|
+
└── api/
|
|
73
|
+
└── webhooks/
|
|
74
|
+
└── route.ts # API route
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Server Components (Default)
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// app/dashboard/page.tsx - Server Component (default)
|
|
81
|
+
import { prisma } from '@repo/database';
|
|
82
|
+
|
|
83
|
+
export default async function DashboardPage() {
|
|
84
|
+
// Fetch data directly in server component
|
|
85
|
+
const users = await prisma.user.findMany();
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div>
|
|
89
|
+
<h1>Dashboard</h1>
|
|
90
|
+
<UserList users={users} />
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// No need for useEffect or useState for data fetching!
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Client Components
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
'use client'; // Required for client-side interactivity
|
|
102
|
+
|
|
103
|
+
import { useState } from 'react';
|
|
104
|
+
|
|
105
|
+
export function Counter() {
|
|
106
|
+
const [count, setCount] = useState(0);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<button onClick={() => setCount(count + 1)}>
|
|
110
|
+
Count: {count}
|
|
111
|
+
</button>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Use client components for:
|
|
116
|
+
// - useState, useEffect, event handlers
|
|
117
|
+
// - Browser-only APIs (localStorage, window)
|
|
118
|
+
// - Interactive UI components
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Server Actions
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// app/actions/user.ts
|
|
125
|
+
'use server';
|
|
126
|
+
|
|
127
|
+
import { prisma } from '@repo/database';
|
|
128
|
+
import { revalidatePath } from 'next/cache';
|
|
129
|
+
|
|
130
|
+
export async function createUser(formData: FormData) {
|
|
131
|
+
const name = formData.get('name') as string;
|
|
132
|
+
const email = formData.get('email') as string;
|
|
133
|
+
|
|
134
|
+
const user = await prisma.user.create({
|
|
135
|
+
data: { name, email }
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
revalidatePath('/users');
|
|
139
|
+
return user;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Call from client component:
|
|
143
|
+
// <form action={createUser}>...</form>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Metadata API
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
// app/dashboard/layout.tsx
|
|
150
|
+
import type { Metadata } from 'next';
|
|
151
|
+
|
|
152
|
+
export const metadata: Metadata = {
|
|
153
|
+
title: 'Dashboard',
|
|
154
|
+
description: 'User dashboard',
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Or dynamic metadata:
|
|
158
|
+
export async function generateMetadata({ params }): Promise<Metadata> {
|
|
159
|
+
const user = await getUser(params.id);
|
|
160
|
+
return {
|
|
161
|
+
title: user.name,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Middleware
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// middleware.ts (root level)
|
|
170
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
171
|
+
|
|
172
|
+
export function middleware(request: NextRequest) {
|
|
173
|
+
// Auth check
|
|
174
|
+
const token = request.cookies.get('session');
|
|
175
|
+
|
|
176
|
+
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
|
177
|
+
return NextResponse.redirect(new URL('/login', request.url));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return NextResponse.next();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const config = {
|
|
184
|
+
matcher: ['/dashboard/:path*', '/admin/:path*'],
|
|
185
|
+
};
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## React 19
|
|
189
|
+
|
|
190
|
+
### New `use` Hook
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { use } from 'react';
|
|
194
|
+
|
|
195
|
+
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
|
|
196
|
+
// `use` hook unwraps promises (React 19)
|
|
197
|
+
const user = use(userPromise);
|
|
198
|
+
|
|
199
|
+
return <div>{user.name}</div>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Call from server component:
|
|
203
|
+
// <UserProfile userPromise={fetchUser()} />
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Transitions
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
'use client';
|
|
210
|
+
|
|
211
|
+
import { useTransition } from 'react';
|
|
212
|
+
|
|
213
|
+
export function SearchForm() {
|
|
214
|
+
const [isPending, startTransition] = useTransition();
|
|
215
|
+
|
|
216
|
+
const handleSearch = (query: string) => {
|
|
217
|
+
startTransition(() => {
|
|
218
|
+
// Non-urgent update
|
|
219
|
+
setSearchResults(query);
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<input
|
|
225
|
+
onChange={(e) => handleSearch(e.target.value)}
|
|
226
|
+
placeholder={isPending ? 'Searching...' : 'Search'}
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Server Components Best Practices
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
// ✅ Good - fetch data in server component
|
|
236
|
+
async function UserDashboard() {
|
|
237
|
+
const users = await prisma.user.findMany();
|
|
238
|
+
return <UserList users={users} />;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ✅ Good - pass data to client component as props
|
|
242
|
+
'use client';
|
|
243
|
+
function UserList({ users }: { users: User[] }) {
|
|
244
|
+
const [selected, setSelected] = useState<User | null>(null);
|
|
245
|
+
return <div>...</div>;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ❌ Bad - fetch in client component (old pattern)
|
|
249
|
+
'use client';
|
|
250
|
+
function UserDashboard() {
|
|
251
|
+
const [users, setUsers] = useState([]);
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
fetch('/api/users').then(r => r.json()).then(setUsers);
|
|
254
|
+
}, []);
|
|
255
|
+
return <div>...</div>;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## tRPC 11
|
|
260
|
+
|
|
261
|
+
### Router Setup
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// packages/trpc/src/router.ts
|
|
265
|
+
import { initTRPC } from '@trpc/server';
|
|
266
|
+
import { prisma } from '@repo/database';
|
|
267
|
+
|
|
268
|
+
const t = initTRPC.create();
|
|
269
|
+
|
|
270
|
+
export const appRouter = t.router({
|
|
271
|
+
user: {
|
|
272
|
+
list: t.procedure.query(async () => {
|
|
273
|
+
return prisma.user.findMany();
|
|
274
|
+
}),
|
|
275
|
+
|
|
276
|
+
create: t.procedure
|
|
277
|
+
.input(z.object({ name: z.string(), email: z.string().email() }))
|
|
278
|
+
.mutation(async ({ input }) => {
|
|
279
|
+
return prisma.user.create({ data: input });
|
|
280
|
+
}),
|
|
281
|
+
|
|
282
|
+
getById: t.procedure
|
|
283
|
+
.input(z.string())
|
|
284
|
+
.query(async ({ input }) => {
|
|
285
|
+
return prisma.user.findUnique({ where: { id: input } });
|
|
286
|
+
}),
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
export type AppRouter = typeof appRouter;
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Client Usage (Server Component)
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
// app/users/page.tsx
|
|
297
|
+
import { api } from '@/lib/trpc/server';
|
|
298
|
+
|
|
299
|
+
export default async function UsersPage() {
|
|
300
|
+
const users = await api.user.list();
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<div>
|
|
304
|
+
{users.map(user => (
|
|
305
|
+
<div key={user.id}>{user.name}</div>
|
|
306
|
+
))}
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Client Usage (Client Component)
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
'use client';
|
|
316
|
+
|
|
317
|
+
import { api } from '@/lib/trpc/client';
|
|
318
|
+
|
|
319
|
+
export function UserForm() {
|
|
320
|
+
const utils = api.useUtils();
|
|
321
|
+
const createUser = api.user.create.useMutation({
|
|
322
|
+
onSuccess: () => {
|
|
323
|
+
utils.user.list.invalidate();
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const handleSubmit = (data: { name: string; email: string }) => {
|
|
328
|
+
createUser.mutate(data);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
return <form>...</form>;
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### tRPC Best Practices
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// ✅ Good - type-safe inputs with Zod
|
|
339
|
+
.input(z.object({ email: z.string().email() }))
|
|
340
|
+
|
|
341
|
+
// ✅ Good - structured routers
|
|
342
|
+
export const appRouter = t.router({
|
|
343
|
+
user: userRouter,
|
|
344
|
+
post: postRouter,
|
|
345
|
+
comment: commentRouter,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// ✅ Good - error handling
|
|
349
|
+
.mutation(async ({ input }) => {
|
|
350
|
+
try {
|
|
351
|
+
return await prisma.user.create({ data: input });
|
|
352
|
+
} catch (error) {
|
|
353
|
+
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to create user' });
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
// ❌ Bad - no input validation
|
|
358
|
+
.mutation(async ({ input }) => {
|
|
359
|
+
return prisma.user.create({ data: input as any });
|
|
360
|
+
})
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Prisma 5.24
|
|
364
|
+
|
|
365
|
+
### Schema Design
|
|
366
|
+
|
|
367
|
+
```prisma
|
|
368
|
+
// packages/database/prisma/schema.prisma
|
|
369
|
+
generator client {
|
|
370
|
+
provider = "prisma-client-js"
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
datasource db {
|
|
374
|
+
provider = "postgresql"
|
|
375
|
+
url = env("DATABASE_URL")
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
model User {
|
|
379
|
+
id String @id @default(cuid())
|
|
380
|
+
email String @unique
|
|
381
|
+
name String?
|
|
382
|
+
createdAt DateTime @default(now())
|
|
383
|
+
updatedAt DateTime @updatedAt
|
|
384
|
+
|
|
385
|
+
posts Post[]
|
|
386
|
+
profile Profile?
|
|
387
|
+
|
|
388
|
+
@@index([email])
|
|
389
|
+
@@map("users")
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
model Post {
|
|
393
|
+
id String @id @default(cuid())
|
|
394
|
+
title String
|
|
395
|
+
content String?
|
|
396
|
+
published Boolean @default(false)
|
|
397
|
+
authorId String
|
|
398
|
+
|
|
399
|
+
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
|
400
|
+
|
|
401
|
+
@@index([authorId])
|
|
402
|
+
@@map("posts")
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
model Profile {
|
|
406
|
+
id String @id @default(cuid())
|
|
407
|
+
bio String?
|
|
408
|
+
userId String @unique
|
|
409
|
+
|
|
410
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
411
|
+
|
|
412
|
+
@@map("profiles")
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Migrations
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
# Create migration
|
|
420
|
+
pnpm --filter database prisma migrate dev --name add_user_model
|
|
421
|
+
|
|
422
|
+
# Apply migrations in production
|
|
423
|
+
pnpm --filter database prisma migrate deploy
|
|
424
|
+
|
|
425
|
+
# Reset database (development only!)
|
|
426
|
+
pnpm --filter database prisma migrate reset
|
|
427
|
+
|
|
428
|
+
# Generate Prisma Client
|
|
429
|
+
pnpm --filter database prisma generate
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Prisma Client Usage
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
// packages/database/src/index.ts
|
|
436
|
+
import { PrismaClient } from '@prisma/client';
|
|
437
|
+
|
|
438
|
+
export const prisma = new PrismaClient();
|
|
439
|
+
|
|
440
|
+
// CRUD operations
|
|
441
|
+
export async function createUser(data: { email: string; name: string }) {
|
|
442
|
+
return prisma.user.create({ data });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export async function getUsers() {
|
|
446
|
+
return prisma.user.findMany({
|
|
447
|
+
include: {
|
|
448
|
+
posts: true,
|
|
449
|
+
profile: true,
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export async function updateUser(id: string, data: { name?: string }) {
|
|
455
|
+
return prisma.user.update({
|
|
456
|
+
where: { id },
|
|
457
|
+
data,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export async function deleteUser(id: string) {
|
|
462
|
+
return prisma.user.delete({ where: { id } });
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Transactions
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
const [user, profile] = await prisma.$transaction([
|
|
470
|
+
prisma.user.create({ data: { email: 'test@example.com' } }),
|
|
471
|
+
prisma.profile.create({ data: { bio: 'Hello', userId: '...' } }),
|
|
472
|
+
]);
|
|
473
|
+
|
|
474
|
+
// Or interactive transactions
|
|
475
|
+
await prisma.$transaction(async (tx) => {
|
|
476
|
+
const user = await tx.user.create({ data: { email: 'test@example.com' } });
|
|
477
|
+
await tx.profile.create({ data: { bio: 'Hello', userId: user.id } });
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Tailwind CSS v4
|
|
482
|
+
|
|
483
|
+
### Configuration
|
|
484
|
+
|
|
485
|
+
```javascript
|
|
486
|
+
// packages/config/tailwind/tailwind.config.js
|
|
487
|
+
export default {
|
|
488
|
+
content: [
|
|
489
|
+
'./app/**/*.{ts,tsx}',
|
|
490
|
+
'./components/**/*.{ts,tsx}',
|
|
491
|
+
'../../packages/ui/src/**/*.{ts,tsx}',
|
|
492
|
+
],
|
|
493
|
+
theme: {
|
|
494
|
+
extend: {
|
|
495
|
+
colors: {
|
|
496
|
+
brand: {
|
|
497
|
+
50: '#f0f9ff',
|
|
498
|
+
500: '#0ea5e9',
|
|
499
|
+
900: '#0c4a6e',
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
plugins: [],
|
|
505
|
+
};
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Usage with shadcn/ui
|
|
509
|
+
|
|
510
|
+
```tsx
|
|
511
|
+
import { Button } from '@repo/ui/button';
|
|
512
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@repo/ui/card';
|
|
513
|
+
|
|
514
|
+
export function UserCard({ user }: { user: User }) {
|
|
515
|
+
return (
|
|
516
|
+
<Card>
|
|
517
|
+
<CardHeader>
|
|
518
|
+
<CardTitle>{user.name}</CardTitle>
|
|
519
|
+
</CardHeader>
|
|
520
|
+
<CardContent>
|
|
521
|
+
<p className="text-sm text-muted-foreground">{user.email}</p>
|
|
522
|
+
<Button variant="outline" className="mt-4">
|
|
523
|
+
Edit Profile
|
|
524
|
+
</Button>
|
|
525
|
+
</CardContent>
|
|
526
|
+
</Card>
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Custom Classes
|
|
532
|
+
|
|
533
|
+
```tsx
|
|
534
|
+
// ✅ Good - utility classes
|
|
535
|
+
<div className="flex items-center gap-4 p-4 rounded-lg bg-slate-100">
|
|
536
|
+
<Avatar src={user.avatar} />
|
|
537
|
+
<div className="flex-1">
|
|
538
|
+
<h3 className="font-semibold text-lg">{user.name}</h3>
|
|
539
|
+
<p className="text-sm text-gray-600">{user.bio}</p>
|
|
540
|
+
</div>
|
|
541
|
+
</div>
|
|
542
|
+
|
|
543
|
+
// ✅ Good - responsive design
|
|
544
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
545
|
+
{items.map(item => <Card key={item.id}>{item.name}</Card>)}
|
|
546
|
+
</div>
|
|
547
|
+
|
|
548
|
+
// ❌ Bad - inline styles (defeats Tailwind's purpose)
|
|
549
|
+
<div style={{ display: 'flex', padding: '16px' }}>...</div>
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## Storybook
|
|
553
|
+
|
|
554
|
+
### Story Structure
|
|
555
|
+
|
|
556
|
+
```tsx
|
|
557
|
+
// packages/ui/stories/Button.stories.tsx
|
|
558
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
559
|
+
import { Button } from '../src/button';
|
|
560
|
+
|
|
561
|
+
const meta: Meta<typeof Button> = {
|
|
562
|
+
title: 'Components/Button',
|
|
563
|
+
component: Button,
|
|
564
|
+
tags: ['autodocs'],
|
|
565
|
+
argTypes: {
|
|
566
|
+
variant: {
|
|
567
|
+
control: 'select',
|
|
568
|
+
options: ['default', 'outline', 'ghost'],
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
export default meta;
|
|
574
|
+
type Story = StoryObj<typeof Button>;
|
|
575
|
+
|
|
576
|
+
export const Default: Story = {
|
|
577
|
+
args: {
|
|
578
|
+
children: 'Click me',
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
export const Outline: Story = {
|
|
583
|
+
args: {
|
|
584
|
+
variant: 'outline',
|
|
585
|
+
children: 'Outline button',
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
export const WithIcon: Story = {
|
|
590
|
+
args: {
|
|
591
|
+
children: (
|
|
592
|
+
<>
|
|
593
|
+
<Icon /> Send
|
|
594
|
+
</>
|
|
595
|
+
),
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Running Storybook
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
# Start Storybook dev server
|
|
604
|
+
pnpm --filter ui storybook
|
|
605
|
+
|
|
606
|
+
# Build static Storybook
|
|
607
|
+
pnpm --filter ui build-storybook
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
## Vitest
|
|
611
|
+
|
|
612
|
+
### Test Setup
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
// packages/ui/vitest.config.ts
|
|
616
|
+
import { defineConfig } from 'vitest/config';
|
|
617
|
+
import react from '@vitejs/plugin-react';
|
|
618
|
+
|
|
619
|
+
export default defineConfig({
|
|
620
|
+
plugins: [react()],
|
|
621
|
+
test: {
|
|
622
|
+
environment: 'jsdom',
|
|
623
|
+
globals: true,
|
|
624
|
+
setupFiles: './tests/setup.ts',
|
|
625
|
+
coverage: {
|
|
626
|
+
provider: 'v8',
|
|
627
|
+
reporter: ['text', 'json', 'html'],
|
|
628
|
+
exclude: ['**/*.stories.tsx', '**/*.config.*'],
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Component Tests
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
// packages/ui/src/button.test.tsx
|
|
638
|
+
import { render, screen } from '@testing-library/react';
|
|
639
|
+
import { userEvent } from '@testing-library/user-event';
|
|
640
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
641
|
+
import { Button } from './button';
|
|
642
|
+
|
|
643
|
+
describe('Button', () => {
|
|
644
|
+
it('renders with text', () => {
|
|
645
|
+
render(<Button>Click me</Button>);
|
|
646
|
+
expect(screen.getByRole('button')).toHaveTextContent('Click me');
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it('calls onClick when clicked', async () => {
|
|
650
|
+
const handleClick = vi.fn();
|
|
651
|
+
render(<Button onClick={handleClick}>Click</Button>);
|
|
652
|
+
|
|
653
|
+
await userEvent.click(screen.getByRole('button'));
|
|
654
|
+
expect(handleClick).toHaveBeenCalledOnce();
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it('applies variant class', () => {
|
|
658
|
+
const { container } = render(<Button variant="outline">Button</Button>);
|
|
659
|
+
expect(container.firstChild).toHaveClass('btn-outline');
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### Running Tests
|
|
665
|
+
|
|
666
|
+
```bash
|
|
667
|
+
# Run all tests
|
|
668
|
+
pnpm test
|
|
669
|
+
|
|
670
|
+
# Run tests in watch mode
|
|
671
|
+
pnpm test:watch
|
|
672
|
+
|
|
673
|
+
# Run tests with coverage
|
|
674
|
+
pnpm test:coverage
|
|
675
|
+
|
|
676
|
+
# Run specific package tests
|
|
677
|
+
pnpm --filter ui test
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
## Turbo
|
|
681
|
+
|
|
682
|
+
### Pipeline Configuration
|
|
683
|
+
|
|
684
|
+
```json
|
|
685
|
+
// turbo.json
|
|
686
|
+
{
|
|
687
|
+
"pipeline": {
|
|
688
|
+
"build": {
|
|
689
|
+
"dependsOn": ["^build"],
|
|
690
|
+
"outputs": [".next/**", "dist/**", "build/**"]
|
|
691
|
+
},
|
|
692
|
+
"test": {
|
|
693
|
+
"dependsOn": ["build"],
|
|
694
|
+
"outputs": ["coverage/**"]
|
|
695
|
+
},
|
|
696
|
+
"lint": {
|
|
697
|
+
"outputs": []
|
|
698
|
+
},
|
|
699
|
+
"dev": {
|
|
700
|
+
"cache": false,
|
|
701
|
+
"persistent": true
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### Running Tasks
|
|
708
|
+
|
|
709
|
+
```bash
|
|
710
|
+
# Build all packages
|
|
711
|
+
turbo build
|
|
712
|
+
|
|
713
|
+
# Build with dependencies
|
|
714
|
+
turbo build --filter=web...
|
|
715
|
+
|
|
716
|
+
# Run in parallel
|
|
717
|
+
turbo build test lint
|
|
718
|
+
|
|
719
|
+
# Clear cache
|
|
720
|
+
turbo build --force
|
|
721
|
+
|
|
722
|
+
# See what would run
|
|
723
|
+
turbo build --dry-run
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Task Dependencies
|
|
727
|
+
|
|
728
|
+
```json
|
|
729
|
+
{
|
|
730
|
+
"scripts": {
|
|
731
|
+
"build": "turbo build",
|
|
732
|
+
"dev": "turbo dev",
|
|
733
|
+
"test": "turbo test",
|
|
734
|
+
"lint": "turbo lint"
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// turbo automatically:
|
|
739
|
+
// - Builds dependencies first (^build)
|
|
740
|
+
// - Caches outputs
|
|
741
|
+
// - Runs tasks in parallel when possible
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
## NextAuth v5
|
|
745
|
+
|
|
746
|
+
### Configuration
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
// packages/auth/src/index.ts
|
|
750
|
+
import NextAuth from 'next-auth';
|
|
751
|
+
import { PrismaAdapter } from '@auth/prisma-adapter';
|
|
752
|
+
import { prisma } from '@repo/database';
|
|
753
|
+
import Credentials from 'next-auth/providers/credentials';
|
|
754
|
+
|
|
755
|
+
export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
756
|
+
adapter: PrismaAdapter(prisma),
|
|
757
|
+
providers: [
|
|
758
|
+
Credentials({
|
|
759
|
+
credentials: {
|
|
760
|
+
email: { label: 'Email', type: 'email' },
|
|
761
|
+
password: { label: 'Password', type: 'password' },
|
|
762
|
+
},
|
|
763
|
+
authorize: async (credentials) => {
|
|
764
|
+
const user = await prisma.user.findUnique({
|
|
765
|
+
where: { email: credentials.email },
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
if (user && await verifyPassword(credentials.password, user.password)) {
|
|
769
|
+
return user;
|
|
770
|
+
}
|
|
771
|
+
return null;
|
|
772
|
+
},
|
|
773
|
+
}),
|
|
774
|
+
],
|
|
775
|
+
session: {
|
|
776
|
+
strategy: 'jwt',
|
|
777
|
+
},
|
|
778
|
+
pages: {
|
|
779
|
+
signIn: '/login',
|
|
780
|
+
},
|
|
781
|
+
});
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
### Usage in App Router
|
|
785
|
+
|
|
786
|
+
```tsx
|
|
787
|
+
// app/api/auth/[...nextauth]/route.ts
|
|
788
|
+
export { handlers as GET, handlers as POST } from '@repo/auth';
|
|
789
|
+
|
|
790
|
+
// app/dashboard/page.tsx
|
|
791
|
+
import { auth } from '@repo/auth';
|
|
792
|
+
import { redirect } from 'next/navigation';
|
|
793
|
+
|
|
794
|
+
export default async function DashboardPage() {
|
|
795
|
+
const session = await auth();
|
|
796
|
+
|
|
797
|
+
if (!session) {
|
|
798
|
+
redirect('/login');
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return <div>Welcome, {session.user.name}</div>;
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### Client-Side Session
|
|
806
|
+
|
|
807
|
+
```tsx
|
|
808
|
+
'use client';
|
|
809
|
+
|
|
810
|
+
import { useSession } from 'next-auth/react';
|
|
811
|
+
|
|
812
|
+
export function UserNav() {
|
|
813
|
+
const { data: session, status } = useSession();
|
|
814
|
+
|
|
815
|
+
if (status === 'loading') {
|
|
816
|
+
return <div>Loading...</div>;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (!session) {
|
|
820
|
+
return <a href="/login">Sign in</a>;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
return <div>Hello, {session.user.name}</div>;
|
|
824
|
+
}
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
## pnpm Workspaces
|
|
828
|
+
|
|
829
|
+
### Workspace Configuration
|
|
830
|
+
|
|
831
|
+
```yaml
|
|
832
|
+
# pnpm-workspace.yaml
|
|
833
|
+
packages:
|
|
834
|
+
- 'apps/*'
|
|
835
|
+
- 'packages/*'
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
### Package Dependencies
|
|
839
|
+
|
|
840
|
+
```json
|
|
841
|
+
// apps/web/package.json
|
|
842
|
+
{
|
|
843
|
+
"name": "web",
|
|
844
|
+
"dependencies": {
|
|
845
|
+
"@repo/ui": "workspace:*",
|
|
846
|
+
"@repo/database": "workspace:*",
|
|
847
|
+
"@repo/trpc": "workspace:*",
|
|
848
|
+
"next": "15.0.0"
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
### pnpm Commands
|
|
854
|
+
|
|
855
|
+
```bash
|
|
856
|
+
# Install all dependencies
|
|
857
|
+
pnpm install
|
|
858
|
+
|
|
859
|
+
# Add dependency to specific package
|
|
860
|
+
pnpm --filter web add react-query
|
|
861
|
+
|
|
862
|
+
# Add dependency to all packages
|
|
863
|
+
pnpm --filter "**" add -D typescript
|
|
864
|
+
|
|
865
|
+
# Run command in specific package
|
|
866
|
+
pnpm --filter database prisma generate
|
|
867
|
+
|
|
868
|
+
# Run command in all packages
|
|
869
|
+
pnpm --recursive test
|
|
870
|
+
|
|
871
|
+
# Update dependencies
|
|
872
|
+
pnpm update --latest
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
## Common Patterns
|
|
876
|
+
|
|
877
|
+
### Data Fetching Pattern
|
|
878
|
+
|
|
879
|
+
```tsx
|
|
880
|
+
// Server Component (app/users/page.tsx)
|
|
881
|
+
import { api } from '@/lib/trpc/server';
|
|
882
|
+
|
|
883
|
+
export default async function UsersPage() {
|
|
884
|
+
const users = await api.user.list();
|
|
885
|
+
|
|
886
|
+
return <UserList initialUsers={users} />;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Client Component (components/UserList.tsx)
|
|
890
|
+
'use client';
|
|
891
|
+
|
|
892
|
+
export function UserList({ initialUsers }: { initialUsers: User[] }) {
|
|
893
|
+
const { data: users } = api.user.list.useQuery(undefined, {
|
|
894
|
+
initialData: initialUsers,
|
|
895
|
+
refetchOnMount: false,
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
return <div>{users.map(u => <UserCard key={u.id} user={u} />)}</div>;
|
|
899
|
+
}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
### Form Handling
|
|
903
|
+
|
|
904
|
+
```tsx
|
|
905
|
+
'use client';
|
|
906
|
+
|
|
907
|
+
import { useForm } from 'react-hook-form';
|
|
908
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
909
|
+
import { z } from 'zod';
|
|
910
|
+
import { api } from '@/lib/trpc/client';
|
|
911
|
+
|
|
912
|
+
const schema = z.object({
|
|
913
|
+
name: z.string().min(2),
|
|
914
|
+
email: z.string().email(),
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
export function UserForm() {
|
|
918
|
+
const form = useForm({
|
|
919
|
+
resolver: zodResolver(schema),
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
const createUser = api.user.create.useMutation();
|
|
923
|
+
|
|
924
|
+
const onSubmit = form.handleSubmit((data) => {
|
|
925
|
+
createUser.mutate(data);
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
return <form onSubmit={onSubmit}>...</form>;
|
|
929
|
+
}
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
### Protected Routes
|
|
933
|
+
|
|
934
|
+
```tsx
|
|
935
|
+
// middleware.ts
|
|
936
|
+
import { auth } from '@repo/auth';
|
|
937
|
+
import { NextResponse } from 'next/server';
|
|
938
|
+
|
|
939
|
+
export default auth((req) => {
|
|
940
|
+
const isAuthenticated = !!req.auth;
|
|
941
|
+
const isProtectedRoute = req.nextUrl.pathname.startsWith('/dashboard');
|
|
942
|
+
|
|
943
|
+
if (isProtectedRoute && !isAuthenticated) {
|
|
944
|
+
return NextResponse.redirect(new URL('/login', req.url));
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
## Performance Optimization
|
|
950
|
+
|
|
951
|
+
### Code Splitting
|
|
952
|
+
|
|
953
|
+
```tsx
|
|
954
|
+
// Lazy load heavy components
|
|
955
|
+
import dynamic from 'next/dynamic';
|
|
956
|
+
|
|
957
|
+
const HeavyChart = dynamic(() => import('@/components/Chart'), {
|
|
958
|
+
loading: () => <div>Loading chart...</div>,
|
|
959
|
+
ssr: false, // Don't render on server
|
|
960
|
+
});
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
### Image Optimization
|
|
964
|
+
|
|
965
|
+
```tsx
|
|
966
|
+
import Image from 'next/image';
|
|
967
|
+
|
|
968
|
+
<Image
|
|
969
|
+
src="/avatar.jpg"
|
|
970
|
+
width={200}
|
|
971
|
+
height={200}
|
|
972
|
+
alt="User avatar"
|
|
973
|
+
priority // Load immediately
|
|
974
|
+
/>
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
### Caching
|
|
978
|
+
|
|
979
|
+
```tsx
|
|
980
|
+
// Static rendering (default for server components)
|
|
981
|
+
export default async function Page() {
|
|
982
|
+
const data = await fetch('https://api.example.com/data', {
|
|
983
|
+
next: { revalidate: 3600 }, // Revalidate every hour
|
|
984
|
+
});
|
|
985
|
+
return <div>{data}</div>;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Dynamic rendering
|
|
989
|
+
export const dynamic = 'force-dynamic';
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
## When to Use This Skill
|
|
993
|
+
|
|
994
|
+
- Building features in Next.js + tRPC + Prisma stack
|
|
995
|
+
- Setting up monorepo packages and dependencies
|
|
996
|
+
- Writing type-safe APIs with tRPC
|
|
997
|
+
- Designing database schemas with Prisma
|
|
998
|
+
- Creating reusable UI components with Storybook
|
|
999
|
+
- Testing React components with Vitest
|
|
1000
|
+
- Configuring Turbo pipelines
|
|
1001
|
+
- Implementing authentication with NextAuth
|
|
1002
|
+
|
|
1003
|
+
## Related Skills
|
|
1004
|
+
|
|
1005
|
+
- `test-first-methodology.md` - TDD/BDD with Vitest
|
|
1006
|
+
- `api-design-patterns.md` - tRPC router design
|
|
1007
|
+
- `component-architecture.md` - React patterns
|
|
1008
|
+
- `security-best-practices.md` - NextAuth security
|
|
1009
|
+
|
|
1010
|
+
## Key Takeaways
|
|
1011
|
+
|
|
1012
|
+
1. **Server Components First** - Use server components by default, client components when needed
|
|
1013
|
+
2. **Type Safety Everywhere** - tRPC + Prisma + TypeScript = end-to-end type safety
|
|
1014
|
+
3. **Monorepo Benefits** - Shared packages, consistent tooling, faster builds with Turbo
|
|
1015
|
+
4. **Test Before Deploy** - Vitest + React Testing Library for comprehensive coverage
|
|
1016
|
+
5. **Performance by Default** - Next.js optimizations (images, fonts, code splitting)
|
|
1017
|
+
6. **Database Type Safety** - Prisma generates types from schema automatically
|