Files
makelore/scripts/after-pack.cjs
2026-07-29 17:22:35 +08:00

217 lines
6.2 KiB
JavaScript

const { execFileSync } = require('child_process');
const { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } = require('fs');
const { join } = require('path');
const MEOWA_RELEASE_CREDENTIAL_FILE_NAME = 'meowa-game-assets-credential.json';
exports.default = async function afterPack(context) {
const platform = context.electronPlatformName;
console.log(`[after-pack] Target: ${platform}/${context.arch}`);
writeMeowaReleaseCredential(context);
assertNoPersistedUserData(context.appOutDir);
if (platform !== 'win32') {
return;
}
patchWindowsExecutableResources(context);
patchNsisExtractionMacro();
};
function writeMeowaReleaseCredential(context) {
const apiKey = process.env.MEOWART_API_KEY?.trim();
if (!apiKey) {
console.log('[after-pack] Meowa release credential not included; MEOWART_API_KEY is unset.');
return false;
}
const resourcesDir = context.packager.getResourcesDir(context.appOutDir);
const target = join(resourcesDir, 'resources', MEOWA_RELEASE_CREDENTIAL_FILE_NAME);
mkdirSync(join(resourcesDir, 'resources'), { recursive: true });
writeFileSync(
target,
`${JSON.stringify({ schemaVersion: 1, apiKey })}\n`,
{ encoding: 'utf8', mode: 0o600 },
);
console.log('[after-pack] Meowa release credential included in packaged Main resources.');
return true;
}
const PERSISTED_USER_DATA_FILES = new Set([
'gateway-prelaunch-maintenance-cache.json',
'niancode-device-identity.json',
'niancode-providers.json',
'opencode-projects.json',
'settings.json',
]);
function assertNoPersistedUserData(appOutDir) {
const offenders = [];
collectPersistedUserDataFiles(appOutDir, offenders);
if (offenders.length > 0) {
throw new Error(
[
'Persisted Makelore user data must not be packaged.',
'Remove these files from the package input:',
...offenders.map((file) => `- ${file}`),
].join('\n'),
);
}
}
function collectPersistedUserDataFiles(dir, offenders) {
if (!existsSync(dir)) return;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) {
collectPersistedUserDataFiles(path, offenders);
continue;
}
if (entry.isFile() && PERSISTED_USER_DATA_FILES.has(entry.name)) {
offenders.push(path);
}
}
}
function patchWindowsExecutableResources(context) {
const rcedit = findRcedit();
if (!rcedit) {
console.log('[after-pack] rcedit-x64.exe not found; skipping executable resource patch.');
return;
}
const appInfo = context.packager.appInfo;
const executable = join(context.appOutDir, `${appInfo.productFilename}.exe`);
if (!existsSync(executable)) {
console.log(`[after-pack] ${appInfo.productFilename}.exe not found; skipping executable resource patch.`);
return;
}
const icon = join(context.packager.info.buildResourcesDir, 'icons', 'icon.ico');
const version = typeof appInfo.getVersionInWeirdWindowsForm === 'function'
? appInfo.getVersionInWeirdWindowsForm()
: normalizeWindowsVersion(appInfo.version);
const args = [
executable,
'--set-version-string',
'FileDescription',
appInfo.productName,
'--set-version-string',
'ProductName',
appInfo.productName,
'--set-version-string',
'LegalCopyright',
appInfo.copyright,
'--set-file-version',
version,
'--set-product-version',
version,
'--set-version-string',
'InternalName',
appInfo.productFilename,
'--set-version-string',
'OriginalFilename',
'',
];
if (existsSync(icon)) {
args.push('--set-icon', icon);
}
execFileSync(rcedit, args, { stdio: 'inherit' });
console.log(`[after-pack] Patched ${appInfo.productFilename}.exe resources with ${appInfo.productName}.`);
}
function normalizeWindowsVersion(version) {
const parts = String(version ?? '0.0.0')
.split('.')
.map((part) => Number.parseInt(part, 10))
.filter((part) => Number.isFinite(part));
while (parts.length < 4) parts.push(0);
return parts.slice(0, 4).join('.');
}
function findRcedit() {
const explicitDir = process.env.ELECTRON_BUILDER_RCEDIT_PATH;
if (explicitDir) {
const explicit = join(explicitDir, 'rcedit-x64.exe');
if (existsSync(explicit)) return explicit;
}
const cacheRoots = [
process.env.ELECTRON_BUILDER_CACHE,
process.env.LOCALAPPDATA ? join(process.env.LOCALAPPDATA, 'electron-builder', 'Cache') : null,
join(__dirname, '..', '.cache', 'electron-builder', 'Cache'),
].filter(Boolean);
for (const root of cacheRoots) {
const winCodeSign = join(root, 'winCodeSign');
const found = findRceditInWinCodeSignCache(winCodeSign);
if (found) return found;
}
return null;
}
function findRceditInWinCodeSignCache(winCodeSign) {
if (!existsSync(winCodeSign)) return null;
for (const entry of readdirSync(winCodeSign)) {
const candidate = join(winCodeSign, entry, 'rcedit-x64.exe');
if (!existsSync(candidate)) continue;
if (!statSync(candidate).isFile()) continue;
return candidate;
}
return null;
}
function patchNsisExtractionMacro() {
const extractNsh = join(
__dirname,
'..',
'node_modules',
'app-builder-lib',
'templates',
'nsis',
'include',
'extractAppPackage.nsh',
);
if (!existsSync(extractNsh)) {
console.log('[after-pack] NSIS extractAppPackage.nsh not found; skipping patch.');
return;
}
const original = readFileSync(extractNsh, 'utf8');
if (original.includes('Makelore-patched')) {
console.log('[after-pack] NSIS extractAppPackage.nsh already patched.');
return;
}
const replacement = [
'!macro extractUsing7za file',
' ; Makelore-patched: extract directly to $INSTDIR.',
' Nsis7z::Extract "$PLUGINSDIR\\\\${file}" "$INSTDIR"',
'!macroend',
].join('\n');
const patched = original.replace(/!macro extractUsing7za file[\s\S]*?!macroend/, replacement);
if (patched === original) {
console.log('[after-pack] NSIS extractUsing7za macro not found; skipping patch.');
return;
}
writeFileSync(extractNsh, patched, 'utf8');
console.log('[after-pack] Patched NSIS extractAppPackage.nsh for direct extraction.');
}
exports.assertNoPersistedUserData = assertNoPersistedUserData;
exports.writeMeowaReleaseCredential = writeMeowaReleaseCredential;