@freeappstore/compliance 0.3.0 → 0.5.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 (129) 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/dark-mode.d.ts +20 -0
  14. package/dist/checks/dark-mode.d.ts.map +1 -0
  15. package/dist/checks/dark-mode.js +48 -0
  16. package/dist/checks/dark-mode.js.map +1 -0
  17. package/dist/checks/html-meta.d.ts +16 -0
  18. package/dist/checks/html-meta.d.ts.map +1 -0
  19. package/dist/checks/html-meta.js +46 -0
  20. package/dist/checks/html-meta.js.map +1 -0
  21. package/dist/checks/license-mit.d.ts +13 -0
  22. package/dist/checks/license-mit.d.ts.map +1 -0
  23. package/dist/checks/license-mit.js +38 -0
  24. package/dist/checks/license-mit.js.map +1 -0
  25. package/dist/checks/manifest.d.ts +2 -1
  26. package/dist/checks/manifest.d.ts.map +1 -1
  27. package/dist/checks/manifest.js +7 -12
  28. package/dist/checks/manifest.js.map +1 -1
  29. package/dist/checks/no-brand-overrides.d.ts +2 -1
  30. package/dist/checks/no-brand-overrides.d.ts.map +1 -1
  31. package/dist/checks/no-brand-overrides.js +13 -18
  32. package/dist/checks/no-brand-overrides.js.map +1 -1
  33. package/dist/checks/no-env-production.d.ts +16 -0
  34. package/dist/checks/no-env-production.d.ts.map +1 -0
  35. package/dist/checks/no-env-production.js +33 -0
  36. package/dist/checks/no-env-production.js.map +1 -0
  37. package/dist/checks/no-placeholders.d.ts +2 -1
  38. package/dist/checks/no-placeholders.d.ts.map +1 -1
  39. package/dist/checks/no-placeholders.js +13 -24
  40. package/dist/checks/no-placeholders.js.map +1 -1
  41. package/dist/checks/no-scroll.d.ts +2 -1
  42. package/dist/checks/no-scroll.d.ts.map +1 -1
  43. package/dist/checks/no-scroll.js +12 -44
  44. package/dist/checks/no-scroll.js.map +1 -1
  45. package/dist/checks/no-tracking.d.ts +2 -1
  46. package/dist/checks/no-tracking.d.ts.map +1 -1
  47. package/dist/checks/no-tracking.js +12 -8
  48. package/dist/checks/no-tracking.js.map +1 -1
  49. package/dist/checks/pwa-meta.d.ts +19 -0
  50. package/dist/checks/pwa-meta.d.ts.map +1 -0
  51. package/dist/checks/pwa-meta.js +38 -0
  52. package/dist/checks/pwa-meta.js.map +1 -0
  53. package/dist/checks/store-link.d.ts +17 -0
  54. package/dist/checks/store-link.d.ts.map +1 -0
  55. package/dist/checks/store-link.js +36 -0
  56. package/dist/checks/store-link.js.map +1 -0
  57. package/dist/checks/unsafe-vh.d.ts +2 -1
  58. package/dist/checks/unsafe-vh.d.ts.map +1 -1
  59. package/dist/checks/unsafe-vh.js +10 -12
  60. package/dist/checks/unsafe-vh.js.map +1 -1
  61. package/dist/checks/viewport-support.d.ts +2 -1
  62. package/dist/checks/viewport-support.d.ts.map +1 -1
  63. package/dist/checks/viewport-support.js +11 -6
  64. package/dist/checks/viewport-support.js.map +1 -1
  65. package/dist/index.d.ts +25 -9
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +45 -18
  68. package/dist/index.js.map +1 -1
  69. package/dist/lib/file-source.d.ts +52 -0
  70. package/dist/lib/file-source.d.ts.map +1 -0
  71. package/dist/lib/file-source.js +148 -0
  72. package/dist/lib/file-source.js.map +1 -0
  73. package/dist/lib/gzip.d.ts +8 -0
  74. package/dist/lib/gzip.d.ts.map +1 -0
  75. package/dist/lib/gzip.js +26 -0
  76. package/dist/lib/gzip.js.map +1 -0
  77. package/dist/lib/project-type.d.ts +18 -0
  78. package/dist/lib/project-type.d.ts.map +1 -0
  79. package/dist/lib/project-type.js +34 -0
  80. package/dist/lib/project-type.js.map +1 -0
  81. package/dist/live/index.d.ts.map +1 -1
  82. package/dist/live/index.js +9 -7
  83. package/dist/live/index.js.map +1 -1
  84. package/package.json +1 -1
  85. package/LICENSE +0 -21
  86. package/dist/checks/brand-fonts.test.d.ts +0 -2
  87. package/dist/checks/brand-fonts.test.d.ts.map +0 -1
  88. package/dist/checks/brand-fonts.test.js +0 -45
  89. package/dist/checks/brand-fonts.test.js.map +0 -1
  90. package/dist/checks/bundle-size.test.d.ts +0 -2
  91. package/dist/checks/bundle-size.test.d.ts.map +0 -1
  92. package/dist/checks/bundle-size.test.js +0 -57
  93. package/dist/checks/bundle-size.test.js.map +0 -1
  94. package/dist/checks/manifest.test.d.ts +0 -2
  95. package/dist/checks/manifest.test.d.ts.map +0 -1
  96. package/dist/checks/manifest.test.js +0 -53
  97. package/dist/checks/manifest.test.js.map +0 -1
  98. package/dist/checks/no-brand-overrides.test.d.ts +0 -2
  99. package/dist/checks/no-brand-overrides.test.d.ts.map +0 -1
  100. package/dist/checks/no-brand-overrides.test.js +0 -102
  101. package/dist/checks/no-brand-overrides.test.js.map +0 -1
  102. package/dist/checks/no-placeholders.test.d.ts +0 -2
  103. package/dist/checks/no-placeholders.test.d.ts.map +0 -1
  104. package/dist/checks/no-placeholders.test.js +0 -46
  105. package/dist/checks/no-placeholders.test.js.map +0 -1
  106. package/dist/checks/no-scroll.test.d.ts +0 -2
  107. package/dist/checks/no-scroll.test.d.ts.map +0 -1
  108. package/dist/checks/no-scroll.test.js +0 -105
  109. package/dist/checks/no-scroll.test.js.map +0 -1
  110. package/dist/checks/no-tracking.test.d.ts +0 -2
  111. package/dist/checks/no-tracking.test.d.ts.map +0 -1
  112. package/dist/checks/no-tracking.test.js +0 -42
  113. package/dist/checks/no-tracking.test.js.map +0 -1
  114. package/dist/checks/unsafe-vh.test.d.ts +0 -2
  115. package/dist/checks/unsafe-vh.test.d.ts.map +0 -1
  116. package/dist/checks/unsafe-vh.test.js +0 -115
  117. package/dist/checks/unsafe-vh.test.js.map +0 -1
  118. package/dist/checks/viewport-support.test.d.ts +0 -2
  119. package/dist/checks/viewport-support.test.d.ts.map +0 -1
  120. package/dist/checks/viewport-support.test.js +0 -63
  121. package/dist/checks/viewport-support.test.js.map +0 -1
  122. package/dist/lib/walk.d.ts +0 -6
  123. package/dist/lib/walk.d.ts.map +0 -1
  124. package/dist/lib/walk.js +0 -35
  125. package/dist/lib/walk.js.map +0 -1
  126. package/dist/live/index.test.d.ts +0 -2
  127. package/dist/live/index.test.d.ts.map +0 -1
  128. package/dist/live/index.test.js +0 -259
  129. 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,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
