@runa-ai/runa-cli 0.7.2 → 0.8.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 (41) hide show
  1. package/dist/{chunk-Z7A4BEWF.js → chunk-3JO6YP3T.js} +1 -1
  2. package/dist/chunk-6E2DRXIL.js +452 -0
  3. package/dist/{chunk-PMXE5XOJ.js → chunk-GHQH6UC5.js} +1 -1
  4. package/dist/{chunk-LCK2LGVR.js → chunk-PAWNJA3N.js} +1 -1
  5. package/dist/{chunk-FWMGC5FP.js → chunk-RB2ZUS76.js} +249 -12
  6. package/dist/{chunk-CKRLVEIO.js → chunk-ZYT7OQJB.js} +16 -11
  7. package/dist/{ci-Z4525QW6.js → ci-ZK3LKYFX.js} +305 -429
  8. package/dist/{cli-Q2XIQDRS.js → cli-ZY5VRIJA.js} +13 -13
  9. package/dist/commands/ci/commands/ci-resolvers.d.ts +1 -2
  10. package/dist/commands/ci/machine/actors/setup/pr-common.d.ts +1 -1
  11. package/dist/commands/ci/machine/contract.d.ts +6 -1
  12. package/dist/commands/ci/machine/guards.d.ts +16 -0
  13. package/dist/commands/ci/machine/machine.d.ts +11 -3
  14. package/dist/commands/db/apply/actors/seed-actors.d.ts +1 -0
  15. package/dist/commands/db/apply/contract.d.ts +23 -0
  16. package/dist/commands/db/apply/helpers/fresh-db-handler.d.ts +2 -1
  17. package/dist/commands/db/apply/helpers/hazard-handler.d.ts +19 -8
  18. package/dist/commands/db/apply/helpers/index.d.ts +2 -1
  19. package/dist/commands/db/apply/helpers/no-change-plan.d.ts +2 -0
  20. package/dist/commands/db/apply/helpers/plan-check-filter.d.ts +11 -0
  21. package/dist/commands/db/apply/machine.d.ts +52 -1
  22. package/dist/commands/db/utils/boundary-policy/types.d.ts +2 -0
  23. package/dist/commands/db/utils/duplicate-function-ownership.d.ts +35 -0
  24. package/dist/commands/db/utils/plan-size-guard.d.ts +16 -0
  25. package/dist/commands/db/utils/preflight-checks/duplicate-function-ownership-checks.d.ts +4 -0
  26. package/dist/constants/versions.d.ts +1 -1
  27. package/dist/{db-BPQ2TEQM.js → db-EPI2DQYN.js} +1203 -410
  28. package/dist/{dev-MLRKIP7F.js → dev-GB5ERUVR.js} +1 -1
  29. package/dist/{env-WNHJVLOT.js → env-WP74UUMO.js} +1 -1
  30. package/dist/{hotfix-Z5EGVSMH.js → hotfix-TOSGTVCW.js} +1 -1
  31. package/dist/index.js +3 -3
  32. package/dist/{init-S2ATHLJ6.js → init-35JLDFHI.js} +1 -1
  33. package/dist/{risk-detector-VO5HJR4R.js → risk-detector-S7XQF4I2.js} +1 -1
  34. package/dist/{risk-detector-core-7WZJZ5ZI.js → risk-detector-core-TGFKWHRS.js} +1 -1
  35. package/dist/{risk-detector-plpgsql-ULV7NLDB.js → risk-detector-plpgsql-O32TUR34.js} +103 -5
  36. package/dist/{upgrade-BDUWBRT5.js → upgrade-7L4JIE4K.js} +1 -1
  37. package/dist/{vuln-check-66RXX3TO.js → vuln-check-G6I4YYDC.js} +1 -1
  38. package/dist/{vuln-checker-FFOGOJPT.js → vuln-checker-CT2AYPIS.js} +1 -1
  39. package/dist/{watch-ITYW57SL.js → watch-AL4LCBRM.js} +1 -1
  40. package/package.json +3 -3
  41. package/dist/chunk-4XHZQRRK.js +0 -215
