92 lines
6.9 KiB
JavaScript
92 lines
6.9 KiB
JavaScript
'use strict';
|
|
const fs = require('fs'), path = require('path'), vm = require('vm'), crypto = require('crypto');
|
|
const zip = require('./zip'), names = require('./bundles.json');
|
|
const hash = data => crypto.createHash('sha256').update(data).digest('hex');
|
|
function files(root, relative = '') {
|
|
return fs.readdirSync(path.join(root, relative)).sort().reduce((all, name) => {
|
|
const file = path.posix.join(relative, name), stat = fs.lstatSync(path.join(root, file));
|
|
if (stat.isSymbolicLink()) throw Error('Unexpected symlink: ' + file);
|
|
return all.concat(stat.isDirectory() ? files(root, file) : [file]);
|
|
}, []);
|
|
}
|
|
function pack(destination, bundles = names) {
|
|
const root = path.resolve(destination), context = { window: {} };
|
|
vm.runInNewContext(fs.readFileSync(path.join(root, 'src/settings.js'), 'utf8'), context);
|
|
const settings = context.window._CCSettings, server = String(settings.server || '').replace(/\/+$/, '');
|
|
if (!/^https:\/\/[^\s?#]+$/.test(server)) throw Error('Configure the WeChat HTTPS resource server');
|
|
const manifest = { schemaVersion: 1, bundles: {} }, archives = [];
|
|
for (const name of bundles) {
|
|
if (!/^[a-z][a-zA-Z0-9_]*$/.test(name) || manifest.bundles[name]) throw Error('Invalid or duplicate bundle name');
|
|
if (!(settings.remoteBundles || []).includes(name)) throw Error('Activity must be a remote bundle: ' + name);
|
|
const version = (settings.bundleVers || {})[name] || '', configFile = 'config.' + (version ? version + '.' : '') + 'json';
|
|
const folder = path.join(root, 'remote', name), config = JSON.parse(fs.readFileSync(path.join(folder, configFile), 'utf8'));
|
|
if (config.name !== name || config.isZip) throw Error('Use Merge All JSON for ' + name);
|
|
if (!fs.existsSync(path.join(root, 'src/scripts', name, 'index.js'))) throw Error('Missing local bundle script: ' + name);
|
|
const entries = files(folder).filter(file => !/^config(?:\.[^.]+)?\.json$/.test(file) || file === configFile).map(file => {
|
|
if (!/^[a-zA-Z0-9_./+@=-]+$/.test(file)) throw Error('Unsupported generated resource path: ' + file);
|
|
if (/\.(?:js|jsc)$/i.test(file)) throw Error('Scripts must remain in the client package: ' + file);
|
|
if (file === 'bundle-manifest.json') throw Error('Reserved resource path: ' + file);
|
|
return { name: file, data: fs.readFileSync(path.join(folder, file)) };
|
|
});
|
|
const metadata = { schemaVersion: 1, name, config: configFile, version,
|
|
files: entries.map(e => ({ path: e.name, size: e.data.length, sha256: hash(e.data) })) };
|
|
entries.push({ name: 'bundle-manifest.json', data: Buffer.from(JSON.stringify(metadata)) });
|
|
const archive = zip(entries), zipHash = hash(archive), file = name + '/' + name + '.' + zipHash + '.zip';
|
|
manifest.bundles[name] = { file, hash: zipHash, size: archive.length };
|
|
archives.push({ file, data: archive });
|
|
}
|
|
// A sibling output cannot accidentally enter the WeChat main code package.
|
|
const publishDir = path.join(path.dirname(root), path.basename(root) + '-activities', 'remote');
|
|
fs.mkdirSync(publishDir, { recursive: true });
|
|
for (const entry of archives) {
|
|
const target = path.join(publishDir, entry.file);
|
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
fs.writeFileSync(target + '.tmp', entry.data); fs.renameSync(target + '.tmp', target);
|
|
}
|
|
// Never overwrite a working manifest until every archive has been written.
|
|
const manifestFile = path.join(publishDir, 'manifest.json');
|
|
fs.writeFileSync(manifestFile + '.tmp', JSON.stringify(manifest, null, 2) + '\n'); fs.renameSync(manifestFile + '.tmp', manifestFile);
|
|
// Pages depend on art: switch this set together while still downloading pages on demand.
|
|
const cloudRise = ['matching', 'stage1', 'stage2', 'stage3', 'art'];
|
|
const groups = cloudRise.every(name => bundles.includes(name)) ? { cloudRise } : {};
|
|
fs.writeFileSync(path.join(root, 'activity-bundles-config.js'), 'module.exports=' + JSON.stringify({ baseUrl: server + '/remote/', bundles, groups }) + ';\n');
|
|
for (const [source, target] of [['runtime.js', 'activity-bundles.js'], ['sha256.js', 'activity-bundles-sha256.js']])
|
|
fs.copyFileSync(path.join(__dirname, source), path.join(root, target));
|
|
const bootFile = path.join(root, 'game.js'); let boot = fs.readFileSync(bootFile, 'utf8');
|
|
// Also repair incremental builds that still contain the former Cloud Rise bootstrap.
|
|
boot = boot.replace(/require\(['"]\.\/cloud-rise-package['"]\);\s*/g, '');
|
|
const entry = "require('./activity-bundles');";
|
|
if (!boot.includes(entry)) {
|
|
if (!boot.includes('window.boot();')) throw Error('Missing WeChat boot insertion point');
|
|
boot = boot.replace('window.boot();', entry + '\nwindow.boot();');
|
|
}
|
|
fs.writeFileSync(bootFile, boot);
|
|
settings.subpackages = (settings.subpackages || []).filter(name => !bundles.includes(name) && name !== 'cloud_rise');
|
|
fs.writeFileSync(path.join(root, 'src/settings.js'), 'window._CCSettings=' + JSON.stringify(settings) + ';\n');
|
|
const gameFile = path.join(root, 'game.json');
|
|
if (fs.existsSync(gameFile)) {
|
|
const game = JSON.parse(fs.readFileSync(gameFile, 'utf8'));
|
|
game.subpackages = (game.subpackages || []).filter(item => !bundles.includes(item.name) && item.name !== 'cloud_rise');
|
|
fs.writeFileSync(gameFile, JSON.stringify(game, null, 2) + '\n');
|
|
}
|
|
if (fs.existsSync(path.join(root, 'memory-diagnostics.js'))) {
|
|
const game = fs.existsSync(gameFile) ? JSON.parse(fs.readFileSync(gameFile, 'utf8')) : {};
|
|
const build = { builtAt: new Date().toISOString(),
|
|
id: hash(Buffer.from(files(root, 'src/scripts').filter(name => /\.js$/.test(name))
|
|
.map(name => name + ':' + hash(fs.readFileSync(path.join(root, name)))).join('\n') + JSON.stringify(manifest))).slice(0, 20),
|
|
flags: { iOSHighPerformance: game.iOSHighPerformance === true, 'iOSHighPerformance+': game['iOSHighPerformance+'] === true } };
|
|
fs.writeFileSync(path.join(root, 'memory-diagnostics-build.js'), 'module.exports=' + JSON.stringify(build) + ';\n');
|
|
}
|
|
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', ...bundles.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, manifest };
|
|
}
|
|
module.exports = { pack, names };
|
|
if (require.main === module) console.log(JSON.stringify(pack(process.argv[2] || 'build/wechatgame'), null, 2));
|