Update README and build-test script
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
49287c15eb
commit
3af2c79326
116
README.md
116
README.md
|
|
@ -77,6 +77,7 @@ The actionable suggestions matter too. "Take a break" is an instruction with no
|
||||||
- **Debug mode** for testing (1-minute cycles)
|
- **Debug mode** for testing (1-minute cycles)
|
||||||
- **Launch at Login** via macOS LaunchAgent
|
- **Launch at Login** via macOS LaunchAgent
|
||||||
- **Native macOS app** — no Electron, no web views, just AppKit
|
- **Native macOS app** — no Electron, no web views, just AppKit
|
||||||
|
- **Self-update** — checks the repo over HTTPS (no login); offers an update only when the server has a newer release (SHA + timestamp)
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
|
@ -111,27 +112,112 @@ Open **Applications** (or use Spotlight: **Cmd+Space**, type "Pommedoro") and do
|
||||||
|
|
||||||
Requires macOS 13+ and Swift 5.9+.
|
Requires macOS 13+ and Swift 5.9+.
|
||||||
|
|
||||||
```bash
|
| Command | What it does |
|
||||||
make bundle
|
|---------|--------------|
|
||||||
```
|
| `make bundle` | Build the `.app` (default target) |
|
||||||
|
| `make install` | Build and install to `/Applications`; stamps as **dev build** (auto-update disabled) |
|
||||||
|
| `make run` | Build and launch the app from `.build/` (does not install) |
|
||||||
|
| `make dmg` | Build the `.app` and create `.build/Pommedoro.dmg` |
|
||||||
|
| `make release` | Build DMG and copy it + SHA256 into `releases/` for publishing |
|
||||||
|
| `make clean` | Remove build artifacts and the app bundle |
|
||||||
|
| `make icons` | Regenerate `Pommedoro.icns` from `Resources/boot-logo.svg` |
|
||||||
|
| `make install-agent` | Install LaunchAgent for Launch at Login (after `make install`) |
|
||||||
|
|
||||||
To install to `/Applications`:
|
For day-to-day development and releasing to users, see **Development & Release** below.
|
||||||
|
|
||||||
```bash
|
---
|
||||||
make install
|
|
||||||
```
|
|
||||||
|
|
||||||
To build the DMG:
|
## Development & Release
|
||||||
|
|
||||||
```bash
|
This section describes how to work on Pommedoro locally, how releases and auto-update work, and how to publish a new version.
|
||||||
make dmg
|
|
||||||
```
|
|
||||||
|
|
||||||
To run directly:
|
### Local development
|
||||||
|
|
||||||
```bash
|
- **Quick run (no install)**
|
||||||
make run
|
`make run` — builds and opens the app from `.build/Pommedoro.app`. Good for quick tests.
|
||||||
```
|
|
||||||
|
- **Install and run from /Applications**
|
||||||
|
`make install` — builds, installs to `/Applications/Pommedoro.app`, and stamps the app as a **dev build** by writing `dev` into the update state file. The app will **not** prompt you to “update” to the release version; the menu item “Check for Updates” will show “Auto-update is disabled for local dev builds.”
|
||||||
|
|
||||||
|
- **After code changes**
|
||||||
|
Run `make install` again (or `make run`). There is no need to touch the update state for normal dev; the dev stamp persists until you install from a DMG or run a release flow.
|
||||||
|
|
||||||
|
- **Launch at Login (optional)**
|
||||||
|
`make install-agent` — installs a LaunchAgent that starts Pommedoro at login. Run after `make install`. Use `make uninstall-agent` to remove it.
|
||||||
|
|
||||||
|
### How releases and auto-update work
|
||||||
|
|
||||||
|
- **Release artifacts**
|
||||||
|
A release consists of:
|
||||||
|
- `releases/Pommedoro.dmg` — the disk image users install from.
|
||||||
|
- `releases/Pommedoro.dmg.sha256` — the SHA256 hash of that DMG (one line, 64 hex chars).
|
||||||
|
|
||||||
|
- **Where the app checks for updates**
|
||||||
|
The app fetches **over HTTPS with no login**:
|
||||||
|
- SHA: `https://git.nixc.us/colin/pommedoro/raw/branch/main/releases/Pommedoro.dmg.sha256`
|
||||||
|
- DMG: `https://git.nixc.us/colin/pommedoro/raw/branch/main/releases/Pommedoro.dmg`
|
||||||
|
It uses the response **Last-Modified** header as the release “timestamp” so it only offers an update when the server has a **newer** release, not just a different one.
|
||||||
|
|
||||||
|
- **Local state (what version am I?)**
|
||||||
|
The app stores its idea of the current release in:
|
||||||
|
- **File:** `~/Library/Application Support/Pommedoro/current.sha256`
|
||||||
|
- **Format:** two lines: `SHA256` (64 hex chars) and a Unix timestamp (or `0`).
|
||||||
|
- **Set when:**
|
||||||
|
- Installing from the DMG (via `Install.command`) — writes the DMG’s SHA and, if it matches the remote, the remote’s Last-Modified timestamp.
|
||||||
|
- After a successful in-app update — the swap script writes the new SHA and timestamp.
|
||||||
|
- After `make install` — writes `dev` so the app never nags for updates.
|
||||||
|
|
||||||
|
- **When “Update available” is shown**
|
||||||
|
Only if **both** are true:
|
||||||
|
1. The release SHA on the server is **different** from the one in the state file.
|
||||||
|
2. The server’s Last-Modified is **newer** than the stored timestamp (so we don’t offer “updates” to an older release).
|
||||||
|
|
||||||
|
If the SHA in the state file matches the server, the app always reports “Up to Date,” regardless of timestamp.
|
||||||
|
|
||||||
|
### Full release workflow (recommended)
|
||||||
|
|
||||||
|
Use this to build, test, and publish a release so the installed app and the server stay in sync and “Check for Updates” behaves correctly.
|
||||||
|
|
||||||
|
1. **Run the full test and push the release**
|
||||||
|
```bash
|
||||||
|
./build-test.sh --push
|
||||||
|
```
|
||||||
|
This will:
|
||||||
|
- Stop any running Pommedoro, clean the build, and run `make release`.
|
||||||
|
- Create the DMG and `releases/Pommedoro.dmg.sha256`.
|
||||||
|
- Simulate a user install (quarantine, mount DMG, run `Install.command`), then verify the app runs and the local state file is stamped with the release SHA.
|
||||||
|
- **Push:** `git add releases/Pommedoro.dmg releases/Pommedoro.dmg.sha256`, commit, and push.
|
||||||
|
- **Prove:** Fetch the remote SHA and confirm it equals the first line of the local state file. If not, the script exits with failure.
|
||||||
|
|
||||||
|
2. **Result**
|
||||||
|
- The app launched by `Install.command` is the same build as the one now on the server.
|
||||||
|
- “Check for Updates” in the menu should show **Up to Date**.
|
||||||
|
|
||||||
|
**Without `--push`:**
|
||||||
|
`./build-test.sh` runs the same build and install checks but does **not** commit or push. Use this to verify the DMG and install flow before you push.
|
||||||
|
|
||||||
|
### When to run what
|
||||||
|
|
||||||
|
| Goal | Command |
|
||||||
|
|------|--------|
|
||||||
|
| Code and run locally without installing | `make run` |
|
||||||
|
| Install to /Applications and develop (no update prompts) | `make install` |
|
||||||
|
| Build a DMG only (e.g. to test the image) | `make release` (or `make dmg` for `.build/` only) |
|
||||||
|
| Test full install from DMG and state stamp | `./build-test.sh` |
|
||||||
|
| Test, push release, and prove update state | `./build-test.sh --push` |
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
- **“Update available” even though I just pushed / same version**
|
||||||
|
The app compares the **first line** of `~/Library/Application Support/Pommedoro/current.sha256` to the SHA from the server. If you installed from a DMG and then pushed that same build, run `./build-test.sh --push` so the script pushes and then verifies remote SHA = local state. If the script’s proof passes but the app still nags, the app may be reading a different path (e.g. different user); the script and app both use the same state file path.
|
||||||
|
|
||||||
|
- **I’m a dev; I don’t want update prompts**
|
||||||
|
Use `make install`. It writes `dev` into the state file so the app never offers to update. Installing from a DMG (or updating in-app) overwrites that with a real SHA + timestamp.
|
||||||
|
|
||||||
|
- **State file location**
|
||||||
|
`~/Library/Application Support/Pommedoro/current.sha256`. Two lines: SHA, then timestamp (or `0`). For a dev build, the first line is the literal string `dev`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,3 +84,25 @@ echo ""
|
||||||
echo "==> All checks passed."
|
echo "==> All checks passed."
|
||||||
echo "==> DMG at: .build/Pommedoro.dmg"
|
echo "==> DMG at: .build/Pommedoro.dmg"
|
||||||
echo "==> Release at: releases/Pommedoro.dmg + releases/Pommedoro.dmg.sha256"
|
echo "==> Release at: releases/Pommedoro.dmg + releases/Pommedoro.dmg.sha256"
|
||||||
|
|
||||||
|
# Optional: push release and prove remote matches launched app
|
||||||
|
if [ "${1:-}" = "--push" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "==> Pushing release to remote..."
|
||||||
|
git add releases/Pommedoro.dmg releases/Pommedoro.dmg.sha256
|
||||||
|
if git diff --staged --quiet 2>/dev/null; then
|
||||||
|
echo "==> No release changes to push."
|
||||||
|
else
|
||||||
|
RELEASE_SHA="$(cat releases/Pommedoro.dmg.sha256)"
|
||||||
|
git commit -m "Release: ${RELEASE_SHA}"
|
||||||
|
git push
|
||||||
|
fi
|
||||||
|
echo "==> Proving remote matches launched app..."
|
||||||
|
REMOTE_SHA="$(curl -sL "https://git.nixc.us/colin/pommedoro/raw/branch/main/releases/Pommedoro.dmg.sha256" | tr -d '\r\n' | head -c 64)"
|
||||||
|
LOCAL_SHA="$(head -1 "${SHA_FILE}" | tr -d '\r\n' | head -c 64)"
|
||||||
|
if [ -z "${REMOTE_SHA}" ] || [ "${REMOTE_SHA}" != "${LOCAL_SHA}" ]; then
|
||||||
|
echo "==> FAIL: remote SHA (${REMOTE_SHA}) != local state (${LOCAL_SHA}). Update check would not show Up to Date."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "==> PROVEN: remote SHA = local state. Launched app is identical to release; Check for Updates will show Up to Date."
|
||||||
|
fi
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue