@freeappstore/compliance 0.1.0 → 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/LICENSE +21 -0
- package/dist/checks/no-brand-overrides.d.ts +24 -0
- package/dist/checks/no-brand-overrides.d.ts.map +1 -0
- package/dist/checks/no-brand-overrides.js +164 -0
- package/dist/checks/no-brand-overrides.js.map +1 -0
- package/dist/checks/no-brand-overrides.test.d.ts +2 -0
- package/dist/checks/no-brand-overrides.test.d.ts.map +1 -0
- package/dist/checks/no-brand-overrides.test.js +102 -0
- package/dist/checks/no-brand-overrides.test.js.map +1 -0
- package/dist/checks/no-scroll.d.ts +24 -0
- package/dist/checks/no-scroll.d.ts.map +1 -0
- package/dist/checks/no-scroll.js +143 -0
- package/dist/checks/no-scroll.js.map +1 -0
- package/dist/checks/no-scroll.test.d.ts +2 -0
- package/dist/checks/no-scroll.test.d.ts.map +1 -0
- package/dist/checks/no-scroll.test.js +105 -0
- package/dist/checks/no-scroll.test.js.map +1 -0
- package/dist/checks/unsafe-vh.d.ts +24 -0
- package/dist/checks/unsafe-vh.d.ts.map +1 -0
- package/dist/checks/unsafe-vh.js +110 -0
- package/dist/checks/unsafe-vh.js.map +1 -0
- package/dist/checks/unsafe-vh.test.d.ts +2 -0
- package/dist/checks/unsafe-vh.test.d.ts.map +1 -0
- package/dist/checks/unsafe-vh.test.js +115 -0
- package/dist/checks/unsafe-vh.test.js.map +1 -0
- package/dist/checks/viewport-support.d.ts +23 -0
- package/dist/checks/viewport-support.d.ts.map +1 -0
- package/dist/checks/viewport-support.js +87 -0
- package/dist/checks/viewport-support.js.map +1 -0
- package/dist/checks/viewport-support.test.d.ts +2 -0
- package/dist/checks/viewport-support.test.d.ts.map +1 -0
- package/dist/checks/viewport-support.test.js +63 -0
- package/dist/checks/viewport-support.test.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/live/index.d.ts +50 -0
- package/dist/live/index.d.ts.map +1 -0
- package/dist/live/index.js +311 -0
- package/dist/live/index.js.map +1 -0
- package/dist/live/index.test.d.ts +2 -0
- package/dist/live/index.test.d.ts.map +1 -0
- package/dist/live/index.test.js +259 -0
- package/dist/live/index.test.js.map +1 -0
- package/package.json +7 -7
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live-URL audit checks. Unlike the file-walking checks in ../checks/,
|
|
3
|
+
* these run inside a Cloudflare Worker (no filesystem) against published
|
|
4
|
+
* apps' live URLs to catch post-publish drift.
|
|
5
|
+
*
|
|
6
|
+
* What we can check from a live URL:
|
|
7
|
+
* - Tracking SDK injection (script src, inline scripts, fetched JS)
|
|
8
|
+
* - Brand fonts presence (link href to fonts.googleapis.com)
|
|
9
|
+
* - PWA manifest reachable + valid
|
|
10
|
+
* - Main bundle size (HEAD request)
|
|
11
|
+
*
|
|
12
|
+
* What we CAN'T check post-publish:
|
|
13
|
+
* - APPNAME placeholders (build erases these)
|
|
14
|
+
* - Brand-override CSS variables (sourcemaps not always shipped)
|
|
15
|
+
*
|
|
16
|
+
* The audit Worker runs these on a weekly cron and stores per-app
|
|
17
|
+
* results in D1 so creators (or platform admins) can see drift over
|
|
18
|
+
* time without leaving the storefront.
|
|
19
|
+
*/
|
|
20
|
+
const TRACKER_PATTERNS = [
|
|
21
|
+
/google-analytics\.com/i,
|
|
22
|
+
/gtag\(/i,
|
|
23
|
+
/googletagmanager\.com/i,
|
|
24
|
+
/\bamplitude\b/i,
|
|
25
|
+
/\bmixpanel\b/i,
|
|
26
|
+
/\bsegment\.com\b/i,
|
|
27
|
+
/\bhotjar\.com\b/i,
|
|
28
|
+
/plausible\.io/i,
|
|
29
|
+
/posthog\.com/i,
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Fetch with a timeout — Workers' default fetch waits 30s+, too slow
|
|
33
|
+
* when iterating 50+ apps in one cron tick.
|
|
34
|
+
*/
|
|
35
|
+
async function fetchWithTimeout(url, ms = 8000) {
|
|
36
|
+
const ctrl = new AbortController();
|
|
37
|
+
const timer = setTimeout(() => ctrl.abort(), ms);
|
|
38
|
+
try {
|
|
39
|
+
return await fetch(url, { signal: ctrl.signal, redirect: 'follow' });
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Worker subrequest cap errors look like "Too many subrequests by
|
|
47
|
+
* single Worker invocation." We don't want to record those as "fail"
|
|
48
|
+
* verdicts because the app isn't actually broken — the audit just
|
|
49
|
+
* couldn't check. Detect and flag for the caller.
|
|
50
|
+
*/
|
|
51
|
+
function isSubrequestCapError(err) {
|
|
52
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
53
|
+
return /too many subrequests|subrequest.*limit/i.test(msg);
|
|
54
|
+
}
|
|
55
|
+
export async function auditLive(input) {
|
|
56
|
+
const checkedAt = Date.now();
|
|
57
|
+
const report = {
|
|
58
|
+
appId: input.appId,
|
|
59
|
+
liveUrl: input.liveUrl,
|
|
60
|
+
checkedAt,
|
|
61
|
+
reachable: false,
|
|
62
|
+
results: [],
|
|
63
|
+
};
|
|
64
|
+
let html;
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetchWithTimeout(input.liveUrl);
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
report.results.push({
|
|
69
|
+
name: 'Reachable',
|
|
70
|
+
status: 'fail',
|
|
71
|
+
detail: `HTTP ${res.status} from ${input.liveUrl}`,
|
|
72
|
+
});
|
|
73
|
+
return report;
|
|
74
|
+
}
|
|
75
|
+
html = await res.text();
|
|
76
|
+
report.reachable = true;
|
|
77
|
+
// Always record a Reachable row on success — otherwise stale
|
|
78
|
+
// "Reachable: fail" rows from previous failed runs (e.g.
|
|
79
|
+
// subrequest-cap noise) never get overwritten, because the
|
|
80
|
+
// upsert is keyed on (app_id, check_name).
|
|
81
|
+
report.results.push({
|
|
82
|
+
name: 'Reachable',
|
|
83
|
+
status: 'pass',
|
|
84
|
+
detail: `${input.liveUrl} → HTTP ${res.status}`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
// Subrequest cap means we couldn't even fetch — that's a "warn"
|
|
89
|
+
// (re-check needed), not a "fail" (app broken). Distinguishing
|
|
90
|
+
// matters: failures show red badges in the storefront.
|
|
91
|
+
const skipped = isSubrequestCapError(err);
|
|
92
|
+
report.results.push({
|
|
93
|
+
name: 'Reachable',
|
|
94
|
+
status: skipped ? 'warn' : 'fail',
|
|
95
|
+
detail: skipped
|
|
96
|
+
? 'audit Worker hit subrequest cap — re-run needed'
|
|
97
|
+
: err instanceof Error
|
|
98
|
+
? err.message
|
|
99
|
+
: 'fetch failed',
|
|
100
|
+
});
|
|
101
|
+
return report;
|
|
102
|
+
}
|
|
103
|
+
// Run all checks in parallel — each is independent.
|
|
104
|
+
const [tracking, fonts, manifest, bundle, vh] = await Promise.all([
|
|
105
|
+
checkNoTrackingLive(html),
|
|
106
|
+
checkBrandFontsLive(html),
|
|
107
|
+
checkManifestLive(html, input.liveUrl),
|
|
108
|
+
checkBundleSizeLive(html, input.liveUrl),
|
|
109
|
+
checkUnsafeVhLive(html, input.liveUrl),
|
|
110
|
+
]);
|
|
111
|
+
report.results.push(tracking, fonts, manifest, bundle, vh);
|
|
112
|
+
return report;
|
|
113
|
+
}
|
|
114
|
+
export function checkNoTrackingLive(html) {
|
|
115
|
+
const found = TRACKER_PATTERNS.filter((re) => re.test(html));
|
|
116
|
+
if (found.length === 0) {
|
|
117
|
+
return { name: 'No tracking SDKs (live)', status: 'pass', detail: 'no known trackers in HTML' };
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
name: 'No tracking SDKs (live)',
|
|
121
|
+
status: 'fail',
|
|
122
|
+
detail: `${found.length} pattern${found.length === 1 ? '' : 's'} matched`,
|
|
123
|
+
suggestions: [
|
|
124
|
+
'Tracking was injected after publish — remove the script tag or third-party loader.',
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export function checkBrandFontsLive(html) {
|
|
129
|
+
const hasManrope = /manrope/i.test(html);
|
|
130
|
+
const hasFraunces = /fraunces/i.test(html);
|
|
131
|
+
if (hasManrope && hasFraunces) {
|
|
132
|
+
return { name: 'Brand fonts (live)', status: 'pass', detail: 'Manrope + Fraunces referenced' };
|
|
133
|
+
}
|
|
134
|
+
const missing = [];
|
|
135
|
+
if (!hasManrope)
|
|
136
|
+
missing.push('Manrope');
|
|
137
|
+
if (!hasFraunces)
|
|
138
|
+
missing.push('Fraunces');
|
|
139
|
+
return {
|
|
140
|
+
name: 'Brand fonts (live)',
|
|
141
|
+
status: 'fail',
|
|
142
|
+
detail: `missing: ${missing.join(', ')}`,
|
|
143
|
+
suggestions: ['Restore the Google Fonts <link> in web/index.html.'],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export async function checkManifestLive(html, liveUrl) {
|
|
147
|
+
// Find the manifest link in the HTML, then fetch + validate it.
|
|
148
|
+
const m = /<link[^>]*rel=["']manifest["'][^>]*href=["']([^"']+)["']/i.exec(html);
|
|
149
|
+
if (!m) {
|
|
150
|
+
return {
|
|
151
|
+
name: 'PWA manifest (live)',
|
|
152
|
+
status: 'fail',
|
|
153
|
+
detail: 'no <link rel="manifest"> in HTML',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const href = m[1];
|
|
157
|
+
const manifestUrl = new URL(href, liveUrl).toString();
|
|
158
|
+
try {
|
|
159
|
+
const res = await fetchWithTimeout(manifestUrl);
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
return {
|
|
162
|
+
name: 'PWA manifest (live)',
|
|
163
|
+
status: 'fail',
|
|
164
|
+
detail: `${manifestUrl} → HTTP ${res.status}`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const body = (await res.json());
|
|
168
|
+
const missing = [];
|
|
169
|
+
if (!body.name)
|
|
170
|
+
missing.push('name');
|
|
171
|
+
if (!body.display)
|
|
172
|
+
missing.push('display');
|
|
173
|
+
if (!body.start_url)
|
|
174
|
+
missing.push('start_url');
|
|
175
|
+
if (missing.length > 0) {
|
|
176
|
+
return {
|
|
177
|
+
name: 'PWA manifest (live)',
|
|
178
|
+
status: 'fail',
|
|
179
|
+
detail: `manifest missing fields: ${missing.join(', ')}`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return { name: 'PWA manifest (live)', status: 'pass', detail: manifestUrl };
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
const skipped = isSubrequestCapError(err);
|
|
186
|
+
return {
|
|
187
|
+
name: 'PWA manifest (live)',
|
|
188
|
+
status: skipped ? 'warn' : 'fail',
|
|
189
|
+
detail: skipped
|
|
190
|
+
? 'audit Worker hit subrequest cap — re-run needed'
|
|
191
|
+
: err instanceof Error
|
|
192
|
+
? err.message
|
|
193
|
+
: 'manifest fetch/parse failed',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export async function checkBundleSizeLive(html, liveUrl) {
|
|
198
|
+
// Find the largest-looking bundled JS reference. Vite output looks like
|
|
199
|
+
// src="/assets/index-XYZ.js" — starts with `/`, no chars before. Use
|
|
200
|
+
// `[^"']*` (zero or more) instead of `[^"']+` (one or more) so the
|
|
201
|
+
// root-relative path matches; with `+` every Vite app was misreported
|
|
202
|
+
// as "non-Vite layout" and the audit silently skipped bundle-size.
|
|
203
|
+
const m = /<script[^>]+src=["']([^"']*\/assets\/[^"']+\.js)["']/i.exec(html);
|
|
204
|
+
if (!m) {
|
|
205
|
+
// Plausible miss for non-Vite apps — not a fail, just skip.
|
|
206
|
+
return {
|
|
207
|
+
name: 'Bundle size (live)',
|
|
208
|
+
status: 'warn',
|
|
209
|
+
detail: 'no /assets/*.js found — non-Vite layout?',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const jsUrl = new URL(m[1], liveUrl).toString();
|
|
213
|
+
try {
|
|
214
|
+
const res = await fetchWithTimeout(jsUrl);
|
|
215
|
+
if (!res.ok) {
|
|
216
|
+
return { name: 'Bundle size (live)', status: 'fail', detail: `${jsUrl} → ${res.status}` };
|
|
217
|
+
}
|
|
218
|
+
const body = await res.arrayBuffer();
|
|
219
|
+
// Approximate gzipped size as raw / 3.5. CompressionStream is in
|
|
220
|
+
// Workers but adding a real gzip pass is more code; the
|
|
221
|
+
// approximation is conservative enough for the 300 KB threshold.
|
|
222
|
+
const rawKb = Math.round(body.byteLength / 1024);
|
|
223
|
+
const approxGzipKb = Math.round(rawKb / 3.5);
|
|
224
|
+
if (approxGzipKb > 300) {
|
|
225
|
+
return {
|
|
226
|
+
name: 'Bundle size (live)',
|
|
227
|
+
status: 'fail',
|
|
228
|
+
detail: `~${approxGzipKb} KB gzipped (raw ${rawKb} KB) — over 300 KB limit`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
name: 'Bundle size (live)',
|
|
233
|
+
status: 'pass',
|
|
234
|
+
detail: `~${approxGzipKb} KB gzipped (raw ${rawKb} KB)`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
const skipped = isSubrequestCapError(err);
|
|
239
|
+
return {
|
|
240
|
+
name: 'Bundle size (live)',
|
|
241
|
+
status: 'warn',
|
|
242
|
+
detail: skipped
|
|
243
|
+
? 'audit Worker hit subrequest cap — re-run needed'
|
|
244
|
+
: err instanceof Error
|
|
245
|
+
? err.message
|
|
246
|
+
: 'bundle fetch failed',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Live counterpart to checkUnsafeVh. Fetches the first bundled
|
|
252
|
+
* stylesheet linked from the HTML and scans it for `100vh` (the
|
|
253
|
+
* unit, not the Tailwind class — by build time those have been
|
|
254
|
+
* compiled to CSS). Catches whiteboard-class iOS Safari URL-bar
|
|
255
|
+
* scroll bugs in apps that have already shipped.
|
|
256
|
+
*
|
|
257
|
+
* Cost: one extra subrequest per app. The audit Worker runs near the
|
|
258
|
+
* free-plan subrequest cap; on cap errors we degrade to `warn`
|
|
259
|
+
* (re-run needed) rather than `fail` (app broken) — the app isn't
|
|
260
|
+
* actually broken, the audit just couldn't check.
|
|
261
|
+
*/
|
|
262
|
+
export async function checkUnsafeVhLive(html, liveUrl) {
|
|
263
|
+
// Find the first bundled stylesheet. Vite output looks like
|
|
264
|
+
// <link rel="stylesheet" href="/assets/index-XYZ.css">. Be
|
|
265
|
+
// tolerant of attribute order — rel and href can appear either way.
|
|
266
|
+
const m = /<link[^>]+rel=["']stylesheet["'][^>]*href=["']([^"']+\.css[^"']*)["']/i.exec(html) ??
|
|
267
|
+
/<link[^>]+href=["']([^"']+\.css[^"']*)["'][^>]*rel=["']stylesheet["']/i.exec(html);
|
|
268
|
+
if (!m) {
|
|
269
|
+
// No stylesheet link — could be inline-only styles, or styles
|
|
270
|
+
// injected at runtime. Not a fail; just nothing to check.
|
|
271
|
+
return {
|
|
272
|
+
name: 'No unsafe 100vh (live)',
|
|
273
|
+
status: 'warn',
|
|
274
|
+
detail: 'no <link rel="stylesheet"> found in HTML — skipped',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const cssUrl = new URL(m[1], liveUrl).toString();
|
|
278
|
+
try {
|
|
279
|
+
const res = await fetchWithTimeout(cssUrl);
|
|
280
|
+
if (!res.ok) {
|
|
281
|
+
return { name: 'No unsafe 100vh (live)', status: 'warn', detail: `${cssUrl} → HTTP ${res.status}` };
|
|
282
|
+
}
|
|
283
|
+
const css = await res.text();
|
|
284
|
+
const matches = css.match(/\b100vh\b/g) ?? [];
|
|
285
|
+
if (matches.length === 0) {
|
|
286
|
+
return { name: 'No unsafe 100vh (live)', status: 'pass', detail: `scanned ${cssUrl}` };
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
name: 'No unsafe 100vh (live)',
|
|
290
|
+
status: 'warn',
|
|
291
|
+
detail: `${matches.length} occurrence(s) of 100vh in ${cssUrl}`,
|
|
292
|
+
suggestions: [
|
|
293
|
+
'100vh resolves to the layout viewport on iOS Safari — when the URL bar is visible the page scrolls.',
|
|
294
|
+
'Replace with 100svh (small viewport — accounts for visible browser UI) or 100dvh (dynamic — recomputes as URL bar moves).',
|
|
295
|
+
],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
const skipped = isSubrequestCapError(err);
|
|
300
|
+
return {
|
|
301
|
+
name: 'No unsafe 100vh (live)',
|
|
302
|
+
status: 'warn',
|
|
303
|
+
detail: skipped
|
|
304
|
+
? 'audit Worker hit subrequest cap — re-run needed'
|
|
305
|
+
: err instanceof Error
|
|
306
|
+
? err.message
|
|
307
|
+
: 'CSS fetch failed',
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/live/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/live/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
2
|
+
import { checkNoTrackingLive, checkBrandFontsLive, checkManifestLive, checkBundleSizeLive, checkUnsafeVhLive, auditLive, } from './index.js';
|
|
3
|
+
describe('checkNoTrackingLive', () => {
|
|
4
|
+
it('passes when HTML is clean of tracker patterns', () => {
|
|
5
|
+
const r = checkNoTrackingLive('<html><body>hello</body></html>');
|
|
6
|
+
expect(r.status).toBe('pass');
|
|
7
|
+
expect(r.detail).toMatch(/no known trackers/);
|
|
8
|
+
});
|
|
9
|
+
it('flags Google Analytics', () => {
|
|
10
|
+
const r = checkNoTrackingLive('<script src="https://google-analytics.com/ga.js"></script>');
|
|
11
|
+
expect(r.status).toBe('fail');
|
|
12
|
+
expect(r.suggestions?.[0]).toMatch(/Tracking was injected/);
|
|
13
|
+
});
|
|
14
|
+
it('flags Plausible, Mixpanel, Hotjar variants', () => {
|
|
15
|
+
expect(checkNoTrackingLive('<script src="https://plausible.io/script.js">').status).toBe('fail');
|
|
16
|
+
expect(checkNoTrackingLive('<div>amplitude tracker</div>').status).toBe('fail');
|
|
17
|
+
expect(checkNoTrackingLive('<script>gtag("event")</script>').status).toBe('fail');
|
|
18
|
+
expect(checkNoTrackingLive('<script src="https://www.googletagmanager.com/gtm.js">').status).toBe('fail');
|
|
19
|
+
expect(checkNoTrackingLive('<script src="//cdn.mixpanel.com/x">').status).toBe('fail');
|
|
20
|
+
expect(checkNoTrackingLive('<script src="https://static.hotjar.com/c/hotjar.js">').status).toBe('fail');
|
|
21
|
+
expect(checkNoTrackingLive('<script src="https://posthog.com/array.js">').status).toBe('fail');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('checkBrandFontsLive', () => {
|
|
25
|
+
it('passes when both Manrope and Fraunces are referenced', () => {
|
|
26
|
+
const r = checkBrandFontsLive('<link href="https://fonts.googleapis.com/css2?family=Manrope&family=Fraunces" rel="stylesheet">');
|
|
27
|
+
expect(r.status).toBe('pass');
|
|
28
|
+
});
|
|
29
|
+
it('matches case-insensitively', () => {
|
|
30
|
+
const r = checkBrandFontsLive('<style>font-family: manrope, fraunces</style>');
|
|
31
|
+
expect(r.status).toBe('pass');
|
|
32
|
+
});
|
|
33
|
+
it('fails when one or both fonts are missing', () => {
|
|
34
|
+
const r1 = checkBrandFontsLive('<link href="...?family=Manrope">');
|
|
35
|
+
expect(r1.status).toBe('fail');
|
|
36
|
+
expect(r1.detail).toMatch(/Fraunces/);
|
|
37
|
+
const r2 = checkBrandFontsLive('<head></head>');
|
|
38
|
+
expect(r2.status).toBe('fail');
|
|
39
|
+
expect(r2.detail).toMatch(/Manrope.*Fraunces/);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('checkManifestLive', () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
vi.stubGlobal('fetch', vi.fn());
|
|
45
|
+
});
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
vi.unstubAllGlobals();
|
|
48
|
+
});
|
|
49
|
+
it('fails when no <link rel="manifest"> in HTML', async () => {
|
|
50
|
+
const r = await checkManifestLive('<html><head></head></html>', 'https://x.example');
|
|
51
|
+
expect(r.status).toBe('fail');
|
|
52
|
+
expect(r.detail).toMatch(/no <link rel="manifest"/);
|
|
53
|
+
});
|
|
54
|
+
it('fails when manifest fetch returns non-2xx', async () => {
|
|
55
|
+
fetch.mockResolvedValue(new Response('', { status: 404 }));
|
|
56
|
+
const html = '<link rel="manifest" href="/manifest.json">';
|
|
57
|
+
const r = await checkManifestLive(html, 'https://x.example');
|
|
58
|
+
expect(r.status).toBe('fail');
|
|
59
|
+
expect(r.detail).toMatch(/404/);
|
|
60
|
+
});
|
|
61
|
+
it('fails when manifest is missing required fields', async () => {
|
|
62
|
+
fetch.mockResolvedValue(new Response(JSON.stringify({ name: 'X' }), {
|
|
63
|
+
status: 200,
|
|
64
|
+
headers: { 'Content-Type': 'application/json' },
|
|
65
|
+
}));
|
|
66
|
+
const html = '<link rel="manifest" href="/manifest.json">';
|
|
67
|
+
const r = await checkManifestLive(html, 'https://x.example');
|
|
68
|
+
expect(r.status).toBe('fail');
|
|
69
|
+
expect(r.detail).toMatch(/display.*start_url|start_url.*display/);
|
|
70
|
+
});
|
|
71
|
+
it('passes when manifest has all required fields', async () => {
|
|
72
|
+
fetch.mockResolvedValue(new Response(JSON.stringify({ name: 'X', display: 'standalone', start_url: '/' }), {
|
|
73
|
+
status: 200,
|
|
74
|
+
}));
|
|
75
|
+
const html = '<link rel="manifest" href="/manifest.json">';
|
|
76
|
+
const r = await checkManifestLive(html, 'https://x.example');
|
|
77
|
+
expect(r.status).toBe('pass');
|
|
78
|
+
});
|
|
79
|
+
it('resolves manifest URL relative to liveUrl', async () => {
|
|
80
|
+
const mockFetch = vi.fn().mockResolvedValue(new Response(JSON.stringify({ name: 'X', display: 'standalone', start_url: '/' }), {
|
|
81
|
+
status: 200,
|
|
82
|
+
}));
|
|
83
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
84
|
+
const html = '<link rel="manifest" href="manifest.json">';
|
|
85
|
+
await checkManifestLive(html, 'https://app.example/');
|
|
86
|
+
// First arg is the URL — should resolve to absolute.
|
|
87
|
+
expect(mockFetch).toHaveBeenCalledWith('https://app.example/manifest.json', expect.any(Object));
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('checkBundleSizeLive', () => {
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
vi.stubGlobal('fetch', vi.fn());
|
|
93
|
+
});
|
|
94
|
+
afterEach(() => {
|
|
95
|
+
vi.unstubAllGlobals();
|
|
96
|
+
});
|
|
97
|
+
it('returns warn when no /assets/*.js found (non-Vite layout)', async () => {
|
|
98
|
+
const r = await checkBundleSizeLive('<html><body></body></html>', 'https://x.example');
|
|
99
|
+
expect(r.status).toBe('warn');
|
|
100
|
+
expect(r.detail).toMatch(/non-Vite/);
|
|
101
|
+
});
|
|
102
|
+
it('passes when bundle is comfortably under 300 KB gzipped', async () => {
|
|
103
|
+
// 200KB raw → ~57KB approx gzipped — well under.
|
|
104
|
+
const small = new ArrayBuffer(200_000);
|
|
105
|
+
fetch.mockResolvedValue(new Response(small, { status: 200 }));
|
|
106
|
+
const html = '<script src="/assets/index-abc.js"></script>';
|
|
107
|
+
const r = await checkBundleSizeLive(html, 'https://x.example');
|
|
108
|
+
expect(r.status).toBe('pass');
|
|
109
|
+
expect(r.detail).toMatch(/KB gzipped/);
|
|
110
|
+
});
|
|
111
|
+
it('fails when bundle approximation exceeds 300 KB gzipped', async () => {
|
|
112
|
+
// 1.5MB raw → ~430KB approx gzipped — over limit.
|
|
113
|
+
const large = new ArrayBuffer(1_500_000);
|
|
114
|
+
fetch.mockResolvedValue(new Response(large, { status: 200 }));
|
|
115
|
+
const html = '<script src="/assets/index-big.js"></script>';
|
|
116
|
+
const r = await checkBundleSizeLive(html, 'https://x.example');
|
|
117
|
+
expect(r.status).toBe('fail');
|
|
118
|
+
expect(r.detail).toMatch(/over 300 KB/);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('auditLive (integration)', () => {
|
|
122
|
+
beforeEach(() => {
|
|
123
|
+
vi.stubGlobal('fetch', vi.fn());
|
|
124
|
+
});
|
|
125
|
+
afterEach(() => {
|
|
126
|
+
vi.unstubAllGlobals();
|
|
127
|
+
});
|
|
128
|
+
it('returns reachable=false + a single fail check when fetch errors', async () => {
|
|
129
|
+
fetch.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
130
|
+
const r = await auditLive({ appId: 'tip', liveUrl: 'https://tip.example' });
|
|
131
|
+
expect(r.reachable).toBe(false);
|
|
132
|
+
expect(r.results).toHaveLength(1);
|
|
133
|
+
expect(r.results[0].status).toBe('fail');
|
|
134
|
+
expect(r.results[0].name).toBe('Reachable');
|
|
135
|
+
});
|
|
136
|
+
it('returns reachable=false + fail when origin returns 5xx', async () => {
|
|
137
|
+
fetch.mockResolvedValue(new Response('boom', { status: 503 }));
|
|
138
|
+
const r = await auditLive({ appId: 'tip', liveUrl: 'https://tip.example' });
|
|
139
|
+
expect(r.reachable).toBe(false);
|
|
140
|
+
expect(r.results[0].detail).toMatch(/HTTP 503/);
|
|
141
|
+
});
|
|
142
|
+
it('runs all 5 checks when origin is reachable', async () => {
|
|
143
|
+
const html = `
|
|
144
|
+
<html><head>
|
|
145
|
+
<link href="https://fonts.googleapis.com/css2?family=Manrope&family=Fraunces" rel="stylesheet">
|
|
146
|
+
<link rel="stylesheet" href="/assets/index-abc.css">
|
|
147
|
+
<link rel="manifest" href="/manifest.json">
|
|
148
|
+
</head><body>
|
|
149
|
+
<script src="/assets/index-abc.js"></script>
|
|
150
|
+
</body></html>
|
|
151
|
+
`;
|
|
152
|
+
let callCount = 0;
|
|
153
|
+
fetch.mockImplementation(async (url) => {
|
|
154
|
+
callCount++;
|
|
155
|
+
if (callCount === 1)
|
|
156
|
+
return new Response(html, { status: 200 });
|
|
157
|
+
if (url.endsWith('/manifest.json')) {
|
|
158
|
+
return new Response(JSON.stringify({ name: 'tip', display: 'standalone', start_url: '/' }), { status: 200 });
|
|
159
|
+
}
|
|
160
|
+
if (url.endsWith('.css')) {
|
|
161
|
+
return new Response('body { height: 100svh; }', { status: 200 });
|
|
162
|
+
}
|
|
163
|
+
// bundle: small size → pass
|
|
164
|
+
return new Response(new ArrayBuffer(150_000), { status: 200 });
|
|
165
|
+
});
|
|
166
|
+
const r = await auditLive({ appId: 'tip', liveUrl: 'https://tip.example' });
|
|
167
|
+
expect(r.reachable).toBe(true);
|
|
168
|
+
// 6 results: Reachable + tracking + fonts + manifest + bundle + vh.
|
|
169
|
+
// Reachable is recorded on success too so stale fail rows from
|
|
170
|
+
// earlier runs get overwritten on the next clean pass.
|
|
171
|
+
expect(r.results).toHaveLength(6);
|
|
172
|
+
const reachable = r.results.find((c) => c.name === 'Reachable');
|
|
173
|
+
expect(reachable?.status).toBe('pass');
|
|
174
|
+
const otherStatuses = r.results.filter((c) => c.name !== 'Reachable').map((c) => c.status);
|
|
175
|
+
expect(otherStatuses).toEqual(['pass', 'pass', 'pass', 'pass', 'pass']);
|
|
176
|
+
});
|
|
177
|
+
it('downgrades subrequest-cap errors from fail → warn (not the app\'s fault)', async () => {
|
|
178
|
+
fetch.mockRejectedValue(new Error('Too many subrequests by single Worker invocation.'));
|
|
179
|
+
const r = await auditLive({ appId: 'tip', liveUrl: 'https://tip.example' });
|
|
180
|
+
expect(r.reachable).toBe(false);
|
|
181
|
+
expect(r.results).toHaveLength(1);
|
|
182
|
+
expect(r.results[0].name).toBe('Reachable');
|
|
183
|
+
expect(r.results[0].status).toBe('warn');
|
|
184
|
+
expect(r.results[0].detail).toMatch(/subrequest cap/);
|
|
185
|
+
});
|
|
186
|
+
it('stamps checkedAt and preserves appId', async () => {
|
|
187
|
+
fetch.mockRejectedValue(new Error('x'));
|
|
188
|
+
const before = Date.now();
|
|
189
|
+
const r = await auditLive({ appId: 'my-app', liveUrl: 'https://my.example' });
|
|
190
|
+
const after = Date.now();
|
|
191
|
+
expect(r.appId).toBe('my-app');
|
|
192
|
+
expect(r.liveUrl).toBe('https://my.example');
|
|
193
|
+
expect(r.checkedAt).toBeGreaterThanOrEqual(before);
|
|
194
|
+
expect(r.checkedAt).toBeLessThanOrEqual(after);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
describe('checkUnsafeVhLive', () => {
|
|
198
|
+
beforeEach(() => {
|
|
199
|
+
vi.stubGlobal('fetch', vi.fn());
|
|
200
|
+
});
|
|
201
|
+
afterEach(() => {
|
|
202
|
+
vi.unstubAllGlobals();
|
|
203
|
+
});
|
|
204
|
+
it('warns when no <link rel="stylesheet"> exists in HTML', async () => {
|
|
205
|
+
const r = await checkUnsafeVhLive('<html><body></body></html>', 'https://x.example');
|
|
206
|
+
expect(r.status).toBe('warn');
|
|
207
|
+
expect(r.detail).toMatch(/no.*stylesheet/);
|
|
208
|
+
});
|
|
209
|
+
it('finds the stylesheet href regardless of attribute order (rel before href)', async () => {
|
|
210
|
+
fetch.mockResolvedValue(new Response('body { height: 100svh; }', { status: 200 }));
|
|
211
|
+
const html = '<link rel="stylesheet" href="/assets/index.css">';
|
|
212
|
+
const r = await checkUnsafeVhLive(html, 'https://x.example');
|
|
213
|
+
expect(r.status).toBe('pass');
|
|
214
|
+
});
|
|
215
|
+
it('finds the stylesheet href regardless of attribute order (href before rel)', async () => {
|
|
216
|
+
fetch.mockResolvedValue(new Response('body { height: 100svh; }', { status: 200 }));
|
|
217
|
+
const html = '<link href="/assets/index.css" rel="stylesheet">';
|
|
218
|
+
const r = await checkUnsafeVhLive(html, 'https://x.example');
|
|
219
|
+
expect(r.status).toBe('pass');
|
|
220
|
+
});
|
|
221
|
+
it('warns when CSS contains 100vh', async () => {
|
|
222
|
+
fetch.mockResolvedValue(new Response('.full { height: 100vh; padding: 100vh; }', { status: 200 }));
|
|
223
|
+
const html = '<link rel="stylesheet" href="/assets/index.css">';
|
|
224
|
+
const r = await checkUnsafeVhLive(html, 'https://x.example');
|
|
225
|
+
expect(r.status).toBe('warn');
|
|
226
|
+
expect(r.detail).toMatch(/2 occurrence\(s\) of 100vh/);
|
|
227
|
+
expect(r.suggestions?.join('\n')).toMatch(/100svh.*100dvh/);
|
|
228
|
+
});
|
|
229
|
+
it('passes when CSS uses 100svh / 100dvh exclusively', async () => {
|
|
230
|
+
fetch.mockResolvedValue(new Response('a{height:100svh}b{height:100dvh}', { status: 200 }));
|
|
231
|
+
const html = '<link rel="stylesheet" href="/assets/index.css">';
|
|
232
|
+
const r = await checkUnsafeVhLive(html, 'https://x.example');
|
|
233
|
+
expect(r.status).toBe('pass');
|
|
234
|
+
});
|
|
235
|
+
it('does not match identifier-like substrings (word boundary on the unit)', async () => {
|
|
236
|
+
// `--my100vhvar` and `var100vh1` aren't actual unit usage; the
|
|
237
|
+
// word-boundary anchors on the regex literal must keep us from
|
|
238
|
+
// flagging them.
|
|
239
|
+
fetch.mockResolvedValue(new Response('.x { --my100vhvar: 0; } .y { content: "var100vh1"; }', { status: 200 }));
|
|
240
|
+
const html = '<link rel="stylesheet" href="/assets/index.css">';
|
|
241
|
+
const r = await checkUnsafeVhLive(html, 'https://x.example');
|
|
242
|
+
expect(r.status).toBe('pass');
|
|
243
|
+
});
|
|
244
|
+
it('warns (not fail) on subrequest cap errors so audit-Worker noise does not flip badges', async () => {
|
|
245
|
+
fetch.mockRejectedValue(new Error('Too many subrequests by single Worker invocation'));
|
|
246
|
+
const html = '<link rel="stylesheet" href="/assets/index.css">';
|
|
247
|
+
const r = await checkUnsafeVhLive(html, 'https://x.example');
|
|
248
|
+
expect(r.status).toBe('warn');
|
|
249
|
+
expect(r.detail).toMatch(/subrequest cap/);
|
|
250
|
+
});
|
|
251
|
+
it('warns on non-200 CSS fetch response', async () => {
|
|
252
|
+
fetch.mockResolvedValue(new Response('', { status: 404 }));
|
|
253
|
+
const html = '<link rel="stylesheet" href="/assets/index.css">';
|
|
254
|
+
const r = await checkUnsafeVhLive(html, 'https://x.example');
|
|
255
|
+
expect(r.status).toBe('warn');
|
|
256
|
+
expect(r.detail).toMatch(/HTTP 404/);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/live/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,GAAG,mBAAmB,CAAC,iCAAiC,CAAC,CAAC;QACjE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,CAAC,GAAG,mBAAmB,CAAC,4DAA4D,CAAC,CAAC;QAC5F,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,mBAAmB,CAAC,+CAA+C,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjG,MAAM,CAAC,mBAAmB,CAAC,8BAA8B,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChF,MAAM,CAAC,mBAAmB,CAAC,gCAAgC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAClF,MAAM,CAAC,mBAAmB,CAAC,wDAAwD,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1G,MAAM,CAAC,mBAAmB,CAAC,qCAAqC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvF,MAAM,CAAC,mBAAmB,CAAC,sDAAsD,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxG,MAAM,CAAC,mBAAmB,CAAC,6CAA6C,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,GAAG,mBAAmB,CAC3B,iGAAiG,CAClG,CAAC;QACF,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,GAAG,mBAAmB,CAAC,+CAA+C,CAAC,CAAC;QAC/E,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,EAAE,GAAG,mBAAmB,CAAC,kCAAkC,CAAC,CAAC;QACnE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/B,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACtC,MAAM,EAAE,GAAG,mBAAmB,CAAC,eAAe,CAAC,CAAC;QAChD,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/B,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,4BAA4B,EAAE,mBAAmB,CAAC,CAAC;QACrF,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACxD,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAClC,CAAC;QACF,MAAM,IAAI,GAAG,6CAA6C,CAAC;QAC3D,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC7D,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE;YAC1C,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CACH,CAAC;QACF,MAAM,IAAI,GAAG,6CAA6C,CAAC;QAC3D,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC3D,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE;YACjF,MAAM,EAAE,GAAG;SACZ,CAAC,CACH,CAAC;QACF,MAAM,IAAI,GAAG,6CAA6C,CAAC;QAC3D,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CACzC,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE;YACjF,MAAM,EAAE,GAAG;SACZ,CAAC,CACH,CAAC;QACF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,4CAA4C,CAAC;QAC1D,MAAM,iBAAiB,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;QACtD,qDAAqD;QACrD,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,CACpC,mCAAmC,EACnC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CACnB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,CAAC,GAAG,MAAM,mBAAmB,CAAC,4BAA4B,EAAE,mBAAmB,CAAC,CAAC;QACvF,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,iDAAiD;QACjD,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;QACtC,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CACrC,CAAC;QACF,MAAM,IAAI,GAAG,8CAA8C,CAAC;QAC5D,MAAM,CAAC,GAAG,MAAM,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC/D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,kDAAkD;QAClD,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC;QACxC,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CACrC,CAAC;QACF,MAAM,IAAI,GAAG,8CAA8C,CAAC;QAC5D,MAAM,CAAC,GAAG,MAAM,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC/D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC9E,KAA6C,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;QAC5F,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACrE,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CACtC,CAAC;QACF,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,IAAI,GAAG;;;;;;;;KAQZ,CAAC;QACF,IAAI,SAAS,GAAG,CAAC,CAAC;QACjB,KAA6C,CAAC,kBAAkB,CAAC,KAAK,EAAE,GAAW,EAAE,EAAE;YACtF,SAAS,EAAE,CAAC;YACZ,IAAI,SAAS,KAAK,CAAC;gBAAE,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YAChE,IAAI,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBACnC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YAC/G,CAAC;YACD,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,OAAO,IAAI,QAAQ,CAAC,0BAA0B,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YACnE,CAAC;YACD,4BAA4B;YAC5B,OAAO,IAAI,QAAQ,CAAC,IAAI,WAAW,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,oEAAoE;QACpE,+DAA+D;QAC/D,uDAAuD;QACvD,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QAChE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,aAAa,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC3F,MAAM,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACvF,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAC/D,CAAC;QACF,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7C,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACnD,KAA6C,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QACjF,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC1B,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAC9E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC7C,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;QACnD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,4BAA4B,EAAE,mBAAmB,CAAC,CAAC;QACrF,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;QACxF,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,0BAA0B,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAC1D,CAAC;QACF,MAAM,IAAI,GAAG,kDAAkD,CAAC;QAChE,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;QACxF,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,0BAA0B,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAC1D,CAAC;QACF,MAAM,IAAI,GAAG,kDAAkD,CAAC;QAChE,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC5C,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,0CAA0C,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAC1E,CAAC;QACF,MAAM,IAAI,GAAG,kDAAkD,CAAC;QAChE,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC;QACvD,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAC/D,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,kCAAkC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAClE,CAAC;QACF,MAAM,IAAI,GAAG,kDAAkD,CAAC;QAChE,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACrF,+DAA+D;QAC/D,+DAA+D;QAC/D,iBAAiB;QAChB,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,sDAAsD,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CACtF,CAAC;QACF,MAAM,IAAI,GAAG,kDAAkD,CAAC;QAChE,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sFAAsF,EAAE,KAAK,IAAI,EAAE;QACnG,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAC9D,CAAC;QACF,MAAM,IAAI,GAAG,kDAAkD,CAAC;QAChE,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QAClD,KAA6C,CAAC,iBAAiB,CAC9D,IAAI,QAAQ,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAClC,CAAC;QACF,MAAM,IAAI,GAAG,kDAAkD,CAAC;QAChE,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freeappstore/compliance",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Compliance checks for apps published on freeappstore.online — used by the CLI for local feedback and by CI for gating.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=22"
|
|
37
37
|
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"build": "tsc",
|
|
40
|
-
"typecheck": "tsc --noEmit"
|
|
41
|
-
},
|
|
42
38
|
"devDependencies": {
|
|
43
39
|
"@types/node": "^22.0.0",
|
|
44
40
|
"typescript": "^5.7.0"
|
|
45
41
|
},
|
|
46
|
-
"sideEffects": false
|
|
47
|
-
|
|
42
|
+
"sideEffects": false,
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
46
|
+
}
|
|
47
|
+
}
|