@cleartrip/frontguard 0.3.3 → 0.3.4
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 +10 -2
- package/dist/cli.js +115 -75
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -61,8 +61,8 @@ The PR comment includes:
|
|
|
61
61
|
|
|
62
62
|
| Check | What it does | Default |
|
|
63
63
|
|-------|-------------|---------|
|
|
64
|
-
| **eslint** | Runs ESLint with your project config | Enabled |
|
|
65
|
-
| **prettier** | Verifies Prettier formatting | Enabled |
|
|
64
|
+
| **eslint** | Runs ESLint with your project config (skips `frontguard.config.*` by default) | Enabled |
|
|
65
|
+
| **prettier** | Verifies Prettier formatting (skips `frontguard.config.*` by default) | Enabled |
|
|
66
66
|
| **typescript** | Runs `tsc --noEmit` for type errors | Enabled |
|
|
67
67
|
| **secrets** | Scans for leaked tokens, keys, passwords | Enabled |
|
|
68
68
|
| **pr-hygiene** | Validates PR description, sections, AI disclosure | Enabled |
|
|
@@ -168,6 +168,14 @@ export default defineConfig({
|
|
|
168
168
|
|
|
169
169
|
Run `frontguard init` to generate a config file with every option documented as comments.
|
|
170
170
|
|
|
171
|
+
### ESLint / Prettier vs `frontguard.config.*`
|
|
172
|
+
|
|
173
|
+
Defaults exclude `frontguard.config.mjs`, `frontguard.config.js`, and `frontguard.config.cjs` via `checks.eslint.ignorePatterns` and `checks.prettier.ignorePatterns` (picomatch globs, repo-relative). That keeps tool config from being judged by product rules. Set `ignorePatterns: []` on either check if you want those files linted/formatted. To extend the list in shared config, spread `DEFAULT_FRONTGUARD_CONFIG_IGNORE_PATTERNS` from `@cleartrip/frontguard`.
|
|
174
|
+
|
|
175
|
+
### Bitbucket PR comment table
|
|
176
|
+
|
|
177
|
+
The short PR comment lists **only checks that reported findings**. Skipped checks stay out of that table; the full HTML report still shows every check, including skips.
|
|
178
|
+
|
|
171
179
|
### Shared Org Config
|
|
172
180
|
|
|
173
181
|
Publish a shared config package and extend it:
|
package/dist/cli.js
CHANGED
|
@@ -8,8 +8,9 @@ import path6, { sep, normalize, delimiter, resolve, dirname } from 'path';
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import { execFileSync, spawn } from 'child_process';
|
|
10
10
|
import { createRequire } from 'module';
|
|
11
|
-
import
|
|
11
|
+
import fs3 from 'fs';
|
|
12
12
|
import { pathToFileURL } from 'url';
|
|
13
|
+
import picomatch from 'picomatch';
|
|
13
14
|
import fg from 'fast-glob';
|
|
14
15
|
import { pipeline } from 'stream/promises';
|
|
15
16
|
import { PassThrough } from 'stream';
|
|
@@ -2430,16 +2431,28 @@ export default defineConfig({
|
|
|
2430
2431
|
checks: {
|
|
2431
2432
|
// \u2500\u2500\u2500 ESLint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2432
2433
|
// Runs ESLint using your project config on PR-scoped files.
|
|
2434
|
+
// Default ignores frontguard.config.{mjs,js,cjs} (tool config, not app code).
|
|
2433
2435
|
// eslint: {
|
|
2434
2436
|
// enabled: true,
|
|
2435
|
-
// glob: '**/*.{js,cjs,mjs,jsx,ts,tsx}',
|
|
2437
|
+
// glob: '**/*.{js,cjs,mjs,jsx,ts,tsx}',
|
|
2438
|
+
// ignorePatterns: [
|
|
2439
|
+
// '**/frontguard.config.mjs',
|
|
2440
|
+
// '**/frontguard.config.js',
|
|
2441
|
+
// '**/frontguard.config.cjs',
|
|
2442
|
+
// ],
|
|
2436
2443
|
// },
|
|
2437
2444
|
|
|
2438
2445
|
// \u2500\u2500\u2500 Prettier \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2439
2446
|
// Checks that files match Prettier formatting.
|
|
2447
|
+
// Default ignores the same FrontGuard config filenames as ESLint.
|
|
2440
2448
|
// prettier: {
|
|
2441
2449
|
// enabled: true,
|
|
2442
2450
|
// glob: '**/*.{js,cjs,mjs,jsx,ts,tsx,json,md,css,scss,yml,yaml}',
|
|
2451
|
+
// ignorePatterns: [
|
|
2452
|
+
// '**/frontguard.config.mjs',
|
|
2453
|
+
// '**/frontguard.config.js',
|
|
2454
|
+
// '**/frontguard.config.cjs',
|
|
2455
|
+
// ],
|
|
2443
2456
|
// },
|
|
2444
2457
|
|
|
2445
2458
|
// \u2500\u2500\u2500 TypeScript \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -2837,18 +2850,13 @@ function mdTableCell(s3) {
|
|
|
2837
2850
|
return s3.replace(/\|/g, "\\|").replace(/\r?\n/g, " ").trim();
|
|
2838
2851
|
}
|
|
2839
2852
|
function statusForPrTable(r4) {
|
|
2840
|
-
if (r4.skipped) {
|
|
2841
|
-
const t3 = r4.skipped.replace(/\s+/g, " ").trim();
|
|
2842
|
-
const short = t3.length > 120 ? `${t3.slice(0, 117)}\u2026` : t3;
|
|
2843
|
-
return `Skipped \u2014 ${short}`;
|
|
2844
|
-
}
|
|
2845
2853
|
const n3 = r4.findings.length;
|
|
2846
2854
|
const blocks = r4.findings.filter((f4) => f4.severity === "block").length;
|
|
2847
2855
|
if (blocks > 0) return `${n3} issue(s), ${blocks} blocking`;
|
|
2848
2856
|
return `${n3} issue(s)`;
|
|
2849
2857
|
}
|
|
2850
2858
|
function checkNeedsRow(r4) {
|
|
2851
|
-
return
|
|
2859
|
+
return !r4.skipped && r4.findings.length > 0;
|
|
2852
2860
|
}
|
|
2853
2861
|
function formatBitbucketPrSnippet(report) {
|
|
2854
2862
|
const publicReport = process.env.FRONTGUARD_PUBLIC_REPORT_URL?.trim();
|
|
@@ -3067,16 +3075,99 @@ function createDefu2(merger) {
|
|
|
3067
3075
|
);
|
|
3068
3076
|
}
|
|
3069
3077
|
var defu2 = createDefu2();
|
|
3078
|
+
function normalizePrPath(p2) {
|
|
3079
|
+
return p2.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3080
|
+
}
|
|
3081
|
+
function hasPrFileList(pr) {
|
|
3082
|
+
return Boolean(pr?.files?.length);
|
|
3083
|
+
}
|
|
3084
|
+
function prPathSet(pr) {
|
|
3085
|
+
if (!hasPrFileList(pr)) return null;
|
|
3086
|
+
return new Set(pr.files.map(normalizePrPath));
|
|
3087
|
+
}
|
|
3088
|
+
function isPathInPrScope(relOrAbs, cwd, prSet) {
|
|
3089
|
+
const raw = relOrAbs.replace(/^file:\/\//, "");
|
|
3090
|
+
let rel = normalizePrPath(raw);
|
|
3091
|
+
if (path6.isAbsolute(raw)) {
|
|
3092
|
+
rel = normalizePrPath(path6.relative(cwd, raw));
|
|
3093
|
+
}
|
|
3094
|
+
if (prSet.has(rel)) return true;
|
|
3095
|
+
const trimmed = rel.replace(/^\.\//, "");
|
|
3096
|
+
if (prSet.has(trimmed)) return true;
|
|
3097
|
+
return false;
|
|
3098
|
+
}
|
|
3099
|
+
var ESLINT_EXT = /\.(js|cjs|mjs|jsx|ts|tsx)$/i;
|
|
3100
|
+
var PRETTIER_EXT = /\.(js|cjs|mjs|jsx|ts|tsx|json|md|css|scss|sass|less|yml|yaml|vue|svelte)$/i;
|
|
3101
|
+
var CYCLES_EXT = /\.(tsx?|jsx?|mjs|cjs|js)$/i;
|
|
3102
|
+
function filterPrFilesForEslint(pr) {
|
|
3103
|
+
if (!hasPrFileList(pr)) return null;
|
|
3104
|
+
return pr.files.map(normalizePrPath).filter((f4) => ESLINT_EXT.test(f4));
|
|
3105
|
+
}
|
|
3106
|
+
function filterPrFilesForPrettier(pr) {
|
|
3107
|
+
if (!hasPrFileList(pr)) return null;
|
|
3108
|
+
return pr.files.map(normalizePrPath).filter((f4) => PRETTIER_EXT.test(f4));
|
|
3109
|
+
}
|
|
3110
|
+
function filterPrFilesForMadge(pr) {
|
|
3111
|
+
if (!hasPrFileList(pr)) return null;
|
|
3112
|
+
return pr.files.map(normalizePrPath).filter((f4) => CYCLES_EXT.test(f4));
|
|
3113
|
+
}
|
|
3114
|
+
async function existingRepoPaths(cwd, rels) {
|
|
3115
|
+
const out = [];
|
|
3116
|
+
for (const rel of rels) {
|
|
3117
|
+
try {
|
|
3118
|
+
await fs.access(path6.join(cwd, rel));
|
|
3119
|
+
out.push(rel);
|
|
3120
|
+
} catch {
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
return out;
|
|
3124
|
+
}
|
|
3125
|
+
function filterTscOutputToPrFiles(output, cwd, prSet) {
|
|
3126
|
+
const lines = output.split("\n");
|
|
3127
|
+
const kept = [];
|
|
3128
|
+
for (const line of lines) {
|
|
3129
|
+
const m3 = /^(.+?)\(\d+,\d+\):\s/.exec(line);
|
|
3130
|
+
if (m3?.[1]) {
|
|
3131
|
+
if (isPathInPrScope(m3[1], cwd, prSet)) kept.push(line);
|
|
3132
|
+
continue;
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
return kept.join("\n").trim();
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
// src/lib/glob-ignore.ts
|
|
3139
|
+
var DEFAULT_FRONTGUARD_CONFIG_IGNORE_PATTERNS = [
|
|
3140
|
+
"**/frontguard.config.mjs",
|
|
3141
|
+
"**/frontguard.config.js",
|
|
3142
|
+
"**/frontguard.config.cjs"
|
|
3143
|
+
];
|
|
3144
|
+
function pathMatchesIgnorePatterns(relPath, patterns) {
|
|
3145
|
+
if (patterns.length === 0) return false;
|
|
3146
|
+
const posixRel = normalizePrPath(relPath);
|
|
3147
|
+
for (const pattern of patterns) {
|
|
3148
|
+
const pm = picomatch(pattern, { dot: true });
|
|
3149
|
+
if (pm(posixRel) || pm(`./${posixRel}`)) return true;
|
|
3150
|
+
}
|
|
3151
|
+
return false;
|
|
3152
|
+
}
|
|
3153
|
+
function negatedPrettierGlobs(patterns) {
|
|
3154
|
+
return patterns.map((p2) => p2.startsWith("!") ? p2 : `!${p2}`);
|
|
3155
|
+
}
|
|
3070
3156
|
|
|
3071
3157
|
// src/config/defaults.ts
|
|
3072
3158
|
var defaultConfig = {
|
|
3073
3159
|
mode: "warn",
|
|
3074
3160
|
rules: {},
|
|
3075
3161
|
checks: {
|
|
3076
|
-
eslint: {
|
|
3162
|
+
eslint: {
|
|
3163
|
+
enabled: true,
|
|
3164
|
+
glob: "**/*.{js,cjs,mjs,jsx,ts,tsx}",
|
|
3165
|
+
ignorePatterns: [...DEFAULT_FRONTGUARD_CONFIG_IGNORE_PATTERNS]
|
|
3166
|
+
},
|
|
3077
3167
|
prettier: {
|
|
3078
3168
|
enabled: true,
|
|
3079
|
-
glob: "**/*.{js,cjs,mjs,jsx,ts,tsx,json,md,css,scss,yml,yaml}"
|
|
3169
|
+
glob: "**/*.{js,cjs,mjs,jsx,ts,tsx,json,md,css,scss,yml,yaml}",
|
|
3170
|
+
ignorePatterns: [...DEFAULT_FRONTGUARD_CONFIG_IGNORE_PATTERNS]
|
|
3080
3171
|
},
|
|
3081
3172
|
typescript: { enabled: true },
|
|
3082
3173
|
secrets: { enabled: true },
|
|
@@ -3203,7 +3294,7 @@ async function loadConfig(cwd) {
|
|
|
3203
3294
|
const loadErrors = [];
|
|
3204
3295
|
for (const name of CONFIG_NAMES) {
|
|
3205
3296
|
const full = path6.join(cwd, name);
|
|
3206
|
-
if (!
|
|
3297
|
+
if (!fs3.existsSync(full)) continue;
|
|
3207
3298
|
try {
|
|
3208
3299
|
const mod = await importConfig(full);
|
|
3209
3300
|
userFile = normalizeExport(mod);
|
|
@@ -3374,65 +3465,6 @@ function stripFrontmatter(content) {
|
|
|
3374
3465
|
if (endIdx === -1) return content;
|
|
3375
3466
|
return content.slice(endIdx + 3).trim();
|
|
3376
3467
|
}
|
|
3377
|
-
function normalizePrPath(p2) {
|
|
3378
|
-
return p2.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3379
|
-
}
|
|
3380
|
-
function hasPrFileList(pr) {
|
|
3381
|
-
return Boolean(pr?.files?.length);
|
|
3382
|
-
}
|
|
3383
|
-
function prPathSet(pr) {
|
|
3384
|
-
if (!hasPrFileList(pr)) return null;
|
|
3385
|
-
return new Set(pr.files.map(normalizePrPath));
|
|
3386
|
-
}
|
|
3387
|
-
function isPathInPrScope(relOrAbs, cwd, prSet) {
|
|
3388
|
-
const raw = relOrAbs.replace(/^file:\/\//, "");
|
|
3389
|
-
let rel = normalizePrPath(raw);
|
|
3390
|
-
if (path6.isAbsolute(raw)) {
|
|
3391
|
-
rel = normalizePrPath(path6.relative(cwd, raw));
|
|
3392
|
-
}
|
|
3393
|
-
if (prSet.has(rel)) return true;
|
|
3394
|
-
const trimmed = rel.replace(/^\.\//, "");
|
|
3395
|
-
if (prSet.has(trimmed)) return true;
|
|
3396
|
-
return false;
|
|
3397
|
-
}
|
|
3398
|
-
var ESLINT_EXT = /\.(js|cjs|mjs|jsx|ts|tsx)$/i;
|
|
3399
|
-
var PRETTIER_EXT = /\.(js|cjs|mjs|jsx|ts|tsx|json|md|css|scss|sass|less|yml|yaml|vue|svelte)$/i;
|
|
3400
|
-
var CYCLES_EXT = /\.(tsx?|jsx?|mjs|cjs|js)$/i;
|
|
3401
|
-
function filterPrFilesForEslint(pr) {
|
|
3402
|
-
if (!hasPrFileList(pr)) return null;
|
|
3403
|
-
return pr.files.map(normalizePrPath).filter((f4) => ESLINT_EXT.test(f4));
|
|
3404
|
-
}
|
|
3405
|
-
function filterPrFilesForPrettier(pr) {
|
|
3406
|
-
if (!hasPrFileList(pr)) return null;
|
|
3407
|
-
return pr.files.map(normalizePrPath).filter((f4) => PRETTIER_EXT.test(f4));
|
|
3408
|
-
}
|
|
3409
|
-
function filterPrFilesForMadge(pr) {
|
|
3410
|
-
if (!hasPrFileList(pr)) return null;
|
|
3411
|
-
return pr.files.map(normalizePrPath).filter((f4) => CYCLES_EXT.test(f4));
|
|
3412
|
-
}
|
|
3413
|
-
async function existingRepoPaths(cwd, rels) {
|
|
3414
|
-
const out = [];
|
|
3415
|
-
for (const rel of rels) {
|
|
3416
|
-
try {
|
|
3417
|
-
await fs.access(path6.join(cwd, rel));
|
|
3418
|
-
out.push(rel);
|
|
3419
|
-
} catch {
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
return out;
|
|
3423
|
-
}
|
|
3424
|
-
function filterTscOutputToPrFiles(output, cwd, prSet) {
|
|
3425
|
-
const lines = output.split("\n");
|
|
3426
|
-
const kept = [];
|
|
3427
|
-
for (const line of lines) {
|
|
3428
|
-
const m3 = /^(.+?)\(\d+,\d+\):\s/.exec(line);
|
|
3429
|
-
if (m3?.[1]) {
|
|
3430
|
-
if (isPathInPrScope(m3[1], cwd, prSet)) kept.push(line);
|
|
3431
|
-
continue;
|
|
3432
|
-
}
|
|
3433
|
-
}
|
|
3434
|
-
return kept.join("\n").trim();
|
|
3435
|
-
}
|
|
3436
3468
|
function stripFileUrl(p2) {
|
|
3437
3469
|
let s3 = p2.trim();
|
|
3438
3470
|
if (!/^file:/i.test(s3)) return s3;
|
|
@@ -4230,6 +4262,8 @@ async function runEslint(cwd, config, _stack, pr) {
|
|
|
4230
4262
|
};
|
|
4231
4263
|
}
|
|
4232
4264
|
const glob = config.checks.eslint.glob ?? "**/*.{js,cjs,mjs,jsx,ts,tsx}";
|
|
4265
|
+
const ignorePatterns = config.checks.eslint.ignorePatterns ?? DEFAULT_FRONTGUARD_CONFIG_IGNORE_PATTERNS;
|
|
4266
|
+
const ignoreArgs = ignorePatterns.flatMap((p2) => ["--ignore-pattern", p2]);
|
|
4233
4267
|
const prPaths = filterPrFilesForEslint(pr);
|
|
4234
4268
|
let exitCode;
|
|
4235
4269
|
let stdout2;
|
|
@@ -4244,7 +4278,8 @@ async function runEslint(cwd, config, _stack, pr) {
|
|
|
4244
4278
|
};
|
|
4245
4279
|
}
|
|
4246
4280
|
const existing = await existingRepoPaths(cwd, prPaths);
|
|
4247
|
-
|
|
4281
|
+
const lintable = existing.filter((p2) => !pathMatchesIgnorePatterns(p2, ignorePatterns));
|
|
4282
|
+
if (lintable.length === 0) {
|
|
4248
4283
|
return {
|
|
4249
4284
|
checkId: "eslint",
|
|
4250
4285
|
findings: [],
|
|
@@ -4252,10 +4287,11 @@ async function runEslint(cwd, config, _stack, pr) {
|
|
|
4252
4287
|
skipped: hasPrFileList(pr) ? "no lintable files in PR diff (added/modified only)" : "no files"
|
|
4253
4288
|
};
|
|
4254
4289
|
}
|
|
4255
|
-
({ exitCode, stdout: stdout2, stderr } = await runEslintOnPaths(cwd,
|
|
4290
|
+
({ exitCode, stdout: stdout2, stderr } = await runEslintOnPaths(cwd, lintable));
|
|
4256
4291
|
} else {
|
|
4257
4292
|
({ exitCode, stdout: stdout2, stderr } = await runNpmBinary(cwd, "eslint", [
|
|
4258
4293
|
glob,
|
|
4294
|
+
...ignoreArgs,
|
|
4259
4295
|
"--max-warnings",
|
|
4260
4296
|
"0",
|
|
4261
4297
|
"--no-error-on-unmatched-pattern",
|
|
@@ -4357,6 +4393,8 @@ async function runPrettier(cwd, config, pr) {
|
|
|
4357
4393
|
};
|
|
4358
4394
|
}
|
|
4359
4395
|
const glob = config.checks.prettier.glob ?? "**/*.{js,cjs,mjs,jsx,ts,tsx,json,md,css,scss,yml,yaml}";
|
|
4396
|
+
const ignorePatterns = config.checks.prettier.ignorePatterns ?? DEFAULT_FRONTGUARD_CONFIG_IGNORE_PATTERNS;
|
|
4397
|
+
const prettierNegations = negatedPrettierGlobs(ignorePatterns);
|
|
4360
4398
|
let exitCode;
|
|
4361
4399
|
let stdout2;
|
|
4362
4400
|
let stderr;
|
|
@@ -4371,7 +4409,8 @@ async function runPrettier(cwd, config, pr) {
|
|
|
4371
4409
|
};
|
|
4372
4410
|
}
|
|
4373
4411
|
const existing = await existingRepoPaths(cwd, prPaths);
|
|
4374
|
-
|
|
4412
|
+
const formattable = existing.filter((p2) => !pathMatchesIgnorePatterns(p2, ignorePatterns));
|
|
4413
|
+
if (formattable.length === 0) {
|
|
4375
4414
|
return {
|
|
4376
4415
|
checkId: "prettier",
|
|
4377
4416
|
findings: [],
|
|
@@ -4379,12 +4418,13 @@ async function runPrettier(cwd, config, pr) {
|
|
|
4379
4418
|
skipped: hasPrFileList(pr) ? "no formattable files in PR diff" : "no files"
|
|
4380
4419
|
};
|
|
4381
4420
|
}
|
|
4382
|
-
({ exitCode, stdout: stdout2, stderr } = await runPrettierOnPaths(cwd,
|
|
4421
|
+
({ exitCode, stdout: stdout2, stderr } = await runPrettierOnPaths(cwd, formattable));
|
|
4383
4422
|
} else {
|
|
4384
4423
|
({ exitCode, stdout: stdout2, stderr } = await runNpmBinary(cwd, "prettier", [
|
|
4385
4424
|
"--check",
|
|
4425
|
+
"--ignore-unknown",
|
|
4386
4426
|
glob,
|
|
4387
|
-
|
|
4427
|
+
...prettierNegations
|
|
4388
4428
|
]));
|
|
4389
4429
|
}
|
|
4390
4430
|
const findings = [];
|