@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
@@ -4,6 +4,7 @@ import path from 'path';
4
4
  import { ViteConfigBuilder } from './vite/config-builder.js';
5
5
  import { GenericViteConfigBuilder } from './vite/config-builder-generic.js';
6
6
  import { PLATFORM_CONFIGS } from './vite/platform-configs.js';
7
+ import { formatValidationSummary, hasBlockingValidationErrors, runPlayableValidation, saveValidationReport, } from './validation/index.js';
7
8
  /**
8
9
  * Vite 构建器 - 使用 Vite 构建 Playable Ads
9
10
  *
@@ -90,6 +91,7 @@ export class ViteBuilder {
90
91
  // 6. 验证输出大小
91
92
  const outputPath = this.getOutputPath();
92
93
  await this.validateSize(outputPath);
94
+ await this.runComplianceValidation(outputPath, metadata);
93
95
  // 7. 生成报告
94
96
  this.generateReport(outputPath);
95
97
  return outputPath;
@@ -350,6 +352,47 @@ export class ViteBuilder {
350
352
  /**
351
353
  * 格式化字节数
352
354
  */
355
+ async runComplianceValidation(outputPath, metadata) {
356
+ const validationOptions = this.options.validation;
357
+ if (validationOptions?.enabled === false) {
358
+ return;
359
+ }
360
+ if (this.engine !== 'phaser') {
361
+ return;
362
+ }
363
+ const isZip = outputPath.toLowerCase().endsWith('.zip');
364
+ const stages = validationOptions?.stages
365
+ ?? (isZip ? ['delivery-package'] : ['html-artifact']);
366
+ const report = await runPlayableValidation({
367
+ projectRoot: this.baseBuildDir,
368
+ channel: this.options.platform,
369
+ engine: this.engine,
370
+ buildTool: metadata?.buildTool,
371
+ buildChain: metadata?.buildTool === 'playable-scripts'
372
+ ? 'devkit'
373
+ : metadata?.buildTool === 'playcanvas-native'
374
+ ? 'playcanvas-native'
375
+ : 'generic',
376
+ htmlPath: isZip ? undefined : outputPath,
377
+ packagePath: isZip ? outputPath : undefined,
378
+ outputDir: this.options.outputDir,
379
+ sourceGlobs: validationOptions?.sourceGlobs,
380
+ metadata: metadata ? { ...metadata } : undefined,
381
+ }, stages);
382
+ const summary = formatValidationSummary(report);
383
+ if (report.summary.status === 'pass') {
384
+ console.log(summary);
385
+ }
386
+ else {
387
+ console.warn(summary);
388
+ }
389
+ if (validationOptions?.reportPath) {
390
+ await saveValidationReport(report, validationOptions.reportPath);
391
+ }
392
+ if (validationOptions?.failOnError && hasBlockingValidationErrors(report)) {
393
+ throw new Error(`Playable validation failed for ${this.options.platform}`);
394
+ }
395
+ }
353
396
  formatBytes(bytes) {
354
397
  if (bytes === 0)
355
398
  return '0 Bytes';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/build",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,7 +32,7 @@
32
32
  "scripts": {
33
33
  "dev": "tsc -w",
34
34
  "build": "tsc",
35
- "release": "node scripts/release.js"
35
+ "release": "node ../../scripts/release-npm.mjs --packages=build --skip-install --skip-tests --skip-git-push"
36
36
  },
37
37
  "dependencies": {
38
38
  "@playcraft/ads-tracking": "^1.0.0",
@@ -1,169 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { OptimizationAnalyzer } from '../optimization-analyzer.js';
3
- describe('OptimizationAnalyzer', () => {
4
- it('should detect large uncompressed files', () => {
5
- const state = {
6
- version: '1.0.0',
7
- buildTime: Date.now(),
8
- assets: {
9
- 'large-file': {
10
- id: 'large-file',
11
- originalName: 'large-file.js',
12
- originalPath: '/path/to/large-file.js',
13
- originalSize: 200 * 1024, // 200KB
14
- finalSize: 195 * 1024, // 只压缩了 5KB
15
- totalCompressionRatio: 0.025, // 2.5% 压缩率
16
- type: 'script',
17
- processingHistory: [
18
- {
19
- stage: 'base-build',
20
- name: 'large-file.js',
21
- size: 195 * 1024,
22
- optimizations: [],
23
- },
24
- ],
25
- },
26
- },
27
- stages: [],
28
- };
29
- const analyzer = new OptimizationAnalyzer(state);
30
- const result = analyzer.analyze();
31
- expect(result.totalSuggestions).toBeGreaterThan(0);
32
- expect(result.suggestions.some(s => s.type === 'large-uncompressed')).toBe(true);
33
- });
34
- it('should detect PNG images that can be converted to WebP', () => {
35
- const state = {
36
- version: '1.0.0',
37
- buildTime: Date.now(),
38
- assets: {
39
- 'image': {
40
- id: 'image',
41
- originalName: 'image.png',
42
- originalPath: '/path/to/image.png',
43
- originalSize: 100 * 1024, // 100KB
44
- finalSize: 100 * 1024,
45
- totalCompressionRatio: 0,
46
- type: 'texture',
47
- processingHistory: [
48
- {
49
- stage: 'base-build',
50
- name: 'image.png',
51
- size: 100 * 1024,
52
- optimizations: [],
53
- },
54
- ],
55
- },
56
- },
57
- stages: [],
58
- };
59
- const analyzer = new OptimizationAnalyzer(state);
60
- const result = analyzer.analyze();
61
- expect(result.suggestions.some(s => s.type === 'format-conversion')).toBe(true);
62
- });
63
- it('should detect missing minification', () => {
64
- const state = {
65
- version: '1.0.0',
66
- buildTime: Date.now(),
67
- assets: {
68
- 'script': {
69
- id: 'script',
70
- originalName: 'script.js',
71
- originalPath: '/path/to/script.js',
72
- originalSize: 50 * 1024, // 50KB
73
- finalSize: 50 * 1024,
74
- totalCompressionRatio: 0,
75
- type: 'script',
76
- processingHistory: [
77
- {
78
- stage: 'base-build',
79
- name: 'script.js',
80
- size: 50 * 1024,
81
- optimizations: [], // 没有 minify
82
- },
83
- ],
84
- },
85
- },
86
- stages: [],
87
- };
88
- const analyzer = new OptimizationAnalyzer(state);
89
- const result = analyzer.analyze();
90
- expect(result.suggestions.some(s => s.type === 'enable-minify')).toBe(true);
91
- });
92
- it('should group suggestions by severity', () => {
93
- const state = {
94
- version: '1.0.0',
95
- buildTime: Date.now(),
96
- assets: {
97
- 'large-uncompressed': {
98
- id: 'large-uncompressed',
99
- originalName: 'large.js',
100
- originalPath: '/path/to/large.js',
101
- originalSize: 200 * 1024,
102
- finalSize: 195 * 1024,
103
- totalCompressionRatio: 0.025,
104
- type: 'script',
105
- processingHistory: [
106
- {
107
- stage: 'base-build',
108
- name: 'large.js',
109
- size: 195 * 1024,
110
- optimizations: [],
111
- },
112
- ],
113
- },
114
- 'png-image': {
115
- id: 'png-image',
116
- originalName: 'image.png',
117
- originalPath: '/path/to/image.png',
118
- originalSize: 100 * 1024,
119
- finalSize: 100 * 1024,
120
- totalCompressionRatio: 0,
121
- type: 'texture',
122
- processingHistory: [
123
- {
124
- stage: 'base-build',
125
- name: 'image.png',
126
- size: 100 * 1024,
127
- optimizations: [],
128
- },
129
- ],
130
- },
131
- },
132
- stages: [],
133
- };
134
- const analyzer = new OptimizationAnalyzer(state);
135
- const result = analyzer.analyze();
136
- expect(result.bySeverity.high.length).toBeGreaterThan(0);
137
- expect(result.bySeverity.medium.length).toBeGreaterThan(0);
138
- });
139
- it('should calculate total estimated savings', () => {
140
- const state = {
141
- version: '1.0.0',
142
- buildTime: Date.now(),
143
- assets: {
144
- 'large-file': {
145
- id: 'large-file',
146
- originalName: 'large-file.js',
147
- originalPath: '/path/to/large-file.js',
148
- originalSize: 200 * 1024,
149
- finalSize: 195 * 1024,
150
- totalCompressionRatio: 0.025,
151
- type: 'script',
152
- processingHistory: [
153
- {
154
- stage: 'base-build',
155
- name: 'large-file.js',
156
- size: 195 * 1024,
157
- optimizations: [],
158
- },
159
- ],
160
- },
161
- },
162
- stages: [],
163
- };
164
- const analyzer = new OptimizationAnalyzer(state);
165
- const result = analyzer.analyze();
166
- expect(result.totalEstimatedSavings).toBeGreaterThan(0);
167
- expect(result.estimatedOptimizationRate).toBeGreaterThan(0);
168
- });
169
- });
@@ -1,100 +0,0 @@
1
- pc.script.createLoadingScreen((app) => {
2
- const createCss = () => {
3
- const css = `
4
- body {
5
- background-color: #283538;
6
- }
7
-
8
- #application-splash-wrapper {
9
- position: absolute;
10
- top: 0;
11
- left: 0;
12
- height: 100%;
13
- width: 100%;
14
- background-color: #283538;
15
- }
16
-
17
- #application-splash {
18
- position: absolute;
19
- top: calc(50% - 28px);
20
- width: 264px;
21
- left: calc(50% - 132px);
22
- }
23
-
24
- #application-splash img {
25
- width: 100%;
26
- }
27
-
28
- #progress-bar-container {
29
- margin: 20px auto 0 auto;
30
- height: 2px;
31
- width: 100%;
32
- background-color: #1d292c;
33
- }
34
-
35
- #progress-bar {
36
- width: 0%;
37
- height: 100%;
38
- background-color: #f60;
39
- }
40
-
41
- @media (max-width: 480px) {
42
- #application-splash {
43
- width: 170px;
44
- left: calc(50% - 85px);
45
- }
46
- }
47
- `;
48
-
49
- const style = document.createElement('style');
50
- style.textContent = css;
51
- document.head.appendChild(style);
52
- };
53
-
54
- const showSplash = () => {
55
- const wrapper = document.createElement('div');
56
- wrapper.id = 'application-splash-wrapper';
57
- document.body.appendChild(wrapper);
58
-
59
- const splash = document.createElement('div');
60
- splash.id = 'application-splash';
61
- wrapper.appendChild(splash);
62
- splash.style.display = 'none';
63
-
64
- const logo = document.createElement('img');
65
- logo.src = `${ASSET_PREFIX}logo.png`;
66
- splash.appendChild(logo);
67
- logo.onload = () => {
68
- splash.style.display = 'block';
69
- };
70
-
71
- const container = document.createElement('div');
72
- container.id = 'progress-bar-container';
73
- splash.appendChild(container);
74
-
75
- const bar = document.createElement('div');
76
- bar.id = 'progress-bar';
77
- container.appendChild(bar);
78
- };
79
-
80
- const setProgress = (value) => {
81
- const bar = document.getElementById('progress-bar');
82
- if (bar) {
83
- value = Math.min(1, Math.max(0, value));
84
- bar.style.width = `${value * 100}%`;
85
- }
86
- };
87
-
88
- const hideSplash = () => {
89
- document.getElementById('application-splash-wrapper').remove();
90
- };
91
-
92
- createCss();
93
- showSplash();
94
-
95
- app.on('preload:end', () => {
96
- app.off('preload:progress');
97
- });
98
- app.on('preload:progress', setProgress);
99
- app.on('start', hideSplash);
100
- });
@@ -1,47 +0,0 @@
1
- var loadModules = function (modules, urlPrefix, doneCallback) { // eslint-disable-line no-unused-vars
2
-
3
- if (typeof modules === "undefined" || modules.length === 0) {
4
- // caller may depend on callback behaviour being async
5
- setTimeout(doneCallback);
6
- } else {
7
- let remaining = modules.length;
8
- const moduleLoaded = () => {
9
- if (--remaining === 0) {
10
- doneCallback();
11
- }
12
- };
13
-
14
- modules.forEach(function (m) {
15
- pc.WasmModule.setConfig(m.moduleName, {
16
- glueUrl: urlPrefix + m.glueUrl,
17
- wasmUrl: urlPrefix + m.wasmUrl,
18
- fallbackUrl: urlPrefix + m.fallbackUrl
19
- });
20
-
21
- if (!m.hasOwnProperty('preload') || m.preload) {
22
- if (m.moduleName === 'BASIS') {
23
- // preload basis transcoder
24
- pc.basisInitialize();
25
- moduleLoaded();
26
- } else if (m.moduleName === 'DracoDecoderModule') {
27
- // preload draco decoder
28
- if (pc.dracoInitialize) {
29
- // 1.63 onwards
30
- pc.dracoInitialize();
31
- moduleLoaded();
32
- } else {
33
- // 1.62 and earlier
34
- pc.WasmModule.getInstance(m.moduleName, () => { moduleLoaded(); });
35
- }
36
- } else {
37
- // load remaining modules in global scope
38
- pc.WasmModule.getInstance(m.moduleName, () => { moduleLoaded(); });
39
- }
40
- } else {
41
- moduleLoaded();
42
- }
43
- });
44
- }
45
- };
46
-
47
- window.loadModules = loadModules;
@@ -1,20 +0,0 @@
1
- window.ASSET_PREFIX = "";
2
- window.SCRIPT_PREFIX = "";
3
- window.SCENE_PATH = "{{SCENE_PATH}}";
4
- window.CONTEXT_OPTIONS = {
5
- 'antialias': {{ANTIALIAS}},
6
- 'alpha': false,
7
- 'preserveDrawingBuffer': {{PRESERVE_DRAWING_BUFFER}},
8
- 'deviceTypes': [`webgl2`, `webgl1`],
9
- 'powerPreference': "{{POWER_PREFERENCE}}"
10
- };
11
- window.SCRIPTS = {{SCRIPTS}};
12
- window.CONFIG_FILENAME = "config.json";
13
- window.INPUT_SETTINGS = {
14
- useKeyboard: {{USE_KEYBOARD}},
15
- useMouse: {{USE_MOUSE}},
16
- useGamepads: {{USE_GAMEPAD}},
17
- useTouch: {{USE_TOUCH}}
18
- };
19
- pc.script.legacy = {{USE_LEGACY_SCRIPTS}};
20
- window.PRELOAD_MODULES = {{PRELOAD_MODULES}};