diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9ce0b2..e8ea399 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,11 +21,11 @@ jobs: name: Verify artifacts, test & lint runs-on: macos-latest steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: 24 cache: npm @@ -46,6 +46,10 @@ jobs: rustup toolchain install 1.93.0 --profile minimal --component clippy rustup default 1.93.0 + # A dependency bump fails here until the inventory and notices cover it. + - name: Third-party notices match the release dependency graph + run: node scripts/release/notices.mjs + - name: Shell unit tests run: cargo test --locked --manifest-path src-tauri/Cargo.toml --lib diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f515d86..11bb540 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -119,6 +119,11 @@ jobs: npm test cargo test --locked --manifest-path src-tauri/Cargo.toml --lib + # Every package bundles THIRD_PARTY_NOTICES.txt, so no release may ship + # notices that miss a dependency or describe another viewer pin. + - name: Third-party notices match the release dependency graph + run: node scripts/release/notices.mjs + - name: Build run: npm run tauri build -- --bundles "$BUNDLES" @@ -171,7 +176,16 @@ jobs: 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)" + name=$(node scripts/release/assemble.mjs asset-name windows) + cp "${exes[0]}" "dist/$name" + # Run the exact checksum command the release notes publish for + # Windows, once against the real hash and once against a wrong one. + check="$RUNNER_TEMP/checksum-check" + mkdir "$check" + cp "dist/$name" "$check/" + command=$(node scripts/release/assemble.mjs windows-checksum-command) + (cd "$check" && sha256sum "$name" > SHA256SUMS && [ "$(pwsh -NoProfile -Command "$command" | tr -d '\r')" = True ]) + (cd "$check" && printf '%064d %s\n' 0 "$name" > SHA256SUMS && [ "$(pwsh -NoProfile -Command "$command" | tr -d '\r')" = False ]) - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: @@ -228,5 +242,10 @@ jobs: GH_TOKEN: ${{ github.token }} REF_NAME: ${{ github.ref_name }} run: | - gh release create "$REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" --draft --verify-tag \ + flags=(--draft --verify-tag) + # A prerelease version must not become Latest when the draft is published. + if [ "$(node -p "require('./dist/release-manifest.json').prerelease")" = true ]; then + flags+=(--prerelease) + fi + gh release create "$REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" "${flags[@]}" \ --title "SQLite Explorer ${REF_NAME#v}" --notes-file "$RUNNER_TEMP/notes.md" diff --git a/README.md b/README.md index 38144a8..1beec35 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Linux and Windows. It shares its viewer with the [SQLite Explorer VS Code extension](https://github.com/zknpr/SQLite-Explorer) and runs in a Tauri desktop shell. -The current desktop version is **0.2.0**, integrating the extension's **1.8.1** +The current desktop version is **0.3.0**, integrating the extension's **1.8.1** 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 @@ -45,13 +45,22 @@ 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: +Download `SHA256SUMS` next to the package and check it with the tool your +system has: + ```sh -sha256sum --check --ignore-missing SHA256SUMS -gh attestation verify --repo zknpr/SQLite-Explorer-App +shasum -a 256 --check --ignore-missing SHA256SUMS # macOS +sha256sum --check --ignore-missing SHA256SUMS # Linux ``` +On Windows, the release notes give a PowerShell `Get-FileHash` command that +prints `True` for a matching installer. With the GitHub CLI on any platform, +`gh attestation verify --repo zknpr/SQLite-Explorer-App` +confirms the file was built by this repository's release workflow. + **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 +Applications. The disk image is unsigned; the app inside 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`. diff --git a/THIRD_PARTY_NOTICES.txt b/THIRD_PARTY_NOTICES.txt index 8bfda55..36ee712 100644 --- a/THIRD_PARTY_NOTICES.txt +++ b/THIRD_PARTY_NOTICES.txt @@ -1,4 +1,4 @@ -SQLite Explorer desktop 0.2.0 private beta — third-party notices +SQLite Explorer desktop — third-party notices The application source is licensed under MIT (see LICENSE.md). Components listed here retain their own licenses. Inclusion in this inventory does not @@ -19,14 +19,15 @@ bytes are unchanged. Supporting Codicons code is MIT. Both texts follow. MPL 2.0 source availability: unmodified source packages for cssparser 0.36.0, cssparser-macros 0.6.1, dtoa-short 0.3.5, option-ext 0.2.0 and selectors 0.36.1 -are supplied alongside the installers in mpl-dependency-sources.tar.gz at -https://github.com/zknpr/SQLite-Explorer-App/releases/tag/v0.2.0-beta.1. +are attached to every binary release as mpl-dependency-sources.tar.gz, on the +release page that carries the installer: +https://github.com/zknpr/SQLite-Explorer-App/releases Each package is also available from the versioned crates.io URL in the inventory. These covered source files remain under MPL 2.0. third-party/inventory.json records package identities, source URLs and notice -hashes. This beta's AppImage is not distributed: the additional bundled Linux -system libraries still require their own distribution inventory. +hashes. No AppImage is distributed: the additional bundled Linux system +libraries still require their own distribution inventory. ============================================================================== diff --git a/docs/code-signing-policy.md b/docs/code-signing-policy.md index 2c74fbb..2ffede4 100644 --- a/docs/code-signing-policy.md +++ b/docs/code-signing-policy.md @@ -11,7 +11,7 @@ published by a maintainer. | Package | Signature | | --- | --- | -| macOS `.dmg` | Ad-hoc signature with the hardened runtime. Not signed with an Apple Developer ID and not notarized. | +| macOS `.dmg` | The disk image is unsigned. The app inside has an ad-hoc signature with the hardened runtime. Neither is signed with an Apple Developer ID, and nothing is notarized. | | Windows installer | Unsigned. Signing through the SignPath Foundation is planned. | | Linux `.deb` | Unsigned, as is usual for packages installed directly rather than from an apt repository. | @@ -20,10 +20,13 @@ Unsigned and ad-hoc packages are still verifiable. Each release lists ties it to the workflow run and commit that produced it: ```sh -sha256sum --check --ignore-missing SHA256SUMS +shasum -a 256 --check --ignore-missing SHA256SUMS # macOS +sha256sum --check --ignore-missing SHA256SUMS # Linux gh attestation verify --repo zknpr/SQLite-Explorer-App ``` +On Windows, use the PowerShell `Get-FileHash` command from the release notes. + ### Windows: what signing will and will not cover Once signing is approved, the application executable and the installer will be diff --git a/docs/release-readiness.md b/docs/release-readiness.md index 0c42544..3a4d747 100644 --- a/docs/release-readiness.md +++ b/docs/release-readiness.md @@ -1,6 +1,6 @@ # Release readiness -Status checked October 8, 2026, for desktop 0.2.0 and the extension 1.8.1 +Status checked October 8, 2026, for desktop 0.3.0 and the extension 1.8.1 integration. The source is public under MIT. Release packages are built by the [release workflow](../.github/workflows/release.yml); the gates below separate source publication from binary distribution. @@ -38,7 +38,7 @@ downloaded package or close the macOS interaction gates. | Security policy | [SECURITY.md](../SECURITY.md), private vulnerability reporting | | Continuous integration | Apple Silicon macOS: manifest verification, script tests, shell unit tests, clippy. No Linux or Windows lane | | Navigation pin and CSP `base-uri`/`form-action`/`frame-ancestors` | Live-verified in an Apple Silicon macOS QA build against a pre-change control build. Linux and Windows runtime behaviour unverified | -| Release pipeline | Builds macOS, Linux and Windows packages on their own OS from the viewer pin, with no cache, and drafts a release for a `v*` tag. Each asset gets SHA256SUMS and a build-provenance attestation. Publishing is manual | +| Release pipeline | Builds macOS, Linux and Windows packages on their own OS from the viewer pin, with no cache, and drafts a release for a `v*` tag; a prerelease version drafts a prerelease. Each asset gets SHA256SUMS and a build-provenance attestation. Publishing is manual | | Third-party notices | Bundled inside every package; the release also attaches them with the MPL Cargo sources, which are checksum-verified against Cargo.lock | | AppImage distribution | Not distributed: its extra bundled Linux system libraries have no notice/source inventory. Linux ships as `.deb` | | Linux glibc floor | The native engine needs glibc 2.38 and the app built on Ubuntu 24.04 needs 2.39; the `.deb` declares `libc6 (>= 2.39)` and the workflow fails if any packaged binary needs more | @@ -46,7 +46,7 @@ downloaded package or close the macOS interaction gates. | Linux/Windows installed-package checks | Passed for the packages and environments in [Linux/Windows QA](linux-windows-release-qa.md), including real shutdown/restart. Those packages predate the navigation pin and CSP change; rerun the affected checks on new packages | | Other Linux desktops, Wayland and physical keyboards | Unverified; limit platform claims to observed coverage | | Windows distribution | Unsigned. SignPath Foundation signing requires a prior public release, so the first release ships unsigned, then apply. Signing will cover the app and installer, not the upstream `tjs.exe`. Smart App Control blocks the unsigned installer; whether it blocks `tjs.exe` under a signed app (the app would fall back to WASM) is untested. SmartScreen on a clean machine unverified | -| macOS distribution | Ad-hoc signature with the hardened runtime, built by the workflow; no Developer ID signing or notarization by choice. Gatekeeper's Open Anyway flow on a downloaded package unverified | +| macOS distribution | The app has an ad-hoc signature with the hardened runtime, built by the workflow; the DMG is unsigned. No Developer ID signing or notarization, by choice. Gatekeeper's Open Anyway flow on a downloaded package unverified | The source MIT license does not replace third-party licenses. The [notice inventory](../third-party/inventory.json) records identities, source diff --git a/package-lock.json b/package-lock.json index 0771a1e..81ffd7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sqlite-explorer-desktop", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sqlite-explorer-desktop", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "devDependencies": { "@tauri-apps/cli": "^2.11.4", diff --git a/package.json b/package.json index 2bb812d..a59a34c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sqlite-explorer-desktop", - "version": "0.2.0", + "version": "0.3.0", "description": "SQLite Explorer desktop shell (Tauri v2) around the extension's desktop viewer artifacts", "type": "module", "scripts": { diff --git a/scripts/release/assemble.mjs b/scripts/release/assemble.mjs index bd19f54..daed43a 100644 --- a/scripts/release/assemble.mjs +++ b/scripts/release/assemble.mjs @@ -53,12 +53,14 @@ export function buildManifest({ version, tag, commit, runUrl, viewerRef, assets product: 'SQLite Explorer', version, tag, + // A semver prerelease (0.3.0-beta.1) must never be published as Latest. + prerelease: version.includes('-'), repository: REPOSITORY, commit, buildRun: runUrl, viewerSource: { repository: VIEWER_REPOSITORY, ref: viewerRef }, signing: { - macos: 'ad-hoc signature; not Developer ID signed or notarized', + macos: 'the app has an ad-hoc signature with the hardened runtime; the disk image is unsigned; not Developer ID signed or notarized', windows: 'unsigned', linux: 'unsigned package; verify SHA256SUMS and the build attestation' }, @@ -72,6 +74,17 @@ export function checksumsText(entries) { .map(({ name, sha256 }) => `${sha256} ${name}`).join('\n') + '\n'; } +/** + * The PowerShell check the notes publish for Windows, which has no sha256sum. + * Exported so the Windows build job runs this exact string against its own + * installer: the documented command is the tested one. Get-FileHash defaults to + * SHA256 and -eq compares strings case-insensitively, so its upper-case hex + * matches the lower-case SHA256SUMS line. + */ +export function windowsChecksumCommand(asset) { + return `(Get-FileHash .\\${asset}).Hash -eq ((Select-String -Path SHA256SUMS -SimpleMatch '${asset}').Line -split ' ')[0]`; +} + export function releaseNotes({ version, commit, runUrl, viewerRef }) { const rows = Object.values(PLATFORMS).map((p) => `| ${p.label} | \`${p.asset(version)}\` |`); return `SQLite Explorer ${version}: a desktop SQLite browser and editor built on the SQLite Explorer viewer. @@ -92,10 +105,13 @@ See the [installation notes](https://github.com/${REPOSITORY}#install) for unins ## Verify the download -\`\`\`sh -sha256sum --check --ignore-missing SHA256SUMS -gh attestation verify ${PLATFORMS.linux.asset(version)} --repo ${REPOSITORY} -\`\`\` +Download \`SHA256SUMS\` next to the package, then check it: + +- **macOS:** \`shasum -a 256 --check --ignore-missing SHA256SUMS\` +- **Linux:** \`sha256sum --check --ignore-missing SHA256SUMS\` +- **Windows (PowerShell):** \`${windowsChecksumCommand(PLATFORMS.windows.asset(version))}\` prints \`True\` when the file matches. + +With the GitHub CLI on any platform, \`gh attestation verify --repo ${REPOSITORY}\` confirms the file was built by this repository's release workflow. ## Licenses @@ -111,6 +127,12 @@ function arg(argv, flag) { function main(argv) { const [command, ...rest] = argv; + if (command === 'windows-checksum-command') { + const version = resolveVersion(readVersions()); + if (version instanceof Error) throw version; + console.log(windowsChecksumCommand(PLATFORMS.windows.asset(version))); + return; + } if (command === 'asset-name') { const platform = PLATFORMS[rest[0]]; if (!platform) throw new Error(`asset-name needs one of ${Object.keys(PLATFORMS).join(', ')}`); diff --git a/scripts/release/notices.mjs b/scripts/release/notices.mjs new file mode 100644 index 0000000..9ea60dd --- /dev/null +++ b/scripts/release/notices.mjs @@ -0,0 +1,75 @@ +#!/usr/bin/env node +/** + * Fail when THIRD_PARTY_NOTICES.txt and third-party/inventory.json no longer + * describe what a release build ships. Both were generated once, and nothing + * regenerates them, so a dependency bump would otherwise ship stale notices + * silently. + * + * node scripts/release/notices.mjs + * + * Checks: + * - the inventory's Cargo components are exactly the packages a release build + * resolves (`cargo metadata` with default features, all targets). The test-only + * `qa-webdriver` feature is not in a release, so it is not resolved; + * - the inventory was built for the viewer pin that viewer-dist/ ships (the + * viewer's JS graph and the native runtime both come from that pin); + * - every inventoried component appears in THIRD_PARTY_NOTICES.txt. + * + * On failure, regenerate the inventory and notices for the new graph (see + * third-party/README.md) rather than editing this check. + */ +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..'); + +/** `name version` of every non-workspace package in a release resolve. */ +export function releaseCargoPackages(metadata) { + const byId = new Map(metadata.packages.map((pkg) => [pkg.id, pkg])); + return new Set(metadata.resolve.nodes + .map((node) => byId.get(node.id)) + .filter((pkg) => pkg && pkg.source) + .map((pkg) => `${pkg.name} ${pkg.version}`)); +} + +export function checkNotices({ releasePackages, inventory, noticesText, viewerRef }) { + const problems = []; + const cargo = new Set(inventory.components.filter((c) => c.ecosystem === 'Cargo').map((c) => c.component)); + for (const pkg of [...releasePackages].sort()) { + if (!cargo.has(pkg)) problems.push(`release ships ${pkg}, which the inventory and notices omit`); + } + for (const pkg of [...cargo].sort()) { + if (!releasePackages.has(pkg)) problems.push(`inventory lists ${pkg}, which a release no longer ships`); + } + if (inventory.viewerSource !== viewerRef) { + problems.push(`inventory was built for viewer ${inventory.viewerSource}, but viewer-dist is pinned to ${viewerRef}`); + } + for (const { component } of inventory.components) { + if (!noticesText.includes(component)) problems.push(`THIRD_PARTY_NOTICES.txt has no section for ${component}`); + } + return problems; +} + +function main() { + const metadata = JSON.parse(execFileSync('cargo', ['metadata', '--locked', '--format-version', '1', + '--manifest-path', path.join(repoRoot, 'src-tauri', 'Cargo.toml')], { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 })); + const read = (file) => fs.readFileSync(path.join(repoRoot, file), 'utf8'); + const problems = checkNotices({ + releasePackages: releaseCargoPackages(metadata), + inventory: JSON.parse(read('third-party/inventory.json')), + noticesText: read('THIRD_PARTY_NOTICES.txt'), + viewerRef: JSON.parse(read('viewer-dist/manifest.json')).ref + }); + if (problems.length > 0) { + for (const problem of problems) console.error(`notices: ${problem}`); + console.error(`notices: ${problems.length} problem(s); regenerate third-party/inventory.json and THIRD_PARTY_NOTICES.txt`); + process.exit(1); + } + console.log('notices: inventory and notices match the release dependency graph and viewer pin'); +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main(); +} diff --git a/scripts/release/release.test.mjs b/scripts/release/release.test.mjs index 5800a7a..0d9f8c1 100644 --- a/scripts/release/release.test.mjs +++ b/scripts/release/release.test.mjs @@ -152,3 +152,60 @@ test('assemble writes a manifest and checksums that verify, and refuses a pollut fs.rmSync(`${dist}-notes.md`, { force: true }); } }); + +test('the bundled notices name no specific release and point at every release for MPL sources', () => { + // The same file ships inside every package of every version, so its header + // must not pin one release: a stale tag URL sends MPL source requests to a + // release that does not exist. + const notices = fs.readFileSync(new URL('../../THIRD_PARTY_NOTICES.txt', import.meta.url), 'utf8'); + const header = notices.slice(0, notices.indexOf('\n=====')); + assert.doesNotMatch(header, /\/releases\/tag\//, 'no pinned release URL'); + assert.doesNotMatch(header, /\bbeta\b/i, 'no beta wording'); + assert.doesNotMatch(header.split('\n')[0], /\d+\.\d+\.\d+/, 'no version in the title'); + assert.match(header, /mpl-dependency-sources\.tar\.gz/); + assert.match(header, /https:\/\/github\.com\/zknpr\/SQLite-Explorer-App\/releases(?!\/tag)/); +}); + +test('a prerelease version drafts a prerelease, and the manifest states the DMG is unsigned', async () => { + const { buildManifest } = await import('./assemble.mjs'); + const base = { tag: 'v', commit: 'c'.repeat(40), runUrl: 'u', viewerRef: 'r', assets: [] }; + assert.equal(buildManifest({ ...base, version: '0.3.0-beta.1' }).prerelease, true); + assert.equal(buildManifest({ ...base, version: '0.3.0' }).prerelease, false); + assert.match(buildManifest({ ...base, version: '0.3.0' }).signing.macos, /disk image is unsigned/); +}); + +test('the release notes give a checksum command that exists on each platform', async () => { + const { releaseNotes } = await import('./assemble.mjs'); + const notes = releaseNotes({ version: '0.3.0', commit: 'c'.repeat(40), runUrl: 'u', viewerRef: 'r' }); + assert.match(notes, /shasum -a 256 --check --ignore-missing SHA256SUMS/, 'macOS'); + assert.match(notes, /Get-FileHash/, 'Windows'); + assert.match(notes, /sha256sum --check --ignore-missing SHA256SUMS/, 'Linux'); +}); + +test('notice drift is caught in every direction', async () => { + const { checkNotices, releaseCargoPackages } = await import('./notices.mjs'); + const metadata = { + packages: [ + { id: 'app', name: 'sqlite-explorer-desktop', version: '0.3.0', source: null }, + { id: 'a', name: 'serde', version: '1.0.0', source: 'registry+x' }, + { id: 'q', name: 'axum', version: '0.8.9', source: 'registry+x' } + ], + // `axum` is in the lock (a qa-webdriver dependency) but not resolved for a release. + resolve: { nodes: [{ id: 'app' }, { id: 'a' }] } + }; + const releasePackages = releaseCargoPackages(metadata); + assert.deepEqual([...releasePackages], ['serde 1.0.0'], 'the workspace crate and unresolved packages are excluded'); + const inventory = { viewerSource: 'pin', components: [{ component: 'serde 1.0.0', ecosystem: 'Cargo' }, { component: 'sql.js 1.14.1' }] }; + const noticesText = 'serde 1.0.0\n...\nsql.js 1.14.1\n'; + assert.deepEqual(checkNotices({ releasePackages, inventory, noticesText, viewerRef: 'pin' }), []); + + const bumped = new Set(['serde 1.0.1']); + assert.deepEqual(checkNotices({ releasePackages: bumped, inventory, noticesText, viewerRef: 'pin' }), [ + 'release ships serde 1.0.1, which the inventory and notices omit', + 'inventory lists serde 1.0.0, which a release no longer ships' + ]); + assert.deepEqual(checkNotices({ releasePackages, inventory, noticesText, viewerRef: 'newpin' }), + ['inventory was built for viewer pin, but viewer-dist is pinned to newpin']); + assert.deepEqual(checkNotices({ releasePackages, inventory, noticesText: 'serde 1.0.0\n', viewerRef: 'pin' }), + ['THIRD_PARTY_NOTICES.txt has no section for sql.js 1.14.1']); +}); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2348e8d..e4b7653 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3154,7 +3154,7 @@ dependencies = [ [[package]] name = "sqlite-explorer-desktop" -version = "0.2.0" +version = "0.3.0" dependencies = [ "libc", "objc2", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a806a38..4c57664 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sqlite-explorer-desktop" -version = "0.2.0" +version = "0.3.0" description = "SQLite Explorer desktop shell" edition = "2021" license = "MIT" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index beb28a6..7550237 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "SQLite Explorer", - "version": "0.2.0", + "version": "0.3.0", "identifier": "xyz.zknpr.sqlite-explorer", "build": { "frontendDist": "../viewer-dist" diff --git a/third-party/README.md b/third-party/README.md index 41a5e81..bfd9c3b 100644 --- a/third-party/README.md +++ b/third-party/README.md @@ -30,3 +30,9 @@ inventoried. Regenerate and review the inventory when the source pins, dependency locks or packaging change. Preserve upstream copyright and attribution notices. +`scripts/release/notices.mjs` runs in CI and in every release build. It fails +when the inventory's Cargo components differ from the packages a release +resolves, when the inventory was built for another viewer pin, or when an +inventoried component has no section in the notices. No generator is checked in +yet: on a failure, add or remove the affected components and their notice texts +by hand, then rerun the check.