supaschema 0.1.0-rc.1

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 (257) hide show
  1. package/.agents/skills/supaschema/SKILL.md +61 -0
  2. package/.claude/hooks/block-generated-migration-edits.mjs +32 -0
  3. package/.claude/rules/supaschema.md +22 -0
  4. package/.claude/settings.json +16 -0
  5. package/.claude/skills/supaschema/SKILL.md +61 -0
  6. package/.codex/hooks/supaschema-tool-gate.mjs +73 -0
  7. package/.codex/hooks.json +16 -0
  8. package/.codex/rules/supaschema.rules +22 -0
  9. package/AGENTS.md +40 -0
  10. package/LICENSE +661 -0
  11. package/LICENSE-COMMERCIAL.md +35 -0
  12. package/README.md +249 -0
  13. package/benchmarks/README.md +104 -0
  14. package/benchmarks/compare.js +489 -0
  15. package/benchmarks/fixtures/additive/from.sql +8 -0
  16. package/benchmarks/fixtures/additive/manifest.json +1 -0
  17. package/benchmarks/fixtures/additive/to.sql +9 -0
  18. package/benchmarks/fixtures/functions-policies/from.sql +24 -0
  19. package/benchmarks/fixtures/functions-policies/manifest.json +5 -0
  20. package/benchmarks/fixtures/functions-policies/to.sql +24 -0
  21. package/benchmarks/plot-lib.js +234 -0
  22. package/benchmarks/plot-svg.js +339 -0
  23. package/benchmarks/plot.js +154 -0
  24. package/benchmarks/tools/bench-all.sh +49 -0
  25. package/benchmarks/tools/build-project-fixture.mjs +245 -0
  26. package/benchmarks/tools/compare-db.mjs +101 -0
  27. package/benchmarks/tools/compare-fixtures.mjs +84 -0
  28. package/benchmarks/tools/compare-report.mjs +90 -0
  29. package/benchmarks/tools/compare-supabase.mjs +67 -0
  30. package/benchmarks/tools/registry.js +266 -0
  31. package/benchmarks/tools/run-workflow.mjs +77 -0
  32. package/bin/postinstall.mjs +26 -0
  33. package/bin/supaschema +2 -0
  34. package/config-schema.json +208 -0
  35. package/corpus/supabase-style/corpus.json +6 -0
  36. package/corpus/supabase-style/migrations/20260101000000_init.sql +28 -0
  37. package/corpus/supabase-style/migrations/20260102000000_noise.sql +13 -0
  38. package/corpus/supabase-style/migrations/20260103000000_churn.sql +4 -0
  39. package/corpus/supabase-style/migrations/20260104000000_triggers.sql +17 -0
  40. package/corpus/supabase-style/roles.sql +13 -0
  41. package/corpus/supabase-style/tree/functions.sql +26 -0
  42. package/corpus/supabase-style/tree/policies.sql +4 -0
  43. package/corpus/supabase-style/tree/schema.sql +4 -0
  44. package/corpus/supabase-style/tree/tables.sql +20 -0
  45. package/corpus/supabase-style/tree/triggers.sql +3 -0
  46. package/corpus/supabase-style/tree/types.sql +2 -0
  47. package/corpus/supabase-style/tree/views.sql +6 -0
  48. package/dist/audit.d.ts +20 -0
  49. package/dist/audit.d.ts.map +1 -0
  50. package/dist/audit.js +68 -0
  51. package/dist/benchmark-db.d.ts +5 -0
  52. package/dist/benchmark-db.d.ts.map +1 -0
  53. package/dist/benchmark-db.js +71 -0
  54. package/dist/benchmark-fixtures.d.ts +10 -0
  55. package/dist/benchmark-fixtures.d.ts.map +1 -0
  56. package/dist/benchmark-fixtures.js +201 -0
  57. package/dist/benchmark.d.ts +2 -0
  58. package/dist/benchmark.d.ts.map +1 -0
  59. package/dist/benchmark.js +308 -0
  60. package/dist/catalog-comments.d.ts +9 -0
  61. package/dist/catalog-comments.d.ts.map +1 -0
  62. package/dist/catalog-comments.js +194 -0
  63. package/dist/catalog-extras.d.ts +12 -0
  64. package/dist/catalog-extras.d.ts.map +1 -0
  65. package/dist/catalog-extras.js +408 -0
  66. package/dist/catalog-foreign.d.ts +15 -0
  67. package/dist/catalog-foreign.d.ts.map +1 -0
  68. package/dist/catalog-foreign.js +114 -0
  69. package/dist/catalog-tables.d.ts +9 -0
  70. package/dist/catalog-tables.d.ts.map +1 -0
  71. package/dist/catalog-tables.js +114 -0
  72. package/dist/catalog.d.ts +8 -0
  73. package/dist/catalog.d.ts.map +1 -0
  74. package/dist/catalog.js +351 -0
  75. package/dist/check-hazards.d.ts +7 -0
  76. package/dist/check-hazards.d.ts.map +1 -0
  77. package/dist/check-hazards.js +83 -0
  78. package/dist/check-reporters.d.ts +8 -0
  79. package/dist/check-reporters.d.ts.map +1 -0
  80. package/dist/check-reporters.js +76 -0
  81. package/dist/check.d.ts +3 -0
  82. package/dist/check.d.ts.map +1 -0
  83. package/dist/check.js +229 -0
  84. package/dist/cli-defaults.d.ts +24 -0
  85. package/dist/cli-defaults.d.ts.map +1 -0
  86. package/dist/cli-defaults.js +65 -0
  87. package/dist/cli-diff.d.ts +13 -0
  88. package/dist/cli-diff.d.ts.map +1 -0
  89. package/dist/cli-diff.js +348 -0
  90. package/dist/cli-reports.d.ts +9 -0
  91. package/dist/cli-reports.d.ts.map +1 -0
  92. package/dist/cli-reports.js +90 -0
  93. package/dist/cli-tools.d.ts +17 -0
  94. package/dist/cli-tools.d.ts.map +1 -0
  95. package/dist/cli-tools.js +136 -0
  96. package/dist/cli.d.ts +2 -0
  97. package/dist/cli.d.ts.map +1 -0
  98. package/dist/cli.js +239 -0
  99. package/dist/config-schema-gen.d.ts +2 -0
  100. package/dist/config-schema-gen.d.ts.map +1 -0
  101. package/dist/config-schema-gen.js +11 -0
  102. package/dist/config.d.ts +58 -0
  103. package/dist/config.d.ts.map +1 -0
  104. package/dist/config.js +132 -0
  105. package/dist/core.d.ts +115 -0
  106. package/dist/core.d.ts.map +1 -0
  107. package/dist/core.js +1 -0
  108. package/dist/corpus.d.ts +26 -0
  109. package/dist/corpus.d.ts.map +1 -0
  110. package/dist/corpus.js +112 -0
  111. package/dist/database-url.d.ts +8 -0
  112. package/dist/database-url.d.ts.map +1 -0
  113. package/dist/database-url.js +74 -0
  114. package/dist/db-admin.d.ts +23 -0
  115. package/dist/db-admin.d.ts.map +1 -0
  116. package/dist/db-admin.js +147 -0
  117. package/dist/diagnostics.d.ts +16 -0
  118. package/dist/diagnostics.d.ts.map +1 -0
  119. package/dist/diagnostics.js +155 -0
  120. package/dist/diff-score.d.ts +12 -0
  121. package/dist/diff-score.d.ts.map +1 -0
  122. package/dist/diff-score.js +339 -0
  123. package/dist/doctor.d.ts +17 -0
  124. package/dist/doctor.d.ts.map +1 -0
  125. package/dist/doctor.js +110 -0
  126. package/dist/hash.d.ts +7 -0
  127. package/dist/hash.d.ts.map +1 -0
  128. package/dist/hash.js +34 -0
  129. package/dist/index.d.ts +24 -0
  130. package/dist/index.d.ts.map +1 -0
  131. package/dist/index.js +17 -0
  132. package/dist/lineage.d.ts +23 -0
  133. package/dist/lineage.d.ts.map +1 -0
  134. package/dist/lineage.js +61 -0
  135. package/dist/migrations-status.d.ts +35 -0
  136. package/dist/migrations-status.d.ts.map +1 -0
  137. package/dist/migrations-status.js +131 -0
  138. package/dist/plan-order.d.ts +4 -0
  139. package/dist/plan-order.d.ts.map +1 -0
  140. package/dist/plan-order.js +178 -0
  141. package/dist/planner-replace.d.ts +4 -0
  142. package/dist/planner-replace.d.ts.map +1 -0
  143. package/dist/planner-replace.js +76 -0
  144. package/dist/planner-table.d.ts +3 -0
  145. package/dist/planner-table.d.ts.map +1 -0
  146. package/dist/planner-table.js +165 -0
  147. package/dist/planner.d.ts +5 -0
  148. package/dist/planner.d.ts.map +1 -0
  149. package/dist/planner.js +385 -0
  150. package/dist/render-guards.d.ts +12 -0
  151. package/dist/render-guards.d.ts.map +1 -0
  152. package/dist/render-guards.js +159 -0
  153. package/dist/render.d.ts +7 -0
  154. package/dist/render.d.ts.map +1 -0
  155. package/dist/render.js +325 -0
  156. package/dist/selfcheck.d.ts +11 -0
  157. package/dist/selfcheck.d.ts.map +1 -0
  158. package/dist/selfcheck.js +43 -0
  159. package/dist/source-normalize.d.ts +14 -0
  160. package/dist/source-normalize.d.ts.map +1 -0
  161. package/dist/source-normalize.js +420 -0
  162. package/dist/source.d.ts +4 -0
  163. package/dist/source.d.ts.map +1 -0
  164. package/dist/source.js +233 -0
  165. package/dist/sql/ast.d.ts +42 -0
  166. package/dist/sql/ast.d.ts.map +1 -0
  167. package/dist/sql/ast.js +241 -0
  168. package/dist/sql/canonical-nodes.d.ts +5 -0
  169. package/dist/sql/canonical-nodes.d.ts.map +1 -0
  170. package/dist/sql/canonical-nodes.js +101 -0
  171. package/dist/sql/extract-helpers.d.ts +18 -0
  172. package/dist/sql/extract-helpers.d.ts.map +1 -0
  173. package/dist/sql/extract-helpers.js +127 -0
  174. package/dist/sql/extract.d.ts +13 -0
  175. package/dist/sql/extract.d.ts.map +1 -0
  176. package/dist/sql/extract.js +323 -0
  177. package/dist/sql/facts.d.ts +34 -0
  178. package/dist/sql/facts.d.ts.map +1 -0
  179. package/dist/sql/facts.js +392 -0
  180. package/dist/sql/identifiers.d.ts +13 -0
  181. package/dist/sql/identifiers.d.ts.map +1 -0
  182. package/dist/sql/identifiers.js +83 -0
  183. package/dist/sql/normalize-deparse.d.ts +25 -0
  184. package/dist/sql/normalize-deparse.d.ts.map +1 -0
  185. package/dist/sql/normalize-deparse.js +96 -0
  186. package/dist/sql/object-hash.d.ts +5 -0
  187. package/dist/sql/object-hash.d.ts.map +1 -0
  188. package/dist/sql/object-hash.js +24 -0
  189. package/dist/sql/parser.d.ts +8 -0
  190. package/dist/sql/parser.d.ts.map +1 -0
  191. package/dist/sql/parser.js +89 -0
  192. package/dist/sql/privileges.d.ts +33 -0
  193. package/dist/sql/privileges.d.ts.map +1 -0
  194. package/dist/sql/privileges.js +379 -0
  195. package/dist/sql/split.d.ts +3 -0
  196. package/dist/sql/split.d.ts.map +1 -0
  197. package/dist/sql/split.js +182 -0
  198. package/dist/sql/statements.d.ts +17 -0
  199. package/dist/sql/statements.d.ts.map +1 -0
  200. package/dist/sql/statements.js +284 -0
  201. package/dist/sql/table-constraints.d.ts +15 -0
  202. package/dist/sql/table-constraints.d.ts.map +1 -0
  203. package/dist/sql/table-constraints.js +304 -0
  204. package/dist/sql/table-shape.d.ts +38 -0
  205. package/dist/sql/table-shape.d.ts.map +1 -0
  206. package/dist/sql/table-shape.js +287 -0
  207. package/dist/sync.d.ts +27 -0
  208. package/dist/sync.d.ts.map +1 -0
  209. package/dist/sync.js +86 -0
  210. package/dist/typegen-model.d.ts +78 -0
  211. package/dist/typegen-model.d.ts.map +1 -0
  212. package/dist/typegen-model.js +338 -0
  213. package/dist/typegen-views.d.ts +7 -0
  214. package/dist/typegen-views.d.ts.map +1 -0
  215. package/dist/typegen-views.js +92 -0
  216. package/dist/typegen-zod.d.ts +3 -0
  217. package/dist/typegen-zod.d.ts.map +1 -0
  218. package/dist/typegen-zod.js +149 -0
  219. package/dist/typegen.d.ts +4 -0
  220. package/dist/typegen.d.ts.map +1 -0
  221. package/dist/typegen.js +184 -0
  222. package/dist/validators.d.ts +3 -0
  223. package/dist/validators.d.ts.map +1 -0
  224. package/dist/validators.js +104 -0
  225. package/dist/verify-environment.d.ts +5 -0
  226. package/dist/verify-environment.d.ts.map +1 -0
  227. package/dist/verify-environment.js +92 -0
  228. package/dist/verify.d.ts +3 -0
  229. package/dist/verify.d.ts.map +1 -0
  230. package/dist/verify.js +261 -0
  231. package/docs/benchmarks/additive-correctness.svg +86 -0
  232. package/docs/benchmarks/additive-latency.svg +60 -0
  233. package/docs/benchmarks/functions-policies-correctness.svg +86 -0
  234. package/docs/benchmarks/functions-policies-latency.svg +60 -0
  235. package/docs/benchmarks/realistic-correctness.svg +86 -0
  236. package/docs/benchmarks/realistic-latency.svg +60 -0
  237. package/docs/benchmarks/scaling-latency.svg +106 -0
  238. package/docs/benchmarks/workflow-latency.svg +98 -0
  239. package/docs/benchmarks/xl-correctness.svg +86 -0
  240. package/docs/benchmarks/xl-latency.svg +60 -0
  241. package/docs/benchmarks/xxl-correctness.svg +86 -0
  242. package/docs/benchmarks/xxl-latency.svg +66 -0
  243. package/docs/case-study-anilize.md +51 -0
  244. package/docs/ci-gate.md +44 -0
  245. package/docs/ci.md +68 -0
  246. package/docs/commands.md +35 -0
  247. package/docs/config.md +72 -0
  248. package/docs/corpus.md +33 -0
  249. package/docs/diagnostics.md +77 -0
  250. package/docs/hints.md +92 -0
  251. package/docs/release.md +19 -0
  252. package/docs/support-matrix.md +57 -0
  253. package/examples/postgres/schemas/001_app.sql +17 -0
  254. package/examples/supabase/schemas/001_app.sql +13 -0
  255. package/examples/supabase/schemas-next/001_app.sql +21 -0
  256. package/examples/supabase/supaschema.config.json +15 -0
  257. package/package.json +99 -0
