diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3393677..bb7495e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,8 +45,6 @@ jobs: - name: Package and Publish macOS arm64 (unsigned DMG) env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WF_SIGN_PRIVATE_KEY: ${{ secrets.WF_SIGN_PRIVATE_KEY }} - WF_SIGNING_REQUIRED: "1" CSC_IDENTITY_AUTO_DISCOVERY: "false" run: | npx electron-builder --mac dmg --arm64 --publish always @@ -84,8 +82,6 @@ jobs: - name: Package and Publish Linux env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WF_SIGN_PRIVATE_KEY: ${{ secrets.WF_SIGN_PRIVATE_KEY }} - WF_SIGNING_REQUIRED: "1" run: | npx electron-builder --linux --publish always @@ -122,8 +118,6 @@ jobs: - name: Package and Publish env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WF_SIGN_PRIVATE_KEY: ${{ secrets.WF_SIGN_PRIVATE_KEY }} - WF_SIGNING_REQUIRED: "1" run: | npx electron-builder --publish always @@ -160,8 +154,6 @@ jobs: - name: Package and Publish Windows arm64 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WF_SIGN_PRIVATE_KEY: ${{ secrets.WF_SIGN_PRIVATE_KEY }} - WF_SIGNING_REQUIRED: "1" run: | npx electron-builder --win nsis --arm64 --publish always '--config.artifactName=${productName}-${version}-arm64-Setup.${ext}' diff --git a/package.json b/package.json index 22177f8..0b237bc 100644 --- a/package.json +++ b/package.json @@ -63,8 +63,6 @@ }, "build": { "appId": "com.WeFlow.app", - "afterPack": "scripts/afterPack-sign-manifest.cjs", - "afterSign": "scripts/afterPack-sign-manifest.cjs", "publish": { "provider": "github", "owner": "hicccc77", diff --git a/scripts/afterPack-sign-manifest.cjs b/scripts/afterPack-sign-manifest.cjs deleted file mode 100644 index ec20532..0000000 --- a/scripts/afterPack-sign-manifest.cjs +++ /dev/null @@ -1,289 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable no-console */ -const fs = require('node:fs'); -const path = require('node:path'); -const crypto = require('node:crypto'); - -const MANIFEST_NAME = '.wf_manifest.json'; -const SIGNATURE_NAME = '.wf_manifest.sig'; -const MODULE_FILENAME = { - win32: 'wcdb_api.dll', - darwin: 'libwcdb_api.dylib', - linux: 'libwcdb_api.so', -}; - -function readTextIfExists(filePath) { - try { - if (!fs.existsSync(filePath)) return null; - return fs.readFileSync(filePath, 'utf8'); - } catch { - return null; - } -} - -function loadEnvFile(projectDir, fileName) { - const envPath = path.join(projectDir, fileName); - const content = readTextIfExists(envPath); - if (!content) return false; - - for (const line of content.split(/\r?\n/)) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith('#')) continue; - const eq = trimmed.indexOf('='); - if (eq <= 0) continue; - const key = trimmed.slice(0, eq).trim(); - let value = trimmed.slice(eq + 1).trim(); - if (!key || process.env[key] !== undefined) continue; - if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) - ) { - value = value.slice(1, -1); - } - process.env[key] = value; - } - return true; -} - -function ensureSigningEnv() { - const projectDir = process.cwd(); - if (!process.env.WF_SIGN_PRIVATE_KEY) { - loadEnvFile(projectDir, '.env.local'); - loadEnvFile(projectDir, '.env'); - } - - const keyB64 = (process.env.WF_SIGN_PRIVATE_KEY || '').trim(); - const required = (process.env.WF_SIGNING_REQUIRED || '').trim() === '1'; - if (!keyB64) { - if (required) { - throw new Error( - 'WF_SIGN_PRIVATE_KEY is missing (WF_SIGNING_REQUIRED=1). ' + - 'Set it in CI Secret or .env.local for local build.', - ); - } - return null; - } - return keyB64; -} - -function getPlatform(context) { - const raw = ( - context?.electronPlatformName || - context?.packager?.platform?.name || - process.platform - ); - return normalizePlatformTag(raw); -} - -function normalizePlatformTag(rawPlatform) { - const p = String(rawPlatform || '').toLowerCase(); - if (p === 'darwin' || p === 'mac' || p === 'macos' || p === 'osx') return 'darwin'; - if (p === 'win32' || p === 'win' || p === 'windows') return 'win32'; - if (p === 'linux') return 'linux'; - return p || process.platform; -} - -function getProductFilename(context) { - return ( - context?.packager?.appInfo?.productFilename || - context?.packager?.config?.productName || - 'WeFlow' - ); -} - -function resolveMacAppBundle(appOutDir, productFilename) { - const candidates = []; - if (String(appOutDir).toLowerCase().endsWith('.app')) { - candidates.push(appOutDir); - } else { - candidates.push(path.join(appOutDir, `${productFilename}.app`)); - if (fs.existsSync(appOutDir) && fs.statSync(appOutDir).isDirectory()) { - const appDirs = fs - .readdirSync(appOutDir, { withFileTypes: true }) - .filter((e) => e.isDirectory() && e.name.toLowerCase().endsWith('.app')) - .map((e) => path.join(appOutDir, e.name)); - candidates.push(...appDirs); - } - } - - for (const bundleDir of candidates) { - const resourcesPath = path.join(bundleDir, 'Contents', 'Resources'); - if (fs.existsSync(resourcesPath) && fs.statSync(resourcesPath).isDirectory()) { - return bundleDir; - } - } - return null; -} - -function getResourcesDir(appOutDir, platform, productFilename) { - if (platform === 'darwin') { - const bundleDir = resolveMacAppBundle(appOutDir, productFilename); - if (!bundleDir) return path.join(appOutDir, 'resources'); - return path.join(bundleDir, 'Contents', 'Resources'); - } - return path.join(appOutDir, 'resources'); -} - -function normalizeRel(baseDir, filePath) { - return path.relative(baseDir, filePath).split(path.sep).join('/'); -} - -function sha256FileHex(filePath) { - const data = fs.readFileSync(filePath); - return crypto.createHash('sha256').update(data).digest('hex'); -} - -function findFirstExisting(paths) { - for (const p of paths) { - if (p && fs.existsSync(p) && fs.statSync(p).isFile()) return p; - } - return null; -} - -function findExecutablePath({ appOutDir, platform, productFilename, executableName }) { - if (platform === 'win32') { - return findFirstExisting([ - path.join(appOutDir, `${productFilename}.exe`), - path.join(appOutDir, `${executableName || ''}.exe`), - ]); - } - - if (platform === 'darwin') { - const bundleDir = resolveMacAppBundle(appOutDir, productFilename) || appOutDir; - const macOsDir = path.join(bundleDir, 'Contents', 'MacOS'); - const preferred = findFirstExisting([path.join(macOsDir, productFilename)]); - if (preferred) return preferred; - if (!fs.existsSync(macOsDir)) return null; - const files = fs - .readdirSync(macOsDir) - .map((name) => path.join(macOsDir, name)) - .filter((p) => fs.statSync(p).isFile()); - return files[0] || null; - } - - return findFirstExisting([ - path.join(appOutDir, executableName || ''), - path.join(appOutDir, productFilename), - path.join(appOutDir, productFilename.toLowerCase()), - ]); -} - -function findByBasenameRecursive(rootDir, basename) { - if (!fs.existsSync(rootDir)) return null; - const stack = [rootDir]; - while (stack.length > 0) { - const dir = stack.pop(); - const entries = fs.readdirSync(dir, { withFileTypes: true }); - for (const entry of entries) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) { - stack.push(full); - } else if (entry.isFile() && entry.name.toLowerCase() === basename.toLowerCase()) { - return full; - } - } - } - return null; -} - -function getModulePath(resourcesDir, appOutDir, platform) { - const filename = MODULE_FILENAME[platform] || MODULE_FILENAME[process.platform]; - if (!filename) return null; - - const direct = findFirstExisting([ - path.join(resourcesDir, 'resources', filename), - path.join(resourcesDir, filename), - ]); - if (direct) return direct; - - const inResources = findByBasenameRecursive(resourcesDir, filename); - if (inResources) return inResources; - - return findByBasenameRecursive(appOutDir, filename); -} - -function signDetachedEd25519(payloadUtf8, privateKeyDerB64) { - const privateKeyDer = Buffer.from(privateKeyDerB64, 'base64'); - const keyObject = crypto.createPrivateKey({ - key: privateKeyDer, - format: 'der', - type: 'pkcs8', - }); - return crypto.sign(null, Buffer.from(payloadUtf8, 'utf8'), keyObject); -} - -module.exports = async function afterPack(context) { - const privateKeyDerB64 = ensureSigningEnv(); - if (!privateKeyDerB64) { - console.log('[wf-sign] skip: WF_SIGN_PRIVATE_KEY not provided and signing not required.'); - return; - } - - const appOutDir = context?.appOutDir; - if (!appOutDir || !fs.existsSync(appOutDir)) { - throw new Error(`[wf-sign] invalid appOutDir: ${String(appOutDir)}`); - } - - const platform = String(getPlatform(context)).toLowerCase(); - const productFilename = getProductFilename(context); - const executableName = context?.packager?.config?.linux?.executableName || ''; - const resourcesDir = getResourcesDir(appOutDir, platform, productFilename); - if (!fs.existsSync(resourcesDir)) { - throw new Error( - `[wf-sign] resources directory not found: ${resourcesDir}; platform=${platform}; appOutDir=${appOutDir}`, - ); - } - - const exePath = findExecutablePath({ - appOutDir, - platform, - productFilename, - executableName, - }); - if (!exePath) { - throw new Error( - `[wf-sign] executable not found. platform=${platform}, appOutDir=${appOutDir}, productFilename=${productFilename}`, - ); - } - - const modulePath = getModulePath(resourcesDir, appOutDir, platform); - if (!modulePath) { - throw new Error( - `[wf-sign] ${MODULE_FILENAME[platform] || 'wcdb_api'} not found under resources: ${resourcesDir}`, - ); - } - - const manifest = { - schema: 1, - platform, - version: context?.packager?.appInfo?.version || '', - generatedAt: new Date().toISOString(), - targets: [ - { - id: 'exe', - path: normalizeRel(resourcesDir, exePath), - sha256: sha256FileHex(exePath), - }, - { - id: 'module', - path: normalizeRel(resourcesDir, modulePath), - sha256: sha256FileHex(modulePath), - }, - ], - }; - - const payload = `${JSON.stringify(manifest, null, 2)}\n`; - const signature = signDetachedEd25519(payload, privateKeyDerB64).toString('base64'); - - const manifestPath = path.join(resourcesDir, MANIFEST_NAME); - const signaturePath = path.join(resourcesDir, SIGNATURE_NAME); - fs.writeFileSync(manifestPath, payload, 'utf8'); - fs.writeFileSync(signaturePath, `${signature}\n`, 'utf8'); - - console.log(`[wf-sign] manifest: ${manifestPath}`); - console.log(`[wf-sign] signature: ${signaturePath}`); - console.log(`[wf-sign] exe: ${manifest.targets[0].path}`); - console.log(`[wf-sign] exe.sha256: ${manifest.targets[0].sha256}`); - console.log(`[wf-sign] module: ${manifest.targets[1].path}`); - console.log(`[wf-sign] module.sha256: ${manifest.targets[1].sha256}`); -};