You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AdLike2048/extensions/tools/dist/build-hooks.js

393 lines
56 KiB

3 weeks ago
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.onAfterBuild = exports.onBeforeBuild = exports.throwError = void 0;
/**
* 构建进程(独立 worker)加载的钩子脚本;custom-builder 的 configs.*.hooks 指向本文件。
* custom-builder.ts 会 re-export 本文件的钩子,供编辑器校验通过。
*/
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const lz4js_1 = __importDefault(require("lz4js"));
const terser_1 = require("terser");
const MembershipSet_1 = require("./MembershipSet");
exports.throwError = true;
function readJson(filePath) {
const raw = fs_1.default.readFileSync(filePath, 'utf8');
return JSON.parse(raw);
}
function resolveConfigNameVersion(projectPath) {
var _a, _b, _c, _d, _e, _f;
const sdkConfigPath = path_1.default.join(projectPath, 'assets', 'script', 'sdk', 'sdkConfig.ts');
const sdkConfigContent = fs_1.default.readFileSync(sdkConfigPath, 'utf8');
const importRegex = /import\s+\{\s*(\w+)\s*\}\s+from\s+["']\.\/config\/([^"']+)["'];?/g;
const importMap = new Map();
let m = null;
while ((m = importRegex.exec(sdkConfigContent))) {
importMap.set(m[1], m[2]);
}
const configAssign = sdkConfigContent.match(/static\s+readonly\s+config\s*:\s*IConfigData\s*=\s*(\w+)\s*;/);
const symbol = (_a = configAssign === null || configAssign === void 0 ? void 0 : configAssign[1]) !== null && _a !== void 0 ? _a : 'DefaultConfig';
const relConfig = (_b = importMap.get(symbol)) !== null && _b !== void 0 ? _b : 'DefaultConfig';
const configPath = path_1.default.join(projectPath, 'assets', 'script', 'sdk', 'config', `${relConfig}.ts`);
const configContent = fs_1.default.readFileSync(configPath, 'utf8');
const name = (_d = (_c = configContent.match(/name\s*:\s*["']([^"']+)["']/)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : 'config';
const vsion = (_f = (_e = configContent.match(/vsion\s*:\s*["']([^"']+)["']/)) === null || _e === void 0 ? void 0 : _e[1]) !== null && _f !== void 0 ? _f : '0.0.0';
return { name, vsion };
}
function encodeForRuntime(jsonObj) {
const jsonString = JSON.stringify(jsonObj);
const uint8 = new TextEncoder().encode(jsonString);
const compressed = Uint8Array.from(lz4js_1.default.compress(uint8));
const binary = Buffer.from(compressed).toString('latin1');
const base64 = Buffer.from(binary, 'latin1').toString('base64');
return Buffer.from(base64, 'latin1');
}
const HOT_UPDATE_TABLE_FILES = [
{ tableKey: 'Monster', fileName: 'Monster.json' },
{ tableKey: 'Const', fileName: 'Const.json' },
{ tableKey: 'Rank', fileName: 'Rank.json' },
{ tableKey: 'Item', fileName: 'Item.json' },
{ tableKey: 'Level', fileName: 'Level.json' },
{ tableKey: 'Skill', fileName: 'Skill.json' },
{ tableKey: 'Battlebuff', fileName: 'Battlebuff.json' },
{ tableKey: 'Weapon', fileName: 'Weapon.json' },
{ tableKey: 'Text', fileName: 'Text.json' },
{ tableKey: 'Hero', fileName: 'Hero.json' },
{ tableKey: 'Headicon', fileName: 'Headicon.json' },
];
function generateConfigBin() {
const projectPath = Editor.Project.path;
const configDir = path_1.default.join(projectPath, 'assets', 'bundles', 'first', 'config');
const mergeJson = {};
for (const { tableKey, fileName } of HOT_UPDATE_TABLE_FILES) {
const fp = path_1.default.join(configDir, fileName);
if (!fs_1.default.existsSync(fp)) {
console.warn(`[tools] skip bin generate: missing ${fileName}`);
return;
}
mergeJson[tableKey] = readJson(fp);
}
const { name, vsion } = resolveConfigNameVersion(projectPath);
const safeName = `${name}${vsion}`.replace(/[<>:"/\\|?*]+/g, '_');
const outDir = path_1.default.join(projectPath, 'bin');
const outFile = path_1.default.join(outDir, `${safeName}.bin`);
fs_1.default.mkdirSync(outDir, { recursive: true });
fs_1.default.writeFileSync(outFile, encodeForRuntime(mergeJson));
console.log(`[tools] generated config bin: ${outFile}`);
}
function collectJsFiles(dir, acc) {
if (!fs_1.default.existsSync(dir))
return;
const st = fs_1.default.statSync(dir);
if (!st.isDirectory())
return;
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
for (const e of entries) {
const full = path_1.default.join(dir, e.name);
if (e.isDirectory()) {
collectJsFiles(full, acc);
}
else if (e.isFile() && e.name.endsWith('.js')) {
acc.push(full);
}
}
}
function collectStripRoots(options, result) {
var _a, _b, _c, _d;
const roots = (0, MembershipSet_1.createMembershipSet)();
const add = (p) => {
if (p == null || p === '')
return;
const s = path_1.default.normalize(String(p).replace(/\//g, path_1.default.sep));
try {
if (fs_1.default.existsSync(s)) {
const st = fs_1.default.statSync(s);
if (st.isDirectory())
(0, MembershipSet_1.membershipAdd)(roots, s);
}
}
catch (_a) {
/* ignore */
}
};
add(result === null || result === void 0 ? void 0 : result.dest);
add((_a = result === null || result === void 0 ? void 0 : result.paths) === null || _a === void 0 ? void 0 : _a.dir);
add((_b = result === null || result === void 0 ? void 0 : result.paths) === null || _b === void 0 ? void 0 : _b.assets);
add((_c = result === null || result === void 0 ? void 0 : result.paths) === null || _c === void 0 ? void 0 : _c.bundleScripts);
add((_d = result === null || result === void 0 ? void 0 : result.paths) === null || _d === void 0 ? void 0 : _d.subpackages);
const projectPath = Editor.Project.path;
if (projectPath && (options === null || options === void 0 ? void 0 : options.buildPath) && (options === null || options === void 0 ? void 0 : options.outputName)) {
let bp = String(options.buildPath);
if (bp.includes('project:')) {
bp = path_1.default.join(projectPath, bp.replace(/^project:\/\//, '').replace(/^project:/, ''));
}
add(path_1.default.join(bp, options.outputName));
}
return (0, MembershipSet_1.membershipToArray)(roots);
}
function collectAllJsFiles(roots) {
const files = (0, MembershipSet_1.createMembershipSet)();
for (const root of roots) {
const acc = [];
collectJsFiles(root, acc);
for (const f of acc)
(0, MembershipSet_1.membershipAdd)(files, path_1.default.normalize(f));
}
return (0, MembershipSet_1.membershipToArray)(files);
}
async function minifyStripConsole(src) {
const base = {
compress: {
drop_console: true,
drop_debugger: true,
passes: 2,
},
mangle: false,
format: { comments: 'some' },
ecma: 2020,
};
let out = await (0, terser_1.minify)(src, base);
if (out.code != null)
return out.code;
out = await (0, terser_1.minify)(src, Object.assign(Object.assign({}, base), { module: true }));
if (out.code != null)
return out.code;
throw new Error('terser returned empty');
}
async function stripConsoleFromBuildOutput(roots) {
const files = collectAllJsFiles(roots);
if (files.length === 0) {
console.warn('[tools] stripConsole: no .js under roots', roots);
return;
}
let ok = 0;
let fail = 0;
for (const file of files) {
try {
const src = fs_1.default.readFileSync(file, 'utf8');
const code = await minifyStripConsole(src);
fs_1.default.writeFileSync(file, code, 'utf8');
ok++;
}
catch (err) {
fail++;
console.warn(`[tools] stripConsole skip (terser error): ${file}`, err);
}
}
console.log(`[tools] stripConsole done: ok=${ok}, fail=${fail}, roots=${roots.join('; ')}`);
}
function shouldStripConsole(options) {
var _a, _b;
const v = (_b = (_a = options === null || options === void 0 ? void 0 : options.packages) === null || _a === void 0 ? void 0 : _a.tools) === null || _b === void 0 ? void 0 : _b.stripConsole;
if (v === true)
return true;
if (v === 'true' || v === 1)
return true;
return false;
}
/** Cocos engine-adapter 中 tt.createOffscreenVideo 鸿蒙暂不支持,构建后替换为带环境判断的调用 */
function patchEngineAdapterHarmony(engineAdapterPath) {
if (!fs_1.default.existsSync(engineAdapterPath)) {
return;
}
const needle = 'tt.createOffscreenVideo()';
let src = fs_1.default.readFileSync(engineAdapterPath, 'utf8');
if (!src.includes(needle)) {
console.warn(`[tools] harmony patch skip: ${needle} not found in ${engineAdapterPath}`);
return;
}
const replacement = '(function(t){try{var i=t.getSystemInfoSync(),p=String(i.platform||"").toLowerCase(),s=String(i.system||"").toLowerCase();'
+ 'if(p==="openharmony"||s.indexOf("openharmony")>=0)return null;var f=t["create"+"Offscreen"+"Video"];'
+ 'return typeof f==="function"?f.call(t):null}catch(e){return null}})(tt)';
src = src.replace(needle, replacement);
fs_1.default.writeFileSync(engineAdapterPath, src, 'utf8');
console.log(`[tools] harmony patch applied: ${engineAdapterPath}`);
}
function patchHarmonyEngineAdapter(roots) {
for (const root of roots) {
patchEngineAdapterHarmony(path_1.default.join(root, 'engine-adapter.js'));
}
}
2 days ago
/**
* 抖音:game.json subPackages 每项 root 下必须有 game.js。
* Cocos 3.8 开启 wasm 资源分包时会写入 `__ccWasmAssetSubpkg__` → `cocos-js/assets/`,
* 目录里通常只有 spine.js.mem*.bin,缺入口会导致开发者工具编译失败。
* @param roots 构建输出根目录列表
*/
function ensureBytedanceWasmSubpkgGameJs(roots) {
var _a, _b;
for (let r = 0; r < roots.length; r++) {
const root = roots[r];
const gameJsonPath = path_1.default.join(root, 'game.json');
if (!fs_1.default.existsSync(gameJsonPath))
continue;
let gameJson;
try {
gameJson = JSON.parse(fs_1.default.readFileSync(gameJsonPath, 'utf8'));
}
catch (err) {
console.warn('[tools] bytedance wasm subpkg: 无法解析 game.json', gameJsonPath, err);
continue;
}
const packs = gameJson.subpackages;
if (!packs || packs.length === 0)
continue;
for (let i = 0; i < packs.length; i++) {
const packRoot = String((_b = (_a = packs[i]) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : '').replace(/\\/g, '/');
if (!packRoot)
continue;
// 仅补 wasm 资源分包;internal/main 由引擎生成完整入口
if (packRoot.indexOf('cocos-js/assets') < 0)
continue;
const dir = path_1.default.join(root, packRoot.replace(/\/+$/, ''));
if (!fs_1.default.existsSync(dir)) {
console.warn('[tools] bytedance wasm subpkg 目录不存在', dir);
continue;
}
const gameJsPath = path_1.default.join(dir, 'game.js');
if (fs_1.default.existsSync(gameJsPath))
continue;
fs_1.default.writeFileSync(gameJsPath, '// cocos wasm asset subpackage stub for bytedance\n', 'utf8');
console.log('[tools] bytedance wasm subpkg: 已补 game.js →', gameJsPath);
}
}
}
3 weeks ago
const WECHAT_ONLY_FALLBACK_FILES = ['Sdk30rWechat.ts', 'Sdk30rWechat.ts.meta'];
function getBuildPlatform(options) {
var _a, _b;
return String((_b = (_a = options === null || options === void 0 ? void 0 : options.platform) !== null && _a !== void 0 ? _a : options === null || options === void 0 ? void 0 : options.buildPlatform) !== null && _b !== void 0 ? _b : '');
}
function wechatOnlySdkPaths(projectPath) {
const srcDir = path_1.default.join(projectPath, 'tools', 'wechat-only');
const destDir = path_1.default.join(projectPath, 'assets', 'script', 'sdk');
return { srcDir, destDir };
}
2 days ago
/** wechat-only 目录下需复制的文件(跳过说明文档) */
3 weeks ago
function listWechatOnlyFiles(srcDir) {
if (!fs_1.default.existsSync(srcDir))
return [];
const names = fs_1.default.readdirSync(srcDir);
const out = [];
for (let i = 0; i < names.length; i++) {
const name = names[i];
if (name.endsWith('.md'))
continue;
const full = path_1.default.join(srcDir, name);
if (fs_1.default.statSync(full).isFile())
out.push(name);
}
return out;
}
2 days ago
/** 从 assets 删除微信独占脚本,避免残留进抖音/快手包 */
3 weeks ago
function removeWechatOnlySdkFromAssets(srcDir, destDir, platform) {
const names = listWechatOnlyFiles(srcDir);
for (let i = 0; i < WECHAT_ONLY_FALLBACK_FILES.length; i++) {
const n = WECHAT_ONLY_FALLBACK_FILES[i];
if (names.indexOf(n) < 0)
names.push(n);
}
let removed = 0;
for (let i = 0; i < names.length; i++) {
const dest = path_1.default.join(destDir, names[i]);
if (fs_1.default.existsSync(dest)) {
fs_1.default.unlinkSync(dest);
removed++;
}
}
if (removed > 0) {
console.log(`[tools] removed wechat-only 30r sdk from assets (${platform || 'unknown platform'})`);
}
}
2 days ago
/**
* 仅纯微信 wechatgame:把 tools/wechat-only 拷进 assets。
* 抖音/快手/B 站(勾选导出 biligame)不拷,避免 30r 进包。
*/
3 weeks ago
function syncWechatOnlySdk(options, mode) {
2 days ago
var _a, _b;
3 weeks ago
const projectPath = Editor.Project.path;
if (!projectPath)
return;
const { srcDir, destDir } = wechatOnlySdkPaths(projectPath);
const platform = getBuildPlatform(options);
const isWechat = platform === 'wechatgame';
2 days ago
const isBiliGame = !!((_b = (_a = options === null || options === void 0 ? void 0 : options.packages) === null || _a === void 0 ? void 0 : _a['biligame-builder']) === null || _b === void 0 ? void 0 : _b.isBiliGame);
3 weeks ago
if (mode === 'after' || !isWechat || isBiliGame) {
removeWechatOnlySdkFromAssets(srcDir, destDir, isBiliGame ? 'biligame' : platform);
return;
}
const files = listWechatOnlyFiles(srcDir);
if (files.length === 0) {
console.warn('[tools] wechat-only sdk missing:', srcDir);
return;
}
fs_1.default.mkdirSync(destDir, { recursive: true });
for (let i = 0; i < files.length; i++) {
const name = files[i];
fs_1.default.copyFileSync(path_1.default.join(srcDir, name), path_1.default.join(destDir, name));
}
console.log('[tools] copied wechat-only 30r sdk into assets/script/sdk');
}
function onBeforeBuild(options, _result) {
try {
syncWechatOnlySdk(options, 'before');
}
catch (err) {
console.error('[tools] onBeforeBuild syncWechatOnlySdk failed:', err);
}
try {
console.log('[tools] onBeforeBuild: generate config bin');
generateConfigBin();
}
catch (err) {
console.error('[tools] onBeforeBuild generate config bin failed:', err);
}
return true;
}
exports.onBeforeBuild = onBeforeBuild;
async function onAfterBuild(options, result) {
try {
2 days ago
// 产物已编译进包,assets 里的拷贝必须删掉,避免下次抖音/快手构建误打入
3 weeks ago
syncWechatOnlySdk(options, 'after');
}
catch (err) {
console.error('[tools] onAfterBuild syncWechatOnlySdk failed:', err);
}
try {
if (shouldStripConsole(options)) {
const roots = collectStripRoots(options, result);
if (roots.length === 0) {
console.warn('[tools] stripConsole 已勾选但未能解析构建输出目录(检查 result.dest / paths / buildPath+outputName)');
}
else {
console.log('[tools] onAfterBuild stripConsole →', roots.join(' | '));
await stripConsoleFromBuildOutput(roots);
}
}
}
catch (err) {
console.error('[tools] onAfterBuild stripConsole failed:', err);
}
try {
if (getBuildPlatform(options) === 'bytedance-mini-game') {
const roots = collectStripRoots(options, result);
patchHarmonyEngineAdapter(roots);
2 days ago
ensureBytedanceWasmSubpkgGameJs(roots);
3 weeks ago
}
}
catch (err) {
2 days ago
console.error('[tools] onAfterBuild bytedance patch failed:', err);
3 weeks ago
}
try {
console.log('[tools] onAfterBuild: generate config bin');
generateConfigBin();
}
catch (err) {
console.error('[tools] onAfterBuild generate config bin failed:', err);
}
return true;
}
exports.onAfterBuild = onAfterBuild;
2 days ago
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGQtaG9va3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zb3VyY2UvYnVpbGQtaG9va3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUE7OztHQUdHO0FBQ0gsNENBQW9CO0FBQ3BCLGdEQUF3QjtBQUN4QixrREFBd0I7QUFDeEIsbUNBQWdDO0FBQ2hDLG1EQUl5QjtBQUVaLFFBQUEsVUFBVSxHQUFHLElBQUksQ0FBQztBQUUvQixTQUFTLFFBQVEsQ0FBQyxRQUFnQjtJQUM5QixNQUFNLEdBQUcsR0FBRyxZQUFFLENBQUMsWUFBWSxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUM5QyxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDM0IsQ0FBQztBQUVELFNBQVMsd0JBQXdCLENBQUMsV0FBbUI7O0lBQ2pELE1BQU0sYUFBYSxHQUFHLGNBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLGNBQWMsQ0FBQyxDQUFDO0lBQ3hGLE1BQU0sZ0JBQWdCLEdBQUcsWUFBRSxDQUFDLFlBQVksQ0FBQyxhQUFhLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFFaEUsTUFBTSxXQUFXLEdBQUcsbUVBQW1FLENBQUM7SUFDeEYsTUFBTSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUM7SUFDNUMsSUFBSSxDQUFDLEdBQTJCLElBQUksQ0FBQztJQUNyQyxPQUFPLENBQUMsQ0FBQyxHQUFHLFdBQVcsQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxFQUFFO1FBQzdDLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0tBQzdCO0lBRUQsTUFBTSxZQUFZLEdBQUcsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLDhEQUE4RCxDQUFDLENBQUM7SUFDNUcsTUFB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