@freegamestore/compliance 0.7.0 → 0.8.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 (87) hide show
  1. package/README.md +1 -1
  2. package/dist/checks/deploy-workflow.d.ts +16 -0
  3. package/dist/checks/deploy-workflow.d.ts.map +1 -0
  4. package/dist/checks/deploy-workflow.js +33 -0
  5. package/dist/checks/deploy-workflow.js.map +1 -0
  6. package/dist/checks/game-naming.d.ts +4 -0
  7. package/dist/checks/game-naming.d.ts.map +1 -0
  8. package/dist/checks/game-naming.js +98 -0
  9. package/dist/checks/game-naming.js.map +1 -0
  10. package/dist/checks/gitignore-complete.d.ts +4 -0
  11. package/dist/checks/gitignore-complete.d.ts.map +1 -0
  12. package/dist/checks/gitignore-complete.js +51 -0
  13. package/dist/checks/gitignore-complete.js.map +1 -0
  14. package/dist/checks/no-any-types.d.ts +4 -0
  15. package/dist/checks/no-any-types.d.ts.map +1 -0
  16. package/dist/checks/no-any-types.js +72 -0
  17. package/dist/checks/no-any-types.js.map +1 -0
  18. package/dist/checks/no-console-log.d.ts +4 -0
  19. package/dist/checks/no-console-log.d.ts.map +1 -0
  20. package/dist/checks/no-console-log.js +63 -0
  21. package/dist/checks/no-console-log.js.map +1 -0
  22. package/dist/checks/no-cookies.d.ts +13 -0
  23. package/dist/checks/no-cookies.d.ts.map +1 -0
  24. package/dist/checks/no-cookies.js +64 -0
  25. package/dist/checks/no-cookies.js.map +1 -0
  26. package/dist/checks/no-excessive-inline-styles.d.ts +4 -0
  27. package/dist/checks/no-excessive-inline-styles.d.ts.map +1 -0
  28. package/dist/checks/no-excessive-inline-styles.js +82 -0
  29. package/dist/checks/no-excessive-inline-styles.js.map +1 -0
  30. package/dist/checks/no-external-fetch.d.ts +4 -0
  31. package/dist/checks/no-external-fetch.d.ts.map +1 -0
  32. package/dist/checks/no-external-fetch.js +102 -0
  33. package/dist/checks/no-external-fetch.js.map +1 -0
  34. package/dist/checks/no-external-scripts.d.ts +16 -0
  35. package/dist/checks/no-external-scripts.d.ts.map +1 -0
  36. package/dist/checks/no-external-scripts.js +91 -0
  37. package/dist/checks/no-external-scripts.js.map +1 -0
  38. package/dist/checks/no-hardcoded-colors.d.ts +4 -0
  39. package/dist/checks/no-hardcoded-colors.d.ts.map +1 -0
  40. package/dist/checks/no-hardcoded-colors.js +79 -0
  41. package/dist/checks/no-hardcoded-colors.js.map +1 -0
  42. package/dist/checks/no-payment-sdk.d.ts +13 -0
  43. package/dist/checks/no-payment-sdk.d.ts.map +1 -0
  44. package/dist/checks/no-payment-sdk.js +118 -0
  45. package/dist/checks/no-payment-sdk.js.map +1 -0
  46. package/dist/checks/no-scroll.js +1 -1
  47. package/dist/checks/no-scroll.js.map +1 -1
  48. package/dist/checks/no-tracking.js +6 -6
  49. package/dist/checks/no-tracking.js.map +1 -1
  50. package/dist/checks/pwa-icons.d.ts +15 -0
  51. package/dist/checks/pwa-icons.d.ts.map +1 -0
  52. package/dist/checks/pwa-icons.js +38 -0
  53. package/dist/checks/pwa-icons.js.map +1 -0
  54. package/dist/checks/react-strict-mode.d.ts +4 -0
  55. package/dist/checks/react-strict-mode.d.ts.map +1 -0
  56. package/dist/checks/react-strict-mode.js +43 -0
  57. package/dist/checks/react-strict-mode.js.map +1 -0
  58. package/dist/checks/sdk-version.d.ts +4 -0
  59. package/dist/checks/sdk-version.d.ts.map +1 -0
  60. package/dist/checks/sdk-version.js +80 -0
  61. package/dist/checks/sdk-version.js.map +1 -0
  62. package/dist/checks/tech-versions.d.ts +4 -0
  63. package/dist/checks/tech-versions.d.ts.map +1 -0
  64. package/dist/checks/tech-versions.js +100 -0
  65. package/dist/checks/tech-versions.js.map +1 -0
  66. package/dist/checks/typescript-strict.d.ts +14 -0
  67. package/dist/checks/typescript-strict.d.ts.map +1 -0
  68. package/dist/checks/typescript-strict.js +155 -0
  69. package/dist/checks/typescript-strict.js.map +1 -0
  70. package/dist/checks/uses-game-sdk.d.ts +16 -0
  71. package/dist/checks/uses-game-sdk.d.ts.map +1 -0
  72. package/dist/checks/uses-game-sdk.js +82 -0
  73. package/dist/checks/uses-game-sdk.js.map +1 -0
  74. package/dist/checks/uses-localstorage.d.ts +14 -0
  75. package/dist/checks/uses-localstorage.d.ts.map +1 -0
  76. package/dist/checks/uses-localstorage.js +45 -0
  77. package/dist/checks/uses-localstorage.js.map +1 -0
  78. package/dist/index.d.ts +19 -1
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +44 -2
  81. package/dist/index.js.map +1 -1
  82. package/dist/lib/strip.js +4 -3
  83. package/dist/lib/strip.js.map +1 -1
  84. package/dist/live/index.d.ts.map +1 -1
  85. package/dist/live/index.js +9 -4
  86. package/dist/live/index.js.map +1 -1
  87. package/package.json +1 -1
