@freegamestore/compliance 0.1.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.
Files changed (94) hide show
  1. package/README.md +27 -0
  2. package/dist/checks/brand-fonts.d.ts +10 -0
  3. package/dist/checks/brand-fonts.d.ts.map +1 -0
  4. package/dist/checks/brand-fonts.js +47 -0
  5. package/dist/checks/brand-fonts.js.map +1 -0
  6. package/dist/checks/brand-tokens.d.ts +22 -0
  7. package/dist/checks/brand-tokens.d.ts.map +1 -0
  8. package/dist/checks/brand-tokens.js +53 -0
  9. package/dist/checks/brand-tokens.js.map +1 -0
  10. package/dist/checks/bundle-size.d.ts +14 -0
  11. package/dist/checks/bundle-size.d.ts.map +1 -0
  12. package/dist/checks/bundle-size.js +84 -0
  13. package/dist/checks/bundle-size.js.map +1 -0
  14. package/dist/checks/claude-md-slim.d.ts +4 -0
  15. package/dist/checks/claude-md-slim.d.ts.map +1 -0
  16. package/dist/checks/claude-md-slim.js +88 -0
  17. package/dist/checks/claude-md-slim.js.map +1 -0
  18. package/dist/checks/dark-mode.d.ts +20 -0
  19. package/dist/checks/dark-mode.d.ts.map +1 -0
  20. package/dist/checks/dark-mode.js +48 -0
  21. package/dist/checks/dark-mode.js.map +1 -0
  22. package/dist/checks/html-meta.d.ts +16 -0
  23. package/dist/checks/html-meta.d.ts.map +1 -0
  24. package/dist/checks/html-meta.js +46 -0
  25. package/dist/checks/html-meta.js.map +1 -0
  26. package/dist/checks/license-mit.d.ts +13 -0
  27. package/dist/checks/license-mit.d.ts.map +1 -0
  28. package/dist/checks/license-mit.js +38 -0
  29. package/dist/checks/license-mit.js.map +1 -0
  30. package/dist/checks/manifest.d.ts +9 -0
  31. package/dist/checks/manifest.d.ts.map +1 -0
  32. package/dist/checks/manifest.js +43 -0
  33. package/dist/checks/manifest.js.map +1 -0
  34. package/dist/checks/no-brand-overrides.d.ts +25 -0
  35. package/dist/checks/no-brand-overrides.d.ts.map +1 -0
  36. package/dist/checks/no-brand-overrides.js +159 -0
  37. package/dist/checks/no-brand-overrides.js.map +1 -0
  38. package/dist/checks/no-env-production.d.ts +16 -0
  39. package/dist/checks/no-env-production.d.ts.map +1 -0
  40. package/dist/checks/no-env-production.js +33 -0
  41. package/dist/checks/no-env-production.js.map +1 -0
  42. package/dist/checks/no-placeholders.d.ts +10 -0
  43. package/dist/checks/no-placeholders.d.ts.map +1 -0
  44. package/dist/checks/no-placeholders.js +41 -0
  45. package/dist/checks/no-placeholders.js.map +1 -0
  46. package/dist/checks/no-scroll.d.ts +25 -0
  47. package/dist/checks/no-scroll.d.ts.map +1 -0
  48. package/dist/checks/no-scroll.js +111 -0
  49. package/dist/checks/no-scroll.js.map +1 -0
  50. package/dist/checks/no-tracking.d.ts +4 -0
  51. package/dist/checks/no-tracking.d.ts.map +1 -0
  52. package/dist/checks/no-tracking.js +52 -0
  53. package/dist/checks/no-tracking.js.map +1 -0
  54. package/dist/checks/pwa-meta.d.ts +19 -0
  55. package/dist/checks/pwa-meta.d.ts.map +1 -0
  56. package/dist/checks/pwa-meta.js +38 -0
  57. package/dist/checks/pwa-meta.js.map +1 -0
  58. package/dist/checks/store-link.d.ts +8 -0
  59. package/dist/checks/store-link.d.ts.map +1 -0
  60. package/dist/checks/store-link.js +25 -0
  61. package/dist/checks/store-link.js.map +1 -0
  62. package/dist/checks/unsafe-vh.d.ts +25 -0
  63. package/dist/checks/unsafe-vh.d.ts.map +1 -0
  64. package/dist/checks/unsafe-vh.js +108 -0
  65. package/dist/checks/unsafe-vh.js.map +1 -0
  66. package/dist/checks/viewport-support.d.ts +24 -0
  67. package/dist/checks/viewport-support.d.ts.map +1 -0
  68. package/dist/checks/viewport-support.js +92 -0
  69. package/dist/checks/viewport-support.js.map +1 -0
  70. package/dist/index.d.ts +38 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +62 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/lib/file-source.d.ts +52 -0
  75. package/dist/lib/file-source.d.ts.map +1 -0
  76. package/dist/lib/file-source.js +148 -0
  77. package/dist/lib/file-source.js.map +1 -0
  78. package/dist/lib/gzip.d.ts +8 -0
  79. package/dist/lib/gzip.d.ts.map +1 -0
  80. package/dist/lib/gzip.js +26 -0
  81. package/dist/lib/gzip.js.map +1 -0
  82. package/dist/lib/project-type.d.ts +11 -0
  83. package/dist/lib/project-type.d.ts.map +1 -0
  84. package/dist/lib/project-type.js +27 -0
  85. package/dist/lib/project-type.js.map +1 -0
  86. package/dist/live/index.d.ts +50 -0
  87. package/dist/live/index.d.ts.map +1 -0
  88. package/dist/live/index.js +313 -0
  89. package/dist/live/index.js.map +1 -0
  90. package/dist/types.d.ts +12 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +2 -0
  93. package/dist/types.js.map +1 -0
  94. package/package.json +47 -0
