diff --git a/electron/package.json b/electron/package.json index 66f9115..a601866 100644 --- a/electron/package.json +++ b/electron/package.json @@ -13,7 +13,8 @@ "stage:offline-runtime": "node ./scripts/stage-offline-runtime.mjs", "prepare:runtime": "node ./scripts/prepare-runtime.mjs", "verify:runtime": "node ./scripts/verify-runtime.mjs", - "dist": "pnpm run prepare:runtime && pnpm run build && pnpm run gen:icons && pnpm run verify:runtime && electron-builder --win --publish never" + "verify:win-package": "node ./scripts/verify-win-package.mjs", + "dist": "pnpm run prepare:runtime && pnpm run build && pnpm run gen:icons && pnpm run verify:runtime && electron-builder --win --publish never && pnpm run verify:win-package" }, "dependencies": { "d3": "^7.9.0", @@ -81,12 +82,19 @@ ], "win": { "icon": "build/icon.ico", + "artifactName": "${productName}-Windows-x64-${version}.${ext}", "target": [ { "target": "portable", "arch": [ "x64" ] + }, + { + "target": "zip", + "arch": [ + "x64" + ] } ] }, diff --git a/electron/scripts/verify-win-package.mjs b/electron/scripts/verify-win-package.mjs new file mode 100644 index 0000000..93aa604 --- /dev/null +++ b/electron/scripts/verify-win-package.mjs @@ -0,0 +1,76 @@ +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const electronRoot = path.resolve(__dirname, '..') +const distRoot = path.join(electronRoot, 'dist') +const unpackedRoot = path.join(distRoot, 'win-unpacked') + +const pkg = JSON.parse(fs.readFileSync(path.join(electronRoot, 'package.json'), 'utf8')) +const productName = pkg.build?.productName || pkg.productName || pkg.name + +const requiredFiles = [ + path.join(unpackedRoot, `${productName}.exe`), + path.join(unpackedRoot, 'ffmpeg.dll'), + path.join(unpackedRoot, 'icudtl.dat'), + path.join(unpackedRoot, 'resources', 'app.asar'), + path.join(unpackedRoot, 'resources', 'backend', 'web2.py'), +] + +const pythonCandidates = [ + path.join(unpackedRoot, 'resources', 'backend', 'python', 'python.exe'), + path.join(unpackedRoot, 'resources', 'backend', 'python', 'Scripts', 'python.exe'), +] + +const ffmpegCandidates = [ + path.join(unpackedRoot, 'resources', 'backend', 'ffmpeg', 'ffmpeg.exe'), + path.join(unpackedRoot, 'resources', 'backend', 'ffmpeg', 'bin', 'ffmpeg.exe'), + path.join(unpackedRoot, 'resources', 'backend', 'tools', 'ffmpeg', 'ffmpeg.exe'), + path.join(unpackedRoot, 'resources', 'backend', 'tools', 'ffmpeg', 'bin', 'ffmpeg.exe'), +] + +const errors = [] + +if (!fs.existsSync(unpackedRoot)) { + errors.push(`missing unpacked app directory: ${unpackedRoot}`) +} + +for (const file of requiredFiles) { + if (!fs.existsSync(file)) errors.push(`missing required file: ${file}`) +} + +if (!pythonCandidates.some((p) => fs.existsSync(p))) { + errors.push(`missing bundled Python executable:\n${pythonCandidates.map((p) => `- ${p}`).join('\n')}`) +} + +if (!ffmpegCandidates.some((p) => fs.existsSync(p))) { + errors.push(`missing bundled backend ffmpeg.exe:\n${ffmpegCandidates.map((p) => `- ${p}`).join('\n')}`) +} + +const distEntries = fs.existsSync(distRoot) ? fs.readdirSync(distRoot, { withFileTypes: true }) : [] +const rootExeArtifacts = distEntries + .filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.exe')) + .map((e) => e.name) +const rootZipArtifacts = distEntries + .filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.zip')) + .map((e) => e.name) + +if (!rootExeArtifacts.length) { + errors.push(`missing single-file portable exe in dist root: ${distRoot}`) +} + +if (!rootZipArtifacts.length) { + errors.push(`missing complete lazy-package zip in dist root: ${distRoot}`) +} + +if (errors.length) { + console.error('[verify:win-package] Windows package check failed:\n') + for (const error of errors) console.error(`- ${error}\n`) + process.exit(1) +} + +console.log('[verify:win-package] OK: Windows portable exe, zip package, Electron DLLs, Python, and ffmpeg are present.') +console.log(`[verify:win-package] portable exe: ${rootExeArtifacts.join(', ')}`) +console.log(`[verify:win-package] lazy zip: ${rootZipArtifacts.join(', ')}`)