@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,24 @@
|
|
|
1
|
+
import type { CheckResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Flag `100vh` (and Tailwind shortcuts that compile to it) in source.
|
|
4
|
+
*
|
|
5
|
+
* Why: on iOS Safari with the URL bar visible, `100vh` resolves to the
|
|
6
|
+
* *layout* viewport (e.g. 852px on iPhone 15) but the actual visible
|
|
7
|
+
* area is the *visual* viewport (~750px). An element sized at `100vh`
|
|
8
|
+
* is then ~100px taller than the screen, forcing the page to scroll on
|
|
9
|
+
* first load. This is invisible to headless Playwright (no dynamic URL
|
|
10
|
+
* bar) and to the static audit Worker (no rendering), so this fast
|
|
11
|
+
* static rule is the cheapest place to catch it.
|
|
12
|
+
*
|
|
13
|
+
* Fix: use `100svh` (small viewport height — accounts for visible
|
|
14
|
+
* browser UI) for "as tall as the screen" intent, or `100dvh` (dynamic
|
|
15
|
+
* viewport height — recomputes as the URL bar shows/hides) for full-
|
|
16
|
+
* height containers that should grow when chrome retracts.
|
|
17
|
+
*
|
|
18
|
+
* Scope: warn-by-default, not fail. There ARE legitimate uses of
|
|
19
|
+
* `100vh` (intentionally taking layout-viewport height for hero
|
|
20
|
+
* sections that should stay constant as URL bar moves). Creators can
|
|
21
|
+
* acknowledge by leaving an `// allow-100vh` comment on the same line.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkUnsafeVh(repoDir: string): Promise<CheckResult>;
|
|
24
|
+
//# sourceMappingURL=unsafe-vh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unsafe-vh.d.ts","sourceRoot":"","sources":["../../src/checks/unsafe-vh.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA2CzE"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { extname, relative } from 'node:path';
|
|
3
|
+
import { walk } from '../lib/walk.js';
|
|
4
|
+
/**
|
|
5
|
+
* Flag `100vh` (and Tailwind shortcuts that compile to it) in source.
|
|
6
|
+
*
|
|
7
|
+
* Why: on iOS Safari with the URL bar visible, `100vh` resolves to the
|
|
8
|
+
* *layout* viewport (e.g. 852px on iPhone 15) but the actual visible
|
|
9
|
+
* area is the *visual* viewport (~750px). An element sized at `100vh`
|
|
10
|
+
* is then ~100px taller than the screen, forcing the page to scroll on
|
|
11
|
+
* first load. This is invisible to headless Playwright (no dynamic URL
|
|
12
|
+
* bar) and to the static audit Worker (no rendering), so this fast
|
|
13
|
+
* static rule is the cheapest place to catch it.
|
|
14
|
+
*
|
|
15
|
+
* Fix: use `100svh` (small viewport height — accounts for visible
|
|
16
|
+
* browser UI) for "as tall as the screen" intent, or `100dvh` (dynamic
|
|
17
|
+
* viewport height — recomputes as the URL bar shows/hides) for full-
|
|
18
|
+
* height containers that should grow when chrome retracts.
|
|
19
|
+
*
|
|
20
|
+
* Scope: warn-by-default, not fail. There ARE legitimate uses of
|
|
21
|
+
* `100vh` (intentionally taking layout-viewport height for hero
|
|
22
|
+
* sections that should stay constant as URL bar moves). Creators can
|
|
23
|
+
* acknowledge by leaving an `// allow-100vh` comment on the same line.
|
|
24
|
+
*/
|
|
25
|
+
export async function checkUnsafeVh(repoDir) {
|
|
26
|
+
const issues = [];
|
|
27
|
+
for await (const file of walk(repoDir)) {
|
|
28
|
+
const ext = extname(file).toLowerCase();
|
|
29
|
+
if (!SCAN_EXTS.has(ext))
|
|
30
|
+
continue;
|
|
31
|
+
const content = await readFile(file, 'utf8').catch(() => '');
|
|
32
|
+
if (!content)
|
|
33
|
+
continue;
|
|
34
|
+
const rel = relative(repoDir, file);
|
|
35
|
+
for (const { re, label } of PATTERNS) {
|
|
36
|
+
// Reset lastIndex defensively; we use `g` to find all matches.
|
|
37
|
+
re.lastIndex = 0;
|
|
38
|
+
let m;
|
|
39
|
+
while ((m = re.exec(content)) !== null) {
|
|
40
|
+
const line = lineNumberAt(content, m.index);
|
|
41
|
+
// Allow opt-out via an inline `allow-100vh` comment on the same line.
|
|
42
|
+
if (lineHasAllowComment(content, m.index))
|
|
43
|
+
continue;
|
|
44
|
+
issues.push(`${rel}:${line} ${label} — use 100svh or 100dvh`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (issues.length === 0) {
|
|
49
|
+
return {
|
|
50
|
+
name: 'No unsafe 100vh',
|
|
51
|
+
status: 'pass',
|
|
52
|
+
detail: 'no 100vh / h-screen patterns found',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const head = issues.slice(0, MAX_REPORTED);
|
|
56
|
+
const tail = issues.length > MAX_REPORTED ? [`...and ${issues.length - MAX_REPORTED} more`] : [];
|
|
57
|
+
return {
|
|
58
|
+
name: 'No unsafe 100vh',
|
|
59
|
+
status: 'warn',
|
|
60
|
+
detail: `${issues.length} occurrence${issues.length === 1 ? '' : 's'} of 100vh / h-screen — breaks on iOS Safari with URL bar visible`,
|
|
61
|
+
suggestions: [
|
|
62
|
+
...head,
|
|
63
|
+
...tail,
|
|
64
|
+
'Replace with 100svh (preferred) or 100dvh. Add `// allow-100vh` on the line if intentional.',
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const SCAN_EXTS = new Set(['.css', '.scss', '.tsx', '.ts', '.jsx', '.js', '.html']);
|
|
69
|
+
const MAX_REPORTED = 5;
|
|
70
|
+
/**
|
|
71
|
+
* Patterns to flag. Each is global (`g`) so we find every occurrence.
|
|
72
|
+
*
|
|
73
|
+
* Boundary handling:
|
|
74
|
+
* - `100vh` uses `\b` since CSS values are tokenised normally.
|
|
75
|
+
* - Tailwind classes use a custom hyphen-aware boundary
|
|
76
|
+
* `(?<![\w-])foo(?![\w-])` so `h-screen` doesn't match inside
|
|
77
|
+
* `min-h-screen` (and we don't double-count `min-h-screen` as both
|
|
78
|
+
* itself and its `h-screen` substring).
|
|
79
|
+
*/
|
|
80
|
+
const PATTERNS = [
|
|
81
|
+
{ re: /\b100vh\b/g, label: '100vh' },
|
|
82
|
+
// Tailwind: h-screen → height: 100vh; same for min-/max-.
|
|
83
|
+
{ re: /(?<![\w-])h-screen(?![\w-])/g, label: 'h-screen (Tailwind: → 100vh)' },
|
|
84
|
+
{ re: /(?<![\w-])min-h-screen(?![\w-])/g, label: 'min-h-screen (Tailwind: → min-height: 100vh)' },
|
|
85
|
+
{ re: /(?<![\w-])max-h-screen(?![\w-])/g, label: 'max-h-screen (Tailwind: → max-height: 100vh)' },
|
|
86
|
+
];
|
|
87
|
+
function lineNumberAt(content, index) {
|
|
88
|
+
let n = 1;
|
|
89
|
+
for (let i = 0; i < index; i++)
|
|
90
|
+
if (content.charCodeAt(i) === 10)
|
|
91
|
+
n++;
|
|
92
|
+
return n;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* True if the rest of the line containing `index` includes the literal
|
|
96
|
+
* string "allow-100vh" (typically inside a comment). Cheap and cheerful
|
|
97
|
+
* — doesn't validate comment syntax, just trusts the marker.
|
|
98
|
+
*/
|
|
99
|
+
function lineHasAllowComment(content, index) {
|
|
100
|
+
// Find end of the current line.
|
|
101
|
+
let end = content.indexOf('\n', index);
|
|
102
|
+
if (end === -1)
|
|
103
|
+
end = content.length;
|
|
104
|
+
// Walk back to the start of the line for full context.
|
|
105
|
+
let start = content.lastIndexOf('\n', index);
|
|
106
|
+
if (start === -1)
|
|
107
|
+
start = 0;
|
|
108
|
+
return content.slice(start, end).includes('allow-100vh');
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=unsafe-vh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unsafe-vh.js","sourceRoot":"","sources":["../../src/checks/unsafe-vh.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unsafe-vh.test.d.ts","sourceRoot":"","sources":["../../src/checks/unsafe-vh.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { checkUnsafeVh } from './unsafe-vh.js';
|
|
6
|
+
async function fixture(files) {
|
|
7
|
+
const dir = await mkdtemp(join(tmpdir(), 'fas-unsafe-vh-'));
|
|
8
|
+
for (const [path, content] of Object.entries(files)) {
|
|
9
|
+
const full = join(dir, path);
|
|
10
|
+
await mkdir(join(full, '..'), { recursive: true });
|
|
11
|
+
await writeFile(full, content);
|
|
12
|
+
}
|
|
13
|
+
return dir;
|
|
14
|
+
}
|
|
15
|
+
describe('checkUnsafeVh', () => {
|
|
16
|
+
it('passes when no 100vh appears anywhere', async () => {
|
|
17
|
+
const dir = await fixture({
|
|
18
|
+
'web/src/index.css': 'body { height: 100svh; }',
|
|
19
|
+
'web/src/App.tsx': 'export default () => <div className="h-screen-not-really" />;',
|
|
20
|
+
});
|
|
21
|
+
const r = await checkUnsafeVh(dir);
|
|
22
|
+
expect(r.status).toBe('pass');
|
|
23
|
+
expect(r.detail).toMatch(/no 100vh/);
|
|
24
|
+
});
|
|
25
|
+
it('warns on a literal 100vh in CSS', async () => {
|
|
26
|
+
const dir = await fixture({
|
|
27
|
+
'web/src/index.css': '.full { height: 100vh; }',
|
|
28
|
+
});
|
|
29
|
+
const r = await checkUnsafeVh(dir);
|
|
30
|
+
expect(r.status).toBe('warn');
|
|
31
|
+
expect(r.detail).toMatch(/1 occurrence/);
|
|
32
|
+
expect(r.suggestions?.join('\n')).toMatch(/web\/src\/index\.css:1.*100vh/);
|
|
33
|
+
expect(r.suggestions?.join('\n')).toMatch(/100svh.*100dvh/);
|
|
34
|
+
});
|
|
35
|
+
it('warns on Tailwind h-screen / min-h-screen / max-h-screen', async () => {
|
|
36
|
+
const dir = await fixture({
|
|
37
|
+
'web/src/App.tsx': `
|
|
38
|
+
export default () => (
|
|
39
|
+
<div className="h-screen">
|
|
40
|
+
<header className="min-h-screen" />
|
|
41
|
+
<main className="max-h-screen" />
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
`,
|
|
45
|
+
});
|
|
46
|
+
const r = await checkUnsafeVh(dir);
|
|
47
|
+
expect(r.status).toBe('warn');
|
|
48
|
+
expect(r.detail).toMatch(/3 occurrences/);
|
|
49
|
+
const suggestionText = r.suggestions?.join('\n') ?? '';
|
|
50
|
+
expect(suggestionText).toMatch(/h-screen/);
|
|
51
|
+
expect(suggestionText).toMatch(/min-h-screen/);
|
|
52
|
+
expect(suggestionText).toMatch(/max-h-screen/);
|
|
53
|
+
});
|
|
54
|
+
it('respects the `allow-100vh` opt-out comment on the same line', async () => {
|
|
55
|
+
const dir = await fixture({
|
|
56
|
+
'web/src/index.css': `
|
|
57
|
+
.hero { height: 100vh; /* allow-100vh — intentional, hero stays constant under URL bar */ }
|
|
58
|
+
`,
|
|
59
|
+
});
|
|
60
|
+
const r = await checkUnsafeVh(dir);
|
|
61
|
+
expect(r.status).toBe('pass');
|
|
62
|
+
});
|
|
63
|
+
it('still flags a different line even when one line has the opt-out', async () => {
|
|
64
|
+
const dir = await fixture({
|
|
65
|
+
'web/src/index.css': `.a { height: 100vh; /* allow-100vh */ }
|
|
66
|
+
.b { height: 100vh; }
|
|
67
|
+
`,
|
|
68
|
+
});
|
|
69
|
+
const r = await checkUnsafeVh(dir);
|
|
70
|
+
expect(r.status).toBe('warn');
|
|
71
|
+
expect(r.detail).toMatch(/1 occurrence/); // only the un-opt-out line
|
|
72
|
+
});
|
|
73
|
+
it('does not flag the strings "100svh" / "100dvh" (the recommended fixes)', async () => {
|
|
74
|
+
const dir = await fixture({
|
|
75
|
+
'web/src/index.css': `
|
|
76
|
+
.a { height: 100svh; }
|
|
77
|
+
.b { height: 100dvh; }
|
|
78
|
+
`,
|
|
79
|
+
});
|
|
80
|
+
const r = await checkUnsafeVh(dir);
|
|
81
|
+
expect(r.status).toBe('pass');
|
|
82
|
+
});
|
|
83
|
+
it('does not match identifiers that contain "100vh" as a substring', async () => {
|
|
84
|
+
const dir = await fixture({
|
|
85
|
+
// `var100vh` and `--my100vhvar` aren't actual unit usage; word
|
|
86
|
+
// boundaries must keep us from flagging them.
|
|
87
|
+
'web/src/foo.ts': 'const var100vh = 1; const x = "--my100vhvar";',
|
|
88
|
+
});
|
|
89
|
+
const r = await checkUnsafeVh(dir);
|
|
90
|
+
expect(r.status).toBe('pass');
|
|
91
|
+
});
|
|
92
|
+
it('caps reported issues at 5 and notes the rest', async () => {
|
|
93
|
+
const lines = Array.from({ length: 8 }, (_, i) => `.x${i} { height: 100vh; }`).join('\n');
|
|
94
|
+
const dir = await fixture({ 'web/src/index.css': lines });
|
|
95
|
+
const r = await checkUnsafeVh(dir);
|
|
96
|
+
expect(r.status).toBe('warn');
|
|
97
|
+
expect(r.detail).toMatch(/8 occurrences/);
|
|
98
|
+
// 5 file:line entries + 1 "...and N more" + 1 final "Replace with..." advice = 7
|
|
99
|
+
expect(r.suggestions?.length).toBe(7);
|
|
100
|
+
expect(r.suggestions?.[5]).toMatch(/and 3 more/);
|
|
101
|
+
});
|
|
102
|
+
it('scans CSS, JS, TS, JSX, TSX, HTML — but skips other extensions', async () => {
|
|
103
|
+
const dir = await fixture({
|
|
104
|
+
'web/src/index.css': '.a { height: 100vh; }',
|
|
105
|
+
'web/src/App.jsx': 'const x = "h-screen";', // string with the class — still flagged (we can't know intent)
|
|
106
|
+
'docs/notes.md': 'Use 100vh somewhere', // Markdown — should NOT be scanned
|
|
107
|
+
'web/src/foo.scss': '$h: 100vh;',
|
|
108
|
+
});
|
|
109
|
+
const r = await checkUnsafeVh(dir);
|
|
110
|
+
expect(r.status).toBe('warn');
|
|
111
|
+
// 3 hits: index.css, App.jsx, foo.scss. notes.md ignored.
|
|
112
|
+
expect(r.detail).toMatch(/3 occurrences/);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
//# sourceMappingURL=unsafe-vh.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unsafe-vh.test.js","sourceRoot":"","sources":["../../src/checks/unsafe-vh.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CheckResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Apps and games must declare which screens and orientations they
|
|
4
|
+
* support, so the storefront can render a coverage badge ("works on
|
|
5
|
+
* 92% of devices · portrait + landscape") and visitors know what to
|
|
6
|
+
* expect before they tap.
|
|
7
|
+
*
|
|
8
|
+
* Two declarations are required, both in `web/public/manifest.json`:
|
|
9
|
+
*
|
|
10
|
+
* "orientation": "any" | "portrait" | "landscape" |
|
|
11
|
+
* "portrait-primary" | "landscape-primary"
|
|
12
|
+
* "min_viewport_width": 320 | 360 | 414 | 600 | 768 | 1024
|
|
13
|
+
*
|
|
14
|
+
* `orientation` is a standard PWA manifest field. `min_viewport_width`
|
|
15
|
+
* is a custom platform field — the smallest screen width (in CSS px)
|
|
16
|
+
* the app renders correctly at. The storefront maps that to a global
|
|
17
|
+
* device-share percentile to render coverage.
|
|
18
|
+
*
|
|
19
|
+
* If you genuinely don't care about orientation, set `"any"`. Don't
|
|
20
|
+
* leave it off — that's the only value that returns a fail.
|
|
21
|
+
*/
|
|
22
|
+
export declare function checkViewportSupport(repoDir: string): Promise<CheckResult>;
|
|
23
|
+
//# sourceMappingURL=viewport-support.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport-support.d.ts","sourceRoot":"","sources":["../../src/checks/viewport-support.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA+DhF"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Apps and games must declare which screens and orientations they
|
|
5
|
+
* support, so the storefront can render a coverage badge ("works on
|
|
6
|
+
* 92% of devices · portrait + landscape") and visitors know what to
|
|
7
|
+
* expect before they tap.
|
|
8
|
+
*
|
|
9
|
+
* Two declarations are required, both in `web/public/manifest.json`:
|
|
10
|
+
*
|
|
11
|
+
* "orientation": "any" | "portrait" | "landscape" |
|
|
12
|
+
* "portrait-primary" | "landscape-primary"
|
|
13
|
+
* "min_viewport_width": 320 | 360 | 414 | 600 | 768 | 1024
|
|
14
|
+
*
|
|
15
|
+
* `orientation` is a standard PWA manifest field. `min_viewport_width`
|
|
16
|
+
* is a custom platform field — the smallest screen width (in CSS px)
|
|
17
|
+
* the app renders correctly at. The storefront maps that to a global
|
|
18
|
+
* device-share percentile to render coverage.
|
|
19
|
+
*
|
|
20
|
+
* If you genuinely don't care about orientation, set `"any"`. Don't
|
|
21
|
+
* leave it off — that's the only value that returns a fail.
|
|
22
|
+
*/
|
|
23
|
+
export async function checkViewportSupport(repoDir) {
|
|
24
|
+
const manifestPath = join(repoDir, 'web', 'public', 'manifest.json');
|
|
25
|
+
let manifest;
|
|
26
|
+
try {
|
|
27
|
+
const raw = await readFile(manifestPath, 'utf8');
|
|
28
|
+
manifest = JSON.parse(raw);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return {
|
|
32
|
+
name: 'Viewport support',
|
|
33
|
+
status: 'fail',
|
|
34
|
+
detail: 'web/public/manifest.json not found or unparseable',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const orientation = manifest['orientation'];
|
|
38
|
+
const minWidth = manifest['min_viewport_width'];
|
|
39
|
+
const issues = [];
|
|
40
|
+
const warnings = [];
|
|
41
|
+
if (typeof orientation !== 'string' || !VALID_ORIENTATIONS.has(orientation)) {
|
|
42
|
+
issues.push(`manifest.orientation is "${String(orientation)}" — must be one of: ${[...VALID_ORIENTATIONS].join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
if (typeof minWidth !== 'number') {
|
|
45
|
+
warnings.push('manifest.min_viewport_width is missing — assuming 320 (strictest). ' +
|
|
46
|
+
'Set explicitly to e.g. 360 / 414 / 600 / 768 / 1024 to declare which devices the app supports.');
|
|
47
|
+
}
|
|
48
|
+
else if (minWidth < 320) {
|
|
49
|
+
issues.push(`manifest.min_viewport_width=${minWidth} is below the 320px minimum`);
|
|
50
|
+
}
|
|
51
|
+
else if (!RECOMMENDED_WIDTHS.includes(minWidth)) {
|
|
52
|
+
warnings.push(`manifest.min_viewport_width=${minWidth} is unusual — recommended values: ${RECOMMENDED_WIDTHS.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
if (issues.length > 0) {
|
|
55
|
+
return {
|
|
56
|
+
name: 'Viewport support',
|
|
57
|
+
status: 'fail',
|
|
58
|
+
detail: `${issues.length} viewport-declaration issue${issues.length === 1 ? '' : 's'}`,
|
|
59
|
+
suggestions: [
|
|
60
|
+
...issues,
|
|
61
|
+
'Edit web/public/manifest.json: add "orientation": "any" (or portrait/landscape) and "min_viewport_width": 360.',
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (warnings.length > 0) {
|
|
66
|
+
return {
|
|
67
|
+
name: 'Viewport support',
|
|
68
|
+
status: 'warn',
|
|
69
|
+
detail: warnings[0],
|
|
70
|
+
suggestions: warnings,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
name: 'Viewport support',
|
|
75
|
+
status: 'pass',
|
|
76
|
+
detail: `orientation=${orientation} · min ${minWidth}px`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const VALID_ORIENTATIONS = new Set([
|
|
80
|
+
'any',
|
|
81
|
+
'portrait',
|
|
82
|
+
'landscape',
|
|
83
|
+
'portrait-primary',
|
|
84
|
+
'landscape-primary',
|
|
85
|
+
]);
|
|
86
|
+
const RECOMMENDED_WIDTHS = [320, 360, 414, 600, 768, 1024];
|
|
87
|
+
//# sourceMappingURL=viewport-support.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport-support.js","sourceRoot":"","sources":["../../src/checks/viewport-support.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport-support.test.d.ts","sourceRoot":"","sources":["../../src/checks/viewport-support.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { checkViewportSupport } from './viewport-support.js';
|
|
6
|
+
async function fixture(manifest) {
|
|
7
|
+
const dir = await mkdtemp(join(tmpdir(), 'fas-viewport-'));
|
|
8
|
+
if (manifest !== null) {
|
|
9
|
+
await mkdir(join(dir, 'web', 'public'), { recursive: true });
|
|
10
|
+
await writeFile(join(dir, 'web', 'public', 'manifest.json'), JSON.stringify(manifest));
|
|
11
|
+
}
|
|
12
|
+
return dir;
|
|
13
|
+
}
|
|
14
|
+
describe('checkViewportSupport', () => {
|
|
15
|
+
it('fails when manifest.json is missing', async () => {
|
|
16
|
+
const dir = await fixture(null);
|
|
17
|
+
const r = await checkViewportSupport(dir);
|
|
18
|
+
expect(r.status).toBe('fail');
|
|
19
|
+
expect(r.detail).toMatch(/manifest\.json/);
|
|
20
|
+
});
|
|
21
|
+
it('fails when orientation is missing', async () => {
|
|
22
|
+
const dir = await fixture({ name: 'x' });
|
|
23
|
+
const r = await checkViewportSupport(dir);
|
|
24
|
+
expect(r.status).toBe('fail');
|
|
25
|
+
expect(r.suggestions?.join(' ')).toMatch(/orientation/);
|
|
26
|
+
});
|
|
27
|
+
it('fails when orientation is invalid', async () => {
|
|
28
|
+
const dir = await fixture({ name: 'x', orientation: 'rotate' });
|
|
29
|
+
const r = await checkViewportSupport(dir);
|
|
30
|
+
expect(r.status).toBe('fail');
|
|
31
|
+
});
|
|
32
|
+
it('warns when min_viewport_width is missing but orientation is valid', async () => {
|
|
33
|
+
const dir = await fixture({ name: 'x', orientation: 'any' });
|
|
34
|
+
const r = await checkViewportSupport(dir);
|
|
35
|
+
expect(r.status).toBe('warn');
|
|
36
|
+
expect(r.detail).toMatch(/min_viewport_width/);
|
|
37
|
+
});
|
|
38
|
+
it('fails when min_viewport_width is below 320', async () => {
|
|
39
|
+
const dir = await fixture({ name: 'x', orientation: 'any', min_viewport_width: 280 });
|
|
40
|
+
const r = await checkViewportSupport(dir);
|
|
41
|
+
expect(r.status).toBe('fail');
|
|
42
|
+
});
|
|
43
|
+
it('warns on unusual but accepted widths', async () => {
|
|
44
|
+
const dir = await fixture({ name: 'x', orientation: 'any', min_viewport_width: 387 });
|
|
45
|
+
const r = await checkViewportSupport(dir);
|
|
46
|
+
expect(r.status).toBe('warn');
|
|
47
|
+
expect(r.detail).toMatch(/unusual/);
|
|
48
|
+
});
|
|
49
|
+
it('passes for valid orientation + recommended width', async () => {
|
|
50
|
+
const dir = await fixture({ name: 'x', orientation: 'any', min_viewport_width: 360 });
|
|
51
|
+
const r = await checkViewportSupport(dir);
|
|
52
|
+
expect(r.status).toBe('pass');
|
|
53
|
+
expect(r.detail).toMatch(/any/);
|
|
54
|
+
expect(r.detail).toMatch(/360/);
|
|
55
|
+
});
|
|
56
|
+
it('passes for portrait-only landscape-only too', async () => {
|
|
57
|
+
const dir = await fixture({ name: 'x', orientation: 'portrait', min_viewport_width: 414 });
|
|
58
|
+
expect((await checkViewportSupport(dir)).status).toBe('pass');
|
|
59
|
+
const dir2 = await fixture({ name: 'x', orientation: 'landscape-primary', min_viewport_width: 768 });
|
|
60
|
+
expect((await checkViewportSupport(dir2)).status).toBe('pass');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=viewport-support.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport-support.test.js","sourceRoot":"","sources":["../../src/checks/viewport-support.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { checkNoPlaceholders } from './checks/no-placeholders.js';
|
|
2
2
|
import { checkNoTracking } from './checks/no-tracking.js';
|
|
3
3
|
import { checkBrandFonts } from './checks/brand-fonts.js';
|
|
4
|
+
import { checkNoBrandOverrides } from './checks/no-brand-overrides.js';
|
|
5
|
+
import { checkNoScroll } from './checks/no-scroll.js';
|
|
6
|
+
import { checkViewportSupport } from './checks/viewport-support.js';
|
|
4
7
|
import { checkManifest } from './checks/manifest.js';
|
|
5
8
|
import { checkBundleSize } from './checks/bundle-size.js';
|
|
9
|
+
import { checkUnsafeVh } from './checks/unsafe-vh.js';
|
|
6
10
|
import type { CheckResult } from './types.js';
|
|
7
11
|
export type { CheckStatus, CheckResult } from './types.js';
|
|
8
|
-
export { checkNoPlaceholders, checkNoTracking, checkBrandFonts, checkManifest, checkBundleSize, };
|
|
12
|
+
export { checkNoPlaceholders, checkNoTracking, checkBrandFonts, checkNoBrandOverrides, checkNoScroll, checkViewportSupport, checkManifest, checkBundleSize, checkUnsafeVh, };
|
|
13
|
+
export { auditLive, checkNoTrackingLive, checkBrandFontsLive, checkManifestLive, checkBundleSizeLive, checkUnsafeVhLive, } from './live/index.js';
|
|
14
|
+
export type { LiveAuditInput, LiveAuditReport } from './live/index.js';
|
|
9
15
|
/**
|
|
10
16
|
* Runs every compliance check against `repoDir` (the root of an app, the
|
|
11
17
|
* directory containing package.json + web/). Returns results in a stable
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,aAAa,EACb,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,aAAa,GACd,CAAC;AAKF,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvE;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAYvE"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { checkNoPlaceholders } from './checks/no-placeholders.js';
|
|
2
2
|
import { checkNoTracking } from './checks/no-tracking.js';
|
|
3
3
|
import { checkBrandFonts } from './checks/brand-fonts.js';
|
|
4
|
+
import { checkNoBrandOverrides } from './checks/no-brand-overrides.js';
|
|
5
|
+
import { checkNoScroll } from './checks/no-scroll.js';
|
|
6
|
+
import { checkViewportSupport } from './checks/viewport-support.js';
|
|
4
7
|
import { checkManifest } from './checks/manifest.js';
|
|
5
8
|
import { checkBundleSize } from './checks/bundle-size.js';
|
|
6
|
-
|
|
9
|
+
import { checkUnsafeVh } from './checks/unsafe-vh.js';
|
|
10
|
+
export { checkNoPlaceholders, checkNoTracking, checkBrandFonts, checkNoBrandOverrides, checkNoScroll, checkViewportSupport, checkManifest, checkBundleSize, checkUnsafeVh, };
|
|
11
|
+
// Live-URL audit (used by the compliance audit Worker; runs in
|
|
12
|
+
// browser/Workers env, no filesystem). Separate export path so callers
|
|
13
|
+
// don't accidentally pull node:fs in via the file-walking checks.
|
|
14
|
+
export { auditLive, checkNoTrackingLive, checkBrandFontsLive, checkManifestLive, checkBundleSizeLive, checkUnsafeVhLive, } from './live/index.js';
|
|
7
15
|
/**
|
|
8
16
|
* Runs every compliance check against `repoDir` (the root of an app, the
|
|
9
17
|
* directory containing package.json + web/). Returns results in a stable
|
|
@@ -14,6 +22,10 @@ export async function runChecks(repoDir) {
|
|
|
14
22
|
checkNoPlaceholders(repoDir),
|
|
15
23
|
checkNoTracking(repoDir),
|
|
16
24
|
checkBrandFonts(repoDir),
|
|
25
|
+
checkNoBrandOverrides(repoDir),
|
|
26
|
+
checkNoScroll(repoDir),
|
|
27
|
+
checkViewportSupport(repoDir),
|
|
28
|
+
checkUnsafeVh(repoDir),
|
|
17
29
|
checkManifest(repoDir),
|
|
18
30
|
checkBundleSize(repoDir),
|
|
19
31
|
]);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAItD,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,aAAa,GACd,CAAC;AAEF,+DAA+D;AAC/D,uEAAuE;AACvE,kEAAkE;AAClE,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAGzB;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAe;IAC7C,OAAO,OAAO,CAAC,GAAG,CAAC;QACjB,mBAAmB,CAAC,OAAO,CAAC;QAC5B,eAAe,CAAC,OAAO,CAAC;QACxB,eAAe,CAAC,OAAO,CAAC;QACxB,qBAAqB,CAAC,OAAO,CAAC;QAC9B,aAAa,CAAC,OAAO,CAAC;QACtB,oBAAoB,CAAC,OAAO,CAAC;QAC7B,aAAa,CAAC,OAAO,CAAC;QACtB,aAAa,CAAC,OAAO,CAAC;QACtB,eAAe,CAAC,OAAO,CAAC;KACzB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
import type { CheckResult } from '../types.js';
|
|
21
|
+
export interface LiveAuditInput {
|
|
22
|
+
appId: string;
|
|
23
|
+
liveUrl: string;
|
|
24
|
+
}
|
|
25
|
+
export interface LiveAuditReport {
|
|
26
|
+
appId: string;
|
|
27
|
+
liveUrl: string;
|
|
28
|
+
checkedAt: number;
|
|
29
|
+
reachable: boolean;
|
|
30
|
+
results: CheckResult[];
|
|
31
|
+
}
|
|
32
|
+
export declare function auditLive(input: LiveAuditInput): Promise<LiveAuditReport>;
|
|
33
|
+
export declare function checkNoTrackingLive(html: string): CheckResult;
|
|
34
|
+
export declare function checkBrandFontsLive(html: string): CheckResult;
|
|
35
|
+
export declare function checkManifestLive(html: string, liveUrl: string): Promise<CheckResult>;
|
|
36
|
+
export declare function checkBundleSizeLive(html: string, liveUrl: string): Promise<CheckResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Live counterpart to checkUnsafeVh. Fetches the first bundled
|
|
39
|
+
* stylesheet linked from the HTML and scans it for `100vh` (the
|
|
40
|
+
* unit, not the Tailwind class — by build time those have been
|
|
41
|
+
* compiled to CSS). Catches whiteboard-class iOS Safari URL-bar
|
|
42
|
+
* scroll bugs in apps that have already shipped.
|
|
43
|
+
*
|
|
44
|
+
* Cost: one extra subrequest per app. The audit Worker runs near the
|
|
45
|
+
* free-plan subrequest cap; on cap errors we degrade to `warn`
|
|
46
|
+
* (re-run needed) rather than `fail` (app broken) — the app isn't
|
|
47
|
+
* actually broken, the audit just couldn't check.
|
|
48
|
+
*/
|
|
49
|
+
export declare function checkUnsafeVhLive(html: string, liveUrl: string): Promise<CheckResult>;
|
|
50
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/live/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAc/C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AA2BD,wBAAsB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CA2D/E;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAa7D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAe7D;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA8C3F;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAmD7F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAgD3F"}
|