adaptorex
Version:
Connect all your live interactive storytelling devices and software
154 lines (132 loc) • 5.07 kB
JavaScript
// scripts/bundle-macos-app.js
const { execSync } = require("child_process")
const fs = require("fs/promises")
const { version } = require("../package.json")
require("dotenv").config({
path: require("path").resolve(__dirname, "../.env")
})
const BUNDLE_ID = "com.machinaex.adaptorex"
const BUNDLE = "dist/adaptorex.app"
const DMG_PATH = `dist/adaptorex ${version}.dmg`
const KEYCHAIN =
process.env.APPLE_BUILD_KEYCHAIN ||
`${process.env.HOME}/Library/Keychains/login.keychain-db`
const KEYCHAIN_PASS = process.env.APPLE_KEYCHAIN_PASSWORD
const APPLE_DEVELOPER_NAME = process.env.APPLE_DEVELOPER_NAME
const APPLE_ID = process.env.APPLE_ID
const APPLE_TEAM_ID = process.env.APPLE_TEAM_ID
const APPLE_PASSWORD = process.env.APPLE_APP_PASSWORD
async function main() {
const args = process.argv.slice(2)
const ARCH = args.includes("--arch")
? args[args.indexOf("--arch") + 1]
: "x86_64"
// Generate .icns
const iconset = `dist/adaptorex.iconset`
await fs.mkdir(iconset, { recursive: true })
for (const size of [16, 32, 64, 128, 256, 512]) {
execSync(
`sips -z ${size} ${size} scripts/assets/icon.png --out ${iconset}/icon_${size}x${size}.png`
)
execSync(
`sips -z ${size * 2} ${size * 2} scripts/assets/icon.png --out ${iconset}/icon_${size}x${size}@2x.png`
)
}
execSync(`iconutil -c icns ${iconset} -o dist/adaptorex.icns`)
// Assemble .app bundle
await fs.mkdir(`${BUNDLE}/Contents/MacOS`, { recursive: true })
await fs.mkdir(`${BUNDLE}/Contents/Resources`, { recursive: true })
// Copy binary as adaptorex-bin — the launcher script is the actual CFBundleExecutable
await fs.copyFile(
`dist/adaptorex-macos-${ARCH}`,
`${BUNDLE}/Contents/MacOS/adaptorex-bin`
)
execSync(`chmod +x "${BUNDLE}/Contents/MacOS/adaptorex-bin"`)
await fs.copyFile(
"scripts/assets/macos-launcher",
`${BUNDLE}/Contents/MacOS/adaptorex`
)
execSync(`chmod +x "${BUNDLE}/Contents/MacOS/adaptorex"`)
await fs.copyFile(
`dist/adaptorex.icns`,
`${BUNDLE}/Contents/Resources/adaptorex.icns`
)
await fs.writeFile(
`${BUNDLE}/Contents/Info.plist`,
`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key><string>adaptorex</string>
<key>CFBundleIconFile</key><string>adaptorex.icns</string>
<key>CFBundleIdentifier</key><string>${BUNDLE_ID}</string>
<key>CFBundleName</key><string>adaptorex</string>
<key>CFBundleDisplayName</key><string>adaptorex</string>
<key>CFBundleVersion</key><string>${version}</string>
<key>CFBundleShortVersionString</key><string>${version}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
<key>CFBundleSupportedPlatforms</key><array><string>MacOSX</string></array>
<key>NSHighResolutionCapable</key><true/>
</dict>
</plist>`
)
// Ad-hoc sign — must run after all files are in place
// execSync(`codesign --force --deep --sign - "${BUNDLE}"`)
// Unlock keychain and grant codesign access to the signing key
// Required when running headless (CI or SSH session)
if (KEYCHAIN_PASS) {
execSync(`security unlock-keychain -p "${KEYCHAIN_PASS}" "${KEYCHAIN}"`)
execSync(
`security set-key-partition-list -S apple-tool:,apple: -s -k "${KEYCHAIN_PASS}" "${KEYCHAIN}"`
)
console.log("Keychain unlocked")
} else {
console.log("No keychain password provided, skipping unlock")
}
execSync(
`codesign --force --deep --sign "Developer ID Application: ${APPLE_DEVELOPER_NAME} (${APPLE_TEAM_ID})" \
--options runtime \
--entitlements scripts/assets/adaptorex.entitlements \
"${BUNDLE}"`,
{ stdio: "inherit" }
)
console.log(`Built ${BUNDLE} v${version}`)
execSync(
`npx create-dmg \
--overwrite \
--dmg-title="adaptorex" \
${BUNDLE} \
dist/`,
{ stdio: "inherit" }
)
execSync(
`codesign --force --sign "Developer ID Application: ${APPLE_DEVELOPER_NAME} (${APPLE_TEAM_ID})" \
"${DMG_PATH}"`,
{ stdio: "inherit" }
)
// Only notarize if credentials are available (skips on local builds without env vars)
if (APPLE_ID && APPLE_TEAM_ID && APPLE_PASSWORD) {
execSync(
`xcrun notarytool submit "${DMG_PATH}" \
--apple-id "${APPLE_ID}" \
--team-id "${APPLE_TEAM_ID}" \
--password "${APPLE_PASSWORD}" \
--wait`,
{ stdio: "inherit" }
)
execSync(`xcrun stapler staple "${DMG_PATH}"`, { stdio: "inherit" })
console.log("Notarization complete")
} else {
console.log("Skipping notarization (no credentials in environment)")
}
// Staple the ticket to the DMG
execSync(`xcrun stapler staple "${DMG_PATH}"`, { stdio: "inherit" })
execSync(`mv "${DMG_PATH}" "dist/adaptorex-${version}-macos-${ARCH}.dmg"`)
console.log(`Created adaptorex-${version}-macos-${ARCH}.dmg`)
}
main().catch((err) => {
console.error(err)
process.exit(1)
})