@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.
- package/README.md +1 -1
- package/dist/checks/deploy-workflow.d.ts +16 -0
- package/dist/checks/deploy-workflow.d.ts.map +1 -0
- package/dist/checks/deploy-workflow.js +33 -0
- package/dist/checks/deploy-workflow.js.map +1 -0
- package/dist/checks/game-naming.d.ts +4 -0
- package/dist/checks/game-naming.d.ts.map +1 -0
- package/dist/checks/game-naming.js +98 -0
- package/dist/checks/game-naming.js.map +1 -0
- package/dist/checks/gitignore-complete.d.ts +4 -0
- package/dist/checks/gitignore-complete.d.ts.map +1 -0
- package/dist/checks/gitignore-complete.js +51 -0
- package/dist/checks/gitignore-complete.js.map +1 -0
- package/dist/checks/no-any-types.d.ts +4 -0
- package/dist/checks/no-any-types.d.ts.map +1 -0
- package/dist/checks/no-any-types.js +72 -0
- package/dist/checks/no-any-types.js.map +1 -0
- package/dist/checks/no-console-log.d.ts +4 -0
- package/dist/checks/no-console-log.d.ts.map +1 -0
- package/dist/checks/no-console-log.js +63 -0
- package/dist/checks/no-console-log.js.map +1 -0
- package/dist/checks/no-cookies.d.ts +13 -0
- package/dist/checks/no-cookies.d.ts.map +1 -0
- package/dist/checks/no-cookies.js +64 -0
- package/dist/checks/no-cookies.js.map +1 -0
- package/dist/checks/no-excessive-inline-styles.d.ts +4 -0
- package/dist/checks/no-excessive-inline-styles.d.ts.map +1 -0
- package/dist/checks/no-excessive-inline-styles.js +82 -0
- package/dist/checks/no-excessive-inline-styles.js.map +1 -0
- package/dist/checks/no-external-fetch.d.ts +4 -0
- package/dist/checks/no-external-fetch.d.ts.map +1 -0
- package/dist/checks/no-external-fetch.js +102 -0
- package/dist/checks/no-external-fetch.js.map +1 -0
- package/dist/checks/no-external-scripts.d.ts +16 -0
- package/dist/checks/no-external-scripts.d.ts.map +1 -0
- package/dist/checks/no-external-scripts.js +91 -0
- package/dist/checks/no-external-scripts.js.map +1 -0
- package/dist/checks/no-hardcoded-colors.d.ts +4 -0
- package/dist/checks/no-hardcoded-colors.d.ts.map +1 -0
- package/dist/checks/no-hardcoded-colors.js +79 -0
- package/dist/checks/no-hardcoded-colors.js.map +1 -0
- package/dist/checks/no-payment-sdk.d.ts +13 -0
- package/dist/checks/no-payment-sdk.d.ts.map +1 -0
- package/dist/checks/no-payment-sdk.js +118 -0
- package/dist/checks/no-payment-sdk.js.map +1 -0
- package/dist/checks/no-scroll.js +1 -1
- package/dist/checks/no-scroll.js.map +1 -1
- package/dist/checks/no-tracking.js +6 -6
- package/dist/checks/no-tracking.js.map +1 -1
- package/dist/checks/pwa-icons.d.ts +15 -0
- package/dist/checks/pwa-icons.d.ts.map +1 -0
- package/dist/checks/pwa-icons.js +38 -0
- package/dist/checks/pwa-icons.js.map +1 -0
- package/dist/checks/react-strict-mode.d.ts +4 -0
- package/dist/checks/react-strict-mode.d.ts.map +1 -0
- package/dist/checks/react-strict-mode.js +43 -0
- package/dist/checks/react-strict-mode.js.map +1 -0
- package/dist/checks/sdk-version.d.ts +4 -0
- package/dist/checks/sdk-version.d.ts.map +1 -0
- package/dist/checks/sdk-version.js +80 -0
- package/dist/checks/sdk-version.js.map +1 -0
- package/dist/checks/tech-versions.d.ts +4 -0
- package/dist/checks/tech-versions.d.ts.map +1 -0
- package/dist/checks/tech-versions.js +100 -0
- package/dist/checks/tech-versions.js.map +1 -0
- package/dist/checks/typescript-strict.d.ts +14 -0
- package/dist/checks/typescript-strict.d.ts.map +1 -0
- package/dist/checks/typescript-strict.js +155 -0
- package/dist/checks/typescript-strict.js.map +1 -0
- package/dist/checks/uses-game-sdk.d.ts +16 -0
- package/dist/checks/uses-game-sdk.d.ts.map +1 -0
- package/dist/checks/uses-game-sdk.js +82 -0
- package/dist/checks/uses-game-sdk.js.map +1 -0
- package/dist/checks/uses-localstorage.d.ts +14 -0
- package/dist/checks/uses-localstorage.d.ts.map +1 -0
- package/dist/checks/uses-localstorage.js +45 -0
- package/dist/checks/uses-localstorage.js.map +1 -0
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/strip.js +4 -3
- package/dist/lib/strip.js.map +1 -1
- package/dist/live/index.d.ts.map +1 -1
- package/dist/live/index.js +9 -4
- package/dist/live/index.js.map +1 -1
- 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
|
-
| `
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAGtD,qEAAqE;AACrE,yEAAyE;AACzE,8CAA8C;AAC9C,MAAM,eAAe,GAAwC;IAC3D,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,6BAA6B,EAAE;IACnE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,2BAA2B,EAAE;IAC3D,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,+BAA+B,EAAE;IACzE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,+BAA+B,EAAE;IACnE,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,mCAAmC,EAAE;IACjF,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,4BAA4B,EAAE;IAC9D,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,4BAA4B,EAAE;CAC/D,CAAC;AAEF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEnE;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAkB;IACrD,MAAM,IAAI,GAA0C,EAAE,CAAC;IAEvD,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAClC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1F,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,2DAA2D;SACpE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,yBAAyB,IAAI;aAChD,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;aACjD,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;QAC9C,WAAW,EAAE;YACX,4HAA4H;YAC5H,4GAA4G;SAC7G;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 @@
|
|
|
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 @@
|
|
|
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"}
|