@runa-ai/runa-cli 0.8.0 → 0.9.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 (109) hide show
  1. package/dist/{build-HQMSVN6N.js → build-P2A6345N.js} +2 -2
  2. package/dist/{check-PCSQPYDM.js → check-4TZHNOZU.js} +4 -4
  3. package/dist/{chunk-DRSUEMAK.js → chunk-B7C7CLW2.js} +2 -5
  4. package/dist/{chunk-B3POLMII.js → chunk-BQ336L5T.js} +1 -1
  5. package/dist/{chunk-6FAU4IGR.js → chunk-ELXXQIGW.js} +4 -1
  6. package/dist/{chunk-RB2ZUS76.js → chunk-EXR4J2JT.js} +52 -16
  7. package/dist/{chunk-GHQH6UC5.js → chunk-GKBE7EIE.js} +1 -1
  8. package/dist/{chunk-JMJP4A47.js → chunk-GT5DMS5R.js} +20 -2
  9. package/dist/{chunk-3JO6YP3T.js → chunk-IEKYTCYA.js} +1 -1
  10. package/dist/{chunk-WPMR7RQ4.js → chunk-IWVXI5O4.js} +2 -2
  11. package/dist/chunk-KUH3G522.js +72 -0
  12. package/dist/{chunk-VSH3IXDQ.js → chunk-MAFJAA2P.js} +1 -1
  13. package/dist/{chunk-CCKG5R4Y.js → chunk-MILCC3B6.js} +1 -1
  14. package/dist/{chunk-5NKWR4FF.js → chunk-OERS32LW.js} +1 -1
  15. package/dist/{chunk-2QX7T24B.js → chunk-QKGL6Q2S.js} +1 -1
  16. package/dist/{chunk-OBYZDT2E.js → chunk-URWDB7YL.js} +15 -78
  17. package/dist/{chunk-ZYT7OQJB.js → chunk-WGRVAGSR.js} +6 -6
  18. package/dist/chunk-YRNQEJQW.js +9043 -0
  19. package/dist/chunk-ZWDWFMOX.js +1514 -0
  20. package/dist/{ci-ZK3LKYFX.js → ci-S5KSBECX.js} +992 -849
  21. package/dist/{cli-ZY5VRIJA.js → cli-TJZCAMB2.js} +30 -30
  22. package/dist/commands/ci/commands/ci-prod-db-operations.d.ts +12 -17
  23. package/dist/commands/ci/commands/ci-prod-utils.d.ts +7 -0
  24. package/dist/commands/ci/commands/layer4-discovery.d.ts +2 -0
  25. package/dist/commands/ci/machine/actors/db/production-preview.d.ts +4 -3
  26. package/dist/commands/ci/machine/actors/db/sync-schema.d.ts +5 -1
  27. package/dist/commands/ci/machine/actors/test/capabilities.d.ts +2 -13
  28. package/dist/commands/ci/machine/actors/test/index.d.ts +1 -0
  29. package/dist/commands/ci/machine/actors/test/layer-content.d.ts +11 -0
  30. package/dist/commands/ci/machine/commands/ci-pr-internal-profile.d.ts +7 -0
  31. package/dist/commands/ci/machine/commands/ci-step-registry.d.ts +25 -0
  32. package/dist/commands/ci/machine/commands/step-telemetry.d.ts +1 -2
  33. package/dist/commands/ci/machine/contract.d.ts +3 -0
  34. package/dist/commands/ci/machine/guards.d.ts +3 -10
  35. package/dist/commands/ci/machine/helpers.d.ts +1 -1
  36. package/dist/commands/ci/machine/machine-execution-helpers.d.ts +5 -2
  37. package/dist/commands/ci/machine/machine.d.ts +24 -30
  38. package/dist/commands/ci/machine/selectors.d.ts +6 -0
  39. package/dist/commands/ci/machine/types.d.ts +3 -1
  40. package/dist/commands/ci/utils/ci-logging.d.ts +16 -0
  41. package/dist/commands/ci/utils/rls-verification.d.ts +3 -2
  42. package/dist/commands/db/apply/actors/pg-schema-diff-actors.d.ts +1 -0
  43. package/dist/commands/db/apply/contract.d.ts +209 -0
  44. package/dist/commands/db/apply/helpers/fresh-db-handler.d.ts +2 -1
  45. package/dist/commands/db/apply/helpers/index.d.ts +3 -1
  46. package/dist/commands/db/apply/helpers/plan-ast-sql-helpers.d.ts +19 -0
  47. package/dist/commands/db/apply/helpers/plan-ast.d.ts +1 -2
  48. package/dist/commands/db/apply/helpers/plan-check-filter.d.ts +0 -14
  49. package/dist/commands/db/apply/helpers/plan-validator.d.ts +34 -0
  50. package/dist/commands/db/apply/helpers/planner-artifact.d.ts +65 -0
  51. package/dist/commands/db/apply/helpers/retry-logic.d.ts +5 -0
  52. package/dist/commands/db/apply/machine.d.ts +50 -15
  53. package/dist/commands/db/commands/db-apply-error.d.ts +6 -1
  54. package/dist/commands/db/commands/db-apply.d.ts +5 -0
  55. package/dist/commands/db/commands/db-plan.d.ts +3 -0
  56. package/dist/commands/db/commands/db-preview-profile.d.ts +23 -0
  57. package/dist/commands/db/commands/db-preview.d.ts +3 -0
  58. package/dist/commands/db/sync/actors.d.ts +1 -0
  59. package/dist/commands/db/sync/contract.d.ts +16 -0
  60. package/dist/commands/db/sync/guardrail-orchestrator.d.ts +15 -0
  61. package/dist/commands/db/sync/guardrail-reporting.d.ts +12 -0
  62. package/dist/commands/db/sync/index.d.ts +4 -0
  63. package/dist/commands/db/sync/machine.d.ts +18 -13
  64. package/dist/commands/db/sync/schema-guardrail-config-test-support.d.ts +15 -0
  65. package/dist/commands/db/sync/schema-guardrail-config.d.ts +11 -0
  66. package/dist/commands/db/sync/schema-guardrail-ddl-order.d.ts +36 -0
  67. package/dist/commands/db/sync/schema-guardrail-graph-guidance.d.ts +15 -0
  68. package/dist/commands/db/sync/schema-guardrail-graph-metadata.d.ts +41 -0
  69. package/dist/commands/db/sync/schema-guardrail-graph-nodes.d.ts +61 -0
  70. package/dist/commands/db/sync/schema-guardrail-graph-sql-helpers.d.ts +31 -0
  71. package/dist/commands/db/sync/schema-guardrail-graph-types.d.ts +56 -0
  72. package/dist/commands/db/sync/schema-guardrail-graph.d.ts +20 -0
  73. package/dist/commands/db/sync/schema-guardrail-local-blockers.d.ts +7 -0
  74. package/dist/commands/db/sync/schema-guardrail-phases.d.ts +26 -0
  75. package/dist/commands/db/sync/schema-guardrail-production-check.d.ts +23 -0
  76. package/dist/commands/db/sync/schema-guardrail-rewrite.d.ts +46 -0
  77. package/dist/commands/db/sync/schema-guardrail-runtime.d.ts +5 -0
  78. package/dist/commands/db/sync/schema-guardrail-semantic-warnings.d.ts +9 -0
  79. package/dist/commands/db/sync/schema-guardrail-types.d.ts +243 -0
  80. package/dist/commands/db/sync/schema-guardrail.d.ts +10 -0
  81. package/dist/commands/db/utils/declarative-dependency-sql-utils.d.ts +1 -1
  82. package/dist/commands/db/utils/duplicate-function-ownership.d.ts +27 -1
  83. package/dist/commands/db/utils/policy-cross-schema-refs.d.ts +12 -0
  84. package/dist/commands/db/utils/sql-table-extractor.d.ts +6 -0
  85. package/dist/commands/test/commands/layer4-prereqs.d.ts +15 -0
  86. package/dist/{config-loader-GT3HAQ7U.js → config-loader-N5ODNMD5.js} +2 -2
  87. package/dist/db-D2OLJDYW.js +12757 -0
  88. package/dist/{dev-GB5ERUVR.js → dev-LGSMDFJN.js} +7 -6
  89. package/dist/{doctor-ROSWSMLH.js → doctor-GYX73IEW.js} +4 -4
  90. package/dist/{env-WP74UUMO.js → env-KYR6Q7WO.js} +15 -10
  91. package/dist/{env-files-HRNUGZ5O.js → env-files-ONBC47I6.js} +3 -3
  92. package/dist/{hotfix-TOSGTVCW.js → hotfix-RJIAPLAM.js} +4 -4
  93. package/dist/index.js +3 -3
  94. package/dist/{init-35JLDFHI.js → init-2O6ODG5Z.js} +2 -2
  95. package/dist/{inject-test-attrs-XN4I2AOR.js → inject-test-attrs-F5A346UV.js} +3 -3
  96. package/dist/{manifest-EGCAZ4TK.js → manifest-CI4BRWEB.js} +2 -2
  97. package/dist/{observability-CJA5UFIC.js → observability-WNSCJ5FV.js} +2 -2
  98. package/dist/pg-schema-diff-helpers-7377FS2D.js +7 -0
  99. package/dist/{sdk-XK6HQU7S.js → sdk-BTIVPEE5.js} +1 -1
  100. package/dist/{test-V4KQL574.js → test-QCPN6Z47.js} +74 -46
  101. package/dist/{upgrade-7L4JIE4K.js → upgrade-QZKEI3NJ.js} +2 -2
  102. package/dist/utils/db-url-utils.d.ts +4 -77
  103. package/dist/{vuln-check-G6I4YYDC.js → vuln-check-5NUTETPW.js} +1 -1
  104. package/dist/{vuln-checker-CT2AYPIS.js → vuln-checker-UV342N66.js} +1 -1
  105. package/dist/{watch-AL4LCBRM.js → watch-RFVCEQLH.js} +3 -3
  106. package/dist/{workflow-UZIZ2JUS.js → workflow-UOG6ZZMH.js} +3 -3
  107. package/package.json +3 -3
  108. package/dist/chunk-6E2DRXIL.js +0 -452
  109. package/dist/db-EPI2DQYN.js +0 -18275
