@cleartrip/frontguard 0.2.8 → 0.3.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 +268 -0
- package/dist/cli.js +747 -638
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +28 -8
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -6
- package/templates/bitbucket-pipelines.yml +15 -33
- package/templates/checks-snapshot-bitbucket-snippet.yml +0 -1
- package/templates/freekit-ci-setup.md +0 -33
package/dist/index.d.ts
CHANGED
|
@@ -58,8 +58,9 @@ interface FrontGuardConfig {
|
|
|
58
58
|
gateWhenAiDisclosureAmbiguous: CheckGate;
|
|
59
59
|
};
|
|
60
60
|
/**
|
|
61
|
-
* Stricter heuristics on AI-generated code
|
|
62
|
-
*
|
|
61
|
+
* **Under development — disabled by default.** Stricter static heuristics on AI-generated code
|
|
62
|
+
* (decorator regions or PR disclosure). When enabled, also drives escalation in `ai-escalation.ts`.
|
|
63
|
+
* Prefer marking regions with `// @frontguard-ai:start` … `// @frontguard-ai:end`.
|
|
63
64
|
* See `strictScanMode` for interaction with PR AI disclosure.
|
|
64
65
|
*/
|
|
65
66
|
aiAssistedReview: {
|
|
@@ -121,25 +122,41 @@ interface FrontGuardConfig {
|
|
|
121
122
|
maxReportLines: number;
|
|
122
123
|
};
|
|
123
124
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
125
|
+
* Bundle size: run `buildCommand`, extract the size metric, compare to a baseline from the base branch.
|
|
126
|
+
*
|
|
127
|
+
* **`bundleSizeStrategy`** selects how to read the bundle size:
|
|
128
|
+
* - `'auto'` — pick from detected stack (Next→next, Vite→vite, CRA→cra, else glob).
|
|
129
|
+
* - `'next'` — parse `next build` stdout for "First Load JS shared by all" (kB).
|
|
130
|
+
* - `'vite'` — sum `dist/assets/*.js` (or `measureGlobs`) after `vite build`.
|
|
131
|
+
* - `'cra'` — parse `react-scripts build` stdout for the gzipped JS total.
|
|
132
|
+
* - `'glob'` — sum raw bytes under `measureGlobs` (generic fallback).
|
|
133
|
+
* - `'custom'` — run `bundleSizeCommand`, read a single number (bytes) from stdout.
|
|
126
134
|
*/
|
|
127
135
|
bundle: {
|
|
128
136
|
enabled: boolean;
|
|
129
137
|
gate: CheckGate;
|
|
130
|
-
/** When false, only measures existing files (no build).
|
|
138
|
+
/** When false, only measures existing files (no build). */
|
|
131
139
|
runBuild: boolean;
|
|
132
|
-
/** E.g. `npm run build`, `
|
|
140
|
+
/** E.g. `npm run build`, `yarn build:prod`, or any command that produces output. */
|
|
133
141
|
buildCommand: string;
|
|
142
|
+
/** How to extract the size number after build. See JSDoc above. */
|
|
143
|
+
bundleSizeStrategy: 'auto' | 'next' | 'vite' | 'cra' | 'glob' | 'custom';
|
|
144
|
+
/**
|
|
145
|
+
* Command that prints a single number (bytes) to stdout. Used only when `bundleSizeStrategy: 'custom'`.
|
|
146
|
+
* E.g. `"node scripts/bundle-size.js"`.
|
|
147
|
+
*/
|
|
148
|
+
bundleSizeCommand: string | null;
|
|
149
|
+
/** Globs for `'glob'` and `'vite'` strategies. */
|
|
134
150
|
measureGlobs: string[];
|
|
135
151
|
baselinePath: string;
|
|
136
152
|
/**
|
|
137
153
|
* Git ref used to read `baselinePath` from the base branch when the file is not on disk.
|
|
138
|
-
* Bitbucket PR pipelines set `BITBUCKET_PR_DESTINATION_BRANCH`, which is preferred
|
|
139
|
-
* Separate from `tsAnyDelta.baseRef` so bundle can track a release branch.
|
|
154
|
+
* Bitbucket PR pipelines set `BITBUCKET_PR_DESTINATION_BRANCH`, which is preferred.
|
|
140
155
|
*/
|
|
141
156
|
baselineRef: string;
|
|
157
|
+
/** Max allowed growth vs baseline in bytes; null = no limit. */
|
|
142
158
|
maxDeltaBytes: number | null;
|
|
159
|
+
/** Absolute cap in bytes; null = no limit. */
|
|
143
160
|
maxTotalBytes: number | null;
|
|
144
161
|
};
|
|
145
162
|
/**
|
|
@@ -152,6 +169,9 @@ interface FrontGuardConfig {
|
|
|
152
169
|
scanGlobs: string[];
|
|
153
170
|
maxFileBytes: number;
|
|
154
171
|
};
|
|
172
|
+
/**
|
|
173
|
+
* **Under development — disabled by default.** Optional automated review / fix hints via OpenAI, Anthropic, or Ollama.
|
|
174
|
+
*/
|
|
155
175
|
llm: {
|
|
156
176
|
enabled: boolean;
|
|
157
177
|
provider: 'openai' | 'anthropic' | 'ollama';
|
package/dist/index.js
CHANGED
|
@@ -75,7 +75,7 @@ var defaultConfig = {
|
|
|
75
75
|
gateWhenAiDisclosureAmbiguous: "warn"
|
|
76
76
|
},
|
|
77
77
|
aiAssistedReview: {
|
|
78
|
-
enabled:
|
|
78
|
+
enabled: false,
|
|
79
79
|
gate: "warn",
|
|
80
80
|
strictScanMode: "both",
|
|
81
81
|
escalate: {
|
|
@@ -107,6 +107,8 @@ var defaultConfig = {
|
|
|
107
107
|
gate: "warn",
|
|
108
108
|
runBuild: true,
|
|
109
109
|
buildCommand: "npm run build",
|
|
110
|
+
bundleSizeStrategy: "auto",
|
|
111
|
+
bundleSizeCommand: null,
|
|
110
112
|
measureGlobs: ["dist/**/*", "build/static/**/*", ".next/static/**/*"],
|
|
111
113
|
baselinePath: ".frontguard/bundle-baseline.json",
|
|
112
114
|
baselineRef: "main",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../node_modules/defu/dist/defu.mjs","../src/config/defaults.ts","../src/config/migrate.ts","../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"sources":["../node_modules/defu/dist/defu.mjs","../src/config/defaults.ts","../src/config/migrate.ts","../src/index.ts"],"names":[],"mappings":";AAAA,SAAS,cAAc,KAAA,EAAO;AAC5B,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,QAAA,EAAU;AAC/C,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,cAAA,CAAe,KAAK,CAAA;AAC7C,EAAA,IAAI,SAAA,KAAc,QAAQ,SAAA,KAAc,MAAA,CAAO,aAAa,MAAA,CAAO,cAAA,CAAe,SAAS,CAAA,KAAM,IAAA,EAAM;AACrG,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI,MAAA,CAAO,YAAY,KAAA,EAAO;AAC5B,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI,MAAA,CAAO,eAAe,KAAA,EAAO;AAC/B,IAAA,OAAO,MAAA,CAAO,SAAA,CAAU,QAAA,CAAS,IAAA,CAAK,KAAK,CAAA,KAAM,iBAAA;AAAA,EACnD;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,KAAA,CAAM,UAAA,EAAY,QAAA,EAAU,SAAA,GAAY,KAAK,MAAA,EAAQ;AAC5D,EAAA,IAAI,CAAC,aAAA,CAAc,QAAQ,CAAA,EAAG;AAC5B,IAAA,OAAO,KAAA,CAAM,UAAA,EAAY,EAAC,EAAG,WAAW,MAAM,CAAA;AAAA,EAChD;AACA,EAAA,MAAM,MAAA,GAAS,EAAE,GAAG,QAAA,EAAS;AAC7B,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,UAAU,CAAA,EAAG;AACzC,IAAA,IAAI,GAAA,KAAQ,WAAA,IAAe,GAAA,KAAQ,aAAA,EAAe;AAChD,MAAA;AAAA,IACF;AACA,IAAA,MAAM,KAAA,GAAQ,WAAW,GAAG,CAAA;AAC5B,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAQ;AACtC,MAAA;AAAA,IACF;AACA,IAAA,IAAI,UAAU,MAAA,CAAO,MAAA,EAAQ,GAAA,EAAK,KAAA,EAAO,SAAS,CAAA,EAAG;AACnD,MAAA;AAAA,IACF;AACA,IAAA,IAAI,KAAA,CAAM,QAAQ,KAAK,CAAA,IAAK,MAAM,OAAA,CAAQ,MAAA,CAAO,GAAG,CAAC,CAAA,EAAG;AACtD,MAAA,MAAA,CAAO,GAAG,IAAI,CAAC,GAAG,OAAO,GAAG,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,IACzC,CAAA,MAAA,IAAW,cAAc,KAAK,CAAA,IAAK,cAAc,MAAA,CAAO,GAAG,CAAC,CAAA,EAAG;AAC7D,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AAAA,QACZ,KAAA;AAAA,QACA,OAAO,GAAG,CAAA;AAAA,QAAA,CACT,YAAY,CAAA,EAAG,SAAS,CAAA,CAAA,CAAA,GAAM,EAAA,IAAM,IAAI,QAAA,EAAS;AAAA,QAClD;AAAA,OACF;AAAA,IACF,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AAAA,IAChB;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AACA,SAAS,WAAW,MAAA,EAAQ;AAC1B,EAAA,OAAO,CAAA,GAAI,UAAA;AAAA;AAAA,IAET,UAAA,CAAW,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAM,KAAA,CAAM,CAAA,EAAG,CAAA,EAAG,EAAA,EAAI,MAAM,CAAA,EAAG,EAAE;AAAA,GAAA;AAE3D;AACA,IAAM,OAAO,UAAA,EAAW;;;ACpDjB,IAAM,aAAA,GAAkC;AAAA,EAC7C,IAAA,EAAM,MAAA;AAAA,EACN,OAAO,EAAC;AAAA,EACR,MAAA,EAAQ;AAAA,IACN,MAAA,EAAQ,EAAE,OAAA,EAAS,IAAA,EAAM,MAAM,8BAAA,EAA+B;AAAA,IAC9D,QAAA,EAAU;AAAA,MACR,OAAA,EAAS,IAAA;AAAA,MACT,IAAA,EAAM;AAAA,KACR;AAAA,IACA,UAAA,EAAY,EAAE,OAAA,EAAS,IAAA,EAAK;AAAA,IAC5B,OAAA,EAAS,EAAE,OAAA,EAAS,IAAA,EAAK;AAAA,IACzB,SAAA,EAAW;AAAA,MACT,OAAA,EAAS,IAAA;AAAA,MACT,aAAA,EAAe,EAAA;AAAA,MACf,eAAA,EAAiB,KAAA;AAAA,MACjB,cAAc,CAAC,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAQ,eAAe,YAAY,CAAA;AAAA,MACjE,0BAAA,EAA4B,IAAA;AAAA,MAC5B,6BAAA,EAA+B;AAAA,KACjC;AAAA,IACA,gBAAA,EAAkB;AAAA,MAChB,OAAA,EAAS,KAAA;AAAA,MACT,IAAA,EAAM,MAAA;AAAA,MACN,cAAA,EAAgB,MAAA;AAAA,MAChB,QAAA,EAAU;AAAA,QACR,qBAAA,EAAuB,IAAA;AAAA,QACvB,iBAAA,EAAmB;AAAA;AACrB,KACF;AAAA,IACA,QAAQ,EAAE,OAAA,EAAS,MAAM,SAAA,EAAW,GAAA,EAAK,gBAAgB,GAAA,EAAI;AAAA,IAE7D,UAAA,EAAY;AAAA,MACV,OAAA,EAAS,IAAA;AAAA,MACT,IAAA,EAAM,MAAA;AAAA,MACN,OAAA,EAAS,MAAA;AAAA,MACT,QAAA,EAAU;AAAA,KACZ;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,OAAA,EAAS,IAAA;AAAA,MACT,IAAA,EAAM,MAAA;AAAA,MACN,OAAA,EAAS,CAAC,KAAK,CAAA;AAAA,MACf,WAAW;AAAC,KACd;AAAA,IACA,QAAA,EAAU;AAAA,MACR,OAAA,EAAS,IAAA;AAAA,MACT,IAAA,EAAM,MAAA;AAAA,MACN,WAAW,EAAC;AAAA,MACZ,cAAA,EAAgB;AAAA,KAClB;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,OAAA,EAAS,IAAA;AAAA,MACT,IAAA,EAAM,MAAA;AAAA,MACN,QAAA,EAAU,IAAA;AAAA,MACV,YAAA,EAAc,eAAA;AAAA,MACd,kBAAA,EAAoB,MAAA;AAAA,MACpB,iBAAA,EAAmB,IAAA;AAAA,MACnB,YAAA,EAAc,CAAC,WAAA,EAAa,mBAAA,EAAqB,mBAAmB,CAAA;AAAA,MACpE,YAAA,EAAc,kCAAA;AAAA,MACd,WAAA,EAAa,MAAA;AAAA,MACb,aAAA,EAAe,IAAA;AAAA,MACf,aAAA,EAAe;AAAA,KACjB;AAAA,IACA,aAAA,EAAe;AAAA,MACb,OAAA,EAAS,IAAA;AAAA,MACT,IAAA,EAAM,MAAA;AAAA,MACN,SAAA,EAAW,CAAC,oBAAA,EAAsB,sBAAA,EAAwB,oBAAoB,CAAA;AAAA,MAC9E,YAAA,EAAc;AAAA,KAChB;AAAA,IACA,GAAA,EAAK;AAAA,MACH,OAAA,EAAS,KAAA;AAAA,MACT,QAAA,EAAU,QAAA;AAAA,MACV,KAAA,EAAO,aAAA;AAAA,MACP,SAAA,EAAW,gBAAA;AAAA,MACX,YAAA,EAAc,IAAA;AAAA,MACd,SAAA,EAAW,GAAA;AAAA,MACX,SAAA,EAAW,wBAAA;AAAA,MACX,eAAA,EAAiB,KAAA;AAAA,MACjB,iBAAA,EAAmB,EAAA;AAAA,MACnB,mBAAA,EAAqB;AAAA;AACvB;AAEJ;;;AC/EO,SAAS,wBAAwB,MAAA,EAAyC;AAC/E,EAAA,MAAM,KAAK,MAAA,CAAO,MAAA;AAClB,EAAA,IAAI,CAAC,EAAA,EAAI;AACT,EAAA,IAAI,KAAA,IAAS,EAAA,IAAM,EAAE,eAAA,IAAmB,EAAA,CAAA,EAAK;AAC3C,IAAA,EAAA,CAAG,gBAAgB,EAAA,CAAG,GAAA;AACtB,IAAA,OAAO,EAAA,CAAG,GAAA;AAAA,EACZ;AACF;;;ACUO,SAAS,aAAa,OAAA,EAAsD;AACjF,EAAA,uBAAA,CAAwB,OAAO,CAAA;AAC/B,EAAA,OAAO,IAAA,CAAK,SAAS,aAAa,CAAA;AACpC","file":"index.js","sourcesContent":["function isPlainObject(value) {\n if (value === null || typeof value !== \"object\") {\n return false;\n }\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) {\n return false;\n }\n if (Symbol.iterator in value) {\n return false;\n }\n if (Symbol.toStringTag in value) {\n return Object.prototype.toString.call(value) === \"[object Module]\";\n }\n return true;\n}\n\nfunction _defu(baseObject, defaults, namespace = \".\", merger) {\n if (!isPlainObject(defaults)) {\n return _defu(baseObject, {}, namespace, merger);\n }\n const object = { ...defaults };\n for (const key of Object.keys(baseObject)) {\n if (key === \"__proto__\" || key === \"constructor\") {\n continue;\n }\n const value = baseObject[key];\n if (value === null || value === void 0) {\n continue;\n }\n if (merger && merger(object, key, value, namespace)) {\n continue;\n }\n if (Array.isArray(value) && Array.isArray(object[key])) {\n object[key] = [...value, ...object[key]];\n } else if (isPlainObject(value) && isPlainObject(object[key])) {\n object[key] = _defu(\n value,\n object[key],\n (namespace ? `${namespace}.` : \"\") + key.toString(),\n merger\n );\n } else {\n object[key] = value;\n }\n }\n return object;\n}\nfunction createDefu(merger) {\n return (...arguments_) => (\n // eslint-disable-next-line unicorn/no-array-reduce\n arguments_.reduce((p, c) => _defu(p, c, \"\", merger), {})\n );\n}\nconst defu = createDefu();\nconst defuFn = createDefu((object, key, currentValue) => {\n if (object[key] !== void 0 && typeof currentValue === \"function\") {\n object[key] = currentValue(object[key]);\n return true;\n }\n});\nconst defuArrayFn = createDefu((object, key, currentValue) => {\n if (Array.isArray(object[key]) && typeof currentValue === \"function\") {\n object[key] = currentValue(object[key]);\n return true;\n }\n});\n\nexport { createDefu, defu as default, defu, defuArrayFn, defuFn };\n","import type { FrontGuardConfig } from './schema.js'\n\nexport const defaultConfig: FrontGuardConfig = {\n mode: 'warn',\n rules: {},\n checks: {\n eslint: { enabled: true, glob: '**/*.{js,cjs,mjs,jsx,ts,tsx}' },\n prettier: {\n enabled: true,\n glob: '**/*.{js,cjs,mjs,jsx,ts,tsx,json,md,css,scss,yml,yaml}',\n },\n typescript: { enabled: true },\n secrets: { enabled: true },\n prHygiene: {\n enabled: true,\n minBodyLength: 80,\n requireSections: false,\n sectionHints: ['what', 'why', 'test', 'how to test', 'screenshot'],\n requireAiDisclosureSection: true,\n gateWhenAiDisclosureAmbiguous: 'warn',\n },\n aiAssistedReview: {\n enabled: false,\n gate: 'warn',\n strictScanMode: 'both',\n escalate: {\n secretFindingsToBlock: true,\n tsAnyDeltaToBlock: true,\n },\n },\n prSize: { enabled: true, warnLines: 400, softBlockLines: 800 },\n\n tsAnyDelta: {\n enabled: true,\n gate: 'warn',\n baseRef: 'main',\n maxAdded: 0,\n },\n cycles: {\n enabled: true,\n gate: 'warn',\n entries: ['src'],\n extraArgs: [],\n },\n deadCode: {\n enabled: true,\n gate: 'info',\n extraArgs: [],\n maxReportLines: 80,\n },\n bundle: {\n enabled: true,\n gate: 'warn',\n runBuild: true,\n buildCommand: 'npm run build',\n bundleSizeStrategy: 'auto',\n bundleSizeCommand: null,\n measureGlobs: ['dist/**/*', 'build/static/**/*', '.next/static/**/*'],\n baselinePath: '.frontguard/bundle-baseline.json',\n baselineRef: 'main',\n maxDeltaBytes: null,\n maxTotalBytes: null,\n },\n coreWebVitals: {\n enabled: true,\n gate: 'warn',\n scanGlobs: ['app/**/*.{tsx,jsx}', 'pages/**/*.{tsx,jsx}', 'src/**/*.{tsx,jsx}'],\n maxFileBytes: 400_000,\n },\n llm: {\n enabled: false,\n provider: 'openai',\n model: 'gpt-4o-mini',\n apiKeyEnv: 'OPENAI_API_KEY',\n maxDiffChars: 48_000,\n timeoutMs: 60_000,\n ollamaUrl: 'http://127.0.0.1:11434',\n perFindingFixes: false,\n maxFixSuggestions: 12,\n maxFileContextChars: 24_000,\n },\n },\n}\n","import type { FrontGuardConfig } from './schema.js'\n\n/** Normalize legacy config keys before merging with defaults. */\nexport function migrateLegacyConfigKeys(config: Partial<FrontGuardConfig>): void {\n const ch = config.checks as Record<string, unknown> | undefined\n if (!ch) return\n if ('cwv' in ch && !('coreWebVitals' in ch)) {\n ch.coreWebVitals = ch.cwv\n delete ch.cwv\n }\n}\n","import defu from 'defu'\nimport type { FrontGuardConfig } from './config/schema.js'\nimport { defaultConfig } from './config/defaults.js'\nimport { migrateLegacyConfigKeys } from './config/migrate.js'\n\nexport type {\n FrontGuardConfig,\n GuardMode,\n CheckGate,\n RulesMap,\n CustomRuleDefinition,\n RuleFileContext,\n PrSizeTier,\n} from './config/schema.js'\nexport { defaultConfig } from './config/defaults.js'\n\n/**\n * Define repo-level config; shallow-merged over built-in defaults.\n * Org packages can be referenced via `extends` in config file.\n */\nexport function defineConfig(partial: Partial<FrontGuardConfig>): FrontGuardConfig {\n migrateLegacyConfigKeys(partial)\n return defu(partial, defaultConfig) as FrontGuardConfig\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleartrip/frontguard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Org-wide frontend PR guardrails:
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Org-wide frontend PR guardrails for Bitbucket: lint, types, secrets, bundle, PR hygiene, and more (AI-assisted / LLM layers WIP, off by default)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"frontguard": "./dist/cli.js"
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"build": "tsup",
|
|
27
27
|
"dev": "tsup --watch",
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
|
-
"test": "npm run build && tsx --test src/lib/bitbucket-checks-image-md.test.ts src/runner/checks-snapshot-png.integration.test.ts",
|
|
30
29
|
"format": "prettier --write .",
|
|
31
30
|
"prepack": "npm run build"
|
|
32
31
|
},
|
|
@@ -39,7 +38,6 @@
|
|
|
39
38
|
],
|
|
40
39
|
"license": "MIT",
|
|
41
40
|
"dependencies": {
|
|
42
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
43
41
|
"fast-glob": "^3.3.3",
|
|
44
42
|
"typescript": "^5.8.2"
|
|
45
43
|
},
|
|
@@ -50,7 +48,6 @@
|
|
|
50
48
|
"picocolors": "^1.1.1",
|
|
51
49
|
"prettier": "^3.5.3",
|
|
52
50
|
"tinyexec": "^1.0.1",
|
|
53
|
-
"tsup": "^8.4.0"
|
|
54
|
-
"tsx": "^4.21.0"
|
|
51
|
+
"tsup": "^8.4.0"
|
|
55
52
|
}
|
|
56
53
|
}
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
# FrontGuard —
|
|
1
|
+
# FrontGuard — reference pipeline (Bitbucket Cloud)
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# • The PNG is embedded in the PR comment as a small base64 image (no Bitbucket Downloads / extra tokens).
|
|
7
|
-
# • Full HTML report is uploaded to FreeKit; the comment links to that URL for details.
|
|
3
|
+
# This is the FULL pipeline that `frontguard init` generates.
|
|
4
|
+
# In most cases, run `frontguard init` instead of copying this manually —
|
|
5
|
+
# it merges the FrontGuard step into your existing bitbucket-pipelines.yml.
|
|
8
6
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# Optional: FREEKIT_BASE_URL — override FreeKit API host (default https://freekit.dev).
|
|
12
|
-
#
|
|
13
|
-
# Deeper clone avoids shallow-git errors (e.g. merge-base / parent rev) for diff-based checks.
|
|
7
|
+
# Required secured variable: BITBUCKET_ACCESS_TOKEN (PR comment permission)
|
|
8
|
+
# Optional: FREEKIT_BASE_URL — override the report hosting endpoint
|
|
14
9
|
|
|
15
10
|
image: node:20
|
|
16
11
|
|
|
@@ -27,30 +22,24 @@ pipelines:
|
|
|
27
22
|
artifacts:
|
|
28
23
|
- frontguard-report.html
|
|
29
24
|
- frontguard-report.md
|
|
30
|
-
- frontguard-
|
|
31
|
-
- frontguard-checks.png
|
|
25
|
+
- frontguard-pr-comment.partial.md
|
|
32
26
|
script:
|
|
33
27
|
- corepack enable
|
|
34
|
-
- apt-get update && apt-get install -y --no-install-recommends fonts-dejavu-core fonts-liberation
|
|
35
28
|
- yarn install --immutable || yarn install
|
|
36
29
|
- |
|
|
37
30
|
yarn frontguard run --markdown \
|
|
38
31
|
--htmlOut frontguard-report.html \
|
|
39
|
-
--
|
|
40
|
-
--checksPngOut frontguard-checks.png \
|
|
32
|
+
--prCommentOut frontguard-pr-comment.partial.md \
|
|
41
33
|
> frontguard-report.md
|
|
34
|
+
- test -n "${BITBUCKET_ACCESS_TOKEN:-}" || { echo "Missing secured var BITBUCKET_ACCESS_TOKEN"; exit 1; }
|
|
42
35
|
- |
|
|
43
36
|
python3 << 'PY'
|
|
44
|
-
import base64
|
|
45
37
|
import json
|
|
46
38
|
import os
|
|
47
39
|
from urllib.error import HTTPError
|
|
48
40
|
from urllib.request import Request, urlopen
|
|
49
41
|
|
|
50
|
-
|
|
51
|
-
# Keep in sync with src/lib/bitbucket-checks-image-md.ts (markdown line length cap).
|
|
52
|
-
MAX_IMAGE_LINE = 300_000
|
|
53
|
-
PNG_SIG = bytes([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
|
|
42
|
+
PLACEHOLDER = "__FRONTGUARD_REPORT_URL__"
|
|
54
43
|
|
|
55
44
|
base = os.environ.get("FREEKIT_BASE_URL", "https://freekit.dev").rstrip("/")
|
|
56
45
|
with open("frontguard-report.html", encoding="utf-8") as f:
|
|
@@ -78,27 +67,20 @@ pipelines:
|
|
|
78
67
|
if not report_url:
|
|
79
68
|
raise SystemExit(f"FreeKit: missing data.url in {json.dumps(parsed)[:2000]}")
|
|
80
69
|
|
|
81
|
-
with open("frontguard-
|
|
82
|
-
|
|
83
|
-
if
|
|
84
|
-
raise SystemExit("frontguard-checks.png is missing or not a valid PNG file.")
|
|
85
|
-
b64 = base64.standard_b64encode(raw).decode("ascii")
|
|
86
|
-
# ASCII alt text; standard Base64 — matches FrontGuard pngBufferToBitbucketImageMarkdownLine.
|
|
87
|
-
img_line = f""
|
|
88
|
-
if len(img_line) > MAX_IMAGE_LINE:
|
|
70
|
+
with open("frontguard-pr-comment.partial.md", encoding="utf-8") as f:
|
|
71
|
+
body = f.read()
|
|
72
|
+
if PLACEHOLDER not in body:
|
|
89
73
|
raise SystemExit(
|
|
90
|
-
f"
|
|
91
|
-
"Shrink the table or host the PNG and link it; see bitbucket-checks-image-md.ts limits."
|
|
74
|
+
f"Expected {PLACEHOLDER!r} in frontguard-pr-comment.partial.md — regenerate with current FrontGuard."
|
|
92
75
|
)
|
|
76
|
+
body = body.replace(PLACEHOLDER, report_url, 1)
|
|
93
77
|
|
|
94
|
-
body = f"{img_line}\n\n{DETAILED}\n{report_url}\n"
|
|
95
78
|
with open("frontguard-pr-comment.md", "w", encoding="utf-8") as out:
|
|
96
79
|
out.write(body)
|
|
97
80
|
with open("frontguard-payload.json", "w", encoding="utf-8") as out:
|
|
98
81
|
json.dump({"content": {"raw": body}}, out, ensure_ascii=False)
|
|
99
82
|
PY
|
|
100
83
|
- |
|
|
101
|
-
test -n "${BITBUCKET_ACCESS_TOKEN:-}" || { echo "Missing secured var BITBUCKET_ACCESS_TOKEN"; exit 1; }
|
|
102
84
|
curl --silent --show-error --fail --request POST \
|
|
103
85
|
--url "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_FULL_NAME}/pullrequests/${BITBUCKET_PR_ID}/comments" \
|
|
104
86
|
--header 'Accept: application/json' \
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# Deprecated: use `templates/bitbucket-pipelines.yml` — it now uses `--checksPngOut` + inline PNG.
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# FreeKit.dev + FrontGuard (Bitbucket Pipelines)
|
|
2
|
-
|
|
3
|
-
The Bitbucket template (`templates/bitbucket-pipelines.yml`) runs FrontGuard with **`--checksPngOut`** (SVG → PNG via **`@resvg/resvg-js`**, no headless browser), uploads the full HTML to **FreeKit**, and posts a PR comment that **inlines** the checks PNG as `data:image/png;base64,...` plus the FreeKit URL. You only need **`BITBUCKET_ACCESS_TOKEN`** with permission to **comment on pull requests** — no Repository Downloads or `repository:write`.
|
|
4
|
-
|
|
5
|
-
The same step uploads `frontguard-report.html` to FreeKit’s public API:
|
|
6
|
-
|
|
7
|
-
- **Docs:** [FreeKit API](https://freekit.dev/docs)
|
|
8
|
-
- **Endpoint:** `POST https://freekit.dev/api/v1/sites` with JSON `{"html": "<full report html>"}`
|
|
9
|
-
- **Response:** `data.url` like `https://freekit.dev/s/<siteId>` — this URL is included in the PR comment below the checks screenshot caption.
|
|
10
|
-
|
|
11
|
-
Optional env (advanced): **`FREEKIT_BASE_URL`** — override the API host (e.g. self‑hosted instance per FreeKit docs).
|
|
12
|
-
|
|
13
|
-
## PR checks (`pr-hygiene`, `pr-size`)
|
|
14
|
-
|
|
15
|
-
On **Bitbucket Pipelines** pull-request builds, FrontGuard reads **`BITBUCKET_PR_ID`** (and related branch env vars) and computes diff size with **`git diff <destination>...HEAD --numstat`**, so **`pr-size` runs** when the destination ref exists in the clone.
|
|
16
|
-
|
|
17
|
-
- **`pr-hygiene`** needs the PR description text. Bitbucket does not expose the body in default env vars — set **`FRONTGUARD_PR_BODY`** in the pipeline (e.g. fetch from the [Bitbucket PR API](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/) in a prior step and `export` it) if you want body-length / section / AI-disclosure rules to apply.
|
|
18
|
-
- **Shallow clones:** If `git` cannot resolve `BITBUCKET_PR_DESTINATION_BRANCH`, add a **`git fetch`** of the base branch so `pr-size` can measure the PR (see Bitbucket “clone depth” docs).
|
|
19
|
-
|
|
20
|
-
## Things to know
|
|
21
|
-
|
|
22
|
-
- **Public:** Anyone with the link can open the report. Do not use for highly sensitive content unless you add FreeKit **password** protection via their API (requires passing `password` in the JSON — not in this template).
|
|
23
|
-
- **Third party:** Uptime, limits, and terms are those of [FreeKit](https://freekit.dev); not controlled by Cleartrip/FrontGuard.
|
|
24
|
-
- **Limits:** Per FreeKit docs — e.g. HTML payload size caps, **rate limits** (creates per minute per IP), possible `429`. Large reports or busy runners may need retries or another host.
|
|
25
|
-
- **No delete token in CI:** Each run creates a **new** site URL. Old FreeKit URLs may still work until you delete them with the `deleteToken` returned in the API response (this template does not store it).
|
|
26
|
-
|
|
27
|
-
## Checks table image in PR comments
|
|
28
|
-
|
|
29
|
-
The default pipeline uses **`--checksPngOut`** so FrontGuard writes **`frontguard-checks.png`** (raster from the checks table). A short Python step base64-embeds that image in the PR comment (no extra hosting). If the PNG is too large for Bitbucket, raise the limit in the pipeline script cautiously or host the image and set **`FRONTGUARD_CHECKS_IMAGE_URL`** with **`--prCommentOut`** instead.
|
|
30
|
-
|
|
31
|
-
## Compared to Surge
|
|
32
|
-
|
|
33
|
-
Surge credentials (`SURGE_LOGIN` / `SURGE_TOKEN`) are **not** required for FreeKit’s open POST flow. If you remove Surge, you can delete any old Surge-related secured variables from the repository.
|