@extenshi/cli 0.0.8 → 0.0.9
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/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -1
- package/dist/render.d.ts +21 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +81 -1
- package/dist/render.js.map +1 -1
- package/dist/report.d.ts +16 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +122 -0
- package/dist/report.js.map +1 -0
- package/dist/review-archive.d.ts +29 -0
- package/dist/review-archive.d.ts.map +1 -0
- package/dist/review-archive.js +106 -0
- package/dist/review-archive.js.map +1 -0
- package/dist/review-catalog.d.ts +43 -0
- package/dist/review-catalog.d.ts.map +1 -0
- package/dist/review-catalog.js +82 -0
- package/dist/review-catalog.js.map +1 -0
- package/dist/review-checks.d.ts +139 -0
- package/dist/review-checks.d.ts.map +1 -0
- package/dist/review-checks.js +560 -0
- package/dist/review-checks.js.map +1 -0
- package/dist/review-render.d.ts +10 -0
- package/dist/review-render.d.ts.map +1 -0
- package/dist/review-render.js +94 -0
- package/dist/review-render.js.map +1 -0
- package/dist/review-risk.d.ts +29 -0
- package/dist/review-risk.d.ts.map +1 -0
- package/dist/review-risk.js +85 -0
- package/dist/review-risk.js.map +1 -0
- package/dist/scan.d.ts +18 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +45 -2
- package/dist/scan.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* review-risk — pure check logic for the pre-publish "will the store reject /
|
|
3
|
+
* slow-walk / silently disable this?" suite.
|
|
4
|
+
*
|
|
5
|
+
* Every finding maps to ONE of three real-world consequences, never a generic
|
|
6
|
+
* lint level:
|
|
7
|
+
*
|
|
8
|
+
* REJECTED — the store will reject the submission (you cannot ship as-is).
|
|
9
|
+
* SLOW — it ships, but review moves from <24h to several days.
|
|
10
|
+
* ATTRITION — it ships and passes review, but installed users are silently
|
|
11
|
+
* dropped on the update until they re-accept a new permission.
|
|
12
|
+
*
|
|
13
|
+
* Each check's `citation` points to the relevant official Chrome Web Store /
|
|
14
|
+
* Chrome for Developers documentation so the user can verify the rule at the
|
|
15
|
+
* source. (We do not cite third-party books in product output.)
|
|
16
|
+
*
|
|
17
|
+
* This module is PURE (no I/O) so the rules are unit-testable against fixtures.
|
|
18
|
+
* Archive extraction lives in review-archive.ts; the network manifest fetch in
|
|
19
|
+
* review-catalog.ts; rendering in review-render.ts.
|
|
20
|
+
*/
|
|
21
|
+
// ── ch.10 permission → Chrome warning dialog ─────────────────────────────────
|
|
22
|
+
//
|
|
23
|
+
// The auto-disable-on-update behaviour (check 3) hinges on ONE thing: does
|
|
24
|
+
// *adding* this required permission trigger a warning dialog? If it does, the
|
|
25
|
+
// browser silently disables the extension for every existing user on the
|
|
26
|
+
// update. Permissions absent from this map are granted silently (alarms,
|
|
27
|
+
// storage, scripting, activeTab, cookies, …) and are safe to add in an update.
|
|
28
|
+
//
|
|
29
|
+
// Values are the verbatim Chrome warning strings from ch.10 "Permissions List",
|
|
30
|
+
// so the CLI can quote the exact dialog the user's installed base will see.
|
|
31
|
+
// Source of truth: chapters/10-permissions.md — do NOT edit from memory.
|
|
32
|
+
export const PERMISSION_WARNINGS = {
|
|
33
|
+
'accessibilityFeatures.modify': 'Change your accessibility settings',
|
|
34
|
+
'accessibilityFeatures.read': 'Read your accessibility settings',
|
|
35
|
+
bookmarks: 'Read and change your bookmarks',
|
|
36
|
+
clipboardRead: 'Read data you copy and paste',
|
|
37
|
+
clipboardWrite: 'Modify data you copy and paste',
|
|
38
|
+
contentSettings: "Change your settings that control websites' access to features such as cookies, JavaScript, plugins, geolocation, microphone, camera etc.",
|
|
39
|
+
debugger: 'Access the page debugger backend / Read and change all your data on the websites you visit',
|
|
40
|
+
declarativeNetRequest: 'Block content on any page',
|
|
41
|
+
declarativeNetRequestFeedback: 'Read your browsing history',
|
|
42
|
+
desktopCapture: 'Capture content of your screen',
|
|
43
|
+
downloads: 'Manage your downloads',
|
|
44
|
+
'downloads.open': 'Open downloaded files',
|
|
45
|
+
favicon: 'Read the icons of the websites you visit',
|
|
46
|
+
geolocation: 'Detect your physical location',
|
|
47
|
+
history: 'Read and change your browsing history',
|
|
48
|
+
identity: 'Know your email address',
|
|
49
|
+
'identity.email': 'Know your email address',
|
|
50
|
+
management: 'Manage your apps, extensions, and themes',
|
|
51
|
+
nativeMessaging: 'Communicate with cooperating native applications',
|
|
52
|
+
notifications: 'Display notifications',
|
|
53
|
+
pageCapture: 'Read and change all your data on the websites you visit',
|
|
54
|
+
privacy: 'Change your privacy-related settings',
|
|
55
|
+
proxy: 'Read and change all your data on the websites you visit',
|
|
56
|
+
readingList: 'Read and change entries in the reading list',
|
|
57
|
+
sessions: 'Read your browsing history on all your signed-in devices',
|
|
58
|
+
'system.storage': 'Identify and eject storage devices',
|
|
59
|
+
tabCapture: 'Read and change all your data on the websites you visit',
|
|
60
|
+
tabs: 'Read your browsing history',
|
|
61
|
+
topSites: 'Read a list of your most frequently visited websites',
|
|
62
|
+
ttsEngine: 'Read all text spoken using synthesized speech',
|
|
63
|
+
webAuthenticationProxy: 'Read and change all your data on all websites',
|
|
64
|
+
webNavigation: 'Read your browsing history',
|
|
65
|
+
};
|
|
66
|
+
// Official Chrome documentation backing each check (shown as the finding source).
|
|
67
|
+
const DOC_REMOTE_CODE = 'https://developer.chrome.com/docs/webstore/program-policies/code-readability';
|
|
68
|
+
const DOC_REVIEW = 'https://developer.chrome.com/docs/webstore/review-process';
|
|
69
|
+
const DOC_PERMISSION_WARNINGS = 'https://developer.chrome.com/docs/extensions/develop/concepts/permission-warnings';
|
|
70
|
+
const DOC_CSP = 'https://developer.chrome.com/docs/extensions/reference/manifest/content-security-policy';
|
|
71
|
+
const DOC_SERVICE_WORKER = 'https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle';
|
|
72
|
+
const DOC_PERMISSIONS = 'https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions';
|
|
73
|
+
// ── Host-permission helpers ──────────────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* The four spellings of "every host" from ch.10 "Global Host Permission" — all
|
|
76
|
+
* have the same effect and all trip the slow manual review queue. `<all_urls>`
|
|
77
|
+
* is included for the rare case it lands in `permissions` rather than
|
|
78
|
+
* `host_permissions`.
|
|
79
|
+
*/
|
|
80
|
+
const GLOBAL_HOST_PATTERNS = new Set(['<all_urls>', '*://*/*', 'http://*/*', 'https://*/*']);
|
|
81
|
+
/** A manifest entry that is a host match pattern (vs an API permission name). */
|
|
82
|
+
function looksLikeHostPattern(value) {
|
|
83
|
+
return value === '<all_urls>' || value.includes('://') || value.startsWith('*://');
|
|
84
|
+
}
|
|
85
|
+
export function isGlobalHost(value) {
|
|
86
|
+
if (GLOBAL_HOST_PATTERNS.has(value))
|
|
87
|
+
return true;
|
|
88
|
+
// Tolerate the trailing-slash-only forms (`https://*/`, `*://*/`) the
|
|
89
|
+
// canonical four omit but browsers treat identically.
|
|
90
|
+
return /^(\*|https?):\/\/\*\/(\*)?$/.test(value);
|
|
91
|
+
}
|
|
92
|
+
function asStringArray(value) {
|
|
93
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : [];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Required API permissions only (host patterns stripped out). In MV2 hosts and
|
|
97
|
+
* API permissions share the `permissions` array, so we split by shape.
|
|
98
|
+
*/
|
|
99
|
+
export function requiredApiPermissions(m) {
|
|
100
|
+
return asStringArray(m.permissions).filter((p) => !looksLikeHostPattern(p));
|
|
101
|
+
}
|
|
102
|
+
/** Required host patterns: `host_permissions` (MV3) plus any host-shaped entry in `permissions` (MV2). */
|
|
103
|
+
export function requiredHostPermissions(m) {
|
|
104
|
+
return [...asStringArray(m.host_permissions), ...asStringArray(m.permissions).filter(looksLikeHostPattern)];
|
|
105
|
+
}
|
|
106
|
+
// ── HTML entrypoint resolution ───────────────────────────────────────────────
|
|
107
|
+
/** Normalise a manifest-declared path to the package-relative key used in the file map. */
|
|
108
|
+
export function normalizePath(p) {
|
|
109
|
+
return p.replace(/^\.?\//, '').split(/[?#]/)[0];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* HTML pages the browser renders inside the extension's own CSP — popup,
|
|
113
|
+
* options, side panel, devtools, newtab/history/bookmarks overrides, sandbox
|
|
114
|
+
* pages, and the MV2 background page. These are exactly where inline scripts and
|
|
115
|
+
* eval get the extension rejected.
|
|
116
|
+
*/
|
|
117
|
+
export function htmlEntrypoints(m) {
|
|
118
|
+
const out = [];
|
|
119
|
+
const add = (v) => {
|
|
120
|
+
if (typeof v === 'string' && v.trim())
|
|
121
|
+
out.push(normalizePath(v));
|
|
122
|
+
};
|
|
123
|
+
add(m.action?.default_popup);
|
|
124
|
+
add(m.browser_action?.default_popup);
|
|
125
|
+
add(m.options_ui?.page);
|
|
126
|
+
add(m.options_page);
|
|
127
|
+
add(m.side_panel?.default_path);
|
|
128
|
+
add(m.devtools_page);
|
|
129
|
+
add(m.background?.page);
|
|
130
|
+
if (m.chrome_url_overrides)
|
|
131
|
+
for (const v of Object.values(m.chrome_url_overrides))
|
|
132
|
+
add(v);
|
|
133
|
+
for (const v of asStringArray(m.sandbox?.pages))
|
|
134
|
+
add(v);
|
|
135
|
+
return [...new Set(out)];
|
|
136
|
+
}
|
|
137
|
+
// ── Check 1: slow-review-queue predictor (ch.10) ─────────────────────────────
|
|
138
|
+
export function checkSlowReviewQueue(m) {
|
|
139
|
+
const global = requiredHostPermissions(m).filter(isGlobalHost);
|
|
140
|
+
if (global.length === 0)
|
|
141
|
+
return [];
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
verdict: 'SLOW',
|
|
145
|
+
check: 'global-host-permissions',
|
|
146
|
+
title: 'Global host permissions put the extension in the slow manual review queue',
|
|
147
|
+
detail: 'Requesting access to every host (any of <all_urls>, *://*/*, http://*/*, https://*/*) ' +
|
|
148
|
+
'forces a manual review, which typically raises review time from under 24 hours to several days.',
|
|
149
|
+
evidence: [...new Set(global)].join(', '),
|
|
150
|
+
recommendation: 'Narrow to the specific hosts you actually call (e.g. https://api.example.com/*), or drop host ' +
|
|
151
|
+
'permissions entirely in favour of activeTab — see the activeTab recommendation below.',
|
|
152
|
+
citation: `Chrome Web Store: review process — ${DOC_REVIEW}`,
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
}
|
|
156
|
+
// ── Check 2: listing-change re-review note (ch.13) ───────────────────────────
|
|
157
|
+
/**
|
|
158
|
+
* Static advisory — there is nothing in the package to inspect; it's a
|
|
159
|
+
* publishing-process fact developers routinely get burned by. Emitted once.
|
|
160
|
+
*/
|
|
161
|
+
export function checkListingChangeReReview() {
|
|
162
|
+
return [
|
|
163
|
+
{
|
|
164
|
+
verdict: 'SLOW',
|
|
165
|
+
check: 'listing-change-rereview',
|
|
166
|
+
title: 'Editing the store listing re-triggers a full slow manual review',
|
|
167
|
+
detail: 'Changing description, screenshots, category, or URLs in the Chrome Web Store listing makes the ' +
|
|
168
|
+
'entire extension undergo a slow manual review — even when the code did not change.',
|
|
169
|
+
recommendation: 'Batch store-listing edits together with a code release so you pay the slow-review cost once, not on ' +
|
|
170
|
+
'every metadata tweak.',
|
|
171
|
+
citation: `Chrome Web Store: review process — ${DOC_REVIEW}`,
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
// ── Check 3: auto-disable-on-update guard (ch.10 / ch.13) — manifest diff ─────
|
|
176
|
+
/**
|
|
177
|
+
* Diff the local manifest against the last-published one. A newly-added
|
|
178
|
+
* *required* permission that shows a warning silently disables the extension for
|
|
179
|
+
* the whole installed base on update (only a small toolbar icon appears) →
|
|
180
|
+
* silent attrition. Newly-added permissions of any kind also re-trip the slow
|
|
181
|
+
* review queue (ch.10).
|
|
182
|
+
*
|
|
183
|
+
* `prior` is the manifest fetched from the catalog's last snapshot; pass null
|
|
184
|
+
* when it could not be fetched (the caller surfaces why and this returns []).
|
|
185
|
+
*/
|
|
186
|
+
export function checkAutoDisableOnUpdate(local, prior) {
|
|
187
|
+
if (!prior)
|
|
188
|
+
return [];
|
|
189
|
+
const findings = [];
|
|
190
|
+
const priorApi = new Set(requiredApiPermissions(prior));
|
|
191
|
+
const priorHosts = new Set(requiredHostPermissions(prior));
|
|
192
|
+
const newApi = requiredApiPermissions(local).filter((p) => !priorApi.has(p));
|
|
193
|
+
const newHosts = requiredHostPermissions(local).filter((h) => !priorHosts.has(h));
|
|
194
|
+
// Newly-added required API permissions that show a warning → auto-disable.
|
|
195
|
+
for (const perm of newApi) {
|
|
196
|
+
const warning = PERMISSION_WARNINGS[perm];
|
|
197
|
+
if (!warning)
|
|
198
|
+
continue; // silently-granted permission — safe to add in an update
|
|
199
|
+
findings.push({
|
|
200
|
+
verdict: 'ATTRITION',
|
|
201
|
+
check: 'auto-disable-new-permission',
|
|
202
|
+
title: `Adding required "${perm}" silently disables the extension for existing users`,
|
|
203
|
+
detail: `"${perm}" shows the warning "${warning}". When an update adds a required permission that warns, ` +
|
|
204
|
+
'the browser silently disables the extension for everyone who already has it installed and shows only ' +
|
|
205
|
+
'a small toolbar icon — most users never re-enable, so you lose them.',
|
|
206
|
+
evidence: `new required permission "${perm}" (absent from the last published manifest)`,
|
|
207
|
+
recommendation: `Move "${perm}" into optional_permissions and request it at runtime with chrome.permissions.request(), ` +
|
|
208
|
+
'so existing users keep the extension enabled.',
|
|
209
|
+
citation: `Chrome: permission warnings re-prompt users on update — ${DOC_PERMISSION_WARNINGS}`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
// Newly-added host permissions always warn ("Read and change your data on …")
|
|
213
|
+
// → same auto-disable behaviour.
|
|
214
|
+
for (const host of newHosts) {
|
|
215
|
+
const global = isGlobalHost(host);
|
|
216
|
+
findings.push({
|
|
217
|
+
verdict: 'ATTRITION',
|
|
218
|
+
check: 'auto-disable-new-host',
|
|
219
|
+
title: `Adding host access "${host}" silently disables the extension for existing users`,
|
|
220
|
+
detail: `Host permissions show a "Read and change ${global ? 'all your data on the websites you visit' : `your data on ${host}`}" ` +
|
|
221
|
+
'warning. Adding one in an update silently disables the extension for the installed base until each user ' +
|
|
222
|
+
're-accepts — only a small toolbar icon signals it.',
|
|
223
|
+
evidence: `new host permission "${host}" (absent from the last published manifest)`,
|
|
224
|
+
recommendation: 'Move it to optional_host_permissions and request it on a user gesture, or use activeTab if you only ' +
|
|
225
|
+
'need the active tab.',
|
|
226
|
+
citation: `Chrome: permission warnings re-prompt users on update — ${DOC_PERMISSION_WARNINGS}`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// Any new permission also re-trips the slow review queue (ch.10), independent
|
|
230
|
+
// of whether it warns. One consolidated note.
|
|
231
|
+
if (newApi.length + newHosts.length > 0) {
|
|
232
|
+
findings.push({
|
|
233
|
+
verdict: 'SLOW',
|
|
234
|
+
check: 'update-adds-permissions',
|
|
235
|
+
title: 'This update adds new permissions, which re-triggers the slow review queue',
|
|
236
|
+
detail: 'Adding new permissions in an update places the update in the slow manual review queue (a few days vs <24h).',
|
|
237
|
+
evidence: [...newApi, ...newHosts].join(', '),
|
|
238
|
+
recommendation: 'Expected if the new permissions are intentional — just plan for the slower review. Drop any permission ' +
|
|
239
|
+
'you do not actually use yet.',
|
|
240
|
+
citation: `Chrome Web Store: review process — ${DOC_REVIEW}`,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return findings;
|
|
244
|
+
}
|
|
245
|
+
// ── Check 4a: remotely-loaded code (ch.13) ───────────────────────────────────
|
|
246
|
+
/** Dynamic analytics loaders + generic remote-script injection — banned under MV3. */
|
|
247
|
+
const REMOTE_CODE_PATTERNS = [
|
|
248
|
+
{
|
|
249
|
+
id: 'analytics-cdn',
|
|
250
|
+
re: /googletagmanager\.com\/gtag\/js|google-analytics\.com\/(?:analytics|ga)\.js|www\.google-analytics\.com\/analytics\.js/i,
|
|
251
|
+
what: 'a Google Analytics script loaded from a CDN',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'dynamic-script-tag',
|
|
255
|
+
re: /document\.createElement\(\s*['"`]script['"`]\s*\)/i,
|
|
256
|
+
what: 'a dynamically created <script> element (remote code injection)',
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: 'remote-importscripts',
|
|
260
|
+
re: /importScripts\(\s*['"`]https?:\/\//i,
|
|
261
|
+
what: 'importScripts() of a remote URL',
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: 'dynamic-import-url',
|
|
265
|
+
re: /\bimport\(\s*['"`]https?:\/\//i,
|
|
266
|
+
what: 'a dynamic import() of a remote URL',
|
|
267
|
+
},
|
|
268
|
+
];
|
|
269
|
+
export function checkRemoteCode(files) {
|
|
270
|
+
const findings = [];
|
|
271
|
+
for (const [path, content] of files) {
|
|
272
|
+
// Match the extensions review-archive actually extracts (TEXT_ENTRY_RE) —
|
|
273
|
+
// .ts/.tsx are stripped by the build before packaging, so never present.
|
|
274
|
+
if (!/\.(?:m?js|cjs|html?)$/i.test(path))
|
|
275
|
+
continue;
|
|
276
|
+
for (const { id, re, what } of REMOTE_CODE_PATTERNS) {
|
|
277
|
+
for (const where of matchAllLocations(content, re)) {
|
|
278
|
+
findings.push({
|
|
279
|
+
verdict: 'REJECTED',
|
|
280
|
+
check: `remote-code-${id}`,
|
|
281
|
+
title: 'Remotely-loaded code is not allowed in Manifest V3',
|
|
282
|
+
detail: `Found ${what}. MV3 requires every executable script to ship inside the package — analytics ` +
|
|
283
|
+
'libraries and other scripts can no longer be fetched and run at runtime.',
|
|
284
|
+
evidence: `${path}:${where.line} ${where.snippet}`,
|
|
285
|
+
recommendation: 'Bundle the script into your package and reference it locally. For Google Analytics, include the ' +
|
|
286
|
+
'gtag/analytics.js file in the build instead of loading it from the CDN.',
|
|
287
|
+
citation: `Chrome Web Store: remote hosted code policy — ${DOC_REMOTE_CODE}`,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return findings;
|
|
293
|
+
}
|
|
294
|
+
// ── Check 4b: inline scripts / eval in HTML entrypoints (ch.4 / ch.5 CSP) ─────
|
|
295
|
+
const INLINE_SCRIPT_RE = /<script(?![^>]*\bsrc\s*=)[^>]*>([\s\S]*?)<\/script>/i;
|
|
296
|
+
const INLINE_HANDLER_RE = /\son[a-z]+\s*=\s*["']/i;
|
|
297
|
+
const JS_URL_RE = /\b(?:href|src|action)\s*=\s*["']\s*javascript:/i;
|
|
298
|
+
const EVAL_RE = /\b(?:eval\s*\(|new\s+Function\s*\()/;
|
|
299
|
+
export function checkHtmlCsp(m, files) {
|
|
300
|
+
const findings = [];
|
|
301
|
+
const entrypoints = new Set(htmlEntrypoints(m));
|
|
302
|
+
// Inline <script> needs the capture group (must have a non-empty body), so it
|
|
303
|
+
// runs its own global scan; the simpler patterns use matchAllLocations. Every
|
|
304
|
+
// occurrence is reported (capped) so the dev fixes them all in one pass.
|
|
305
|
+
const inlineScriptG = new RegExp(INLINE_SCRIPT_RE.source, 'gi');
|
|
306
|
+
for (const [path, content] of files) {
|
|
307
|
+
if (!/\.html?$/i.test(path))
|
|
308
|
+
continue;
|
|
309
|
+
// Only the entrypoints the browser renders under the extension CSP matter.
|
|
310
|
+
// If the manifest declared none we still scan any HTML (better a heads-up
|
|
311
|
+
// than a miss), but prefer flagging declared entrypoints.
|
|
312
|
+
const isEntrypoint = entrypoints.size === 0 || entrypoints.has(normalizePath(path));
|
|
313
|
+
if (!isEntrypoint)
|
|
314
|
+
continue;
|
|
315
|
+
let inlineCount = 0;
|
|
316
|
+
for (const match of content.matchAll(inlineScriptG)) {
|
|
317
|
+
if (!match[1]?.trim())
|
|
318
|
+
continue; // empty <script></script> or src-only — not inline code
|
|
319
|
+
const where = locate(content, match.index ?? 0);
|
|
320
|
+
findings.push({
|
|
321
|
+
verdict: 'REJECTED',
|
|
322
|
+
check: 'inline-script',
|
|
323
|
+
title: 'Inline <script> violates the Manifest V3 content security policy',
|
|
324
|
+
detail: 'MV3 forbids inline script in extension pages. The default CSP blocks it, so the page silently fails ' +
|
|
325
|
+
'and the store rejects the package.',
|
|
326
|
+
evidence: `${path}:${where.line} inline <script>…</script>`,
|
|
327
|
+
recommendation: 'Move the code into a separate .js file and load it with <script src="…">.',
|
|
328
|
+
citation: `Chrome extensions: content security policy — ${DOC_CSP}`,
|
|
329
|
+
});
|
|
330
|
+
if (++inlineCount >= 10)
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
for (const where of matchAllLocations(content, INLINE_HANDLER_RE)) {
|
|
334
|
+
findings.push({
|
|
335
|
+
verdict: 'REJECTED',
|
|
336
|
+
check: 'inline-event-handler',
|
|
337
|
+
title: 'Inline event handler (onclick=…) violates the Manifest V3 CSP',
|
|
338
|
+
detail: 'Inline event-handler attributes execute inline JavaScript, which the MV3 CSP blocks — the handler ' +
|
|
339
|
+
'never fires and the package is rejected.',
|
|
340
|
+
evidence: `${path}:${where.line} ${where.snippet}`,
|
|
341
|
+
recommendation: 'Attach handlers from a bundled script with addEventListener() instead of on* attributes.',
|
|
342
|
+
citation: `Chrome extensions: content security policy — ${DOC_CSP}`,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
for (const where of matchAllLocations(content, JS_URL_RE)) {
|
|
346
|
+
findings.push({
|
|
347
|
+
verdict: 'REJECTED',
|
|
348
|
+
check: 'javascript-url',
|
|
349
|
+
title: 'javascript: URL violates the Manifest V3 CSP',
|
|
350
|
+
detail: 'javascript: URLs run inline code, which the MV3 CSP blocks.',
|
|
351
|
+
evidence: `${path}:${where.line}`,
|
|
352
|
+
recommendation: 'Replace the javascript: URL with a bundled-script event handler.',
|
|
353
|
+
citation: `Chrome extensions: content security policy — ${DOC_CSP}`,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// eval / new Function anywhere in shipped JS or HTML entrypoints — also CSP-blocked.
|
|
358
|
+
for (const [path, content] of files) {
|
|
359
|
+
if (!/\.(?:m?js|cjs|html?)$/i.test(path))
|
|
360
|
+
continue;
|
|
361
|
+
for (const where of matchAllLocations(content, EVAL_RE)) {
|
|
362
|
+
findings.push({
|
|
363
|
+
verdict: 'REJECTED',
|
|
364
|
+
check: 'eval',
|
|
365
|
+
title: 'eval()/new Function() violates the Manifest V3 CSP',
|
|
366
|
+
detail: "MV3's content security policy blocks eval() and new Function(); calling them throws at runtime.",
|
|
367
|
+
evidence: `${path}:${where.line} ${where.snippet}`,
|
|
368
|
+
recommendation: 'Remove eval/new Function — parse JSON with JSON.parse and replace dynamic code with real functions.',
|
|
369
|
+
citation: `Chrome extensions: content security policy — ${DOC_CSP}`,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return findings;
|
|
374
|
+
}
|
|
375
|
+
// ── Check 4c: service-worker anti-patterns (ch.6) ────────────────────────────
|
|
376
|
+
const SW_DOM_APIS = [
|
|
377
|
+
{
|
|
378
|
+
id: 'window',
|
|
379
|
+
re: /\bwindow\s*\./,
|
|
380
|
+
api: 'window',
|
|
381
|
+
why: 'the service worker global is ServiceWorkerGlobalScope; window is undefined',
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: 'document',
|
|
385
|
+
re: /\bdocument\s*\./,
|
|
386
|
+
api: 'document',
|
|
387
|
+
why: 'service workers have no DOM, so document is undefined',
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
id: 'localStorage',
|
|
391
|
+
re: /\blocalStorage\b/,
|
|
392
|
+
api: 'localStorage',
|
|
393
|
+
why: 'localStorage is unavailable in a service worker',
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: 'sessionStorage',
|
|
397
|
+
re: /\bsessionStorage\b/,
|
|
398
|
+
api: 'sessionStorage',
|
|
399
|
+
why: 'sessionStorage is unavailable in a service worker',
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
id: 'xhr',
|
|
403
|
+
re: /\bXMLHttpRequest\b/,
|
|
404
|
+
api: 'XMLHttpRequest',
|
|
405
|
+
why: 'XMLHttpRequest is unavailable in a service worker — use fetch()',
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
id: 'get-background-page',
|
|
409
|
+
re: /getBackgroundPage\s*\(/,
|
|
410
|
+
api: 'chrome.extension.getBackgroundPage()',
|
|
411
|
+
why: 'programmatic background-page access was removed in MV3',
|
|
412
|
+
},
|
|
413
|
+
];
|
|
414
|
+
// Only setInterval — it's unambiguously a recurring scheduler. A one-shot
|
|
415
|
+
// setTimeout is legitimate (a short in-task delay fires before the worker idles
|
|
416
|
+
// out), so flagging it would be a false positive.
|
|
417
|
+
const SW_TIMER_RE = /\bsetInterval\s*\(/;
|
|
418
|
+
export function checkServiceWorker(m, files) {
|
|
419
|
+
const swPath = m.background?.service_worker;
|
|
420
|
+
if (typeof swPath !== 'string' || !swPath.trim())
|
|
421
|
+
return [];
|
|
422
|
+
const content = readFile(files, swPath);
|
|
423
|
+
if (content === undefined)
|
|
424
|
+
return [];
|
|
425
|
+
// Only the service_worker entry-point file is scanned — imported modules
|
|
426
|
+
// (importScripts / ESM import) are not followed, so a clean result here does
|
|
427
|
+
// not prove the whole worker graph is clean. The finding wording says
|
|
428
|
+
// "entry-point" to avoid implying otherwise.
|
|
429
|
+
const findings = [];
|
|
430
|
+
for (const { id, re, api, why } of SW_DOM_APIS) {
|
|
431
|
+
const where = matchLocation(content, re);
|
|
432
|
+
if (!where)
|
|
433
|
+
continue;
|
|
434
|
+
findings.push({
|
|
435
|
+
verdict: 'REJECTED',
|
|
436
|
+
check: `sw-${id}`,
|
|
437
|
+
title: `Service worker references ${api}, which does not exist in Manifest V3`,
|
|
438
|
+
detail: `The service worker entry-point references ${api}, but ${why}. A reference thrown in the first turn ` +
|
|
439
|
+
'of the event loop makes the worker fail to register, so the extension never runs.',
|
|
440
|
+
evidence: `${normalizePath(swPath)}:${where.line} ${where.snippet}`,
|
|
441
|
+
recommendation: id === 'xhr'
|
|
442
|
+
? 'Use fetch() instead of XMLHttpRequest.'
|
|
443
|
+
: 'Move DOM/window work to a content script, popup, or offscreen document and message the worker.',
|
|
444
|
+
citation: `Chrome extensions: service worker lifecycle — ${DOC_SERVICE_WORKER}`,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
const timer = matchLocation(content, SW_TIMER_RE);
|
|
448
|
+
if (timer) {
|
|
449
|
+
findings.push({
|
|
450
|
+
// ATTRITION, not REJECTED: the store ships it, but the scheduler silently
|
|
451
|
+
// dies at runtime so installed users quietly lose the feature.
|
|
452
|
+
verdict: 'ATTRITION',
|
|
453
|
+
check: 'sw-timer-scheduler',
|
|
454
|
+
title: 'setInterval in a service worker is an unreliable scheduler',
|
|
455
|
+
detail: 'This passes store review, but the service worker is terminated after ~30s idle, so a setInterval ' +
|
|
456
|
+
'scheduler silently stops firing once it sleeps — the recurring work quietly breaks for installed users.',
|
|
457
|
+
evidence: `${normalizePath(swPath)}:${timer.line} ${timer.snippet}`,
|
|
458
|
+
recommendation: 'Use chrome.alarms for recurring work (note its minimum interval is 1 minute) instead of setInterval.',
|
|
459
|
+
citation: `Chrome extensions: service worker lifecycle — ${DOC_SERVICE_WORKER}`,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
return findings;
|
|
463
|
+
}
|
|
464
|
+
// ── Check 5: activeTab advisor (ch.10) ───────────────────────────────────────
|
|
465
|
+
/** True when a content script auto-injects into all pages (then activeTab can't replace its host perms). */
|
|
466
|
+
function hasBroadContentScript(m) {
|
|
467
|
+
for (const cs of m.content_scripts ?? []) {
|
|
468
|
+
if (asStringArray(cs.matches).some(isGlobalHost))
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
export function checkActiveTabAdvisor(m) {
|
|
474
|
+
const global = requiredHostPermissions(m).filter(isGlobalHost);
|
|
475
|
+
if (global.length === 0)
|
|
476
|
+
return [];
|
|
477
|
+
// activeTab only covers gesture-driven access to the focused tab. If the
|
|
478
|
+
// extension declaratively injects into every page, it genuinely needs the
|
|
479
|
+
// broad host permission — recommending activeTab there would be wrong.
|
|
480
|
+
if (hasBroadContentScript(m))
|
|
481
|
+
return [];
|
|
482
|
+
const hasGestureSurface = Boolean(m.action || m.browser_action || m.devtools_page || asStringArray(m.permissions).includes('scripting'));
|
|
483
|
+
if (!hasGestureSurface)
|
|
484
|
+
return [];
|
|
485
|
+
return [
|
|
486
|
+
{
|
|
487
|
+
verdict: 'SLOW',
|
|
488
|
+
check: 'prefer-activetab',
|
|
489
|
+
title: 'Use activeTab instead of global host permissions to skip the warning and the slow queue',
|
|
490
|
+
detail: 'This extension requests every host but appears to act on the active tab after a user gesture (toolbar ' +
|
|
491
|
+
'action / command / devtools). activeTab grants the same access on a user interaction with NO permission ' +
|
|
492
|
+
'warning and keeps the extension out of the slow manual review queue.',
|
|
493
|
+
evidence: `global host access: ${[...new Set(global)].join(', ')}`,
|
|
494
|
+
recommendation: 'Replace the global host permission with "activeTab" (plus "scripting" if you inject), and request any ' +
|
|
495
|
+
'wider access at runtime only when needed.',
|
|
496
|
+
citation: `Chrome extensions: declare permissions — ${DOC_PERMISSIONS}`,
|
|
497
|
+
},
|
|
498
|
+
];
|
|
499
|
+
}
|
|
500
|
+
export function runChecks(input) {
|
|
501
|
+
return [
|
|
502
|
+
...checkRemoteCode(input.files),
|
|
503
|
+
...checkHtmlCsp(input.manifest, input.files),
|
|
504
|
+
...checkServiceWorker(input.manifest, input.files),
|
|
505
|
+
...checkAutoDisableOnUpdate(input.manifest, input.priorManifest),
|
|
506
|
+
...checkSlowReviewQueue(input.manifest),
|
|
507
|
+
...checkActiveTabAdvisor(input.manifest),
|
|
508
|
+
...checkListingChangeReReview(),
|
|
509
|
+
];
|
|
510
|
+
}
|
|
511
|
+
const VERDICT_ORDER = { REJECTED: 0, ATTRITION: 1, SLOW: 2 };
|
|
512
|
+
export function summarize(findings) {
|
|
513
|
+
const sorted = [...findings].sort((a, b) => VERDICT_ORDER[a.verdict] - VERDICT_ORDER[b.verdict]);
|
|
514
|
+
const counts = { REJECTED: 0, ATTRITION: 0, SLOW: 0 };
|
|
515
|
+
for (const f of sorted)
|
|
516
|
+
counts[f.verdict]++;
|
|
517
|
+
return { findings: sorted, counts, blocking: counts.REJECTED > 0 };
|
|
518
|
+
}
|
|
519
|
+
// ── Small text utilities ─────────────────────────────────────────────────────
|
|
520
|
+
/** Read a manifest-declared file out of the map, tolerating ./ and / prefixes. */
|
|
521
|
+
function readFile(files, declaredPath) {
|
|
522
|
+
const key = normalizePath(declaredPath);
|
|
523
|
+
if (files.has(key))
|
|
524
|
+
return files.get(key);
|
|
525
|
+
for (const [p, content] of files)
|
|
526
|
+
if (normalizePath(p) === key)
|
|
527
|
+
return content;
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
/** Line number (1-based) + a trimmed snippet for the first match of `re`, or null. */
|
|
531
|
+
function matchLocation(content, re) {
|
|
532
|
+
const m = re.exec(content);
|
|
533
|
+
if (!m)
|
|
534
|
+
return null;
|
|
535
|
+
return locate(content, m.index);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Up to `cap` match locations for `re` across the whole file — not just the
|
|
539
|
+
* first. Reporting every occurrence means a developer who fixes one inline
|
|
540
|
+
* handler and re-runs sees the rest immediately, instead of one-at-a-time
|
|
541
|
+
* (which erodes trust in the tool). Caps the count so a minified file with
|
|
542
|
+
* hundreds of matches can't flood the report.
|
|
543
|
+
*/
|
|
544
|
+
function matchAllLocations(content, re, cap = 10) {
|
|
545
|
+
// Clone with the global flag so we never mutate a shared regex's lastIndex.
|
|
546
|
+
const global = re.global ? re : new RegExp(re.source, `${re.flags}g`);
|
|
547
|
+
const out = [];
|
|
548
|
+
for (const m of content.matchAll(global)) {
|
|
549
|
+
out.push(locate(content, m.index ?? 0));
|
|
550
|
+
if (out.length >= cap)
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
return out;
|
|
554
|
+
}
|
|
555
|
+
function locate(content, index) {
|
|
556
|
+
const line = content.slice(0, index).split('\n').length;
|
|
557
|
+
const lineText = content.split('\n')[line - 1] ?? '';
|
|
558
|
+
return { line, snippet: lineText.trim().slice(0, 100) };
|
|
559
|
+
}
|
|
560
|
+
//# sourceMappingURL=review-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-checks.js","sourceRoot":"","sources":["../src/review-checks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAsDH,gFAAgF;AAChF,EAAE;AACF,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,yEAAyE;AACzE,+EAA+E;AAC/E,EAAE;AACF,gFAAgF;AAChF,4EAA4E;AAC5E,yEAAyE;AACzE,MAAM,CAAC,MAAM,mBAAmB,GAAqC;IACpE,8BAA8B,EAAE,oCAAoC;IACpE,4BAA4B,EAAE,kCAAkC;IAChE,SAAS,EAAE,gCAAgC;IAC3C,aAAa,EAAE,8BAA8B;IAC7C,cAAc,EAAE,gCAAgC;IAChD,eAAe,EACd,2IAA2I;IAC5I,QAAQ,EAAE,4FAA4F;IACtG,qBAAqB,EAAE,2BAA2B;IAClD,6BAA6B,EAAE,4BAA4B;IAC3D,cAAc,EAAE,gCAAgC;IAChD,SAAS,EAAE,uBAAuB;IAClC,gBAAgB,EAAE,uBAAuB;IACzC,OAAO,EAAE,0CAA0C;IACnD,WAAW,EAAE,+BAA+B;IAC5C,OAAO,EAAE,uCAAuC;IAChD,QAAQ,EAAE,yBAAyB;IACnC,gBAAgB,EAAE,yBAAyB;IAC3C,UAAU,EAAE,0CAA0C;IACtD,eAAe,EAAE,kDAAkD;IACnE,aAAa,EAAE,uBAAuB;IACtC,WAAW,EAAE,yDAAyD;IACtE,OAAO,EAAE,sCAAsC;IAC/C,KAAK,EAAE,yDAAyD;IAChE,WAAW,EAAE,6CAA6C;IAC1D,QAAQ,EAAE,0DAA0D;IACpE,gBAAgB,EAAE,oCAAoC;IACtD,UAAU,EAAE,yDAAyD;IACrE,IAAI,EAAE,4BAA4B;IAClC,QAAQ,EAAE,sDAAsD;IAChE,SAAS,EAAE,+CAA+C;IAC1D,sBAAsB,EAAE,+CAA+C;IACvE,aAAa,EAAE,4BAA4B;CAC3C,CAAA;AAED,kFAAkF;AAClF,MAAM,eAAe,GAAG,8EAA8E,CAAA;AACtG,MAAM,UAAU,GAAG,2DAA2D,CAAA;AAC9E,MAAM,uBAAuB,GAC5B,mFAAmF,CAAA;AACpF,MAAM,OAAO,GAAG,yFAAyF,CAAA;AACzG,MAAM,kBAAkB,GACvB,yFAAyF,CAAA;AAC1F,MAAM,eAAe,GAAG,mFAAmF,CAAA;AAE3G,gFAAgF;AAEhF;;;;;GAKG;AACH,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC,CAAA;AAE5F,iFAAiF;AACjF,SAAS,oBAAoB,CAAC,KAAa;IAC1C,OAAO,KAAK,KAAK,YAAY,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;AACnF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACzC,IAAI,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAChD,sEAAsE;IACtE,sDAAsD;IACtD,OAAO,6BAA6B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACjD,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACpC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC3F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,CAAW;IACjD,OAAO,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAA;AAC5E,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,uBAAuB,CAAC,CAAW;IAClD,OAAO,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,gBAAgB,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAA;AAC5G,CAAC;AAED,gFAAgF;AAEhF,2FAA2F;AAC3F,MAAM,UAAU,aAAa,CAAC,CAAS;IACtC,OAAO,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,CAAW;IAC1C,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,MAAM,GAAG,GAAG,CAAC,CAAU,EAAE,EAAE;QAC1B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IAClE,CAAC,CAAA;IACD,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IAC5B,GAAG,CAAC,CAAC,CAAC,cAAc,EAAE,aAAa,CAAC,CAAA;IACpC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IACvB,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAA;IACnB,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;IAC/B,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,CAAA;IACpB,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;IACvB,IAAI,CAAC,CAAC,oBAAoB;QAAE,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,CAAA;IACzF,KAAK,MAAM,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,CAAC,CAAC,CAAC,CAAA;IACvD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;AACzB,CAAC;AAED,gFAAgF;AAEhF,MAAM,UAAU,oBAAoB,CAAC,CAAW;IAC/C,MAAM,MAAM,GAAG,uBAAuB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IAC9D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAClC,OAAO;QACN;YACC,OAAO,EAAE,MAAM;YACf,KAAK,EAAE,yBAAyB;YAChC,KAAK,EAAE,2EAA2E;YAClF,MAAM,EACL,wFAAwF;gBACxF,iGAAiG;YAClG,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YACzC,cAAc,EACb,gGAAgG;gBAChG,uFAAuF;YACxF,QAAQ,EAAE,sCAAsC,UAAU,EAAE;SAC5D;KACD,CAAA;AACF,CAAC;AAED,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,UAAU,0BAA0B;IACzC,OAAO;QACN;YACC,OAAO,EAAE,MAAM;YACf,KAAK,EAAE,yBAAyB;YAChC,KAAK,EAAE,iEAAiE;YACxE,MAAM,EACL,iGAAiG;gBACjG,oFAAoF;YACrF,cAAc,EACb,sGAAsG;gBACtG,uBAAuB;YACxB,QAAQ,EAAE,sCAAsC,UAAU,EAAE;SAC5D;KACD,CAAA;AACF,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAe,EAAE,KAAsB;IAC/E,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IAErB,MAAM,QAAQ,GAAoB,EAAE,CAAA;IAEpC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,CAAA;IACvD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAA;IAC1D,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC5E,MAAM,QAAQ,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAEjF,2EAA2E;IAC3E,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;QACzC,IAAI,CAAC,OAAO;YAAE,SAAQ,CAAC,yDAAyD;QAChF,QAAQ,CAAC,IAAI,CAAC;YACb,OAAO,EAAE,WAAW;YACpB,KAAK,EAAE,6BAA6B;YACpC,KAAK,EAAE,oBAAoB,IAAI,sDAAsD;YACrF,MAAM,EACL,IAAI,IAAI,wBAAwB,OAAO,2DAA2D;gBAClG,uGAAuG;gBACvG,sEAAsE;YACvE,QAAQ,EAAE,4BAA4B,IAAI,6CAA6C;YACvF,cAAc,EACb,SAAS,IAAI,2FAA2F;gBACxG,+CAA+C;YAChD,QAAQ,EAAE,2DAA2D,uBAAuB,EAAE;SAC9F,CAAC,CAAA;IACH,CAAC;IAED,8EAA8E;IAC9E,iCAAiC;IACjC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;QACjC,QAAQ,CAAC,IAAI,CAAC;YACb,OAAO,EAAE,WAAW;YACpB,KAAK,EAAE,uBAAuB;YAC9B,KAAK,EAAE,uBAAuB,IAAI,sDAAsD;YACxF,MAAM,EACL,4CAA4C,MAAM,CAAC,CAAC,CAAC,yCAAyC,CAAC,CAAC,CAAC,gBAAgB,IAAI,EAAE,IAAI;gBAC3H,0GAA0G;gBAC1G,oDAAoD;YACrD,QAAQ,EAAE,wBAAwB,IAAI,6CAA6C;YACnF,cAAc,EACb,sGAAsG;gBACtG,sBAAsB;YACvB,QAAQ,EAAE,2DAA2D,uBAAuB,EAAE;SAC9F,CAAC,CAAA;IACH,CAAC;IAED,8EAA8E;IAC9E,8CAA8C;IAC9C,IAAI,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzC,QAAQ,CAAC,IAAI,CAAC;YACb,OAAO,EAAE,MAAM;YACf,KAAK,EAAE,yBAAyB;YAChC,KAAK,EAAE,2EAA2E;YAClF,MAAM,EACL,6GAA6G;YAC9G,QAAQ,EAAE,CAAC,GAAG,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAC7C,cAAc,EACb,yGAAyG;gBACzG,8BAA8B;YAC/B,QAAQ,EAAE,sCAAsC,UAAU,EAAE;SAC5D,CAAC,CAAA;IACH,CAAC;IAED,OAAO,QAAQ,CAAA;AAChB,CAAC;AAED,gFAAgF;AAEhF,sFAAsF;AACtF,MAAM,oBAAoB,GAAoD;IAC7E;QACC,EAAE,EAAE,eAAe;QACnB,EAAE,EAAE,wHAAwH;QAC5H,IAAI,EAAE,6CAA6C;KACnD;IACD;QACC,EAAE,EAAE,oBAAoB;QACxB,EAAE,EAAE,oDAAoD;QACxD,IAAI,EAAE,gEAAgE;KACtE;IACD;QACC,EAAE,EAAE,sBAAsB;QAC1B,EAAE,EAAE,qCAAqC;QACzC,IAAI,EAAE,iCAAiC;KACvC;IACD;QACC,EAAE,EAAE,oBAAoB;QACxB,EAAE,EAAE,gCAAgC;QACpC,IAAI,EAAE,oCAAoC;KAC1C;CACD,CAAA;AAED,MAAM,UAAU,eAAe,CAAC,KAAc;IAC7C,MAAM,QAAQ,GAAoB,EAAE,CAAA;IACpC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC;QACrC,0EAA0E;QAC1E,yEAAyE;QACzE,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QAClD,KAAK,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,oBAAoB,EAAE,CAAC;YACrD,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;gBACpD,QAAQ,CAAC,IAAI,CAAC;oBACb,OAAO,EAAE,UAAU;oBACnB,KAAK,EAAE,eAAe,EAAE,EAAE;oBAC1B,KAAK,EAAE,oDAAoD;oBAC3D,MAAM,EACL,SAAS,IAAI,gFAAgF;wBAC7F,0EAA0E;oBAC3E,QAAQ,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE;oBACnD,cAAc,EACb,kGAAkG;wBAClG,yEAAyE;oBAC1E,QAAQ,EAAE,iDAAiD,eAAe,EAAE;iBAC5E,CAAC,CAAA;YACH,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,QAAQ,CAAA;AAChB,CAAC;AAED,iFAAiF;AAEjF,MAAM,gBAAgB,GAAG,sDAAsD,CAAA;AAC/E,MAAM,iBAAiB,GAAG,wBAAwB,CAAA;AAClD,MAAM,SAAS,GAAG,iDAAiD,CAAA;AACnE,MAAM,OAAO,GAAG,qCAAqC,CAAA;AAErD,MAAM,UAAU,YAAY,CAAC,CAAW,EAAE,KAAc;IACvD,MAAM,QAAQ,GAAoB,EAAE,CAAA;IACpC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAA;IAE/C,8EAA8E;IAC9E,8EAA8E;IAC9E,yEAAyE;IACzE,MAAM,aAAa,GAAG,IAAI,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAE/D,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC;QACrC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QACrC,2EAA2E;QAC3E,0EAA0E;QAC1E,0DAA0D;QAC1D,MAAM,YAAY,GAAG,WAAW,CAAC,IAAI,KAAK,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAA;QACnF,IAAI,CAAC,YAAY;YAAE,SAAQ;QAE3B,IAAI,WAAW,GAAG,CAAC,CAAA;QACnB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACrD,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;gBAAE,SAAQ,CAAC,wDAAwD;YACxF,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAA;YAC/C,QAAQ,CAAC,IAAI,CAAC;gBACb,OAAO,EAAE,UAAU;gBACnB,KAAK,EAAE,eAAe;gBACtB,KAAK,EAAE,kEAAkE;gBACzE,MAAM,EACL,sGAAsG;oBACtG,oCAAoC;gBACrC,QAAQ,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,6BAA6B;gBAC5D,cAAc,EAAE,2EAA2E;gBAC3F,QAAQ,EAAE,gDAAgD,OAAO,EAAE;aACnE,CAAC,CAAA;YACF,IAAI,EAAE,WAAW,IAAI,EAAE;gBAAE,MAAK;QAC/B,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,CAAC,EAAE,CAAC;YACnE,QAAQ,CAAC,IAAI,CAAC;gBACb,OAAO,EAAE,UAAU;gBACnB,KAAK,EAAE,sBAAsB;gBAC7B,KAAK,EAAE,+DAA+D;gBACtE,MAAM,EACL,oGAAoG;oBACpG,0CAA0C;gBAC3C,QAAQ,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE;gBACnD,cAAc,EACb,0FAA0F;gBAC3F,QAAQ,EAAE,gDAAgD,OAAO,EAAE;aACnE,CAAC,CAAA;QACH,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;YAC3D,QAAQ,CAAC,IAAI,CAAC;gBACb,OAAO,EAAE,UAAU;gBACnB,KAAK,EAAE,gBAAgB;gBACvB,KAAK,EAAE,8CAA8C;gBACrD,MAAM,EAAE,6DAA6D;gBACrE,QAAQ,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE;gBACjC,cAAc,EAAE,kEAAkE;gBAClF,QAAQ,EAAE,gDAAgD,OAAO,EAAE;aACnE,CAAC,CAAA;QACH,CAAC;IACF,CAAC;IAED,qFAAqF;IACrF,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC;QACrC,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QAClD,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;YACzD,QAAQ,CAAC,IAAI,CAAC;gBACb,OAAO,EAAE,UAAU;gBACnB,KAAK,EAAE,MAAM;gBACb,KAAK,EAAE,oDAAoD;gBAC3D,MAAM,EACL,iGAAiG;gBAClG,QAAQ,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE;gBACnD,cAAc,EACb,qGAAqG;gBACtG,QAAQ,EAAE,gDAAgD,OAAO,EAAE;aACnE,CAAC,CAAA;QACH,CAAC;IACF,CAAC;IAED,OAAO,QAAQ,CAAA;AAChB,CAAC;AAED,gFAAgF;AAEhF,MAAM,WAAW,GAAgE;IAChF;QACC,EAAE,EAAE,QAAQ;QACZ,EAAE,EAAE,eAAe;QACnB,GAAG,EAAE,QAAQ;QACb,GAAG,EAAE,4EAA4E;KACjF;IACD;QACC,EAAE,EAAE,UAAU;QACd,EAAE,EAAE,iBAAiB;QACrB,GAAG,EAAE,UAAU;QACf,GAAG,EAAE,uDAAuD;KAC5D;IACD;QACC,EAAE,EAAE,cAAc;QAClB,EAAE,EAAE,kBAAkB;QACtB,GAAG,EAAE,cAAc;QACnB,GAAG,EAAE,iDAAiD;KACtD;IACD;QACC,EAAE,EAAE,gBAAgB;QACpB,EAAE,EAAE,oBAAoB;QACxB,GAAG,EAAE,gBAAgB;QACrB,GAAG,EAAE,mDAAmD;KACxD;IACD;QACC,EAAE,EAAE,KAAK;QACT,EAAE,EAAE,oBAAoB;QACxB,GAAG,EAAE,gBAAgB;QACrB,GAAG,EAAE,iEAAiE;KACtE;IACD;QACC,EAAE,EAAE,qBAAqB;QACzB,EAAE,EAAE,wBAAwB;QAC5B,GAAG,EAAE,sCAAsC;QAC3C,GAAG,EAAE,wDAAwD;KAC7D;CACD,CAAA;AAED,0EAA0E;AAC1E,gFAAgF;AAChF,kDAAkD;AAClD,MAAM,WAAW,GAAG,oBAAoB,CAAA;AAExC,MAAM,UAAU,kBAAkB,CAAC,CAAW,EAAE,KAAc;IAC7D,MAAM,MAAM,GAAG,CAAC,CAAC,UAAU,EAAE,cAAc,CAAA;IAC3C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAA;IAC3D,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACvC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,CAAA;IAEpC,yEAAyE;IACzE,6EAA6E;IAC7E,sEAAsE;IACtE,6CAA6C;IAC7C,MAAM,QAAQ,GAAoB,EAAE,CAAA;IAEpC,KAAK,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,WAAW,EAAE,CAAC;QAChD,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;QACxC,IAAI,CAAC,KAAK;YAAE,SAAQ;QACpB,QAAQ,CAAC,IAAI,CAAC;YACb,OAAO,EAAE,UAAU;YACnB,KAAK,EAAE,MAAM,EAAE,EAAE;YACjB,KAAK,EAAE,6BAA6B,GAAG,uCAAuC;YAC9E,MAAM,EACL,6CAA6C,GAAG,SAAS,GAAG,yCAAyC;gBACrG,mFAAmF;YACpF,QAAQ,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE;YACpE,cAAc,EACb,EAAE,KAAK,KAAK;gBACX,CAAC,CAAC,wCAAwC;gBAC1C,CAAC,CAAC,gGAAgG;YACpG,QAAQ,EAAE,iDAAiD,kBAAkB,EAAE;SAC/E,CAAC,CAAA;IACH,CAAC;IAED,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IACjD,IAAI,KAAK,EAAE,CAAC;QACX,QAAQ,CAAC,IAAI,CAAC;YACb,0EAA0E;YAC1E,+DAA+D;YAC/D,OAAO,EAAE,WAAW;YACpB,KAAK,EAAE,oBAAoB;YAC3B,KAAK,EAAE,4DAA4D;YACnE,MAAM,EACL,mGAAmG;gBACnG,yGAAyG;YAC1G,QAAQ,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE;YACpE,cAAc,EACb,sGAAsG;YACvG,QAAQ,EAAE,iDAAiD,kBAAkB,EAAE;SAC/E,CAAC,CAAA;IACH,CAAC;IAED,OAAO,QAAQ,CAAA;AAChB,CAAC;AAED,gFAAgF;AAEhF,4GAA4G;AAC5G,SAAS,qBAAqB,CAAC,CAAW;IACzC,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,eAAe,IAAI,EAAE,EAAE,CAAC;QAC1C,IAAI,aAAa,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC;YAAE,OAAO,IAAI,CAAA;IAC9D,CAAC;IACD,OAAO,KAAK,CAAA;AACb,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,CAAW;IAChD,MAAM,MAAM,GAAG,uBAAuB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IAC9D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAClC,yEAAyE;IACzE,0EAA0E;IAC1E,uEAAuE;IACvE,IAAI,qBAAqB,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IAEvC,MAAM,iBAAiB,GAAG,OAAO,CAChC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,aAAa,IAAI,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CACrG,CAAA;IACD,IAAI,CAAC,iBAAiB;QAAE,OAAO,EAAE,CAAA;IAEjC,OAAO;QACN;YACC,OAAO,EAAE,MAAM;YACf,KAAK,EAAE,kBAAkB;YACzB,KAAK,EAAE,yFAAyF;YAChG,MAAM,EACL,wGAAwG;gBACxG,0GAA0G;gBAC1G,sEAAsE;YACvE,QAAQ,EAAE,uBAAuB,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAClE,cAAc,EACb,wGAAwG;gBACxG,2CAA2C;YAC5C,QAAQ,EAAE,4CAA4C,eAAe,EAAE;SACvE;KACD,CAAA;AACF,CAAC;AAWD,MAAM,UAAU,SAAS,CAAC,KAAmB;IAC5C,OAAO;QACN,GAAG,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC;QAC/B,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC;QAC5C,GAAG,kBAAkB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC;QAClD,GAAG,wBAAwB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,aAAa,CAAC;QAChE,GAAG,oBAAoB,CAAC,KAAK,CAAC,QAAQ,CAAC;QACvC,GAAG,qBAAqB,CAAC,KAAK,CAAC,QAAQ,CAAC;QACxC,GAAG,0BAA0B,EAAE;KAC/B,CAAA;AACF,CAAC;AAED,MAAM,aAAa,GAA4B,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAA;AASrF,MAAM,UAAU,SAAS,CAAC,QAAyB;IAClD,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;IAChG,MAAM,MAAM,GAA4B,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAA;IAC9E,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAA;IAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAA;AACnE,CAAC;AAED,gFAAgF;AAEhF,kFAAkF;AAClF,SAAS,QAAQ,CAAC,KAAc,EAAE,YAAoB;IACrD,MAAM,GAAG,GAAG,aAAa,CAAC,YAAY,CAAC,CAAA;IACvC,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACzC,KAAK,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,KAAK;QAAE,IAAI,aAAa,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,OAAO,CAAA;IAC9E,OAAO,SAAS,CAAA;AACjB,CAAC;AAOD,sFAAsF;AACtF,SAAS,aAAa,CAAC,OAAe,EAAE,EAAU;IACjD,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC1B,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IACnB,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;AAChC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,OAAe,EAAE,EAAU,EAAE,GAAG,GAAG,EAAE;IAC/D,4EAA4E;IAC5E,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,CAAA;IACrE,MAAM,GAAG,GAAe,EAAE,CAAA;IAC1B,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAA;QACvC,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG;YAAE,MAAK;IAC7B,CAAC;IACD,OAAO,GAAG,CAAA;AACX,CAAC;AAED,SAAS,MAAM,CAAC,OAAe,EAAE,KAAa;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAA;IACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IACpD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAA;AACxD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* review-risk — terminal rendering.
|
|
3
|
+
*
|
|
4
|
+
* Findings are grouped by consequence, worst first: what the store will REJECT,
|
|
5
|
+
* then what causes user ATTRITION on update, then what merely triggers a SLOW
|
|
6
|
+
* review. The label is the consequence, never a generic "error/warning".
|
|
7
|
+
*/
|
|
8
|
+
import type { ReviewSummary } from './review-checks.js';
|
|
9
|
+
export declare function renderReviewReport(summary: ReviewSummary): void;
|
|
10
|
+
//# sourceMappingURL=review-render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-render.d.ts","sourceRoot":"","sources":["../src/review-render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAiB,aAAa,EAAW,MAAM,oBAAoB,CAAA;AAwB/E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAyC/D"}
|