# The nightly: every day at 06:00 UTC (or by hand), build `main` as # `X.Y.Z-nightly.YYYYMMDD.` — X.Y.Z being the version the tree declares # with its prerelease part dropped — and publish it as the rolling `nightly` # prerelease, replacing yesterday's. The app's Nightly channel reads # `releases/download/nightly/latest.json`; a different build is always an # update there (updater.rs), so the number does not need to climb. # # Same secrets and optional publish target as release.yml. name: nightly on: schedule: - cron: "0 6 * * *" workflow_dispatch: permissions: contents: write jobs: windows: runs-on: windows-latest steps: - uses: actions/checkout@v4 with: ref: main - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 with: workspaces: "./src-tauri -> ../target" - run: npm ci - name: Stamp the nightly version id: stamp shell: bash run: | base=$(node -p "require('./package.json').version.split('-')[0]") ver="$base-nightly.$(date -u +%Y%m%d).$(git rev-parse --short HEAD)" echo "version=$ver" >> "$GITHUB_OUTPUT" node -e ' const fs = require("fs"); const v = process.argv[1]; for (const f of ["package.json", "package-lock.json"]) { const j = JSON.parse(fs.readFileSync(f, "utf8")); j.version = v; if (j.packages && j.packages[""]) j.packages[""].version = v; fs.writeFileSync(f, JSON.stringify(j, null, 2) + "\n"); } const c = JSON.parse(fs.readFileSync("src-tauri/tauri.conf.json", "utf8")); c.version = v; fs.writeFileSync("src-tauri/tauri.conf.json", JSON.stringify(c, null, 2) + "\n"); ' "$ver" sed -i "0,/^version = \"[^\"]*\"/s//version = \"$ver\"/" Cargo.toml echo "nightly $ver" - name: Replace yesterday's nightly shell: bash env: GH_TOKEN: ${{ secrets.RELEASES_TOKEN || secrets.GITHUB_TOKEN }} TARGET: ${{ vars.RELEASES_OWNER || github.repository_owner }}/${{ vars.RELEASES_REPO || github.event.repository.name }} run: gh release delete nightly --repo "$TARGET" --cleanup-tag --yes || true - name: Build, sign and publish uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.RELEASES_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: owner: ${{ vars.RELEASES_OWNER || github.repository_owner }} repo: ${{ vars.RELEASES_REPO || github.event.repository.name }} tagName: nightly releaseName: "Nightly ${{ steps.stamp.outputs.version }}" releaseBody: "Built from main at ${{ github.sha }}. Nightlies can break." releaseDraft: false prerelease: true includeUpdaterJson: true args: --bundles nsis # A private repository's assets are reachable only through the API, so the # manifest's download URLs become API asset URLs — the app sends its token # and `Accept: application/octet-stream` for those (updater.rs). Public # repositories serve the same URLs to anyone, so nothing is lost. - name: Point the manifest at the API assets shell: bash env: GH_TOKEN: ${{ secrets.RELEASES_TOKEN || secrets.GITHUB_TOKEN }} TARGET: ${{ vars.RELEASES_OWNER || github.repository_owner }}/${{ vars.RELEASES_REPO || github.event.repository.name }} TAG: nightly run: | gh release download "$TAG" --repo "$TARGET" --pattern latest.json --output latest.json --clobber gh api "repos/$TARGET/releases/tags/$TAG" > release.json node -e ' const fs = require("fs"); const m = JSON.parse(fs.readFileSync("latest.json", "utf8")); const rel = JSON.parse(fs.readFileSync("release.json", "utf8")); for (const p of Object.values(m.platforms)) { const name = decodeURIComponent(p.url.split("/").pop()); const a = rel.assets.find((x) => x.name === name); if (!a) { console.error("no asset named", name); process.exit(1); } p.url = a.url; } fs.writeFileSync("latest.json", JSON.stringify(m, null, 2) + "\n"); console.log(JSON.stringify(m, null, 2)); ' gh release upload "$TAG" latest.json --repo "$TARGET" --clobber # The file testers run: our own setup window (setup/). It carries no # installer — it downloads the latest release with the tester's key and # verifies the signature — so it is built the same for every release. - name: Build SoulStream Setup shell: bash env: VERSION: ${{ steps.stamp.outputs.version }} run: | npx vite build --config vite.setup.config.js cargo build --release -p soulstream-setup cp target/release/SoulStream-Setup.exe "SoulStream-Setup-$VERSION.exe" ls -la "SoulStream-Setup-$VERSION.exe" # Which of the files on the page a tester runs, said on the page: the # bare NSIS installer reads like the download and installs with no key # gate and no signature check. - name: Publish SoulStream Setup shell: bash env: GH_TOKEN: ${{ secrets.RELEASES_TOKEN || secrets.GITHUB_TOKEN }} TARGET: ${{ vars.RELEASES_OWNER || github.repository_owner }}/${{ vars.RELEASES_REPO || github.event.repository.name }} TAG: nightly VERSION: ${{ steps.stamp.outputs.version }} SHA: ${{ github.sha }} run: | gh release upload "$TAG" "SoulStream-Setup-$VERSION.exe" --repo "$TARGET" --clobber { echo "### Download \`SoulStream-Setup-$VERSION.exe\`" echo echo "It asks for your alpha key, fetches this build with it, checks the signature and installs — no admin prompt." echo echo "The other files are the ones Setup uses:" echo echo "- \`SoulStream_${VERSION}_x64-setup.exe\` — the bare installer Setup downloads. Running it yourself skips the key and the signature check." echo "- \`SoulStream_${VERSION}_x64-setup.exe.sig\` — its signature." echo "- \`latest.json\` — the manifest the app's Nightly channel reads." echo echo "Built from main at $SHA. Nightlies can break." } > notes.md gh release edit "$TAG" --repo "$TARGET" --notes-file notes.md