create-win-project 1.3.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/LICENSE +21 -0
- package/README.md +187 -0
- package/ci/expo.yml +28 -0
- package/ci/laravel.yml +49 -0
- package/ci/nextjs.yml +29 -0
- package/ci/springboot.yml +33 -0
- package/ci/vite.yml +36 -0
- package/compatibility/profiles.json +237 -0
- package/docs/ARCHITECTURE.md +118 -0
- package/docs/CONTENT_MODEL.md +36 -0
- package/docs/DEPENDENCY_MAINTENANCE.md +31 -0
- package/index.js +431 -0
- package/lib/application-shapes.js +95 -0
- package/lib/banner.js +45 -0
- package/lib/catalog.js +343 -0
- package/lib/compatibility.js +116 -0
- package/lib/constants.js +3 -0
- package/lib/doctor.js +45 -0
- package/lib/files.js +158 -0
- package/lib/generator.js +366 -0
- package/lib/interview.js +77 -0
- package/lib/laravel-scaffold.js +3 -0
- package/lib/playbooks.js +172 -0
- package/lib/project-location.js +18 -0
- package/lib/scaffold.js +467 -0
- package/lib/stacks/context.js +27 -0
- package/lib/stacks/contract.js +142 -0
- package/lib/stacks/index.js +8 -0
- package/lib/stacks/laravel/architecture.js +27 -0
- package/lib/stacks/laravel/auth/oidc.js +13 -0
- package/lib/stacks/laravel/auth/public.js +28 -0
- package/lib/stacks/laravel/auth/sanctum.js +14 -0
- package/lib/stacks/laravel/auth/session.js +17 -0
- package/lib/stacks/laravel/composer.js +50 -0
- package/lib/stacks/laravel/generate.js +362 -0
- package/lib/stacks/laravel/index.js +18 -0
- package/lib/stacks/laravel/ui/blade.js +15 -0
- package/lib/stacks/laravel/ui/index.js +29 -0
- package/lib/stacks/laravel/ui/inertia-react.js +35 -0
- package/lib/stacks/laravel/ui/livewire.js +39 -0
- package/lib/stacks/laravel/ui/shared.js +14 -0
- package/lib/stacks/registry.js +42 -0
- package/lib/stacks/shared/contributions.js +25 -0
- package/lib/template.js +62 -0
- package/package.json +49 -0
- package/playbooks/INDEX.md +58 -0
- package/playbooks/capabilities/auth/oidc-resource-server.md +15 -0
- package/playbooks/capabilities/auth/spring-session.md +15 -0
- package/playbooks/capabilities/ci/github-actions.manifest.json +10 -0
- package/playbooks/capabilities/ci/github-actions.md +61 -0
- package/playbooks/capabilities/docker/docker.manifest.json +11 -0
- package/playbooks/capabilities/docker/overview.md +55 -0
- package/playbooks/capabilities/flyway/environments.md +5 -0
- package/playbooks/capabilities/flyway/flyway.manifest.json +12 -0
- package/playbooks/capabilities/flyway/migrations.md +7 -0
- package/playbooks/capabilities/flyway/testing.md +5 -0
- package/playbooks/capabilities/laravel/database.md +8 -0
- package/playbooks/capabilities/laravel/migrations.md +15 -0
- package/playbooks/capabilities/laravel/observability.md +8 -0
- package/playbooks/capabilities/laravel/oidc-resource-server.md +10 -0
- package/playbooks/capabilities/laravel/queues.md +8 -0
- package/playbooks/capabilities/laravel/sanctum-spa.md +8 -0
- package/playbooks/capabilities/laravel/scheduler.md +7 -0
- package/playbooks/capabilities/laravel/session-auth.md +8 -0
- package/playbooks/capabilities/laravel/storage-uploads.md +7 -0
- package/playbooks/capabilities/postgresql/architecture.md +5 -0
- package/playbooks/capabilities/postgresql/migrations.md +5 -0
- package/playbooks/capabilities/postgresql/postgresql.manifest.json +15 -0
- package/playbooks/capabilities/postgresql/schema-design.md +7 -0
- package/playbooks/capabilities/postgresql/security.md +5 -0
- package/playbooks/capabilities/postgresql/testing.md +5 -0
- package/playbooks/capabilities/prisma/architecture.md +5 -0
- package/playbooks/capabilities/prisma/migrations.md +11 -0
- package/playbooks/capabilities/prisma/prisma.manifest.json +14 -0
- package/playbooks/capabilities/prisma/runtime.md +5 -0
- package/playbooks/capabilities/prisma/schema.md +5 -0
- package/playbooks/capabilities/prisma/testing.md +5 -0
- package/playbooks/capabilities/supabase/architecture.md +5 -0
- package/playbooks/capabilities/supabase/authentication.md +5 -0
- package/playbooks/capabilities/supabase/expo.md +16 -0
- package/playbooks/capabilities/supabase/migrations.md +7 -0
- package/playbooks/capabilities/supabase/nextjs.md +24 -0
- package/playbooks/capabilities/supabase/rls.md +7 -0
- package/playbooks/capabilities/supabase/supabase.manifest.json +32 -0
- package/playbooks/capabilities/supabase/testing.md +5 -0
- package/playbooks/capabilities/supabase/vite.md +14 -0
- package/playbooks/concerns/axios.md +123 -0
- package/playbooks/concerns/next-safe-action.md +66 -0
- package/playbooks/concerns/next-themes.md +87 -0
- package/playbooks/concerns/nuqs.md +59 -0
- package/playbooks/concerns/t3-env.md +48 -0
- package/playbooks/concerns/tanstack-query.md +145 -0
- package/playbooks/concerns/zod.md +174 -0
- package/playbooks/concerns/zustand.md +93 -0
- package/playbooks/devops/makefile.manifest.json +10 -0
- package/playbooks/devops/makefile.md +556 -0
- package/playbooks/devops/pr-template.manifest.json +10 -0
- package/playbooks/devops/pr-template.md +106 -0
- package/playbooks/platform/laravel-ui/blade/architecture.md +5 -0
- package/playbooks/platform/laravel-ui/blade/runtime.md +5 -0
- package/playbooks/platform/laravel-ui/blade/security.md +5 -0
- package/playbooks/platform/laravel-ui/blade/structure.md +5 -0
- package/playbooks/platform/laravel-ui/blade/testing.md +5 -0
- package/playbooks/platform/laravel-ui/inertia-react/architecture.md +5 -0
- package/playbooks/platform/laravel-ui/inertia-react/runtime.md +5 -0
- package/playbooks/platform/laravel-ui/inertia-react/security.md +5 -0
- package/playbooks/platform/laravel-ui/inertia-react/structure.md +5 -0
- package/playbooks/platform/laravel-ui/inertia-react/testing.md +5 -0
- package/playbooks/platform/laravel-ui/livewire/architecture.md +5 -0
- package/playbooks/platform/laravel-ui/livewire/runtime.md +5 -0
- package/playbooks/platform/laravel-ui/livewire/security.md +5 -0
- package/playbooks/platform/laravel-ui/livewire/structure.md +5 -0
- package/playbooks/platform/laravel-ui/livewire/testing.md +5 -0
- package/playbooks/platform/mobile.manifest.json +17 -0
- package/playbooks/platform/mobile.md +15 -0
- package/playbooks/platform/web.manifest.json +17 -0
- package/playbooks/platform/web.md +17 -0
- package/playbooks/stack/expo/architecture.md +27 -0
- package/playbooks/stack/expo/runtime.md +14 -0
- package/playbooks/stack/expo/security.md +18 -0
- package/playbooks/stack/expo/structure.md +26 -0
- package/playbooks/stack/expo/testing.md +13 -0
- package/playbooks/stack/laravel/architecture.md +34 -0
- package/playbooks/stack/laravel/runtime.md +27 -0
- package/playbooks/stack/laravel/security.md +23 -0
- package/playbooks/stack/laravel/structure.md +29 -0
- package/playbooks/stack/laravel/testing.md +22 -0
- package/playbooks/stack/laravel-ui.manifest.json +42 -0
- package/playbooks/stack/laravel.manifest.json +35 -0
- package/playbooks/stack/nextjs/architecture.md +62 -0
- package/playbooks/stack/nextjs/runtime.md +31 -0
- package/playbooks/stack/nextjs/security.md +32 -0
- package/playbooks/stack/nextjs/structure.md +50 -0
- package/playbooks/stack/nextjs/testing.md +20 -0
- package/playbooks/stack/nextjs.manifest.json +49 -0
- package/playbooks/stack/no-frontend.manifest.json +25 -0
- package/playbooks/stack/none.manifest.json +21 -0
- package/playbooks/stack/react-native.manifest.json +49 -0
- package/playbooks/stack/react-vite/architecture.md +33 -0
- package/playbooks/stack/react-vite/runtime.md +13 -0
- package/playbooks/stack/react-vite/security.md +16 -0
- package/playbooks/stack/react-vite/structure.md +23 -0
- package/playbooks/stack/react-vite/testing.md +12 -0
- package/playbooks/stack/react-vite.manifest.json +46 -0
- package/playbooks/stack/springboot/architecture.md +40 -0
- package/playbooks/stack/springboot/runtime.md +28 -0
- package/playbooks/stack/springboot/security.md +28 -0
- package/playbooks/stack/springboot/structure.md +30 -0
- package/playbooks/stack/springboot/testing.md +22 -0
- package/playbooks/stack/springboot.manifest.json +38 -0
- package/playbooks/styling/css-modules-extensions.md +267 -0
- package/playbooks/styling/css-modules.manifest.json +11 -0
- package/playbooks/styling/native-styles.manifest.json +9 -0
- package/playbooks/styling/native-styles.md +29 -0
- package/playbooks/styling/tailwind-extensions.md +182 -0
- package/playbooks/styling/tailwind.manifest.json +11 -0
- package/playbooks/universal/accessibility.manifest.json +11 -0
- package/playbooks/universal/accessibility.md +45 -0
- package/playbooks/universal/coding-rules.manifest.json +12 -0
- package/playbooks/universal/coding-rules.md +281 -0
- package/playbooks/universal/error-handling.manifest.json +10 -0
- package/playbooks/universal/error-handling.md +21 -0
- package/playbooks/universal/git-conventions.manifest.json +10 -0
- package/playbooks/universal/git-conventions.md +186 -0
- package/playbooks/universal/observability.manifest.json +10 -0
- package/playbooks/universal/observability.md +29 -0
- package/playbooks/universal/security.manifest.json +11 -0
- package/playbooks/universal/security.md +30 -0
- package/playbooks/universal/typescript.manifest.json +11 -0
- package/playbooks/universal/typescript.md +272 -0
- package/scripts/compatibility-matrix.mjs +48 -0
- package/scripts/validate-content.mjs +76 -0
- package/scripts/verify-generated.mjs +153 -0
- package/templates/agents/nextjs.md +47 -0
- package/templates/agents/react-native.md +48 -0
- package/templates/agents/react-vite.md +48 -0
- package/templates/docker/compose/postgres.yml +29 -0
- package/templates/docker/compose/springboot.yml +69 -0
- package/templates/docker/compose/supabase.yml +18 -0
- package/templates/docker/compose-prod/springboot.yml +64 -0
- package/templates/docker/dockerfile/laravel-inertia.dev.dockerfile +21 -0
- package/templates/docker/dockerfile/laravel.dev.dockerfile +12 -0
- package/templates/docker/dockerfile/laravel.prod.dockerfile +13 -0
- package/templates/docker/dockerfile/nextjs.dev.dockerfile +6 -0
- package/templates/docker/dockerfile/nextjs.prod.dockerfile +18 -0
- package/templates/docker/dockerfile/springboot.dev.dockerfile +5 -0
- package/templates/docker/dockerfile/springboot.prod.dockerfile +10 -0
- package/templates/docker/dockerfile/vite.dev.dockerfile +6 -0
- package/templates/docker/dockerfile/vite.prod.dockerfile +12 -0
- package/templates/gitignore/base.gitignore +32 -0
- package/templates/gitignore/nextjs.gitignore +40 -0
- package/templates/gitignore/react-native.gitignore +43 -0
- package/templates/gitignore/react-vite.gitignore +39 -0
- package/templates/makefile/frontend.mk +31 -0
- package/templates/makefile/laravel.mk +47 -0
- package/templates/makefile/postgres.mk +40 -0
- package/templates/makefile/springboot.mk +125 -0
- package/templates/makefile/supabase.mk +42 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Laravel Auth0 OIDC Resource Server
|
|
2
|
+
|
|
3
|
+
The generated adapter uses the pinned `auth0/login` package. Auth0 owns login,
|
|
4
|
+
Authorization Code with PKCE, access/refresh issuance,
|
|
5
|
+
rotation, recovery, and revocation. Laravel accepts access tokens only and validates the
|
|
6
|
+
signature against trusted keys plus issuer, audience, expiry/not-before, and required
|
|
7
|
+
permissions. Cache key discovery with safe rotation and fail closed. Never send refresh
|
|
8
|
+
tokens to this API. Configure the exact tenant domain and API audience; never accept an ID
|
|
9
|
+
token as an API access token. Test invalid signature, issuer, audience, time, permission,
|
|
10
|
+
and owner.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Laravel Queues
|
|
2
|
+
|
|
3
|
+
Jobs are small, idempotent, timeout-bounded, retry-aware, and safe when delivered more
|
|
4
|
+
than once. Dispatch after commit when a job depends on written records. Record terminal
|
|
5
|
+
failure and operational ownership. Use an outbox/relay when a database change and an
|
|
6
|
+
external message must be delivered reliably; a plain event or queued listener is not an
|
|
7
|
+
atomic cross-system guarantee.
|
|
8
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Sanctum Stateful SPA Authentication
|
|
2
|
+
|
|
3
|
+
Use this only for a first-party browser frontend sharing the Laravel application's parent
|
|
4
|
+
domain. Sanctum authenticates with Laravel's session cookie, not a browser bearer token.
|
|
5
|
+
Initialize CSRF protection, send credentials deliberately, configure exact stateful
|
|
6
|
+
domains/CORS origins, and authorize every resource server-side. Test CSRF rejection,
|
|
7
|
+
credentialed CORS, login/logout, expiry, and forbidden ownership.
|
|
8
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Laravel Scheduler
|
|
2
|
+
|
|
3
|
+
Scheduled commands are idempotent, observable, and protected from unintended overlap.
|
|
4
|
+
Use one scheduler trigger per environment, explicit time zones, distributed locking where
|
|
5
|
+
multiple instances run, and safe catch-up rules. Test the command behavior separately
|
|
6
|
+
from its clock registration.
|
|
7
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Laravel Session Authentication
|
|
2
|
+
|
|
3
|
+
Laravel owns the server session and sends only an HttpOnly, Secure cookie with a deployed
|
|
4
|
+
SameSite policy. Rotate the session after login and invalidate it plus its CSRF token on
|
|
5
|
+
logout. Keep CSRF protection on every cookie-authenticated mutation. Generate login,
|
|
6
|
+
logout, recovery, verification as selected, and test fixation resistance, expiry,
|
|
7
|
+
unauthenticated redirects/responses, forbidden resources, and logout invalidation.
|
|
8
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Laravel Storage and Uploads
|
|
2
|
+
|
|
3
|
+
Treat names, MIME claims, and content as untrusted. Enforce size/type/ownership limits,
|
|
4
|
+
generate server-owned object names, keep sensitive files off public disks, authorize
|
|
5
|
+
downloads, and use short-lived signed access where appropriate. Scan risky formats and
|
|
6
|
+
remove abandoned uploads. Never build filesystem paths directly from user input.
|
|
7
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# PostgreSQL Architecture
|
|
2
|
+
|
|
3
|
+
PostgreSQL is the durable source of truth. Application operations own transaction boundaries;
|
|
4
|
+
database constraints protect invariants under concurrency. Keep one authoritative migration history,
|
|
5
|
+
use connection pooling deliberately, and isolate privileged maintenance from normal application roles.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# PostgreSQL Migrations
|
|
2
|
+
|
|
3
|
+
Migrations are immutable after deployment. Review locks and table rewrites, prefer expand/contract
|
|
4
|
+
changes, separate long backfills, and keep application versions compatible during rollout. Production
|
|
5
|
+
execution needs a lock, backup/recovery plan, observability, and explicit approval for destructive SQL.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "postgres",
|
|
3
|
+
"kind": "database",
|
|
4
|
+
"label": "PostgreSQL",
|
|
5
|
+
"playbooks": ["capabilities/postgresql/architecture.md", "capabilities/postgresql/schema-design.md", "capabilities/postgresql/migrations.md", "capabilities/postgresql/security.md", "capabilities/postgresql/testing.md"],
|
|
6
|
+
"appliesTo": { "backend": ["postgres", "springboot"] },
|
|
7
|
+
"required": true,
|
|
8
|
+
"architectureProfiles": ["small", "medium", "large"],
|
|
9
|
+
"makefileTemplate": "postgres",
|
|
10
|
+
"folders": [],
|
|
11
|
+
"env": ["DATABASE_URL"],
|
|
12
|
+
"concerns": [
|
|
13
|
+
{ "id": "schema", "required": true, "playbook": "capabilities/postgresql/schema-design.md", "sections": ["Core Rules"] }
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# PostgreSQL Schema Design
|
|
2
|
+
|
|
3
|
+
## Core Rules
|
|
4
|
+
|
|
5
|
+
Use explicit primary/foreign keys, nullability, unique/check constraints, and timestamps with time
|
|
6
|
+
zone. Choose types for domain meaning, not convenience. Index foreign keys and measured filter/sort
|
|
7
|
+
paths; avoid speculative indexes. Define deletion behavior and tenant/resource ownership explicitly.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# PostgreSQL Security
|
|
2
|
+
|
|
3
|
+
Use least-privilege roles, TLS outside trusted local networks, rotated secrets, and separate migration
|
|
4
|
+
credentials. Parameterize values; allowlist identifiers. Never expose PostgreSQL directly to browsers
|
|
5
|
+
or mobile clients. Log operational context without statements/parameters containing secrets or PII.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# PostgreSQL Testing
|
|
2
|
+
|
|
3
|
+
Test migrations from empty and supported prior schemas against the target PostgreSQL major. Cover
|
|
4
|
+
constraints, conflicts, transaction rollback, locking/concurrency risks, query plans for critical
|
|
5
|
+
paths, and backup restoration. SQLite/H2 cannot prove PostgreSQL-specific behavior.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Prisma Architecture
|
|
2
|
+
|
|
3
|
+
Create one server-only Prisma client and inject/use it behind Next-owned data access. Browser code
|
|
4
|
+
never imports Prisma. Services own business operations; repositories are optional boundaries for
|
|
5
|
+
complex owned persistence, not wrappers around every generated method.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Prisma Migrations
|
|
2
|
+
|
|
3
|
+
## Development
|
|
4
|
+
|
|
5
|
+
Use `prisma migrate dev` only against disposable development data, review generated SQL, and commit
|
|
6
|
+
the schema and migration together. Never rewrite an applied migration.
|
|
7
|
+
|
|
8
|
+
## Production
|
|
9
|
+
|
|
10
|
+
Use `prisma migrate deploy` in one controlled release job. Never run `migrate reset` or development
|
|
11
|
+
migration commands against production. Use expand/contract changes and forward fixes.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "prisma",
|
|
3
|
+
"kind": "migration",
|
|
4
|
+
"label": "Prisma",
|
|
5
|
+
"playbooks": ["capabilities/prisma/architecture.md", "capabilities/prisma/schema.md", "capabilities/prisma/migrations.md", "capabilities/prisma/runtime.md", "capabilities/prisma/testing.md"],
|
|
6
|
+
"appliesTo": { "backend": ["postgres"] },
|
|
7
|
+
"required": true,
|
|
8
|
+
"folders": [],
|
|
9
|
+
"deps": ["@prisma/client", "@prisma/adapter-pg", "pg"],
|
|
10
|
+
"devDeps": ["prisma", "dotenv", "@types/pg"],
|
|
11
|
+
"concerns": [
|
|
12
|
+
{ "id": "migration", "required": true, "playbook": "capabilities/prisma/migrations.md", "sections": ["Development", "Production"] }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Prisma Runtime
|
|
2
|
+
|
|
3
|
+
Use the catalog-tested driver adapter, reuse the client, select only required data, paginate bounded
|
|
4
|
+
queries, avoid N+1 access, and allowlist sorting/filtering. Application operations own `$transaction`;
|
|
5
|
+
keep external network calls out of long database transactions.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Prisma Schema
|
|
2
|
+
|
|
3
|
+
Model real PostgreSQL constraints, relations, deletion behavior, mapped names, indexes, and deliberate
|
|
4
|
+
nullability. Keep generated client output out of hand-edited source. Validate application input and
|
|
5
|
+
still handle database uniqueness/foreign-key failures safely under races.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Prisma Testing
|
|
2
|
+
|
|
3
|
+
Unit-test pure rules without Prisma. Test repositories/queries and migrations against real PostgreSQL,
|
|
4
|
+
including constraints, relations, transactions, conflicts, and representative pagination. Reset an
|
|
5
|
+
isolated test database safely; never point tests at shared or production data.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Supabase Architecture
|
|
2
|
+
|
|
3
|
+
Supabase PostgreSQL, Auth, Storage, and Realtime are separate capabilities sharing an authorization
|
|
4
|
+
boundary. Normal clients use the publishable key plus user identity; RLS protects rows. Secret/service
|
|
5
|
+
operations run only in trusted server jobs and never share the normal client path.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Supabase Authentication
|
|
2
|
+
|
|
3
|
+
Supabase owns access/refresh issuance, persistence, rotation, and revocation. Next.js synchronizes SSR
|
|
4
|
+
cookies through Proxy and verifies claims; Vite uses browser SDK refresh; Expo stores sessions in
|
|
5
|
+
SecureStore and binds refresh to app lifecycle. Allowlist web callbacks and native deep links exactly.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Supabase with Expo
|
|
2
|
+
|
|
3
|
+
## Native Session
|
|
4
|
+
|
|
5
|
+
Persist Supabase session material with the generated SecureStore adapter, never
|
|
6
|
+
AsyncStorage. Disable URL-session detection intended for browsers. Start/stop automatic
|
|
7
|
+
refresh with application foreground/background lifecycle when required by the installed
|
|
8
|
+
SDK version, and handle revoked/expired sessions without a retry loop.
|
|
9
|
+
|
|
10
|
+
Register exact deep-link schemes and callback URLs for sign-in, verification, recovery,
|
|
11
|
+
and OAuth. Validate in-app destinations. `EXPO_PUBLIC_` values are public; only the
|
|
12
|
+
Supabase URL and publishable key belong there.
|
|
13
|
+
|
|
14
|
+
RLS remains authorization. A decoded token or local role flag is not permission to access
|
|
15
|
+
a row. Test session restore, foreground refresh, logout, deep-link rejection, and secure
|
|
16
|
+
storage failures on Android and iOS.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Supabase Migrations
|
|
2
|
+
|
|
3
|
+
## Local and Hosted Flow
|
|
4
|
+
|
|
5
|
+
The pinned local CLI owns `supabase/config.toml`, ordered SQL migrations, generated types, and pgTAP
|
|
6
|
+
tests. Develop/reset only locally, review diffs, test from empty state, and use a reviewed linked
|
|
7
|
+
deployment command in CI/release. Production reset is forbidden; deployed history receives a forward fix.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Supabase with Next.js
|
|
2
|
+
|
|
3
|
+
## Clients and Proxy
|
|
4
|
+
|
|
5
|
+
Use `@supabase/ssr` with three responsibilities:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Client Component → browser client
|
|
9
|
+
Server Component/Function/Route Handler → cookie-aware server client
|
|
10
|
+
Next Proxy → getClaims() + synchronized request/response cookies
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Proxy refresh keeps cookies current but is not authorization. Every protected server
|
|
14
|
+
operation verifies claims and lets RLS enforce row access. Do not trust `getSession()`'s
|
|
15
|
+
user object alone on the server. Use `getUser()` only when a fresh Auth user record is
|
|
16
|
+
specifically required.
|
|
17
|
+
|
|
18
|
+
Use PKCE callbacks. Allow only registered origins and validated application-relative
|
|
19
|
+
destinations; never redirect to an arbitrary query-string URL. Mark server/admin modules
|
|
20
|
+
`server-only`. The admin client is not imported into Client Components.
|
|
21
|
+
|
|
22
|
+
Repositories under a Next feature may use the authenticated server client when Next owns
|
|
23
|
+
the application operation. Preserve the caller's RLS context instead of automatically
|
|
24
|
+
switching to a secret client.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Supabase Row-Level Security
|
|
2
|
+
|
|
3
|
+
## Grants and Policies
|
|
4
|
+
|
|
5
|
+
Enable RLS, grant only required table operations to explicit roles, and write separate policies for
|
|
6
|
+
select/insert/update/delete with both `using` and `with check` where applicable. Use `(select auth.uid())`,
|
|
7
|
+
index policy columns, enforce ownership beside API operations, and test owner/non-owner/anonymous paths.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "supabase",
|
|
3
|
+
"kind": "database",
|
|
4
|
+
"label": "Supabase",
|
|
5
|
+
"appliesTo": { "backend": ["supabase"] },
|
|
6
|
+
"required": true,
|
|
7
|
+
"architectureProfiles": ["small", "medium", "large"],
|
|
8
|
+
"playbooks": [
|
|
9
|
+
"capabilities/supabase/architecture.md",
|
|
10
|
+
"capabilities/supabase/migrations.md",
|
|
11
|
+
"capabilities/supabase/rls.md",
|
|
12
|
+
"capabilities/supabase/authentication.md",
|
|
13
|
+
"capabilities/supabase/testing.md",
|
|
14
|
+
{ "file": "capabilities/supabase/nextjs.md", "appliesWhen": { "frontend": "nextjs" } },
|
|
15
|
+
{ "file": "capabilities/supabase/vite.md", "appliesWhen": { "frontend": "react" } },
|
|
16
|
+
{ "file": "capabilities/supabase/expo.md", "appliesWhen": { "frontend": "react-native" } },
|
|
17
|
+
"capabilities/supabase/testing.md"
|
|
18
|
+
],
|
|
19
|
+
"makefileTemplate": "supabase",
|
|
20
|
+
"folders": [],
|
|
21
|
+
"deps": ["@supabase/ssr", "@supabase/supabase-js"],
|
|
22
|
+
"devDeps": ["supabase"],
|
|
23
|
+
"env": ["SUPABASE_URL", "SUPABASE_PUBLISHABLE_KEY"],
|
|
24
|
+
"clientEnv": ["SUPABASE_URL", "SUPABASE_PUBLISHABLE_KEY"],
|
|
25
|
+
"concerns": [
|
|
26
|
+
{ "id": "supabase-runtime", "required": true, "playbook": "capabilities/supabase/migrations.md", "sections": ["Local and Hosted Flow"] },
|
|
27
|
+
{ "id": "rls", "required": true, "playbook": "capabilities/supabase/rls.md", "sections": ["Grants and Policies"] },
|
|
28
|
+
{ "id": "supabase-next", "required": true, "appliesWhen": { "frontend": "nextjs" }, "playbook": "capabilities/supabase/nextjs.md", "sections": ["Clients and Proxy"] },
|
|
29
|
+
{ "id": "supabase-vite", "required": true, "appliesWhen": { "frontend": "react" }, "playbook": "capabilities/supabase/vite.md", "sections": ["Browser Session"] },
|
|
30
|
+
{ "id": "supabase-expo", "required": true, "appliesWhen": { "frontend": "react-native" }, "playbook": "capabilities/supabase/expo.md", "sections": ["Native Session"] }
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Supabase Testing
|
|
2
|
+
|
|
3
|
+
Run migrations and pgTAP against the local stack. Include allow and deny RLS cases for anonymous,
|
|
4
|
+
authenticated owner, authenticated non-owner, and trusted system operations. Test callback/deep-link
|
|
5
|
+
allowlists, expiry, refresh, logout/revocation, generated types, storage policies, and destructive safety.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Supabase with Vite
|
|
2
|
+
|
|
3
|
+
## Browser Session
|
|
4
|
+
|
|
5
|
+
The browser SDK owns session persistence and refresh. Do not create a second refresh-token
|
|
6
|
+
interceptor or copy credentials into an application store. UI route guards prevent visual
|
|
7
|
+
confusion; grants and RLS authorize every database operation.
|
|
8
|
+
|
|
9
|
+
Only `VITE_SUPABASE_URL` and the publishable key enter browser code. A secret/service-role
|
|
10
|
+
key, database password, or OAuth client secret is never a Vite environment value.
|
|
11
|
+
|
|
12
|
+
Place Supabase queries in feature `data.ts` functions, validate data crossing external
|
|
13
|
+
boundaries, and clean up Realtime channels. Handle signed-out, expired, revoked, loading,
|
|
14
|
+
and retry states without refresh loops.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Axios — HTTP Client
|
|
2
|
+
|
|
3
|
+
> **When to use:** Any stack that communicates with a REST API via a typed axios client.
|
|
4
|
+
> Next.js uses `apiFetch` (server-only). React Vite and React Native use this pattern.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 1. One Shared Client
|
|
9
|
+
|
|
10
|
+
Never create ad-hoc axios instances per feature. One shared `apiClient` for all HTTP calls.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// lib/apiClient.ts
|
|
14
|
+
import axios from 'axios'
|
|
15
|
+
import { ENV } from '@/config/env' // RN
|
|
16
|
+
// import { env } from '@/lib/env' // React Vite — adjust import to your env helper
|
|
17
|
+
|
|
18
|
+
const http = axios.create({
|
|
19
|
+
baseURL: ENV.API_BASE_URL,
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
timeout: 10_000,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
http.interceptors.request.use(async (config) => {
|
|
25
|
+
const token = await tokenStorage.get() // RN: expo-secure-store
|
|
26
|
+
// Browser apps should prefer same-origin HttpOnly cookies. If bearer access
|
|
27
|
+
// tokens are required, keep the short-lived access token in memory.
|
|
28
|
+
if (token) config.headers.Authorization = `Bearer ${token}`
|
|
29
|
+
return config
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
http.interceptors.response.use(
|
|
33
|
+
(response) => response.data,
|
|
34
|
+
(error) => {
|
|
35
|
+
const appError: AppError = {
|
|
36
|
+
status: error.response?.status ?? 0,
|
|
37
|
+
code: error.response?.data?.code ?? 'NETWORK_ERROR',
|
|
38
|
+
message: error.response?.data?.message ?? error.message,
|
|
39
|
+
}
|
|
40
|
+
return Promise.reject(appError)
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
export const apiClient = {
|
|
45
|
+
get: <T>(path: string) => http.get<T, T>(path),
|
|
46
|
+
post: <T>(path: string, body?: unknown) => http.post<T, T>(path, body),
|
|
47
|
+
put: <T>(path: string, body?: unknown) => http.put<T, T>(path, body),
|
|
48
|
+
patch: <T>(path: string, body?: unknown) => http.patch<T, T>(path, body),
|
|
49
|
+
delete: <T>(path: string) => http.delete<T, T>(path),
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# 2. AppError Type
|
|
56
|
+
|
|
57
|
+
Errors are normalized in the response interceptor. Everything above sees `AppError` only.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// types/errors.ts (web: src/types/errors.ts | RN: types/errors.ts)
|
|
61
|
+
export type AppError = {
|
|
62
|
+
status: number
|
|
63
|
+
code: string
|
|
64
|
+
message: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isAppError(err: unknown): err is AppError {
|
|
68
|
+
return typeof err === 'object' && err !== null && 'code' in err && 'status' in err
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
# 3. Service Calls apiClient, Hook Calls Service
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// features/products/services/productService.ts
|
|
78
|
+
import { apiClient } from '@/lib/apiClient'
|
|
79
|
+
|
|
80
|
+
export const productService = {
|
|
81
|
+
getAll: (): Promise<Product[]> => apiClient.get('/products'),
|
|
82
|
+
getById: (id: string): Promise<Product> => apiClient.get(`/products/${id}`),
|
|
83
|
+
create: (input: CreateProductInput) => apiClient.post<Product>('/products', input),
|
|
84
|
+
delete: (id: string): Promise<void> => apiClient.delete(`/products/${id}`),
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Hooks call `productService`. Never call `apiClient` directly from a hook.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
# 4. Rules
|
|
93
|
+
|
|
94
|
+
- One `apiClient` instance. Never create new axios instances per feature.
|
|
95
|
+
- Auth token injection → request interceptor only. Not in every service call.
|
|
96
|
+
- Error normalization → response interceptor only. Services receive `AppError`.
|
|
97
|
+
- Route on `error.code`, never `error.message` — codes are stable, messages change.
|
|
98
|
+
- Services are the only callers of `apiClient`. Hooks call services.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
# 5. Platform Notes
|
|
103
|
+
|
|
104
|
+
> **React Native** — token storage uses `expo-secure-store` via a `tokenStorage` helper.
|
|
105
|
+
> Never use `localStorage` in React Native — it does not exist.
|
|
106
|
+
|
|
107
|
+
> **React Vite** — prefer a same-origin `HttpOnly` session/refresh cookie. Do not
|
|
108
|
+
> store session IDs, access tokens, or refresh tokens in `localStorage` or
|
|
109
|
+
> `sessionStorage`. Cookie-authenticated mutations also require CSRF protection.
|
|
110
|
+
> See `playbooks/universal/security.md`.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
# 6. Agent Quick Reference
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
New API endpoint? → new method in features/[name]/services/[name]Service.ts
|
|
118
|
+
→ calls apiClient, never raw fetch/axios
|
|
119
|
+
Hook needs data? → call service from hook, not apiClient directly
|
|
120
|
+
Auth not attaching? → check request interceptor in lib/apiClient.ts
|
|
121
|
+
Error not normalized? → check response interceptor in lib/apiClient.ts
|
|
122
|
+
New error code to handle? → route on error.code in the hook or component
|
|
123
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# next-safe-action — Type-Safe Server Actions (Next.js)
|
|
2
|
+
|
|
3
|
+
> **When to use:** Next.js projects using Server Actions that want typed, validated,
|
|
4
|
+
> and composable action middleware (auth guards, error handling).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 1. Setup
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
// src/lib/safe-action.ts
|
|
12
|
+
import { createSafeActionClient } from 'next-safe-action'
|
|
13
|
+
|
|
14
|
+
// Unauthenticated actions
|
|
15
|
+
export const action = createSafeActionClient()
|
|
16
|
+
|
|
17
|
+
// Authenticated actions — throws if no session
|
|
18
|
+
export const authAction = createSafeActionClient({
|
|
19
|
+
async middleware() {
|
|
20
|
+
const user = await getCurrentUser()
|
|
21
|
+
if (!user) throw new Error('UNAUTHORIZED')
|
|
22
|
+
return { user }
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# 2. Defining an Action
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
// features/users/actions/createUser.action.ts
|
|
33
|
+
'use server'
|
|
34
|
+
import { authAction } from '@/lib/safe-action'
|
|
35
|
+
import { createUserSchema } from '@/features/users/schemas/user.schema'
|
|
36
|
+
|
|
37
|
+
export const createUserAction = authAction
|
|
38
|
+
.schema(createUserSchema)
|
|
39
|
+
.action(async ({ parsedInput: { name, email }, ctx: { user } }) => {
|
|
40
|
+
// Input already validated by Zod, user already confirmed by middleware
|
|
41
|
+
const created = await userService.create({ name, email, createdBy: user.id })
|
|
42
|
+
revalidatePath('/users')
|
|
43
|
+
return { user: created }
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
# 3. Rules
|
|
50
|
+
|
|
51
|
+
- Every server action that touches protected data uses `authAction`, not `action`.
|
|
52
|
+
- Always call `revalidatePath` or `revalidateTag` after mutations that affect cached pages.
|
|
53
|
+
- Actions are entry points — business logic lives in services, not inside `.action()`.
|
|
54
|
+
- Schema validation is automatic — never manually parse `parsedInput`.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
# 4. Agent Quick Reference
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
New server action? → features/[name]/actions/[name].action.ts
|
|
62
|
+
Public action (no auth)? → action.schema(schema).action(...)
|
|
63
|
+
Protected action? → authAction.schema(schema).action(...)
|
|
64
|
+
After mutation? → revalidatePath('/affected-path')
|
|
65
|
+
Business logic in action? → move to a service, call from action
|
|
66
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# next-themes — Dark Mode (Next.js)
|
|
2
|
+
|
|
3
|
+
> **When to use:** Next.js projects that need system-aware or user-toggled dark mode
|
|
4
|
+
> with Tailwind CSS.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 1. Provider Setup
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
// src/app/providers.tsx
|
|
12
|
+
'use client'
|
|
13
|
+
import { ThemeProvider } from 'next-themes'
|
|
14
|
+
|
|
15
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
16
|
+
return (
|
|
17
|
+
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
|
18
|
+
{children}
|
|
19
|
+
</ThemeProvider>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
// src/app/layout.tsx
|
|
26
|
+
import { Providers } from './providers'
|
|
27
|
+
|
|
28
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
29
|
+
return (
|
|
30
|
+
<html lang="en" suppressHydrationWarning>
|
|
31
|
+
<body>
|
|
32
|
+
<Providers>{children}</Providers>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
# 2. Toggle Component
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// src/components/ui/ThemeToggle.tsx
|
|
45
|
+
'use client'
|
|
46
|
+
import { useTheme } from 'next-themes'
|
|
47
|
+
|
|
48
|
+
export function ThemeToggle() {
|
|
49
|
+
const { theme, setTheme } = useTheme()
|
|
50
|
+
return (
|
|
51
|
+
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
|
|
52
|
+
{theme === 'dark' ? 'Light' : 'Dark'}
|
|
53
|
+
</button>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
# 3. Tailwind Config
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// tailwind.config.ts
|
|
64
|
+
export default {
|
|
65
|
+
darkMode: 'class', // controlled by next-themes
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
# 4. Rules
|
|
72
|
+
|
|
73
|
+
- Always add `suppressHydrationWarning` to `<html>` — prevents hydration mismatch on theme read.
|
|
74
|
+
- Use Tailwind `dark:` variants for all dark mode styles — not inline style checks.
|
|
75
|
+
- `defaultTheme: 'system'` is the right default — respects user OS preference.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
# 5. Agent Quick Reference
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
Add dark mode? → ThemeProvider in app/providers.tsx
|
|
83
|
+
→ tailwind.config.ts darkMode: 'class'
|
|
84
|
+
Toggle theme? → useTheme() from next-themes
|
|
85
|
+
Apply dark style? → Tailwind dark: variant (dark:bg-gray-900)
|
|
86
|
+
Hydration mismatch? → add suppressHydrationWarning to <html>
|
|
87
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# nuqs — URL State Management (Next.js)
|
|
2
|
+
|
|
3
|
+
> **When to use:** Filters, search, pagination, tabs — any state that belongs in the URL
|
|
4
|
+
> so it survives refresh and is shareable via link.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 1. Basic Usage
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
'use client'
|
|
12
|
+
import { useQueryState, parseAsInteger, parseAsString } from 'nuqs'
|
|
13
|
+
|
|
14
|
+
export function UserFilters() {
|
|
15
|
+
const [search, setSearch] = useQueryState('search', parseAsString.withDefault(''))
|
|
16
|
+
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1))
|
|
17
|
+
const [role, setRole] = useQueryState('role', parseAsString)
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div>
|
|
21
|
+
<input value={search} onChange={(e) => setSearch(e.target.value)} />
|
|
22
|
+
{/* URL auto-updates: /users?search=john&page=2&role=ADMIN */}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
# 2. nuqs vs Zustand Decision
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
nuqs (URL state):
|
|
34
|
+
→ search queries, pagination (page/limit), active filters,
|
|
35
|
+
selected tab that should survive refresh, anything shareable via URL
|
|
36
|
+
|
|
37
|
+
Zustand (memory state):
|
|
38
|
+
→ modal open/close, sidebar state, wizard steps,
|
|
39
|
+
anything that should reset on page refresh or navigation
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
# 3. Rules
|
|
45
|
+
|
|
46
|
+
- Use nuqs for anything the user might want to bookmark, share, or return to via back button.
|
|
47
|
+
- Use Zustand for ephemeral UI state that should reset when the user navigates away.
|
|
48
|
+
- Never duplicate URL state in component `useState` — read from `useQueryState` directly.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
# 4. Agent Quick Reference
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
Filter / search / pagination? → nuqs useQueryState
|
|
56
|
+
Tab that survives refresh? → nuqs useQueryState
|
|
57
|
+
Modal open state? → useState (or Zustand if shared)
|
|
58
|
+
State that resets on nav? → useState or Zustand
|
|
59
|
+
```
|