From 5e8a737da8d7245c9a8e5e65ca5724e0ed9fd2cd Mon Sep 17 00:00:00 2001 From: PathGao Date: Sat, 1 Aug 2026 11:50:43 +0800 Subject: [PATCH] build: harden release inputs and isolate test bundles --- .github/workflows/build.yml | 8 +++---- README.md | 10 ++++++++ RELEASING.md | 6 +++-- package.json | 1 + scripts/build-test-bundle.mjs | 41 +++++++++++++++++++++++++++++++++ scripts/releaseWorkflow.test.ts | 20 ++++++++++++++++ 6 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 scripts/build-test-bundle.mjs create mode 100644 scripts/releaseWorkflow.test.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 604a1218..83f1dc04 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -110,7 +110,7 @@ jobs: sudo snap install snapcraft --classic - name: Install Frontend Dependencies - run: npm install + run: npm ci # --- Windows Build (x64) --- - name: Build Windows x64 @@ -128,8 +128,7 @@ jobs: run: | VERSION="${{ needs.create-release.outputs.version }}" cp src-tauri/target/release/Markpad.exe "Markpad_${VERSION}_x64.exe" - cp "Markpad_${VERSION}_x64.exe" "MarkpadInstaller_${VERSION}_x64.exe" - gh release upload v$VERSION "Markpad_${VERSION}_x64.exe" "MarkpadInstaller_${VERSION}_x64.exe" --clobber + gh release upload v$VERSION "Markpad_${VERSION}_x64.exe" --clobber find src-tauri/target/release/bundle/nsis -name "*-setup.exe" -exec gh release upload v$VERSION {} --clobber \; find src-tauri/target/release/bundle/nsis -name "*-setup.exe.sig" -exec gh release upload v$VERSION {} --clobber \; @@ -171,8 +170,7 @@ jobs: run: | VERSION="${{ needs.create-release.outputs.version }}" cp src-tauri/target/aarch64-pc-windows-msvc/release/Markpad.exe "Markpad_${VERSION}_arm64.exe" - cp "Markpad_${VERSION}_arm64.exe" "MarkpadInstaller_${VERSION}_arm64.exe" - gh release upload v$VERSION "Markpad_${VERSION}_arm64.exe" "MarkpadInstaller_${VERSION}_arm64.exe" --clobber + gh release upload v$VERSION "Markpad_${VERSION}_arm64.exe" --clobber find src-tauri/target/aarch64-pc-windows-msvc/release/bundle/nsis -name "*-setup.exe" -exec gh release upload v$VERSION {} --clobber \; find src-tauri/target/aarch64-pc-windows-msvc/release/bundle/nsis -name "*-setup.exe.sig" -exec gh release upload v$VERSION {} --clobber \; diff --git a/README.md b/README.md index 2fd57cc9..42bbeb92 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,16 @@ Download the latest executable or installer from the [releases page](https://git - Run `npm run tauri build` to build the executable - [Optional] Rename to `MarkpadInstaller.exe` to run as installer +### Isolated macOS test bundle + +For local verification without opening or replacing `/Applications/Markpad.app`, build an unsigned test-only app with an independent identifier: + +```bash +MARKPAD_TEST_BUNDLE_ID=dev.example.markpad.test npm run build:test-bundle +``` + +The result is placed in `dist/test-bundle/`. It is not a distributable release: it has no Developer ID notarization or Windows Authenticode signature. + ## Issues & Feedback If you find a bug, have a feature request, or just want to leave some feedback, please [open an issue](https://github.com/alecdotdev/Markpad/issues/new/choose). I'm actively developing Markpad and love hearing from users! diff --git a/RELEASING.md b/RELEASING.md index ac07ede7..1ba75793 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -44,6 +44,8 @@ If you ever lose the private key: ## Per-release workflow +The workflow uses `npm ci`, so its installed dependency graph is exactly the committed lockfile. Do not replace it with `npm install` in release jobs. + 1. **Bump version in both files** (mandatory — Tauri reads runtime version from `Cargo.toml`): - [`package.json`](package.json) `version` - [`src-tauri/Cargo.toml`](src-tauri/Cargo.toml) `[package].version` @@ -59,8 +61,8 @@ If you ever lose the private key: 4. **Wait** ~30 min for matrix builds to finish, plus ~2 min for `generate-update-feed`. 5. **Open the draft release** on the [Releases page](https://github.com/alecdotdev/Markpad/releases). Verify the assets: - **macOS**: `*.dmg`, `*.app.tar.gz`, `*.app.tar.gz.sig` - - **Windows x64**: `*_x64.exe` (portable), `*_x64-setup.exe` (NSIS installer), `*_x64-setup.exe.sig` - - **Windows ARM64**: `*_arm64.exe` (portable), `*_arm64-setup.exe` (NSIS installer), `*_arm64-setup.exe.sig` + - **Windows x64**: `Markpad__x64.exe` (portable), `*_x64-setup.exe` (NSIS installer), `*_x64-setup.exe.sig` + - **Windows ARM64**: `Markpad__arm64.exe` (portable), `*_arm64-setup.exe` (NSIS installer), `*_arm64-setup.exe.sig` - **Linux**: `*.deb`, `*.rpm`, `*.AppImage`, `*.AppImage.sig` - **Update feed**: `latest.json` (one entry per successfully built platform) 6. **Click "Publish release"** — this is the gate that activates auto-update for all clients pointing at `releases/latest/download/latest.json`. diff --git a/package.json b/package.json index f2b2f637..c7676f16 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "test:settings-scroll": "node --test --import tsx scripts/previewScrollSync.test.ts scripts/toolbarCustomizationWiring.test.ts", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "build:test-bundle": "node scripts/build-test-bundle.mjs", "tauri": "tauri" }, "license": "MIT", diff --git a/scripts/build-test-bundle.mjs b/scripts/build-test-bundle.mjs new file mode 100644 index 00000000..b82c913b --- /dev/null +++ b/scripts/build-test-bundle.mjs @@ -0,0 +1,41 @@ +import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { execFileSync } from 'node:child_process'; + +const root = process.cwd(); +const identifier = process.env.MARKPAD_TEST_BUNDLE_ID; + +if (!identifier || !identifier.startsWith('dev.') || identifier === 'com.alecdotdev.markpad') { + throw new Error('Set MARKPAD_TEST_BUNDLE_ID to a non-production identifier beginning with dev.'); +} + +const config = JSON.stringify({ + identifier, + productName: 'Markpad 2.7.0 Test', + bundle: { targets: ['app'], createUpdaterArtifacts: false }, +}); +const rustRoot = resolve(root, '.local-rust'); +const env = { + ...process.env, + CARGO_HOME: resolve(rustRoot, 'cargo'), + RUSTUP_HOME: resolve(rustRoot, 'rustup'), + PATH: `${resolve(rustRoot, 'rustup/toolchains/stable-aarch64-apple-darwin/bin')}:${process.env.PATH}`, +}; + +execFileSync('npm', ['run', 'tauri', '--', 'build', '--bundles', 'app', '--config', config], { + cwd: root, + env, + stdio: 'inherit', +}); + +const macosDir = resolve(root, 'src-tauri/target/release/bundle/macos'); +const appName = readdirSync(macosDir).find((entry) => entry.endsWith('.app')); +if (!appName) throw new Error('Tauri did not produce a macOS .app bundle.'); + +const outputDir = resolve(root, 'dist/test-bundle'); +const output = resolve(outputDir, appName); +mkdirSync(outputDir, { recursive: true }); +rmSync(output, { recursive: true, force: true }); +cpSync(resolve(macosDir, appName), output, { recursive: true }); +if (!existsSync(output)) throw new Error(`Failed to copy test bundle to ${output}`); +console.log(`Test bundle: ${output}`); diff --git a/scripts/releaseWorkflow.test.ts b/scripts/releaseWorkflow.test.ts new file mode 100644 index 00000000..2ea7e6c6 --- /dev/null +++ b/scripts/releaseWorkflow.test.ts @@ -0,0 +1,20 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const workflow = readFileSync('.github/workflows/build.yml', 'utf8'); +const packageJson = JSON.parse(readFileSync('package.json', 'utf8')) as { scripts: Record }; + +test('release builds install the locked dependency graph', () => { + assert.match(workflow, /name: Install Frontend Dependencies\s+run: npm ci/); + assert.doesNotMatch(workflow, /npm install/); +}); + +test('portable executables are not mislabeled as installers', () => { + assert.doesNotMatch(workflow, /MarkpadInstaller_/); + assert.match(workflow, /bundle\/nsis.*-setup\.exe/); +}); + +test('the test bundle command uses an isolated builder', () => { + assert.equal(packageJson.scripts['build:test-bundle'], 'node scripts/build-test-bundle.mjs'); +});