@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 CHANGED
@@ -27,7 +27,7 @@ npx frontguard init
27
27
 
28
28
  This does three things:
29
29
 
30
- - **Creates `frontguard.config.js`** with all available options as commented examples
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.js` and uncomment the checks you want. Every option is documented inline.
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.js — no strategy override needed
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
- All configuration lives in `frontguard.config.js` at your project root:
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.js` comments).
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 fs2 from 'fs';
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}', // files to lint
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 cfgPath = path6.join(cwd, "frontguard.config.js");
2772
- if (!await fileExists(cfgPath)) {
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.js");
2795
+ actions.push("created frontguard.config.mjs");
2775
2796
  } else {
2776
- actions.push("frontguard.config.js already exists (skipped)");
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 Boolean(r4.skipped) || r4.findings.length > 0;
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: { enabled: true, glob: "**/*.{js,cjs,mjs,jsx,ts,tsx}" },
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 (!fs2.existsSync(full)) continue;
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
- if (existing.length === 0) {
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, existing));
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
- if (existing.length === 0) {
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, existing));
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
- "--ignore-unknown"
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.js \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"
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
  });