@cleartrip/frontguard 0.3.2 → 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 +33 -7
- package/dist/cli.js +150 -84
- 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
|
@@ -27,7 +27,7 @@ npx frontguard init
|
|
|
27
27
|
|
|
28
28
|
This does three things:
|
|
29
29
|
|
|
30
|
-
- **Creates `frontguard.config.
|
|
30
|
+
- **Creates `frontguard.config.mjs`** (ESM) with all available options as commented examples — works even when `package.json` is not `"type": "module"`
|
|
31
31
|
- **Creates `pull_request_template.md`** with AI disclosure checkboxes
|
|
32
32
|
- **Merges the FrontGuard step** into your existing `bitbucket-pipelines.yml` (or creates one if missing)
|
|
33
33
|
|
|
@@ -35,7 +35,7 @@ This does three things:
|
|
|
35
35
|
|
|
36
36
|
### 3. Configure
|
|
37
37
|
|
|
38
|
-
Open `frontguard.config.
|
|
38
|
+
Open `frontguard.config.mjs` and uncomment the checks you want. Every option is documented inline.
|
|
39
39
|
|
|
40
40
|
### 4. Set Up CI
|
|
41
41
|
|
|
@@ -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 |
|
|
@@ -95,7 +95,7 @@ The bundle check supports multiple strategies for extracting the size metric:
|
|
|
95
95
|
|
|
96
96
|
**Next.js (auto-detected):**
|
|
97
97
|
```js
|
|
98
|
-
// frontguard.config.
|
|
98
|
+
// frontguard.config.mjs — no strategy override needed
|
|
99
99
|
bundle: {
|
|
100
100
|
buildCommand: 'yarn build',
|
|
101
101
|
maxDeltaBytes: 50_000,
|
|
@@ -122,7 +122,25 @@ Commit this to your base branch. FrontGuard compares the current build against i
|
|
|
122
122
|
|
|
123
123
|
## Configuration
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
FrontGuard loads the first file that exists, in order: **`frontguard.config.mjs`** → **`frontguard.config.cjs`** → **`frontguard.config.js`**.
|
|
126
|
+
|
|
127
|
+
- **`.mjs`** — ESM (`import` / `export default`). Use this in CommonJS-only repos (no `"type": "module"` needed). **Recommended.**
|
|
128
|
+
- **`.cjs`** — CommonJS without importing the package (plain object merges like `defineConfig`):
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
// frontguard.config.cjs
|
|
132
|
+
module.exports = {
|
|
133
|
+
checks: {
|
|
134
|
+
bundle: { buildCommand: 'yarn build:prod', bundleSizeStrategy: 'next' },
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- **`.js`** — Only reliable as ESM if your `package.json` has `"type": "module"`; otherwise Node may refuse to load it (you will see an ESM warning and defaults will apply).
|
|
140
|
+
|
|
141
|
+
**CI / monorepos:** Run `frontguard` with `cwd` set to the app root that contains `package.json` and the config file. **Git:** If you see `fatal: Needed a single revision`, increase clone depth or fetch the PR destination branch (e.g. `git fetch origin main:refs/remotes/origin/main`) so diff and baseline reads can resolve `origin/<branch>`.
|
|
142
|
+
|
|
143
|
+
All configuration lives in one of those files at your project root (same directory you run `frontguard` from):
|
|
126
144
|
|
|
127
145
|
```js
|
|
128
146
|
import { defineConfig } from '@cleartrip/frontguard'
|
|
@@ -150,6 +168,14 @@ export default defineConfig({
|
|
|
150
168
|
|
|
151
169
|
Run `frontguard init` to generate a config file with every option documented as comments.
|
|
152
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
|
+
|
|
153
179
|
### Shared Org Config
|
|
154
180
|
|
|
155
181
|
Publish a shared config package and extend it:
|
|
@@ -235,7 +261,7 @@ These features are **implemented in code but turned off by default** so day-to-d
|
|
|
235
261
|
|
|
236
262
|
When you are ready to try them:
|
|
237
263
|
|
|
238
|
-
1. Set `checks.aiAssistedReview.enabled: true` for static heuristics on `@frontguard-ai` regions or AI-disclosed PRs (see `frontguard.config.
|
|
264
|
+
1. Set `checks.aiAssistedReview.enabled: true` for static heuristics on `@frontguard-ai` regions or AI-disclosed PRs (see `frontguard.config.mjs` comments).
|
|
239
265
|
2. Set `checks.llm.enabled: true` and choose `provider: 'ollama' | 'openai' | 'anthropic'` for automated review text.
|
|
240
266
|
|
|
241
267
|
**PR template:** The init flow still adds an **AI disclosure** section to `pull_request_template.md` for human reviewers. With AI-assisted review off, that disclosure is recorded as an info finding and does not enable extra static checks until you opt in.
|
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
|
|
@@ -2768,12 +2781,20 @@ function findEndOfLastSection(lines, pipelinesIdx) {
|
|
|
2768
2781
|
}
|
|
2769
2782
|
async function initFrontGuard(cwd) {
|
|
2770
2783
|
const actions = [];
|
|
2771
|
-
const
|
|
2772
|
-
|
|
2784
|
+
const cfgCandidates = [
|
|
2785
|
+
"frontguard.config.mjs",
|
|
2786
|
+
"frontguard.config.js",
|
|
2787
|
+
"frontguard.config.cjs"
|
|
2788
|
+
];
|
|
2789
|
+
const cfgPath = path6.join(cwd, "frontguard.config.mjs");
|
|
2790
|
+
const hasAny = await Promise.all(
|
|
2791
|
+
cfgCandidates.map((n3) => fileExists(path6.join(cwd, n3)))
|
|
2792
|
+
).then((xs) => xs.some(Boolean));
|
|
2793
|
+
if (!hasAny) {
|
|
2773
2794
|
await fs.writeFile(cfgPath, CONFIG_TEMPLATE, "utf8");
|
|
2774
|
-
actions.push("created frontguard.config.
|
|
2795
|
+
actions.push("created frontguard.config.mjs");
|
|
2775
2796
|
} else {
|
|
2776
|
-
actions.push("frontguard
|
|
2797
|
+
actions.push("frontguard config already exists (.mjs / .js / .cjs) \u2014 skipped");
|
|
2777
2798
|
}
|
|
2778
2799
|
const prTplPath = path6.join(cwd, "pull_request_template.md");
|
|
2779
2800
|
if (!await fileExists(prTplPath)) {
|
|
@@ -2829,18 +2850,13 @@ function mdTableCell(s3) {
|
|
|
2829
2850
|
return s3.replace(/\|/g, "\\|").replace(/\r?\n/g, " ").trim();
|
|
2830
2851
|
}
|
|
2831
2852
|
function statusForPrTable(r4) {
|
|
2832
|
-
if (r4.skipped) {
|
|
2833
|
-
const t3 = r4.skipped.replace(/\s+/g, " ").trim();
|
|
2834
|
-
const short = t3.length > 120 ? `${t3.slice(0, 117)}\u2026` : t3;
|
|
2835
|
-
return `Skipped \u2014 ${short}`;
|
|
2836
|
-
}
|
|
2837
2853
|
const n3 = r4.findings.length;
|
|
2838
2854
|
const blocks = r4.findings.filter((f4) => f4.severity === "block").length;
|
|
2839
2855
|
if (blocks > 0) return `${n3} issue(s), ${blocks} blocking`;
|
|
2840
2856
|
return `${n3} issue(s)`;
|
|
2841
2857
|
}
|
|
2842
2858
|
function checkNeedsRow(r4) {
|
|
2843
|
-
return
|
|
2859
|
+
return !r4.skipped && r4.findings.length > 0;
|
|
2844
2860
|
}
|
|
2845
2861
|
function formatBitbucketPrSnippet(report) {
|
|
2846
2862
|
const publicReport = process.env.FRONTGUARD_PUBLIC_REPORT_URL?.trim();
|
|
@@ -3059,16 +3075,99 @@ function createDefu2(merger) {
|
|
|
3059
3075
|
);
|
|
3060
3076
|
}
|
|
3061
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
|
+
}
|
|
3062
3156
|
|
|
3063
3157
|
// src/config/defaults.ts
|
|
3064
3158
|
var defaultConfig = {
|
|
3065
3159
|
mode: "warn",
|
|
3066
3160
|
rules: {},
|
|
3067
3161
|
checks: {
|
|
3068
|
-
eslint: {
|
|
3162
|
+
eslint: {
|
|
3163
|
+
enabled: true,
|
|
3164
|
+
glob: "**/*.{js,cjs,mjs,jsx,ts,tsx}",
|
|
3165
|
+
ignorePatterns: [...DEFAULT_FRONTGUARD_CONFIG_IGNORE_PATTERNS]
|
|
3166
|
+
},
|
|
3069
3167
|
prettier: {
|
|
3070
3168
|
enabled: true,
|
|
3071
|
-
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]
|
|
3072
3171
|
},
|
|
3073
3172
|
typescript: { enabled: true },
|
|
3074
3173
|
secrets: { enabled: true },
|
|
@@ -3154,9 +3253,9 @@ function migrateLegacyConfigKeys(config) {
|
|
|
3154
3253
|
|
|
3155
3254
|
// src/config/load.ts
|
|
3156
3255
|
var CONFIG_NAMES = [
|
|
3157
|
-
"frontguard.config.js",
|
|
3158
3256
|
"frontguard.config.mjs",
|
|
3159
|
-
"frontguard.config.cjs"
|
|
3257
|
+
"frontguard.config.cjs",
|
|
3258
|
+
"frontguard.config.js"
|
|
3160
3259
|
];
|
|
3161
3260
|
async function importConfig(absolutePath) {
|
|
3162
3261
|
const url = pathToFileURL(absolutePath).href;
|
|
@@ -3192,14 +3291,17 @@ async function loadExtendsLayer(cwd, spec) {
|
|
|
3192
3291
|
}
|
|
3193
3292
|
async function loadConfig(cwd) {
|
|
3194
3293
|
let userFile = null;
|
|
3294
|
+
const loadErrors = [];
|
|
3195
3295
|
for (const name of CONFIG_NAMES) {
|
|
3196
3296
|
const full = path6.join(cwd, name);
|
|
3197
|
-
if (!
|
|
3297
|
+
if (!fs3.existsSync(full)) continue;
|
|
3198
3298
|
try {
|
|
3199
3299
|
const mod = await importConfig(full);
|
|
3200
3300
|
userFile = normalizeExport(mod);
|
|
3201
3301
|
break;
|
|
3202
|
-
} catch {
|
|
3302
|
+
} catch (e3) {
|
|
3303
|
+
const msg = e3 instanceof Error ? e3.message : String(e3);
|
|
3304
|
+
loadErrors.push(`${name}: ${msg}`);
|
|
3203
3305
|
continue;
|
|
3204
3306
|
}
|
|
3205
3307
|
}
|
|
@@ -3208,6 +3310,21 @@ async function loadConfig(cwd) {
|
|
|
3208
3310
|
migrateLegacyConfigKeys(orgLayer);
|
|
3209
3311
|
if (userFile) migrateLegacyConfigKeys(userFile);
|
|
3210
3312
|
const user = userFile ? stripExtends(userFile) : {};
|
|
3313
|
+
if (!userFile) {
|
|
3314
|
+
if (loadErrors.length > 0) {
|
|
3315
|
+
g.stderr.write(
|
|
3316
|
+
`FrontGuard: config file(s) exist under ${path6.resolve(cwd)} but failed to load:
|
|
3317
|
+
${loadErrors.map((l3) => ` \u2022 ${l3}`).join("\n")}
|
|
3318
|
+
Common fix: use frontguard.config.mjs (ESM, no package.json "type" required), or frontguard.config.cjs with require(). See README.
|
|
3319
|
+
`
|
|
3320
|
+
);
|
|
3321
|
+
} else {
|
|
3322
|
+
g.stderr.write(
|
|
3323
|
+
`FrontGuard: no ${CONFIG_NAMES.join(", ")} found under ${path6.resolve(cwd)} \u2014 using built-in defaults only (e.g. checks.bundle.buildCommand defaults to "npm run build"). Add a config file or run from the directory that contains it.
|
|
3324
|
+
`
|
|
3325
|
+
);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3211
3328
|
const base = structuredClone(defaultConfig);
|
|
3212
3329
|
const withOrg = defu2(orgLayer, base);
|
|
3213
3330
|
return defu2(user, withOrg);
|
|
@@ -3348,65 +3465,6 @@ function stripFrontmatter(content) {
|
|
|
3348
3465
|
if (endIdx === -1) return content;
|
|
3349
3466
|
return content.slice(endIdx + 3).trim();
|
|
3350
3467
|
}
|
|
3351
|
-
function normalizePrPath(p2) {
|
|
3352
|
-
return p2.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3353
|
-
}
|
|
3354
|
-
function hasPrFileList(pr) {
|
|
3355
|
-
return Boolean(pr?.files?.length);
|
|
3356
|
-
}
|
|
3357
|
-
function prPathSet(pr) {
|
|
3358
|
-
if (!hasPrFileList(pr)) return null;
|
|
3359
|
-
return new Set(pr.files.map(normalizePrPath));
|
|
3360
|
-
}
|
|
3361
|
-
function isPathInPrScope(relOrAbs, cwd, prSet) {
|
|
3362
|
-
const raw = relOrAbs.replace(/^file:\/\//, "");
|
|
3363
|
-
let rel = normalizePrPath(raw);
|
|
3364
|
-
if (path6.isAbsolute(raw)) {
|
|
3365
|
-
rel = normalizePrPath(path6.relative(cwd, raw));
|
|
3366
|
-
}
|
|
3367
|
-
if (prSet.has(rel)) return true;
|
|
3368
|
-
const trimmed = rel.replace(/^\.\//, "");
|
|
3369
|
-
if (prSet.has(trimmed)) return true;
|
|
3370
|
-
return false;
|
|
3371
|
-
}
|
|
3372
|
-
var ESLINT_EXT = /\.(js|cjs|mjs|jsx|ts|tsx)$/i;
|
|
3373
|
-
var PRETTIER_EXT = /\.(js|cjs|mjs|jsx|ts|tsx|json|md|css|scss|sass|less|yml|yaml|vue|svelte)$/i;
|
|
3374
|
-
var CYCLES_EXT = /\.(tsx?|jsx?|mjs|cjs|js)$/i;
|
|
3375
|
-
function filterPrFilesForEslint(pr) {
|
|
3376
|
-
if (!hasPrFileList(pr)) return null;
|
|
3377
|
-
return pr.files.map(normalizePrPath).filter((f4) => ESLINT_EXT.test(f4));
|
|
3378
|
-
}
|
|
3379
|
-
function filterPrFilesForPrettier(pr) {
|
|
3380
|
-
if (!hasPrFileList(pr)) return null;
|
|
3381
|
-
return pr.files.map(normalizePrPath).filter((f4) => PRETTIER_EXT.test(f4));
|
|
3382
|
-
}
|
|
3383
|
-
function filterPrFilesForMadge(pr) {
|
|
3384
|
-
if (!hasPrFileList(pr)) return null;
|
|
3385
|
-
return pr.files.map(normalizePrPath).filter((f4) => CYCLES_EXT.test(f4));
|
|
3386
|
-
}
|
|
3387
|
-
async function existingRepoPaths(cwd, rels) {
|
|
3388
|
-
const out = [];
|
|
3389
|
-
for (const rel of rels) {
|
|
3390
|
-
try {
|
|
3391
|
-
await fs.access(path6.join(cwd, rel));
|
|
3392
|
-
out.push(rel);
|
|
3393
|
-
} catch {
|
|
3394
|
-
}
|
|
3395
|
-
}
|
|
3396
|
-
return out;
|
|
3397
|
-
}
|
|
3398
|
-
function filterTscOutputToPrFiles(output, cwd, prSet) {
|
|
3399
|
-
const lines = output.split("\n");
|
|
3400
|
-
const kept = [];
|
|
3401
|
-
for (const line of lines) {
|
|
3402
|
-
const m3 = /^(.+?)\(\d+,\d+\):\s/.exec(line);
|
|
3403
|
-
if (m3?.[1]) {
|
|
3404
|
-
if (isPathInPrScope(m3[1], cwd, prSet)) kept.push(line);
|
|
3405
|
-
continue;
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
return kept.join("\n").trim();
|
|
3409
|
-
}
|
|
3410
3468
|
function stripFileUrl(p2) {
|
|
3411
3469
|
let s3 = p2.trim();
|
|
3412
3470
|
if (!/^file:/i.test(s3)) return s3;
|
|
@@ -4204,6 +4262,8 @@ async function runEslint(cwd, config, _stack, pr) {
|
|
|
4204
4262
|
};
|
|
4205
4263
|
}
|
|
4206
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]);
|
|
4207
4267
|
const prPaths = filterPrFilesForEslint(pr);
|
|
4208
4268
|
let exitCode;
|
|
4209
4269
|
let stdout2;
|
|
@@ -4218,7 +4278,8 @@ async function runEslint(cwd, config, _stack, pr) {
|
|
|
4218
4278
|
};
|
|
4219
4279
|
}
|
|
4220
4280
|
const existing = await existingRepoPaths(cwd, prPaths);
|
|
4221
|
-
|
|
4281
|
+
const lintable = existing.filter((p2) => !pathMatchesIgnorePatterns(p2, ignorePatterns));
|
|
4282
|
+
if (lintable.length === 0) {
|
|
4222
4283
|
return {
|
|
4223
4284
|
checkId: "eslint",
|
|
4224
4285
|
findings: [],
|
|
@@ -4226,10 +4287,11 @@ async function runEslint(cwd, config, _stack, pr) {
|
|
|
4226
4287
|
skipped: hasPrFileList(pr) ? "no lintable files in PR diff (added/modified only)" : "no files"
|
|
4227
4288
|
};
|
|
4228
4289
|
}
|
|
4229
|
-
({ exitCode, stdout: stdout2, stderr } = await runEslintOnPaths(cwd,
|
|
4290
|
+
({ exitCode, stdout: stdout2, stderr } = await runEslintOnPaths(cwd, lintable));
|
|
4230
4291
|
} else {
|
|
4231
4292
|
({ exitCode, stdout: stdout2, stderr } = await runNpmBinary(cwd, "eslint", [
|
|
4232
4293
|
glob,
|
|
4294
|
+
...ignoreArgs,
|
|
4233
4295
|
"--max-warnings",
|
|
4234
4296
|
"0",
|
|
4235
4297
|
"--no-error-on-unmatched-pattern",
|
|
@@ -4331,6 +4393,8 @@ async function runPrettier(cwd, config, pr) {
|
|
|
4331
4393
|
};
|
|
4332
4394
|
}
|
|
4333
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);
|
|
4334
4398
|
let exitCode;
|
|
4335
4399
|
let stdout2;
|
|
4336
4400
|
let stderr;
|
|
@@ -4345,7 +4409,8 @@ async function runPrettier(cwd, config, pr) {
|
|
|
4345
4409
|
};
|
|
4346
4410
|
}
|
|
4347
4411
|
const existing = await existingRepoPaths(cwd, prPaths);
|
|
4348
|
-
|
|
4412
|
+
const formattable = existing.filter((p2) => !pathMatchesIgnorePatterns(p2, ignorePatterns));
|
|
4413
|
+
if (formattable.length === 0) {
|
|
4349
4414
|
return {
|
|
4350
4415
|
checkId: "prettier",
|
|
4351
4416
|
findings: [],
|
|
@@ -4353,12 +4418,13 @@ async function runPrettier(cwd, config, pr) {
|
|
|
4353
4418
|
skipped: hasPrFileList(pr) ? "no formattable files in PR diff" : "no files"
|
|
4354
4419
|
};
|
|
4355
4420
|
}
|
|
4356
|
-
({ exitCode, stdout: stdout2, stderr } = await runPrettierOnPaths(cwd,
|
|
4421
|
+
({ exitCode, stdout: stdout2, stderr } = await runPrettierOnPaths(cwd, formattable));
|
|
4357
4422
|
} else {
|
|
4358
4423
|
({ exitCode, stdout: stdout2, stderr } = await runNpmBinary(cwd, "prettier", [
|
|
4359
4424
|
"--check",
|
|
4425
|
+
"--ignore-unknown",
|
|
4360
4426
|
glob,
|
|
4361
|
-
|
|
4427
|
+
...prettierNegations
|
|
4362
4428
|
]));
|
|
4363
4429
|
}
|
|
4364
4430
|
const findings = [];
|
|
@@ -6891,7 +6957,7 @@ function formatMarkdown(p2) {
|
|
|
6891
6957
|
);
|
|
6892
6958
|
sb.push("");
|
|
6893
6959
|
sb.push(
|
|
6894
|
-
"_Configure checks in `frontguard.config.js` \xB7 [Shields.io](https://shields.io) badge images load in Bitbucket PR comments (HTML tags like `<details>` are not supported there)._"
|
|
6960
|
+
"_Configure checks in `frontguard.config.mjs` (or `.cjs` / `.js`) \xB7 [Shields.io](https://shields.io) badge images load in Bitbucket PR comments (HTML tags like `<details>` are not supported there)._"
|
|
6895
6961
|
);
|
|
6896
6962
|
return sb.join("\n");
|
|
6897
6963
|
}
|
|
@@ -7419,7 +7485,7 @@ var init2 = defineCommand({
|
|
|
7419
7485
|
`);
|
|
7420
7486
|
}
|
|
7421
7487
|
g.stdout.write(
|
|
7422
|
-
"\nNext steps:\n 1. Review frontguard.config.
|
|
7488
|
+
"\nNext steps:\n 1. Review frontguard.config.mjs \u2014 uncomment and tune the checks you need\n 2. Review bitbucket-pipelines.yml \u2014 check the merged FrontGuard step\n 3. Set BITBUCKET_ACCESS_TOKEN as a secured variable in your repo\n 4. Install the package: yarn add -D @cleartrip/frontguard\n\n"
|
|
7423
7489
|
);
|
|
7424
7490
|
}
|
|
7425
7491
|
});
|