UNPKG

adaptorex

Version:

Connect all your live interactive storytelling devices and software

154 lines (132 loc) 5.07 kB
// 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) })