@donotdev/cli 0.0.14 → 0.0.15

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 (151) hide show
  1. package/dependencies-matrix.json +356 -88
  2. package/dist/bin/commands/agent-setup.js +7 -1
  3. package/dist/bin/commands/build.js +118 -38
  4. package/dist/bin/commands/bump.js +74 -28
  5. package/dist/bin/commands/cacheout.js +37 -9
  6. package/dist/bin/commands/create-app.js +222 -115
  7. package/dist/bin/commands/create-project.js +455 -140
  8. package/dist/bin/commands/deploy.js +1736 -697
  9. package/dist/bin/commands/dev.js +138 -23
  10. package/dist/bin/commands/emu.js +215 -58
  11. package/dist/bin/commands/format.js +37 -9
  12. package/dist/bin/commands/lint.js +37 -9
  13. package/dist/bin/commands/preview.js +142 -23
  14. package/dist/bin/commands/supabase-setup.d.ts +6 -0
  15. package/dist/bin/commands/supabase-setup.d.ts.map +1 -0
  16. package/dist/bin/commands/supabase-setup.js +7 -0
  17. package/dist/bin/commands/supabase-setup.js.map +1 -0
  18. package/dist/bin/commands/sync-secrets.js +211 -34
  19. package/dist/bin/commands/type-check.d.ts +14 -0
  20. package/dist/bin/commands/type-check.d.ts.map +1 -0
  21. package/dist/bin/commands/type-check.js +314 -0
  22. package/dist/bin/commands/type-check.js.map +1 -0
  23. package/dist/bin/commands/wai.js +3 -1
  24. package/dist/bin/dndev.js +27 -2
  25. package/dist/bin/donotdev.js +27 -2
  26. package/dist/index.js +3960 -3015
  27. package/package.json +2 -2
  28. package/templates/app-demo/src/App.tsx.example +1 -0
  29. package/templates/app-demo/src/pages/FullPage.tsx.example +2 -2
  30. package/templates/app-demo/src/pages/components/DemoLayout.tsx.example +2 -2
  31. package/templates/app-demo/src/themes.css.example +5 -12
  32. package/templates/app-expo/.env.example +64 -0
  33. package/templates/app-expo/.expo/README.md.example +5 -0
  34. package/templates/app-expo/.gitignore.example +36 -0
  35. package/templates/app-expo/README.md.example +58 -0
  36. package/templates/app-expo/app/.gitkeep +2 -0
  37. package/templates/app-expo/app/_layout.tsx.example +41 -0
  38. package/templates/app-expo/app/form.tsx.example +52 -0
  39. package/templates/app-expo/app/index.tsx.example +89 -0
  40. package/templates/app-expo/app/list.tsx.example +32 -0
  41. package/templates/app-expo/app/profile.tsx.example +76 -0
  42. package/templates/app-expo/app/signin.tsx.example +53 -0
  43. package/templates/app-expo/app.json.example +39 -0
  44. package/templates/app-expo/babel.config.js.example +10 -0
  45. package/templates/app-expo/eas.json.example +20 -0
  46. package/templates/app-expo/expo-env.d.ts.example +4 -0
  47. package/templates/app-expo/metro.config.js.example +20 -0
  48. package/templates/app-expo/service-account-key.json.example +12 -0
  49. package/templates/app-expo/tsconfig.json.example +19 -0
  50. package/templates/app-next/.env.example +4 -33
  51. package/templates/app-next/src/app/ClientLayout.tsx.example +2 -0
  52. package/templates/app-next/src/app/layout.tsx.example +7 -6
  53. package/templates/app-next/src/globals.css.example +2 -11
  54. package/templates/app-next/src/pages/HomePage.tsx.example +1 -1
  55. package/templates/app-next/src/themes.css.example +10 -13
  56. package/templates/app-vite/.env.example +3 -32
  57. package/templates/app-vite/index.html.example +2 -24
  58. package/templates/app-vite/src/App.tsx.example +2 -0
  59. package/templates/app-vite/src/globals.css.example +2 -12
  60. package/templates/app-vite/src/pages/FormPageExample.tsx.example +1 -2
  61. package/templates/app-vite/src/pages/HomePage.tsx.example +1 -1
  62. package/templates/app-vite/src/themes.css.example +109 -79
  63. package/templates/app-vite/vercel.json.example +11 -0
  64. package/templates/functions-firebase/build.mjs.example +2 -72
  65. package/templates/functions-firebase/functions-firebase/.env.example.example +23 -25
  66. package/templates/functions-firebase/functions-firebase/build.mjs.example +2 -72
  67. package/templates/functions-firebase/functions-firebase/tsconfig.json.example +1 -1
  68. package/templates/functions-supabase/supabase/functions/cancel-subscription/index.ts.example +7 -0
  69. package/templates/functions-supabase/supabase/functions/change-plan/index.ts.example +11 -0
  70. package/templates/functions-supabase/supabase/functions/create-checkout-session/index.ts.example +11 -0
  71. package/templates/functions-supabase/supabase/functions/create-customer-portal/index.ts.example +7 -0
  72. package/templates/functions-supabase/supabase/functions/crud/index.ts.example +16 -0
  73. package/templates/functions-supabase/supabase/functions/delete-account/index.ts.example +7 -0
  74. package/templates/functions-supabase/supabase/functions/get-custom-claims/index.ts.example +7 -0
  75. package/templates/functions-supabase/supabase/functions/get-user-auth-status/index.ts.example +7 -0
  76. package/templates/functions-supabase/supabase/functions/refresh-subscription-status/index.ts.example +7 -0
  77. package/templates/functions-supabase/supabase/functions/remove-custom-claims/index.ts.example +7 -0
  78. package/templates/functions-supabase/supabase/functions/set-custom-claims/index.ts.example +7 -0
  79. package/templates/functions-supabase/supabase/migrations/20250101000000_idempotency.sql +24 -0
  80. package/templates/functions-supabase/supabase/migrations/20250101000001_rate_limits.sql +22 -0
  81. package/templates/functions-supabase/supabase/migrations/20250101000002_cleanup_jobs.sql +28 -0
  82. package/templates/functions-supabase/supabase/migrations/20250101000003_operation_metrics.sql +28 -0
  83. package/templates/functions-vercel/functions-vercel/tsconfig.json.example +1 -1
  84. package/templates/functions-vercel/functions-vercel/vercel.json.example +1 -1
  85. package/templates/functions-vercel/vercel.json.example +1 -1
  86. package/templates/github/github/workflows/firebase-deploy.yml.example +1 -1
  87. package/templates/github/workflows/firebase-deploy.yml.example +1 -1
  88. package/templates/overlay-firebase/env.fragment.example +34 -0
  89. package/templates/overlay-firebase/env.fragment.expo.example +34 -0
  90. package/templates/overlay-firebase/env.fragment.nextjs.example +34 -0
  91. package/templates/overlay-firebase/src/config/providers.expo.ts.example +49 -0
  92. package/templates/overlay-firebase/src/config/providers.ts.example +23 -0
  93. package/templates/overlay-supabase/env.fragment.example +7 -0
  94. package/templates/overlay-supabase/env.fragment.expo.example +7 -0
  95. package/templates/overlay-supabase/env.fragment.nextjs.example +7 -0
  96. package/templates/overlay-supabase/src/config/providers.expo.ts.example +35 -0
  97. package/templates/overlay-supabase/src/config/providers.ts.example +33 -0
  98. package/templates/overlay-supabase/vercel.headers.example +23 -0
  99. package/templates/overlay-supabase/vercel.json.example +22 -0
  100. package/templates/overlay-vercel/env.fragment.example +34 -0
  101. package/templates/overlay-vercel/env.fragment.nextjs.example +34 -0
  102. package/templates/overlay-vercel/src/config/providers.ts.example +24 -0
  103. package/templates/root-consumer/.claude/agents/architect.md.example +2 -310
  104. package/templates/root-consumer/.claude/agents/builder.md.example +2 -326
  105. package/templates/root-consumer/.claude/agents/coder.md.example +2 -83
  106. package/templates/root-consumer/.claude/agents/extractor.md.example +2 -231
  107. package/templates/root-consumer/.claude/agents/polisher.md.example +2 -132
  108. package/templates/root-consumer/.claude/agents/prompt-engineer.md.example +2 -81
  109. package/templates/root-consumer/.claude/commands/grill.md.example +30 -0
  110. package/templates/root-consumer/.claude/commands/techdebt.md.example +28 -0
  111. package/templates/root-consumer/.clinerules.example +1 -0
  112. package/templates/root-consumer/.cursor/rules/no-docs.mdc.example +15 -0
  113. package/templates/root-consumer/.cursorrules.example +1 -0
  114. package/templates/root-consumer/.github/copilot-instructions.md.example +1 -0
  115. package/templates/root-consumer/.windsurfrules.example +1 -0
  116. package/templates/root-consumer/AI.md.example +29 -123
  117. package/templates/root-consumer/CLAUDE.md.example +1 -134
  118. package/templates/root-consumer/CONVENTIONS.md.example +1 -0
  119. package/templates/root-consumer/GEMINI.md.example +1 -0
  120. package/templates/root-consumer/firebase.json.example +1 -1
  121. package/templates/root-consumer/guides/dndev/AGENT_START_HERE.md.example +20 -0
  122. package/templates/root-consumer/guides/dndev/COMPONENTS_ADV.md.example +0 -18
  123. package/templates/root-consumer/guides/dndev/COMPONENTS_UI.md.example +1 -1
  124. package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +99 -30
  125. package/templates/root-consumer/guides/dndev/INDEX.md.example +3 -1
  126. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +143 -12
  127. package/templates/root-consumer/guides/dndev/SETUP_FIREBASE.md.example +9 -3
  128. package/templates/root-consumer/guides/dndev/SETUP_SOC2.md.example +234 -0
  129. package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +124 -0
  130. package/templates/root-consumer/guides/dndev/SETUP_THEMES.md.example +6 -2
  131. package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +176 -0
  132. package/templates/root-consumer/guides/dndev/USE_ROUTING.md.example +5 -9
  133. package/templates/root-consumer/guides/dndev/essences_reference.css.example +174 -0
  134. package/templates/root-consumer/guides/wai-way/agents/builder.md.example +10 -0
  135. package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +25 -5
  136. package/templates/root-consumer/guides/wai-way/agents/polisher.md.example +13 -2
  137. package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +2 -2
  138. package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +47 -11
  139. package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +15 -4
  140. package/templates/root-consumer/guides/wai-way/spec_template.md.example +7 -6
  141. package/templates/app-payload/.env.example +0 -28
  142. package/templates/app-payload/README.md.example +0 -233
  143. package/templates/app-payload/collections/Company.ts.example +0 -125
  144. package/templates/app-payload/collections/Hero.ts.example +0 -62
  145. package/templates/app-payload/collections/Media.ts.example +0 -41
  146. package/templates/app-payload/collections/Products.ts.example +0 -115
  147. package/templates/app-payload/collections/Services.ts.example +0 -104
  148. package/templates/app-payload/collections/Testimonials.ts.example +0 -92
  149. package/templates/app-payload/collections/Users.ts.example +0 -35
  150. package/templates/app-payload/src/server.ts.example +0 -79
  151. package/templates/app-payload/tsconfig.json.example +0 -24
