@forgeailab/create-spark 0.1.1 → 0.1.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/.claude/skills/architecture-cutline/SKILL.md +96 -0
- package/.claude/skills/board-review/SKILL.md +77 -0
- package/.claude/skills/code-review/SKILL.md +76 -0
- package/.claude/skills/execute-task/SKILL.md +80 -0
- package/.claude/skills/idea-sharpen/SKILL.md +65 -0
- package/.claude/skills/implementation-brief/SKILL.md +87 -0
- package/.claude/skills/mvp-board/SKILL.md +95 -0
- package/.claude/skills/mvp-grill/SKILL.md +60 -0
- package/.claude/skills/mvp-spec/SKILL.md +78 -0
- package/.claude/skills/new-pack/SKILL.md +156 -0
- package/.claude/skills/next-task/SKILL.md +65 -0
- package/.claude/skills/pack-add/SKILL.md +64 -0
- package/.claude/skills/pack-resolve/SKILL.md +67 -0
- package/.claude/skills/parallel-execution/SKILL.md +68 -0
- package/.claude/skills/qa-verify/SKILL.md +77 -0
- package/.claude/skills/risk-check/SKILL.md +88 -0
- package/.claude/skills/sync-board/SKILL.md +76 -0
- package/.claude/skills/ux-theme/SKILL.md +93 -0
- package/.codex/skills/architecture-cutline/SKILL.md +94 -0
- package/.codex/skills/board-review/SKILL.md +75 -0
- package/.codex/skills/code-review/SKILL.md +73 -0
- package/.codex/skills/execute-task/SKILL.md +76 -0
- package/.codex/skills/idea-sharpen/SKILL.md +63 -0
- package/.codex/skills/implementation-brief/SKILL.md +85 -0
- package/.codex/skills/mvp-board/SKILL.md +93 -0
- package/.codex/skills/mvp-grill/SKILL.md +58 -0
- package/.codex/skills/mvp-spec/SKILL.md +76 -0
- package/.codex/skills/new-pack/SKILL.md +153 -0
- package/.codex/skills/next-task/SKILL.md +64 -0
- package/.codex/skills/pack-add/SKILL.md +62 -0
- package/.codex/skills/pack-resolve/SKILL.md +65 -0
- package/.codex/skills/parallel-execution/SKILL.md +66 -0
- package/.codex/skills/qa-verify/SKILL.md +74 -0
- package/.codex/skills/risk-check/SKILL.md +86 -0
- package/.codex/skills/sync-board/SKILL.md +72 -0
- package/.codex/skills/ux-theme/SKILL.md +91 -0
- package/package.json +10 -4
- package/packs/README.md +132 -0
- package/packs/ai-anthropic/files/app/api/ai/route.ts +57 -0
- package/packs/ai-anthropic/files/lib/anthropic.ts +15 -0
- package/packs/ai-anthropic/pack.toml +32 -0
- package/packs/ai-anthropic/skills/ai-feature-patterns/SKILL.md +87 -0
- package/packs/ai-anthropic/tasks.yaml +9 -0
- package/packs/ai-openai/files/app/api/ai-openai/route.ts +55 -0
- package/packs/ai-openai/files/lib/openai.ts +21 -0
- package/packs/ai-openai/pack.toml +30 -0
- package/packs/ai-openai/tasks.yaml +9 -0
- package/packs/analytics-posthog/files/components/PostHogProvider.tsx +19 -0
- package/packs/analytics-posthog/files/lib/posthog/client.ts +20 -0
- package/packs/analytics-posthog/files/lib/posthog/server.ts +24 -0
- package/packs/analytics-posthog/pack.toml +35 -0
- package/packs/analytics-posthog/tasks.yaml +15 -0
- package/packs/auth-better-auth/files/app/(auth)/login/page.tsx +58 -0
- package/packs/auth-better-auth/files/app/api/auth/[...all]/route.ts +4 -0
- package/packs/auth-better-auth/files/lib/auth.ts +21 -0
- package/packs/auth-better-auth/pack.toml +32 -0
- package/packs/auth-better-auth/tasks.yaml +10 -0
- package/packs/auth-better-auth-pg/files/app/api/auth/[...all]/route.ts +4 -0
- package/packs/auth-better-auth-pg/files/lib/auth.ts +86 -0
- package/packs/auth-better-auth-pg/pack.toml +32 -0
- package/packs/auth-better-auth-pg/tasks.yaml +17 -0
- package/packs/auth-supabase/files/app/(auth)/login/page.tsx +64 -0
- package/packs/auth-supabase/files/app/auth/callback/route.ts +15 -0
- package/packs/auth-supabase/files/middleware.ts +41 -0
- package/packs/auth-supabase/pack.toml +34 -0
- package/packs/auth-supabase/tasks.yaml +10 -0
- package/packs/db-postgres/files/compose/postgres.yml +28 -0
- package/packs/db-postgres/files/docker-compose.include.yml +1 -0
- package/packs/db-postgres/files/docker-compose.yml +6 -0
- package/packs/db-postgres/files/drizzle.config.ts +10 -0
- package/packs/db-postgres/files/lib/db/index.ts +10 -0
- package/packs/db-postgres/files/lib/db/schema.ts +11 -0
- package/packs/db-postgres/pack.toml +53 -0
- package/packs/db-postgres/tasks.yaml +11 -0
- package/packs/db-sqlite/files/drizzle.config.ts +10 -0
- package/packs/db-sqlite/files/lib/db.ts +8 -0
- package/packs/db-sqlite/files/lib/schema.ts +13 -0
- package/packs/db-sqlite/pack.toml +34 -0
- package/packs/db-sqlite/tasks.yaml +6 -0
- package/packs/db-supabase/files/lib/supabase/client.ts +8 -0
- package/packs/db-supabase/files/lib/supabase/server.ts +27 -0
- package/packs/db-supabase/pack.toml +32 -0
- package/packs/db-supabase/skills/supabase-patterns/SKILL.md +82 -0
- package/packs/db-supabase/tasks.yaml +6 -0
- package/packs/deploy-vercel/files/docs/deploy.md +21 -0
- package/packs/deploy-vercel/files/vercel.json +4 -0
- package/packs/deploy-vercel/pack.toml +30 -0
- package/packs/deploy-vercel/tasks.yaml +14 -0
- package/packs/docker-compose-dev/files/.env.docker.example +2 -0
- package/packs/docker-compose-dev/files/compose/redis.yml +17 -0
- package/packs/docker-compose-dev/files/docker-compose.include.yml +1 -0
- package/packs/docker-compose-dev/files/docker-compose.yml +6 -0
- package/packs/docker-compose-dev/pack.toml +38 -0
- package/packs/docker-compose-dev/tasks.yaml +9 -0
- package/packs/email-resend/files/app/api/email/test/route.ts +38 -0
- package/packs/email-resend/files/emails/welcome.tsx +66 -0
- package/packs/email-resend/files/lib/email.ts +40 -0
- package/packs/email-resend/pack.toml +34 -0
- package/packs/email-resend/tasks.yaml +9 -0
- package/packs/example/pack.toml +69 -0
- package/packs/payments-stripe/files/app/api/billing-portal/route.ts +24 -0
- package/packs/payments-stripe/files/app/api/checkout/route.ts +58 -0
- package/packs/payments-stripe/files/app/api/webhooks/stripe/route.ts +84 -0
- package/packs/payments-stripe/files/lib/stripe.ts +60 -0
- package/packs/payments-stripe/pack.toml +49 -0
- package/packs/payments-stripe/skills/stripe-patterns/SKILL.md +93 -0
- package/packs/payments-stripe/tasks.yaml +16 -0
- package/packs/sync-zero/files/components/ZeroProvider.tsx +3 -0
- package/packs/sync-zero/files/compose/zero-cache.yml +26 -0
- package/packs/sync-zero/files/docker-compose.include.yml +1 -0
- package/packs/sync-zero/files/docker-compose.yml +6 -0
- package/packs/sync-zero/files/lib/zero/client.ts +18 -0
- package/packs/sync-zero/files/lib/zero/schema.ts +17 -0
- package/packs/sync-zero/files/zero.config.ts +26 -0
- package/packs/sync-zero/pack.toml +61 -0
- package/packs/sync-zero/skills/zero-patterns/SKILL.md +69 -0
- package/packs/sync-zero/tasks.yaml +16 -0
- package/packs/testing-playwright/files/e2e/example.spec.ts +7 -0
- package/packs/testing-playwright/files/playwright.config.ts +33 -0
- package/packs/testing-playwright/pack.toml +25 -0
- package/packs/testing-playwright/tasks.yaml +9 -0
- package/packs/ui-shadcn/files/app/globals.css +56 -0
- package/packs/ui-shadcn/files/components/ui/button.tsx +47 -0
- package/packs/ui-shadcn/files/components/ui/card.tsx +33 -0
- package/packs/ui-shadcn/files/lib/utils.ts +6 -0
- package/packs/ui-shadcn/files/postcss.config.mjs +7 -0
- package/packs/ui-shadcn/files/tailwind.config.ts +57 -0
- package/packs/ui-shadcn/pack.toml +44 -0
- package/packs/ui-shadcn/skills/shadcn-dashboard-patterns/SKILL.md +85 -0
- package/packs/ui-shadcn/tasks.yaml +6 -0
- package/presets/docs-site.toml +4 -0
- package/presets/internal-tool.toml +4 -0
- package/presets/lean-saas.toml +4 -0
- package/presets/local-ai-mvp.toml +4 -0
- package/presets/saas-classic.toml +4 -0
- package/scripts/sync-skills.ts +223 -0
- package/src/paths.ts +22 -4
- package/templates/README.md +43 -0
- package/templates/astro/README.md +3 -0
- package/templates/astro/template.toml +4 -0
- package/templates/astro-starlight/README.md +3 -0
- package/templates/astro-starlight/template.toml +4 -0
- package/templates/nextjs/.ai/architecture.md +13 -0
- package/templates/nextjs/.ai/board.md +7 -0
- package/templates/nextjs/.ai/product-spec.md +11 -0
- package/templates/nextjs/.claude/skills/.gitkeep +0 -0
- package/templates/nextjs/.codex/skills/.gitkeep +0 -0
- package/templates/nextjs/AGENTS.md +95 -0
- package/templates/nextjs/CLAUDE.md +3 -0
- package/templates/nextjs/README.md +20 -0
- package/templates/nextjs/app/(app)/home/page.tsx +43 -0
- package/templates/nextjs/app/(app)/home/posts-panel.tsx +83 -0
- package/templates/nextjs/app/(app)/layout.tsx +12 -0
- package/templates/nextjs/app/(auth)/login/page.tsx +97 -0
- package/templates/nextjs/app/globals.css +23 -0
- package/templates/nextjs/app/layout.tsx +20 -0
- package/templates/nextjs/app/page.tsx +39 -0
- package/templates/nextjs/lib/auth-placeholder.ts +21 -0
- package/templates/nextjs/lib/posts-placeholder.ts +30 -0
- package/templates/nextjs/next.config.ts +5 -0
- package/templates/nextjs/package.json +26 -0
- package/templates/nextjs/postcss.config.mjs +7 -0
- package/templates/nextjs/spark.config.json +4 -0
- package/templates/nextjs/template.toml +4 -0
- package/templates/nextjs/tsconfig.json +27 -0
- package/templates/nextjs/types/post.ts +13 -0
- package/templates/one/README.md +5 -0
- package/templates/one/template.toml +4 -0
- package/templates/vite-react/README.md +3 -0
- package/templates/vite-react/template.toml +4 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name = "auth-supabase"
|
|
2
|
+
version = "1.0.0"
|
|
3
|
+
category = "auth"
|
|
4
|
+
description = "Supabase Auth routes, login page, and session middleware for Next.js."
|
|
5
|
+
provides = ["auth"]
|
|
6
|
+
requires = ["db-pg"]
|
|
7
|
+
conflicts = ["auth"]
|
|
8
|
+
requires_runtime = ["server"]
|
|
9
|
+
compatible_scaffolds = ["nextjs"]
|
|
10
|
+
|
|
11
|
+
# NOTE: This pack is meaningfully compatible only when db-supabase is the installed db provider.
|
|
12
|
+
|
|
13
|
+
[dependencies]
|
|
14
|
+
runtime = ["@supabase/supabase-js", "@supabase/ssr"]
|
|
15
|
+
|
|
16
|
+
[env]
|
|
17
|
+
required = ["NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY"]
|
|
18
|
+
|
|
19
|
+
[[files]]
|
|
20
|
+
mode = "create"
|
|
21
|
+
from = "app/auth/callback/route.ts"
|
|
22
|
+
to = "app/auth/callback/route.ts"
|
|
23
|
+
|
|
24
|
+
# The nextjs template ships app/(auth)/login/page.tsx as a placeholder;
|
|
25
|
+
# Supabase users wire the supabase client into that template-owned file rather
|
|
26
|
+
# than overwriting it from the pack.
|
|
27
|
+
|
|
28
|
+
[[files]]
|
|
29
|
+
mode = "create"
|
|
30
|
+
from = "middleware.ts"
|
|
31
|
+
to = "middleware.ts"
|
|
32
|
+
|
|
33
|
+
[tasks]
|
|
34
|
+
file = "tasks.yaml"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
epic: Auth
|
|
2
|
+
tasks:
|
|
3
|
+
- id: AUTH-001
|
|
4
|
+
title: Configure OAuth provider in Supabase dashboard
|
|
5
|
+
acceptance:
|
|
6
|
+
- Supabase OAuth provider completes sign-in through /auth/callback
|
|
7
|
+
- id: AUTH-002
|
|
8
|
+
title: Add protected route example
|
|
9
|
+
acceptance:
|
|
10
|
+
- Anonymous users are redirected away from the protected route example
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16
|
|
4
|
+
restart: unless-stopped
|
|
5
|
+
command:
|
|
6
|
+
- postgres
|
|
7
|
+
- -c
|
|
8
|
+
- wal_level=logical
|
|
9
|
+
- -c
|
|
10
|
+
- max_wal_senders=10
|
|
11
|
+
- -c
|
|
12
|
+
- max_replication_slots=10
|
|
13
|
+
environment:
|
|
14
|
+
POSTGRES_DB: ${POSTGRES_DB:-app}
|
|
15
|
+
POSTGRES_USER: ${POSTGRES_USER:-app}
|
|
16
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-app}
|
|
17
|
+
ports:
|
|
18
|
+
- "${POSTGRES_PORT:-5432}:5432"
|
|
19
|
+
volumes:
|
|
20
|
+
- postgres_data:/var/lib/postgresql/data
|
|
21
|
+
healthcheck:
|
|
22
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-app} -d ${POSTGRES_DB:-app}"]
|
|
23
|
+
interval: 10s
|
|
24
|
+
timeout: 5s
|
|
25
|
+
retries: 5
|
|
26
|
+
|
|
27
|
+
volumes:
|
|
28
|
+
postgres_data:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
- compose/postgres.yml
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineConfig } from 'drizzle-kit';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
schema: './lib/db/schema.ts',
|
|
5
|
+
out: './drizzle',
|
|
6
|
+
dialect: 'postgresql',
|
|
7
|
+
dbCredentials: {
|
|
8
|
+
url: process.env.DATABASE_URL ?? 'postgres://postgres:postgres@localhost:5432/postgres',
|
|
9
|
+
},
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import postgres from 'postgres';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
3
|
+
import * as schema from './schema';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_URL = 'postgres://postgres:postgres@localhost:5432/postgres';
|
|
6
|
+
const url = process.env.DATABASE_URL ?? DEFAULT_URL;
|
|
7
|
+
|
|
8
|
+
export const sql = postgres(url, { max: 10 });
|
|
9
|
+
export const db = drizzle(sql, { schema });
|
|
10
|
+
export type Db = typeof db;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { pgTable, text, timestamp } from 'drizzle-orm/pg-core';
|
|
2
|
+
|
|
3
|
+
export const users = pgTable('users', {
|
|
4
|
+
id: text('id').primaryKey(),
|
|
5
|
+
email: text('email').notNull().unique(),
|
|
6
|
+
name: text('name'),
|
|
7
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export type User = typeof users.$inferSelect;
|
|
11
|
+
export type NewUser = typeof users.$inferInsert;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name = "db-postgres"
|
|
2
|
+
version = "1.0.0"
|
|
3
|
+
category = "db"
|
|
4
|
+
description = "Postgres database setup with Drizzle ORM and postgres-js (Bun-compatible)."
|
|
5
|
+
provides = ["db", "db-pg"]
|
|
6
|
+
requires = []
|
|
7
|
+
conflicts = ["db"]
|
|
8
|
+
requires_runtime = ["server"]
|
|
9
|
+
compatible_scaffolds = ["nextjs"]
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
runtime = ["drizzle-orm", "postgres"]
|
|
13
|
+
dev = ["drizzle-kit"]
|
|
14
|
+
|
|
15
|
+
[env]
|
|
16
|
+
required = ["DATABASE_URL"]
|
|
17
|
+
|
|
18
|
+
[[files]]
|
|
19
|
+
mode = "create"
|
|
20
|
+
from = "lib/db/index.ts"
|
|
21
|
+
to = "lib/db/index.ts"
|
|
22
|
+
|
|
23
|
+
[[files]]
|
|
24
|
+
mode = "create"
|
|
25
|
+
from = "lib/db/schema.ts"
|
|
26
|
+
to = "lib/db/schema.ts"
|
|
27
|
+
|
|
28
|
+
[[files]]
|
|
29
|
+
mode = "create"
|
|
30
|
+
from = "drizzle.config.ts"
|
|
31
|
+
to = "drizzle.config.ts"
|
|
32
|
+
|
|
33
|
+
# Compose root + postgres fragment. Multiple packs (db-postgres, sync-zero,
|
|
34
|
+
# docker-compose-dev) each ship the same root file with `create-or-skip` —
|
|
35
|
+
# whichever installs first creates it, the rest no-op. Each pack then appends
|
|
36
|
+
# its own include line under a marker.
|
|
37
|
+
[[files]]
|
|
38
|
+
mode = "create-or-skip"
|
|
39
|
+
from = "docker-compose.yml"
|
|
40
|
+
to = "docker-compose.yml"
|
|
41
|
+
|
|
42
|
+
[[files]]
|
|
43
|
+
mode = "create"
|
|
44
|
+
from = "compose/postgres.yml"
|
|
45
|
+
to = "compose/postgres.yml"
|
|
46
|
+
|
|
47
|
+
[[files]]
|
|
48
|
+
mode = "append"
|
|
49
|
+
from = "docker-compose.include.yml"
|
|
50
|
+
to = "docker-compose.yml"
|
|
51
|
+
|
|
52
|
+
[tasks]
|
|
53
|
+
file = "tasks.yaml"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
epic: Database
|
|
2
|
+
tasks:
|
|
3
|
+
- id: DB-001
|
|
4
|
+
title: Run initial migration
|
|
5
|
+
acceptance:
|
|
6
|
+
- Schema applied to Postgres via `bun drizzle-kit push`
|
|
7
|
+
- id: DB-002
|
|
8
|
+
title: Provision local Postgres
|
|
9
|
+
acceptance:
|
|
10
|
+
- Postgres reachable at DATABASE_URL (docker-compose, Homebrew, or hosted)
|
|
11
|
+
- wal_level=logical set if pairing with sync-zero
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
|
3
|
+
import * as schema from './schema';
|
|
4
|
+
|
|
5
|
+
const databaseUrl = process.env.DATABASE_URL ?? 'local.db';
|
|
6
|
+
const sqlite = new Database(databaseUrl.replace(/^file:/u, ''));
|
|
7
|
+
|
|
8
|
+
export const db = drizzle(sqlite, { schema });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
|
|
3
|
+
export const users = sqliteTable('users', {
|
|
4
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
5
|
+
email: text('email').notNull().unique(),
|
|
6
|
+
name: text('name'),
|
|
7
|
+
createdAt: integer('created_at', { mode: 'timestamp' })
|
|
8
|
+
.notNull()
|
|
9
|
+
.$defaultFn(() => new Date()),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type User = typeof users.$inferSelect;
|
|
13
|
+
export type NewUser = typeof users.$inferInsert;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name = "db-sqlite"
|
|
2
|
+
version = "1.0.0"
|
|
3
|
+
category = "db"
|
|
4
|
+
description = "SQLite database setup with Drizzle ORM and Bun."
|
|
5
|
+
provides = ["db"]
|
|
6
|
+
requires = []
|
|
7
|
+
conflicts = ["db"]
|
|
8
|
+
requires_runtime = ["server"]
|
|
9
|
+
compatible_scaffolds = ["nextjs"]
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
runtime = ["drizzle-orm"]
|
|
13
|
+
dev = ["drizzle-kit"]
|
|
14
|
+
|
|
15
|
+
[env]
|
|
16
|
+
required = ["DATABASE_URL"]
|
|
17
|
+
|
|
18
|
+
[[files]]
|
|
19
|
+
mode = "create"
|
|
20
|
+
from = "lib/db.ts"
|
|
21
|
+
to = "lib/db.ts"
|
|
22
|
+
|
|
23
|
+
[[files]]
|
|
24
|
+
mode = "create"
|
|
25
|
+
from = "lib/schema.ts"
|
|
26
|
+
to = "lib/schema.ts"
|
|
27
|
+
|
|
28
|
+
[[files]]
|
|
29
|
+
mode = "create"
|
|
30
|
+
from = "drizzle.config.ts"
|
|
31
|
+
to = "drizzle.config.ts"
|
|
32
|
+
|
|
33
|
+
[tasks]
|
|
34
|
+
file = "tasks.yaml"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createServerClient } from '@supabase/ssr';
|
|
2
|
+
import { cookies } from 'next/headers';
|
|
3
|
+
|
|
4
|
+
export async function createSupabaseServerClient() {
|
|
5
|
+
const cookieStore = await cookies();
|
|
6
|
+
|
|
7
|
+
return createServerClient(
|
|
8
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
9
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
10
|
+
{
|
|
11
|
+
cookies: {
|
|
12
|
+
getAll() {
|
|
13
|
+
return cookieStore.getAll();
|
|
14
|
+
},
|
|
15
|
+
setAll(cookiesToSet) {
|
|
16
|
+
try {
|
|
17
|
+
cookiesToSet.forEach(({ name, value, options }) => {
|
|
18
|
+
cookieStore.set(name, value, options);
|
|
19
|
+
});
|
|
20
|
+
} catch {
|
|
21
|
+
// Server Components cannot write cookies. Middleware refreshes sessions.
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name = "db-supabase"
|
|
2
|
+
version = "1.0.0"
|
|
3
|
+
category = "db"
|
|
4
|
+
description = "Supabase browser and server clients for Next.js."
|
|
5
|
+
provides = ["db", "db-pg"]
|
|
6
|
+
requires = []
|
|
7
|
+
conflicts = ["db"]
|
|
8
|
+
requires_runtime = ["server"]
|
|
9
|
+
compatible_scaffolds = ["nextjs"]
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
runtime = ["@supabase/supabase-js", "@supabase/ssr"]
|
|
13
|
+
|
|
14
|
+
[env]
|
|
15
|
+
required = ["NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY"]
|
|
16
|
+
optional = ["SUPABASE_SERVICE_ROLE_KEY"]
|
|
17
|
+
|
|
18
|
+
[[files]]
|
|
19
|
+
mode = "create"
|
|
20
|
+
from = "lib/supabase/server.ts"
|
|
21
|
+
to = "lib/supabase/server.ts"
|
|
22
|
+
|
|
23
|
+
[[files]]
|
|
24
|
+
mode = "create"
|
|
25
|
+
from = "lib/supabase/client.ts"
|
|
26
|
+
to = "lib/supabase/client.ts"
|
|
27
|
+
|
|
28
|
+
[skills]
|
|
29
|
+
copy = ["skills/supabase-patterns"]
|
|
30
|
+
|
|
31
|
+
[tasks]
|
|
32
|
+
file = "tasks.yaml"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: supabase-patterns
|
|
3
|
+
description: Apply Supabase client, server, and RLS patterns in Next.js apps without leaking authorization into the browser.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: supabase-patterns
|
|
7
|
+
|
|
8
|
+
## Goal
|
|
9
|
+
|
|
10
|
+
Use Supabase as the database boundary for a Next.js app while keeping data
|
|
11
|
+
access explicit, RLS-backed, and easy to review. Client code may improve user
|
|
12
|
+
experience, but database authorization belongs in Postgres policies.
|
|
13
|
+
|
|
14
|
+
## Client Choice
|
|
15
|
+
|
|
16
|
+
- Use the browser client only inside Client Components and browser utilities.
|
|
17
|
+
- Use the server client inside Server Components, Server Actions, and Route Handlers.
|
|
18
|
+
- Use the service role key only in server-only code.
|
|
19
|
+
- Never expose the service role key through `NEXT_PUBLIC_` variables.
|
|
20
|
+
- Keep client creation in `lib/supabase/client.ts` and `lib/supabase/server.ts`.
|
|
21
|
+
- Import those helpers instead of constructing clients throughout the app.
|
|
22
|
+
|
|
23
|
+
## RLS Baseline
|
|
24
|
+
|
|
25
|
+
- Enable RLS on every user-facing table before shipping.
|
|
26
|
+
- Write one policy per action: select, insert, update, delete.
|
|
27
|
+
- Start restrictive, then add policies for real workflows.
|
|
28
|
+
- Prefer `auth.uid()` ownership checks for user-owned rows.
|
|
29
|
+
- Prefer membership tables for team or organization access.
|
|
30
|
+
- Avoid policies that only check whether a user is signed in.
|
|
31
|
+
- Avoid policies that mirror UI visibility without enforcing ownership.
|
|
32
|
+
|
|
33
|
+
## Schema Patterns
|
|
34
|
+
|
|
35
|
+
- Add `user_id uuid references auth.users(id)` for personal records.
|
|
36
|
+
- Add `organization_id` plus a membership table for multi-tenant data.
|
|
37
|
+
- Use `created_at` and `updated_at` consistently.
|
|
38
|
+
- Add indexes for policy predicates such as `user_id` and `organization_id`.
|
|
39
|
+
- Keep public profile data separate from private account data.
|
|
40
|
+
- Do not join to private tables from public views unless the policy is clear.
|
|
41
|
+
|
|
42
|
+
## Server Reads And Writes
|
|
43
|
+
|
|
44
|
+
- Prefer server reads when data is needed for initial page render.
|
|
45
|
+
- Put writes in Server Actions or Route Handlers when they affect trusted state.
|
|
46
|
+
- Re-check authorization in SQL policies, even when the server action checks it.
|
|
47
|
+
- Use `select()` projections instead of fetching whole rows by default.
|
|
48
|
+
- Return typed view models to Client Components.
|
|
49
|
+
- Avoid passing raw Supabase errors directly into user-facing copy.
|
|
50
|
+
|
|
51
|
+
## Browser Reads
|
|
52
|
+
|
|
53
|
+
- Browser reads are fine for realtime lists, optimistic UI, and user-owned data.
|
|
54
|
+
- Keep filters aligned with RLS policies so results are predictable.
|
|
55
|
+
- Treat browser filters as performance hints, not authorization.
|
|
56
|
+
- Use loading, empty, and error states for every browser query.
|
|
57
|
+
- Do not fetch admin data from the browser, even behind hidden UI.
|
|
58
|
+
|
|
59
|
+
## Auth And Cookies
|
|
60
|
+
|
|
61
|
+
- Middleware should refresh auth cookies before protected routes read sessions.
|
|
62
|
+
- Use `getUser()` or claims validation on the server before trusted actions.
|
|
63
|
+
- Avoid trusting session data that only came from local storage.
|
|
64
|
+
- Redirect unauthenticated users at route boundaries.
|
|
65
|
+
- Keep callback routes small: exchange the code, then redirect.
|
|
66
|
+
|
|
67
|
+
## Service Role Use
|
|
68
|
+
|
|
69
|
+
- Reserve the service role for background jobs and admin workflows.
|
|
70
|
+
- Create a separate helper for service-role clients.
|
|
71
|
+
- Keep service-role operations narrow and logged.
|
|
72
|
+
- Never use the service role to bypass missing user policies in normal flows.
|
|
73
|
+
- If a user action needs service role access, reconsider the table design.
|
|
74
|
+
|
|
75
|
+
## Review Checklist
|
|
76
|
+
|
|
77
|
+
- Every exposed table has RLS enabled.
|
|
78
|
+
- Every policy has a named workflow it supports.
|
|
79
|
+
- Server-only keys are not imported by Client Components.
|
|
80
|
+
- Client queries cannot reveal another tenant's data.
|
|
81
|
+
- Seeded sample data matches the policies.
|
|
82
|
+
- Error states do not expose table names or policy details.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Vercel Deploy Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist after installing the `deploy-vercel` pack.
|
|
4
|
+
|
|
5
|
+
## Project setup
|
|
6
|
+
|
|
7
|
+
- Create or link the Vercel project.
|
|
8
|
+
- Confirm the project framework preset is `Next.js`.
|
|
9
|
+
- Set `VERCEL_PROJECT_ID` and `VERCEL_ORG_ID` locally when using Vercel CLI automation.
|
|
10
|
+
|
|
11
|
+
## Environment variables
|
|
12
|
+
|
|
13
|
+
- Add every required pack environment variable to Vercel.
|
|
14
|
+
- Keep preview and production values separate when credentials differ.
|
|
15
|
+
- Redeploy after changing environment variables.
|
|
16
|
+
|
|
17
|
+
## Preview deploys
|
|
18
|
+
|
|
19
|
+
- Enable pull request preview deploys.
|
|
20
|
+
- Confirm preview deploys run against non-production services.
|
|
21
|
+
- Keep production-only credentials out of preview environments.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name = "deploy-vercel"
|
|
2
|
+
version = "1.0.0"
|
|
3
|
+
category = "deploy"
|
|
4
|
+
description = "Vercel deployment target for Next.js apps."
|
|
5
|
+
provides = ["deploy-target"]
|
|
6
|
+
requires = []
|
|
7
|
+
conflicts = []
|
|
8
|
+
requires_runtime = []
|
|
9
|
+
compatible_scaffolds = ["nextjs"]
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
runtime = []
|
|
13
|
+
dev = []
|
|
14
|
+
|
|
15
|
+
[env]
|
|
16
|
+
required = []
|
|
17
|
+
optional = ["VERCEL_PROJECT_ID", "VERCEL_ORG_ID"]
|
|
18
|
+
|
|
19
|
+
[[files]]
|
|
20
|
+
mode = "create"
|
|
21
|
+
from = "vercel.json"
|
|
22
|
+
to = "vercel.json"
|
|
23
|
+
|
|
24
|
+
[[files]]
|
|
25
|
+
mode = "create"
|
|
26
|
+
from = "docs/deploy.md"
|
|
27
|
+
to = "docs/deploy.md"
|
|
28
|
+
|
|
29
|
+
[tasks]
|
|
30
|
+
file = "tasks.yaml"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
epic: Deploy
|
|
2
|
+
tasks:
|
|
3
|
+
- id: DEPLOY-001
|
|
4
|
+
title: Configure environment variables in Vercel dashboard
|
|
5
|
+
status: Clarifying
|
|
6
|
+
acceptance:
|
|
7
|
+
- Required pack environment variables are present in the Vercel project.
|
|
8
|
+
- Preview and production values are documented when they differ.
|
|
9
|
+
- id: DEPLOY-002
|
|
10
|
+
title: Set up preview deploys for pull requests
|
|
11
|
+
status: Clarifying
|
|
12
|
+
acceptance:
|
|
13
|
+
- Pull requests create Vercel preview deployments.
|
|
14
|
+
- Preview deployments use non-production credentials where applicable.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
services:
|
|
2
|
+
redis:
|
|
3
|
+
image: redis:7-alpine
|
|
4
|
+
restart: unless-stopped
|
|
5
|
+
command: ["redis-server", "--appendonly", "yes"]
|
|
6
|
+
ports:
|
|
7
|
+
- "${REDIS_PORT:-6379}:6379"
|
|
8
|
+
volumes:
|
|
9
|
+
- redis_data:/data
|
|
10
|
+
healthcheck:
|
|
11
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
12
|
+
interval: 10s
|
|
13
|
+
timeout: 5s
|
|
14
|
+
retries: 5
|
|
15
|
+
|
|
16
|
+
volumes:
|
|
17
|
+
redis_data:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
- compose/redis.yml
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name = "docker-compose-dev"
|
|
2
|
+
version = "1.0.0"
|
|
3
|
+
category = "infra"
|
|
4
|
+
description = "Local Redis for development via Docker Compose. Composes with db-postgres / sync-zero."
|
|
5
|
+
provides = ["local-runtime"]
|
|
6
|
+
requires = []
|
|
7
|
+
conflicts = []
|
|
8
|
+
requires_runtime = []
|
|
9
|
+
compatible_scaffolds = []
|
|
10
|
+
|
|
11
|
+
[env]
|
|
12
|
+
optional = ["REDIS_PORT"]
|
|
13
|
+
|
|
14
|
+
# Compose root + redis fragment. Other infra-providing packs (db-postgres,
|
|
15
|
+
# sync-zero) follow the same pattern: each uses `create-or-skip` to bootstrap
|
|
16
|
+
# the root file, then `append` to add their include line under a marker.
|
|
17
|
+
[[files]]
|
|
18
|
+
mode = "create-or-skip"
|
|
19
|
+
from = "docker-compose.yml"
|
|
20
|
+
to = "docker-compose.yml"
|
|
21
|
+
|
|
22
|
+
[[files]]
|
|
23
|
+
mode = "create"
|
|
24
|
+
from = "compose/redis.yml"
|
|
25
|
+
to = "compose/redis.yml"
|
|
26
|
+
|
|
27
|
+
[[files]]
|
|
28
|
+
mode = "append"
|
|
29
|
+
from = "docker-compose.include.yml"
|
|
30
|
+
to = "docker-compose.yml"
|
|
31
|
+
|
|
32
|
+
[[files]]
|
|
33
|
+
mode = "create"
|
|
34
|
+
from = ".env.docker.example"
|
|
35
|
+
to = ".env.docker.example"
|
|
36
|
+
|
|
37
|
+
[tasks]
|
|
38
|
+
file = "tasks.yaml"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
epic: Infrastructure
|
|
2
|
+
|
|
3
|
+
tasks:
|
|
4
|
+
- id: INFRA-001
|
|
5
|
+
title: Run docker compose up -d and verify Postgres reachable on port 5432
|
|
6
|
+
status: Clarifying
|
|
7
|
+
acceptance:
|
|
8
|
+
- docker compose up -d starts Postgres and Redis containers.
|
|
9
|
+
- Postgres accepts connections on the configured local port.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import WelcomeEmail from "@/emails/welcome";
|
|
2
|
+
import { sendEmail } from "@/lib/email";
|
|
3
|
+
import { NextResponse, type NextRequest } from "next/server";
|
|
4
|
+
|
|
5
|
+
export const runtime = "nodejs";
|
|
6
|
+
|
|
7
|
+
type TestEmailRequest = {
|
|
8
|
+
to?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
productName?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export async function POST(request: NextRequest) {
|
|
14
|
+
if (process.env.NODE_ENV !== "development") {
|
|
15
|
+
return NextResponse.json({ error: "Not found" }, { status: 404 });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (request.headers.get("x-spark-dev-email") !== "true") {
|
|
19
|
+
return NextResponse.json({ error: "Dev email flag is required" }, { status: 403 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const body = (await request.json().catch(() => ({}))) as TestEmailRequest;
|
|
23
|
+
|
|
24
|
+
if (!body.to) {
|
|
25
|
+
return NextResponse.json({ error: "to is required" }, { status: 400 });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = await sendEmail({
|
|
29
|
+
to: body.to,
|
|
30
|
+
subject: `Welcome to ${body.productName ?? "the app"}`,
|
|
31
|
+
react: WelcomeEmail({
|
|
32
|
+
name: body.name,
|
|
33
|
+
productName: body.productName,
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return NextResponse.json({ id: result.data?.id });
|
|
38
|
+
}
|