@freeappstore/compliance 0.3.0 → 0.6.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 (141) hide show
  1. package/dist/checks/brand-fonts.d.ts +2 -1
  2. package/dist/checks/brand-fonts.d.ts.map +1 -1
  3. package/dist/checks/brand-fonts.js +11 -7
  4. package/dist/checks/brand-fonts.js.map +1 -1
  5. package/dist/checks/brand-tokens.d.ts +22 -0
  6. package/dist/checks/brand-tokens.d.ts.map +1 -0
  7. package/dist/checks/brand-tokens.js +53 -0
  8. package/dist/checks/brand-tokens.js.map +1 -0
  9. package/dist/checks/bundle-size.d.ts +7 -1
  10. package/dist/checks/bundle-size.d.ts.map +1 -1
  11. package/dist/checks/bundle-size.js +35 -15
  12. package/dist/checks/bundle-size.js.map +1 -1
  13. package/dist/checks/claude-md-slim.d.ts +4 -0
  14. package/dist/checks/claude-md-slim.d.ts.map +1 -0
  15. package/dist/checks/claude-md-slim.js +88 -0
  16. package/dist/checks/claude-md-slim.js.map +1 -0
  17. package/dist/checks/dark-mode.d.ts +20 -0
  18. package/dist/checks/dark-mode.d.ts.map +1 -0
  19. package/dist/checks/dark-mode.js +56 -0
  20. package/dist/checks/dark-mode.js.map +1 -0
  21. package/dist/checks/html-meta.d.ts +16 -0
  22. package/dist/checks/html-meta.d.ts.map +1 -0
  23. package/dist/checks/html-meta.js +51 -0
  24. package/dist/checks/html-meta.js.map +1 -0
  25. package/dist/checks/license-mit.d.ts +13 -0
  26. package/dist/checks/license-mit.d.ts.map +1 -0
  27. package/dist/checks/license-mit.js +45 -0
  28. package/dist/checks/license-mit.js.map +1 -0
  29. package/dist/checks/manifest.d.ts +2 -1
  30. package/dist/checks/manifest.d.ts.map +1 -1
  31. package/dist/checks/manifest.js +7 -12
  32. package/dist/checks/manifest.js.map +1 -1
  33. package/dist/checks/no-brand-overrides.d.ts +2 -1
  34. package/dist/checks/no-brand-overrides.d.ts.map +1 -1
  35. package/dist/checks/no-brand-overrides.js +94 -30
  36. package/dist/checks/no-brand-overrides.js.map +1 -1
  37. package/dist/checks/no-env-production.d.ts +16 -0
  38. package/dist/checks/no-env-production.d.ts.map +1 -0
  39. package/dist/checks/no-env-production.js +37 -0
  40. package/dist/checks/no-env-production.js.map +1 -0
  41. package/dist/checks/no-placeholders.d.ts +2 -1
  42. package/dist/checks/no-placeholders.d.ts.map +1 -1
  43. package/dist/checks/no-placeholders.js +11 -9
  44. package/dist/checks/no-placeholders.js.map +1 -1
  45. package/dist/checks/no-scroll.d.ts +2 -1
  46. package/dist/checks/no-scroll.d.ts.map +1 -1
  47. package/dist/checks/no-scroll.js +13 -47
  48. package/dist/checks/no-scroll.js.map +1 -1
  49. package/dist/checks/no-tracking.d.ts +2 -1
  50. package/dist/checks/no-tracking.d.ts.map +1 -1
  51. package/dist/checks/no-tracking.js +77 -21
  52. package/dist/checks/no-tracking.js.map +1 -1
  53. package/dist/checks/pwa-meta.d.ts +19 -0
  54. package/dist/checks/pwa-meta.d.ts.map +1 -0
  55. package/dist/checks/pwa-meta.js +42 -0
  56. package/dist/checks/pwa-meta.js.map +1 -0
  57. package/dist/checks/pwa-offline.d.ts +40 -0
  58. package/dist/checks/pwa-offline.d.ts.map +1 -0
  59. package/dist/checks/pwa-offline.js +388 -0
  60. package/dist/checks/pwa-offline.js.map +1 -0
  61. package/dist/checks/store-link.d.ts +17 -0
  62. package/dist/checks/store-link.d.ts.map +1 -0
  63. package/dist/checks/store-link.js +36 -0
  64. package/dist/checks/store-link.js.map +1 -0
  65. package/dist/checks/unsafe-vh.d.ts +2 -1
  66. package/dist/checks/unsafe-vh.d.ts.map +1 -1
  67. package/dist/checks/unsafe-vh.js +25 -15
  68. package/dist/checks/unsafe-vh.js.map +1 -1
  69. package/dist/checks/viewport-support.d.ts +2 -1
  70. package/dist/checks/viewport-support.d.ts.map +1 -1
  71. package/dist/checks/viewport-support.js +13 -8
  72. package/dist/checks/viewport-support.js.map +1 -1
  73. package/dist/index.d.ts +28 -11
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +49 -19
  76. package/dist/index.js.map +1 -1
  77. package/dist/lib/file-source.d.ts +52 -0
  78. package/dist/lib/file-source.d.ts.map +1 -0
  79. package/dist/lib/file-source.js +156 -0
  80. package/dist/lib/file-source.js.map +1 -0
  81. package/dist/lib/gzip.d.ts +8 -0
  82. package/dist/lib/gzip.d.ts.map +1 -0
  83. package/dist/lib/gzip.js +26 -0
  84. package/dist/lib/gzip.js.map +1 -0
  85. package/dist/lib/project-type.d.ts +18 -0
  86. package/dist/lib/project-type.d.ts.map +1 -0
  87. package/dist/lib/project-type.js +34 -0
  88. package/dist/lib/project-type.js.map +1 -0
  89. package/dist/lib/strip.d.ts +60 -0
  90. package/dist/lib/strip.d.ts.map +1 -0
  91. package/dist/lib/strip.js +169 -0
  92. package/dist/lib/strip.js.map +1 -0
  93. package/dist/live/index.d.ts.map +1 -1
  94. package/dist/live/index.js +14 -8
  95. package/dist/live/index.js.map +1 -1
  96. package/package.json +10 -10
  97. package/LICENSE +0 -21
  98. package/dist/checks/brand-fonts.test.d.ts +0 -2
  99. package/dist/checks/brand-fonts.test.d.ts.map +0 -1
  100. package/dist/checks/brand-fonts.test.js +0 -45
  101. package/dist/checks/brand-fonts.test.js.map +0 -1
  102. package/dist/checks/bundle-size.test.d.ts +0 -2
  103. package/dist/checks/bundle-size.test.d.ts.map +0 -1
  104. package/dist/checks/bundle-size.test.js +0 -57
  105. package/dist/checks/bundle-size.test.js.map +0 -1
  106. package/dist/checks/manifest.test.d.ts +0 -2
  107. package/dist/checks/manifest.test.d.ts.map +0 -1
  108. package/dist/checks/manifest.test.js +0 -53
  109. package/dist/checks/manifest.test.js.map +0 -1
  110. package/dist/checks/no-brand-overrides.test.d.ts +0 -2
  111. package/dist/checks/no-brand-overrides.test.d.ts.map +0 -1
  112. package/dist/checks/no-brand-overrides.test.js +0 -102
  113. package/dist/checks/no-brand-overrides.test.js.map +0 -1
  114. package/dist/checks/no-placeholders.test.d.ts +0 -2
  115. package/dist/checks/no-placeholders.test.d.ts.map +0 -1
  116. package/dist/checks/no-placeholders.test.js +0 -46
  117. package/dist/checks/no-placeholders.test.js.map +0 -1
  118. package/dist/checks/no-scroll.test.d.ts +0 -2
  119. package/dist/checks/no-scroll.test.d.ts.map +0 -1
  120. package/dist/checks/no-scroll.test.js +0 -105
  121. package/dist/checks/no-scroll.test.js.map +0 -1
  122. package/dist/checks/no-tracking.test.d.ts +0 -2
  123. package/dist/checks/no-tracking.test.d.ts.map +0 -1
  124. package/dist/checks/no-tracking.test.js +0 -42
  125. package/dist/checks/no-tracking.test.js.map +0 -1
  126. package/dist/checks/unsafe-vh.test.d.ts +0 -2
  127. package/dist/checks/unsafe-vh.test.d.ts.map +0 -1
  128. package/dist/checks/unsafe-vh.test.js +0 -115
  129. package/dist/checks/unsafe-vh.test.js.map +0 -1
  130. package/dist/checks/viewport-support.test.d.ts +0 -2
  131. package/dist/checks/viewport-support.test.d.ts.map +0 -1
  132. package/dist/checks/viewport-support.test.js +0 -63
  133. package/dist/checks/viewport-support.test.js.map +0 -1
  134. package/dist/lib/walk.d.ts +0 -6
  135. package/dist/lib/walk.d.ts.map +0 -1
  136. package/dist/lib/walk.js +0 -35
  137. package/dist/lib/walk.js.map +0 -1
  138. package/dist/live/index.test.d.ts +0 -2
  139. package/dist/live/index.test.d.ts.map +0 -1
  140. package/dist/live/index.test.js +0 -259
  141. package/dist/live/index.test.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import type { FileSource } from '../lib/file-source.js';
