@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.
Files changed (170) hide show
  1. package/.claude/skills/architecture-cutline/SKILL.md +96 -0
  2. package/.claude/skills/board-review/SKILL.md +77 -0
  3. package/.claude/skills/code-review/SKILL.md +76 -0
  4. package/.claude/skills/execute-task/SKILL.md +80 -0
  5. package/.claude/skills/idea-sharpen/SKILL.md +65 -0
  6. package/.claude/skills/implementation-brief/SKILL.md +87 -0
  7. package/.claude/skills/mvp-board/SKILL.md +95 -0
  8. package/.claude/skills/mvp-grill/SKILL.md +60 -0
  9. package/.claude/skills/mvp-spec/SKILL.md +78 -0
  10. package/.claude/skills/new-pack/SKILL.md +156 -0
  11. package/.claude/skills/next-task/SKILL.md +65 -0
  12. package/.claude/skills/pack-add/SKILL.md +64 -0
  13. package/.claude/skills/pack-resolve/SKILL.md +67 -0
  14. package/.claude/skills/parallel-execution/SKILL.md +68 -0
  15. package/.claude/skills/qa-verify/SKILL.md +77 -0
  16. package/.claude/skills/risk-check/SKILL.md +88 -0
  17. package/.claude/skills/sync-board/SKILL.md +76 -0
  18. package/.claude/skills/ux-theme/SKILL.md +93 -0
  19. package/.codex/skills/architecture-cutline/SKILL.md +94 -0
  20. package/.codex/skills/board-review/SKILL.md +75 -0
  21. package/.codex/skills/code-review/SKILL.md +73 -0
  22. package/.codex/skills/execute-task/SKILL.md +76 -0
  23. package/.codex/skills/idea-sharpen/SKILL.md +63 -0
  24. package/.codex/skills/implementation-brief/SKILL.md +85 -0
  25. package/.codex/skills/mvp-board/SKILL.md +93 -0
  26. package/.codex/skills/mvp-grill/SKILL.md +58 -0
  27. package/.codex/skills/mvp-spec/SKILL.md +76 -0
  28. package/.codex/skills/new-pack/SKILL.md +153 -0
  29. package/.codex/skills/next-task/SKILL.md +64 -0
  30. package/.codex/skills/pack-add/SKILL.md +62 -0
  31. package/.codex/skills/pack-resolve/SKILL.md +65 -0
  32. package/.codex/skills/parallel-execution/SKILL.md +66 -0
  33. package/.codex/skills/qa-verify/SKILL.md +74 -0
  34. package/.codex/skills/risk-check/SKILL.md +86 -0
  35. package/.codex/skills/sync-board/SKILL.md +72 -0
  36. package/.codex/skills/ux-theme/SKILL.md +91 -0
  37. package/package.json +10 -4
  38. package/packs/README.md +132 -0
  39. package/packs/ai-anthropic/files/app/api/ai/route.ts +57 -0
  40. package/packs/ai-anthropic/files/lib/anthropic.ts +15 -0
  41. package/packs/ai-anthropic/pack.toml +32 -0
  42. package/packs/ai-anthropic/skills/ai-feature-patterns/SKILL.md +87 -0
  43. package/packs/ai-anthropic/tasks.yaml +9 -0
  44. package/packs/ai-openai/files/app/api/ai-openai/route.ts +55 -0
  45. package/packs/ai-openai/files/lib/openai.ts +21 -0
  46. package/packs/ai-openai/pack.toml +30 -0
  47. package/packs/ai-openai/tasks.yaml +9 -0
  48. package/packs/analytics-posthog/files/components/PostHogProvider.tsx +19 -0
  49. package/packs/analytics-posthog/files/lib/posthog/client.ts +20 -0
  50. package/packs/analytics-posthog/files/lib/posthog/server.ts +24 -0
  51. package/packs/analytics-posthog/pack.toml +35 -0
  52. package/packs/analytics-posthog/tasks.yaml +15 -0
  53. package/packs/auth-better-auth/files/app/(auth)/login/page.tsx +58 -0
  54. package/packs/auth-better-auth/files/app/api/auth/[...all]/route.ts +4 -0
  55. package/packs/auth-better-auth/files/lib/auth.ts +21 -0
  56. package/packs/auth-better-auth/pack.toml +32 -0
  57. package/packs/auth-better-auth/tasks.yaml +10 -0
  58. package/packs/auth-better-auth-pg/files/app/api/auth/[...all]/route.ts +4 -0
  59. package/packs/auth-better-auth-pg/files/lib/auth.ts +86 -0
  60. package/packs/auth-better-auth-pg/pack.toml +32 -0
  61. package/packs/auth-better-auth-pg/tasks.yaml +17 -0
  62. package/packs/auth-supabase/files/app/(auth)/login/page.tsx +64 -0
  63. package/packs/auth-supabase/files/app/auth/callback/route.ts +15 -0
  64. package/packs/auth-supabase/files/middleware.ts +41 -0
  65. package/packs/auth-supabase/pack.toml +34 -0
  66. package/packs/auth-supabase/tasks.yaml +10 -0
  67. package/packs/db-postgres/files/compose/postgres.yml +28 -0
  68. package/packs/db-postgres/files/docker-compose.include.yml +1 -0
  69. package/packs/db-postgres/files/docker-compose.yml +6 -0
  70. package/packs/db-postgres/files/drizzle.config.ts +10 -0
  71. package/packs/db-postgres/files/lib/db/index.ts +10 -0
  72. package/packs/db-postgres/files/lib/db/schema.ts +11 -0
  73. package/packs/db-postgres/pack.toml +53 -0
  74. package/packs/db-postgres/tasks.yaml +11 -0
  75. package/packs/db-sqlite/files/drizzle.config.ts +10 -0
  76. package/packs/db-sqlite/files/lib/db.ts +8 -0
  77. package/packs/db-sqlite/files/lib/schema.ts +13 -0
  78. package/packs/db-sqlite/pack.toml +34 -0
  79. package/packs/db-sqlite/tasks.yaml +6 -0
  80. package/packs/db-supabase/files/lib/supabase/client.ts +8 -0
  81. package/packs/db-supabase/files/lib/supabase/server.ts +27 -0
  82. package/packs/db-supabase/pack.toml +32 -0
  83. package/packs/db-supabase/skills/supabase-patterns/SKILL.md +82 -0
  84. package/packs/db-supabase/tasks.yaml +6 -0
  85. package/packs/deploy-vercel/files/docs/deploy.md +21 -0
  86. package/packs/deploy-vercel/files/vercel.json +4 -0
  87. package/packs/deploy-vercel/pack.toml +30 -0
  88. package/packs/deploy-vercel/tasks.yaml +14 -0
  89. package/packs/docker-compose-dev/files/.env.docker.example +2 -0
  90. package/packs/docker-compose-dev/files/compose/redis.yml +17 -0
  91. package/packs/docker-compose-dev/files/docker-compose.include.yml +1 -0
  92. package/packs/docker-compose-dev/files/docker-compose.yml +6 -0
  93. package/packs/docker-compose-dev/pack.toml +38 -0
  94. package/packs/docker-compose-dev/tasks.yaml +9 -0
  95. package/packs/email-resend/files/app/api/email/test/route.ts +38 -0
  96. package/packs/email-resend/files/emails/welcome.tsx +66 -0
  97. package/packs/email-resend/files/lib/email.ts +40 -0
  98. package/packs/email-resend/pack.toml +34 -0
  99. package/packs/email-resend/tasks.yaml +9 -0
  100. package/packs/example/pack.toml +69 -0
  101. package/packs/payments-stripe/files/app/api/billing-portal/route.ts +24 -0
  102. package/packs/payments-stripe/files/app/api/checkout/route.ts +58 -0
  103. package/packs/payments-stripe/files/app/api/webhooks/stripe/route.ts +84 -0
  104. package/packs/payments-stripe/files/lib/stripe.ts +60 -0
  105. package/packs/payments-stripe/pack.toml +49 -0
  106. package/packs/payments-stripe/skills/stripe-patterns/SKILL.md +93 -0
  107. package/packs/payments-stripe/tasks.yaml +16 -0
  108. package/packs/sync-zero/files/components/ZeroProvider.tsx +3 -0
  109. package/packs/sync-zero/files/compose/zero-cache.yml +26 -0
  110. package/packs/sync-zero/files/docker-compose.include.yml +1 -0
  111. package/packs/sync-zero/files/docker-compose.yml +6 -0
  112. package/packs/sync-zero/files/lib/zero/client.ts +18 -0
  113. package/packs/sync-zero/files/lib/zero/schema.ts +17 -0
  114. package/packs/sync-zero/files/zero.config.ts +26 -0
  115. package/packs/sync-zero/pack.toml +61 -0
  116. package/packs/sync-zero/skills/zero-patterns/SKILL.md +69 -0
  117. package/packs/sync-zero/tasks.yaml +16 -0
  118. package/packs/testing-playwright/files/e2e/example.spec.ts +7 -0
  119. package/packs/testing-playwright/files/playwright.config.ts +33 -0
  120. package/packs/testing-playwright/pack.toml +25 -0
  121. package/packs/testing-playwright/tasks.yaml +9 -0
  122. package/packs/ui-shadcn/files/app/globals.css +56 -0
  123. package/packs/ui-shadcn/files/components/ui/button.tsx +47 -0
  124. package/packs/ui-shadcn/files/components/ui/card.tsx +33 -0
  125. package/packs/ui-shadcn/files/lib/utils.ts +6 -0
  126. package/packs/ui-shadcn/files/postcss.config.mjs +7 -0
  127. package/packs/ui-shadcn/files/tailwind.config.ts +57 -0
  128. package/packs/ui-shadcn/pack.toml +44 -0
  129. package/packs/ui-shadcn/skills/shadcn-dashboard-patterns/SKILL.md +85 -0
  130. package/packs/ui-shadcn/tasks.yaml +6 -0
  131. package/presets/docs-site.toml +4 -0
  132. package/presets/internal-tool.toml +4 -0
  133. package/presets/lean-saas.toml +4 -0
  134. package/presets/local-ai-mvp.toml +4 -0
  135. package/presets/saas-classic.toml +4 -0
  136. package/scripts/sync-skills.ts +223 -0
  137. package/src/paths.ts +22 -4
  138. package/templates/README.md +43 -0
  139. package/templates/astro/README.md +3 -0
  140. package/templates/astro/template.toml +4 -0
  141. package/templates/astro-starlight/README.md +3 -0
  142. package/templates/astro-starlight/template.toml +4 -0
  143. package/templates/nextjs/.ai/architecture.md +13 -0
  144. package/templates/nextjs/.ai/board.md +7 -0
  145. package/templates/nextjs/.ai/product-spec.md +11 -0
  146. package/templates/nextjs/.claude/skills/.gitkeep +0 -0
  147. package/templates/nextjs/.codex/skills/.gitkeep +0 -0
  148. package/templates/nextjs/AGENTS.md +95 -0
  149. package/templates/nextjs/CLAUDE.md +3 -0
  150. package/templates/nextjs/README.md +20 -0
  151. package/templates/nextjs/app/(app)/home/page.tsx +43 -0
  152. package/templates/nextjs/app/(app)/home/posts-panel.tsx +83 -0
  153. package/templates/nextjs/app/(app)/layout.tsx +12 -0
  154. package/templates/nextjs/app/(auth)/login/page.tsx +97 -0
  155. package/templates/nextjs/app/globals.css +23 -0
  156. package/templates/nextjs/app/layout.tsx +20 -0
  157. package/templates/nextjs/app/page.tsx +39 -0
  158. package/templates/nextjs/lib/auth-placeholder.ts +21 -0
  159. package/templates/nextjs/lib/posts-placeholder.ts +30 -0
  160. package/templates/nextjs/next.config.ts +5 -0
  161. package/templates/nextjs/package.json +26 -0
  162. package/templates/nextjs/postcss.config.mjs +7 -0
  163. package/templates/nextjs/spark.config.json +4 -0
  164. package/templates/nextjs/template.toml +4 -0
  165. package/templates/nextjs/tsconfig.json +27 -0
  166. package/templates/nextjs/types/post.ts +13 -0
  167. package/templates/one/README.md +5 -0
  168. package/templates/one/template.toml +4 -0
  169. package/templates/vite-react/README.md +3 -0
  170. 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,6 @@
