MatchMaster/packages/cloud-rise-package/pack.js
2026-09-24 16:50:43 +08:00

96 lines
6.2 KiB
JavaScript

'use strict';
const fs = require('fs'), path = require('path'), vm = require('vm');
const names = ['matching', 'stage1', 'stage2', 'stage3', 'art'];
function files(folder) {
if (!fs.existsSync(folder)) return [];
return fs.readdirSync(folder).sort().reduce((out, name) => {
const file = path.join(folder, name), stat = fs.lstatSync(file);
if (stat.isSymbolicLink()) throw Error('Unexpected symlink in build output: ' + file);
return out.concat(stat.isDirectory() ? files(file) : [file]);
}, []);
}
function pack(destination, versionFile = path.join(__dirname, '../../assets/cloud_rise/version.json')) {
const release = JSON.parse(fs.readFileSync(versionFile, 'utf8'));
if (typeof release.version !== 'string' || release.version.length > 32 || !/^\d+\.\d+\.\d+$/.test(release.version)) {
throw Error('Cloud Rise version must have three numeric parts, e.g. 1.0.0');
}
const root = path.resolve(destination), remote = path.join(root, 'remote');
const gameFile = path.join(root, 'game.json'), settingsFile = path.join(root, 'src/settings.js');
const game = JSON.parse(fs.readFileSync(gameFile, 'utf8'));
const context = { window: {} };
vm.runInNewContext(fs.readFileSync(settingsFile, 'utf8'), context);
const settings = context.window._CCSettings;
if (!settings || !settings.bundleVers) throw Error('Missing Creator bundle settings');
const server = String(settings.server || '').replace(/\/+$/, '');
if (!/^https:\/\/[^\s?#]+$/.test(server)) throw Error('Configure the WeChat HTTPS resource server before building Cloud Rise');
const entries = [];
for (const name of names) {
const folder = path.join(remote, name), version = settings.bundleVers[name];
if (!version || !fs.existsSync(path.join(folder, 'config.' + version + '.json'))) {
throw Error('Missing remote Cloud Rise bundle (enable MD5 Cache): ' + name);
}
if (!(settings.remoteBundles || []).includes(name)) throw Error('Cloud Rise must be a remote bundle: ' + name);
const config = JSON.parse(fs.readFileSync(path.join(folder, 'config.' + version + '.json'), 'utf8'));
if (config.name !== name || config.isZip) throw Error('Use Merge All JSON for independent remote bundles: ' + name);
if (!fs.existsSync(path.join(root, 'src/scripts', name, 'index.js'))) {
throw Error('Missing locally shipped Creator bundle script: ' + name);
}
for (const file of files(folder)) {
if (path.dirname(file) === folder && /^config\..*\.json$/.test(path.basename(file))
&& path.basename(file) !== 'config.' + version + '.json') continue;
if (/\.js$/i.test(file)) throw Error('Executable scripts must remain in the client package: ' + file);
const relative = path.relative(folder, file).replace(/\\/g, '/');
const isConfig = relative === 'config.' + version + '.json';
// A fixed entry point avoids a separate manifest of Creator's config hashes.
const target = isConfig ? 'config.json' : relative;
const data = isConfig ? Buffer.from(JSON.stringify(Object.assign({}, config, { cloudRiseVersion: release.version }))) : fs.readFileSync(file);
entries.push({ name: name + '/' + target, data });
}
}
const config = { baseUrl: server + '/remote/cloud_rise/' };
const bootFile = path.join(root, 'game.js');
let boot = fs.readFileSync(bootFile, 'utf8');
const entry = "require('./cloud-rise-package');";
if (!boot.includes(entry)) {
if (!boot.includes('window.boot();')) throw Error('Missing WeChat boot insertion point');
boot = boot.replace('window.boot();', entry + '\nwindow.boot();');
}
// Publish loose remote bundles outside the WeChat code upload directory.
const publishDir = path.join(path.dirname(root), path.basename(root) + '-cloud-rise', 'remote', 'cloud_rise');
fs.mkdirSync(publishDir, { recursive: true });
const oldZip = path.join(publishDir, 'cloud_rise.zip');
if (fs.existsSync(oldZip)) fs.unlinkSync(oldZip);
for (const name of names) {
const target = path.resolve(publishDir, name);
if (path.dirname(target) !== path.resolve(publishDir) || !names.includes(path.basename(target))) throw Error('Unsafe bundle output');
if (fs.existsSync(target)) fs.rmdirSync(target, { recursive: true });
}
for (const file of entries) {
const target = path.join(publishDir, file.name);
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, file.data);
}
fs.writeFileSync(path.join(publishDir, 'version.json'), JSON.stringify({ version: release.version }, null, 2) + '\n');
fs.writeFileSync(path.join(root, 'cloud-rise-package-config.js'), 'module.exports=' + JSON.stringify(config) + ';\n');
fs.copyFileSync(path.join(__dirname, 'runtime.js'), path.join(root, 'cloud-rise-package.js'));
game.subpackages = (game.subpackages || []).filter(item => !names.includes(item.name) && item.name !== 'cloud_rise');
settings.subpackages = (settings.subpackages || []).filter(name => !names.includes(name) && name !== 'cloud_rise');
fs.writeFileSync(gameFile, JSON.stringify(game, null, 2) + '\n');
fs.writeFileSync(settingsFile, 'window._CCSettings=' + JSON.stringify(settings) + ';\n');
fs.writeFileSync(bootFile, boot);
// Exclude remote output and stale subpackages from the WeChat code upload.
const projectFile = path.join(root, 'project.config.json');
if (fs.existsSync(projectFile)) {
const project = JSON.parse(fs.readFileSync(projectFile, 'utf8'));
project.packOptions = project.packOptions || {};
const ignore = project.packOptions.ignore || [];
for (const value of ['remote', 'subpackages/cloud_rise', ...names.map(name => 'subpackages/' + name)]) {
if (!ignore.some(item => item.type === 'folder' && item.value === value)) ignore.push({ type: 'folder', value });
}
project.packOptions.ignore = ignore;
fs.writeFileSync(projectFile, JSON.stringify(project, null, 2) + '\n');
}
return { publishDir, version: release.version, size: entries.reduce((sum, file) => sum + file.data.length, 0) };
}
module.exports = { pack, names };