@@ -335,24 +335,6 @@ import { StartChallengeForm } from '@donotdev/adv-comps';
335
335
  />
336
336
  ```
337
337
 
338
- ### Waterfall
339
-
340
- Waterfall component with lazy loading built-in. Features clean cascade layout with diagonal staircase effect, perfect for step-by-step processes or feature showcases.
341
-
342
- ```tsx
343
- import { Waterfall } from '@donotdev/adv-comps';
344
- import type { WaterfallProps } from '@donotdev/adv-comps';
345
-
346
- <Waterfall
347
- items: ComponentData[]
348
- className?: string
349
- connectorClassName?: string
350
- density?: 'compact' | 'comfortable'
351
- ariaLabel?: string
352
- direction?: 'horizontal' | 'descending'
353
- />
354
- ```
355
-
356
338
  ## Notes
357
339
 
358
340
  - All components are lazy-loaded by default for optimal performance
@@ -83,7 +83,7 @@
83
83
  - **usePrefetch** - Prefetch route data.
84
84
  - **useLocation** - Get current location.
85
85
  - **useParams** - Get route parameters.
86
- - **useSearchParams** - Get URL search parameters.
86
+ - **useSearchParams** - Get URL search parameters. Returns `URLSearchParams` directly (NOT a tuple). Use `.get('key')` to read values.
87
87
  - **useMatch** - Match current route pattern.
88
88
  - **useQueryParams** - Get and set query parameters.
89
89
  - **useRedirectGuard** - Guard against redirects.
@@ -2,16 +2,20 @@
2
2
 
3
3
  **The complete onboarding flow — from `dndev init` to deployed app.**
4
4
 
5
+ If you haven’t run `dndev init` yet, see [AGENT_START_HERE.md](./AGENT_START_HERE.md) § **Getting started (for humans)** (P0 → Install CLI → Run AI.md → Enjoy).
6
+
5
7
  ---
6
8
 
7
9
  ## The Flow
8
10
 
9
11
  ```
