@donotdev/cli 0.0.13 → 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 (162) hide show
  1. package/dependencies-matrix.json +357 -89
  2. package/dist/bin/commands/agent-setup.d.ts +6 -0
  3. package/dist/bin/commands/agent-setup.d.ts.map +1 -0
  4. package/dist/bin/commands/agent-setup.js +629 -0
  5. package/dist/bin/commands/agent-setup.js.map +1 -0
  6. package/dist/bin/commands/build.js +131 -50
  7. package/dist/bin/commands/bump.js +137 -49
  8. package/dist/bin/commands/cacheout.js +50 -21
  9. package/dist/bin/commands/create-app.js +270 -261
  10. package/dist/bin/commands/create-project.js +418 -197
  11. package/dist/bin/commands/deploy.js +1752 -712
  12. package/dist/bin/commands/dev.js +151 -35
  13. package/dist/bin/commands/emu.js +228 -70
  14. package/dist/bin/commands/format.js +50 -21
  15. package/dist/bin/commands/lint.js +50 -21
  16. package/dist/bin/commands/preview.js +155 -35
  17. package/dist/bin/commands/supabase-setup.d.ts +6 -0
  18. package/dist/bin/commands/supabase-setup.d.ts.map +1 -0
  19. package/dist/bin/commands/supabase-setup.js +7 -0
  20. package/dist/bin/commands/supabase-setup.js.map +1 -0
  21. package/dist/bin/commands/sync-secrets.js +224 -46
  22. package/dist/bin/commands/type-check.d.ts +14 -0
  23. package/dist/bin/commands/type-check.d.ts.map +1 -0
  24. package/dist/bin/commands/type-check.js +314 -0
  25. package/dist/bin/commands/type-check.js.map +1 -0
  26. package/dist/bin/commands/wai.js +7399 -11
  27. package/dist/bin/dndev.js +27 -2
  28. package/dist/bin/donotdev.js +27 -2
  29. package/dist/index.js +3960 -2996
  30. package/package.json +2 -2
  31. package/templates/app-demo/src/App.tsx.example +1 -0
  32. package/templates/app-demo/src/pages/FullPage.tsx.example +2 -2
  33. package/templates/app-demo/src/pages/components/DemoLayout.tsx.example +2 -2
  34. package/templates/app-demo/src/themes.css.example +5 -12
  35. package/templates/app-expo/.env.example +64 -0
  36. package/templates/app-expo/.expo/README.md.example +5 -0
  37. package/templates/app-expo/.gitignore.example +36 -0
  38. package/templates/app-expo/README.md.example +58 -0
  39. package/templates/app-expo/app/.gitkeep +2 -0
  40. package/templates/app-expo/app/_layout.tsx.example +41 -0
  41. package/templates/app-expo/app/form.tsx.example +52 -0
  42. package/templates/app-expo/app/index.tsx.example +89 -0
  43. package/templates/app-expo/app/list.tsx.example +32 -0
  44. package/templates/app-expo/app/profile.tsx.example +76 -0
  45. package/templates/app-expo/app/signin.tsx.example +53 -0
  46. package/templates/app-expo/app.json.example +39 -0
  47. package/templates/app-expo/babel.config.js.example +10 -0
  48. package/templates/app-expo/eas.json.example +20 -0
  49. package/templates/app-expo/expo-env.d.ts.example +4 -0
  50. package/templates/app-expo/metro.config.js.example +20 -0
  51. package/templates/app-expo/service-account-key.json.example +12 -0
  52. package/templates/app-expo/tsconfig.json.example +19 -0
  53. package/templates/app-next/.env.example +4 -33
  54. package/templates/app-next/src/app/ClientLayout.tsx.example +2 -0
  55. package/templates/app-next/src/app/layout.tsx.example +7 -6
  56. package/templates/app-next/src/globals.css.example +2 -11
  57. package/templates/app-next/src/pages/HomePage.tsx.example +1 -1
  58. package/templates/app-next/src/themes.css.example +10 -13
  59. package/templates/app-vite/.env.example +3 -32
  60. package/templates/app-vite/index.html.example +2 -24
  61. package/templates/app-vite/src/App.tsx.example +2 -0
  62. package/templates/app-vite/src/globals.css.example +2 -12
  63. package/templates/app-vite/src/pages/FormPageExample.tsx.example +1 -2
  64. package/templates/app-vite/src/pages/HomePage.tsx.example +1 -1
  65. package/templates/app-vite/src/themes.css.example +109 -79
  66. package/templates/app-vite/vercel.json.example +11 -0
  67. package/templates/functions-firebase/build.mjs.example +2 -72
  68. package/templates/functions-firebase/functions-firebase/.env.example.example +23 -25
  69. package/templates/functions-firebase/functions-firebase/build.mjs.example +2 -72
  70. package/templates/functions-firebase/functions-firebase/tsconfig.json.example +1 -1
  71. package/templates/functions-supabase/supabase/functions/cancel-subscription/index.ts.example +7 -0
  72. package/templates/functions-supabase/supabase/functions/change-plan/index.ts.example +11 -0
  73. package/templates/functions-supabase/supabase/functions/create-checkout-session/index.ts.example +11 -0
  74. package/templates/functions-supabase/supabase/functions/create-customer-portal/index.ts.example +7 -0
  75. package/templates/functions-supabase/supabase/functions/crud/index.ts.example +16 -0
  76. package/templates/functions-supabase/supabase/functions/delete-account/index.ts.example +7 -0
  77. package/templates/functions-supabase/supabase/functions/get-custom-claims/index.ts.example +7 -0
  78. package/templates/functions-supabase/supabase/functions/get-user-auth-status/index.ts.example +7 -0
  79. package/templates/functions-supabase/supabase/functions/refresh-subscription-status/index.ts.example +7 -0
  80. package/templates/functions-supabase/supabase/functions/remove-custom-claims/index.ts.example +7 -0
  81. package/templates/functions-supabase/supabase/functions/set-custom-claims/index.ts.example +7 -0
  82. package/templates/functions-supabase/supabase/migrations/20250101000000_idempotency.sql +24 -0
  83. package/templates/functions-supabase/supabase/migrations/20250101000001_rate_limits.sql +22 -0
  84. package/templates/functions-supabase/supabase/migrations/20250101000002_cleanup_jobs.sql +28 -0
  85. package/templates/functions-supabase/supabase/migrations/20250101000003_operation_metrics.sql +28 -0
  86. package/templates/functions-vercel/functions-vercel/tsconfig.json.example +1 -1
  87. package/templates/functions-vercel/functions-vercel/vercel.json.example +1 -1
  88. package/templates/functions-vercel/vercel.json.example +1 -1
  89. package/templates/github/github/workflows/firebase-deploy.yml.example +1 -1
  90. package/templates/github/workflows/firebase-deploy.yml.example +1 -1
  91. package/templates/overlay-firebase/env.fragment.example +34 -0
  92. package/templates/overlay-firebase/env.fragment.expo.example +34 -0
  93. package/templates/overlay-firebase/env.fragment.nextjs.example +34 -0
  94. package/templates/overlay-firebase/src/config/providers.expo.ts.example +49 -0
  95. package/templates/overlay-firebase/src/config/providers.ts.example +23 -0
  96. package/templates/overlay-supabase/env.fragment.example +7 -0
  97. package/templates/overlay-supabase/env.fragment.expo.example +7 -0
  98. package/templates/overlay-supabase/env.fragment.nextjs.example +7 -0
  99. package/templates/overlay-supabase/src/config/providers.expo.ts.example +35 -0
  100. package/templates/overlay-supabase/src/config/providers.ts.example +33 -0
  101. package/templates/overlay-supabase/vercel.headers.example +23 -0
  102. package/templates/overlay-supabase/vercel.json.example +22 -0
  103. package/templates/overlay-vercel/env.fragment.example +34 -0
  104. package/templates/overlay-vercel/env.fragment.nextjs.example +34 -0
  105. package/templates/overlay-vercel/src/config/providers.ts.example +24 -0
  106. package/templates/root-consumer/.claude/agents/architect.md.example +2 -310
  107. package/templates/root-consumer/.claude/agents/builder.md.example +2 -326
  108. package/templates/root-consumer/.claude/agents/coder.md.example +2 -83
  109. package/templates/root-consumer/.claude/agents/extractor.md.example +2 -231
  110. package/templates/root-consumer/.claude/agents/polisher.md.example +2 -132
  111. package/templates/root-consumer/.claude/agents/prompt-engineer.md.example +2 -81
  112. package/templates/root-consumer/.claude/commands/brainstorm.md.example +1 -1
  113. package/templates/root-consumer/.claude/commands/build.md.example +1 -1
  114. package/templates/root-consumer/.claude/commands/design.md.example +1 -1
  115. package/templates/root-consumer/.claude/commands/grill.md.example +30 -0
  116. package/templates/root-consumer/.claude/commands/polish.md.example +1 -1
  117. package/templates/root-consumer/.claude/commands/techdebt.md.example +28 -0
  118. package/templates/root-consumer/.clinerules.example +1 -0
  119. package/templates/root-consumer/.cursor/rules/no-docs.mdc.example +15 -0
  120. package/templates/root-consumer/.cursorrules.example +1 -0
  121. package/templates/root-consumer/.dndev/args.json.example +6 -0
  122. package/templates/root-consumer/.gemini/settings.json.example +2 -2
  123. package/templates/root-consumer/.github/copilot-instructions.md.example +1 -0
  124. package/templates/root-consumer/.windsurfrules.example +1 -0
  125. package/templates/root-consumer/AI.md.example +25 -108
  126. package/templates/root-consumer/CLAUDE.md.example +1 -128
  127. package/templates/root-consumer/CONVENTIONS.md.example +1 -0
  128. package/templates/root-consumer/GEMINI.md.example +1 -0
  129. package/templates/root-consumer/firebase.json.example +1 -1
  130. package/templates/root-consumer/guides/dndev/AGENT_START_HERE.md.example +54 -0
  131. package/templates/root-consumer/guides/dndev/COMPONENTS_ADV.md.example +0 -18
  132. package/templates/root-consumer/guides/dndev/COMPONENTS_UI.md.example +1 -1
  133. package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +99 -30
  134. package/templates/root-consumer/guides/dndev/GOTCHAS.md.example +186 -0
  135. package/templates/root-consumer/guides/dndev/INDEX.md.example +4 -1
  136. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +143 -12
  137. package/templates/root-consumer/guides/dndev/SETUP_FIREBASE.md.example +9 -3
  138. package/templates/root-consumer/guides/dndev/SETUP_FUNCTIONS.md.example +12 -7
  139. package/templates/root-consumer/guides/dndev/SETUP_SOC2.md.example +234 -0
  140. package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +124 -0
  141. package/templates/root-consumer/guides/dndev/SETUP_THEMES.md.example +6 -2
  142. package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +176 -0
  143. package/templates/root-consumer/guides/dndev/USE_ROUTING.md.example +5 -9
  144. package/templates/root-consumer/guides/dndev/essences_reference.css.example +174 -0
  145. package/templates/root-consumer/guides/wai-way/agents/builder.md.example +10 -0
  146. package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +25 -5
  147. package/templates/root-consumer/guides/wai-way/agents/polisher.md.example +13 -2
  148. package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +2 -2
  149. package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +47 -11
  150. package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +15 -4
  151. package/templates/root-consumer/guides/wai-way/spec_template.md.example +7 -6
  152. package/templates/app-payload/.env.example +0 -28
  153. package/templates/app-payload/README.md.example +0 -233
  154. package/templates/app-payload/collections/Company.ts.example +0 -125
  155. package/templates/app-payload/collections/Hero.ts.example +0 -62
  156. package/templates/app-payload/collections/Media.ts.example +0 -41
  157. package/templates/app-payload/collections/Products.ts.example +0 -115
  158. package/templates/app-payload/collections/Services.ts.example +0 -104
  159. package/templates/app-payload/collections/Testimonials.ts.example +0 -92
  160. package/templates/app-payload/collections/Users.ts.example +0 -35
  161. package/templates/app-payload/src/server.ts.example +0 -79
  162. package/templates/app-payload/tsconfig.json.example +0 -24
