@playcraft/build 0.0.44 → 0.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/base-builder.d.ts +2 -0
  2. package/dist/base-builder.js +44 -0
  3. package/dist/engines/playable-scripts-adapter.d.ts +6 -1
  4. package/dist/engines/playable-scripts-adapter.js +92 -14
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +1 -0
  7. package/dist/platforms/bigo.js +3 -5
  8. package/dist/platforms/inmobi.js +3 -5
  9. package/dist/platforms/liftoff.js +3 -5
  10. package/dist/platforms/mintegral.js +4 -6
  11. package/dist/platforms/snapchat.js +1 -1
  12. package/dist/platforms/tiktok.js +3 -5
  13. package/dist/types.d.ts +13 -0
  14. package/dist/validation/html-validator.d.ts +2 -0
  15. package/dist/validation/html-validator.js +140 -0
  16. package/dist/validation/index.d.ts +8 -0
  17. package/dist/validation/index.js +8 -0
  18. package/dist/validation/package-validator.d.ts +2 -0
  19. package/dist/validation/package-validator.js +198 -0
  20. package/dist/validation/profiles.d.ts +5 -0
  21. package/dist/validation/profiles.js +222 -0
  22. package/dist/validation/reporter.d.ts +4 -0
  23. package/dist/validation/reporter.js +30 -0
  24. package/dist/validation/source-scanner.d.ts +4 -0
  25. package/dist/validation/source-scanner.js +271 -0
  26. package/dist/validation/types.d.ts +112 -0
  27. package/dist/validation/validation-runner.d.ts +5 -0
  28. package/dist/validation/validation-runner.js +96 -0
  29. package/dist/validation/zip-reader.d.ts +17 -0
  30. package/dist/validation/zip-reader.js +84 -0
  31. package/dist/vite/platform-configs.js +7 -7
  32. package/dist/vite-builder.d.ts +1 -0
  33. package/dist/vite-builder.js +43 -0
  34. package/package.json +2 -2
  35. package/dist/analyzers/__tests__/optimization-analyzer.test.js +0 -169
  36. package/dist/templates/__loading__.js +0 -100
  37. package/dist/templates/__modules__.js +0 -47
  38. package/dist/templates/__settings__.template.js +0 -20
  39. package/dist/templates/__start__.js +0 -332
  40. package/dist/templates/index.html +0 -18
  41. package/dist/templates/logo.png +0 -0
  42. package/dist/templates/manifest.json +0 -1
  43. package/dist/templates/patches/cannon.min.js +0 -28
  44. package/dist/templates/patches/lz4.js +0 -10
  45. package/dist/templates/patches/one-page-http-get.js +0 -20
  46. package/dist/templates/patches/one-page-inline-game-scripts.js +0 -52
  47. package/dist/templates/patches/one-page-mraid-resize-canvas.js +0 -46
  48. package/dist/templates/patches/p2.min.js +0 -27
  49. package/dist/templates/patches/playcraft-no-xhr.js +0 -76
  50. package/dist/templates/playcanvas-stable.min.js +0 -16363
  51. package/dist/templates/styles.css +0 -43
  52. /package/dist/{analyzers/__tests__/optimization-analyzer.test.d.ts → validation/types.js} +0 -0