package/README.md CHANGED
@@ -17,7 +17,7 @@ for (const r of results) {
17
17
  | Name | What it checks | Status on fail |
18
18
  |---|---|---|
19
19
  | `No template placeholders` | No file still contains `APPNAME` | **fail** |
20
- | `No tracking SDKs` | No reference to google-analytics / gtag / amplitude / mixpanel / segment / hotjar / plausible / posthog | **fail** |
20
+ | `Analytics routed through platform loader` | No direct install of google-analytics / gtag / amplitude / mixpanel / segment / hotjar / plausible / posthog — analytics must flow through `api.freegamestore.online/v1/analytics.js?app=<id>` | **fail** |
21
21
  | `Brand fonts present` | Manrope + Fraunces referenced in CSS or HTML | **fail** |
22
22
  | `PWA manifest` | `web/public/manifest.json` exists and has name / short_name / start_url / display | warn |
23
23
  | `Bundle size` | Largest JS in `web/dist/assets/` is ≤ 300 KB gzipped | **fail** if too big; warn if not built yet |
@@ -0,0 +1,16 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ /**
4
+ * Games auto-deploy to R2 via GitHub Actions on push to main. Without
5
+ * `.github/workflows/deploy.yml`, pushes won't trigger a deploy and
6
+ * the game stays stale on the storefront.
7
+ *
8
+ * Simple existence check — the deploy workflow template is provided
9
+ * by `fgs create` and rarely needs customisation.
10
+ *
11
+ * WARN level — some games might use an alternative deploy mechanism
12
+ * (e.g. deploy.yaml, a different workflow name, or manual deploys
13
+ * during early development).
14
+ */
15
+ export declare function checkDeployWorkflow(source: FileSource): Promise<CheckResult>;
16
+ //# sourceMappingURL=deploy-workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy-workflow.d.ts","sourceRoot":"","sources":["../../src/checks/deploy-workflow.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,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAoBlF"}
@@ -0,0 +1,33 @@
1
+ const DEPLOY_YML = '.github/workflows/deploy.yml';
2
+ /**
3
+ * Games auto-deploy to R2 via GitHub Actions on push to main. Without
4
+ * `.github/workflows/deploy.yml`, pushes won't trigger a deploy and
5
+ * the game stays stale on the storefront.
6
+ *
7
+ * Simple existence check — the deploy workflow template is provided
8
+ * by `fgs create` and rarely needs customisation.
9
+ *
10
+ * WARN level — some games might use an alternative deploy mechanism
11
+ * (e.g. deploy.yaml, a different workflow name, or manual deploys
12
+ * during early development).
13
+ */
14
+ export async function checkDeployWorkflow(source) {
15
+ const content = await source.read(DEPLOY_YML);
16
+ if (content !== null) {
17
+ return {
18
+ name: 'Deploy workflow',
19
+ status: 'pass',
20
+ detail: `${DEPLOY_YML} exists`,
21
+ };
22
+ }
23
+ return {
24
+ name: 'Deploy workflow',
25
+ status: 'warn',
26
+ detail: `${DEPLOY_YML} not found`,
27
+ suggestions: [
28
+ 'Add a deploy workflow so pushes to main auto-deploy to the storefront.',
29
+ 'Run `fgs create` to scaffold a game with the standard deploy workflow, or copy one from an existing game repo.',
30
+ ],
31
+ };
32
+ }
33
+ //# sourceMappingURL=deploy-workflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy-workflow.js","sourceRoot":"","sources":["../../src/checks/deploy-workflow.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,GAAG,8BAA8B,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAkB;IAC1D,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAE9C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO;YACL,IAAI,EAAE,iBAAiB;YACvB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,UAAU,SAAS;SAC/B,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,GAAG,UAAU,YAAY;QACjC,WAAW,EAAE;YACX,wEAAwE;YACxE,gHAAgH;SACjH;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ export declare function checkGameNaming(source: FileSource): Promise<CheckResult>;
4
+ //# sourceMappingURL=game-naming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-naming.d.ts","sourceRoot":"","sources":["../../src/checks/game-naming.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA2D/C,wBAAsB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAuD9E"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Validates game naming conventions from the platform guidelines:
3
+ *
4
+ * - Package name should be lowercase
5
+ * - Should not start with "free" or "pro" (reserved platform prefixes)
6
+ * - Should not contain spaces or special characters (beyond hyphens)
7
+ * - If CLAUDE.md exists, the subdomain should match the expected
8
+ * pattern (`gamename.freegamestore.online`)
9
+ *
10
+ * Checks both root `package.json` and `web/package.json`. WARN level —
11
+ * a bad name won't break the build, but it will cause issues at publish
12
+ * time when the admin Worker derives the subdomain and repo name.
13
+ */
14
+ const RESERVED_PREFIXES = ['free', 'pro'];
15
+ function checkName(name, source) {
16
+ const issues = [];
17
+ if (name !== name.toLowerCase()) {
18
+ issues.push({
19
+ field: source,
20
+ value: name,
21
+ problem: 'package name should be lowercase',
22
+ });
23
+ }
24
+ for (const prefix of RESERVED_PREFIXES) {
25
+ if (name.toLowerCase().startsWith(prefix)) {
26
+ issues.push({
27
+ field: source,
28
+ value: name,
29
+ problem: `"${prefix}" is a reserved platform prefix`,
30
+ });
31
+ break;
32
+ }
33
+ }
34
+ // Allow lowercase letters, digits, hyphens, and npm scoped names (@scope/name).
35
+ // Reject spaces and other special characters.
36
+ if (/[^a-z0-9\-@/.]/.test(name)) {
37
+ issues.push({
38
+ field: source,
39
+ value: name,
40
+ problem: 'contains spaces or special characters',
41
+ });
42
+ }
43
+ return issues;
44
+ }
45
+ export async function checkGameNaming(source) {
46
+ const issues = [];
47
+ // Only check web/package.json for naming conventions. The root
48
+ // package.json name often follows CF Pages project naming (e.g.
49
+ // "freechessapp") which legitimately uses the "free" prefix — that's
50
+ // the platform's naming scheme, not the game developer's choice.
51
+ const webRaw = await source.read('web/package.json');
52
+ if (webRaw) {
53
+ try {
54
+ const webPkg = JSON.parse(webRaw);
55
+ if (typeof webPkg.name === 'string') {
56
+ issues.push(...checkName(webPkg.name, 'web/package.json name'));
57
+ }
58
+ }
59
+ catch {
60
+ // invalid JSON — other checks will flag this
61
+ }
62
+ }
63
+ // Check CLAUDE.md for subdomain pattern consistency
64
+ const claudeMd = await source.read('CLAUDE.md');
65
+ if (claudeMd) {
66
+ const subdomainMatch = claudeMd.match(/([a-z0-9-]+)\.freegamestore\.online/);
67
+ if (subdomainMatch) {
68
+ const subdomain = subdomainMatch[1];
69
+ // The subdomain should be a clean slug — no underscores, no uppercase
70
+ if (/[^a-z0-9-]/.test(subdomain)) {
71
+ issues.push({
72
+ field: 'CLAUDE.md subdomain',
73
+ value: `${subdomain}.freegamestore.online`,
74
+ problem: 'subdomain should only contain lowercase letters, digits, and hyphens',
75
+ });
76
+ }
77
+ }
78
+ }
79
+ if (issues.length === 0) {
80
+ return {
81
+ name: 'Game naming conventions',
82
+ status: 'pass',
83
+ detail: 'package names follow platform naming conventions',
84
+ };
85
+ }
86
+ return {
87
+ name: 'Game naming conventions',
88
+ status: 'warn',
89
+ detail: issues
90
+ .map((i) => `${i.field}: "${i.value}" — ${i.problem}`)
91
+ .join('; '),
92
+ suggestions: [
93
+ 'Package names should be lowercase, hyphen-separated, without reserved prefixes (free*, pro*).',
94
+ 'The publish flow derives the subdomain and GitHub repo name from the package name — clean names prevent issues.',
95
+ ],
96
+ };
97
+ }
98
+ //# sourceMappingURL=game-naming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"game-naming.js","sourceRoot":"","sources":["../../src/checks/game-naming.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 checkGitignoreComplete(source: FileSource): Promise<CheckResult>;
4
+ //# sourceMappingURL=gitignore-complete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitignore-complete.d.ts","sourceRoot":"","sources":["../../src/checks/gitignore-complete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAoC/C,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAwCrF"}
@@ -0,0 +1,51 @@
1
+ const REQUIRED = [
2
+ {
3
+ name: 'node_modules',
4
+ test: (line) => /^node_modules\/?$/.test(line) || line === '**/node_modules',
5
+ },
6
+ {
7
+ name: 'dist',
8
+ test: (line) => /^dist\/?$/.test(line) || /^web\/dist\/?$/.test(line) || line === '**/dist',
9
+ },
10
+ {
11
+ name: '.env',
12
+ test: (line) => /^\.env/.test(line),
13
+ },
14
+ ];
15
+ export async function checkGitignoreComplete(source) {
16
+ const raw = await source.read('.gitignore');
17
+ if (raw === null) {
18
+ return {
19
+ name: 'Gitignore complete',
20
+ status: 'warn',
21
+ detail: 'no .gitignore file at repo root',
22
+ suggestions: [
23
+ 'Add a .gitignore that covers at least: node_modules, dist, .env.',
24
+ 'Run `fgs create` to scaffold a game with a standard .gitignore.',
25
+ ],
26
+ };
27
+ }
28
+ // Parse non-empty, non-comment lines.
29
+ const lines = raw
30
+ .split('\n')
31
+ .map((l) => l.trim())
32
+ .filter((l) => l.length > 0 && !l.startsWith('#'));
33
+ const missing = REQUIRED.filter((req) => !lines.some((line) => req.test(line)));
34
+ if (missing.length === 0) {
35
+ return {
36
+ name: 'Gitignore complete',
37
+ status: 'pass',
38
+ detail: '.gitignore covers node_modules, dist, and .env',
39
+ };
40
+ }
41
+ return {
42
+ name: 'Gitignore complete',
43
+ status: 'warn',
44
+ detail: `.gitignore is missing patterns for: ${missing.map((m) => m.name).join(', ')}`,
45
+ suggestions: [
46
+ `Add the missing patterns to .gitignore: ${missing.map((m) => m.name).join(', ')}.`,
47
+ 'These prevent build artefacts, dependencies, or secrets from being committed.',
48
+ ],
49
+ };
50
+ }
51
+ //# sourceMappingURL=gitignore-complete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitignore-complete.js","sourceRoot":"","sources":["../../src/checks/gitignore-complete.ts"],"names":[],"mappings":"AAqBA,MAAM,QAAQ,GAAsB;IAClC;QACE,IAAI,EAAE,cAAc;QACpB,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,iBAAiB;KAC7E;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CACb,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,SAAS;KAC9E;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;KACpC;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAAkB;IAC7D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAE5C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,OAAO;YACL,IAAI,EAAE,oBAAoB;YAC1B,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,iCAAiC;YACzC,WAAW,EAAE;gBACX,kEAAkE;gBAClE,iEAAiE;aAClE;SACF,CAAC;IACJ,CAAC;IAED,sCAAsC;IACtC,MAAM,KAAK,GAAG,GAAG;SACd,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAErD,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEhF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,IAAI,EAAE,oBAAoB;YAC1B,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,gDAAgD;SACzD,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,uCAAuC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACtF,WAAW,EAAE;YACX,2CAA2C,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACnF,+EAA+E;SAChF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ export declare function checkNoAnyTypes(source: FileSource): Promise<CheckResult>;
4
+ //# sourceMappingURL=no-any-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-any-types.d.ts","sourceRoot":"","sources":["../../src/checks/no-any-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA+B/C,wBAAsB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA4C9E"}
@@ -0,0 +1,72 @@
1
+ import { stripCommentsAndStrings } from '../lib/strip.js';
2
+ /**
3
+ * Flags explicit `any` type annotations in game source files. Both
4
+ * `: any` (parameter/return type) and `as any` (type assertion) are
5
+ * counted — both are escape hatches that weaken type safety.
6
+ *
7
+ * This is a WARN, not a hard fail. Some third-party lib compat genuinely
8
+ * requires `as any`, and the occasional `: any` parameter in an event
9
+ * handler is pragmatic. The goal is visibility: developers should know
10
+ * how many escape hatches they're carrying.
11
+ *
12
+ * Exclusions:
13
+ * - `web/src/test/` — test helpers often use loose typing
14
+ * - `web/src/types.ts` — type definition files may have legitimate `any`
15
+ */
16
+ const SCAN_EXTS = new Set(['.ts', '.tsx']);
17
+ function extOf(path) {
18
+ const dot = path.lastIndexOf('.');
19
+ const slash = path.lastIndexOf('/');
20
+ return dot > slash ? path.slice(dot).toLowerCase() : '';
21
+ }
22
+ function isExcluded(path) {
23
+ if (path.startsWith('web/src/test/'))
24
+ return true;
25
+ if (path === 'web/src/types.ts')
26
+ return true;
27
+ return false;
28
+ }
29
+ export async function checkNoAnyTypes(source) {
30
+ let totalCount = 0;
31
+ const hitFiles = [];
32
+ for await (const path of source.list()) {
33
+ if (!path.startsWith('web/src/'))
34
+ continue;
35
+ if (!SCAN_EXTS.has(extOf(path)))
36
+ continue;
37
+ if (isExcluded(path))
38
+ continue;
39
+ const raw = await source.read(path);
40
+ if (!raw)
41
+ continue;
42
+ // Strip comments AND strings — `: any` inside a string literal
43
+ // (e.g. a log message like 'cast to any') is not a real annotation.
44
+ const stripped = stripCommentsAndStrings(raw);
45
+ // Match `: any` (type annotation) and `as any` (type assertion).
46
+ // Word boundaries prevent matching `canary`, `anyString`, etc.
47
+ const matches = stripped.match(/(?::\s*any|as\s+any)\b/g);
48
+ if (matches && matches.length > 0) {
49
+ totalCount += matches.length;
50
+ hitFiles.push(path);
51
+ }
52
+ }
53
+ if (totalCount === 0) {
54
+ return {
55
+ name: 'No explicit any types',
56
+ status: 'pass',
57
+ detail: 'no `: any` or `as any` found in web/src/',
58
+ };
59
+ }
60
+ return {
61
+ name: 'No explicit any types',
62
+ status: 'warn',
63
+ detail: `${totalCount} \`any\` usage(s) across ${hitFiles.length} file(s): ${hitFiles
64
+ .slice(0, 5)
65
+ .join(', ')}${hitFiles.length > 5 ? '...' : ''}`,
66
+ suggestions: [
67
+ 'Replace `: any` with a specific type or `unknown` where the shape is genuinely unpredictable.',
68
+ '`as any` type assertions can often be replaced with `as unknown as TargetType` for safer narrowing.',
69
+ ],
70
+ };
71
+ }
72
+ //# sourceMappingURL=no-any-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-any-types.js","sourceRoot":"","sources":["../../src/checks/no-any-types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG1D;;;;;;;;;;;;;GAaG;AAEH,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAE3C,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;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAC;IAClD,IAAI,IAAI,KAAK,kBAAkB;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAkB;IACtD,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,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,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAAE,SAAS;QAC1C,IAAI,UAAU,CAAC,IAAI,CAAC;YAAE,SAAS;QAE/B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,SAAS;QAEnB,+DAA+D;QAC/D,oEAAoE;QACpE,MAAM,QAAQ,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;QAE9C,iEAAiE;QACjE,+DAA+D;QAC/D,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC1D,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,IAAI,EAAE,uBAAuB;YAC7B,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,0CAA0C;SACnD,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,uBAAuB;QAC7B,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,GAAG,UAAU,4BAA4B,QAAQ,CAAC,MAAM,aAAa,QAAQ;aAClF,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;aACX,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;QAClD,WAAW,EAAE;YACX,+FAA+F;YAC/F,qGAAqG;SACtG;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { FileSource } from '../lib/file-source.js';
2
+ import type { CheckResult } from '../types.js';
3
+ export declare function checkNoConsoleLog(source: FileSource): Promise<CheckResult>;
4
+ //# sourceMappingURL=no-console-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-console-log.d.ts","sourceRoot":"","sources":["../../src/checks/no-console-log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAyB/C,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA0ChF"}
@@ -0,0 +1,63 @@
1
+ import { stripCommentsAndStrings } from '../lib/strip.js';
2
+ /**
3
+ * Flags `console.log()` calls in game source files. Production games
4
+ * should not ship with debug logging — it clutters the browser console
5
+ * and can leak internal state to curious players.
6
+ *
7
+ * `console.error` and `console.warn` are deliberately excluded — those
8
+ * are legitimate runtime diagnostics that help in production debugging.
9
+ *
10
+ * WARN level. Not a hard fail — some games log performance stats or
11
+ * frame timing intentionally. The goal is visibility.
12
+ *
13
+ * Exclusions:
14
+ * - `web/src/test/` — test files may log freely
15
+ */
16
+ const SCAN_EXTS = new Set(['.ts', '.tsx']);
17
+ function extOf(path) {
18
+ const dot = path.lastIndexOf('.');
19
+ const slash = path.lastIndexOf('/');
20
+ return dot > slash ? path.slice(dot).toLowerCase() : '';
21
+ }
22
+ export async function checkNoConsoleLog(source) {
23
+ let totalCount = 0;
24
+ const hitFiles = [];
25
+ for await (const path of source.list()) {
26
+ if (!path.startsWith('web/src/'))
27
+ continue;
28
+ if (path.startsWith('web/src/test/'))
29
+ continue;
30
+ if (!SCAN_EXTS.has(extOf(path)))
31
+ continue;
32
+ const raw = await source.read(path);
33
+ if (!raw)
34
+ continue;
35
+ // Strip comments and strings so we don't flag console.log inside
36
+ // a comment or a string literal like 'use console.log for debug'.
37
+ const stripped = stripCommentsAndStrings(raw);
38
+ const matches = stripped.match(/\bconsole\.log\s*\(/g);
39
+ if (matches && matches.length > 0) {
40
+ totalCount += matches.length;
41
+ hitFiles.push(path);
42
+ }
43
+ }
44
+ if (totalCount === 0) {
45
+ return {
46
+ name: 'No console.log',
47
+ status: 'pass',
48
+ detail: 'no console.log() calls found in web/src/',
49
+ };
50
+ }
51
+ return {
52
+ name: 'No console.log',
53
+ status: 'warn',
54
+ detail: `${totalCount} console.log() call(s) across ${hitFiles.length} file(s): ${hitFiles
55
+ .slice(0, 5)
56
+ .join(', ')}${hitFiles.length > 5 ? '...' : ''}`,
57
+ suggestions: [
58
+ 'Remove debug logging before publishing. Use console.error or console.warn for runtime diagnostics that should stay.',
59
+ 'If the logging is intentional (e.g. performance stats), consider gating it behind a DEBUG flag.',
60
+ ],
61
+ };
62
+ }
63
+ //# sourceMappingURL=no-console-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-console-log.js","sourceRoot":"","sources":["../../src/checks/no-console-log.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG1D;;;;;;;;;;;;;GAaG;AAEH,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAE3C,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;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAkB;IACxD,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,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,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC;YAAE,SAAS;QAC/C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAAE,SAAS;QAE1C,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,SAAS;QAEnB,iEAAiE;QACjE,kEAAkE;QAClE,MAAM,QAAQ,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;QAE9C,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACvD,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,IAAI,EAAE,gBAAgB;YACtB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,0CAA0C;SACnD,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,GAAG,UAAU,iCAAiC,QAAQ,CAAC,MAAM,aAAa,QAAQ;aACvF,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;aACX,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;QAClD,WAAW,EAAE;YACX,qHAAqH;YACrH,iGAAiG;SAClG;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
+ * All player data must stay in localStorage (or the platform KV SDK).
5
+ * Cookies are not allowed — they leak data to servers on every request
6
+ * and conflict with the cookieless analytics mandate.
7
+ *
8
+ * Scans source files for `document.cookie`, js-cookie, cookie-parser,
9
+ * and `Cookies.set`/`Cookies.get` patterns. Comments are stripped
10
+ * before scanning to avoid false positives from documentation.
11
+ */
12
+ export declare function checkNoCookies(source: FileSource): Promise<CheckResult>;
13
+ //# sourceMappingURL=no-cookies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-cookies.d.ts","sourceRoot":"","sources":["../../src/checks/no-cookies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiB/C;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAmC7E"}
@@ -0,0 +1,64 @@
1
+ import { stripCommentsForExt } from '../lib/strip.js';
2
+ // Patterns that indicate cookie usage — either the native DOM API or
3
+ // popular helper libraries. Each pattern is SDK-specific enough to avoid
4
+ // false positives against bare English words.
5
+ const COOKIE_PATTERNS = [
6
+ { name: 'document.cookie', pattern: /\bdocument\s*\.\s*cookie\b/i },
7
+ { name: 'js-cookie', pattern: /from\s+['"]js-cookie['"]/i },
8
+ { name: 'js-cookie (require)', pattern: /require\(['"]js-cookie['"]\)/i },
9
+ { name: 'cookie-parser', pattern: /from\s+['"]cookie-parser['"]/i },
10
+ { name: 'cookie-parser (require)', pattern: /require\(['"]cookie-parser['"]\)/i },
11
+ { name: 'Cookies.set', pattern: /\bCookies\s*\.\s*set\s*\(/i },
12
+ { name: 'Cookies.get', pattern: /\bCookies\s*\.\s*get\s*\(/i },
13
+ ];
14
+ const SCAN_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.html']);
15
+ /**
16
+ * All player data must stay in localStorage (or the platform KV SDK).
17
+ * Cookies are not allowed — they leak data to servers on every request
18
+ * and conflict with the cookieless analytics mandate.
19
+ *
20
+ * Scans source files for `document.cookie`, js-cookie, cookie-parser,
21
+ * and `Cookies.set`/`Cookies.get` patterns. Comments are stripped
22
+ * before scanning to avoid false positives from documentation.
23
+ */
24
+ export async function checkNoCookies(source) {
25
+ const hits = [];
26
+ for await (const path of source.list()) {
27
+ const ext = extOf(path);
28
+ if (!SCAN_EXTS.has(ext))
29
+ continue;
30
+ const raw = await source.read(path);
31
+ if (!raw)
32
+ continue;
33
+ const content = stripCommentsForExt(raw, ext);
34
+ const matches = COOKIE_PATTERNS.filter((p) => p.pattern.test(content)).map((p) => p.name);
35
+ if (matches.length > 0) {
36
+ hits.push({ file: path, matches });
37
+ }
38
+ }
39
+ if (hits.length === 0) {
40
+ return {
41
+ name: 'No cookies',
42
+ status: 'pass',
43
+ detail: 'no cookie usage found — player data stays in localStorage',
44
+ };
45
+ }
46
+ return {
47
+ name: 'No cookies',
48
+ status: 'fail',
49
+ detail: `${hits.length} file(s) use cookies: ${hits
50
+ .slice(0, 3)
51
+ .map((h) => `${h.file} (${h.matches.join(', ')})`)
52
+ .join('; ')}${hits.length > 3 ? '...' : ''}`,
53
+ suggestions: [
54
+ 'Replace cookie usage with localStorage or the platform KV SDK (@freegamestore/games provides app.kv for per-user storage).',
55
+ 'Cookies leak data to servers on every request and conflict with the platform cookieless analytics mandate.',
56
+ ],
57
+ };
58
+ }
59
+ function extOf(path) {
60
+ const dot = path.lastIndexOf('.');
61
+ const slash = path.lastIndexOf('/');
62
+ return dot > slash ? path.slice(dot).toLowerCase() : '';
63
+ }
64
+ //# sourceMappingURL=no-cookies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-cookies.js","sourceRoot":"","sources":["../../src/checks/no-cookies.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 checkNoExcessiveInlineStyles(source: FileSource): Promise<CheckResult>;
4
+ //# sourceMappingURL=no-excessive-inline-styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-excessive-inline-styles.d.ts","sourceRoot":"","sources":["../../src/checks/no-excessive-inline-styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiC/C,wBAAsB,4BAA4B,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA2D3F"}
@@ -0,0 +1,82 @@
1
+ import { stripCommentsAndStrings } from '../lib/strip.js';
2
+ /**
3
+ * Flags excessive use of React inline style objects (`style={{ ... }}`)
4
+ * in TSX files under web/src/. Some inline styles are fine — dynamic
5
+ * values, canvas positioning, animation transforms. But heavy reliance
6
+ * on `style={{` means the game isn't leveraging Tailwind CSS or the
7
+ * design system, making theming and dark mode harder.
8
+ *
9
+ * Thresholds:
10
+ * - Any single file with > 15 inline style objects → warn
11
+ * - Total across all TSX files > 30 → warn
12
+ *
13
+ * Strips comments before scanning. Excludes test files.
14
+ *
15
+ * WARN level — guidelines, not a hard gate.
16
+ */
17
+ const PER_FILE_THRESHOLD = 15;
18
+ const TOTAL_THRESHOLD = 30;
19
+ const INLINE_STYLE_RE = /\bstyle\s*=\s*\{\{/g;
20
+ function extOf(path) {
21
+ const dot = path.lastIndexOf('.');
22
+ const slash = path.lastIndexOf('/');
23
+ return dot > slash ? path.slice(dot).toLowerCase() : '';
24
+ }
25
+ function isTestFile(path) {
26
+ return /\.(?:test|spec)\.[jt]sx?$/.test(path) || /(?:^|\/)(?:test|tests|__tests__)\//.test(path);
27
+ }
28
+ export async function checkNoExcessiveInlineStyles(source) {
29
+ let totalCount = 0;
30
+ const heavyFiles = [];
31
+ let fileCount = 0;
32
+ for await (const path of source.list()) {
33
+ if (!path.startsWith('web/src/'))
34
+ continue;
35
+ if (extOf(path) !== '.tsx')
36
+ continue;
37
+ if (isTestFile(path))
38
+ continue;
39
+ const raw = await source.read(path);
40
+ if (!raw)
41
+ continue;
42
+ const stripped = stripCommentsAndStrings(raw);
43
+ const matches = stripped.match(INLINE_STYLE_RE);
44
+ const count = matches ? matches.length : 0;
45
+ if (count > 0) {
46
+ totalCount += count;
47
+ fileCount++;
48
+ if (count > PER_FILE_THRESHOLD) {
49
+ heavyFiles.push({ file: path, count });
50
+ }
51
+ }
52
+ }
53
+ const perFileViolation = heavyFiles.length > 0;
54
+ const totalViolation = totalCount > TOTAL_THRESHOLD;
55
+ if (!perFileViolation && !totalViolation) {
56
+ return {
57
+ name: 'No excessive inline styles',
58
+ status: 'pass',
59
+ detail: `${totalCount} inline style(s) across ${fileCount} TSX file(s) (thresholds: ${PER_FILE_THRESHOLD}/file, ${TOTAL_THRESHOLD} total)`,
60
+ };
61
+ }
62
+ const parts = [];
63
+ if (totalViolation) {
64
+ parts.push(`${totalCount} total inline styles (threshold: ${TOTAL_THRESHOLD})`);
65
+ }
66
+ if (perFileViolation) {
67
+ parts.push(`${heavyFiles.length} file(s) exceed ${PER_FILE_THRESHOLD}/file: ${heavyFiles
68
+ .slice(0, 3)
69
+ .map((h) => `${h.file} (${h.count})`)
70
+ .join(', ')}${heavyFiles.length > 3 ? '...' : ''}`);
71
+ }
72
+ return {
73
+ name: 'No excessive inline styles',
74
+ status: 'warn',
75
+ detail: parts.join('; '),
76
+ suggestions: [
77
+ 'Replace inline style objects with Tailwind CSS utility classes where possible.',
78
+ 'Keep inline styles for truly dynamic values (e.g. computed positions, animation transforms).',
79
+ ],
80
+ };
81
+ }
82
+ //# sourceMappingURL=no-excessive-inline-styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-excessive-inline-styles.js","sourceRoot":"","sources":["../../src/checks/no-excessive-inline-styles.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 checkNoExternalFetch(source: FileSource): Promise<CheckResult>;
4
+ //# sourceMappingURL=no-external-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-external-fetch.d.ts","sourceRoot":"","sources":["../../src/checks/no-external-fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAmD/C,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA4DnF"}