10
- bun install install dependencies
11
- bun dev start app, read the homepage setup guide
12
- dndev firebase:setup configure Firebase project + .env
13
- dndev emu start → test locally with emulators
14
- dndev deploy → deploy to production
12
+ bun install -> install dependencies
13
+ bun dev -> start app, read the homepage setup guide
14
+ dndev firebase:setup -> configure Firebase project + .env
15
+ -- or --
16
+ dndev supabase:setup -> configure Supabase project + .env
17
+ dndev emu start -> test locally with emulators (Firebase)
18
+ dndev deploy -> deploy to production
15
19
  ```
16
20
 
17
21
  ---
@@ -43,29 +47,45 @@ git push -u origin main
43
47
 
44
48
  ---
45
49
 
46
- ## Step 3: Firebase
50
+ ## Step 3: Backend Setup
51
+
52
+ ### Firebase
47
53
 
48
54
  ```bash
49
55
  dndev firebase:setup
50
56
  ```
51
57
 
52
- Automates: project selection/creation, web app, SDK config `.env`, `.firebaserc`.
58
+ Automates: project selection/creation, web app, SDK config -> `.env`, `.firebaserc`.
53
59
 
54
60
  Then 2 manual steps (CLI gives you direct links):
55
- 1. Download service account key save as `service-account-key.json`
61
+ 1. Download service account key -> save as `service-account-key.json`
56
62
  2. Enable Auth + Firestore in Firebase Console
57
63
 
58
64
  See [SETUP_FIREBASE.md](./SETUP_FIREBASE.md) for full details.
59
65
 
66
+ ### Supabase
67
+
68
+ ```bash
69
+ dndev supabase:setup
70
+ ```
71
+
72
+ Asks for your **public** project URL and anon key (both safe to share -- shipped in client bundle).
73
+
74
+ Get them from: https://supabase.com/dashboard -> your project -> Settings -> API
75
+
76
+ See [SETUP_SUPABASE.md](./SETUP_SUPABASE.md) for full details (tables, RLS, `dn generate sql`). See [Secrets Philosophy](#secrets-philosophy) for how we handle secret keys.
77
+
60
78
  ---
61
79
 
62
80
  ## Step 4: Test Locally
63
81
 
64
82
  ```bash
