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

on:
release:
types:
- published
workflow_dispatch:
inputs:
tag:
description: Existing GitHub release tag like v0.0.102. Leave empty for artifact-only builds.
required: false
type: string
publish:
description: Publish the built snap to the Snap Store.
required: false
type: boolean
default: false
release_channel:
description: Snap Store channel to release to.
required: false
type: choice
default: stable
options:
- stable
- candidate
- beta
- edge

permissions:
contents: write

jobs:
snap:
runs-on: ubuntu-latest
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
steps:
- uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98

- name: Resolve release metadata
id: meta
shell: bash
run: |
if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then
TAG="${{ github.event.release.tag_name }}"
else
TAG="${{ inputs.tag }}"
fi

VERSION="$(python -c "import json; print(json.load(open('package.json'))['version'])")"
if [[ -n "$TAG" ]]; then
VERSION="${TAG#v}"
fi

CHANNEL="stable"
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
CHANNEL="${{ inputs.release_channel }}"
fi

echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT"
echo "DORA_RELEASE_VERSION=$VERSION" >> "$GITHUB_ENV"

- name: Build snap
uses: snapcore/action-build@v1
id: build

- name: Upload snap artifact
uses: actions/upload-artifact@v4
with:
name: dora-snap-${{ steps.meta.outputs.version }}
path: ${{ steps.build.outputs.snap }}

- name: Upload snap to GitHub release
if: ${{ steps.meta.outputs.tag != '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload ${{ steps.meta.outputs.tag }} ${{ steps.build.outputs.snap }} --clobber

- name: Publish snap to Snap Store
if: ${{ env.SNAPCRAFT_STORE_CREDENTIALS != '' && (github.event_name == 'release' || inputs.publish) }}
uses: snapcore/action-publish@v1
with:
snap: ${{ steps.build.outputs.snap }}
release: ${{ steps.meta.outputs.channel }}
113 changes: 113 additions & 0 deletions .github/workflows/winget.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Winget

on:
release:
types:
- published
workflow_dispatch:
inputs:
tag:
description: Existing GitHub release tag like v0.0.102.
required: true
type: string
submit_update:
description: Submit a wingetcreate update PR.
required: false
type: boolean
default: false

permissions:
contents: write

jobs:
generate-manifests:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.meta.outputs.tag }}
version: ${{ steps.meta.outputs.version }}
installer_url: ${{ steps.meta.outputs.installer_url }}
steps:
- uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98

- name: Setup bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6

- name: Resolve release metadata
id: meta
shell: bash
run: |
if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then
TAG="${{ github.event.release.tag_name }}"
else
TAG="${{ inputs.tag }}"
fi

VERSION="${TAG#v}"
INSTALLER_URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/Dora_${VERSION}_x64_en-US.msi"
CHECKSUMS_URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/checksums-windows.txt"

echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "installer_url=$INSTALLER_URL" >> "$GITHUB_OUTPUT"
echo "checksums_url=$CHECKSUMS_URL" >> "$GITHUB_OUTPUT"

- name: Download Windows checksums
run: curl --fail --location --output checksums-windows.txt "${{ steps.meta.outputs.checksums_url }}"

- name: Generate Winget manifests
run: |
bun run release:winget -- \
--version=${{ steps.meta.outputs.version }} \
--installer-url=${{ steps.meta.outputs.installer_url }} \
--checksums-file=checksums-windows.txt \
--installer-file=Dora_${{ steps.meta.outputs.version }}_x64_en-US.msi

- name: Archive Winget manifests
run: |
tar -czf winget-manifests-${{ steps.meta.outputs.version }}.tar.gz \
-C packaging/winget/manifests/${{ steps.meta.outputs.version }} .

- name: Upload Winget manifest artifact
uses: actions/upload-artifact@v4
with:
name: winget-manifests-${{ steps.meta.outputs.version }}
path: winget-manifests-${{ steps.meta.outputs.version }}.tar.gz

