@pgsql/lint 18.1.2 → 18.2.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.
package/README.md CHANGED
@@ -46,11 +46,49 @@ pgsql-lint . --rules no-dynamic-sql # only some rules
46
46
  pgsql-lint . --warn require-qualified-refs # downgrade to a warning (won't fail)
47
47
  pgsql-lint . --off C2 # disable a rule (by id or code)
48
48
  pgsql-lint . --json # machine-readable
49
+ pgsql-lint . --ignore 'sql/,**/generated/**' # exclude generated trees
50
+ pgsql-lint --changed # only .sql that this branch touched
51
+ pgsql-lint --changed origin/main # …against an explicit base
49
52
  ```
50
53
 
51
54
  Exit code is `1` when any **error**-severity (and non-waived) finding remains,
52
55
  `0` otherwise — drop it straight into CI. `--warn` findings print but don't fail.
53
56
 
57
+ ### Changed files only
58
+
59
+ `--changed[=<base>]` lints just the `.sql` files a branch touched, so a CI gate
60
+ costs a second instead of scanning the whole tree. The base defaults to the pull
61
+ request's base branch (`$GITHUB_BASE_REF`) and otherwise to the repository's
62
+ default branch; the diff is taken against `git merge-base HEAD <base>`, so
63
+ commits landed on the base branch afterwards don't widen the set. Uncommitted and
64
+ untracked changes are included, deleted/renamed-away paths are dropped, and a
65
+ shallow clone or detached checkout (no resolvable merge base) falls back to the
66
+ working-tree diff against `HEAD`. Nothing changed is an exit-0 pass.
67
+
68
+ ### Config file
69
+
70
+ `.pgsqllintrc.json`, discovered by walking up from the working directory (or
71
+ passed with `--config <file>`; `--no-config` skips discovery). The keys mirror the
72
+ flags, and any flag overrides the file:
73
+
74
+ ```json
75
+ {
76
+ "extends": "./ci/lint-base.json",
77
+ "paths": ["packages", "application/app"],
78
+ "ignore": ["sql/", "application/constructive/", "**/generated/**"],
79
+ "warn": ["require-qualified-refs"],
80
+ "off": ["C2"]
81
+ }
82
+ ```
83
+
84
+ `paths` supplies the default targets when none are given on the command line, so
85
+ `pgsql-lint` and `pgsql-lint --changed` need no arguments. `extends` names another
86
+ config *file* — a path relative to the file that declared it, or an npm module —
87
+ and the inheriting file wins key by key. Ignore patterns are gitignore-flavoured
88
+ globs relative to the config file's directory: `*` within a segment, `**` across
89
+ segments, a plain path excludes the whole subtree, and an unanchored pattern
90
+ matches at any segment boundary (`/` anchors it to the root).
91
+
54
92
  ## Suppressions
55
93
 
56
94
  ESLint / Prettier-style comments, authored in the function body (they survive
package/changed.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Changed-file detection for `--changed`, so a CI gate (or a human before a
3
+ * commit) pays only for the SQL that a branch actually touched.
4
+ *
5
+ * Modelled on pgpm's bundle-drift check (`pgpm/core/src/packaging/check.ts` in
6
+ * constructive): resolve a base ref (explicit → `origin/$GITHUB_BASE_REF` in a
7
+ * PR → the repository's default branch), diff `HEAD` against the **merge base**
8
+ * so unrelated commits on the base branch don't widen the set, and union that
9
+ * with uncommitted/untracked working-tree changes. Deleted paths are dropped —
10
+ * there is nothing left on disk to lint.
11
+ */
12
+ /**
13
+ * Resolve the ref to diff against. An explicit `base` wins; otherwise the PR
14
+ * base branch (`origin/$GITHUB_BASE_REF`) when running in GitHub Actions;
15
+ * otherwise the repository's default branch. `undefined` means "no base" and
16
+ * the caller falls back to working-tree changes only.
17
+ */
18
+ export declare function resolveChangedBase(base?: string, cwd?: string): string | undefined;
19
+ export interface ChangedFilesResult {
20
+ /** Absolute paths of changed files that still exist on disk. */
21
+ files: string[];
22
+ /** The base ref used, if any. */
23
+ base?: string;
24
+ /** The merge base actually diffed against, if one was resolvable. */
25
+ mergeBase?: string;
26
+ }
27
+ /**
28
+ * Collect the files that differ from `base` (via `git merge-base`) plus any
29
+ * uncommitted/untracked working-tree changes. Falls back to `git diff HEAD`
30
+ * when no base is resolvable or no merge base exists — a shallow clone or a
31
+ * detached CI checkout — rather than failing the run.
32
+ */
33
+ export declare function changedFiles(options?: {
34
+ cwd?: string;
35
+ base?: string;
36
+ }): ChangedFilesResult;
37
+ /** {@link changedFiles}, narrowed to `.sql`. */
38
+ export declare function changedSqlFiles(options?: {
39
+ cwd?: string;
40
+ base?: string;
41
+ }): ChangedFilesResult;
package/changed.js ADDED
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ /**
3
+ * Changed-file detection for `--changed`, so a CI gate (or a human before a
4
+ * commit) pays only for the SQL that a branch actually touched.
5
+ *
6
+ * Modelled on pgpm's bundle-drift check (`pgpm/core/src/packaging/check.ts` in
7
+ * constructive): resolve a base ref (explicit → `origin/$GITHUB_BASE_REF` in a
8
+ * PR → the repository's default branch), diff `HEAD` against the **merge base**
9
+ * so unrelated commits on the base branch don't widen the set, and union that
10
+ * with uncommitted/untracked working-tree changes. Deleted paths are dropped —
11
+ * there is nothing left on disk to lint.
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.resolveChangedBase = resolveChangedBase;
48
+ exports.changedFiles = changedFiles;
49
+ exports.changedSqlFiles = changedSqlFiles;
50
+ const child_process_1 = require("child_process");
51
+ const fs_1 = require("fs");
52
+ const path = __importStar(require("path"));
53
+ function git(args, cwd) {
54
+ return (0, child_process_1.execFileSync)('git', args, {
55
+ cwd,
56
+ encoding: 'utf-8',
57
+ stdio: ['ignore', 'pipe', 'ignore'],
58
+ maxBuffer: 64 * 1024 * 1024
59
+ });
60
+ }
61
+ function tryGit(args, cwd) {
62
+ try {
63
+ return git(args, cwd);
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ /** The repository's default branch as a remote-tracking ref, when discoverable. */
70
+ function defaultBranch(cwd) {
71
+ const head = tryGit(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], cwd);
72
+ if (head && head.trim())
73
+ return head.trim();
74
+ for (const candidate of ['origin/main', 'origin/master', 'main', 'master']) {
75
+ if (tryGit(['rev-parse', '--verify', '--quiet', candidate], cwd))
76
+ return candidate;
77
+ }
78
+ return undefined;
79
+ }
80
+ /**
81
+ * Resolve the ref to diff against. An explicit `base` wins; otherwise the PR
82
+ * base branch (`origin/$GITHUB_BASE_REF`) when running in GitHub Actions;
83
+ * otherwise the repository's default branch. `undefined` means "no base" and
84
+ * the caller falls back to working-tree changes only.
85
+ */
86
+ function resolveChangedBase(base, cwd = process.cwd()) {
87
+ if (base && base.trim())
88
+ return base.trim();
89
+ const prBase = process.env.GITHUB_BASE_REF;
90
+ if (prBase && prBase.trim()) {
91
+ const ref = `origin/${prBase.trim()}`;
92
+ if (tryGit(['rev-parse', '--verify', '--quiet', ref], cwd))
93
+ return ref;
94
+ return prBase.trim();
95
+ }
96
+ return defaultBranch(cwd);
97
+ }
98
+ /** Parse `git status --porcelain` into paths (rename target wins). */
99
+ function workingTreePaths(cwd) {
100
+ const out = [];
101
+ // `-uall` lists untracked *files*; the default collapses a new directory to
102
+ // the directory name, which would hide every file a new module adds.
103
+ const status = tryGit(['status', '--porcelain', '-uall'], cwd) ?? '';
104
+ for (const rawLine of status.split('\n')) {
105
+ const line = rawLine.trimEnd();
106
+ if (!line)
107
+ continue;
108
+ let p = line.slice(3);
109
+ const arrow = p.indexOf(' -> ');
110
+ if (arrow !== -1)
111
+ p = p.slice(arrow + 4);
112
+ p = p.replace(/^"|"$/g, '');
113
+ if (p)
114
+ out.push(p);
115
+ }
116
+ return out;
117
+ }
118
+ /**
119
+ * Collect the files that differ from `base` (via `git merge-base`) plus any
120
+ * uncommitted/untracked working-tree changes. Falls back to `git diff HEAD`
121
+ * when no base is resolvable or no merge base exists — a shallow clone or a
122
+ * detached CI checkout — rather than failing the run.
123
+ */
124
+ function changedFiles(options = {}) {
125
+ const cwd = options.cwd ?? process.cwd();
126
+ if (!tryGit(['rev-parse', '--git-dir'], cwd)) {
127
+ throw new Error(`--changed needs a git repository; ${cwd} is not inside one`);
128
+ }
129
+ const files = new Set(workingTreePaths(cwd));
130
+ const base = resolveChangedBase(options.base, cwd);
131
+ let mergeBase;
132
+ if (base) {
133
+ const found = tryGit(['merge-base', 'HEAD', base], cwd);
134
+ mergeBase = found?.trim() || undefined;
135
+ }
136
+ // No base, or no common ancestor (shallow clone / detached checkout): the
137
+ // uncommitted diff against HEAD is all the history we can see.
138
+ const diffArgs = mergeBase
139
+ ? ['diff', '--name-only', '--diff-filter=ACMR', mergeBase, 'HEAD']
140
+ : ['diff', '--name-only', '--diff-filter=ACMR', 'HEAD'];
141
+ for (const rawLine of (tryGit(diffArgs, cwd) ?? '').split('\n')) {
142
+ const p = rawLine.trim();
143
+ if (p)
144
+ files.add(p);
145
+ }
146
+ const abs = [];
147
+ for (const rel of files) {
148
+ const full = path.resolve(cwd, rel);
149
+ // Deleted or renamed-away paths have nothing left to lint.
150
+ if ((0, fs_1.existsSync)(full) && (0, fs_1.statSync)(full).isFile())
151
+ abs.push(full);
152
+ }
153
+ return { files: abs.sort(), base, mergeBase };
154
+ }
155
+ /** {@link changedFiles}, narrowed to `.sql`. */
156
+ function changedSqlFiles(options = {}) {
157
+ const result = changedFiles(options);
158
+ return { ...result, files: result.files.filter((f) => f.toLowerCase().endsWith('.sql')) };
159
+ }
package/cli.d.ts CHANGED
@@ -2,11 +2,13 @@
2
2
  /**
3
3
  * `pgsql-lint` — lint SQL source files for the convention rules.
4
4
  *
5
- * pgsql-lint <path…> [--rules a,b] [--warn a,b] [--off a,b] [--json] [--quiet]
5
+ * pgsql-lint <path…> [--changed[=<base>]] [--ignore <globs>] [--rules a,b]
6
+ * [--warn a,b] [--off a,b] [--config <file>] [--json] [--quiet]
6
7
  *
7
8
  * Paths may be files or directories (directories are scanned recursively for
8
- * `.sql`). Exit code is 1 when any *error*-severity finding remains, 0
9
- * otherwise — so it drops straight into a pre-commit hook or CI step. Findings
10
- * downgraded with `--warn` print but never fail the run.
9
+ * `.sql`), and default to `paths` from the config file. Exit code is 1 when any
10
+ * *error*-severity finding remains, 0 otherwise — so it drops straight into a
11
+ * pre-commit hook or CI step. Findings downgraded with `--warn` print but never
12
+ * fail the run.
11
13
  */
12
14
  export {};
package/cli.js CHANGED
@@ -3,19 +3,58 @@
3
3
  /**
4
4
  * `pgsql-lint` — lint SQL source files for the convention rules.
5
5
  *
6
- * pgsql-lint <path…> [--rules a,b] [--warn a,b] [--off a,b] [--json] [--quiet]
6
+ * pgsql-lint <path…> [--changed[=<base>]] [--ignore <globs>] [--rules a,b]
7
+ * [--warn a,b] [--off a,b] [--config <file>] [--json] [--quiet]
7
8
  *
8
9
  * Paths may be files or directories (directories are scanned recursively for
9
- * `.sql`). Exit code is 1 when any *error*-severity finding remains, 0
10
- * otherwise — so it drops straight into a pre-commit hook or CI step. Findings
11
- * downgraded with `--warn` print but never fail the run.
10
+ * `.sql`), and default to `paths` from the config file. Exit code is 1 when any
11
+ * *error*-severity finding remains, 0 otherwise — so it drops straight into a
12
+ * pre-commit hook or CI step. Findings downgraded with `--warn` print but never
13
+ * fail the run.
12
14
  */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
13
48
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
49
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
50
  };
16
51
  Object.defineProperty(exports, "__esModule", { value: true });
17
52
  const chalk_1 = __importDefault(require("chalk"));
53
+ const fs_1 = require("fs");
18
54
  const minimist_1 = __importDefault(require("minimist"));
55
+ const path = __importStar(require("path"));
56
+ const changed_1 = require("./changed");
57
+ const config_1 = require("./config");
19
58
  const file_runner_1 = require("./file-runner");
20
59
  const rules_1 = require("./rules");
21
60
  const HELP = `pgsql-lint — source-level SQL/PL/pgSQL convention linter
