create-bw-app 0.9.5 → 0.9.7
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/README.md +2 -0
- package/package.json +1 -1
- package/src/generator.mjs +148 -36
- package/template/supabase/README.md +62 -0
- package/template/supabase/clients/README.md +19 -0
- package/template/supabase/module-registry.json +28 -0
- package/template/supabase/modules/admin/README.md +18 -0
- package/template/supabase/modules/admin/migrations/.gitkeep +1 -0
- package/template/supabase/modules/admin/migrations/20260316091000_admin_v1.sql +317 -0
- package/template/supabase/modules/core/README.md +24 -0
- package/template/supabase/modules/core/migrations/.gitkeep +1 -0
- package/template/supabase/modules/core/migrations/20260316090000_core_v1.sql +497 -0
- package/template/supabase/modules/crm/README.md +27 -0
- package/template/supabase/modules/crm/migrations/.gitkeep +1 -0
- package/template/supabase/modules/crm/migrations/20260316092000_crm_v1.sql +392 -0
- package/template/supabase/modules/projects/README.md +22 -0
- package/template/supabase/modules/projects/migrations/.gitkeep +1 -0
- package/template/supabase/modules/projects/migrations/20260316093000_projects_v1.sql +1120 -0
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ Current updater behavior:
|
|
|
57
57
|
- copies a clean Next.js App Router starter template
|
|
58
58
|
- platform apps include BrightWeb auth, shell wiring, and optional module starter surfaces
|
|
59
59
|
- platform apps include a local `components/` folder for app-owned UI alongside the shared BrightWeb packages
|
|
60
|
+
- platform apps in published mode also write `supabase/config.toml`, a CLI-ready flat `supabase/migrations/` folder, `supabase/module-registry.json`, `supabase/clients/<slug>/stack.json`, and the resolved shared SQL migrations under `supabase/modules/<module>/migrations`
|
|
60
61
|
- site apps include Next.js, Tailwind CSS v4, and local component primitives
|
|
61
62
|
- writes `package.json`, `next.config.ts`, `.gitignore`, and `README.md` for both templates
|
|
62
63
|
- platform apps also write `.env.local`, `AGENTS.md`, `docs/ai/README.md`, `docs/ai/examples.md`, `docs/ai/app-context.json`, and generated config files for brand and module state
|
|
@@ -81,3 +82,4 @@ Platform mode always resolves to the `Core + Admin` database baseline. Selecting
|
|
|
81
82
|
- `packages/create-bw-app/template/base`
|
|
82
83
|
- `packages/create-bw-app/template/site/base`
|
|
83
84
|
- `packages/create-bw-app/template/modules`
|
|
85
|
+
- `packages/create-bw-app/template/supabase`
|
package/package.json
CHANGED
package/src/generator.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
|
|
19
19
|
export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
20
20
|
export const TEMPLATE_ROOT = path.join(PACKAGE_ROOT, "template");
|
|
21
|
+
const TEMPLATE_SUPABASE_ROOT = path.join(TEMPLATE_ROOT, "supabase");
|
|
21
22
|
const TEMPLATE_KEY_SET = new Set(TEMPLATE_OPTIONS.map((templateOption) => templateOption.key));
|
|
22
23
|
const DEFAULT_DB_MODULE_REGISTRY = {
|
|
23
24
|
modules: {
|
|
@@ -112,12 +113,22 @@ export async function readJsonIfPresent(filePath) {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
export async function getDbModuleRegistry(workspaceRoot) {
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
const candidatePaths = [];
|
|
117
|
+
|
|
118
|
+
if (workspaceRoot) {
|
|
119
|
+
candidatePaths.push(path.join(workspaceRoot, "supabase", "module-registry.json"));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
candidatePaths.push(path.join(TEMPLATE_SUPABASE_ROOT, "module-registry.json"));
|
|
123
|
+
|
|
124
|
+
for (const registryPath of candidatePaths) {
|
|
125
|
+
const registry = await readJsonIfPresent(registryPath);
|
|
126
|
+
if (registry) {
|
|
127
|
+
return registry;
|
|
128
|
+
}
|
|
117
129
|
}
|
|
118
130
|
|
|
119
|
-
|
|
120
|
-
return (await readJsonIfPresent(registryPath)) || DEFAULT_DB_MODULE_REGISTRY;
|
|
131
|
+
return DEFAULT_DB_MODULE_REGISTRY;
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
function resolveModuleOrder(registry, enabledModules) {
|
|
@@ -172,20 +183,14 @@ function getModuleLabel(moduleKey) {
|
|
|
172
183
|
}
|
|
173
184
|
|
|
174
185
|
export function createDbInstallPlan({ selectedModules, workspaceMode, registry }) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
selectedLabels: getSelectedModuleLabels(selectedModules),
|
|
178
|
-
resolvedOrder: [],
|
|
179
|
-
notes: [],
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
186
|
+
void workspaceMode;
|
|
187
|
+
const activeRegistry = registry?.modules ? registry : DEFAULT_DB_MODULE_REGISTRY;
|
|
183
188
|
const requestedModules = Array.from(new Set(["core", ...selectedModules]));
|
|
184
189
|
if (!requestedModules.includes("admin")) {
|
|
185
190
|
requestedModules.push("admin");
|
|
186
191
|
}
|
|
187
192
|
|
|
188
|
-
const resolvedOrder = resolveModuleOrder(
|
|
193
|
+
const resolvedOrder = resolveModuleOrder(activeRegistry, requestedModules);
|
|
189
194
|
const notes = [];
|
|
190
195
|
|
|
191
196
|
if (!selectedModules.includes("admin") && resolvedOrder.includes("admin")) {
|
|
@@ -198,16 +203,16 @@ export function createDbInstallPlan({ selectedModules, workspaceMode, registry }
|
|
|
198
203
|
}
|
|
199
204
|
|
|
200
205
|
const dependents = resolvedOrder.filter((candidateKey) => {
|
|
201
|
-
const dependencyList =
|
|
206
|
+
const dependencyList = activeRegistry.modules?.[candidateKey]?.dependsOn || [];
|
|
202
207
|
return dependencyList.includes(moduleKey);
|
|
203
208
|
});
|
|
204
209
|
|
|
205
210
|
if (dependents.length === 0) continue;
|
|
206
211
|
|
|
207
212
|
const dependentLabels = dependents
|
|
208
|
-
.map((candidateKey) =>
|
|
213
|
+
.map((candidateKey) => activeRegistry.modules?.[candidateKey]?.label || getModuleLabel(candidateKey))
|
|
209
214
|
.join(", ");
|
|
210
|
-
const moduleLabel =
|
|
215
|
+
const moduleLabel = activeRegistry.modules?.[moduleKey]?.label || getModuleLabel(moduleKey);
|
|
211
216
|
notes.push(`${moduleLabel} is included because ${dependentLabels} depends on it.`);
|
|
212
217
|
}
|
|
213
218
|
|
|
@@ -467,20 +472,22 @@ function createPlatformReadme({
|
|
|
467
472
|
"",
|
|
468
473
|
...moduleLines,
|
|
469
474
|
"",
|
|
475
|
+
"## Resolved database stack",
|
|
476
|
+
"",
|
|
477
|
+
...resolvedDbStackLines,
|
|
478
|
+
"",
|
|
470
479
|
...(workspaceMode
|
|
480
|
+
? []
|
|
481
|
+
: [
|
|
482
|
+
"Bundled Supabase SQL migrations live under `supabase/modules/<module>/migrations`.",
|
|
483
|
+
"",
|
|
484
|
+
]),
|
|
485
|
+
...(dependencyNotes.length > 0
|
|
471
486
|
? [
|
|
472
|
-
"##
|
|
487
|
+
"## Dependency notes",
|
|
473
488
|
"",
|
|
474
|
-
...
|
|
489
|
+
...dependencyNotes,
|
|
475
490
|
"",
|
|
476
|
-
...(dependencyNotes.length > 0
|
|
477
|
-
? [
|
|
478
|
-
"## Dependency notes",
|
|
479
|
-
"",
|
|
480
|
-
...dependencyNotes,
|
|
481
|
-
"",
|
|
482
|
-
]
|
|
483
|
-
: []),
|
|
484
491
|
]
|
|
485
492
|
: []),
|
|
486
493
|
"## Starter routes",
|
|
@@ -894,20 +901,52 @@ async function copyDirectory(sourceDir, targetDir) {
|
|
|
894
901
|
await fs.cp(sourceDir, targetDir, { recursive: true });
|
|
895
902
|
}
|
|
896
903
|
|
|
904
|
+
async function copyFileIfPresent(sourcePath, targetPath) {
|
|
905
|
+
if (!(await pathExists(sourcePath))) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
await ensureDirectory(path.dirname(targetPath));
|
|
910
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
911
|
+
}
|
|
912
|
+
|
|
897
913
|
export async function ensureDirectory(targetDir) {
|
|
898
914
|
await fs.mkdir(targetDir, { recursive: true });
|
|
899
915
|
}
|
|
900
916
|
|
|
901
|
-
|
|
902
|
-
|
|
917
|
+
function createGeneratedSupabaseConfig(projectId) {
|
|
918
|
+
return [
|
|
919
|
+
`project_id = "${projectId}"`,
|
|
920
|
+
"",
|
|
921
|
+
"[db]",
|
|
922
|
+
"major_version = 17",
|
|
923
|
+
"",
|
|
924
|
+
"[db.migrations]",
|
|
925
|
+
"enabled = true",
|
|
926
|
+
'schema_paths = []',
|
|
927
|
+
"",
|
|
928
|
+
"[db.seed]",
|
|
929
|
+
"enabled = false",
|
|
930
|
+
'sql_paths = []',
|
|
931
|
+
"",
|
|
932
|
+
].join("\n");
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function createScopedDbModuleRegistry(registry, moduleKeys) {
|
|
936
|
+
return {
|
|
937
|
+
modules: Object.fromEntries(
|
|
938
|
+
moduleKeys
|
|
939
|
+
.map((moduleKey) => [moduleKey, registry.modules?.[moduleKey]])
|
|
940
|
+
.filter(([, moduleConfig]) => Boolean(moduleConfig)),
|
|
941
|
+
),
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
async function writeClientStack(baseRoot, slug, dbInstallPlan, options = {}) {
|
|
946
|
+
const generatedInWorkspaceMode = options.workspaceMode === true;
|
|
947
|
+
const clientDir = path.join(baseRoot, "supabase", "clients", slug);
|
|
903
948
|
const stackPath = path.join(clientDir, "stack.json");
|
|
904
949
|
const migrationsDir = path.join(clientDir, "migrations");
|
|
905
|
-
const registry = await getDbModuleRegistry(workspaceRoot);
|
|
906
|
-
const dbInstallPlan = createDbInstallPlan({
|
|
907
|
-
selectedModules,
|
|
908
|
-
workspaceMode: true,
|
|
909
|
-
registry,
|
|
910
|
-
});
|
|
911
950
|
const enabledModules = dbInstallPlan.resolvedOrder;
|
|
912
951
|
|
|
913
952
|
if (await pathExists(stackPath)) {
|
|
@@ -929,7 +968,9 @@ async function writeWorkspaceClientStack(workspaceRoot, slug, selectedModules) {
|
|
|
929
968
|
enabledModules,
|
|
930
969
|
clientMigrationPath: `supabase/clients/${slug}/migrations`,
|
|
931
970
|
notes: [
|
|
932
|
-
|
|
971
|
+
generatedInWorkspaceMode
|
|
972
|
+
? "Generated by create-bw-app in workspace mode."
|
|
973
|
+
: "Generated by create-bw-app in published mode.",
|
|
933
974
|
`Selected app modules: ${dbInstallPlan.selectedLabels.length > 0 ? dbInstallPlan.selectedLabels.join(", ") : "none"}.`,
|
|
934
975
|
`Resolved database stack: ${enabledModules.map((moduleKey) => getModuleLabel(moduleKey)).join(" -> ")}.`,
|
|
935
976
|
"Platform always resolves to the Core + Admin database baseline; selecting Admin only controls whether the Admin starter UI and package wiring are scaffolded.",
|
|
@@ -942,6 +983,68 @@ async function writeWorkspaceClientStack(workspaceRoot, slug, selectedModules) {
|
|
|
942
983
|
);
|
|
943
984
|
}
|
|
944
985
|
|
|
986
|
+
async function writeSupabaseCliMigrations({ targetDir, dbInstallPlan }) {
|
|
987
|
+
const targetSupabaseDir = path.join(targetDir, "supabase");
|
|
988
|
+
const targetMigrationsDir = path.join(targetSupabaseDir, "migrations");
|
|
989
|
+
|
|
990
|
+
await ensureDirectory(targetMigrationsDir);
|
|
991
|
+
await fs.writeFile(path.join(targetSupabaseDir, "config.toml"), createGeneratedSupabaseConfig(path.basename(targetDir)), "utf8");
|
|
992
|
+
|
|
993
|
+
let sequence = 1;
|
|
994
|
+
for (const moduleKey of dbInstallPlan.resolvedOrder) {
|
|
995
|
+
const sourceModuleDir = path.join(TEMPLATE_SUPABASE_ROOT, "modules", moduleKey, "migrations");
|
|
996
|
+
if (!(await pathExists(sourceModuleDir))) {
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const fileNames = (await fs.readdir(sourceModuleDir))
|
|
1001
|
+
.filter((fileName) => fileName.endsWith(".sql"))
|
|
1002
|
+
.sort();
|
|
1003
|
+
|
|
1004
|
+
for (const fileName of fileNames) {
|
|
1005
|
+
const targetFileName = `${String(sequence).padStart(4, "0")}_${moduleKey}__${fileName}`;
|
|
1006
|
+
await fs.copyFile(
|
|
1007
|
+
path.join(sourceModuleDir, fileName),
|
|
1008
|
+
path.join(targetMigrationsDir, targetFileName),
|
|
1009
|
+
);
|
|
1010
|
+
sequence += 1;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
async function writeBundledSupabaseBaseline({ targetDir, slug, dbInstallPlan, registry }) {
|
|
1016
|
+
const shippedModuleKeys = dbInstallPlan.resolvedOrder;
|
|
1017
|
+
if (shippedModuleKeys.length === 0) {
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const targetSupabaseDir = path.join(targetDir, "supabase");
|
|
1022
|
+
const targetModulesDir = path.join(targetSupabaseDir, "modules");
|
|
1023
|
+
const scopedRegistry = createScopedDbModuleRegistry(registry, shippedModuleKeys);
|
|
1024
|
+
|
|
1025
|
+
await ensureDirectory(targetModulesDir);
|
|
1026
|
+
await copyFileIfPresent(path.join(TEMPLATE_SUPABASE_ROOT, "README.md"), path.join(targetSupabaseDir, "README.md"));
|
|
1027
|
+
await copyFileIfPresent(
|
|
1028
|
+
path.join(TEMPLATE_SUPABASE_ROOT, "clients", "README.md"),
|
|
1029
|
+
path.join(targetSupabaseDir, "clients", "README.md"),
|
|
1030
|
+
);
|
|
1031
|
+
await fs.writeFile(
|
|
1032
|
+
path.join(targetSupabaseDir, "module-registry.json"),
|
|
1033
|
+
`${JSON.stringify(scopedRegistry, null, 2)}\n`,
|
|
1034
|
+
"utf8",
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1037
|
+
for (const moduleKey of shippedModuleKeys) {
|
|
1038
|
+
await copyDirectory(
|
|
1039
|
+
path.join(TEMPLATE_SUPABASE_ROOT, "modules", moduleKey),
|
|
1040
|
+
path.join(targetModulesDir, moduleKey),
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
await writeClientStack(targetDir, slug, dbInstallPlan);
|
|
1045
|
+
await writeSupabaseCliMigrations({ targetDir, dbInstallPlan });
|
|
1046
|
+
}
|
|
1047
|
+
|
|
945
1048
|
export async function runInstall(command, cwd) {
|
|
946
1049
|
return new Promise((resolve, reject) => {
|
|
947
1050
|
const child = spawn(command, ["install"], {
|
|
@@ -1086,6 +1189,7 @@ async function scaffoldPlatformProject({
|
|
|
1086
1189
|
workspaceRoot,
|
|
1087
1190
|
answers,
|
|
1088
1191
|
dbInstallPlan,
|
|
1192
|
+
dbRegistry,
|
|
1089
1193
|
}) {
|
|
1090
1194
|
const brandValues = createDerivedBrandValues(answers.slug);
|
|
1091
1195
|
const baseTemplateDir = path.join(TEMPLATE_ROOT, "base");
|
|
@@ -1146,7 +1250,14 @@ async function scaffoldPlatformProject({
|
|
|
1146
1250
|
);
|
|
1147
1251
|
|
|
1148
1252
|
if (workspaceMode) {
|
|
1149
|
-
await
|
|
1253
|
+
await writeClientStack(workspaceRoot, answers.slug, dbInstallPlan, { workspaceMode: true });
|
|
1254
|
+
} else {
|
|
1255
|
+
await writeBundledSupabaseBaseline({
|
|
1256
|
+
targetDir,
|
|
1257
|
+
slug: answers.slug,
|
|
1258
|
+
dbInstallPlan,
|
|
1259
|
+
registry: dbRegistry,
|
|
1260
|
+
});
|
|
1150
1261
|
}
|
|
1151
1262
|
}
|
|
1152
1263
|
|
|
@@ -1301,6 +1412,7 @@ export async function createBrightwebClientApp(argvOptions, runtimeOptions = {})
|
|
|
1301
1412
|
workspaceRoot,
|
|
1302
1413
|
answers,
|
|
1303
1414
|
dbInstallPlan,
|
|
1415
|
+
dbRegistry: dbModuleRegistry,
|
|
1304
1416
|
});
|
|
1305
1417
|
}
|
|
1306
1418
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Brightweb Supabase Structure
|
|
2
|
+
|
|
3
|
+
This directory is the scaffold-owned home for the Brightweb shared database module baselines and forward migrations that were selected for this project.
|
|
4
|
+
|
|
5
|
+
## Directory map
|
|
6
|
+
|
|
7
|
+
- `module-registry.json`: shared module dependency graph and migration source paths
|
|
8
|
+
- `config.toml`: standard Supabase CLI config for this project
|
|
9
|
+
- `migrations/*.sql`: CLI-ready ordered migrations for `supabase db push`
|
|
10
|
+
- `modules/core`: always-on platform foundations
|
|
11
|
+
- `modules/admin`: RBAC and privileged governance behavior
|
|
12
|
+
- `modules/crm`: organizations, CRM contacts, and invitation flows
|
|
13
|
+
- `modules/projects`: project and work-management data
|
|
14
|
+
- `clients/<client-slug>`: true client-only schema deltas plus the client stack plan
|
|
15
|
+
- `clients/<client-slug>`: client stack metadata and client-only migrations for this project
|
|
16
|
+
|
|
17
|
+
## Ownership rule
|
|
18
|
+
|
|
19
|
+
Shared database changes should be authored by ownership area, not by client app:
|
|
20
|
+
|
|
21
|
+
- `core` is applied to every client
|
|
22
|
+
- shared module migrations are applied only when that module is enabled for the client
|
|
23
|
+
- client-specific migrations are the exception, not the default
|
|
24
|
+
|
|
25
|
+
`create-bw-app` writes `supabase/clients/<slug>/stack.json` so the generated app modules and the database install plan stay aligned.
|
|
26
|
+
|
|
27
|
+
These module baselines are the project-local Brightweb install path for the selected stack. Future schema work should extend them with forward migrations instead of carrying historical cleanup sequences.
|
|
28
|
+
|
|
29
|
+
Maintainer references:
|
|
30
|
+
|
|
31
|
+
- `docs/internal/architecture/database-module-migration-structure.md`
|
|
32
|
+
- `docs/internal/architecture/database-migration-authoring-workflow.md`
|
|
33
|
+
- `docs/internal/architecture/database-migration-safety-policy.md`
|
|
34
|
+
|
|
35
|
+
## Authoring workflow
|
|
36
|
+
|
|
37
|
+
Create a new shared module migration:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm db:new core profile_notification_cursor
|
|
41
|
+
pnpm db:new admin role_change_guard
|
|
42
|
+
pnpm db:new crm organization_invite_expiry
|
|
43
|
+
pnpm db:new projects task_due_date_index
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Create a client-only migration:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm db:new client:acme bespoke_reporting_table
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The shipped `module-registry.json` records the shared module dependency graph for this project.
|
|
53
|
+
|
|
54
|
+
Projects created from the published scaffold should use the Supabase files in this folder directly. `supabase/migrations/*.sql` is the flat ordered layout that Supabase CLI reads, while `supabase/modules/*/migrations` remains the project-local modular source layout. Repo-level `db:materialize` is a deprecated Brightweb workspace compatibility command and is not the intended workflow for standalone generated apps.
|
|
55
|
+
|
|
56
|
+
## Related READMEs
|
|
57
|
+
|
|
58
|
+
- `supabase/modules/core/README.md`
|
|
59
|
+
- `supabase/modules/admin/README.md`
|
|
60
|
+
- `supabase/modules/crm/README.md`
|
|
61
|
+
- `supabase/modules/projects/README.md`
|
|
62
|
+
- `supabase/clients/README.md`
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Client-Specific Migrations
|
|
2
|
+
|
|
3
|
+
This directory is reserved for true client-only schema deltas.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
Only place SQL here when the change:
|
|
8
|
+
|
|
9
|
+
- cannot be reused by other clients
|
|
10
|
+
- should not be part of a shared module
|
|
11
|
+
- is intentionally isolated to a single client deployment
|
|
12
|
+
|
|
13
|
+
## Expected shape
|
|
14
|
+
|
|
15
|
+
- `clients/<slug>/...`
|
|
16
|
+
|
|
17
|
+
Temporary smoke-test client stacks should be removed after generator verification. This directory is for real client-specific deltas only.
|
|
18
|
+
|
|
19
|
+
If a client-specific migration later proves reusable, move the concept into the appropriate shared module and stop extending the client-only path.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"modules": {
|
|
3
|
+
"core": {
|
|
4
|
+
"label": "Core",
|
|
5
|
+
"path": "supabase/modules/core/migrations",
|
|
6
|
+
"dependsOn": [],
|
|
7
|
+
"description": "Shared auth/profile, events, rate limits, and always-on platform foundations."
|
|
8
|
+
},
|
|
9
|
+
"admin": {
|
|
10
|
+
"label": "Admin",
|
|
11
|
+
"path": "supabase/modules/admin/migrations",
|
|
12
|
+
"dependsOn": ["core"],
|
|
13
|
+
"description": "RBAC, user governance, and privileged role-management behavior."
|
|
14
|
+
},
|
|
15
|
+
"crm": {
|
|
16
|
+
"label": "CRM",
|
|
17
|
+
"path": "supabase/modules/crm/migrations",
|
|
18
|
+
"dependsOn": ["core", "admin"],
|
|
19
|
+
"description": "Organizations, CRM contacts, org membership, and invitation flows."
|
|
20
|
+
},
|
|
21
|
+
"projects": {
|
|
22
|
+
"label": "Projects",
|
|
23
|
+
"path": "supabase/modules/projects/migrations",
|
|
24
|
+
"dependsOn": ["core", "admin", "crm"],
|
|
25
|
+
"description": "Projects, tasks, milestones, members, links, and project activity policies."
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Admin Migrations
|
|
2
|
+
|
|
3
|
+
`admin` owns user governance, RBAC, and privileged role-management behavior.
|
|
4
|
+
|
|
5
|
+
## Owns
|
|
6
|
+
|
|
7
|
+
- `roles`
|
|
8
|
+
- `user_role_assignments`
|
|
9
|
+
- `role_change_audit`
|
|
10
|
+
- admin-only helper functions like `admin_set_user_role(...)`
|
|
11
|
+
- privileged profile-field guards
|
|
12
|
+
- default role-assignment triggers/backfills
|
|
13
|
+
|
|
14
|
+
## Dependency
|
|
15
|
+
|
|
16
|
+
Depends on:
|
|
17
|
+
|
|
18
|
+
- `core` (`profiles`, `current_profile_id()`, auth linkage)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|