postgresai 0.14.0-dev.69 → 0.14.0-dev.70

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.
@@ -13064,7 +13064,7 @@ var {
13064
13064
  // package.json
13065
13065
  var package_default = {
13066
13066
  name: "postgresai",
13067
- version: "0.14.0-dev.69",
13067
+ version: "0.14.0-dev.70",
13068
13068
  description: "postgres_ai CLI",
13069
13069
  license: "Apache-2.0",
13070
13070
  private: false,
@@ -15887,7 +15887,7 @@ var Result = import_lib.default.Result;
15887
15887
  var TypeOverrides = import_lib.default.TypeOverrides;
15888
15888
  var defaults = import_lib.default.defaults;
15889
15889
  // package.json
15890
- var version = "0.14.0-dev.69";
15890
+ var version = "0.14.0-dev.70";
15891
15891
  var package_default2 = {
15892
15892
  name: "postgresai",
15893
15893
  version,
@@ -24459,6 +24459,397 @@ async function verifyInitSetup(params) {
24459
24459
  }
24460
24460
  }
24461
24461
 
24462
+ // lib/supabase.ts
24463
+ var SUPABASE_API_BASE = "https://api.supabase.com";
24464
+ function isValidProjectRef(ref) {
24465
+ return /^[a-z0-9]{10,30}$/i.test(ref);
24466
+ }
24467
+
24468
+ class SupabaseClient {
24469
+ config;
24470
+ constructor(config2) {
24471
+ if (!config2.projectRef) {
24472
+ throw new Error("Supabase project reference is required");
24473
+ }
24474
+ if (!config2.accessToken) {
24475
+ throw new Error("Supabase access token is required");
24476
+ }
24477
+ if (!isValidProjectRef(config2.projectRef)) {
24478
+ throw new Error(`Invalid Supabase project reference format: "${config2.projectRef}". Expected 10-30 alphanumeric characters.`);
24479
+ }
24480
+ this.config = config2;
24481
+ }
24482
+ async query(sql, readOnly = false) {
24483
+ const url = `${SUPABASE_API_BASE}/v1/projects/${encodeURIComponent(this.config.projectRef)}/database/query`;
24484
+ const response = await fetch(url, {
24485
+ method: "POST",
24486
+ headers: {
24487
+ "Content-Type": "application/json",
24488
+ Authorization: `Bearer ${this.config.accessToken}`
24489
+ },
24490
+ body: JSON.stringify({
24491
+ query: sql,
24492
+ read_only: readOnly
24493
+ })
24494
+ });
24495
+ const body = await response.text();
24496
+ let data;
24497
+ try {
24498
+ data = JSON.parse(body);
24499
+ } catch {
24500
+ throw this.createPgError({
24501
+ message: `Supabase API returned non-JSON response: ${body.slice(0, 200)}`,
24502
+ httpStatus: response.status
24503
+ });
24504
+ }
24505
+ if (!response.ok) {
24506
+ throw this.parseApiError(data, response.status);
24507
+ }
24508
+ if (data && typeof data === "object" && "error" in data && data.error) {
24509
+ throw this.parseApiError(data, response.status);
24510
+ }
24511
+ const rows = Array.isArray(data) ? data : [];
24512
+ return {
24513
+ rows,
24514
+ rowCount: rows.length
24515
+ };
24516
+ }
24517
+ async testConnection() {
24518
+ const result = await this.query("SELECT current_database() as db, version() as version", true);
24519
+ const row = result.rows[0] ?? {};
24520
+ return {
24521
+ database: String(row.db ?? ""),
24522
+ version: String(row.version ?? "")
24523
+ };
24524
+ }
24525
+ async getCurrentDatabase() {
24526
+ const result = await this.query("SELECT current_database() as db", true);
24527
+ const row = result.rows[0] ?? {};
24528
+ return String(row.db ?? "");
24529
+ }
24530
+ parseApiError(data, httpStatus) {
24531
+ if (data && typeof data === "object" && !Array.isArray(data)) {
24532
+ const errObj = "error" in data && data.error ? data.error : data;
24533
+ const pgCode = this.extractPgErrorCode(errObj);
24534
+ const message = this.extractErrorMessage(errObj);
24535
+ const detail = this.extractField(errObj, ["details", "detail"]);
24536
+ const hint = this.extractField(errObj, ["hint"]);
24537
+ return this.createPgError({
24538
+ message,
24539
+ code: pgCode,
24540
+ detail,
24541
+ hint,
24542
+ httpStatus,
24543
+ supabaseErrorCode: typeof errObj === "object" && errObj && "code" in errObj ? String(errObj.code ?? "") : undefined
24544
+ });
24545
+ }
24546
+ return this.createPgError({
24547
+ message: `Supabase API error (HTTP ${httpStatus})`,
24548
+ httpStatus
24549
+ });
24550
+ }
24551
+ extractPgErrorCode(errObj) {
24552
+ if (!errObj || typeof errObj !== "object")
24553
+ return;
24554
+ const obj = errObj;
24555
+ if (typeof obj.code === "string") {
24556
+ const code = obj.code;
24557
+ if (/^\d{5}$/.test(code)) {
24558
+ return code;
24559
+ }
24560
+ return this.mapSupabaseCodeToPg(code);
24561
+ }
24562
+ return;
24563
+ }
24564
+ mapSupabaseCodeToPg(code) {
24565
+ const mapping = {
24566
+ PGRST301: "28000",
24567
+ PGRST302: "28P01",
24568
+ "42501": "42501",
24569
+ PGRST000: "42501",
24570
+ "42601": "42601",
24571
+ "42P01": "42P01",
24572
+ PGRST200: "42P01",
24573
+ "42883": "42883",
24574
+ "08000": "08000",
24575
+ "08003": "08003",
24576
+ "08006": "08006",
24577
+ "42710": "42710"
24578
+ };
24579
+ return mapping[code];
24580
+ }
24581
+ extractErrorMessage(errObj) {
24582
+ if (!errObj || typeof errObj !== "object") {
24583
+ return "Unknown Supabase API error";
24584
+ }
24585
+ const obj = errObj;
24586
+ for (const field of ["message", "error", "msg", "description"]) {
24587
+ if (typeof obj[field] === "string" && obj[field]) {
24588
+ return obj[field];
24589
+ }
24590
+ }
24591
+ if (obj.error && typeof obj.error === "object") {
24592
+ return this.extractErrorMessage(obj.error);
24593
+ }
24594
+ return "Unknown Supabase API error";
24595
+ }
24596
+ extractField(errObj, fieldNames) {
24597
+ if (!errObj || typeof errObj !== "object")
24598
+ return;
24599
+ const obj = errObj;
24600
+ for (const field of fieldNames) {
24601
+ if (typeof obj[field] === "string" && obj[field]) {
24602
+ return obj[field];
24603
+ }
24604
+ }
24605
+ return;
24606
+ }
24607
+ createPgError(opts) {
24608
+ const err = new Error(opts.message);
24609
+ if (opts.code)
24610
+ err.code = opts.code;
24611
+ if (opts.detail)
24612
+ err.detail = opts.detail;
24613
+ if (opts.hint)
24614
+ err.hint = opts.hint;
24615
+ if (opts.httpStatus)
24616
+ err.httpStatus = opts.httpStatus;
24617
+ if (opts.supabaseErrorCode)
24618
+ err.supabaseErrorCode = opts.supabaseErrorCode;
24619
+ return err;
24620
+ }
24621
+ }
24622
+ function resolveSupabaseConfig(opts) {
24623
+ const accessToken = opts.accessToken?.trim() || process.env.SUPABASE_ACCESS_TOKEN?.trim() || "";
24624
+ const projectRef = opts.projectRef?.trim() || process.env.SUPABASE_PROJECT_REF?.trim() || "";
24625
+ if (!accessToken) {
24626
+ throw new Error(`Supabase access token is required.
24627
+ ` + `Provide it via --supabase-access-token or SUPABASE_ACCESS_TOKEN environment variable.
24628
+ ` + "Generate a token at: https://supabase.com/dashboard/account/tokens");
24629
+ }
24630
+ if (!projectRef) {
24631
+ throw new Error(`Supabase project reference is required.
24632
+ ` + `Provide it via --supabase-project-ref or SUPABASE_PROJECT_REF environment variable.
24633
+ ` + "Find your project ref in the Supabase dashboard URL: https://supabase.com/dashboard/project/<ref>");
24634
+ }
24635
+ return { accessToken, projectRef };
24636
+ }
24637
+ function extractProjectRefFromUrl(dbUrl) {
24638
+ try {
24639
+ const url = new URL(dbUrl);
24640
+ const host = url.hostname;
24641
+ const match = host.match(/^(?:db\.)?([^.]+)\.supabase\.co$/i);
24642
+ if (match && match[1]) {
24643
+ return match[1];
24644
+ }
24645
+ if (host.includes("pooler.supabase.com")) {
24646
+ const username = url.username;
24647
+ const userMatch = username.match(/^postgres\.([a-z0-9]+)$/i);
24648
+ if (userMatch && userMatch[1]) {
24649
+ return userMatch[1];
24650
+ }
24651
+ }
24652
+ const poolerMatch = host.match(/^([a-z0-9]+)\.pooler\.supabase\.com$/i);
24653
+ if (poolerMatch && poolerMatch[1] && !poolerMatch[1].startsWith("aws-")) {
24654
+ return poolerMatch[1];
24655
+ }
24656
+ return;
24657
+ } catch {
24658
+ return;
24659
+ }
24660
+ }
24661
+ async function applyInitPlanViaSupabase(params) {
24662
+ const applied = [];
24663
+ const skippedOptional = [];
24664
+ const executeStep = async (step) => {
24665
+ const wrappedSql = `BEGIN;
24666
+ ${step.sql}
24667
+ COMMIT;`;
24668
+ await params.client.query(wrappedSql, false);
24669
+ };
24670
+ for (const step of params.plan.steps.filter((s) => !s.optional)) {
24671
+ try {
24672
+ if (params.verbose) {
24673
+ console.log(`Executing step: ${step.name}`);
24674
+ }
24675
+ await executeStep(step);
24676
+ applied.push(step.name);
24677
+ } catch (e) {
24678
+ const msg = e instanceof Error ? e.message : String(e);
24679
+ const errAny = e;
24680
+ const wrapped = new Error(`Failed at step "${step.name}": ${msg}`);
24681
+ const pgErrorFields = [
24682
+ "code",
24683
+ "detail",
24684
+ "hint",
24685
+ "position",
24686
+ "internalPosition",
24687
+ "internalQuery",
24688
+ "where",
24689
+ "schema",
24690
+ "table",
24691
+ "column",
24692
+ "dataType",
24693
+ "constraint",
24694
+ "file",
24695
+ "line",
24696
+ "routine",
24697
+ "httpStatus",
24698
+ "supabaseErrorCode"
24699
+ ];
24700
+ for (const field of pgErrorFields) {
24701
+ if (errAny[field] !== undefined) {
24702
+ wrapped[field] = errAny[field];
24703
+ }
24704
+ }
24705
+ if (e instanceof Error && e.stack) {
24706
+ wrapped.stack = e.stack;
24707
+ }
24708
+ throw wrapped;
24709
+ }
24710
+ }
24711
+ for (const step of params.plan.steps.filter((s) => s.optional)) {
24712
+ try {
24713
+ if (params.verbose) {
24714
+ console.log(`Executing optional step: ${step.name}`);
24715
+ }
24716
+ await executeStep(step);
24717
+ applied.push(step.name);
24718
+ } catch {
24719
+ skippedOptional.push(step.name);
24720
+ }
24721
+ }
24722
+ return { applied, skippedOptional };
24723
+ }
24724
+ async function verifyInitSetupViaSupabase(params) {
24725
+ const missingRequired = [];
24726
+ const missingOptional = [];
24727
+ const role = params.monitoringUser;
24728
+ const db = params.database;
24729
+ if (!isValidIdentifier(role)) {
24730
+ throw new Error(`Invalid monitoring user name: "${role}". Must be a valid PostgreSQL identifier (letters, digits, underscores, max 63 chars, starting with letter or underscore).`);
24731
+ }
24732
+ const roleRes = await params.client.query(`SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '${escapeLiteral2(role)}'`, true);
24733
+ const roleExists = roleRes.rowCount > 0;
24734
+ if (!roleExists) {
24735
+ missingRequired.push(`role "${role}" does not exist`);
24736
+ return { ok: false, missingRequired, missingOptional };
24737
+ }
24738
+ const connectRes = await params.client.query(`SELECT has_database_privilege('${escapeLiteral2(role)}', '${escapeLiteral2(db)}', 'CONNECT') as ok`, true);
24739
+ if (!connectRes.rows?.[0]?.ok) {
24740
+ missingRequired.push(`CONNECT on database "${db}"`);
24741
+ }
24742
+ const pgMonitorRes = await params.client.query(`SELECT pg_has_role('${escapeLiteral2(role)}', 'pg_monitor', 'member') as ok`, true);
24743
+ if (!pgMonitorRes.rows?.[0]?.ok) {
24744
+ missingRequired.push("membership in role pg_monitor");
24745
+ }
24746
+ const pgIndexRes = await params.client.query(`SELECT has_table_privilege('${escapeLiteral2(role)}', 'pg_catalog.pg_index', 'SELECT') as ok`, true);
24747
+ if (!pgIndexRes.rows?.[0]?.ok) {
24748
+ missingRequired.push("SELECT on pg_catalog.pg_index");
24749
+ }
24750
+ const schemaExistsRes = await params.client.query("SELECT nspname FROM pg_namespace WHERE nspname = 'postgres_ai'", true);
24751
+ if (schemaExistsRes.rowCount === 0) {
24752
+ missingRequired.push("schema postgres_ai exists");
24753
+ } else {
24754
+ const schemaPrivRes = await params.client.query(`SELECT has_schema_privilege('${escapeLiteral2(role)}', 'postgres_ai', 'USAGE') as ok`, true);
24755
+ if (!schemaPrivRes.rows?.[0]?.ok) {
24756
+ missingRequired.push("USAGE on schema postgres_ai");
24757
+ }
24758
+ }
24759
+ const viewExistsRes = await params.client.query("SELECT to_regclass('postgres_ai.pg_statistic') IS NOT NULL as ok", true);
24760
+ if (!viewExistsRes.rows?.[0]?.ok) {
24761
+ missingRequired.push("view postgres_ai.pg_statistic exists");
24762
+ } else {
24763
+ const viewPrivRes = await params.client.query(`SELECT has_table_privilege('${escapeLiteral2(role)}', 'postgres_ai.pg_statistic', 'SELECT') as ok`, true);
24764
+ if (!viewPrivRes.rows?.[0]?.ok) {
24765
+ missingRequired.push("SELECT on view postgres_ai.pg_statistic");
24766
+ }
24767
+ }
24768
+ const publicSchemaExistsRes = await params.client.query("SELECT nspname FROM pg_namespace WHERE nspname = 'public'", true);
24769
+ if (publicSchemaExistsRes.rowCount === 0) {
24770
+ missingRequired.push("schema public exists");
24771
+ } else {
24772
+ const schemaUsageRes = await params.client.query(`SELECT has_schema_privilege('${escapeLiteral2(role)}', 'public', 'USAGE') as ok`, true);
24773
+ if (!schemaUsageRes.rows?.[0]?.ok) {
24774
+ missingRequired.push("USAGE on schema public");
24775
+ }
24776
+ }
24777
+ const rolcfgRes = await params.client.query(`SELECT rolconfig FROM pg_catalog.pg_roles WHERE rolname = '${escapeLiteral2(role)}'`, true);
24778
+ const rolconfig = rolcfgRes.rows?.[0]?.rolconfig;
24779
+ const spLine = Array.isArray(rolconfig) ? rolconfig.find((v) => String(v).startsWith("search_path=")) : undefined;
24780
+ if (typeof spLine !== "string" || !spLine) {
24781
+ missingRequired.push("role search_path is set");
24782
+ } else {
24783
+ const sp = spLine.toLowerCase();
24784
+ if (!sp.includes("postgres_ai") || !sp.includes("public") || !sp.includes("pg_catalog")) {
24785
+ missingRequired.push("role search_path includes postgres_ai, public and pg_catalog");
24786
+ }
24787
+ }
24788
+ const explainFnExistsRes = await params.client.query("SELECT oid FROM pg_proc WHERE proname = 'explain_generic' AND pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'postgres_ai')", true);
24789
+ if (explainFnExistsRes.rowCount === 0) {
24790
+ missingRequired.push("function postgres_ai.explain_generic exists");
24791
+ } else {
24792
+ const explainFnRes = await params.client.query(`SELECT has_function_privilege('${escapeLiteral2(role)}', 'postgres_ai.explain_generic(text, text, text)', 'EXECUTE') as ok`, true);
24793
+ if (!explainFnRes.rows?.[0]?.ok) {
24794
+ missingRequired.push("EXECUTE on postgres_ai.explain_generic(text, text, text)");
24795
+ }
24796
+ }
24797
+ const tableDescribeFnExistsRes = await params.client.query("SELECT oid FROM pg_proc WHERE proname = 'table_describe' AND pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'postgres_ai')", true);
24798
+ if (tableDescribeFnExistsRes.rowCount === 0) {
24799
+ missingRequired.push("function postgres_ai.table_describe exists");
24800
+ } else {
24801
+ const tableDescribeFnRes = await params.client.query(`SELECT has_function_privilege('${escapeLiteral2(role)}', 'postgres_ai.table_describe(text)', 'EXECUTE') as ok`, true);
24802
+ if (!tableDescribeFnRes.rows?.[0]?.ok) {
24803
+ missingRequired.push("EXECUTE on postgres_ai.table_describe(text)");
24804
+ }
24805
+ }
24806
+ if (params.includeOptionalPermissions) {
24807
+ const extRes = await params.client.query("SELECT 1 FROM pg_extension WHERE extname = 'rds_tools'", true);
24808
+ if (extRes.rowCount === 0) {
24809
+ missingOptional.push("extension rds_tools");
24810
+ } else {
24811
+ try {
24812
+ const fnRes = await params.client.query(`SELECT has_function_privilege('${escapeLiteral2(role)}', 'rds_tools.pg_ls_multixactdir()', 'EXECUTE') as ok`, true);
24813
+ if (!fnRes.rows?.[0]?.ok) {
24814
+ missingOptional.push("EXECUTE on rds_tools.pg_ls_multixactdir()");
24815
+ }
24816
+ } catch {
24817
+ missingOptional.push("EXECUTE on rds_tools.pg_ls_multixactdir()");
24818
+ }
24819
+ }
24820
+ const optionalFns = [
24821
+ "pg_catalog.pg_stat_file(text)",
24822
+ "pg_catalog.pg_stat_file(text, boolean)",
24823
+ "pg_catalog.pg_ls_dir(text)",
24824
+ "pg_catalog.pg_ls_dir(text, boolean, boolean)"
24825
+ ];
24826
+ for (const fn of optionalFns) {
24827
+ try {
24828
+ const fnRes = await params.client.query(`SELECT has_function_privilege('${escapeLiteral2(role)}', '${fn}', 'EXECUTE') as ok`, true);
24829
+ if (!fnRes.rows?.[0]?.ok) {
24830
+ missingOptional.push(`EXECUTE on ${fn}`);
24831
+ }
24832
+ } catch {
24833
+ missingOptional.push(`EXECUTE on ${fn}`);
24834
+ }
24835
+ }
24836
+ }
24837
+ return {
24838
+ ok: missingRequired.length === 0,
24839
+ missingRequired,
24840
+ missingOptional
24841
+ };
24842
+ }
24843
+ function isValidIdentifier(name) {
24844
+ return /^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(name);
24845
+ }
24846
+ function escapeLiteral2(value) {
24847
+ if (value.includes("\x00")) {
24848
+ throw new Error("SQL literal cannot contain null bytes");
24849
+ }
24850
+ return value.replace(/'/g, "''");
24851
+ }
24852
+
24462
24853
  // lib/pkce.ts
24463
24854
  import * as crypto from "crypto";
24464
24855
  function generateRandomString(length = 64) {
@@ -26743,7 +27134,7 @@ program2.command("set-default-project <project>").description("store default pro
26743
27134
  writeConfig({ defaultProject: value });
26744
27135
  console.log(`Default project saved: ${value}`);
26745
27136
  });
26746
- program2.command("prepare-db [conn]").description("prepare database for monitoring: create monitoring user, required view(s), and grant permissions (idempotent)").option("--db-url <url>", "PostgreSQL connection URL (admin) to run the setup against (deprecated; pass it as positional arg)").option("-h, --host <host>", "PostgreSQL host (psql-like)").option("-p, --port <port>", "PostgreSQL port (psql-like)").option("-U, --username <username>", "PostgreSQL user (psql-like)").option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)").option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)").option("--monitoring-user <name>", "Monitoring role name to create/update", DEFAULT_MONITORING_USER).option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)").option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false).option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false).option("--reset-password", "Reset monitoring role password only (no other changes)", false).option("--print-sql", "Print SQL plan and exit (no changes applied)", false).option("--print-password", "Print generated monitoring password (DANGEROUS in CI logs)", false).addHelpText("after", [
27137
+ program2.command("prepare-db [conn]").description("prepare database for monitoring: create monitoring user, required view(s), and grant permissions (idempotent)").option("--db-url <url>", "PostgreSQL connection URL (admin) to run the setup against (deprecated; pass it as positional arg)").option("-h, --host <host>", "PostgreSQL host (psql-like)").option("-p, --port <port>", "PostgreSQL port (psql-like)").option("-U, --username <username>", "PostgreSQL user (psql-like)").option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)").option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)").option("--monitoring-user <name>", "Monitoring role name to create/update", DEFAULT_MONITORING_USER).option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)").option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false).option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false).option("--reset-password", "Reset monitoring role password only (no other changes)", false).option("--print-sql", "Print SQL plan and exit (no changes applied)", false).option("--print-password", "Print generated monitoring password (DANGEROUS in CI logs)", false).option("--supabase", "Use Supabase Management API instead of direct PostgreSQL connection", false).option("--supabase-access-token <token>", "Supabase Management API access token (or SUPABASE_ACCESS_TOKEN env)").option("--supabase-project-ref <ref>", "Supabase project reference (or SUPABASE_PROJECT_REF env)").option("--json", "Output result as JSON (machine-readable)", false).addHelpText("after", [
26747
27138
  "",
