Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build/app-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
owner: bfulton
repo: localmost
provider: github
38 changes: 31 additions & 7 deletions docs/release-checklist.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
24 changes: 3 additions & 21 deletions forge.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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
Expand Down
65 changes: 65 additions & 0 deletions scripts/generate-latest-mac-yml.js
Original file line number Diff line number Diff line change
@@ -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);
3 changes: 3 additions & 0 deletions scripts/localmost-cli
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
# localmost CLI wrapper
exec /usr/bin/env node "${0%/*}/cli.js" "$@"