这是indexloc提供的服务,不要输入任何密码
Skip to content

Build macOS Release Artifacts #154

Build macOS Release Artifacts

Build macOS Release Artifacts #154

Workflow file for this run

name: Build macOS Release Artifacts
on:
#schedule:
# Run daily at 7am PT (3pm UTC during standard time, 2pm UTC during daylight saving)
#- cron: "0 14 * * *"
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+" # Matches v0.2.0, v1.0.0, etc.
workflow_dispatch:
inputs:
release_version:
description: "Release version (e.g., 0.2.0). Leave empty to auto-increment patch version for stable builds."
required: false
type: string
default: ""
release_nightly:
description: "Build and release a nightly build"
required: false
type: boolean
default: false
permissions:
contents: write # Needed to create releases
jobs:
build-macos:
runs-on: macos-latest
env:
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
# PostHog host is always the same
VITE_PUBLIC_POSTHOG_HOST: https://us.i.posthog.com
steps:
- name: Generate version
id: version
uses: actions/github-script@v7
with:
script: |
let buildVersion, isNightly, isStable, shouldCreateTag = false;
// Determine build type
if (context.eventName === 'schedule' || context.payload.inputs?.release_nightly === 'true') {
// Nightly build
const timestamp = new Date().toISOString()
.replace(/[T:]/g, '-')
.replace(/\..+/, '')
.replace(/-/g, '')
.slice(0, -2); // Format: YYYYMMDDHHMMSS
buildVersion = `0.1.0-${timestamp}-nightly`;
isNightly = true;
isStable = false;
} else if (context.eventName === 'push' && context.ref.startsWith('refs/tags/v')) {
// Tag push - extract version from tag
const tagVersion = context.ref.replace('refs/tags/v', '');
// Validate it's a proper semver (no pre-release identifiers)
const semverRegex = /^[0-9]+\.[0-9]+\.[0-9]+$/;
if (!semverRegex.test(tagVersion)) {
throw new Error(`Tag ${tagVersion} contains pre-release identifier. Only stable semver tags supported.`);
}
buildVersion = tagVersion;
isNightly = false;
isStable = true;
} else if (context.eventName === 'workflow_dispatch') {
const inputVersion = context.payload.inputs?.release_version;
if (inputVersion) {
// Manual dispatch with explicit version
// Strip leading 'v' if present for normalization
buildVersion = inputVersion.replace(/^v/, '');
} else {
// Manual dispatch without version - auto-increment patch
// Fetch tags from GitHub API
const tags = await github.rest.repos.listTags({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
});
// Filter for stable semver tags (v1.2.3 format, no pre-release)
const semverRegex = /^v([0-9]+)\.([0-9]+)\.([0-9]+)$/;
const stableTags = tags.data
.map(tag => tag.name)
.filter(name => semverRegex.test(name))
.map(name => {
const match = name.match(semverRegex);
return {
tag: name,
major: parseInt(match[1]),
minor: parseInt(match[2]),
patch: parseInt(match[3])
};
})
.sort((a, b) => {
if (a.major !== b.major) return b.major - a.major;
if (a.minor !== b.minor) return b.minor - a.minor;
return b.patch - a.patch;
});
let newVersion;
if (stableTags.length === 0) {
// No stable tags exist, start with v0.2.0
newVersion = '0.2.0';
} else {
// Increment patch version of the latest tag
const latest = stableTags[0];
newVersion = `${latest.major}.${latest.minor}.${latest.patch + 1}`;
}
buildVersion = newVersion;
shouldCreateTag = true; // Mark that we need to create a tag after checkout
}
isNightly = false;
isStable = true;
} else {
// Fallback (shouldn't happen)
const timestamp = new Date().toISOString()
.replace(/[T:]/g, '-')
.replace(/\..+/, '')
.replace(/-/g, '')
.slice(0, -2);
buildVersion = `0.1.0-${timestamp}`;
isNightly = false;
isStable = false;
}
// Set outputs
core.setOutput('release_version', buildVersion);
core.setOutput('is_nightly', isNightly.toString());
core.setOutput('is_stable', isStable.toString());
core.setOutput('should_create_tag', shouldCreateTag.toString());
console.log(`Build Version: ${buildVersion}`);
console.log(`Is Nightly: ${isNightly}`);
console.log(`Is Stable: ${isStable}`);
console.log(`Should Create Tag: ${shouldCreateTag}`);
- name: Set PostHog API Key
run: |
if [[ "${{ steps.version.outputs.is_nightly }}" == "true" ]]; then
echo "VITE_PUBLIC_POSTHOG_KEY=phc_de6RVF0G7CkTzv2UvxHddSk7nfFnE5QWD7KmZV5KfSo" >> $GITHUB_ENV
else
echo "VITE_PUBLIC_POSTHOG_KEY=phc_6RQ0mVrcMDwSgbKeGToTXC4ja11Hzhkm7tKyO5gjBrK" >> $GITHUB_ENV
fi
echo "PostHog key set for ${{ steps.version.outputs.is_nightly == 'true' && 'nightly' || 'stable' }} build"
- name: Checkout code
uses: actions/checkout@v4
- name: Create and push tag
if: steps.version.outputs.should_create_tag == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "v${{ steps.version.outputs.release_version }}" -m "Release v${{ steps.version.outputs.release_version }}"
git push origin "v${{ steps.version.outputs.release_version }}"
echo "Created and pushed tag: v${{ steps.version.outputs.release_version }}"
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: humanlayer-wui/src-tauri
- name: Setup Go
uses: actions/setup-go@v5
id: setup-go
with:
go-version-file: "hld/go.mod"
cache: false
- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: go-mod-v2-${{ runner.os }}-go${{ steps.setup-go.outputs.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-mod-v2-${{ runner.os }}-go${{ steps.setup-go.outputs.go-version }}-
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Cache Go tools
uses: actions/cache@v4
id: go-tools-cache-release
with:
path: ~/go/bin
key: go-tools-${{ runner.os }}-mockgen-0.5
- name: Run repository setup
run: make setup
- name: Install WUI dependencies
working-directory: humanlayer-wui
run: bun install
- name: Build daemon for macOS ARM
run: |
cd hld
# Set explicit LDFLAGS for each build type
if [[ "${{ steps.version.outputs.is_nightly }}" == "true" ]]; then
# Nightly build - use isolated paths and ports
LDFLAGS="-X github.com/humanlayer/humanlayer/hld/internal/version.BuildVersion=${{ steps.version.outputs.release_version }}"
LDFLAGS="${LDFLAGS} -X github.com/humanlayer/humanlayer/hld/config.DefaultDatabasePath=~/.humanlayer/daemon-nightly.db"
LDFLAGS="${LDFLAGS} -X github.com/humanlayer/humanlayer/hld/config.DefaultSocketPath=~/.humanlayer/daemon-nightly.sock"
LDFLAGS="${LDFLAGS} -X github.com/humanlayer/humanlayer/hld/config.DefaultHTTPPort=7778"
LDFLAGS="${LDFLAGS} -X github.com/humanlayer/humanlayer/hld/config.DefaultCLICommand=humanlayer-nightly"
else
# Stable build - explicitly set standard paths and ports
LDFLAGS="-X github.com/humanlayer/humanlayer/hld/internal/version.BuildVersion=${{ steps.version.outputs.release_version }}"
LDFLAGS="${LDFLAGS} -X github.com/humanlayer/humanlayer/hld/config.DefaultDatabasePath=~/.humanlayer/daemon.db"
LDFLAGS="${LDFLAGS} -X github.com/humanlayer/humanlayer/hld/config.DefaultSocketPath=~/.humanlayer/daemon.sock"
LDFLAGS="${LDFLAGS} -X github.com/humanlayer/humanlayer/hld/config.DefaultHTTPPort=7777"
LDFLAGS="${LDFLAGS} -X github.com/humanlayer/humanlayer/hld/config.DefaultCLICommand=humanlayer"
fi
echo "Using LDFLAGS: ${LDFLAGS}"
GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o hld-darwin-arm64 ./cmd/hld
- name: Build humanlayer CLI for macOS ARM
working-directory: hlyr
run: |
bun install
bun run build
bun build ./dist/index.js --compile --target=bun-darwin-arm64 --outfile=humanlayer-darwin-arm64
chmod +x humanlayer-darwin-arm64
- name: Copy binaries to Tauri resources
run: |
mkdir -p humanlayer-wui/src-tauri/bin
cp hld/hld-darwin-arm64 humanlayer-wui/src-tauri/bin/hld
cp hlyr/humanlayer-darwin-arm64 humanlayer-wui/src-tauri/bin/humanlayer
chmod +x humanlayer-wui/src-tauri/bin/hld
chmod +x humanlayer-wui/src-tauri/bin/humanlayer
- name: Swap icons for nightly build
if: steps.version.outputs.is_nightly == 'true'
working-directory: humanlayer-wui/src-tauri
run: |
# Only swap icons for nightly builds
mv icons icons-original
cp -r icons-nightly icons
- name: Build Tauri app
working-directory: humanlayer-wui
run: |
export VITE_APP_VERSION="${{ steps.version.outputs.release_version }}"
bun install
if [[ "${{ steps.version.outputs.is_nightly }}" == "true" ]]; then
bun run tauri build --config src-tauri/tauri.nightly.conf.json
else
bun run tauri build
fi
env:
APPLE_SIGNING_IDENTITY: "-"
NODE_ENV: "production"
- name: Rename DMG for consistent naming
working-directory: humanlayer-wui
run: |
DMG_PATH=$(find src-tauri/target/release/bundle/dmg -name "*.dmg" | head -1)
if [[ "${{ steps.version.outputs.is_stable }}" == "true" ]]; then
# Rename to CodeLayer-darwin-arm64.dmg for stable
NEW_NAME="CodeLayer-darwin-arm64.dmg"
else
# Keep versioned name for nightly
NEW_NAME=$(basename "$DMG_PATH")
fi
mv "$DMG_PATH" "src-tauri/target/release/bundle/dmg/$NEW_NAME"
echo "dmg_filename=$NEW_NAME" >> $GITHUB_OUTPUT
id: dmg_rename
- name: Upload DMG artifact
uses: actions/upload-artifact@v4
with:
name: humanlayer-wui-macos-dmg
path: humanlayer-wui/src-tauri/target/release/bundle/dmg/*.dmg
if-no-files-found: error
# Create GitHub Release with artifacts
- name: Create Release
if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.event_name == 'push'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.is_stable == 'true' && format('v{0}', steps.version.outputs.release_version) || steps.version.outputs.release_version }}
name: codelayer-${{ steps.version.outputs.release_version }}
files: |
humanlayer-wui/src-tauri/target/release/bundle/dmg/${{ steps.dmg_rename.outputs.dmg_filename }}
draft: false
prerelease: ${{ steps.version.outputs.is_nightly == 'true' }}
make_latest: ${{ steps.version.outputs.is_stable == 'true' }}
body: |
# CodeLayer ${{ steps.version.outputs.is_nightly == 'true' && 'Nightly' || 'Stable' }} Release
Version: ${{ steps.version.outputs.release_version }}
## Installation
### Homebrew (Recommended)
```bash
brew install --cask --no-quarantine humanlayer/humanlayer/codelayer${{ steps.version.outputs.is_nightly == 'true' && '-nightly' || '' }}
```
### Manual Installation
1. Download the DMG file below
2. Open the DMG and drag CodeLayer${{ steps.version.outputs.is_nightly == 'true' && '-Nightly' || '' }} to Applications
3. Launch CodeLayer${{ steps.version.outputs.is_nightly == 'true' && '-Nightly' || '' }} from Applications
## Troubleshooting
If you encounter "damaged app" warnings:
```bash
xattr -rc /Applications/CodeLayer${{ steps.version.outputs.is_nightly == 'true' && '-Nightly' || '' }}.app
```
## Logs
Logs can be found at:
```
~/Library/Logs/dev.humanlayer.wui${{ steps.version.outputs.is_nightly == 'true' && '.nightly' || '' }}/CodeLayer${{ steps.version.outputs.is_nightly == 'true' && '-Nightly' || '' }}.log
```
- name: Update Homebrew Cask
if: steps.version.outputs.is_nightly == 'true' || steps.version.outputs.is_stable == 'true'
run: |
# Prepare variables
DMG_FILE="humanlayer-wui/src-tauri/target/release/bundle/dmg/${{ steps.dmg_rename.outputs.dmg_filename }}"
SHA256=$(shasum -a 256 "$DMG_FILE" | awk '{print $1}')
if [[ "${{ steps.version.outputs.is_stable }}" == "true" ]]; then
RELEASE_TAG="v${{ steps.version.outputs.release_version }}"
CASK_FILE="Casks/codelayer.rb"
DMG_FILENAME="CodeLayer-darwin-arm64.dmg"
else
RELEASE_TAG="${{ steps.version.outputs.release_version }}"
CASK_FILE="Casks/codelayer-nightly.rb"
DMG_FILENAME="${{ steps.dmg_rename.outputs.dmg_filename }}"
fi
DMG_URL="https://github.com/humanlayer/humanlayer/releases/download/${RELEASE_TAG}/${DMG_FILENAME}"
echo "DMG_FILE=$DMG_FILE" >> $GITHUB_ENV
echo "SHA256=$SHA256" >> $GITHUB_ENV
echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV
echo "DMG_URL=$DMG_URL" >> $GITHUB_ENV
echo "CASK_FILE=$CASK_FILE" >> $GITHUB_ENV
- name: Checkout homebrew-humanlayer
if: steps.version.outputs.is_nightly == 'true' || steps.version.outputs.is_stable == 'true'
uses: actions/checkout@v4
with:
repository: humanlayer/homebrew-humanlayer
path: homebrew-humanlayer
token: ${{ secrets.HUMANLAYER_HOMEBREW_CASK_WRITE_GITHUB_PAT }}
- name: Generate stable cask file
if: steps.version.outputs.is_stable == 'true'
working-directory: homebrew-humanlayer
run: |
cat > Casks/codelayer.rb << 'EOF'
cask "codelayer" do
version "${{ steps.version.outputs.release_version }}"
sha256 "${{ env.SHA256 }}"
url "${{ env.DMG_URL }}"
name "CodeLayer"
desc "Desktop application for HumanLayer AI approvals"
homepage "https://github.com/humanlayer/humanlayer"
livecheck do
url :url
strategy :github_latest
regex(/v?(\d+(?:\.\d+)+)$/i)
end
depends_on macos: ">= :monterey"
app "CodeLayer.app"
binary "#{appdir}/CodeLayer.app/Contents/Resources/bin/humanlayer"
binary "#{appdir}/CodeLayer.app/Contents/Resources/bin/humanlayer", target: "codelayer"
binary "#{appdir}/CodeLayer.app/Contents/Resources/bin/hld"
zap trash: [
"~/.config/humanlayer/",
"~/.humanlayer/daemon*.db",
"~/.humanlayer/daemon*.sock",
"~/.humanlayer/logs/",
"~/Library/Application Support/CodeLayer/",
"~/Library/Logs/dev.humanlayer.wui/",
"~/Library/Preferences/dev.humanlayer.wui.plist",
"~/Library/Saved Application State/dev.humanlayer.wui.savedState",
]
end
EOF
- name: Generate nightly cask file
if: steps.version.outputs.is_nightly == 'true'
working-directory: homebrew-humanlayer
run: |
cat > Casks/codelayer-nightly.rb << 'EOF'
cask "codelayer-nightly" do
version "${{ steps.version.outputs.release_version }}"
sha256 "${{ env.SHA256 }}"
url "${{ env.DMG_URL }}",
verified: "github.com/humanlayer/humanlayer/"
name "CodeLayer Nightly"
desc "Nightly build of CodeLayer - Desktop application for managing AI agent approvals"
homepage "https://humanlayer.dev/"
# No conflicts - can install alongside stable
app "CodeLayer-Nightly.app"
binary "#{appdir}/CodeLayer-Nightly.app/Contents/Resources/bin/humanlayer", target: "humanlayer-nightly"
binary "#{appdir}/CodeLayer-Nightly.app/Contents/Resources/bin/humanlayer", target: "codelayer-nightly"
binary "#{appdir}/CodeLayer-Nightly.app/Contents/Resources/bin/hld", target: "hld-nightly"
zap trash: [
"~/Library/Application Support/CodeLayer-Nightly",
"~/Library/Preferences/dev.humanlayer.wui.nightly.plist",
"~/Library/Saved Application State/dev.humanlayer.wui.nightly.savedState",
"~/.humanlayer/codelayer-nightly*.json",
"~/.humanlayer/daemon-nightly*.db",
"~/.humanlayer/daemon-nightly*.sock",
"~/Library/Logs/dev.humanlayer.wui.nightly/",
]
end
EOF
- name: Commit and push cask update
if: steps.version.outputs.is_nightly == 'true' || steps.version.outputs.is_stable == 'true'
working-directory: homebrew-humanlayer
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add "${{ env.CASK_FILE }}"
if [[ "${{ steps.version.outputs.is_stable }}" == "true" ]]; then
COMMIT_MSG="Update codelayer to ${{ steps.version.outputs.release_version }}"
else
COMMIT_MSG="Update codelayer-nightly to ${{ steps.version.outputs.release_version }}"
fi
git diff --staged --quiet || (git commit -m "$COMMIT_MSG" && git push)