65
- dndev emu start
83
+ dndev emu start # Firebase emulators
66
84
  ```
67
85
 
68
- Select Auth + Firestore + Functions. Develops against local emulators.
86
+ Select services: Auth + Firestore + Functions. This starts Firebase emulators so you can develop without touching production.
87
+
88
+ For Supabase, development runs against your Supabase project directly (or use `supabase start` for local Supabase if installed).
69
89
 
70
90
  ---
71
91
 
@@ -74,11 +94,11 @@ Select Auth + Firestore + Functions. Develops against local emulators.
74
94
  Open the project in **Cursor**, **Claude Code**, **Windsurf**, or **AntiGravity**.
75
95
 
76
96
  The AI reads `AI.md` and follows the WAI-WAY protocol:
77
- - Phase 0: Brainstorm produce a spec
78
- - Phase 1: Scaffold create pages
79
- - Phase 2: Entities define data models
80
- - Phase 3: Compose build pages with components
81
- - Phase 4: Configure generate tests, firestore rules, CI/CD
97
+ - Phase 0: Brainstorm -> produce a spec
98
+ - Phase 1: Scaffold -> create pages
99
+ - Phase 2: Entities -> define data models
100
+ - Phase 3: Compose -> build pages with components
101
+ - Phase 4: Configure -> generate tests, firestore rules, CI/CD
82
102
 
83
103
  Read `guides/wai-way/WAI_WAY_CLI.md` for the full workflow.
84
104
 
@@ -90,7 +110,51 @@ Read `guides/wai-way/WAI_WAY_CLI.md` for the full workflow.
90
110
  dndev deploy
91
111
  ```
92
112
 
