@playcraft/build 0.0.17 → 0.0.19
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/analyzers/scene-asset-collector.js +259 -135
- package/dist/audio-optimizer.d.ts +70 -0
- package/dist/audio-optimizer.js +226 -0
- package/dist/base-builder.d.ts +25 -13
- package/dist/base-builder.js +69 -29
- package/dist/engines/engine-detector.d.ts +13 -4
- package/dist/engines/engine-detector.js +74 -10
- package/dist/engines/generic-adapter.d.ts +12 -6
- package/dist/engines/generic-adapter.js +46 -15
- package/dist/engines/index.d.ts +1 -0
- package/dist/engines/index.js +1 -0
- package/dist/engines/playable-scripts-adapter.d.ts +148 -0
- package/dist/engines/playable-scripts-adapter.js +1084 -0
- package/dist/engines/playcanvas-adapter.js +3 -0
- package/dist/generators/config-generator.js +10 -17
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/platforms/google.d.ts +9 -0
- package/dist/platforms/google.js +68 -7
- package/dist/templates/__loading__.js +100 -0
- package/dist/templates/__modules__.js +47 -0
- package/dist/templates/__settings__.template.js +20 -0
- package/dist/templates/__start__.js +332 -0
- package/dist/templates/index.html +18 -0
- package/dist/templates/logo.png +0 -0
- package/dist/templates/manifest.json +1 -0
- package/dist/templates/patches/cannon.min.js +28 -0
- package/dist/templates/patches/lz4.js +10 -0
- package/dist/templates/patches/one-page-http-get.js +20 -0
- package/dist/templates/patches/one-page-inline-game-scripts.js +52 -0
- package/dist/templates/patches/one-page-mraid-resize-canvas.js +46 -0
- package/dist/templates/patches/p2.min.js +27 -0
- package/dist/templates/patches/playcraft-no-xhr.js +76 -0
- package/dist/templates/playcanvas-stable.min.js +16363 -0
- package/dist/templates/styles.css +43 -0
- package/dist/types.d.ts +60 -13
- package/dist/utils/build-mode-detector.js +2 -0
- package/dist/vite/plugin-playcanvas.js +14 -19
- package/dist/vite/plugin-source-builder.js +383 -97
- package/package.json +7 -4
- package/dist/utils/obfuscate.d.ts +0 -42
- package/dist/utils/obfuscate.js +0 -216
- package/dist/vite/plugin-obfuscate.d.ts +0 -22
- package/dist/vite/plugin-obfuscate.js +0 -52
- package/dist/vite/plugin-template-minifier.d.ts +0 -20
- package/dist/vite/plugin-template-minifier.js +0 -392
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = path.dirname(__filename);
|
|
6
|
+
const AUDIO_EXTENSIONS = new Set(['.mp3', '.wav', '.ogg', '.aac', '.m4a']);
|
|
7
|
+
const SPECTRAL_SUFFIX = '.spectral.json';
|
|
8
|
+
/**
|
|
9
|
+
* Read SpectralAudioEngine runtime source from packages/build/src/runtime/
|
|
10
|
+
*/
|
|
11
|
+
async function readSpectralRuntime() {
|
|
12
|
+
const runtimePath = path.join(__dirname, 'runtime', 'spectral-audio-engine.js');
|
|
13
|
+
try {
|
|
14
|
+
return await fs.readFile(runtimePath, 'utf-8');
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Fallback minimal runtime if file not found
|
|
18
|
+
return '/* SpectralAudioEngine runtime not found */';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function formatScanError(err) {
|
|
22
|
+
return err instanceof Error ? err.message : String(err);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Recursively scan a directory for audio and spectral JSON files.
|
|
26
|
+
*/
|
|
27
|
+
async function scanDirectory(dir, baseDir, report) {
|
|
28
|
+
const audioFiles = [];
|
|
29
|
+
const spectralFiles = [];
|
|
30
|
+
const resolvedRoot = path.resolve(dir);
|
|
31
|
+
async function walk(current) {
|
|
32
|
+
let entries;
|
|
33
|
+
try {
|
|
34
|
+
entries = (await fs.readdir(current, { withFileTypes: true }));
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const rel = path.relative(baseDir, current) || current;
|
|
38
|
+
if (path.resolve(current) === resolvedRoot) {
|
|
39
|
+
throw new Error(`[AudioOptimizer] Cannot read build directory "${current}": ${formatScanError(err)}`);
|
|
40
|
+
}
|
|
41
|
+
report.warn(`[AudioOptimizer] Skipping unreadable directory "${rel}": ${formatScanError(err)}`, err);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const fullPath = path.join(current, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
await walk(fullPath);
|
|
48
|
+
}
|
|
49
|
+
else if (entry.isFile()) {
|
|
50
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
51
|
+
if (AUDIO_EXTENSIONS.has(ext)) {
|
|
52
|
+
try {
|
|
53
|
+
const st = await fs.stat(fullPath);
|
|
54
|
+
audioFiles.push({
|
|
55
|
+
filePath: fullPath,
|
|
56
|
+
relativePath: path.relative(baseDir, fullPath),
|
|
57
|
+
size: st.size,
|
|
58
|
+
hasSpectral: false,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const rel = path.relative(baseDir, fullPath);
|
|
63
|
+
report.warn(`[AudioOptimizer] Could not stat audio file "${rel}": ${formatScanError(err)}`, err);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (entry.name.endsWith(SPECTRAL_SUFFIX)) {
|
|
67
|
+
spectralFiles.push(fullPath);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
await walk(dir);
|
|
73
|
+
// Cross-reference: for each audio file, check if a spectral counterpart exists
|
|
74
|
+
const spectralSet = new Set(spectralFiles);
|
|
75
|
+
for (const audio of audioFiles) {
|
|
76
|
+
const spectralPath = audio.filePath.replace(/\.[^.]+$/, SPECTRAL_SUFFIX);
|
|
77
|
+
if (spectralSet.has(spectralPath)) {
|
|
78
|
+
audio.hasSpectral = true;
|
|
79
|
+
try {
|
|
80
|
+
const st = await fs.stat(spectralPath);
|
|
81
|
+
audio.spectralSize = st.size;
|
|
82
|
+
audio.potentialSavings = audio.size - st.size;
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
report.warn(`[AudioOptimizer] Could not stat spectral JSON for "${audio.relativePath}" ` +
|
|
86
|
+
`("${path.relative(baseDir, spectralPath)}"): ${formatScanError(err)}`, err);
|
|
87
|
+
audio.hasSpectral = false;
|
|
88
|
+
audio.spectralSize = undefined;
|
|
89
|
+
audio.potentialSavings = undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { audioFiles, spectralFiles };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Inject SpectralAudioEngine runtime into an HTML string.
|
|
97
|
+
* Inserts a <script> tag before </head> (or at top of <body> if no </head>).
|
|
98
|
+
*/
|
|
99
|
+
function injectRuntimeIntoHtml(html, runtimeCode) {
|
|
100
|
+
const scriptTag = `<script>/* SpectralAudioEngine v1 */\n${runtimeCode}\n</script>`;
|
|
101
|
+
const headCloseIdx = html.lastIndexOf('</head>');
|
|
102
|
+
if (headCloseIdx !== -1) {
|
|
103
|
+
return html.slice(0, headCloseIdx) + scriptTag + '\n' + html.slice(headCloseIdx);
|
|
104
|
+
}
|
|
105
|
+
const bodyOpenIdx = html.indexOf('<body');
|
|
106
|
+
if (bodyOpenIdx !== -1) {
|
|
107
|
+
const bodyTagEnd = html.indexOf('>', bodyOpenIdx) + 1;
|
|
108
|
+
return html.slice(0, bodyTagEnd) + '\n' + scriptTag + html.slice(bodyTagEnd);
|
|
109
|
+
}
|
|
110
|
+
return scriptTag + '\n' + html;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* AudioOptimizer — Build-time audio analysis and optimization.
|
|
114
|
+
*
|
|
115
|
+
* Scans a build directory for audio files and .spectral.json counterparts.
|
|
116
|
+
* When spectral files are present, optionally injects the SpectralAudioEngine
|
|
117
|
+
* runtime (< 3KB) into the output HTML so the browser can synthesize them.
|
|
118
|
+
*
|
|
119
|
+
* Usage:
|
|
120
|
+
* const optimizer = new AudioOptimizer({ buildDir: './dist', htmlOutputPath: './dist/index.html' });
|
|
121
|
+
* const report = await optimizer.optimize();
|
|
122
|
+
* console.log(report);
|
|
123
|
+
*/
|
|
124
|
+
export class AudioOptimizer {
|
|
125
|
+
constructor(options) {
|
|
126
|
+
this.options = {
|
|
127
|
+
injectRuntime: true,
|
|
128
|
+
htmlOutputPath: '',
|
|
129
|
+
verbose: false,
|
|
130
|
+
...options,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Run the optimizer. Returns a report with audio file details and optimization results.
|
|
135
|
+
*/
|
|
136
|
+
async optimize() {
|
|
137
|
+
const { buildDir, injectRuntime, htmlOutputPath, verbose } = this.options;
|
|
138
|
+
const log = (msg) => {
|
|
139
|
+
if (verbose)
|
|
140
|
+
console.log(`[AudioOptimizer] ${msg}`);
|
|
141
|
+
};
|
|
142
|
+
log(`Scanning ${buildDir} for audio files...`);
|
|
143
|
+
const warn = (message, cause) => {
|
|
144
|
+
console.warn(message);
|
|
145
|
+
if (verbose && cause !== undefined) {
|
|
146
|
+
console.warn(cause);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const { audioFiles, spectralFiles } = await scanDirectory(buildDir, buildDir, { warn });
|
|
150
|
+
const totalAudioSize = audioFiles.reduce((sum, f) => sum + f.size, 0);
|
|
151
|
+
const totalSpectralSize = audioFiles.reduce((sum, f) => {
|
|
152
|
+
return sum + (f.hasSpectral && f.spectralSize !== undefined ? f.spectralSize : f.size);
|
|
153
|
+
}, 0);
|
|
154
|
+
log(`Found ${audioFiles.length} audio file(s), ${spectralFiles.length} spectral JSON(s)`);
|
|
155
|
+
log(`Total audio: ${(totalAudioSize / 1024).toFixed(1)}KB → with spectral: ${(totalSpectralSize / 1024).toFixed(1)}KB`);
|
|
156
|
+
let runtimeInjected = false;
|
|
157
|
+
let runtimeSize = 0;
|
|
158
|
+
if (injectRuntime && spectralFiles.length > 0 && htmlOutputPath) {
|
|
159
|
+
log(`Injecting SpectralAudioEngine runtime into ${htmlOutputPath}...`);
|
|
160
|
+
try {
|
|
161
|
+
const [html, runtimeCode] = await Promise.all([
|
|
162
|
+
fs.readFile(htmlOutputPath, 'utf-8'),
|
|
163
|
+
readSpectralRuntime(),
|
|
164
|
+
]);
|
|
165
|
+
runtimeSize = Buffer.byteLength(runtimeCode, 'utf-8');
|
|
166
|
+
const injectedHtml = injectRuntimeIntoHtml(html, runtimeCode);
|
|
167
|
+
await fs.writeFile(htmlOutputPath, injectedHtml, 'utf-8');
|
|
168
|
+
runtimeInjected = true;
|
|
169
|
+
log(`Runtime injected (${runtimeSize}B)`);
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
log(`Warning: failed to inject runtime: ${err.message}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (spectralFiles.length === 0) {
|
|
176
|
+
log('No spectral files found — runtime injection skipped');
|
|
177
|
+
}
|
|
178
|
+
const netSavings = totalAudioSize - totalSpectralSize - runtimeSize;
|
|
179
|
+
if (verbose) {
|
|
180
|
+
for (const f of audioFiles) {
|
|
181
|
+
if (f.hasSpectral) {
|
|
182
|
+
log(` ✓ ${f.relativePath} ${(f.size / 1024).toFixed(1)}KB → spectral ${(f.spectralSize ?? 0)}B ` +
|
|
183
|
+
`(saved ${((f.potentialSavings ?? 0) / 1024).toFixed(1)}KB)`);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
log(` - ${f.relativePath} ${(f.size / 1024).toFixed(1)}KB (no spectral counterpart)`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
audioFiles,
|
|
192
|
+
spectralFiles,
|
|
193
|
+
totalAudioSize,
|
|
194
|
+
totalSpectralSize,
|
|
195
|
+
runtimeInjected,
|
|
196
|
+
runtimeSize,
|
|
197
|
+
netSavings,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Print a human-readable summary of the optimization report.
|
|
202
|
+
*/
|
|
203
|
+
static printReport(report) {
|
|
204
|
+
console.log('\n── Audio Optimization Report ──────────────────────');
|
|
205
|
+
console.log(` Audio files: ${report.audioFiles.length}`);
|
|
206
|
+
console.log(` Spectral JSONs: ${report.spectralFiles.length}`);
|
|
207
|
+
console.log(` Total audio: ${(report.totalAudioSize / 1024).toFixed(1)}KB`);
|
|
208
|
+
if (report.spectralFiles.length > 0) {
|
|
209
|
+
console.log(` With spectral: ${(report.totalSpectralSize / 1024).toFixed(1)}KB`);
|
|
210
|
+
if (report.runtimeInjected) {
|
|
211
|
+
console.log(` + Runtime: +${report.runtimeSize}B`);
|
|
212
|
+
}
|
|
213
|
+
const savings = report.netSavings;
|
|
214
|
+
const pct = report.totalAudioSize > 0
|
|
215
|
+
? ((savings / report.totalAudioSize) * 100).toFixed(1)
|
|
216
|
+
: '0';
|
|
217
|
+
if (savings > 0) {
|
|
218
|
+
console.log(` Net savings: ${(savings / 1024).toFixed(1)}KB (${pct}%)`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (report.runtimeInjected) {
|
|
222
|
+
console.log(' ✓ SpectralAudioEngine runtime injected');
|
|
223
|
+
}
|
|
224
|
+
console.log('────────────────────────────────────────────────────\n');
|
|
225
|
+
}
|
|
226
|
+
}
|
package/dist/base-builder.d.ts
CHANGED
|
@@ -1,30 +1,42 @@
|
|
|
1
|
-
import type { BaseBuildOptions, BaseBuildOutput } from './types.js';
|
|
1
|
+
import type { BaseBuildOptions, BaseBuildOutput, PlayableScriptsConfig } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* 基础构建器 -
|
|
3
|
+
* 基础构建器 - 路由到不同构建工具适配器
|
|
4
4
|
*
|
|
5
5
|
* 职责:
|
|
6
|
-
* 1.
|
|
7
|
-
* 2.
|
|
6
|
+
* 1. 确定引擎类型和构建工具(从 options 或自动检测)
|
|
7
|
+
* 2. 根据构建工具路由到对应适配器执行 Base Build
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* - PlayCanvasAdapter:处理 PlayCanvas
|
|
11
|
-
* -
|
|
9
|
+
* 路由规则(按 buildTool):
|
|
10
|
+
* - playcanvas-native → PlayCanvasAdapter:处理 PlayCanvas 项目
|
|
11
|
+
* - playable-scripts → PlayableScriptsAdapter:处理 @playcraft/devkit 项目
|
|
12
|
+
* - generic → GenericAdapter:处理外部引擎(npm install + npm run build)
|
|
12
13
|
*/
|
|
13
14
|
export declare class BaseBuilder {
|
|
14
15
|
private projectDir;
|
|
15
16
|
private options;
|
|
16
|
-
|
|
17
|
+
private playableScriptsConfig?;
|
|
18
|
+
private usePlayableScripts?;
|
|
19
|
+
private _metadata?;
|
|
20
|
+
constructor(projectDir: string, options: BaseBuildOptions, extraOptions?: {
|
|
21
|
+
playableScriptsConfig?: PlayableScriptsConfig;
|
|
22
|
+
usePlayableScripts?: boolean;
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* 获取构建元数据(在 build() 完成后可用)
|
|
26
|
+
*/
|
|
27
|
+
get metadata(): BaseBuildOutput['metadata'] | undefined;
|
|
17
28
|
/**
|
|
18
29
|
* 执行基础构建
|
|
19
|
-
*
|
|
30
|
+
* 根据构建工具路由到对应适配器
|
|
20
31
|
*/
|
|
21
32
|
build(): Promise<BaseBuildOutput>;
|
|
22
33
|
/**
|
|
23
|
-
*
|
|
34
|
+
* 解析引擎类型和构建工具
|
|
24
35
|
* 优先级:
|
|
25
|
-
* 1.
|
|
26
|
-
* 2.
|
|
36
|
+
* 1. --use-playable-scripts 显式指定
|
|
37
|
+
* 2. options.engine 用户显式指定引擎
|
|
38
|
+
* 3. 自动检测(detectFull)
|
|
27
39
|
*/
|
|
28
|
-
private
|
|
40
|
+
private resolveDetection;
|
|
29
41
|
}
|
|
30
42
|
export type { BaseBuildOptions, BaseBuildOutput } from './types.js';
|
package/dist/base-builder.js
CHANGED
|
@@ -1,58 +1,98 @@
|
|
|
1
|
-
import { EngineDetector, PlayCanvasAdapter, GenericAdapter } from './engines/index.js';
|
|
1
|
+
import { EngineDetector, PlayCanvasAdapter, GenericAdapter, PlayableScriptsAdapter } from './engines/index.js';
|
|
2
2
|
/**
|
|
3
|
-
* 基础构建器 -
|
|
3
|
+
* 基础构建器 - 路由到不同构建工具适配器
|
|
4
4
|
*
|
|
5
5
|
* 职责:
|
|
6
|
-
* 1.
|
|
7
|
-
* 2.
|
|
6
|
+
* 1. 确定引擎类型和构建工具(从 options 或自动检测)
|
|
7
|
+
* 2. 根据构建工具路由到对应适配器执行 Base Build
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* - PlayCanvasAdapter:处理 PlayCanvas
|
|
11
|
-
* -
|
|
9
|
+
* 路由规则(按 buildTool):
|
|
10
|
+
* - playcanvas-native → PlayCanvasAdapter:处理 PlayCanvas 项目
|
|
11
|
+
* - playable-scripts → PlayableScriptsAdapter:处理 @playcraft/devkit 项目
|
|
12
|
+
* - generic → GenericAdapter:处理外部引擎(npm install + npm run build)
|
|
12
13
|
*/
|
|
13
14
|
export class BaseBuilder {
|
|
14
|
-
constructor(projectDir, options) {
|
|
15
|
+
constructor(projectDir, options, extraOptions) {
|
|
15
16
|
this.projectDir = projectDir;
|
|
16
17
|
this.options = options;
|
|
18
|
+
this.playableScriptsConfig = extraOptions?.playableScriptsConfig;
|
|
19
|
+
this.usePlayableScripts = extraOptions?.usePlayableScripts;
|
|
17
20
|
console.log(`[BaseBuilder] 初始化: projectDir=${projectDir}, outputDir=${options.outputDir}`);
|
|
18
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* 获取构建元数据(在 build() 完成后可用)
|
|
24
|
+
*/
|
|
25
|
+
get metadata() {
|
|
26
|
+
return this._metadata;
|
|
27
|
+
}
|
|
19
28
|
/**
|
|
20
29
|
* 执行基础构建
|
|
21
|
-
*
|
|
30
|
+
* 根据构建工具路由到对应适配器
|
|
22
31
|
*/
|
|
23
32
|
async build() {
|
|
24
|
-
// 1.
|
|
25
|
-
const engine = await this.
|
|
26
|
-
console.log(`[BaseBuilder] 使用引擎: ${engine}`);
|
|
27
|
-
// 2.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// 1. 确定引擎类型和构建工具
|
|
34
|
+
const { engine, buildTool } = await this.resolveDetection();
|
|
35
|
+
console.log(`[BaseBuilder] 使用引擎: ${engine}, 构建工具: ${buildTool}`);
|
|
36
|
+
// 2. 根据构建工具路由到对应适配器
|
|
37
|
+
let result;
|
|
38
|
+
switch (buildTool) {
|
|
39
|
+
case 'playcanvas-native': {
|
|
40
|
+
const adapter = new PlayCanvasAdapter(this.projectDir, this.options);
|
|
41
|
+
result = await adapter.baseBuild();
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case 'playable-scripts': {
|
|
45
|
+
const adapter = new PlayableScriptsAdapter(this.projectDir, this.options, this.playableScriptsConfig);
|
|
46
|
+
result = await adapter.baseBuild();
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case 'generic':
|
|
50
|
+
default: {
|
|
51
|
+
const adapter = new GenericAdapter(this.projectDir, this.options, engine);
|
|
52
|
+
result = await adapter.baseBuild();
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
35
55
|
}
|
|
56
|
+
// 3. 存储 metadata 供外部访问
|
|
57
|
+
this._metadata = result.metadata;
|
|
58
|
+
return result;
|
|
36
59
|
}
|
|
37
60
|
/**
|
|
38
|
-
*
|
|
61
|
+
* 解析引擎类型和构建工具
|
|
39
62
|
* 优先级:
|
|
40
|
-
* 1.
|
|
41
|
-
* 2.
|
|
63
|
+
* 1. --use-playable-scripts 显式指定
|
|
64
|
+
* 2. options.engine 用户显式指定引擎
|
|
65
|
+
* 3. 自动检测(detectFull)
|
|
42
66
|
*/
|
|
43
|
-
async
|
|
44
|
-
// 1. 用户显式指定
|
|
67
|
+
async resolveDetection() {
|
|
68
|
+
// 1. 用户显式指定 --use-playable-scripts
|
|
69
|
+
if (this.usePlayableScripts) {
|
|
70
|
+
console.log('[BaseBuilder] 用户显式指定使用 playable-scripts 构建工具');
|
|
71
|
+
return { engine: 'generic', buildTool: 'playable-scripts' };
|
|
72
|
+
}
|
|
73
|
+
// 2. 用户显式指定引擎
|
|
45
74
|
if (this.options.engine) {
|
|
46
75
|
const validated = EngineDetector.validateEngine(this.options.engine);
|
|
47
76
|
if (validated) {
|
|
48
77
|
console.log(`[BaseBuilder] 使用用户指定的引擎: ${validated}`);
|
|
49
|
-
|
|
78
|
+
// PlayCanvas 直接使用原生适配器
|
|
79
|
+
if (validated === 'playcanvas') {
|
|
80
|
+
return { engine: 'playcanvas', buildTool: 'playcanvas-native' };
|
|
81
|
+
}
|
|
82
|
+
// 其他引擎:检测是否可用 playable-scripts(@playcraft/adsdk 项目)
|
|
83
|
+
const detected = await EngineDetector.detectFull(this.projectDir);
|
|
84
|
+
if (detected.buildTool === 'playable-scripts') {
|
|
85
|
+
console.log(`[BaseBuilder] 检测到 playable-scripts 项目,优先使用 devkit 构建`);
|
|
86
|
+
return detected;
|
|
87
|
+
}
|
|
88
|
+
// 否则使用 generic 适配器
|
|
89
|
+
return { engine: validated, buildTool: 'generic' };
|
|
50
90
|
}
|
|
51
91
|
console.warn(`[BaseBuilder] 无效的引擎类型: ${this.options.engine},将自动检测`);
|
|
52
92
|
}
|
|
53
|
-
//
|
|
54
|
-
const detected = await EngineDetector.
|
|
55
|
-
console.log(`[BaseBuilder]
|
|
93
|
+
// 3. 自动检测
|
|
94
|
+
const detected = await EngineDetector.detectFull(this.projectDir);
|
|
95
|
+
console.log(`[BaseBuilder] 自动检测结果: engine=${detected.engine}, buildTool=${detected.buildTool}`);
|
|
56
96
|
return detected;
|
|
57
97
|
}
|
|
58
98
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import type { EngineType } from '../types.js';
|
|
1
|
+
import type { EngineType, DetectionResult } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* 引擎检测器
|
|
4
|
-
*
|
|
4
|
+
* 根据项目文件结构自动检测引擎类型和构建工具
|
|
5
5
|
*/
|
|
6
6
|
export declare class EngineDetector {
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* 完整检测:返回引擎类型 + 构建工具的二维检测结果
|
|
9
|
+
* @param projectDir 项目目录
|
|
10
|
+
* @returns 包含 engine 和 buildTool 的检测结果
|
|
11
|
+
*/
|
|
12
|
+
static detectFull(projectDir: string): Promise<DetectionResult>;
|
|
13
|
+
/**
|
|
14
|
+
* 检测项目的引擎类型(向后兼容)
|
|
9
15
|
* @param projectDir 项目目录
|
|
10
16
|
* @returns 引擎类型
|
|
11
|
-
* @throws 如果无法识别引擎,抛出错误
|
|
12
17
|
*/
|
|
13
18
|
static detect(projectDir: string): Promise<EngineType>;
|
|
14
19
|
/**
|
|
@@ -19,6 +24,10 @@ export declare class EngineDetector {
|
|
|
19
24
|
* 读取 package.json
|
|
20
25
|
*/
|
|
21
26
|
private static readPackageJson;
|
|
27
|
+
/**
|
|
28
|
+
* 从 package.json 检测构建工具(优先级 200,高于引擎检测)
|
|
29
|
+
*/
|
|
30
|
+
private static detectBuildToolFromPackageJson;
|
|
22
31
|
/**
|
|
23
32
|
* 从 package.json 检测引擎类型
|
|
24
33
|
*/
|
|
@@ -15,22 +15,46 @@ const ENGINE_DETECTION_MAP = [
|
|
|
15
15
|
{ match: (n) => n.startsWith('laya-') || n.startsWith('@layabox/'), engine: 'layaair', priority: 100 },
|
|
16
16
|
{ match: (n) => n === 'egret', engine: 'egret', priority: 100 },
|
|
17
17
|
];
|
|
18
|
+
/**
|
|
19
|
+
* 构建工具检测映射表 - 优先级高于引擎检测
|
|
20
|
+
* 当检测到特定构建工具或 SDK 时,可以隐含引擎类型和构建工具
|
|
21
|
+
*
|
|
22
|
+
* 注意:@playcraft/devkit 是 PlayCraft 平台内置的构建工具(已集成到 @playcraft/cli)。
|
|
23
|
+
* 用户项目只需依赖 @playcraft/adsdk(运行时 SDK),不需要安装构建工具。
|
|
24
|
+
* 检测到 adsdk 时,自动使用平台内置的 devkit 进行构建。
|
|
25
|
+
*/
|
|
26
|
+
const BUILD_TOOL_DETECTION_MAP = [
|
|
27
|
+
{
|
|
28
|
+
// 检测 @playcraft/adsdk(运行时 SDK)- 使用平台内置的 devkit 构建
|
|
29
|
+
// 或旧的内部包名 @tencent/playable-sdk
|
|
30
|
+
match: (n) => n === '@playcraft/adsdk' || n === '@tencent/playable-sdk',
|
|
31
|
+
buildTool: 'playable-scripts',
|
|
32
|
+
impliedEngine: 'phaser', // adsdk 项目通常是 Phaser 游戏
|
|
33
|
+
priority: 200, // 高于引擎检测优先级(200 > 100)
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
// 向后兼容:检测旧的 devkit 包名(用户项目可能还在使用)
|
|
37
|
+
match: (n) => n === '@playcraft/devkit' || n === '@tencent/playable-scripts',
|
|
38
|
+
buildTool: 'playable-scripts',
|
|
39
|
+
impliedEngine: 'generic',
|
|
40
|
+
priority: 200,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
18
43
|
/**
|
|
19
44
|
* 引擎检测器
|
|
20
|
-
*
|
|
45
|
+
* 根据项目文件结构自动检测引擎类型和构建工具
|
|
21
46
|
*/
|
|
22
47
|
export class EngineDetector {
|
|
23
48
|
/**
|
|
24
|
-
*
|
|
49
|
+
* 完整检测:返回引擎类型 + 构建工具的二维检测结果
|
|
25
50
|
* @param projectDir 项目目录
|
|
26
|
-
* @returns
|
|
27
|
-
* @throws 如果无法识别引擎,抛出错误
|
|
51
|
+
* @returns 包含 engine 和 buildTool 的检测结果
|
|
28
52
|
*/
|
|
29
|
-
static async
|
|
53
|
+
static async detectFull(projectDir) {
|
|
30
54
|
// 1. 优先检测 PlayCanvas / PlayCraft 特有文件(最快路径)
|
|
31
55
|
const isPlayCanvas = await this.detectPlayCanvasFiles(projectDir);
|
|
32
56
|
if (isPlayCanvas) {
|
|
33
|
-
return 'playcanvas';
|
|
57
|
+
return { engine: 'playcanvas', buildTool: 'playcanvas-native' };
|
|
34
58
|
}
|
|
35
59
|
// 2. 检测 package.json
|
|
36
60
|
const pkg = await this.readPackageJson(projectDir);
|
|
@@ -39,15 +63,30 @@ export class EngineDetector {
|
|
|
39
63
|
`项目目录: ${projectDir}\n` +
|
|
40
64
|
`请确保这是一个有效的项目目录。`);
|
|
41
65
|
}
|
|
42
|
-
// 3.
|
|
66
|
+
// 3. 优先检测构建工具(BUILD_TOOL_DETECTION_MAP,优先级 200)
|
|
67
|
+
const buildToolResult = this.detectBuildToolFromPackageJson(pkg);
|
|
68
|
+
if (buildToolResult) {
|
|
69
|
+
console.log(`[EngineDetector] 检测到构建工具: ${buildToolResult.buildTool}(隐含引擎: ${buildToolResult.engine})`);
|
|
70
|
+
return buildToolResult;
|
|
71
|
+
}
|
|
72
|
+
// 4. 检测引擎类型(ENGINE_DETECTION_MAP,优先级 100)
|
|
43
73
|
const engine = this.detectFromPackageJson(pkg);
|
|
44
74
|
if (engine) {
|
|
45
75
|
console.log(`[EngineDetector] 检测到引擎: ${engine}(通过 package.json)`);
|
|
46
|
-
return engine;
|
|
76
|
+
return { engine, buildTool: 'generic' };
|
|
47
77
|
}
|
|
48
|
-
//
|
|
78
|
+
// 5. 兜底
|
|
49
79
|
console.log('[EngineDetector] 无法识别具体引擎,使用 generic 类型');
|
|
50
|
-
return 'generic';
|
|
80
|
+
return { engine: 'generic', buildTool: 'generic' };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 检测项目的引擎类型(向后兼容)
|
|
84
|
+
* @param projectDir 项目目录
|
|
85
|
+
* @returns 引擎类型
|
|
86
|
+
*/
|
|
87
|
+
static async detect(projectDir) {
|
|
88
|
+
const result = await this.detectFull(projectDir);
|
|
89
|
+
return result.engine;
|
|
51
90
|
}
|
|
52
91
|
/**
|
|
53
92
|
* 检测 PlayCanvas 特有文件
|
|
@@ -122,6 +161,31 @@ export class EngineDetector {
|
|
|
122
161
|
return null;
|
|
123
162
|
}
|
|
124
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* 从 package.json 检测构建工具(优先级 200,高于引擎检测)
|
|
166
|
+
*/
|
|
167
|
+
static detectBuildToolFromPackageJson(pkg) {
|
|
168
|
+
const allDeps = {
|
|
169
|
+
...pkg.dependencies,
|
|
170
|
+
...pkg.devDependencies,
|
|
171
|
+
};
|
|
172
|
+
// 收集所有匹配的构建工具,按优先级排序后返回最高优先级的项
|
|
173
|
+
const matches = [];
|
|
174
|
+
for (const [depName] of Object.entries(allDeps)) {
|
|
175
|
+
for (const { match, buildTool, impliedEngine, priority } of BUILD_TOOL_DETECTION_MAP) {
|
|
176
|
+
if (match(depName)) {
|
|
177
|
+
matches.push({ engine: impliedEngine, buildTool, priority });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (matches.length === 0) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
// 按优先级排序(数字越大优先级越高),返回最高优先级的项
|
|
185
|
+
matches.sort((a, b) => b.priority - a.priority);
|
|
186
|
+
const bestMatch = matches[0];
|
|
187
|
+
return { engine: bestMatch.engine, buildTool: bestMatch.buildTool };
|
|
188
|
+
}
|
|
125
189
|
/**
|
|
126
190
|
* 从 package.json 检测引擎类型
|
|
127
191
|
*/
|
|
@@ -13,18 +13,24 @@ export declare class GenericAdapter {
|
|
|
13
13
|
* 执行通用基础构建
|
|
14
14
|
* 流程:
|
|
15
15
|
* 1. 检测包管理器
|
|
16
|
-
* 2.
|
|
17
|
-
* 3.
|
|
18
|
-
* 4.
|
|
19
|
-
* 5.
|
|
20
|
-
* 6.
|
|
21
|
-
* 7.
|
|
16
|
+
* 2. 检查 node_modules(存在则跳过安装)
|
|
17
|
+
* 3. 执行 npm install(超时 3 分钟)
|
|
18
|
+
* 4. 检测 build script
|
|
19
|
+
* 5. 执行 npm run build(超时 5 分钟)
|
|
20
|
+
* 6. 查找构建产物目录
|
|
21
|
+
* 7. 复制构建产物到 outputDir
|
|
22
|
+
* 8. 写入 .build-metadata.json
|
|
22
23
|
*/
|
|
23
24
|
baseBuild(): Promise<BaseBuildOutput>;
|
|
24
25
|
/**
|
|
25
26
|
* 检测包管理器
|
|
26
27
|
*/
|
|
27
28
|
private detectPackageManager;
|
|
29
|
+
/**
|
|
30
|
+
* 检查 node_modules 是否存在且不为空
|
|
31
|
+
* 用于判断是否需要执行安装
|
|
32
|
+
*/
|
|
33
|
+
private checkNodeModules;
|
|
28
34
|
/**
|
|
29
35
|
* 执行 npm install
|
|
30
36
|
* 超时:3 分钟
|