Skip to content

Add v0.12.3 changelog entry #54

Add v0.12.3 changelog entry

Add v0.12.3 changelog entry #54

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
build:
runs-on: macos-26
steps:
- uses: actions/checkout@v4
- name: Run tests
run: swift test
- name: Build CLI release binaries
run: swift build -c release
- name: Package CLI tarball
run: |
mkdir -p staging/agents
cp .build/release/Engram staging/memory
cp .build/release/EngramHooks staging/memory-hooks
cp -R .build/release/Engram_EngramKit.bundle staging/
cp -R .build/release/swift-transformers_Hub.bundle staging/
cp -R .build/release/SwiftLM_SwiftLM.bundle staging/
cp agents/*.md staging/agents/
cd staging && tar czf ../engram-macos-arm64.tar.gz *
- name: Import signing certificate
if: env.DEVELOPER_ID_CERT_BASE64 != ''
env:
DEVELOPER_ID_CERT_BASE64: ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.DEVELOPER_ID_CERT_PASSWORD }}
run: |
KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
# Create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Import certificate
echo "$DEVELOPER_ID_CERT_BASE64" | base64 --decode > $RUNNER_TEMP/cert.p12
security import $RUNNER_TEMP/cert.p12 -P "$DEVELOPER_ID_CERT_PASSWORD" \
-A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Add to search list
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
- name: Archive app with xcodebuild
if: env.DEVELOPER_ID_CERT_BASE64 != ''
env:
DEVELOPER_ID_CERT_BASE64: ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
VERSION=${GITHUB_REF_NAME#v}
PREV_BUILD=$(sed -n 's/.*<sparkle:version>\([0-9]*\)<.*/\1/p' appcast.xml | head -1)
BUILD_NUMBER=$(( ${PREV_BUILD:-0} + 1 ))
xcodebuild archive \
-project Engram.xcodeproj \
-scheme Engram \
-archivePath build/Engram.xcarchive \
-configuration Release \
-skipMacroValidation \
-skipPackagePluginValidation \
ARCHS=arm64 \
CODE_SIGN_IDENTITY="Developer ID Application" \
CODE_SIGN_STYLE=Manual \
DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \
MARKETING_VERSION="$VERSION" \
CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \
OTHER_CODE_SIGN_FLAGS="--keychain $RUNNER_TEMP/signing.keychain-db"
- name: Export archive to .app
if: env.DEVELOPER_ID_CERT_BASE64 != ''
env:
DEVELOPER_ID_CERT_BASE64: ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
# Inject team ID into ExportOptions.plist
sed -i '' "s|</dict>| <key>teamID</key><string>$APPLE_TEAM_ID</string></dict>|" ExportOptions.plist
xcodebuild -exportArchive \
-archivePath build/Engram.xcarchive \
-exportPath build/export \
-exportOptionsPlist ExportOptions.plist
- name: Bundle CLI binaries into .app
if: env.DEVELOPER_ID_CERT_BASE64 != ''
env:
DEVELOPER_ID_CERT_BASE64: ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
APP="build/export/Engram.app"
CLI_DIR="$APP/Contents/Resources/cli"
AGENTS_DIR="$CLI_DIR/agents"
mkdir -p "$CLI_DIR" "$AGENTS_DIR"
cp .build/release/Engram "$CLI_DIR/memory"
cp .build/release/EngramHooks "$CLI_DIR/memory-hooks"
cp -R .build/release/Engram_EngramKit.bundle "$CLI_DIR/"
cp -R .build/release/swift-transformers_Hub.bundle "$CLI_DIR/"
cp -R .build/release/SwiftLM_SwiftLM.bundle "$CLI_DIR/"
cp agents/*.md "$AGENTS_DIR/"
# Sign CLI binaries with Developer ID, hardened runtime, and timestamp
for bin in "$CLI_DIR/memory" "$CLI_DIR/memory-hooks"; do
codesign --force --sign "Developer ID Application" \
--keychain "$RUNNER_TEMP/signing.keychain-db" \
--options runtime \
--timestamp \
"$bin"
done
# Re-codesign the entire .app since contents changed
codesign --deep --force --sign "Developer ID Application" \
--keychain "$RUNNER_TEMP/signing.keychain-db" \
--options runtime \
--timestamp \
"$APP"
- name: Create DMG
if: env.DEVELOPER_ID_CERT_BASE64 != ''
env:
DEVELOPER_ID_CERT_BASE64: ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
run: |
brew install create-dmg
VERSION=${GITHUB_REF_NAME#v}
create-dmg \
--volname "Engram $VERSION" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "Engram.app" 150 190 \
--app-drop-link 450 190 \
--no-internet-enable \
"Engram-${VERSION}.dmg" \
"build/export/Engram.app"
- name: Notarize DMG
if: env.DEVELOPER_ID_CERT_BASE64 != ''
env:
DEVELOPER_ID_CERT_BASE64: ${{ secrets.DEVELOPER_ID_CERT_BASE64 }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
VERSION=${GITHUB_REF_NAME#v}
DMG="Engram-${VERSION}.dmg"
SUBMIT_OUT=$(xcrun notarytool submit "$DMG" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait 2>&1) || true
echo "$SUBMIT_OUT"
# Extract submission ID and fetch log on failure
SUB_ID=$(echo "$SUBMIT_OUT" | grep "id:" | head -1 | awk '{print $2}')
if echo "$SUBMIT_OUT" | grep -q "status: Invalid"; then
echo "--- Notarization log ---"
xcrun notarytool log "$SUB_ID" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" || true
exit 1
fi
xcrun stapler staple "$DMG"
- name: Generate Sparkle appcast
if: env.SPARKLE_PRIVATE_KEY != ''
env:
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
run: |
VERSION=${GITHUB_REF_NAME#v}
DMG="Engram-${VERSION}.dmg"
# Download Sparkle tools
SPARKLE_VERSION="2.8.1"
mkdir -p /tmp/sparkle-tools
curl -sL "https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz" | tar xJ -C /tmp/sparkle-tools bin/
# Move DMG to a staging dir for generate_appcast
mkdir -p appcast_staging
cp "$DMG" appcast_staging/
# Generate appcast from the DMG
echo "$SPARKLE_PRIVATE_KEY" | /tmp/sparkle-tools/bin/generate_appcast \
--ed-key-file - \
--download-url-prefix "https://github.com/jsflax/Engram/releases/download/${GITHUB_REF_NAME}/" \
appcast_staging
# Use generated appcast
cp appcast_staging/appcast.xml appcast.xml
- name: Extract changelog for release
id: changelog
run: |
VERSION=${GITHUB_REF_NAME#v}
BODY=$(awk "/^## \\[${VERSION}\\]/{found=1; next} /^## \\[/{if(found) exit} found{print}" CHANGELOG.md)
# Write to file for multi-line handling
echo "$BODY" > /tmp/release-body.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
engram-macos-arm64.tar.gz
Engram-*.dmg
body_path: /tmp/release-body.md
- name: Commit appcast.xml to main
if: env.SPARKLE_PRIVATE_KEY != ''
env:
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git fetch origin main
git checkout main
git add appcast.xml
git diff --cached --quiet || (git commit -m "Update appcast.xml for ${GITHUB_REF_NAME}" && git push origin main)
- name: Update Homebrew formula
if: env.TAP_TOKEN != ''
env:
TAP_TOKEN: ${{ secrets.TAP_TOKEN }}
run: |
SHA=$(shasum -a 256 engram-macos-arm64.tar.gz | awk '{print $1}')
VERSION=${GITHUB_REF_NAME#v}
git clone https://x-access-token:${TAP_TOKEN}@github.com/jsflax/homebrew-tap.git tap
cd tap
# Update version and sha256 in existing formula
sed -i '' "s/version \".*\"/version \"${VERSION}\"/" Formula/engram.rb
sed -i '' "s/sha256 \".*\"/sha256 \"${SHA}\"/" Formula/engram.rb
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/engram.rb
git diff --cached --quiet || (git commit -m "Update engram to ${VERSION}" && git push)
- name: Notify Slack
if: success() && env.SLACK_WEBHOOK_URL != ''
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
TAG="${GITHUB_REF_NAME}"
VERSION="${TAG#v}"
REPO="${{ github.repository }}"
URL="https://github.com/${REPO}/releases/tag/${TAG}"
# Extract changelog section and convert Markdown → Slack mrkdwn
CHANGES=$(awk "/^## \\[${VERSION}\\]/{found=1; next} /^## \\[/{if(found) exit} found{print}" CHANGELOG.md \
| sed '/^$/d' \
| sed 's/^### \(.*\)/*\1*/' \
| sed 's/\*\*\([^*]*\)\*\*/*\1*/g' \
| sed 's/^- /• /' \
| sed 's/`\([^`]*\)`/`\1`/g' \
| head -20)
# Escape for JSON
CHANGES_ESCAPED=$(echo "$CHANGES" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read())[1:-1])')
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{
\"blocks\": [
{
\"type\": \"header\",
\"text\": {\"type\": \"plain_text\", \"text\": \"✅ Engram ${TAG} released\"}
},
{
\"type\": \"section\",
\"text\": {\"type\": \"mrkdwn\", \"text\": \"${CHANGES_ESCAPED}\"},
\"accessory\": {
\"type\": \"button\",
\"text\": {\"type\": \"plain_text\", \"text\": \"View Release\"},
\"url\": \"${URL}\"
}
}
]
}"
- name: Cleanup keychain
if: always()
run: |
if [ -f "$RUNNER_TEMP/signing.keychain-db" ]; then
security delete-keychain "$RUNNER_TEMP/signing.keychain-db" 2>/dev/null || true
fi