@@ -0,0 +1,1514 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module';
3
+ import { blankDollarQuotedBodies, stripSqlComments, psqlSyncQuery, parsePostgresUrl, buildPsqlArgs, buildPsqlEnv } from './chunk-A6A7JIRD.js';
4
+ import { init_esm_shims } from './chunk-VRXHCR5K.js';
5
+ import path from 'path';
6
+ import { execFileSync, spawnSync, spawn } from 'child_process';
7
+ import { createCLILogger } from '@runa-ai/runa';
8
+ import { existsSync, readdirSync, readFileSync } from 'fs';
9
+
10
+ createRequire(import.meta.url);
11
+
12
+ // src/commands/db/apply/helpers/pg-schema-diff-helpers.ts
13
+ init_esm_shims();
14
+
15
+ // src/commands/db/utils/declarative-dependency-contract.ts
16
+ init_esm_shims();
17
+
18
+ // src/commands/db/utils/declarative-dependency-collectors.ts
19
+ init_esm_shims();
20
+
21
+ // src/commands/db/utils/declarative-dependency-sql-utils.ts
22
+ init_esm_shims();
23
+
24
+ // src/commands/db/utils/sql-filename-parser.ts
25
+ init_esm_shims();
26
+ var SQL_FILENAME_PATTERN = /^(\d{2,3})_([a-z][a-z0-9_]*)\.sql$/;
27
+ function parseSqlFilename(filename) {
28
+ const match = filename.match(SQL_FILENAME_PATTERN);
29
+ if (!match) return null;
30
+ const prefixStr = match[1];
31
+ const domain = match[2];
32
+ if (!prefixStr || !domain) return null;
33
+ return {
34
+ prefix: Number.parseInt(prefixStr, 10),
35
+ domain,
36
+ raw: filename
37
+ };
38
+ }
39
+
40
+ // src/commands/db/utils/declarative-dependency-sql-utils.ts
41
+ var FUNCTION_DEFINITION_RE = /^\s*CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+(?:(?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_]*))\s*\.\s*)?(?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_]*))/i;
42
+ var QUALIFIED_FUNCTION_CALL_RE = /\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
43
+ var EXECUTE_FUNCTION_RE = /\bEXECUTE\s+FUNCTION\s+([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
44
+ var UNQUALIFIED_FUNCTION_CALL_RE = /\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
45
+ var DEFAULT_UNQUALIFIED_CALL_RE = /\bDEFAULT\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
46
+ var CHECK_UNQUALIFIED_CALL_RE = /\bCHECK\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
47
+ var DYNAMIC_SQL_EXECUTE_RE = /\bEXECUTE\b/g;
48
+ var STATIC_EXECUTE_LITERAL_RE = /\bEXECUTE\s+('(?:[^']|'')*')/gi;
49
+ var DOLLAR_TAG_RE = /\$([A-Za-z_][A-Za-z0-9_]*)?\$/g;
50
+ var PUBLIC_SCHEMA = "public";
51
+ function countNewlines(text) {
52
+ let count = 0;
53
+ for (const char of text) {
54
+ if (char === "\n") count += 1;
55
+ }
56
+ return count;
57
+ }
58
+ function toRelative(targetDir, filePath) {
59
+ return path.relative(targetDir, filePath).replaceAll(path.sep, "/");
60
+ }
61
+ function collectSqlFiles(targetDir, layer, relativeDir) {
62
+ const directory = relativeDir ? path.isAbsolute(relativeDir) ? relativeDir : path.join(targetDir, relativeDir) : path.join(targetDir, "supabase", "schemas", layer);
63
+ if (!existsSync(directory)) {
64
+ return [];
65
+ }
66
+ return readdirSync(directory).filter((file) => file.endsWith(".sql")).sort((a, b) => a.localeCompare(b)).map((file) => {
67
+ const absolutePath = path.join(directory, file);
68
+ return {
69
+ absolutePath,
70
+ relativePath: toRelative(targetDir, absolutePath),
71
+ content: readFileSync(absolutePath, "utf-8")
72
+ };
73
+ });
74
+ }
75
+ function blankQuotedStrings(content) {
76
+ let result = "";
77
+ let index = 0;
78
+ while (index < content.length) {
79
+ const current = content[index] ?? "";
80
+ const next = content[index + 1] ?? "";
81
+ if ((current === "E" || current === "e") && next === "'") {
82
+ result += " ";
83
+ index += 2;
84
+ while (index < content.length) {
85
+ const inner = content[index] ?? "";
86
+ const innerNext = content[index + 1] ?? "";
87
+ if (inner === "\\" && index + 1 < content.length) {
88
+ result += " ";
89
+ index += 2;
90
+ continue;
91
+ }
92
+ if (inner === "'" && innerNext === "'") {
93
+ result += " ";
94
+ index += 2;
95
+ continue;
96
+ }
97
+ result += inner === "\n" ? "\n" : " ";
98
+ index += 1;
99
+ if (inner === "'") break;
100
+ }
101
+ continue;
102
+ }
103
+ if (current === "'") {
104
+ result += " ";
105
+ index += 1;
106
+ while (index < content.length) {
107
+ const inner = content[index] ?? "";
108
+ const innerNext = content[index + 1] ?? "";
109
+ if (inner === "'" && innerNext === "'") {
110
+ result += " ";
111
+ index += 2;
112
+ continue;
113
+ }
114
+ result += inner === "\n" ? "\n" : " ";
115
+ index += 1;
116
+ if (inner === "'") break;
117
+ }
118
+ continue;
119
+ }
120
+ if (current === '"') {
121
+ result += " ";
122
+ index += 1;
123
+ while (index < content.length) {
124
+ const inner = content[index] ?? "";
125
+ const innerNext = content[index + 1] ?? "";
126
+ if (inner === '"' && innerNext === '"') {
127
+ result += " ";
128
+ index += 2;
129
+ continue;
130
+ }
131
+ result += inner === "\n" ? "\n" : " ";
132
+ index += 1;
133
+ if (inner === '"') break;
134
+ }
135
+ continue;
136
+ }
137
+ result += current;
138
+ index += 1;
139
+ }
140
+ return result;
141
+ }
142
+ function sanitizeTopLevelSql(statement) {
143
+ return blankQuotedStrings(blankDollarQuotedBodies(stripSqlComments(statement)));
144
+ }
145
+ function sanitizeExecutableCode(content) {
146
+ return blankQuotedStrings(blankDollarQuotedBodies(stripSqlComments(content)));
147
+ }
148
+ function sanitizeExecutableCodePreserveStrings(content) {
149
+ return blankDollarQuotedBodies(stripSqlComments(content));
150
+ }
151
+ function extractManagedSchemas(files) {
152
+ const schemas = /* @__PURE__ */ new Set([PUBLIC_SCHEMA]);
153
+ for (const file of files) {
154
+ const filename = path.basename(file.relativePath);
155
+ const parsed = parseSqlFilename(filename);
156
+ if (parsed) {
157
+ schemas.add(parsed.domain);
158
+ }
159
+ }
160
+ return schemas;
161
+ }
162
+ function extractDefinitionFromStatement(statement, file, line, layer) {
163
+ const match = statement.match(FUNCTION_DEFINITION_RE);
164
+ if (!match) return null;
165
+ const schema = (match[1] ?? match[2] ?? PUBLIC_SCHEMA).toLowerCase();
166
+ const name = (match[3] ?? match[4] ?? "").toLowerCase();
167
+ if (!name) return null;
168
+ return {
169
+ qualifiedName: `${schema}.${name}`,
170
+ schema,
171
+ name,
172
+ file,
173
+ line,
174
+ layer
175
+ };
176
+ }
177
+ function extractFirstDollarBody(statement, baseLine) {
178
+ DOLLAR_TAG_RE.lastIndex = 0;
179
+ const opening = DOLLAR_TAG_RE.exec(statement);
180
+ if (!opening || opening.index === void 0) {
181
+ return null;
182
+ }
183
+ const delimiter = opening[0];
184
+ const bodyStart = opening.index + delimiter.length;
185
+ const closingIndex = statement.indexOf(delimiter, bodyStart);
186
+ if (closingIndex < 0) {
187
+ return null;
188
+ }
189
+ return {
190
+ body: statement.slice(bodyStart, closingIndex),
191
+ startLine: baseLine + countNewlines(statement.slice(0, bodyStart))
192
+ };
193
+ }
194
+ function shouldIgnoreQualifiedFunctionCall(prefix) {
195
+ const compactPrefix = prefix.replace(/\s+/g, " ").trim().toUpperCase();
196
+ return [
197
+ /\bON FUNCTION$/i,
198
+ /\bCREATE FUNCTION$/i,
199
+ /\bCREATE OR REPLACE FUNCTION$/i,
200
+ /\bALTER FUNCTION$/i,
201
+ /\bDROP FUNCTION$/i,
202
+ /\bCOMMENT ON FUNCTION$/i,
203
+ /\bCREATE TABLE(?: IF NOT EXISTS)?$/i,
204
+ /\bALTER TABLE$/i,
205
+ /\bINSERT INTO$/i,
206
+ /\bUPDATE$/i,
207
+ /\bDELETE FROM$/i,
208
+ /\bFROM$/i,
209
+ /\bJOIN$/i,
210
+ /\bREFERENCES$/i,
211
+ /\bTABLE$/i,
212
+ /\bON$/i
213
+ ].some((pattern) => pattern.test(compactPrefix));
214
+ }
215
+ function decodeSqlStringLiteral(literal) {
216
+ if (!literal.startsWith("'") || !literal.endsWith("'") || literal.length < 2) {
217
+ return null;
218
+ }
219
+ return literal.slice(1, -1).replaceAll("''", "'");
220
+ }
221
+
222
+ // src/commands/db/utils/declarative-dependency-collectors.ts
223
+ var WARNING_CODE_LEVELS = {
224
+ UNQUALIFIED_CUSTOM_FUNCTION_CALL: "medium",
225
+ DYNAMIC_SQL_EXECUTE: "high"
226
+ };
227
+ function collectReferencesFromCode(params) {
228
+ const references = [];
229
+ const seen = /* @__PURE__ */ new Set();
230
+ const sanitized = params.code;
231
+ const pattern = params.pattern ?? QUALIFIED_FUNCTION_CALL_RE;
232
+ pattern.lastIndex = 0;
233
+ for (const match of sanitized.matchAll(pattern)) {
234
+ const schema = match[1]?.toLowerCase();
235
+ const name = match[2]?.toLowerCase();
236
+ const index = match.index ?? 0;
237
+ if (!schema || !name) continue;
238
+ const prefix = sanitized.slice(Math.max(0, index - 80), index);
239
+ if (shouldIgnoreQualifiedFunctionCall(prefix)) continue;
240
+ const line = params.baseLine + countNewlines(sanitized.slice(0, index));
241
+ const qualifiedName = `${schema}.${name}`;
242
+ const dedupeKey = `${params.file}:${line}:${qualifiedName}:${params.source}`;
243
+ if (seen.has(dedupeKey)) continue;
244
+ seen.add(dedupeKey);
245
+ references.push({
246
+ qualifiedName,
247
+ schema,
248
+ name,
249
+ file: params.file,
250
+ line,
251
+ source: params.source
252
+ });
253
+ }
254
+ return references;
255
+ }
256
+ function collectStaticExecuteLiterals(params) {
257
+ const literals = [];
258
+ STATIC_EXECUTE_LITERAL_RE.lastIndex = 0;
259
+ for (const match of params.code.matchAll(STATIC_EXECUTE_LITERAL_RE)) {
260
+ const quotedSql = match[1];
261
+ const index = match.index ?? 0;
262
+ if (!quotedSql) continue;
263
+ const suffix = params.code.slice(index + match[0].length);
264
+ const nextToken = suffix.match(/^\s*([A-Za-z_]+|\|\|)/)?.[1]?.toUpperCase();
265
+ if (nextToken === "||") {
266
+ continue;
267
+ }
268
+ const decodedSql = decodeSqlStringLiteral(quotedSql);
269
+ if (!decodedSql) continue;
270
+ literals.push({
271
+ index,
272
+ line: params.baseLine + countNewlines(params.code.slice(0, index)),
273
+ decodedSql
274
+ });
275
+ }
276
+ return literals;
277
+ }
278
+ function collectReferencesFromStaticExecuteLiterals(params) {
279
+ const references = [];
280
+ const seen = /* @__PURE__ */ new Set();
281
+ for (const literal of collectStaticExecuteLiterals({
282
+ code: params.code,
283
+ baseLine: params.baseLine
284
+ })) {
285
+ for (const reference of collectReferencesFromCode({
286
+ code: literal.decodedSql,
287
+ file: params.file,
288
+ baseLine: literal.line,
289
+ source: params.source
290
+ })) {
291
+ const dedupeKey = `${reference.file}:${reference.line}:${reference.qualifiedName}:${reference.source}`;
292
+ if (seen.has(dedupeKey)) continue;
293
+ seen.add(dedupeKey);
294
+ references.push(reference);
295
+ }
296
+ }
297
+ return references;
298
+ }
299
+ function collectReferencesForStatement(statement, file, line) {
300
+ const sanitizedTopLevel = sanitizeTopLevelSql(statement);
301
+ const trimmed = statement.trimStart().toUpperCase();
302
+ const references = collectReferencesFromCode({
303
+ code: sanitizedTopLevel,
304
+ file,
305
+ baseLine: line,
306
+ source: "statement"
307
+ });
308
+ if (/\bEXECUTE\s+FUNCTION\b/i.test(sanitizedTopLevel)) {
309
+ references.push(
310
+ ...collectReferencesFromCode({
311
+ code: sanitizedTopLevel,
312
+ file,
313
+ baseLine: line,
314
+ source: "statement",
315
+ pattern: EXECUTE_FUNCTION_RE
316
+ })
317
+ );
318
+ }
319
+ const uniqueTopLevelReferences = references.filter((reference, index, list) => {
320
+ return list.findIndex(
321
+ (candidate) => candidate.file === reference.file && candidate.line === reference.line && candidate.qualifiedName === reference.qualifiedName && candidate.source === reference.source
322
+ ) === index;
323
+ });
324
+ const definition = extractDefinitionFromStatement(statement, file, line, "declarative");
325
+ const filteredTopLevel = definition ? uniqueTopLevelReferences.filter(
326
+ (reference) => reference.qualifiedName !== definition.qualifiedName
327
+ ) : uniqueTopLevelReferences;
328
+ const body = extractFirstDollarBody(statement, line);
329
+ if (!body) {
330
+ return filteredTopLevel;
331
+ }
332
+ const source = trimmed.startsWith("DO ") || trimmed.startsWith("DO$$") ? "do-body" : "function-body";
333
+ const rawExecutableBody = sanitizeExecutableCodePreserveStrings(body.body);
334
+ const bodyReferences = collectReferencesFromCode({
335
+ code: sanitizeExecutableCode(body.body),
336
+ file,
337
+ baseLine: body.startLine,
338
+ source
339
+ });
340
+ const staticExecuteReferences = collectReferencesFromStaticExecuteLiterals({
341
+ code: rawExecutableBody,
342
+ file,
343
+ baseLine: body.startLine,
344
+ source
345
+ });
346
+ return [...filteredTopLevel, ...bodyReferences, ...staticExecuteReferences];
347
+ }
348
+ function collectCustomFunctionNames(definitions) {
349
+ return new Set(definitions.map((definition) => definition.name));
350
+ }
351
+ function collectWarningsFromCode(params) {
352
+ const warnings = [];
353
+ const seen = /* @__PURE__ */ new Set();
354
+ const patterns = params.patterns ?? [UNQUALIFIED_FUNCTION_CALL_RE];
355
+ for (const pattern of patterns) {
356
+ pattern.lastIndex = 0;
357
+ for (const match of params.code.matchAll(pattern)) {
358
+ const name = match[1]?.toLowerCase();
359
+ const index = match.index ?? 0;
360
+ if (!name || !params.knownFunctionNames.has(name)) continue;
361
+ const previousChar = params.code[Math.max(0, index - 1)] ?? "";
362
+ if (previousChar === ".") continue;
363
+ const line = params.baseLine + countNewlines(params.code.slice(0, index));
364
+ const dedupeKey = `${params.file}:${line}:${name}:${params.source}:unqualified`;
365
+ if (seen.has(dedupeKey)) continue;
366
+ seen.add(dedupeKey);
367
+ warnings.push({
368
+ code: "UNQUALIFIED_CUSTOM_FUNCTION_CALL",
369
+ level: WARNING_CODE_LEVELS.UNQUALIFIED_CUSTOM_FUNCTION_CALL,
370
+ file: params.file,
371
+ line,
372
+ source: params.source,
373
+ message: `Declarative SQL uses custom function via unqualified call: ${name}(...)`,
374
+ suggestion: "Schema-qualify custom helper calls so declarative dependency resolution does not depend on search_path."
375
+ });
376
+ }
377
+ }
378
+ return warnings;
379
+ }
380
+ function collectDynamicSqlWarnings(params) {
381
+ const warnings = [];
382
+ const seen = /* @__PURE__ */ new Set();
383
+ DYNAMIC_SQL_EXECUTE_RE.lastIndex = 0;
384
+ for (const match of params.code.matchAll(DYNAMIC_SQL_EXECUTE_RE)) {
385
+ const index = match.index ?? 0;
386
+ const line = params.baseLine + countNewlines(params.code.slice(0, index));
387
+ if (params.resolvedExecuteIndexes?.has(index)) continue;
388
+ const dedupeKey = `${params.file}:${line}:${params.source}:execute`;
389
+ if (seen.has(dedupeKey)) continue;
390
+ seen.add(dedupeKey);
391
+ warnings.push({
392
+ code: "DYNAMIC_SQL_EXECUTE",
393
+ level: WARNING_CODE_LEVELS.DYNAMIC_SQL_EXECUTE,
394
+ file: params.file,
395
+ line,
396
+ source: params.source,
397
+ message: "Declarative SQL uses dynamic SQL via EXECUTE; dependency closure cannot be proven statically.",
398
+ suggestion: "Prefer schema-qualified static SQL when possible, or manually review EXECUTE blocks for declarative-only dependency closure."
399
+ });
400
+ }
401
+ return warnings;
402
+ }
403
+ function collectWarningsForStatement(statement, file, line, knownFunctionNames) {
404
+ const warnings = [];
405
+ const seen = /* @__PURE__ */ new Set();
406
+ const sanitizedTopLevel = sanitizeTopLevelSql(statement);
407
+ const trimmed = statement.trimStart().toUpperCase();
408
+ for (const warning of collectWarningsFromCode({
409
+ code: sanitizedTopLevel,
410
+ file,
411
+ baseLine: line,
412
+ source: "statement",
413
+ knownFunctionNames,
414
+ patterns: [DEFAULT_UNQUALIFIED_CALL_RE, CHECK_UNQUALIFIED_CALL_RE]
415
+ })) {
416
+ const key = `${warning.file}:${warning.line}:${warning.code}:${warning.source}:${warning.message}`;
417
+ if (seen.has(key)) continue;
418
+ seen.add(key);
419
+ warnings.push(warning);
420
+ }
421
+ const body = extractFirstDollarBody(statement, line);
422
+ if (!body) {
423
+ return warnings;
424
+ }
425
+ const source = trimmed.startsWith("DO ") || trimmed.startsWith("DO$$") ? "do-body" : "function-body";
426
+ const rawExecutableBody = sanitizeExecutableCodePreserveStrings(body.body);
427
+ const sanitizedBody = sanitizeExecutableCode(body.body);
428
+ const staticExecuteLiterals = collectStaticExecuteLiterals({
429
+ code: rawExecutableBody,
430
+ baseLine: body.startLine
431
+ });
432
+ const resolvedExecuteIndexes = new Set(staticExecuteLiterals.map((literal) => literal.index));
433
+ for (const warning of collectWarningsFromCode({
434
+ code: sanitizedBody,
435
+ file,
436
+ baseLine: body.startLine,
437
+ source,
438
+ knownFunctionNames
439
+ })) {
440
+ const key = `${warning.file}:${warning.line}:${warning.code}:${warning.source}:${warning.message}`;
441
+ if (seen.has(key)) continue;
442
+ seen.add(key);
443
+ warnings.push(warning);
444
+ }
445
+ for (const literal of staticExecuteLiterals) {
446
+ for (const warning of collectWarningsFromCode({
447
+ code: literal.decodedSql,
448
+ file,
449
+ baseLine: literal.line,
450
+ source,
451
+ knownFunctionNames
452
+ })) {
453
+ const key = `${warning.file}:${warning.line}:${warning.code}:${warning.source}:${warning.message}`;
454
+ if (seen.has(key)) continue;
455
+ seen.add(key);
456
+ warnings.push(warning);
457
+ }
458
+ }
459
+ for (const warning of collectDynamicSqlWarnings({
460
+ code: rawExecutableBody,
461
+ file,
462
+ baseLine: body.startLine,
463
+ source,
464
+ resolvedExecuteIndexes
465
+ })) {
466
+ const key = `${warning.file}:${warning.line}:${warning.code}:${warning.source}:${warning.message}`;
467
+ if (seen.has(key)) continue;
468
+ seen.add(key);
469
+ warnings.push(warning);
470
+ }
471
+ return warnings;
472
+ }
473
+
474
+ // src/commands/db/utils/sql-boundary-parser.ts
475
+ init_esm_shims();
476
+ var DOLLAR_QUOTE_PATTERN = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/;
477
+ function countNewlines2(text) {
478
+ let count = 0;
479
+ for (const char of text) {
480
+ if (char === "\n") {
481
+ count += 1;
482
+ }
483
+ }
484
+ return count;
485
+ }
486
+ function appendToCurrent(state, text) {
487
+ if (!text) return;
488
+ if (state.atStatementStart) {
489
+ state.atStatementStart = false;
490
+ state.statementStartLine = state.currentLine;
491
+ }
492
+ state.current += text;
493
+ state.currentLine += countNewlines2(text);
494
+ }
495
+ function finalizeCurrentStatement(state) {
496
+ const statement = state.current.trim();
497
+ if (statement.length > 0) {
498
+ state.statements.push({ statement, line: state.statementStartLine });
499
+ }
500
+ state.current = "";
501
+ state.atStatementStart = true;
502
+ state.statementStartLine = state.currentLine;
503
+ }
504
+ function trySkipLeadingWhitespace(content, state) {
505
+ if (!state.atStatementStart) return false;
506
+ let skipped = false;
507
+ while (state.index < content.length) {
508
+ const char = content[state.index] ?? "";
509
+ if (!char || !/\s/.test(char)) break;
510
+ if (char === "\n") {
511
+ state.currentLine += 1;
512
+ }
513
+ state.index += 1;
514
+ skipped = true;
515
+ }
516
+ return skipped;
517
+ }
518
+ function trySkipLeadingLineComment(content, state) {
519
+ if (!state.atStatementStart) return false;
520
+ if (content[state.index] !== "-" || content[state.index + 1] !== "-") return false;
521
+ state.index += 2;
522
+ while (state.index < content.length) {
523
+ const char = content[state.index] ?? "";
524
+ state.index += 1;
525
+ if (char === "\n") {
526
+ state.currentLine += 1;
527
+ break;
528
+ }
529
+ }
530
+ return true;
531
+ }
532
+ function trySkipLeadingBlockComment(content, state) {
533
+ if (!state.atStatementStart) return false;
534
+ if (content[state.index] !== "/" || content[state.index + 1] !== "*") return false;
535
+ state.index += 2;
536
+ while (state.index + 1 < content.length && !(content[state.index] === "*" && content[state.index + 1] === "/")) {
537
+ if (content[state.index] === "\n") {
538
+ state.currentLine += 1;
539
+ }
540
+ state.index += 1;
541
+ }
542
+ if (state.index + 1 < content.length) {
543
+ state.index += 2;
544
+ }
545
+ return true;
546
+ }
547
+ function tryStartInlineLineComment(content, state) {
548
+ if (content[state.index] !== "-" || content[state.index + 1] !== "-") return false;
549
+ appendToCurrent(state, "--");
550
+ state.mode = "lineComment";
551
+ state.index += 2;
552
+ return true;
553
+ }
554
+ function tryStartInlineBlockComment(content, state) {
555
+ if (content[state.index] !== "/" || content[state.index + 1] !== "*") return false;
556
+ appendToCurrent(state, "/*");
557
+ state.mode = "blockComment";
558
+ state.index += 2;
559
+ return true;
560
+ }
561
+ function parseDollarDelimiterAt(content, index) {
562
+ const match = content.slice(index).match(DOLLAR_QUOTE_PATTERN);
563
+ return match ? match[0] : null;
564
+ }
565
+ function tryStartDollarQuote(content, state) {
566
+ const delimiter = parseDollarDelimiterAt(content, state.index);
567
+ if (!delimiter) return false;
568
+ appendToCurrent(state, delimiter);
569
+ state.mode = "dollarQuote";
570
+ state.dollarDelimiter = delimiter;
571
+ state.index += delimiter.length;
572
+ return true;
573
+ }
574
+ function tryStartSingleQuote(content, state) {
575
+ if (content[state.index] !== "'") return false;
576
+ appendToCurrent(state, "'");
577
+ state.mode = "singleQuote";
578
+ state.index += 1;
579
+ return true;
580
+ }
581
+ function tryStartDoubleQuote(content, state) {
582
+ if (content[state.index] !== '"') return false;
583
+ appendToCurrent(state, '"');
584
+ state.mode = "doubleQuote";
585
+ state.index += 1;
586
+ return true;
587
+ }
588
+ function tryTerminateStatement(content, state) {
589
+ if (content[state.index] !== ";") return false;
590
+ finalizeCurrentStatement(state);
591
+ state.index += 1;
592
+ return true;
593
+ }
594
+ function consumeLineComment(content, state) {
595
+ const char = content[state.index] ?? "";
596
+ appendToCurrent(state, char);
597
+ state.index += 1;
598
+ if (char === "\n") {
599
+ state.mode = "normal";
600
+ }
601
+ }
602
+ function consumeBlockComment(content, state) {
603
+ const char = content[state.index] ?? "";
604
+ const next = content[state.index + 1] ?? "";
605
+ if (char === "*" && next === "/") {
606
+ appendToCurrent(state, "*/");
607
+ state.index += 2;
608
+ state.mode = "normal";
609
+ return;
610
+ }
611
+ appendToCurrent(state, char);
612
+ state.index += 1;
613
+ }
614
+ function consumeSingleQuote(content, state) {
615
+ const char = content[state.index] ?? "";
616
+ const next = content[state.index + 1] ?? "";
617
+ if (char === "'" && next === "'") {
618
+ appendToCurrent(state, "''");
619
+ state.index += 2;
620
+ return;
621
+ }
622
+ appendToCurrent(state, char);
623
+ state.index += 1;
624
+ if (char === "'") {
625
+ state.mode = "normal";
626
+ }
627
+ }
628
+ function consumeDoubleQuote(content, state) {
629
+ const char = content[state.index] ?? "";
630
+ const next = content[state.index + 1] ?? "";
631
+ if (char === '"' && next === '"') {
632
+ appendToCurrent(state, '""');
633
+ state.index += 2;
634
+ return;
635
+ }
636
+ appendToCurrent(state, char);
637
+ state.index += 1;
638
+ if (char === '"') {
639
+ state.mode = "normal";
640
+ }
641
+ }
642
+ function consumeDollarQuote(content, state) {
643
+ if (state.dollarDelimiter && content.startsWith(state.dollarDelimiter, state.index)) {
644
+ appendToCurrent(state, state.dollarDelimiter);
645
+ state.index += state.dollarDelimiter.length;
646
+ state.dollarDelimiter = "";
647
+ state.mode = "normal";
648
+ return;
649
+ }
650
+ const char = content[state.index] ?? "";
651
+ appendToCurrent(state, char);
652
+ state.index += 1;
653
+ }
654
+ function consumeCurrentMode(content, state) {
655
+ if (state.mode === "lineComment") {
656
+ consumeLineComment(content, state);
657
+ return true;
658
+ }
659
+ if (state.mode === "blockComment") {
660
+ consumeBlockComment(content, state);
661
+ return true;
662
+ }
663
+ if (state.mode === "singleQuote") {
664
+ consumeSingleQuote(content, state);
665
+ return true;
666
+ }
667
+ if (state.mode === "doubleQuote") {
668
+ consumeDoubleQuote(content, state);
669
+ return true;
670
+ }
671
+ if (state.mode === "dollarQuote") {
672
+ consumeDollarQuote(content, state);
673
+ return true;
674
+ }
675
+ return false;
676
+ }
677
+ var SPLIT_STEPS = [
678
+ consumeCurrentMode,
679
+ trySkipLeadingWhitespace,
680
+ trySkipLeadingLineComment,
681
+ trySkipLeadingBlockComment,
682
+ tryStartInlineLineComment,
683
+ tryStartInlineBlockComment,
684
+ tryStartDollarQuote,
685
+ tryStartSingleQuote,
686
+ tryStartDoubleQuote,
687
+ tryTerminateStatement
688
+ ];
689
+ function runSplitStep(content, state) {
690
+ for (const step of SPLIT_STEPS) {
691
+ if (step(content, state)) {
692
+ return true;
693
+ }
694
+ }
695
+ return false;
696
+ }
697
+ function splitSqlStatements(content) {
698
+ const state = {
699
+ statements: [],
700
+ current: "",
701
+ mode: "normal",
702
+ dollarDelimiter: "",
703
+ index: 0,
704
+ currentLine: 1,
705
+ statementStartLine: 1,
706
+ atStatementStart: true
707
+ };
708
+ while (state.index < content.length) {
709
+ if (runSplitStep(content, state)) continue;
710
+ appendToCurrent(state, content[state.index] ?? "");
711
+ state.index += 1;
712
+ }
713
+ finalizeCurrentStatement(state);
714
+ return state.statements;
715
+ }
716
+ var KNOWN_DDL_COMMANDS = /* @__PURE__ */ new Set(["CREATE", "ALTER", "DROP", "RENAME", "TRUNCATE", "COMMENT"]);
717
+ var CREATE_NOISE_KEYWORDS = /* @__PURE__ */ new Set([
718
+ "OR",
719
+ "REPLACE",
720
+ "IF",
721
+ "NOT",
722
+ "EXISTS",
723
+ "GLOBAL",
724
+ "TEMP",
725
+ "TEMPORARY",
726
+ "UNLOGGED",
727
+ "UNIQUE",
728
+ "CONCURRENTLY",
729
+ "RECURSIVE"
730
+ ]);
731
+ var ALTER_DROP_NOISE_KEYWORDS = /* @__PURE__ */ new Set([
732
+ "COLUMN",
733
+ "IF",
734
+ "NOT",
735
+ "EXISTS",
736
+ "ONLY",
737
+ "CONCURRENTLY"
738
+ ]);
739
+ var MULTI_TOKEN_DDL_OBJECT_PATTERNS = [
740
+ { tokens: ["MATERIALIZED", "VIEW"], object: "MATERIALIZED_VIEW" },
741
+ { tokens: ["FOREIGN", "TABLE"], object: "FOREIGN_TABLE" },
742
+ { tokens: ["FOREIGN", "DATA", "WRAPPER"], object: "FOREIGN_DATA_WRAPPER" },
743
+ { tokens: ["USER", "MAPPING"], object: "USER_MAPPING" },
744
+ { tokens: ["TEXT", "SEARCH", "CONFIGURATION"], object: "TEXT_SEARCH_CONFIGURATION" },
745
+ { tokens: ["TEXT", "SEARCH", "DICTIONARY"], object: "TEXT_SEARCH_DICTIONARY" },
746
+ { tokens: ["TEXT", "SEARCH", "PARSER"], object: "TEXT_SEARCH_PARSER" },
747
+ { tokens: ["TEXT", "SEARCH", "TEMPLATE"], object: "TEXT_SEARCH_TEMPLATE" },
748
+ { tokens: ["DEFAULT", "PRIVILEGES"], object: "DEFAULT_PRIVILEGES" },
749
+ { tokens: ["OPERATOR", "CLASS"], object: "OPERATOR_CLASS" },
750
+ { tokens: ["OPERATOR", "FAMILY"], object: "OPERATOR_FAMILY" },
751
+ { tokens: ["ACCESS", "METHOD"], object: "ACCESS_METHOD" },
752
+ { tokens: ["EVENT", "TRIGGER"], object: "EVENT_TRIGGER" }
753
+ ];
754
+ var COMMENT_NOISE_KEYWORDS = /* @__PURE__ */ new Set(["ON"]);
755
+ var NON_DDL_MAINTENANCE_PATTERNS = [
756
+ /^\s*(?:ANALYZE|VACUUM|REINDEX|CLUSTER)\b/i,
757
+ /^\s*REFRESH\s+MATERIALIZED\s+VIEW\b/i
758
+ ];
759
+ var NON_SCHEMA_OPERATION_PATTERNS = [
760
+ /^\s*DO\b/i,
761
+ /^\s*ALTER\s+DEFAULT\s+PRIVILEGES\b/i,
762
+ /^\s*EXECUTE\b/i,
763
+ /^\s*(?:INSERT|UPDATE|DELETE|COPY|MERGE|TRUNCATE|VACUUM|ANALYZE|REINDEX|CLUSTER)\b/i,
764
+ /^\s*REFRESH\s+MATERIALIZED\s+VIEW\b/i
765
+ ];
766
+ function isNonDdlMaintenanceStatement(statement) {
767
+ return NON_DDL_MAINTENANCE_PATTERNS.some((pattern) => pattern.test(statement));
768
+ }
769
+ function isNonSchemaOperation(statement) {
770
+ return NON_SCHEMA_OPERATION_PATTERNS.some((pattern) => pattern.test(statement));
771
+ }
772
+ function getTokenAt(tokens, index, offset = 0) {
773
+ return tokens[index + offset] ?? "";
774
+ }
775
+ function advancePastNoiseKeywords(tokens, startIndex, noiseKeywords) {
776
+ let index = Math.max(1, startIndex);
777
+ while (index < tokens.length && noiseKeywords.has(tokens[index])) {
778
+ index += 1;
779
+ }
780
+ return index;
781
+ }
782
+ function matchMultiTokenDdlObject(tokens, startIndex) {
783
+ for (const pattern of MULTI_TOKEN_DDL_OBJECT_PATTERNS) {
784
+ if (pattern.tokens.length > 0 && pattern.tokens.every((value, offset) => getTokenAt(tokens, startIndex, offset) === value)) {
785
+ return pattern.object;
786
+ }
787
+ }
788
+ if (getTokenAt(tokens, startIndex) === "TEXT" && getTokenAt(tokens, startIndex + 1) === "SEARCH") {
789
+ return "TEXT_SEARCH";
790
+ }
791
+ return null;
792
+ }
793
+ function tokenizeForPlacementCheck(statement) {
794
+ return statement.replace(/\s+/g, " ").trim().split(" ").map((token) => token.toUpperCase()).filter((token) => token.length > 0);
795
+ }
796
+ function sanitizeObjectToken(token) {
797
+ return token.replace(/^[^A-Z0-9_]+|[^A-Z0-9_]+$/g, "");
798
+ }
799
+ function extractDdlObject(statement) {
800
+ const tokens = tokenizeForPlacementCheck(statement);
801
+ if (tokens.length < 2) return null;
802
+ const command = tokens[0];
803
+ if (!command || !KNOWN_DDL_COMMANDS.has(command)) return null;
804
+ let index = 1;
805
+ const token = (offset = 0) => getTokenAt(tokens, index, offset);
806
+ const resolveCommandObject = () => {
807
+ const object = matchMultiTokenDdlObject(tokens, index);
808
+ if (object) return object;
809
+ const next = token();
810
+ if (!next) return null;
811
+ return sanitizeObjectToken(next) || null;
812
+ };
813
+ if (command === "CREATE") {
814
+ index = advancePastNoiseKeywords(tokens, index, CREATE_NOISE_KEYWORDS);
815
+ return resolveCommandObject();
816
+ }
817
+ if (command === "ALTER" || command === "DROP" || command === "TRUNCATE" || command === "RENAME") {
818
+ index = advancePastNoiseKeywords(tokens, index, ALTER_DROP_NOISE_KEYWORDS);
819
+ return resolveCommandObject();
820
+ }
821
+ if (command === "COMMENT") {
822
+ index = advancePastNoiseKeywords(tokens, index, COMMENT_NOISE_KEYWORDS);
823
+ return resolveCommandObject();
824
+ }
825
+ return null;
826
+ }
827
+ function isPartitionOfCreateTable(statement) {
828
+ return /\bCREATE\s+TABLE\b.*\bPARTITION\s+OF\b/i.test(statement);
829
+ }
830
+ function shouldReviewUnknownDeclarativeDdl(statement, declarativePreferredObjects, idempotentPreferredObjects) {
831
+ const object = extractDdlObject(statement);
832
+ if (!object) return null;
833
+ if (declarativePreferredObjects.has(object)) return null;
834
+ if (idempotentPreferredObjects.has(object)) return object;
835
+ return object;
836
+ }
837
+ function shouldReviewUnknownIdempotentDdl(statement, idempotentPreferredObjects, declarativePreferredObjects) {
838
+ const object = extractDdlObject(statement);
839
+ if (!object) return null;
840
+ if (idempotentPreferredObjects.has(object)) return null;
841
+ if (declarativePreferredObjects.has(object)) return object;
842
+ if (object === "TABLE" && isPartitionOfCreateTable(statement)) return null;
843
+ return object;
844
+ }
845
+
846
+ // src/commands/db/utils/declarative-dependency-contract.ts
847
+ var DECLARATIVE_CONTRACT_NOTE = "Declarative desired-state build ignores idempotent-only helpers from supabase/schemas/idempotent/*.sql.";
848
+ var DB_APPLY_CHECK_MODE_CONTRACT_NOTE = "Check mode ignores idempotent-only helpers when building declarative desired state.";
849
+ var MAX_DETAILED_DECLARATIVE_WARNINGS = 10;
850
+ var WARNING_CODE_LABELS = {
851
+ UNQUALIFIED_CUSTOM_FUNCTION_CALL: "Unqualified custom helper call",
852
+ DYNAMIC_SQL_EXECUTE: "Dynamic SQL via EXECUTE"
853
+ };
854
+ function collectDefinitions(files, layer) {
855
+ const definitions = [];
856
+ for (const file of files) {
857
+ const statements = splitSqlStatements(file.content);
858
+ for (const statement of statements) {
859
+ const definition = extractDefinitionFromStatement(
860
+ statement.statement,
861
+ file.relativePath,
862
+ statement.line,
863
+ layer
864
+ );
865
+ if (definition) {
866
+ definitions.push(definition);
867
+ }
868
+ }
869
+ }
870
+ return definitions;
871
+ }
872
+ function collectDeclarativeReferences(files) {
873
+ const references = [];
874
+ const seen = /* @__PURE__ */ new Set();
875
+ for (const file of files) {
876
+ const statements = splitSqlStatements(file.content);
877
+ for (const statement of statements) {
878
+ for (const reference of collectReferencesForStatement(
879
+ statement.statement,
880
+ file.relativePath,
881
+ statement.line
882
+ )) {
883
+ const key = `${reference.file}:${reference.line}:${reference.qualifiedName}:${reference.source}`;
884
+ if (seen.has(key)) continue;
885
+ seen.add(key);
886
+ references.push(reference);
887
+ }
888
+ }
889
+ }
890
+ return references;
891
+ }
892
+ function collectDeclarativeWarnings(files, knownFunctionNames) {
893
+ const warnings = [];
894
+ const seen = /* @__PURE__ */ new Set();
895
+ for (const file of files) {
896
+ const statements = splitSqlStatements(file.content);
897
+ for (const statement of statements) {
898
+ for (const warning of collectWarningsForStatement(
899
+ statement.statement,
900
+ file.relativePath,
901
+ statement.line,
902
+ knownFunctionNames
903
+ )) {
904
+ const key = `${warning.file}:${warning.line}:${warning.code}:${warning.source}:${warning.message}`;
905
+ if (seen.has(key)) continue;
906
+ seen.add(key);
907
+ warnings.push(warning);
908
+ }
909
+ }
910
+ }
911
+ return warnings;
912
+ }
913
+ function buildDefinitionMap(definitions) {
914
+ const map = /* @__PURE__ */ new Map();
915
+ for (const definition of definitions) {
916
+ map.set(definition.qualifiedName, definition);
917
+ }
918
+ return map;
919
+ }
920
+ function buildViolations(params) {
921
+ const violations = [];
922
+ const seen = /* @__PURE__ */ new Set();
923
+ for (const reference of params.references) {
924
+ const declarativeDefinition = params.declarativeDefinitions.get(reference.qualifiedName);
925
+ if (declarativeDefinition) continue;
926
+ const idempotentDefinition = params.idempotentDefinitions.get(reference.qualifiedName);
927
+ if (idempotentDefinition) {
928
+ const key2 = `${reference.file}:${reference.line}:${reference.qualifiedName}:idempotent`;
929
+ if (seen.has(key2)) continue;
930
+ seen.add(key2);
931
+ violations.push({
932
+ code: "DECLARATIVE_IDEMPOTENT_ONLY_FUNCTION",
933
+ file: reference.file,
934
+ line: reference.line,
935
+ qualifiedName: reference.qualifiedName,
936
+ source: reference.source,
937
+ message: `Declarative SQL references function defined only in idempotent SQL: ${reference.qualifiedName}`,
938
+ suggestion: "Move the helper into declarative SQL or stop calling it from declarative desired-state SQL.",
939
+ definedIn: {
940
+ file: idempotentDefinition.file,
941
+ line: idempotentDefinition.line,
942
+ layer: idempotentDefinition.layer
943
+ }
944
+ });
945
+ continue;
946
+ }
947
+ if (!params.managedSchemas.has(reference.schema)) continue;
948
+ const key = `${reference.file}:${reference.line}:${reference.qualifiedName}:managed`;
949
+ if (seen.has(key)) continue;
950
+ seen.add(key);
951
+ if (reference.schema === PUBLIC_SCHEMA) {
952
+ violations.push({
953
+ code: "DECLARATIVE_PUBLIC_UNKNOWN_FUNCTION",
954
+ file: reference.file,
955
+ line: reference.line,
956
+ qualifiedName: reference.qualifiedName,
957
+ source: reference.source,
958
+ message: `Declarative SQL references public function not defined in declarative SQL: ${reference.qualifiedName}`,
959
+ suggestion: "Define the helper in declarative SQL or use an unqualified built-in/extension function if this is not schema-owned."
960
+ });
961
+ continue;
962
+ }
963
+ violations.push({
964
+ code: "DECLARATIVE_UNKNOWN_MANAGED_FUNCTION",
965
+ file: reference.file,
966
+ line: reference.line,
967
+ qualifiedName: reference.qualifiedName,
968
+ source: reference.source,
969
+ message: `Declarative SQL references managed-schema function not defined in declarative SQL: ${reference.qualifiedName}`,
970
+ suggestion: "Define the function in declarative SQL before referencing it from schema-owned desired-state SQL."
971
+ });
972
+ }
973
+ return violations;
974
+ }
975
+ function analyzeDeclarativeDependencyContract(targetDir = process.cwd()) {
976
+ const declarativeFiles = collectSqlFiles(targetDir, "declarative");
977
+ const idempotentFiles = collectSqlFiles(targetDir, "idempotent");
978
+ const managedSchemas = extractManagedSchemas(declarativeFiles);
979
+ const declarativeDefinitions = collectDefinitions(declarativeFiles, "declarative");
980
+ const idempotentDefinitions = collectDefinitions(idempotentFiles, "idempotent");
981
+ const knownFunctionNames = collectCustomFunctionNames([
982
+ ...declarativeDefinitions,
983
+ ...idempotentDefinitions
984
+ ]);
985
+ const references = collectDeclarativeReferences(declarativeFiles);
986
+ const warnings = collectDeclarativeWarnings(declarativeFiles, knownFunctionNames);
987
+ const violations = buildViolations({
988
+ references,
989
+ declarativeDefinitions: buildDefinitionMap(declarativeDefinitions),
990
+ idempotentDefinitions: buildDefinitionMap(idempotentDefinitions),
991
+ managedSchemas
992
+ });
993
+ return {
994
+ contractNote: DECLARATIVE_CONTRACT_NOTE,
995
+ managedSchemas: [...managedSchemas].sort((a, b) => a.localeCompare(b)),
996
+ violations,
997
+ warnings,
998
+ references,
999
+ definitions: {
1000
+ declarative: declarativeDefinitions,
1001
+ idempotent: idempotentDefinitions
1002
+ }
1003
+ };
1004
+ }
1005
+ function formatDeclarativeDependencyViolation(violation) {
1006
+ return {
1007
+ summary: `${violation.file}:${violation.line} - ${violation.message}`,
1008
+ callee: `callee: ${violation.qualifiedName}`,
1009
+ definedIn: violation.definedIn ? `defined in ${violation.definedIn.layer}: ${violation.definedIn.file}:${violation.definedIn.line}` : void 0,
1010
+ suggestion: `suggestion: ${violation.suggestion}`
1011
+ };
1012
+ }
1013
+ function formatDeclarativeDependencyWarning(warning) {
1014
+ return {
1015
+ summary: `${warning.file}:${warning.line} - [${warning.level}] ${warning.message}`,
1016
+ suggestion: `suggestion: ${warning.suggestion}`
1017
+ };
1018
+ }
1019
+ function summarizeDeclarativeDependencyWarnings(warnings) {
1020
+ const counts = /* @__PURE__ */ new Map();
1021
+ for (const warning of warnings) {
1022
+ counts.set(warning.code, (counts.get(warning.code) ?? 0) + 1);
1023
+ }
1024
+ return [...counts.entries()].map(([code, count]) => ({
1025
+ code,
1026
+ count,
1027
+ label: WARNING_CODE_LABELS[code]
1028
+ })).sort((left, right) => right.count - left.count || left.code.localeCompare(right.code));
1029
+ }
1030
+
1031
+ // src/commands/db/apply/helpers/pg-schema-diff-patterns.ts
1032
+ init_esm_shims();
1033
+ var STATEMENT_IDX_REGEX = /^-- Statement (?:Idx\.|Index|#)\s*(\d+)/;
1034
+ var HAZARD_REGEX = /^-- (?:Hazard|Warning|Risk)\s+([\w-]+):\s*(.+)/;
1035
+ var EXTENSION_TYPE_MAP = {
1036
+ geometry: "postgis",
1037
+ geography: "postgis",
1038
+ box2d: "postgis",
1039
+ box3d: "postgis",
1040
+ raster: "postgis_raster",
1041
+ vector: "vector",
1042
+ halfvec: "vector",
1043
+ sparsevec: "vector",
1044
+ cube: "cube",
1045
+ citext: "citext",
1046
+ hstore: "hstore",
1047
+ ltree: "ltree"
1048
+ };
1049
+ var HAZARD_HINT_KEYWORDS = ["hazard", "warning", "dangerous", "destructive"];
1050
+ function hasUnparsedHazardHints(stderr) {
1051
+ const lower = stderr.toLowerCase();
1052
+ return HAZARD_HINT_KEYWORDS.some((kw) => lower.includes(kw));
1053
+ }
1054
+
1055
+ // src/commands/db/apply/helpers/pg-schema-diff-version.ts
1056
+ init_esm_shims();
1057
+ var logger = createCLILogger("db:apply:version");
1058
+ var SUPPORTED_PG_SCHEMA_DIFF_VERSIONS = {
1059
+ minMajor: 0,
1060
+ minMinor: 9,
1061
+ maxMajor: 1,
1062
+ maxMinor: 0
1063
+ };
1064
+ function parseVersion(raw) {
1065
+ const match = raw.trim().match(/v?(\d+)\.(\d+)\.(\d+)/);
1066
+ if (!match) return null;
1067
+ return {
1068
+ raw: raw.trim(),
1069
+ major: parseInt(match[1], 10),
1070
+ minor: parseInt(match[2], 10),
1071
+ patch: parseInt(match[3], 10)
1072
+ };
1073
+ }
1074
+ function isVersionSupported(version) {
1075
+ const { minMajor, minMinor, maxMajor, maxMinor } = SUPPORTED_PG_SCHEMA_DIFF_VERSIONS;
1076
+ if (version.major < minMajor || version.major > maxMajor) return false;
1077
+ if (version.major === minMajor && version.minor < minMinor) return false;
1078
+ if (version.major === maxMajor && version.minor > maxMinor) return false;
1079
+ return true;
1080
+ }
1081
+ function detectPgSchemaDiffVersion(binaryPath) {
1082
+ try {
1083
+ const output = execFileSync(binaryPath, ["version"], {
1084
+ encoding: "utf-8",
1085
+ timeout: 5e3
1086
+ }).trim();
1087
+ const parsed = parseVersion(output);
1088
+ if (parsed) return parsed;
1089
+ } catch {
1090
+ }
1091
+ try {
1092
+ const output = execFileSync(binaryPath, ["--version"], {
1093
+ encoding: "utf-8",
1094
+ timeout: 5e3
1095
+ }).trim();
1096
+ return parseVersion(output);
1097
+ } catch {
1098
+ return null;
1099
+ }
1100
+ }
1101
+ function getSupportedRangeLabel() {
1102
+ const { minMajor, minMinor, maxMajor, maxMinor } = SUPPORTED_PG_SCHEMA_DIFF_VERSIONS;
1103
+ return `${minMajor}.${minMinor}.x \u2013 ${maxMajor}.${maxMinor}.x`;
1104
+ }
1105
+ function verifyPgSchemaDiffVersion(binaryPath, options = {}) {
1106
+ const strict = options.strict === true;
1107
+ const version = detectPgSchemaDiffVersion(binaryPath);
1108
+ if (!version) {
1109
+ const message = `Could not detect pg-schema-diff version (--version failed). Expected tested range: ${getSupportedRangeLabel()}.`;
1110
+ if (strict) {
1111
+ throw new Error(`${message} Aborting in strict mode.`);
1112
+ }
1113
+ logger.debug(message);
1114
+ return;
1115
+ }
1116
+ if (!isVersionSupported(version)) {
1117
+ const message = `pg-schema-diff version ${version.raw} is outside tested range ${getSupportedRangeLabel()}. Plan parsing or hazard detection may behave unexpectedly.`;
1118
+ if (strict) {
1119
+ throw new Error(`${message} Aborting in strict mode.`);
1120
+ }
1121
+ logger.warn(message);
1122
+ } else {
1123
+ logger.debug(`pg-schema-diff version ${version.raw} (compatible)`);
1124
+ }
1125
+ }
1126
+
1127
+ // src/commands/db/apply/helpers/pg-schema-diff-helpers.ts
1128
+ var logger2 = createCLILogger("db:apply");
1129
+ var STALE_IDLE_CONNECTION_MIN_AGE_SECONDS = 30;
1130
+ function verifyPgSchemaDiffBinary(options = {}) {
1131
+ const whichResult = spawnSync("which", ["pg-schema-diff"], {
1132
+ encoding: "utf-8",
1133
+ stdio: ["pipe", "pipe", "pipe"]
1134
+ });
1135
+ if (whichResult.status !== 0) {
1136
+ logger2.error("pg-schema-diff not found in PATH");
1137
+ logger2.error(`PATH: ${process.env.PATH?.split(":").slice(0, 10).join(":") || "undefined"}...`);
1138
+ throw new Error(
1139
+ "pg-schema-diff binary not found in PATH. Check that setup-pg-schema-diff action completed successfully."
1140
+ );
1141
+ }
1142
+ const binaryPath = (whichResult.stdout || "").trim();
1143
+ logger2.step(`pg-schema-diff found: ${binaryPath}`);
1144
+ verifyPgSchemaDiffVersion(binaryPath, { strict: options.strictVersion === true });
1145
+ }
1146
+ function isTransientStartupError(errorOutput) {
1147
+ return errorOutput.includes("the database system is starting up") || errorOutput.includes("the database system is shutting down") || errorOutput.includes("could not connect to server") || errorOutput.includes("Connection refused") || errorOutput.includes("server closed the connection unexpectedly");
1148
+ }
1149
+ function isTransientConnectionError(errorOutput) {
1150
+ return errorOutput.includes("terminating connection due to administrator command") || errorOutput.includes("SQLSTATE 57P01") || errorOutput.includes("server closed the connection unexpectedly") || errorOutput.includes("SSL connection has been closed unexpectedly") || errorOutput.includes("connection to server was lost");
1151
+ }
1152
+ var DB_READINESS_MAX_RETRIES = 5;
1153
+ var DB_READINESS_BASE_DELAY_MS = 1e3;
1154
+ var DB_READINESS_MAX_DELAY_MS = 8e3;
1155
+ async function verifyDatabaseConnection(dbUrl, options) {
1156
+ const maxRetries = options?.maxRetries ?? DB_READINESS_MAX_RETRIES;
1157
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1158
+ const connTestResult = psqlSyncQuery({
1159
+ databaseUrl: dbUrl,
1160
+ sql: "SELECT 1",
1161
+ timeout: 3e4
1162
+ });
1163
+ if (connTestResult.status === 0) {
1164
+ if (attempt > 0) {
1165
+ logger2.step(`Database connection verified (after ${attempt} retries)`);
1166
+ } else {
1167
+ logger2.step("Database connection verified");
1168
+ }
1169
+ return;
1170
+ }
1171
+ const stderr = connTestResult.stderr || connTestResult.stdout || "";
1172
+ if (!isTransientStartupError(stderr) || attempt === maxRetries) {
1173
+ logger2.error("Database connection test failed");
1174
+ logger2.error(`Exit code: ${connTestResult.status}`);
1175
+ logger2.error(`Stderr: ${stderr || "(empty)"}`);
1176
+ throw new Error(`Database connection failed: ${stderr || "(no output)"}`);
1177
+ }
1178
+ const delay = Math.min(
1179
+ DB_READINESS_BASE_DELAY_MS * 2 ** attempt + Math.random() * 500,
1180
+ DB_READINESS_MAX_DELAY_MS
1181
+ );
1182
+ logger2.warn(
1183
+ `Database not ready (attempt ${attempt + 1}/${DB_READINESS_MAX_RETRIES}), retrying in ${Math.round(delay)}ms...`
1184
+ );
1185
+ await new Promise((resolve) => setTimeout(resolve, delay));
1186
+ }
1187
+ }
1188
+ var TYPE_TO_EXTENSION = EXTENSION_TYPE_MAP;
1189
+ function detectMissingExtensionType(errorOutput) {
1190
+ const typePattern = /type ["']?(\w+)["']? does not exist/gi;
1191
+ const matches = [...errorOutput.matchAll(typePattern)];
1192
+ const missingTypes = [...new Set(matches.map((m) => m[1].toLowerCase()))];
1193
+ if (missingTypes.length === 0) {
1194
+ return { detected: false, missingTypes: [], suggestedExtensions: [] };
1195
+ }
1196
+ const extensions = /* @__PURE__ */ new Set();
1197
+ for (const type of missingTypes) {
1198
+ const ext = TYPE_TO_EXTENSION[type];
1199
+ if (ext) extensions.add(ext);
1200
+ }
1201
+ return {
1202
+ detected: true,
1203
+ missingTypes,
1204
+ suggestedExtensions: [...extensions]
1205
+ };
1206
+ }
1207
+ function formatExtensionErrorHint(detection) {
1208
+ if (!detection.detected) return "";
1209
+ const lines = [];
1210
+ lines.push("");
1211
+ lines.push("Missing extension type(s) detected:");
1212
+ for (const type of detection.missingTypes) {
1213
+ const ext = TYPE_TO_EXTENSION[type];
1214
+ lines.push(` type "${type}" \u2192 extension: ${ext ?? "unknown"}`);
1215
+ }
1216
+ if (detection.suggestedExtensions.length > 0) {
1217
+ lines.push("");
1218
+ lines.push("Fix 1: Add to supabase/schemas/idempotent/00_extensions.sql:");
1219
+ for (const ext of detection.suggestedExtensions) {
1220
+ lines.push(` CREATE EXTENSION IF NOT EXISTS ${ext};`);
1221
+ }
1222
+ const extList = detection.suggestedExtensions.map((e) => `'${e}'`).join(", ");
1223
+ lines.push("");
1224
+ lines.push("Fix 2: Configure shadow DB in runa.config.ts:");
1225
+ lines.push(` database: { pgSchemaDiff: { shadowDbExtensions: [${extList}] } }`);
1226
+ }
1227
+ return lines.join("\n");
1228
+ }
1229
+ function detectPartitionPrivilegeError(errorOutput) {
1230
+ const pattern = /privileges on partitions:?\s*not implemented/i;
1231
+ const match = errorOutput.match(pattern);
1232
+ return {
1233
+ detected: !!match,
1234
+ errorFragment: match ? match[0] : ""
1235
+ };
1236
+ }
1237
+ function formatPartitionPrivilegeHint(detection) {
1238
+ if (!detection.detected) return "";
1239
+ const lines = [];
1240
+ lines.push("");
1241
+ lines.push("Partition privilege error detected:");
1242
+ lines.push(` pg-schema-diff rejected: "${detection.errorFragment}"`);
1243
+ lines.push("");
1244
+ lines.push("Cause: Non-owner GRANTs exist on partition child tables in the database.");
1245
+ lines.push("pg-schema-diff v1.0.5 does not support privilege diffs on partitions.");
1246
+ lines.push("");
1247
+ lines.push("Fix options:");
1248
+ lines.push(" 1. Update runa CLI: npm install -g @runa-ai/runa-cli@latest");
1249
+ lines.push(" (Auto-cleans partition ACLs before pg-schema-diff)");
1250
+ lines.push(" 2. Manual: Revoke non-owner privileges from partition children:");
1251
+ lines.push(" REVOKE ALL ON <partition_child> FROM <role>;");
1252
+ lines.push(" 3. Define partitions ONLY in idempotent/*.sql (not declarative/*.sql)");
1253
+ return lines.join("\n");
1254
+ }
1255
+ function detectMissingQualifiedFunction(errorOutput) {
1256
+ const patterns = [
1257
+ /function\s+"?([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\([^"\n]*\)"?\s+does not exist/i,
1258
+ /function\s+([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\s*\([^)\n]*\)\s+does not exist/i
1259
+ ];
1260
+ for (const pattern of patterns) {
1261
+ const match = errorOutput.match(pattern);
1262
+ const schema = match?.[1]?.toLowerCase();
1263
+ const name = match?.[2]?.toLowerCase();
1264
+ if (!schema || !name) continue;
1265
+ return {
1266
+ detected: true,
1267
+ qualifiedName: `${schema}.${name}`,
1268
+ signature: `${schema}.${name}(...)`
1269
+ };
1270
+ }
1271
+ return { detected: false };
1272
+ }
1273
+ function buildBoundaryHintTargetDir(schemasDir, targetDir) {
1274
+ if (targetDir) {
1275
+ return targetDir;
1276
+ }
1277
+ const normalizedSchemasDir = path.normalize(schemasDir);
1278
+ const declarativeSuffix = path.normalize(path.join("supabase", "schemas", "declarative"));
1279
+ if (normalizedSchemasDir.endsWith(declarativeSuffix)) {
1280
+ return path.resolve(schemasDir, "..", "..", "..");
1281
+ }
1282
+ return null;
1283
+ }
1284
+ function formatDeclarativeDependencyBoundaryHint(errorOutput, schemasDir, targetDir) {
1285
+ const detection = detectMissingQualifiedFunction(errorOutput);
1286
+ if (!detection.detected || !detection.qualifiedName) {
1287
+ return "";
1288
+ }
1289
+ const resolvedTargetDir = buildBoundaryHintTargetDir(schemasDir, targetDir);
1290
+ if (!resolvedTargetDir) {
1291
+ return "";
1292
+ }
1293
+ const analysis = analyzeDeclarativeDependencyContract(resolvedTargetDir);
1294
+ const matchingViolations = analysis.violations.filter(
1295
+ (violation) => violation.qualifiedName === detection.qualifiedName
1296
+ );
1297
+ if (matchingViolations.length === 0) {
1298
+ return "";
1299
+ }
1300
+ const lines = [];
1301
+ lines.push("");
1302
+ lines.push("Declarative dependency boundary hint:");
1303
+ lines.push(
1304
+ ` pg-schema-diff could not resolve ${detection.signature ?? detection.qualifiedName} while building declarative desired state.`
1305
+ );
1306
+ for (const violation of matchingViolations.slice(0, 3)) {
1307
+ const formatted = formatDeclarativeDependencyViolation(violation);
1308
+ lines.push(` ${formatted.summary}`);
1309
+ lines.push(` ${formatted.callee}`);
1310
+ if (formatted.definedIn) {
1311
+ lines.push(` ${formatted.definedIn}`);
1312
+ }
1313
+ lines.push(` ${formatted.suggestion}`);
1314
+ }
1315
+ if (matchingViolations.length > 3) {
1316
+ lines.push(` ...and ${matchingViolations.length - 3} more caller(s).`);
1317
+ }
1318
+ return lines.join("\n");
1319
+ }
1320
+ function detectDropTableStatements(planOutput) {
1321
+ const tables = [];
1322
+ for (const line of planOutput.split("\n")) {
1323
+ const match = line.trim().match(/^DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:"([^"]+)"|(\w+))\.(?:"([^"]+)"|(\w+))/i);
1324
+ if (match) {
1325
+ tables.push(`${match[1] ?? match[2]}.${match[3] ?? match[4]}`);
1326
+ }
1327
+ }
1328
+ return tables;
1329
+ }
1330
+ function freeConnectionSlotsForPgSchemaDiff(dbUrl, verbose) {
1331
+ try {
1332
+ const result = psqlSyncQuery({
1333
+ databaseUrl: dbUrl,
1334
+ sql: buildIdleConnectionCleanupSql(),
1335
+ timeout: 1e4
1336
+ });
1337
+ if (result.status === 0 && verbose) {
1338
+ const lines = (result.stdout || "").split("\n").filter((l) => l.trim() === "t");
1339
+ logger2.debug(`Freed ${lines.length} idle connection(s) for pg-schema-diff`);
1340
+ }
1341
+ } catch {
1342
+ logger2.warn("Could not free connection slots (non-fatal, pg-schema-diff may still succeed)");
1343
+ }
1344
+ }
1345
+ var CLEANUP_INTERVAL_SECONDS = 5;
1346
+ var CLEANUP_DURATION_SECONDS = 330;
1347
+ function buildIdleConnectionCleanupSql() {
1348
+ return `SELECT pg_terminate_backend(pid)
1349
+ FROM pg_stat_activity
1350
+ WHERE pid <> pg_backend_pid()
1351
+ AND state = 'idle'
1352
+ AND backend_type = 'client backend'
1353
+ AND COALESCE(application_name, '') <> '${PG_SCHEMA_DIFF_APP_NAME}'
1354
+ AND state_change < now() - interval '${STALE_IDLE_CONNECTION_MIN_AGE_SECONDS} seconds'`;
1355
+ }
1356
+ function startConnectionCleanupDaemon(dbUrl, verbose) {
1357
+ try {
1358
+ const conn = parsePostgresUrl(dbUrl);
1359
+ const args = buildPsqlArgs(conn, { onErrorStop: false });
1360
+ const rounds = Math.ceil(CLEANUP_DURATION_SECONDS / CLEANUP_INTERVAL_SECONDS);
1361
+ const terminateSql = buildIdleConnectionCleanupSql().replace(/'/g, "''");
1362
+ const sql = `DO $$ BEGIN FOR i IN 1..${rounds} LOOP PERFORM * FROM (${terminateSql}) AS terminated; PERFORM pg_sleep(${CLEANUP_INTERVAL_SECONDS}); END LOOP; END $$;`;
1363
+ const child = spawn("psql", [...args, "-t", "-c", sql], {
1364
+ stdio: "ignore",
1365
+ env: buildPsqlEnv(conn),
1366
+ detached: true
1367
+ });
1368
+ child.unref();
1369
+ if (verbose) {
1370
+ logger2.debug(
1371
+ `Connection cleanup daemon started (pid=${child.pid}, interval=${CLEANUP_INTERVAL_SECONDS}s)`
1372
+ );
1373
+ }
1374
+ return child;
1375
+ } catch {
1376
+ logger2.warn("Could not start connection cleanup daemon (non-fatal)");
1377
+ return null;
1378
+ }
1379
+ }
1380
+ function stopConnectionCleanupDaemon(child, verbose) {
1381
+ if (!child) return;
1382
+ try {
1383
+ child.kill("SIGTERM");
1384
+ if (verbose) {
1385
+ logger2.debug("Connection cleanup daemon stopped");
1386
+ }
1387
+ } catch {
1388
+ }
1389
+ }
1390
+ var PG_SCHEMA_DIFF_PLAN_TIMEOUT_MS = 3e5;
1391
+ var PG_SCHEMA_DIFF_APPLY_TIMEOUT_MS = 6e5;
1392
+ var PG_SCHEMA_DIFF_APP_NAME = "runa_pg_schema_diff";
1393
+ var PG_SCHEMA_DIFF_PLAN_MAX_RETRIES = 2;
1394
+ var PG_SCHEMA_DIFF_PLAN_RETRY_DELAY_MS = 3e3;
1395
+ function appendApplicationName(dsn, appName) {
1396
+ try {
1397
+ const url = new URL(dsn);
1398
+ url.searchParams.set("application_name", appName);
1399
+ return url.toString();
1400
+ } catch {
1401
+ const separator = dsn.includes("?") ? "&" : "?";
1402
+ return `${dsn}${separator}application_name=${appName}`;
1403
+ }
1404
+ }
1405
+ function buildPgSchemaDiffPlanArgs(dbUrl, schemasDir, includeSchemas, options) {
1406
+ const taggedDsn = appendApplicationName(dbUrl, PG_SCHEMA_DIFF_APP_NAME);
1407
+ const planArgs = [
1408
+ "plan",
1409
+ "--from-dsn",
1410
+ taggedDsn,
1411
+ "--to-dir",
1412
+ schemasDir,
1413
+ "--disable-plan-validation",
1414
+ ...includeSchemas.flatMap((schema) => ["--include-schema", schema])
1415
+ ];
1416
+ if (options?.tempDbDsn) {
1417
+ planArgs.push("--temp-db-dsn", options.tempDbDsn);
1418
+ }
1419
+ return planArgs;
1420
+ }
1421
+ function throwPgSchemaDiffSpawnError(error, signal) {
1422
+ logger2.error("pg-schema-diff failed to spawn:");
1423
+ logger2.error(`Error: ${error.message}`);
1424
+ if (error.code === "ENOENT") {
1425
+ throw new Error("pg-schema-diff binary not found. Ensure it is installed and in PATH.");
1426
+ }
1427
+ if (signal === "SIGTERM") {
1428
+ throw new Error(
1429
+ `pg-schema-diff plan timed out after ${PG_SCHEMA_DIFF_PLAN_TIMEOUT_MS / 1e3}s. This may indicate a connection issue or an extremely large schema diff.`
1430
+ );
1431
+ }
1432
+ throw new Error(`pg-schema-diff spawn failed: ${error.message}`);
1433
+ }
1434
+ function logPgSchemaDiffPlanFailure(status, signal, schemasDir, planStderr, planOutput, targetDir) {
1435
+ logger2.error("pg-schema-diff plan failed:");
1436
+ logger2.error(`Exit code: ${status}`);
1437
+ logger2.error(`Signal: ${signal || "none"}`);
1438
+ logger2.error(`Stderr: ${planStderr || "(empty)"}`);
1439
+ const combinedOutput = planStderr || planOutput;
1440
+ const extensionDetection = detectMissingExtensionType(combinedOutput);
1441
+ if (extensionDetection.detected) {
1442
+ logger2.error(formatExtensionErrorHint(extensionDetection));
1443
+ }
1444
+ const partitionDetection = detectPartitionPrivilegeError(combinedOutput);
1445
+ if (partitionDetection.detected) {
1446
+ logger2.error(formatPartitionPrivilegeHint(partitionDetection));
1447
+ }
1448
+ const boundaryHint = formatDeclarativeDependencyBoundaryHint(
1449
+ combinedOutput,
1450
+ schemasDir,
1451
+ targetDir
1452
+ );
1453
+ if (boundaryHint) {
1454
+ logger2.error(boundaryHint);
1455
+ }
1456
+ }
1457
+ function executePgSchemaDiffPlan(dbUrl, schemasDir, includeSchemas, verbose, options) {
1458
+ const planArgs = buildPgSchemaDiffPlanArgs(dbUrl, schemasDir, includeSchemas, options);
1459
+ if (verbose) {
1460
+ logger2.debug(`pg-schema-diff ${planArgs.join(" ")}`);
1461
+ }
1462
+ for (let attempt = 0; attempt <= PG_SCHEMA_DIFF_PLAN_MAX_RETRIES; attempt++) {
1463
+ if (attempt > 0) {
1464
+ logger2.warn(
1465
+ `Retrying pg-schema-diff plan (attempt ${attempt + 1}/${PG_SCHEMA_DIFF_PLAN_MAX_RETRIES + 1}) after ${PG_SCHEMA_DIFF_PLAN_RETRY_DELAY_MS}ms...`
1466
+ );
1467
+ spawnSync("sleep", [String(PG_SCHEMA_DIFF_PLAN_RETRY_DELAY_MS / 1e3)], {
1468
+ stdio: "ignore"
1469
+ });
1470
+ }
1471
+ const cleanupDaemon = startConnectionCleanupDaemon(dbUrl, verbose);
1472
+ let planResult;
1473
+ try {
1474
+ planResult = spawnSync("pg-schema-diff", planArgs, {
1475
+ encoding: "utf-8",
1476
+ stdio: ["pipe", "pipe", "pipe"],
1477
+ timeout: PG_SCHEMA_DIFF_PLAN_TIMEOUT_MS
1478
+ });
1479
+ } finally {
1480
+ stopConnectionCleanupDaemon(cleanupDaemon, verbose);
1481
+ }
1482
+ const planOutput = planResult.stdout || "";
1483
+ const planStderr = planResult.stderr || "";
1484
+ if (planResult.error) {
1485
+ throwPgSchemaDiffSpawnError(planResult.error, planResult.signal);
1486
+ }
1487
+ if (planResult.status !== 0 && !planOutput.trim()) {
1488
+ if (attempt < PG_SCHEMA_DIFF_PLAN_MAX_RETRIES && isTransientConnectionError(planStderr)) {
1489
+ logger2.warn(
1490
+ `pg-schema-diff plan failed with transient connection error: ${planStderr.split("\n")[0]}`
1491
+ );
1492
+ continue;
1493
+ }
1494
+ logPgSchemaDiffPlanFailure(
1495
+ planResult.status,
1496
+ planResult.signal,
1497
+ schemasDir,
1498
+ planStderr,
1499
+ planOutput,
1500
+ options?.targetDir
1501
+ );
1502
+ throw new Error(
1503
+ `pg-schema-diff plan failed (exit ${planResult.status}): ${planStderr || "(no output)"}`
1504
+ );
1505
+ }
1506
+ if (attempt > 0) {
1507
+ logger2.step(`pg-schema-diff plan succeeded on attempt ${attempt + 1}`);
1508
+ }
1509
+ return { planOutput, planStderr };
1510
+ }
1511
+ throw new Error("pg-schema-diff plan failed after all retries");
1512
+ }
1513
+
1514
+ export { DB_APPLY_CHECK_MODE_CONTRACT_NOTE, FUNCTION_DEFINITION_RE, HAZARD_REGEX, MAX_DETAILED_DECLARATIVE_WARNINGS, PG_SCHEMA_DIFF_APPLY_TIMEOUT_MS, PUBLIC_SCHEMA, STATEMENT_IDX_REGEX, analyzeDeclarativeDependencyContract, blankQuotedStrings, buildIdleConnectionCleanupSql, collectSqlFiles, countNewlines, detectDropTableStatements, detectMissingExtensionType, detectMissingQualifiedFunction, detectPartitionPrivilegeError, detectPgSchemaDiffVersion, executePgSchemaDiffPlan, extractDdlObject, extractFirstDollarBody, formatDeclarativeDependencyBoundaryHint, formatDeclarativeDependencyViolation, formatDeclarativeDependencyWarning, formatExtensionErrorHint, formatPartitionPrivilegeHint, freeConnectionSlotsForPgSchemaDiff, hasUnparsedHazardHints, isNonDdlMaintenanceStatement, isNonSchemaOperation, parseSqlFilename, sanitizeExecutableCode, sanitizeExecutableCodePreserveStrings, shouldReviewUnknownDeclarativeDdl, shouldReviewUnknownIdempotentDdl, splitSqlStatements, startConnectionCleanupDaemon, stopConnectionCleanupDaemon, summarizeDeclarativeDependencyWarnings, verifyDatabaseConnection, verifyPgSchemaDiffBinary };