@techstream/quark-create-app 1.9.0 → 1.10.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 +2 -2
- package/package.json +1 -1
- package/src/index.js +376 -143
- package/templates/base-project/.cursor/rules/quark.mdc +172 -0
- package/templates/base-project/.github/copilot-instructions.md +55 -0
- package/templates/base-project/CLAUDE.md +273 -0
- package/templates/base-project/README.md +72 -30
- package/templates/base-project/apps/web/next.config.js +5 -1
- package/templates/base-project/apps/web/package.json +3 -3
- package/templates/base-project/apps/web/public/quark.svg +46 -0
- package/templates/base-project/apps/web/railway.json +2 -2
- package/templates/base-project/apps/web/src/app/_components/HealthIndicator.js +85 -0
- package/templates/base-project/apps/web/src/app/_components/HomeThemeToggle.js +63 -0
- package/templates/base-project/apps/web/src/app/_components/QuarkAnimation.js +168 -0
- package/templates/base-project/apps/web/src/app/api/health/route.js +28 -17
- package/templates/base-project/apps/web/src/app/favicon.ico +0 -0
- package/templates/base-project/apps/web/src/app/global-error.js +53 -0
- package/templates/base-project/apps/web/src/app/globals.css +121 -15
- package/templates/base-project/apps/web/src/app/icon.svg +46 -0
- package/templates/base-project/apps/web/src/app/layout.js +1 -0
- package/templates/base-project/apps/web/src/app/not-found.js +35 -0
- package/templates/base-project/apps/web/src/app/page.js +38 -5
- package/templates/base-project/apps/web/src/lib/theme.js +23 -0
- package/templates/base-project/apps/web/src/proxy.js +10 -2
- package/templates/base-project/package.json +2 -0
- package/templates/base-project/packages/db/src/client.js +6 -1
- package/templates/base-project/packages/db/src/index.js +1 -0
- package/templates/base-project/packages/db/src/ping.js +66 -0
- package/templates/base-project/scripts/doctor.js +261 -0
- package/templates/base-project/turbo.json +2 -1
- package/templates/config/package.json +1 -0
- package/templates/jobs/package.json +2 -1
- package/templates/ui/README.md +67 -0
- package/templates/ui/package.json +1 -0
- package/templates/ui/src/badge.js +32 -0
- package/templates/ui/src/badge.test.js +42 -0
- package/templates/ui/src/button.js +64 -15
- package/templates/ui/src/button.test.js +34 -5
- package/templates/ui/src/card.js +58 -0
- package/templates/ui/src/card.test.js +59 -0
- package/templates/ui/src/checkbox.js +35 -0
- package/templates/ui/src/checkbox.test.js +35 -0
- package/templates/ui/src/dialog.js +139 -0
- package/templates/ui/src/dialog.test.js +15 -0
- package/templates/ui/src/index.js +16 -0
- package/templates/ui/src/input.js +15 -0
- package/templates/ui/src/input.test.js +27 -0
- package/templates/ui/src/label.js +14 -0
- package/templates/ui/src/label.test.js +22 -0
- package/templates/ui/src/select.js +42 -0
- package/templates/ui/src/select.test.js +27 -0
- package/templates/ui/src/skeleton.js +14 -0
- package/templates/ui/src/skeleton.test.js +22 -0
- package/templates/ui/src/table.js +75 -0
- package/templates/ui/src/table.test.js +69 -0
- package/templates/ui/src/textarea.js +15 -0
- package/templates/ui/src/textarea.test.js +27 -0
- package/templates/ui/src/theme-constants.js +24 -0
- package/templates/ui/src/theme.js +132 -0
- package/templates/ui/src/toast.js +229 -0
- package/templates/ui/src/toast.test.js +23 -0
- package/templates/{base-project/apps/worker → worker}/package.json +2 -2
- package/templates/{base-project/apps/worker → worker}/src/index.js +38 -23
- package/templates/{base-project/apps/worker → worker}/src/index.test.js +19 -20
- package/templates/base-project/apps/web/public/file.svg +0 -1
- package/templates/base-project/apps/web/public/globe.svg +0 -1
- package/templates/base-project/apps/web/public/next.svg +0 -1
- package/templates/base-project/apps/web/public/vercel.svg +0 -1
- package/templates/base-project/apps/web/public/window.svg +0 -1
- /package/templates/{base-project/apps/worker → worker}/README.md +0 -0
- /package/templates/{base-project/apps/worker → worker}/railway.json +0 -0
- /package/templates/{base-project/apps/worker → worker}/src/handlers/email.js +0 -0
- /package/templates/{base-project/apps/worker → worker}/src/handlers/files.js +0 -0
- /package/templates/{base-project/apps/worker → worker}/src/handlers/index.js +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Quark project conventions for __QUARK_PROJECT_NAME__. Covers all layers: UI, backend, database, auth, jobs, testing, and deployment.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Quark Project: __QUARK_PROJECT_NAME__
|
|
7
|
+
|
|
8
|
+
Package scope: `@__QUARK_SCOPE__` — use this for ALL workspace packages.
|
|
9
|
+
Scaffolded: __QUARK_SCAFFOLD_DATE__
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Non-Negotiable Rules
|
|
14
|
+
|
|
15
|
+
1. **ESM only.** Always `import`/`export`. Never `require()` or `module.exports`.
|
|
16
|
+
2. **No TypeScript.** Plain `.js` and `.jsx` files only — no `.ts`, `.tsx`, or type annotations.
|
|
17
|
+
3. **Validate everything at boundaries.** Server Actions and API routes must use Zod. No raw `req.body` access without parsing.
|
|
18
|
+
4. **Use AppError — not Error.** Throw `AppError` / `ValidationError` / `NotFoundError` from `@techstream/quark-core/errors`. Never `throw new Error()` in application code.
|
|
19
|
+
5. **Log — never console.** Use `createLogger(name)` from `@techstream/quark-core`. Zero `console.log` / `console.error` in non-test code.
|
|
20
|
+
6. **DB models always have timestamps.** Every Prisma model: `createdAt DateTime @default(now())` + `updatedAt DateTime @updatedAt`.
|
|
21
|
+
7. **Workspace import scope.** Use `@__QUARK_SCOPE__/*` for local packages. Never `@techstream/quark-db`, `@techstream/quark-config`, etc.
|
|
22
|
+
8. **Co-located tests.** `feature.test.js` lives next to `feature.js`. Tests use `node --test` — no Jest, no Vitest.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Import Reference
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
// ✅ Workspace packages — always @__QUARK_SCOPE__/*:
|
|
30
|
+
import { prisma, user, post } from "@__QUARK_SCOPE__/db";
|
|
31
|
+
import { loadConfig } from "@__QUARK_SCOPE__/config";
|
|
32
|
+
import { Button, Card, Input } from "@__QUARK_SCOPE__/ui";
|
|
33
|
+
import { JOB_TYPES } from "@__QUARK_SCOPE__/jobs";
|
|
34
|
+
|
|
35
|
+
// ✅ Published runtime — @techstream/quark-core:
|
|
36
|
+
import { AppError, ValidationError, NotFoundError } from "@techstream/quark-core/errors";
|
|
37
|
+
import { createLogger, getCurrentSession } from "@techstream/quark-core";
|
|
38
|
+
import { createQueue, addJob } from "@techstream/quark-core";
|
|
39
|
+
import { metrics, cache, storage } from "@techstream/quark-core";
|
|
40
|
+
|
|
41
|
+
// ❌ Never:
|
|
42
|
+
import { Button } from "@/components/ui/button"; // wrong path convention
|
|
43
|
+
import { prisma } from "@techstream/quark-db"; // wrong scope for workspace pkg
|
|
44
|
+
import something from "@techstream/quark-config"; // wrong scope for workspace pkg
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## UI & Design System
|
|
50
|
+
|
|
51
|
+
- Styling: **Tailwind CSS utility classes only**. No inline styles, no CSS modules.
|
|
52
|
+
- Components from `@__QUARK_SCOPE__/ui`. All accept `className` for overrides.
|
|
53
|
+
- Available: `Button`, `Input`, `Label`, `Textarea`, `Select`, `Checkbox`, `Badge`, `Card`/`CardHeader`/`CardTitle`/`CardContent`/`CardFooter`, `Table`/`TableHeader`/`TableBody`/`TableRow`/`TableHead`/`TableCell`, `Skeleton`, `Dialog` *(client)*, `Toast`/`useToast` *(client)*.
|
|
54
|
+
- Loading states → `<Skeleton>` + `<Suspense>` boundaries.
|
|
55
|
+
- User feedback → `useToast()` (mark component `"use client"`).
|
|
56
|
+
- Modals → `<Dialog>` (mark parent `"use client"`).
|
|
57
|
+
- Default to **Server Components**. Only add `"use client"` when necessary.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Server Action Pattern
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
"use server";
|
|
65
|
+
import { z } from "zod";
|
|
66
|
+
import { ValidationError, AppError } from "@techstream/quark-core/errors";
|
|
67
|
+
import { getCurrentSession, createLogger } from "@techstream/quark-core";
|
|
68
|
+
import { prisma } from "@__QUARK_SCOPE__/db";
|
|
69
|
+
|
|
70
|
+
const log = createLogger("action:example");
|
|
71
|
+
const schema = z.object({ title: z.string().min(1).max(255) });
|
|
72
|
+
|
|
73
|
+
export async function createExample(formData) {
|
|
74
|
+
const session = await getCurrentSession();
|
|
75
|
+
if (!session) throw new AppError("Unauthorized", 401);
|
|
76
|
+
|
|
77
|
+
const parsed = schema.safeParse(Object.fromEntries(formData));
|
|
78
|
+
if (!parsed.success) throw new ValidationError(parsed.error.flatten());
|
|
79
|
+
|
|
80
|
+
const result = await prisma.example.create({ data: parsed.data });
|
|
81
|
+
log.info("created", { id: result.id });
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## API Route Pattern
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
import { NextResponse } from "next/server";
|
|
92
|
+
import { AppError } from "@techstream/quark-core/errors";
|
|
93
|
+
import { createLogger } from "@techstream/quark-core";
|
|
94
|
+
|
|
95
|
+
const log = createLogger("api:example");
|
|
96
|
+
|
|
97
|
+
export async function GET(request) {
|
|
98
|
+
try {
|
|
99
|
+
return NextResponse.json({ data });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
log.error("request failed", { error });
|
|
102
|
+
if (error instanceof AppError)
|
|
103
|
+
return NextResponse.json({ error: error.message }, { status: error.statusCode });
|
|
104
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Database
|
|
112
|
+
|
|
113
|
+
- Schema: `packages/db/prisma/schema.prisma`
|
|
114
|
+
- Query helpers: `packages/db/src/queries.js` — add functions here, don't call `prisma.*` directly in components or actions.
|
|
115
|
+
- Always run `pnpm db:generate` after schema changes, then `pnpm db:migrate`.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Auth
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
import { getCurrentSession } from "@techstream/quark-core";
|
|
123
|
+
|
|
124
|
+
const session = await getCurrentSession(); // null if unauthenticated
|
|
125
|
+
if (!session) redirect("/login"); // Server Component
|
|
126
|
+
if (!session) throw new AppError("Unauthorized", 401); // Server Action / API
|
|
127
|
+
if (session.user.role !== "ADMIN") throw new AppError("Forbidden", 403);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Background Jobs
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
// Dispatch (from Server Action or API route):
|
|
136
|
+
import { createQueue, addJob } from "@techstream/quark-core";
|
|
137
|
+
import { JOB_TYPES } from "@__QUARK_SCOPE__/jobs";
|
|
138
|
+
const queue = createQueue("default");
|
|
139
|
+
await addJob(queue, JOB_TYPES.MY_JOB, { userId });
|
|
140
|
+
|
|
141
|
+
// Handle (apps/worker/src/handlers/):
|
|
142
|
+
import { createLogger } from "@techstream/quark-core";
|
|
143
|
+
const log = createLogger("job:my-job");
|
|
144
|
+
export async function handleMyJob({ data }) { log.info("processing", data); }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Testing
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
import { test } from "node:test";
|
|
153
|
+
import assert from "node:assert";
|
|
154
|
+
|
|
155
|
+
test("feature", async (t) => {
|
|
156
|
+
await t.test("does X", async () => {
|
|
157
|
+
assert.strictEqual(actual, expected);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Environment
|
|
165
|
+
|
|
166
|
+
New env vars must be registered in `packages/config/src/validate-env.js` before use. Load config via `loadConfig()` from `@__QUARK_SCOPE__/config`.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Deployment
|
|
171
|
+
|
|
172
|
+
Railway: two services (`web` + `worker`). Migrations auto-run on deploy via `releaseCommand` in `apps/web/railway.json`. Env vars managed in Railway dashboard — never committed.
|
|
@@ -5,3 +5,58 @@
|
|
|
5
5
|
<file>.github/skills/project-context/SKILL.md</file>
|
|
6
6
|
</skill>
|
|
7
7
|
</skills>
|
|
8
|
+
|
|
9
|
+
# __QUARK_PROJECT_NAME__ — Project Conventions
|
|
10
|
+
|
|
11
|
+
> Scaffolded with Quark on __QUARK_SCAFFOLD_DATE__. Package scope: `@__QUARK_SCOPE__`
|
|
12
|
+
> Also see `CLAUDE.md` at the project root for the full context reference.
|
|
13
|
+
|
|
14
|
+
## Non-Negotiable Rules
|
|
15
|
+
|
|
16
|
+
- **ESM only** — `import`/`export` everywhere. Never `require()`.
|
|
17
|
+
- **No TypeScript** — `.js` and `.jsx` only.
|
|
18
|
+
- **Validate at every boundary** — Zod schemas on all Server Actions and API routes.
|
|
19
|
+
- **Errors** — `AppError` / `ValidationError` from `@techstream/quark-core/errors`. Never `throw new Error()`.
|
|
20
|
+
- **Logging** — `createLogger(name)` from `@techstream/quark-core`. No `console.log` in production code.
|
|
21
|
+
- **DB models** — Always add `createdAt` and `updatedAt` to every Prisma model.
|
|
22
|
+
- **Workspace imports** — Use `@__QUARK_SCOPE__/*` for local packages (`db`, `config`, `ui`, `jobs`). Never `@techstream/quark-db` etc.
|
|
23
|
+
- **Tests** — Co-located `*.test.js`, run with `node --test`.
|
|
24
|
+
|
|
25
|
+
## Import Patterns
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// Workspace packages:
|
|
29
|
+
import { prisma, user } from "@__QUARK_SCOPE__/db";
|
|
30
|
+
import { loadConfig } from "@__QUARK_SCOPE__/config";
|
|
31
|
+
import { Button, Card, Input } from "@__QUARK_SCOPE__/ui";
|
|
32
|
+
import { JOB_TYPES } from "@__QUARK_SCOPE__/jobs";
|
|
33
|
+
|
|
34
|
+
// Published runtime:
|
|
35
|
+
import { AppError, ValidationError } from "@techstream/quark-core/errors";
|
|
36
|
+
import { createLogger, getCurrentSession, createQueue, addJob } from "@techstream/quark-core";
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## UI & Design System
|
|
40
|
+
|
|
41
|
+
Components from `@__QUARK_SCOPE__/ui` — Tailwind-only, Server Component safe:
|
|
42
|
+
`Button`, `Input`, `Label`, `Textarea`, `Select`, `Checkbox`, `Badge`,
|
|
43
|
+
`Card`/`CardHeader`/`CardTitle`/`CardContent`/`CardFooter`,
|
|
44
|
+
`Table`/`TableHeader`/`TableBody`/`TableRow`/`TableHead`/`TableCell`,
|
|
45
|
+
`Skeleton`, `Dialog` *(client)*, `Toast`/`useToast` *(client)*.
|
|
46
|
+
|
|
47
|
+
All accept `className`. Never import from `@/components/ui/*`.
|
|
48
|
+
|
|
49
|
+
## Standard Patterns
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
// Server Action:
|
|
53
|
+
"use server";
|
|
54
|
+
const parsed = schema.safeParse(Object.fromEntries(formData));
|
|
55
|
+
if (!parsed.success) throw new ValidationError(parsed.error.flatten());
|
|
56
|
+
const session = await getCurrentSession();
|
|
57
|
+
if (!session) throw new AppError("Unauthorized", 401);
|
|
58
|
+
|
|
59
|
+
// New env var: register in packages/config/src/validate-env.js first.
|
|
60
|
+
// New DB model: schema.prisma → pnpm db:generate → pnpm db:migrate.
|
|
61
|
+
// New job: define type in packages/jobs/src/index.js, handler in apps/worker/src/handlers/.
|
|
62
|
+
```
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# __QUARK_PROJECT_NAME__ — AI Context
|
|
2
|
+
|
|
3
|
+
> Scaffolded with [Quark](https://github.com/Bobnoddle/quark) on __QUARK_SCAFFOLD_DATE__.
|
|
4
|
+
> **Keep this file updated** as your project grows — it's what Claude Code, Cursor, and other AI tools read first.
|
|
5
|
+
|
|
6
|
+
## Quick Start
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
docker compose up -d # Start PostgreSQL, Redis, Mailpit
|
|
10
|
+
pnpm install # Install dependencies
|
|
11
|
+
pnpm db:generate # Generate Prisma client
|
|
12
|
+
pnpm db:migrate # Apply database migrations
|
|
13
|
+
pnpm db:seed # Seed development data
|
|
14
|
+
pnpm dev # Start web + worker
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Scaffolding Another Project (non-interactive)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# With default features (ui + jobs):
|
|
21
|
+
npx @techstream/quark-create-app my-app --no-prompts
|
|
22
|
+
|
|
23
|
+
# With specific features:
|
|
24
|
+
npx @techstream/quark-create-app my-app --no-prompts --features ui,jobs
|
|
25
|
+
npx @techstream/quark-create-app my-app --no-prompts --features ui,jobs,admin
|
|
26
|
+
npx @techstream/quark-create-app my-app --no-prompts --features "" # minimal (db + config only)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Project Structure
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
__QUARK_PROJECT_NAME__/
|
|
33
|
+
├── apps/
|
|
34
|
+
│ ├── web/ # Next.js 16 (App Router, Server Actions)
|
|
35
|
+
│ └── worker/ # BullMQ background worker
|
|
36
|
+
├── packages/
|
|
37
|
+
│ ├── db/ # Prisma schema, client, query functions
|
|
38
|
+
│ ├── config/ # Environment validation & shared config
|
|
39
|
+
__QUARK_OPTIONAL_PACKAGES__├── docker-compose.yml
|
|
40
|
+
├── .env # Local environment secrets (never commit)
|
|
41
|
+
└── .env.example # Template — copy to .env to get started
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Tech Stack
|
|
45
|
+
|
|
46
|
+
| Layer | Technology |
|
|
47
|
+
|---|---|
|
|
48
|
+
| Framework | Next.js 16 (App Router) |
|
|
49
|
+
| Language | JavaScript — ESM only, no TypeScript |
|
|
50
|
+
| Database | PostgreSQL 16 + Prisma 7 |
|
|
51
|
+
| Queue | BullMQ + Redis 7 |
|
|
52
|
+
| Auth | NextAuth v5 |
|
|
53
|
+
| Validation | Zod 4 |
|
|
54
|
+
| UI | Tailwind CSS + `@__QUARK_SCOPE__/ui` components |
|
|
55
|
+
| Email | Nodemailer (Mailpit local / Resend or Zeptomail prod) |
|
|
56
|
+
| Storage | Local filesystem or S3/R2 (pluggable via `STORAGE_PROVIDER`) |
|
|
57
|
+
| Logging | Structured logger via `createLogger()` from `@techstream/quark-core` |
|
|
58
|
+
| Metrics | Prometheus via `metrics` singleton from `@techstream/quark-core` |
|
|
59
|
+
| Testing | Node.js built-in `node --test` |
|
|
60
|
+
| Linting | Biome |
|
|
61
|
+
| Monorepo | Turborepo + pnpm workspaces |
|
|
62
|
+
| Deployment | Railway (web service + worker service) |
|
|
63
|
+
|
|
64
|
+
## Package Imports
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// Published (installed from npm) — use @techstream/ prefix:
|
|
68
|
+
import { AppError, ValidationError, NotFoundError, UnauthorizedError } from "@techstream/quark-core/errors";
|
|
69
|
+
import { createLogger } from "@techstream/quark-core";
|
|
70
|
+
import { getCurrentSession } from "@techstream/quark-core";
|
|
71
|
+
import { createQueue, addJob } from "@techstream/quark-core";
|
|
72
|
+
import { metrics } from "@techstream/quark-core";
|
|
73
|
+
import { cache } from "@techstream/quark-core";
|
|
74
|
+
import { rateLimit } from "@techstream/quark-core";
|
|
75
|
+
import { storage } from "@techstream/quark-core";
|
|
76
|
+
|
|
77
|
+
// Workspace packages — ALWAYS use @__QUARK_SCOPE__/* (never @techstream/quark-*):
|
|
78
|
+
import { prisma, user } from "@__QUARK_SCOPE__/db"; // add more as you create query helpers
|
|
79
|
+
import { loadConfig } from "@__QUARK_SCOPE__/config";
|
|
80
|
+
import { Button, Card, Input } from "@__QUARK_SCOPE__/ui"; // if ui package selected
|
|
81
|
+
import { JOB_TYPES } from "@__QUARK_SCOPE__/jobs"; // if jobs package selected
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## UI & Design System
|
|
85
|
+
|
|
86
|
+
All UI components come from `@__QUARK_SCOPE__/ui`. They are Tailwind-only, dependency-free, and Server Component-safe.
|
|
87
|
+
|
|
88
|
+
**Available components:**
|
|
89
|
+
- `Button`, `Input`, `Label`, `Textarea`, `Select`, `Checkbox` — form primitives
|
|
90
|
+
- `Badge` — status labels
|
|
91
|
+
- `Card` / `CardHeader` / `CardTitle` / `CardContent` / `CardFooter` — content containers
|
|
92
|
+
- `Table` / `TableHeader` / `TableBody` / `TableRow` / `TableHead` / `TableCell` — data tables
|
|
93
|
+
- `Skeleton` — loading placeholders
|
|
94
|
+
- `Dialog` *(client)* — modal dialogs
|
|
95
|
+
- `Toast` / `useToast` *(client)* — notifications
|
|
96
|
+
|
|
97
|
+
**Rules:**
|
|
98
|
+
- All styling via Tailwind CSS utility classes. No inline styles, no CSS modules.
|
|
99
|
+
- Import from `@__QUARK_SCOPE__/ui` — never from `@/components/ui/*` or direct paths.
|
|
100
|
+
- Every component accepts `className` for Tailwind overrides.
|
|
101
|
+
- Loading states → `<Skeleton>` / `<Suspense>`.
|
|
102
|
+
- User feedback → `useToast()` hook (client component).
|
|
103
|
+
- Modals → `<Dialog>` (mark parent as `"use client"`).
|
|
104
|
+
- Server Components are the default — only add `"use client"` when the component uses hooks, browser APIs, or the marked client components above.
|
|
105
|
+
|
|
106
|
+
## Server Actions (preferred for all mutations)
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
"use server";
|
|
110
|
+
import { z } from "zod";
|
|
111
|
+
import { ValidationError, AppError } from "@techstream/quark-core/errors";
|
|
112
|
+
import { getCurrentSession } from "@techstream/quark-core";
|
|
113
|
+
import { createLogger } from "@techstream/quark-core";
|
|
114
|
+
import { prisma } from "@__QUARK_SCOPE__/db";
|
|
115
|
+
|
|
116
|
+
const log = createLogger("action:example");
|
|
117
|
+
|
|
118
|
+
const schema = z.object({
|
|
119
|
+
title: z.string().min(1).max(255),
|
|
120
|
+
body: z.string().optional(),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
export async function createExample(formData) {
|
|
124
|
+
const session = await getCurrentSession();
|
|
125
|
+
if (!session) throw new AppError("Unauthorized", 401);
|
|
126
|
+
|
|
127
|
+
const parsed = schema.safeParse(Object.fromEntries(formData));
|
|
128
|
+
if (!parsed.success) throw new ValidationError(parsed.error.flatten());
|
|
129
|
+
|
|
130
|
+
const result = await prisma.example.create({ data: parsed.data });
|
|
131
|
+
log.info("created", { id: result.id });
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## API Routes
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
// apps/web/src/app/api/example/route.js
|
|
140
|
+
import { NextResponse } from "next/server";
|
|
141
|
+
import { AppError } from "@techstream/quark-core/errors";
|
|
142
|
+
import { createLogger } from "@techstream/quark-core";
|
|
143
|
+
|
|
144
|
+
const log = createLogger("api:example");
|
|
145
|
+
|
|
146
|
+
export async function GET(request) {
|
|
147
|
+
try {
|
|
148
|
+
// ... logic
|
|
149
|
+
return NextResponse.json({ data });
|
|
150
|
+
} catch (error) {
|
|
151
|
+
log.error("request failed", { error });
|
|
152
|
+
if (error instanceof AppError) {
|
|
153
|
+
return NextResponse.json({ error: error.message }, { status: error.statusCode });
|
|
154
|
+
}
|
|
155
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Database
|
|
161
|
+
|
|
162
|
+
- Schema: `packages/db/prisma/schema.prisma`
|
|
163
|
+
- Query helpers: `packages/db/src/queries.js`
|
|
164
|
+
- Every model **must** include `createdAt DateTime @default(now())` and `updatedAt DateTime @updatedAt`.
|
|
165
|
+
- After any schema change: `pnpm db:generate` then `pnpm db:migrate`.
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
// Add query functions in packages/db/src/queries.js:
|
|
169
|
+
export const example = {
|
|
170
|
+
findMany: (args) => prisma.example.findMany(args),
|
|
171
|
+
findById: (id) => prisma.example.findUnique({ where: { id } }),
|
|
172
|
+
create: (data) => prisma.example.create({ data }),
|
|
173
|
+
update: (id, data) => prisma.example.update({ where: { id }, data }),
|
|
174
|
+
delete: (id) => prisma.example.delete({ where: { id } }),
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Do not call `prisma.*` directly inside pages or Server Actions — use the query helper functions.
|
|
179
|
+
|
|
180
|
+
## Auth & Authorization
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
import { getCurrentSession } from "@techstream/quark-core";
|
|
184
|
+
|
|
185
|
+
// Server Component — check session:
|
|
186
|
+
const session = await getCurrentSession();
|
|
187
|
+
if (!session) redirect("/login");
|
|
188
|
+
|
|
189
|
+
// Server Action — guard:
|
|
190
|
+
const session = await getCurrentSession();
|
|
191
|
+
if (!session) throw new AppError("Unauthorized", 401);
|
|
192
|
+
|
|
193
|
+
// Role check (ADMIN | VIEWER defined in Prisma schema):
|
|
194
|
+
if (session.user.role !== "ADMIN") throw new AppError("Forbidden", 403);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Background Jobs (if jobs package selected)
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
// 1. Dispatch from web (Server Action or API route):
|
|
201
|
+
import { createQueue, addJob } from "@techstream/quark-core";
|
|
202
|
+
import { JOB_TYPES } from "@__QUARK_SCOPE__/jobs";
|
|
203
|
+
const queue = createQueue("default");
|
|
204
|
+
await addJob(queue, JOB_TYPES.SEND_EMAIL, { userId: session.user.id, template: "welcome" });
|
|
205
|
+
|
|
206
|
+
// 2. Handle in apps/worker/src/handlers/<job-name>.js:
|
|
207
|
+
import { createLogger } from "@techstream/quark-core";
|
|
208
|
+
const log = createLogger("job:send-email");
|
|
209
|
+
export async function handleSendEmail({ data }) {
|
|
210
|
+
log.info("processing", data);
|
|
211
|
+
// ... job logic
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Environment Variables
|
|
216
|
+
|
|
217
|
+
Validated at startup in `packages/config/src/validate-env.js`. **Always add new env vars there before using them.**
|
|
218
|
+
|
|
219
|
+
Config loaded via `loadConfig()` from `@__QUARK_SCOPE__/config`.
|
|
220
|
+
|
|
221
|
+
Key variables:
|
|
222
|
+
| Variable | Purpose |
|
|
223
|
+
|---|---|
|
|
224
|
+
| `DATABASE_URL` / `POSTGRES_*` | PostgreSQL connection |
|
|
225
|
+
| `REDIS_URL` / `REDIS_HOST` + `REDIS_PORT` | Redis connection |
|
|
226
|
+
| `NEXTAUTH_SECRET` | Auth JWT encryption (`openssl rand -base64 32`) |
|
|
227
|
+
| `APP_URL` | Production domain (auto-derived from `PORT` in dev) |
|
|
228
|
+
| `STORAGE_PROVIDER` | `local` (default) or `s3` |
|
|
229
|
+
| `APP_NAME` / `APP_DESCRIPTION` | Used in metadata, emails, page titles |
|
|
230
|
+
|
|
231
|
+
## Testing
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
import { test } from "node:test";
|
|
235
|
+
import assert from "node:assert";
|
|
236
|
+
|
|
237
|
+
test("feature name", async (t) => {
|
|
238
|
+
await t.test("does the thing", async () => {
|
|
239
|
+
// arrange → act → assert
|
|
240
|
+
assert.strictEqual(actual, expected);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- Tests are co-located: `feature.test.js` lives next to `feature.js`.
|
|
246
|
+
- Run all: `pnpm test` | Run one package: `pnpm --filter @__QUARK_SCOPE__/web test`.
|
|
247
|
+
- Test utilities and model factories: `@techstream/quark-core/testing`.
|
|
248
|
+
|
|
249
|
+
## Deployment
|
|
250
|
+
|
|
251
|
+
Railway with two services: **web** (`apps/web`) and **worker** (`apps/worker`).
|
|
252
|
+
|
|
253
|
+
- Migrations run automatically on every deploy via `releaseCommand` in `apps/web/railway.json`.
|
|
254
|
+
- Environment variables live in Railway dashboard — never in committed files.
|
|
255
|
+
- Staging: auto-deploys on push to `main`. Production: deploy from a version tag.
|
|
256
|
+
- Generate a production seed: `SEED_PROFILE=minimal pnpm db:seed`.
|
|
257
|
+
|
|
258
|
+
## Key Files
|
|
259
|
+
|
|
260
|
+
| File | Purpose |
|
|
261
|
+
|---|---|
|
|
262
|
+
| `packages/db/prisma/schema.prisma` | Database schema — edit this to add models |
|
|
263
|
+
| `packages/db/src/queries.js` | Database query functions — add helpers here |
|
|
264
|
+
| `packages/config/src/validate-env.js` | Environment variable validation — register new vars here |
|
|
265
|
+
| `apps/web/src/app/` | Next.js pages and API routes |
|
|
266
|
+
| `apps/web/src/lib/auth.js` | NextAuth configuration |
|
|
267
|
+
| `apps/worker/src/handlers/` | Background job handler functions |
|
|
268
|
+
| `packages/ui/src/` | UI component source |
|
|
269
|
+
| `.github/skills/project-context/SKILL.md` | VS Code Copilot skill context |
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
*Update this file whenever you make structural changes: new packages, deployment targets, major new conventions, or significant architectural decisions. The AI tools you use daily depend on it being accurate.*
|
|
@@ -1,49 +1,91 @@
|
|
|
1
|
-
#
|
|
1
|
+
# __QUARK_PROJECT_NAME__
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scaffolded with [Quark](https://github.com/Bobnoddle/quark) on __QUARK_SCAFFOLD_DATE__.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Local Development
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
pnpm db:migrate
|
|
11
|
-
pnpm dev
|
|
8
|
+
docker compose up -d # Start PostgreSQL, Redis, Mailpit
|
|
9
|
+
pnpm install # Install dependencies
|
|
10
|
+
pnpm db:migrate # Apply migrations
|
|
11
|
+
pnpm dev # Start web + worker
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Open http://localhost:3000
|
|
14
|
+
Open [http://localhost:3000](http://localhost:3000)
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Database
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
| Task | Command |
|
|
19
|
+
|------|---------|
|
|
20
|
+
| Run migrations | `pnpm db:migrate` |
|
|
21
|
+
| Push schema (no migration) | `pnpm db:push` |
|
|
22
|
+
| Generate Prisma client | `pnpm db:generate` |
|
|
23
|
+
| Seed database | `pnpm db:seed` |
|
|
24
|
+
| Open Prisma Studio | `pnpm db:studio` |
|
|
22
25
|
|
|
23
|
-
##
|
|
26
|
+
## Other Commands
|
|
24
27
|
|
|
25
28
|
```bash
|
|
26
|
-
# Build all packages
|
|
27
|
-
pnpm
|
|
29
|
+
pnpm build # Build all packages
|
|
30
|
+
pnpm test # Run all tests (requires Docker)
|
|
31
|
+
pnpm lint # Lint + format check (Biome)
|
|
32
|
+
```
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
pnpm test
|
|
34
|
+
## Launch
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
### 1. Push to GitHub
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
gh repo create __QUARK_PROJECT_NAME__ --private --source=. --push
|
|
34
40
|
```
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
No `gh` CLI? Go to [github.com/new](https://github.com/new), create the repo, then:
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
```bash
|
|
45
|
+
git remote add origin https://github.com/<you>/__QUARK_PROJECT_NAME__.git
|
|
46
|
+
git push -u origin main
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Deploy on Railway
|
|
50
|
+
|
|
51
|
+
> **One-click Railway template coming soon.** For now, follow these steps:
|
|
52
|
+
|
|
53
|
+
1. Go to [railway.app](https://railway.app) → **New Project** → **Deploy from GitHub repo** → select `__QUARK_PROJECT_NAME__`
|
|
54
|
+
2. In the service → **Settings** → **Source** → set **Config File** to `apps/web/railway.json`
|
|
55
|
+
3. In the project → **+ New** → **Database** → **Add PostgreSQL** (injects `DATABASE_URL` automatically)
|
|
56
|
+
4. In the project → **+ New** → **Database** → **Add Redis** (injects `REDIS_URL` automatically)
|
|
57
|
+
5. In your web service → **Variables** → add:
|
|
58
|
+
- `NEXTAUTH_SECRET` → run `openssl rand -base64 32` locally and paste the result
|
|
59
|
+
- `APP_URL` → your Railway public domain (e.g. `https://__QUARK_PROJECT_NAME__.railway.app`)
|
|
60
|
+
6. *(If you included the worker)* **+ New** → **GitHub Repo** → same repo → **Config File** → `apps/worker/railway.json`
|
|
61
|
+
|
|
62
|
+
Railway auto-deploys on every push to `main`. Migrations run automatically before each deploy.
|
|
63
|
+
|
|
64
|
+
## AI-Assisted Development
|
|
65
|
+
|
|
66
|
+
This project ships with pre-loaded context for Claude Code, Cursor, GitHub Copilot, and others — see `CLAUDE.md` for the full reference.
|
|
67
|
+
|
|
68
|
+
**Suggested first prompt:**
|
|
69
|
+
|
|
70
|
+
> I'm building __QUARK_PROJECT_NAME__ — [describe what your app does in one sentence].
|
|
71
|
+
>
|
|
72
|
+
> Start by reviewing `CLAUDE.md` so you understand the full stack, then:
|
|
73
|
+
> 1. Add the Prisma models we need to `packages/db/prisma/schema.prisma`
|
|
74
|
+
> 2. Create the query helpers in `packages/db/src/queries.js`
|
|
75
|
+
> 3. Build the first page in `apps/web/src/app/` using our UI component system
|
|
76
|
+
|
|
77
|
+
Replace that bracketed description with what you're actually building, and the AI has everything it needs.
|
|
45
78
|
|
|
46
79
|
## Structure
|
|
47
80
|
|
|
48
|
-
|
|
49
|
-
|
|
81
|
+
```
|
|
82
|
+
__QUARK_PROJECT_NAME__/
|
|
83
|
+
├── apps/
|
|
84
|
+
│ ├── web/ # Next.js 16 (App Router, Server Actions)
|
|
85
|
+
│ └── worker/ # BullMQ background worker
|
|
86
|
+
├── packages/
|
|
87
|
+
│ ├── db/ # Prisma schema + query helpers
|
|
88
|
+
│ └── config/ # Environment validation
|
|
89
|
+
├── docker-compose.yml
|
|
90
|
+
└── CLAUDE.md # AI tool context — keep this updated
|
|
91
|
+
```
|
|
@@ -43,8 +43,12 @@ const nextConfig = {
|
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
key: "Content-Security-Policy",
|
|
46
|
+
// unsafe-eval is required by Turbopack in development only.
|
|
47
|
+
// It is deliberately excluded from the production directive.
|
|
46
48
|
value:
|
|
47
|
-
|
|
49
|
+
process.env.NODE_ENV !== "production"
|
|
50
|
+
? "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self';"
|
|
51
|
+
: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self';",
|
|
48
52
|
},
|
|
49
53
|
],
|
|
50
54
|
},
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@auth/prisma-adapter": "^2.11.1",
|
|
15
|
-
"@aws-sdk/client-s3": "^3.
|
|
16
|
-
"@aws-sdk/s3-request-presigner": "^3.
|
|
15
|
+
"@aws-sdk/client-s3": "^3.1004.0",
|
|
16
|
+
"@aws-sdk/s3-request-presigner": "^3.1004.0",
|
|
17
17
|
"@techstream/quark-core": "^1.0.0",
|
|
18
18
|
"@techstream/quark-db": "workspace:*",
|
|
19
19
|
"@techstream/quark-jobs": "workspace:*",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@tailwindcss/postcss": "^4.2.1",
|
|
31
|
-
"@types/node": "^25.3.
|
|
31
|
+
"@types/node": "^25.3.5",
|
|
32
32
|
"tailwindcss": "^4.2.1",
|
|
33
33
|
"@techstream/quark-config": "workspace:*"
|
|
34
34
|
}
|