diff --git a/build/app-update.yml b/build/app-update.yml new file mode 100644 index 0000000..a978334 --- /dev/null +++ b/build/app-update.yml @@ -0,0 +1,3 @@ +owner: bfulton +repo: localmost +provider: github diff --git a/docs/release-checklist.md b/docs/release-checklist.md index 64a5159..fdfbedd 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -1,7 +1,7 @@ # Release Checklist ## Pre-Release -- [ ] Cut release-prep-vX.Y.Z branch +- [ ] Cut release-prep-X.Y.Z branch - [ ] Ensure correct release version in package.json - [ ] Update CHANGELOG.md with release notes for unreleased version - [ ] Merge release-prep branch to main and delete branch @@ -13,22 +13,46 @@ - [ ] `npm test` passes with no warnings ## Build -- [ ] Set notarization credentials: +- [ ] Set notarization credentials (usually in .envrc): ```bash export APPLE_ID="your@email.com" export APPLE_APP_SPECIFIC_PASSWORD="xxxx-xxxx-xxxx-xxxx" export APPLE_TEAM_ID="XXXXXXXXXX" ``` -- [ ] `npm run make` +- [ ] `rm -rf out/make` +- [ ] `npm run make -- --arch=x64` - [ ] Verify output shows: - `Signing: Developer ID Application: ...` - `Notarize: true` - `Release build: true` +- [ ] `npm run make -- --arch=arm64` +- [ ] Verify output shows: + - `Signing: Developer ID Application: ...` + - `Notarize: true` + - `Release build: true` +- [ ] Test the DMG installs correctly +- [ ] `node scripts/generate-latest-mac-yml.js` ## Post-Build -- [ ] Test the DMG installs correctly -- [ ] Test basic functionality (add target, run job) -- [ ] Upload DMG to GitHub release -- [ ] Tag the release: `git tag vX.Y.Z && git push --tags` +- [ ] Smoke test basic functionality through installed app: + - Start from scratch + - Authenticate + - Download runner + - Add targets + - Run job + - Exit + - Restart + - Run job +- [ ] Draft a [new release](https://github.com/bfulton/localmost/releases/new) + - Tag: vX.Y.Z + - Target: main + - Release title: X.Y.Z + - Release notes: copy from CHANGELOG.md + - Attach `out/make/localmost-X.Y.Z-arm64.dmg` + - Attach `out/make/localmost-X.Y.Z-x64.dmg` + - Attach `out/make/latest-mac.yml` + - Attach `out/make/zip/darwin/arm64/localmost-darwin-arm64-X.Y.Z.zip` + - Attach `out/make/zip/darwin/x64/localmost-darwin-x64-X.Y.Z.zip` +- [ ] Publish release - [ ] Bump the release version in [package.json](https://github.com/bfulton/localmost/edit/main/package.json) - [ ] Update [CHANGELOG.md](https://github.com/bfulton/localmost/edit/main/CHANGELOG.md) with proper release dates and links, and section for next unreleased version diff --git a/forge.config.js b/forge.config.js index 2f15145..70c411c 100644 --- a/forge.config.js +++ b/forge.config.js @@ -79,6 +79,9 @@ const packagerConfig = { darwinDarkModeSupport: true, extraResource: [ path.join(__dirname, 'assets', 'generated'), + path.join(__dirname, 'dist', 'cli.js'), + path.join(__dirname, 'scripts', 'localmost-cli'), + path.join(__dirname, 'build', 'app-update.yml'), ], // Only include dist/, package.json, and LICENSE in the app bundle ignore: [ @@ -132,27 +135,6 @@ module.exports = { console.log(`Stripped ${removed} unused locale files (kept: ${keepLanguages.join(', ')})`); } - // Copy CLI to Resources for easy access outside the asar - const appPath = packageResult.outputPaths[0]; - const resourcesPath = path.join(appPath, 'localmost.app', 'Contents', 'Resources'); - const cliSrc = path.join(__dirname, 'dist', 'cli.js'); - const cliDest = path.join(resourcesPath, 'cli.js'); - - if (fs.existsSync(cliSrc)) { - fs.copyFileSync(cliSrc, cliDest); - fs.chmodSync(cliDest, 0o755); - console.log('Copied CLI to Resources/cli.js'); - - // Create a shell wrapper script for easier invocation - const wrapperPath = path.join(resourcesPath, 'localmost-cli'); - const wrapperContent = `#!/bin/bash -# localmost CLI wrapper -exec /usr/bin/env node "\${0%/*}/cli.js" "$@" -`; - fs.writeFileSync(wrapperPath, wrapperContent); - fs.chmodSync(wrapperPath, 0o755); - console.log('Created CLI wrapper at Resources/localmost-cli'); - } }, postMake: async (config, makeResults) => { // Open the DMG after build diff --git a/scripts/generate-latest-mac-yml.js b/scripts/generate-latest-mac-yml.js new file mode 100644 index 0000000..c78b1d4 --- /dev/null +++ b/scripts/generate-latest-mac-yml.js @@ -0,0 +1,65 @@ +#!/usr/bin/env node +/** + * Generate latest-mac.yml for electron-updater. + * Run after `npm run make` to create the file for GitHub release upload. + */ + +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const pkg = require('../package.json'); +const version = pkg.version; + +const outDir = path.join(__dirname, '..', 'out', 'make'); + +function sha512(filePath) { + const data = fs.readFileSync(filePath); + return crypto.createHash('sha512').update(data).digest('base64'); +} + +function getFileSize(filePath) { + return fs.statSync(filePath).size; +} + +const files = []; + +// Check for arm64 DMG +const arm64Dmg = path.join(outDir, `localmost-${version}-arm64.dmg`); +if (fs.existsSync(arm64Dmg)) { + files.push({ + url: path.basename(arm64Dmg), + sha512: sha512(arm64Dmg), + size: getFileSize(arm64Dmg), + }); +} + +// Check for x64 DMG +const x64Dmg = path.join(outDir, `localmost-${version}-x64.dmg`); +if (fs.existsSync(x64Dmg)) { + files.push({ + url: path.basename(x64Dmg), + sha512: sha512(x64Dmg), + size: getFileSize(x64Dmg), + }); +} + +if (files.length === 0) { + console.error('No DMG files found in out/make/'); + process.exit(1); +} + +const yaml = `version: ${version} +files: +${files.map(f => ` - url: ${f.url} + sha512: ${f.sha512} + size: ${f.size}`).join('\n')} +path: ${files[0].url} +sha512: ${files[0].sha512} +releaseDate: '${new Date().toISOString()}' +`; + +const outFile = path.join(outDir, 'latest-mac.yml'); +fs.writeFileSync(outFile, yaml); +console.log(`Generated ${outFile}`); +console.log(yaml); diff --git a/scripts/localmost-cli b/scripts/localmost-cli new file mode 100755 index 0000000..d8bc7ce --- /dev/null +++ b/scripts/localmost-cli @@ -0,0 +1,3 @@ +#!/bin/bash +# localmost CLI wrapper +exec /usr/bin/env node "${0%/*}/cli.js" "$@"