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
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
test:
runs-on: macos-14
steps:
- uses: actions/checkout@v4

- name: Run Swift tests
run: swift test

- name: Check release script syntax
run: zsh -n Scripts/*.sh

- name: Package unsigned release
env:
GITHUB_REPOSITORY: tcballard/Meanwhile
RELEASE_OUTPUT_DIR: ${{ runner.temp }}/meanwhile-dist
SWIFT_BUILD_PATH: ${{ runner.temp }}/meanwhile-build
run: ./Scripts/release-unsigned.sh

- name: Verify packaged artifacts
run: |
test -f "${{ runner.temp }}/meanwhile-dist/Meanwhile-0.1.0-unsigned.zip"
test -f "${{ runner.temp }}/meanwhile-dist/meanwhile.rb"
ruby -c "${{ runner.temp }}/meanwhile-dist/meanwhile.rb"
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,12 @@ GITHUB_REPOSITORY="owner/Meanwhile" \
./Scripts/release.sh
```

The script produces `dist/Meanwhile-0.1.0.zip` and `dist/meanwhile.rb`. Publishing
the archive, cask, and GitHub release remains an explicit release-owner action.
The script builds and signs the app in a temporary local directory, then
produces `dist/Meanwhile-0.1.0.zip` and `dist/meanwhile.rb`. It verifies the
stapled app and a clean extraction of the final archive before returning.
Publishing the archive, cask, and GitHub release remains an explicit
release-owner action. Set `RELEASE_OUTPUT_DIR` to write the final artifacts
somewhere other than `dist`.

## Package layout

Expand All @@ -125,5 +129,3 @@ the archive, cask, and GitHub release remains an explicit release-owner action.
dispositions, terminal focus, and integration installation.
- `Meanwhile`: the menu-bar executable.
- `MeanwhileHook`: the Claude/Codex hook and Claude status-line helper.

See [ROADMAP.md](ROADMAP.md) for the release gate and post-v0.1 decision rule.
38 changes: 0 additions & 38 deletions ROADMAP.md

This file was deleted.

11 changes: 6 additions & 5 deletions Scripts/make-cask.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ REPOSITORY="$1"
VERSION="$2"
SHA256="$3"
CHANNEL="${4:-stable}"
OUTPUT="$ROOT/dist/meanwhile.rb"
OUTPUT="${CASK_OUTPUT_PATH:-$ROOT/dist/meanwhile.rb}"

if [[ "$CHANNEL" == "unsigned" ]]; then
DESCRIPTION="Unsigned pre-release of Meanwhile — for testing only"
CAVEATS=$'\n\n caveats <<~EOS\n This is an unsigned pre-release for testing only. macOS Gatekeeper will\n block its first launch. If you trust this build, explicitly remove quarantine:\n\n xattr -dr com.apple.quarantine /Applications/Meanwhile.app\n EOS'
CAVEATS=$'\n caveats <<~EOS\n This is an unsigned pre-release for testing only. macOS Gatekeeper will\n block its first launch. If you trust this build, explicitly remove quarantine:\n\n xattr -dr com.apple.quarantine /Applications/Meanwhile.app\n EOS'
else
DESCRIPTION="Use coding-agent wait time for reviews and failing CI"
CAVEATS=""
fi

mkdir -p "$ROOT/dist"
mkdir -p "${OUTPUT:h}"
cat > "$OUTPUT" <<EOF
cask "meanwhile" do
version "$VERSION"
Expand All @@ -32,14 +32,15 @@ cask "meanwhile" do
desc "$DESCRIPTION"
homepage "https://github.com/$REPOSITORY"

depends_on macos: ">= :sonoma"
depends_on macos: :sonoma

app "Meanwhile.app"
$CAVEATS

zap trash: [
"~/Library/Application Support/Meanwhile",
"~/Library/Preferences/com.meanwhile.Meanwhile.plist",
]
$CAVEATS
end
EOF

Expand Down
3 changes: 2 additions & 1 deletion Scripts/package-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ set -euo pipefail
ROOT="${0:A:h:h}"
CONFIGURATION="${CONFIGURATION:-debug}"
CODE_SIGN_IDENTITY="${CODE_SIGN_IDENTITY:--}"
APP="$ROOT/dist/Meanwhile.app"
APP_OUTPUT_DIR="${APP_OUTPUT_DIR:-$ROOT/dist}"
APP="$APP_OUTPUT_DIR/Meanwhile.app"
CONTENTS="$APP/Contents"
BUILD_PATH="${SWIFT_BUILD_PATH:-$ROOT/.build}"

Expand Down
23 changes: 21 additions & 2 deletions Scripts/release-unsigned.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,37 @@ set -euo pipefail
ROOT="${0:A:h:h}"
: "${GITHUB_REPOSITORY:?Set GITHUB_REPOSITORY to owner/repository}"

TEMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/meanwhile-unsigned-release.XXXXXX")"
cleanup() {
rm -rf "$TEMP_ROOT"
}
trap cleanup EXIT INT TERM

export CONFIGURATION=release
export CODE_SIGN_IDENTITY=-
export SWIFT_BUILD_PATH="${SWIFT_BUILD_PATH:-${TMPDIR:-/tmp}/meanwhile-unsigned-release-build}"
export APP_OUTPUT_DIR="$TEMP_ROOT/package"

OUTPUT_DIR="${RELEASE_OUTPUT_DIR:-$ROOT/dist}"
mkdir -p "$OUTPUT_DIR"

APP="$("$ROOT/Scripts/package-app.sh")"
BASE_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APP/Contents/Info.plist")"
VERSION="$BASE_VERSION-unsigned"
ZIP="$ROOT/dist/Meanwhile-$VERSION.zip"
ZIP="$OUTPUT_DIR/Meanwhile-$VERSION.zip"

rm -f "$ZIP"
ditto -c -k --keepParent "$APP" "$ZIP"

VERIFY_DIR="$TEMP_ROOT/verify"
mkdir -p "$VERIFY_DIR"
ditto -x -k "$ZIP" "$VERIFY_DIR"
codesign --verify --deep --strict --verbose=2 "$VERIFY_DIR/Meanwhile.app"

SHA256="$(shasum -a 256 "$ZIP" | awk '{print $1}')"
export CASK_OUTPUT_PATH="$OUTPUT_DIR/meanwhile.rb"
"$ROOT/Scripts/make-cask.sh" "$GITHUB_REPOSITORY" "$VERSION" "$SHA256" unsigned

echo "UNSIGNED PRE-RELEASE — FOR TESTING ONLY"
echo "$ZIP"
echo "$ROOT/dist/meanwhile.rb"
echo "$CASK_OUTPUT_PATH"
29 changes: 27 additions & 2 deletions Scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,44 @@ ROOT="${0:A:h:h}"
: "${NOTARYTOOL_PROFILE:?Set NOTARYTOOL_PROFILE to an xcrun notarytool keychain profile}"
: "${GITHUB_REPOSITORY:?Set GITHUB_REPOSITORY to owner/repository}"

TEMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/meanwhile-release.XXXXXX")"
cleanup() {
rm -rf "$TEMP_ROOT"
}
trap cleanup EXIT INT TERM

export CONFIGURATION=release
export SWIFT_BUILD_PATH="${SWIFT_BUILD_PATH:-${TMPDIR:-/tmp}/meanwhile-release-build}"
export APP_OUTPUT_DIR="$TEMP_ROOT/package"

OUTPUT_DIR="${RELEASE_OUTPUT_DIR:-$ROOT/dist}"
mkdir -p "$OUTPUT_DIR"

APP="$("$ROOT/Scripts/package-app.sh")"
VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APP/Contents/Info.plist")"
ZIP="$ROOT/dist/Meanwhile-$VERSION.zip"
ZIP="$OUTPUT_DIR/Meanwhile-$VERSION.zip"

rm -f "$ZIP"
ditto -c -k --keepParent "$APP" "$ZIP"
xcrun notarytool submit "$ZIP" --keychain-profile "$NOTARYTOOL_PROFILE" --wait
xcrun stapler staple "$APP"
xcrun stapler validate "$APP"
codesign --verify --deep --strict --verbose=2 "$APP"

rm -f "$ZIP"
ditto -c -k --keepParent "$APP" "$ZIP"

VERIFY_DIR="$TEMP_ROOT/verify"
mkdir -p "$VERIFY_DIR"
ditto -x -k "$ZIP" "$VERIFY_DIR"
VERIFIED_APP="$VERIFY_DIR/Meanwhile.app"
codesign --verify --deep --strict --verbose=2 "$VERIFIED_APP"
spctl --assess --type execute --verbose=2 "$VERIFIED_APP"
xcrun stapler validate "$VERIFIED_APP"

SHA256="$(shasum -a 256 "$ZIP" | awk '{print $1}')"
export CASK_OUTPUT_PATH="$OUTPUT_DIR/meanwhile.rb"
"$ROOT/Scripts/make-cask.sh" "$GITHUB_REPOSITORY" "$VERSION" "$SHA256"

echo "$ZIP"
echo "$ROOT/dist/meanwhile.rb"
echo "$CASK_OUTPUT_PATH"
Loading