migraguard 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/COMMANDS.md CHANGED
@@ -64,7 +64,27 @@ migraguard check
64
64
 
65
65
  ### `migraguard lint`
66
66
 
67
- Run Squawk lint on migration files to detect idempotency and safety rule violations.
67
+ Run built-in safety rules on all migration files. Rules use libpg-query AST analysis no external tools required.
68
+
69
+ Rules (all enabled by default):
70
+ - `require-if-not-exists` — CREATE/DROP must use IF NOT EXISTS / IF EXISTS
71
+ - `require-concurrent-index` — CREATE INDEX must use CONCURRENTLY (skipped for tables created in the same file)
72
+ - `require-lock-timeout` — SET lock_timeout must appear before DDL statements
73
+ - `ban-concurrent-index-in-transaction` — CONCURRENTLY cannot be inside BEGIN...COMMIT
74
+ - `adding-not-nullable-field` — NOT NULL column must have a DEFAULT value
75
+ - `constraint-missing-not-valid` — ADD CONSTRAINT must use NOT VALID
76
+
77
+ Disable specific rules or add custom rules via config:
78
+ ```json
79
+ {
80
+ "lint": {
81
+ "rules": { "require-lock-timeout": false },
82
+ "customRulesDir": "lint-rules"
83
+ }
84
+ }
85
+ ```
86
+
87
+ Custom rule files (`.js` / `.mjs`) in the specified directory are loaded automatically. Each file must default-export a `LintRule` object. See README for an example.
68
88
 
69
89
  ```bash
70
90
  migraguard lint
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # migraguard
2
2
 
3
+ [![npm version](https://badge.fury.io/js/migraguard.svg)](https://www.npmjs.com/package/migraguard) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+
3
5
  An incident-prevention migration tool for PostgreSQL. Enforces safe operational policies via CI gates and DB state tracking, so that common migration accidents are structurally impossible.
4
6
 
5
7
  **Prevented accidents:**
@@ -48,11 +50,7 @@ npx migraguard dump
48
50
  - **One release = one file**: Migration files are squashed into a single file before release, simplifying error recovery. In DAG mode, independent DDL can be released individually
49
51
  - **Parallel releases via dependency tree**: DDL dependencies are analyzed to build a DAG, enabling parallel releases for independent changes
50
52
  - **Shift verification left**: Linting, checksum-based tamper detection, and schema dump diffs run at the PR stage
51
- - **Minimal footprint**: The runtime depends on four external tools with clear responsibilities:
52
- - `psql` — executes migration SQL
53
- - `pg_dump` — produces schema dumps for drift detection
54
- - [Squawk](https://squawkhq.com/) — lints SQL for safety (optional)
55
- - [libpg-query](https://github.com/pganalyze/libpg-query) — parses DDL for dependency analysis (DAG model)
53
+ - **Minimal footprint**: Two CLI tools (`psql`, `pg_dump`) and one npm library ([libpg-query](https://github.com/pganalyze/libpg-query)). No external linter required lint rules are built in via AST analysis
56
54
 
57
55
  ## Core Concepts
58
56
 
@@ -157,7 +155,7 @@ See [docs/state-model.md](docs/state-model.md) for detailed apply, check, resolv
157
155
  | `status` | Display migration status per file |
158
156
  | `editable` | List currently editable files (tail / leaf) |
159
157
  | `check` | Verify file integrity via metadata.json (no DB required) |
160
- | `lint` | SQL lint via Squawk |
158
+ | `lint` | Run built-in safety rules (AST-based) |
161
159
  | `verify` / `verify --all` | Prove idempotency on shadow DB |
162
160
  | `dump` | Save normalized schema dump |
163
161
  | `diff` | Show schema diff (DB vs saved dump) |
@@ -242,7 +240,14 @@ jobs:
242
240
  "excludePrivileges": true
243
241
  },
244
242
  "lint": {
245
- "squawk": true
243
+ "rules": {
244
+ "require-concurrent-index": true,
245
+ "require-if-not-exists": true,
246
+ "require-lock-timeout": true,
247
+ "ban-concurrent-index-in-transaction": true,
248
+ "adding-not-nullable-field": true,
249
+ "constraint-missing-not-valid": true
250
+ }
246
251
  }
247
252
  }
248
253
  ```
@@ -309,7 +314,20 @@ CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email ON users (email);
309
314
  UPDATE users SET status = 'active' WHERE status IS NULL;
310
315
  ```
311
316
 
312
- For safe DDL patterns (lock timeout, CONCURRENTLY, batch backfills), see [docs/safe-ddl.md](docs/safe-ddl.md).
317
+ `migraguard lint` enforces these patterns with built-in rules (no external tools required):
318
+
319
+ | Rule | Detects |
320
+ |------|---------|
321
+ | `require-if-not-exists` | CREATE/DROP without IF NOT EXISTS / IF EXISTS |
322
+ | `require-concurrent-index` | CREATE INDEX without CONCURRENTLY on existing tables |
323
+ | `require-lock-timeout` | DDL without prior SET lock_timeout |
324
+ | `ban-concurrent-index-in-transaction` | CONCURRENTLY inside BEGIN...COMMIT |
325
+ | `adding-not-nullable-field` | NOT NULL column without DEFAULT |
326
+ | `constraint-missing-not-valid` | ADD CONSTRAINT without NOT VALID |
327
+
328
+ Rules are enabled by default and can be disabled per-rule in `migraguard.config.json` under `lint.rules`.
329
+
330
+ Project-specific rules can be added via `lint.customRulesDir`. See [docs/safe-ddl.md](docs/safe-ddl.md) for built-in rule details and custom rule examples.
313
331
 
314
332
  ## Directory Structure
315
333
 
@@ -422,8 +440,8 @@ No. `verify` creates a temporary shadow DB, applies migrations twice, then drops
422
440
  | Language | TypeScript (Node.js) |
423
441
  | DB execution | `psql` CLI |
424
442
  | Schema dump | `pg_dump --schema-only` |
425
- | SQL lint | [Squawk](https://squawkhq.com/) |
426
- | SQL parser | [libpg-query](https://github.com/pganalyze/libpg-query) (PostgreSQL real parser WASM build, for DDL dependency analysis) |
443
+ | SQL lint | Built-in rules via [libpg-query](https://github.com/pganalyze/libpg-query) AST analysis |
444
+ | SQL parser | [libpg-query](https://github.com/pganalyze/libpg-query) (PostgreSQL real parser WASM build) |
427
445
  | Package manager | npm |
428
446
 
429
447
  ## Detailed Documentation
package/dist/cli.js CHANGED
@@ -1,19 +1,21 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk6 from 'chalk';
3
+ import { createRequire } from 'module';
3
4
  import { readFile, mkdir, writeFile, unlink, readdir } from 'fs/promises';
4
5
  import { dirname, resolve, join } from 'path';
5
6
  import { existsSync } from 'fs';
6
7
  import { randomBytes, createHash } from 'crypto';
7
8
  import libpg from 'libpg-query';
9
+ import { pathToFileURL } from 'url';
10
+ import pg from 'pg';
8
11
  import { execFile } from 'child_process';
9
12
  import { promisify } from 'util';
10
- import pg from 'pg';
11
13
  import { tmpdir } from 'os';
12
14
 
13
15
  // src/cli/index.ts
14
-
15
- // src/index.ts
16
- var VERSION = "0.2.2";
16
+ var require2 = createRequire(import.meta.url);
17
+ var pkg = require2("../package.json");
18
+ var VERSION = pkg.version;
17
19
  var CONFIG_FILE_NAME = "migraguard.config.json";
18
20
  var DEFAULT_NAMING = {
19
21
  pattern: "{timestamp}__{description}.sql",
@@ -33,7 +35,14 @@ var DEFAULT_DUMP = {
33
35
  excludePrivileges: true
34
36
  };
35
37
  var DEFAULT_LINT = {
36
- squawk: true
38
+ rules: {
39
+ "require-concurrent-index": true,
40
+ "require-if-not-exists": true,
41
+ "require-lock-timeout": true,
42
+ "ban-concurrent-index-in-transaction": true,
43
+ "adding-not-nullable-field": true,
44
+ "constraint-missing-not-valid": true
45
+ }
37
46
  };
38
47
  function applyEnvOverrides(connection) {
39
48
  return {
@@ -81,7 +90,11 @@ function buildConfig(raw, configDir) {
81
90
  naming: { ...DEFAULT_NAMING, ...raw.naming },
82
91
  connection: applyEnvOverrides(connection),
83
92
  dump: { ...DEFAULT_DUMP, ...raw.dump },
84
- lint: { ...DEFAULT_LINT, ...raw.lint }
93
+ lint: {
94
+ ...DEFAULT_LINT,
95
+ ...raw.lint,
96
+ rules: { ...DEFAULT_LINT.rules, ...raw.lint?.rules }
97
+ }
85
98
  };
86
99
  }
87
100
  async function loadConfig(startDir) {
@@ -1007,51 +1020,311 @@ function findConnectedComponents(newFiles, graph) {
1007
1020
  }
1008
1021
  return components;
1009
1022
  }
1010
- var execFileAsync = promisify(execFile);
1011
- async function isSquawkAvailable() {
1023
+ async function runRules(sql, rules) {
1024
+ const violations = [];
1025
+ let stmts;
1012
1026
  try {
1013
- await execFileAsync("squawk", ["--version"]);
1014
- return true;
1027
+ const ast = await libpg.parse(sql);
1028
+ stmts = ast.stmts;
1015
1029
  } catch {
1016
- return false;
1030
+ return violations;
1017
1031
  }
1032
+ const visitors = [];
1033
+ for (const rule of rules) {
1034
+ visitors.push({ ruleId: rule.id, handlers: rule.create() });
1035
+ }
1036
+ const createdTables = /* @__PURE__ */ new Set();
1037
+ let lockTimeoutSet = false;
1038
+ let inTransaction = false;
1039
+ for (const { stmt } of stmts) {
1040
+ const s = stmt;
1041
+ if ("VariableSetStmt" in s) {
1042
+ const name = s.VariableSetStmt.name;
1043
+ if (name === "lock_timeout") lockTimeoutSet = true;
1044
+ }
1045
+ if ("TransactionStmt" in s) {
1046
+ const kind = s.TransactionStmt.kind;
1047
+ if (kind === "TRANS_STMT_BEGIN") inTransaction = true;
1048
+ else if (kind === "TRANS_STMT_COMMIT" || kind === "TRANS_STMT_ROLLBACK") inTransaction = false;
1049
+ }
1050
+ if ("CreateStmt" in s) {
1051
+ const rel = s.CreateStmt.relation;
1052
+ if (rel?.relname) createdTables.add(rel.relname);
1053
+ }
1054
+ const ctx = {
1055
+ report: null,
1056
+ createdTables,
1057
+ lockTimeoutSet,
1058
+ inTransaction
1059
+ };
1060
+ for (const key of Object.keys(s)) {
1061
+ const node = s[key];
1062
+ for (const { ruleId, handlers } of visitors) {
1063
+ const handler = handlers[key];
1064
+ if (!handler) continue;
1065
+ ctx.report = (v) => violations.push({ rule: ruleId, ...v });
1066
+ handler(node, ctx);
1067
+ }
1068
+ }
1069
+ }
1070
+ return violations;
1018
1071
  }
1019
- async function commandLint(config) {
1020
- if (!config.lint.squawk) {
1021
- console.log(chalk6.yellow("Squawk lint is disabled in config."));
1022
- return { ok: true, filesLinted: 0 };
1072
+
1073
+ // src/rules/require-concurrent-index.ts
1074
+ var requireConcurrentIndex = {
1075
+ id: "require-concurrent-index",
1076
+ description: "CREATE INDEX must use CONCURRENTLY on existing tables",
1077
+ create() {
1078
+ return {
1079
+ IndexStmt(node, ctx) {
1080
+ const rel = node.relation;
1081
+ const tableName = rel?.relname ?? "(unknown)";
1082
+ const isNewTable = ctx.createdTables.has(tableName);
1083
+ if (!node.concurrent && !isNewTable) {
1084
+ ctx.report({
1085
+ message: `CREATE INDEX on "${tableName}" without CONCURRENTLY`,
1086
+ hint: "Use CREATE INDEX CONCURRENTLY to avoid blocking writes"
1087
+ });
1088
+ }
1089
+ }
1090
+ };
1023
1091
  }
1024
- const available = await isSquawkAvailable();
1025
- if (!available) {
1026
- throw new Error(
1027
- "Squawk is not installed or not in PATH. Install it: npm install -g squawk-cli (or see https://squawkhq.com/)"
1028
- );
1092
+ };
1093
+
1094
+ // src/rules/require-if-not-exists.ts
1095
+ var requireIfNotExists = {
1096
+ id: "require-if-not-exists",
1097
+ description: "CREATE must use IF NOT EXISTS, DROP must use IF EXISTS",
1098
+ create() {
1099
+ return {
1100
+ CreateStmt(node, ctx) {
1101
+ if (!node.if_not_exists) {
1102
+ const rel = node.relation;
1103
+ ctx.report({
1104
+ message: `CREATE TABLE ${rel?.relname ?? "(unknown)"} without IF NOT EXISTS`,
1105
+ hint: "Use CREATE TABLE IF NOT EXISTS for idempotent migrations"
1106
+ });
1107
+ }
1108
+ },
1109
+ IndexStmt(node, ctx) {
1110
+ if (!node.if_not_exists) {
1111
+ ctx.report({
1112
+ message: "CREATE INDEX without IF NOT EXISTS",
1113
+ hint: "Use CREATE INDEX ... IF NOT EXISTS for idempotent migrations"
1114
+ });
1115
+ }
1116
+ },
1117
+ DropStmt(node, ctx) {
1118
+ if (!node.missing_ok) {
1119
+ ctx.report({
1120
+ message: "DROP without IF EXISTS",
1121
+ hint: "Use DROP ... IF EXISTS for idempotent migrations"
1122
+ });
1123
+ }
1124
+ }
1125
+ };
1126
+ }
1127
+ };
1128
+
1129
+ // src/rules/require-lock-timeout.ts
1130
+ var requireLockTimeout = {
1131
+ id: "require-lock-timeout",
1132
+ description: "SET lock_timeout must appear before DDL statements",
1133
+ create() {
1134
+ let flagged = false;
1135
+ function checkTimeout(ctx) {
1136
+ if (!ctx.lockTimeoutSet && !flagged) {
1137
+ flagged = true;
1138
+ ctx.report({
1139
+ message: "DDL appears before SET lock_timeout",
1140
+ hint: "Add SET lock_timeout = '5s'; before DDL statements"
1141
+ });
1142
+ }
1143
+ }
1144
+ return {
1145
+ CreateStmt(_node, ctx) {
1146
+ checkTimeout(ctx);
1147
+ },
1148
+ IndexStmt(node, ctx) {
1149
+ if (!node.concurrent) checkTimeout(ctx);
1150
+ },
1151
+ AlterTableStmt(_node, ctx) {
1152
+ checkTimeout(ctx);
1153
+ },
1154
+ DropStmt(_node, ctx) {
1155
+ checkTimeout(ctx);
1156
+ }
1157
+ };
1029
1158
  }
1159
+ };
1160
+
1161
+ // src/rules/ban-concurrent-index-in-transaction.ts
1162
+ var banConcurrentIndexInTransaction = {
1163
+ id: "ban-concurrent-index-in-transaction",
1164
+ description: "CREATE INDEX CONCURRENTLY cannot run inside a transaction",
1165
+ create() {
1166
+ return {
1167
+ IndexStmt(node, ctx) {
1168
+ if (node.concurrent && ctx.inTransaction) {
1169
+ ctx.report({
1170
+ message: "CREATE INDEX CONCURRENTLY inside a transaction",
1171
+ hint: "Remove BEGIN/COMMIT \u2014 CONCURRENTLY cannot run inside a transaction block"
1172
+ });
1173
+ }
1174
+ }
1175
+ };
1176
+ }
1177
+ };
1178
+
1179
+ // src/rules/adding-not-nullable-field.ts
1180
+ var addingNotNullableField = {
1181
+ id: "adding-not-nullable-field",
1182
+ description: "Adding a NOT NULL column requires a DEFAULT value",
1183
+ create() {
1184
+ return {
1185
+ AlterTableStmt(node, ctx) {
1186
+ const cmds = node.cmds;
1187
+ if (!cmds) return;
1188
+ for (const cmd of cmds) {
1189
+ const alterCmd = cmd.AlterTableCmd;
1190
+ if (!alterCmd) continue;
1191
+ const subtype = alterCmd.subtype;
1192
+ const isAddColumn = subtype === "AT_AddColumn" || subtype === 0;
1193
+ if (!isAddColumn) continue;
1194
+ const colDef = alterCmd.def;
1195
+ if (!colDef?.ColumnDef) continue;
1196
+ const col = colDef.ColumnDef;
1197
+ const constraints = col.constraints;
1198
+ if (!constraints) continue;
1199
+ let hasNotNull = false;
1200
+ let hasDefault = false;
1201
+ for (const c of constraints) {
1202
+ const constr = c.Constraint;
1203
+ if (!constr) continue;
1204
+ if (constr.contype === "CONSTR_NOTNULL") hasNotNull = true;
1205
+ if (constr.contype === "CONSTR_DEFAULT") hasDefault = true;
1206
+ }
1207
+ if (hasNotNull && !hasDefault) {
1208
+ const colname = col.colname;
1209
+ ctx.report({
1210
+ message: `Adding NOT NULL column "${colname ?? "(unknown)"}" without DEFAULT`,
1211
+ hint: "Add a DEFAULT value or add the column as nullable first, then backfill, then set NOT NULL"
1212
+ });
1213
+ }
1214
+ }
1215
+ }
1216
+ };
1217
+ }
1218
+ };
1219
+
1220
+ // src/rules/constraint-missing-not-valid.ts
1221
+ var constraintMissingNotValid = {
1222
+ id: "constraint-missing-not-valid",
1223
+ description: "ADD CONSTRAINT should use NOT VALID to avoid full table scan",
1224
+ create() {
1225
+ return {
1226
+ AlterTableStmt(node, ctx) {
1227
+ const cmds = node.cmds;
1228
+ if (!cmds) return;
1229
+ for (const cmd of cmds) {
1230
+ const alterCmd = cmd.AlterTableCmd;
1231
+ if (!alterCmd) continue;
1232
+ const subtype = alterCmd.subtype;
1233
+ const isAddConstraint = subtype === "AT_AddConstraint" || subtype === 14;
1234
+ if (!isAddConstraint) continue;
1235
+ const def = alterCmd.def;
1236
+ if (!def?.Constraint) continue;
1237
+ const constr = def.Constraint;
1238
+ const contype = constr.contype;
1239
+ const needsNotValid = contype === "CONSTR_FOREIGN" || contype === "CONSTR_CHECK";
1240
+ if (!needsNotValid) continue;
1241
+ if (!constr.skip_validation) {
1242
+ const conname = constr.conname;
1243
+ ctx.report({
1244
+ message: `ADD CONSTRAINT ${conname ? `"${conname}" ` : ""}without NOT VALID`,
1245
+ hint: "Use NOT VALID to avoid a full table scan that blocks writes, then VALIDATE CONSTRAINT separately"
1246
+ });
1247
+ }
1248
+ }
1249
+ }
1250
+ };
1251
+ }
1252
+ };
1253
+
1254
+ // src/rules/index.ts
1255
+ var ALL_RULES = [
1256
+ requireConcurrentIndex,
1257
+ requireIfNotExists,
1258
+ requireLockTimeout,
1259
+ banConcurrentIndexInTransaction,
1260
+ addingNotNullableField,
1261
+ constraintMissingNotValid
1262
+ ];
1263
+
1264
+ // src/commands/lint.ts
1265
+ async function loadCustomRules(config) {
1266
+ const dir = config.lint.customRulesDir;
1267
+ if (!dir) return [];
1268
+ const absDir = resolveFromConfig(config, dir);
1269
+ let entries;
1270
+ try {
1271
+ entries = await readdir(absDir);
1272
+ } catch {
1273
+ return [];
1274
+ }
1275
+ const rules = [];
1276
+ for (const entry of entries) {
1277
+ if (!entry.endsWith(".js") && !entry.endsWith(".mjs")) continue;
1278
+ const filePath = resolve(absDir, entry);
1279
+ try {
1280
+ const mod = await import(pathToFileURL(filePath).href);
1281
+ const rule = mod.default ?? mod;
1282
+ if (rule && typeof rule.id === "string" && typeof rule.create === "function") {
1283
+ rules.push(rule);
1284
+ }
1285
+ } catch (err) {
1286
+ console.error(chalk6.yellow(`Warning: failed to load custom rule from ${entry}: ${err.message}`));
1287
+ }
1288
+ }
1289
+ return rules;
1290
+ }
1291
+ async function commandLint(config) {
1030
1292
  const files = await scanMigrations(config);
1031
1293
  if (files.length === 0) {
1032
1294
  console.log(chalk6.yellow("No migration files to lint."));
1033
- return { ok: true, filesLinted: 0 };
1295
+ return { ok: true, filesLinted: 0, violations: 0 };
1296
+ }
1297
+ const customRules = await loadCustomRules(config);
1298
+ const allRules = [...ALL_RULES, ...customRules];
1299
+ const enabledRules = allRules.filter((r) => config.lint.rules[r.id] !== false);
1300
+ if (enabledRules.length === 0) {
1301
+ console.log(chalk6.yellow("All lint rules are disabled."));
1302
+ return { ok: true, filesLinted: files.length, violations: 0 };
1034
1303
  }
1035
- let hasErrors = false;
1304
+ let totalViolations = 0;
1036
1305
  for (const f of files) {
1037
- try {
1038
- await execFileAsync("squawk", [f.filePath]);
1039
- } catch (err) {
1040
- hasErrors = true;
1041
- const execErr = err;
1042
- console.error(chalk6.red(`
1043
- \u2717 ${f.fileName}:`));
1044
- if (execErr.stdout) console.error(execErr.stdout);
1045
- if (execErr.stderr) console.error(execErr.stderr);
1306
+ const sql = await readFile(f.filePath, "utf-8");
1307
+ const violations = await runRules(sql, enabledRules);
1308
+ if (violations.length > 0) {
1309
+ totalViolations += violations.length;
1310
+ printViolations(f.fileName, violations);
1046
1311
  }
1047
1312
  }
1048
- if (hasErrors) {
1313
+ if (totalViolations > 0) {
1049
1314
  console.error(chalk6.red(`