@@ -0,0 +1,90 @@
1
+ export function skippedResult(adapter, fixture, reason) {
2
+ return {
3
+ adapter: adapter.id,
4
+ fixture: fixture.name,
5
+ mode: adapter.mode,
6
+ outputFormat: adapter.output,
7
+ reason,
8
+ skipped: true,
9
+ };
10
+ }
11
+
12
+ export function unsupportedResult(adapter, fixture, reason) {
13
+ return {
14
+ adapter: adapter.id,
15
+ fixture: fixture.name,
16
+ mode: adapter.mode,
17
+ outputFormat: adapter.output,
18
+ reason,
19
+ skipped: false,
20
+ unsupported: true,
21
+ };
22
+ }
23
+
24
+ export function failedResult(adapter, fixture, warmup, iteration, error) {
25
+ const message = errorMessage(error);
26
+ return {
27
+ adapter: adapter.id,
28
+ appliesOnce: false,
29
+ appliesTwice: false,
30
+ attempts: 0,
31
+ commandFailed: true,
32
+ elapsedMs: 0,
33
+ exitCode: 1,
34
+ fixture: fixture.name,
35
+ matchesTargetAfterFirstApply: false,
36
+ matchesTargetAfterSecondApply: false,
37
+ matchesTargetFingerprint: false,
38
+ mode: adapter.mode,
39
+ outputBytes: 0,
40
+ outputFormat: adapter.output,
41
+ skipped: false,
42
+ stderrBytes: Buffer.byteLength(message),
43
+ stderrPreview: preview(redactSecrets(message)),
44
+ timedOut: false,
45
+ totalElapsedMs: 0,
46
+ verificationReason: message,
47
+ warmup,
48
+ iteration,
49
+ };
50
+ }
51
+
52
+ export function combineExecutions(executions) {
53
+ const latest = executions.at(-1) ?? {
54
+ exitCode: 1,
55
+ stderr: "command was not executed",
56
+ stdout: "",
57
+ timedOut: false,
58
+ };
59
+ return {
60
+ ...latest,
61
+ stderr: executions
62
+ .map((item, index) =>
63
+ index === 0 ? item.stderr : `--- retry ${index + 1} ---\n${item.stderr}`,
64
+ )
65
+ .filter(Boolean)
66
+ .join("\n"),
67
+ timedOut: executions.some((item) => item.timedOut),
68
+ };
69
+ }
70
+
71
+ export function preview(value) {
72
+ const trimmed = value.trim();
73
+ return trimmed ? trimmed.slice(0, 500) : undefined;
74
+ }
75
+
76
+ export function redactSecrets(value) {
77
+ return value.replace(/([a-z][a-z0-9+.-]*:\/\/)([^:@/\s]+):([^@/\s]+)@/gi, "$1***:***@");
78
+ }
79
+
80
+ export function summary(payload) {
81
+ const runnable = payload.results.filter((item) => !item.skipped && !item.unsupported);
82
+ const skipped = payload.results.filter((item) => item.skipped);
83
+ const unsupported = payload.results.filter((item) => item.unsupported);
84
+ const failed = runnable.filter((item) => item.commandFailed ?? item.exitCode !== 0);
85
+ return `comparison benchmark: ${runnable.length} runs, ${unsupported.length} unsupported, ${skipped.length} skips, ${failed.length} command failures`;
86
+ }
87
+
88
+ export function errorMessage(error) {
89
+ return error instanceof Error ? error.message : String(error);
90
+ }
@@ -0,0 +1,67 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { createServer } from "node:net";
3
+ import { join } from "node:path";
4
+
5
+ let portCursor = Number(process.env.SUPASCHEMA_COMPARE_PORT_BASE ?? 55_400);
6
+
7
+ export async function prepareSupabaseWorkdir(context, adapter, iteration) {
8
+ if (!adapter.id.startsWith("supabase-")) {
9
+ return;
10
+ }
11
+ const [dbPort, shadowPort] = await availablePortPair();
12
+ const supabaseDirectory = join(context.runRoot, "supabase");
13
+ const projectId = stableProjectId(context.fixture.name, adapter.id, iteration);
14
+ await mkdir(supabaseDirectory, { recursive: true });
15
+ await writeFile(
16
+ join(supabaseDirectory, "config.toml"),
17
+ `project_id = "${projectId}"
18
+
19
+ [db]
20
+ port = ${dbPort}
21
+ shadow_port = ${shadowPort}
22
+ major_version = 17
23
+ `,
24
+ "utf8",
25
+ );
26
+ context.supabaseConfig = {
27
+ dbPort,
28
+ projectId,
29
+ shadowPort,
30
+ };
31
+ }
32
+
33
+ async function availablePortPair() {
34
+ const first = await availablePort(portCursor);
35
+ const second = await availablePort(first + 1);
36
+ portCursor = second + 1;
37
+ return [first, second];
38
+ }
39
+
40
+ async function availablePort(start) {
41
+ for (let port = start; port < 65_535; port += 1) {
42
+ if (await isPortAvailable(port)) {
43
+ return port;
44
+ }
45
+ }
46
+ throw new Error(`could not find an available local port from ${start}`);
47
+ }
48
+
49
+ function isPortAvailable(port) {
50
+ return new Promise((resolvePort) => {
51
+ const server = createServer();
52
+ server.unref();
53
+ server.on("error", () => resolvePort(false));
54
+ server.listen({ host: "127.0.0.1", port }, () => {
55
+ server.close(() => resolvePort(true));
56
+ });
57
+ });
58
+ }
59
+
60
+ function stableProjectId(fixtureName, adapterId, iteration) {
61
+ const source = `${fixtureName}:${adapterId}:${iteration}:${process.pid}`;
62
+ let hash = 0;
63
+ for (const character of source) {
64
+ hash = (hash * 31 + character.charCodeAt(0)) >>> 0;
65
+ }
66
+ return `supaschema-${hash.toString(36)}`;
67
+ }
@@ -0,0 +1,266 @@
1
+ import { access, writeFile } from "node:fs/promises";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const here = dirname(fileURLToPath(import.meta.url));
6
+ const root = resolve(here, "../..");
7
+ const localSupabaseBinary = resolve(root, "node_modules/.bin/supabase");
8
+
9
+ export const adapters = [
10
+ {
11
+ binary: process.execPath,
12
+ id: "supaschema-file",
13
+ mode: "source-file diff",
14
+ output: "sql",
15
+ requiresDatabase: false,
16
+ async command(context) {
17
+ return {
18
+ args: [
19
+ resolve(root, "dist/cli.js"),
20
+ ...(context.supaschemaConfigPath ? ["--config", context.supaschemaConfigPath] : []),
21
+ "--quiet",
22
+ "diff",
23
+ "--from",
24
+ `dump:${context.fromSqlPath}`,
25
+ "--to",
26
+ `dump:${context.toSqlPath}`,
27
+ "--out",
28
+ "stdout",
29
+ ],
30
+ command: process.execPath,
31
+ };
32
+ },
33
+ },
34
+ {
35
+ binary: process.execPath,
36
+ id: "supaschema-db",
37
+ mode: "live-catalog diff",
38
+ output: "sql",
39
+ requiresDatabase: true,
40
+ async command(context) {
41
+ return {
42
+ args: [
43
+ resolve(root, "dist/cli.js"),
44
+ ...(context.supaschemaConfigPath ? ["--config", context.supaschemaConfigPath] : []),
45
+ "--quiet",
46
+ "diff",
47
+ "--from",
48
+ `database:${context.fromDatabaseUrl}`,
49
+ "--to",
50
+ `database:${context.toDatabaseUrl}`,
51
+ "--out",
52
+ "stdout",
53
+ ],
54
+ command: process.execPath,
55
+ };
56
+ },
57
+ },
58
+ {
59
+ binary: process.execPath,
60
+ id: "supaschema-workflow",
61
+ mode: "full workflow: diff + migration + TS types + Zod validators",
62
+ output: "sql",
63
+ requiresDatabase: false,
64
+ async command(context) {
65
+ const typesFile = join(context.runRoot, "database.types.ts");
66
+ const zodFile = join(context.runRoot, "database.zod.ts");
67
+ await writeFile(typesFile, "", "utf8");
68
+ await writeFile(zodFile, "", "utf8");
69
+ const configPath = join(context.runRoot, "supaschema.workflow.config.json");
70
+ await writeFile(
71
+ configPath,
72
+ `${JSON.stringify({
73
+ ...(context.supaschemaAdapter ? { adapter: context.supaschemaAdapter } : {}),
74
+ typesFile,
75
+ zodFile,
76
+ })}\n`,
77
+ "utf8",
78
+ );
79
+ const spec = {
80
+ diff: {
81
+ args: [
82
+ resolve(root, "dist/cli.js"),
83
+ "--config",
84
+ configPath,
85
+ "--quiet",
86
+ "diff",
87
+ "--from",
88
+ `dump:${context.fromSqlPath}`,
89
+ "--to",
90
+ `dump:${context.toSqlPath}`,
91
+ "--out",
92
+ context.outputPath,
93
+ ],
94
+ command: process.execPath,
95
+ },
96
+ migrationPath: context.outputPath,
97
+ };
98
+ const specPath = join(context.runRoot, "workflow.json");
99
+ await writeFile(specPath, `${JSON.stringify(spec)}\n`, "utf8");
100
+ return {
101
+ args: [resolve(here, "run-workflow.mjs"), specPath],
102
+ command: process.execPath,
103
+ };
104
+ },
105
+ },
106
+ supabaseAdapter("supabase-default"),
107
+ supabaseAdapter("supabase-migra", "--use-migra"),
108
+ supabaseAdapter("supabase-pg-delta", "--use-pg-delta"),
109
+ supabaseAdapter("supabase-pg-schema", "--use-pg-schema"),
110
+ supabaseAdapter("supabase-pgadmin", "--use-pgadmin"),
111
+ supabaseWorkflowAdapter("supabase-default-workflow"),
112
+ supabaseWorkflowAdapter("supabase-migra-workflow", "--use-migra"),
113
+ supabaseWorkflowAdapter("supabase-pg-delta-workflow", "--use-pg-delta"),
114
+ supabaseWorkflowAdapter("supabase-pg-schema-workflow", "--use-pg-schema"),
115
+ supabaseWorkflowAdapter("supabase-pgadmin-workflow", "--use-pgadmin"),
116
+ ];
117
+
118
+ export async function adapterAvailability(adapter) {
119
+ const binary = adapter.resolveBinary ? await adapter.resolveBinary() : adapter.binary;
120
+ if (binary === process.execPath) {
121
+ return { available: true };
122
+ }
123
+ if (binary.includes("/")) {
124
+ try {
125
+ await access(binary);
126
+ return { available: true };
127
+ } catch {
128
+ return { available: false, reason: `binary "${binary}" was not found` };
129
+ }
130
+ }
131
+ const found = await findOnPath(binary);
132
+ if (found) {
133
+ return { available: true };
134
+ }
135
+ return { available: false, reason: `binary "${binary}" was not found on PATH` };
136
+ }
137
+
138
+ async function findOnPath(binary) {
139
+ const path = process.env.PATH ?? "";
140
+ for (const segment of path.split(":")) {
141
+ if (!segment) {
142
+ continue;
143
+ }
144
+ const candidate = resolve(segment, binary);
145
+ try {
146
+ await access(candidate);
147
+ return candidate;
148
+ } catch {
149
+ // Continue scanning PATH.
150
+ }
151
+ }
152
+ return undefined;
153
+ }
154
+
155
+ async function resolveSupabaseBinary() {
156
+ try {
157
+ await access(localSupabaseBinary);
158
+ return localSupabaseBinary;
159
+ } catch {
160
+ return "supabase";
161
+ }
162
+ }
163
+
164
+ function supabaseWorkflowAdapter(id, engineFlag) {
165
+ return {
166
+ binary: "supabase",
167
+ id,
168
+ maxAttempts: 3,
169
+ mode: "full workflow: db diff + apply + gen types",
170
+ output: "sql",
171
+ requiresDatabase: true,
172
+ resolveBinary: resolveSupabaseBinary,
173
+ retryDelayMs: 2_000,
174
+ retryOnFailure(execution) {
175
+ return (
176
+ execution.stderr.includes("Address already in use") &&
177
+ !execution.stderr.includes("workflow: applying migration")
178
+ );
179
+ },
180
+ async command(context) {
181
+ const supabaseBinary = await resolveSupabaseBinary();
182
+ const schemas = context.schemas ?? ["app"];
183
+ const diffArgs = [
184
+ "--workdir",
185
+ context.runRoot,
186
+ "db",
187
+ "diff",
188
+ "--from",
189
+ context.fromDatabaseUrl,
190
+ "--to",
191
+ context.toDatabaseUrl,
192
+ "--schema",
193
+ schemas.join(","),
194
+ "--output",
195
+ context.outputPath,
196
+ ];
197
+ if (engineFlag) {
198
+ diffArgs.push(engineFlag);
199
+ }
200
+ const spec = {
201
+ applyDatabaseUrl: context.fromDatabaseUrl,
202
+ diff: { args: diffArgs, command: supabaseBinary },
203
+ genTypes: {
204
+ args: [
205
+ "--workdir",
206
+ context.runRoot,
207
+ "gen",
208
+ "types",
209
+ "--lang=typescript",
210
+ "--db-url",
211
+ context.fromDatabaseUrl,
212
+ ...schemas.flatMap((schema) => ["--schema", schema]),
213
+ ],
214
+ command: supabaseBinary,
215
+ outPath: join(context.runRoot, "database.types.ts"),
216
+ },
217
+ migrationPath: context.outputPath,
218
+ };
219
+ const specPath = join(context.runRoot, "workflow.json");
220
+ await writeFile(specPath, `${JSON.stringify(spec)}\n`, "utf8");
221
+ return {
222
+ args: [resolve(here, "run-workflow.mjs"), specPath],
223
+ command: process.execPath,
224
+ };
225
+ },
226
+ };
227
+ }
228
+
229
+ function supabaseAdapter(id, engineFlag) {
230
+ return {
231
+ binary: "supabase",
232
+ id,
233
+ maxAttempts: 3,
234
+ mode: engineFlag ? `Supabase db diff ${engineFlag}` : "Supabase db diff default",
235
+ output: "sql",
236
+ requiresDatabase: true,
237
+ resolveBinary: resolveSupabaseBinary,
238
+ retryDelayMs: 2_000,
239
+ retryOnFailure(execution) {
240
+ return execution.stderr.includes("Address already in use");
241
+ },
242
+ async command(context) {
243
+ const args = [
244
+ "--workdir",
245
+ context.runRoot,
246
+ "db",
247
+ "diff",
248
+ "--from",
249
+ context.fromDatabaseUrl,
250
+ "--to",
251
+ context.toDatabaseUrl,
252
+ "--schema",
253
+ (context.schemas ?? ["app"]).join(","),
254
+ "--output",
255
+ context.outputPath,
256
+ ];
257
+ if (engineFlag) {
258
+ args.push(engineFlag);
259
+ }
260
+ return {
261
+ args,
262
+ command: await resolveSupabaseBinary(),
263
+ };
264
+ },
265
+ };
266
+ }
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { applyMigrationSql } from "./compare-db.mjs";
5
+
6
+ const specPath = process.argv[2];
7
+ if (!specPath) {
8
+ process.stderr.write("usage: run-workflow.mjs <workflow-spec.json>\n");
9
+ process.exit(64);
10
+ }
11
+ const spec = JSON.parse(await readFile(specPath, "utf8"));
12
+
13
+ const diff = await run(spec.diff);
14
+ if (diff.exitCode !== 0) {
15
+ process.stderr.write(`workflow diff failed (exit ${diff.exitCode})\n`);
16
+ process.exit(diff.exitCode || 1);
17
+ }
18
+
19
+ if (spec.applyDatabaseUrl) {
20
+ const migrationSql = await readFile(spec.migrationPath, "utf8");
21
+ if (migrationSql.trim()) {
22
+ process.stderr.write("workflow: applying migration\n");
23
+ await applyMigrationSql(spec.applyDatabaseUrl, migrationSql);
24
+ }
25
+ }
26
+
27
+ if (spec.genTypes) {
28
+ const genTypes = await run(spec.genTypes);
29
+ if (genTypes.exitCode !== 0) {
30
+ process.stderr.write(`workflow gen types failed (exit ${genTypes.exitCode})\n`);
31
+ process.exit(genTypes.exitCode || 1);
32
+ }
33
+ await writeFile(spec.genTypes.outPath, genTypes.stdout, "utf8");
34
+ }
35
+
36
+ function run(step) {
37
+ return new Promise((resolveRun, rejectRun) => {
38
+ const child = spawn(step.command, step.args, {
39
+ detached: process.platform !== "win32",
40
+ stdio: ["ignore", "pipe", "pipe"],
41
+ });
42
+ const killChild = (signal) => {
43
+ if (process.platform !== "win32" && child.pid) {
44
+ try {
45
+ process.kill(-child.pid, signal);
46
+ return;
47
+ } catch {
48
+ child.kill(signal);
49
+ return;
50
+ }
51
+ }
52
+ child.kill(signal);
53
+ };
54
+ const onTerm = () => killChild("SIGTERM");
55
+ const onInt = () => killChild("SIGINT");
56
+ process.on("SIGTERM", onTerm);
57
+ process.on("SIGINT", onInt);
58
+ let stdout = "";
59
+ child.stdout.setEncoding("utf8");
60
+ child.stdout.on("data", (chunk) => {
61
+ stdout += chunk;
62
+ });
63
+ child.stderr.setEncoding("utf8");
64
+ child.stderr.on("data", (chunk) => {
65
+ process.stderr.write(chunk);
66
+ });
67
+ const finish = (handler) => (value) => {
68
+ process.removeListener("SIGTERM", onTerm);
69
+ process.removeListener("SIGINT", onInt);
70
+ handler(value);
71
+ };
72
+ child.on("error", finish(rejectRun));
73
+ child.on("close", (exitCode) => {
74
+ finish(resolveRun)({ exitCode: exitCode ?? 1, stdout });
75
+ });
76
+ });
77
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ // Install-time notice only: point the consumer at `supaschema init` rather
3
+ // than writing into their project. A package postinstall that writes files
4
+ // is flagged by supply-chain scanners, so scaffolding stays an explicit
5
+ // step. Never prints inside supaschema's own checkout; never fails install.
6
+ import { existsSync } from "node:fs";
7
+ import { dirname, join, resolve, sep } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
11
+ const target = resolve(process.env.INIT_CWD ?? process.cwd());
12
+
13
+ try {
14
+ if (target === packageRoot || target.startsWith(packageRoot + sep)) {
15
+ process.exit(0);
16
+ }
17
+ const existing = ["supaschema.config.json", "supaschema.config.mjs", "supaschema.config.js"];
18
+ if (existing.some((name) => existsSync(join(target, name)))) {
19
+ process.exit(0);
20
+ }
21
+ process.stdout.write(
22
+ "supaschema: run `npx supaschema init` to scaffold supaschema.config.json\n",
23
+ );
24
+ } catch {
25
+ process.exit(0);
26
+ }
package/bin/supaschema ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/cli.js";