Repository navigation
Release pipeline, packaging and distribution docs #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # Check out every text file with LF on every platform. Shell tests scan their | ||
| # own source for "\n"-delimited markers, and viewer-dist/ is sha256-manifested, | ||
| # so a CRLF checkout on Windows would fail both. | ||
| * text=auto eol=lf |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| name: Release | ||
|
|
||
| # Builds each package on its own OS from the committed viewer pin, and drafts a | ||
| # GitHub release for a v* tag. Publishing the draft stays a manual maintainer | ||
| # step. Nothing here restores a cache: every package comes from a clean runner, | ||
| # so a poisoned cache can never reach a release. Pull requests that touch the | ||
| # pipeline run the build jobs only. | ||
| on: | ||
| push: | ||
| tags: ['v*'] | ||
| workflow_dispatch: | ||
| pull_request: | ||
| paths: | ||
| - .github/workflows/release.yml | ||
| - scripts/release/** | ||
| - scripts/sync-viewer.mjs | ||
| - src-tauri/tauri*.conf.json | ||
| - viewer-dist/manifest.json | ||
|
|
||
| concurrency: | ||
| group: release-${{ github.ref }} | ||
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| VIEWER_REPOSITORY: https://github.com/zknpr/SQLite-Explorer | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build ${{ matrix.platform }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - platform: macos | ||
| runner: macos-latest | ||
| bundles: app,dmg | ||
| # tjs needs glibc 2.38 and the app built here 2.39, so an older build | ||
| # host would not widen support much; 24.04 is the release-tested Linux. | ||
| - platform: linux | ||
| runner: ubuntu-24.04 | ||
| bundles: deb | ||
| - platform: windows | ||
| runner: windows-latest | ||
| bundles: nsis | ||
| runs-on: ${{ matrix.runner }} | ||
| timeout-minutes: 60 | ||
| defaults: | ||
| run: | ||
| shell: bash | ||
| env: | ||
| PLATFORM: ${{ matrix.platform }} | ||
| BUNDLES: ${{ matrix.bundles }} | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 | ||
| with: | ||
| node-version: 24 | ||
|
|
||
| - name: Check the version files agree (and match the tag) | ||
| env: | ||
| REF_TYPE: ${{ github.ref_type }} | ||
| REF_NAME: ${{ github.ref_name }} | ||
| run: | | ||
| if [ "$REF_TYPE" = tag ]; then | ||
| node scripts/release/version.mjs --tag "$REF_NAME" | ||
| else | ||
| node scripts/release/version.mjs | ||
| fi | ||
|
|
||
| - name: Install Linux build dependencies | ||
| if: matrix.platform == 'linux' | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev \ | ||
| librsvg2-dev libxdo-dev libssl-dev libsm-dev libice-dev patchelf file | ||
|
|
||
| - name: Install Rust 1.93.0 | ||
| run: | | ||
| rustup toolchain install 1.93.0 --profile minimal | ||
| rustup default 1.93.0 | ||
|
|
||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Fetch the viewer source pin | ||
| run: | | ||
| pin=$(node -p "require('./viewer-dist/manifest.json').ref") | ||
| git init -q ../SQLite-Explorer | ||
| git -C ../SQLite-Explorer fetch -q --depth 1 "$VIEWER_REPOSITORY" "$pin" | ||
| echo "VIEWER_PIN=$pin" >> "$GITHUB_ENV" | ||
|
|
||
| - name: Sync this platform's viewer and native runtime from the pin | ||
| run: | | ||
| cp viewer-dist/manifest.json "$RUNNER_TEMP/committed-manifest.json" | ||
| node scripts/sync-viewer.mjs --source ../SQLite-Explorer --ref "$VIEWER_PIN" | ||
| node scripts/sync-viewer.mjs --verify | ||
|
|
||
| # The committed viewer-dist/ targets Apple Silicon macOS, so here the | ||
| # fresh sync must reproduce it byte for byte. | ||
| - name: The committed viewer-dist matches the pin | ||
| if: matrix.platform == 'macos' | ||
| run: | | ||
| node -e ' | ||
| const fs = require("node:fs"); | ||
| const files = (p) => JSON.stringify(JSON.parse(fs.readFileSync(p, "utf8")).files); | ||
| if (files(process.argv[1]) !== files("viewer-dist/manifest.json")) { | ||
| console.error("committed viewer-dist differs from a fresh sync of the pin"); | ||
| process.exit(1); | ||
| }' "$RUNNER_TEMP/committed-manifest.json" | ||
|
|
||
| - name: Tests | ||
| run: | | ||
| npm test | ||
| cargo test --locked --manifest-path src-tauri/Cargo.toml --lib | ||
|
|
||
| - name: Build | ||
| run: npm run tauri build -- --bundles "$BUNDLES" | ||
|
|
||
| - name: Check and stage the macOS package | ||
| if: matrix.platform == 'macos' | ||
| run: | | ||
| app="src-tauri/target/release/bundle/macos/SQLite Explorer.app" | ||
| codesign --verify --deep --strict --verbose=2 "$app" | ||
| test -f "$app/Contents/Resources/THIRD_PARTY_NOTICES.txt" | ||
| test -x "$app/Contents/Resources/native/tjs" | ||
| dmgs=(src-tauri/target/release/bundle/dmg/*.dmg) | ||
| [ "${#dmgs[@]}" -eq 1 ] || { echo "expected one DMG, found ${#dmgs[@]}"; exit 1; } | ||
| hdiutil verify "${dmgs[0]}" | ||
| mkdir dist | ||
| cp "${dmgs[0]}" "dist/$(node scripts/release/assemble.mjs asset-name macos)" | ||
|
|
||
| - name: Check and stage the Linux package | ||
| if: matrix.platform == 'linux' | ||
| run: | | ||
| debs=(src-tauri/target/release/bundle/deb/*.deb) | ||
| [ "${#debs[@]}" -eq 1 ] || { echo "expected one .deb, found ${#debs[@]}"; exit 1; } | ||
| deb="${debs[0]}" | ||
| dpkg-deb --info "$deb" | ||
| # Listed once into a variable: `grep -q` on a live pipe exits early, | ||
| # and under pipefail the writer's SIGPIPE would fail a passing check. | ||
| contents=$(dpkg-deb --contents "$deb") | ||
| grep -q 'THIRD_PARTY_NOTICES.txt$' <<<"$contents" | ||
| grep -q '/native/tjs$' <<<"$contents" | ||
| # apt must refuse a system whose glibc is older than any binary in | ||
| # the package needs, so the declared floor has to cover them all. | ||
| declared=$(dpkg-deb -f "$deb" Depends | grep -oE 'libc6 \(>= [0-9.]+\)' | grep -oE '[0-9]+\.[0-9]+' || true) | ||
| [ -n "$declared" ] || { echo "the package declares no libc6 floor"; exit 1; } | ||
| root="$RUNNER_TEMP/deb-root" | ||
| dpkg-deb -x "$deb" "$root" | ||
| required=$(find "$root" -type f -exec file {} + | grep ELF | cut -d: -f1 | while read -r elf; do | ||
| strings -a "$elf" | grep -oE '^GLIBC_[0-9]+\.[0-9]+$' || true | ||
| done | sed 's/GLIBC_//' | sort -V | tail -1) | ||
| echo "declared libc6 >= $declared; binaries need GLIBC_$required" | ||
| [ "$(printf '%s\n%s\n' "$required" "$declared" | sort -V | tail -1)" = "$declared" ] \ | ||
| || { echo "binaries need glibc $required but the package declares $declared"; exit 1; } | ||
| mkdir dist | ||
| cp "$deb" "dist/$(node scripts/release/assemble.mjs asset-name linux)" | ||
|
|
||
| - name: Check and stage the Windows package | ||
| if: matrix.platform == 'windows' | ||
| run: | | ||
| exes=(src-tauri/target/release/bundle/nsis/*-setup.exe) | ||
| [ "${#exes[@]}" -eq 1 ] || { echo "expected one installer, found ${#exes[@]}"; exit 1; } | ||
| listing=$(7z l "${exes[0]}") | ||
| grep -q 'THIRD_PARTY_NOTICES.txt' <<<"$listing" | ||
| grep -q 'tjs.exe' <<<"$listing" | ||
| mkdir dist | ||
| cp "${exes[0]}" "dist/$(node scripts/release/assemble.mjs asset-name windows)" | ||
|
|
||
| - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | ||
| with: | ||
| name: package-${{ matrix.platform }} | ||
| path: dist/ | ||
| if-no-files-found: error | ||
|
|
||
| release: | ||
| name: Draft the release | ||
| needs: build | ||
| if: github.event_name == 'push' && github.ref_type == 'tag' | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 30 | ||
| permissions: | ||
| contents: write | ||
| id-token: write | ||
| attestations: write | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 | ||
| with: | ||
| node-version: 24 | ||
|
|
||
| - name: Install Rust 1.93.0 | ||
| run: | | ||
| rustup toolchain install 1.93.0 --profile minimal | ||
| rustup default 1.93.0 | ||
|
|
||
| - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 | ||
| with: | ||
| pattern: package-* | ||
| merge-multiple: true | ||
| path: dist | ||
|
|
||
| - name: Add notices and MPL sources, then write the manifest, checksums and notes | ||
| env: | ||
| REF_NAME: ${{ github.ref_name }} | ||
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | ||
| run: | | ||
| cp THIRD_PARTY_NOTICES.txt dist/ | ||
| node scripts/release/mpl-sources.mjs --out dist | ||
| node scripts/release/assemble.mjs assemble --dist dist --tag "$REF_NAME" \ | ||
| --commit "$GITHUB_SHA" --run-url "$RUN_URL" --notes "$RUNNER_TEMP/notes.md" | ||
|
|
||
| - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 | ||
| with: | ||
| subject-path: dist/* | ||
|
|
||
| - name: Create the draft release | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| REF_NAME: ${{ github.ref_name }} | ||
| run: | | ||
| gh release create "$REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" --draft --verify-tag \ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a supported tag such as Useful? React with 👍 / 👎. |
||
| --title "SQLite Explorer ${REF_NAME#v}" --notes-file "$RUNNER_TEMP/notes.md" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,10 +6,10 @@ Linux and Windows. It shares its viewer with the | |
| and runs in a Tauri desktop shell. | ||
|
|
||
| The current desktop version is **0.2.0**, integrating the extension's **1.8.1** | ||
| release and desktop-specific fixes. This repository does not publish binary | ||
| releases yet; build from a checkout as described below. See | ||
| [release readiness](docs/release-readiness.md) for the remaining distribution | ||
| gates. | ||
| release and desktop-specific fixes. Packages for macOS, Linux and Windows are | ||
| attached to each [release](https://github.com/zknpr/SQLite-Explorer-App/releases); | ||
| see [Install](#install). See [release readiness](docs/release-readiness.md) for | ||
| the remaining distribution gates. | ||
|
|
||
| ## Working with databases | ||
|
|
||
|
|
@@ -29,17 +29,49 @@ refuse; retain pending work with a separate export before reloading. | |
| | Platform tested | Package | Coverage | | ||
| | --- | --- | --- | | ||
| | macOS, Apple Silicon | `.app` | Native/WASM workflows; remaining OS checks are recorded in [macOS QA](docs/macos-release-qa.md) | | ||
| | Ubuntu 24.04, x86-64, Xfce/X11 | Debian, AppImage | Installed-app checks; shutdown/restart checks used the Debian package | | ||
| | Ubuntu 24.04, x86-64, Xfce/X11 | Debian package | Installed-app checks, shutdown/restart | | ||
| | Windows 11, x86-64 | NSIS installer | Installed-app checks, shutdown/restart and post-boot saves | | ||
|
|
||
| See [Linux and Windows QA](docs/linux-windows-release-qa.md) for tested package | ||
| hashes and limits. Other Linux desktops and Wayland remain unverified. Linux | ||
| session-end cancellation requires a working inherited XSMP session connection. | ||
| Forced termination or power loss cannot preserve unsaved edits. | ||
|
|
||
| The Windows build is unsigned. The local macOS build has an ad-hoc signature, | ||
| without Developer ID signing or notarization. Downloaded-package trust prompts | ||
| have not been signed off. | ||
| ## Install | ||
|
|
||
| Download the package for your system from the | ||
| [releases page](https://github.com/zknpr/SQLite-Explorer-App/releases). The | ||
| packages are not signed with a publisher identity yet; see the | ||
| [code signing policy](docs/code-signing-policy.md). Verify a download before | ||
| installing it: | ||
|
|
||
| ```sh | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For users following the new macOS or Windows installation paths on a stock system, Useful? React with 👍 / 👎. |
||
| sha256sum --check --ignore-missing SHA256SUMS | ||
| gh attestation verify <downloaded file> --repo zknpr/SQLite-Explorer-App | ||
| ``` | ||
|
|
||
| **macOS (Apple Silicon).** Open the `.dmg` and drag SQLite Explorer to | ||
| Applications. The app has an ad-hoc signature and is not notarized, so macOS | ||
| blocks the first launch. Open **System Settings → Privacy & Security** and | ||
| choose **Open Anyway** for SQLite Explorer. To uninstall, move the app to the | ||
| Trash; settings live in `~/Library/Application Support/xyz.zknpr.sqlite-explorer`. | ||
|
|
||
| **Windows (x86-64).** Run the `-setup.exe` installer. It is unsigned, so | ||
| SmartScreen shows "Windows protected your PC"; choose **More info → Run | ||
| anyway**. If **Smart App Control** is on, Windows blocks unsigned programs and | ||
| offers no override, so the installer cannot run there. To uninstall, use | ||
| **Settings → Apps → Installed apps → SQLite Explorer → Uninstall**. | ||
|
|
||
| **Linux (x86-64, Debian/Ubuntu).** The package needs glibc 2.39 or newer | ||
| (Ubuntu 24.04+, Debian 13+): | ||
|
|
||
| ```sh | ||
| sudo apt install ./SQLite-Explorer-<version>-linux-amd64.deb | ||
| ``` | ||
|
|
||
| To uninstall, run `sudo apt remove sq-lite-explorer` (the package name Tauri | ||
| derives from the product name). AppImage packages are not | ||
| provided. | ||
|
|
||
| ## Build from a checkout | ||
|
|
||
|
|
@@ -74,29 +106,22 @@ npm test | |
| cargo test --locked --manifest-path src-tauri/Cargo.toml --lib | ||
| ``` | ||
|
|
||
| On Linux and Windows, create the platform's packages: | ||
| Create the platform's packages: | ||
|
|
||
| ```sh | ||
| npm run tauri build | ||
| ``` | ||
|
|
||
| On macOS without a configured signing identity, build and seal a local `.app`: | ||
|
|
||
| ```sh | ||
| npm run tauri build -- --bundles app | ||
| codesign --force --sign - 'src-tauri/target/release/bundle/macos/SQLite Explorer.app' | ||
| codesign --verify --deep --strict 'src-tauri/target/release/bundle/macos/SQLite Explorer.app' | ||
| ``` | ||
|
|
||
| The explicit ad-hoc signing step seals the app's resources. The linker's default | ||
| signature alone fails strict bundle verification. This local signature does not | ||
| provide Developer ID signing or notarization. Distribution packages need their | ||
| own [signing and packaging setup](https://v2.tauri.app/distribute/). | ||
| Packages appear under `src-tauri/target/release/bundle/`: `.app` and DMG on | ||
| macOS, Debian on Linux and NSIS on Windows. `src-tauri/tauri.macos.conf.json` | ||
| signs the macOS app ad-hoc with the hardened runtime, which seals its resources | ||
| so `codesign --verify --deep --strict` passes; it is not Developer ID signing or | ||
| notarization. Selecting another native target in the sync script stages its | ||
| resources; it does not cross-compile the app. | ||
|
|
||
| Packages appear under `src-tauri/target/release/bundle/`: `.app` for the local | ||
| macOS command above, Debian/AppImage on Linux and NSIS on Windows. A configured | ||
| macOS distribution build can also produce a DMG. Selecting another native target | ||
| in the sync script stages its resources; it does not cross-compile the app. | ||
| Release packages are built by the [release workflow](.github/workflows/release.yml), | ||
| which runs these steps on each OS from the pinned viewer source and drafts a | ||
| release for a `v*` tag. | ||
|
|
||
| For development, run `npm run tauri dev`. Packaged-app checks are still required | ||
| for native dialogs, OS integration and the custom asset protocol. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this
v0.2.0release and every future version, copying the static notice ships text headed “0.2.0 private beta” that directs recipients tov0.2.0-beta.1for the MPL source archive, even though the workflow attaches the corresponding archive to the current tag. Generate or validate the notice againstREF_NAMEbefore packaging it so installed copies identify the actual release and source location.Useful? React with 👍 / 👎.