@@ -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)
@@ -47,21 +47,26 @@ export const crud = createCrudFunctions(entities);
47
47
 
48
48
  ## Custom Functions
49
49
 
50
- **When writing custom functions, use `/server` imports:**
50
+ **Use `createFunction` handles config, validation, auth, rate limiting, metrics automatically:**
51
51
 
52
52
  ```typescript
53
- import { onCall } from 'firebase-functions/v2/https';
54
- import { FUNCTION_CONFIG } from '@donotdev/functions/firebase';
53
+ import * as v from 'valibot';
54
+ import { createFunction } from '@donotdev/functions/firebase';
55
55
  import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
56
- import { handleError } from '@donotdev/core/server';
57
56
 
58
- export const myFunction = onCall(FUNCTION_CONFIG, async (request) => {
57
+ const schema = v.object({ productId: v.string() });
58
+
59
+ export const getProductDetails = createFunction(schema, 'get_product_details', async (data, { uid }) => {
59
60
  const db = getFirebaseAdminFirestore();
60
- // Your logic here
61
- return { data: 'result' };
61
+ const doc = await db.collection('products').doc(data.productId).get();
62
+ return doc.data();
62
63
  });
63
64
  ```
64
65
 
66
+ **That's it.** Rate limiting, metrics, auth, schema validation — all included by default. No config needed.
67
+
68
+ **Advanced:** Use `createBaseFunction` if you need custom config (memory, timeout, region override).
69
+
65
70
  ---
