@cogito.ai/cli 0.3.1 → 0.3.3
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 +181 -0
- package/dist/templates/web-nextjs/.env.example +4 -0
- package/dist/templates/web-nextjs/.vscode/settings.json +3 -0
- package/dist/templates/web-nextjs/README.md +25 -1
- package/dist/templates/web-nextjs/apps/docs/.source/browser.ts +1 -1
- package/dist/templates/web-nextjs/apps/docs/.source/server.ts +4 -3
- package/dist/templates/web-nextjs/apps/docs/content/docs/features/auth.mdx +139 -0
- package/dist/templates/web-nextjs/apps/web/components.json +25 -0
- package/dist/templates/web-nextjs/apps/web/messages/en.json +27 -3
- package/dist/templates/web-nextjs/apps/web/messages/zh.json +29 -5
- package/dist/templates/web-nextjs/apps/web/middleware.ts +54 -9
- package/dist/templates/web-nextjs/apps/web/package.json +13 -1
- package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(auth)/login/page.tsx +142 -0
- package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(auth)/signup/page.tsx +151 -0
- package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/dashboard/page.tsx +42 -0
- package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/layout.tsx +22 -0
- package/dist/templates/web-nextjs/apps/web/src/app/[locale]/globals.css +129 -3
- package/dist/templates/web-nextjs/apps/web/src/app/[locale]/layout.tsx +12 -7
- package/dist/templates/web-nextjs/apps/web/src/app/auth/callback/route.ts +21 -0
- package/dist/templates/web-nextjs/apps/web/src/components/ui/alert.tsx +76 -0
- package/dist/templates/web-nextjs/apps/web/src/components/ui/button.tsx +58 -0
- package/dist/templates/web-nextjs/apps/web/src/components/ui/form.tsx +154 -0
- package/dist/templates/web-nextjs/apps/web/src/components/ui/input.tsx +20 -0
- package/dist/templates/web-nextjs/apps/web/src/components/ui/label.tsx +20 -0
- package/dist/templates/web-nextjs/apps/web/src/components/ui/sonner.tsx +50 -0
- package/dist/templates/web-nextjs/apps/web/src/core/repositories/IAuthRepository.ts +9 -0
- package/dist/templates/web-nextjs/apps/web/src/core/types/auth.ts +14 -0
- package/dist/templates/web-nextjs/apps/web/src/features/auth/__contract__.ts +14 -0
- package/dist/templates/web-nextjs/apps/web/src/features/auth/actions.ts +86 -0
- package/dist/templates/web-nextjs/apps/web/src/features/auth/index.ts +1 -0
- package/dist/templates/web-nextjs/apps/web/src/i18n/config.ts +12 -0
- package/dist/templates/web-nextjs/apps/web/src/i18n/request.ts +3 -1
- package/dist/templates/web-nextjs/apps/web/src/infra/db/SupabaseAuthRepository.ts +63 -0
- package/dist/templates/web-nextjs/apps/web/src/infra/db/client.ts +39 -1
- package/dist/templates/web-nextjs/apps/web/src/infra/db/schema.ts +5 -5
- package/dist/templates/web-nextjs/apps/web/src/infra/providers.ts +6 -0
- package/dist/templates/web-nextjs/apps/web/src/lib/utils.ts +6 -0
- package/dist/templates/web-nextjs/apps/web/src/lib/validations/auth.ts +20 -0
- package/dist/templates/web-nextjs/apps/web/src/styles/shadcn-tailwind.css +95 -0
- package/dist/templates/web-nextjs/apps/web/src/styles/tw-animate.css +1 -0
- package/dist/templates/web-nextjs/pnpm-lock.yaml +2327 -17
- package/package.json +1 -1
- package/dist/templates/web-nextjs/apps/web/src/app/[locale]/hello/page.tsx +0 -45
- package/dist/templates/web-nextjs/apps/web/src/core/repositories/IGreetingRepository.ts +0 -14
- package/dist/templates/web-nextjs/apps/web/src/core/types/greeting.ts +0 -9
- package/dist/templates/web-nextjs/apps/web/src/features/hello/__contract__.ts +0 -16
- package/dist/templates/web-nextjs/apps/web/src/features/hello/hello.test.ts +0 -16
- package/dist/templates/web-nextjs/apps/web/src/features/hello/index.ts +0 -5
- package/dist/templates/web-nextjs/apps/web/src/features/hello/service.ts +0 -12
- package/dist/templates/web-nextjs/apps/web/src/infra/db/SupabaseGreetingRepository.ts +0 -58
package/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# AgentDock CLI
|
|
2
|
+
|
|
3
|
+
**`@cogito.ai/cli`** — Scaffold production-ready AI coding agent projects for humans, CI pipelines, and AI agents.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@cogito.ai/cli)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Interactive mode (for humans)
|
|
13
|
+
npx @cogito.ai/cli init
|
|
14
|
+
|
|
15
|
+
# Headless mode (for CI / AI agents)
|
|
16
|
+
npx @cogito.ai/cli init --name my-app --template web-nextjs --pm pnpm --json
|
|
17
|
+
|
|
18
|
+
# Start MCP Stdio server (for AI agents via Model Context Protocol)
|
|
19
|
+
npx @cogito.ai/cli mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Run without installing (recommended)
|
|
26
|
+
npx @cogito.ai/cli <command>
|
|
27
|
+
|
|
28
|
+
# Global install
|
|
29
|
+
npm install -g @cogito.ai/cli
|
|
30
|
+
pnpm add -g @cogito.ai/cli
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
### `agentdock init`
|
|
38
|
+
|
|
39
|
+
Scaffold a new project from a template. Auto-detects the environment:
|
|
40
|
+
|
|
41
|
+
- **TTY** → interactive prompts (human mode)
|
|
42
|
+
- **Non-TTY / `--silent` / `--json`** → headless execution (agent/CI mode)
|
|
43
|
+
|
|
44
|
+
| Flag | Type | Default | Description |
|
|
45
|
+
| ---- | ---- | ------- | ----------- |
|
|
46
|
+
| `--name` | string | required | Project name and target directory name |
|
|
47
|
+
| `--template` | string | required | Template ID (e.g. `web-nextjs`) |
|
|
48
|
+
| `--pm` | string | `pnpm` | Package manager: `pnpm` / `npm` / `yarn` / `bun` |
|
|
49
|
+
| `--dir` | string | `./<name>` | Target directory (absolute or relative to cwd) |
|
|
50
|
+
| `--json` | boolean | `false` | Output NDJSON result to stdout |
|
|
51
|
+
| `--silent` | boolean | `false` | Suppress all output |
|
|
52
|
+
|
|
53
|
+
**JSON output (success):**
|
|
54
|
+
```json
|
|
55
|
+
{"ok":true,"targetDir":"/path/to/my-app","name":"my-app","template":"web-nextjs"}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**JSON output (failure):**
|
|
59
|
+
```json
|
|
60
|
+
{"ok":false,"error":"TARGET_DIR_EXISTS","message":"Directory already exists: /path/to/my-app"}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Error codes:** `MISSING_ARG` · `TEMPLATE_NOT_FOUND` · `TARGET_DIR_EXISTS` · `CLI_VERSION_OUTDATED` · `SCAFFOLD_FAILED`
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### `agentdock mcp`
|
|
68
|
+
|
|
69
|
+
Start an MCP (Model Context Protocol) Stdio server. Exposes CLI capabilities as tools directly callable by AI agents.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
agentdock mcp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Available tools:**
|
|
76
|
+
|
|
77
|
+
| Tool | Description |
|
|
78
|
+
| ---- | ----------- |
|
|
79
|
+
| `list_templates` | List all available project templates |
|
|
80
|
+
| `scaffold_project` | Scaffold a project into a target directory |
|
|
81
|
+
|
|
82
|
+
**VS Code Copilot MCP config (`.vscode/mcp.json`):**
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"servers": {
|
|
86
|
+
"agentdock": {
|
|
87
|
+
"type": "stdio",
|
|
88
|
+
"command": "npx",
|
|
89
|
+
"args": ["@cogito.ai/cli", "mcp"]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Available Templates
|
|
98
|
+
|
|
99
|
+
| Template ID | Description |
|
|
100
|
+
| ----------- | ----------- |
|
|
101
|
+
| `web-nextjs` | Next.js 16 + Supabase + next-intl + Tailwind CSS v4 + Vitest + Fumadocs — full-stack monorepo starter |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Architecture
|
|
106
|
+
|
|
107
|
+
The CLI uses an **Adapter pattern** — a single executor core serves three consumer surfaces:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
agentdock init
|
|
111
|
+
│
|
|
112
|
+
├── TTY detected ──→ Human Adapter (Clack interactive UI)
|
|
113
|
+
└── Non-TTY / flags ──→ Agent Adapter (structured JSON output)
|
|
114
|
+
|
|
115
|
+
agentdock mcp ──→ MCP Adapter (MCP Stdio Server)
|
|
116
|
+
|
|
117
|
+
All adapters share the same Core Executor (scaffold + registry)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Design principles:**
|
|
121
|
+
|
|
122
|
+
1. **Headless-first** — the executor core has no TTY dependency; the human UI is a thin shell on top.
|
|
123
|
+
2. **Structured output as protocol** — `--json` mode produces stable machine-parseable NDJSON, not natural language.
|
|
124
|
+
3. **One capability, three surfaces** — `scaffold_project` is implemented once and projected to CLI flags, JSON mode, and MCP tool automatically.
|
|
125
|
+
|
|
126
|
+
**Tech stack:**
|
|
127
|
+
|
|
128
|
+
| Component | Role |
|
|
129
|
+
| --------- | ---- |
|
|
130
|
+
| [Bun](https://github.com/oven-sh/bun) | Build: single-file Node.js executable |
|
|
131
|
+
| [Citty](https://github.com/unjs/citty) | Command modeling: sub-commands, flag schema, lazy loading |
|
|
132
|
+
| [Clack](https://github.com/bombshell-dev/clack) | Human interaction: TTY prompts |
|
|
133
|
+
| [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) | Agent protocol: MCP Stdio server |
|
|
134
|
+
| [Changesets](https://github.com/changesets/changesets) | Release governance: semantic versioning + changelog |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Install dependencies
|
|
142
|
+
pnpm install
|
|
143
|
+
|
|
144
|
+
# Regenerate template registry
|
|
145
|
+
pnpm --filter @cogito.ai/cli generate-registry
|
|
146
|
+
|
|
147
|
+
# Run from source (no build needed)
|
|
148
|
+
npx tsx packages/cli/bin/agentdock.ts init
|
|
149
|
+
|
|
150
|
+
# Run tests
|
|
151
|
+
pnpm --filter @cogito.ai/cli test
|
|
152
|
+
|
|
153
|
+
# Build
|
|
154
|
+
pnpm --filter @cogito.ai/cli build
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Adding a template
|
|
158
|
+
|
|
159
|
+
1. Create `templates/<id>/` with a complete project structure.
|
|
160
|
+
2. Add `package.json` with `"agentdock": { "minCliVersion": "x.y.z" }`.
|
|
161
|
+
3. Run `pnpm --filter @cogito.ai/cli generate-registry`.
|
|
162
|
+
4. Publish a new CLI version — templates are bundled inside the npm package.
|
|
163
|
+
|
|
164
|
+
### Publishing a new version
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
pnpm changeset # describe the change
|
|
168
|
+
pnpm changeset version # bump version + update CHANGELOG
|
|
169
|
+
pnpm --filter @cogito.ai/cli build
|
|
170
|
+
pnpm --filter @cogito.ai/cli publish
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
[中文文档 →](./README-zh.md)
|
|
@@ -20,3 +20,7 @@ SUPABASE_SERVICE_ROLE_KEY=<your-service-role-key>
|
|
|
20
20
|
|
|
21
21
|
# Public URL of this app (used for OG image URLs, absolute redirects, etc.)
|
|
22
22
|
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
23
|
+
|
|
24
|
+
# Default locale used for root redirects and i18n fallback
|
|
25
|
+
# Allowed values: en, zh
|
|
26
|
+
APP_DEFAULT_LOCALE=zh
|
|
@@ -71,7 +71,31 @@ Open `.env.local` and fill in your values:
|
|
|
71
71
|
| `SUPABASE_SERVICE_ROLE_KEY` | Supabase Dashboard → Project Settings → API → service_role (server only) |
|
|
72
72
|
| `NEXT_PUBLIC_APP_URL` | `http://localhost:3000` for local dev |
|
|
73
73
|
|
|
74
|
-
### 4.
|
|
74
|
+
### 4. Set up Supabase Auth (required for login / signup)
|
|
75
|
+
|
|
76
|
+
**4a. Enable GitHub OAuth provider**
|
|
77
|
+
|
|
78
|
+
1. Go to **Supabase Dashboard → Authentication → Providers → GitHub**.
|
|
79
|
+
2. Toggle **Enable Sign in with GitHub** on.
|
|
80
|
+
3. Create a GitHub OAuth App at [github.com/settings/developers](https://github.com/settings/developers):
|
|
81
|
+
- **Homepage URL**: `http://localhost:3000`
|
|
82
|
+
- **Authorization callback URL**: copy the **Callback URL (for OAuth)** shown in the Supabase GitHub provider dialog (e.g. `https://<project-ref>.supabase.co/auth/v1/callback`)
|
|
83
|
+
4. Paste the GitHub **Client ID** and **Client Secret** back into the Supabase provider settings and click **Save**.
|
|
84
|
+
|
|
85
|
+
**4b. Add redirect URL**
|
|
86
|
+
|
|
87
|
+
1. Go to **Supabase Dashboard → Authentication → URL Configuration**.
|
|
88
|
+
2. Under **Redirect URLs**, click **Add URL** and add:
|
|
89
|
+
- `http://localhost:3000/auth/callback` ← for local development
|
|
90
|
+
- `https://<your-production-domain>/auth/callback` ← for production
|
|
91
|
+
3. Under **Site URL** set `http://localhost:3000` (or your production URL).
|
|
92
|
+
|
|
93
|
+
**4c. (Optional) Email confirmation**
|
|
94
|
+
|
|
95
|
+
By default Supabase requires email confirmation before a user can sign in.
|
|
96
|
+
To disable during development: **Authentication → Email → Enable email confirmations** → toggle off.
|
|
97
|
+
|
|
98
|
+
### 5. Start development server
|
|
75
99
|
|
|
76
100
|
```bash
|
|
77
101
|
pnpm dev
|
|
@@ -7,6 +7,6 @@ const create = browser<typeof Config, import("fumadocs-mdx/runtime/types").Inter
|
|
|
7
7
|
}
|
|
8
8
|
}>();
|
|
9
9
|
const browserCollections = {
|
|
10
|
-
docs: create.doc("docs", {"index.mdx": () => import("../content/docs/index.mdx?collection=docs"), "changelog/index.mdx": () => import("../content/docs/changelog/index.mdx?collection=docs"), "features/
|
|
10
|
+
docs: create.doc("docs", {"index.mdx": () => import("../content/docs/index.mdx?collection=docs"), "changelog/index.mdx": () => import("../content/docs/changelog/index.mdx?collection=docs"), "roadmap/index.mdx": () => import("../content/docs/roadmap/index.mdx?collection=docs"), "features/auth.mdx": () => import("../content/docs/features/auth.mdx?collection=docs"), "features/hello.mdx": () => import("../content/docs/features/hello.mdx?collection=docs"), }),
|
|
11
11
|
};
|
|
12
12
|
export default browserCollections;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as __fd_glob_9 from "../content/docs/features/hello.mdx?collection=docs"
|
|
3
|
+
import * as __fd_glob_8 from "../content/docs/features/auth.mdx?collection=docs"
|
|
4
|
+
import * as __fd_glob_7 from "../content/docs/roadmap/index.mdx?collection=docs"
|
|
4
5
|
import * as __fd_glob_6 from "../content/docs/changelog/index.mdx?collection=docs"
|
|
5
6
|
import * as __fd_glob_5 from "../content/docs/index.mdx?collection=docs"
|
|
6
7
|
import { default as __fd_glob_4 } from "../content/docs/roadmap/meta.json?collection=docs"
|
|
@@ -16,4 +17,4 @@ const create = server<typeof Config, import("fumadocs-mdx/runtime/types").Intern
|
|
|
16
17
|
}
|
|
17
18
|
}>({"doc":{"passthroughs":["extractedReferences"]}});
|
|
18
19
|
|
|
19
|
-
export const docs = await create.docs("docs", "content/docs", {"meta.json": __fd_glob_0, "changelog/meta.json": __fd_glob_1, "decisions/meta.json": __fd_glob_2, "features/meta.json": __fd_glob_3, "roadmap/meta.json": __fd_glob_4, }, {"index.mdx": __fd_glob_5, "changelog/index.mdx": __fd_glob_6, "
|
|
20
|
+
export const docs = await create.docs("docs", "content/docs", {"meta.json": __fd_glob_0, "changelog/meta.json": __fd_glob_1, "decisions/meta.json": __fd_glob_2, "features/meta.json": __fd_glob_3, "roadmap/meta.json": __fd_glob_4, }, {"index.mdx": __fd_glob_5, "changelog/index.mdx": __fd_glob_6, "roadmap/index.mdx": __fd_glob_7, "features/auth.mdx": __fd_glob_8, "features/hello.mdx": __fd_glob_9, });
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Authentication (auth)
|
|
3
|
+
description: Email/password and GitHub OAuth authentication using Supabase Auth, implemented following the four-layer contract.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
The `auth` feature provides full authentication using **Supabase Auth**:
|
|
9
|
+
|
|
10
|
+
- Email + password sign-in and sign-up (with email verification)
|
|
11
|
+
- GitHub OAuth sign-in
|
|
12
|
+
- Session management via `@supabase/ssr` (httpOnly cookies, auto-refresh)
|
|
13
|
+
- Route protection via `(protected)` route group + server-side session check
|
|
14
|
+
|
|
15
|
+
UI components: **shadcn/ui** blocks (`login-03`, `signup-03`, `dashboard-01`)
|
|
16
|
+
Forms: **react-hook-form + zod** (all multi-field forms use this pattern)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Architecture: Four-Layer Contract
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
core/types/auth.ts ← Domain types (AuthUser, AuthResult, ActionResult)
|
|
24
|
+
core/repositories/IAuthRepository.ts ← Interface (no Supabase types)
|
|
25
|
+
infra/db/SupabaseAuthRepository.ts ← Implementation (Supabase SDK)
|
|
26
|
+
infra/providers.ts ← DI factory: getAuthRepository()
|
|
27
|
+
features/auth/__contract__.ts ← Public API types
|
|
28
|
+
features/auth/actions.ts ← Server Actions ('use server')
|
|
29
|
+
features/auth/index.ts ← Barrel export
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Why the interface layer?
|
|
33
|
+
|
|
34
|
+
`IAuthRepository` decouples features from the Supabase SDK. To swap to Auth.js:
|
|
35
|
+
1. Implement `IAuthRepository` with Auth.js
|
|
36
|
+
2. Update `infra/providers.ts` to return the new implementation
|
|
37
|
+
3. `features/auth/actions.ts` is unchanged — zero feature churn
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Server Actions
|
|
42
|
+
|
|
43
|
+
Import from the feature barrel:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { signIn, signUp, signOut, signInWithGithub } from '@/features/auth'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Action | Returns | Side effect |
|
|
50
|
+
|--------|---------|------------|
|
|
51
|
+
| `signIn(prevState, formData)` | `ActionResult` | Redirects to `/${locale}/dashboard` on success |
|
|
52
|
+
| `signUp(prevState, formData)` | `ActionResult<{ success: true; email: string }>` | No redirect — waits for email verification |
|
|
53
|
+
| `signOut()` | `void` | Clears session, redirects to `/${locale}/login` |
|
|
54
|
+
| `signInWithGithub()` | `ActionResult<{ url: string }>` | Returns OAuth URL — caller does `router.push(url)` |
|
|
55
|
+
|
|
56
|
+
All actions use `useActionState` on the client side:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
const [state, formAction, isPending] = useActionState(signIn, null)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Route Structure
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
src/app/
|
|
68
|
+
[locale]/
|
|
69
|
+
(auth)/ ← Unauthenticated pages (no layout)
|
|
70
|
+
login/page.tsx
|
|
71
|
+
signup/page.tsx
|
|
72
|
+
(protected)/ ← Authenticated pages
|
|
73
|
+
layout.tsx ← Server: checks session, redirects to /login if missing
|
|
74
|
+
dashboard/page.tsx
|
|
75
|
+
auth/
|
|
76
|
+
callback/route.ts ← OAuth + email verification callback (no locale prefix)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Why no locale prefix on `/auth/callback`?
|
|
80
|
+
|
|
81
|
+
Supabase callbacks must be registered with a fixed URL. Using `/auth/callback` (without `[locale]`)
|
|
82
|
+
avoids registering multiple locale variants in the Supabase dashboard.
|
|
83
|
+
After a successful callback, the app redirects to `/en/dashboard` (default locale).
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Route Protection
|
|
88
|
+
|
|
89
|
+
Protection happens at the **layout level** (server-side), not middleware-only:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// (protected)/layout.tsx
|
|
93
|
+
const { data: { user } } = await supabase.auth.getUser()
|
|
94
|
+
if (!user) redirect(`/${locale}/login`)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`getUser()` is used (not `getSession()`) because it validates the JWT server-side,
|
|
98
|
+
preventing forged tokens.
|
|
99
|
+
|
|
100
|
+
Middleware additionally:
|
|
101
|
+
1. Refreshes the Supabase session token on every request
|
|
102
|
+
2. Redirects authenticated users away from `/(auth)` pages to `/dashboard`
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Zod Schemas
|
|
107
|
+
|
|
108
|
+
Defined in `src/lib/validations/auth.ts` — shared between Server Actions and UI:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { signInSchema, signUpSchema } from '@/lib/validations/auth'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Schema | Fields | Rules |
|
|
115
|
+
|--------|--------|-------|
|
|
116
|
+
| `signInSchema` | `email`, `password` | email format, min 8 chars |
|
|
117
|
+
| `signUpSchema` | `email`, `password`, `confirmPassword` | above + passwords match |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Setup Requirements
|
|
122
|
+
|
|
123
|
+
Before auth works, configure Supabase:
|
|
124
|
+
|
|
125
|
+
1. **GitHub OAuth** — Supabase Dashboard → Authentication → Providers → GitHub
|
|
126
|
+
2. **Redirect URL** — Supabase Dashboard → Authentication → URL Configuration → add `http://localhost:3000/auth/callback`
|
|
127
|
+
|
|
128
|
+
See the template `README.md` for the full step-by-step guide.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Non-Goals
|
|
133
|
+
|
|
134
|
+
This feature intentionally excludes:
|
|
135
|
+
- Password reset / forgot password
|
|
136
|
+
- User profile / avatar / account settings
|
|
137
|
+
- Role-based access control (RBAC)
|
|
138
|
+
- Magic link / OTP / phone login
|
|
139
|
+
- Custom email templates
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-nova",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "tailwind.config.ts",
|
|
8
|
+
"css": "src/app/[locale]/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"rtl": false,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "@/components",
|
|
17
|
+
"utils": "@/lib/utils",
|
|
18
|
+
"ui": "@/components/ui",
|
|
19
|
+
"lib": "@/lib",
|
|
20
|
+
"hooks": "@/hooks"
|
|
21
|
+
},
|
|
22
|
+
"menuColor": "default",
|
|
23
|
+
"menuAccent": "subtle",
|
|
24
|
+
"registries": {}
|
|
25
|
+
}
|
|
@@ -3,8 +3,32 @@
|
|
|
3
3
|
"title": "Welcome to AgentDock Web Template",
|
|
4
4
|
"description": "A Next.js scaffold with four-layer architecture."
|
|
5
5
|
},
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
6
|
+
"auth": {
|
|
7
|
+
"loginTitle": "Welcome back",
|
|
8
|
+
"loginSubtitle": "Sign in to your account",
|
|
9
|
+
"signupTitle": "Create an account",
|
|
10
|
+
"signupSubtitle": "Enter your details to get started",
|
|
11
|
+
"emailLabel": "Email",
|
|
12
|
+
"emailPlaceholder": "you@example.com",
|
|
13
|
+
"emailHelperText": "Use a real email address — you'll need to verify it after sign up.",
|
|
14
|
+
"passwordLabel": "Password",
|
|
15
|
+
"passwordPlaceholder": "••••••••",
|
|
16
|
+
"confirmPasswordLabel": "Confirm password",
|
|
17
|
+
"confirmPasswordPlaceholder": "••••••••",
|
|
18
|
+
"signInButton": "Sign in",
|
|
19
|
+
"signUpButton": "Create account",
|
|
20
|
+
"signOutButton": "Sign out",
|
|
21
|
+
"githubButton": "Sign in with GitHub",
|
|
22
|
+
"noAccountText": "Don't have an account?",
|
|
23
|
+
"signUpLink": "Sign up",
|
|
24
|
+
"hasAccountText": "Already have an account?",
|
|
25
|
+
"signInLink": "Sign in",
|
|
26
|
+
"orContinueWith": "Or continue with",
|
|
27
|
+
"verifyEmailTitle": "Check your email",
|
|
28
|
+
"verifyEmailMessage": "We sent a verification link to {email}. Click the link to complete your sign up.",
|
|
29
|
+
"dashboardTitle": "Dashboard",
|
|
30
|
+
"dashboardWelcome": "Welcome, {email}",
|
|
31
|
+
"errorInvalidCredentials": "Invalid email or password.",
|
|
32
|
+
"errorGeneric": "Something went wrong. Please try again."
|
|
9
33
|
}
|
|
10
34
|
}
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"home": {
|
|
3
|
-
"title": "
|
|
4
|
-
"description": "
|
|
3
|
+
"title": "欢迎使用 AgentDock Web 模板",
|
|
4
|
+
"description": "基于四层架构的 Next.js 脚手架。"
|
|
5
5
|
},
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
6
|
+
"auth": {
|
|
7
|
+
"loginTitle": "欢迎回来",
|
|
8
|
+
"loginSubtitle": "登录您的账号",
|
|
9
|
+
"signupTitle": "创建账号",
|
|
10
|
+
"signupSubtitle": "输入您的信息以开始使用",
|
|
11
|
+
"emailLabel": "邮箱",
|
|
12
|
+
"emailPlaceholder": "you@example.com",
|
|
13
|
+
"emailHelperText": "请使用真实邮箱地址 — 注册后需要验证。",
|
|
14
|
+
"passwordLabel": "密码",
|
|
15
|
+
"passwordPlaceholder": "••••••••",
|
|
16
|
+
"confirmPasswordLabel": "确认密码",
|
|
17
|
+
"confirmPasswordPlaceholder": "••••••••",
|
|
18
|
+
"signInButton": "登录",
|
|
19
|
+
"signUpButton": "创建账号",
|
|
20
|
+
"signOutButton": "退出登录",
|
|
21
|
+
"githubButton": "使用 GitHub 登录",
|
|
22
|
+
"noAccountText": "还没有账号?",
|
|
23
|
+
"signUpLink": "立即注册",
|
|
24
|
+
"hasAccountText": "已有账号?",
|
|
25
|
+
"signInLink": "立即登录",
|
|
26
|
+
"orContinueWith": "或使用以下方式",
|
|
27
|
+
"verifyEmailTitle": "请验证您的邮箱",
|
|
28
|
+
"verifyEmailMessage": "我们已发送验证链接至 {email},请点击链接完成注册。",
|
|
29
|
+
"dashboardTitle": "控制台",
|
|
30
|
+
"dashboardWelcome": "欢迎,{email}",
|
|
31
|
+
"errorInvalidCredentials": "邮箱或密码错误。",
|
|
32
|
+
"errorGeneric": "出现错误,请稍后重试。"
|
|
9
33
|
}
|
|
10
34
|
}
|
|
@@ -1,23 +1,67 @@
|
|
|
1
1
|
import createMiddleware from 'next-intl/middleware'
|
|
2
2
|
import { defineRouting } from 'next-intl/routing'
|
|
3
3
|
import { NextResponse, type NextRequest } from 'next/server'
|
|
4
|
+
import { createMiddlewareClient } from '@/infra/db/client'
|
|
5
|
+
import { defaultLocale, isLocale, locales } from '@/i18n/config'
|
|
4
6
|
|
|
5
7
|
export const routing = defineRouting({
|
|
6
|
-
locales
|
|
7
|
-
defaultLocale
|
|
8
|
+
locales,
|
|
9
|
+
defaultLocale,
|
|
8
10
|
})
|
|
9
11
|
|
|
10
12
|
const handleI18nRouting = createMiddleware(routing)
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
+
function copyCookies(from: NextResponse, to: NextResponse) {
|
|
15
|
+
for (const cookie of from.cookies.getAll()) {
|
|
16
|
+
to.cookies.set(cookie)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default async function middleware(request: NextRequest) {
|
|
21
|
+
const pathname = request.nextUrl.pathname
|
|
22
|
+
|
|
23
|
+
if (pathname === '/') {
|
|
24
|
+
const url = request.nextUrl.clone()
|
|
25
|
+
url.pathname = `/${defaultLocale}`
|
|
26
|
+
return NextResponse.redirect(url)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Skip Supabase session refresh for the auth callback route
|
|
30
|
+
// (it's handled by the route handler itself)
|
|
31
|
+
if (pathname.startsWith('/auth/')) {
|
|
32
|
+
return NextResponse.next()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 1. Let next-intl build the final response first.
|
|
36
|
+
// Supabase cookie refresh should write into this response to avoid cookie loss.
|
|
37
|
+
const response = handleI18nRouting(request)
|
|
38
|
+
|
|
39
|
+
// 2. Refresh the Supabase session (keeps token alive, writes updated cookie)
|
|
40
|
+
const { supabase } = createMiddlewareClient(request, response)
|
|
41
|
+
const {
|
|
42
|
+
data: { user },
|
|
43
|
+
} = await supabase.auth.getUser()
|
|
44
|
+
|
|
45
|
+
// 3. Redirect authenticated users away from auth pages (/(auth)/ routes)
|
|
46
|
+
const authPagePattern = /^\/[a-z]{2}\/(login|signup)(\/|$)/
|
|
47
|
+
if (user && authPagePattern.test(pathname)) {
|
|
48
|
+
const localeSegment = pathname.split('/')[1] ?? defaultLocale
|
|
49
|
+
const locale = isLocale(localeSegment) ? localeSegment : defaultLocale
|
|
50
|
+
const url = request.nextUrl.clone()
|
|
51
|
+
url.pathname = `/${locale}/dashboard`
|
|
52
|
+
const redirectResponse = NextResponse.redirect(url)
|
|
53
|
+
copyCookies(response, redirectResponse)
|
|
54
|
+
return redirectResponse
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 4. Normalize unknown locale-like prefixes: /fr/hello -> /en/hello
|
|
58
|
+
const segments = pathname.split('/').filter(Boolean)
|
|
14
59
|
const firstSegment = segments[0]
|
|
15
60
|
|
|
16
61
|
if (firstSegment !== undefined) {
|
|
17
62
|
const isLocaleLike = /^[a-z]{2}(?:-[A-Z]{2})?$/.test(firstSegment)
|
|
18
|
-
const isSupportedLocale =
|
|
63
|
+
const isSupportedLocale = isLocale(firstSegment)
|
|
19
64
|
|
|
20
|
-
// Normalize unknown locale-like prefixes: /fr/hello -> /en/hello.
|
|
21
65
|
if (isLocaleLike && !isSupportedLocale) {
|
|
22
66
|
const url = request.nextUrl.clone()
|
|
23
67
|
url.pathname = `/${routing.defaultLocale}/${segments.slice(1).join('/')}`.replace(/\/$/, '')
|
|
@@ -25,10 +69,11 @@ export default function middleware(request: NextRequest) {
|
|
|
25
69
|
}
|
|
26
70
|
}
|
|
27
71
|
|
|
28
|
-
return
|
|
72
|
+
return response
|
|
29
73
|
}
|
|
30
74
|
|
|
31
75
|
export const config = {
|
|
32
|
-
// Match all pathnames except Next.js internals and
|
|
33
|
-
|
|
76
|
+
// Match all pathnames except Next.js internals, static files, and auth callback.
|
|
77
|
+
// The explicit '/' entry ensures the root redirect to the default locale always fires.
|
|
78
|
+
matcher: ['/', '/((?!_next|_vercel|auth/callback|.*\\..*).*)'],
|
|
34
79
|
}
|
|
@@ -11,11 +11,23 @@
|
|
|
11
11
|
"check-types": "tsc --noEmit"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
+
"@base-ui/react": "^1.5.0",
|
|
15
|
+
"@hookform/resolvers": "^5.4.0",
|
|
14
16
|
"@supabase/ssr": "^0.10.3",
|
|
17
|
+
"class-variance-authority": "^0.7.1",
|
|
18
|
+
"clsx": "^2.1.1",
|
|
19
|
+
"lucide-react": "^1.17.0",
|
|
15
20
|
"next": "16",
|
|
16
21
|
"next-intl": "^4.13.0",
|
|
22
|
+
"next-themes": "^0.4.6",
|
|
17
23
|
"react": "19",
|
|
18
|
-
"react-dom": "19"
|
|
24
|
+
"react-dom": "19",
|
|
25
|
+
"react-hook-form": "^7.77.0",
|
|
26
|
+
"shadcn": "^4.10.0",
|
|
27
|
+
"sonner": "^2.0.7",
|
|
28
|
+
"tailwind-merge": "^3.6.0",
|
|
29
|
+
"tw-animate-css": "^1.4.0",
|
|
30
|
+
"zod": "^4.4.3"
|
|
19
31
|
},
|
|
20
32
|
"devDependencies": {
|
|
21
33
|
"@cogito.ai/eslint-config": "workspace:*",
|