Files
airi/apps/stage-tamagotchi/electron-builder.config.ts
T

256 lines
11 KiB
TypeScript

/* eslint-disable no-template-curly-in-string */
import type { Configuration } from 'electron-builder'
import { execSync } from 'node:child_process'
import { isMacOS } from 'std-env'
function hasXcode26OrAbove() {
if (!isMacOS)
return false
try {
const output = execSync('xcodebuild -version')
.toString()
.match(/Xcode (\d+)/)
if (!output)
return false
return Number.parseInt(output[1], 10) >= 26
}
catch {
return false
}
}
/**
* Determine whether to use the .icon format for the macOS app icon based on the
* Xcode version while building.
* This is friendly to developers whose macOS and/or Xcode versions are below 26.
*/
const useIconFormattedMacAppIcon = hasXcode26OrAbove()
if (!useIconFormattedMacAppIcon) {
console.warn('[electron-builder/config] Warning: Xcode version is below 26. Using .icns format for macOS app icon.')
}
else {
// NOTICE: This success-path message intentionally uses stderr via `console.warn`.
// The artifact metadata CLI imports this config and is used in GitHub Actions
// command substitution for `GITHUB_ENV`; writing this log to stdout would break
// machine-readable output such as `BUNDLE_NAME=$(...)`.
console.warn('[electron-builder/config] Xcode version is 26 or above. Using .icon format for macOS app icon.')
}
export default {
appId: 'ai.moeru.airi',
productName: 'AIRI',
directories: {
output: 'dist',
buildResources: 'build',
},
// // For self-publishing, testing, and distribution after modified the code without access to
// // an Apple Developer account, comment and uncomment the following lines.
// // Later on when you obtained one, you can set up the necessary certificates and provisioning
// // profiles to enable these security features.
// //
// // https://www.bigbinary.com/blog/code-sign-notorize-mac-desktop-app
// // https://kilianvalkhof.com/2019/electron/notarizing-your-electron-application/
// afterSign: async (context) => {
// const { electronPlatformName, appOutDir } = context
// if (electronPlatformName !== 'darwin')
// return
// if (env.CI !== 'true') {
// console.warn('Skipping notarizing step. Packaging is not running in CI')
// return
// }
// const appName = context.packager.appInfo.productFilename
// await notarize({
// appPath: `${appOutDir}/${appName}.app`,
// teamId: env.APPLE_DEVELOPER_TEAM_ID!,
// appleId: env.APPLE_DEVELOPER_APPLE_ID!,
// appleIdPassword: env.APPLE_DEVELOPER_APPLE_APP_SPECIFIC_PASSWORD!,
// })
// },
files: [
'out/**',
'resources/**',
'package.json',
// NOTICE: Exclude npm `electron` package from app payload.
// Electron runtime is already provided by the outer app bundle; bundling a nested
// `node_modules/electron/dist/Electron.app` makes electron-builder deep-sign it and
// fails on non-code resources (for example `locale.pak`) with timestamp/signing errors.
'!**/node_modules/electron{,/**}',
'!**/.vscode/*',
'!src/**/*',
'!**/node_modules/**/{CHANGELOG.md,README.md,README,readme.md,readme}',
'!**/node_modules/**/{.turbo,test,src,__tests__,tests,example,examples}',
'**/node_modules/debug/**/*',
'**/node_modules/superjson/**/*',
'!electron.vite.config.{js,ts,mjs,cjs}',
'!vite.config.{js,ts,mjs,cjs}',
'!uno.config.{js,ts,mjs,cjs}',
'!{.eslintcache,eslint.config.ts,.yaml,dev-app-update.yml,CHANGELOG.md,README.md}',
'!{.env,.env.*,.npmrc,pnpm-lock.yaml}',
'!{tsconfig.json}',
],
asar: true,
asarUnpack: [
'**/*.node',
],
extraResources: [
{
from: '../../engines/stage-tamagotchi-godot/out/${os}',
to: 'godot-stage',
filter: ['**/*'],
},
],
extraMetadata: {
name: 'ai.moeru.airi',
main: 'out/main/index.js',
homepage: 'https://airi.moeru.ai/docs/',
repository: 'https://github.com/moeru-ai/airi',
license: 'MIT',
},
win: {
executableName: 'airi',
// NOTICE: Keep `channel: 'latest-${arch}'` for architecture-aware updater metadata.
// electron-builder expands `${arch}` at publish-time (for example: `latest-x64`, `latest-arm64`),
// and electron-updater later consumes that expanded channel to resolve platform-specific *.yml files.
// This prevents cross-arch lookups such as arm64 clients reading x64 metadata.
publish: {
provider: 'github',
owner: 'moeru-ai',
repo: 'airi',
channel: 'latest-${arch}',
},
},
nsis: {
artifactName: '${productName}-${version}-windows-${arch}-setup.${ext}',
shortcutName: '${productName}',
uninstallDisplayName: '${productName}',
createDesktopShortcut: 'always',
deleteAppDataOnUninstall: true,
oneClick: false,
allowToChangeInstallationDirectory: true,
runAfterFinish: true,
},
mac: {
entitlementsInherit: 'build/entitlements.mac.plist',
// NOTICE: Same channel rule as Windows. Keep `${arch}` here so generated metadata resolves
// to architecture-specific update feeds on macOS (for example: `latest-x64-mac.yml`, `latest-arm64-mac.yml`).
publish: {
provider: 'github',
owner: 'moeru-ai',
repo: 'airi',
// NOTICE: `channel: 'latest-${arch}'` matters because electron-builder expands
// `${arch}` before it writes any publish metadata, and electron-updater later
// reuses that expanded channel string when deciding which `*.yml` file to fetch.
//
// Without this, the updater would look for `latest-mac.yml` for both x64 and arm64 macOS builds,
// which means the x64 build would be used for arm64 (Apple Silicon) users, causing suboptimal performance and higher resource usage. By embedding `${arch}`
// into the channel name, we ensure that the updater looks for `latest-x64-mac.yml` and `latest-arm64-mac.yml` respectively.
//
// This is how channel name was constructed:
//
// 1. `expandPublishConfig(...)` expands string values in the publish config.
// That is where `latest-${arch}` becomes `latest-x64` or `latest-arm64`.
// https://github.com/electron-userland/electron-builder/blob/ed422f36540a93e9bd2a19bc7a5e729bf2b033ea/packages/app-builder-lib/src/publish/PublishManager.ts#L521-L532
//
// 2. The expanded publish config is written into `app-update.yml`.
// The packaged app therefore carries `channel: latest-x64` or
// `channel: latest-arm64`, not the literal template string.
// https://github.com/electron-userland/electron-builder/blob/ed422f36540a93e9bd2a19bc7a5e729bf2b033ea/packages/app-builder-lib/src/publish/PublishManager.ts#L93-L96
//
// 3. electron-builder also uses that expanded channel when generating update
// metadata files. `getUpdateInfoFileName(channel, packager, arch)` builds the
// filename as:
// `${channel}${osSuffix}${getArchPrefixForUpdateFile(arch, packager)}.yml`
// https://github.com/electron-userland/electron-builder/blob/ed422f36540a93e9bd2a19bc7a5e729bf2b033ea/packages/app-builder-lib/src/publish/updateInfoBuilder.ts#L65-L68
//
// 4. For macOS, `osSuffix` is `-mac` and `getArchPrefixForUpdateFile(...)`
// returns an empty string. So:
// `latest-x64` -> `latest-x64-mac.yml`
// `latest-arm64` -> `latest-arm64-mac.yml`
// This is the publish-time side of the behavior.
//
// 5. At runtime, electron-updater reads the embedded `app-update.yml` and takes
// its `channel` value. It does not reconstruct `latest-${arch}` itself; it
// consumes the already-expanded value from step 2.
//
// 6. `Provider.getChannelFilePrefix()` appends the platform suffix:
// - macOS -> `-mac`
// - Windows -> ``
// - Linux x64 -> `-linux`
// - Linux non-x64 -> `-linux-${arch}`
// https://github.com/electron-userland/electron-builder/blob/ed422f36540a93e9bd2a19bc7a5e729bf2b033ea/packages/electron-updater/src/providers/Provider.ts#L44-L52
//
// 7. `getCustomChannelName(channel)` then returns:
// `${channel}${this.getChannelFilePrefix()}`
// So the updater turns:
// `latest-x64` -> `latest-x64-mac`
// `latest-arm64` -> `latest-arm64-mac`
// https://github.com/electron-userland/electron-builder/blob/ed422f36540a93e9bd2a19bc7a5e729bf2b033ea/packages/electron-updater/src/providers/Provider.ts#L58-L60
//
// 8. GitHubProvider passes that channel name into `getChannelFilename(channel)`,
// which simply appends `.yml`, so the final lookup becomes:
// `latest-x64-mac.yml`
// `latest-arm64-mac.yml`
// https://github.com/electron-userland/electron-builder/blob/ed422f36540a93e9bd2a19bc7a5e729bf2b033ea/packages/electron-updater/src/util.ts#L27-L29
// https://github.com/electron-userland/electron-builder/blob/ed422f36540a93e9bd2a19bc7a5e729bf2b033ea/packages/electron-updater/src/providers/GitHubProvider.ts#L132-L145
//
// Resulting filenames with this config:
// - macOS x64 -> `latest-x64-mac.yml`
// - macOS arm64 -> `latest-arm64-mac.yml`
// - Windows x64 -> `latest-x64.yml`
// - Linux x64 -> `latest-x64-linux.yml`
// - Linux arm64 -> `latest-arm64-linux-arm64.yml`
channel: 'latest-${arch}',
},
extendInfo: [
{
NSMicrophoneUsageDescription: 'AIRI requires microphone access for voice interaction',
},
{
NSCameraUsageDescription: 'AIRI requires camera access for vision understanding',
},
],
// For self-publishing, testing, and distribution after modified the code without access to
// an Apple Developer account, comment and uncomment the following 4 lines.
// Later on when you obtained one, you can set up the necessary certificates and provisioning
// profiles to enable these security features.
// hardenedRuntime: false,
hardenedRuntime: true,
// notarize: false,
notarize: true,
executableName: 'airi',
icon: useIconFormattedMacAppIcon ? 'icon.icon' : 'icon.icns',
},
dmg: {
artifactName: '${productName}-${version}-darwin-${arch}.${ext}',
},
linux: {
target: [
'deb',
'rpm',
],
// NOTICE: Same channel rule as Windows/macOS. Keep `${arch}` to avoid x64/arm64 feed collisions on Linux.
publish: {
provider: 'github',
owner: 'moeru-ai',
repo: 'airi',
channel: 'latest-${arch}',
},
category: 'Utility',
synopsis: 'AI VTuber/Waifu chatbot app inspired by Neuro-sama.',
description: 'AIRI is an AI VTuber/Waifu chatbot supporting Live2D/VRM avatars, featuring human-like interactions and modular stage-based rendering.',
executableName: 'airi',
artifactName: '${productName}-${version}-linux-${arch}.${ext}',
icon: 'build/icons/icon.png',
},
appImage: {
artifactName: '${productName}-${version}-linux-${arch}.${ext}',
},
npmRebuild: false,
} satisfies Configuration