26748
27139
  "Examples:",
26749
27140
  " postgresai prepare-db postgresql://admin@host:5432/dbname",
@@ -26779,17 +27170,48 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
26779
27170
  " postgresai prepare-db <conn> --reset-password --password '...'",
26780
27171
  "",
26781
27172
  "Offline SQL plan (no DB connection):",
26782
- " postgresai prepare-db --print-sql"
27173
+ " postgresai prepare-db --print-sql",
27174
+ "",
27175
+ "Supabase mode (use Management API instead of direct connection):",
27176
+ " postgresai prepare-db --supabase --supabase-project-ref <ref>",
27177
+ " SUPABASE_ACCESS_TOKEN=... postgresai prepare-db --supabase --supabase-project-ref <ref>",
27178
+ "",
27179
+ " Generate a token at: https://supabase.com/dashboard/account/tokens",
27180
+ " Find your project ref in: https://supabase.com/dashboard/project/<ref>"
26783
27181
  ].join(`
26784
27182
  `)).action(async (conn, opts, cmd) => {
26785
- if (opts.verify && opts.resetPassword) {
26786
- console.error("\u2717 Provide only one of --verify or --reset-password");
27183
+ const jsonOutput = opts.json;
27184
+ const outputJson = (data) => {
27185
+ console.log(JSON.stringify(data, null, 2));
27186
+ };
27187
+ const outputError = (error2) => {
27188
+ if (jsonOutput) {
27189
+ outputJson({
27190
+ success: false,
27191
+ mode: opts.supabase ? "supabase" : "direct",
27192
+ error: error2
27193
+ });
27194
+ } else {
27195
+ console.error(`Error: prepare-db${opts.supabase ? " (Supabase)" : ""}: ${error2.message}`);
27196
+ if (error2.step)
27197
+ console.error(` Step: ${error2.step}`);
27198
+ if (error2.code)
27199
+ console.error(` Code: ${error2.code}`);
27200
+ if (error2.detail)
27201
+ console.error(` Detail: ${error2.detail}`);
27202
+ if (error2.hint)
27203
+ console.error(` Hint: ${error2.hint}`);
27204
+ if (error2.httpStatus)
27205
+ console.error(` HTTP Status: ${error2.httpStatus}`);
27206
+ }
26787
27207
  process.exitCode = 1;
27208
+ };
27209
+ if (opts.verify && opts.resetPassword) {
27210
+ outputError({ message: "Provide only one of --verify or --reset-password" });
26788
27211
  return;
26789
27212
  }
26790
27213
  if (opts.verify && opts.printSql) {
26791
- console.error("\u2717 --verify cannot be combined with --print-sql");
26792
- process.exitCode = 1;
27214
+ outputError({ message: "--verify cannot be combined with --print-sql" });
26793
27215
  return;
26794
27216
  }
26795
27217
  const shouldPrintSql = !!opts.printSql;
@@ -26822,6 +27244,266 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
26822
27244
  return;
26823
27245
  }
26824
27246
  }
27247
+ if (opts.supabase) {
27248
+ let supabaseConfig;
27249
+ try {
27250
+ let projectRef = opts.supabaseProjectRef;
27251
+ if (!projectRef && conn) {
27252
+ projectRef = extractProjectRefFromUrl(conn);
27253
+ }
27254
+ supabaseConfig = resolveSupabaseConfig({
27255
+ accessToken: opts.supabaseAccessToken,
27256
+ projectRef
27257
+ });
27258
+ } catch (e) {
27259
+ const msg = e instanceof Error ? e.message : String(e);
27260
+ outputError({ message: msg });
27261
+ return;
27262
+ }
27263
+ const includeOptionalPermissions2 = !opts.skipOptionalPermissions;
27264
+ if (!jsonOutput) {
27265
+ console.log(`Supabase mode: project ref ${supabaseConfig.projectRef}`);
27266
+ console.log(`Monitoring user: ${opts.monitoringUser}`);
27267
+ console.log(`Optional permissions: ${includeOptionalPermissions2 ? "enabled" : "skipped"}`);
27268
+ }
27269
+ const supabaseClient = new SupabaseClient(supabaseConfig);
27270
+ try {
27271
+ const database = await supabaseClient.getCurrentDatabase();
27272
+ if (!database) {
27273
+ throw new Error("Failed to resolve current database name");
27274
+ }
27275
+ if (!jsonOutput) {
27276
+ console.log(`Database: ${database}`);
27277
+ }
27278
+ if (opts.verify) {
27279
+ const v = await verifyInitSetupViaSupabase({
27280
+ client: supabaseClient,
27281
+ database,
27282
+ monitoringUser: opts.monitoringUser,
27283
+ includeOptionalPermissions: includeOptionalPermissions2
27284
+ });
27285
+ if (v.ok) {
27286
+ if (jsonOutput) {
27287
+ outputJson({
27288
+ success: true,
27289
+ mode: "supabase",
27290
+ action: "verify",
27291
+ database,
27292
+ monitoringUser: opts.monitoringUser,
27293
+ verified: true,
27294
+ missingOptional: v.missingOptional
27295
+ });
27296
+ } else {
27297
+ console.log("\u2713 prepare-db verify: OK");
27298
+ if (v.missingOptional.length > 0) {
27299
+ console.log("\u26A0 Optional items missing:");
27300
+ for (const m of v.missingOptional)
27301
+ console.log(`- ${m}`);
27302
+ }
27303
+ }
27304
+ return;
27305
+ }
27306
+ if (jsonOutput) {
27307
+ outputJson({
27308
+ success: false,
27309
+ mode: "supabase",
27310
+ action: "verify",
27311
+ database,
27312
+ monitoringUser: opts.monitoringUser,
27313
+ verified: false,
27314
+ missingRequired: v.missingRequired,
27315
+ missingOptional: v.missingOptional
27316
+ });
27317
+ } else {
27318
+ console.error("\u2717 prepare-db verify failed: missing required items");
27319
+ for (const m of v.missingRequired)
27320
+ console.error(`- ${m}`);
27321
+ if (v.missingOptional.length > 0) {
27322
+ console.error("Optional items missing:");
27323
+ for (const m of v.missingOptional)
27324
+ console.error(`- ${m}`);
27325
+ }
27326
+ }
27327
+ process.exitCode = 1;
27328
+ return;
27329
+ }
27330
+ let monPassword;
27331
+ let passwordGenerated = false;
27332
+ try {
27333
+ const resolved = await resolveMonitoringPassword({
27334
+ passwordFlag: opts.password,
27335
+ passwordEnv: process.env.PGAI_MON_PASSWORD,
27336
+ monitoringUser: opts.monitoringUser
27337
+ });
27338
+ monPassword = resolved.password;
27339
+ passwordGenerated = resolved.generated;
27340
+ if (resolved.generated) {
27341
+ const canPrint = process.stdout.isTTY || !!opts.printPassword || jsonOutput;
27342
+ if (canPrint) {
27343
+ if (!jsonOutput) {
27344
+ const shellSafe = monPassword.replace(/'/g, "'\\''");
27345
+ console.error("");
27346
+ console.error(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
27347
+ console.error(`PGAI_MON_PASSWORD='${shellSafe}'`);
27348
+ console.error("");
27349
+ console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
27350
+ }
27351
+ } else {
27352
+ console.error([
27353
+ `\u2717 Monitoring password was auto-generated for ${opts.monitoringUser} but not printed in non-interactive mode.`,
27354
+ "",
27355
+ "Provide it explicitly:",
27356
+ " --password <password> or PGAI_MON_PASSWORD=...",
27357
+ "",
27358
+ "Or (NOT recommended) print the generated password:",
27359
+ " --print-password"
27360
+ ].join(`
27361
+ `));
27362
+ process.exitCode = 1;
27363
+ return;
27364
+ }
27365
+ }
27366
+ } catch (e) {
27367
+ const msg = e instanceof Error ? e.message : String(e);
27368
+ outputError({ message: msg });
27369
+ return;
27370
+ }
27371
+ const plan = await buildInitPlan({
27372
+ database,
27373
+ monitoringUser: opts.monitoringUser,
27374
+ monitoringPassword: monPassword,
27375
+ includeOptionalPermissions: includeOptionalPermissions2
27376
+ });
27377
+ const supabaseApplicableSteps = plan.steps.filter((s) => s.name !== "03.optional_rds" && s.name !== "04.optional_self_managed");
27378
+ const effectivePlan = opts.resetPassword ? { ...plan, steps: supabaseApplicableSteps.filter((s) => s.name === "01.role") } : { ...plan, steps: supabaseApplicableSteps };
27379
+ if (shouldPrintSql) {
27380
+ console.log(`
27381
+ --- SQL plan ---`);
27382
+ for (const step of effectivePlan.steps) {
27383
+ console.log(`
27384
+ -- ${step.name}${step.optional ? " (optional)" : ""}`);
27385
+ console.log(redactPasswords(step.sql));
27386
+ }
27387
+ console.log(`
27388
+ --- end SQL plan ---
27389
+ `);
27390
+ console.log("Note: passwords are redacted in the printed SQL output.");
27391
+ return;
27392
+ }
27393
+ const { applied, skippedOptional } = await applyInitPlanViaSupabase({
27394
+ client: supabaseClient,
27395
+ plan: effectivePlan
27396
+ });
27397
+ if (jsonOutput) {
27398
+ const result = {
27399
+ success: true,
27400
+ mode: "supabase",
27401
+ action: opts.resetPassword ? "reset-password" : "apply",
27402
+ database,
27403
+ monitoringUser: opts.monitoringUser,
27404
+ applied,
27405
+ skippedOptional,
27406
+ warnings: skippedOptional.length > 0 ? ["Some optional steps were skipped (not supported or insufficient privileges)"] : []
27407
+ };
27408
+ if (passwordGenerated) {
27409
+ result.generatedPassword = monPassword;
27410
+ }
27411
+ outputJson(result);
27412
+ } else {
27413
+ console.log(opts.resetPassword ? "\u2713 prepare-db password reset completed" : "\u2713 prepare-db completed");
27414
+ if (skippedOptional.length > 0) {
27415
+ console.log("\u26A0 Some optional steps were skipped (not supported or insufficient privileges):");
27416
+ for (const s of skippedOptional)
27417
+ console.log(`- ${s}`);
27418
+ }
27419
+ if (process.stdout.isTTY) {
27420
+ console.log(`Applied ${applied.length} steps`);
27421
+ }
27422
+ }
27423
+ } catch (error2) {
27424
+ const errAny = error2;
27425
+ let message = "";
27426
+ if (error2 instanceof Error && error2.message) {
27427
+ message = error2.message;
27428
+ } else if (errAny && typeof errAny === "object" && typeof errAny.message === "string" && errAny.message) {
27429
+ message = errAny.message;
27430
+ } else {
27431
+ message = String(error2);
27432
+ }
27433
+ if (!message || message === "[object Object]") {
27434
+ message = "Unknown error";
27435
+ }
27436
+ const stepMatch = typeof message === "string" ? message.match(/Failed at step "([^"]+)":/i) : null;
27437
+ const failedStep = stepMatch?.[1];
27438
+ const errorObj = { message };
27439
+ if (failedStep)
27440
+ errorObj.step = failedStep;
27441
+ if (errAny && typeof errAny === "object") {
27442
+ if (typeof errAny.code === "string" && errAny.code)
27443
+ errorObj.code = errAny.code;
27444
+ if (typeof errAny.detail === "string" && errAny.detail)
27445
+ errorObj.detail = errAny.detail;
27446
+ if (typeof errAny.hint === "string" && errAny.hint)
27447
+ errorObj.hint = errAny.hint;
27448
+ if (typeof errAny.httpStatus === "number")
27449
+ errorObj.httpStatus = errAny.httpStatus;
27450
+ }
27451
+ if (jsonOutput) {
27452
+ outputJson({
27453
+ success: false,
27454
+ mode: "supabase",
27455
+ error: errorObj
27456
+ });
27457
+ process.exitCode = 1;
27458
+ } else {
27459
+ console.error(`Error: prepare-db (Supabase): ${message}`);
27460
+ if (failedStep) {
27461
+ console.error(` Step: ${failedStep}`);
27462
+ }
27463
+ if (errAny && typeof errAny === "object") {
27464
+ if (typeof errAny.code === "string" && errAny.code) {
27465
+ console.error(` Code: ${errAny.code}`);
27466
+ }
27467
+ if (typeof errAny.detail === "string" && errAny.detail) {
27468
+ console.error(` Detail: ${errAny.detail}`);
27469
+ }
27470
+ if (typeof errAny.hint === "string" && errAny.hint) {
27471
+ console.error(` Hint: ${errAny.hint}`);
27472
+ }
27473
+ if (typeof errAny.httpStatus === "number") {
27474
+ console.error(` HTTP Status: ${errAny.httpStatus}`);
27475
+ }
27476
+ }
27477
+ if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
27478
+ if (errAny.code === "42501") {
27479
+ if (failedStep === "01.role") {
27480
+ console.error(" Context: role creation/update requires CREATEROLE or superuser");
27481
+ } else if (failedStep === "02.permissions") {
27482
+ console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
27483
+ }
27484
+ console.error(" Fix: ensure your Supabase access token has sufficient permissions");
27485
+ console.error(" Tip: run with --print-sql to review the exact SQL plan");
27486
+ }
27487
+ }
27488
+ if (errAny && typeof errAny === "object" && typeof errAny.httpStatus === "number") {
27489
+ if (errAny.httpStatus === 401) {
27490
+ console.error(" Hint: invalid or expired access token; generate a new one at https://supabase.com/dashboard/account/tokens");
27491
+ }
27492
+ if (errAny.httpStatus === 403) {
27493
+ console.error(" Hint: access denied; check your token permissions and project access");
27494
+ }
27495
+ if (errAny.httpStatus === 404) {
27496
+ console.error(" Hint: project not found; verify the project reference is correct");
27497
+ }
27498
+ if (errAny.httpStatus === 429) {
27499
+ console.error(" Hint: rate limited; wait a moment and try again");
27500
+ }
27501
+ }
27502
+ process.exitCode = 1;
27503
+ }
27504
+ }
27505
+ return;
27506
+ }
26825
27507
  let adminConn;
26826
27508
  try {
26827
27509
  adminConn = resolveAdminConnection({
@@ -26836,18 +27518,24 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
26836
27518
  });
26837
27519
  } catch (e) {
26838
27520
  const msg = e instanceof Error ? e.message : String(e);
26839
- console.error(`Error: prepare-db: ${msg}`);
26840
- if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
26841
- console.error("");
26842
- cmd.outputHelp({ error: true });
27521
+ if (jsonOutput) {
27522
+ outputError({ message: msg });
27523
+ } else {
27524
+ console.error(`Error: prepare-db: ${msg}`);
27525
+ if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
27526
+ console.error("");
27527
+ cmd.outputHelp({ error: true });
27528
+ }
27529
+ process.exitCode = 1;
26843
27530
  }
26844
- process.exitCode = 1;
26845
27531
  return;
26846
27532
  }
