better-supabase 0.3.0 → 0.5.0

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 (211) hide show
  1. package/README.md +9 -6
  2. package/bin/better-supabase.js +4 -0
  3. package/dist/{base64-CQeB1eSf.js → base64-D2gsioCY.js} +2 -2
  4. package/dist/channel-FQW-CnGR.d.ts +70 -0
  5. package/dist/claims-BAf6c7is.js +48 -0
  6. package/dist/cli/_format-Cax5EsM9.js +101 -0
  7. package/dist/cli/bin.d.ts +1 -0
  8. package/dist/cli/bin.js +48 -0
  9. package/dist/cli/codemod-DqV7612m.js +388 -0
  10. package/dist/cli/command-CK14fP91.js +502 -0
  11. package/dist/cli/compare-BmIWVEui.js +9 -0
  12. package/dist/cli/config-CPVpXP5I.js +1965 -0
  13. package/dist/cli/config-dtoekM0v.js +2 -0
  14. package/dist/cli/db-DOBPt660.js +52 -0
  15. package/dist/cli/diff-SjkO5rNr.js +752 -0
  16. package/dist/cli/dist-DBPdlPYK.js +1112 -0
  17. package/dist/cli/dist-DreZ24Sz.js +129 -0
  18. package/dist/cli/dist-xn4wlboc.js +732 -0
  19. package/dist/cli/doctor-L7Z8mYTx.js +2863 -0
  20. package/dist/cli/env-DmKv_G4A.js +108 -0
  21. package/dist/cli/env-jIFUADLY.js +511 -0
  22. package/dist/cli/errors-4v4DTtda.js +56 -0
  23. package/dist/cli/fastest-levenshtein-CFN-lNDv.js +120 -0
  24. package/dist/cli/gen-BJc9vdyu.js +2 -0
  25. package/dist/cli/gen-C2osRcr5.js +2204 -0
  26. package/dist/cli/index.d.ts +493 -0
  27. package/dist/cli/index.js +184 -0
  28. package/dist/cli/init-Saf5-R9T.js +550 -0
  29. package/dist/cli/introspect--H7oGmcd.js +81 -0
  30. package/dist/cli/io-k41_z-jT.js +24 -0
  31. package/dist/cli/json5-ChRycquj.js +787 -0
  32. package/dist/cli/jsonc-BUniUpOk.js +2 -0
  33. package/dist/cli/jsonc-D5Fu67jw.js +45 -0
  34. package/dist/cli/keys-ByKHnvP9.js +83 -0
  35. package/dist/cli/ohash-D7bG_EOb.js +277 -0
  36. package/dist/cli/openapi-Cp5Fyta-.js +66 -0
  37. package/dist/cli/project-B_GeXyHh.js +70 -0
  38. package/dist/cli/prompts-BNcNfAve.js +1142 -0
  39. package/dist/cli/prompts-BTYQGunc.js +2 -0
  40. package/dist/cli/read-sets-BfMQcKy8.js +289 -0
  41. package/dist/cli/rolldown-runtime-BPOCksWG.js +24 -0
  42. package/dist/cli/run-BKR2W_79.js +351 -0
  43. package/dist/cli/seed-CUlqWh-8.js +98 -0
  44. package/dist/cli/skills-LJJIhLFR.js +137 -0
  45. package/dist/cli/sql-DzPbfngx.js +348 -0
  46. package/dist/cli/style-C0NFdJRL.js +29 -0
  47. package/dist/cli/toml-DB-MNnFs.js +424 -0
  48. package/dist/cli/version-BDJuO0UR.js +4 -0
  49. package/dist/cli/yaml-CYCbJ6et.js +2252 -0
  50. package/dist/client/index.d.ts +2 -2
  51. package/dist/client/index.js +15 -22
  52. package/dist/clone-BfgmLU90.js +21 -0
  53. package/dist/{compiler-2zqGzxVZ.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
  54. package/dist/config/index.d.ts +4 -4
  55. package/dist/config/index.js +5 -5
  56. package/dist/{config-DOsGYSiT.js → config-DreERlKo.js} +1 -1
  57. package/dist/{config-C1CYcm6o.d.ts → config-T_ejwyd3.d.ts} +176 -11
  58. package/dist/{define-DUbydmJa.d.ts → define-Bp_BZa5h.d.ts} +37 -29
  59. package/dist/{define-DF341GDj.js → define-cNKzqwL_.js} +356 -89
  60. package/dist/edge/index.d.ts +20 -13
  61. package/dist/edge/index.js +55 -26
  62. package/dist/{entitlements-DvJXoI02.d.ts → entitlements-DUxU4oHe.d.ts} +2 -2
  63. package/dist/env/index.d.ts +2 -2
  64. package/dist/env/index.js +34 -1
  65. package/dist/{errors-Bl3rkOzo.js → errors-DtwXa6b8.js} +2 -1
  66. package/dist/events/index.d.ts +3 -2
  67. package/dist/events/index.js +69 -16
  68. package/dist/events-aIO7vsIk.d.ts +230 -0
  69. package/dist/executor-BunPOO-k.d.ts +402 -0
  70. package/dist/{executor-B26lgbHD.d.ts → executor-Cghdstwu.d.ts} +1 -2
  71. package/dist/{executor-C57jx2pn.js → executor-XycESQPH.js} +3 -3
  72. package/dist/hono/index.d.ts +27 -23
  73. package/dist/hono/index.js +46 -11
  74. package/dist/impersonation-VfaGc4Q_.js +41 -0
  75. package/dist/{index-BNsABq7P.d.ts → index-BGdgBPdf.d.ts} +66 -25
  76. package/dist/{index-qriRM9cZ.d.ts → index-C7f8cOBM.d.ts} +4 -4
  77. package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
  78. package/dist/{index-GV77NpRP.d.ts → index-DOESKG27.d.ts} +34 -9
  79. package/dist/{index-CriFFJ_p.d.ts → index-DgILJRXj.d.ts} +43 -17
  80. package/dist/{index-UJ-LUxtI.d.ts → index-DmXSxbLi.d.ts} +14 -14
  81. package/dist/{index-Cf-D2jk1.d.ts → index-Dxn2a5cw.d.ts} +2 -2
  82. package/dist/{index-CTTMS6_1.d.ts → index-SaTsEfGP.d.ts} +86 -9
  83. package/dist/{index-CADlNHK-.d.ts → index-fag8VJZ2.d.ts} +5 -6
  84. package/dist/index.d.ts +34 -17
  85. package/dist/index.js +15 -14
  86. package/dist/jobs/index.d.ts +147 -95
  87. package/dist/jobs/index.js +773 -250
  88. package/dist/{json-schema--aOXAGfU.js → json-schema-m1fiwKgN.js} +23 -2
  89. package/dist/kit-events-CTFDfSwb.js +60 -0
  90. package/dist/kit-transport-B82k1djG.d.ts +33 -0
  91. package/dist/kit-transport-DJlHQUBU.js +55 -0
  92. package/dist/{config-Be4GwsqI.js → kits-CGLLrNPd.js} +38 -10
  93. package/dist/list/index.d.ts +1 -1
  94. package/dist/list/index.js +3 -3
  95. package/dist/{live-D8rV1Ldw.js → live-CdJBOc4f.js} +13 -9
  96. package/dist/{live-QE3UPbGm.d.ts → live-D9W2M5aO.d.ts} +16 -15
  97. package/dist/lookup-BaP5bI27.js +18 -0
  98. package/dist/mcp/index.d.ts +39 -22
  99. package/dist/mcp/index.js +39 -25
  100. package/dist/{mfa-x3V3vGh-.d.ts → mfa-CusDgPaD.d.ts} +5 -1
  101. package/dist/next/image/index.js +6 -3
  102. package/dist/next/index.d.ts +60 -36
  103. package/dist/next/index.js +167 -50
  104. package/dist/notifications/index.d.ts +162 -0
  105. package/dist/notifications/index.js +264 -0
  106. package/dist/openapi/index.d.ts +1 -1
  107. package/dist/openapi/index.js +6 -6
  108. package/dist/orgs/index.d.ts +132 -0
  109. package/dist/orgs/index.js +245 -0
  110. package/dist/orpc/index.d.ts +37 -20
  111. package/dist/orpc/index.js +24 -11
  112. package/dist/otel/index.d.ts +24 -5
  113. package/dist/otel/index.js +124 -64
  114. package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
  115. package/dist/permdock-sql-DgOfeixi.js +51 -0
  116. package/dist/plugin-DwARMuQp.js +59 -0
  117. package/dist/{plugin-DVRB8qNE.d.ts → plugin-o144f322.d.ts} +17 -5
  118. package/dist/plugins/actor/index.d.ts +5 -2
  119. package/dist/plugins/actor/index.js +22 -8
  120. package/dist/plugins/rules/index.d.ts +2 -2
  121. package/dist/plugins/rules/index.js +52 -17
  122. package/dist/plugins/soft-delete/index.d.ts +3 -3
  123. package/dist/plugins/soft-delete/index.js +14 -5
  124. package/dist/plugins/tenant/index.d.ts +3 -3
  125. package/dist/plugins/tenant/index.js +45 -21
  126. package/dist/plugins/timestamps/index.d.ts +1 -1
  127. package/dist/plugins/timestamps/index.js +4 -3
  128. package/dist/plugins/validation/index.d.ts +2 -2
  129. package/dist/plugins/validation/index.js +26 -6
  130. package/dist/{pool-B9qsLyFR.d.ts → pool-C9voM4Kw.d.ts} +24 -6
  131. package/dist/postgres/index.d.ts +5 -5
  132. package/dist/postgres/index.js +20 -10
  133. package/dist/{postgrest-DmBlevh-.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
  134. package/dist/{problem-CGt1ADix.js → problem-BW1KhzSG.js} +2 -1
  135. package/dist/{problem-Z2RTQjZD.d.ts → problem-Blxe8S31.d.ts} +1 -1
  136. package/dist/query/index.d.ts +2 -2
  137. package/dist/query/index.js +3 -3
  138. package/dist/{query-BdKLTPlj.js → query-CCbo44lG.js} +18 -18
  139. package/dist/queue-BYUCjr59.d.ts +252 -0
  140. package/dist/react/index.d.ts +8 -7
  141. package/dist/react/index.js +4 -3
  142. package/dist/react/server.d.ts +16 -5
  143. package/dist/react/server.js +12 -4
  144. package/dist/react/session.d.ts +10 -4
  145. package/dist/react/session.js +11 -3
  146. package/dist/{react-DNBYNazu.js → react-C_s7kiNz.js} +138 -46
  147. package/dist/{read-set-D0qBcboM.js → read-set-DQFt1N_Y.js} +71 -15
  148. package/dist/realtime/index.d.ts +3 -3
  149. package/dist/realtime/index.js +14 -12
  150. package/dist/{index-kpgopLNv.d.ts → refresh-BTrQ_ZTp.d.ts} +6 -3
  151. package/dist/{resolve-BRciX2jK.d.ts → resolve-C15TJq77.d.ts} +22 -11
  152. package/dist/{resource-D30rp0GN.js → resource-CRxxwkQq.js} +24 -9
  153. package/dist/{resource-CwVgiI5q.d.ts → resource-D8iqWMr-.d.ts} +6 -6
  154. package/dist/respond-D2OQl62-.d.ts +371 -0
  155. package/dist/{result-DWXatkd6.js → result-BKiliPY7.js} +2 -2
  156. package/dist/server/index.d.ts +17 -15
  157. package/dist/server/index.js +16 -22
  158. package/dist/server-Csx2sRjg.js +655 -0
  159. package/dist/{shared-_6-uPL7E.js → shared-D1dRUrAg.js} +19 -4
  160. package/dist/signers-Cc6r98SW.d.ts +162 -0
  161. package/dist/{simplify-LakqK1vH.js → simplify-_0oUKqCd.js} +27 -95
  162. package/dist/sql/index.d.ts +247 -10
  163. package/dist/sql/index.js +8591 -780
  164. package/dist/{sql-BIgk12ew.js → sql-Coh6n4cC.js} +11 -3
  165. package/dist/ssr/index.d.ts +4 -4
  166. package/dist/ssr/index.js +1 -2
  167. package/dist/{standard-FUi3Z3-7.js → standard-D7r6dMUu.js} +6 -3
  168. package/dist/{standard-eudBiRk1.d.ts → standard-J4YgMOLm.d.ts} +1 -1
  169. package/dist/{stats-D78UQ8Yu.js → stats-Bqob3WqT.js} +34 -9
  170. package/dist/storage/index.d.ts +4 -4
  171. package/dist/storage/index.js +174 -27
  172. package/dist/support-DT-OhSxD.d.ts +88 -0
  173. package/dist/support-Dk6vdmEw.js +291 -0
  174. package/dist/support-view-Da5peKFg.d.ts +17 -0
  175. package/dist/support-view-g4YLBeqX.js +18 -0
  176. package/dist/{tables-Dhha3BXM.js → tables-Cb-3_VWc.js} +14 -0
  177. package/dist/{permdock-sql-Bta_G0v-.js → template-TxrGI5jR.js} +2 -31
  178. package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
  179. package/dist/{temporal-required-CMxeSVPo.js → temporal-required-CAt4Dc9Z.js} +2 -2
  180. package/dist/testing/index.d.ts +113 -24
  181. package/dist/testing/index.js +375 -47
  182. package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
  183. package/dist/user-change-CHFEUro1.js +49 -0
  184. package/dist/{webhooks-CAIFQSTM.js → verify-Ce49ETPz.js} +38 -19
  185. package/dist/{version-BfpG2gks.js → version-DM_Roffb.js} +1 -1
  186. package/dist/{view-3Po_PVlr.d.ts → view-BEf0diC5.d.ts} +10 -5
  187. package/dist/{mfa-BxoR1RkN.js → view-Bq64ki8-.js} +210 -64
  188. package/dist/webhooks/index.d.ts +136 -5
  189. package/dist/webhooks/index.js +588 -2
  190. package/dist/wire-D2dT86vi.js +35 -0
  191. package/package.json +50 -10
  192. package/schemas/config-v1.json +131 -1
  193. package/schemas/doctor-report-v1.json +10 -0
  194. package/skills/better-supabase/SKILL.md +60 -10
  195. package/skills/better-supabase/references/plugins.md +13 -14
  196. package/skills/better-supabase/references/troubleshooting.md +25 -4
  197. package/skills/better-supabase-api/SKILL.md +74 -19
  198. package/skills/better-supabase-api/references/adapters.md +48 -27
  199. package/skills/better-supabase-auth/SKILL.md +149 -0
  200. package/skills/better-supabase-auth/references/permdock.md +100 -0
  201. package/skills/better-supabase-testing/SKILL.md +58 -4
  202. package/dist/claims-CoTobNC-.js +0 -25
  203. package/dist/events-Do8jXh4D.d.ts +0 -84
  204. package/dist/executor-DynmIWhq.d.ts +0 -195
  205. package/dist/invalidate-DbxuL2jL.js +0 -18
  206. package/dist/permdock-sql-DImJULY-.d.ts +0 -17
  207. package/dist/plugin-BqR6wKMB.js +0 -23
  208. package/dist/respond-C44EZAJo.js +0 -399
  209. package/dist/respond-DAkPIdTH.d.ts +0 -175
  210. package/dist/result-c2OYgCV2.d.ts +0 -203
  211. package/dist/view-wZ-9qIUz.js +0 -45
@@ -0,0 +1,98 @@
1
+ import { r as defineCliCommand } from "./command-CK14fP91.js";
2
+ import { r as writeIfChanged, t as display } from "./io-k41_z-jT.js";
3
+ import { r as importExport, s as stdinDatabaseUrl, t as databaseUrl } from "./config-CPVpXP5I.js";
4
+ import { n as withSpinner } from "./prompts-BNcNfAve.js";
5
+ import { t as connect } from "./db-DOBPt660.js";
6
+ import { resolve } from "node:path";
7
+ import { existsSync } from "node:fs";
8
+ import { readFile } from "node:fs/promises";
9
+ import { isSeed } from "better-supabase/testing";
10
+ //#region src/cli/commands/seed.ts
11
+ const ARGS = {
12
+ check: {
13
+ type: "boolean",
14
+ description: "Fail when the SQL file is out of date"
15
+ },
16
+ apply: {
17
+ type: "boolean",
18
+ description: "Also insert the rows into the database ($DATABASE_URL, --db-url-stdin, or the local stack)"
19
+ },
20
+ entry: {
21
+ type: "string",
22
+ description: "Defaults to seed.entry (supabase/seed.ts)",
23
+ valueHint: "file"
24
+ },
25
+ out: {
26
+ type: "string",
27
+ description: "Defaults to seed.output (supabase/seeds/000_better_supabase.sql)",
28
+ valueHint: "file"
29
+ },
30
+ "db-url-stdin": {
31
+ type: "boolean",
32
+ description: "Read the connection string from stdin instead of $DATABASE_URL or the config"
33
+ }
34
+ };
35
+ const HEADER = "-- Generated by `better-supabase seed`";
36
+ async function runSeed(config, args, env, open = connect, dbUrl) {
37
+ const entry = args.entry ?? config.seed.entry;
38
+ const out = args.out ?? config.seed.output;
39
+ const seed = await importExport(resolve(config.root, entry), "seed");
40
+ if (!isSeed(seed)) return {
41
+ code: 1,
42
+ error: `${display(config.root, entry)} must export defineSeed(...) as \`seed\` or default.`
43
+ };
44
+ const statements = seed.statements();
45
+ const contents = `${HEADER} from ${display(config.root, entry)}. Do not edit.\n\n${statements.join("\n\n")}\n`;
46
+ const path = resolve(config.root, out);
47
+ const current = existsSync(path) ? await readFile(path, "utf8") : void 0;
48
+ if (args.check === true) return current === contents ? {
49
+ code: 0,
50
+ output: `${display(config.root, out)} is up to date.`
51
+ } : {
52
+ code: 1,
53
+ error: `${display(config.root, out)} is out of date. Run \`better-supabase seed\`.`
54
+ };
55
+ if (current !== void 0 && !current.startsWith(HEADER)) return {
56
+ code: 1,
57
+ error: `${display(config.root, out)} was not written by better-supabase; pass --out to write elsewhere.`
58
+ };
59
+ const lines = [`${await writeIfChanged(path, contents) ? "Wrote" : "Unchanged"} ${display(config.root, out)} (${statements.length} table${statements.length === 1 ? "" : "s"})`];
60
+ if (args.apply === true) {
61
+ const { client, close } = await open(await databaseUrl(config, env, dbUrl));
62
+ try {
63
+ await client.query("begin");
64
+ for (const text of statements) await client.query(text);
65
+ await client.query("commit");
66
+ } catch (error) {
67
+ await client.query("rollback");
68
+ throw error;
69
+ } finally {
70
+ await close();
71
+ }
72
+ lines.push("Inserted the fixtures (existing rows were kept).");
73
+ }
74
+ const toml = resolve(config.root, "supabase/config.toml");
75
+ const seedPath = out.startsWith("supabase/") ? `./${out.slice(9)}` : out;
76
+ if (existsSync(toml)) {
77
+ const text = await readFile(toml, "utf8");
78
+ const folder = seedPath.replace(/[^/]+$/, "");
79
+ if (!text.includes(seedPath) && !text.includes(`${folder}*`)) lines.push("", "Add it to supabase/config.toml so `supabase db reset` runs it:", " [db.seed]", ` sql_paths = ["./seed.sql", "${seedPath}"]`);
80
+ }
81
+ return {
82
+ code: 0,
83
+ output: lines.join("\n")
84
+ };
85
+ }
86
+ const seedCommand = defineCliCommand({
87
+ meta: {
88
+ name: "seed",
89
+ description: "Renders the fixtures from defineSeed() (better-supabase/testing) to SQL"
90
+ },
91
+ args: ARGS,
92
+ run: async (args, { config, env, io }) => {
93
+ const dbUrl = await stdinDatabaseUrl(args["db-url-stdin"], io);
94
+ return withSpinner(args.apply === true ? io.prompts : void 0, "Inserting the seed rows", () => runSeed(config, args, env, connect, dbUrl));
95
+ }
96
+ });
97
+ //#endregion
98
+ export { seedCommand };
@@ -0,0 +1,137 @@
1
+ import { a as list, r as defineCliCommand } from "./command-CK14fP91.js";
2
+ import { r as writeIfChanged, t as display } from "./io-k41_z-jT.js";
3
+ import { t as byCodePoint } from "./compare-BmIWVEui.js";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { existsSync } from "node:fs";
6
+ import { readFile, readdir } from "node:fs/promises";
7
+ import { fileURLToPath } from "node:url";
8
+ import { homedir } from "node:os";
9
+ //#region src/cli/commands/skills.ts
10
+ const ARGS = {
11
+ action: {
12
+ type: "positional",
13
+ required: false,
14
+ description: "list or install"
15
+ },
16
+ agent: {
17
+ type: "string",
18
+ description: "cursor (.cursor/skills), claude (.claude/skills) or agents (.agents/skills). Defaults to the agent folders the project has, else agents",
19
+ valueHint: "names"
20
+ },
21
+ global: {
22
+ type: "boolean",
23
+ description: "Install into your home directory instead of the project"
24
+ },
25
+ check: {
26
+ type: "boolean",
27
+ description: "Fail when installed skills differ from this version"
28
+ },
29
+ from: {
30
+ type: "string",
31
+ description: "Read skills from this folder instead of the package",
32
+ valueHint: "dir"
33
+ }
34
+ };
35
+ const AGENT_DIRS = {
36
+ cursor: ".cursor/skills",
37
+ claude: ".claude/skills",
38
+ agents: ".agents/skills"
39
+ };
40
+ const isAgent = (name) => name in AGENT_DIRS;
41
+ /** The `skills/` folder of the `better-supabase` package this CLI depends on. */
42
+ function skillsRoot() {
43
+ const root = join(dirname(fileURLToPath(import.meta.resolve("better-supabase/package.json"))), "skills");
44
+ if (!existsSync(root)) throw new Error("Could not find the better-supabase skills folder.");
45
+ return root;
46
+ }
47
+ async function readTree(root, prefix = "") {
48
+ const files = [];
49
+ const entries = await readdir(join(root, prefix), { withFileTypes: true });
50
+ for (const entry of entries.sort((a, b) => byCodePoint(a.name, b.name))) {
51
+ const path = prefix ? `${prefix}/${entry.name}` : entry.name;
52
+ if (entry.isDirectory()) files.push(...await readTree(root, path));
53
+ else if (entry.isFile()) files.push({
54
+ path,
55
+ contents: await readFile(join(root, path), "utf8")
56
+ });
57
+ }
58
+ return files;
59
+ }
60
+ async function loadSkills(root = skillsRoot()) {
61
+ const skills = [];
62
+ for (const entry of (await readdir(root, { withFileTypes: true })).filter((item) => item.isDirectory())) {
63
+ const folder = join(root, entry.name);
64
+ if (!existsSync(join(folder, "SKILL.md"))) continue;
65
+ const tree = await readTree(folder);
66
+ const main = tree.find((file) => file.path === "SKILL.md");
67
+ const files = [main, ...tree.filter((file) => file !== main)];
68
+ const description = /^description:\s*(.+)$/m.exec(main.contents)?.[1] ?? "";
69
+ skills.push({
70
+ name: entry.name,
71
+ description,
72
+ contents: main.contents,
73
+ files
74
+ });
75
+ }
76
+ return skills.sort((a, b) => byCodePoint(a.name, b.name));
77
+ }
78
+ async function runSkills(config, args, env) {
79
+ const [action] = args._;
80
+ const skills = await loadSkills(args.from ?? skillsRoot());
81
+ if (action === "list") return {
82
+ code: 0,
83
+ output: skills.map((skill) => `${skill.name}\n ${skill.description}`).join("\n")
84
+ };
85
+ if (action !== "install") return {
86
+ code: 2,
87
+ error: `${action ? `Unknown skills action "${action}"` : "Name an action"}. Use \`skills list\` or \`skills install\`.`
88
+ };
89
+ const global = args.global === true;
90
+ const base = global ? env.HOME ?? homedir() : config.root;
91
+ const requested = list(args.agent);
92
+ const unknown = requested.filter((name) => !isAgent(name));
93
+ if (unknown.length > 0) return {
94
+ code: 2,
95
+ error: `Unknown agent ${unknown.join(", ")}. Use ${Object.keys(AGENT_DIRS).join(", ")}.`
96
+ };
97
+ const detected = Object.keys(AGENT_DIRS).filter((agent) => existsSync(join(base, AGENT_DIRS[agent].split("/")[0])));
98
+ const agents = requested.length > 0 ? requested.filter(isAgent) : detected.length > 0 ? detected : ["agents"];
99
+ const check = args.check === true;
100
+ const lines = [];
101
+ const stale = [];
102
+ const targets = agents.flatMap((agent) => skills.flatMap((skill) => skill.files.map((file) => ({
103
+ file,
104
+ relativePath: join(AGENT_DIRS[agent], skill.name, file.path)
105
+ }))));
106
+ for (const { file, relativePath } of targets) {
107
+ const path = resolve(base, relativePath);
108
+ const shown = global ? `~/${relativePath}` : display(config.root, relativePath);
109
+ if (check) {
110
+ if ((existsSync(path) ? await readFile(path, "utf8") : void 0) !== file.contents) stale.push(shown);
111
+ continue;
112
+ }
113
+ lines.push(`${await writeIfChanged(path, file.contents) ? "Wrote" : "Unchanged"} ${shown}`);
114
+ }
115
+ if (check) return stale.length === 0 ? {
116
+ code: 0,
117
+ output: "Skills are up to date."
118
+ } : {
119
+ code: 1,
120
+ error: `Out of date: ${stale.join(", ")}. Run \`better-supabase skills install\`.`
121
+ };
122
+ return {
123
+ code: 0,
124
+ output: lines.join("\n")
125
+ };
126
+ }
127
+ const skillsCommand = defineCliCommand({
128
+ meta: {
129
+ name: "skills",
130
+ description: "Installs the Agent Skills that ship with this version of better-supabase"
131
+ },
132
+ args: ARGS,
133
+ lists: ["agent"],
134
+ run: (args, { config, env }) => runSkills(config, args, env)
135
+ });
136
+ //#endregion
137
+ export { skillsCommand };
@@ -0,0 +1,348 @@
1
+ import { r as defineCliCommand } from "./command-CK14fP91.js";
2
+ import { r as writeIfChanged, t as display } from "./io-k41_z-jT.js";
3
+ import { i as plain, r as painter } from "./style-C0NFdJRL.js";
4
+ import { f as readSupabaseToml, p as schemaPaths, u as migrationCommand } from "./config-CPVpXP5I.js";
5
+ import { t as fileDiff } from "./diff-SjkO5rNr.js";
6
+ import { o as permdockSource, r as entitlementsMode, s as readPermdock, t as compiledReadSets } from "./read-sets-BfMQcKy8.js";
7
+ import { dirname, join, relative, resolve, sep } from "node:path";
8
+ import { existsSync } from "node:fs";
9
+ import { readFile, readdir } from "node:fs/promises";
10
+ import { SQL_MODULES, contractSignature, customContracts, kitFileVersion, kitLayout, moduleBody, renderKit, resolveModules, sameKitFile, upgradePlan } from "better-supabase/sql";
11
+ //#region src/cli/commands/sql.ts
12
+ const SQL_ARGS = {
13
+ action: {
14
+ type: "positional",
15
+ required: false,
16
+ description: "list (modules and whether they are installed), add <module...>, sync, upgrade, data (a migration with the rows a schema diff skips) or print <module>"
17
+ },
18
+ check: {
19
+ type: "boolean",
20
+ description: "With sync: fail when a file is stale. With upgrade: fail when a module is behind"
21
+ },
22
+ "tests-dir": {
23
+ type: "string",
24
+ description: "Where the pgtap module goes. Defaults to sql.testsDir",
25
+ valueHint: "dir"
26
+ },
27
+ "dry-run": {
28
+ type: "boolean",
29
+ description: "Show what would be written"
30
+ },
31
+ force: {
32
+ type: "boolean",
33
+ description: "Write tenant even though a permdock.config.ts is present"
34
+ }
35
+ };
36
+ const USAGE = "Run `better-supabase sql --help` for the actions.";
37
+ /** Modules that fill a claim PermDock's hook also writes (`memberships`). */
38
+ const PERMDOCK_OWNED = /* @__PURE__ */ new Set(["tenant"]);
39
+ /**
40
+ * PermDock's helpers for the `entitlements` module. When `names` resolve to
41
+ * that module, an invalid PermDock mode (no manifest, a scope it lacks, an
42
+ * unknown id type) throws instead of falling back to the tenant module.
43
+ * Without `names` (`sql list`) it only describes the modules, so it doesn't.
44
+ */
45
+ async function permdockFor(config, names) {
46
+ const mode = entitlementsMode(config, await readPermdock(config.root, config.permdock));
47
+ switch (mode.kind) {
48
+ case "tenant": return;
49
+ case "permdock": return mode.permdock;
50
+ case "invalid":
51
+ if (names === void 0 || !resolveModules(names, {}).some((module) => module.name === "entitlements")) return void 0;
52
+ throw new TypeError(mode.problem);
53
+ default: return mode;
54
+ }
55
+ }
56
+ async function layout(config, args, names) {
57
+ return kitLayout(config, args["tests-dir"], [], await permdockFor(config, names));
58
+ }
59
+ /** The layout, with `config.readSets` compiled when `names` includes `read-sets`. */
60
+ async function layoutFor(config, args, names) {
61
+ const permdock = await permdockFor(config, names);
62
+ const needsReadSets = resolveModules(names, { ...permdock ? { permdock } : {} }).some((module) => module.name === "read-sets");
63
+ return kitLayout(config, args["tests-dir"], needsReadSets ? await compiledReadSets(config) : [], permdock);
64
+ }
65
+ /**
66
+ * The migrations folder next to the `config.toml` found walking up from
67
+ * `sql.dir`, relative to the root; `supabase/migrations` without one. The
68
+ * walk stops where `sql.dir` and the root meet, so a `sql.dir` outside the
69
+ * project (a shared stack in a monorepo) finds that stack's folder.
70
+ */
71
+ function migrationsDir(config) {
72
+ const root = resolve(config.root);
73
+ let dir = resolve(root, config.sql.dir);
74
+ let stop = root;
75
+ while (dir !== stop && !dir.startsWith(stop + sep) && dirname(stop) !== stop) stop = dirname(stop);
76
+ for (;;) {
77
+ if (existsSync(join(dir, "config.toml"))) return relative(root, join(dir, "migrations")).replaceAll("\\", "/");
78
+ if (dir === stop || dirname(dir) === dir) break;
79
+ dir = dirname(dir);
80
+ }
81
+ return "supabase/migrations";
82
+ }
83
+ /** `YYYYMMDDHHMMSS` in UTC, the Supabase CLI's migration prefix. */
84
+ const migrationStamp = (now) => now.toISOString().replaceAll(/[-:T]/g, "").slice(0, 14);
85
+ /**
86
+ * A stamp for `now`, or one second after the newest migration when that one
87
+ * has the same stamp or a later one: the Supabase CLI keys migrations by
88
+ * stamp, and the data has to run after the schema.
89
+ */
90
+ function stampAfter(migrations, now) {
91
+ const newest = migrations.map((name) => /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})_/.exec(name)).filter((match) => match !== null).map((match) => Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]), Number(match[5]), Number(match[6]))).reduce((max, time) => Math.max(max, time), Number.NEGATIVE_INFINITY);
92
+ const at = Math.floor(now.getTime() / 1e3) * 1e3;
93
+ return migrationStamp(new Date(Math.max(at, newest + 1e3)));
94
+ }
95
+ async function write(config, args, names, kit) {
96
+ const lines = [];
97
+ const dryRun = args["dry-run"] === true;
98
+ let data = false;
99
+ for (const file of renderKit(names, kit)) {
100
+ const path = resolve(config.root, file.path);
101
+ const shown = display(config.root, file.path);
102
+ if (dryRun) {
103
+ lines.push(`Would write ${shown} (${file.module})`);
104
+ continue;
105
+ }
106
+ const wrote = await writeIfChanged(path, file.contents);
107
+ if (wrote && file.kind === "data") data = true;
108
+ lines.push(`${wrote ? "Wrote" : "Unchanged"} ${shown} (${file.module})`);
109
+ }
110
+ if (data) lines.push("", DATA_NEXT);
111
+ return lines;
112
+ }
113
+ const DATA_NEXT = "The better-supabase-data files hold rows and settings a schema diff skips: after the schema migration, run `better-supabase sql data`.";
114
+ const DATA_SUFFIX = "_better_supabase_kit_data.sql";
115
+ /**
116
+ * Writes the data files of `sql.kit` into one migration, meant to run after
117
+ * the schema migration that creates their tables. Every statement is
118
+ * idempotent, so the migration carries all of them, not only the changes.
119
+ */
120
+ async function dataMigration(config, args) {
121
+ if (config.sql.kit.length === 0) return {
122
+ code: 0,
123
+ output: "sql.kit is empty; nothing to write."
124
+ };
125
+ const files = renderKit(config.sql.kit, await layoutFor(config, args, config.sql.kit)).filter((file) => file.kind === "data");
126
+ const contents = `-- better-supabase sql data: the rows and settings of ${files.map((file) => file.module).join(", ")}, which a schema diff skips.\n\n${files.map((file) => file.contents.trim()).join("\n\n")}\n`;
127
+ const dir = migrationsDir(config);
128
+ const existing = await readdir(resolve(config.root, dir)).catch(() => []);
129
+ const latest = existing.toSorted().findLast((name) => name.endsWith(DATA_SUFFIX));
130
+ if (latest !== void 0 && await readFile(resolve(config.root, dir, latest), "utf8") === contents) return {
131
+ code: 0,
132
+ output: `${dir}/${latest} already has these rows; nothing to write.`
133
+ };
134
+ const path = `${dir}/${stampAfter(existing, /* @__PURE__ */ new Date())}${DATA_SUFFIX}`;
135
+ if (args["dry-run"] === true) return {
136
+ code: 0,
137
+ output: `Would write ${path}`
138
+ };
139
+ await writeIfChanged(resolve(config.root, path), contents);
140
+ return {
141
+ code: 0,
142
+ output: `Wrote ${path}`
143
+ };
144
+ }
145
+ /** A note when migra's `schema_paths` is set and misses kit files, which `supabase db diff` would then skip. */
146
+ async function unlistedKitFiles(config, names, kit) {
147
+ const order = await schemaPaths(config.root, await readSupabaseToml(config.root));
148
+ if (!order.configured) return [];
149
+ const unlisted = new Set(order.unlisted);
150
+ const listed = new Set(order.files.filter((path) => !unlisted.has(path)));
151
+ const dir = `${config.sql.dir.replace(/\/$/, "")}/`;
152
+ const missing = renderKit(names, kit).filter((file) => file.kind !== "data").map((file) => file.path).filter((path) => path.startsWith(dir) && !listed.has(path));
153
+ if (missing.length === 0) return [];
154
+ return [
155
+ "",
156
+ "supabase/config.toml sets [db.migrations] schema_paths, and no entry matches these files, so `supabase db diff` skips them.",
157
+ "Add them before the schemas that call their functions:",
158
+ ...missing.map((path) => ` "./${path.startsWith("supabase/") ? path.slice(9) : path}",`)
159
+ ];
160
+ }
161
+ /**
162
+ * Rewrites the kit files of modules behind the current version, and writes
163
+ * their upgrade steps (renames, backfills) as a migration that runs before
164
+ * the one the schema diff creates.
165
+ */
166
+ async function upgrade(config, args, paint) {
167
+ if (config.sql.kit.length === 0) return {
168
+ code: 0,
169
+ output: "sql.kit is empty; nothing to upgrade."
170
+ };
171
+ const kit = await layoutFor(config, args, config.sql.kit);
172
+ const files = renderKit(config.sql.kit, kit);
173
+ const installed = [];
174
+ const stale = [];
175
+ const diffs = [];
176
+ for (const file of files) {
177
+ if (file.kind === "data") continue;
178
+ const current = await readFile(resolve(config.root, file.path), "utf8").catch(() => void 0);
179
+ if (current === void 0) continue;
180
+ installed.push({
181
+ module: file.module,
182
+ version: kitFileVersion(current)?.version ?? 1
183
+ });
184
+ if (!sameKitFile(current, file.contents)) {
185
+ stale.push(file);
186
+ diffs.push(fileDiff(display(config.root, file.path), current, file.contents, paint));
187
+ }
188
+ }
189
+ const plan = upgradePlan(installed, kit);
190
+ const behind = plan.map((entry) => `${entry.module} is at version ${String(entry.from)}; the current version is ${String(entry.to)}`);
191
+ if (args.check === true) {
192
+ if (plan.length === 0 && stale.length === 0) return {
193
+ code: 0,
194
+ output: "SQL kit modules are at their current versions."
195
+ };
196
+ return {
197
+ code: 1,
198
+ output: diffs.join("\n\n"),
199
+ error: [
200
+ ...behind,
201
+ ...stale.length > 0 ? [`Out of date: ${stale.map((file) => display(config.root, file.path)).join(", ")}.`] : [],
202
+ "Run `better-supabase sql upgrade`."
203
+ ].join("\n")
204
+ };
205
+ }
206
+ if (plan.length === 0 && stale.length === 0) return {
207
+ code: 0,
208
+ output: "SQL kit modules are at their current versions."
209
+ };
210
+ const lines = [];
211
+ const steps = plan.flatMap((entry) => entry.steps.filter((step) => step.sql !== "").map((step) => `-- ${entry.module}: version ${String(step.from)} to ${String(step.from + 1)}. ${step.description}\n${step.sql}`));
212
+ const dryRun = args["dry-run"] === true;
213
+ if (steps.length > 0) {
214
+ const path = `${migrationsDir(config)}/${migrationStamp(/* @__PURE__ */ new Date())}_better_supabase_kit_upgrade.sql`;
215
+ const contents = `-- better-supabase sql upgrade: steps that run before the schema diff.\n\n${steps.join("\n\n")}\n`;
216
+ if (dryRun) lines.push(`Would write ${path}`);
217
+ else {
218
+ await writeIfChanged(resolve(config.root, path), contents);
219
+ lines.push(`Wrote ${path}`);
220
+ }
221
+ }
222
+ for (const entry of plan) lines.push(`${entry.module}: version ${String(entry.from)} to ${String(entry.to)}`, ...entry.steps.map((step) => ` ${step.description}`));
223
+ lines.push(...await write(config, args, config.sql.kit, kit), "", `Then create a migration: ${migrationCommand(await readSupabaseToml(config.root), "better_supabase_kit")}`);
224
+ return {
225
+ code: 0,
226
+ output: lines.join("\n")
227
+ };
228
+ }
229
+ async function runSql(config, args, paint = plain) {
230
+ const [action, ...names] = args._;
231
+ switch (action) {
232
+ case "list": {
233
+ const kit = await layout(config, args);
234
+ const files = new Map(renderKit(Object.keys(SQL_MODULES), kit).filter((file) => file.kind !== "data").map((file) => [file.module, file.path]));
235
+ return {
236
+ code: 0,
237
+ output: `${Object.values(SQL_MODULES).map((module) => {
238
+ const path = files.get(module.name);
239
+ const installed = path !== void 0 && existsSync(resolve(config.root, path));
240
+ const tracked = config.sql.kit.includes(module.name);
241
+ const mark = path === void 0 ? "◇" : installed ? tracked ? "●" : "○" : " ";
242
+ const requires = kit.permdock && module.permdockRequires ? module.permdockRequires : module.requires;
243
+ const needs = requires.length > 0 ? ` (needs ${requires.join(", ")})` : "";
244
+ return `${mark} ${module.name.padEnd(15)} ${module.description}${needs}`;
245
+ }).join("\n")}\n\n● installed and in sql.kit ○ installed, not in sql.kit ◇ custom mode (the app implements it)`
246
+ };
247
+ }
248
+ case "add": {
249
+ if (names.length === 0) return {
250
+ code: 2,
251
+ error: `Name at least one module.\n${USAGE}`
252
+ };
253
+ const unknown = names.filter((name) => !(name in SQL_MODULES));
254
+ if (unknown.length > 0) return {
255
+ code: 2,
256
+ error: `Unknown module ${unknown.join(", ")}. Available: ${Object.keys(SQL_MODULES).join(", ")}`
257
+ };
258
+ const project = await readPermdock(config.root, config.permdock);
259
+ const permdock = project ? permdockSource(project) : void 0;
260
+ const hookModules = names.filter((name) => PERMDOCK_OWNED.has(name));
261
+ if (permdock && hookModules.length > 0 && args.force !== true) return {
262
+ code: 1,
263
+ error: [
264
+ `${permdock} is present, so PermDock owns the access token hook and the memberships claim.`,
265
+ `${hookModules.join(" and ")} would add a second source for them. Use \`permdock supabase hook generate\` instead,`,
266
+ "or pass --force to write the modules anyway."
267
+ ].join("\n")
268
+ };
269
+ const kit = await layoutFor(config, args, names);
270
+ const lines = await write(config, args, names, kit);
271
+ const pulledIn = resolveModules(names, kit).map((module) => module.name).filter((name) => PERMDOCK_OWNED.has(name) && !names.includes(name));
272
+ if (permdock && pulledIn.length > 0) lines.push("", `${pulledIn.join(" and ")} came along as a dependency: its memberships table backs has_org_role() and has_entitlement().`, `PermDock's hook still owns the memberships claim, so don't call better_supabase.membership_claims from a hook.`, "List better_supabase.memberships as a PermDock membership source if both should agree.");
273
+ const untracked = resolveModules(names, kit).map((module) => module.name).filter((name) => !config.sql.kit.includes(name));
274
+ if (untracked.length > 0) lines.push("", `Add them to your config so \`sql sync --check\` keeps them current:`, ` sql: { kit: [${[...config.sql.kit, ...untracked].map((name) => `'${name}'`).join(", ")}] }`);
275
+ if (args["dry-run"] !== true) lines.push(...await unlistedKitFiles(config, names, kit));
276
+ lines.push("", `Then create a migration: ${migrationCommand(await readSupabaseToml(config.root), "better_supabase_kit")}`);
277
+ return {
278
+ code: 0,
279
+ output: lines.join("\n")
280
+ };
281
+ }
282
+ case "sync": {
283
+ if (config.sql.kit.length === 0) return {
284
+ code: 0,
285
+ output: "sql.kit is empty; nothing to sync."
286
+ };
287
+ if (args.check !== true) return {
288
+ code: 0,
289
+ output: (await write(config, args, config.sql.kit, await layoutFor(config, args, config.sql.kit))).join("\n")
290
+ };
291
+ const stale = [];
292
+ const diffs = [];
293
+ for (const file of renderKit(config.sql.kit, await layoutFor(config, args, config.sql.kit))) {
294
+ const current = await readFile(resolve(config.root, file.path), "utf8").catch(() => void 0);
295
+ if (!sameKitFile(current, file.contents)) {
296
+ const shown = display(config.root, file.path);
297
+ stale.push(shown);
298
+ diffs.push(fileDiff(shown, current, file.contents, paint));
299
+ }
300
+ }
301
+ return stale.length === 0 ? {
302
+ code: 0,
303
+ output: "SQL kit files are up to date."
304
+ } : {
305
+ code: 1,
306
+ output: diffs.join("\n\n"),
307
+ error: `Out of date: ${stale.join(", ")}. Run \`better-supabase sql sync\`.`
308
+ };
309
+ }
310
+ case "upgrade": return upgrade(config, args, paint);
311
+ case "data": return dataMigration(config, args);
312
+ case "print": {
313
+ const [name] = names;
314
+ const module = name ? SQL_MODULES[name] : void 0;
315
+ if (!module) return {
316
+ code: 2,
317
+ error: `Name one module: ${Object.keys(SQL_MODULES).join(", ")}`
318
+ };
319
+ const body = moduleBody(module.name, await layout(config, args, [module.name]));
320
+ if (body === void 0) return {
321
+ code: 0,
322
+ output: `-- kits.${module.name} is in custom mode: the app writes these functions.\n${customContracts([module.name], await layout(config, args, [module.name])).flatMap((contract) => contract.functions.map((fn) => `-- ${contract.schema}.${fn.name}(${contractSignature(fn, contract.idType)}) returns ${fn.returns.replaceAll("{id}", contract.idType)}`)).join("\n")}`
323
+ };
324
+ return {
325
+ code: 0,
326
+ output: body
327
+ };
328
+ }
329
+ case void 0: return {
330
+ code: 2,
331
+ error: `Name an action.\n${USAGE}`
332
+ };
333
+ default: return {
334
+ code: 2,
335
+ error: `Unknown sql action "${action}".\n${USAGE}`
336
+ };
337
+ }
338
+ }
339
+ const sqlCommand = defineCliCommand({
340
+ meta: {
341
+ name: "sql",
342
+ description: "Lists, adds, syncs, upgrades and prints SQL kit modules, and writes their data migration"
343
+ },
344
+ args: SQL_ARGS,
345
+ run: (args, { config, io }) => runSql(config, args, painter(io.color))
346
+ });
347
+ //#endregion
348
+ export { sqlCommand };
@@ -0,0 +1,29 @@
1
+ import { styleText } from "node:util";
2
+ //#region src/cli/style.ts
3
+ const plain = (_format, text) => text;
4
+ const colored = (format, text) => styleText(format, text, { validateStream: false });
5
+ function painter(color) {
6
+ return color === true ? colored : plain;
7
+ }
8
+ /** Whether stdout takes colors: a TTY, and neither `NO_COLOR` nor `NODE_DISABLE_COLORS` set (`FORCE_COLOR` overrides). */
9
+ function colorEnabled() {
10
+ return styleText("red", "x", { stream: process.stdout }) !== "x";
11
+ }
12
+ const VERBS = [
13
+ [/^(?:Wrote|Updated)\b/, "green"],
14
+ [/^Would write\b/, "yellow"],
15
+ [/^(?:Unchanged|Kept|Same)\b/, "dim"]
16
+ ];
17
+ /** Colors the verb that starts each `Wrote`, `Kept`, `Unchanged` or `Would write` line. */
18
+ function highlight(text, paint) {
19
+ if (paint === plain) return text;
20
+ return text.split("\n").map((line) => {
21
+ for (const [pattern, format] of VERBS) {
22
+ const verb = pattern.exec(line)?.[0];
23
+ if (verb) return `${paint(format, verb)}${line.slice(verb.length)}`;
24
+ }
25
+ return line;
26
+ }).join("\n");
27
+ }
28
+ //#endregion
29
+ export { plain as i, highlight as n, painter as r, colorEnabled as t };