@freeappstore/compliance 0.2.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.
- package/dist/checks/brand-fonts.d.ts +2 -1
- package/dist/checks/brand-fonts.d.ts.map +1 -1
- package/dist/checks/brand-fonts.js +11 -7
- package/dist/checks/brand-fonts.js.map +1 -1
- package/dist/checks/brand-tokens.d.ts +22 -0
- package/dist/checks/brand-tokens.d.ts.map +1 -0
- package/dist/checks/brand-tokens.js +53 -0
- package/dist/checks/brand-tokens.js.map +1 -0
- package/dist/checks/bundle-size.d.ts +7 -1
- package/dist/checks/bundle-size.d.ts.map +1 -1
- package/dist/checks/bundle-size.js +35 -15
- package/dist/checks/bundle-size.js.map +1 -1
- package/dist/checks/dark-mode.d.ts +20 -0
- package/dist/checks/dark-mode.d.ts.map +1 -0
- package/dist/checks/dark-mode.js +48 -0
- package/dist/checks/dark-mode.js.map +1 -0
- package/dist/checks/html-meta.d.ts +16 -0
- package/dist/checks/html-meta.d.ts.map +1 -0
- package/dist/checks/html-meta.js +46 -0
- package/dist/checks/html-meta.js.map +1 -0
- package/dist/checks/license-mit.d.ts +13 -0
- package/dist/checks/license-mit.d.ts.map +1 -0
- package/dist/checks/license-mit.js +38 -0
- package/dist/checks/license-mit.js.map +1 -0
- package/dist/checks/manifest.d.ts +2 -1
- package/dist/checks/manifest.d.ts.map +1 -1
- package/dist/checks/manifest.js +7 -12
- package/dist/checks/manifest.js.map +1 -1
- package/dist/checks/no-brand-overrides.d.ts +2 -1
- package/dist/checks/no-brand-overrides.d.ts.map +1 -1
- package/dist/checks/no-brand-overrides.js +13 -18
- package/dist/checks/no-brand-overrides.js.map +1 -1
- package/dist/checks/no-env-production.d.ts +16 -0
- package/dist/checks/no-env-production.d.ts.map +1 -0
- package/dist/checks/no-env-production.js +33 -0
- package/dist/checks/no-env-production.js.map +1 -0
- package/dist/checks/no-placeholders.d.ts +2 -1
- package/dist/checks/no-placeholders.d.ts.map +1 -1
- package/dist/checks/no-placeholders.js +13 -24
- package/dist/checks/no-placeholders.js.map +1 -1
- package/dist/checks/no-scroll.d.ts +25 -0
- package/dist/checks/no-scroll.d.ts.map +1 -0
- package/dist/checks/no-scroll.js +111 -0
- package/dist/checks/no-scroll.js.map +1 -0
- package/dist/checks/no-tracking.d.ts +2 -1
- package/dist/checks/no-tracking.d.ts.map +1 -1
- package/dist/checks/no-tracking.js +12 -8
- package/dist/checks/no-tracking.js.map +1 -1
- package/dist/checks/pwa-meta.d.ts +19 -0
- package/dist/checks/pwa-meta.d.ts.map +1 -0
- package/dist/checks/pwa-meta.js +38 -0
- package/dist/checks/pwa-meta.js.map +1 -0
- package/dist/checks/store-link.d.ts +17 -0
- package/dist/checks/store-link.d.ts.map +1 -0
- package/dist/checks/store-link.js +36 -0
- package/dist/checks/store-link.js.map +1 -0
- package/dist/checks/unsafe-vh.d.ts +25 -0
- package/dist/checks/unsafe-vh.d.ts.map +1 -0
- package/dist/checks/unsafe-vh.js +108 -0
- package/dist/checks/unsafe-vh.js.map +1 -0
- package/dist/checks/viewport-support.d.ts +24 -0
- package/dist/checks/viewport-support.d.ts.map +1 -0
- package/dist/checks/viewport-support.js +92 -0
- package/dist/checks/viewport-support.js.map +1 -0
- package/dist/index.d.ts +29 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -14
- package/dist/index.js.map +1 -1
- package/dist/lib/file-source.d.ts +52 -0
- package/dist/lib/file-source.d.ts.map +1 -0
- package/dist/lib/file-source.js +148 -0
- package/dist/lib/file-source.js.map +1 -0
- package/dist/lib/gzip.d.ts +8 -0
- package/dist/lib/gzip.d.ts.map +1 -0
- package/dist/lib/gzip.js +26 -0
- package/dist/lib/gzip.js.map +1 -0
- package/dist/lib/project-type.d.ts +18 -0
- package/dist/lib/project-type.d.ts.map +1 -0
- package/dist/lib/project-type.js +34 -0
- package/dist/lib/project-type.js.map +1 -0
- package/dist/live/index.d.ts +50 -0
- package/dist/live/index.d.ts.map +1 -0
- package/dist/live/index.js +313 -0
- package/dist/live/index.js.map +1 -0
- package/package.json +1 -1
- package/LICENSE +0 -21
- package/dist/checks/brand-fonts.test.d.ts +0 -2
- package/dist/checks/brand-fonts.test.d.ts.map +0 -1
- package/dist/checks/brand-fonts.test.js +0 -45
- package/dist/checks/brand-fonts.test.js.map +0 -1
- package/dist/checks/bundle-size.test.d.ts +0 -2
- package/dist/checks/bundle-size.test.d.ts.map +0 -1
- package/dist/checks/bundle-size.test.js +0 -57
- package/dist/checks/bundle-size.test.js.map +0 -1
- package/dist/checks/manifest.test.d.ts +0 -2
- package/dist/checks/manifest.test.d.ts.map +0 -1
- package/dist/checks/manifest.test.js +0 -53
- package/dist/checks/manifest.test.js.map +0 -1
- package/dist/checks/no-brand-overrides.test.d.ts +0 -2
- package/dist/checks/no-brand-overrides.test.d.ts.map +0 -1
- package/dist/checks/no-brand-overrides.test.js +0 -102
- package/dist/checks/no-brand-overrides.test.js.map +0 -1
- package/dist/checks/no-placeholders.test.d.ts +0 -2
- package/dist/checks/no-placeholders.test.d.ts.map +0 -1
- package/dist/checks/no-placeholders.test.js +0 -46
- package/dist/checks/no-placeholders.test.js.map +0 -1
- package/dist/checks/no-tracking.test.d.ts +0 -2
- package/dist/checks/no-tracking.test.d.ts.map +0 -1
- package/dist/checks/no-tracking.test.js +0 -42
- package/dist/checks/no-tracking.test.js.map +0 -1
- package/dist/lib/walk.d.ts +0 -6
- package/dist/lib/walk.d.ts.map +0 -1
- package/dist/lib/walk.js +0 -35
- package/dist/lib/walk.js.map +0 -1
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { extname, relative } from 'node:path';
|
|
3
|
-
import { walk } from '../lib/walk.js';
|
|
4
1
|
/**
|
|
5
2
|
* Apps and games on the platform must look consistent with the storefront
|
|
6
3
|
* and with each other — brand colors, fonts, and layout rhythm come from
|
|
@@ -19,23 +16,22 @@ import { walk } from '../lib/walk.js';
|
|
|
19
16
|
* red, etc.). We don't try to gate every hex literal — too noisy and
|
|
20
17
|
* too many false positives.
|
|
21
18
|
*/
|
|
22
|
-
export async function checkNoBrandOverrides(
|
|
19
|
+
export async function checkNoBrandOverrides(source) {
|
|
23
20
|
const issues = [];
|
|
24
|
-
for await (const
|
|
25
|
-
const ext =
|
|
21
|
+
for await (const path of source.list()) {
|
|
22
|
+
const ext = extOf(path);
|
|
26
23
|
if (!SCANNED_EXTS.has(ext))
|
|
27
24
|
continue;
|
|
28
|
-
const content = await
|
|
25
|
+
const content = await source.read(path);
|
|
29
26
|
if (!content)
|
|
30
27
|
continue;
|
|
31
|
-
const rel = relative(repoDir, file);
|
|
32
28
|
// Canonical theme file: the platform's CSS variables ARE defined here.
|
|
33
29
|
// Apps own this file post-scaffold (they can technically modify token
|
|
34
30
|
// values, and we accept the imperfection — the loud places where a
|
|
35
31
|
// brand override actually causes inconsistency are inline styles +
|
|
36
32
|
// per-component CSS, which this check still covers).
|
|
37
|
-
const isThemeFile =
|
|
38
|
-
const fileIssues = scanContent(
|
|
33
|
+
const isThemeFile = path === 'web/src/index.css' || path === 'web/src/main.css';
|
|
34
|
+
const fileIssues = scanContent(path, content, { skipVarRedefs: isThemeFile });
|
|
39
35
|
issues.push(...fileIssues);
|
|
40
36
|
// Cap at 10 — beyond that the user has bigger problems and we don't
|
|
41
37
|
// want a 200-line failure report.
|
|
@@ -118,10 +114,6 @@ const ALLOWED_FONT_TOKENS = new Set([
|
|
|
118
114
|
export function scanContent(filename, content, opts = {}) {
|
|
119
115
|
const out = [];
|
|
120
116
|
// 1. CSS variable overrides for protected tokens.
|
|
121
|
-
// Match `--accent: <something>;` outside of var() invocations.
|
|
122
|
-
// The regex looks for "<two dashes><name>:" near the start of a line,
|
|
123
|
-
// which is how CSS declarations look. var(--accent) won't match because
|
|
124
|
-
// there's a `(` not a `:`.
|
|
125
117
|
if (!opts.skipVarRedefs) {
|
|
126
118
|
const varDeclRe = /(?:^|[\s;{,])(--[a-z-]+)\s*:/gim;
|
|
127
119
|
let m;
|
|
@@ -133,9 +125,7 @@ export function scanContent(filename, content, opts = {}) {
|
|
|
133
125
|
}
|
|
134
126
|
}
|
|
135
127
|
}
|
|
136
|
-
// 2. font-family overrides.
|
|
137
|
-
// CSS) or `fontFamily:` (in JSX/TS), parse the comma-separated list,
|
|
138
|
-
// and flag any quoted token not in ALLOWED_FONT_TOKENS.
|
|
128
|
+
// 2. font-family overrides.
|
|
139
129
|
const fontRe = /(?:font-family|fontFamily)\s*[:=]\s*["'`]?([^;"'`}\n]+)["'`]?/g;
|
|
140
130
|
let m;
|
|
141
131
|
while ((m = fontRe.exec(content)) !== null) {
|
|
@@ -148,12 +138,17 @@ export function scanContent(filename, content, opts = {}) {
|
|
|
148
138
|
if (!ALLOWED_FONT_TOKENS.has(t)) {
|
|
149
139
|
const line = lineNumberAt(content, m.index);
|
|
150
140
|
out.push(`${filename}:${line} non-brand font "${t}"`);
|
|
151
|
-
break;
|
|
141
|
+
break;
|
|
152
142
|
}
|
|
153
143
|
}
|
|
154
144
|
}
|
|
155
145
|
return out;
|
|
156
146
|
}
|
|
147
|
+
function extOf(path) {
|
|
148
|
+
const dot = path.lastIndexOf('.');
|
|
149
|
+
const slash = path.lastIndexOf('/');
|
|
150
|
+
return dot > slash ? path.slice(dot).toLowerCase() : '';
|
|
151
|
+
}
|
|
157
152
|
function lineNumberAt(content, index) {
|
|
158
153
|
let n = 1;
|
|
159
154
|
for (let i = 0; i < index; i++)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-brand-overrides.js","sourceRoot":"","sources":["../../src/checks/no-brand-overrides.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-brand-overrides.js","sourceRoot":"","sources":["../../src/checks/no-brand-overrides.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FileSource } from '../lib/file-source.js';
|
|
2
|
+
import type { CheckResult } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* `.env.production` is a frequent accidental commit — Vite reads it at
|
|
5
|
+
* build time, and it's the file most likely to contain real API keys
|
|
6
|
+
* for the production deploy. The platform's templates instruct creators
|
|
7
|
+
* to use Cloudflare Pages env vars or Wrangler secrets instead. This
|
|
8
|
+
* check is a safety net: even if the creator's gitignore misses it, we
|
|
9
|
+
* fail compliance before publish.
|
|
10
|
+
*
|
|
11
|
+
* Scope: any `.env.production` file anywhere in the repo. We don't try
|
|
12
|
+
* to differentiate "this one's empty" from "this one has secrets" —
|
|
13
|
+
* the file shouldn't exist at all.
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkNoEnvProduction(source: FileSource): Promise<CheckResult>;
|
|
16
|
+
//# sourceMappingURL=no-env-production.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-env-production.d.ts","sourceRoot":"","sources":["../../src/checks/no-env-production.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAkBnF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.env.production` is a frequent accidental commit — Vite reads it at
|
|
3
|
+
* build time, and it's the file most likely to contain real API keys
|
|
4
|
+
* for the production deploy. The platform's templates instruct creators
|
|
5
|
+
* to use Cloudflare Pages env vars or Wrangler secrets instead. This
|
|
6
|
+
* check is a safety net: even if the creator's gitignore misses it, we
|
|
7
|
+
* fail compliance before publish.
|
|
8
|
+
*
|
|
9
|
+
* Scope: any `.env.production` file anywhere in the repo. We don't try
|
|
10
|
+
* to differentiate "this one's empty" from "this one has secrets" —
|
|
11
|
+
* the file shouldn't exist at all.
|
|
12
|
+
*/
|
|
13
|
+
export async function checkNoEnvProduction(source) {
|
|
14
|
+
const offenders = [];
|
|
15
|
+
for await (const path of source.list()) {
|
|
16
|
+
const base = path.split('/').pop() ?? '';
|
|
17
|
+
if (base === '.env.production')
|
|
18
|
+
offenders.push(path);
|
|
19
|
+
}
|
|
20
|
+
if (offenders.length === 0) {
|
|
21
|
+
return { name: 'No .env.production', status: 'pass', detail: 'no production env files committed' };
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
name: 'No .env.production',
|
|
25
|
+
status: 'fail',
|
|
26
|
+
detail: `${offenders.length} .env.production file(s): ${offenders.join(', ')}`,
|
|
27
|
+
suggestions: [
|
|
28
|
+
'Delete the file and add `.env.production` to .gitignore.',
|
|
29
|
+
'Use Cloudflare Pages → Settings → Environment Variables for production secrets instead.',
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=no-env-production.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-env-production.js","sourceRoot":"","sources":["../../src/checks/no-env-production.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAAkB;IAC3D,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;QACzC,IAAI,IAAI,KAAK,iBAAiB;YAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,mCAAmC,EAAE,CAAC;IACrG,CAAC;IACD,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,6BAA6B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC9E,WAAW,EAAE;YACX,0DAA0D;YAC1D,yFAAyF;SAC1F;KACF,CAAC;AACJ,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
|
* Catches the case where APPNAME placeholders from the template were
|
|
@@ -5,5 +6,5 @@ import type { CheckResult } from '../types.js';
|
|
|
5
6
|
* but if someone clones the template manually and forgets the find/
|
|
6
7
|
* replace step, every page renders with `<title>APPNAME — …</title>`.
|
|
7
8
|
*/
|
|
8
|
-
export declare function checkNoPlaceholders(
|
|
9
|
+
export declare function checkNoPlaceholders(source: FileSource): Promise<CheckResult>;
|
|
9
10
|
//# sourceMappingURL=no-placeholders.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-placeholders.d.ts","sourceRoot":"","sources":["../../src/checks/no-placeholders.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-placeholders.d.ts","sourceRoot":"","sources":["../../src/checks/no-placeholders.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAsBlF"}
|
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { extname } from 'node:path';
|
|
3
|
-
import { walk } from '../lib/walk.js';
|
|
4
1
|
const TEXT_EXTS = new Set([
|
|
5
|
-
'.md',
|
|
6
|
-
'.
|
|
7
|
-
'.json',
|
|
8
|
-
'.ts',
|
|
9
|
-
'.tsx',
|
|
10
|
-
'.js',
|
|
11
|
-
'.jsx',
|
|
12
|
-
'.html',
|
|
13
|
-
'.htm',
|
|
14
|
-
'.css',
|
|
15
|
-
'.scss',
|
|
16
|
-
'.yaml',
|
|
17
|
-
'.yml',
|
|
18
|
-
'.toml',
|
|
19
|
-
'.svg',
|
|
2
|
+
'.md', '.txt', '.json', '.ts', '.tsx', '.js', '.jsx',
|
|
3
|
+
'.html', '.htm', '.css', '.scss', '.yaml', '.yml', '.toml', '.svg',
|
|
20
4
|
]);
|
|
21
5
|
/**
|
|
22
6
|
* Catches the case where APPNAME placeholders from the template were
|
|
@@ -24,14 +8,14 @@ const TEXT_EXTS = new Set([
|
|
|
24
8
|
* but if someone clones the template manually and forgets the find/
|
|
25
9
|
* replace step, every page renders with `<title>APPNAME — …</title>`.
|
|
26
10
|
*/
|
|
27
|
-
export async function checkNoPlaceholders(
|
|
11
|
+
export async function checkNoPlaceholders(source) {
|
|
28
12
|
const offenders = [];
|
|
29
|
-
for await (const
|
|
30
|
-
if (!TEXT_EXTS.has(
|
|
13
|
+
for await (const path of source.list()) {
|
|
14
|
+
if (!TEXT_EXTS.has(extOf(path)))
|
|
31
15
|
continue;
|
|
32
|
-
const content = await
|
|
33
|
-
if (content.includes('APPNAME')) {
|
|
34
|
-
offenders.push(
|
|
16
|
+
const content = await source.read(path);
|
|
17
|
+
if (content && content.includes('APPNAME')) {
|
|
18
|
+
offenders.push(path);
|
|
35
19
|
if (offenders.length >= 5)
|
|
36
20
|
break; // don't spam, summarize
|
|
37
21
|
}
|
|
@@ -49,4 +33,9 @@ export async function checkNoPlaceholders(repoDir) {
|
|
|
49
33
|
],
|
|
50
34
|
};
|
|
51
35
|
}
|
|
36
|
+
function extOf(path) {
|
|
37
|
+
const dot = path.lastIndexOf('.');
|
|
38
|
+
const slash = path.lastIndexOf('/');
|
|
39
|
+
return dot > slash ? path.slice(dot).toLowerCase() : '';
|
|
40
|
+
}
|
|
52
41
|
//# sourceMappingURL=no-placeholders.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-placeholders.js","sourceRoot":"","sources":["../../src/checks/no-placeholders.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-placeholders.js","sourceRoot":"","sources":["../../src/checks/no-placeholders.ts"],"names":[],"mappings":"AAGA,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACxB,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IACpD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CACnE,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAkB;IAC1D,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAAE,SAAS;QAC1C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3C,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC;gBAAE,MAAM,CAAC,wBAAwB;QAC5D,CAAC;IACH,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,0BAA0B,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC;IACjG,CAAC;IACD,OAAO;QACL,IAAI,EAAE,0BAA0B;QAChC,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,mCAAmC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACpH,WAAW,EAAE;YACX,0FAA0F;YAC1F,mFAAmF;SACpF;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,25 @@
|
|
|
1
|
+
import type { FileSource } from '../lib/file-source.js';
|
|
2
|
+
import type { CheckResult } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Games on FreeGameStore must fit the viewport — no horizontal or
|
|
5
|
+
* vertical scroll at the document level. Static analysis catches the
|
|
6
|
+
* common CSS / inline-style anti-patterns:
|
|
7
|
+
*
|
|
8
|
+
* - `overflow: scroll` or `overflow: auto` on `html` or `body`.
|
|
9
|
+
* - `min-height: 100vh` on `html` / `body` (creates page that's at
|
|
10
|
+
* least viewport-tall, but content + topbar = > 100vh = scroll).
|
|
11
|
+
* - Missing the `100svh` / `100vh` height constraint on the layout
|
|
12
|
+
* wrapper (loose check — "at least one root element should hard-cap
|
|
13
|
+
* to viewport height").
|
|
14
|
+
*
|
|
15
|
+
* Runtime guarantees (real DOM measurement) live in the e2e Playwright
|
|
16
|
+
* suite; this check exists as a fast pre-publish gate so creators
|
|
17
|
+
* don't ship trivially-scrolling games.
|
|
18
|
+
*
|
|
19
|
+
* Apps (FreeAppStore) are not subject to this check — the apps Shell
|
|
20
|
+
* wraps them in a sidebar+main layout that legitimately scrolls. We
|
|
21
|
+
* detect "is this a game project" by looking for the @freeappstore/games
|
|
22
|
+
* dep, the canonical signal that the app uses GameShell.
|
|
23
|
+
*/
|
|
24
|
+
export declare function checkNoScroll(source: FileSource): Promise<CheckResult>;
|
|
25
|
+
//# sourceMappingURL=no-scroll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-scroll.d.ts","sourceRoot":"","sources":["../../src/checks/no-scroll.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAuE5E"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { isGameProject } from '../lib/project-type.js';
|
|
2
|
+
/**
|
|
3
|
+
* Games on FreeGameStore must fit the viewport — no horizontal or
|
|
4
|
+
* vertical scroll at the document level. Static analysis catches the
|
|
5
|
+
* common CSS / inline-style anti-patterns:
|
|
6
|
+
*
|
|
7
|
+
* - `overflow: scroll` or `overflow: auto` on `html` or `body`.
|
|
8
|
+
* - `min-height: 100vh` on `html` / `body` (creates page that's at
|
|
9
|
+
* least viewport-tall, but content + topbar = > 100vh = scroll).
|
|
10
|
+
* - Missing the `100svh` / `100vh` height constraint on the layout
|
|
11
|
+
* wrapper (loose check — "at least one root element should hard-cap
|
|
12
|
+
* to viewport height").
|
|
13
|
+
*
|
|
14
|
+
* Runtime guarantees (real DOM measurement) live in the e2e Playwright
|
|
15
|
+
* suite; this check exists as a fast pre-publish gate so creators
|
|
16
|
+
* don't ship trivially-scrolling games.
|
|
17
|
+
*
|
|
18
|
+
* Apps (FreeAppStore) are not subject to this check — the apps Shell
|
|
19
|
+
* wraps them in a sidebar+main layout that legitimately scrolls. We
|
|
20
|
+
* detect "is this a game project" by looking for the @freeappstore/games
|
|
21
|
+
* dep, the canonical signal that the app uses GameShell.
|
|
22
|
+
*/
|
|
23
|
+
export async function checkNoScroll(source) {
|
|
24
|
+
// Skip apps — only games are subject to no-scroll.
|
|
25
|
+
if (!(await isGameProject(source))) {
|
|
26
|
+
return {
|
|
27
|
+
name: 'No scroll (games only)',
|
|
28
|
+
status: 'pass',
|
|
29
|
+
detail: 'not a game project — check skipped',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const issues = [];
|
|
33
|
+
let sawViewportLock = false;
|
|
34
|
+
for await (const path of source.list()) {
|
|
35
|
+
const ext = extOf(path);
|
|
36
|
+
if (!SCAN_EXTS.has(ext))
|
|
37
|
+
continue;
|
|
38
|
+
const content = await source.read(path);
|
|
39
|
+
if (!content)
|
|
40
|
+
continue;
|
|
41
|
+
for (const re of FORBIDDEN_OVERFLOW) {
|
|
42
|
+
const m = re.exec(content);
|
|
43
|
+
if (m) {
|
|
44
|
+
const line = lineNumberAt(content, m.index);
|
|
45
|
+
issues.push(`${path}:${line} ${m[0]} — root scrolling not allowed in games`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const re of FORBIDDEN_MIN_HEIGHT) {
|
|
49
|
+
const m = re.exec(content);
|
|
50
|
+
if (m) {
|
|
51
|
+
const line = lineNumberAt(content, m.index);
|
|
52
|
+
issues.push(`${path}:${line} ${m[0]} — use exact viewport height (100svh) instead`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (VIEWPORT_LOCK.test(content))
|
|
56
|
+
sawViewportLock = true;
|
|
57
|
+
if (issues.length >= 8)
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
if (issues.length > 0) {
|
|
61
|
+
return {
|
|
62
|
+
name: 'No scroll (games only)',
|
|
63
|
+
status: 'fail',
|
|
64
|
+
detail: `${issues.length} scroll-enabling pattern${issues.length === 1 ? '' : 's'}`,
|
|
65
|
+
suggestions: [
|
|
66
|
+
...issues.slice(0, 5),
|
|
67
|
+
...(issues.length > 5 ? [`...and ${issues.length - 5} more`] : []),
|
|
68
|
+
'Use <GameShell> from @freeappstore/games — it locks layout to 100svh.',
|
|
69
|
+
'Inside the play area, use overflow: hidden instead of overflow: auto.',
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (!sawViewportLock) {
|
|
74
|
+
return {
|
|
75
|
+
name: 'No scroll (games only)',
|
|
76
|
+
status: 'warn',
|
|
77
|
+
detail: 'no 100svh/100vh viewport lock detected — game may scroll on small viewports',
|
|
78
|
+
suggestions: [
|
|
79
|
+
'Wrap your game in <GameShell> from @freeappstore/games, or',
|
|
80
|
+
'Add `height: 100svh` to your root container.',
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
name: 'No scroll (games only)',
|
|
86
|
+
status: 'pass',
|
|
87
|
+
detail: 'viewport-locked layout detected',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const SCAN_EXTS = new Set(['.css', '.scss', '.tsx', '.ts', '.jsx', '.js', '.html']);
|
|
91
|
+
const FORBIDDEN_OVERFLOW = [
|
|
92
|
+
/(?:^|[\s,{])(?:html|body)\s*\{[^}]*overflow\s*:\s*(?:scroll|auto)/im,
|
|
93
|
+
/(?:^|[\s,{])(?:html|body)\s*\{[^}]*overflow-(?:x|y)\s*:\s*(?:scroll|auto)/im,
|
|
94
|
+
];
|
|
95
|
+
const FORBIDDEN_MIN_HEIGHT = [
|
|
96
|
+
/(?:^|[\s,{])(?:html|body)\s*\{[^}]*min-height\s*:\s*100vh/im,
|
|
97
|
+
];
|
|
98
|
+
const VIEWPORT_LOCK = /(?:height|max-height)\s*:\s*100(?:s?vh)|GameShell|@freeappstore\/games/i;
|
|
99
|
+
function extOf(path) {
|
|
100
|
+
const dot = path.lastIndexOf('.');
|
|
101
|
+
const slash = path.lastIndexOf('/');
|
|
102
|
+
return dot > slash ? path.slice(dot).toLowerCase() : '';
|
|
103
|
+
}
|
|
104
|
+
function lineNumberAt(content, index) {
|
|
105
|
+
let n = 1;
|
|
106
|
+
for (let i = 0; i < index; i++)
|
|
107
|
+
if (content.charCodeAt(i) === 10)
|
|
108
|
+
n++;
|
|
109
|
+
return n;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=no-scroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-scroll.js","sourceRoot":"","sources":["../../src/checks/no-scroll.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FileSource } from '../lib/file-source.js';
|
|
1
2
|
import type { CheckResult } from '../types.js';
|
|
2
|
-
export declare function checkNoTracking(
|
|
3
|
+
export declare function checkNoTracking(source: FileSource): Promise<CheckResult>;
|
|
3
4
|
//# sourceMappingURL=no-tracking.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-tracking.d.ts","sourceRoot":"","sources":["../../src/checks/no-tracking.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-tracking.d.ts","sourceRoot":"","sources":["../../src/checks/no-tracking.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiB/C,wBAAsB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC9E"}
|
|
@@ -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
|
// Same forbidden list the template's compliance.yml enforces — kept in
|
|
5
2
|
// sync because both are downstream consumers of this package long-term.
|
|
6
3
|
const FORBIDDEN = [
|
|
@@ -14,15 +11,17 @@ const FORBIDDEN = [
|
|
|
14
11
|
'posthog',
|
|
15
12
|
];
|
|
16
13
|
const SCAN_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.html', '.json']);
|
|
17
|
-
export async function checkNoTracking(
|
|
14
|
+
export async function checkNoTracking(source) {
|
|
18
15
|
const hits = [];
|
|
19
|
-
for await (const
|
|
20
|
-
if (!SCAN_EXTS.has(
|
|
16
|
+
for await (const path of source.list()) {
|
|
17
|
+
if (!SCAN_EXTS.has(extOf(path)))
|
|
18
|
+
continue;
|
|
19
|
+
const content = await source.read(path);
|
|
20
|
+
if (!content)
|
|
21
21
|
continue;
|
|
22
|
-
const content = await readFile(file, 'utf8').catch(() => '');
|
|
23
22
|
const matches = FORBIDDEN.filter((sdk) => content.includes(sdk));
|
|
24
23
|
if (matches.length > 0) {
|
|
25
|
-
hits.push({ file:
|
|
24
|
+
hits.push({ file: path, matches });
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
if (hits.length === 0) {
|
|
@@ -45,4 +44,9 @@ export async function checkNoTracking(repoDir) {
|
|
|
45
44
|
],
|
|
46
45
|
};
|
|
47
46
|
}
|
|
47
|
+
function extOf(path) {
|
|
48
|
+
const dot = path.lastIndexOf('.');
|
|
49
|
+
const slash = path.lastIndexOf('/');
|
|
50
|
+
return dot > slash ? path.slice(dot).toLowerCase() : '';
|
|
51
|
+
}
|
|
48
52
|
//# sourceMappingURL=no-tracking.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-tracking.js","sourceRoot":"","sources":["../../src/checks/no-tracking.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-tracking.js","sourceRoot":"","sources":["../../src/checks/no-tracking.ts"],"names":[],"mappings":"AAGA,uEAAuE;AACvE,wEAAwE;AACxE,MAAM,SAAS,GAAG;IAChB,kBAAkB;IAClB,MAAM;IACN,WAAW;IACX,UAAU;IACV,SAAS;IACT,QAAQ;IACR,WAAW;IACX,SAAS;CACV,CAAC;AAEF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAE5E,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAkB;IACtD,MAAM,IAAI,GAA0C,EAAE,CAAC;IAEvD,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAAE,SAAS;QAC1C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QACjE,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,kBAAkB;YACxB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,eAAe,SAAS,CAAC,MAAM,iBAAiB;SACzD,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,kBAAkB;QACxB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,gCAAgC,IAAI;aACvD,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,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC5C,WAAW,EAAE;YACX,gFAAgF;YAChF,uFAAuF;SACxF;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,19 @@
|
|
|
1
|
+
import type { FileSource } from '../lib/file-source.js';
|
|
2
|
+
import type { CheckResult } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* For an "Add to Home Screen" install on iOS Safari to launch the app
|
|
5
|
+
* standalone (no browser chrome), `web/index.html` needs at least one
|
|
6
|
+
* of:
|
|
7
|
+
*
|
|
8
|
+
* <meta name="apple-mobile-web-app-capable" content="yes" />
|
|
9
|
+
* <meta name="mobile-web-app-capable" content="yes" />
|
|
10
|
+
*
|
|
11
|
+
* Without this, iOS opens the installed icon as a normal Safari tab,
|
|
12
|
+
* defeating the PWA install. Android Chrome reads the manifest's
|
|
13
|
+
* `display: standalone` instead, but iOS still requires the meta tag.
|
|
14
|
+
*
|
|
15
|
+
* The manifest itself is a separate check (`checkManifest`); this one
|
|
16
|
+
* is specifically the iOS install hint that lives in the HTML head.
|
|
17
|
+
*/
|
|
18
|
+
export declare function checkPwaMeta(source: FileSource): Promise<CheckResult>;
|
|
19
|
+
//# sourceMappingURL=pwa-meta.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pwa-meta.d.ts","sourceRoot":"","sources":["../../src/checks/pwa-meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAoB3E"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const HTML_PATH = 'web/index.html';
|
|
2
|
+
/**
|
|
3
|
+
* For an "Add to Home Screen" install on iOS Safari to launch the app
|
|
4
|
+
* standalone (no browser chrome), `web/index.html` needs at least one
|
|
5
|
+
* of:
|
|
6
|
+
*
|
|
7
|
+
* <meta name="apple-mobile-web-app-capable" content="yes" />
|
|
8
|
+
* <meta name="mobile-web-app-capable" content="yes" />
|
|
9
|
+
*
|
|
10
|
+
* Without this, iOS opens the installed icon as a normal Safari tab,
|
|
11
|
+
* defeating the PWA install. Android Chrome reads the manifest's
|
|
12
|
+
* `display: standalone` instead, but iOS still requires the meta tag.
|
|
13
|
+
*
|
|
14
|
+
* The manifest itself is a separate check (`checkManifest`); this one
|
|
15
|
+
* is specifically the iOS install hint that lives in the HTML head.
|
|
16
|
+
*/
|
|
17
|
+
export async function checkPwaMeta(source) {
|
|
18
|
+
const html = await source.read(HTML_PATH);
|
|
19
|
+
if (html === null) {
|
|
20
|
+
return {
|
|
21
|
+
name: 'PWA meta tags',
|
|
22
|
+
status: 'fail',
|
|
23
|
+
detail: `${HTML_PATH} not found`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (/<meta[^>]*\bname\s*=\s*["'](?:apple-)?mobile-web-app-capable["']/i.test(html)) {
|
|
27
|
+
return { name: 'PWA meta tags', status: 'pass', detail: 'iOS install hint present' };
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
name: 'PWA meta tags',
|
|
31
|
+
status: 'warn',
|
|
32
|
+
detail: 'no apple-mobile-web-app-capable / mobile-web-app-capable meta',
|
|
33
|
+
suggestions: [
|
|
34
|
+
'Add `<meta name="apple-mobile-web-app-capable" content="yes" />` to <head> so iOS installs launch standalone.',
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=pwa-meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pwa-meta.js","sourceRoot":"","sources":["../../src/checks/pwa-meta.ts"],"names":[],"mappings":"AAGA,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAEnC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAkB;IACnD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO;YACL,IAAI,EAAE,eAAe;YACrB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,GAAG,SAAS,YAAY;SACjC,CAAC;IACJ,CAAC;IACD,IAAI,mEAAmE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC;IACvF,CAAC;IACD,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,+DAA+D;QACvE,WAAW,EAAE;YACX,+GAA+G;SAChH;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FileSource } from '../lib/file-source.js';
|
|
2
|
+
import type { CheckResult } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Apps and games should link back to their storefront — it's how
|
|
5
|
+
* visitors discover the rest of the catalog from inside any single
|
|
6
|
+
* app. The check passes if any source file under `web/src/` references
|
|
7
|
+
* the appropriate domain:
|
|
8
|
+
*
|
|
9
|
+
* - games → freegamestore.online
|
|
10
|
+
* - apps → freeappstore.online
|
|
11
|
+
*
|
|
12
|
+
* The link can be in JSX (`<a href="https://freeappstore.online">`),
|
|
13
|
+
* a string constant, or even a footer comment — we don't enforce a
|
|
14
|
+
* specific component, just that the link exists somewhere visible.
|
|
15
|
+
*/
|
|
16
|
+
export declare function checkStoreLink(source: FileSource): Promise<CheckResult>;
|
|
17
|
+
//# sourceMappingURL=store-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-link.d.ts","sourceRoot":"","sources":["../../src/checks/store-link.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAoB7E"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isGameProject } from '../lib/project-type.js';
|
|
2
|
+
/**
|
|
3
|
+
* Apps and games should link back to their storefront — it's how
|
|
4
|
+
* visitors discover the rest of the catalog from inside any single
|
|
5
|
+
* app. The check passes if any source file under `web/src/` references
|
|
6
|
+
* the appropriate domain:
|
|
7
|
+
*
|
|
8
|
+
* - games → freegamestore.online
|
|
9
|
+
* - apps → freeappstore.online
|
|
10
|
+
*
|
|
11
|
+
* The link can be in JSX (`<a href="https://freeappstore.online">`),
|
|
12
|
+
* a string constant, or even a footer comment — we don't enforce a
|
|
13
|
+
* specific component, just that the link exists somewhere visible.
|
|
14
|
+
*/
|
|
15
|
+
export async function checkStoreLink(source) {
|
|
16
|
+
const isGame = await isGameProject(source);
|
|
17
|
+
const domain = isGame ? 'freegamestore.online' : 'freeappstore.online';
|
|
18
|
+
for await (const path of source.list()) {
|
|
19
|
+
if (!path.startsWith('web/src/'))
|
|
20
|
+
continue;
|
|
21
|
+
const content = await source.read(path);
|
|
22
|
+
if (content && content.includes(domain)) {
|
|
23
|
+
return { name: 'Store link', status: 'pass', detail: `${domain} referenced in ${path}` };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
name: 'Store link',
|
|
28
|
+
status: 'warn',
|
|
29
|
+
detail: `no link to ${domain} found in web/src/`,
|
|
30
|
+
suggestions: [
|
|
31
|
+
`Add a small "Built for ${domain}" link in the footer or about screen.`,
|
|
32
|
+
'It helps visitors find the rest of the catalog — and it counts for storefront ranking.',
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=store-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-link.js","sourceRoot":"","sources":["../../src/checks/store-link.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAkB;IACrD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,qBAAqB,CAAC;IAEvE,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,SAAS;QAC3C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACxC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,kBAAkB,IAAI,EAAE,EAAE,CAAC;QAC3F,CAAC;IACH,CAAC;IACD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,cAAc,MAAM,oBAAoB;QAChD,WAAW,EAAE;YACX,0BAA0B,MAAM,uCAAuC;YACvE,wFAAwF;SACzF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FileSource } from '../lib/file-source.js';
|
|
2
|
+
import type { CheckResult } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Flag `100vh` (and Tailwind shortcuts that compile to it) in source.
|
|
5
|
+
*
|
|
6
|
+
* Why: on iOS Safari with the URL bar visible, `100vh` resolves to the
|
|
7
|
+
* *layout* viewport (e.g. 852px on iPhone 15) but the actual visible
|
|
8
|
+
* area is the *visual* viewport (~750px). An element sized at `100vh`
|
|
9
|
+
* is then ~100px taller than the screen, forcing the page to scroll on
|
|
10
|
+
* first load. This is invisible to headless Playwright (no dynamic URL
|
|
11
|
+
* bar) and to the static audit Worker (no rendering), so this fast
|
|
12
|
+
* static rule is the cheapest place to catch it.
|
|
13
|
+
*
|
|
14
|
+
* Fix: use `100svh` (small viewport height — accounts for visible
|
|
15
|
+
* browser UI) for "as tall as the screen" intent, or `100dvh` (dynamic
|
|
16
|
+
* viewport height — recomputes as the URL bar shows/hides) for full-
|
|
17
|
+
* height containers that should grow when chrome retracts.
|
|
18
|
+
*
|
|
19
|
+
* Scope: warn-by-default, not fail. There ARE legitimate uses of
|
|
20
|
+
* `100vh` (intentionally taking layout-viewport height for hero
|
|
21
|
+
* sections that should stay constant as URL bar moves). Creators can
|
|
22
|
+
* acknowledge by leaving an `// allow-100vh` comment on the same line.
|
|
23
|
+
*/
|
|
24
|
+
export declare function checkUnsafeVh(source: FileSource): Promise<CheckResult>;
|
|
25
|
+
//# sourceMappingURL=unsafe-vh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unsafe-vh.d.ts","sourceRoot":"","sources":["../../src/checks/unsafe-vh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAyC5E"}
|