@@ -25,17 +64,29 @@ Usage:
25
64
 
26
65
  Arguments:
27
66
  path One or more .sql files or directories (scanned recursively).
67
+ Defaults to "paths" from the config file.
28
68
 
29
69
  Options:
70
+ --changed[=<base>] Lint only .sql files that differ from <base> (default: the
71
+ PR base branch, else the repository default branch), using
72
+ the merge base, plus working-tree changes. Exits 0 when
73
+ nothing changed.
74
+ --ignore <globs> Exclude paths (comma-separated, repeatable).
30
75
  --rules <ids> Comma-separated rule ids/codes to run (default: all).
31
76
  --warn <ids> Report these rules as warnings (do not fail the run).
32
77
  --off <ids> Disable these rules entirely.
33
78
  --keyword <kw> Suppression directive keyword (default: pgsql-lint,safegres).
79
+ --config <file> Config file to use (default: nearest .pgsqllintrc.json).
80
+ --no-config Ignore any config file.
34
81
  --json Emit findings as JSON.
35
82
  --quiet Only print active findings (hide acknowledged waivers).
36
83
  -h, --help Show this help.
37
84
  -v, --version Show version.
38
85
 
86
+ Config file (.pgsqllintrc.json, discovered upward from cwd; flags override it):
87
+ { "extends": "./base.json", "rules": [], "warn": [], "off": [],
88
+ "ignore": ["sql/", "**/generated/**"], "keyword": [], "paths": ["packages"] }
89
+
39
90
  Rules:
40
91
  ${rules_1.LINT_RULES.map((r) => ` ${r.code} ${r.id} — ${r.title}`).join('\n')}
41
92
 
@@ -43,33 +94,114 @@ Suppress inline (in the function body):
43
94
  -- pgsql-lint-disable-next-line no-dynamic-sql -- lookup-only: <why>
44
95
  `;
45
96
  function csv(v) {
46
- if (!v)
97
+ if (v === undefined)
47
98
  return undefined;
48
- const parts = v.split(',').map((s) => s.trim()).filter(Boolean);
99
+ const parts = (Array.isArray(v) ? v : [v])
100
+ .flatMap((s) => s.split(','))
101
+ .map((s) => s.trim())
102
+ .filter(Boolean);
49
103
  return parts.length > 0 ? parts : undefined;
50
104
  }
105
+ /**
106
+ * Own version. In the repo the compiled CLI sits in `dist/`, but the published
107
+ * package flattens `dist/` to its root, so `../package.json` only resolves in
108
+ * one of the two layouts — try both rather than crashing on `--version`.
109
+ */
110
+ function packageVersion() {
111
+ for (const candidate of ['../package.json', './package.json']) {
112
+ try {
113
+ return require(candidate).version;
114
+ }
115
+ catch {
116
+ // Wrong layout; try the next candidate.
117
+ }
118
+ }
119
+ return 'unknown';
120
+ }
121
+ /**
122
+ * `--changed` takes an *optional* value, which minimist cannot express: with
123
+ * `string: ['changed']` it would swallow a following path. Bind the next token
124
+ * only when it is not a flag and not an existing path — i.e. when it reads as a
125
+ * git ref — and otherwise rewrite the flag to an empty value.
126
+ */
127
+ function normalizeChanged(args) {
128
+ const out = [];
129
+ for (let i = 0; i < args.length; i++) {
130
+ if (args[i] !== '--changed') {
131
+ out.push(args[i]);
132
+ continue;
133
+ }
134
+ const next = args[i + 1];
135
+ if (next && !next.startsWith('-') && !(0, fs_1.existsSync)(next)) {
136
+ out.push(`--changed=${next}`);
137
+ i++;
138
+ }
139
+ else {
140
+ out.push('--changed=');
141
+ }
142
+ }
143
+ return out;
144
+ }
51
145
  async function main() {
52
- const argv = (0, minimist_1.default)(process.argv.slice(2), {
146
+ const raw = process.argv.slice(2);
147
+ // Handled here rather than as a minimist boolean: `config` also takes a value.
148
+ const noConfig = raw.includes('--no-config');
149
+ const argv = (0, minimist_1.default)(normalizeChanged(raw.filter((a) => a !== '--no-config')), {
53
150
  boolean: ['json', 'quiet', 'help', 'version'],
54
- string: ['rules', 'warn', 'off', 'keyword'],
151
+ string: ['rules', 'warn', 'off', 'keyword', 'changed', 'ignore', 'config'],
55
152
  alias: { h: 'help', v: 'version' }
56
153
  });
57
154
  if (argv.version) {
58
- console.log(require('../package.json').version);
155
+ console.log(packageVersion());
59
156
  return;
60
157
  }
61
- if (argv.help || argv._.length === 0) {
158
+ const cwd = process.cwd();
159
+ const loaded = noConfig
160
+ ? { config: {} }
161
+ : (0, config_1.loadLintConfig)({ cwd, configFile: argv.config || undefined });
162
+ const fileConfig = loaded.config;
163
+ const baseDir = (0, config_1.configDir)(loaded, cwd);
164
+ const useChanged = argv.changed !== undefined;
165
+ const paths = argv._.length > 0
166
+ ? argv._
167
+ : (fileConfig.paths ?? []).map((p) => path.resolve(baseDir, p));
168
+ if (argv.help || (!useChanged && paths.length === 0)) {
62
169
  console.log(HELP);
63
- process.exit(argv._.length === 0 && !argv.help ? 1 : 0);
170
+ process.exit(argv.help ? 0 : 1);
64
171
  }
65
- const rules = csv(argv.rules);
66
- const keyword = csv(argv.keyword);
172
+ const rules = csv(argv.rules) ?? fileConfig.rules;
173
+ const keyword = csv(argv.keyword) ?? (fileConfig.keyword ? csv(fileConfig.keyword) : undefined);
174
+ const ignore = csv(argv.ignore) ?? fileConfig.ignore;
67
175
  const severity = {};
68
- for (const id of csv(argv.warn) ?? [])
176
+ for (const id of csv(argv.warn) ?? fileConfig.warn ?? [])
69
177
  severity[id] = 'warn';
70
- for (const id of csv(argv.off) ?? [])
178
+ for (const id of csv(argv.off) ?? fileConfig.off ?? [])
71
179
  severity[id] = 'off';
72
- const reports = await (0, file_runner_1.lintFiles)(argv._, { rules, keyword, severity });
180
+ let targets;
181
+ if (useChanged) {
182
+ const changed = (0, changed_1.changedSqlFiles)({ cwd, base: argv.changed || undefined });
183
+ // Nothing to lint is a pass, not an error — the common case on a branch
184
+ // that touched no SQL at all.
185
+ if (changed.files.length === 0) {
186
+ if (argv.json)
187
+ console.log('[]');
188
+ else
189
+ console.log(chalk_1.default.green('0 errors'), chalk_1.default.gray('(no changed .sql files)'));
190
+ process.exit(0);
191
+ }
192
+ targets = paths.length > 0 ? withinPaths(changed.files, paths) : changed.files;
193
+ if (targets.length === 0) {
194
+ if (argv.json)
195
+ console.log('[]');
196
+ else
197
+ console.log(chalk_1.default.green('0 errors'), chalk_1.default.gray('(no changed .sql files under the given paths)'));
198
+ process.exit(0);
199
+ }
200
+ }
201
+ else {
202
+ targets = paths;
203
+ }
204
+ const reports = await (0, file_runner_1.lintFiles)(targets, { rules, keyword, severity, ignore, cwd: baseDir });
73
205
  if (argv.json) {
74
206
  console.log(JSON.stringify(reports, null, 2));
75
207
  }
@@ -109,6 +241,16 @@ async function main() {
109
241
  }
110
242
  process.exit(errors > 0 ? 1 : 0);
111
243
  }
244
+ /** Keep only changed files that sit inside one of the requested paths. */
245
+ function withinPaths(files, paths) {
246
+ const roots = paths.map((p) => path.resolve(p));
247
+ return files.filter((file) => roots.some((root) => {
248
+ if (file === root)
249
+ return true;
250
+ const rel = path.relative(root, file);
251
+ return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
252
+ }));
253
+ }
112
254
  function formatFinding(f) {
113
255
  const loc = chalk_1.default.gray(`${String(f.line).padStart(4)}`);
114
256
  const tag = f.acknowledged
package/config.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Config-file support — so a repository states its lint policy once, in a file,
3
+ * instead of repeating flags in every workflow step and every shell history.
4
+ *
5
+ * The keys mirror the CLI flags exactly (`rules`, `warn`, `off`, `ignore`,
6
+ * `keyword`, `paths`), and a flag always overrides the file. Discovery walks up
7
+ * from `cwd` looking for `.pgsqllintrc.json` / `.pgsqllintrc`.
8
+ *
9
+ * `extends` names another config *file* (a relative path resolved against the
10
+ * file that declared it, or a resolvable npm module). This package has no
11
+ * built-in presets — its rule set is injected as values, not discovered by name
12
+ * — so unlike safegres's `extends: "safegres:constructive"` there is nothing to
13
+ * name but a file.
14
+ */
15
+ /** The shape of `.pgsqllintrc.json`. Every key mirrors a CLI flag. */
16
+ export interface LintConfigFile {
17
+ /** Another config file to inherit from (path, or npm module). */
18
+ extends?: string | string[];
19
+ /** Rule ids/codes to run (default: all). */
20
+ rules?: string[];
21
+ /** Rules reported as warnings. */
22
+ warn?: string[];
23
+ /** Rules disabled entirely. */
24
+ off?: string[];
25
+ /** Glob patterns to exclude. */
26
+ ignore?: string[];
27
+ /** Suppression directive keyword(s). */
28
+ keyword?: string | string[];
29
+ /** Default paths to lint when none are given on the command line. */
30
+ paths?: string[];
31
+ }
32
+ export declare const CONFIG_FILENAMES: readonly [".pgsqllintrc.json", ".pgsqllintrc"];
33
+ /** Walk up from `cwd` for the first config file. */
34
+ export declare function findConfigFile(cwd?: string): string | undefined;
35
+ export interface LoadedLintConfig {
36
+ config: LintConfigFile;
37
+ /** The file the config was read from, when one was found. */
38
+ filepath?: string;
39
+ }
40
+ /**
41
+ * Load the effective config: an explicit `configFile`, else the nearest
42
+ * discovered one, else empty. Paths in `ignore`/`paths` stay as written — the
43
+ * caller resolves them against the config file's directory (see `configDir`).
44
+ */
45
+ export declare function loadLintConfig(params?: {
46
+ cwd?: string;
47
+ configFile?: string;
48
+ }): LoadedLintConfig;
49
+ /** The directory a discovered config's relative paths resolve against. */
50
+ export declare function configDir(loaded: LoadedLintConfig, cwd?: string): string;