@@ -0,0 +1,38 @@
1
+ const HTML_PATH = 'web/index.html';
2
+ /**
3
+ * For an "Add to Home Screen" install on iOS Safari to launch the app
4
+ * standalone (no browser chrome), `web/index.html` needs at least one
5
+ * of:
6
+ *
7
+ * <meta name="apple-mobile-web-app-capable" content="yes" />
8
+ * <meta name="mobile-web-app-capable" content="yes" />
9
+ *
10
+ * Without this, iOS opens the installed icon as a normal Safari tab,
11
+ * defeating the PWA install. Android Chrome reads the manifest's
12
+ * `display: standalone` instead, but iOS still requires the meta tag.
13
+ *
14
+ * The manifest itself is a separate check (`checkManifest`); this one
15
+ * is specifically the iOS install hint that lives in the HTML head.
16
+ */
17
+ export async function checkPwaMeta(source) {
18
+ const html = await source.read(HTML_PATH);
19
+ if (html === null) {
20
+ return {
21
+ name: 'PWA meta tags',
22
+ status: 'fail',
23
+ detail: `${HTML_PATH} not found`,
24
+ };
25
+ }
26
+ if (/<meta[^>]*\bname\s*=\s*["'](?:apple-)?mobile-web-app-capable["']/i.test(html)) {
27
+ return { name: 'PWA meta tags', status: 'pass', detail: 'iOS install hint present' };
28
+ }
29
+ return {
30
+ name: 'PWA meta tags',
31
+ status: 'warn',
32
+ detail: 'no apple-mobile-web-app-capable / mobile-web-app-capable meta',
33
+ suggestions: [
34
+ 'Add `<meta name="apple-mobile-web-app-capable" content="yes" />` to <head> so iOS installs launch standalone.',
35
+ ],
36
+ };
37
+ }
38
+ //# sourceMappingURL=pwa-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pwa-meta.js","sourceRoot":"","sources":["../../src/checks/pwa-meta.ts"],"names":[],"mappings":"AAGA,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAEnC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAkB;IACnD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO;YACL,IAAI,EAAE,eAAe;YACrB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,SAAS,YAAY;SACjC,CAAC;IACJ,CAAC;IACD,IAAI,mEAAmE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC;IACvF,CAAC;IACD,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,+DAA+D;QACvE,WAAW,EAAE;YACX,+GAA+G;SAChH;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ /**
4
+ * Games should link back to freegamestore.online — it's how visitors
5
+ * discover the rest of the catalog from inside any single game.
6
+ */
7
+ export declare function checkStoreLink(source: FileSource): Promise<CheckResult>;
8
+ //# sourceMappingURL=store-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-link.d.ts","sourceRoot":"","sources":["../../src/checks/store-link.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAmB7E"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Games should link back to freegamestore.online — it's how visitors
3
+ * discover the rest of the catalog from inside any single game.
4
+ */
5
+ export async function checkStoreLink(source) {
6
+ const domain = 'freegamestore.online';
7
+ for await (const path of source.list()) {
8
+ if (!path.startsWith('web/src/'))
9
+ continue;
10
+ const content = await source.read(path);
11
+ if (content && content.includes(domain)) {
12
+ return { name: 'Store link', status: 'pass', detail: `${domain} referenced in ${path}` };
13
+ }
14
+ }
15
+ return {
16
+ name: 'Store link',
17
+ status: 'warn',
18
+ detail: `no link to ${domain} found in web/src/`,
19
+ suggestions: [
20
+ `Add a small "Built for ${domain}" link in the footer or about screen.`,
21
+ 'It helps visitors find the rest of the catalog — and it counts for storefront ranking.',
22
+ ],
23
+ };
24
+ }
25
+ //# sourceMappingURL=store-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-link.js","sourceRoot":"","sources":["../../src/checks/store-link.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAkB;IACrD,MAAM,MAAM,GAAG,sBAAsB,CAAC;IAEtC,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,SAAS;QAC3C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACxC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,kBAAkB,IAAI,EAAE,EAAE,CAAC;QAC3F,CAAC;IACH,CAAC;IACD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,cAAc,MAAM,oBAAoB;QAChD,WAAW,EAAE;YACX,0BAA0B,MAAM,uCAAuC;YACvE,wFAAwF;SACzF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ /**
4
+ * Flag `100vh` (and Tailwind shortcuts that compile to it) in source.
5
+ *
6
+ * Why: on iOS Safari with the URL bar visible, `100vh` resolves to the
7
+ * *layout* viewport (e.g. 852px on iPhone 15) but the actual visible
8
+ * area is the *visual* viewport (~750px). An element sized at `100vh`
9
+ * is then ~100px taller than the screen, forcing the page to scroll on
10
+ * first load. This is invisible to headless Playwright (no dynamic URL
11
+ * bar) and to the static audit Worker (no rendering), so this fast
12
+ * static rule is the cheapest place to catch it.
13
+ *
14
+ * Fix: use `100svh` (small viewport height — accounts for visible
15
+ * browser UI) for "as tall as the screen" intent, or `100dvh` (dynamic
16
+ * viewport height — recomputes as the URL bar shows/hides) for full-
17
+ * height containers that should grow when chrome retracts.
18
+ *
19
+ * Scope: warn-by-default, not fail. There ARE legitimate uses of
20
+ * `100vh` (intentionally taking layout-viewport height for hero
21
+ * sections that should stay constant as URL bar moves). Creators can
22
+ * acknowledge by leaving an `// allow-100vh` comment on the same line.
23
+ */
24
+ export declare function checkUnsafeVh(source: FileSource): Promise<CheckResult>;
25
+ //# 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":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAyC5E"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Flag `100vh` (and Tailwind shortcuts that compile to it) in source.
3
+ *
4
+ * Why: on iOS Safari with the URL bar visible, `100vh` resolves to the
5
+ * *layout* viewport (e.g. 852px on iPhone 15) but the actual visible
6
+ * area is the *visual* viewport (~750px). An element sized at `100vh`
7
+ * is then ~100px taller than the screen, forcing the page to scroll on
8
+ * first load. This is invisible to headless Playwright (no dynamic URL
9
+ * bar) and to the static audit Worker (no rendering), so this fast
10
+ * static rule is the cheapest place to catch it.
11
+ *
12
+ * Fix: use `100svh` (small viewport height — accounts for visible
13
+ * browser UI) for "as tall as the screen" intent, or `100dvh` (dynamic
14
+ * viewport height — recomputes as the URL bar shows/hides) for full-
15
+ * height containers that should grow when chrome retracts.
16
+ *
17
+ * Scope: warn-by-default, not fail. There ARE legitimate uses of
18
+ * `100vh` (intentionally taking layout-viewport height for hero
19
+ * sections that should stay constant as URL bar moves). Creators can
20
+ * acknowledge by leaving an `// allow-100vh` comment on the same line.
21
+ */
22
+ export async function checkUnsafeVh(source) {
23
+ const issues = [];
24
+ for await (const path of source.list()) {
25
+ if (!SCAN_EXTS.has(extOf(path)))
26
+ continue;
27
+ const content = await source.read(path);
28
+ if (!content)
29
+ continue;
30
+ for (const { re, label } of PATTERNS) {
31
+ // Reset lastIndex defensively; we use `g` to find all matches.
32
+ re.lastIndex = 0;
33
+ let m;
34
+ while ((m = re.exec(content)) !== null) {
35
+ const line = lineNumberAt(content, m.index);
36
+ // Allow opt-out via an inline `allow-100vh` comment on the same line.
37
+ if (lineHasAllowComment(content, m.index))
38
+ continue;
39
+ issues.push(`${path}:${line} ${label} — use 100svh or 100dvh`);
40
+ }
41
+ }
42
+ }
43
+ if (issues.length === 0) {
44
+ return {
45
+ name: 'No unsafe 100vh',
46
+ status: 'pass',
47
+ detail: 'no 100vh / h-screen patterns found',
48
+ };
49
+ }
50
+ const head = issues.slice(0, MAX_REPORTED);
51
+ const tail = issues.length > MAX_REPORTED ? [`...and ${issues.length - MAX_REPORTED} more`] : [];
52
+ return {
53
+ name: 'No unsafe 100vh',
54
+ status: 'warn',
55
+ detail: `${issues.length} occurrence${issues.length === 1 ? '' : 's'} of 100vh / h-screen — breaks on iOS Safari with URL bar visible`,
56
+ suggestions: [
57
+ ...head,
58
+ ...tail,
59
+ 'Replace with 100svh (preferred) or 100dvh. Add `// allow-100vh` on the line if intentional.',
60
+ ],
61
+ };
62
+ }
63
+ const SCAN_EXTS = new Set(['.css', '.scss', '.tsx', '.ts', '.jsx', '.js', '.html']);
64
+ const MAX_REPORTED = 5;
65
+ /**
66
+ * Patterns to flag. Each is global (`g`) so we find every occurrence.
67
+ *
68
+ * Boundary handling:
69
+ * - `100vh` uses `\b` since CSS values are tokenised normally.
70
+ * - Tailwind classes use a custom hyphen-aware boundary
71
+ * `(?<![\w-])foo(?![\w-])` so `h-screen` doesn't match inside
72
+ * `min-h-screen` (and we don't double-count `min-h-screen` as both
73
+ * itself and its `h-screen` substring).
74
+ */
75
+ const PATTERNS = [
76
+ { re: /\b100vh\b/g, label: '100vh' },
77
+ // Tailwind: h-screen → height: 100vh; same for min-/max-.
78
+ { re: /(?<![\w-])h-screen(?![\w-])/g, label: 'h-screen (Tailwind: → 100vh)' },
79
+ { re: /(?<![\w-])min-h-screen(?![\w-])/g, label: 'min-h-screen (Tailwind: → min-height: 100vh)' },
80
+ { re: /(?<![\w-])max-h-screen(?![\w-])/g, label: 'max-h-screen (Tailwind: → max-height: 100vh)' },
81
+ ];
82
+ function extOf(path) {
83
+ const dot = path.lastIndexOf('.');
84
+ const slash = path.lastIndexOf('/');
85
+ return dot > slash ? path.slice(dot).toLowerCase() : '';
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
+ let end = content.indexOf('\n', index);
101
+ if (end === -1)
102
+ end = content.length;
103
+ let start = content.lastIndexOf('\n', index);
104
+ if (start === -1)
105
+ start = 0;
106
+ return content.slice(start, end).includes('allow-100vh');
107
+ }
108
+ //# 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,24 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
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 declare function checkViewportSupport(source: FileSource): Promise<CheckResult>;
24
+ //# 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":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAqEnF"}
@@ -0,0 +1,92 @@
1
+ const MANIFEST_PATH = 'web/public/manifest.json';
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 async function checkViewportSupport(source) {
23
+ const raw = await source.read(MANIFEST_PATH);
24
+ if (raw === null) {
25
+ return {
26
+ name: 'Viewport support',
27
+ status: 'fail',
28
+ detail: `${MANIFEST_PATH} not found`,
29
+ };
30
+ }
31
+ let manifest;
32
+ try {
33
+ manifest = JSON.parse(raw);
34
+ }
35
+ catch {
36
+ return {
37
+ name: 'Viewport support',
38
+ status: 'fail',
39
+ detail: `${MANIFEST_PATH} unparseable`,
40
+ };
41
+ }
42
+ const orientation = manifest['orientation'];
43
+ const minWidth = manifest['min_viewport_width'];
44
+ const issues = [];
45
+ const warnings = [];
46
+ if (typeof orientation !== 'string' || !VALID_ORIENTATIONS.has(orientation)) {
47
+ issues.push(`manifest.orientation is "${String(orientation)}" — must be one of: ${[...VALID_ORIENTATIONS].join(', ')}`);
48
+ }
49
+ if (typeof minWidth !== 'number') {
50
+ warnings.push('manifest.min_viewport_width is missing — assuming 320 (strictest). ' +
51
+ 'Set explicitly to e.g. 360 / 414 / 600 / 768 / 1024 to declare which devices the app supports.');
52
+ }
53
+ else if (minWidth < 320) {
54
+ issues.push(`manifest.min_viewport_width=${minWidth} is below the 320px minimum`);
55
+ }
56
+ else if (!RECOMMENDED_WIDTHS.includes(minWidth)) {
57
+ warnings.push(`manifest.min_viewport_width=${minWidth} is unusual — recommended values: ${RECOMMENDED_WIDTHS.join(', ')}`);
58
+ }
59
+ if (issues.length > 0) {
60
+ return {
61
+ name: 'Viewport support',
62
+ status: 'fail',
63
+ detail: `${issues.length} viewport-declaration issue${issues.length === 1 ? '' : 's'}`,
64
+ suggestions: [
65
+ ...issues,
66
+ 'Edit web/public/manifest.json: add "orientation": "any" (or portrait/landscape) and "min_viewport_width": 360.',
67
+ ],
68
+ };
69
+ }
70
+ if (warnings.length > 0) {
71
+ return {
72
+ name: 'Viewport support',
73
+ status: 'warn',
74
+ detail: warnings[0],
75
+ suggestions: warnings,
76
+ };
77
+ }
78
+ return {
79
+ name: 'Viewport support',
80
+ status: 'pass',
81
+ detail: `orientation=${orientation} · min ${minWidth}px`,
82
+ };
83
+ }
84
+ const VALID_ORIENTATIONS = new Set([
85
+ 'any',
86
+ 'portrait',
87
+ 'landscape',
88
+ 'portrait-primary',
89
+ 'landscape-primary',
90
+ ]);
91
+ const RECOMMENDED_WIDTHS = [320, 360, 414, 600, 768, 1024];
92
+ //# 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,38 @@
1
+ import { checkBrandFonts } from './checks/brand-fonts.js';
2
+ import { checkBrandTokens } from './checks/brand-tokens.js';
3
+ import { checkBundleSize } from './checks/bundle-size.js';
4
+ import { checkClaudeMdSlim } from './checks/claude-md-slim.js';
5
+ import { checkDarkMode } from './checks/dark-mode.js';
6
+ import { checkHtmlMeta } from './checks/html-meta.js';
7
+ import { checkLicenseMit } from './checks/license-mit.js';
8
+ import { checkManifest } from './checks/manifest.js';
9
+ import { checkNoBrandOverrides } from './checks/no-brand-overrides.js';
10
+ import { checkNoEnvProduction } from './checks/no-env-production.js';
11
+ import { checkNoPlaceholders } from './checks/no-placeholders.js';
12
+ import { checkNoScroll } from './checks/no-scroll.js';
13
+ import { checkNoTracking } from './checks/no-tracking.js';
14
+ import { checkPwaMeta } from './checks/pwa-meta.js';
15
+ import { checkStoreLink } from './checks/store-link.js';
16
+ import { checkUnsafeVh } from './checks/unsafe-vh.js';
17
+ import { checkViewportSupport } from './checks/viewport-support.js';
18
+ import { fsFileSource, mapFileSource } from './lib/file-source.js';
19
+ import { isGameProject } from './lib/project-type.js';
20
+ import type { CheckResult } from './types.js';
21
+ export type { CheckStatus, CheckResult } from './types.js';
22
+ export type { FileSource } from './lib/file-source.js';
23
+ export { fsFileSource, mapFileSource, isGameProject };
24
+ export { checkBrandFonts, checkBrandTokens, checkBundleSize, checkClaudeMdSlim, checkDarkMode, checkHtmlMeta, checkLicenseMit, checkManifest, checkNoBrandOverrides, checkNoEnvProduction, checkNoPlaceholders, checkNoScroll, checkNoTracking, checkPwaMeta, checkStoreLink, checkUnsafeVh, checkViewportSupport, };
25
+ export { auditLive, checkNoTrackingLive, checkBrandFontsLive, checkManifestLive, checkBundleSizeLive, checkUnsafeVhLive, } from './live/index.js';
26
+ export type { LiveAuditInput, LiveAuditReport } from './live/index.js';
27
+ /**
28
+ * Runs every compliance check against the source. Two front doors:
29
+ * - `runChecks(repoDir)` — CLI / CI; reads from disk.
30
+ * - `runChecksFromFiles(map)` — VibeCode agent; reads from a Map.
31
+ *
32
+ * Both call the same underlying check functions via the FileSource
33
+ * abstraction, so rules stay in one place. Results are returned in a
34
+ * stable order so callers can render predictable output.
35
+ */
36
+ export declare function runChecks(repoDir: string): Promise<CheckResult[]>;
37
+ export declare function runChecksFromFiles(files: Map<string, string>): Promise<CheckResult[]>;
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAmB,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACpF,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,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC;AACtD,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,eAAe,EACf,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,YAAY,EACZ,cAAc,EACd,aAAa,EACb,oBAAoB,GACrB,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;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAEvE;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAE3F"}
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ import { checkBrandFonts } from './checks/brand-fonts.js';
2
+ import { checkBrandTokens } from './checks/brand-tokens.js';
3
+ import { checkBundleSize } from './checks/bundle-size.js';
4
+ import { checkClaudeMdSlim } from './checks/claude-md-slim.js';
5
+ import { checkDarkMode } from './checks/dark-mode.js';
6
+ import { checkHtmlMeta } from './checks/html-meta.js';
7
+ import { checkLicenseMit } from './checks/license-mit.js';
8
+ import { checkManifest } from './checks/manifest.js';
9
+ import { checkNoBrandOverrides } from './checks/no-brand-overrides.js';
10
+ import { checkNoEnvProduction } from './checks/no-env-production.js';
11
+ import { checkNoPlaceholders } from './checks/no-placeholders.js';
12
+ import { checkNoScroll } from './checks/no-scroll.js';
13
+ import { checkNoTracking } from './checks/no-tracking.js';
14
+ import { checkPwaMeta } from './checks/pwa-meta.js';
15
+ import { checkStoreLink } from './checks/store-link.js';
16
+ import { checkUnsafeVh } from './checks/unsafe-vh.js';
17
+ import { checkViewportSupport } from './checks/viewport-support.js';
18
+ import { fsFileSource, mapFileSource } from './lib/file-source.js';
19
+ import { isGameProject } from './lib/project-type.js';
20
+ export { fsFileSource, mapFileSource, isGameProject };
21
+ export { checkBrandFonts, checkBrandTokens, checkBundleSize, checkClaudeMdSlim, checkDarkMode, checkHtmlMeta, checkLicenseMit, checkManifest, checkNoBrandOverrides, checkNoEnvProduction, checkNoPlaceholders, checkNoScroll, checkNoTracking, checkPwaMeta, checkStoreLink, checkUnsafeVh, checkViewportSupport, };
22
+ // Live-URL audit (used by the compliance audit Worker; runs in
23
+ // browser/Workers env, no filesystem). Separate export path so callers
24
+ // don't accidentally pull node:fs in via the file-walking checks.
25
+ export { auditLive, checkNoTrackingLive, checkBrandFontsLive, checkManifestLive, checkBundleSizeLive, checkUnsafeVhLive, } from './live/index.js';
26
+ /**
27
+ * Runs every compliance check against the source. Two front doors:
28
+ * - `runChecks(repoDir)` — CLI / CI; reads from disk.
29
+ * - `runChecksFromFiles(map)` — VibeCode agent; reads from a Map.
30
+ *
31
+ * Both call the same underlying check functions via the FileSource
32
+ * abstraction, so rules stay in one place. Results are returned in a
33
+ * stable order so callers can render predictable output.
34
+ */
35
+ export async function runChecks(repoDir) {
36
+ return runChecksOn(fsFileSource(repoDir));
37
+ }
38
+ export async function runChecksFromFiles(files) {
39
+ return runChecksOn(mapFileSource(files));
40
+ }
41
+ async function runChecksOn(source) {
42
+ return Promise.all([
43
+ checkLicenseMit(source),
44
+ checkNoEnvProduction(source),
45
+ checkNoPlaceholders(source),
46
+ checkNoTracking(source),
47
+ checkBrandFonts(source),
48
+ checkBrandTokens(source),
49
+ checkNoBrandOverrides(source),
50
+ checkNoScroll(source),
51
+ checkViewportSupport(source),
52
+ checkUnsafeVh(source),
53
+ checkHtmlMeta(source),
54
+ checkPwaMeta(source),
55
+ checkManifest(source),
56
+ checkStoreLink(source),
57
+ checkDarkMode(source),
58
+ checkBundleSize(source),
59
+ checkClaudeMdSlim(source),
60
+ ]);
61
+ }
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Filesystem-agnostic file access for compliance checks.
3
+ *
4
+ * Why: the CLI scans a real directory on disk; the VibeCode agent runs
5
+ * in a Cloudflare Worker (no node:fs) and holds files in a Map. Both
6
+ * call the same checks via this interface so rules stay in one place.
7
+ *
8
+ * Conventions:
9
+ * - Paths are POSIX-style and relative to the repo root
10
+ * (e.g. `web/src/App.tsx`).
11
+ * - `read` returns null for missing files — checks decide whether
12
+ * missing == fail or missing == not-applicable.
13
+ * - `list()` already excludes noise dirs (.git, node_modules, dist,
14
+ * etc.). Implementations are responsible for that filtering.
15
+ */
16
+ export interface FileSource {
17
+ /** Yield every relevant file path. Implementations skip noise dirs. */
18
+ list(): AsyncIterable<string>;
19
+ /** UTF-8 text. Returns null if the path doesn't exist. */
20
+ read(path: string): Promise<string | null>;
21
+ /** Raw bytes. Returns null if the path doesn't exist. Optional — only
22
+ * bundle-size needs binary access today. */
23
+ readBytes?(path: string): Promise<Uint8Array | null>;
24
+ /** Names of direct children of `dir`. Returns null if dir missing.
25
+ * Optional — only bundle-size needs directory enumeration. */
26
+ listDir?(dir: string): Promise<string[] | null>;
27
+ }
28
+ /**
29
+ * Filesystem-backed FileSource — used by the CLI and CI. Walks the
30
+ * directory tree once on first list() iteration; uses node:fs for
31
+ * read/readBytes/listDir.
32
+ *
33
+ * Paths containing `..` segments are rejected — every read returns
34
+ * null. Defense in depth: today's checks only pass paths that came
35
+ * from `list()` (which stays inside the root), but a future buggy or
36
+ * malicious check could try `source.read('../etc/passwd')`. We refuse
37
+ * rather than letting `path.join` resolve outside repoDir.
38
+ */
39
+ export declare function fsFileSource(repoDir: string): FileSource;
40
+ /**
41
+ * Map-backed FileSource — used by the VibeCode agent (Workers env).
42
+ * The agent's session DO holds the virtual filesystem as a Map; this
43
+ * adapter exposes it through the same interface as the on-disk CLI.
44
+ *
45
+ * `readBytes` decodes the stored UTF-8 string back to bytes — fine for
46
+ * text-only checks. The agent has no built artefacts (it runs pre-
47
+ * deploy), so bundle-size will return its "not built yet" warn anyway.
48
+ *
49
+ * `listDir` synthesises directory listings from the Map keys.
50
+ */
51
+ export declare function mapFileSource(files: Map<string, string>): FileSource;
52
+ //# sourceMappingURL=file-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-source.d.ts","sourceRoot":"","sources":["../../src/lib/file-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,IAAI,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9B,0DAA0D;IAC1D,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3C;iDAC6C;IAC7C,SAAS,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACrD;mEAC+D;IAC/D,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;CACjD;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CA+BxD;AA4BD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,UAAU,CA8BpE"}