diff --git a/.buildkite/commands/prepare-environment.sh b/.buildkite/commands/prepare-environment.sh index d5648d80a..6cbc984be 100755 --- a/.buildkite/commands/prepare-environment.sh +++ b/.buildkite/commands/prepare-environment.sh @@ -10,4 +10,4 @@ echo "--- :closed_lock_with_key: Installing Secrets" bundle exec fastlane run configure_apply echo "--- :testflight: Fetching Signing Certificates" -bundle exec fastlane set_up_signing +bundle exec fastlane set_up_signing \ No newline at end of file diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index a54a8437d..1071af75c 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -71,17 +71,39 @@ steps: command: | .buildkite/commands/prepare-environment.sh - echo "--- :node: Building Binary" .buildkite/commands/install-node-dependencies.sh + node ./scripts/prepare-dev-build-version.mjs + + echo "--- :node: Building Binary" npm run make:macos-{{matrix}} + # Local trial and error show this needs to run before the DMG generation (obviously) but after the binary has been built + echo "--- :hammer: Rebuild fs-attr if necessary before generating DMG" + case {{matrix}} in + universal | x64) + echo "Rebuilding fs-xattr for {{matrix}} architecture" + npm rebuild fs-xattr --cpu universal + ;; + arm64) + echo "No need to rebuild fs-xattr because it works out of the box on Apple Silicon" + ;; + *) + echo "^^^ +++ Unexpected architecture {{matrix}}" + exit 1 + ;; + esac + + echo "--- :node: Packaging in DMG" + npm run make:dmg-{{matrix}} + echo "--- 📃 Notarizing Binary" bundle exec fastlane notarize_binary plugins: *common_plugins artifact_paths: - 'out/**/*.app.zip' + - 'out/*.dmg' matrix: - "universal" - "x64" @@ -139,17 +161,38 @@ steps: command: | .buildkite/commands/prepare-environment.sh - echo "--- :node: Building Binary" .buildkite/commands/install-node-dependencies.sh node ./scripts/confirm-tag-matches-version.mjs + + echo "--- :node: Building Binary" npm run make:macos-{{matrix}} + # Local trial and error show this needs to run before the DMG generation (obviously) but after the binary has been built + echo "--- :hammer: Rebuild fs-attr if necessary before generating DMG" + case {{matrix}} in + universal | x64) + echo "Rebuilding fs-xattr for {{matrix}} architecture" + npm rebuild fs-xattr --cpu universal + ;; + arm64) + echo "No need to rebuild fs-xattr because it works out of the box on Apple Silicon" + ;; + *) + echo "^^^ +++ Unexpected architecture {{matrix}}" + exit 1 + ;; + esac + + echo "--- :node: Packaging in DMG" + npm run make:dmg-{{matrix}} + echo "--- 📃 Notarizing Binary" bundle exec fastlane notarize_binary plugins: *common_plugins artifact_paths: - 'out/**/*.app.zip' + - 'out/*.dmg' matrix: - "universal" - "x64" diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 719fc5c1d..aedda3205 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -45,13 +45,20 @@ lane :set_up_signing do |_options| end desc 'Notarize the compiled binary' -lane :notarize_binary do |_options| +lane :notarize_binary do Dir[File.join(BUILDS_FOLDER, '**', 'Studio.app')].each do |path| notarize( package: path, api_key_path: APPLE_API_KEY_PATH ) end + Dir[File.join(BUILDS_FOLDER, '**', 'Studio-*.dmg')].each do |path| + notarize( + bundle_id: APPLE_BUNDLE_IDENTIFIER, + package: path, + api_key_path: APPLE_API_KEY_PATH + ) + end end desc 'Ship the binary to internal testers' @@ -96,18 +103,36 @@ def distribute_builds( extension: 'app.zip', name: 'Mac Universal' }, + mac_universal_dmg: { + binary_path: File.join(BUILDS_FOLDER, 'Studio-darwin-universal', 'Studio.dmg'), + filename_core: 'darwin-universal', + extension: 'dmg', + name: 'Mac Universal (DMG)' + }, x64: { binary_path: File.join(BUILDS_FOLDER, 'Studio-darwin-x64', 'Studio.app.zip'), filename_core: 'darwin-x64', extension: 'app.zip', name: 'Mac Intel' }, + x64_dmg: { + binary_path: File.join(BUILDS_FOLDER, 'Studio-darwin-x64', 'Studio.dmg'), + filename_core: 'darwin-x64', + extension: 'dmg', + name: 'Mac Intel (DMG)' + }, arm64: { binary_path: File.join(BUILDS_FOLDER, 'Studio-darwin-arm64', 'Studio.app.zip'), filename_core: 'darwin-arm64', extension: 'app.zip', name: 'Mac Apple Silicon' }, + arm64_dmg: { + binary_path: File.join(BUILDS_FOLDER, 'Studio-darwin-arm64', 'Studio.dmg'), + filename_core: 'darwin-arm64', + extension: 'dmg', + name: 'Mac Apple Silicon (DMG)' + }, windows: { binary_path: File.join(BUILDS_FOLDER, 'make', 'squirrel.windows', 'x64', 'studio-setup.exe'), filename_core: 'win32', diff --git a/package.json b/package.json index 66fd1dc25..b7cfada23 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,9 @@ "make:macos-universal": "SKIP_DMG=true electron-forge make --arch=universal", "make:macos-x64": "SKIP_DMG=true FILE_ARCHITECTURE=x64 electron-forge make --arch=x64 --platform=darwin", "make:macos-arm64": "SKIP_DMG=true FILE_ARCHITECTURE=arm64 electron-forge make --arch=arm64 --platform=darwin", + "make:dmg-universal": "FILE_ARCHITECTURE=universal node ./scripts/make-dmg.mjs", + "make:dmg-x64": "FILE_ARCHITECTURE=x64 node ./scripts/make-dmg.mjs", + "make:dmg-arm64": "FILE_ARCHITECTURE=arm64 node ./scripts/make-dmg.mjs", "publish": "electron-forge publish", "lint": "eslint --ext .ts,.tsx,.js,.jsx,.mjs .", "format": "prettier . --write", diff --git a/scripts/make-dmg.mjs b/scripts/make-dmg.mjs new file mode 100644 index 000000000..5d21d8b0d --- /dev/null +++ b/scripts/make-dmg.mjs @@ -0,0 +1,46 @@ +import child_process from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import { fileURLToPath } from 'url'; +import packageJson from '../package.json' assert { type: 'json' }; + +const __dirname = path.dirname( fileURLToPath( import.meta.url ) ); + +const appPath = path.resolve( + __dirname, + '../out', + `${ packageJson.productName }-darwin-${ process.env.FILE_ARCHITECTURE }`, + `${ packageJson.productName }.app` +); + +const dmgPath = path.resolve( + __dirname, + '../out', + `${ packageJson.productName }-darwin-${ process.env.FILE_ARCHITECTURE }.dmg` +); + +const volumeIconPath = path.resolve( __dirname, '../assets/studio-app-icon.icns' ); +const backgroundPath = path.resolve( __dirname, '../assets/dmg-background.png' ); + +const dmgSpecs = { + title: packageJson.productName, + icon: volumeIconPath, + 'icon-size': 80, + background: backgroundPath, + window: { size: { width: 710, height: 502 } }, + contents: [ + { type: 'file', path: appPath, x: 533, y: 122 }, + { type: 'link', path: '/Applications', x: 533, y: 354 }, + ], +}; + +if ( fs.existsSync( dmgPath ) ) { + fs.unlinkSync( dmgPath ); +} + +const specsFile = path.resolve( __dirname, '..', 'appdmg-specs.json' ); +fs.writeFileSync( specsFile, JSON.stringify( dmgSpecs ) ); +child_process.execSync( + [ path.join( __dirname, '..', 'node_modules', '.bin', `appdmg` ), specsFile, dmgPath ].join( ' ' ) +); +fs.unlinkSync( specsFile );