From 05ff37df226e8e6801005524727328822dd02b32 Mon Sep 17 00:00:00 2001 From: NimBold Date: Sun, 18 Oct 2026 12:09:43 +0400 Subject: [PATCH] chore(deps): refresh compatible packages and FFmpeg sources - update i18next, React i18n, Lucide, and compatible npm and Cargo locks - refresh Windows and Linux FFmpeg 9.0.2 archives with verified hashes - report compatible npm wanted versions and handle nested, duplicate, and optional packages - update Node and Rust CI pins and document Windows 10 22H2 qualification --- .github/workflows/ci.yml | 11 +- .github/workflows/release.yml | 5 +- README.md | 4 +- RELEASE.md | 11 +- engine-sources.lock.json | 8 +- package-lock.json | 186 ++++++++++--------- package.json | 7 +- scripts/check-updates.js | 194 ++++++++++++++++++-- scripts/check-updates.node-test.js | 203 ++++++++++++++++++++- scripts/engine-cache-workflow.node-test.js | 7 +- scripts/release-workflow.node-test.js | 24 +++ src-tauri/Cargo.lock | 32 ++-- src-tauri/Cargo.toml | 2 +- 13 files changed, 555 insertions(+), 139 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d77da0..c0a0266 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,9 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v7.0.1 - - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable action branch (2026-10-09) + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable action branch (2026-10-16) + with: + toolchain: stable - name: Install locked cargo-audit run: cargo install cargo-audit --version 0.22.2 --locked - name: Reject vulnerable resolved dependencies @@ -27,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22.23.3, 24.21.0] + node: [22.23.3, 24.22.0, 26.12.0] steps: - uses: actions/checkout@v7.0.1 with: @@ -62,10 +64,11 @@ jobs: submodules: recursive - uses: actions/setup-node@v7.1.0 with: - node-version: 24.21.0 + node-version: 24.22.0 cache: npm - - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable action branch (2026-10-09) + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable action branch (2026-10-16) with: + toolchain: stable targets: ${{ matrix.target }} - name: Cache Rust dependencies and build targets uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bbb44b5..02ef195 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@v7.1.0 with: - node-version: 24.21.0 + node-version: 24.22.0 cache: npm - name: Verify tagged release version if: github.event_name == 'push' || inputs.publish_release @@ -58,8 +58,9 @@ jobs: - name: Verify non-publishing Companion package metadata if: github.event_name == 'workflow_dispatch' && !inputs.publish_release run: node scripts/verify-companion-release.js --allow-untagged - - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable action branch (2026-10-09) + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable action branch (2026-10-16) with: + toolchain: stable targets: ${{ matrix.target }} - name: Install Linux dependencies if: runner.os == 'Linux' diff --git a/README.md b/README.md index 0bf0f0b..88fb359 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,8 @@ Download the [latest Firelink release](https://github.com/nimbold/Firelink/relea | Platform | Package | Notes | | --- | --- | --- | | macOS Apple silicon | `.dmg` | Ad-hoc signed and not notarized. macOS may require approval in **System Settings -> Privacy & Security**. | -| Windows x64 | NSIS `.exe` | Unsigned. Windows SmartScreen may display a warning. | -| Windows x64 portable | `.zip` | Extract to a writable folder and launch `firelink.exe`. | +| Windows x64 | NSIS `.exe` | Windows 10 22H2 or newer. Unsigned; SmartScreen may display a warning. | +| Windows x64 portable | `.zip` | Windows 10 22H2 or newer. Extract to a writable folder and launch `firelink.exe`. | | Linux x64 | `.deb`, `.rpm`, or `.AppImage` | The current AppImage requires host glibc 2.35 or newer. | All packages include aria2, yt-dlp, FFmpeg, Deno, and SQLite support. No separate engine installation is required. diff --git a/RELEASE.md b/RELEASE.md index 31bb533..54576f3 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -31,7 +31,7 @@ provisioning now builds the checksum-pinned upstream source archive with `scripts/aria2/firelink.patch`; this patch also retains Firelink's native DNS, network target policy, and Torrent routing changes. CI installs the compiler and static-library prerequisites. Windows uses the MSYS2 UCRT64 environment, -supported by Firelink's Windows 10+ target and returned by the setup action +supported by Firelink's Windows 10 22H2+ target and returned by the setup action (`FIRELINK_MSYS2_ROOT`, default `C:/msys64` for local builds). The patch checksum is recorded in both source and payload provenance. Never replace these builds with stock Aria2 archives: package verification requires @@ -141,4 +141,13 @@ GitHub Actions builds all targets on native runners, verifies engines inside final package contents, performs packaged launch smoke where supported, and publishes the GitHub Release after the build matrix passes. +The Windows hosted runner smoke does not qualify the declared Windows 10 +22H2 minimum. Before claiming that minimum as verified for a release, run the +NSIS-installed app and extracted portable package on a clean Windows 10 22H2 +client (OS build 19045.x). Run `scripts/smoke-packaged-app.js` against the +installed executable with `--assert-no-visible-child-windows`; run it against +the portable executable with both `--assert-no-visible-child-windows` and +`--assert-portable-data`. Record the tested OS build and artifact version with +release QA results. + No target may silently skip missing engines, failed extraction, checksum mismatch, or missing package output. diff --git a/engine-sources.lock.json b/engine-sources.lock.json index edd5eb2..15d6dc8 100644 --- a/engine-sources.lock.json +++ b/engine-sources.lock.json @@ -14,8 +14,8 @@ }, "ffmpeg": { "version": "9.0.2-28-g669f5ab083", - "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-10-15-13-03/ffmpeg-n9.0.2-28-g669f5ab083-win64-gpl-9.0.zip", - "sha256": "1bb62cfd4bdc12a63a6a1151458ea5ae7001fd055202afc5463517028882a631" + "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-10-17-20-51/ffmpeg-n9.0.2-28-g669f5ab083-win64-gpl-9.0.zip", + "sha256": "e84429075cf1b1fa9a85c5d4a5f97899145b4f6a712da02a864393203a3d5cd3" }, "aria2c": { "version": "1.37.0-firelink-native-dns-v1", @@ -46,8 +46,8 @@ }, "ffmpeg": { "version": "9.0.2-28-g669f5ab083", - "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-10-15-13-03/ffmpeg-n9.0.2-28-g669f5ab083-linux64-gpl-9.0.tar.xz", - "sha256": "715ce3c292c749c00b196fc0ff703fe3be12351aa71a626c00d19617b3b280e4" + "url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-10-17-20-51/ffmpeg-n9.0.2-28-g669f5ab083-linux64-gpl-9.0.tar.xz", + "sha256": "cc171d4d373f9ca633741d38fae6a17cd914075818febb1465f6f64e6d57f7ca" }, "aria2c": { "version": "1.37.0-firelink-native-dns-v1", diff --git a/package-lock.json b/package-lock.json index 8229ec6..b14a658 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,11 +22,11 @@ "@tauri-apps/plugin-log": "^2.10.1", "@tauri-apps/plugin-notification": "^2.7.0", "@tauri-apps/plugin-opener": "^2.7.2", - "i18next": "^26.4.7", - "lucide-react": "^1.58.0", + "i18next": "^26.5.2", + "lucide-react": "^1.59.0", "react": "^19.3.1", "react-dom": "^19.3.1", - "react-i18next": "^17.0.19", + "react-i18next": "^17.1.0", "zustand": "^5.0.15" }, "devDependencies": { @@ -34,6 +34,7 @@ "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^6.1.2", + "semver": "^7.8.5", "tailwindcss": "^4.3.3", "typescript": "^7.0.2", "vite": "^8.3.4", @@ -44,9 +45,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", - "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.10.tgz", + "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -152,18 +153,18 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.152.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", - "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz", + "integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", - "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz", + "integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==", "cpu": [ "arm" ], @@ -177,9 +178,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", - "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz", + "integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==", "cpu": [ "arm64" ], @@ -193,9 +194,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", - "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz", + "integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==", "cpu": [ "arm64" ], @@ -209,9 +210,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", - "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz", + "integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==", "cpu": [ "x64" ], @@ -225,9 +226,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", - "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz", + "integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==", "cpu": [ "x64" ], @@ -241,9 +242,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", - "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz", + "integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==", "cpu": [ "arm" ], @@ -257,9 +258,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", - "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz", + "integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==", "cpu": [ "arm64" ], @@ -276,9 +277,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", - "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz", + "integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==", "cpu": [ "arm64" ], @@ -295,9 +296,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", - "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz", + "integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==", "cpu": [ "ppc64" ], @@ -314,9 +315,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", - "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz", + "integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==", "cpu": [ "s390x" ], @@ -333,9 +334,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", - "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz", + "integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==", "cpu": [ "x64" ], @@ -352,9 +353,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", - "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz", + "integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==", "cpu": [ "x64" ], @@ -371,9 +372,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", - "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz", + "integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==", "cpu": [ "arm64" ], @@ -387,9 +388,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", - "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz", + "integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==", "cpu": [ "arm64" ], @@ -403,9 +404,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", - "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz", + "integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==", "cpu": [ "x64" ], @@ -1606,9 +1607,9 @@ } }, "node_modules/i18next": { - "version": "26.4.7", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.4.7.tgz", - "integrity": "sha512-p/3hqy6EcuqRs6wNwBYwGGC1fzBJSMxV9zMuN814ab/7QjdZbXfjXwuPZdQbngX6KDOTxM01uUiZyqmMB8ghZg==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.5.2.tgz", + "integrity": "sha512-6fIXt//2MZNok3tULyjnypxe3vbsy3pV1bVg8p2x3yKrzZvp7z3T+wXx/88LQsATRdhyJeAUAvUxEtxsHdJ29w==", "funding": [ { "type": "individual", @@ -1904,9 +1905,9 @@ } }, "node_modules/lucide-react": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.58.0.tgz", - "integrity": "sha512-hXaFNm6mk6QVtBnDGBn1YAs8j2tGUk0A4s/CpclIcczfxeXiW1rdOLVo6zCsoIih9XXsGvDODQerl749a2n7bA==", + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.59.0.tgz", + "integrity": "sha512-5qgMyyCPU6OvnKpLS2t693+ZtiqA8oaSweWoUows0BLFKFf6V75m4+CptpmohZHpD5IuOVIBXHBBiv2vpLJmtw==", "license": "ISC", "peerDependencies": { "@types/react": "*", @@ -1978,9 +1979,9 @@ } }, "node_modules/postcss": { - "version": "8.5.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", - "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "version": "8.5.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz", + "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==", "funding": [ { "type": "opencollective", @@ -1997,7 +1998,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.19", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" }, @@ -2027,9 +2028,9 @@ } }, "node_modules/react-i18next": { - "version": "17.0.19", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.19.tgz", - "integrity": "sha512-BRHqiqOfW4J/LBVtbBFHoBoZm/Ez2/tkHPitYtn0CSpkYhEaEAsZMPsg7hZlomu30UJYQ6ZKiP3pwa3QnxGvQw==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.1.0.tgz", + "integrity": "sha512-4VouOnTDyvhvqXx6WZ+s3/cgSiaLutILdYkO3P1Ms+4qcKR7At+JP4Vi7IeHkVLyVV+DmN1l23Pn73UcwSGGPA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.7", @@ -2054,12 +2055,12 @@ } }, "node_modules/rolldown": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", - "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz", + "integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.152.0", + "@oxc-project/types": "=0.154.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -2069,21 +2070,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.12", - "@rolldown/binding-android-arm64": "1.2.12", - "@rolldown/binding-darwin-arm64": "1.2.12", - "@rolldown/binding-darwin-x64": "1.2.12", - "@rolldown/binding-freebsd-x64": "1.2.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", - "@rolldown/binding-linux-arm64-gnu": "1.2.12", - "@rolldown/binding-linux-arm64-musl": "1.2.12", - "@rolldown/binding-linux-ppc64-gnu": "1.2.12", - "@rolldown/binding-linux-s390x-gnu": "1.2.12", - "@rolldown/binding-linux-x64-gnu": "1.2.12", - "@rolldown/binding-linux-x64-musl": "1.2.12", - "@rolldown/binding-openharmony-arm64": "1.2.12", - "@rolldown/binding-win32-arm64-msvc": "1.2.12", - "@rolldown/binding-win32-x64-msvc": "1.2.12" + "@rolldown/binding-android-arm-eabi": "1.2.14", + "@rolldown/binding-android-arm64": "1.2.14", + "@rolldown/binding-darwin-arm64": "1.2.14", + "@rolldown/binding-darwin-x64": "1.2.14", + "@rolldown/binding-freebsd-x64": "1.2.14", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", + "@rolldown/binding-linux-arm64-gnu": "1.2.14", + "@rolldown/binding-linux-arm64-musl": "1.2.14", + "@rolldown/binding-linux-ppc64-gnu": "1.2.14", + "@rolldown/binding-linux-s390x-gnu": "1.2.14", + "@rolldown/binding-linux-x64-gnu": "1.2.14", + "@rolldown/binding-linux-x64-musl": "1.2.14", + "@rolldown/binding-openharmony-arm64": "1.2.14", + "@rolldown/binding-win32-arm64-msvc": "1.2.14", + "@rolldown/binding-win32-x64-msvc": "1.2.14" } }, "node_modules/scheduler": { @@ -2092,6 +2093,19 @@ "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/source-map-js": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", diff --git a/package.json b/package.json index 2e90329..a97aec9 100644 --- a/package.json +++ b/package.json @@ -58,11 +58,11 @@ "@tauri-apps/plugin-log": "^2.10.1", "@tauri-apps/plugin-notification": "^2.7.0", "@tauri-apps/plugin-opener": "^2.7.2", - "i18next": "^26.4.7", - "lucide-react": "^1.58.0", + "i18next": "^26.5.2", + "lucide-react": "^1.59.0", "react": "^19.3.1", "react-dom": "^19.3.1", - "react-i18next": "^17.0.19", + "react-i18next": "^17.1.0", "zustand": "^5.0.15" }, "devDependencies": { @@ -70,6 +70,7 @@ "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^6.1.2", + "semver": "^7.8.5", "tailwindcss": "^4.3.3", "typescript": "^7.0.2", "vite": "^8.3.4", diff --git a/scripts/check-updates.js b/scripts/check-updates.js index 165c1c2..e467373 100644 --- a/scripts/check-updates.js +++ b/scripts/check-updates.js @@ -1,9 +1,10 @@ #!/usr/bin/env node -import { execFileSync } from 'node:child_process'; +import { execFileSync, execSync } from 'node:child_process'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import semver from 'semver'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(__dirname, '..'); @@ -72,16 +73,29 @@ function npmExecutable(platform = process.platform) { return platform === 'win32' ? 'npm.cmd' : 'npm'; } -function npmOutdated(cwd) { +function npmOutdated(cwd, { + platform = process.platform, + runFile = execFileSync, + runShell = execSync, +} = {}) { if (!fs.existsSync(path.join(cwd, 'package.json'))) { throw new Error(`npm workspace is missing package.json: ${cwd}`); } + const args = ['outdated', '--all', '--json']; + const options = { + cwd, + encoding: 'utf8', + maxBuffer: 16 * 1024 * 1024, + stdio: ['ignore', 'pipe', 'pipe'], + }; try { - execFileSync(npmExecutable(), ['outdated', '--json'], { - cwd, - encoding: 'utf8', - stdio: ['ignore', 'pipe', 'pipe'], - }); + if (platform === 'win32') { + // npm.cmd is a command shim, not a native executable. Keep the command + // fixed here (no user-controlled arguments) and let cmd.exe run it. + runShell(`${npmExecutable(platform)} ${args.join(' ')}`, options); + } else { + runFile(npmExecutable(platform), args, options); + } return {}; } catch (error) { if (error.status !== 1) { @@ -323,6 +337,140 @@ function printCargoReport(updates) { return updates.length; } +function npmUpdateRows(outdated, { cwd = process.cwd(), lockfilePackages = {} } = {}) { + if (!outdated || typeof outdated !== 'object' || Array.isArray(outdated)) { + throw new Error('npm outdated response was not a package map'); + } + + const lockfileVersionAtLocation = location => { + if (typeof location !== 'string' || !location.trim()) return undefined; + let lockfilePath = location.trim().replaceAll('\\', '/'); + if (path.isAbsolute(location)) { + const relativePath = path.relative(cwd, location); + if (path.isAbsolute(relativePath) || relativePath === '..' || relativePath.startsWith(`..${path.sep}`)) { + return undefined; + } + lockfilePath = relativePath.split(path.sep).join('/'); + } + lockfilePath = lockfilePath.replace(/^\.\//, ''); + if (lockfilePath === '..' || lockfilePath.startsWith('../')) return undefined; + return normalizeVersion(lockfilePackages[lockfilePath]?.version); + }; + const lockfileVersionsForName = name => { + const suffix = `/node_modules/${name}`; + return Object.entries(lockfilePackages) + .filter(([lockfilePath]) => lockfilePath === `node_modules/${name}` || lockfilePath.endsWith(suffix)) + .map(([, entry]) => normalizeVersion(entry?.version)) + .filter(Boolean); + }; + + const rows = new Map(); + for (const [name, value] of Object.entries(outdated)) { + const records = Array.isArray(value) ? value : [value]; + const normalizedRecords = records.map(info => { + if (!info || typeof info !== 'object' || Array.isArray(info)) { + throw new Error(`npm outdated response for ${name} was not a package record`); + } + return { + info, + installedVersion: normalizeVersion(info.current), + locatedVersion: lockfileVersionAtLocation(info.location), + wanted: normalizeVersion(info.wanted), + latest: normalizeVersion(info.latest), + }; + }); + for (const record of normalizedRecords) { + if (!record.wanted || !record.latest) { + throw new Error(`npm outdated response for ${name} omitted wanted or latest version`); + } + } + + const unresolved = normalizedRecords + .map((record, index) => ({ record, index })) + .filter(({ record }) => !record.installedVersion && !record.locatedVersion); + const lockVersions = unresolved.length ? lockfileVersionsForName(name) : []; + const resolvedLockVersions = new Map(); + if (lockVersions.length === 1) { + for (const { index } of unresolved) resolvedLockVersions.set(index, lockVersions[0]); + } else if (lockVersions.length > 1) { + if (lockVersions.length !== unresolved.length) { + throw new Error(`npm outdated response for ${name} omitted locations for duplicate package-lock entries`); + } + + const remainingLockVersions = [...lockVersions]; + const remainingRecords = []; + for (const { record, index } of unresolved) { + const wanted = semver.valid(record.wanted); + if (!wanted) continue; + const exactIndex = remainingLockVersions.findIndex(version => semver.valid(version) + && semver.eq(version, wanted)); + if (exactIndex >= 0) { + resolvedLockVersions.set(index, remainingLockVersions.splice(exactIndex, 1)[0]); + } else { + remainingRecords.push({ record, index, wanted }); + } + } + + if (remainingRecords.length !== remainingLockVersions.length) { + throw new Error(`npm outdated response for ${name} could not be matched safely to package-lock entries`); + } + remainingRecords.sort((left, right) => semver.compare(left.wanted, right.wanted)); + remainingLockVersions.sort((left, right) => { + const leftVersion = semver.valid(left); + const rightVersion = semver.valid(right); + if (!leftVersion || !rightVersion) { + throw new Error(`npm lockfile contains a non-semver duplicate for ${name}`); + } + return semver.compare(leftVersion, rightVersion); + }); + remainingRecords.forEach(({ index }, offset) => { + resolvedLockVersions.set(index, remainingLockVersions[offset]); + }); + } + + for (const [index, record] of normalizedRecords.entries()) { + const currentVersions = record.installedVersion + ? [record.installedVersion] + : record.locatedVersion + ? [record.locatedVersion] + : resolvedLockVersions.has(index) + ? [resolvedLockVersions.get(index)] + : []; + // npm can include optional or peer packages that are absent from both + // this host's physical tree and the lockfile. They are not resolved + // Firelink dependencies. When lock entries exist, map them to records + // by physical location or the matching npm `wanted` version. + if (!currentVersions.length) continue; + + if (!semver.valid(record.wanted) || !semver.valid(record.latest)) continue; + for (const current of currentVersions) { + if (!semver.valid(current) || semver.gte(current, record.wanted)) continue; + + const key = `${name}\0${current}\0${record.wanted}\0${record.latest}`; + const row = rows.get(key) || { + name, + current, + wanted: record.wanted, + latest: record.latest, + lockfileOnly: !record.installedVersion, + dependents: new Set(), + }; + row.lockfileOnly = row.lockfileOnly && !record.installedVersion; + if (typeof record.info.dependent === 'string' && record.info.dependent.trim()) { + row.dependents.add(record.info.dependent.trim()); + } + rows.set(key, row); + } + } + } + + return [...rows.values()] + .map(row => ({ ...row, dependents: [...row.dependents].sort() })) + .sort((left, right) => left.name.localeCompare(right.name) + || left.current.localeCompare(right.current) + || left.wanted.localeCompare(right.wanted)); +} + function hasUpdateCheckFailures({ outdatedCount, engineIssueCount, @@ -335,17 +483,28 @@ function hasUpdateCheckFailures({ || providerBehind.length > 0; } -function printNpmReport(label, outdated) { - const entries = Object.entries(outdated); - if (!entries.length) { +function printNpmReport(label, outdated, cwd) { + let lockfilePackages = {}; + if (Object.keys(outdated).length > 0) { + const lockfilePath = path.join(cwd, 'package-lock.json'); + const lockfile = JSON.parse(fs.readFileSync(lockfilePath, 'utf8')); + if (!lockfile.packages || typeof lockfile.packages !== 'object' || Array.isArray(lockfile.packages)) { + throw new Error(`npm lockfile did not contain a package map: ${lockfilePath}`); + } + lockfilePackages = lockfile.packages; + } + const rows = npmUpdateRows(outdated, { cwd, lockfilePackages }); + if (!rows.length) { console.log(`${label}: current`); return 0; } - console.log(`${label}: ${entries.length} outdated package(s)`); - for (const [name, info] of entries) { - console.log(` ${name}: ${info.current} -> ${info.latest} (wanted ${info.wanted})`); + console.log(`${label}: ${rows.length} compatible package update(s)`); + for (const row of rows) { + const dependents = row.dependents.length ? ` via ${row.dependents.join(', ')}` : ''; + const source = row.lockfileOnly ? ' (from lockfile)' : ''; + console.log(` ${row.name}: ${row.current}${source} -> ${row.wanted} (latest ${row.latest})${dependents}`); } - return entries.length; + return rows.length; } function sourceEngineVersions(sourceLock) { @@ -500,10 +659,11 @@ function checkRows( async function main() { let outdatedCount = 0; - outdatedCount += printNpmReport('root npm', npmOutdated(repoRoot)); + outdatedCount += printNpmReport('root npm', npmOutdated(repoRoot), repoRoot); outdatedCount += printNpmReport( 'Browser extension npm', - npmOutdated(path.join(repoRoot, 'Extensions', 'Browser')) + npmOutdated(path.join(repoRoot, 'Extensions', 'Browser')), + path.join(repoRoot, 'Extensions', 'Browser') ); outdatedCount += printCargoReport(cargoCompatibleUpdates()); @@ -726,5 +886,7 @@ export { latestBtbnFfmpegStableBuild, latestMartinRiedlMacArm64Release, npmExecutable, + npmOutdated, + npmUpdateRows, providerAssetHashes, }; diff --git a/scripts/check-updates.node-test.js b/scripts/check-updates.node-test.js index 7c60431..e21a7b4 100644 --- a/scripts/check-updates.node-test.js +++ b/scripts/check-updates.node-test.js @@ -10,6 +10,8 @@ import { latestMartinRiedlMacArm64Release, hasUpdateCheckFailures, npmExecutable, + npmOutdated, + npmUpdateRows, providerAssetHashes, } from './check-updates.js'; @@ -243,10 +245,209 @@ test('update checks pass only when every blocking count is clear', () => { }), false); }); -test('npm executable selection uses the Windows command shim when needed', () => { +test('npm outdated runs the Windows command shim through a shell', () => { assert.equal(npmExecutable('win32'), 'npm.cmd'); assert.equal(npmExecutable('darwin'), 'npm'); assert.equal(npmExecutable('linux'), 'npm'); + + let shellCommand; + let shellOptions; + let fileRunCalled = false; + assert.deepEqual(npmOutdated(process.cwd(), { + platform: 'win32', + runFile: () => { fileRunCalled = true; }, + runShell: (command, options) => { + shellCommand = command; + shellOptions = options; + return '{}'; + }, + }), {}); + assert.equal(shellCommand, 'npm.cmd outdated --all --json'); + assert.equal(shellOptions.cwd, process.cwd()); + assert.equal(shellOptions.encoding, 'utf8'); + assert.equal(fileRunCalled, false); +}); + +test('npm update rows deduplicate nested records and retain compatible and major versions', () => { + const rows = npmUpdateRows({ + '@babel/runtime': [ + { + current: '7.29.7', + wanted: '7.29.10', + latest: '8.0.7', + dependent: 'react-i18next', + location: '/workspace/node_modules/@babel/runtime', + }, + { + current: '7.29.7', + wanted: '7.29.10', + latest: '8.0.7', + dependent: 'another-package', + location: '/workspace/node_modules/@babel/runtime', + }, + { + current: '7.29.7', + wanted: '7.29.10', + latest: '8.0.7', + dependent: 'react-i18next', + location: '/workspace/node_modules/@babel/runtime', + }, + ], + }); + + assert.deepEqual(rows, [{ + name: '@babel/runtime', + current: '7.29.7', + wanted: '7.29.10', + latest: '8.0.7', + lockfileOnly: false, + dependents: ['another-package', 'react-i18next'], + }]); +}); + +test('npm update rows safely ignore absent optional platform packages', () => { + const rows = npmUpdateRows({ + '@rolldown/binding-linux-x64-gnu': { + wanted: '1.2.14', + latest: '1.2.14', + dependent: 'rolldown', + location: '/workspace/node_modules/@rolldown/binding-linux-x64-gnu', + }, + '@types/node': [ + { + wanted: '26.6.5', + latest: '26.6.5', + dependent: 'vite', + }, + { + wanted: '26.6.5', + latest: '26.6.5', + dependent: 'vitest', + }, + ], + }, { + cwd: '/workspace', + lockfilePackages: { + 'node_modules/@rolldown/binding-linux-x64-gnu': { version: '1.2.14' }, + 'node_modules/@types/node': { version: '26.6.5' }, + }, + }); + + assert.deepEqual(rows, []); +}); + +test('npm update rows find compatible lockfile updates for absent optional packages', () => { + const rows = npmUpdateRows({ + '@rolldown/binding-linux-x64-gnu': { + wanted: '1.2.14', + latest: '1.2.14', + dependent: 'rolldown', + }, + }, { + cwd: '/workspace', + lockfilePackages: { + 'node_modules/@rolldown/binding-linux-x64-gnu': { version: '1.2.12' }, + }, + }); + + assert.deepEqual(rows, [{ + name: '@rolldown/binding-linux-x64-gnu', + current: '1.2.12', + wanted: '1.2.14', + latest: '1.2.14', + lockfileOnly: true, + dependents: ['rolldown'], + }]); +}); + +test('npm update rows match duplicate optional records to lockfile wanted versions', () => { + const rows = npmUpdateRows({ + 'lightningcss-linux-x64-gnu': [ + { wanted: '1.32.0', latest: '1.33.0', dependent: 'lightningcss' }, + { wanted: '1.33.0', latest: '1.33.0', dependent: 'lightningcss' }, + ], + }, { + cwd: '/workspace', + lockfilePackages: { + 'node_modules/lightningcss-linux-x64-gnu': { version: '1.32.0' }, + 'node_modules/vite/node_modules/lightningcss-linux-x64-gnu': { version: '1.33.0' }, + }, + }); + + assert.deepEqual(rows, []); +}); + +test('npm update rows retain stale duplicates when current is absent on this platform', () => { + const rows = npmUpdateRows({ + 'lightningcss-linux-x64-gnu': [ + { wanted: '1.33.0', latest: '1.33.0', dependent: 'lightningcss' }, + { wanted: '1.33.0', latest: '1.33.0', dependent: 'lightningcss' }, + ], + }, { + cwd: '/workspace', + lockfilePackages: { + 'node_modules/lightningcss-linux-x64-gnu': { version: '1.32.0' }, + 'node_modules/vite/node_modules/lightningcss-linux-x64-gnu': { version: '1.33.0' }, + }, + }); + + assert.deepEqual(rows, [{ + name: 'lightningcss-linux-x64-gnu', + current: '1.32.0', + wanted: '1.33.0', + latest: '1.33.0', + lockfileOnly: true, + dependents: ['lightningcss'], + }]); +}); + +test('npm update rows recognize a prerelease to its compatible stable version', () => { + const rows = npmUpdateRows({ + package: { + current: '1.0.0-rc.1', + wanted: '1.0.0', + latest: '2.0.0', + }, + }); + + assert.deepEqual(rows, [{ + name: 'package', + current: '1.0.0-rc.1', + wanted: '1.0.0', + latest: '2.0.0', + lockfileOnly: false, + dependents: [], + }]); +}); + +test('npm update rows ignore absent optional packages that are not in the lockfile', () => { + assert.deepEqual(npmUpdateRows({ + 'optional-package': { + wanted: '1.2.0', + latest: '2.0.0', + }, + }, { cwd: '/workspace' }), []); +}); + +test('npm update rows fail closed when an absent duplicate has ambiguous lockfile versions', () => { + assert.throws(() => npmUpdateRows({ + package: { + wanted: '1.2.0', + latest: '2.0.0', + }, + }, { + lockfilePackages: { + 'node_modules/first/node_modules/package': { version: '1.0.0' }, + 'node_modules/second/node_modules/package': { version: '1.1.0' }, + }, + }), /omitted locations for duplicate package-lock entries/); +}); + +test('npm update rows fail closed for malformed package records', () => { + assert.throws( + () => npmUpdateRows({ broken: [{ current: '1.0.0', latest: '2.0.0' }] }), + /omitted wanted or latest version/, + ); }); test('selects a complete BtbN build for the current stable series', async () => { diff --git a/scripts/engine-cache-workflow.node-test.js b/scripts/engine-cache-workflow.node-test.js index 2fd1de0..a5f9af9 100644 --- a/scripts/engine-cache-workflow.node-test.js +++ b/scripts/engine-cache-workflow.node-test.js @@ -34,10 +34,11 @@ test('frontend CI covers the supported Node lines and native builds use the curr const desktopJob = ciWorkflow.slice(ciWorkflow.indexOf('\n desktop:')); assert.match(ciWorkflow, /uses: actions\/setup-node@v7(?:\.\d+){0,2}/); - assert.match(frontendJob, /node:\s*\[22\.23\.3,\s*24\.21\.0\]/); + assert.match(frontendJob, /node:\s*\[22\.23\.3,\s*24\.22\.0,\s*26\.12\.0\]/); assert.match(frontendJob, /node-version: \$\{\{ matrix\.node \}\}/); - assert.match(desktopJob, /node-version: 24\.21\.0/); - assert.match(releaseWorkflow, /node-version: 24\.21\.0/); + assert.match(desktopJob, /node-version: 24\.22\.0/); + assert.match(releaseWorkflow, /node-version: 24\.22\.0/); + assert.doesNotMatch(`${ciWorkflow}\n${releaseWorkflow}`, /node-version: 24\.21\.0/); assert.doesNotMatch(`${ciWorkflow}\n${releaseWorkflow}`, /node-version: 22\.12(?:\D|$)/); }); diff --git a/scripts/release-workflow.node-test.js b/scripts/release-workflow.node-test.js index 3c49238..5eb5dcf 100644 --- a/scripts/release-workflow.node-test.js +++ b/scripts/release-workflow.node-test.js @@ -8,6 +8,12 @@ const releaseWorkflow = fs.readFileSync( path.join(repositoryRoot, '.github', 'workflows', 'release.yml'), 'utf8', ); +const ciWorkflow = fs.readFileSync( + path.join(repositoryRoot, '.github', 'workflows', 'ci.yml'), + 'utf8', +); +const readme = fs.readFileSync(path.join(repositoryRoot, 'README.md'), 'utf8'); +const releaseGuide = fs.readFileSync(path.join(repositoryRoot, 'RELEASE.md'), 'utf8'); test('release Linux dependency installation is mirror-normalized and bounded', () => { assert.match(releaseWorkflow, /azure\\\.archive\\\.ubuntu\\\.com/); @@ -40,6 +46,24 @@ test('Linux release pins and verifies appimagetool and its runtime inputs', () = assert.match(releaseWorkflow, /APPIMAGETOOL_RUNTIME_FILE: \$\{\{ runner\.temp \}\}\/appimage-runtime/); }); +test('CI covers the declared Node floor and current runtimes while native jobs use Node 24 LTS', () => { + assert.match(ciWorkflow, /node: \[22\.23\.3, 24\.22\.0, 26\.12\.0\]/); + assert.equal((ciWorkflow.match(/node-version: 24\.22\.0/g) || []).length, 1); + assert.equal((releaseWorkflow.match(/node-version: 24\.22\.0/g) || []).length, 1); + assert.doesNotMatch(`${ciWorkflow}\n${releaseWorkflow}`, /node-version: 24\.21\.0/); +}); + +test('Tauri CI and release workflows pin stable explicitly and Windows support floor is consistent', () => { + const workflows = `${ciWorkflow}\n${releaseWorkflow}`; + assert.equal((workflows.match(/dtolnay\/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec/g) || []).length, 3); + assert.equal((workflows.match(/toolchain: stable/g) || []).length, 3); + assert.doesNotMatch(workflows, /dtolnay\/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249/); + assert.match(readme, /Windows 10 22H2 or newer/); + assert.match(releaseGuide, /Windows 10 22H2\+ target/); + assert.match(releaseGuide, /clean Windows 10 22H2\s+client \(OS build 19045\.x\)/); + assert.match(releaseGuide, /--assert-no-visible-child-windows.*--assert-portable-data/s); +}); + test('Windows release job packages portable ZIP with portable.flag and data cleanup', () => { assert.match(releaseWorkflow, /Set-Content -Path \(Join-Path \$portableRoot 'portable\.flag'\) -Value 'portable'/); assert.match(releaseWorkflow, /node scripts\/smoke-packaged-app\.js --executable \$portableExe --assert-no-visible-child-windows --assert-portable-data/); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 19f6e72..001edd4 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2341,9 +2341,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c0d789294fb457b791f7797a04478704788f30b407e5d0b01deaa846572d9f" +checksum = "3ce983bb6fb399067d101fbfc9e74174e07e9f29a2bc679efefeb8c645819c3f" dependencies = [ "block-padding", "hybrid-array", @@ -2624,9 +2624,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.190" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "libdbus-sys" @@ -2996,9 +2996,9 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +checksum = "cabcc654086861f9e1bc5115258de62ef92c79b20f4615a1b0cc57c4e82027d4" dependencies = [ "libc", ] @@ -4056,9 +4056,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" dependencies = [ "windows-sys 0.61.2", ] @@ -5705,9 +5705,9 @@ checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "unicode-normalization" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +checksum = "75cd3fb03d5288b3ba9a1ee103ac7e9cbb59f0b03d64f8ddfcc04dd42758a85d" dependencies = [ "tinyvec", ] @@ -6747,9 +6747,9 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.4" +version = "4.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +checksum = "812a12be4ead7a59e18ea8f047d08add0066b73bc62fc1467066a2472ae088dd" dependencies = [ "serde", "winnow 1.0.4", @@ -6874,9 +6874,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.15.0" +version = "5.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +checksum = "80bf6d6cd6a4b601acb64cfb8284cdc643fcfa75ef26ae3c46ec8aa4e8a1fbb7" dependencies = [ "endi", "enumflags2", @@ -6889,9 +6889,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.15.0" +version = "5.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +checksum = "ef2672456ba1cb54b5eaec136e63135b3c7495fb938e53b28a62e633496d486d" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 575e7cd..99ef26c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -74,7 +74,7 @@ objc2-app-kit = { version = "0.3", default-features = false, features = ["NSAppl objc2-core-services = "0.3" objc2-foundation = { version = "0.3", default-features = false, features = ["NSAppleEventDescriptor", "NSAppleEventManager", "NSAppleScript", "NSDictionary", "NSNotification", "NSObject", "NSString", "objc2-core-services", "std"] } smappservice-rs = "0.1.3" -unicode-normalization = "0.1.25" +unicode-normalization = "0.1.26" [target.'cfg(target_os = "windows")'.dependencies] windows-native-keyring-store = "1.1.0"