1050
- Lint failed.`));
1315
+ Lint failed: ${totalViolations} violation(s).`));
1051
1316
  } else {
1052
1317
  console.log(chalk6.green(`\u2713 ${files.length} file(s) passed lint.`));
1053
1318
  }
1054
- return { ok: !hasErrors, filesLinted: files.length };
1319
+ return { ok: totalViolations === 0, filesLinted: files.length, violations: totalViolations };
1320
+ }
1321
+ function printViolations(fileName, violations) {
1322
+ console.error(chalk6.red(`
1323
+ \u2717 ${fileName}:`));
1324
+ for (const v of violations) {
1325
+ console.error(chalk6.red(` [${v.rule}] ${v.message}`));
1326
+ console.error(chalk6.gray(` hint: ${v.hint}`));
1327
+ }
1055
1328
  }
1056
1329
  var { Client } = pg;
1057
1330
  var ADVISORY_LOCK_KEY = "migraguard-apply";
@@ -1228,7 +1501,7 @@ async function commandEditable(config) {
1228
1501
  }
1229
1502
  return { editableFiles, entries };
1230
1503
  }
1231
- var execFileAsync2 = promisify(execFile);
1504
+ var execFileAsync = promisify(execFile);
1232
1505
  function buildPsqlEnv(config) {
1233
1506
  const env = { ...process.env };
1234
1507
  env["PGHOST"] = config.connection.host;
@@ -1243,7 +1516,7 @@ function buildPsqlEnv(config) {
1243
1516
  async function executePsqlFile(config, filePath) {
1244
1517
  const env = buildPsqlEnv(config);
1245
1518
  try {
1246
- const { stdout, stderr } = await execFileAsync2(
1519
+ const { stdout, stderr } = await execFileAsync(
1247
1520
  "psql",
1248
1521
  ["-v", "ON_ERROR_STOP=1", "-f", filePath],
1249
1522
  { env }
@@ -1258,7 +1531,7 @@ async function executePsqlFile(config, filePath) {
1258
1531
  };
1259
1532
  }
1260
1533
  }
1261
- var execFileAsync3 = promisify(execFile);
1534
+ var execFileAsync2 = promisify(execFile);
1262
1535
  function buildPgDumpEnv(config) {
1263
1536
  const env = { ...process.env };
1264
1537
  env["PGHOST"] = config.connection.host;
@@ -1278,11 +1551,11 @@ async function dumpSchema(config) {
1278
1551
  let stdout;
1279
1552
  if (pgDumpCmd && pgDumpCmd.length > 0) {
1280
1553
  const [cmd, ...baseArgs] = pgDumpCmd;
1281
- const { stdout: out } = await execFileAsync3(cmd, [...baseArgs, ...dumpArgs]);
1554
+ const { stdout: out } = await execFileAsync2(cmd, [...baseArgs, ...dumpArgs]);
1282
1555
  stdout = out;
1283
1556
  } else {
1284
1557
  const env = buildPgDumpEnv(config);
1285
- const { stdout: out } = await execFileAsync3("pg_dump", dumpArgs, { env });
1558
+ const { stdout: out } = await execFileAsync2("pg_dump", dumpArgs, { env });
1286
1559
  stdout = out;
1287
1560
  }
1288
1561
  if (config.dump.normalize) {
@@ -1682,7 +1955,7 @@ async function commandDiff(config) {
1682
1955
  return { identical: false, diff };
1683
1956
  }
1684
1957
  var { Client: Client2 } = pg;
1685
- var execFileAsync4 = promisify(execFile);
1958
+ var execFileAsync3 = promisify(execFile);
1686
1959
  function shadowDbName() {
1687
1960
  const suffix = randomBytes(4).toString("hex");
1688
1961
  return `migraguard_shadow_${suffix}`;
@@ -1732,11 +2005,11 @@ async function dumpSourceToShadow(config, shadowName) {
1732
2005
  let dumpOutput;
1733
2006
  if (pgDumpCmd && pgDumpCmd.length > 0) {
1734
2007
  const [cmd, ...baseArgs] = pgDumpCmd;
1735
- const { stdout } = await execFileAsync4(cmd, [...baseArgs, "--no-owner", "--no-privileges"]);
2008
+ const { stdout } = await execFileAsync3(cmd, [...baseArgs, "--no-owner", "--no-privileges"]);
1736
2009
  dumpOutput = stdout;
1737
2010
  } else {
1738
2011
  env["PGDATABASE"] = conn.database;
1739
- const { stdout } = await execFileAsync4("pg_dump", ["--no-owner", "--no-privileges"], { env });
2012
+ const { stdout } = await execFileAsync3("pg_dump", ["--no-owner", "--no-privileges"], { env });
1740
2013
  dumpOutput = stdout;
1741
2014
  }
1742
2015
  const tmpFile = join(tmpdir(), `migraguard-dump-${randomBytes(4).toString("hex")}.sql`);
@@ -1744,7 +2017,7 @@ async function dumpSourceToShadow(config, shadowName) {
1744
2017
  try {
1745
2018
  const restoreEnv = buildEnv(conn);
1746
2019
  restoreEnv["PGDATABASE"] = shadowName;
1747
- await execFileAsync4("psql", ["-v", "ON_ERROR_STOP=1", "-f", tmpFile], {
2020
+ await execFileAsync3("psql", ["-v", "ON_ERROR_STOP=1", "-f", tmpFile], {
1748
2021
  env: restoreEnv,
1749
2022
  maxBuffer: 50 * 1024 * 1024
1750
2023
  });
@@ -2202,7 +2475,7 @@ program.command("squash").description("Squash multiple new migration files into
2202
2475
  const config = await loadConfig();
2203
2476
  await commandSquash(config);
2204
2477
  }));
2205
- program.command("lint").description("Run Squawk lint on migration files").action(() => run(async () => {
2478
+ program.command("lint").description("Run built-in safety rules on migration files").action(() => run(async () => {
2206
2479
  const config = await loadConfig();
2207
2480
  const result = await commandLint(config);
2208
2481
  if (!result.ok) process.exit(1);