Files
peardata/forge.config.cjs
T
Raven Scott 5f82722df8
CI / test (push) Successful in 1m4s
Release rolling / release (push) Successful in 7m22s
update
2026-07-18 16:47:48 -04:00

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)
}
}
},
},
}