1
+ # Spark-managed Docker Compose root.
2
+ #
3
+ # Each infra-providing pack appends an entry below. Service definitions live
4
+ # in compose/*.yml fragments. Run with `docker compose up -d` — Compose 2.20+
5
+ # resolves `include:` for you.
6
+ include:
@@ -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,10 @@
1
+ import { defineConfig } from 'drizzle-kit';
2
+
3
+ export default defineConfig({
4
+ schema: './lib/schema.ts',
5
+ out: './drizzle',
6
+ dialect: 'sqlite',
7
+ dbCredentials: {
8
+ url: process.env.DATABASE_URL ?? 'local.db',
9
+ },
10
+ });
@@ -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,6 @@
1
+ epic: Database
2
+ tasks:
3
+ - id: DB-001
4
+ title: Run initial migration
5
+ acceptance:
6
+ - Schema applied to local sqlite file
@@ -0,0 +1,8 @@
1
+ import { createBrowserClient } from '@supabase/ssr';
2
+
3
+ export function createSupabaseBrowserClient() {
4
+ return createBrowserClient(
5
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
6
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
7
+ );
8
+ }
@@ -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,6 @@
1
+ epic: Database
2
+ tasks:
3
+ - id: DB-101
4
+ title: Configure RLS policies
5
+ acceptance:
6
+ - RLS enabled on all user-facing tables
@@ -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,4 @@
1
+ {
2
+ "$schema": "https://openapi.vercel.sh/vercel.json",
3
+ "framework": "nextjs"
4
+ }
@@ -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,2 @@
1
+ POSTGRES_PORT=5432
2
+ REDIS_PORT=6379
@@ -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,6 @@
1
+ # Spark-managed Docker Compose root.
2
+ #
3
+ # Each infra-providing pack appends an entry below. Service definitions live
4
+ # in compose/*.yml fragments. Run with `docker compose up -d` — Compose 2.20+
5
+ # resolves `include:` for you.
6
+ include:
@@ -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
+ }