@@ -16,7 +16,7 @@ init_esm_shims();
16
16
 
17
17
  // src/constants/versions.ts
18
18
  init_esm_shims();
19
- var COMPATIBLE_TEMPLATES_VERSION = "0.7.2";
19
+ var COMPATIBLE_TEMPLATES_VERSION = "0.7.3";
20
20
  var TEMPLATES_PACKAGE_NAME = "@r06-dev/runa-templates";
21
21
  var GITHUB_PACKAGES_REGISTRY = "https://npm.pkg.github.com";
22
22
 
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module';
3
+ import { stripSqlComments, blankDollarQuotedBodies, psqlSyncQuery } from './chunk-A6A7JIRD.js';
4
+ import { init_esm_shims } from './chunk-VRXHCR5K.js';
5
+ import { existsSync, readFileSync, readdirSync, lstatSync } from 'fs';
6
+ import { join, basename } from 'path';
7
+ import { execFileSync } from 'child_process';
8
+ import { isIP } from 'net';
9
+
10
+ createRequire(import.meta.url);
11
+
12
+ // src/commands/db/utils/schema-detector.ts
13
+ init_esm_shims();
14
+ var EXCLUDED_SCHEMAS = /* @__PURE__ */ new Set([
15
+ // PostgreSQL system
16
+ "pg_catalog",
17
+ "information_schema",
18
+ "pg_toast",
19
+ // Supabase services
20
+ "auth",
21
+ "storage",
22
+ "realtime",
23
+ "pgsodium",
24
+ "pgsodium_masks",
25
+ // Supabase extensions
26
+ "extensions",
27
+ "graphql",
28
+ "graphql_public",
29
+ "net",
30
+ "vault",
31
+ // Supabase internal
32
+ "supabase_functions",
33
+ "supabase_migrations",
34
+ "pgbouncer",
35
+ "cron"
36
+ ]);
37
+ var IDENTIFIER_PATTERN = "[A-Za-z_][A-Za-z0-9_]*";
38
+ var SQL_IDENTIFIER = `(?:"(?:[^"]|"")*"|${IDENTIFIER_PATTERN})`;
39
+ var CREATE_SCHEMA_PATTERN = new RegExp(
40
+ `^\\s*CREATE\\s+SCHEMA\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${SQL_IDENTIFIER})`,
41
+ "gim"
42
+ );
43
+ function collectSchemaSqlFiles(schemasDir) {
44
+ const entries = readdirSync(schemasDir, { withFileTypes: true });
45
+ const files = [];
46
+ for (const entry of entries) {
47
+ const filePath = join(schemasDir, entry.name);
48
+ if (entry.isDirectory()) {
49
+ files.push(...collectSchemaSqlFiles(filePath));
50
+ } else if (entry.isFile() && filePath.endsWith(".sql")) {
51
+ files.push(filePath);
52
+ }
53
+ }
54
+ return files;
55
+ }
56
+ function unquoteIdentifier(identifier) {
57
+ const trimmed = identifier.trim();
58
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
59
+ return trimmed.slice(1, -1).replace(/""/g, '"');
60
+ }
61
+ return trimmed;
62
+ }
63
+ function detectAppSchemas(schemasDir, verbose) {
64
+ const schemas = /* @__PURE__ */ new Set(["public"]);
65
+ if (!existsSync(schemasDir)) {
66
+ return Array.from(schemas);
67
+ }
68
+ const files = collectSchemaSqlFiles(schemasDir).sort();
69
+ for (const file of files) {
70
+ const content = readFileSync(file, "utf-8");
71
+ const contentWithoutComments = stripSqlComments(content);
72
+ CREATE_SCHEMA_PATTERN.lastIndex = 0;
73
+ const matches = contentWithoutComments.matchAll(CREATE_SCHEMA_PATTERN);
74
+ for (const match of Array.from(matches)) {
75
+ const schemaNameRaw = unquoteIdentifier(match[1] ?? "");
76
+ if (!schemaNameRaw) {
77
+ continue;
78
+ }
79
+ const schemaName = schemaNameRaw.toLowerCase();
80
+ if (!EXCLUDED_SCHEMAS.has(schemaName)) {
81
+ schemas.add(schemaName);
82
+ }
83
+ }
84
+ }
85
+ const result = Array.from(schemas).sort();
86
+ if (verbose) {
87
+ console.log(` \u2192 Detected app schemas: ${result.join(", ")}`);
88
+ }
89
+ return result;
90
+ }
91
+ var VALID_PG_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/;
92
+ function formatSchemasForSql(schemas) {
93
+ return schemas.map((s) => {
94
+ if (!VALID_PG_IDENTIFIER.test(s)) {
95
+ throw new Error(`Invalid schema name "${s}": must be a valid PostgreSQL identifier`);
96
+ }
97
+ return `'${s.replace(/'/g, "''")}'`;
98
+ }).join(", ");
99
+ }
100
+
101
+ // src/utils/db-url-utils.ts
102
+ init_esm_shims();
103
+ function resolveHostToIPv4(hostname) {
104
+ const ipVersion = isIP(hostname);
105
+ if (ipVersion === 4) return hostname;
106
+ if (ipVersion === 6) return null;
107
+ try {
108
+ const output = execFileSync("getent", ["ahostsv4", hostname], {
109
+ encoding: "utf-8",
110
+ timeout: 5e3,
111
+ stdio: ["ignore", "pipe", "ignore"]
112
+ });
113
+ const firstLine = output.split("\n").find((l) => l.trim().length > 0);
114
+ if (!firstLine) return null;
115
+ const ip = firstLine.trim().split(/\s+/u)[0] ?? "";
116
+ return isIP(ip) === 4 ? ip : null;
117
+ } catch {
118
+ return null;
119
+ }
120
+ }
121
+ function normalizeDatabaseUrlForDdl(databaseUrl) {
122
+ try {
123
+ const url = new URL(databaseUrl);
124
+ const port = url.port ? Number(url.port) : void 0;
125
+ if (port === 6543) url.port = "5432";
126
+ if (url.searchParams.has("pgbouncer")) url.searchParams.delete("pgbouncer");
127
+ if (process.env.CI === "true") {
128
+ const ipv4 = resolveHostToIPv4(url.hostname);
129
+ if (ipv4) {
130
+ url.hostname = ipv4;
131
+ }
132
+ }
133
+ return url.toString();
134
+ } catch {
135
+ return databaseUrl;
136
+ }
137
+ }
138
+ function parseBoolish(value) {
139
+ return value === "t" || value === "true" || value === "1";
140
+ }
141
+ function isIPv6Only(hostname) {
142
+ const ipVersion = isIP(hostname);
143
+ if (ipVersion === 4) return false;
144
+ if (ipVersion === 6) return true;
145
+ const ipv4 = resolveHostToIPv4(hostname);
146
+ if (ipv4) return false;
147
+ try {
148
+ const output = execFileSync("getent", ["ahosts", hostname], {
149
+ encoding: "utf-8",
150
+ timeout: 5e3,
151
+ stdio: ["ignore", "pipe", "ignore"]
152
+ });
153
+ return output.trim().length > 0;
154
+ } catch {
155
+ return false;
156
+ }
157
+ }
158
+ function appendIpv4AddonGuidance(error) {
159
+ return `${error}
160
+
161
+ \u{1F4A1} **\u89E3\u6C7A\u7B56**: Supabase IPv4 Add-on \u3092\u6709\u52B9\u5316\u3057\u3066\u304F\u3060\u3055\u3044 ($4/month)
162
+ Supabase Dashboard \u2192 Project Settings \u2192 Add-ons \u2192 IPv4 Add-on
163
+ (GitHub Actions \u306F IPv6 \u3092\u30B5\u30DD\u30FC\u30C8\u3057\u3066\u3044\u307E\u305B\u3093)`;
164
+ }
165
+ function appendConnectionRefusedGuidance(error) {
166
+ return `${error}
167
+
168
+ \u{1F4A1} **\u78BA\u8A8D\u4E8B\u9805**:
169
+ 1. DATABASE_URL \u304C Direct URL (port 5432) \u3092\u4F7F\u7528\u3057\u3066\u3044\u307E\u3059\u304B\uFF1F
170
+ 2. Supabase \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u304C\u7A3C\u50CD\u4E2D\u3067\u3059\u304B\uFF1F`;
171
+ }
172
+ function appendAuthFailureGuidance(error) {
173
+ return `${error}
174
+
175
+ \u{1F4A1} **\u78BA\u8A8D\u4E8B\u9805**:
176
+ 1. DATABASE_URL \u306E\u30D1\u30B9\u30EF\u30FC\u30C9\u304C\u6B63\u3057\u3044\u3067\u3059\u304B\uFF1F
177
+ 2. Supabase Database Settings \u3067\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u30EA\u30BB\u30C3\u30C8\u3067\u304D\u307E\u3059`;
178
+ }
179
+ function isIpv6ConnectionError(error) {
180
+ return error.includes("Network is unreachable") || error.includes("IPv6");
181
+ }
182
+ function hasIpv6AddressPattern(error) {
183
+ return /\([0-9a-f:]+\)/i.test(error);
184
+ }
185
+ function canDetectIpv6OnlyFromUrl(databaseUrl) {
186
+ try {
187
+ const url = new URL(databaseUrl);
188
+ return isIPv6Only(url.hostname);
189
+ } catch {
190
+ return false;
191
+ }
192
+ }
193
+ function shouldShowIpv4AddonGuidance(error, databaseUrl) {
194
+ if (!isIpv6ConnectionError(error)) {
195
+ return false;
196
+ }
197
+ if (databaseUrl && canDetectIpv6OnlyFromUrl(databaseUrl)) {
198
+ return true;
199
+ }
200
+ return hasIpv6AddressPattern(error);
201
+ }
202
+ function enhanceConnectionError(error, databaseUrl) {
203
+ if (shouldShowIpv4AddonGuidance(error, databaseUrl)) {
204
+ return appendIpv4AddonGuidance(error);
205
+ }
206
+ if (error.includes("Connection refused") || error.includes("connection refused")) {
207
+ return appendConnectionRefusedGuidance(error);
208
+ }
209
+ if (error.includes("password authentication failed") || error.includes("FATAL: password")) {
210
+ return appendAuthFailureGuidance(error);
211
+ }
212
+ return error;
213
+ }
214
+
215
+ // src/commands/db/apply/helpers/partition-validator.ts
216
+ init_esm_shims();
217
+ var QUALIFIED_NAME = '(?:(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))\\s*\\.\\s*)?(?:"([^"]+)"|([a-zA-Z_][a-zA-Z0-9_]*))';
218
+ var PARTITION_OF_REGEX = new RegExp(
219
+ `CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?${QUALIFIED_NAME}\\s+PARTITION\\s+OF\\s+${QUALIFIED_NAME}`,
220
+ "gi"
221
+ );
222
+ function extractQualifiedName(quotedSchema, unquotedSchema, quotedTable, unquotedTable) {
223
+ const schema = quotedSchema || unquotedSchema || "";
224
+ const table = quotedTable || unquotedTable || "";
225
+ return schema ? `${schema}.${table}` : table;
226
+ }
227
+ function parseExpectedPartitions(idempotentDir) {
228
+ if (!existsSync(idempotentDir)) return [];
229
+ const files = readdirSync(idempotentDir).filter((f) => f.endsWith(".sql")).sort();
230
+ const partitions = [];
231
+ for (const file of files) {
232
+ const fullPath = join(idempotentDir, file);
233
+ try {
234
+ if (lstatSync(fullPath).isSymbolicLink()) continue;
235
+ } catch {
236
+ continue;
237
+ }
238
+ const raw = readFileSync(fullPath, "utf-8");
239
+ const cleaned = blankDollarQuotedBodies(stripSqlComments(raw));
240
+ for (const match of cleaned.matchAll(PARTITION_OF_REGEX)) {
241
+ const child = extractQualifiedName(match[1], match[2], match[3], match[4]);
242
+ const parent = extractQualifiedName(match[5], match[6], match[7], match[8]);
243
+ partitions.push({ child, parent, sourceFile: basename(file) });
244
+ }
245
+ }
246
+ return partitions;
247
+ }
248
+ function isValidSchemaName(name) {
249
+ return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name);
250
+ }
251
+ function queryActualPartitions(dbUrl, schemas) {
252
+ if (schemas.length === 0) return /* @__PURE__ */ new Map();
253
+ const validSchemas = schemas.filter(isValidSchemaName);
254
+ if (validSchemas.length === 0) return /* @__PURE__ */ new Map();
255
+ const schemaList = validSchemas.map((s) => `'${s}'`).join(", ");
256
+ const sql = `
257
+ SELECT
258
+ child_ns.nspname || '.' || child.relname AS child_name,
259
+ parent_ns.nspname || '.' || parent.relname AS parent_name
260
+ FROM pg_inherits i
261
+ JOIN pg_class child ON child.oid = i.inhrelid
262
+ JOIN pg_namespace child_ns ON child_ns.oid = child.relnamespace
263
+ JOIN pg_class parent ON parent.oid = i.inhparent
264
+ JOIN pg_namespace parent_ns ON parent_ns.oid = parent.relnamespace
265
+ WHERE child.relkind IN ('r', 'p')
266
+ AND child_ns.nspname IN (${schemaList})
267
+ `;
268
+ const result = psqlSyncQuery({ databaseUrl: dbUrl, sql: sql.trim(), timeout: 1e4 });
269
+ const map = /* @__PURE__ */ new Map();
270
+ if (result.status !== 0) return map;
271
+ const lines = (result.stdout || "").split("\n").filter((l) => l.trim());
272
+ for (const line of lines) {
273
+ const parts = line.split("|").map((p) => p.trim());
274
+ if (parts.length === 2 && parts[0] && parts[1]) {
275
+ map.set(parts[0], parts[1]);
276
+ }
277
+ }
278
+ return map;
279
+ }
280
+ function detectPartitionDrift(expected, actual) {
281
+ const missing = expected.filter((ep) => !actual.has(ep.child));
282
+ return { missing };
283
+ }
284
+ function formatPartitionWarnings(drift) {
285
+ return drift.missing.map(
286
+ (ep) => `Missing partition: ${ep.child} (PARTITION OF ${ep.parent}, defined in ${ep.sourceFile})`
287
+ );
288
+ }
289
+
290
+ // src/commands/db/apply/helpers/idempotent-object-registry.ts
291
+ init_esm_shims();
292
+ function resolveIdempotentDir(schemasDir) {
293
+ return schemasDir ? join(schemasDir, "..", "idempotent") : join(process.cwd(), "supabase", "schemas", "idempotent");
294
+ }
295
+ function readIdempotentSqlFiles(idempotentDir) {
296
+ if (!existsSync(idempotentDir)) return null;
297
+ try {
298
+ const files = readdirSync(idempotentDir).filter((f) => f.endsWith(".sql"));
299
+ return files.map((file) => ({
300
+ file,
301
+ content: stripSqlComments(readFileSync(join(idempotentDir, file), "utf-8"))
302
+ }));
303
+ } catch {
304
+ return null;
305
+ }
306
+ }
307
+ function readIdempotentSqlContent(idempotentDir) {
308
+ const files = readIdempotentSqlFiles(idempotentDir);
309
+ if (!files) return null;
310
+ return files.map((f) => f.content).join("\n");
311
+ }
312
+ function extractQualifiedName2(m, schemaIdx1, schemaIdx2, nameIdx1, nameIdx2) {
313
+ const schema = (m[schemaIdx1] ?? m[schemaIdx2] ?? "").toLowerCase();
314
+ const name = (m[nameIdx1] ?? m[nameIdx2] ?? "").toLowerCase();
315
+ return schema ? `${schema}.${name}` : name;
316
+ }
317
+ var cachedIdempotentRoles = null;
318
+ function getIdempotentRoles(schemasDir) {
319
+ if (cachedIdempotentRoles !== null) {
320
+ return cachedIdempotentRoles;
321
+ }
322
+ const roles = [];
323
+ const idempotentDir = resolveIdempotentDir(schemasDir);
324
+ const sqlFiles = readIdempotentSqlFiles(idempotentDir);
325
+ if (!sqlFiles) {
326
+ cachedIdempotentRoles = [];
327
+ return [];
328
+ }
329
+ for (const { content } of sqlFiles) {
330
+ const roleMatches = content.matchAll(/CREATE\s+ROLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)/gi);
331
+ for (const match of roleMatches) {
332
+ if (match[1]) {
333
+ roles.push(match[1].toLowerCase());
334
+ }
335
+ }
336
+ const existsMatches = content.matchAll(/rolname\s*=\s*'(\w+)'/gi);
337
+ for (const match of existsMatches) {
338
+ if (match[1] && !roles.includes(match[1].toLowerCase())) {
339
+ roles.push(match[1].toLowerCase());
340
+ }
341
+ }
342
+ }
343
+ cachedIdempotentRoles = [...new Set(roles)];
344
+ return cachedIdempotentRoles;
345
+ }
346
+ function getIdempotentProtectedTables(schemasDir, configExclusions) {
347
+ const tables = /* @__PURE__ */ new Set();
348
+ const idempotentDir = resolveIdempotentDir(schemasDir);
349
+ const sqlFiles = readIdempotentSqlFiles(idempotentDir);
350
+ if (sqlFiles) {
351
+ for (const { content } of sqlFiles) {
352
+ const matches = content.matchAll(
353
+ /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:"([^"]+)"|(\w+))\.(?:"([^"]+)"|(\w+))/gi
354
+ );
355
+ for (const m of matches) {
356
+ const schema = (m[1] ?? m[2]).toLowerCase();
357
+ const table = (m[3] ?? m[4]).toLowerCase();
358
+ tables.add(`${schema}.${table}`);
359
+ }
360
+ }
361
+ }
362
+ if (configExclusions) {
363
+ for (const pattern of configExclusions) {
364
+ tables.add(pattern.toLowerCase());
365
+ }
366
+ }
367
+ return [...tables];
368
+ }
369
+ function getIdempotentProtectedObjects(schemasDir, configExclusions) {
370
+ const tables = getIdempotentProtectedTables(schemasDir, configExclusions);
371
+ const functions = /* @__PURE__ */ new Set();
372
+ const triggers = /* @__PURE__ */ new Set();
373
+ const views = /* @__PURE__ */ new Set();
374
+ const types = /* @__PURE__ */ new Set();
375
+ const sequences = /* @__PURE__ */ new Set();
376
+ const idempotentDir = resolveIdempotentDir(schemasDir);
377
+ const content = readIdempotentSqlContent(idempotentDir);
378
+ if (!content) {
379
+ return { tables, functions: [], triggers: [], views: [], types: [], sequences: [] };
380
+ }
381
+ const funcRe = /CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+(?:(?:"([^"]+)"|(\w+))\.)?(?:"([^"]+)"|(\w+))\s*\(/gi;
382
+ for (const m of content.matchAll(funcRe)) {
383
+ functions.add(extractQualifiedName2(m, 1, 2, 3, 4));
384
+ }
385
+ const trigRe = /CREATE\s+(?:OR\s+REPLACE\s+)?TRIGGER\s+(?:"([^"]+)"|(\w+))\s+.*?\s+ON\s+(?:(?:"([^"]+)"|(\w+))\.)?(?:"([^"]+)"|(\w+))/gi;
386
+ for (const m of content.matchAll(trigRe)) {
387
+ const trigName = (m[1] ?? m[2] ?? "").toLowerCase();
388
+ const tblSchema = (m[3] ?? m[4] ?? "").toLowerCase();
389
+ const tblName = (m[5] ?? m[6] ?? "").toLowerCase();
390
+ const qualified = tblSchema ? `${tblSchema}.${trigName}` : trigName;
391
+ triggers.add(qualified);
392
+ if (tblSchema) {
393
+ triggers.add(`${tblSchema}.${trigName}_on_${tblName}`);
394
+ }
395
+ }
396
+ const viewRe = /CREATE\s+(?:OR\s+REPLACE\s+)?(?:MATERIALIZED\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(?:"([^"]+)"|(\w+))\.)?(?:"([^"]+)"|(\w+))/gi;
397
+ for (const m of content.matchAll(viewRe)) {
398
+ views.add(extractQualifiedName2(m, 1, 2, 3, 4));
399
+ }
400
+ const typeRe = /CREATE\s+TYPE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(?:"([^"]+)"|(\w+))\.)?(?:"([^"]+)"|(\w+))/gi;
401
+ for (const m of content.matchAll(typeRe)) {
402
+ types.add(extractQualifiedName2(m, 1, 2, 3, 4));
403
+ }
404
+ const seqRe = /CREATE\s+SEQUENCE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(?:"([^"]+)"|(\w+))\.)?(?:"([^"]+)"|(\w+))/gi;
405
+ for (const m of content.matchAll(seqRe)) {
406
+ sequences.add(extractQualifiedName2(m, 1, 2, 3, 4));
407
+ }
408
+ return {
409
+ tables,
410
+ functions: [...functions],
411
+ triggers: [...triggers],
412
+ views: [...views],
413
+ types: [...types],
414
+ sequences: [...sequences]
415
+ };
416
+ }
417
+ function isIdempotentRoleHazard(hazard, schemasDir) {
418
+ if (hazard.type !== "AUTHZ_UPDATE") {
419
+ return false;
420
+ }
421
+ const idempotentRoles = getIdempotentRoles(schemasDir);
422
+ if (idempotentRoles.length === 0) {
423
+ return false;
424
+ }
425
+ const sql = hazard.causingSql?.trim().toLowerCase() || "";
426
+ const isGrantRevoke = /^\s*(?:grant|revoke)\b/i.test(sql);
427
+ if (!isGrantRevoke) {
428
+ return false;
429
+ }
430
+ for (const role of idempotentRoles) {
431
+ const escapedRole = role.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
432
+ const roleRegex = new RegExp(`\\b${escapedRole}\\b`, "i");
433
+ if (roleRegex.test(sql)) {
434
+ return true;
435
+ }
436
+ }
437
+ return false;
438
+ }
439
+ function filterFalsePositiveHazards(hazards, schemasDir) {
440
+ const filtered = [];
441
+ const falsePositives = [];
442
+ for (const hazard of hazards) {
443
+ if (isIdempotentRoleHazard(hazard, schemasDir)) {
444
+ falsePositives.push(hazard);
445
+ } else {
446
+ filtered.push(hazard);
447
+ }
448
+ }
449
+ return { filtered, falsePositives };
450
+ }
451
+
452
+ export { PARTITION_OF_REGEX, detectAppSchemas, detectPartitionDrift, enhanceConnectionError, extractQualifiedName, filterFalsePositiveHazards, formatPartitionWarnings, formatSchemasForSql, getIdempotentProtectedObjects, getIdempotentProtectedTables, getIdempotentRoles, isIdempotentRoleHazard, normalizeDatabaseUrlForDdl, parseBoolish, parseExpectedPartitions, queryActualPartitions, readIdempotentSqlFiles, resolveIdempotentDir };
@@ -6,7 +6,7 @@ createRequire(import.meta.url);
6
6
 
7
7
  // src/version.ts
8
8
  init_esm_shims();
9
- var CLI_VERSION = "0.7.2";
9
+ var CLI_VERSION = "0.8.0";
10
10
  var HAS_ADMIN_COMMAND = false;
11
11
 
12
12
  export { CLI_VERSION, HAS_ADMIN_COMMAND };
@@ -9,7 +9,7 @@ init_esm_shims();
9
9
  var riskDetectorModulePromise = null;
10
10
  async function loadRiskDetectorModule() {
11
11
  if (!riskDetectorModulePromise) {
12
- riskDetectorModulePromise = import('./risk-detector-core-7WZJZ5ZI.js').catch((error) => {
12
+ riskDetectorModulePromise = import('./risk-detector-core-TGFKWHRS.js').catch((error) => {
13
13
  riskDetectorModulePromise = null;
14
14
  throw error;
15
15
  });