- name: Upload Winget manifests to GitHub release
if: ${{ github.event_name == 'release' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload ${{ steps.meta.outputs.tag }} winget-manifests-${{ steps.meta.outputs.version }}.tar.gz --clobber

submit-update:
needs: generate-manifests
if: ${{ (github.event_name == 'release' && vars.WINGET_PACKAGE_READY == 'true') || (github.event_name == 'workflow_dispatch' && inputs.submit_update) }}
runs-on: windows-latest
Comment on lines +82 to +85
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): The submit-update job always allocates a Windows runner even when the Winget token is missing.

Because the job always runs on windows-latest, it still allocates a Windows runner even when WINGET_CREATE_GITHUB_TOKEN is empty and the first step exits immediately. Consider moving the token check into the job-level if: (e.g. append && secrets.WINGET_CREATE_GITHUB_TOKEN != '') so the job is entirely skipped when the token isn’t configured.

Suggested implementation:

  submit-update:
    needs: generate-manifests
    if: ${{ ((github.event_name == 'release' && vars.WINGET_PACKAGE_READY == 'true') || (github.event_name == 'workflow_dispatch' && inputs.submit_update)) && secrets.WINGET_CREATE_GITHUB_TOKEN != '' }}
    runs-on: windows-latest
    env:
      WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_CREATE_GITHUB_TOKEN }}

    steps:
      - name: Install WingetCreate dependencies

env:
WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_CREATE_GITHUB_TOKEN }}
steps:
- name: Skip when Winget token is not configured
if: ${{ env.WINGET_CREATE_GITHUB_TOKEN == '' }}
shell: pwsh
run: |
Write-Host "WINGET_CREATE_GITHUB_TOKEN is not configured. Skipping automated Winget submission."
exit 0

- name: Install WingetCreate dependencies
if: ${{ env.WINGET_CREATE_GITHUB_TOKEN != '' }}
shell: pwsh
run: |
Invoke-WebRequest https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx
Add-AppxPackage .\Microsoft.VCLibs.x64.14.00.Desktop.appx
Invoke-WebRequest https://aka.ms/wingetcreate/latest/msixbundle -OutFile wingetcreate.msixbundle
Add-AppxPackage .\wingetcreate.msixbundle

- name: Submit Winget update
if: ${{ env.WINGET_CREATE_GITHUB_TOKEN != '' }}
shell: pwsh
run: |
wingetcreate update RemcoStoeten.Dora `
-u "${{ needs.generate-manifests.outputs.installer_url }}" `
-v "${{ needs.generate-manifests.outputs.version }}" `
-t "${env:WINGET_CREATE_GITHUB_TOKEN}" `
--submit
171 changes: 171 additions & 0 deletions docs/distribution/one-machine-playbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# One-Machine Packaging Playbook

This is the shortest path if you only have one main machine and need to use Docker or VMs for the ecosystem-specific steps.

## VM bootstrap on Ubuntu

If you want the repo to manage the packaging VMs for you, start here:

```bash
bash tools/scripts/vm-lab.sh
```

Or directly:

```bash
bash tools/scripts/vm-lab.sh setup-host
bash tools/scripts/vm-lab.sh create ubuntu
bash tools/scripts/vm-lab.sh create arch
```

Details live in `docs/distribution/vm-lab.md`.

## First milestone: create a real tagged release

Package-manager publishing should start only after a real tagged release exists
and its assets are visible on the GitHub release page.

From the main repo:

```bash
git tag v0.1.0
git push origin v0.1.0
```

Then wait for the `Release` GitHub Actions workflow to finish. That workflow now uploads:

- release installers
- `checksums-linux.txt`
- `checksums-windows.txt`

Verify the release page first. If the release assets are missing, stop there and fix the release before touching Winget, AUR, or Snap.

## Winget from a Windows VM

Use a Windows 11 VM because `wingetcreate` is Windows-native. You need this VM
for the first public package submission only.

### In the VM

1. Install GitHub CLI and sign in if needed.
2. Install `wingetcreate`:

```powershell
winget install wingetcreate
```

3. Create the first package submission:

```powershell
wingetcreate new "https://github.com/remcostoeten/dora/releases/download/v0.1.0/Dora_0.1.0_x64_en-US.msi"
```

4. Review the detected metadata carefully.
5. Let `wingetcreate` create the PR to `microsoft/winget-pkgs`.

### On later releases

Use:

```powershell
wingetcreate update RemcoStoeten.Dora --urls "https://github.com/remcostoeten/dora/releases/download/v0.1.1/Dora_0.1.1_x64_en-US.msi"
```

After the package exists in `microsoft/winget-pkgs`, you can stop doing that by
hand. Set `WINGET_CREATE_GITHUB_TOKEN` in GitHub Actions, add the repository
variable `WINGET_PACKAGE_READY=true`, and let `.github/workflows/winget.yml`
submit update PRs automatically from published releases.

## AUR from Docker on your main machine

You do not need a full Arch VM just to validate the package.

### Generate the package files

```bash
bun run release:aur -- \
--version=0.1.0 \
--checksums-file=apps/desktop/src-tauri/target/release/bundle/checksums-linux.txt \
--appimage-file=Dora_0.1.0_amd64.AppImage
```

### Validate in Docker

```bash
bash tools/scripts/test-aur-docker.sh
```

That spins up `archlinux:latest`, installs `base-devel`, and runs `makepkg` against `packaging/aur`.

### Publish to the real AUR repo

1. Create an AUR account at `aur.archlinux.org`.
2. Add your SSH key there.
3. Clone the AUR repo:

```bash
git clone ssh://aur@aur.archlinux.org/dora-bin.git ~/code/dora-bin-aur
```

4. Sync the generated files:

```bash
bash tools/scripts/sync-aur-repo.sh ~/code/dora-bin-aur
```

5. Commit and push:

```bash
cd ~/code/dora-bin-aur
git add PKGBUILD .SRCINFO
git commit -m "Update dora-bin to 0.1.0"
git push
```

## Snap from your main machine or GitHub Actions

Snap does not need a separate repo.

### Local build path

If your machine is Ubuntu or another Linux system with Snap available:

```bash
sudo snap install snapcraft --classic
sudo /snap/bin/snapcraft pack --destructive-mode
```

### CI build path

The repo now has:

- `.github/workflows/snap.yml`
- `snap/snapcraft.yaml`

When a GitHub release is published, the workflow builds the `.snap`, uploads it
to the release, and publishes it to the Snap Store if the store credential
secret exists. Manual dispatch still works for artifact-only or test runs.

### Store publish path

1. Create or log into your Snapcraft account.
2. Register the `dora` snap name.
3. Export store credentials:

```bash
snapcraft export-login --snaps=dora \
--acls package_access,package_push,package_update,package_release \
exported.txt
```

4. Add the contents of `exported.txt` as the GitHub Actions secret
`SNAPCRAFT_STORE_CREDENTIALS`.
5. Publish a GitHub release and let `.github/workflows/snap.yml` handle the
build and store upload.

## Recommended order

1. Make the tagged GitHub release actually publish assets.
2. Submit Winget from a Windows VM.
3. Validate and publish AUR from Docker plus your normal host shell.
4. Register the Snap name and add `SNAPCRAFT_STORE_CREDENTIALS`.
19 changes: 19 additions & 0 deletions docs/distribution/release-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Interactive Release Guide

Run this from your normal shell, including `fish`:

```bash
bash tools/scripts/release-guide.sh
```

The script gives you an interactive menu that:

- checks branch, tag, versions, dirty worktree, and GitHub auth
- generates a release notes draft from `CHANGELOG.md`
- creates the local tag when the tree is clean
- pushes the tag
- creates the GitHub release with the generated notes
- prints the next AUR command and the one-time bootstrap steps for Winget and
Snap if those channels are not configured yet

It intentionally refuses to create a tag while the worktree is dirty.
Loading
Loading