+ * `@freeappstore/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;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA2B5E"}
@@ -0,0 +1,48 @@
1
+ import { isGameProject } from '../lib/project-type.js';
2
+ /**
3
+ * Apps on FreeAppStore should respect the user's system colour-scheme
4
+ * preference — dark interfaces are the default for many users on iOS
5
+ * and recent Android. Returns `warn` (not `fail`) if no signal is
6
+ * found because some apps are inherently visual (drawing canvas,
7
+ * camera viewfinder) and don't have a colour scheme to switch.
8
+ *
9
+ * Accepted signals (any one passes):
10
+ * - `prefers-color-scheme` media query in CSS or JSX
11
+ * - `data-theme` attribute (manual toggle)
12
+ * - `color-scheme` CSS property
13
+ *
14
+ * Games are exempt — game UIs are full-bleed and pick their own
15
+ * palette, so the system preference rarely applies. Skipped if
16
+ * `@freeappstore/games` is detected.
17
+ */
18
+ export async function checkDarkMode(source) {
19
+ if (await isGameProject(source)) {
20
+ return {
21
+ name: 'Dark mode support',
22
+ status: 'pass',
23
+ detail: 'game project — check skipped',
24
+ };
25
+ }
26
+ for await (const path of source.list()) {
27
+ if (!path.startsWith('web/src/'))
28
+ continue;
29
+ if (!/\.(?:css|scss|tsx?|jsx?|html)$/i.test(path))
30
+ continue;
31
+ const content = await source.read(path);
32
+ if (!content)
33
+ continue;
34
+ if (/prefers-color-scheme|data-theme|color-scheme\s*:/i.test(content)) {
35
+ return { name: 'Dark mode support', status: 'pass', detail: `signal in ${path}` };
36
+ }
37
+ }
38
+ return {
39
+ name: 'Dark mode support',
40
+ status: 'warn',
41
+ detail: 'no prefers-color-scheme / data-theme / color-scheme signal in web/src/',
42
+ suggestions: [
43
+ 'Add a `@media (prefers-color-scheme: dark) { ... }` block to your theme CSS, or',
44
+ 'Use `color-scheme: light dark;` to opt the page into native scrollbars / form controls.',
45
+ ],
46
+ };
47
+ }
48
+ //# 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;AAGvD;;;;;;;;;;;;;;;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,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,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;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA8B5E"}
@@ -0,0 +1,46 @@
1
+ const HTML_PATH = 'web/index.html';
2
+ /**
3
+ * Verifies `web/index.html` declares the basics: a `lang` attribute, a
4
+ * viewport meta, and a non-empty `<title>`. These three together cover:
5
+ *
6
+ * - lang → screen readers + auto-translate work correctly
7
+ * - viewport → mobile rendering is sized to device, not desktop default
8
+ * - title → the only thing visible in tabs, history, search results
9
+ *
10
+ * All three are baked into the canonical template — this check exists
11
+ * to catch creators who edited index.html and accidentally stripped
12
+ * something they needed.
13
+ */
14
+ export async function checkHtmlMeta(source) {
15
+ const html = await source.read(HTML_PATH);
16
+ if (html === null) {
17
+ return {
18
+ name: 'HTML meta tags',
19
+ status: 'fail',
20
+ detail: `${HTML_PATH} not found`,
21
+ };
22
+ }
23
+ const missing = [];
24
+ if (!/<html[^>]*\blang\s*=/i.test(html))
25
+ missing.push('lang attribute on <html>');
26
+ if (!/<meta[^>]*\bname\s*=\s*["']viewport["']/i.test(html))
27
+ missing.push('viewport meta');
28
+ // Extract the <title> body (multiline-safe), trim, and require it
29
+ // be non-empty. Avoids rejecting valid titles like `<title> Hello</title>`
30
+ // where the body has leading whitespace.
31
+ const titleMatch = html.match(/<title>([\s\S]*?)<\/title>/i);
32
+ if (!titleMatch || titleMatch[1].trim() === '')
33
+ missing.push('non-empty <title>');
34
+ if (missing.length === 0) {
35
+ return { name: 'HTML meta tags', status: 'pass', detail: 'lang + viewport + title present' };
36
+ }
37
+ return {
38
+ name: 'HTML meta tags',
39
+ status: 'fail',
40
+ detail: `missing: ${missing.join(', ')}`,
41
+ suggestions: [
42
+ 'Restore the canonical <head>: `<html lang="en">`, `<meta name="viewport" content="width=device-width, initial-scale=1" />`, `<title>App name</title>`.',
43
+ ],
44
+ };
45
+ }
46
+ //# sourceMappingURL=html-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-meta.js","sourceRoot":"","sources":["../../src/checks/html-meta.ts"],"names":[],"mappings":"AAGA,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAEnC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAkB;IACpD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO;YACL,IAAI,EAAE,gBAAgB;YACtB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,SAAS,YAAY;SACjC,CAAC;IACJ,CAAC;IAED,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,CA0B9E"}
@@ -0,0 +1,38 @@
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 = ['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'license', 'license.md', 'license.txt'];
12
+ for (const path of candidates) {
13
+ const content = await source.read(path);
14
+ if (content === null)
15
+ continue;
16
+ if (/\bmit\b/i.test(content)) {
17
+ return { name: 'MIT License', status: 'pass', detail: path };
18
+ }
19
+ return {
20
+ name: 'MIT License',
21
+ status: 'fail',
22
+ detail: `${path} exists but does not mention MIT`,
23
+ suggestions: [
24
+ 'Replace LICENSE with the standard MIT license text from https://opensource.org/license/mit',
25
+ ],
26
+ };
27
+ }
28
+ return {
29
+ name: 'MIT License',
30
+ status: 'fail',
31
+ detail: 'no LICENSE file at repo root',
32
+ suggestions: [
33
+ 'Add a LICENSE file with the MIT license text. The platform requires MIT for publication.',
34
+ 'Template: https://github.com/freeappstore-online/template-standalone/blob/main/LICENSE',
35
+ ],
36
+ };
37
+ }
38
+ //# 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,CAAC,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;IACpG,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"}
@@ -1,21 +1,16 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { join } from 'node:path';
1
+ const MANIFEST_PATH = 'web/public/manifest.json';
3
2
  /**
4
3
  * Verifies the PWA manifest exists at the expected location and parses
5
4
  * to JSON. Apps without a manifest can't be "Add to Home Screen"-d on
6
5
  * mobile; required by the platform.
7
6
  */
8
- export async function checkManifest(repoDir) {
9
- const path = join(repoDir, 'web', 'public', 'manifest.json');
10
- let raw;
11
- try {
12
- raw = await readFile(path, 'utf8');
13
- }
14
- catch {
7
+ export async function checkManifest(source) {
8
+ const raw = await source.read(MANIFEST_PATH);
9
+ if (raw === null) {
15
10
  return {
16
11
  name: 'PWA manifest',
17
12
  status: 'fail',
18
- detail: 'web/public/manifest.json missing',
13
+ detail: `${MANIFEST_PATH} missing`,
19
14
  suggestions: [
20
15
  'Add a manifest.json with at least name, short_name, start_url, display, icons.',
21
16
  'See template-standalone for a working example.',
@@ -30,7 +25,7 @@ export async function checkManifest(repoDir) {
30
25
  return {
31
26
  name: 'PWA manifest',
32
27
  status: 'fail',
33
- detail: `web/public/manifest.json is not valid JSON`,
28
+ detail: `${MANIFEST_PATH} is not valid JSON`,
34
29
  };
35
30
  }
36
31
  const required = ['name', 'short_name', 'start_url', 'display'];
@@ -43,6 +38,6 @@ export async function checkManifest(repoDir) {
43
38
  suggestions: [`Add the ${missing.join(', ')} field(s) to manifest.json so installs work.`],
44
39
  };
45
40
  }
46
- return { name: 'PWA manifest', status: 'pass', detail: 'web/public/manifest.json' };
41
+ return { name: 'PWA manifest', status: 'pass', detail: MANIFEST_PATH };
47
42
  }
48
43
  //# sourceMappingURL=manifest.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/checks/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAe;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;IAC7D,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,kCAAkC;YAC1C,WAAW,EAAE;gBACX,gFAAgF;gBAChF,gDAAgD;aACjD;SACF,CAAC;IACJ,CAAC;IAED,IAAI,MAAwF,CAAC;IAC7F,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,4CAA4C;SACrD,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,CAAU,CAAC;IACzE,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC1F,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,mBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAC/C,WAAW,EAAE,CAAC,WAAW,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,8CAA8C,CAAC;SAC3F,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC;AACtF,CAAC"}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/checks/manifest.ts"],"names":[],"mappings":"AAGA,MAAM,aAAa,GAAG,0BAA0B,CAAC;AAEjD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAkB;IACpD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC7C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,aAAa,UAAU;YAClC,WAAW,EAAE;gBACX,gFAAgF;gBAChF,gDAAgD;aACjD;SACF,CAAC;IACJ,CAAC;IAED,IAAI,MAAwF,CAAC;IAC7F,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,aAAa,oBAAoB;SAC7C,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,CAAU,CAAC;IACzE,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC1F,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,mBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAC/C,WAAW,EAAE,CAAC,WAAW,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,8CAA8C,CAAC;SAC3F,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;AACzE,CAAC"}
@@ -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
  * Apps and games on the platform must look consistent with the storefront
@@ -17,7 +18,7 @@ import type { CheckResult } from '../types.js';
17
18
  * red, etc.). We don't try to gate every hex literal — too noisy and
18
19
  * too many false positives.
19
20
  */
20
- export declare function checkNoBrandOverrides(repoDir: string): Promise<CheckResult>;
21
+ export declare function checkNoBrandOverrides(source: FileSource): Promise<CheckResult>;
21
22
  export declare function scanContent(filename: string, content: string, opts?: {
22
23
  skipVarRedefs?: boolean;
23
24
  }): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"no-brand-overrides.d.ts","sourceRoot":"","sources":["../../src/checks/no-brand-overrides.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAwCjF;AA2DD,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACrC,MAAM,EAAE,CAyCV"}
1
+ {"version":3,"file":"no-brand-overrides.d.ts","sourceRoot":"","sources":["../../src/checks/no-brand-overrides.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAuCpF;AA2DD,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACrC,MAAM,EAAE,CAmCV"}