362 lines
9.5 KiB
JavaScript
362 lines
9.5 KiB
JavaScript
/**
|
|
* Electron Forge config for peardata-client.
|
|
*
|
|
* CI note: asar keeps finalize fast; ignore list strips server/tooling deps.
|
|
* Only ship prebuilds for the package target platform/arch.
|
|
*/
|
|
'use strict'
|
|
|
|
const path = require('path')
|
|
const fs = require('fs')
|
|
|
|
const pkg = require('./package.json')
|
|
// Use lowercase package name for out/<name>-<host>/ so release staging is stable.
|
|
// productName (PearData) remains the display name in package.json / UI.
|
|
const appName = pkg.name || 'peardata'
|
|
|
|
/**
|
|
* Resolve packaging target from forge CLI args or env (set by make.cjs).
|
|
* @returns {{ platform: string, arch: string }}
|
|
*/
|
|
function resolvePackageTarget() {
|
|
const argv = process.argv
|
|
const flag = (name) => {
|
|
const i = argv.indexOf(name)
|
|
return i >= 0 && argv[i + 1] ? argv[i + 1] : null
|
|
}
|
|
const platform =
|
|
process.env.PEARDATA_PACKAGE_PLATFORM ||
|
|
flag('--platform') ||
|
|
process.platform
|
|
const arch =
|
|
process.env.PEARDATA_PACKAGE_ARCH || flag('--arch') || process.arch
|
|
return { platform, arch }
|
|
}
|
|
|
|
const packageTarget = resolvePackageTarget()
|
|
const packageHost = `${packageTarget.platform}-${packageTarget.arch}`
|
|
|
|
/** Path prefixes (packager paths start with /) to exclude from the app bundle */
|
|
const IGNORE_PREFIXES = [
|
|
'/.git',
|
|
'/.gitea',
|
|
'/.github',
|
|
'/out',
|
|
'/dist',
|
|
'/deploy',
|
|
'/test',
|
|
'/docs',
|
|
'/spec',
|
|
'/server',
|
|
'/bin',
|
|
'/scripts',
|
|
'/tools',
|
|
'/.cache',
|
|
'/build/stubs',
|
|
'/build/shims',
|
|
'/README.md',
|
|
'/LICENSE',
|
|
// Packaging / Bare server toolchain (not needed at Electron runtime)
|
|
'/node_modules/electron',
|
|
'/node_modules/electron-',
|
|
'/node_modules/@electron',
|
|
'/node_modules/@electron-forge',
|
|
'/node_modules/bare-build',
|
|
'/node_modules/bare-runtime',
|
|
'/node_modules/bare-sidecar',
|
|
'/node_modules/bare-link',
|
|
'/node_modules/bare-lief',
|
|
'/node_modules/bare-apk',
|
|
'/node_modules/bare-app-image',
|
|
'/node_modules/bare-make',
|
|
'/node_modules/bare-pack',
|
|
'/node_modules/bare-dev',
|
|
'/node_modules/bare-bundle',
|
|
'/node_modules/bare-module-traverse',
|
|
'/node_modules/bare-sqlite',
|
|
'/node_modules/postject',
|
|
'/node_modules/@inquirer',
|
|
'/node_modules/terser',
|
|
'/node_modules/pear-runtime/',
|
|
'/node_modules/pear-electron',
|
|
// Heavy / unused tooling
|
|
'/node_modules/typescript',
|
|
'/node_modules/prettier',
|
|
'/node_modules/webpack',
|
|
'/node_modules/caniuse-lite',
|
|
'/node_modules/brittle',
|
|
'/node_modules/@types',
|
|
'/node_modules/esbuild',
|
|
'/node_modules/@esbuild',
|
|
'/node_modules/node-gyp',
|
|
'/node_modules/node-addon-api',
|
|
]
|
|
|
|
const IGNORE_REGEX = [
|
|
/^\/node_modules\/bare-build-/,
|
|
/^\/node_modules\/bare-runtime-/,
|
|
/^\/node_modules\/bare-pack-/,
|
|
/^\/node_modules\/@esbuild\//,
|
|
/\.md$/i,
|
|
/\.map$/,
|
|
/\.d\.ts$/,
|
|
/^\/peardata-.*\.json$/,
|
|
/^\/\.env$/,
|
|
/^\/package-lock\.json$/,
|
|
/^\/node_modules\/[^/]+\/test\//,
|
|
/^\/node_modules\/[^/]+\/tests\//,
|
|
/^\/node_modules\/[^/]+\/docs\//,
|
|
/^\/node_modules\/[^/]+\/example\//,
|
|
/^\/node_modules\/[^/]+\/examples\//,
|
|
/^\/node_modules\/[^/]+\/\.github\//,
|
|
]
|
|
|
|
function isForeignPrebuild(file) {
|
|
const marker = '/prebuilds/'
|
|
const idx = file.indexOf(marker)
|
|
if (idx === -1) return false
|
|
const host = file.slice(idx + marker.length).split('/')[0]
|
|
if (!host) return false
|
|
if (
|
|
host.startsWith('android') ||
|
|
host.startsWith('ios') ||
|
|
host.includes('simulator')
|
|
) {
|
|
return true
|
|
}
|
|
return host !== packageHost
|
|
}
|
|
|
|
function shouldIgnore(file) {
|
|
if (!file) return false
|
|
if (file === '/package.json') return false
|
|
if (file === '/electron/app.bundle.cjs') return false
|
|
if (file === '/electron/app.bundle.cjs.map') {
|
|
return process.env.PEARDATA_KEEP_SOURCEMAP !== '1'
|
|
}
|
|
for (const p of IGNORE_PREFIXES) {
|
|
if (file === p || file.startsWith(p + '/') || file.startsWith(p)) return true
|
|
}
|
|
for (const re of IGNORE_REGEX) {
|
|
if (re.test(file)) return true
|
|
}
|
|
if (isForeignPrebuild(file)) return true
|
|
return false
|
|
}
|
|
|
|
const skipRebuild =
|
|
process.env.PEARDATA_FORCE_REBUILD !== '1' &&
|
|
process.env.PEARDATA_SKIP_REBUILD !== '0'
|
|
|
|
const electronZipDir = path.join(__dirname, '.cache', 'electron-zips')
|
|
const useElectronZipDir =
|
|
process.env.PEARDATA_USE_ELECTRON_ZIP_DIR !== '0' &&
|
|
fs.existsSync(electronZipDir)
|
|
|
|
function stripBuildPath(buildPath) {
|
|
const t0 = Date.now()
|
|
let removed = 0
|
|
|
|
function rm(rel) {
|
|
const p = path.join(buildPath, rel)
|
|
try {
|
|
if (fs.existsSync(p)) {
|
|
fs.rmSync(p, { recursive: true, force: true })
|
|
removed++
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
const junk = [
|
|
'node_modules/bare-sidecar',
|
|
'node_modules/electron',
|
|
'node_modules/@electron-forge',
|
|
'node_modules/bare-build',
|
|
'node_modules/bare-runtime',
|
|
'node_modules/pear-runtime',
|
|
'node_modules/pear-electron',
|
|
'node_modules/esbuild',
|
|
'node_modules/bare-sqlite',
|
|
'node_modules/postject',
|
|
'node_modules/@inquirer',
|
|
'node_modules/terser',
|
|
'server',
|
|
'bin',
|
|
'scripts',
|
|
'spec',
|
|
'deploy',
|
|
'out',
|
|
'test',
|
|
'docs',
|
|
'tools',
|
|
'.cache',
|
|
'README.md',
|
|
'LICENSE',
|
|
]
|
|
for (const rel of junk) rm(rel)
|
|
|
|
const nm = path.join(buildPath, 'node_modules')
|
|
if (fs.existsSync(nm)) {
|
|
const stack = [nm]
|
|
const seen = new Set()
|
|
while (stack.length) {
|
|
const dir = stack.pop()
|
|
let real
|
|
try {
|
|
real = fs.realpathSync(dir)
|
|
} catch {
|
|
continue
|
|
}
|
|
if (seen.has(real)) continue
|
|
seen.add(real)
|
|
let entries
|
|
try {
|
|
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
} catch {
|
|
continue
|
|
}
|
|
for (const ent of entries) {
|
|
const full = path.join(dir, ent.name)
|
|
if (ent.isSymbolicLink()) continue
|
|
if (!ent.isDirectory()) continue
|
|
if (ent.name === 'prebuilds') {
|
|
let hosts
|
|
try {
|
|
hosts = fs.readdirSync(full)
|
|
} catch {
|
|
continue
|
|
}
|
|
for (const host of hosts) {
|
|
if (host === packageHost) continue
|
|
try {
|
|
fs.rmSync(path.join(full, host), { recursive: true, force: true })
|
|
removed++
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if (ent.name === '.bin') continue
|
|
stack.push(full)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (process.env.PEARDATA_KEEP_SOURCEMAP !== '1') {
|
|
rm('electron/app.bundle.cjs.map')
|
|
}
|
|
|
|
console.log(
|
|
`[forge] packageAfterCopy target=${packageHost} stripped=${removed} in ${Date.now() - t0}ms`
|
|
)
|
|
}
|
|
|
|
module.exports = {
|
|
packagerConfig: {
|
|
name: appName,
|
|
executableName: 'peardata-client',
|
|
appBundleId: 'com.peardata.app',
|
|
icon: fs.existsSync(path.join(__dirname, 'build', 'icon.png'))
|
|
? path.join(__dirname, 'build', 'icon')
|
|
: undefined,
|
|
asar: {
|
|
unpack: '**/*.{node,bare,dll,dylib,so}',
|
|
},
|
|
ignore: shouldIgnore,
|
|
derefSymlinks: false,
|
|
prune: false,
|
|
...(useElectronZipDir ? { electronZipDir } : {}),
|
|
quiet: process.env.CI ? false : true,
|
|
osxSign: false,
|
|
},
|
|
|
|
rebuildConfig: skipRebuild
|
|
? { onlyModules: [], force: false }
|
|
: {
|
|
force: false,
|
|
onlyModules: [
|
|
'udx-native',
|
|
'sodium-native',
|
|
'rocksdb-native',
|
|
'fs-native-extensions',
|
|
'quickbit-native',
|
|
'simdle-native',
|
|
'bare-fs',
|
|
'bare-os',
|
|
'bare-crypto',
|
|
],
|
|
},
|
|
|
|
makers: [
|
|
{
|
|
name: '@electron-forge/maker-zip',
|
|
platforms: ['darwin', 'linux', 'win32'],
|
|
},
|
|
],
|
|
|
|
plugins: [],
|
|
|
|
hooks: {
|
|
prePackage: async () => {
|
|
console.log(`[forge] packaging target host: ${packageHost}`)
|
|
console.log(
|
|
`[forge] electronZipDir: ${useElectronZipDir ? electronZipDir : '(none — packager will download)'}`
|
|
)
|
|
console.log(
|
|
`[forge] rebuild: ${skipRebuild ? 'skip (onlyModules:[])' : 'enabled'}`
|
|
)
|
|
if (process.env.PEARDATA_SKIP_PREPACKAGE_BUNDLE === '1') {
|
|
const bundle = path.join(__dirname, 'electron', 'app.bundle.cjs')
|
|
if (fs.existsSync(bundle)) {
|
|
console.log('[forge] prePackage: skip bundle (already built)')
|
|
return
|
|
}
|
|
}
|
|
require('child_process').execFileSync(
|
|
process.execPath,
|
|
[path.join(__dirname, 'scripts', 'build-client-bundle.cjs')],
|
|
{ stdio: 'inherit', cwd: __dirname }
|
|
)
|
|
},
|
|
preMake: async () => {
|
|
fs.rmSync(path.join(__dirname, 'out', 'make'), { recursive: true, force: true })
|
|
},
|
|
packageAfterCopy: async (_forgeConfig, buildPath) => {
|
|
const pkgPath = path.join(buildPath, 'package.json')
|
|
const appPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
appPkg.main = 'electron/main.cjs'
|
|
delete appPkg.devDependencies
|
|
delete appPkg.scripts
|
|
if (appPkg.dependencies) {
|
|
for (const name of [
|
|
'bare-build',
|
|
'bare-runtime',
|
|
'bare-sqlite',
|
|
'pear-electron',
|
|
]) {
|
|
delete appPkg.dependencies[name]
|
|
}
|
|
}
|
|
fs.writeFileSync(pkgPath, JSON.stringify(appPkg, null, 2) + '\n')
|
|
|
|
stripBuildPath(buildPath)
|
|
},
|
|
postPackage: async (_forgeConfig, options) => {
|
|
const platform = options.platform || process.platform
|
|
if (platform !== 'darwin') return
|
|
const { signApp, findApps } = require('./scripts/sign-macos-app.cjs')
|
|
const paths = options.outputPaths || []
|
|
for (const outPath of paths) {
|
|
const apps = findApps(outPath)
|
|
for (const app of apps) {
|
|
console.log('[forge] postPackage codesign', app)
|
|
await signApp(app)
|
|
}
|
|
}
|
|
},
|
|
},
|
|
}
|