1
2
  import type { CheckResult } from '../types.js';
2
3
  /**
3
4
  * The platform's brand wants Manrope (body) + Fraunces (display) so apps
@@ -5,5 +6,5 @@ import type { CheckResult } from '../types.js';
5
6
  * CSS file referencing both font names — the actual font-loading mechanism
6
7
  * (Google Fonts, self-hosted, etc.) is the app's choice.
7
8
  */
8
- export declare function checkBrandFonts(repoDir: string): Promise<CheckResult>;
9
+ export declare function checkBrandFonts(source: FileSource): Promise<CheckResult>;
9
10
  //# sourceMappingURL=brand-fonts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"brand-fonts.d.ts","sourceRoot":"","sources":["../../src/checks/brand-fonts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA6B3E"}
1
+ {"version":3,"file":"brand-fonts.d.ts","sourceRoot":"","sources":["../../src/checks/brand-fonts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA8B9E"}
@@ -1,6 +1,3 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { extname } from 'node:path';
3
- import { walk } from '../lib/walk.js';
4
1
  const REQUIRED_FONTS = ['Manrope', 'Fraunces'];
5
2
  /**
6
3
  * The platform's brand wants Manrope (body) + Fraunces (display) so apps
@@ -8,13 +5,15 @@ const REQUIRED_FONTS = ['Manrope', 'Fraunces'];
8
5
  * CSS file referencing both font names — the actual font-loading mechanism
9
6
  * (Google Fonts, self-hosted, etc.) is the app's choice.
10
7
  */
11
- export async function checkBrandFonts(repoDir) {
8
+ export async function checkBrandFonts(source) {
12
9
  const found = new Set();
13
- for await (const file of walk(repoDir)) {
14
- const ext = extname(file).toLowerCase();
10
+ for await (const path of source.list()) {
11
+ const ext = extOf(path);
15
12
  if (ext !== '.css' && ext !== '.scss' && ext !== '.html')
16
13
  continue;
17
- const content = await readFile(file, 'utf8').catch(() => '');
14
+ const content = await source.read(path);
15
+ if (!content)
16
+ continue;
18
17
  for (const font of REQUIRED_FONTS) {
19
18
  if (content.includes(font))
20
19
  found.add(font);
@@ -40,4 +39,9 @@ export async function checkBrandFonts(repoDir) {
40
39
  ],
41
40
  };
42
41
  }
42
+ function extOf(path) {
43
+ const dot = path.lastIndexOf('.');
44
+ const slash = path.lastIndexOf('/');
45
+ return dot > slash ? path.slice(dot).toLowerCase() : '';
46
+ }
43
47
  //# sourceMappingURL=brand-fonts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"brand-fonts.js","sourceRoot":"","sources":["../../src/checks/brand-fonts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAe;IACnD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QACxC,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,OAAO;YAAE,SAAS;QACnE,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC7D,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAClC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,MAAM;YAAE,MAAM;IAClD,CAAC;IAED,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,IAAI,EAAE,qBAAqB;YAC3B,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC;SACnC,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,qBAAqB;QAC3B,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,YAAY,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACxC,WAAW,EAAE;YACX,uEAAuE;YACvE,sFAAsF;SACvF;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"brand-fonts.js","sourceRoot":"","sources":["../../src/checks/brand-fonts.ts"],"names":[],"mappings":"AAGA,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAkB;IACtD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,OAAO;YAAE,SAAS;QACnE,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAClC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,MAAM;YAAE,MAAM;IAClD,CAAC;IAED,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,IAAI,EAAE,qBAAqB;YAC3B,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC;SACnC,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,qBAAqB;QAC3B,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,YAAY,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACxC,WAAW,EAAE;YACX,uEAAuE;YACvE,sFAAsF;SACvF;KACF,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,IAAY;IACzB,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,OAAO,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ /**
4
+ * Verifies the canonical CSS custom properties are *defined* in the
5
+ * project's theme CSS. This is the inverse of `no-brand-overrides`:
6
+ * one ensures the tokens haven't been redefined elsewhere, the other
7
+ * ensures they exist in the first place.
8
+ *
9
+ * - apps → must define `--paper`, `--ink`, `--accent`
10
+ * - games → must define `--bg`, `--ink`, `--accent`
11
+ *
12
+ * Different surface tokens reflect intent: apps live on a paper-toned
13
+ * neutral; games live on a bg-toned canvas (often dark). The shared
14
+ * `--ink` and `--accent` keep typography + interactive states
15
+ * consistent across both stores.
16
+ *
17
+ * We scan all CSS / SCSS files (not just the canonical theme path) so
18
+ * the check passes whether the creator's tokens live in `index.css`,
19
+ * `main.css`, or some other theme file.
20
+ */
21
+ export declare function checkBrandTokens(source: FileSource): Promise<CheckResult>;
22
+ //# sourceMappingURL=brand-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand-tokens.d.ts","sourceRoot":"","sources":["../../src/checks/brand-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA8B/E"}
@@ -0,0 +1,53 @@
1
+ import { isGameProject } from '../lib/project-type.js';
2
+ /**
3
+ * Verifies the canonical CSS custom properties are *defined* in the
4
+ * project's theme CSS. This is the inverse of `no-brand-overrides`:
5
+ * one ensures the tokens haven't been redefined elsewhere, the other
6
+ * ensures they exist in the first place.
7
+ *
8
+ * - apps → must define `--paper`, `--ink`, `--accent`
9
+ * - games → must define `--bg`, `--ink`, `--accent`
10
+ *
11
+ * Different surface tokens reflect intent: apps live on a paper-toned
12
+ * neutral; games live on a bg-toned canvas (often dark). The shared
13
+ * `--ink` and `--accent` keep typography + interactive states
14
+ * consistent across both stores.
15
+ *
16
+ * We scan all CSS / SCSS files (not just the canonical theme path) so
17
+ * the check passes whether the creator's tokens live in `index.css`,
18
+ * `main.css`, or some other theme file.
19
+ */
20
+ export async function checkBrandTokens(source) {
21
+ const isGame = await isGameProject(source);
22
+ const required = isGame ? ['--bg', '--ink', '--accent'] : ['--paper', '--ink', '--accent'];
23
+ const found = new Set();
24
+ for await (const path of source.list()) {
25
+ if (!path.endsWith('.css') && !path.endsWith('.scss'))
26
+ continue;
27
+ const content = await source.read(path);
28
+ if (!content)
29
+ continue;
30
+ for (const token of required) {
31
+ // Match `--token:` as a CSS declaration (not `var(--token)`).
32
+ const re = new RegExp(`(?:^|[\\s;{,])${token}\\s*:`, 'm');
33
+ if (re.test(content))
34
+ found.add(token);
35
+ }
36
+ if (found.size === required.length)
37
+ break;
38
+ }
39
+ const missing = required.filter((t) => !found.has(t));
40
+ if (missing.length === 0) {
41
+ return { name: 'Brand tokens defined', status: 'pass', detail: required.join(' + ') };
42
+ }
43
+ return {
44
+ name: 'Brand tokens defined',
45
+ status: 'fail',
46
+ detail: `missing CSS tokens: ${missing.join(', ')}`,
47
+ suggestions: [
48
+ `Define ${missing.join(', ')} in your theme CSS (typically web/src/index.css).`,
49
+ `Apps use --paper / --ink / --accent; games use --bg / --ink / --accent.`,
50
+ ],
51
+ };
52
+ }
53
+ //# sourceMappingURL=brand-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand-tokens.js","sourceRoot":"","sources":["../../src/checks/brand-tokens.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAkB;IACvD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;IAC3F,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAEhC,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,SAAS;QAChE,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,8DAA8D;YAC9D,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,iBAAiB,KAAK,OAAO,EAAE,GAAG,CAAC,CAAC;YAC1D,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzC,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM;YAAE,MAAM;IAC5C,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IACxF,CAAC;IACD,OAAO;QACL,IAAI,EAAE,sBAAsB;QAC5B,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,uBAAuB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACnD,WAAW,EAAE;YACX,UAAU,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,mDAAmD;YAC/E,yEAAyE;SAC1E;KACF,CAAC;AACJ,CAAC"}
@@ -1,8 +1,14 @@
1
+ import type { FileSource } from '../lib/file-source.js';
1
2
  import type { CheckResult } from '../types.js';
2
3
  /**
3
4
  * Checks the largest JS asset under web/dist/assets/ against the 300KB-gzip
4
5
  * limit. Returns 'warn' if dist hasn't been built yet (we don't want to
5
6
  * silently pass when there's nothing to measure).
7
+ *
8
+ * Uses the Web `CompressionStream('gzip')` API rather than `node:zlib` so
9
+ * the same module loads in Cloudflare Workers (the VibeCode agent
10
+ * bundles this package). The agent will always hit the "not built yet"
11
+ * branch since the virtual filesystem holds source, not artefacts.
6
12
  */
7
- export declare function checkBundleSize(repoDir: string): Promise<CheckResult>;
13
+ export declare function checkBundleSize(source: FileSource): Promise<CheckResult>;
8
14
  //# sourceMappingURL=bundle-size.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundle-size.d.ts","sourceRoot":"","sources":["../../src/checks/bundle-size.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAyD3E"}
1
+ {"version":3,"file":"bundle-size.d.ts","sourceRoot":"","sources":["../../src/checks/bundle-size.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA2E9E"}
@@ -1,19 +1,26 @@
1
- import { readdir, readFile, stat } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import { gzipSync } from 'node:zlib';
1
+ import { gzipByteLength } from '../lib/gzip.js';
4
2
  const MAX_GZIP_BYTES = 300 * 1024; // 300 KB — matches the template's compliance.yml
3
+ const ASSETS_DIR = 'web/dist/assets';
5
4
  /**
6
5
  * Checks the largest JS asset under web/dist/assets/ against the 300KB-gzip
7
6
  * limit. Returns 'warn' if dist hasn't been built yet (we don't want to
8
7
  * silently pass when there's nothing to measure).
8
+ *
9
+ * Uses the Web `CompressionStream('gzip')` API rather than `node:zlib` so
10
+ * the same module loads in Cloudflare Workers (the VibeCode agent
11
+ * bundles this package). The agent will always hit the "not built yet"
12
+ * branch since the virtual filesystem holds source, not artefacts.
9
13
  */
10
- export async function checkBundleSize(repoDir) {
11
- const assetsDir = join(repoDir, 'web', 'dist', 'assets');
12
- let entries;
13
- try {
14
- entries = await readdir(assetsDir);
14
+ export async function checkBundleSize(source) {
15
+ if (!source.listDir) {
16
+ return {
17
+ name: 'Bundle size',
18
+ status: 'warn',
19
+ detail: 'file source does not support directory listing',
20
+ };
15
21
  }
16
- catch {
22
+ const entries = await source.listDir(ASSETS_DIR);
23
+ if (entries === null) {
17
24
  return {
18
25
  name: 'Bundle size',
19
26
  status: 'warn',
@@ -25,7 +32,14 @@ export async function checkBundleSize(repoDir) {
25
32
  return {
26
33
  name: 'Bundle size',
27
34
  status: 'warn',
28
- detail: `no JS files in ${assetsDir}`,
35
+ detail: `no JS files in ${ASSETS_DIR}`,
36
+ };
37
+ }
38
+ if (!source.readBytes) {
39
+ return {
40
+ name: 'Bundle size',
41
+ status: 'warn',
42
+ detail: 'file source does not support binary reads',
29
43
  };
30
44
  }
31
45
  // Find the largest JS file (the entry chunk) — same behavior as the
@@ -33,14 +47,20 @@ export async function checkBundleSize(repoDir) {
33
47
  let largest = '';
34
48
  let largestSize = 0;
35
49
  for (const f of jsFiles) {
36
- const s = await stat(join(assetsDir, f));
37
- if (s.size > largestSize) {
50
+ const bytes = await source.readBytes(`${ASSETS_DIR}/${f}`);
51
+ if (bytes && bytes.byteLength > largestSize) {
38
52
  largest = f;
39
- largestSize = s.size;
53
+ largestSize = bytes.byteLength;
40
54
  }
41
55
  }
42
- const content = await readFile(join(assetsDir, largest));
43
- const gzipped = gzipSync(content).byteLength;
56
+ if (!largest) {
57
+ return { name: 'Bundle size', status: 'warn', detail: `no readable JS in ${ASSETS_DIR}` };
58
+ }
59
+ const content = await source.readBytes(`${ASSETS_DIR}/${largest}`);
60
+ if (!content) {
61
+ return { name: 'Bundle size', status: 'warn', detail: `could not read ${largest}` };
62
+ }
63
+ const gzipped = await gzipByteLength(content);
44
64
  const kb = (gzipped / 1024).toFixed(1);
45
65
  const limitKb = (MAX_GZIP_BYTES / 1024).toFixed(0);
46
66
  if (gzipped > MAX_GZIP_BYTES) {
@@ -1 +1 @@
1
- {"version":3,"file":"bundle-size.js","sourceRoot":"","sources":["../../src/checks/bundle-size.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,iDAAiD;AAEpF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAe;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzD,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,sDAAsD;SAC/D,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,kBAAkB,SAAS,EAAE;SACtC,CAAC;IACJ,CAAC;IAED,oEAAoE;IACpE,4CAA4C;IAC5C,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;QACzC,IAAI,CAAC,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;YACzB,OAAO,GAAG,CAAC,CAAC;YACZ,WAAW,GAAG,CAAC,CAAC,IAAI,CAAC;QACvB,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC;IAC7C,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnD,IAAI,OAAO,GAAG,cAAc,EAAE,CAAC;QAC7B,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,OAAO,KAAK,EAAE,sBAAsB,OAAO,MAAM;YAC5D,WAAW,EAAE;gBACX,4DAA4D;gBAC5D,uDAAuD;gBACvD,qDAAqD;aACtD;SACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,GAAG,OAAO,KAAK,EAAE,sBAAsB,OAAO,MAAM;KAC7D,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"bundle-size.js","sourceRoot":"","sources":["../../src/checks/bundle-size.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ export declare function checkClaudeMdSlim(source: FileSource): Promise<CheckResult>;
4
+ //# sourceMappingURL=claude-md-slim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-md-slim.d.ts","sourceRoot":"","sources":["../../src/checks/claude-md-slim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAyC/C,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAwDhF"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Per-repo CLAUDE.md is intended to be slim: the H1, a 1-line description,
3
+ * dev/build/deploy commands, and a pointer to SKILLS.md. Anything that
4
+ * belongs in the platform-wide guide (tech stack, brand, deploy mechanism,
5
+ * mobile rules, paths) creates drift surface area and produces stale
6
+ * instructions for AI agents.
7
+ *
8
+ * This check warns (does not fail) when the repo's CLAUDE.md looks
9
+ * boilerplate-heavy. Warn-only so a contributor mid-edit isn't blocked,
10
+ * and so existing repos that haven't been swept yet don't 404 their CI.
11
+ *
12
+ * The SKILLS.md convention section is the source-of-truth for what
13
+ * counts as boilerplate; this check is the enforcement arm.
14
+ */
15
+ const NAME = 'CLAUDE.md is slim (no platform boilerplate)';
16
+ const SKILLS_URL = 'https://raw.githubusercontent.com/freeappstore-online/freeappstore/main/SKILLS.md';
17
+ // Section headers that should NOT appear in a per-repo CLAUDE.md — they
18
+ // belong in SKILLS.md. Match leading-`##` headers, case-insensitive.
19
+ const BOILERPLATE_HEADERS = [
20
+ 'platform: freeappstore',
21
+ 'platform: freegamestore',
22
+ 'tech stack',
23
+ 'brand guidelines',
24
+ 'brand', // bare "## Brand"
25
+ 'rules', // sweeping platform rules; per-repo exceptions can be a "## Notes"
26
+ 'platform docs',
27
+ 'platform docs & publishing',
28
+ 'publishing',
29
+ ];
30
+ // Anything over this many non-blank lines is suspiciously verbose for a
31
+ // slim per-repo file. Tier-3 cases (legitimate per-repo docs like setup
32
+ // instructions, architecture diagrams) can grow naturally; this is a
33
+ // warning, not a hard cap.
34
+ const SOFT_LINE_LIMIT = 60;
35
+ export async function checkClaudeMdSlim(source) {
36
+ const body = await source.read('CLAUDE.md');
37
+ if (body == null) {
38
+ // No CLAUDE.md is fine — agents fall back to SKILLS.md via README/docs.
39
+ return { name: NAME, status: 'pass', detail: 'no CLAUDE.md present' };
40
+ }
41
+ const offendingHeaders = [];
42
+ const seen = new Set();
43
+ for (const raw of body.split('\n')) {
44
+ const m = /^#{2,}\s+(.+?)\s*$/.exec(raw);
45
+ if (!m)
46
+ continue;
47
+ const text = m[1].toLowerCase().replace(/:$/, '');
48
+ for (const banned of BOILERPLATE_HEADERS) {
49
+ if (text === banned || text.startsWith(`${banned} `) || text.startsWith(`${banned}:`)) {
50
+ if (!seen.has(banned)) {
51
+ offendingHeaders.push(m[1]);
52
+ seen.add(banned);
53
+ }
54
+ break;
55
+ }
56
+ }
57
+ }
58
+ const nonBlankLines = body.split('\n').filter((l) => l.trim().length > 0).length;
59
+ const hasSkillsUrl = body.includes(SKILLS_URL);
60
+ const issues = [];
61
+ if (offendingHeaders.length > 0) {
62
+ issues.push(`platform-boilerplate sections: ${offendingHeaders.join(', ')}`);
63
+ }
64
+ if (nonBlankLines > SOFT_LINE_LIMIT) {
65
+ issues.push(`${nonBlankLines} non-blank lines (soft limit ${SOFT_LINE_LIMIT})`);
66
+ }
67
+ if (!hasSkillsUrl) {
68
+ issues.push('missing SKILLS.md pointer');
69
+ }
70
+ if (issues.length === 0) {
71
+ return {
72
+ name: NAME,
73
+ status: 'pass',
74
+ detail: `${nonBlankLines} non-blank lines, SKILLS.md referenced, no banned sections`,
75
+ };
76
+ }
77
+ return {
78
+ name: NAME,
79
+ status: 'warn',
80
+ detail: issues.join('; '),
81
+ suggestions: [
82
+ 'Per-repo CLAUDE.md should hold only what is unique to this repo (description, dev/build/deploy, repo-specific setup).',
83
+ 'Anything platform-wide (tech stack, brand, mobile rules, deploy flow) belongs in SKILLS.md instead.',
84
+ `See the "Per-repo CLAUDE.md convention" section of SKILLS.md: ${SKILLS_URL}`,
85
+ ],
86
+ };
87
+ }
88
+ //# sourceMappingURL=claude-md-slim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-md-slim.js","sourceRoot":"","sources":["../../src/checks/claude-md-slim.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH,MAAM,IAAI,GAAG,6CAA6C,CAAC;AAE3D,MAAM,UAAU,GACd,mFAAmF,CAAC;AAEtF,wEAAwE;AACxE,qEAAqE;AACrE,MAAM,mBAAmB,GAAG;IAC1B,wBAAwB;IACxB,yBAAyB;IACzB,YAAY;IACZ,kBAAkB;IAClB,OAAO,EAAE,kBAAkB;IAC3B,OAAO,EAAE,mEAAmE;IAC5E,eAAe;IACf,4BAA4B;IAC5B,YAAY;CACb,CAAC;AAEF,wEAAwE;AACxE,wEAAwE;AACxE,qEAAqE;AACrE,2BAA2B;AAC3B,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAkB;IACxD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5C,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,wEAAwE;QACxE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACxE,CAAC;IAED,MAAM,gBAAgB,GAAa,EAAE,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACnD,KAAK,MAAM,MAAM,IAAI,mBAAmB,EAAE,CAAC;YACzC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;oBACtB,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC;oBAC7B,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACnB,CAAC;gBACD,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;IACjF,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAE/C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,kCAAkC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,aAAa,GAAG,eAAe,EAAE,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,GAAG,aAAa,gCAAgC,eAAe,GAAG,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO;YACL,IAAI,EAAE,IAAI;YACV,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,aAAa,4DAA4D;SACrF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QACzB,WAAW,EAAE;YACX,uHAAuH;YACvH,qGAAqG;YACrG,iEAAiE,UAAU,EAAE;SAC9E;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ /**
4
+ * Apps on FreeAppStore should respect the user's system colour-scheme
5
+ * preference — dark interfaces are the default for many users on iOS
6
+ * and recent Android. Returns `warn` (not `fail`) if no signal is
7
+ * found because some apps are inherently visual (drawing canvas,
8
+ * camera viewfinder) and don't have a colour scheme to switch.
9
+ *
10
+ * Accepted signals (any one passes):
11
+ * - `prefers-color-scheme` media query in CSS or JSX
12
+ * - `data-theme` attribute (manual toggle)
13
+ * - `color-scheme` CSS property
14
+ *
15
+ * Games are exempt — game UIs are full-bleed and pick their own
16
+ * palette, so the system preference rarely applies. Skipped if
17
+ * `@freegamestore/games` is detected.
18
+ */
19
+ export declare function checkDarkMode(source: FileSource): Promise<CheckResult>;
20
+ //# sourceMappingURL=dark-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dark-mode.d.ts","sourceRoot":"","sources":["../../src/checks/dark-mode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAmC5E"}
@@ -0,0 +1,56 @@
1
+ import { isGameProject } from '../lib/project-type.js';
2
+ import { stripCommentsOnly, stripCssComments } from '../lib/strip.js';
3
+ /**
4
+ * Apps on FreeAppStore should respect the user's system colour-scheme
5
+ * preference — dark interfaces are the default for many users on iOS
6
+ * and recent Android. Returns `warn` (not `fail`) if no signal is
7
+ * found because some apps are inherently visual (drawing canvas,
8
+ * camera viewfinder) and don't have a colour scheme to switch.
9
+ *
10
+ * Accepted signals (any one passes):
11
+ * - `prefers-color-scheme` media query in CSS or JSX
12
+ * - `data-theme` attribute (manual toggle)
13
+ * - `color-scheme` CSS property
14
+ *
15
+ * Games are exempt — game UIs are full-bleed and pick their own
16
+ * palette, so the system preference rarely applies. Skipped if
17
+ * `@freegamestore/games` is detected.
18
+ */
19
+ export async function checkDarkMode(source) {
20
+ if (await isGameProject(source)) {
21
+ return {
22
+ name: 'Dark mode support',
23
+ status: 'pass',
24
+ detail: 'game project — check skipped',
25
+ };
26
+ }
27
+ for await (const path of source.list()) {
28
+ if (!path.startsWith('web/src/'))
29
+ continue;
30
+ if (!/\.(?:css|scss|tsx?|jsx?|html)$/i.test(path))
31
+ continue;
32
+ const raw = await source.read(path);
33
+ if (!raw)
34
+ continue;
35
+ // Strip comments so a `// We could use prefers-color-scheme` note
36
+ // doesn't false-pass the check. Preserve string contents — the
37
+ // signal might be in a template literal that injects CSS.
38
+ const lower = path.toLowerCase();
39
+ const content = lower.endsWith('.css') || lower.endsWith('.scss')
40
+ ? stripCssComments(raw)
41
+ : stripCommentsOnly(raw);
42
+ if (/prefers-color-scheme|data-theme|color-scheme\s*:/i.test(content)) {
43
+ return { name: 'Dark mode support', status: 'pass', detail: `signal in ${path}` };
44
+ }
45
+ }
46
+ return {
47
+ name: 'Dark mode support',
48
+ status: 'warn',
49
+ detail: 'no prefers-color-scheme / data-theme / color-scheme signal in web/src/',
50
+ suggestions: [
51
+ 'Add a `@media (prefers-color-scheme: dark) { ... }` block to your theme CSS, or',
52
+ 'Use `color-scheme: light dark;` to opt the page into native scrollbars / form controls.',
53
+ ],
54
+ };
55
+ }
56
+ //# sourceMappingURL=dark-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dark-mode.js","sourceRoot":"","sources":["../../src/checks/dark-mode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGtE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAkB;IACpD,IAAI,MAAM,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAChC,OAAO;YACL,IAAI,EAAE,mBAAmB;YACzB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,8BAA8B;SACvC,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,SAAS;QAC3C,IAAI,CAAC,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAC5D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,kEAAkE;QAClE,+DAA+D;QAC/D,0DAA0D;QAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACjC,MAAM,OAAO,GACX,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC;YAC/C,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC;YACvB,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,mDAAmD,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACtE,OAAO,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,IAAI,EAAE,EAAE,CAAC;QACpF,CAAC;IACH,CAAC;IACD,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,wEAAwE;QAChF,WAAW,EAAE;YACX,iFAAiF;YACjF,yFAAyF;SAC1F;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ /**
4
+ * Verifies `web/index.html` declares the basics: a `lang` attribute, a
5
+ * viewport meta, and a non-empty `<title>`. These three together cover:
6
+ *
7
+ * - lang → screen readers + auto-translate work correctly
8
+ * - viewport → mobile rendering is sized to device, not desktop default
9
+ * - title → the only thing visible in tabs, history, search results
10
+ *
11
+ * All three are baked into the canonical template — this check exists
12
+ * to catch creators who edited index.html and accidentally stripped
13
+ * something they needed.
14
+ */
15
+ export declare function checkHtmlMeta(source: FileSource): Promise<CheckResult>;
16
+ //# sourceMappingURL=html-meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-meta.d.ts","sourceRoot":"","sources":["../../src/checks/html-meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAkC5E"}
@@ -0,0 +1,51 @@
1
+ import { stripHtmlComments } from '../lib/strip.js';
2
+ const HTML_PATH = 'web/index.html';
3
+ /**
4
+ * Verifies `web/index.html` declares the basics: a `lang` attribute, a
5
+ * viewport meta, and a non-empty `<title>`. These three together cover:
6
+ *
7
+ * - lang → screen readers + auto-translate work correctly
8
+ * - viewport → mobile rendering is sized to device, not desktop default
9
+ * - title → the only thing visible in tabs, history, search results
10
+ *
11
+ * All three are baked into the canonical template — this check exists
12
+ * to catch creators who edited index.html and accidentally stripped
13
+ * something they needed.
14
+ */
15
+ export async function checkHtmlMeta(source) {
16
+ const rawHtml = await source.read(HTML_PATH);
17
+ if (rawHtml === null) {
18
+ return {
19
+ name: 'HTML meta tags',
20
+ status: 'fail',
21
+ detail: `${HTML_PATH} not found`,
22
+ };
23
+ }
24
+ // Strip HTML comment bodies before matching — `<!-- <meta name=
25
+ // "viewport" ...> -->` is not a real viewport meta, and a commented-
26
+ // out `<title>` shouldn't count either.
27
+ const html = stripHtmlComments(rawHtml);
28
+ const missing = [];
29
+ if (!/<html[^>]*\blang\s*=/i.test(html))
30
+ missing.push('lang attribute on <html>');
31
+ if (!/<meta[^>]*\bname\s*=\s*["']viewport["']/i.test(html))
32
+ missing.push('viewport meta');
33
+ // Extract the <title> body (multiline-safe), trim, and require it
34
+ // be non-empty. Avoids rejecting valid titles like `<title> Hello</title>`
35
+ // where the body has leading whitespace.
36
+ const titleMatch = html.match(/<title>([\s\S]*?)<\/title>/i);
37
+ if (!titleMatch || titleMatch[1].trim() === '')
38
+ missing.push('non-empty <title>');
39
+ if (missing.length === 0) {
40
+ return { name: 'HTML meta tags', status: 'pass', detail: 'lang + viewport + title present' };
41
+ }
42
+ return {
43
+ name: 'HTML meta tags',
44
+ status: 'fail',
45
+ detail: `missing: ${missing.join(', ')}`,
46
+ suggestions: [
47
+ 'Restore the canonical <head>: `<html lang="en">`, `<meta name="viewport" content="width=device-width, initial-scale=1" />`, `<title>App name</title>`.',
48
+ ],
49
+ };
50
+ }
51
+ //# sourceMappingURL=html-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-meta.js","sourceRoot":"","sources":["../../src/checks/html-meta.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGpD,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAEnC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAkB;IACpD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO;YACL,IAAI,EAAE,gBAAgB;YACtB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,SAAS,YAAY;SACjC,CAAC;IACJ,CAAC;IACD,gEAAgE;IAChE,qEAAqE;IACrE,wCAAwC;IACxC,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAExC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAClF,IAAI,CAAC,0CAA0C,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC1F,kEAAkE;IAClE,2EAA2E;IAC3E,yCAAyC;IACzC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IAC7D,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAEnF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,iCAAiC,EAAE,CAAC;IAC/F,CAAC;IACD,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,YAAY,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACxC,WAAW,EAAE;YACX,wJAAwJ;SACzJ;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ /**
4
+ * The platform requires every published app/game to be MIT-licensed —
5
+ * it's part of the "free forever, open source" promise on the
6
+ * storefront. Looks for a top-level LICENSE file containing "MIT".
7
+ *
8
+ * Accepts LICENSE, LICENSE.md, LICENSE.txt — case-insensitive on the
9
+ * basename. The MIT keyword check is intentionally loose: the canonical
10
+ * MIT text starts with "MIT License" but variations exist.
11
+ */
12
+ export declare function checkLicenseMit(source: FileSource): Promise<CheckResult>;
13
+ //# sourceMappingURL=license-mit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license-mit.d.ts","sourceRoot":"","sources":["../../src/checks/license-mit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC9E"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The platform requires every published app/game to be MIT-licensed —
3
+ * it's part of the "free forever, open source" promise on the
4
+ * storefront. Looks for a top-level LICENSE file containing "MIT".
5
+ *
6
+ * Accepts LICENSE, LICENSE.md, LICENSE.txt — case-insensitive on the
7
+ * basename. The MIT keyword check is intentionally loose: the canonical
8
+ * MIT text starts with "MIT License" but variations exist.
9
+ */
10
+ export async function checkLicenseMit(source) {
11
+ const candidates = [
12
+ 'LICENSE',
13
+ 'LICENSE.md',
14
+ 'LICENSE.txt',
15
+ 'license',
16
+ 'license.md',
17
+ 'license.txt',
18
+ ];
19
+ for (const path of candidates) {
20
+ const content = await source.read(path);
21
+ if (content === null)
22
+ continue;
23
+ if (/\bmit\b/i.test(content)) {
24
+ return { name: 'MIT License', status: 'pass', detail: path };
25
+ }
26
+ return {
27
+ name: 'MIT License',
28
+ status: 'fail',
29
+ detail: `${path} exists but does not mention MIT`,
30
+ suggestions: [
31
+ 'Replace LICENSE with the standard MIT license text from https://opensource.org/license/mit',
32
+ ],
33
+ };
34
+ }
35
+ return {
36
+ name: 'MIT License',
37
+ status: 'fail',
38
+ detail: 'no LICENSE file at repo root',
39
+ suggestions: [
40
+ 'Add a LICENSE file with the MIT license text. The platform requires MIT for publication.',
41
+ 'Template: https://github.com/freeappstore-online/template-standalone/blob/main/LICENSE',
42
+ ],
43
+ };
44
+ }
45
+ //# sourceMappingURL=license-mit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license-mit.js","sourceRoot":"","sources":["../../src/checks/license-mit.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAkB;IACtD,MAAM,UAAU,GAAG;QACjB,SAAS;QACT,YAAY;QACZ,aAAa;QACb,SAAS;QACT,YAAY;QACZ,aAAa;KACd,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,OAAO,KAAK,IAAI;YAAE,SAAS;QAC/B,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAC/D,CAAC;QACD,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,IAAI,kCAAkC;YACjD,WAAW,EAAE;gBACX,4FAA4F;aAC7F;SACF,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,8BAA8B;QACtC,WAAW,EAAE;YACX,0FAA0F;YAC1F,wFAAwF;SACzF;KACF,CAAC;AACJ,CAAC"}
@@ -1,8 +1,9 @@
1
+ import type { FileSource } from '../lib/file-source.js';
1
2
  import type { CheckResult } from '../types.js';
2
3
  /**
3
4
  * Verifies the PWA manifest exists at the expected location and parses
4
5
  * to JSON. Apps without a manifest can't be "Add to Home Screen"-d on
5
6
  * mobile; required by the platform.
6
7
  */
7
- export declare function checkManifest(repoDir: string): Promise<CheckResult>;
8
+ export declare function checkManifest(source: FileSource): Promise<CheckResult>;
8
9
  //# sourceMappingURL=manifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/checks/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAuCzE"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/checks/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAoC5E"}