66
71
 
67
72
  ## Post-Deployment: Cloud Run IAM
@@ -0,0 +1,234 @@
1
+ # Setup: SOC2 Readiness
2
+
3
+ **DoNotDev auto-enforces SOC2 baseline controls.** Zero config needed for the MVP. This guide covers advanced controls and audit verification.
4
+
5
+ ---
6
+
7
+ ## Zero-Config Baseline (Always On)
8
+
9
+ When you wire `DndevSecurity` into your app, these controls activate automatically:
10
+
11
+ | Control | Default | SOC2 Criteria |
12
+ |---------|---------|---------------|
13
+ | Structured audit logging (all CRUD + auth) | ✅ Automatic | CC7.1 |
14
+ | Rate limiting (100 writes/min, 500 reads/min) | ✅ Automatic | CC6.6 |
15
+ | Brute-force lockout (5 attempts → 15 min) | ✅ Automatic | CC6.1 |
16
+ | Session timeout (8h idle) | ✅ Automatic | CC6.1 |
17
+ | Field-level RBAC + visibility | ✅ Always on (entity config) | CC6.3 |
18
+ | Supabase RLS / Firestore default-deny | ✅ By convention | CC6.3 |
19
+
20
+ ---
21
+
22
+ ## 1. Wire `DndevSecurity` (Required Once)
23
+
24
+ ```typescript
25
+ // src/security.ts
26
+ import { DndevSecurity } from '@donotdev/security/server';
27
+
28
+ export const security = new DndevSecurity({
29
+ // Optional: override defaults
30
+ // rateLimit: { writesPerMin: 50, readsPerMin: 200 },
31
+ // lockout: { maxAttempts: 3, lockoutMs: 30 * 60 * 1000 },
32
+ // sessionTimeoutMs: 4 * 60 * 60 * 1000,
33
+
34
+ // Required for PII encryption (see § PII Encryption below)
35
+ // piiSecret: process.env.PII_SECRET,
36
+
37
+ // Optional: send anomaly alerts to your SIEM/Slack
38
+ // anomalyHandler: async (alert) => { await fetch(process.env.SIEM_WEBHOOK!, { method: 'POST', body: JSON.stringify(alert) }); },
39
+ });
40
+ ```
41
+
42
+ ```typescript
43
+ // src/main.ts (or app entry point)
44
+ import { security } from './security';
45
+ import { crudService } from './crud'; // your CrudService instance
46
+ import { auth } from './auth'; // your SupabaseAuth or FirebaseSDK instance
47
+
48
+ crudService.setSecurity(security);
49
+ auth.setSecurity(security);
50
+ ```
51
+
52
+ ---
53
+
54
+ ## 2. PII Encryption (Opt-In — C1 Confidentiality)
55
+
56
+ Required only for entities storing personal data (email, phone, SSN, health data, etc.).
57
+
58
+ ```typescript
59
+ // entities/user.ts
60
+ export const userEntity = defineEntity({
61
+ name: 'User',
62
+ collection: 'users',
63
+ security: {
64
+ piiFields: ['email', 'phone', 'dateOfBirth'], // Encrypted at rest with AES-256-GCM
65
+ },
66
+ fields: { /* ... */ },
67
+ });
68
+ ```
69
+
70
+ ```bash
71
+ # Add to .env (min 32 chars, generate with: openssl rand -hex 32)
72
+ PII_SECRET=your-secret-key-min-32-chars-here
73
+ ```
74
+
75
+ Pass to security:
76
+ ```typescript
77
+ export const security = new DndevSecurity({
78
+ piiSecret: process.env.PII_SECRET,
79
+ });
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 3. MFA Enforcement (Opt-In — CC6.1)
85
+
86
+ Enforce MFA for specific roles on sensitive entities:
87
+
88
+ ```typescript
89
+ export const paymentEntity = defineEntity({
90
+ name: 'Payment',
91
+ collection: 'payments',
92
+ security: {
93
+ requireMfa: 'admin', // 'admin' | 'manager' | 'user'
94
+ },
95
+ fields: { /* ... */ },
96
+ });
97
+ ```
98
+
99
+ Enable MFA in your provider:
100
+ - **Supabase:** Dashboard → Auth → Multi Factor Authentication → Enable TOTP
101
+ - **Firebase:** Firebase Console → Authentication → Multi-factor Auth → Enable
102
+
103
+ ---
104
+
105
+ ## 4. Data Retention (Opt-In — P6 Privacy)
106
+
107
+ ```typescript
108
+ export const auditLogEntity = defineEntity({
109
+ name: 'AuditLog',
110
+ collection: 'audit_logs',
111
+ security: {
112
+ retention: { days: 365 }, // Auto-flag records for purge after 1 year
113
+ },
114
+ fields: { /* ... */ },
115
+ });
116
+ ```
117
+
118
+ Run retention purge (e.g., in a scheduled Cloud Function):
119
+ ```typescript
120
+ import { privacyManager } from '@donotdev/security/server';
121
+ // privacyManager.shouldPurge(record.createdAt, retentionDays) → boolean
122
+ ```
123
+
124
+ ---
125
+
126
+ ## 5. Right to Erasure (GDPR Art. 17 / P8)
127
+
128
+ ```typescript
129
+ import { DndevSecurity } from '@donotdev/security/server';
130
+
131
+ // In your account deletion handler:
132
+ await security.privacyManager.eraseUser(userId, async (uid) => {
133
+ // Your custom erasure logic per collection
134
+ await db.from('orders').update({ userId: null }).eq('userId', uid);
135
+ await db.from('profiles').delete().eq('id', uid);
136
+ });
137
+ ```
138
+
139
+ ---
140
+
141
+ ## 6. Health Monitoring (Opt-In — A1 Availability)
142
+
143
+ ```typescript
144
+ // src/client/health.ts
145
+ import { HealthMonitor } from '@donotdev/security';
146
+
147
+ const dbMonitor = new HealthMonitor({ failureThreshold: 3, successThreshold: 2, timeoutMs: 5000 });
148
+
149
+ // Wrap all downstream DB calls through the circuit breaker
150
+ export const safeQuery = (fn: () => Promise<unknown>) => dbMonitor.protect(fn);
151
+
152
+ // Expose a health endpoint (Next.js example)
153
+ // app/api/health/route.ts
154
+ export async function GET() {
155
+ return Response.json({ status: dbMonitor.checkLiveness() });
156
+ }
157
+ ```
158
+
159
+ ---
160
+
161
+ ## 7. Anomaly Detection (Opt-In — CC7.2)
162
+
163
+ Triggered automatically when brute-force or bulk-delete thresholds are exceeded. Wire a handler to send alerts:
164
+
165
+ ```typescript
166
+ export const security = new DndevSecurity({
167
+ anomalyHandler: async (alert) => {
168
+ // Send to Slack, PagerDuty, Datadog, etc.
169
+ await fetch(process.env.SLACK_WEBHOOK!, {
170
+ method: 'POST',
171
+ body: JSON.stringify({ text: `🚨 SOC2 Anomaly: ${alert.type} — ${alert.details}` }),
172
+ });
173
+ },
174
+ });
175
+ ```
176
+
177
+ ---
178
+
179
+ ## 8. Audit Log Output
180
+
181
+ Audit events write to `process.stdout` as NDJSON — pipe to your SIEM:
182
+
183
+ ```json
184
+ {"timestamp":"2025-01-15T10:23:00.000Z","type":"crud.create","userId":"u_123","resource":"products","resourceId":"p_456","ip":"192.168.1.1"}
185
+ {"timestamp":"2025-01-15T10:23:01.000Z","type":"auth.login.failure","userId":"unknown","resource":"auth","ip":"10.0.0.5","details":"brute_force_lockout"}
186
+ ```
187
+
188
+ **Cloud Logging (Firebase / GCP):** Stdout is captured automatically.
189
+ **Datadog:** Set `DD_LOGS_INJECTION=true` and pipe stdout to the Datadog agent.
190
+ **Supabase:** Export `process.stderr` anomaly alerts to Logflare.
191
+
192
+ ---
193
+
194
+ ## SOC2 Readiness Check
195
+
196
+ Run before any audit:
197
+
198
+ ```bash
199
+ dn soc2 # Scan current app
200
+ dn soc2 --app my-app # Scan specific app
201
+ dn soc2 --json # Machine-readable JSON output (CI/CD)
202
+ dn soc2 --verbose # Show details for passing checks too
203
+ ```
204
+
205
+ **Exit codes:** `0` = all checks pass, `1` = one or more checks failed (CI-friendly).
206
+
207
+ **Checks run:**
208
+
209
+ | Check | Criteria | What It Looks For |
210
+ |-------|----------|--------------------|
211
+ | SecurityContext wired | CC6.1 | `DndevSecurity` in source |
212
+ | PII encryption secret | C1 | `PII_SECRET` in env when `piiFields` defined |
213
+ | Firestore default-deny | CC6.3 | `allow read, write: if false` in `firestore.rules` |
214
+ | Supabase RLS | CC6.3 | `ENABLE ROW LEVEL SECURITY` in SQL migrations |
215
+ | Audit logging | CC7.1 | `.audit()` / `AuditLogger` usage |
216
+ | Rate limiting | CC6.6 | `rateLimit` / `DndevRateLimiter` usage |
217
+ | Retention policies | P6 | `retention.days` in entity config |
218
+ | Health monitoring | A1 | `HealthMonitor` or `/api/health` endpoint |
219
+ | MFA enforcement | CC6.1 | `requireMfa` in sensitive entities |
220
+
221
+ ---
222
+
223
+ ## Coverage After Full Setup
224
+
225
+ | SOC2 Criteria | Control | Status |
226
+ |---------------|---------|--------|
227
+ | CC6.1 Auth hardening | Lockout + session timeout + MFA | Framework-enforced |
228
+ | CC6.3 Authorization | RBAC + field visibility + RLS/Firestore rules | Framework-enforced |
229
+ | CC6.6 Rate limiting | Per-user sliding window | Framework-enforced |
230
+ | CC7.1 Audit logging | All CRUD + auth events as NDJSON | Framework-enforced |
231
+ | CC7.2 Anomaly detection | Auth failures + bulk ops threshold alerts | Opt-in handler |
232
+ | A1 Availability | Circuit breaker + liveness probes | Opt-in `HealthMonitor` |
233
+ | C1 Confidentiality | AES-256-GCM field encryption | Opt-in `piiFields` |
234
+ | P1–P8 Privacy | Retention policies + right-to-erasure | Opt-in `retention` |
@@ -0,0 +1,124 @@
1
+ # Setup: Supabase
2
+
3
+ **From zero to a working Supabase backend: env, tables, RLS, and adapter behavior.**
4
+
5
+ ---
6
+
7
+ ## Step 1: Run Supabase Setup
8
+
9
+ ```bash
10
+ dndev supabase:setup
11
+ ```
12
+
13
+ This command:
14
+ - Lets you choose the target app (if you have an `apps/` directory)
15
+ - Asks for your **public** Supabase project URL and anon key
16
+ - Writes `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY` to your app's `.env`
17
+
18
+ **We only ask for public credentials** (safe to ship in your client bundle). We never ask for the service_role key.
19
+
20
+ Get URL and anon key from: [Supabase Dashboard](https://supabase.com/dashboard) → your project → **Settings → API**.
21
+
22
+ ---
23
+
24
+ ## Step 2: Generate Tables from Entities
25
+
26
+ The framework can generate PostgreSQL migrations from your entity definitions (same source as the schema used by the app).
27
+
28
+ ```bash
29
+ dn generate sql
30
+ ```
31
+
32
+ **What it does:**
33
+ - Discovers entities (e.g. in `entities/` or your configured `--entity-dir`)
34
+ - For each entity: `CREATE TABLE` with columns mapped from field types (text, number, boolean, timestamptz, uuid, jsonb, etc.)
35
+ - Adds technical columns: `id` (uuid, default `gen_random_uuid()`), `user_id`, `created_at`, `updated_at` (with `DEFAULT now()`), `created_by_id`, `updated_by_id`, `status`
36
+ - Enables **Row Level Security (RLS)** and creates policies so rows are scoped by `auth.uid() = user_id`
37
+ - Adds a trigger so `updated_at` is set automatically on every `UPDATE`
38
+
39
+ **Options (optional):**
40
+ - `--entity-dir <path>` — where to find entity files (default: from app root)
41
+ - `--output-dir <path>` — where to write migrations (default: `supabase/migrations`)
42
+ - `--no-single-file` — one migration file per entity instead of one combined file
43
+
44
+ Output is written to `supabase/migrations/` (or your `--output-dir`) as a timestamped `.sql` file. Apply it with the Supabase CLI or Dashboard SQL editor.
45
+
46
+ ---
47
+
48
+ ## Step 3: Apply Migrations
49
+
50
+ After generating SQL:
51
+
52
+ **Option A — Supabase CLI (recommended)**
53
+
54
+ ```bash
55
+ supabase db push
56
+ ```
57
+
58
+ (or `supabase migration up` if you manage migrations locally)
59
+
60
+ **Option B — Dashboard**
61
+
62
+ Copy the contents of the generated migration file into the SQL Editor in the Supabase Dashboard and run it.
63
+
64
+ ---
65
+
66
+ ## Step 4: Adapter Behavior (DB-Managed Timestamps)
67
+
68
+ Tables use **snake_case** column names and **timestamptz** for `created_at` / `updated_at`. The framework expects **camelCase** and **ISO date strings** in the app.
69
+
70
+ The **Supabase CRUD adapter** handles this automatically:
71
+
72
+ | Direction | Behavior |
73
+ |-----------|----------|
74
+ | **Read** (get, query, subscribe) | Rows from Supabase are normalized: snake_case → camelCase (e.g. `created_at` → `createdAt`), and timestamp columns are converted to ISO strings. Your UI receives the same shape as with Firebase. |
75
+ | **Write** (add, set, update) | The adapter does **not** send `createdAt` / `updatedAt` (or snake equivalents). The database sets them via `DEFAULT now()` and the `updated_at` trigger. |
76
+
77
+ So you never set timestamps in app code when using Supabase — the DB owns them.
78
+
79
+ ---
80
+
81
+ ## Environment Variables
82
+
83
+ **Client (Vite):** in `apps/<app>/.env`
84
+
85
+ | Variable | Purpose |
86
+ |----------|---------|
87
+ | `VITE_SUPABASE_URL` | Project URL (public) |
88
+ | `VITE_SUPABASE_ANON_KEY` | Anon key (public, safe in bundle) |
89
+
90
+ **Server (e.g. API routes, Edge Functions):** use the same URL and `SUPABASE_SERVICE_ROLE_KEY` for admin operations. Never expose the service_role key to the client. Put it in `functions/.env` or your host’s env (Vercel, etc.).
91
+
92
+ See [ENV_SETUP.md](./ENV_SETUP.md) for where to put secrets.
93
+
94
+ ---
95
+
96
+ ## Storage (Optional)
97
+
98
+ If your app uploads files, create a storage bucket in the Supabase Dashboard (e.g. `uploads`). The default bucket name used by the framework is `uploads`. Configure public or RLS policies in the Dashboard as needed.
99
+
100
+ ---
101
+
102
+ ## Hosting the frontend
103
+
104
+ Supabase gives you **Auth, Postgres, Storage, and Edge Functions**. It does **not** host your built frontend (Vite/Next SPA). You need a separate host for the `dist/` output.
105
+
106
+ **We recommend:**
107
+
108
+ - **Vercel** — Connect your repo, set `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY` in the project env, then deploy. Good fit for Next.js or Vite.
109
+ - We scaffold **vercel.json**; run `dndev deploy` and choose Frontend (Vercel) or Frontend + Edge Functions. Set `VITE_SUPABASE_*` in Vercel project env.
110
+
111
+ **Deploy:** Frontend goes to Vercel (scaffolded vercel.json); Edge Functions to Supabase. It does **not** deploy your Supabase app’s frontend to Vercel/Netlify. For that, use the host’s dashboard or CLI (e.g. `vercel`, `netlify deploy`) after building. See [ENV_SETUP.md](./ENV_SETUP.md) for production env vars on Vercel.
112
+
113
+ ---
114
+
115
+ ## Local Development
116
+
117
+ - **Against hosted Supabase:** After `supabase:setup`, run `bun dev` — the app talks to your Supabase project.
118
+ - **Local Supabase:** Install the [Supabase CLI](https://supabase.com/docs/guides/cli) and run `supabase start` for a local Postgres + Auth + Storage stack. Point `VITE_SUPABASE_URL` and keys to the local instance.
119
+
120
+ ---
121
+
122
+ ## Summary
123
+
124
+ **`dndev supabase:setup`** → paste URL + anon key → **`dn generate sql`** → apply migrations → **`bun dev`**. The adapter normalizes read (snake→camel, ISO) and leaves timestamps to the DB on write.
@@ -1,12 +1,16 @@
1
1
  # Setup: Themes
2
2
 
3
- **Most is pre-configured.** Override CSS variables in `src/themes.css`. Framework handles theme switching, auto-computed colors.
3
+ **Single source of truth:** `src/themes.css`. Import it from `globals.css`; do not set font/color overrides in globals. Framework handles theme switching.
4
+
5
+ **Default essence = SaaS** (Inter, neutral). Optional essences (Brutalist, Luxury) are in the scaffold; they do **not** apply until you set the class on `<html>` (e.g. `class="brutalist"`) or use the theme switcher.
6
+
7
+ **Reference:** Copy Brutalist/Luxury blocks from `guides/dndev/essences_reference.css` into your `src/themes.css` if you need them. Default-essence fonts (Inter, Space Grotesk, Playfair, Roboto) are bundled via `@donotdev/ui`; no Google Fonts, no `public/fonts/` required.
4
8
 
5
9
  ---
6
10
 
7
11
  ## Standard Use
8
12
 
9
- **File:** `src/themes.css` (scaffolded with all variables)
13
+ **File:** `src/themes.css` (scaffolded with light, dark, and optional Brutalist/Luxury)
10
14
 
11
15
  **Override colors:**
12
16
  ```css