diff --git a/.github/workflows/typescript-bindings.yml b/.github/workflows/typescript-bindings.yml index e870d28..de0e8cd 100644 --- a/.github/workflows/typescript-bindings.yml +++ b/.github/workflows/typescript-bindings.yml @@ -12,10 +12,10 @@ jobs: os: [ubuntu-latest, macos-14, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 with: - node-version: "20" + node-version: "22" - uses: dtolnay/rust-toolchain@stable - name: Install deps and run tests run: | @@ -26,13 +26,13 @@ jobs: name: Test TypeScript Bindings (Alpine/musl) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Run tests in node:20-alpine + - uses: actions/checkout@v7 + - name: Run tests in node:22-alpine run: | docker run --rm \ -v "${{ github.workspace }}:/repo" \ -w "/repo/bindings/typescript/docprims" \ - node:20-alpine \ + node:22-alpine \ sh -lc ' set -eu apk add --no-cache bash curl build-base python3 ca-certificates diff --git a/.github/workflows/typescript-napi-prebuilds.yml b/.github/workflows/typescript-napi-prebuilds.yml index f720ef0..1be9d59 100644 --- a/.github/workflows/typescript-napi-prebuilds.yml +++ b/.github/workflows/typescript-napi-prebuilds.yml @@ -8,11 +8,11 @@ jobs: name: Build Linux prebuilds runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '22' - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install Zig @@ -61,11 +61,11 @@ jobs: name: Build Linux arm64 prebuild (native) runs-on: ubuntu-latest-arm64-s steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '22' - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install Node dependencies @@ -89,11 +89,11 @@ jobs: name: Build macOS arm64 prebuild runs-on: macos-14 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '22' - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install Node dependencies @@ -117,11 +117,11 @@ jobs: name: Build Windows x64 prebuild runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '22' - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install Node dependencies @@ -146,11 +146,11 @@ jobs: runs-on: ubuntu-latest needs: [build-linux, build-linux-arm64, build-macos-arm64, build-windows] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '22' - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install Node dependencies diff --git a/.github/workflows/typescript-npm-publish.yml b/.github/workflows/typescript-npm-publish.yml index 8344ca1..98ce517 100644 --- a/.github/workflows/typescript-npm-publish.yml +++ b/.github/workflows/typescript-npm-publish.yml @@ -34,19 +34,54 @@ jobs: steps: # Note: Do NOT use fetch-tags here. When running from a tag ref, # it causes a git fetch conflict. The tag is already checked out. - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '24' registry-url: 'https://registry.npmjs.org' - - name: Ensure npm CLI supports OIDC + - name: Ensure npm trusted publishing runtime + env: + MIN_TRUSTED_PUBLISH_NODE: '22.14.0' + MIN_TRUSTED_PUBLISH_NPM: '11.5.1' run: | set -euo pipefail + version_ge() { + local got="${1#v}" + local min="${2#v}" + local got_major got_minor got_patch min_major min_minor min_patch + IFS=. read -r got_major got_minor got_patch _ <<<"${got}" + IFS=. read -r min_major min_minor min_patch _ <<<"${min}" + got_major="${got_major:-0}" + got_minor="${got_minor:-0}" + got_patch="${got_patch:-0}" + min_major="${min_major:-0}" + min_minor="${min_minor:-0}" + min_patch="${min_patch:-0}" + if [ "${got_major}" -gt "${min_major}" ]; then return 0; fi + if [ "${got_major}" -eq "${min_major}" ] && + [ "${got_minor}" -gt "${min_minor}" ]; then return 0; fi + if [ "${got_major}" -eq "${min_major}" ] && + [ "${got_minor}" -eq "${min_minor}" ] && + [ "${got_patch}" -ge "${min_patch}" ]; then return 0; fi + return 1 + } + + NODE_VERSION="$(node -p 'process.versions.node')" + if ! version_ge "${NODE_VERSION}" "${MIN_TRUSTED_PUBLISH_NODE}"; then + echo "::error::Node ${NODE_VERSION} < ${MIN_TRUSTED_PUBLISH_NODE}" + exit 1 + fi + echo "node: ${NODE_VERSION}" + echo "npm (before): $(npm --version)" - # Trusted publishing requires npm >= 11.5.1 - npm install -g npm@11.5.1 - echo "npm (after): $(npm --version)" + npm install -g "npm@${MIN_TRUSTED_PUBLISH_NPM}" + NPM_VERSION="$(npm --version)" + if ! version_ge "${NPM_VERSION}" "${MIN_TRUSTED_PUBLISH_NPM}"; then + echo "::error::npm ${NPM_VERSION} < ${MIN_TRUSTED_PUBLISH_NPM}" + exit 1 + fi + echo "npm (after): ${NPM_VERSION}" - name: Validate running from a tag ref run: | set -euo pipefail diff --git a/.github/workflows/validate-release.yml b/.github/workflows/validate-release.yml index 15fcb0a..b71e17b 100644 --- a/.github/workflows/validate-release.yml +++ b/.github/workflows/validate-release.yml @@ -40,12 +40,12 @@ jobs: matrix: os: [ubuntu-latest, macos-14, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: ${{ inputs.tag }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: "20" + node-version: "22" - uses: dtolnay/rust-toolchain@stable - name: Build and test TypeScript bindings run: | @@ -56,9 +56,9 @@ jobs: name: Validate TypeScript Bindings (Alpine/musl) runs-on: ubuntu-latest container: - image: node:20-alpine + image: node:22-alpine steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: ${{ inputs.tag }} - name: Install build dependencies diff --git a/Cargo.toml b/Cargo.toml index 3b1d231..72500b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ [workspace.package] version = "0.1.4" edition = "2021" -rust-version = "1.85.0" +rust-version = "1.88.0" license = "MIT OR Apache-2.0" repository = "https://github.com/3leaps/docprims" authors = ["3leaps Architecture Council"] diff --git a/Makefile b/Makefile index cccc9d6..b970b4c 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ # make build - Build all crates .PHONY: all help bootstrap bootstrap-force tools check test fmt lint build clean version install -.PHONY: precommit prepush deps-check audit deny miri msrv fmt-check +.PHONY: precommit prepush deps-check audit deny miri msrv fmt-check npm-publish-prereqs-check .PHONY: build-release build-ffi cbindgen .PHONY: release-clean release-download release-checksums release-sign .PHONY: release-export-keys release-verify-checksums release-verify-signatures @@ -82,7 +82,8 @@ help: ## Show available targets @echo " deny Run cargo-deny license and advisory checks" @echo " audit Run cargo-audit security scan" @echo " miri Run Miri UB detection on unsafe code (nightly)" - @echo " msrv Verify build with MSRV (Rust 1.85)" + @echo " msrv Verify build with MSRV (Rust 1.88.0)" + @echo " npm-publish-prereqs-check Verify npm trusted publishing runtime guard" @echo " check-windows Cross-check Windows targets (no SDK required)" @echo "" @echo "Version management:" @@ -235,6 +236,7 @@ fmt-check: ## Check formatting without modifying lint: ## Run linting (goneat assess or cargo clippy) @echo "Linting..." + @bash scripts/check-npm-trusted-publish-runtime.sh @if command -v goneat >/dev/null 2>&1; then \ goneat assess --categories lint; \ else \ @@ -243,6 +245,9 @@ lint: ## Run linting (goneat assess or cargo clippy) fi @echo "[ok] Linting passed" +npm-publish-prereqs-check: ## Verify npm trusted publishing runtime guard + @bash scripts/check-npm-trusted-publish-runtime.sh + deny: ## Run cargo-deny license and advisory checks @echo "Running cargo-deny..." @if command -v cargo-deny >/dev/null 2>&1; then \ @@ -278,14 +283,14 @@ miri: ## Run Miri to detect undefined behavior in unsafe code (requires nightly) fi @echo "[ok] Miri passed" -msrv: ## Verify build with Minimum Supported Rust Version (1.85) - @echo "Checking MSRV (1.85)..." - @if rustup run 1.85 cargo --version >/dev/null 2>&1; then \ - rustup run 1.85 cargo build --workspace && \ - rustup run 1.85 cargo test --workspace; \ +msrv: ## Verify build with Minimum Supported Rust Version (1.88.0) + @echo "Checking MSRV (1.88.0)..." + @if rustup run 1.88.0 cargo --version >/dev/null 2>&1; then \ + rustup run 1.88.0 cargo build --workspace && \ + rustup run 1.88.0 cargo test --workspace; \ else \ - echo "[!!] Rust 1.85 not installed. Install with:"; \ - echo " rustup install 1.85"; \ + echo "[!!] Rust 1.88.0 not installed. Install with:"; \ + echo " rustup install 1.88.0"; \ exit 1; \ fi @echo "[ok] MSRV check passed" diff --git a/README.md b/README.md index dccdcc5..f928f9b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Content extraction from documents in a permissive licensing toolkit. [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT) -[![Rust](https://img.shields.io/badge/rust-1.85%2B-orange.svg)](https://www.rust-lang.org/) +[![Rust](https://img.shields.io/badge/rust-1.88%2B-orange.svg)](https://www.rust-lang.org/) ## Overview @@ -104,8 +104,10 @@ From a consumer project using `file:` protocol: ### CLI +From the repository root: + ```bash -cargo install docprims-cli +cargo install --path crates/docprims-cli ``` ## Usage @@ -159,7 +161,7 @@ Project documentation conventions (what is canonical vs planning notes): ### Prerequisites -- Rust 1.85+ +- Rust 1.88+ - curl (for bootstrap) ### Setup diff --git a/bindings/typescript/docprims/README.md b/bindings/typescript/docprims/README.md index d7afc86..b2b40d3 100644 --- a/bindings/typescript/docprims/README.md +++ b/bindings/typescript/docprims/README.md @@ -2,6 +2,8 @@ TypeScript/Node.js bindings for docprims using a Node-API (N-API) native addon (napi-rs). +Requires Node.js 22 or newer. + ## Platform support (v0.1.x) Supported: diff --git a/bindings/typescript/docprims/package.json b/bindings/typescript/docprims/package.json index 667a3fa..9cf0264 100644 --- a/bindings/typescript/docprims/package.json +++ b/bindings/typescript/docprims/package.json @@ -17,7 +17,7 @@ "LICENSE" ], "engines": { - "node": ">=18" + "node": ">=22" }, "napi": { "name": "docprims", diff --git a/scripts/check-npm-trusted-publish-runtime.sh b/scripts/check-npm-trusted-publish-runtime.sh new file mode 100755 index 0000000..133090a --- /dev/null +++ b/scripts/check-npm-trusted-publish-runtime.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +workflow=".github/workflows/typescript-npm-publish.yml" +min_node="22.14.0" +min_npm="11.5.1" + +fail() { + echo "error: $*" >&2 + exit 1 +} + +normalize_version() { + local value="${1#v}" + value="$(printf '%s' "$value" | tr -d "\"'")" + value="${value%%[!0-9.]*}" + IFS=. read -r major minor patch _ <<<"$value" + printf '%s.%s.%s\n' "${major:-0}" "${minor:-0}" "${patch:-0}" +} + +version_ge() { + local got + local min + got="$(normalize_version "$1")" + min="$(normalize_version "$2")" + + IFS=. read -r got_major got_minor got_patch <<<"$got" + IFS=. read -r min_major min_minor min_patch <<<"$min" + + if ((got_major > min_major)); then + return 0 + fi + if ((got_major == min_major && got_minor > min_minor)); then + return 0 + fi + if ((got_major == min_major && got_minor == min_minor && got_patch >= min_patch)); then + return 0 + fi + return 1 +} + +[[ -f "$workflow" ]] || fail "missing workflow: $workflow" + +node_version="$( + sed -nE "s/^[[:space:]]*node-version:[[:space:]]*['\"]?([^'\"]+)['\"]?.*/\1/p" "$workflow" | head -n 1 +)" +[[ -n "$node_version" ]] || fail "publish workflow has no setup-node node-version" +version_ge "$node_version" "$min_node" || fail "publish workflow uses Node $node_version, need >= $min_node" + +declared_min_node="$( + sed -nE "s/.*MIN_TRUSTED_PUBLISH_NODE:[[:space:]]*['\"]?([^'\"]+)['\"]?.*/\1/p" "$workflow" | head -n 1 +)" +[[ -n "$declared_min_node" ]] || fail "workflow does not declare MIN_TRUSTED_PUBLISH_NODE" +version_ge "$declared_min_node" "$min_node" || fail "MIN_TRUSTED_PUBLISH_NODE is $declared_min_node, need >= $min_node" + +declared_min_npm="$( + sed -nE "s/.*MIN_TRUSTED_PUBLISH_NPM:[[:space:]]*['\"]?([^'\"]+)['\"]?.*/\1/p" "$workflow" | head -n 1 +)" +[[ -n "$declared_min_npm" ]] || fail "workflow does not declare MIN_TRUSTED_PUBLISH_NPM" +version_ge "$declared_min_npm" "$min_npm" || fail "MIN_TRUSTED_PUBLISH_NPM is $declared_min_npm, need >= $min_npm" + +# shellcheck disable=SC2016 +grep -Fq 'npm install -g "npm@${MIN_TRUSTED_PUBLISH_NPM}"' "$workflow" || + fail "workflow must install the declared npm trusted publishing minimum" +grep -Fq 'NODE_VERSION="$(node -p' "$workflow" || + fail "workflow must read the active Node runtime version" +grep -Fq 'version_ge "${NODE_VERSION}" "${MIN_TRUSTED_PUBLISH_NODE}"' "$workflow" || + fail "workflow must hard-fail when Node is below the trusted publishing minimum" +# shellcheck disable=SC2016 +grep -Fq 'NPM_VERSION="$(npm --version)"' "$workflow" || + fail "workflow must hard-fail when npm is below the trusted publishing minimum" + +echo "[ok] npm trusted publishing runtime guard passed"