@donotdev/cli 0.0.19 → 0.0.21

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 (128) hide show
  1. package/README.md +31 -0
  2. package/dependencies-matrix.json +205 -50
  3. package/dist/bin/commands/agent-setup.js +2 -2
  4. package/dist/bin/commands/build.js +6 -6
  5. package/dist/bin/commands/bump.js +495 -70
  6. package/dist/bin/commands/cacheout.js +6 -6
  7. package/dist/bin/commands/coach.js +6 -6
  8. package/dist/bin/commands/create-app.js +24 -16
  9. package/dist/bin/commands/create-project.js +114 -18
  10. package/dist/bin/commands/db.js +142136 -0
  11. package/dist/bin/commands/deploy.js +354 -126
  12. package/dist/bin/commands/dev.js +6 -6
  13. package/dist/bin/commands/doctor.js +140 -33
  14. package/dist/bin/commands/emu.js +6 -6
  15. package/dist/bin/commands/format.js +6 -6
  16. package/dist/bin/commands/get-demo.js +11 -6
  17. package/dist/bin/commands/make-admin.js +14210 -13770
  18. package/dist/bin/commands/preview.js +6 -6
  19. package/dist/bin/commands/seed.js +142426 -0
  20. package/dist/bin/commands/setup-cicd.js +8904 -0
  21. package/dist/bin/commands/setup.js +259 -212
  22. package/dist/bin/commands/staging.js +361 -127
  23. package/dist/bin/commands/sync-secrets.js +55 -33
  24. package/dist/bin/commands/type-check.js +16 -10
  25. package/dist/bin/commands/wai.js +6 -6
  26. package/dist/bin/dndev.js +194 -188
  27. package/dist/bin/donotdev.js +139 -189
  28. package/dist/index.js +468 -144
  29. package/package.json +1 -1
  30. package/templates/app-demo/.env.example +1 -0
  31. package/templates/{root-consumer → app-demo}/entities/ExampleEntity.ts.example +15 -9
  32. package/templates/app-demo/index.html.example +1 -1
  33. package/templates/app-demo/public/apple-touch-icon.png.example +0 -0
  34. package/templates/app-demo/public/favicon.svg.example +1 -0
  35. package/templates/app-demo/public/icon-192x192.png.example +0 -0
  36. package/templates/app-demo/public/icon-512x512.png.example +0 -0
  37. package/templates/app-demo/src/App.tsx.example +3 -1
  38. package/templates/app-demo/src/config/app.ts.example +1 -0
  39. package/templates/app-demo/src/entities/booking.ts.example +75 -0
  40. package/templates/app-demo/src/entities/onboarding.ts.example +160 -0
  41. package/templates/app-demo/src/entities/product.ts.example +12 -0
  42. package/templates/app-demo/src/entities/quote.ts.example +70 -0
  43. package/templates/app-demo/src/pages/ChangelogPage.tsx.example +28 -1
  44. package/templates/app-demo/src/pages/ConditionalFormPage.tsx.example +88 -0
  45. package/templates/app-demo/src/pages/DashboardPage.tsx.example +2 -0
  46. package/templates/app-demo/src/pages/HomePage.tsx.example +355 -2
  47. package/templates/app-demo/src/pages/OnboardingPage.tsx.example +47 -0
  48. package/templates/app-demo/src/pages/PricingPage.tsx.example +28 -1
  49. package/templates/app-demo/src/pages/ProductsPage.tsx.example +2 -0
  50. package/templates/app-demo/src/pages/ProfilePage.tsx.example +2 -0
  51. package/templates/app-demo/src/pages/SettingsPage.tsx.example +2 -0
  52. package/templates/app-demo/src/pages/ShowcaseDetailPage.tsx.example +22 -16
  53. package/templates/app-demo/src/pages/ShowcasePage.tsx.example +3 -1
  54. package/templates/app-demo/src/pages/components/ComponentRenderer.tsx.example +147 -51
  55. package/templates/app-demo/src/pages/components/ComponentsData.tsx.example +103 -21
  56. package/templates/app-demo/src/pages/components/componentConfig.ts.example +139 -59
  57. package/templates/app-demo/src/pages/legal/LegalPage.tsx.example +12 -1
  58. package/templates/app-demo/src/pages/legal/PrivacyPage.tsx.example +10 -1
  59. package/templates/app-demo/src/pages/legal/TermsPage.tsx.example +10 -1
  60. package/templates/app-demo/src/themes.css.example +289 -77
  61. package/templates/app-demo/stats.html.example +4949 -0
  62. package/templates/app-dndev/index.html.example +164 -0
  63. package/templates/app-dndev/public/logo.svg.example +1 -0
  64. package/templates/app-dndev/public/manifest.json.example +10 -0
  65. package/templates/app-dndev/src/App.tsx.example +35 -0
  66. package/templates/app-dndev/src/components/CockpitLayout.css.example +181 -0
  67. package/templates/app-dndev/src/components/CockpitLayout.tsx.example +209 -0
  68. package/templates/app-dndev/src/components/Kanban.css.example +385 -0
  69. package/templates/app-dndev/src/components/ModeToggle.tsx.example +32 -0
  70. package/templates/app-dndev/src/components/OverlaySlot.tsx.example +68 -0
  71. package/templates/app-dndev/src/components/TerminalPanel.css.example +228 -0
  72. package/templates/app-dndev/src/components/TerminalPanel.tsx.example +714 -0
  73. package/templates/app-dndev/src/components/markdown-prose.css.example +49 -0
  74. package/templates/app-dndev/src/components/phases/CaptainLog.tsx.example +107 -0
  75. package/templates/app-dndev/src/components/phases/ContextTabs.tsx.example +352 -0
  76. package/templates/app-dndev/src/components/phases/PhaseCard.tsx.example +126 -0
  77. package/templates/app-dndev/src/components/phases/PhaseDetail.tsx.example +147 -0
  78. package/templates/app-dndev/src/components/phases/ReviewPanel.tsx.example +115 -0
  79. package/templates/app-dndev/src/components/phases/phaseData.ts.example +366 -0
  80. package/templates/app-dndev/src/config/app.ts.example +103 -0
  81. package/templates/app-dndev/src/config/commands.ts.example +171 -0
  82. package/templates/app-dndev/src/config/legal.ts.example +170 -0
  83. package/templates/app-dndev/src/config/providers.ts.example +7 -0
  84. package/templates/app-dndev/src/globals.css.example +10 -0
  85. package/templates/app-dndev/src/hooks/useDndevFile.ts.example +144 -0
  86. package/templates/app-dndev/src/main.tsx.example +21 -0
  87. package/templates/app-dndev/src/pages/BoardPage.tsx.example +640 -0
  88. package/templates/app-dndev/src/pages/GrillPage.tsx.example +658 -0
  89. package/templates/app-dndev/src/pages/HomePage.tsx.example +347 -0
  90. package/templates/app-dndev/src/pages/NotFoundPage.tsx.example +33 -0
  91. package/templates/app-dndev/src/pages/PhasesPage.tsx.example +137 -0
  92. package/templates/app-dndev/src/pages/SettingsPage.tsx.example +64 -0
  93. package/templates/app-dndev/src/pages/legal/LegalNoticePage.tsx.example +75 -0
  94. package/templates/app-dndev/src/pages/legal/PrivacyPage.tsx.example +69 -0
  95. package/templates/app-dndev/src/pages/legal/TermsPage.tsx.example +71 -0
  96. package/templates/app-dndev/src/stores/dndevStore.ts.example +386 -0
  97. package/templates/app-dndev/src/themes.css.example +161 -0
  98. package/templates/app-dndev/terminal-sidecar.cjs.example +341 -0
  99. package/templates/app-dndev/tsconfig.json.example +9 -0
  100. package/templates/app-dndev/vite.config.ts.example +24 -0
  101. package/templates/app-vite/index.html.example +1 -1
  102. package/templates/functions-supabase/supabase/functions/.env.example +0 -2
  103. package/templates/root-consumer/.claude/commands/grill.md.example +86 -8
  104. package/templates/root-consumer/.dndev.secrets.example +32 -0
  105. package/templates/root-consumer/.gitignore.example +3 -0
  106. package/templates/root-consumer/AI.md.example +4 -0
  107. package/templates/root-consumer/entities/index.ts.example +2 -5
  108. package/templates/root-consumer/guides/dndev/COMPONENTS_ATOMIC.md.example +4 -0
  109. package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +23 -20
  110. package/templates/root-consumer/guides/dndev/INDEX.md.example +1 -0
  111. package/templates/root-consumer/guides/dndev/SETUP_BILLING.md.example +3 -7
  112. package/templates/root-consumer/guides/dndev/SETUP_CICD.md.example +115 -0
  113. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +41 -0
  114. package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +13 -18
  115. package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +17 -12
  116. package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +185 -251
  117. package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +26 -8
  118. package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +66 -49
  119. package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +6 -5
  120. package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +9 -9
  121. package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +1 -1
  122. package/templates/root-consumer/guides/wai-way/blueprints/4_configure.md.example +7 -6
  123. package/templates/root-consumer/guides/wai-way/context_map.json.example +51 -20
  124. package/templates/root-consumer/guides/wai-way/hld_template.md.example +138 -0
  125. package/templates/root-consumer/guides/wai-way/lld_template.md.example +103 -0
  126. package/templates/root-consumer/guides/wai-way/prd_template.md.example +140 -0
  127. /package/templates/{root-consumer → app-demo}/entities/Contact.ts.example +0 -0
  128. /package/templates/{root-consumer → app-demo}/entities/demo.ts.example +0 -0
