@donotdev/cli 0.0.20 → 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 (103) hide show
  1. package/README.md +31 -0
  2. package/dependencies-matrix.json +86 -19
  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 +491 -69
  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 +23 -15
  9. package/dist/bin/commands/create-project.js +101 -16
  10. package/dist/bin/commands/db.js +142136 -0
  11. package/dist/bin/commands/deploy.js +336 -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 +256 -212
  22. package/dist/bin/commands/staging.js +343 -127
  23. package/dist/bin/commands/sync-secrets.js +55 -33
  24. package/dist/bin/commands/type-check.js +6 -6
  25. package/dist/bin/commands/wai.js +6 -6
  26. package/dist/bin/dndev.js +76 -11
  27. package/dist/bin/donotdev.js +21 -12
  28. package/dist/index.js +437 -142
  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-dndev/index.html.example +164 -0
  34. package/templates/app-dndev/public/logo.svg.example +1 -0
  35. package/templates/app-dndev/public/manifest.json.example +10 -0
  36. package/templates/app-dndev/src/App.tsx.example +35 -0
  37. package/templates/app-dndev/src/components/CockpitLayout.css.example +181 -0
  38. package/templates/app-dndev/src/components/CockpitLayout.tsx.example +209 -0
  39. package/templates/app-dndev/src/components/Kanban.css.example +385 -0
  40. package/templates/app-dndev/src/components/ModeToggle.tsx.example +32 -0
  41. package/templates/app-dndev/src/components/OverlaySlot.tsx.example +68 -0
  42. package/templates/app-dndev/src/components/TerminalPanel.css.example +228 -0
  43. package/templates/app-dndev/src/components/TerminalPanel.tsx.example +714 -0
  44. package/templates/app-dndev/src/components/markdown-prose.css.example +49 -0
  45. package/templates/app-dndev/src/components/phases/CaptainLog.tsx.example +107 -0
  46. package/templates/app-dndev/src/components/phases/ContextTabs.tsx.example +352 -0
  47. package/templates/app-dndev/src/components/phases/PhaseCard.tsx.example +126 -0
  48. package/templates/app-dndev/src/components/phases/PhaseDetail.tsx.example +147 -0
  49. package/templates/app-dndev/src/components/phases/ReviewPanel.tsx.example +115 -0
  50. package/templates/app-dndev/src/components/phases/phaseData.ts.example +366 -0
  51. package/templates/app-dndev/src/config/app.ts.example +103 -0
  52. package/templates/app-dndev/src/config/commands.ts.example +171 -0
  53. package/templates/app-dndev/src/config/legal.ts.example +170 -0
  54. package/templates/app-dndev/src/config/providers.ts.example +7 -0
  55. package/templates/app-dndev/src/globals.css.example +10 -0
  56. package/templates/app-dndev/src/hooks/useDndevFile.ts.example +144 -0
  57. package/templates/app-dndev/src/main.tsx.example +21 -0
  58. package/templates/app-dndev/src/pages/BoardPage.tsx.example +640 -0
  59. package/templates/app-dndev/src/pages/GrillPage.tsx.example +658 -0
  60. package/templates/app-dndev/src/pages/HomePage.tsx.example +347 -0
  61. package/templates/app-dndev/src/pages/NotFoundPage.tsx.example +33 -0
  62. package/templates/app-dndev/src/pages/PhasesPage.tsx.example +137 -0
  63. package/templates/app-dndev/src/pages/SettingsPage.tsx.example +64 -0
  64. package/templates/app-dndev/src/pages/legal/LegalNoticePage.tsx.example +75 -0
  65. package/templates/app-dndev/src/pages/legal/PrivacyPage.tsx.example +69 -0
  66. package/templates/app-dndev/src/pages/legal/TermsPage.tsx.example +71 -0
  67. package/templates/app-dndev/src/stores/dndevStore.ts.example +386 -0
  68. package/templates/app-dndev/src/themes.css.example +161 -0
  69. package/templates/app-dndev/terminal-sidecar.cjs.example +341 -0
  70. package/templates/app-dndev/tsconfig.json.example +9 -0
  71. package/templates/app-dndev/vite.config.ts.example +24 -0
  72. package/templates/app-next/src/locales/home_en.json.example +6 -6
  73. package/templates/app-vite/index.html.example +1 -1
  74. package/templates/app-vite/src/locales/home_en.json.example +6 -6
  75. package/templates/functions-supabase/supabase/functions/.env.example +0 -2
  76. package/templates/root-consumer/.claude/commands/grill.md.example +86 -8
  77. package/templates/root-consumer/.dndev.secrets.example +32 -0
  78. package/templates/root-consumer/.gitignore.example +3 -0
  79. package/templates/root-consumer/AI.md.example +4 -0
  80. package/templates/root-consumer/entities/index.ts.example +2 -5
  81. package/templates/root-consumer/guides/dndev/COMPONENTS_ATOMIC.md.example +4 -0
  82. package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +23 -20
  83. package/templates/root-consumer/guides/dndev/INDEX.md.example +1 -0
  84. package/templates/root-consumer/guides/dndev/SETUP_BILLING.md.example +3 -7
  85. package/templates/root-consumer/guides/dndev/SETUP_CICD.md.example +115 -0
  86. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +41 -0
  87. package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +13 -18
  88. package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +17 -12
  89. package/templates/root-consumer/guides/dndev/advanced/COOKIE_REFERENCE.md.example +252 -252
  90. package/templates/root-consumer/guides/dndev/advanced/VERSION_CONTROL.md.example +174 -174
  91. package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +185 -251
  92. package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +26 -8
  93. package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +66 -49
  94. package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +6 -5
  95. package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +9 -9
  96. package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +1 -1
  97. package/templates/root-consumer/guides/wai-way/blueprints/4_configure.md.example +7 -6
  98. package/templates/root-consumer/guides/wai-way/context_map.json.example +51 -20
  99. package/templates/root-consumer/guides/wai-way/hld_template.md.example +138 -0
  100. package/templates/root-consumer/guides/wai-way/lld_template.md.example +103 -0
  101. package/templates/root-consumer/guides/wai-way/prd_template.md.example +140 -0
  102. /package/templates/{root-consumer → app-demo}/entities/Contact.ts.example +0 -0
  103. /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++) {
@@ -9670,7 +9698,20 @@ function camelToSnake(str) {
9670
9698
  return str.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
9671
9699
  }
9672
9700
  function fieldTypeToSql(fieldType) {
9673
- 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";
9674
9715
  }
9675
9716
  function schemaRequiresJsonb(schema) {
9676
9717
  if (!schema || typeof schema !== "object") return false;
@@ -9841,7 +9882,6 @@ var init_sql_generator = __esm({
9841
9882
  const tableName = this.safeTableName(entity.collection);
9842
9883
  const columns = [];
9843
9884
  columns.push("id uuid PRIMARY KEY DEFAULT gen_random_uuid()");
9844
- columns.push("user_id uuid NOT NULL");
9845
9885
  columns.push("created_at timestamptz NOT NULL DEFAULT now()");
9846
9886
  columns.push("updated_at timestamptz NOT NULL DEFAULT now()");
9847
9887
  columns.push("created_by_id uuid");
@@ -9857,14 +9897,23 @@ var init_sql_generator = __esm({
9857
9897
  if (schema && schemaRequiresJsonb(schema)) {
9858
9898
  sqlType = "jsonb";
9859
9899
  } else {
9860
- sqlType = fieldTypeToSql(field.type || "text");
9900
+ sqlType = fieldTypeToSql(field.type || "text") ?? inferSqlTypeFromValidation(field.validation);
9861
9901
  }
9862
9902
  columns.push(`${colName} ${sqlType}`);
9863
9903
  }
9864
9904
  const columnList = columns.join(",\n ");
9865
- return `CREATE TABLE IF NOT EXISTS ${tableName} (
9905
+ const createSql = `CREATE TABLE IF NOT EXISTS ${tableName} (
9866
9906
  ${columnList}
9867
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}`;
9868
9917
  }
9869
9918
  buildRlsSql(tableName, access) {
9870
9919
  const quoted = this.safeTableName(tableName);
@@ -10151,29 +10200,9 @@ function detectPublicConfig(appDir, framework) {
10151
10200
  }
10152
10201
  return null;
10153
10202
  }
10154
- function detectSecretKey(appDir) {
10155
- const candidates = [
10156
- joinPath(appDir, "supabase", "functions", ".env"),
10157
- joinPath(appDir, "functions", ".env")
10158
- ];
10159
- for (const functionsEnvPath of candidates) {
10160
- if (!pathExists(functionsEnvPath)) continue;
10161
- const envContentRaw = readSync(functionsEnvPath, { format: "text" });
10162
- const envContent = typeof envContentRaw === "string" ? envContentRaw : "";
10163
- const lines = envContent.split("\n");
10164
- for (const line of lines) {
10165
- const trimmed = line.trim();
10166
- if (trimmed.startsWith("SUPABASE_SECRET_KEY=") || trimmed.startsWith("SUPABASE_SERVICE_ROLE_KEY=")) {
10167
- const match = trimmed.match(
10168
- /^SUPABASE_(?:SECRET|SERVICE_ROLE)_KEY=(.+)$/
10169
- );
10170
- if (match && match[1]) {
10171
- return match[1].trim();
10172
- }
10173
- }
10174
- }
10175
- }
10176
- return null;
10203
+ function detectSecretKey(appDir, projectRoot) {
10204
+ const root = projectRoot ?? process.cwd();
10205
+ return resolveSecret("SUPABASE_SECRET_KEY", root, { appDir })?.value ?? null;
10177
10206
  }
10178
10207
  async function generateCrudFunction(supabaseDir, projectRoot) {
10179
10208
  const templatesRoot = getTemplatesRoot();
@@ -10257,26 +10286,23 @@ async function main3(options = {}) {
10257
10286
  const supabaseUrl = existingConfig.url;
10258
10287
  log.success(`Supabase URL: ${supabaseUrl}`);
10259
10288
  const secretKey = detectSecretKey(appDir);
10260
- const dbUrl = detectDbUrl(appDir);
10289
+ const accessToken = resolveSecret("SUPABASE_ACCESS_TOKEN", projectRoot, { appDir })?.value ?? null;
10261
10290
  if (secretKey) log.success("Secret key detected");
10262
- else log.error("Missing SUPABASE_SECRET_KEY in supabase/functions/.env");
10263
- if (dbUrl) log.success("DB URL detected");
10264
- else log.error("Missing SUPABASE_DB_URL in supabase/functions/.env");
10265
- if (!secretKey || !dbUrl) {
10266
- 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) {
10267
10295
  const missing = [];
10268
10296
  if (!secretKey) missing.push(" SUPABASE_SECRET_KEY=your-service-role-key");
10269
- if (!dbUrl)
10297
+ if (!accessToken)
10270
10298
  missing.push(
10271
- " SUPABASE_DB_URL=postgresql://postgres:...@db.<ref>.supabase.co:5432/postgres"
10299
+ " SUPABASE_ACCESS_TOKEN=sbp_... (from https://supabase.com/dashboard/account/tokens)"
10272
10300
  );
10273
10301
  Me(
10274
10302
  [
10275
- `Fill in ${functionsEnvPath}:`,
10303
+ "Fill in .dndev.secrets at project root:",
10276
10304
  ...missing,
10277
10305
  "",
10278
- "Get these from: https://supabase.com/dashboard > Settings > Database",
10279
- "",
10280
10306
  "Then re-run: dndev setup supabase"
10281
10307
  ].join("\n"),
10282
10308
  "Missing Credentials"
@@ -10343,7 +10369,7 @@ async function main3(options = {}) {
10343
10369
  const s = Y2();
10344
10370
  s.start("Pushing migrations...");
10345
10371
  try {
10346
- await executeMigration({ supabaseDir, dbUrl });
10372
+ await executeMigration({ supabaseDir, supabaseUrl, accessToken });
10347
10373
  s.stop("Migrations pushed successfully");
10348
10374
  migrationsPushed = true;
10349
10375
  } catch (error2) {
@@ -10362,10 +10388,13 @@ async function main3(options = {}) {
10362
10388
  const steps = [];
10363
10389
  steps.push("\u2705 Credentials validated from .env");
10364
10390
  if (secretKey) steps.push("\u2705 Secret key detected");
10365
- 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");
10366
10394
  if (sqlGenerated) steps.push("\u2705 Entity SQL migrations generated");
10367
- if (migrationsPushed) steps.push("\u2705 Migrations pushed to remote DB");
10368
- 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)");
10369
10398
  const hasErrors = steps.some((s) => s.startsWith("\u274C"));
10370
10399
  const title = hasErrors ? "Setup Incomplete" : "Setup Complete";
10371
10400
  Me(
@@ -10396,6 +10425,7 @@ var init_supabase = __esm({
10396
10425
  init_pathResolver();
10397
10426
  init_app_selector();
10398
10427
  init_supabase_management();
10428
+ init_secrets_resolver();
10399
10429
  init_error_handling();
10400
10430
  init_types();
10401
10431
  supabase_default = main3;
@@ -10419,15 +10449,17 @@ var init_supabase = __esm({
10419
10449
  return { provider: "supabase", steps, overallStatus: "failed" };
10420
10450
  }
10421
10451
  const secretKey = detectSecretKey(ctx.appDir);
10422
- const dbUrl = detectDbUrl(ctx.appDir);
10452
+ const accessToken = resolveSecret("SUPABASE_ACCESS_TOKEN", ctx.projectRoot, {
10453
+ appDir: ctx.appDir
10454
+ })?.value ?? null;
10423
10455
  const missingSecrets = [];
10424
10456
  if (!secretKey) missingSecrets.push("SUPABASE_SECRET_KEY");
10425
- if (!dbUrl) missingSecrets.push("SUPABASE_DB_URL");
10457
+ if (!accessToken) missingSecrets.push("SUPABASE_ACCESS_TOKEN");
10426
10458
  if (missingSecrets.length > 0) {
10427
10459
  steps.push({
10428
10460
  name: "Credentials",
10429
10461
  status: "failed",
10430
- message: `Missing in functions/.env: ${missingSecrets.join(", ")}`
10462
+ message: `Missing in .dndev.secrets: ${missingSecrets.join(", ")}`
10431
10463
  });
10432
10464
  return { provider: "supabase", steps, overallStatus: "failed" };
10433
10465
  }
@@ -10491,7 +10523,11 @@ var init_supabase = __esm({
10491
10523
  const s = Y2();
10492
10524
  s.start("Pushing migrations...");
10493
10525
  try {
10494
- await executeMigration({ supabaseDir, dbUrl });
10526
+ await executeMigration({
10527
+ supabaseDir,
10528
+ supabaseUrl: existingConfig.url,
10529
+ accessToken
10530
+ });
10495
10531
  s.stop("Migrations pushed");
10496
10532
  steps.push({
10497
10533
  name: "DB migrations",
@@ -10600,6 +10636,7 @@ init_cross_app_detection();
10600
10636
  init_utils();
10601
10637
  init_pathResolver();
10602
10638
  init_cli_output();
10639
+ init_secrets_resolver();
10603
10640
  var FIREBASE_VARS = [
10604
10641
  {
10605
10642
  provider: "Firebase",
@@ -10638,32 +10675,28 @@ var SUPABASE_SECRET_VARS = [
10638
10675
  {
10639
10676
  provider: "Supabase",
10640
10677
  varName: "SUPABASE_SECRET_KEY",
10641
- envFile: "supabase/functions/.env"
10678
+ envFile: ".dndev.secrets",
10679
+ isSecret: true
10642
10680
  },
10643
10681
  {
10644
10682
  provider: "Supabase",
10645
- varName: "SUPABASE_DB_URL",
10646
- envFile: "supabase/functions/.env"
10683
+ varName: "SUPABASE_ACCESS_TOKEN",
10684
+ envFile: ".dndev.secrets",
10685
+ isSecret: true
10647
10686
  }
10648
10687
  ];
10649
- var VERCEL_VARS = [
10650
- { provider: "Vercel", varName: "VERCEL_TOKEN", envFile: ".env.local" },
10651
- { provider: "Vercel", varName: "VERCEL_ORG_ID", envFile: ".env.local" },
10652
- { provider: "Vercel", varName: "VERCEL_PROJECT_ID", envFile: ".env.local" }
10653
- ];
10654
- function readEnvValue(envPath, varName) {
10655
- if (!pathExists(envPath)) return null;
10656
- const content = readSync(envPath, { format: "text" });
10657
- if (typeof content !== "string") return null;
10658
- for (const line of content.split(/\r?\n/)) {
10659
- const trimmed = line.trim();
10660
- if (!trimmed || trimmed.startsWith("#")) continue;
10661
- if (trimmed.startsWith(`${varName}=`)) {
10662
- return trimmed.substring(`${varName}=`.length).trim();
10663
- }
10688
+ var VERCEL_SECRET_VARS = [
10689
+ {
10690
+ provider: "Vercel",
10691
+ varName: "VERCEL_TOKEN",
10692
+ envFile: ".dndev.secrets",
10693
+ isSecret: true
10664
10694
  }
10665
- return null;
10666
- }
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
+ ];
10667
10700
  function isProviderRelevant(ctx, provider) {
10668
10701
  switch (provider) {
10669
10702
  case "Firebase":
@@ -10686,32 +10719,33 @@ function validateRequiredEnvVars(ctx) {
10686
10719
  }
10687
10720
  if (isProviderRelevant(ctx, "Supabase")) {
10688
10721
  requirements.push(...SUPABASE_VARS);
10689
- const sbFunctionsEnv = joinPath(
10690
- ctx.appDir,
10691
- "supabase",
10692
- "functions",
10693
- ".env"
10694
- );
10695
- const altFunctionsEnv = joinPath(ctx.appDir, "functions", ".env");
10696
- if (pathExists(joinPath(ctx.appDir, "supabase", "functions")) || pathExists(sbFunctionsEnv)) {
10697
- requirements.push(...SUPABASE_SECRET_VARS);
10698
- } else if (pathExists(altFunctionsEnv)) {
10699
- requirements.push({
10700
- provider: "Supabase",
10701
- varName: "SUPABASE_SECRET_KEY",
10702
- envFile: "functions/.env"
10703
- });
10704
- }
10722
+ requirements.push(...SUPABASE_SECRET_VARS);
10705
10723
  }
10706
10724
  if (isProviderRelevant(ctx, "Vercel")) {
10707
- requirements.push(...VERCEL_VARS);
10725
+ requirements.push(...VERCEL_SECRET_VARS);
10726
+ requirements.push(...VERCEL_APP_VARS);
10708
10727
  }
10709
10728
  for (const req of requirements) {
10710
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
+ }
10711
10745
  const envFilePath = joinPath(ctx.appDir, req.envFile);
10712
10746
  if (req.varName === "SUPABASE_PUBLIC_KEY") {
10713
- const pkValue = readEnvValue(envFilePath, actualVarName);
10714
- const anonValue = readEnvValue(envFilePath, `${prefix}SUPABASE_ANON_KEY`);
10747
+ const pkValue = readEnvVar(envFilePath, actualVarName);
10748
+ const anonValue = readEnvVar(envFilePath, `${prefix}SUPABASE_ANON_KEY`);
10715
10749
  if ((!pkValue || pkValue === "") && (!anonValue || anonValue === "")) {
10716
10750
  missing.push({
10717
10751
  provider: req.provider,
@@ -10722,20 +10756,30 @@ function validateRequiredEnvVars(ctx) {
10722
10756
  }
10723
10757
  continue;
10724
10758
  }
10725
- 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);
10726
10777
  if (value === null) {
10727
10778
  missing.push({
10728
10779
  provider: req.provider,
10729
10780
  varName: actualVarName,
10730
10781
  envFile: req.envFile,
10731
- status: pathExists(envFilePath) ? "missing" : "missing"
10732
- });
10733
- } else if (value === "") {
10734
- missing.push({
10735
- provider: req.provider,
10736
- varName: actualVarName,
10737
- envFile: req.envFile,
10738
- status: "empty"
10782
+ status: pathExists(envFilePath) ? "missing" : "no_env_file"
10739
10783
  });
10740
10784
  }
10741
10785
  }
@@ -10767,7 +10811,7 @@ function printAllValidationResults(results, multiApp) {
10767
10811
  }
10768
10812
  for (const { appName, m: m2 } of allMissing) {
10769
10813
  const prefix = multiApp ? `${appName}: ` : "";
10770
- 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}`;
10771
10815
  log.error(` ${prefix}${m2.varName} (${detail})`);
10772
10816
  }
10773
10817
  log.warn(`Run 'dndev coach' to see where to get them.`);