93
- Deploys hosting + functions + rules. Cloud Run IAM handled automatically.
113
+ Deploys hosting + functions + rules. Syncs runtime secrets automatically before deploying functions.
114
+
115
+ ---
116
+
117
+ ## Secrets Philosophy
118
+
119
+ **DoNotDev follows strict rules about credentials:**
120
+
121
+ ### Tier 1: Public Keys -- We Ask Directly
122
+
123
+ These are safe to share, shipped in your client JS bundle. We ask for them during setup.
124
+
125
+ | Key | Where It Goes |
126
+ |-----|--------------|
127
+ | Firebase API key, project ID, auth domain, etc. | `apps/<app>/.env` as `VITE_FIREBASE_*` |
128
+ | Supabase project URL | `apps/<app>/.env` as `VITE_SUPABASE_URL` |
129
+ | Supabase anon key (public JWT) | `apps/<app>/.env` as `VITE_SUPABASE_ANON_KEY` |
130
+ | Stripe publishable key | `apps/<app>/.env` as `VITE_STRIPE_PUBLISHABLE_KEY` |
131
+
132
+ ### Tier 2: Secret Keys -- We NEVER Ask, We Tell You Where To Put Them
133
+
134
+ These are server-side only. We never prompt for them, never store them in client code.
135
+
136
+ | Key | Where It Goes | How To Get It |
137
+ |-----|--------------|---------------|
138
+ | Stripe secret key | `functions/.env` as `STRIPE_SECRET_KEY` | https://dashboard.stripe.com/apikeys |
139
+ | Stripe webhook secret | `functions/.env` as `STRIPE_WEBHOOK_SECRET` | Stripe Dashboard -> Webhooks |
140
+ | Supabase service_role key | `functions/.env` as `SUPABASE_SERVICE_ROLE_KEY` | Supabase Dashboard -> Settings -> API |
141
+ | OAuth client secrets | `functions/.env` as `*_CLIENT_SECRET` | Provider dashboard |
142
+
143
+ Then sync to your runtime:
144
+
145
+ ```bash
146
+ dndev sync-secrets # -> Firebase Secret Manager or Vercel env
147
+ dndev sync-secrets --target github # -> GitHub Secrets (for CI/CD)
148
+ ```
149
+
150
+ ### Tier 3: Service Account Files -- Download, Place, Gitignore
151
+
152
+ | File | Where It Goes | How To Get It |
153
+ |------|--------------|---------------|
154
+ | `service-account-key.json` | App root (next to firebase.json) | Firebase Console -> Settings -> Service Accounts |
155
+ | `service-account-key.staging.json` | Same location, for staging | Same, from staging project |
156
+
157
+ These files are `.gitignored`. Never commit them. For CI/CD, upload the file content as a GitHub Secret and decode it in your workflow.
94
158
 
95
159
  ---
96
160
 
@@ -100,18 +164,21 @@ Deploys hosting + functions + rules. Cloud Run IAM handled automatically.
100
164
 
101
165
  ```
102
166
  my-project/
103
- ├── .env.example NOT loaded by Vite (reference only)
104
- ├── apps/
105
- └── my-app/
106
- ├── .env Vite reads THIS
107
- ├── .env.local Overrides .env (gitignored)
108
- ├── .env.staging Used by dndev staging
109
- └── .env.production Production overrides
110
- └── functions/
111
- └── .env Server secrets (Stripe, OAuth)
167
+ +-- .env.example <-- NOT loaded by Vite (reference only)
168
+ +-- apps/
169
+ | +-- my-app/
170
+ | +-- .env <-- Vite reads THIS (public keys: VITE_*)
171
+ | +-- .env.local <-- Overrides .env (gitignored)
172
+ | +-- .env.staging <-- Used by dndev staging
173
+ | +-- .env.production <-- Production overrides
174
+ +-- functions/
175
+ +-- .env <-- Server secrets (Stripe, OAuth, service_role)
112
176
  ```
113
177
 
114
- **Rule:** `VITE_*` vars go in `apps/<app>/.env`. Server secrets go in `functions/.env`.
178
+ **Rules:**
179
+ - `VITE_*` / `NEXT_PUBLIC_*` vars -> `apps/<app>/.env` (public, shipped to browser)
180
+ - Server secrets -> `functions/.env` (never exposed to client)
181
+ - Service account files -> app root, gitignored
115
182
 
116
183
  ---
117
184
 
@@ -122,9 +189,11 @@ my-project/
122
189
  | `bun dev` | Start dev server |
123
190
  | `dndev emu start` | Start Firebase emulators |
124
191
  | `dndev firebase:setup` | Configure Firebase project + .env |
