ios-app-review-plugin 1.0.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/.claude/settings.local.json +42 -0
- package/.github/actions/ios-review/action.yml +106 -0
- package/.github/workflows/ci.yml +103 -0
- package/.github/workflows/publish.yml +57 -0
- package/CHANGELOG.md +66 -0
- package/CONTRIBUTING.md +175 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/bitrise/step.sh +128 -0
- package/bitrise/step.yml +101 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzers/asc-iap.d.ts.map +1 -0
- package/dist/analyzers/asc-metadata.d.ts.map +1 -0
- package/dist/analyzers/asc-screenshots.d.ts.map +1 -0
- package/dist/analyzers/asc-version.d.ts.map +1 -0
- package/dist/analyzers/code-scanner.d.ts.map +1 -0
- package/dist/analyzers/deprecated-api.d.ts.map +1 -0
- package/dist/analyzers/entitlements.d.ts.map +1 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/info-plist.d.ts.map +1 -0
- package/dist/analyzers/privacy.d.ts.map +1 -0
- package/dist/analyzers/private-api.d.ts.map +1 -0
- package/dist/analyzers/security.d.ts.map +1 -0
- package/dist/analyzers/ui-ux.d.ts.map +1 -0
- package/dist/asc/auth.d.ts.map +1 -0
- package/dist/asc/client.d.ts.map +1 -0
- package/dist/asc/endpoints/apps.d.ts.map +1 -0
- package/dist/asc/endpoints/iap.d.ts.map +1 -0
- package/dist/asc/endpoints/screenshots.d.ts.map +1 -0
- package/dist/asc/endpoints/versions.d.ts.map +1 -0
- package/dist/asc/errors.d.ts.map +1 -0
- package/dist/asc/index.d.ts.map +1 -0
- package/dist/asc/types.d.ts.map +1 -0
- package/dist/badge/generator.d.ts.map +1 -0
- package/dist/badge/index.d.ts.map +1 -0
- package/dist/badge/types.d.ts.map +1 -0
- package/dist/cache/file-cache.d.ts.map +1 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/version.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/git/diff.d.ts.map +1 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/types.d.ts.map +1 -0
- package/dist/guidelines/database.d.ts.map +1 -0
- package/dist/guidelines/index.d.ts.map +1 -0
- package/dist/guidelines/matcher.d.ts.map +1 -0
- package/dist/guidelines/types.d.ts.map +1 -0
- package/dist/history/comparator.d.ts.map +1 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/store.d.ts.map +1 -0
- package/dist/history/types.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +994 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/plist.d.ts.map +1 -0
- package/dist/parsers/xcodeproj.d.ts.map +1 -0
- package/dist/progress/index.d.ts.map +1 -0
- package/dist/progress/reporter.d.ts.map +1 -0
- package/dist/progress/types.d.ts.map +1 -0
- package/dist/reports/html.d.ts.map +1 -0
- package/dist/reports/index.d.ts.map +1 -0
- package/dist/reports/json.d.ts.map +1 -0
- package/dist/reports/markdown.d.ts.map +1 -0
- package/dist/reports/types.d.ts.map +1 -0
- package/dist/rules/engine.d.ts.map +1 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/loader.d.ts.map +1 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/types/index.d.ts.map +1 -0
- package/docs/ANALYZERS.md +237 -0
- package/docs/API.md +308 -0
- package/docs/BADGES.md +130 -0
- package/docs/CI_CD.md +283 -0
- package/docs/CLI.md +140 -0
- package/docs/REPORTS.md +212 -0
- package/docs/ROADMAP.md +267 -0
- package/docs/RULES.md +182 -0
- package/docs/SECURITY.md +89 -0
- package/docs/TROUBLESHOOTING.md +227 -0
- package/docs/tutorials/ASC_SETUP.md +188 -0
- package/docs/tutorials/CI_INTEGRATION.md +292 -0
- package/docs/tutorials/CUSTOM_RULES.md +291 -0
- package/docs/tutorials/GETTING_STARTED.md +226 -0
- package/docs/video-scripts/01-introduction.md +106 -0
- package/docs/video-scripts/02-cli-usage.md +120 -0
- package/docs/video-scripts/03-ci-integration.md +198 -0
- package/eslint.config.js +33 -0
- package/examples/.ios-review-rules.json +82 -0
- package/examples/bitrise-workflow.yml +129 -0
- package/examples/fastlane-lane.rb +71 -0
- package/examples/github-action.yml +147 -0
- package/fastlane/Fastfile.example +114 -0
- package/fastlane/README.md +99 -0
- package/jest.config.js +36 -0
- package/package.json +65 -0
- package/scripts/benchmark.ts +112 -0
- package/scripts/debug-parser.ts +37 -0
- package/scripts/debug-pbxproj.ts +36 -0
- package/scripts/debug-specific.ts +47 -0
- package/scripts/test-analyze.ts +67 -0
- package/scripts/xcode-cloud-review.sh +167 -0
- package/src/analyzer.ts +227 -0
- package/src/analyzers/asc-iap.ts +300 -0
- package/src/analyzers/asc-metadata.ts +326 -0
- package/src/analyzers/asc-screenshots.ts +310 -0
- package/src/analyzers/asc-version.ts +368 -0
- package/src/analyzers/code-scanner.ts +408 -0
- package/src/analyzers/deprecated-api.ts +390 -0
- package/src/analyzers/entitlements.ts +345 -0
- package/src/analyzers/index.ts +12 -0
- package/src/analyzers/info-plist.ts +409 -0
- package/src/analyzers/privacy.ts +376 -0
- package/src/analyzers/private-api.ts +377 -0
- package/src/analyzers/security.ts +327 -0
- package/src/analyzers/ui-ux.ts +509 -0
- package/src/asc/auth.ts +204 -0
- package/src/asc/client.ts +258 -0
- package/src/asc/endpoints/apps.ts +115 -0
- package/src/asc/endpoints/iap.ts +171 -0
- package/src/asc/endpoints/screenshots.ts +164 -0
- package/src/asc/endpoints/versions.ts +174 -0
- package/src/asc/errors.ts +109 -0
- package/src/asc/index.ts +108 -0
- package/src/asc/types.ts +369 -0
- package/src/badge/generator.ts +48 -0
- package/src/badge/index.ts +2 -0
- package/src/badge/types.ts +5 -0
- package/src/cache/file-cache.ts +75 -0
- package/src/cache/index.ts +2 -0
- package/src/cache/types.ts +10 -0
- package/src/cli/commands/help.ts +41 -0
- package/src/cli/commands/scan.ts +44 -0
- package/src/cli/commands/version.ts +12 -0
- package/src/cli/index.ts +92 -0
- package/src/cli/types.ts +17 -0
- package/src/git/diff.ts +21 -0
- package/src/git/index.ts +2 -0
- package/src/git/types.ts +5 -0
- package/src/guidelines/database.ts +344 -0
- package/src/guidelines/index.ts +4 -0
- package/src/guidelines/matcher.ts +84 -0
- package/src/guidelines/types.ts +28 -0
- package/src/history/comparator.ts +114 -0
- package/src/history/index.ts +3 -0
- package/src/history/store.ts +135 -0
- package/src/history/types.ts +40 -0
- package/src/index.ts +1113 -0
- package/src/parsers/index.ts +3 -0
- package/src/parsers/plist.ts +253 -0
- package/src/parsers/xcodeproj.ts +265 -0
- package/src/progress/index.ts +2 -0
- package/src/progress/reporter.ts +65 -0
- package/src/progress/types.ts +9 -0
- package/src/reports/html.ts +322 -0
- package/src/reports/index.ts +20 -0
- package/src/reports/json.ts +92 -0
- package/src/reports/markdown.ts +187 -0
- package/src/reports/types.ts +26 -0
- package/src/rules/engine.ts +121 -0
- package/src/rules/index.ts +3 -0
- package/src/rules/loader.ts +83 -0
- package/src/rules/types.ts +25 -0
- package/src/types/index.ts +247 -0
- package/tests/analyzer.test.ts +142 -0
- package/tests/analyzers/asc-iap.test.ts +228 -0
- package/tests/analyzers/asc-metadata.test.ts +210 -0
- package/tests/analyzers/asc-screenshots.test.ts +135 -0
- package/tests/analyzers/asc-version.test.ts +259 -0
- package/tests/analyzers/code-scanner.test.ts +745 -0
- package/tests/analyzers/deprecated-api.test.ts +286 -0
- package/tests/analyzers/entitlements.test.ts +411 -0
- package/tests/analyzers/info-plist.test.ts +148 -0
- package/tests/analyzers/privacy.test.ts +623 -0
- package/tests/analyzers/private-api.test.ts +255 -0
- package/tests/analyzers/security.test.ts +300 -0
- package/tests/analyzers/ui-ux.test.ts +357 -0
- package/tests/asc/auth.test.ts +189 -0
- package/tests/asc/client.test.ts +207 -0
- package/tests/asc/endpoints.test.ts +1359 -0
- package/tests/badge/generator.test.ts +73 -0
- package/tests/cache/file-cache.test.ts +124 -0
- package/tests/cli/cli-index.test.ts +510 -0
- package/tests/cli/commands.test.ts +67 -0
- package/tests/cli/scan.test.ts +152 -0
- package/tests/git/diff.test.ts +69 -0
- package/tests/guidelines/matcher.test.ts +209 -0
- package/tests/history/comparator.test.ts +272 -0
- package/tests/history/store.test.ts +200 -0
- package/tests/integration/cli.test.ts +95 -0
- package/tests/integration/e2e.test.ts +130 -0
- package/tests/parsers/plist.test.ts +240 -0
- package/tests/parsers/xcodeproj.test.ts +289 -0
- package/tests/progress/reporter.test.ts +117 -0
- package/tests/reports/html.test.ts +176 -0
- package/tests/reports/json.test.ts +235 -0
- package/tests/reports/markdown.test.ts +196 -0
- package/tests/rules/engine.test.ts +229 -0
- package/tests/rules/loader.test.ts +187 -0
- package/tests/setup.ts +15 -0
- package/tsconfig.json +27 -0
- package/tsconfig.test.json +9 -0
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { printHelp } from './commands/help.js';
|
|
3
|
+
import { printVersion } from './commands/version.js';
|
|
4
|
+
import { runScan } from './commands/scan.js';
|
|
5
|
+
import type { ScanOptions } from './types.js';
|
|
6
|
+
|
|
7
|
+
export async function runCli(argv: string[]): Promise<void> {
|
|
8
|
+
const args = argv.slice(2);
|
|
9
|
+
|
|
10
|
+
if (args.length === 0 || args[0] === 'help' || args.includes('--help') || args.includes('-h')) {
|
|
11
|
+
printHelp();
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (args[0] === 'version' || args.includes('--version') || args.includes('-v')) {
|
|
16
|
+
printVersion();
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (args[0] !== 'scan') {
|
|
21
|
+
console.error(`Unknown command: ${args[0]}`);
|
|
22
|
+
console.error('Run "ios-app-review help" for usage information.');
|
|
23
|
+
process.exit(2);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Parse scan command args
|
|
27
|
+
const scanArgs = args.slice(1);
|
|
28
|
+
|
|
29
|
+
let parsed: ReturnType<typeof parseArgs>;
|
|
30
|
+
try {
|
|
31
|
+
parsed = parseArgs({
|
|
32
|
+
args: scanArgs,
|
|
33
|
+
options: {
|
|
34
|
+
format: { type: 'string', short: 'f', default: 'markdown' },
|
|
35
|
+
output: { type: 'string', short: 'o' },
|
|
36
|
+
analyzers: { type: 'string', short: 'a' },
|
|
37
|
+
'include-asc': { type: 'boolean', default: false },
|
|
38
|
+
'changed-since': { type: 'string' },
|
|
39
|
+
config: { type: 'string', short: 'c' },
|
|
40
|
+
badge: { type: 'boolean', default: false },
|
|
41
|
+
'save-history': { type: 'boolean', default: false },
|
|
42
|
+
},
|
|
43
|
+
allowPositionals: true,
|
|
44
|
+
strict: true,
|
|
45
|
+
});
|
|
46
|
+
} catch (err) {
|
|
47
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
48
|
+
console.error(`Error: ${message}`);
|
|
49
|
+
console.error('Run "ios-app-review help" for usage information.');
|
|
50
|
+
process.exit(2);
|
|
51
|
+
return; // unreachable, satisfies TS
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { values, positionals } = parsed;
|
|
55
|
+
|
|
56
|
+
if (positionals.length === 0) {
|
|
57
|
+
console.error('Error: project path is required');
|
|
58
|
+
console.error('Usage: ios-app-review scan <path> [options]');
|
|
59
|
+
process.exit(2);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const format = values['format'] as string;
|
|
63
|
+
if (format !== 'markdown' && format !== 'html' && format !== 'json') {
|
|
64
|
+
console.error(`Error: invalid format "${format}". Must be markdown, html, or json.`);
|
|
65
|
+
process.exit(2);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const analyzersList = values['analyzers']
|
|
69
|
+
? (values['analyzers'] as string).split(',').map((s) => s.trim())
|
|
70
|
+
: undefined;
|
|
71
|
+
|
|
72
|
+
const scanOptions: ScanOptions = {
|
|
73
|
+
projectPath: positionals[0]!,
|
|
74
|
+
format,
|
|
75
|
+
output: values['output'] as string | undefined,
|
|
76
|
+
analyzers: analyzersList,
|
|
77
|
+
includeAsc: values['include-asc'] as boolean,
|
|
78
|
+
changedSince: values['changed-since'] as string | undefined,
|
|
79
|
+
config: values['config'] as string | undefined,
|
|
80
|
+
badge: values['badge'] as boolean,
|
|
81
|
+
saveHistory: values['save-history'] as boolean,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const exitCode = await runScan(scanOptions);
|
|
86
|
+
process.exit(exitCode);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
89
|
+
console.error(`Error: ${message}`);
|
|
90
|
+
process.exit(2);
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/cli/types.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface CliOptions {
|
|
2
|
+
command: string;
|
|
3
|
+
help: boolean;
|
|
4
|
+
version: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface ScanOptions {
|
|
8
|
+
projectPath: string;
|
|
9
|
+
format: 'markdown' | 'html' | 'json';
|
|
10
|
+
output?: string | undefined;
|
|
11
|
+
analyzers?: string[] | undefined;
|
|
12
|
+
includeAsc: boolean;
|
|
13
|
+
changedSince?: string | undefined;
|
|
14
|
+
config?: string | undefined;
|
|
15
|
+
badge: boolean;
|
|
16
|
+
saveHistory: boolean;
|
|
17
|
+
}
|
package/src/git/diff.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
export function getChangedFiles(basePath: string, since: string): string[] {
|
|
5
|
+
try {
|
|
6
|
+
const output = execSync(`git diff --name-only ${since}`, {
|
|
7
|
+
cwd: basePath,
|
|
8
|
+
encoding: 'utf-8',
|
|
9
|
+
timeout: 10000,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return output
|
|
13
|
+
.trim()
|
|
14
|
+
.split('\n')
|
|
15
|
+
.filter((f) => f.length > 0)
|
|
16
|
+
.map((f) => path.resolve(basePath, f));
|
|
17
|
+
} catch {
|
|
18
|
+
// If git diff fails (not a git repo, invalid ref, etc.), return empty
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/git/index.ts
ADDED
package/src/git/types.ts
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import type { GuidelineEntry } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Static database of Apple App Store Review Guidelines.
|
|
5
|
+
* Keyed by guideline section number (e.g., "2.5.1").
|
|
6
|
+
*/
|
|
7
|
+
export const GUIDELINES: Record<string, GuidelineEntry> = {
|
|
8
|
+
'1.2': {
|
|
9
|
+
section: '1.2',
|
|
10
|
+
title: 'User Generated Content',
|
|
11
|
+
excerpt:
|
|
12
|
+
'Apps with user-generated content must include a method for filtering objectionable material, a mechanism to report offensive content, and the ability to block abusive users.',
|
|
13
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#user-generated-content',
|
|
14
|
+
category: 'safety',
|
|
15
|
+
severityWeight: 8,
|
|
16
|
+
},
|
|
17
|
+
'2.1': {
|
|
18
|
+
section: '2.1',
|
|
19
|
+
title: 'App Completeness',
|
|
20
|
+
excerpt:
|
|
21
|
+
'Submissions must be final versions with all necessary metadata and URLs fully functional. Apps with placeholder text, empty URLs, or other temporary content will be rejected.',
|
|
22
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#app-completeness',
|
|
23
|
+
category: 'performance',
|
|
24
|
+
severityWeight: 9,
|
|
25
|
+
},
|
|
26
|
+
'2.3': {
|
|
27
|
+
section: '2.3',
|
|
28
|
+
title: 'Accurate Metadata',
|
|
29
|
+
excerpt:
|
|
30
|
+
'Apps must have accurate metadata including descriptions, screenshots, and previews that clearly reflect the app. Do not include placeholder or irrelevant content.',
|
|
31
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#accurate-metadata',
|
|
32
|
+
category: 'performance',
|
|
33
|
+
severityWeight: 7,
|
|
34
|
+
},
|
|
35
|
+
'2.3.7': {
|
|
36
|
+
section: '2.3.7',
|
|
37
|
+
title: 'Accurate Metadata - App Name',
|
|
38
|
+
excerpt:
|
|
39
|
+
'Choose a unique app name, assign relevant keywords, and provide accurate metadata. App names must be limited to 30 characters and should not include prices, terms, or descriptions that are not the name of the app.',
|
|
40
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#accurate-metadata',
|
|
41
|
+
category: 'performance',
|
|
42
|
+
severityWeight: 6,
|
|
43
|
+
},
|
|
44
|
+
'2.4.1': {
|
|
45
|
+
section: '2.4.1',
|
|
46
|
+
title: 'Hardware Compatibility',
|
|
47
|
+
excerpt:
|
|
48
|
+
'Apps should work on all device sizes and orientations declared as supported. Universal apps must support both iPhone and iPad, including Multitasking on iPad.',
|
|
49
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#hardware-compatibility',
|
|
50
|
+
category: 'performance',
|
|
51
|
+
severityWeight: 6,
|
|
52
|
+
},
|
|
53
|
+
'2.5.1': {
|
|
54
|
+
section: '2.5.1',
|
|
55
|
+
title: 'Software Requirements',
|
|
56
|
+
excerpt:
|
|
57
|
+
'Apps must use public APIs and run on the currently shipping OS. Apps that use non-public APIs, private frameworks, or deprecated technologies will be rejected. Apps must support IPv6 networking.',
|
|
58
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#software-requirements',
|
|
59
|
+
category: 'performance',
|
|
60
|
+
severityWeight: 9,
|
|
61
|
+
},
|
|
62
|
+
'2.5.4': {
|
|
63
|
+
section: '2.5.4',
|
|
64
|
+
title: 'Security',
|
|
65
|
+
excerpt:
|
|
66
|
+
'Apps must use appropriate security measures. Apps that attempt to access or modify the device in unauthorized ways, disable security features, or use weak cryptography may be rejected.',
|
|
67
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#software-requirements',
|
|
68
|
+
category: 'performance',
|
|
69
|
+
severityWeight: 8,
|
|
70
|
+
},
|
|
71
|
+
'2.5.6': {
|
|
72
|
+
section: '2.5.6',
|
|
73
|
+
title: 'Background Execution',
|
|
74
|
+
excerpt:
|
|
75
|
+
'Apps that run background processes must use the appropriate background modes and must not misuse background execution to drain battery or consume excessive resources.',
|
|
76
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#software-requirements',
|
|
77
|
+
category: 'performance',
|
|
78
|
+
severityWeight: 5,
|
|
79
|
+
},
|
|
80
|
+
'3.1.1': {
|
|
81
|
+
section: '3.1.1',
|
|
82
|
+
title: 'In-App Purchase',
|
|
83
|
+
excerpt:
|
|
84
|
+
'Apps offering digital content, subscriptions, or premium features must use in-app purchase. Each IAP must have complete metadata, a review screenshot, and accurate pricing.',
|
|
85
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#in-app-purchase',
|
|
86
|
+
category: 'business',
|
|
87
|
+
severityWeight: 9,
|
|
88
|
+
},
|
|
89
|
+
'3.1.2': {
|
|
90
|
+
section: '3.1.2',
|
|
91
|
+
title: 'Subscriptions',
|
|
92
|
+
excerpt:
|
|
93
|
+
'Auto-renewable subscriptions must provide ongoing value. Apps must clearly explain the subscription terms, pricing, and cancellation process before purchase.',
|
|
94
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#subscriptions',
|
|
95
|
+
category: 'business',
|
|
96
|
+
severityWeight: 8,
|
|
97
|
+
},
|
|
98
|
+
'3.2.2': {
|
|
99
|
+
section: '3.2.2',
|
|
100
|
+
title: 'Unacceptable Business Model',
|
|
101
|
+
excerpt:
|
|
102
|
+
'Apps should not create an alternative app store or facilitate distribution of software outside the App Store. Apps cannot offer features that rely solely on third-party purchasing.',
|
|
103
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#other-business-model-issues',
|
|
104
|
+
category: 'business',
|
|
105
|
+
severityWeight: 10,
|
|
106
|
+
},
|
|
107
|
+
'4.0': {
|
|
108
|
+
section: '4.0',
|
|
109
|
+
title: 'Design',
|
|
110
|
+
excerpt:
|
|
111
|
+
'Apps must include a complete set of app icons in all required sizes. Icons must be unique and clearly represent the app without misleading imagery.',
|
|
112
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#design',
|
|
113
|
+
category: 'design',
|
|
114
|
+
severityWeight: 7,
|
|
115
|
+
},
|
|
116
|
+
'4.1': {
|
|
117
|
+
section: '4.1',
|
|
118
|
+
title: 'Copycats',
|
|
119
|
+
excerpt:
|
|
120
|
+
'Apps that are simply copies of another app or are substantially similar in concept, appearance, and functionality to an existing app will be rejected.',
|
|
121
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#copycats',
|
|
122
|
+
category: 'design',
|
|
123
|
+
severityWeight: 10,
|
|
124
|
+
},
|
|
125
|
+
'4.2': {
|
|
126
|
+
section: '4.2',
|
|
127
|
+
title: 'Minimum Functionality',
|
|
128
|
+
excerpt:
|
|
129
|
+
'Your app should include features, content, and UI that elevate it beyond a repackaged website. Apps that are not useful, unique, or app-like may be rejected.',
|
|
130
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#minimum-functionality',
|
|
131
|
+
category: 'design',
|
|
132
|
+
severityWeight: 8,
|
|
133
|
+
},
|
|
134
|
+
'4.6': {
|
|
135
|
+
section: '4.6',
|
|
136
|
+
title: 'Launch Screen',
|
|
137
|
+
excerpt:
|
|
138
|
+
'Apps must include a launch screen (or launch storyboard) that provides a seamless transition into the app. The launch screen should not include advertising or branding beyond a simple logo.',
|
|
139
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#apple-sites-and-services',
|
|
140
|
+
category: 'design',
|
|
141
|
+
severityWeight: 5,
|
|
142
|
+
},
|
|
143
|
+
'5.1.1': {
|
|
144
|
+
section: '5.1.1',
|
|
145
|
+
title: 'Data Collection and Storage',
|
|
146
|
+
excerpt:
|
|
147
|
+
'Apps that collect user or device data must have a privacy policy and must declare all data collection in the privacy manifest. Apps must request user consent before collecting personal data and must include required usage description strings.',
|
|
148
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#data-collection-and-storage',
|
|
149
|
+
category: 'legal',
|
|
150
|
+
severityWeight: 9,
|
|
151
|
+
},
|
|
152
|
+
'5.1.2': {
|
|
153
|
+
section: '5.1.2',
|
|
154
|
+
title: 'Data Use and Sharing',
|
|
155
|
+
excerpt:
|
|
156
|
+
'Apps may not share user data with third parties without user consent. Data collected for one purpose may not be repurposed without consent. Apps must comply with applicable privacy laws.',
|
|
157
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#data-use-and-sharing',
|
|
158
|
+
category: 'legal',
|
|
159
|
+
severityWeight: 9,
|
|
160
|
+
},
|
|
161
|
+
'5.1.4': {
|
|
162
|
+
section: '5.1.4',
|
|
163
|
+
title: 'Kids Category',
|
|
164
|
+
excerpt:
|
|
165
|
+
'Apps in the Kids category must not include third-party advertising or analytics, must not transmit data to third parties, and must comply with applicable children\u2019s privacy laws such as COPPA.',
|
|
166
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#kids',
|
|
167
|
+
category: 'legal',
|
|
168
|
+
severityWeight: 10,
|
|
169
|
+
},
|
|
170
|
+
'5.2.1': {
|
|
171
|
+
section: '5.2.1',
|
|
172
|
+
title: 'Intellectual Property - General',
|
|
173
|
+
excerpt:
|
|
174
|
+
'Apps must not infringe on third-party intellectual property rights. If your app uses copyrighted content, trademarks, or patented technology from others, you must have the necessary rights.',
|
|
175
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#intellectual-property',
|
|
176
|
+
category: 'legal',
|
|
177
|
+
severityWeight: 8,
|
|
178
|
+
},
|
|
179
|
+
'hig-accessibility': {
|
|
180
|
+
section: 'hig-accessibility',
|
|
181
|
+
title: 'Human Interface Guidelines - Accessibility',
|
|
182
|
+
excerpt:
|
|
183
|
+
'Apps should support accessibility features including Dynamic Type, VoiceOver, and sufficient color contrast. All interactive elements should have accessibility labels.',
|
|
184
|
+
url: 'https://developer.apple.com/design/human-interface-guidelines/accessibility',
|
|
185
|
+
category: 'design',
|
|
186
|
+
severityWeight: 5,
|
|
187
|
+
},
|
|
188
|
+
'hig-app-icons': {
|
|
189
|
+
section: 'hig-app-icons',
|
|
190
|
+
title: 'Human Interface Guidelines - App Icons',
|
|
191
|
+
excerpt:
|
|
192
|
+
'Every app must supply app icons in all required sizes. Icons should be simple, recognizable, and consistent across platforms. Avoid including text in icons.',
|
|
193
|
+
url: 'https://developer.apple.com/design/human-interface-guidelines/app-icons',
|
|
194
|
+
category: 'design',
|
|
195
|
+
severityWeight: 6,
|
|
196
|
+
},
|
|
197
|
+
'hig-launch-screens': {
|
|
198
|
+
section: 'hig-launch-screens',
|
|
199
|
+
title: 'Human Interface Guidelines - Launch Screens',
|
|
200
|
+
excerpt:
|
|
201
|
+
'A launch screen appears the moment your app starts and is quickly replaced by the first screen. Design it to be nearly identical to the first screen of your app for a seamless experience.',
|
|
202
|
+
url: 'https://developer.apple.com/design/human-interface-guidelines/launching',
|
|
203
|
+
category: 'design',
|
|
204
|
+
severityWeight: 4,
|
|
205
|
+
},
|
|
206
|
+
'1.4.1': {
|
|
207
|
+
section: '1.4.1',
|
|
208
|
+
title: 'Physical Harm',
|
|
209
|
+
excerpt:
|
|
210
|
+
'Medical apps that provide inaccurate data or information could lead to patient harm. Apps must clearly disclaim that they are not intended to replace professional medical advice.',
|
|
211
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#physical-harm',
|
|
212
|
+
category: 'safety',
|
|
213
|
+
severityWeight: 9,
|
|
214
|
+
},
|
|
215
|
+
'2.3.1': {
|
|
216
|
+
section: '2.3.1',
|
|
217
|
+
title: 'Accurate Metadata - Hidden Features',
|
|
218
|
+
excerpt:
|
|
219
|
+
'Apps should not include hidden or undocumented features. All features must be clearly described in the app metadata and accessible during review.',
|
|
220
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#accurate-metadata',
|
|
221
|
+
category: 'performance',
|
|
222
|
+
severityWeight: 7,
|
|
223
|
+
},
|
|
224
|
+
'3.1.3': {
|
|
225
|
+
section: '3.1.3',
|
|
226
|
+
title: 'Other Purchase Methods',
|
|
227
|
+
excerpt:
|
|
228
|
+
'Apps may not use their own mechanisms to unlock content or functionality, such as license keys, augmented reality markers, QR codes, etc., unless the purchase was made through in-app purchase or the App Store.',
|
|
229
|
+
url: 'https://developer.apple.com/app-store/review/guidelines/#other-purchase-methods',
|
|
230
|
+
category: 'business',
|
|
231
|
+
severityWeight: 8,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Maps issue IDs from analyzers to their corresponding guideline sections.
|
|
237
|
+
* Used by GuidelineMatcher to enrich issues with guideline references.
|
|
238
|
+
*/
|
|
239
|
+
export const ISSUE_GUIDELINE_MAP: Record<string, string[]> = {
|
|
240
|
+
// Code scanner - IPv6 compatibility
|
|
241
|
+
'hardcoded-ipv4': ['2.5.1'],
|
|
242
|
+
|
|
243
|
+
// Private API usage
|
|
244
|
+
'private-underscore-selector': ['2.5.1'],
|
|
245
|
+
'private-class-from-string': ['2.5.1'],
|
|
246
|
+
'private-perform-selector': ['2.5.1'],
|
|
247
|
+
'private-value-for-key': ['2.5.1'],
|
|
248
|
+
'private-dlopen': ['2.5.1'],
|
|
249
|
+
'private-dlsym': ['2.5.1'],
|
|
250
|
+
'private-objc-msgsend': ['2.5.1'],
|
|
251
|
+
'private-iokit': ['2.5.1'],
|
|
252
|
+
'private-statusbar': ['2.5.1'],
|
|
253
|
+
'private-sandbox-escape': ['2.5.1'],
|
|
254
|
+
'private-url-scheme': ['2.5.1'],
|
|
255
|
+
|
|
256
|
+
// Security issues
|
|
257
|
+
'security-md5': ['2.5.4'],
|
|
258
|
+
'security-sha1': ['2.5.4'],
|
|
259
|
+
'security-des': ['2.5.4'],
|
|
260
|
+
'security-ecb-mode': ['2.5.4'],
|
|
261
|
+
'security-userdefaults-sensitive': ['2.5.4'],
|
|
262
|
+
'security-userdefaults-sensitive-set': ['2.5.4'],
|
|
263
|
+
'security-insecure-random': ['2.5.4'],
|
|
264
|
+
'security-keychain-accessible-always': ['2.5.4'],
|
|
265
|
+
'security-keychain-accessible-always-this-device': ['2.5.4'],
|
|
266
|
+
'security-clipboard-sensitive': ['2.5.4'],
|
|
267
|
+
'security-sql-injection': ['2.5.4'],
|
|
268
|
+
'security-logging-sensitive': ['2.5.4'],
|
|
269
|
+
'security-hardcoded-encryption-key': ['2.5.4'],
|
|
270
|
+
'security-webview-js-injection': ['2.5.4'],
|
|
271
|
+
'security-disabled-ssl': ['2.5.4'],
|
|
272
|
+
'insecure-http': ['2.5.4'],
|
|
273
|
+
'ats-allows-arbitrary-loads': ['2.5.4'],
|
|
274
|
+
|
|
275
|
+
// Placeholder / inaccurate metadata
|
|
276
|
+
'placeholder-text': ['2.3'],
|
|
277
|
+
'uiux-placeholder-text': ['2.3'],
|
|
278
|
+
'asc-name-placeholder': ['2.3'],
|
|
279
|
+
'asc-subtitle-placeholder': ['2.3'],
|
|
280
|
+
|
|
281
|
+
// App name metadata
|
|
282
|
+
'asc-name-too-long': ['2.3.7'],
|
|
283
|
+
'asc-missing-name': ['2.3.7'],
|
|
284
|
+
|
|
285
|
+
// Deprecated APIs
|
|
286
|
+
'deprecated-uiwebview': ['2.5.1'],
|
|
287
|
+
'deprecated-uialertview': ['2.5.1'],
|
|
288
|
+
'deprecated-uiactionsheet': ['2.5.1'],
|
|
289
|
+
'deprecated-uipopovercontroller': ['2.5.1'],
|
|
290
|
+
'deprecated-uisearchdisplaycontroller': ['2.5.1'],
|
|
291
|
+
'deprecated-uitableviewrowaction': ['2.5.1'],
|
|
292
|
+
'deprecated-nsurlconnection': ['2.5.1'],
|
|
293
|
+
'deprecated-abaddressbook': ['2.5.1'],
|
|
294
|
+
'deprecated-addressbookui': ['2.5.1'],
|
|
295
|
+
'deprecated-mpmovieplayercontroller': ['2.5.1'],
|
|
296
|
+
'deprecated-mpmovieplayerviewcontroller': ['2.5.1'],
|
|
297
|
+
'deprecated-alassetslibrary': ['2.5.1'],
|
|
298
|
+
'deprecated-uilocalnotification': ['2.5.1'],
|
|
299
|
+
'deprecated-uiusernotificationsettings': ['2.5.1'],
|
|
300
|
+
'deprecated-uiaccelerometer': ['2.5.1'],
|
|
301
|
+
'deprecated-openurl-sync': ['2.5.1'],
|
|
302
|
+
'deprecated-statusbar-style': ['2.5.1'],
|
|
303
|
+
'deprecated-statusbar-hidden': ['2.5.1'],
|
|
304
|
+
'deprecated-statusbar-orientation': ['2.5.1'],
|
|
305
|
+
'deprecated-nsstring-drawing': ['2.5.1'],
|
|
306
|
+
'deprecated-addressbook': ['2.5.1'],
|
|
307
|
+
|
|
308
|
+
// In-App Purchase
|
|
309
|
+
'asc-iap-missing-metadata': ['3.1.1'],
|
|
310
|
+
'asc-iap-action-needed': ['3.1.1'],
|
|
311
|
+
'asc-iap-rejected': ['3.1.1'],
|
|
312
|
+
'asc-iap-no-localizations': ['3.1.1'],
|
|
313
|
+
'asc-iap-missing-name': ['3.1.1'],
|
|
314
|
+
'asc-iap-missing-description': ['3.1.1'],
|
|
315
|
+
'asc-iap-missing-screenshot': ['3.1.1'],
|
|
316
|
+
'asc-iap-screenshot-failed': ['3.1.1'],
|
|
317
|
+
'asc-iaps-not-ready': ['3.1.1'],
|
|
318
|
+
'asc-no-iaps': ['3.1.1'],
|
|
319
|
+
|
|
320
|
+
// Launch screen
|
|
321
|
+
'uiux-no-launch-screen': ['4.6'],
|
|
322
|
+
'missing-launch-screen': ['4.6'],
|
|
323
|
+
|
|
324
|
+
// App icons
|
|
325
|
+
'uiux-no-app-icon': ['4.0'],
|
|
326
|
+
'uiux-missing-appstore-icon': ['4.0'],
|
|
327
|
+
'uiux-missing-iphone-icon': ['4.0'],
|
|
328
|
+
'uiux-missing-ipad-icon': ['4.0'],
|
|
329
|
+
'uiux-invalid-icon-contents': ['4.0'],
|
|
330
|
+
|
|
331
|
+
// Privacy and data collection
|
|
332
|
+
'missing-privacy-manifest': ['5.1.1'],
|
|
333
|
+
'privacy-manifest-not-found': ['5.1.1'],
|
|
334
|
+
'privacy-manifest-parse-error': ['5.1.1'],
|
|
335
|
+
'tracking-no-domains': ['5.1.1'],
|
|
336
|
+
'asc-missing-privacy-policy': ['5.1.1'],
|
|
337
|
+
|
|
338
|
+
// iPad compatibility
|
|
339
|
+
'uiux-ipad-missing-orientations': ['2.4.1'],
|
|
340
|
+
|
|
341
|
+
// Accessibility
|
|
342
|
+
'uiux-no-accessibility-labels': ['2.5.1'],
|
|
343
|
+
'uiux-no-dynamic-type': ['2.5.1'],
|
|
344
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { GuidelineMatcher } from './matcher.js';
|
|
2
|
+
export type { GuidelineEntry, GuidelineCategory, GuidelineMatch, EnrichedIssue } from './types.js';
|
|
3
|
+
export type { EnrichedAnalysisReport } from './matcher.js';
|
|
4
|
+
export { GUIDELINES, ISSUE_GUIDELINE_MAP } from './database.js';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Issue, AnalysisReport, Severity } from '../types/index.js';
|
|
2
|
+
import type { GuidelineEntry, EnrichedIssue } from './types.js';
|
|
3
|
+
import { GUIDELINES, ISSUE_GUIDELINE_MAP } from './database.js';
|
|
4
|
+
|
|
5
|
+
export interface EnrichedAnalysisReport extends AnalysisReport {
|
|
6
|
+
score: number;
|
|
7
|
+
enrichedIssues: EnrichedIssue[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const SEVERITY_MULTIPLIER: Record<Severity, number> = {
|
|
11
|
+
error: 1.0,
|
|
12
|
+
warning: 0.5,
|
|
13
|
+
info: 0.1,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class GuidelineMatcher {
|
|
17
|
+
matchIssue(issue: Issue): GuidelineEntry[] {
|
|
18
|
+
const matches: GuidelineEntry[] = [];
|
|
19
|
+
|
|
20
|
+
// First try ISSUE_GUIDELINE_MAP for the issue id
|
|
21
|
+
const mappedSections = ISSUE_GUIDELINE_MAP[issue.id];
|
|
22
|
+
if (mappedSections) {
|
|
23
|
+
for (const section of mappedSections) {
|
|
24
|
+
const entry = GUIDELINES[section];
|
|
25
|
+
if (entry) {
|
|
26
|
+
matches.push(entry);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Fallback: parse section from guideline string like "Guideline 2.5.4 - Security"
|
|
32
|
+
if (matches.length === 0 && issue.guideline) {
|
|
33
|
+
const sectionMatch = issue.guideline.match(/(\d+\.\d+(?:\.\d+)?)/);
|
|
34
|
+
if (sectionMatch?.[1]) {
|
|
35
|
+
const entry = GUIDELINES[sectionMatch[1]];
|
|
36
|
+
if (entry) {
|
|
37
|
+
matches.push(entry);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return matches;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
enrichReport(report: AnalysisReport): EnrichedAnalysisReport {
|
|
46
|
+
const enrichedIssues: EnrichedIssue[] = [];
|
|
47
|
+
|
|
48
|
+
for (const result of report.results) {
|
|
49
|
+
for (const issue of result.issues) {
|
|
50
|
+
const guidelines = this.matchIssue(issue);
|
|
51
|
+
const primary = guidelines[0];
|
|
52
|
+
const enriched: EnrichedIssue = {
|
|
53
|
+
...issue,
|
|
54
|
+
};
|
|
55
|
+
if (primary) {
|
|
56
|
+
enriched.guidelineUrl = primary.url;
|
|
57
|
+
enriched.guidelineExcerpt = primary.excerpt;
|
|
58
|
+
enriched.severityScore = primary.severityWeight * SEVERITY_MULTIPLIER[issue.severity];
|
|
59
|
+
}
|
|
60
|
+
enrichedIssues.push(enriched);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
...report,
|
|
66
|
+
score: this.calculateScore(report),
|
|
67
|
+
enrichedIssues,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
calculateScore(report: AnalysisReport): number {
|
|
72
|
+
let totalDeduction = 0;
|
|
73
|
+
|
|
74
|
+
for (const result of report.results) {
|
|
75
|
+
for (const issue of result.issues) {
|
|
76
|
+
const guidelines = this.matchIssue(issue);
|
|
77
|
+
const weight = guidelines[0]?.severityWeight ?? 3; // default weight for unmapped issues
|
|
78
|
+
totalDeduction += weight * SEVERITY_MULTIPLIER[issue.severity];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return Math.max(0, Math.min(100, Math.round(100 - totalDeduction)));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Issue } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
export type GuidelineCategory =
|
|
4
|
+
| 'safety'
|
|
5
|
+
| 'performance'
|
|
6
|
+
| 'business'
|
|
7
|
+
| 'design'
|
|
8
|
+
| 'legal';
|
|
9
|
+
|
|
10
|
+
export interface GuidelineEntry {
|
|
11
|
+
section: string;
|
|
12
|
+
title: string;
|
|
13
|
+
excerpt: string;
|
|
14
|
+
url: string;
|
|
15
|
+
category: GuidelineCategory;
|
|
16
|
+
severityWeight: number; // 1-10
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GuidelineMatch {
|
|
20
|
+
issue: Issue;
|
|
21
|
+
guideline: GuidelineEntry;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface EnrichedIssue extends Issue {
|
|
25
|
+
guidelineUrl?: string | undefined;
|
|
26
|
+
guidelineExcerpt?: string | undefined;
|
|
27
|
+
severityScore?: number | undefined;
|
|
28
|
+
}
|