@@ -0,0 +1,271 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { getRulesForStage } from './profiles.js';
4
+ const SOURCE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.html', '.css']);
5
+ const IGNORED_DIRS = new Set([
6
+ '.git',
7
+ '.playcraft-temp',
8
+ 'coverage',
9
+ 'dist',
10
+ 'build',
11
+ 'node_modules',
12
+ 'out',
13
+ ]);
14
+ const COMMON_SOURCE_PATTERNS = [
15
+ {
16
+ ruleId: 'common.redirect.location.forbidden',
17
+ pattern: /\blocation\.(?:href|assign|replace)\b/g,
18
+ message: 'Source contains a direct location redirect.',
19
+ recommendation: 'Move store jumps behind the channel CTA API and an explicit user action.',
20
+ severity: 'warning',
21
+ confidence: 'medium',
22
+ },
23
+ {
24
+ ruleId: 'common.redirect.window-open.review',
25
+ pattern: /\bwindow\.open\s*\(/g,
26
+ message: 'Source contains window.open().',
27
+ recommendation: 'Confirm the call is only triggered by an explicit CTA click.',
28
+ severity: 'warning',
29
+ confidence: 'medium',
30
+ },
31
+ {
32
+ ruleId: 'common.network.fetch.review',
33
+ pattern: /\b(?:fetch|XMLHttpRequest|WebSocket|EventSource)\b/g,
34
+ message: 'Source contains a runtime network API.',
35
+ recommendation: 'Remove runtime network dependency unless the channel allows analytics-only calls.',
36
+ severity: 'warning',
37
+ confidence: 'medium',
38
+ },
39
+ {
40
+ ruleId: 'common.audio.autoplay.review',
41
+ pattern: /<audio\b[^>]*\bautoplay\b|new\s+Audio\s*\([^)]*\)\.play\s*\(|AudioContext\s*\([^)]*\).*?resume\s*\(/gs,
42
+ message: 'Source contains a possible audio autoplay pattern.',
43
+ recommendation: 'Start audio only after user interaction and stop it when the ad is not viewable.',
44
+ severity: 'warning',
45
+ confidence: 'low',
46
+ },
47
+ {
48
+ ruleId: 'common.layout.fixed-size.review',
49
+ pattern: /\b(?:canvas|body|#?app|#?root)[^{]{0,80}\{[^}]*\b(?:width|height)\s*:\s*\d{3,4}px/gs,
50
+ message: 'Source contains a possible fixed-size layout.',
51
+ recommendation: 'Use responsive sizing so the playable can adapt to mobile viewports.',
52
+ severity: 'warning',
53
+ confidence: 'low',
54
+ },
55
+ ];
56
+ const CHANNEL_SOURCE_PATTERNS = {
57
+ unity: [
58
+ {
59
+ ruleId: 'unity.mraid.viewable-change.required',
60
+ pattern: /viewableChange/g,
61
+ message: 'Unity requires waiting for MRAID viewableChange before starting playable content.',
62
+ recommendation: 'Add a viewableChange listener and start gameplay only after the ad is viewable.',
63
+ severity: 'error',
64
+ confidence: 'high',
65
+ },
66
+ ],
67
+ mintegral: [
68
+ {
69
+ ruleId: 'mintegral.lifecycle.sdk-finish.required',
70
+ pattern: /\bsdk\.finish\s*\(/g,
71
+ message: 'Mintegral requires sdk.finish() after gameplay ends.',
72
+ recommendation: 'Call sdk.finish() when the win or end state is reached.',
73
+ severity: 'error',
74
+ confidence: 'high',
75
+ },
76
+ ],
77
+ bigo: [
78
+ {
79
+ ruleId: 'bigo.lifecycle.game-end.required',
80
+ pattern: /window\.BGY_MRAID\.gameEnd\s*\(/g,
81
+ message: 'BIGO requires window.BGY_MRAID.gameEnd() when gameplay ends.',
82
+ recommendation: 'Call window.BGY_MRAID.gameEnd() at the end of gameplay.',
83
+ severity: 'error',
84
+ confidence: 'high',
85
+ },
86
+ ],
87
+ };
88
+ export async function scanSourceForCompliance(context) {
89
+ const sourceFiles = await collectSourceFiles(context.projectRoot, context.sourceGlobs);
90
+ return scanSourceFilesForChannel(context, sourceFiles, true);
91
+ }
92
+ export async function scanSourceForChannels(context, channels) {
93
+ const sourceFiles = await collectSourceFiles(context.projectRoot, context.sourceGlobs);
94
+ const results = [];
95
+ for (const filePath of sourceFiles) {
96
+ let content;
97
+ try {
98
+ content = await fs.readFile(filePath, 'utf-8');
99
+ }
100
+ catch {
101
+ continue;
102
+ }
103
+ for (const pattern of COMMON_SOURCE_PATTERNS) {
104
+ results.push(...evaluateSourcePatternForChannel(context, 'common', filePath, content, pattern));
105
+ }
106
+ }
107
+ for (const channel of unique(channels)) {
108
+ results.push(...await scanSourceFilesForChannel({ ...context, channel }, sourceFiles, false));
109
+ }
110
+ return results;
111
+ }
112
+ async function scanSourceFilesForChannel(context, sourceFiles, includeCommonPatterns) {
113
+ const results = [];
114
+ const requiredPatterns = CHANNEL_SOURCE_PATTERNS[context.channel] ?? [];
115
+ const matchedRequiredRuleIds = new Set();
116
+ for (const filePath of sourceFiles) {
117
+ let content;
118
+ try {
119
+ content = await fs.readFile(filePath, 'utf-8');
120
+ }
121
+ catch {
122
+ continue;
123
+ }
124
+ if (includeCommonPatterns) {
125
+ for (const pattern of COMMON_SOURCE_PATTERNS) {
126
+ results.push(...evaluateSourcePattern(context, filePath, content, pattern, false));
127
+ }
128
+ }
129
+ for (const pattern of requiredPatterns) {
130
+ if (matchedRequiredRuleIds.has(pattern.ruleId)) {
131
+ continue;
132
+ }
133
+ const match = content.match(pattern.pattern);
134
+ if (match) {
135
+ matchedRequiredRuleIds.add(pattern.ruleId);
136
+ results.push({
137
+ ruleId: pattern.ruleId,
138
+ channel: context.channel,
139
+ stage: 'prebuild-source',
140
+ severity: pattern.severity,
141
+ status: 'pass',
142
+ message: `${pattern.message}: passed`,
143
+ recommendation: pattern.recommendation,
144
+ file: filePath,
145
+ line: lineOfIndex(content, content.search(pattern.pattern)),
146
+ evidence: trimEvidence(match[0]),
147
+ owner: ownerForContext(context),
148
+ confidence: pattern.confidence,
149
+ });
150
+ }
151
+ }
152
+ }
153
+ results.push(...missingRequiredPatternResults(context, matchedRequiredRuleIds));
154
+ return results;
155
+ }
156
+ function evaluateSourcePatternForChannel(context, channel, filePath, content, sourcePattern) {
157
+ const matches = Array.from(content.matchAll(sourcePattern.pattern));
158
+ return matches.slice(0, 10).map((match) => ({
159
+ ruleId: sourcePattern.ruleId,
160
+ channel,
161
+ stage: 'prebuild-source',
162
+ severity: sourcePattern.severity,
163
+ status: sourcePattern.severity === 'error' ? 'fail' : 'warning',
164
+ message: sourcePattern.message,
165
+ recommendation: sourcePattern.recommendation,
166
+ file: filePath,
167
+ line: lineOfIndex(content, match.index ?? 0),
168
+ evidence: trimEvidence(match[0]),
169
+ owner: ownerForContext(context),
170
+ confidence: sourcePattern.confidence,
171
+ }));
172
+ }
173
+ async function collectSourceFiles(projectRoot, explicitGlobs) {
174
+ if (explicitGlobs && explicitGlobs.length > 0) {
175
+ const files = [];
176
+ for (const item of explicitGlobs) {
177
+ const fullPath = path.isAbsolute(item) ? item : path.join(projectRoot, item);
178
+ try {
179
+ const stat = await fs.stat(fullPath);
180
+ if (stat.isFile() && SOURCE_EXTENSIONS.has(path.extname(fullPath).toLowerCase())) {
181
+ files.push(fullPath);
182
+ }
183
+ else if (stat.isDirectory()) {
184
+ files.push(...await walkSourceFiles(fullPath));
185
+ }
186
+ }
187
+ catch {
188
+ // Ignore missing explicit entries; the report stays focused on detected evidence.
189
+ }
190
+ }
191
+ return unique(files);
192
+ }
193
+ return walkSourceFiles(projectRoot);
194
+ }
195
+ async function walkSourceFiles(dir) {
196
+ const files = [];
197
+ let entries;
198
+ try {
199
+ entries = await fs.readdir(dir, { withFileTypes: true });
200
+ }
201
+ catch {
202
+ return files;
203
+ }
204
+ for (const entry of entries) {
205
+ const fullPath = path.join(dir, entry.name);
206
+ if (entry.isDirectory()) {
207
+ if (!IGNORED_DIRS.has(entry.name)) {
208
+ files.push(...await walkSourceFiles(fullPath));
209
+ }
210
+ continue;
211
+ }
212
+ if (entry.isFile() && SOURCE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
213
+ files.push(fullPath);
214
+ }
215
+ }
216
+ return files;
217
+ }
218
+ function evaluateSourcePattern(context, filePath, content, sourcePattern, requiredPresence) {
219
+ const matches = Array.from(content.matchAll(sourcePattern.pattern));
220
+ if (requiredPresence) {
221
+ return [];
222
+ }
223
+ return matches.slice(0, 10).map((match) => ({
224
+ ruleId: sourcePattern.ruleId,
225
+ channel: context.channel,
226
+ stage: 'prebuild-source',
227
+ severity: sourcePattern.severity,
228
+ status: sourcePattern.severity === 'error' ? 'fail' : 'warning',
229
+ message: sourcePattern.message,
230
+ recommendation: sourcePattern.recommendation,
231
+ file: filePath,
232
+ line: lineOfIndex(content, match.index ?? 0),
233
+ evidence: trimEvidence(match[0]),
234
+ owner: ownerForContext(context),
235
+ confidence: sourcePattern.confidence,
236
+ }));
237
+ }
238
+ function missingRequiredPatternResults(context, matchedRequiredRuleIds) {
239
+ const channelPatterns = CHANNEL_SOURCE_PATTERNS[context.channel] ?? [];
240
+ const results = [];
241
+ const sourceRuleIds = new Set(getRulesForStage(context.channel, 'prebuild-source').map((rule) => rule.id));
242
+ for (const pattern of channelPatterns) {
243
+ if (matchedRequiredRuleIds.has(pattern.ruleId) || !sourceRuleIds.has(pattern.ruleId)) {
244
+ continue;
245
+ }
246
+ results.push({
247
+ ruleId: pattern.ruleId,
248
+ channel: context.channel,
249
+ stage: 'prebuild-source',
250
+ severity: pattern.severity,
251
+ status: pattern.severity === 'error' ? 'fail' : 'warning',
252
+ message: pattern.message,
253
+ recommendation: pattern.recommendation,
254
+ owner: ownerForContext(context),
255
+ confidence: pattern.confidence,
256
+ });
257
+ }
258
+ return results;
259
+ }
260
+ function ownerForContext(context) {
261
+ return context.buildChain === 'devkit' ? 'devkit' : 'playcraft-build';
262
+ }
263
+ function lineOfIndex(content, index) {
264
+ return content.slice(0, index).split(/\r?\n/).length;
265
+ }
266
+ function trimEvidence(value) {
267
+ return value.replace(/\s+/g, ' ').trim().slice(0, 200);
268
+ }
269
+ function unique(items) {
270
+ return Array.from(new Set(items));
271
+ }
@@ -0,0 +1,112 @@
1
+ import type { BuildTool, EngineType, OutputFormat, Platform } from '../types.js';
2
+ export type ValidationStage = 'prebuild-source' | 'base-output' | 'html-artifact' | 'delivery-package' | 'runtime-manual' | 'external-validator';
3
+ export type RuleSeverity = 'error' | 'warning' | 'info';
4
+ export type RuleStatus = 'pass' | 'fail' | 'warning' | 'manual' | 'external' | 'not-applicable';
5
+ export type RuleConfidence = 'high' | 'medium' | 'low';
6
+ export type RuleOwner = 'playcraft-build' | 'playcraft-delivery' | 'devkit' | 'manual' | 'external';
7
+ export type DeliveryMode = 'single-html' | 'zip' | 'html-backed-zip' | 'fragment-assets' | 'mixed';
8
+ export type BuildChain = 'generic' | 'devkit' | 'playcanvas-native';
9
+ export type EvidenceType = 'file' | 'pattern' | 'package' | 'runtime' | 'external';
10
+ export type AutoCheck = {
11
+ kind: 'max-size';
12
+ maxBytes: number;
13
+ } | {
14
+ kind: 'external-resources';
15
+ allowedPatterns?: string[];
16
+ } | {
17
+ kind: 'required-pattern';
18
+ pattern: string;
19
+ flags?: string;
20
+ evidence?: string;
21
+ } | {
22
+ kind: 'forbidden-pattern';
23
+ pattern: string;
24
+ flags?: string;
25
+ evidence?: string;
26
+ } | {
27
+ kind: 'single-html';
28
+ };
29
+ export interface ValidationRule {
30
+ id: string;
31
+ title: string;
32
+ channel: Platform | 'common';
33
+ stage: ValidationStage;
34
+ severity: RuleSeverity;
35
+ owner: RuleOwner;
36
+ detectable: boolean;
37
+ confidence: RuleConfidence;
38
+ description: string;
39
+ recommendation: string;
40
+ evidenceType: EvidenceType;
41
+ source: string;
42
+ check?: AutoCheck;
43
+ }
44
+ export interface ChannelRuleProfile {
45
+ channel: Platform;
46
+ displayName: string;
47
+ defaultStages: ValidationStage[];
48
+ optionalStages: ValidationStage[];
49
+ deliveryMode: DeliveryMode;
50
+ internalBuildFormat: OutputFormat | 'delegated';
51
+ deliveryPackageFormat?: OutputFormat | 'fragment-assets' | 'mixed';
52
+ devkitOwned?: boolean;
53
+ rules: ValidationRule[];
54
+ }
55
+ export interface ValidationContext {
56
+ projectRoot: string;
57
+ channel: Platform;
58
+ engine: EngineType | 'unknown';
59
+ buildTool?: BuildTool;
60
+ buildChain: BuildChain;
61
+ htmlPath?: string;
62
+ packagePath?: string;
63
+ outputDir?: string;
64
+ sourceGlobs?: string[];
65
+ metadata?: Record<string, unknown>;
66
+ }
67
+ export interface ManualChecklistItem {
68
+ ruleId: string;
69
+ channel: Platform | 'common';
70
+ stage: ValidationStage;
71
+ message: string;
72
+ recommendation: string;
73
+ owner: RuleOwner;
74
+ }
75
+ export interface ExternalChecklistItem {
76
+ ruleId: string;
77
+ channel: Platform | 'common';
78
+ stage: ValidationStage;
79
+ message: string;
80
+ recommendation: string;
81
+ owner: RuleOwner;
82
+ }
83
+ export interface ValidationResult {
84
+ ruleId: string;
85
+ channel: Platform | 'common';
86
+ stage: ValidationStage;
87
+ severity: RuleSeverity;
88
+ status: RuleStatus;
89
+ message: string;
90
+ recommendation: string;
91
+ file?: string;
92
+ line?: number;
93
+ evidence?: string;
94
+ owner: RuleOwner;
95
+ confidence: RuleConfidence;
96
+ }
97
+ export interface ValidationSummary {
98
+ status: 'pass' | 'failed' | 'warning' | 'manual-required';
99
+ errorCount: number;
100
+ warningCount: number;
101
+ manualCount: number;
102
+ externalCount: number;
103
+ }
104
+ export interface ValidationReport {
105
+ version: string;
106
+ channel: Platform;
107
+ buildChain: BuildChain;
108
+ summary: ValidationSummary;
109
+ results: ValidationResult[];
110
+ manualChecklist: ManualChecklistItem[];
111
+ externalChecklist: ExternalChecklistItem[];
112
+ }
@@ -0,0 +1,5 @@
1
+ import type { Platform } from '../types.js';
2
+ import type { ValidationContext, ValidationReport, ValidationStage } from './types.js';
3
+ export declare const VALIDATION_REPORT_VERSION = "0.1.0";
4
+ export declare function runPlayableValidation(context: ValidationContext, stages?: ValidationStage[]): Promise<ValidationReport>;
5
+ export declare function runPlayableSourceValidationForChannels(context: Omit<ValidationContext, 'channel'>, channels: Platform[]): Promise<ValidationReport>;
@@ -0,0 +1,96 @@
1
+ import { getChannelRuleProfile } from './profiles.js';
2
+ import { validateHtmlArtifact } from './html-validator.js';
3
+ import { validateDeliveryPackage } from './package-validator.js';
4
+ import { scanSourceForChannels, scanSourceForCompliance } from './source-scanner.js';
5
+ export const VALIDATION_REPORT_VERSION = '0.1.0';
6
+ export async function runPlayableValidation(context, stages) {
7
+ const profile = getChannelRuleProfile(context.channel);
8
+ const defaultStages = [...profile.defaultStages];
9
+ if (context.packagePath && profile.optionalStages.includes('delivery-package')) {
10
+ defaultStages.push('delivery-package');
11
+ }
12
+ const enabledStages = new Set(stages ?? defaultStages);
13
+ const results = [];
14
+ if (enabledStages.has('prebuild-source')) {
15
+ results.push(...await scanSourceForCompliance(context));
16
+ }
17
+ if (enabledStages.has('html-artifact')) {
18
+ results.push(...await validateHtmlArtifact(context, profile));
19
+ }
20
+ if (enabledStages.has('delivery-package')) {
21
+ results.push(...await validateDeliveryPackage(context, profile));
22
+ }
23
+ const manualChecklist = buildManualChecklist(profile.rules, enabledStages);
24
+ const externalChecklist = buildExternalChecklist(profile.rules, enabledStages);
25
+ return {
26
+ version: VALIDATION_REPORT_VERSION,
27
+ channel: context.channel,
28
+ buildChain: context.buildChain,
29
+ summary: summarize(results, manualChecklist, externalChecklist),
30
+ results,
31
+ manualChecklist,
32
+ externalChecklist,
33
+ };
34
+ }
35
+ export async function runPlayableSourceValidationForChannels(context, channels) {
36
+ const uniqueChannels = Array.from(new Set(channels));
37
+ const results = await scanSourceForChannels(context, uniqueChannels);
38
+ return {
39
+ version: VALIDATION_REPORT_VERSION,
40
+ channel: uniqueChannels[0] ?? 'playcraft',
41
+ buildChain: context.buildChain,
42
+ summary: summarize(results, [], []),
43
+ results,
44
+ manualChecklist: [],
45
+ externalChecklist: [],
46
+ };
47
+ }
48
+ function buildManualChecklist(rules, enabledStages) {
49
+ return rules
50
+ .filter((rule) => rule.owner === 'manual')
51
+ .filter((rule) => enabledStages.has(rule.stage) || rule.stage === 'runtime-manual')
52
+ .map((rule) => ({
53
+ ruleId: rule.id,
54
+ channel: rule.channel,
55
+ stage: rule.stage,
56
+ message: rule.title,
57
+ recommendation: rule.recommendation,
58
+ owner: rule.owner,
59
+ }));
60
+ }
61
+ function buildExternalChecklist(rules, enabledStages) {
62
+ return rules
63
+ .filter((rule) => rule.owner === 'external')
64
+ .filter((rule) => enabledStages.has(rule.stage) || rule.stage === 'external-validator')
65
+ .map((rule) => ({
66
+ ruleId: rule.id,
67
+ channel: rule.channel,
68
+ stage: rule.stage,
69
+ message: rule.title,
70
+ recommendation: rule.recommendation,
71
+ owner: rule.owner,
72
+ }));
73
+ }
74
+ function summarize(results, manualChecklist, externalChecklist) {
75
+ const errorCount = results.filter((result) => result.status === 'fail').length;
76
+ const warningCount = results.filter((result) => result.status === 'warning').length;
77
+ const manualCount = manualChecklist.length;
78
+ const externalCount = externalChecklist.length;
79
+ let status = 'pass';
80
+ if (errorCount > 0) {
81
+ status = 'failed';
82
+ }
83
+ else if (warningCount > 0) {
84
+ status = 'warning';
85
+ }
86
+ else if (manualCount > 0 || externalCount > 0) {
87
+ status = 'manual-required';
88
+ }
89
+ return {
90
+ status,
91
+ errorCount,
92
+ warningCount,
93
+ manualCount,
94
+ externalCount,
95
+ };
96
+ }
@@ -0,0 +1,17 @@
1
+ export interface ZipEntryInfo {
2
+ path: string;
3
+ compressedSize: number;
4
+ uncompressedSize: number;
5
+ compressionMethod: number;
6
+ isDirectory: boolean;
7
+ }
8
+ export declare class SimpleZipReader {
9
+ private readonly buffer;
10
+ private entriesCache?;
11
+ constructor(buffer: Buffer);
12
+ listEntries(): ZipEntryInfo[];
13
+ readEntryText(entryPath: string): string | null;
14
+ private readCentralDirectory;
15
+ private readEntryData;
16
+ private findEndOfCentralDirectory;
17
+ }
@@ -0,0 +1,84 @@
1
+ import { inflateRawSync } from 'zlib';
2
+ export class SimpleZipReader {
3
+ constructor(buffer) {
4
+ this.buffer = buffer;
5
+ }
6
+ listEntries() {
7
+ return this.readCentralDirectory().map(({ localHeaderOffset: _offset, ...entry }) => entry);
8
+ }
9
+ readEntryText(entryPath) {
10
+ const entry = this.readCentralDirectory().find((item) => item.path === entryPath);
11
+ if (!entry || entry.isDirectory)
12
+ return null;
13
+ const data = this.readEntryData(entry);
14
+ return data.toString('utf-8');
15
+ }
16
+ readCentralDirectory() {
17
+ if (this.entriesCache)
18
+ return this.entriesCache;
19
+ const eocdOffset = this.findEndOfCentralDirectory();
20
+ if (eocdOffset < 0) {
21
+ throw new Error('Invalid ZIP: end of central directory not found');
22
+ }
23
+ const totalEntries = this.buffer.readUInt16LE(eocdOffset + 10);
24
+ const centralDirectoryOffset = this.buffer.readUInt32LE(eocdOffset + 16);
25
+ const entries = [];
26
+ let offset = centralDirectoryOffset;
27
+ for (let i = 0; i < totalEntries; i++) {
28
+ const signature = this.buffer.readUInt32LE(offset);
29
+ if (signature !== 0x02014b50) {
30
+ throw new Error(`Invalid ZIP: bad central directory signature at ${offset}`);
31
+ }
32
+ const compressionMethod = this.buffer.readUInt16LE(offset + 10);
33
+ const compressedSize = this.buffer.readUInt32LE(offset + 20);
34
+ const uncompressedSize = this.buffer.readUInt32LE(offset + 24);
35
+ const fileNameLength = this.buffer.readUInt16LE(offset + 28);
36
+ const extraLength = this.buffer.readUInt16LE(offset + 30);
37
+ const commentLength = this.buffer.readUInt16LE(offset + 32);
38
+ const localHeaderOffset = this.buffer.readUInt32LE(offset + 42);
39
+ const fileNameStart = offset + 46;
40
+ const entryPath = this.buffer
41
+ .subarray(fileNameStart, fileNameStart + fileNameLength)
42
+ .toString('utf-8')
43
+ .replace(/\\/g, '/');
44
+ entries.push({
45
+ path: entryPath,
46
+ compressedSize,
47
+ uncompressedSize,
48
+ compressionMethod,
49
+ localHeaderOffset,
50
+ isDirectory: entryPath.endsWith('/'),
51
+ });
52
+ offset = fileNameStart + fileNameLength + extraLength + commentLength;
53
+ }
54
+ this.entriesCache = entries;
55
+ return entries;
56
+ }
57
+ readEntryData(entry) {
58
+ const offset = entry.localHeaderOffset;
59
+ const signature = this.buffer.readUInt32LE(offset);
60
+ if (signature !== 0x04034b50) {
61
+ throw new Error(`Invalid ZIP: bad local header signature at ${offset}`);
62
+ }
63
+ const fileNameLength = this.buffer.readUInt16LE(offset + 26);
64
+ const extraLength = this.buffer.readUInt16LE(offset + 28);
65
+ const dataStart = offset + 30 + fileNameLength + extraLength;
66
+ const compressed = this.buffer.subarray(dataStart, dataStart + entry.compressedSize);
67
+ if (entry.compressionMethod === 0) {
68
+ return Buffer.from(compressed);
69
+ }
70
+ if (entry.compressionMethod === 8) {
71
+ return inflateRawSync(compressed);
72
+ }
73
+ throw new Error(`Unsupported ZIP compression method: ${entry.compressionMethod}`);
74
+ }
75
+ findEndOfCentralDirectory() {
76
+ const minOffset = Math.max(0, this.buffer.length - 0xffff - 22);
77
+ for (let offset = this.buffer.length - 22; offset >= minOffset; offset--) {
78
+ if (this.buffer.readUInt32LE(offset) === 0x06054b50) {
79
+ return offset;
80
+ }
81
+ }
82
+ return -1;
83
+ }
84
+ }
@@ -67,7 +67,7 @@ export const PLATFORM_CONFIGS = {
67
67
  },
68
68
  snapchat: {
69
69
  sizeLimit: 5 * 1024 * 1024, // 5MB
70
- outputFormat: 'zip',
70
+ outputFormat: 'html',
71
71
  minifyCSS: true,
72
72
  minifyJS: true,
73
73
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -185,7 +185,7 @@ export const PLATFORM_CONFIGS = {
185
185
  },
186
186
  tiktok: {
187
187
  sizeLimit: 5 * 1024 * 1024, // 5MB
188
- outputFormat: 'zip',
188
+ outputFormat: 'html',
189
189
  minifyCSS: true,
190
190
  minifyJS: true,
191
191
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -243,7 +243,7 @@ export const PLATFORM_CONFIGS = {
243
243
  },
244
244
  liftoff: {
245
245
  sizeLimit: 5 * 1024 * 1024, // 5MB
246
- outputFormat: 'zip',
246
+ outputFormat: 'html',
247
247
  minifyCSS: true,
248
248
  minifyJS: true,
249
249
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -301,7 +301,7 @@ export const PLATFORM_CONFIGS = {
301
301
  },
302
302
  bigo: {
303
303
  sizeLimit: 5 * 1024 * 1024, // 5MB
304
- outputFormat: 'zip',
304
+ outputFormat: 'html',
305
305
  minifyCSS: true,
306
306
  minifyJS: true,
307
307
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -329,7 +329,7 @@ export const PLATFORM_CONFIGS = {
329
329
  },
330
330
  inmobi: {
331
331
  sizeLimit: 5 * 1024 * 1024, // 5MB
332
- outputFormat: 'zip',
332
+ outputFormat: 'html',
333
333
  minifyCSS: true,
334
334
  minifyJS: true,
335
335
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -416,8 +416,8 @@ export const PLATFORM_CONFIGS = {
416
416
  },
417
417
  },
418
418
  mintegral: {
419
- sizeLimit: 5 * 1024 * 1024, // 5MB (ZIP)
420
- outputFormat: 'zip',
419
+ sizeLimit: 5 * 1024 * 1024, // 5MB
420
+ outputFormat: 'html',
421
421
  minifyCSS: true,
422
422
  minifyJS: true,
423
423
  compressImages: false, // 禁用图片压缩,原图输出效果更好
@@ -82,5 +82,6 @@ export declare class ViteBuilder {
82
82
  /**
83
83
  * 格式化字节数
84
84
  */
85
+ private runComplianceValidation;
85
86
  private formatBytes;
86
87
  }