diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4f649754..19647e9b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,8 +12,8 @@ ## Checklist -- [ ] `npm run format` passes (Biome) +- [ ] `pnpm run format` passes (Biome) - [ ] Example has `start` and `start:e2e` scripts in `package.json` -- [ ] `SERVER_DIR=examples/ npm run test` passes (Playwright smoke test) +- [ ] `SERVER_DIR=examples/ pnpm run test` passes (Playwright smoke test) - [ ] `README.md` included with prerequisites, setup, and usage - [ ] Uses current pinned `@nutrient-sdk/viewer` version diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 42487ee5..68cd00bd 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -16,15 +16,19 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Setup pnpm + uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: lts/* + - name: Check pnpm workflows + run: ./tests/pnpm-workflows.test.sh - name: Install dependencies - run: npm install && npm run install-dependencies + run: pnpm install --frozen-lockfile && pnpm run install-dependencies - name: Install Playwright Browsers - run: npm exec playwright install chromium --with-deps + run: pnpm exec playwright install chromium --with-deps - name: Run Playwright tests - run: npm run e2e-tests + run: pnpm run e2e-tests - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: ${{ !cancelled() }} with: diff --git a/.github/workflows/update-nutrient-sdk.yml b/.github/workflows/update-nutrient-sdk.yml index 47bba4a1..1b26ca3b 100644 --- a/.github/workflows/update-nutrient-sdk.yml +++ b/.github/workflows/update-nutrient-sdk.yml @@ -34,8 +34,6 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - with: - version: 10 - name: Setup Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -158,6 +156,8 @@ jobs: pull request created by \`GITHUB_TOKEN\` does not trigger the Biome or Playwright workflows, so the checks tab here will be empty. + - Dependency audit fixes: pnpm examples may include security overrides + in \`pnpm-workspace.yaml\` and the corresponding lockfile updates - Formatting (\`pnpm run format\`): applied to \`examples/\` - E2E smoke tests (\`pnpm run e2e-tests\`): ${e2e_result} diff --git a/.husky/pre-commit b/.husky/pre-commit index 87be203e..6f97e948 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1,2 @@ -npm run lint-staged -npm run check-biome-version +pnpm run lint-staged +pnpm run check-biome-version diff --git a/AGENTS.md b/AGENTS.md index c8ce52de..e16dda5f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,31 +71,43 @@ This updates `package.json` in every example directory. ## Commands ```bash -# Install all example dependencies -npm run install-dependencies +# One-time: enable the package-manager shims bundled with Node.js +corepack enable + +# Install the root and all example dependencies with the pinned pnpm version +pnpm install --frozen-lockfile +pnpm run install-dependencies # Format code (Biome) -npm run format +pnpm run format # Run e2e smoke tests (starts each example, checks PSPDFKit loads) -npm run e2e-tests +pnpm run e2e-tests # Run Playwright directly for a single example -SERVER_DIR=examples/javascript-vite npm run test +SERVER_DIR=examples/javascript-vite pnpm run test # Audit and fix vulnerabilities across all examples -npm run audit-fix +pnpm run audit-fix # Bump Nutrient SDK version in all examples (version is required) -npm run update-nutrient-version -- +pnpm run update-nutrient-version -- ``` +The root `packageManager` field pins pnpm, and Corepack applies that pin to every +directory below the root, including the npm-based examples. If you enabled +Corepack's npm shim (`corepack enable npm`), set `COREPACK_ENABLE_STRICT=0` before +running `npm` inside an example by hand; the scripts and the Playwright config set it +for you. Root-level scripts, hooks, and docs use pnpm. + ## Adding a New Example 1. Create `examples//` 2. Add a `package.json` with: - `start` script — dev server - `start:e2e` script — dev server on port 3000 (for Playwright) + - If it uses `pnpm-lock.yaml`, a sibling `pnpm-workspace.yaml`; the scripts + reject pnpm examples without one so they cannot silently join the root workspace. 3. The example must call `PSPDFKit.load()` and render `.PSPDFKit-Container` 4. Add a `README.md` following the pattern of existing examples: - Prerequisites @@ -104,7 +116,7 @@ npm run update-nutrient-version -- 5. Use the current pinned `@nutrient-sdk/viewer` version 6. Run the Playwright smoke test against your example: ```bash - SERVER_DIR=examples/ npm run test + SERVER_DIR=examples/ pnpm run test ``` 7. Update the root `README.md` if adding a new framework category diff --git a/README.md b/README.md index 8f49890d..c8f726b7 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,6 @@ This repository includes a check to ensure that the Biome version in `package.js - During CI/CD in the GitHub workflow - As a pre-commit hook to prevent commits with mismatched versions -- Manually using `npm run check-biome-version` +- Manually using `pnpm run check-biome-version` If you update the Biome version in `package.json`, make sure to update it in `.github/workflows/biome.yml` as well. diff --git a/examples/svelte-kit/pnpm-workspace.yaml b/examples/svelte-kit/pnpm-workspace.yaml index 8f277024..e1bc04d5 100644 --- a/examples/svelte-kit/pnpm-workspace.yaml +++ b/examples/svelte-kit/pnpm-workspace.yaml @@ -1,3 +1,5 @@ +minimumReleaseAge: 0 + allowBuilds: esbuild: true diff --git a/examples/vue-composition-api/pnpm-workspace.yaml b/examples/vue-composition-api/pnpm-workspace.yaml index a373992b..b229c20d 100644 --- a/examples/vue-composition-api/pnpm-workspace.yaml +++ b/examples/vue-composition-api/pnpm-workspace.yaml @@ -1,3 +1,5 @@ +minimumReleaseAge: 0 + allowBuilds: esbuild: true diff --git a/package.json b/package.json index 0a294196..32af15eb 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "keywords": [], "author": "", "license": "MIT", + "packageManager": "pnpm@11.20.0+sha512.9a6f330a95b66446ea088faf1521405a8a01f07fde7124cc9958dfed52d4bb436737e65b08f85f37b46fcba375092558ac51262b816844b22f63406ed166bfee", "devDependencies": { "@biomejs/biome": "^1.9.4", "@playwright/test": "^1.56.1", @@ -23,28 +24,12 @@ "e2e-tests": "./scripts/e2e-tests.sh", "lint-staged": "lint-staged", "test": "playwright test", + "test:pnpm-workflows": "./tests/pnpm-workflows.test.sh", "audit-fix": "./scripts/audit-dependencies.sh", "update-nutrient-version": "./scripts/update-nutrient-in-examples.sh", "check-biome-version": "./scripts/check-biome-version.sh" }, "lint-staged": { "*": ["biome check --write --no-errors-on-unmatched"] - }, - "pnpm": { - "overrides": { - "ajv": "^8.18.0", - "http-proxy-middleware": "^2.0.10", - "on-headers": "^1.1.0", - "node-forge": "^1.4.0", - "qs": "^6.15.1", - "shell-quote": "^1.9.0", - "fast-uri": "^3.1.4", - "picomatch": "^2.3.2", - "path-to-regexp": "^0.1.13", - "ws": "^8.21.0", - "websocket-driver": "^0.7.5", - "uuid": "^11.1.1" - }, - "onlyBuiltDependencies": ["@biomejs/biome"] } } diff --git a/playwright.config.ts b/playwright.config.ts index 7a3bbed0..7d15364b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -33,6 +33,9 @@ export default defineConfig({ ], webServer: { command: `cd ${process.env.SERVER_DIR} && npm run start:e2e`, + // The examples are npm projects, but Corepack applies the root pnpm pin to + // them and rejects npm when its strict npm shim is enabled. + env: { COREPACK_ENABLE_STRICT: "0" }, url: "http://localhost:3000", reuseExistingServer: true, }, diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..f412f38a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,27 @@ +packages: + - "." + +# pnpm 11 defaults verify-deps-before-run to "install", which would run a +# networked install from `pnpm run` (the pre-commit hook included) whenever +# node_modules is out of date. Warn instead so scripts never mutate the tree. +verifyDepsBeforeRun: warn + +allowBuilds: + "@biomejs/biome": true + +onlyBuiltDependencies: + - "@biomejs/biome" + +overrides: + "ajv": "^8.18.0" + "http-proxy-middleware": "^2.0.10" + "on-headers": "^1.1.0" + "node-forge": "^1.4.0" + "qs": "^6.15.1" + "shell-quote": "^1.9.0" + "fast-uri": "^3.1.4" + "picomatch": "^2.3.2" + "path-to-regexp": "^0.1.13" + "ws": "^8.21.0" + "websocket-driver": "^0.7.5" + "uuid": "^11.1.1" diff --git a/scripts/audit-dependencies.sh b/scripts/audit-dependencies.sh index d9cd6db3..0cf80fd7 100755 --- a/scripts/audit-dependencies.sh +++ b/scripts/audit-dependencies.sh @@ -1,7 +1,11 @@ #!/bin/bash -# Runs npm/pnpm audit fix on all examples and outputs the result +# Runs npm/pnpm audit fix on all examples and outputs the result. +# This aggregate report deliberately does not enable errexit: predicates and +# arithmetic expressions below use nonzero statuses as ordinary control flow. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# shellcheck source=./pnpm-helpers.sh +source "${SCRIPT_DIR}/pnpm-helpers.sh" echo -e "\033[37;1mAuditing npm vulnerabilities in examples\033[0m\r" @@ -29,17 +33,30 @@ for dir in examples/*; do result=0 initialresult=0 audit_error=0 + audit_error_reason="pnpm/npm command failed or returned invalid data" has_lockfile=0 + initial_json="" + audit_json="" if [ -f "pnpm-lock.yaml" ]; then has_lockfile=1 - initial_json=$(pnpm audit --json 2>/dev/null) - # pnpm spells this `--fix` (there is no `pnpm audit fix` subcommand), - # and it only writes overrides into package.json — the install is what - # actually applies them to the lockfile and node_modules. - pnpm audit --fix > /dev/null 2>&1 - pnpm install --no-frozen-lockfile > /dev/null 2>&1 - audit_json=$(pnpm audit --json 2>/dev/null) + if ! require_local_pnpm_workspace "$dir"; then + audit_error=1 + audit_error_reason="no sibling pnpm-workspace.yaml" + else + initial_json=$(pnpm audit --json 2>/dev/null) + # pnpm writes override fixes to the local workspace file. The + # install applies them to the lockfile and node_modules. Real + # command failures remain visible and make the audit fail. + if ! run_pnpm_audit_fix; then + audit_error=1 + elif ! run_pnpm_install_quietly --no-frozen-lockfile; then + audit_error=1 + audit_error_reason="pnpm install failed; inspect ${dir}/pnpm-workspace.yaml for partial allowBuilds changes" + else + audit_json=$(pnpm audit --json 2>/dev/null) + fi + fi elif [ -f "package-lock.json" ]; then has_lockfile=1 initial_json=$(npm audit --json 2>/dev/null) @@ -57,7 +74,7 @@ for dir in examples/*; do fi if (( audit_error )); then - echo -e " \033[91mAudit could not complete\033[0m (registry endpoint error — result unknown)" + echo -e " \033[91mAudit could not complete\033[0m (${audit_error_reason})" error_dirs+=("$dir") elif (( initialresult > 0 )); then ((fixed = initialresult - result)) @@ -106,9 +123,7 @@ echo -e "\033[37;1m──────────────────── if [ ${#vuln_dirs[@]} -eq 0 ] && [ ${#error_dirs[@]} -eq 0 ]; then echo -e "\033[92;1mAll examples are vulnerability-free!\033[0m" -elif [ ${#vuln_dirs[@]} -eq 0 ]; then - echo -e "\033[92mNo remaining vulnerabilities found.\033[0m" -else +elif [ ${#vuln_dirs[@]} -gt 0 ]; then total_remaining=0 for i in "${!vuln_dirs[@]}"; do echo -e " \033[31m${vuln_dirs[$i]}: ${vuln_counts[$i]} vulnerabilities\033[0m (${vuln_severities[$i]})" @@ -118,8 +133,9 @@ else fi if [ ${#error_dirs[@]} -gt 0 ]; then - echo -e "\n \033[91;1m${#error_dirs[@]} example(s) could not be audited (registry endpoint error):\033[0m" + echo -e "\n \033[91;1m${#error_dirs[@]} example(s) could not be audited:\033[0m" for d in "${error_dirs[@]}"; do echo -e " \033[91m${d}\033[0m" done + exit 1 fi diff --git a/scripts/e2e-tests.sh b/scripts/e2e-tests.sh index 3f1e8bbb..44a35ae0 100755 --- a/scripts/e2e-tests.sh +++ b/scripts/e2e-tests.sh @@ -16,7 +16,7 @@ for dir in examples/*; do done if [ "$skip" = false ]; then echo "Running e2e tests in $dir" - if ! (SERVER_DIR="$dir" npm run test); then + if ! (SERVER_DIR="$dir" pnpm run test); then failed+=("$dir") fi fi diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh index 4349b2c1..f3c9f31f 100755 --- a/scripts/install-dependencies.sh +++ b/scripts/install-dependencies.sh @@ -1,7 +1,10 @@ #!/bin/bash # Runs npm/pnpm install on all examples +set -euo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# shellcheck source=./pnpm-helpers.sh +source "${SCRIPT_DIR}/pnpm-helpers.sh" for dir in examples/*; do if [ -d "$dir" ]; then @@ -10,6 +13,7 @@ for dir in examples/*; do pushd "${SCRIPT_DIR}/../${dir}/" > /dev/null if [ -f "pnpm-lock.yaml" ]; then + require_local_pnpm_workspace "$dir" pnpm install elif [ -f "package-lock.json" ]; then npm install diff --git a/scripts/pnpm-helpers.sh b/scripts/pnpm-helpers.sh new file mode 100644 index 00000000..e2baade3 --- /dev/null +++ b/scripts/pnpm-helpers.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# These helpers back scripts that run npm inside the npm-based examples below +# the root pnpm pin. Corepack applies that pin to every directory under the +# root and, with its strict npm shim enabled, would reject those npm calls. +export COREPACK_ENABLE_STRICT=0 + +# pnpm 11 applies its minimum release age to every lockfile entry during +# install, not only while resolving new versions. All scripts must use the same +# policy so a lockfile written by the audit or update script remains installable +# in CI. The opt-out also avoids tracked minimumReleaseAgeExclude entries. +export pnpm_config_minimum_release_age=0 + +require_local_pnpm_workspace() { + local example="${1:-The current directory}" + + if [ ! -f "pnpm-workspace.yaml" ]; then + echo "${example} has a pnpm-lock.yaml but no sibling pnpm-workspace.yaml; refusing to use an ancestor workspace." >&2 + return 1 + fi +} + +run_pnpm_command_quietly() { + local output + local status=0 + + output=$(pnpm "$@" 2>&1) || status=$? + if (( status != 0 )); then + printf '%s\n' "$output" >&2 + return "$status" + fi + if [ -n "$output" ]; then + printf '%s\n' "$output" + fi +} + +run_pnpm_audit_fix() { + # A successful fix exits zero even when the initial audit found advisories. + # Preserve every real pnpm failure instead of hiding bad flags, registry + # failures, or an unwritable workspace file. + run_pnpm_command_quietly audit --fix=override --ignore-registry-errors +} + +run_pnpm_install_quietly() { + # pnpm 11 fails when it encounters an unapproved dependency build. Keep the + # successful output quiet, but print the complete failure before propagating it. + run_pnpm_command_quietly install "$@" +} diff --git a/scripts/update-nutrient-in-examples.sh b/scripts/update-nutrient-in-examples.sh index cf0eb1e8..8c17390e 100755 --- a/scripts/update-nutrient-in-examples.sh +++ b/scripts/update-nutrient-in-examples.sh @@ -2,6 +2,8 @@ set -euo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# shellcheck source=./pnpm-helpers.sh +source "${SCRIPT_DIR}/pnpm-helpers.sh" VERSION="${1:-}" @@ -27,11 +29,15 @@ upgrade_npm_in_example() { echo -e "\n${Green}Upgrading ${Yellow}${directory}${Green} to ${Yellow}${VERSION}${NoColor}" if [ -f "pnpm-lock.yaml" ]; then + require_local_pnpm_workspace "examples/${directory}" pnpm install "@nutrient-sdk/viewer@${VERSION}" --save --save-exact - pnpm install > /dev/null + run_pnpm_audit_fix - pnpm audit fix > /dev/null || true + # pnpm 11 treats an unapproved dependency build as an error. Keep this + # fail-fast so the automated bump never continues with only some examples + # updated; stderr remains visible in the workflow log. + run_pnpm_install_quietly --no-frozen-lockfile elif [ -f "package-lock.json" ]; then npm install "@nutrient-sdk/viewer@${VERSION}" --save --save-exact diff --git a/tests/pnpm-workflows.test.sh b/tests/pnpm-workflows.test.sh new file mode 100755 index 00000000..8da738b9 --- /dev/null +++ b/tests/pnpm-workflows.test.sh @@ -0,0 +1,191 @@ +#!/bin/bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +EXPECTED_PACKAGE_MANAGER='pnpm@11.20.0+sha512.9a6f330a95b66446ea088faf1521405a8a01f07fde7124cc9958dfed52d4bb436737e65b08f85f37b46fcba375092558ac51262b816844b22f63406ed166bfee' + +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +# Prints the body of the "Setup pnpm" step: everything after its name line up to +# the next step, so a `version:` input is caught wherever it sits in the step and +# a neighbouring step's `node-version:` never is. +setup_pnpm_step() { + awk '/name: Setup pnpm/ { inside = 1; next } inside && /^ *- / { exit } inside { print }' "$1" +} + +# Corepack applies the root packageManager pin to every directory below it and, +# with its strict npm shim enabled, rejects npm package-management commands. +# Bare npx remains transparent to Corepack, so ban it as unpinned execution too. +# Root-level workflows, hooks, runners, and docs therefore use pnpm. +npm_invocation='(^|[^[:alnum:]])(npm (run|exec|install|ci)|npx)( |$)' + +actual_package_manager="$(node -p "require('${REPO_ROOT}/package.json').packageManager")" +[[ "$actual_package_manager" == "$EXPECTED_PACKAGE_MANAGER" ]] || \ + fail "packageManager is not pinned to the expected integrity hash" + +for workflow in playwright.yml update-nutrient-sdk.yml; do + workflow_path="${REPO_ROOT}/.github/workflows/${workflow}" + grep -q 'name: Setup pnpm' "$workflow_path" || \ + fail "${workflow} does not set up pnpm" + if setup_pnpm_step "$workflow_path" | grep -Eq '^[[:space:]]*version:'; then + fail "${workflow} overrides the packageManager pnpm version" + fi +done + +grep -q 'run: pnpm install --frozen-lockfile && pnpm run install-dependencies' \ + "${REPO_ROOT}/.github/workflows/playwright.yml" || \ + fail "Playwright CI does not use the pinned pnpm for the root install" + +for file in .github/workflows/playwright.yml .github/workflows/update-nutrient-sdk.yml \ + .github/pull_request_template.md AGENTS.md README.md .husky/pre-commit scripts/e2e-tests.sh; do + [ -f "${REPO_ROOT}/${file}" ] || \ + fail "${file} is missing; the npm guard no longer covers it" + if grep -Eq "$npm_invocation" "${REPO_ROOT}/${file}"; then + fail "${file} invokes npm or unpinned npx at the repository root" + fi +done + +# Under pnpm 11 `pnpm run` verifies dependencies first (installing them by +# default), so the workflow calls this script directly and the repository +# downgrades that check to a warning so scripts never mutate the tree. +grep -q 'run: ./tests/pnpm-workflows.test.sh' "${REPO_ROOT}/.github/workflows/playwright.yml" || \ + fail "Playwright CI must run this script directly instead of through pnpm run" +grep -q '^verifyDepsBeforeRun: warn$' "${REPO_ROOT}/pnpm-workspace.yaml" || \ + fail "pnpm-workspace.yaml must set verifyDepsBeforeRun: warn so pnpm run never installs implicitly" + +# The scripts and the e2e web server intentionally run npm inside the npm-based +# examples; Corepack would reject that under the root pin unless strict mode is off. +grep -q '^export COREPACK_ENABLE_STRICT=0$' "${REPO_ROOT}/scripts/pnpm-helpers.sh" || \ + fail "pnpm-helpers.sh must export COREPACK_ENABLE_STRICT=0 for the npm examples" +grep -q 'COREPACK_ENABLE_STRICT: "0"' "${REPO_ROOT}/playwright.config.ts" || \ + fail "playwright.config.ts must set COREPACK_ENABLE_STRICT=0 for the example web servers" + +for script in audit-dependencies.sh install-dependencies.sh update-nutrient-in-examples.sh; do + script_path="${REPO_ROOT}/scripts/${script}" + grep -q 'pnpm-helpers.sh' "$script_path" || \ + fail "${script} does not load the pnpm guards" + grep -q 'require_local_pnpm_workspace' "$script_path" || \ + fail "${script} does not reject pnpm examples without a local workspace file" +done + +# shellcheck source=../scripts/pnpm-helpers.sh +source "${REPO_ROOT}/scripts/pnpm-helpers.sh" + +# pnpm 11 checks every lockfile entry against minimum-release-age during install. +# The shared opt-out keeps all three callers on one effective policy. +[[ "${pnpm_config_minimum_release_age:-}" == "0" ]] || \ + fail "pnpm callers do not effectively opt out of the minimum release age" + +for lockfile in "${REPO_ROOT}"/examples/*/pnpm-lock.yaml; do + workspace="$(dirname "$lockfile")/pnpm-workspace.yaml" + [ -f "$workspace" ] || \ + fail "${workspace#"${REPO_ROOT}/"} is missing" + grep -q '^minimumReleaseAge: 0$' "$workspace" || \ + fail "${workspace#"${REPO_ROOT}/"} does not disable the minimum release age for direct installs" +done + +fixture="$(mktemp -d)" +trap 'rm -rf "$fixture"' EXIT +cd "$fixture" + +if require_local_pnpm_workspace "fixture" 2>workspace-error.txt; then + fail "missing pnpm-workspace.yaml was accepted" +fi +grep -q 'pnpm-workspace.yaml' workspace-error.txt || \ + fail "missing-workspace failure did not explain the invariant" + +touch pnpm-workspace.yaml +require_local_pnpm_workspace "fixture" || \ + fail "local pnpm-workspace.yaml was rejected" + +mkdir bin +cat > bin/pnpm <<'PNPM' +#!/bin/bash +printf '%s\n' "$*" > "${PNPM_ARGS_FILE}" + +if [ "${PNPM_MODE:-fail}" = "fail-install" ]; then + if [ "${1:-}" = "audit" ] && [ "${2:-}" = "--json" ]; then + printf '%s\n' '{"metadata":{"vulnerabilities":{"total":0}}}' + exit 0 + fi + if [ "${1:-}" = "audit" ]; then + echo "3 overrides were added to pnpm-workspace.yaml" + exit 0 + fi +fi + +if [ -n "${PNPM_SUCCESS_OUTPUT:-}" ]; then + printf '%s\n' "$PNPM_SUCCESS_OUTPUT" + exit 0 +fi + +echo "simulated pnpm failure" >&2 +exit 42 +PNPM +chmod +x bin/pnpm +export PATH="${fixture}/bin:${PATH}" +export PNPM_ARGS_FILE="${fixture}/pnpm-args.txt" + +set +e +run_pnpm_audit_fix >audit-stdout.txt 2>audit-stderr.txt +status=$? +set -e + +[[ "$status" -eq 42 ]] || fail "pnpm audit failure status was swallowed" +[[ "$(cat pnpm-args.txt)" == 'audit --fix=override --ignore-registry-errors' ]] || \ + fail "pnpm audit fix used unexpected arguments" +grep -q 'simulated pnpm failure' audit-stderr.txt || \ + fail "pnpm audit failure details were hidden" + +set +e +run_pnpm_install_quietly --no-frozen-lockfile >install-stdout.txt 2>install-stderr.txt +status=$? +set -e + +[[ "$status" -eq 42 ]] || fail "pnpm install failure status was swallowed" +[[ "$(cat pnpm-args.txt)" == 'install --no-frozen-lockfile' ]] || \ + fail "pnpm install used unexpected arguments" +grep -q 'simulated pnpm failure' install-stderr.txt || \ + fail "pnpm install failure details were hidden" + +PNPM_SUCCESS_OUTPUT="3 overrides were added" run_pnpm_audit_fix >audit-success.txt +[[ "$(cat audit-success.txt)" == '3 overrides were added' ]] || \ + fail "successful pnpm mutation output was hidden" + +install_case="${fixture}/install-case" +mkdir -p "${install_case}/scripts" "${install_case}/examples/example" +cp "${REPO_ROOT}/scripts/install-dependencies.sh" \ + "${REPO_ROOT}/scripts/pnpm-helpers.sh" "${install_case}/scripts/" +touch "${install_case}/examples/example/pnpm-lock.yaml" \ + "${install_case}/examples/example/pnpm-workspace.yaml" +set +e +(cd "$install_case" && scripts/install-dependencies.sh) >install-script-stdout.txt 2>install-script-stderr.txt +status=$? +set -e +[[ "$status" -eq 42 ]] || \ + fail "install-dependencies.sh did not fail fast when pnpm install failed" + +audit_case="${fixture}/audit-case" +mkdir -p "${audit_case}/scripts" "${audit_case}/examples/example" +cp "${REPO_ROOT}/scripts/audit-dependencies.sh" \ + "${REPO_ROOT}/scripts/pnpm-helpers.sh" "${audit_case}/scripts/" +touch "${audit_case}/examples/example/pnpm-lock.yaml" \ + "${audit_case}/examples/example/pnpm-workspace.yaml" +set +e +PNPM_MODE=fail-install \ + bash -c 'cd "$1" && scripts/audit-dependencies.sh' _ "$audit_case" \ + >audit-script-stdout.txt 2>audit-script-stderr.txt +status=$? +set -e +[[ "$status" -eq 1 ]] || \ + fail "audit-dependencies.sh did not fail when an example could not be audited" +grep -q 'examples/example/pnpm-workspace.yaml' audit-script-stdout.txt || \ + fail "audit failure did not name the workspace file that may have changed" +if grep -q 'No remaining vulnerabilities found' audit-script-stdout.txt; then + fail "audit failure printed a contradictory success summary" +fi + +echo "pnpm workflow checks passed"