diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c0efa69..15c0830e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,10 @@ name: CI on: + push: + branches: [main, dev] pull_request: - branches: [main] + branches: [main, dev] permissions: contents: read @@ -16,14 +18,14 @@ jobs: matrix: os: [ubuntu-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Install Rust toolchain - uses: dtolnay/rust-action@stable + uses: dtolnay/rust-toolchain@v1 - name: Install dependencies (Ubuntu only) if: matrix.os == 'ubuntu-latest' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bcc59816..b07fb6e9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,11 +30,11 @@ jobs: env: RELEASE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tag }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ env.RELEASE_TAG }} - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@v1 with: targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || 'x86_64-pc-windows-msvc' }} - uses: swatinem/rust-cache@v2 diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml new file mode 100644 index 00000000..0f3dc26c --- /dev/null +++ b/.github/workflows/release-dev.yml @@ -0,0 +1,126 @@ +name: Release Dev + +on: + push: + branches: + - dev + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: release-dev-${{ github.ref }} + cancel-in-progress: false + +jobs: + prepare: + name: Prepare dev release + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.meta.outputs.release_tag }} + release_name: ${{ steps.meta.outputs.release_name }} + steps: + - uses: actions/checkout@v6 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - run: bun install --frozen-lockfile + + - name: Type-check and build + run: bun run build + + - name: Run tests + run: bun run test + + - name: Validate version alignment + shell: bash + run: | + TAURI_CONF_VERSION=$(node -p 'require("./src-tauri/tauri.conf.json").version') + CARGO_VERSION=$(awk -F'"' '/^version =/ { print $2; exit }' ./src-tauri/Cargo.toml) + PKG_VERSION=$(node -p 'require("./package.json").version') + + if [[ "$TAURI_CONF_VERSION" != "$CARGO_VERSION" ]]; then + echo "src-tauri/tauri.conf.json version ($TAURI_CONF_VERSION) != Cargo.toml version ($CARGO_VERSION)" + exit 1 + fi + + if [[ "$TAURI_CONF_VERSION" != "$PKG_VERSION" ]]; then + echo "src-tauri/tauri.conf.json version ($TAURI_CONF_VERSION) != package.json version ($PKG_VERSION)" + exit 1 + fi + + - name: Compute dev release metadata + id: meta + shell: bash + run: | + APP_VERSION=$(node -p 'require("./src-tauri/tauri.conf.json").version') + SHORT_SHA=$(echo "$GITHUB_SHA" | cut -c1-7) + DATE_UTC=$(date -u +%Y%m%d) + RELEASE_TAG="dev-v${APP_VERSION}-${DATE_UTC}-${SHORT_SHA}" + RELEASE_NAME="Dev ${APP_VERSION} (${SHORT_SHA})" + + echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT" + echo "release_name=$RELEASE_NAME" >> "$GITHUB_OUTPUT" + + publish: + name: Publish dev binaries (${{ matrix.target }}) + needs: prepare + runs-on: ${{ matrix.platform }} + strategy: + fail-fast: false + matrix: + include: + - platform: macos-latest + target: aarch64-apple-darwin + args: --target aarch64-apple-darwin + - platform: macos-latest + target: x86_64-apple-darwin + args: --target x86_64-apple-darwin + - platform: windows-latest + target: x86_64-pc-windows-msvc + args: --target x86_64-pc-windows-msvc + steps: + - uses: actions/checkout@v6 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - run: bun install --frozen-lockfile + + - uses: dtolnay/rust-toolchain@v1 + with: + targets: ${{ matrix.target }} + + - uses: swatinem/rust-cache@v2 + with: + workspaces: "./src-tauri -> target" + + - name: Bundle plugins + run: bun run bundle:plugins + + - name: Validate updater signing key + shell: bash + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + run: | + if [[ -z "$TAURI_SIGNING_PRIVATE_KEY" ]]; then + echo "Missing TAURI_SIGNING_PRIVATE_KEY secret." + exit 1 + fi + + - uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + with: + tagName: ${{ needs.prepare.outputs.release_tag }} + releaseName: ${{ needs.prepare.outputs.release_name }} + releaseDraft: false + prerelease: true + includeUpdaterJson: true + args: ${{ matrix.args }} diff --git a/README.md b/README.md index d60661a7..2d18cb0f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Amp, Cursor, Claude, Codex, and more coming. See your usage at a glance from you ## Download -[**Download the latest release**](https://github.com/robinebers/openusage/releases/latest) (macOS, Apple Silicon & Intel) +[**Download the latest release**](https://github.com/Noisemaker111/openusage-opencode/releases/latest) (macOS, Apple Silicon & Intel, Windows x64) The app auto-updates. Install once and you're set. @@ -48,7 +48,13 @@ I maintain the project as a guide and quality gatekeeper, but this is your app a Plugins are currently bundled as we build our the API, but soon will be made flexible so you can build and load their own. -**Windows/Linux:** high-priority and on the todo, but I need testers with some time, willing to help out. +**Windows:** supported in this fork. + +**Linux:** planned; contributions and testing help are welcome. + +## Development and Release Channels + +See [`docs/release-flow.md`](docs/release-flow.md) for the `dev` -> `main` promotion workflow and prerelease/stable publishing flow. ### How to Contribute @@ -89,4 +95,4 @@ Inspired by [CodexBar](https://github.com/steipete/CodexBar) by [@steipete](http ### Stack -... \ No newline at end of file +... diff --git a/docs/release-flow.md b/docs/release-flow.md new file mode 100644 index 00000000..2641b52c --- /dev/null +++ b/docs/release-flow.md @@ -0,0 +1,49 @@ +# Release Flow + +Two-channel delivery flow: + +- `dev` branch: prerelease builds for testing and staging +- `main` branch + `v*` tags: stable production releases + +## Branch Strategy + +1. Feature PRs merge into `dev`. +2. `dev` triggers CI and prerelease desktop binaries (`Release Dev` workflow). +3. Once validated, merge `dev` into `main`. +4. Create and push a semantic tag (`vX.Y.Z`) from `main` to publish stable binaries. + +## Workflows + +- `ci.yml`: runs checks on pushes and PRs for `main` and `dev` +- `release-dev.yml`: publishes prerelease artifacts from `dev` +- `publish.yml`: publishes stable artifacts from tags + +## Version Sync + +Keep versions aligned before stable tags: + +```bash +bun run release:version 0.6.3 +``` + +This updates: + +- `package.json` +- `src-tauri/tauri.conf.json` +- `src-tauri/Cargo.toml` + +Then commit, tag, and push: + +```bash +git add . +git commit -m "release: prepare v0.6.3" +git tag v0.6.3 +git push origin main --follow-tags +``` + +## Required Secrets + +- `TAURI_SIGNING_PRIVATE_KEY` +- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` + +Optional platform-signing secrets can be added as needed for platform notarization/certificate requirements. diff --git a/package.json b/package.json index e1a3d40c..5889bb46 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "vite", "build": "tsc && vite build", "build:release": "./scripts/build-release.sh", + "release:version": "node scripts/sync-version.mjs", "bundle:plugins": "bun copy-bundled.cjs", "preview": "vite preview", "test": "vitest", diff --git a/scripts/sync-version.mjs b/scripts/sync-version.mjs new file mode 100644 index 00000000..b643aa9a --- /dev/null +++ b/scripts/sync-version.mjs @@ -0,0 +1,60 @@ +import { readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import process from "node:process"; + +const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/; + +function fail(message) { + console.error(message); + process.exit(1); +} + +function absolutePath(relativePath) { + return path.resolve(process.cwd(), relativePath); +} + +async function updateJsonVersion(filePath, version) { + const source = await readFile(absolutePath(filePath), "utf8"); + const parsed = JSON.parse(source); + + if (typeof parsed !== "object" || parsed === null || !("version" in parsed)) { + fail(`Missing version field in ${filePath}`); + } + + parsed.version = version; + await writeFile(absolutePath(filePath), `${JSON.stringify(parsed, null, 2)}\n`, "utf8"); +} + +async function updateCargoVersion(filePath, version) { + const source = await readFile(absolutePath(filePath), "utf8"); + const updated = source.replace(/^version\s*=\s*"[^"]+"/m, `version = "${version}"`); + + if (updated === source) { + fail(`Could not locate package version in ${filePath}`); + } + + await writeFile(absolutePath(filePath), updated, "utf8"); +} + +async function main() { + const version = process.argv[2]?.trim(); + + if (!version) { + fail("Usage: bun run release:version "); + } + + if (!VERSION_PATTERN.test(version)) { + fail(`Invalid version '${version}'. Expected semantic version like 1.2.3 or 1.2.3-beta.1`); + } + + await updateJsonVersion("package.json", version); + await updateJsonVersion("src-tauri/tauri.conf.json", version); + await updateCargoVersion("src-tauri/Cargo.toml", version); + + console.log(`Synchronized release version to ${version}`); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : error); + process.exit(1); +}); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e32d121d..9c3fa62a 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -64,9 +64,9 @@ }, "plugins": { "updater": { - "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDU2Njk4QTU0MDMzMEY5MTkKUldRWitUQURWSXBwVnFTY1FBWitxNlpaQnB5S3RVWW1qWHJ3NlRuZ2p3c2hPVzNTa3BUR0V0SXkK", + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDI1NTcyNzBEOTVBNzY2ODMKUldTRFpxZVZEU2RYSlppL3h1Y2lNblpQWEpHeHNmQ3BESjlxSk0zcmNhdnBKV1NCY25uZC9lRmUK", "endpoints": [ - "https://github.com/robinebers/openusage/releases/latest/download/latest.json" + "https://github.com/Noisemaker111/openusage-opencode/releases/latest/download/latest.json" ] } }