@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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
3
  import { generateTablesManifest, writeEnvLocalBridge } from './chunk-OBYZDT2E.js';
4
- import { startAppBackground, waitForAppReady, detectApp } from './chunk-FWMGC5FP.js';
4
+ import { startAppBackground, waitForAppReady, detectApp } from './chunk-RB2ZUS76.js';
5
5
  import './chunk-A6A7JIRD.js';
6
6
  import { manifestActor, supabaseStartActor, envCheckActor, depsInstallActor, detectManifestTask, detectDatabase } from './chunk-VSH3IXDQ.js';
7
7
  import { findRepoRoot } from './chunk-3WDV32GA.js';
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
3
  import { registerCleanup } from './chunk-TYIAD6SB.js';
4
- import { tryResolveDatabaseUrl } from './chunk-CKRLVEIO.js';
4
+ import { tryResolveDatabaseUrl } from './chunk-ZYT7OQJB.js';
5
5
  import { isNonInteractiveEnabled } from './chunk-6Y3LAUGL.js';
6
6
  import './chunk-UHDAYPHH.js';
7
7
  import { loadEnvFiles } from './chunk-WPMR7RQ4.js';
@@ -2,7 +2,7 @@
2
2
  import { createRequire } from 'module';
3
3
  import './chunk-ZZOXM6Q4.js';
4
4
  import { createError } from './chunk-JQXOVCOP.js';
5
- import { tryResolveDatabaseUrl } from './chunk-CKRLVEIO.js';
5
+ import { tryResolveDatabaseUrl } from './chunk-ZYT7OQJB.js';
6
6
  import './chunk-UHDAYPHH.js';
7
7
  import { loadEnvFiles } from './chunk-WPMR7RQ4.js';
8
8
  import { isPathContained } from './chunk-DRSUEMAK.js';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
3
  import { getRequestedCommandNameFromArgv } from './chunk-UWWSAPDR.js';
4
- import { CLI_VERSION } from './chunk-PMXE5XOJ.js';
4
+ import { CLI_VERSION } from './chunk-GHQH6UC5.js';
5
5
  import { init_esm_shims } from './chunk-VRXHCR5K.js';
6
6
  import { realpathSync } from 'fs';
7
7
  import { fileURLToPath } from 'url';
@@ -36,7 +36,7 @@ async function getProgram(options) {
36
36
  };
37
37
  const nextKey = getProgramCacheKey(resolvedOptions);
38
38
  if (!programInstance || programCacheKey !== nextKey) {
39
- const { createProgram } = await import('./cli-Q2XIQDRS.js');
39
+ const { createProgram } = await import('./cli-ZY5VRIJA.js');
40
40
  programInstance = await createProgram(resolvedOptions);
41
41
  programCacheKey = nextKey;
42
42
  }
@@ -60,7 +60,7 @@ async function runCliFromProcessArgv() {
60
60
  return;
61
61
  }
62
62
  const { setupSignalHandlers } = await import('./signal-handler-DO3OANW5.js');
63
- const { executeProgram } = await import('./cli-Q2XIQDRS.js');
63
+ const { executeProgram } = await import('./cli-ZY5VRIJA.js');
64
64
  setupSignalHandlers();
65
65
  const options = getProgramLoadOptions(argv);
66
66
  const program = await getProgram(options);
@@ -2,7 +2,7 @@
2
2
  import { createRequire } from 'module';
3
3
  import { diagnoseInitFailure } from './chunk-AAIE4F2U.js';
4
4
  import { getVercelRootDirectory } from './chunk-MXRWBNIY.js';
5
- import { fetchTemplates } from './chunk-Z7A4BEWF.js';
5
+ import { fetchTemplates } from './chunk-3JO6YP3T.js';
6
6
  import { syncRunaConfigWithVercel } from './chunk-6AALH2ED.js';
7
7
  import './chunk-DRSUEMAK.js';
8
8
  import './chunk-RZLYEO4U.js';
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- export { categorizeRisks, detectSchemaRisks } from './chunk-LCK2LGVR.js';
3
+ export { categorizeRisks, detectSchemaRisks } from './chunk-PAWNJA3N.js';
4
4
  import './chunk-VRXHCR5K.js';
5
5
 
6
6
  createRequire(import.meta.url);
@@ -54,7 +54,7 @@ var UNQUALIFIED_EXTENSION_REFERENCE_PATTERNS = [
54
54
  var plpgsqlModulePromise = null;
55
55
  async function loadPlpgsqlRiskDetectorModule() {
56
56
  if (!plpgsqlModulePromise) {
57
- plpgsqlModulePromise = import('./risk-detector-plpgsql-ULV7NLDB.js').catch((error) => {
57
+ plpgsqlModulePromise = import('./risk-detector-plpgsql-O32TUR34.js').catch((error) => {
58
58
  plpgsqlModulePromise = null;
59
59
  throw error;
60
60
  });
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { splitPlpgsqlStatementsWithOffsets, extractExecuteExpressions, skipWhitespace, parseTopLevelAssignment, extractStaticSqlFromExpression, mergeStringEnvValue, skipIdentifier } from './chunk-Y5ANTCKE.js';
3
+ import { splitPlpgsqlStatementsWithOffsets, parseTopLevelAssignment, extractExecuteExpressions, skipWhitespace, extractStaticSqlFromExpression, mergeStringEnvValue, skipIdentifier } from './chunk-Y5ANTCKE.js';
4
4
  import { stripSqlCommentsPreserveLines, buildLineStarts, lineNumberFromIndex, stripSqlStringsPreserveLines, detectRisksFromContent, stripSqlForPatternMatching } from './chunk-3FDQW524.js';
5
5
  import { init_esm_shims } from './chunk-VRXHCR5K.js';
6
6
 
@@ -537,13 +537,74 @@ function maybeBuildUsingClauseRisk(execute, executeLine) {
537
537
  }
538
538
  };
539
539
  }
540
+ var GUARD_START_PATTERN = /\bIF\s+NOT\s+EXISTS\s*\(/i;
541
+ var PG_CONSTRAINT_PATTERN = /\bpg_constraint\b/i;
542
+ var ADD_CONSTRAINT_PATTERN = /\bADD\s+CONSTRAINT\b/i;
543
+ var END_IF_PATTERN = /\bEND\s+IF\b/i;
544
+ var THEN_PATTERN = /\bTHEN\b/i;
545
+ function detectGuardedDdlLines(body) {
546
+ const guardedLines = /* @__PURE__ */ new Set();
547
+ const lines = body.split("\n");
548
+ const guardedRanges = [];
549
+ for (let i = 0; i < lines.length; i++) {
550
+ if (!GUARD_START_PATTERN.test(lines[i])) continue;
551
+ let foundPgConstraint = false;
552
+ let thenLine = -1;
553
+ const scanEnd = Math.min(i + 20, lines.length);
554
+ for (let j = i; j < scanEnd; j++) {
555
+ if (PG_CONSTRAINT_PATTERN.test(lines[j])) foundPgConstraint = true;
556
+ if (THEN_PATTERN.test(lines[j])) {
557
+ thenLine = j;
558
+ break;
559
+ }
560
+ }
561
+ if (!foundPgConstraint || thenLine < 0) continue;
562
+ const endScan = Math.min(thenLine + 30, lines.length);
563
+ for (let j = thenLine + 1; j < endScan; j++) {
564
+ if (END_IF_PATTERN.test(lines[j])) {
565
+ guardedRanges.push({ thenLine: thenLine + 1, endIfLine: j + 1 });
566
+ break;
567
+ }
568
+ }
569
+ }
570
+ for (let i = 0; i < lines.length; i++) {
571
+ if (!ADD_CONSTRAINT_PATTERN.test(lines[i])) continue;
572
+ const line1 = i + 1;
573
+ for (const range of guardedRanges) {
574
+ if (line1 >= range.thenLine && line1 <= range.endIfLine) {
575
+ guardedLines.add(line1);
576
+ break;
577
+ }
578
+ }
579
+ }
580
+ return guardedLines;
581
+ }
540
582
  function pushBodyStaticRisks(risks, dedupe, body, bodyStartLine) {
541
583
  const searchableBody = stripSqlStringsPreserveLines(body);
542
584
  if (!searchableBody.trim()) return;
543
585
  const bodyLineStarts = buildLineStarts(body);
544
586
  const bodyRisks = detectRisksFromContent(searchableBody, body, bodyLineStarts);
587
+ const guardedDdlLines = detectGuardedDdlLines(body);
545
588
  for (const risk of bodyRisks) {
546
- const line = bodyStartLine + ((risk.line ?? 1) - 1);
589
+ const bodyRelativeLine = risk.line ?? 1;
590
+ const line = bodyStartLine + (bodyRelativeLine - 1);
591
+ if (risk.reasonCode === "MEDIUM_RISK_ADD_UNIQUE" && guardedDdlLines.has(bodyRelativeLine)) {
592
+ const dedupeKey2 = `body:GUARDED_DDL_ADD_CONSTRAINT:${line}`;
593
+ if (dedupe.has(dedupeKey2)) continue;
594
+ dedupe.add(dedupeKey2);
595
+ risks.push({
596
+ ...risk,
597
+ level: "medium",
598
+ line,
599
+ reasonCode: "GUARDED_DDL_ADD_CONSTRAINT",
600
+ evidence: {
601
+ source: "plpgsql body",
602
+ snippet: body.trim().slice(0, 200),
603
+ detail: "ADD CONSTRAINT is guarded by IF NOT EXISTS (pg_constraint check)"
604
+ }
605
+ });
606
+ continue;
607
+ }
547
608
  const dedupeKey = `body:${risk.reasonCode ?? risk.description}:${line}`;
548
609
  if (dedupe.has(dedupeKey)) continue;
549
610
  dedupe.add(dedupeKey);
@@ -574,14 +635,40 @@ function buildUnresolvedExecuteRisk(execute, executeLine, reason) {
574
635
  }
575
636
  };
576
637
  }
577
- function analyzeExecuteStatement(risks, dedupe, content, lineStarts, range, statement, execute, stringEnv) {
638
+ function executeReferencesBoundedVars(expression, conditionallyRemoved) {
639
+ if (conditionallyRemoved.size === 0) return false;
640
+ const normalized = expression.toLowerCase();
641
+ for (const varName of conditionallyRemoved) {
642
+ if (normalized.includes(varName.toLowerCase())) return true;
643
+ }
644
+ return false;
645
+ }
646
+ function buildBoundedDispatchRisk(execute, executeLine, reason) {
647
+ return {
648
+ level: "medium",
649
+ description: `Bounded dynamic SQL dispatch in PL/pgSQL EXECUTE: ${reason}`,
650
+ mitigation: "SQL is constructed from a finite set of branches (IF/CASE). Review each branch for correctness.",
651
+ line: executeLine,
652
+ reasonCode: "PLPGSQL_EXECUTE_BOUNDED_DISPATCH",
653
+ confidence: "medium",
654
+ evidence: {
655
+ source: "extractStaticSqlFromExpression",
656
+ snippet: execute.expression.slice(0, 200),
657
+ detail: "EXECUTE expression uses variables assigned in conditional branches (bounded dispatch)"
658
+ }
659
+ };
660
+ }
661
+ function analyzeExecuteStatement(risks, dedupe, content, lineStarts, range, statement, execute, stringEnv, conditionallyRemoved = /* @__PURE__ */ new Set()) {
578
662
  const executeLine = buildExecuteLine(content, lineStarts, range, statement, execute);
579
663
  const executeKey = buildExecuteKey(range, statement, execute, executeLine);
580
664
  if (dedupe.has(executeKey)) return;
581
665
  dedupe.add(executeKey);
582
666
  const extracted = extractStaticSqlFromExpression(execute.expression, stringEnv);
583
667
  if (extracted.kind !== "static") {
584
- risks.push(buildUnresolvedExecuteRisk(execute, executeLine, extracted.reason));
668
+ const isBoundedDispatch = !execute.hasUsingClause && !execute.hasIntoClause && executeReferencesBoundedVars(execute.expression, conditionallyRemoved);
669
+ risks.push(
670
+ isBoundedDispatch ? buildBoundedDispatchRisk(execute, executeLine, extracted.reason) : buildUnresolvedExecuteRisk(execute, executeLine, extracted.reason)
671
+ );
585
672
  return;
586
673
  }
587
674
  risks.push(buildStaticExecuteRisk(execute, executeLine));
@@ -605,8 +692,18 @@ function analyzeBodyRange(risks, dedupe, content, commentlessContent, lineStarts
605
692
  }
606
693
  pushBodyStaticRisks(risks, dedupe, body, declarationLine);
607
694
  const stringEnv = /* @__PURE__ */ new Map();
695
+ const conditionallyRemoved = /* @__PURE__ */ new Set();
608
696
  for (const statement of splitPlpgsqlStatementsWithOffsets(body)) {
697
+ const beforeKeys = new Set(stringEnv.keys());
609
698
  applyStatementAssignment(statement, stringEnv);
699
+ const assignment = parseTopLevelAssignment(statement.statement);
700
+ if (assignment?.isConditionalContext) {
701
+ for (const key of beforeKeys) {
702
+ if (!stringEnv.has(key)) {
703
+ conditionallyRemoved.add(key);
704
+ }
705
+ }
706
+ }
610
707
  const executeStatements = extractExecuteExpressions(statement.statement);
611
708
  for (const execute of executeStatements) {
612
709
  analyzeExecuteStatement(
@@ -617,7 +714,8 @@ function analyzeBodyRange(risks, dedupe, content, commentlessContent, lineStarts
617
714
  range,
618
715
  statement,
619
716
  execute,
620
- stringEnv
717
+ stringEnv,
718
+ conditionallyRemoved
621
719
  );
622
720
  }
623
721
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { fetchTemplates } from './chunk-Z7A4BEWF.js';
3
+ import { fetchTemplates } from './chunk-3JO6YP3T.js';
4
4
  import { updateRunaConfigSdkVersion } from './chunk-6AALH2ED.js';
5
5
  import './chunk-DRSUEMAK.js';
6
6
  import './chunk-RZLYEO4U.js';
@@ -71,7 +71,7 @@ var vulnCheckCommand = new Command("vuln-check").description("Run comprehensive
71
71
  const logger = createCLILogger("vuln-check");
72
72
  const isJsonMode = getOutputFormatFromEnv() === "json" || options.format === "json";
73
73
  try {
74
- const { VulnChecker } = await import('./vuln-checker-FFOGOJPT.js');
74
+ const { VulnChecker } = await import('./vuln-checker-CT2AYPIS.js');
75
75
  const categoryMap = {
76
76
  code: ["injection", "auth", "crypto"],
77
77
  deps: ["dependency"],
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { CLI_VERSION } from './chunk-PMXE5XOJ.js';
3
+ import { CLI_VERSION } from './chunk-GHQH6UC5.js';
4
4
  import { init_esm_shims } from './chunk-VRXHCR5K.js';
5
5
  import { glob } from 'glob';
6
6
  import { exec } from 'child_process';
@@ -284,7 +284,7 @@ function validateSqlSchema(content, errors, warnings) {
284
284
  var riskDetectorLoader = null;
285
285
  function loadRiskDetectorModule() {
286
286
  if (!riskDetectorLoader) {
287
- riskDetectorLoader = import('./risk-detector-VO5HJR4R.js').then((module) => ({
287
+ riskDetectorLoader = import('./risk-detector-S7XQF4I2.js').then((module) => ({
288
288
  detectSchemaRisks: module.detectSchemaRisks
289
289
  })).catch((error) => {
290
290
  riskDetectorLoader = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runa-ai/runa-cli",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "description": "AI-powered DevOps CLI",
6
6
  "type": "module",
@@ -56,8 +56,8 @@
56
56
  "typescript": "5.9.3",
57
57
  "xstate": "5.28.0",
58
58
  "zod": "4.3.6",
59
- "@runa-ai/runa-xstate-test-plugin": "0.5.58",
60
- "@runa-ai/runa": "0.7.2"
59
+ "@runa-ai/runa": "0.8.0",
60
+ "@runa-ai/runa-xstate-test-plugin": "0.5.58"
61
61
  },
62
62
  "engines": {
63
63
  "node": ">=20.0.0"
@@ -1,215 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createRequire } from 'module';
3
- import { stripSqlComments } from './chunk-A6A7JIRD.js';
4
- import { init_esm_shims } from './chunk-VRXHCR5K.js';
5
- import { existsSync, readFileSync, readdirSync } from 'fs';
6
- import { join } 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
- export { detectAppSchemas, enhanceConnectionError, formatSchemasForSql, normalizeDatabaseUrlForDdl, parseBoolish };