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
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,37 @@ jobs:

- name: Run clippy
run: cargo clippy --all-targets -- -D warnings

macos-app:
name: macOS app package
runs-on: macos-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust components
run: rustup component add rustfmt clippy

- name: Check formatting
run: cargo fmt --check

- name: Run tests
run: cargo test

- name: Run clippy
run: cargo clippy --all-targets -- -D warnings

- name: Build and smoke-test menu bar app
run: scripts/test-macos-app.sh

- name: Package menu bar app
run: scripts/package-macos-app.sh

- name: Upload app package
uses: actions/upload-artifact@v4
with:
name: plainlink-macos-app
path: |
dist/packages/*.zip
dist/packages/*.sha256
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ For macOS menu bar app changes:

```sh
scripts/test-macos-app.sh
scripts/package-macos-app.sh
```

## Rule Contributions
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ scripts/test-macos-app.sh

This creates `dist/PlainLink.app`.

To build a local unsigned release zip:

```sh
scripts/package-macos-app.sh
```

This creates `dist/packages/PlainLink-<version>-macos-<arch>.zip` and a `.sha256` checksum.

## Project Layout

```text
Expand All @@ -94,6 +102,7 @@ docs/
scripts/
build-macos-app.sh Build dist/PlainLink.app
test-macos-app.sh Build and smoke-test the app bundle
package-macos-app.sh Create an unsigned zip and checksum
```

## Contributing
Expand Down
12 changes: 12 additions & 0 deletions docs/MACOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ The generated app lives at:
dist/PlainLink.app
```

Create an unsigned zip and checksum for testing:

```sh
scripts/package-macos-app.sh
```

Packages are written to:

```text
dist/packages/
```

The app is built with Swift/AppKit and Apple Command Line Tools. It embeds the release Rust CLI, then uses that CLI for all product actions:

```mermaid
Expand Down
22 changes: 22 additions & 0 deletions docs/MENUBAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,28 @@ scripts/test-macos-app.sh

This builds the bundle, validates `Info.plist`, confirms both executables exist, runs the menu app smoke test, and checks the embedded CLI version command.

## Package

```sh
scripts/package-macos-app.sh
```

The package script rebuilds and smoke-tests the app, then writes:

```text
dist/packages/PlainLink-<version>-macos-<arch>.zip
dist/packages/PlainLink-<version>-macos-<arch>.zip.sha256
```

The zip is unsigned and not notarized. It is meant for MVP testing and GitHub Actions artifacts.

Verify a downloaded package:

```sh
cd dist/packages
shasum -a 256 -c PlainLink-<version>-macos-<arch>.zip.sha256
```

## Runtime Flow

```mermaid
Expand Down
3 changes: 2 additions & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
- Restore last original URL.
- User LaunchAgent install/status/restart/uninstall.
- Stable user install/uninstall and doctor checks.
- Unsigned macOS release packaging.
- Human-readable default rules.
- Fixture-backed rule QA.
- Contributor docs with Mermaid diagrams.

## Next

- Add signed release builds.
- Add a downloadable app artifact.
- Publish downloadable app artifacts from GitHub Actions.
- Add clearer first-run onboarding inside the menu bar app.
- Add optional remote ruleset updates with clear privacy controls.

Expand Down
51 changes: 51 additions & 0 deletions scripts/package-macos-app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/sh
set -eu

ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
APP_NAME="PlainLink"
APP_DIR="$ROOT_DIR/dist/$APP_NAME.app"
PACKAGE_DIR="$ROOT_DIR/dist/packages"
VERSION=$(awk -F '=' '$1 ~ /^[[:space:]]*version[[:space:]]*$/ { gsub(/[ "]/, "", $2); print $2; exit }' "$ROOT_DIR/Cargo.toml")
ARCH=$(uname -m)
ARTIFACT_NAME="$APP_NAME-$VERSION-macos-$ARCH.zip"
ZIP_PATH="$PACKAGE_DIR/$ARTIFACT_NAME"
SHA_PATH="$ZIP_PATH.sha256"

if [ "$(uname -s)" != "Darwin" ]; then
echo "PlainLink.app packages can only be built on macOS." >&2
exit 1
fi

if [ -z "$VERSION" ]; then
echo "could not read package version from Cargo.toml" >&2
exit 1
fi

command -v ditto >/dev/null 2>&1 || {
echo "ditto is required to package PlainLink.app." >&2
exit 1
}

command -v shasum >/dev/null 2>&1 || {
echo "shasum is required to create package checksums." >&2
exit 1
}

"$ROOT_DIR/scripts/test-macos-app.sh"

test -d "$APP_DIR" || {
echo "missing app bundle: $APP_DIR" >&2
exit 1
}

mkdir -p "$PACKAGE_DIR"
rm -f "$ZIP_PATH" "$SHA_PATH"

cd "$ROOT_DIR/dist"
ditto -c -k --sequesterRsrc --keepParent "$APP_NAME.app" "$ZIP_PATH"

cd "$PACKAGE_DIR"
shasum -a 256 "$ARTIFACT_NAME" > "$ARTIFACT_NAME.sha256"

echo "Packaged $ZIP_PATH"
echo "Checksum $SHA_PATH"
7 changes: 7 additions & 0 deletions tests/macos_app_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ fn macos_menu_app_assets_are_wired() {
fs::read_to_string("scripts/build-macos-app.sh").expect("build script should exist");
let test_script =
fs::read_to_string("scripts/test-macos-app.sh").expect("test script should exist");
let package_script =
fs::read_to_string("scripts/package-macos-app.sh").expect("package script should exist");

for command in [
"install",
Expand All @@ -36,4 +38,9 @@ fn macos_menu_app_assets_are_wired() {
assert!(build_script.contains("-module-cache-path"));
assert!(test_script.contains("--smoke-test"));
assert!(test_script.contains("plutil -lint"));
assert!(package_script.contains("scripts/test-macos-app.sh"));
assert!(package_script.contains("ditto -c -k --sequesterRsrc --keepParent"));
assert!(package_script.contains("shasum -a 256"));
assert!(package_script.contains("ARTIFACT_NAME="));
assert!(package_script.contains("macos-$ARCH.zip"));
}
Loading