125
- | `dndev deploy` | Deploy to Firebase (hosting + functions + rules) |
192
+ | `dndev supabase:setup` | Configure Supabase project + .env |
193
+ | `dndev deploy` | **Firebase:** hosting + functions + rules. **Supabase:** deploys frontend to [Vercel](https://vercel.com) (via scaffolded vercel.json) and Edge Functions to Supabase. Set `VITE_SUPABASE_*` in Vercel project env. |
126
194
  | `dndev staging` | Deploy to staging environment |
127
- | `dndev sync-secrets` | Push functions/.env to Firebase Secret Manager |
195
+ | `dndev sync-secrets` | Push functions/.env to runtime (Firebase/Vercel) |
196
+ | `dndev sync-secrets --target github` | Push secrets to GitHub Secrets (CI/CD) |
128
197
  | `bun test` | Run tests (after Phase 4) |
129
198
  | `bun run type-check` | TypeScript validation |
130
199
 
@@ -135,8 +204,8 @@ my-project/
135
204
  | Feature | Required? | When to Set Up |
136
205
  |---------|-----------|---------------|
137
206
  | Git + GitHub | Recommended | Before starting development |
138
- | Firebase (Auth + Firestore) | Yes | Before building any features |
139
- | Cloud Functions | If using backend | Before deploying functions |
207
+ | Firebase or Supabase | Yes (pick one) | Before building any features |
208
+ | Cloud Functions | If using server logic | Before deploying functions |
140
209
  | Stripe | If using billing | When adding payment features |
141
210
  | GitHub Actions CI/CD | Optional | Phase 4 generates the workflow |
142
211
  | Staging environment | Optional | When you want a test environment |
@@ -8,9 +8,11 @@
8
8
 
9
9
  ## Getting Started
10
10
 
11
- - [ENV_SETUP.md](./ENV_SETUP.md) - **START HERE** Full onboarding flow (installfirebasedeploy)
11
+ - **Human flow:** P0 (Bun/Node + AI IDE) Install CLI & `dndev init` Run **AI.md** Enjoy. See [AGENT_START_HERE.md](./AGENT_START_HERE.md) § Getting started (for humans).
12
+ - [ENV_SETUP.md](./ENV_SETUP.md) — After `dndev init`: env, Firebase/Supabase, deploy
12
13
  - [GOTCHAS.md](./GOTCHAS.md) - **Common mistakes & pitfalls** (phase-tagged, read before coding)
13
14
  - [SETUP_FIREBASE.md](./SETUP_FIREBASE.md) - Firebase project setup (`dndev firebase:setup`)
15
+ - [SETUP_SUPABASE.md](./SETUP_SUPABASE.md) - Supabase project setup (`dndev supabase:setup`, `dn generate sql`)
14
16
  - [SETUP_TESTING.md](./SETUP_TESTING.md) - Test generation (Phase 4)
15
17
 
16
18
  ---
@@ -20,6 +20,34 @@
20
20
 
21
21
  ---
22
22
 
23
+ ## 0. Provider configuration (required)
24
+
25
+ CRUD operations use the **CRUD provider** registered at app startup. You must call `configureProviders()` before any component uses `useCrud`.
26
+
27
+ - **Where:** In a dedicated module, e.g. `src/config/providers.ts`.
28
+ - **When:** Import that module from your root component (e.g. `App.tsx`) so it runs before any CRUD usage.
29
+
30
+ ```typescript
31
+ // App.tsx
32
+ import './config/providers'; // Before any useCrud
33
+ // ...
34
+ ```
35
+
36
+ ```typescript
37
+ // config/providers.ts
38
+ import { configureProviders } from '@donotdev/core';
39
+ import { FirestoreAdapter } from '@donotdev/firebase'; // or SupabaseCrudAdapter from '@donotdev/supabase'
40
+
41
+ configureProviders({
42
+ crud: new FirestoreAdapter(),
43
+ // auth, storage optional
44
+ });
45
+ ```
46
+
47
+ If you skip this step, `useCrud` will throw at runtime: "Provider \"crud\" not available. Call configureProviders() at app startup."
48
+
49
+ ---
50
+
23
51
  ## 1. Define Entity
24
52
 
25
53
  ```typescript
@@ -80,7 +108,7 @@ For multi-tenant apps where data belongs to a company/tenant/workspace:
80
108
  ### Step 1: Register Scope Provider (once at app startup)
81
109
 
82
110
  ```typescript
83
- // src/main.tsx or src/App.tsx
111
+ // src/App.tsx
84
112
  import { registerScopeProvider } from '@donotdev/core';
85
113
  import { useCurrentCompanyStore } from './stores/currentCompanyStore';
86
114
 
@@ -665,9 +693,15 @@ transmission: {
665
693
 
666
694
  ## 7. Custom Fields & Schemas
667
695
 
668
- ### Custom Field Types with UI Components
696
+ A custom field type (when the requirement is not a built-in type) needs up to four things: **(1)** entity field with your custom `type` and `validation.schema`, **(2)** form component(s) so the field is editable, **(3)** optional **display** formatter so list/card/detail views render the value correctly, **(4)** optional **filter** so the field appears in EntityFilters with the right filter UI. All four are wired through a single `registerFieldType({ ... })` call.
697
+
698
+ > **Custom type strings work out of the box.** Just use any string as the `type` — no declaration file or module augmentation needed. The framework accepts any string while still autocompleting built-in types.
699
+ >
700
+ > `CustomFieldOptionsMap` augmentation (see below) is only needed if you want type-checked `options.fieldSpecific` for your custom type.
669
701
 
670
- For custom field types that need custom UI components:
702
+ ### Form (required for editable fields)
703
+
704
+ You must register a **controlled component** so the field appears in forms. Optionally register an **uncontrolled component** (e.g. for submit/reset-style fields).
671
705
 
672
706
  ```typescript
673
707
  import { registerFieldType, useController } from '@donotdev/crud';
@@ -687,7 +721,6 @@ function RepairOperationsField({
687
721
  control: control,
688
722
  });
689
723
 
690
- // Use field.value and field.onChange for form state
691
724
  const value = (field.value as any) || [];
692
725
 
693
726
  return (
@@ -701,13 +734,13 @@ function RepairOperationsField({
701
734
  );
702
735
  }
703
736
 
704
- // Register UI component
737
+ // Minimal registration (form only)
705
738
  registerFieldType({
706
739
  type: 'repairOperations',
707
740
  controlledComponent: RepairOperationsField,
708
741
  });
709
742
 
710
- // Then define schema in entity
743
+ // Entity with schema
711
744
  export const carEntity = defineEntity({
712
745
  name: 'Car',
713
746
  collection: 'cars',
@@ -715,11 +748,10 @@ export const carEntity = defineEntity({
715
748
  repairs: {
716
749
  name: 'repairs',
717
750
  label: 'repairs',
718
- type: 'repairOperations' as any,
751
+ type: 'repairOperations',
719
752
  visibility: 'admin',
720
753
  validation: {
721
754
  required: false,
722
- // Custom Valibot schema - single source of truth
723
755
  schema: v.nullish(v.array(v.object({
724
756
  operation: v.string(),
725
757
  cost: v.number(),
@@ -730,10 +762,109 @@ export const carEntity = defineEntity({
730
762
  });
731
763
  ```
732
764
 
733
- **Important:**
734
- - Custom controlled components receive `control` prop, NOT `field` prop
735
- - You MUST use `useController` hook to get `field` and `fieldState`
736
- - Define schema in `validation.schema` - it's the single source of truth
765
+ **Important:** Custom controlled components receive `control` prop, NOT `field` prop. You MUST use `useController` to get `field` and `fieldState`. Define schema in `validation.schema` — it's the single source of truth.
766
+
767
+ ### Display (list/card/detail)
768
+
769
+ Without a **displayFormatter**, list/card/detail views show the raw value (or a fallback). To control how the value is rendered in read-only views, pass `displayFormatter` in the same `registerFieldType` call. Signature: `(value, fieldConfig, t, options?) => string | ReactNode`. Use `options?.compact` for list/card vs detail.
770
+
771
+ ```typescript
772
+ // Example: format a custom object for display
773
+ displayFormatter: (value, fieldConfig, t, options) => {
774
+ if (value == null) return '';
775
+ const arr = Array.isArray(value) ? value : [];
776
+ if (options?.compact) return `${arr.length} item(s)`;
777
+ return arr.map((item: any) => `${item.operation}: ${item.cost}`).join(', ') || '—';
778
+ }
779
+ ```
780
+
781
+ ### Filter (EntityFilters)
782
+
783
+ For the field to appear in the list/card **filters** section (EntityFilters), set **filterable: true** and **filterType** in `registerFieldType`. The filter type determines the filter UI:
784
+
785
+ | filterType | Use for |
786
+ |----------------|--------|
787
+ | `'text'` | Free text search |
788
+ | `'range'` | Numbers, dates (min/max) |
789
+ | `'select'` | Single choice (e.g. enum) |
790
+ | `'multiselect'`| Multiple choices |
791
+ | `'address'` | Address-based filter |
792
+ | `'none'` | Not filterable (omit or set filterable: false) |
793
+
794
+ ```typescript
795
+ registerFieldType({
796
+ type: 'repairOperations',
797
+ controlledComponent: RepairOperationsField,
798
+ displayFormatter: (value, fieldConfig, t, options) => { /* ... */ },
799
+ filterable: true,
800
+ filterType: 'text', // or 'range', 'select', 'multiselect', 'address', 'none'
801
+ });
802
+ ```
803
+
804
+ ### Full example: form + display + filter
805
+
806
+ One registration with form, displayFormatter, and filter:
807
+
808
+ ```typescript
809
+ import { registerFieldType, useController } from '@donotdev/crud';
810
+ import type { ControlledFieldProps } from '@donotdev/crud';
811
+ import { defineEntity } from '@donotdev/core';
812
+ import * as v from 'valibot';
813
+
814
+ function StatusTierField({ fieldConfig, control, errors, t }: ControlledFieldProps) {
815
+ const { field, fieldState } = useController({ name: fieldConfig.name, control });
816
+ return (
817
+ <div>
818
+ <label>{t(fieldConfig.label)}</label>
819
+ <select value={field.value ?? ''} onChange={(e) => field.onChange(e.target.value)}>
820
+ <option value="">—</option>
821
+ <option value="basic">Basic</option>
822
+ <option value="premium">Premium</option>
823
+ </select>
824
+ {fieldState?.error && <span className="error">{fieldState.error.message}</span>}
825
+ </div>
826
+ );
827
+ }
828
+
829
+ registerFieldType({
830
+ type: 'statusTier',
831
+ controlledComponent: StatusTierField,
832
+ displayFormatter: (value) => (value ? String(value) : '—'),
833
+ filterable: true,
834
+ filterType: 'select',
835
+ });
836
+
837
+ export const productEntity = defineEntity({
838
+ name: 'Product',
839
+ collection: 'products',
840
+ fields: {
841
+ statusTier: {
842
+ name: 'statusTier',
843
+ label: 'statusTier',
844
+ type: 'statusTier',
845
+ visibility: 'guest',
846
+ validation: { schema: v.optional(v.picklist(['basic', 'premium'])) },
847
+ },
848
+ },
849
+ });
850
+ ```
851
+
852
+ ### Typing custom field options
853
+
854
+ To get type-checked `options.fieldSpecific` for custom types (e.g. `extractDistrictCode: boolean`), augment the framework's `CustomFieldOptionsMap` in your app:
855
+
856
+ ```typescript
857
+ // e.g. in src/types/crud.d.ts or next to your entity
858
+ import '@donotdev/core';
859
+ declare module '@donotdev/core' {
860
+ interface CustomFieldOptionsMap {
861
+ 'repairOperations': { maxItems?: number };
862
+ 'isousou-address': { extractDistrictCode?: boolean };
863
+ }
864
+ }
865
+ ```
866
+
867
+ Then in entity fields you can use `type: 'repairOperations'` and `options: { fieldSpecific: { maxItems: 10 } }` without type errors or `as any`.
737
868
 
738
869
  ### Custom Schemas (No Custom UI)
739
870
 
@@ -112,23 +112,29 @@ This handles everything:
112
112
 
113
113
  ## Secrets (Stripe, OAuth, etc.)
114
114
 
115
- Server-side secrets go in `functions/.env`, not the app `.env`:
115
+ Server-side secrets go in `functions/.env`, not the app `.env`.
116
+
117
+ **We NEVER ask for secret keys.** You place them yourself:
116
118
 
117
119
  ```bash
118
120
  # functions/.env
119
121
  STRIPE_SECRET_KEY=sk_live_...
120
122
  STRIPE_WEBHOOK_SECRET=whsec_...
123
+ SUPABASE_SERVICE_ROLE_KEY=eyJ... # if using Supabase
121
124
  GITHUB_CLIENT_SECRET=...
122
125
  ```
123
126
 
124
- Push to Firebase Secret Manager:
127
+ Then sync to your runtime and CI/CD:
125
128
 
126
129
  ```bash
127
- dndev sync-secrets
130
+ dndev sync-secrets # Firebase Secret Manager / Vercel env
131
+ dndev sync-secrets --target github # GitHub Secrets (for CI/CD workflows)
128
132
  ```
129
133
 
130
134
  Secrets are auto-loaded by Cloud Functions at runtime. Never put server secrets in `VITE_*` variables — those are exposed to the browser.
131
135
 
136
+ See [ENV_SETUP.md → Secrets Philosophy](./ENV_SETUP.md#secrets-philosophy) for the full policy.
137
+
132
138
  ---
133
139
 
134
140
  ## Cloud Run IAM (Technical Detail)