dragonflight/services/premiere-plugin/build/build-zxp.mjs

107 lines
3.6 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
// Build a signed .zxp for the Dragonflight Premiere panel.
//
// - Reads version from ../CSXS/manifest.xml (<ExtensionBundleVersion>)
// - Generates a self-signed cert on first run (cert/dragonflight-selfsigned.p12)
// - Stages the panel bundle into stage/ (excludes build/ + dev cruft)
// - Calls zxp-sign-cmd to sign + package into dist/
//
// Usage: node build-zxp.mjs
// Output: dist/dragonflight-premiere-panel-<version>.zxp
import { mkdirSync, existsSync, readFileSync, writeFileSync, rmSync, cpSync, readdirSync, statSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import { randomBytes } from 'node:crypto';
import zxp from 'zxp-sign-cmd';
const HERE = dirname(fileURLToPath(import.meta.url));
const PANEL_DIR = resolve(HERE, '..');
const MANIFEST = join(PANEL_DIR, 'CSXS', 'manifest.xml');
const CERT_DIR = join(HERE, 'cert');
const CERT_FILE = join(CERT_DIR, 'dragonflight-selfsigned.p12');
const PASS_FILE = join(CERT_DIR, 'cert-passphrase.txt');
const STAGE_DIR = join(HERE, 'stage');
const DIST_DIR = join(HERE, 'dist');
// Files/dirs to exclude from the staged bundle.
const EXCLUDE = new Set(['build', 'install-windows.ps1', '.git', '.gitignore', 'node_modules']);
function readVersion() {
const xml = readFileSync(MANIFEST, 'utf8');
const m = xml.match(/<ExtensionBundleVersion>([^<]+)<\/ExtensionBundleVersion>/);
if (!m) throw new Error(`Could not find <ExtensionBundleVersion> in ${MANIFEST}`);
return m[1].trim();
}
function ensureCert() {
mkdirSync(CERT_DIR, { recursive: true });
if (existsSync(CERT_FILE) && existsSync(PASS_FILE)) {
return readFileSync(PASS_FILE, 'utf8').trim();
}
console.log('No signing cert found — generating self-signed cert (one-time)…');
const passphrase = randomBytes(24).toString('base64url');
writeFileSync(PASS_FILE, passphrase + '\n', { mode: 0o600 });
return new Promise((res, rej) => {
zxp.selfSignedCert({
country: 'US',
province: 'WA',
org: 'Wild Dragon LLC',
name: 'Wild Dragon LLC',
password: passphrase,
output: CERT_FILE,
validityDays: 365 * 25,
}, (err) => {
if (err) return rej(err);
console.log(` wrote ${CERT_FILE}`);
console.log(` wrote ${PASS_FILE}`);
console.log(' >> COMMIT both files so future builds reuse them. <<');
res(passphrase);
});
});
}
function stageBundle() {
if (existsSync(STAGE_DIR)) rmSync(STAGE_DIR, { recursive: true, force: true });
mkdirSync(STAGE_DIR, { recursive: true });
for (const entry of readdirSync(PANEL_DIR)) {
if (EXCLUDE.has(entry)) continue;
const src = join(PANEL_DIR, entry);
const dst = join(STAGE_DIR, entry);
cpSync(src, dst, { recursive: true });
}
}
function signZxp(version, passphrase) {
mkdirSync(DIST_DIR, { recursive: true });
const output = join(DIST_DIR, `dragonflight-premiere-panel-${version}.zxp`);
if (existsSync(output)) rmSync(output);
return new Promise((res, rej) => {
zxp.sign({
input: STAGE_DIR,
output,
cert: CERT_FILE,
password: passphrase,
}, (err) => {
if (err) return rej(err);
const bytes = statSync(output).size;
console.log(`Built ${output} (${(bytes / 1024).toFixed(1)} KB)`);
res(output);
});
});
}
async function main() {
const version = readVersion();
console.log(`Dragonflight Premiere panel — ZXP build v${version}`);
const passphrase = await ensureCert();
stageBundle();
await signZxp(version, passphrase);
rmSync(STAGE_DIR, { recursive: true, force: true });
}
main().catch((err) => {
console.error('ZXP build failed:', err);
process.exit(1);
});