@cleartrip/frontguard 0.2.9 → 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 +16 -44
- 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":"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","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,15 +1,11 @@
|
|
|
1
|
-
# FrontGuard —
|
|
1
|
+
# FrontGuard — reference pipeline (Bitbucket Cloud)
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# • Raster: @resvg/resvg-js (no browser). Full HTML report → FreeKit; comment links there 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.
|
|
7
6
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# Optional: FREEKIT_BASE_URL — override FreeKit API host (default https://freekit.dev).
|
|
11
|
-
#
|
|
12
|
-
# 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
|
|
13
9
|
|
|
14
10
|
image: node:20
|
|
15
11
|
|
|
@@ -26,40 +22,24 @@ pipelines:
|
|
|
26
22
|
artifacts:
|
|
27
23
|
- frontguard-report.html
|
|
28
24
|
- frontguard-report.md
|
|
29
|
-
- frontguard-
|
|
30
|
-
- frontguard-checks.png
|
|
25
|
+
- frontguard-pr-comment.partial.md
|
|
31
26
|
script:
|
|
32
27
|
- corepack enable
|
|
33
|
-
- apt-get update && apt-get install -y --no-install-recommends fonts-dejavu-core fonts-liberation
|
|
34
28
|
- yarn install --immutable || yarn install
|
|
35
29
|
- |
|
|
36
30
|
yarn frontguard run --markdown \
|
|
37
31
|
--htmlOut frontguard-report.html \
|
|
38
|
-
--
|
|
39
|
-
--checksPngOut frontguard-checks.png \
|
|
32
|
+
--prCommentOut frontguard-pr-comment.partial.md \
|
|
40
33
|
> frontguard-report.md
|
|
34
|
+
- test -n "${BITBUCKET_ACCESS_TOKEN:-}" || { echo "Missing secured var BITBUCKET_ACCESS_TOKEN"; exit 1; }
|
|
41
35
|
- |
|
|
42
|
-
test -n "${BITBUCKET_ACCESS_TOKEN:-}" || { echo "Missing secured var BITBUCKET_ACCESS_TOKEN"; exit 1; }
|
|
43
|
-
BB_PNG_NAME="frontguard-checks-pipeline-${BITBUCKET_BUILD_NUMBER}-pr-${BITBUCKET_PR_ID}.png"
|
|
44
|
-
cp frontguard-checks.png "${BB_PNG_NAME}"
|
|
45
|
-
if curl --silent --show-error --fail --request POST \
|
|
46
|
-
--url "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_FULL_NAME}/downloads" \
|
|
47
|
-
--header "Authorization: Bearer ${BITBUCKET_ACCESS_TOKEN}" \
|
|
48
|
-
--form "files=@${BB_PNG_NAME}"; then
|
|
49
|
-
export FRONTGUARD_CHECKS_IMAGE_URL="https://bitbucket.org/${BITBUCKET_REPO_FULL_NAME}/downloads/${BB_PNG_NAME}"
|
|
50
|
-
echo "Uploaded checks PNG to Downloads; image URL set for PR comment."
|
|
51
|
-
else
|
|
52
|
-
echo "WARNING: Downloads upload failed (token needs repository write for uploads). PR comment will not include an inline image."
|
|
53
|
-
export FRONTGUARD_CHECKS_IMAGE_URL=""
|
|
54
|
-
fi
|
|
55
36
|
python3 << 'PY'
|
|
56
37
|
import json
|
|
57
38
|
import os
|
|
58
39
|
from urllib.error import HTTPError
|
|
59
40
|
from urllib.request import Request, urlopen
|
|
60
41
|
|
|
61
|
-
|
|
62
|
-
PNG_SIG = bytes([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
|
|
42
|
+
PLACEHOLDER = "__FRONTGUARD_REPORT_URL__"
|
|
63
43
|
|
|
64
44
|
base = os.environ.get("FREEKIT_BASE_URL", "https://freekit.dev").rstrip("/")
|
|
65
45
|
with open("frontguard-report.html", encoding="utf-8") as f:
|
|
@@ -87,22 +67,14 @@ pipelines:
|
|
|
87
67
|
if not report_url:
|
|
88
68
|
raise SystemExit(f"FreeKit: missing data.url in {json.dumps(parsed)[:2000]}")
|
|
89
69
|
|
|
90
|
-
with open("frontguard-
|
|
91
|
-
|
|
92
|
-
if
|
|
93
|
-
raise SystemExit(
|
|
94
|
-
|
|
95
|
-
img_url = (os.environ.get("FRONTGUARD_CHECKS_IMAGE_URL") or "").strip()
|
|
96
|
-
if img_url:
|
|
97
|
-
# Bitbucket PR comments: only normal https:// URLs render; data: URIs show “Preview unavailable”.
|
|
98
|
-
img_line = f""
|
|
99
|
-
else:
|
|
100
|
-
img_line = (
|
|
101
|
-
"_Checks summary image: open **Pipeline artifacts** (`frontguard-checks.png`) "
|
|
102
|
-
"or grant the pipeline token permission to **upload to Repository → Downloads**._"
|
|
70
|
+
with open("frontguard-pr-comment.partial.md", encoding="utf-8") as f:
|
|
71
|
+
body = f.read()
|
|
72
|
+
if PLACEHOLDER not in body:
|
|
73
|
+
raise SystemExit(
|
|
74
|
+
f"Expected {PLACEHOLDER!r} in frontguard-pr-comment.partial.md — regenerate with current FrontGuard."
|
|
103
75
|
)
|
|
76
|
+
body = body.replace(PLACEHOLDER, report_url, 1)
|
|
104
77
|
|
|
105
|
-
body = f"{img_line}\n\n{DETAILED}\n{report_url}\n"
|
|
106
78
|
with open("frontguard-pr-comment.md", "w", encoding="utf-8") as out:
|
|
107
79
|
out.write(body)
|
|
108
80
|
with open("frontguard-payload.json", "w", encoding="utf-8") as out:
|
|
@@ -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 runs FrontGuard with **`--checksPngOut`** (SVG → PNG via **`@resvg/resvg-js`**), uploads the full HTML to **FreeKit**, **uploads the checks PNG to Repository → Downloads**, and posts a PR comment with **``** plus the FreeKit URL. Bitbucket does **not** render `data:image/...` in PR comments (you get “Preview unavailable”), so the template uses an HTTPS Downloads link instead. **`BITBUCKET_ACCESS_TOKEN`** must allow **PR comments** and **POST** to **`/2.0/repositories/{workspace}/{repo}/downloads`** (Repositories write / equivalent).
|
|
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 pipeline writes **`frontguard-checks.png`**, uploads it via the Downloads API, and references that **HTTPS** URL in Markdown. If the upload fails (token scope), the comment falls back to text pointing at pipeline artifacts. You can still set **`FRONTGUARD_CHECKS_IMAGE_URL`** before a custom comment step if you host the PNG elsewhere.
|
|
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.
|