@primstack/cli 0.0.5 → 0.0.7
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/dist/templates/hosted/root/CLAUDE.md.template +5 -17
- package/dist/templates/hosted/root/next.config.ts.template +1 -0
- package/dist/templates/hosted/root/package.json.template +3 -4
- package/dist/templates/hosted/src/app/api/auth/[...nextauth]/route.ts.template +3 -0
- package/dist/templates/hosted/src/app/api/health/route.ts.template +0 -2
- package/dist/templates/hosted/src/app/auth/login/page.tsx.template +0 -2
- package/dist/templates/hosted/src/app/layout.tsx.template +4 -1
- package/dist/templates/hosted/src/app/page.tsx.template +0 -2
- package/dist/templates/hosted/src/db/schema.ts.template +8 -8
- package/dist/templates/hosted/src/env.d.ts.template +8 -9
- package/dist/templates/hosted/src/lib/auth.config.ts.template +21 -0
- package/dist/templates/hosted/src/lib/auth.ts.template +6 -23
- package/dist/templates/hosted/src/lib/db.ts.template +17 -10
- package/dist/templates/hosted/src/middleware.ts.template +6 -2
- package/package.json +1 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# {{PROJECT_NAME}}
|
|
2
2
|
|
|
3
|
-
A Primstack hosted project — Next.js + Primstack UI +
|
|
3
|
+
A Primstack hosted project — Next.js + Primstack UI + SQLite + Auth.js, deployed to Primstack hosting.
|
|
4
4
|
|
|
5
5
|
## Stack
|
|
6
6
|
|
|
7
|
-
- **Framework:** Next.js
|
|
7
|
+
- **Framework:** Next.js 16 (App Router, Turbopack)
|
|
8
8
|
- **Components:** @primstack/ui (60+ Radix-based React components)
|
|
9
9
|
- **Styling:** Tailwind CSS + Primstack design tokens
|
|
10
|
-
- **Database:**
|
|
10
|
+
- **Database:** SQLite via Drizzle ORM (better-sqlite3 locally)
|
|
11
11
|
- **Auth:** Auth.js v5 with GitHub provider + Drizzle adapter
|
|
12
|
-
- **Hosting:** Primstack hosting
|
|
12
|
+
- **Hosting:** Primstack hosting
|
|
13
13
|
|
|
14
14
|
## Component Usage
|
|
15
15
|
|
|
@@ -70,15 +70,9 @@ export async function createPost(formData: FormData) {
|
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`getDatabase()` uses the D1 binding via `getRequestContext()` in both production and local dev (via `setupDevPlatform` in `src/instrumentation.ts`).
|
|
74
|
-
|
|
75
73
|
## Adding a New Page
|
|
76
74
|
|
|
77
|
-
All pages that use CF bindings (D1, env vars) must declare edge runtime:
|
|
78
|
-
|
|
79
75
|
```tsx
|
|
80
|
-
export const runtime = 'edge';
|
|
81
|
-
|
|
82
76
|
export default function MyPage() {
|
|
83
77
|
return <div>Hello</div>;
|
|
84
78
|
}
|
|
@@ -91,8 +85,6 @@ export default function MyPage() {
|
|
|
91
85
|
import { auth } from '@/lib/auth';
|
|
92
86
|
import { redirect } from 'next/navigation';
|
|
93
87
|
|
|
94
|
-
export const runtime = 'edge';
|
|
95
|
-
|
|
96
88
|
export default async function ProtectedPage() {
|
|
97
89
|
const session = await auth();
|
|
98
90
|
if (!session) redirect('/auth/login');
|
|
@@ -112,8 +104,6 @@ prim env set GITHUB_CLIENT_SECRET=... # GitHub OAuth secret
|
|
|
112
104
|
prim deploy # Vars injected automatically
|
|
113
105
|
```
|
|
114
106
|
|
|
115
|
-
D1 is bound automatically via the Primstack platform — no need to set `DATABASE_URL`.
|
|
116
|
-
|
|
117
107
|
## Deployment
|
|
118
108
|
|
|
119
109
|
```bash
|
|
@@ -130,10 +120,8 @@ prim deploy -m "message" # Deploy with message
|
|
|
130
120
|
| `src/app/layout.tsx` | Root layout with ThemeProvider |
|
|
131
121
|
| `src/app/page.tsx` | Home page |
|
|
132
122
|
| `src/db/schema.ts` | Database schema (Drizzle) |
|
|
133
|
-
| `src/lib/db.ts` | Database connection (
|
|
123
|
+
| `src/lib/db.ts` | Database connection (better-sqlite3) |
|
|
134
124
|
| `src/lib/auth.ts` | Auth.js configuration + Drizzle adapter |
|
|
135
125
|
| `src/middleware.ts` | Auth middleware |
|
|
136
|
-
| `src/instrumentation.ts` | setupDevPlatform for local D1 |
|
|
137
126
|
| `src/app/actions/*.ts` | Server Actions |
|
|
138
|
-
| `wrangler.toml` | CF Pages config (D1 binding) |
|
|
139
127
|
| `primstack.config.json` | Project configuration |
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "next dev --turbopack",
|
|
8
8
|
"build": "next build",
|
|
9
|
-
"preview": "npx wrangler pages dev .vercel/output/static",
|
|
10
9
|
"lint": "next lint",
|
|
11
10
|
"db:generate": "drizzle-kit generate",
|
|
12
11
|
"db:migrate": "drizzle-kit migrate",
|
|
@@ -18,6 +17,7 @@
|
|
|
18
17
|
"@primstack/theme": "^0.0.2",
|
|
19
18
|
"@primstack/tokens": "^0.0.2",
|
|
20
19
|
"@primstack/ui": "^0.0.2",
|
|
20
|
+
"better-sqlite3": "^11.0.0",
|
|
21
21
|
"drizzle-orm": "^0.38.0",
|
|
22
22
|
"next": "^16.0.0",
|
|
23
23
|
"next-auth": "^5.0.0-beta.25",
|
|
@@ -25,15 +25,14 @@
|
|
|
25
25
|
"react-dom": "^19.0.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
28
29
|
"@types/node": "^22.0.0",
|
|
29
30
|
"@types/react": "^19.0.0",
|
|
30
31
|
"@types/react-dom": "^19.0.0",
|
|
31
32
|
"autoprefixer": "^10.4.0",
|
|
32
|
-
"better-sqlite3": "^11.0.0",
|
|
33
33
|
"drizzle-kit": "^0.30.0",
|
|
34
34
|
"postcss": "^8.4.0",
|
|
35
35
|
"tailwindcss": "^3.4.0",
|
|
36
|
-
"typescript": "^5.7.0"
|
|
37
|
-
"wrangler": "^4.0.0"
|
|
36
|
+
"typescript": "^5.7.0"
|
|
38
37
|
}
|
|
39
38
|
}
|
|
@@ -2,8 +2,6 @@ import { signIn } from '@/lib/auth';
|
|
|
2
2
|
import { Button } from '@primstack/ui/button';
|
|
3
3
|
import { Heading, Text } from '@primstack/ui/text';
|
|
4
4
|
|
|
5
|
-
export const runtime = 'edge';
|
|
6
|
-
|
|
7
5
|
export default function LoginPage() {
|
|
8
6
|
return (
|
|
9
7
|
<div className="min-h-screen flex items-center justify-center bg-background px-4">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Metadata } from 'next';
|
|
2
2
|
import { ThemeProvider } from '@primstack/theme';
|
|
3
|
+
import { personalities } from '@primstack/tokens';
|
|
3
4
|
import './globals.css';
|
|
4
5
|
|
|
5
6
|
export const metadata: Metadata = {
|
|
@@ -7,6 +8,8 @@ export const metadata: Metadata = {
|
|
|
7
8
|
description: 'Built with Primstack',
|
|
8
9
|
};
|
|
9
10
|
|
|
11
|
+
const personality = personalities['{{PERSONALITY}}'] || personalities['evergreen'];
|
|
12
|
+
|
|
10
13
|
export default function RootLayout({
|
|
11
14
|
children,
|
|
12
15
|
}: {
|
|
@@ -15,7 +18,7 @@ export default function RootLayout({
|
|
|
15
18
|
return (
|
|
16
19
|
<html lang="en" suppressHydrationWarning>
|
|
17
20
|
<body className="min-h-screen antialiased">
|
|
18
|
-
<ThemeProvider personality=
|
|
21
|
+
<ThemeProvider personality={personality}>
|
|
19
22
|
{children}
|
|
20
23
|
</ThemeProvider>
|
|
21
24
|
</body>
|
|
@@ -2,6 +2,7 @@ import { sqliteTable, text, integer, uniqueIndex } from 'drizzle-orm/sqlite-core
|
|
|
2
2
|
import { sql } from 'drizzle-orm';
|
|
3
3
|
|
|
4
4
|
// ── Auth.js tables ─────────────────────────────────────────────────
|
|
5
|
+
// Column property names must match @auth/drizzle-adapter expectations (snake_case).
|
|
5
6
|
|
|
6
7
|
export const authUsers = sqliteTable('auth_users', {
|
|
7
8
|
id: text('id').primaryKey(),
|
|
@@ -17,20 +18,19 @@ export const accounts = sqliteTable('accounts', {
|
|
|
17
18
|
type: text('type').notNull(),
|
|
18
19
|
provider: text('provider').notNull(),
|
|
19
20
|
providerAccountId: text('provider_account_id').notNull(),
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
refresh_token: text('refresh_token'),
|
|
22
|
+
access_token: text('access_token'),
|
|
23
|
+
expires_at: integer('expires_at'),
|
|
24
|
+
token_type: text('token_type'),
|
|
24
25
|
scope: text('scope'),
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
id_token: text('id_token'),
|
|
27
|
+
session_state: text('session_state'),
|
|
27
28
|
}, (table) => [
|
|
28
29
|
uniqueIndex('accounts_provider_idx').on(table.provider, table.providerAccountId),
|
|
29
30
|
]);
|
|
30
31
|
|
|
31
32
|
export const sessions = sqliteTable('sessions', {
|
|
32
|
-
|
|
33
|
-
sessionToken: text('session_token').notNull().unique(),
|
|
33
|
+
sessionToken: text('session_token').primaryKey(),
|
|
34
34
|
userId: text('user_id').notNull().references(() => authUsers.id, { onDelete: 'cascade' }),
|
|
35
35
|
expires: integer('expires', { mode: 'timestamp' }).notNull(),
|
|
36
36
|
});
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
};
|
|
1
|
+
// Environment variable declarations
|
|
2
|
+
declare namespace NodeJS {
|
|
3
|
+
interface ProcessEnv {
|
|
4
|
+
AUTH_SECRET: string;
|
|
5
|
+
GITHUB_CLIENT_ID: string;
|
|
6
|
+
GITHUB_CLIENT_SECRET: string;
|
|
7
|
+
DATABASE_URL?: string;
|
|
8
|
+
}
|
|
10
9
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import GitHub from 'next-auth/providers/github';
|
|
2
|
+
import type { NextAuthConfig } from 'next-auth';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Auth.js config — edge-compatible (no Node.js dependencies).
|
|
6
|
+
* Used by middleware for JWT session checks.
|
|
7
|
+
*/
|
|
8
|
+
export default {
|
|
9
|
+
providers: [
|
|
10
|
+
GitHub({
|
|
11
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
12
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
13
|
+
}),
|
|
14
|
+
],
|
|
15
|
+
session: {
|
|
16
|
+
strategy: 'jwt',
|
|
17
|
+
},
|
|
18
|
+
pages: {
|
|
19
|
+
signIn: '/auth/login',
|
|
20
|
+
},
|
|
21
|
+
} satisfies NextAuthConfig;
|
|
@@ -1,35 +1,18 @@
|
|
|
1
1
|
import NextAuth from 'next-auth';
|
|
2
|
-
import GitHub from 'next-auth/providers/github';
|
|
3
2
|
import { DrizzleAdapter } from '@auth/drizzle-adapter';
|
|
4
3
|
import { getDatabase } from './db';
|
|
5
4
|
import { authUsers, accounts, sessions } from '../db/schema';
|
|
5
|
+
import authConfig from './auth.config';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Auth.js
|
|
9
|
-
*
|
|
10
|
-
* Uses the Drizzle adapter to persist users, accounts, and sessions in D1.
|
|
11
|
-
* JWT strategy is used so middleware can validate sessions at the edge
|
|
12
|
-
* without a DB round-trip on every request.
|
|
13
|
-
*
|
|
14
|
-
* NextAuth is called with a callback so the adapter is created per-request
|
|
15
|
-
* (getDatabase() requires an active request context for getRequestContext()).
|
|
8
|
+
* Full Auth.js configuration with Drizzle adapter.
|
|
9
|
+
* Only used in Node.js server context (not middleware/edge).
|
|
16
10
|
*/
|
|
17
|
-
export const { handlers, signIn, signOut, auth } = NextAuth(
|
|
11
|
+
export const { handlers, signIn, signOut, auth } = NextAuth({
|
|
12
|
+
...authConfig,
|
|
18
13
|
adapter: DrizzleAdapter(getDatabase(), {
|
|
19
14
|
usersTable: authUsers,
|
|
20
15
|
accountsTable: accounts,
|
|
21
16
|
sessionsTable: sessions,
|
|
22
17
|
}),
|
|
23
|
-
|
|
24
|
-
GitHub({
|
|
25
|
-
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
26
|
-
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
27
|
-
}),
|
|
28
|
-
],
|
|
29
|
-
session: {
|
|
30
|
-
strategy: 'jwt',
|
|
31
|
-
},
|
|
32
|
-
pages: {
|
|
33
|
-
signIn: '/auth/login',
|
|
34
|
-
},
|
|
35
|
-
}));
|
|
18
|
+
});
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { drizzle, type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
3
|
import * as schema from '../db/schema';
|
|
4
|
+
import path from 'path';
|
|
4
5
|
|
|
5
|
-
export type AppDb =
|
|
6
|
+
export type AppDb = BetterSQLite3Database<typeof schema>;
|
|
7
|
+
|
|
8
|
+
let _db: AppDb | null = null;
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
|
-
* Get the database
|
|
11
|
+
* Get the database connection.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
* In
|
|
12
|
-
* (configured in src/instrumentation.ts).
|
|
13
|
+
* Uses better-sqlite3 with a local SQLite file.
|
|
14
|
+
* In production (Primstack hosting), D1 bindings are injected at deploy time.
|
|
13
15
|
*/
|
|
14
|
-
export function getDatabase() {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
export function getDatabase(): AppDb {
|
|
17
|
+
if (!_db) {
|
|
18
|
+
const dbPath = process.env.DATABASE_URL || path.join(process.cwd(), 'local.db');
|
|
19
|
+
const sqlite = new Database(dbPath);
|
|
20
|
+
sqlite.pragma('journal_mode = WAL');
|
|
21
|
+
_db = drizzle(sqlite, { schema });
|
|
22
|
+
}
|
|
23
|
+
return _db;
|
|
17
24
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import NextAuth from 'next-auth';
|
|
2
|
+
import authConfig from '@/lib/auth.config';
|
|
3
|
+
|
|
4
|
+
const { auth } = NextAuth(authConfig);
|
|
5
|
+
|
|
6
|
+
export default auth;
|
|
2
7
|
|
|
3
8
|
export const config = {
|
|
4
|
-
// Protect all routes under /dashboard, /api (except health and auth)
|
|
5
9
|
matcher: ['/dashboard/:path*'],
|
|
6
10
|
};
|