26847
27533
  const includeOptionalPermissions = !opts.skipOptionalPermissions;
26848
- console.log(`Connecting to: ${adminConn.display}`);
26849
- console.log(`Monitoring user: ${opts.monitoringUser}`);
26850
- console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
27534
+ if (!jsonOutput) {
27535
+ console.log(`Connecting to: ${adminConn.display}`);
27536
+ console.log(`Monitoring user: ${opts.monitoringUser}`);
27537
+ console.log(`Optional permissions: ${includeOptionalPermissions ? "enabled" : "skipped"}`);
27538
+ }
26851
27539
  let client;
26852
27540
  try {
26853
27541
  const connResult = await connectWithSslFallback(Client, adminConn);
@@ -26865,26 +27553,52 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
26865
27553
  includeOptionalPermissions
26866
27554
  });
26867
27555
  if (v.ok) {
26868
- console.log("\u2713 prepare-db verify: OK");
26869
- if (v.missingOptional.length > 0) {
26870
- console.log("\u26A0 Optional items missing:");
26871
- for (const m of v.missingOptional)
26872
- console.log(`- ${m}`);
27556
+ if (jsonOutput) {
27557
+ outputJson({
27558
+ success: true,
27559
+ mode: "direct",
27560
+ action: "verify",
27561
+ database,
27562
+ monitoringUser: opts.monitoringUser,
27563
+ verified: true,
27564
+ missingOptional: v.missingOptional
27565
+ });
27566
+ } else {
27567
+ console.log("\u2713 prepare-db verify: OK");
27568
+ if (v.missingOptional.length > 0) {
27569
+ console.log("\u26A0 Optional items missing:");
27570
+ for (const m of v.missingOptional)
27571
+ console.log(`- ${m}`);
27572
+ }
26873
27573
  }
26874
27574
  return;
26875
27575
  }
26876
- console.error("\u2717 prepare-db verify failed: missing required items");
26877
- for (const m of v.missingRequired)
26878
- console.error(`- ${m}`);
26879
- if (v.missingOptional.length > 0) {
26880
- console.error("Optional items missing:");
26881
- for (const m of v.missingOptional)
27576
+ if (jsonOutput) {
27577
+ outputJson({
27578
+ success: false,
27579
+ mode: "direct",
27580
+ action: "verify",
27581
+ database,
27582
+ monitoringUser: opts.monitoringUser,
27583
+ verified: false,
27584
+ missingRequired: v.missingRequired,
27585
+ missingOptional: v.missingOptional
27586
+ });
27587
+ } else {
27588
+ console.error("\u2717 prepare-db verify failed: missing required items");
27589
+ for (const m of v.missingRequired)
26882
27590
  console.error(`- ${m}`);
27591
+ if (v.missingOptional.length > 0) {
27592
+ console.error("Optional items missing:");
27593
+ for (const m of v.missingOptional)
27594
+ console.error(`- ${m}`);
27595
+ }
26883
27596
  }
26884
27597
  process.exitCode = 1;
26885
27598
  return;
26886
27599
  }
26887
27600
  let monPassword;
27601
+ let passwordGenerated = false;
26888
27602
  try {
26889
27603
  const resolved = await resolveMonitoringPassword({
26890
27604
  passwordFlag: opts.password,
@@ -26892,15 +27606,18 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
26892
27606
  monitoringUser: opts.monitoringUser
26893
27607
  });
26894
27608
  monPassword = resolved.password;
27609
+ passwordGenerated = resolved.generated;
26895
27610
  if (resolved.generated) {
26896
- const canPrint = process.stdout.isTTY || !!opts.printPassword;
27611
+ const canPrint = process.stdout.isTTY || !!opts.printPassword || jsonOutput;
26897
27612
  if (canPrint) {
26898
- const shellSafe = monPassword.replace(/'/g, "'\\''");
26899
- console.error("");
26900
- console.error(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
26901
- console.error(`PGAI_MON_PASSWORD='${shellSafe}'`);
26902
- console.error("");
26903
- console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
27613
+ if (!jsonOutput) {
27614
+ const shellSafe = monPassword.replace(/'/g, "'\\''");
27615
+ console.error("");
27616
+ console.error(`Generated monitoring password for ${opts.monitoringUser} (copy/paste):`);
27617
+ console.error(`PGAI_MON_PASSWORD='${shellSafe}'`);
27618
+ console.error("");
27619
+ console.log("Store it securely (or rerun with --password / PGAI_MON_PASSWORD to set your own).");
27620
+ }
26904
27621
  } else {
26905
27622
  console.error([
26906
27623
  `\u2717 Monitoring password was auto-generated for ${opts.monitoringUser} but not printed in non-interactive mode.`,
@@ -26918,8 +27635,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
26918
27635
  }
26919
27636
  } catch (e) {
26920
27637
  const msg = e instanceof Error ? e.message : String(e);
26921
- console.error(`\u2717 ${msg}`);
26922
- process.exitCode = 1;
27638
+ outputError({ message: msg });
26923
27639
  return;
26924
27640
  }
26925
27641
  const plan = await buildInitPlan({
@@ -26944,14 +27660,31 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
26944
27660
  return;
26945
27661
  }
26946
27662
  const { applied, skippedOptional } = await applyInitPlan({ client, plan: effectivePlan });
26947
- console.log(opts.resetPassword ? "\u2713 prepare-db password reset completed" : "\u2713 prepare-db completed");
26948
- if (skippedOptional.length > 0) {
26949
- console.log("\u26A0 Some optional steps were skipped (not supported or insufficient privileges):");
26950
- for (const s of skippedOptional)
26951
- console.log(`- ${s}`);
26952
- }
26953
- if (process.stdout.isTTY) {
26954
- console.log(`Applied ${applied.length} steps`);
27663
+ if (jsonOutput) {
27664
+ const result = {
27665
+ success: true,
27666
+ mode: "direct",
27667
+ action: opts.resetPassword ? "reset-password" : "apply",
27668
+ database,
27669
+ monitoringUser: opts.monitoringUser,
27670
+ applied,
27671
+ skippedOptional,
27672
+ warnings: skippedOptional.length > 0 ? ["Some optional steps were skipped (not supported or insufficient privileges)"] : []
27673
+ };
27674
+ if (passwordGenerated) {
27675
+ result.generatedPassword = monPassword;
27676
+ }
27677
+ outputJson(result);
27678
+ } else {
27679
+ console.log(opts.resetPassword ? "\u2713 prepare-db password reset completed" : "\u2713 prepare-db completed");
27680
+ if (skippedOptional.length > 0) {
27681
+ console.log("\u26A0 Some optional steps were skipped (not supported or insufficient privileges):");
27682
+ for (const s of skippedOptional)
27683
+ console.log(`- ${s}`);
27684
+ }
27685
+ if (process.stdout.isTTY) {
27686
+ console.log(`Applied ${applied.length} steps`);
27687
+ }
26955
27688
  }
26956
27689
  } catch (error2) {
26957
27690
  const errAny = error2;
@@ -26966,45 +27699,65 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
26966
27699
  if (!message || message === "[object Object]") {
26967
27700
  message = "Unknown error";
26968
27701
  }
26969
- console.error(`Error: prepare-db: ${message}`);
26970
27702
  const stepMatch = typeof message === "string" ? message.match(/Failed at step "([^"]+)":/i) : null;
26971
27703
  const failedStep = stepMatch?.[1];
26972
- if (failedStep) {
26973
- console.error(` Step: ${failedStep}`);
26974
- }
27704
+ const errorObj = { message };
27705
+ if (failedStep)
27706
+ errorObj.step = failedStep;
26975
27707
  if (errAny && typeof errAny === "object") {
26976
- if (typeof errAny.code === "string" && errAny.code) {
26977
- console.error(` Code: ${errAny.code}`);
26978
- }
26979
- if (typeof errAny.detail === "string" && errAny.detail) {
26980
- console.error(` Detail: ${errAny.detail}`);
26981
- }
26982
- if (typeof errAny.hint === "string" && errAny.hint) {
26983
- console.error(` Hint: ${errAny.hint}`);
27708
+ if (typeof errAny.code === "string" && errAny.code)
27709
+ errorObj.code = errAny.code;
27710
+ if (typeof errAny.detail === "string" && errAny.detail)
27711
+ errorObj.detail = errAny.detail;
27712
+ if (typeof errAny.hint === "string" && errAny.hint)
27713
+ errorObj.hint = errAny.hint;
27714
+ }
27715
+ if (jsonOutput) {
27716
+ outputJson({
27717
+ success: false,
27718
+ mode: "direct",
27719
+ error: errorObj
27720
+ });
27721
+ process.exitCode = 1;
27722
+ } else {
27723
+ console.error(`Error: prepare-db: ${message}`);
27724
+ if (failedStep) {
27725
+ console.error(` Step: ${failedStep}`);
26984
27726
  }
26985
- }
26986
- if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
26987
- if (errAny.code === "42501") {
26988
- if (failedStep === "01.role") {
26989
- console.error(" Context: role creation/update requires CREATEROLE or superuser");
26990
- } else if (failedStep === "02.permissions") {
26991
- console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
27727
+ if (errAny && typeof errAny === "object") {
27728
+ if (typeof errAny.code === "string" && errAny.code) {
27729
+ console.error(` Code: ${errAny.code}`);
27730
+ }
27731
+ if (typeof errAny.detail === "string" && errAny.detail) {
27732
+ console.error(` Detail: ${errAny.detail}`);
27733
+ }
27734
+ if (typeof errAny.hint === "string" && errAny.hint) {
27735
+ console.error(` Hint: ${errAny.hint}`);
26992
27736
  }
26993
- console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
26994
- console.error(" Fix: on managed Postgres, use the provider's admin/master user");
26995
- console.error(" Tip: run with --print-sql to review the exact SQL plan");
26996
- }
26997
- if (errAny.code === "ECONNREFUSED") {
26998
- console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
26999
- }
27000
- if (errAny.code === "ENOTFOUND") {
27001
- console.error(" Hint: DNS resolution failed; double-check the host name");
27002
27737
  }
27003
- if (errAny.code === "ETIMEDOUT") {
27004
- console.error(" Hint: connection timed out; check network/firewall rules");
27738
+ if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
27739
+ if (errAny.code === "42501") {
27740
+ if (failedStep === "01.role") {
27741
+ console.error(" Context: role creation/update requires CREATEROLE or superuser");
27742
+ } else if (failedStep === "02.permissions") {
27743
+ console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
27744
+ }
27745
+ console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
27746
+ console.error(" Fix: on managed Postgres, use the provider's admin/master user");
27747
+ console.error(" Tip: run with --print-sql to review the exact SQL plan");
27748
+ }
27749
+ if (errAny.code === "ECONNREFUSED") {
27750
+ console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
27751
+ }
27752
+ if (errAny.code === "ENOTFOUND") {
27753
+ console.error(" Hint: DNS resolution failed; double-check the host name");
27754
+ }
27755
+ if (errAny.code === "ETIMEDOUT") {
27756
+ console.error(" Hint: connection timed out; check network/firewall rules");
27757
+ }
27005
27758
  }
27759
+ process.exitCode = 1;
27006
27760
  }
27007
- process.exitCode = 1;
27008
27761
  } finally {
27009
27762
  if (client) {
27010
27763
  try {