@@ -7077,7 +7077,7 @@ var init_PathResolver = __esm({
7077
7077
  }
7078
7078
  const detectedFormat = this._detectFormat(filePath, format);
7079
7079
  let writeContent;
7080
- if (Buffer2.isBuffer(content)) {
7080
+ if (Buffer.isBuffer(content)) {
7081
7081
  writeContent = content;
7082
7082
  } else if (detectedFormat === "json" && typeof content === "object") {
7083
7083
  writeContent = JSON.stringify(content, null, 2);
@@ -7086,7 +7086,7 @@ var init_PathResolver = __esm({
7086
7086
  }
7087
7087
  try {
7088
7088
  return await safeExecuteAsync(async () => {
7089
- if (Buffer2.isBuffer(writeContent)) {
7089
+ if (Buffer.isBuffer(writeContent)) {
7090
7090
  await fs.promises.writeFile(normalizedPath, writeContent);
7091
7091
  } else {
7092
7092
  await fs.promises.writeFile(normalizedPath, writeContent, "utf8");
@@ -7138,7 +7138,7 @@ var init_PathResolver = __esm({
7138
7138
  }
7139
7139
  const detectedFormat = this._detectFormat(filePath, format);
7140
7140
  let writeContent;
7141
- if (Buffer2.isBuffer(content)) {
7141
+ if (Buffer.isBuffer(content)) {
7142
7142
  writeContent = content;
7143
7143
  } else if (detectedFormat === "json" && typeof content === "object") {
7144
7144
  writeContent = JSON.stringify(content, null, 2);
@@ -7146,7 +7146,7 @@ var init_PathResolver = __esm({
7146
7146
  writeContent = String(content);
7147
7147
  }
7148
7148
  try {
7149
- if (Buffer2.isBuffer(writeContent)) {
7149
+ if (Buffer.isBuffer(writeContent)) {
7150
7150
  fs.writeFileSync(normalizedPath, writeContent);
7151
7151
  } else {
7152
7152
  fs.writeFileSync(normalizedPath, writeContent, "utf8");
@@ -8034,7 +8034,7 @@ var init_typed_file_operations = __esm({
8034
8034
  });
8035
8035
 
8036
8036
  // packages/tooling/src/bundler/utils.ts
8037
- import { Buffer as Buffer2 } from "node:buffer";
8037
+ import { Buffer } from "node:buffer";
8038
8038
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
8039
8039
  import { createRequire as createRequire3 } from "node:module";
8040
8040
  import { dirname as dirname3, resolve as resolve3 } from "node:path";
@@ -8051,7 +8051,7 @@ var init_utils = __esm({
8051
8051
  globalThis.require = require2;
8052
8052
  globalThis.__filename = __filename;
8053
8053
  globalThis.__dirname = __dirname;
8054
- globalThis.Buffer = Buffer2;
8054
+ globalThis.Buffer = Buffer;
8055
8055
  globalThis.process = process;
8056
8056
  if (typeof global === "undefined") {
8057
8057
  globalThis.global = globalThis;
@@ -8251,7 +8251,28 @@ var init_cli_input = __esm({
8251
8251
  }
8252
8252
  });
8253
8253
 
8254
- // packages/tooling/src/cli/setup/vercel-token.ts
8254
+ // packages/tooling/src/utils/secrets-resolver.ts
8255
+ function parseEnvFile(filePath) {
8256
+ if (!pathExists(filePath)) return {};
8257
+ const content = readSync(filePath, { format: "text" });
8258
+ if (typeof content !== "string" || !content) return {};
8259
+ const result = {};
8260
+ for (const line of content.split(/\r?\n/)) {
8261
+ const trimmed = line.trim();
8262
+ if (!trimmed || trimmed.startsWith("#")) continue;
8263
+ const eqIdx = trimmed.indexOf("=");
8264
+ if (eqIdx === -1) continue;
8265
+ const key = trimmed.slice(0, eqIdx).trim();
8266
+ let value = trimmed.slice(eqIdx + 1).trim();
8267
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
8268
+ value = value.slice(1, -1);
8269
+ }
8270
+ if (key && value) {
8271
+ result[key] = value;
8272
+ }
8273
+ }
8274
+ return result;
8275
+ }
8255
8276
  function readEnvVar(filePath, varName) {
8256
8277
  if (!pathExists(filePath)) return null;
8257
8278
  const content = readSync(filePath, { format: "text" });
@@ -8260,16 +8281,93 @@ function readEnvVar(filePath, varName) {
8260
8281
  const trimmed = line.trim();
8261
8282
  if (!trimmed || trimmed.startsWith("#")) continue;
8262
8283
  if (trimmed.startsWith(`${varName}=`)) {
8263
- const val = trimmed.substring(`${varName}=`.length).trim();
8284
+ let val = trimmed.substring(`${varName}=`.length).trim();
8264
8285
  if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
8265
- return val.slice(1, -1);
8286
+ val = val.slice(1, -1);
8266
8287
  }
8267
8288
  return val || null;
8268
8289
  }
8269
8290
  }
8270
8291
  return null;
8271
8292
  }
8272
- function resolveVercelVar(appDir, varName) {
8293
+ function loadDndevSecrets(projectRoot) {
8294
+ const secretsPath = joinPath(projectRoot, ".dndev.secrets");
8295
+ if (cachedSecretsPath === secretsPath && cachedSecrets) {
8296
+ return cachedSecrets;
8297
+ }
8298
+ cachedSecrets = parseEnvFile(secretsPath);
8299
+ cachedSecretsPath = secretsPath;
8300
+ return cachedSecrets;
8301
+ }
8302
+ function resolveSecret(name, projectRoot, opts) {
8303
+ const appDir = opts?.appDir ?? projectRoot;
8304
+ const envValue = process.env[name];
8305
+ if (envValue) {
8306
+ return { value: envValue, source: "process.env" };
8307
+ }
8308
+ const secrets = loadDndevSecrets(projectRoot);
8309
+ if (secrets[name]) {
8310
+ return { value: secrets[name], source: ".dndev.secrets" };
8311
+ }
8312
+ if (name === "SUPABASE_SECRET_KEY" && secrets["SUPABASE_SERVICE_ROLE_KEY"]) {
8313
+ return {
8314
+ value: secrets["SUPABASE_SERVICE_ROLE_KEY"],
8315
+ source: ".dndev.secrets"
8316
+ };
8317
+ }
8318
+ const legacyPaths = LEGACY_PATHS[name];
8319
+ if (legacyPaths) {
8320
+ for (const relPath of legacyPaths) {
8321
+ const fullPath = joinPath(appDir, relPath);
8322
+ const value = readEnvVar(fullPath, name);
8323
+ if (value) {
8324
+ if (!opts?.silent && !warnedLegacy.has(name)) {
8325
+ warnedLegacy.add(name);
8326
+ log.warn(
8327
+ `${name} found in ${relPath} (legacy). Move it to .dndev.secrets at project root.`
8328
+ );
8329
+ }
8330
+ return { value, source: "legacy", legacyPath: relPath };
8331
+ }
8332
+ if (name === "SUPABASE_SECRET_KEY") {
8333
+ const aliasValue = readEnvVar(fullPath, "SUPABASE_SERVICE_ROLE_KEY");
8334
+ if (aliasValue) {
8335
+ if (!opts?.silent && !warnedLegacy.has(name)) {
8336
+ warnedLegacy.add(name);
8337
+ log.warn(
8338
+ `${name} found in ${relPath} (legacy). Move it to .dndev.secrets at project root.`
8339
+ );
8340
+ }
8341
+ return { value: aliasValue, source: "legacy", legacyPath: relPath };
8342
+ }
8343
+ }
8344
+ }
8345
+ }
8346
+ return null;
8347
+ }
8348
+ var LEGACY_PATHS, warnedLegacy, cachedSecretsPath, cachedSecrets;
8349
+ var init_secrets_resolver = __esm({
8350
+ "packages/tooling/src/utils/secrets-resolver.ts"() {
8351
+ "use strict";
8352
+ init_utils();
8353
+ init_pathResolver();
8354
+ init_cli_output();
8355
+ LEGACY_PATHS = {
8356
+ VERCEL_TOKEN: [".env.local"],
8357
+ SUPABASE_SECRET_KEY: ["supabase/functions/.env", "functions/.env"],
8358
+ SUPABASE_DB_URL: ["supabase/functions/.env", "functions/.env"],
8359
+ SUPABASE_ACCESS_TOKEN: ["supabase/functions/.env", "functions/.env"],
8360
+ STRIPE_SECRET_KEY: ["supabase/functions/.env", "functions/.env"],
8361
+ STRIPE_WEBHOOK_SECRET: ["supabase/functions/.env", "functions/.env"]
8362
+ };
8363
+ warnedLegacy = /* @__PURE__ */ new Set();
8364
+ cachedSecretsPath = null;
8365
+ cachedSecrets = null;
8366
+ }
8367
+ });
8368
+
8369
+ // packages/tooling/src/cli/setup/vercel-token.ts
8370
+ function resolvePerAppVar(appDir, varName) {
8273
8371
  if (process.env[varName]) return process.env[varName];
8274
8372
  const fromLocal = readEnvVar(joinPath(appDir, ".env.local"), varName);
8275
8373
  if (fromLocal) return fromLocal;
@@ -8277,10 +8375,11 @@ function resolveVercelVar(appDir, varName) {
8277
8375
  if (fromEnv) return fromEnv;
8278
8376
  return null;
8279
8377
  }
8280
- function resolveVercelCredentials(appDir) {
8281
- const token = resolveVercelVar(appDir, "VERCEL_TOKEN");
8282
- const orgId = resolveVercelVar(appDir, "VERCEL_ORG_ID");
8283
- const projectId = resolveVercelVar(appDir, "VERCEL_PROJECT_ID");
8378
+ function resolveVercelCredentials(appDir, projectRoot) {
8379
+ const root = projectRoot ?? process.cwd();
8380
+ const token = resolveSecret("VERCEL_TOKEN", root, { appDir })?.value ?? null;
8381
+ const orgId = resolvePerAppVar(appDir, "VERCEL_ORG_ID");
8382
+ const projectId = resolvePerAppVar(appDir, "VERCEL_PROJECT_ID");
8284
8383
  const missing = [];
8285
8384
  if (!token) missing.push("VERCEL_TOKEN");
8286
8385
  if (!orgId) missing.push("VERCEL_ORG_ID");
@@ -8294,6 +8393,7 @@ var init_vercel_token = __esm({
8294
8393
  "packages/tooling/src/cli/setup/vercel-token.ts"() {
8295
8394
  "use strict";
8296
8395
  init_utils();
8396
+ init_secrets_resolver();
8297
8397
  init_pathResolver();
8298
8398
  }
8299
8399
  });
@@ -8912,40 +9012,15 @@ var init_firebase = __esm({
8912
9012
  });
8913
9013
 
8914
9014
  // packages/tooling/src/utils/supabase-management.ts
8915
- import { execSync, spawnSync as spawnSync2 } from "node:child_process";
8916
- function stripQuotes(value) {
8917
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
8918
- return value.slice(1, -1);
8919
- }
8920
- return value;
8921
- }
8922
- function detectDbUrl(appDir) {
8923
- const envPaths = [
8924
- joinPath(appDir, "supabase", "functions", ".env"),
8925
- joinPath(appDir, "functions", ".env"),
8926
- joinPath(appDir, ".env.local"),
8927
- joinPath(appDir, ".env")
8928
- ];
8929
- for (const envPath of envPaths) {
8930
- if (!pathExists(envPath)) continue;
8931
- try {
8932
- const content = readSync(envPath);
8933
- if (!content) continue;
8934
- for (const line of content.split("\n")) {
8935
- const trimmed = line.trim();
8936
- if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
8937
- const eqIdx = trimmed.indexOf("=");
8938
- const key = trimmed.slice(0, eqIdx).trim();
8939
- const value = stripQuotes(trimmed.slice(eqIdx + 1).trim());
8940
- if (key === "SUPABASE_DB_URL" && value) {
8941
- return value;
8942
- }
8943
- }
8944
- } catch {
8945
- continue;
8946
- }
9015
+ import { execSync } from "node:child_process";
9016
+ function extractProjectRef(supabaseUrl) {
9017
+ const match = supabaseUrl.match(/https?:\/\/([a-z0-9]+)\.supabase\.co/);
9018
+ if (!match?.[1]) {
9019
+ throw new Error(
9020
+ `Cannot extract project ref from URL: ${supabaseUrl}. Expected format: https://<ref>.supabase.co`
9021
+ );
8947
9022
  }
8948
- return null;
9023
+ return match[1];
8949
9024
  }
8950
9025
  async function cleanupOldEntityMigrations(supabaseDir, latestFile) {
8951
9026
  const migrationsDir = joinPath(supabaseDir, "migrations");
@@ -8959,29 +9034,6 @@ async function cleanupOldEntityMigrations(supabaseDir, latestFile) {
8959
9034
  const latestBasename = getBasename(latestFile);
8960
9035
  const toRemove = oldFiles.filter((f) => getBasename(f) !== latestBasename);
8961
9036
  if (toRemove.length === 0) return;
8962
- const timestamps = [];
8963
- for (const f of toRemove) {
8964
- const match = getBasename(f).match(/^(\d{14})_/);
8965
- if (match?.[1]) timestamps.push(match[1]);
8966
- }
8967
- if (timestamps.length > 0) {
8968
- const cmd = getSupabaseCmd();
8969
- const { dirname: dirname4 } = await import("node:path");
8970
- const projectRoot = dirname4(supabaseDir);
8971
- try {
8972
- execSync(
8973
- `${cmd} migration repair --status reverted ${timestamps.join(" ")}`,
8974
- { cwd: projectRoot, stdio: "pipe", env: { ...process.env } }
8975
- );
8976
- log.info(
8977
- `Repaired ${timestamps.length} old entity migration(s) in remote`
8978
- );
8979
- } catch (error2) {
8980
- log.debug(
8981
- `Migration repair skipped: ${error2 instanceof Error ? error2.message : String(error2)}`
8982
- );
8983
- }
8984
- }
8985
9037
  for (const f of toRemove) {
8986
9038
  try {
8987
9039
  removeSync(f);
@@ -8991,7 +9043,7 @@ async function cleanupOldEntityMigrations(supabaseDir, latestFile) {
8991
9043
  log.info(`Cleaned up ${toRemove.length} old entity migration file(s)`);
8992
9044
  }
8993
9045
  async function executeMigration(options) {
8994
- const { supabaseDir } = options;
9046
+ const { supabaseDir, supabaseUrl, accessToken } = options;
8995
9047
  if (!pathExists(supabaseDir)) {
8996
9048
  throw new Error(`Supabase directory not found: ${supabaseDir}`);
8997
9049
  }
@@ -8999,54 +9051,30 @@ async function executeMigration(options) {
8999
9051
  if (!pathExists(migrationsDir)) {
9000
9052
  throw new Error(`Migrations directory not found: ${migrationsDir}`);
9001
9053
  }
9002
- const { dirname: dirname4 } = await import("node:path");
9003
- const projectRoot = dirname4(supabaseDir);
9004
- try {
9005
- const cmd = getSupabaseCmd();
9006
- const parts = cmd.split(" ");
9007
- const bin = parts[0];
9008
- const baseArgs = [
9009
- ...parts.slice(1),
9010
- "db",
9011
- "push",
9012
- "--include-all",
9013
- "--db-url",
9014
- options.dbUrl
9015
- ];
9016
- const result = spawnSync2(bin, baseArgs, {
9017
- cwd: projectRoot,
9018
- stdio: ["pipe", "pipe", "pipe"],
9019
- input: "Y\n",
9020
- env: { ...process.env }
9021
- });
9022
- if (result.status !== 0) {
9023
- const stderr = result.stderr?.toString() || "";
9024
- throw new Error(stderr || `Process exited with code ${result.status}`);
9025
- }
9026
- } catch (error2) {
9027
- const raw = error2 instanceof Error ? error2.message : String(error2);
9028
- const sanitized = raw.replace(
9029
- /postgresql:\/\/[^\s"']*/g,
9030
- "postgresql://***"
9031
- );
9054
+ const sqlFiles = readdirSync2(migrationsDir).filter((f) => f.endsWith(".sql")).sort().map((f) => joinPath(migrationsDir, f));
9055
+ if (sqlFiles.length === 0) {
9056
+ throw new Error(`No .sql files found in ${migrationsDir}`);
9057
+ }
9058
+ const sql = sqlFiles.map((f) => readSync(f)).filter(Boolean).join("\n\n");
9059
+ const projectRef = extractProjectRef(supabaseUrl);
9060
+ const mgmtUrl = `https://api.supabase.com/v1/projects/${projectRef}/database/query`;
9061
+ const response = await fetch(mgmtUrl, {
9062
+ method: "POST",
9063
+ headers: {
9064
+ "Content-Type": "application/json",
9065
+ Authorization: `Bearer ${accessToken}`
9066
+ },
9067
+ body: JSON.stringify({ query: sql })
9068
+ });
9069
+ if (!response.ok) {
9070
+ const body = await response.text();
9071
+ const safe = body.replace(/sbp_[^\s"']*/g, "***").replace(/sb_secret_[^\s"']*/g, "***");
9032
9072
  throw new Error(
9033
- `Failed to push migrations: ${sanitized}
9034
- Check your SUPABASE_DB_URL (format: postgresql://postgres:<password>@db.<ref>.supabase.co:5432/postgres)`
9073
+ `Supabase Management API error (HTTP ${response.status}): ${safe}
9074
+ Ensure SUPABASE_ACCESS_TOKEN is valid. Get one from: https://supabase.com/dashboard/account/tokens`
9035
9075
  );
9036
9076
  }
9037
- }
9038
- function getSupabaseCmd() {
9039
- try {
9040
- execSync("supabase --version", { stdio: "pipe" });
9041
- return "supabase";
9042
- } catch {
9043
- try {
9044
- execSync("bunx supabase --version", { stdio: "pipe" });
9045
- return "bunx supabase";
9046
- } catch {
9047
- return "npx supabase";
9048
- }
9049
- }
9077
+ log.info("Migrations executed via Supabase Management API");
9050
9078
  }
9051
9079
  var init_supabase_management = __esm({
9052
9080
  "packages/tooling/src/utils/supabase-management.ts"() {
@@ -9501,7 +9529,7 @@ var init_base_generator = __esm({
9501
9529
  async isBinaryFile(filePath) {
9502
9530
  try {
9503
9531
  const fd = await openFile(filePath, "r");
9504
- const buffer = Buffer2.alloc(4096);
9532
+ const buffer = Buffer.alloc(4096);
9505
9533
  await fd.read(buffer, 0, 4096, 0);
9506
9534
  await fd.close();
9507
9535
  for (let i = 0; i < buffer.length; i++) {
@@ -9660,6 +9688,9 @@ function roleToSqlCondition(role) {
9660
9688
  case "super":
9661
9689
  return "(auth.jwt()->'app_metadata'->>'role') = 'super'";
9662
9690
  default:
9691
+ log.warn(
9692
+ `Unknown access role "${role}" \u2014 defaulting to deny (false). Use guest|user|admin|super.`
9693
+ );
9663
9694
  return "false";
9664
9695
  }
9665
9696
  }
@@ -9667,7 +9698,20 @@ function camelToSnake(str) {
9667
9698
  return str.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
9668
9699
  }
9669
9700
  function fieldTypeToSql(fieldType) {
9670
- return FIELD_TYPE_TO_SQL[fieldType] ?? "text";
9701
+ return FIELD_TYPE_TO_SQL[fieldType] ?? null;
9702
+ }
9703
+ function inferSqlTypeFromValidation(validation) {
9704
+ if (!validation) return "text";
9705
+ if (validation.min !== void 0 || validation.max !== void 0)
9706
+ return "numeric";
9707
+ if (validation.minLength !== void 0 || validation.maxLength !== void 0)
9708
+ return "text";
9709
+ if (Array.isArray(validation.options) && validation.options.length > 0) {
9710
+ const first = validation.options[0];
9711
+ if (first && typeof first === "object" && typeof first.value === "number")
9712
+ return "numeric";
9713
+ }
9714
+ return "text";
9671
9715
  }
9672
9716
  function schemaRequiresJsonb(schema) {
9673
9717
  if (!schema || typeof schema !== "object") return false;
@@ -9838,7 +9882,6 @@ var init_sql_generator = __esm({
9838
9882
  const tableName = this.safeTableName(entity.collection);
9839
9883
  const columns = [];
9840
9884
  columns.push("id uuid PRIMARY KEY DEFAULT gen_random_uuid()");
9841
- columns.push("user_id uuid NOT NULL");
9842
9885
  columns.push("created_at timestamptz NOT NULL DEFAULT now()");
9843
9886
  columns.push("updated_at timestamptz NOT NULL DEFAULT now()");
9844
9887
  columns.push("created_by_id uuid");
@@ -9854,14 +9897,23 @@ var init_sql_generator = __esm({
9854
9897
  if (schema && schemaRequiresJsonb(schema)) {
9855
9898
  sqlType = "jsonb";
9856
9899
  } else {
9857
- sqlType = fieldTypeToSql(field.type || "text");
9900
+ sqlType = fieldTypeToSql(field.type || "text") ?? inferSqlTypeFromValidation(field.validation);
9858
9901
  }
9859
9902
  columns.push(`${colName} ${sqlType}`);
9860
9903
  }
9861
9904
  const columnList = columns.join(",\n ");
9862
- return `CREATE TABLE IF NOT EXISTS ${tableName} (
9905
+ const createSql = `CREATE TABLE IF NOT EXISTS ${tableName} (
9863
9906
  ${columnList}
9864
9907
  );`;
9908
+ const alterDefaults = [
9909
+ `ALTER TABLE ${tableName} ALTER COLUMN id SET DEFAULT gen_random_uuid();`,
9910
+ `ALTER TABLE ${tableName} ALTER COLUMN created_at SET DEFAULT now();`,
9911
+ `ALTER TABLE ${tableName} ALTER COLUMN updated_at SET DEFAULT now();`,
9912
+ `ALTER TABLE ${tableName} ALTER COLUMN status SET DEFAULT 'available';`
9913
+ ].join("\n");
9914
+ return `${createSql}
9915
+
9916
+ ${alterDefaults}`;
9865
9917
  }
9866
9918
  buildRlsSql(tableName, access) {
9867
9919
  const quoted = this.safeTableName(tableName);
@@ -10148,29 +10200,9 @@ function detectPublicConfig(appDir, framework) {
10148
10200
  }
10149
10201
  return null;
10150
10202
  }
10151
- function detectSecretKey(appDir) {
10152
- const candidates = [
10153
- joinPath(appDir, "supabase", "functions", ".env"),
10154
- joinPath(appDir, "functions", ".env")
10155
- ];
10156
- for (const functionsEnvPath of candidates) {
10157
- if (!pathExists(functionsEnvPath)) continue;
10158
- const envContentRaw = readSync(functionsEnvPath, { format: "text" });
10159
- const envContent = typeof envContentRaw === "string" ? envContentRaw : "";
10160
- const lines = envContent.split("\n");
10161
- for (const line of lines) {
10162
- const trimmed = line.trim();
10163
- if (trimmed.startsWith("SUPABASE_SECRET_KEY=") || trimmed.startsWith("SUPABASE_SERVICE_ROLE_KEY=")) {
10164
- const match = trimmed.match(
10165
- /^SUPABASE_(?:SECRET|SERVICE_ROLE)_KEY=(.+)$/
10166
- );
10167
- if (match && match[1]) {
10168
- return match[1].trim();
10169
- }
10170
- }
10171
- }
10172
- }
10173
- return null;
10203
+ function detectSecretKey(appDir, projectRoot) {
10204
+ const root = projectRoot ?? process.cwd();
10205
+ return resolveSecret("SUPABASE_SECRET_KEY", root, { appDir })?.value ?? null;
10174
10206
  }
10175
10207
  async function generateCrudFunction(supabaseDir, projectRoot) {
10176
10208
  const templatesRoot = getTemplatesRoot();
@@ -10254,26 +10286,23 @@ async function main3(options = {}) {
10254
10286
  const supabaseUrl = existingConfig.url;
10255
10287
  log.success(`Supabase URL: ${supabaseUrl}`);
10256
10288
  const secretKey = detectSecretKey(appDir);
10257
- const dbUrl = detectDbUrl(appDir);
10289
+ const accessToken = resolveSecret("SUPABASE_ACCESS_TOKEN", projectRoot, { appDir })?.value ?? null;
10258
10290
  if (secretKey) log.success("Secret key detected");
10259
- else log.error("Missing SUPABASE_SECRET_KEY in supabase/functions/.env");
10260
- if (dbUrl) log.success("DB URL detected");
10261
- else log.error("Missing SUPABASE_DB_URL in supabase/functions/.env");
10262
- if (!secretKey || !dbUrl) {
10263
- const functionsEnvPath = joinPath(supabaseDir, "functions", ".env");
10291
+ else log.error("Missing SUPABASE_SECRET_KEY");
10292
+ if (accessToken) log.success("Access token detected");
10293
+ else log.error("Missing SUPABASE_ACCESS_TOKEN");
10294
+ if (!secretKey || !accessToken) {
10264
10295
  const missing = [];
10265
10296
  if (!secretKey) missing.push(" SUPABASE_SECRET_KEY=your-service-role-key");
10266
- if (!dbUrl)
10297
+ if (!accessToken)
10267
10298
  missing.push(
10268
- " SUPABASE_DB_URL=postgresql://postgres:...@db.<ref>.supabase.co:5432/postgres"
10299
+ " SUPABASE_ACCESS_TOKEN=sbp_... (from https://supabase.com/dashboard/account/tokens)"
10269
10300
  );
10270
10301
  Me(
10271
10302
  [
10272
- `Fill in ${functionsEnvPath}:`,
10303
+ "Fill in .dndev.secrets at project root:",
10273
10304
  ...missing,
10274
10305
  "",
10275
- "Get these from: https://supabase.com/dashboard > Settings > Database",
10276
- "",
10277
10306
  "Then re-run: dndev setup supabase"
10278
10307
  ].join("\n"),
10279
10308
  "Missing Credentials"
@@ -10340,7 +10369,7 @@ async function main3(options = {}) {
10340
10369
  const s = Y2();
10341
10370
  s.start("Pushing migrations...");
10342
10371
  try {
10343
- await executeMigration({ supabaseDir, dbUrl });
10372
+ await executeMigration({ supabaseDir, supabaseUrl, accessToken });
10344
10373
  s.stop("Migrations pushed successfully");
10345
10374
  migrationsPushed = true;
10346
10375
  } catch (error2) {
@@ -10359,10 +10388,13 @@ async function main3(options = {}) {
10359
10388
  const steps = [];
10360
10389
  steps.push("\u2705 Credentials validated from .env");
10361
10390
  if (secretKey) steps.push("\u2705 Secret key detected");
10362
- else steps.push("\u274C Secret key missing \u2014 cannot push migrations");
10391
+ else steps.push("\u274C Secret key missing");
10392
+ if (accessToken) steps.push("\u2705 Access token detected");
10393
+ else steps.push("\u274C Access token missing \u2014 cannot push migrations");
10363
10394
  if (sqlGenerated) steps.push("\u2705 Entity SQL migrations generated");
10364
- if (migrationsPushed) steps.push("\u2705 Migrations pushed to remote DB");
10365
- else if (secretKey) steps.push("\u274C Migration push failed (see error above)");
10395
+ if (migrationsPushed) steps.push("\u2705 Migrations pushed via Management API");
10396
+ else if (accessToken)
10397
+ steps.push("\u274C Migration push failed (see error above)");
10366
10398
  const hasErrors = steps.some((s) => s.startsWith("\u274C"));
10367
10399
  const title = hasErrors ? "Setup Incomplete" : "Setup Complete";
10368
10400
  Me(
@@ -10393,6 +10425,7 @@ var init_supabase = __esm({
10393
10425
  init_pathResolver();
10394
10426
  init_app_selector();
10395
10427
  init_supabase_management();
10428
+ init_secrets_resolver();
10396
10429
  init_error_handling();
10397
10430
  init_types();
10398
10431
  supabase_default = main3;
@@ -10416,15 +10449,17 @@ var init_supabase = __esm({
10416
10449
  return { provider: "supabase", steps, overallStatus: "failed" };
10417
10450
  }
10418
10451
  const secretKey = detectSecretKey(ctx.appDir);
10419
- const dbUrl = detectDbUrl(ctx.appDir);
10452
+ const accessToken = resolveSecret("SUPABASE_ACCESS_TOKEN", ctx.projectRoot, {
10453
+ appDir: ctx.appDir
10454
+ })?.value ?? null;
10420
10455
  const missingSecrets = [];
10421
10456
  if (!secretKey) missingSecrets.push("SUPABASE_SECRET_KEY");
10422
- if (!dbUrl) missingSecrets.push("SUPABASE_DB_URL");
10457
+ if (!accessToken) missingSecrets.push("SUPABASE_ACCESS_TOKEN");
10423
10458
  if (missingSecrets.length > 0) {
10424
10459
  steps.push({
10425
10460
  name: "Credentials",
10426
10461
  status: "failed",
10427
- message: `Missing in functions/.env: ${missingSecrets.join(", ")}`
10462
+ message: `Missing in .dndev.secrets: ${missingSecrets.join(", ")}`
10428
10463
  });
10429
10464
  return { provider: "supabase", steps, overallStatus: "failed" };
10430
10465
  }
@@ -10488,7 +10523,11 @@ var init_supabase = __esm({
10488
10523
  const s = Y2();
10489
10524
  s.start("Pushing migrations...");
10490
10525
  try {
10491
- await executeMigration({ supabaseDir, dbUrl });
10526
+ await executeMigration({
10527
+ supabaseDir,
10528
+ supabaseUrl: existingConfig.url,
10529
+ accessToken
10530
+ });
10492
10531
  s.stop("Migrations pushed");
10493
10532
  steps.push({
10494
10533
  name: "DB migrations",
@@ -10597,6 +10636,7 @@ init_cross_app_detection();
10597
10636
  init_utils();
10598
10637
  init_pathResolver();
10599
10638
  init_cli_output();
10639
+ init_secrets_resolver();
10600
10640
  var FIREBASE_VARS = [
10601
10641
  {
10602
10642
  provider: "Firebase",
@@ -10635,32 +10675,28 @@ var SUPABASE_SECRET_VARS = [
10635
10675
  {
10636
10676
  provider: "Supabase",
10637
10677
  varName: "SUPABASE_SECRET_KEY",
10638
- envFile: "supabase/functions/.env"
10678
+ envFile: ".dndev.secrets",
10679
+ isSecret: true
10639
10680
  },
10640
10681
  {
10641
10682
  provider: "Supabase",
10642
- varName: "SUPABASE_DB_URL",
10643
- envFile: "supabase/functions/.env"
10683
+ varName: "SUPABASE_ACCESS_TOKEN",
10684
+ envFile: ".dndev.secrets",
10685
+ isSecret: true
10644
10686
  }
10645
10687
  ];
10646
- var VERCEL_VARS = [
10647
- { provider: "Vercel", varName: "VERCEL_TOKEN", envFile: ".env.local" },
10648
- { provider: "Vercel", varName: "VERCEL_ORG_ID", envFile: ".env.local" },
10649
- { provider: "Vercel", varName: "VERCEL_PROJECT_ID", envFile: ".env.local" }
10650
- ];
10651
- function readEnvValue(envPath, varName) {
10652
- if (!pathExists(envPath)) return null;
10653
- const content = readSync(envPath, { format: "text" });
10654
- if (typeof content !== "string") return null;
10655
- for (const line of content.split(/\r?\n/)) {
10656
- const trimmed = line.trim();
10657
- if (!trimmed || trimmed.startsWith("#")) continue;
10658
- if (trimmed.startsWith(`${varName}=`)) {
10659
- return trimmed.substring(`${varName}=`.length).trim();
10660
- }
10688
+ var VERCEL_SECRET_VARS = [
10689
+ {
10690
+ provider: "Vercel",
10691
+ varName: "VERCEL_TOKEN",
10692
+ envFile: ".dndev.secrets",
10693
+ isSecret: true
10661
10694
  }
10662
- return null;
10663
- }
10695
+ ];
10696
+ var VERCEL_APP_VARS = [
10697
+ { provider: "Vercel", varName: "VERCEL_ORG_ID", envFile: ".env" },
10698
+ { provider: "Vercel", varName: "VERCEL_PROJECT_ID", envFile: ".env" }
10699
+ ];
10664
10700
  function isProviderRelevant(ctx, provider) {
10665
10701
  switch (provider) {
10666
10702
  case "Firebase":
@@ -10683,32 +10719,33 @@ function validateRequiredEnvVars(ctx) {
10683
10719
  }
10684
10720
  if (isProviderRelevant(ctx, "Supabase")) {
10685
10721
  requirements.push(...SUPABASE_VARS);
10686
- const sbFunctionsEnv = joinPath(
10687
- ctx.appDir,
10688
- "supabase",
10689
- "functions",
10690
- ".env"
10691
- );
10692
- const altFunctionsEnv = joinPath(ctx.appDir, "functions", ".env");
10693
- if (pathExists(joinPath(ctx.appDir, "supabase", "functions")) || pathExists(sbFunctionsEnv)) {
10694
- requirements.push(...SUPABASE_SECRET_VARS);
10695
- } else if (pathExists(altFunctionsEnv)) {
10696
- requirements.push({
10697
- provider: "Supabase",
10698
- varName: "SUPABASE_SECRET_KEY",
10699
- envFile: "functions/.env"
10700
- });
10701
- }
10722
+ requirements.push(...SUPABASE_SECRET_VARS);
10702
10723
  }
10703
10724
  if (isProviderRelevant(ctx, "Vercel")) {
10704
- requirements.push(...VERCEL_VARS);
10725
+ requirements.push(...VERCEL_SECRET_VARS);
10726
+ requirements.push(...VERCEL_APP_VARS);
10705
10727
  }
10706
10728
  for (const req of requirements) {
10707
10729
  const actualVarName = req.needsPrefix ? `${prefix}${req.varName}` : req.varName;
10730
+ if (req.isSecret) {
10731
+ const resolved = resolveSecret(req.varName, ctx.projectRoot, {
10732
+ appDir: ctx.appDir,
10733
+ silent: true
10734
+ });
10735
+ if (!resolved) {
10736
+ missing.push({
10737
+ provider: req.provider,
10738
+ varName: req.varName,
10739
+ envFile: req.envFile,
10740
+ status: "missing"
10741
+ });
10742
+ }
10743
+ continue;
10744
+ }
10708
10745
  const envFilePath = joinPath(ctx.appDir, req.envFile);
10709
10746
  if (req.varName === "SUPABASE_PUBLIC_KEY") {
10710
- const pkValue = readEnvValue(envFilePath, actualVarName);
10711
- const anonValue = readEnvValue(envFilePath, `${prefix}SUPABASE_ANON_KEY`);
10747
+ const pkValue = readEnvVar(envFilePath, actualVarName);
10748
+ const anonValue = readEnvVar(envFilePath, `${prefix}SUPABASE_ANON_KEY`);
10712
10749
  if ((!pkValue || pkValue === "") && (!anonValue || anonValue === "")) {
10713
10750
  missing.push({
10714
10751
  provider: req.provider,
@@ -10719,20 +10756,30 @@ function validateRequiredEnvVars(ctx) {
10719
10756
  }
10720
10757
  continue;
10721
10758
  }
10722
- const value = readEnvValue(envFilePath, actualVarName);
10759
+ if (req.varName === "VERCEL_ORG_ID" || req.varName === "VERCEL_PROJECT_ID") {
10760
+ const fromEnv = readEnvVar(envFilePath, actualVarName);
10761
+ const fromLocal = readEnvVar(
10762
+ joinPath(ctx.appDir, ".env.local"),
10763
+ actualVarName
10764
+ );
10765
+ const fromProcessEnv = process.env[actualVarName];
10766
+ if (!fromEnv && !fromLocal && !fromProcessEnv) {
10767
+ missing.push({
10768
+ provider: req.provider,
10769
+ varName: actualVarName,
10770
+ envFile: req.envFile,
10771
+ status: pathExists(envFilePath) ? "missing" : "missing"
10772
+ });
10773
+ }
10774
+ continue;
10775
+ }
10776
+ const value = readEnvVar(envFilePath, actualVarName);
10723
10777
  if (value === null) {
10724
10778
  missing.push({
10725
10779
  provider: req.provider,
10726
10780
  varName: actualVarName,
10727
10781
  envFile: req.envFile,
10728
- status: pathExists(envFilePath) ? "missing" : "missing"
10729
- });
10730
- } else if (value === "") {
10731
- missing.push({
10732
- provider: req.provider,
10733
- varName: actualVarName,
10734
- envFile: req.envFile,
10735
- status: "empty"
10782
+ status: pathExists(envFilePath) ? "missing" : "no_env_file"
10736
10783
  });
10737
10784
  }
10738
10785
  }
@@ -10764,7 +10811,7 @@ function printAllValidationResults(results, multiApp) {
10764
10811
  }
10765
10812
  for (const { appName, m: m2 } of allMissing) {
10766
10813
  const prefix = multiApp ? `${appName}: ` : "";
10767
- const detail = m2.status === "empty" ? `empty in ${m2.envFile}` : `missing \u2014 ${m2.envFile}`;
10814
+ const detail = m2.status === "empty" ? `empty in ${m2.envFile}` : m2.status === "no_env_file" ? `env file not found: ${m2.envFile}` : `missing \u2014 ${m2.envFile}`;
10768
10815
  log.error(` ${prefix}${m2.varName} (${detail})`);
10769
10816
  }
10770
10817
  log.warn(`Run 'dndev coach' to see where to get them.`);