Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/instructions/testing.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,7 @@ applyTo: "tests/**"

## Coverage targets

- Statements ≥ 90% · Branches ≥ 80% · Functions ≥ 85% · Lines ≥ 90%.
- `src/index.ts` excluded (entry point, covered by smoke test).
- Enforced thresholds live in `vitest.config.ts` (single source of truth):
Statements ≥ 79% · Branches ≥ 68% · Functions ≥ 83% · Lines ≥ 79%.
- `src/index.ts` and the network-transport / CMS-engine modules are excluded
(see the commented exclude block in `vitest.config.ts`).
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [20, 22]
node-version: [22, 24]

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down
63 changes: 62 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '>=20'
# A range resolves to the NEWEST available Node (currently 24.x),
# whose bundled npm >= 11.5.1 is required by npm Trusted Publishing
# (token-less OIDC). A '22.x' pin would ship npm 10.9 and break the
# publish. The floor mirrors the engines >= 22 support policy and
# the sibling pdfnative workflow.
node-version: '>=22.14.0'
registry-url: https://registry.npmjs.org
cache: npm

Expand Down Expand Up @@ -58,6 +63,62 @@ jobs:
- name: Binary smoke test
run: node dist/cli.cjs --help

# PDF/A gate: the release must never publish samples/claims that the
# reference validator rejects. Same pinned veraPDF (URL + SHA-256) as
# .github/workflows/verapdf.yml — BLOCKING, no continue-on-error.
- name: Setup Java (Temurin 17 LTS)
uses: actions/setup-java@d7793b545071e98d581d3bf084a51c3213318a07 # v4.9.0
with:
distribution: temurin
java-version: '17'

- name: Install veraPDF CLI
env:
VERAPDF_INSTALL_URL: 'https://software.verapdf.org/rel/1.30/verapdf-greenfield-1.30.2-installer.zip'
# SHA-256 of the installer zip. The step fails on mismatch: the
# archive is executed with `java -jar`, so a swapped artefact on
# the download host would otherwise run arbitrary code on the
# runner. Recompute when bumping the URL:
# curl -fsSL <url> | sha256sum
VERAPDF_INSTALL_SHA256: '6cc6341cb1af644044054b81f00a6590a7918abb18f762243de115258bcad838'
run: |
set -euo pipefail
mkdir -p "$HOME/verapdf-installer"
curl -fsSL -o "$HOME/verapdf-installer/installer.zip" "${VERAPDF_INSTALL_URL}"
echo "${VERAPDF_INSTALL_SHA256} $HOME/verapdf-installer/installer.zip" | sha256sum --check --strict
unzip -q "$HOME/verapdf-installer/installer.zip" -d "$HOME/verapdf-installer"
INSTALLER_JAR=$(find "$HOME/verapdf-installer" -name 'verapdf-izpack-installer-*.jar' | head -n1)
if [ -z "${INSTALLER_JAR}" ]; then
echo "veraPDF installer jar not found"; exit 1
fi
mkdir -p "$HOME/verapdf"
# Auto-install via izpack XML descriptor (headless).
cat > "$HOME/verapdf/auto-install.xml" <<XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<AutomatedInstallation langpack="eng">
<com.izforge.izpack.panels.htmlhello.HTMLHelloPanel id="welcome"/>
<com.izforge.izpack.panels.target.TargetPanel id="install_dir"><installpath>$HOME/verapdf</installpath></com.izforge.izpack.panels.target.TargetPanel>
<com.izforge.izpack.panels.packs.PacksPanel id="sdk_pack_select"><pack index="0" name="veraPDF GUI" selected="true"/><pack index="1" name="veraPDF Mac and *nix Scripts" selected="true"/><pack index="2" name="veraPDF Documentation" selected="false"/><pack index="3" name="veraPDF Sample Plugins" selected="false"/></com.izforge.izpack.panels.packs.PacksPanel>
<com.izforge.izpack.panels.install.InstallPanel id="install"/>
<com.izforge.izpack.panels.finish.FinishPanel id="finish"/>
</AutomatedInstallation>
XML
java -jar "${INSTALLER_JAR}" "$HOME/verapdf/auto-install.xml"
echo "VERAPDF_HOME=$HOME/verapdf" >> "$GITHUB_ENV"
echo "$HOME/verapdf" >> "$GITHUB_PATH"

- name: Generate PDF/A corpus
# dist/ is already built by the Build step above.
run: npm run corpus:pdfa

- name: Validate PDF/A corpus (blocking)
env:
# Fail-closed: a missing / broken veraPDF or Java is an INFRA
# failure (exit 3), never a silent skip.
VERAPDF_REQUIRED: '1'
VERAPDF_REPORT_DIR: test-output/pdfa/reports
run: node scripts/validate-pdfa.mjs

- name: Generate SBOM (CycloneDX)
# Software Bill of Materials for supply-chain transparency. Uses the
# CycloneDX generator via npx (build-time only — adds ZERO runtime
Expand Down
200 changes: 200 additions & 0 deletions .github/workflows/verapdf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
name: PDF/A Validation (veraPDF)

# Runs the veraPDF reference validator against a corpus of PDF/A-claiming
# documents produced by the CLI itself (scripts/generate-pdfa-corpus.mjs →
# test-output/pdfa/), covering a representative sample of the PDF/A-relevant
# features the CLI exposes. Each file is validated against the profile it
# claims in XMP and compared with the manifest's `expectCompliant` flag; the
# corpus includes negative canaries that veraPDF MUST reject (an unexpected
# pass — XPASS — is fatal), so a validator that accepts everything fails the
# run instead of turning it green.
#
# Status: BLOCKING in pdfnative-cli as of v1.4.0 (precedent: pdfnative made
# its gate blocking in v1.1.0+). The validate step has no `continue-on-error`,
# so any deviation from the manifest expectations fails the workflow.
# Exit codes of scripts/validate-pdfa.mjs:
# 0 all expectations met · 1 conformance FAIL / XPASS · 2 no corpus ·
# 3 INFRA (veraPDF unusable or produced no report — not a verdict).
#
# NOTE on `paths:` filters vs. required checks: this workflow only triggers
# when the listed paths change. GitHub reports a path-filtered workflow that
# did not run as "Expected — Waiting for status" on a PR, which BLOCKS merging
# forever if the check is marked "required" in branch protection. Before
# making this job a required check, either drop the `paths:` filters (run it
# on every PR) or add a companion workflow with `paths-ignore` mirroring this
# list that reports a passing status of the same name.
#
# veraPDF is an external CI tool (a Java CLI installed on the runner). It is
# not a dependency of pdfnative-cli: the package keeps its zero-runtime-
# dependency policy (`pdfnative` is the only runtime dependency), and the
# validator is never bundled or linked.

on:
push:
branches: [main, master]
paths:
- 'src/**'
- 'samples/**'
- 'scripts/**'
- 'package.json'
- 'package-lock.json'
- 'tsup.config.ts'
- 'tsconfig.json'
- '.github/workflows/verapdf.yml'
pull_request:
branches: [main, master]
paths:
- 'src/**'
- 'samples/**'
- 'scripts/**'
- 'package.json'
- 'package-lock.json'
- 'tsup.config.ts'
- 'tsconfig.json'
- '.github/workflows/verapdf.yml'
# Manual runs against any branch from the Actions UI.
workflow_dispatch:

permissions:
contents: read

concurrency:
group: verapdf-${{ github.ref }}
cancel-in-progress: true

jobs:
verapdf:
runs-on: ubuntu-latest
timeout-minutes: 20
env:
# Pinned veraPDF release (greenfield) so CI and the documented local
# setup validate against the same validator version — a floating
# "latest" URL can silently change the rule set between two runs of
# the same commit. Bump deliberately, with the release index:
# https://software.verapdf.org/rel/
VERAPDF_INSTALL_URL: 'https://software.verapdf.org/rel/1.30/verapdf-greenfield-1.30.2-installer.zip'
# SHA-256 of the installer zip above. The job fails on mismatch: the
# archive is executed with `java -jar`, so a swapped artefact on the
# download host would otherwise run arbitrary code on the runner.
# Recompute when bumping the URL:
# curl -fsSL <url> | sha256sum
VERAPDF_INSTALL_SHA256: '6cc6341cb1af644044054b81f00a6590a7918abb18f762243de115258bcad838'
# Fail-closed: a missing / broken veraPDF or Java is an INFRA failure
# (exit 3), never a silent skip that would leave the step green.
VERAPDF_REQUIRED: '1'
VERAPDF_REPORT_DIR: test-output/pdfa/reports

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: npm

- name: Setup Java (Temurin 17 LTS)
uses: actions/setup-java@d7793b545071e98d581d3bf084a51c3213318a07 # v4.9.0
with:
distribution: temurin
java-version: '17'

- name: Install veraPDF CLI
run: |
set -euo pipefail
mkdir -p "$HOME/verapdf-installer"
curl -fsSL -o "$HOME/verapdf-installer/installer.zip" "${VERAPDF_INSTALL_URL}"
echo "${VERAPDF_INSTALL_SHA256} $HOME/verapdf-installer/installer.zip" | sha256sum --check --strict
unzip -q "$HOME/verapdf-installer/installer.zip" -d "$HOME/verapdf-installer"
INSTALLER_JAR=$(find "$HOME/verapdf-installer" -name 'verapdf-izpack-installer-*.jar' | head -n1)
if [ -z "${INSTALLER_JAR}" ]; then
echo "veraPDF installer jar not found"; exit 1
fi
mkdir -p "$HOME/verapdf"
# Auto-install via izpack XML descriptor (headless).
cat > "$HOME/verapdf/auto-install.xml" <<XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<AutomatedInstallation langpack="eng">
<com.izforge.izpack.panels.htmlhello.HTMLHelloPanel id="welcome"/>
<com.izforge.izpack.panels.target.TargetPanel id="install_dir"><installpath>$HOME/verapdf</installpath></com.izforge.izpack.panels.target.TargetPanel>
<com.izforge.izpack.panels.packs.PacksPanel id="sdk_pack_select"><pack index="0" name="veraPDF GUI" selected="true"/><pack index="1" name="veraPDF Mac and *nix Scripts" selected="true"/><pack index="2" name="veraPDF Documentation" selected="false"/><pack index="3" name="veraPDF Sample Plugins" selected="false"/></com.izforge.izpack.panels.packs.PacksPanel>
<com.izforge.izpack.panels.install.InstallPanel id="install"/>
<com.izforge.izpack.panels.finish.FinishPanel id="finish"/>
</AutomatedInstallation>
XML
java -jar "${INSTALLER_JAR}" "$HOME/verapdf/auto-install.xml"
echo "VERAPDF_HOME=$HOME/verapdf" >> "$GITHUB_ENV"
echo "$HOME/verapdf" >> "$GITHUB_PATH"

- name: Verify veraPDF
run: |
verapdf --version

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Generate PDF/A corpus
run: npm run corpus:pdfa

# BLOCKING (see header). The exit code drives the step outcome — the
# step fails naturally on a non-zero exit — and the summary step below
# surfaces the verdict:
# 0 all expectations met · 1 conformance FAIL / XPASS · 2 no corpus ·
# 3 INFRA (veraPDF unusable or produced no report — not a verdict).
- name: Validate PDF/A corpus (blocking)
id: validate
run: |
set +e
node scripts/validate-pdfa.mjs 2> >(tee verapdf-stderr.txt >&2) | tee verapdf-report.txt
code=${PIPESTATUS[0]}
echo "exit_code=${code}" >> "$GITHUB_OUTPUT"
exit "${code}"

# Raw per-file veraPDF XML (+ stderr when non-empty) lives under
# test-output/pdfa/reports/ — upload it so a FAIL / INFRA line can be
# diagnosed without re-running the job.
- name: Upload veraPDF report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: verapdf-report
path: |
verapdf-report.txt
verapdf-stderr.txt
test-output/pdfa/manifest.json
test-output/pdfa/reports/
if-no-files-found: warn
retention-days: 14

- name: Job summary
if: always()
run: |
code='${{ steps.validate.outputs.exit_code }}'
case "${code}" in
0) verdict='all expectations met' ;;
1) verdict='CONFORMANCE failure (FAIL or XPASS — see report)' ;;
2) verdict='no corpus generated' ;;
3) verdict='INFRA failure — veraPDF unusable or produced no report (NOT a conformance verdict)' ;;
*) verdict="unknown (exit ${code:-n/a})" ;;
esac
{
echo "## PDF/A validation (veraPDF, blocking)"
echo
echo "Step outcome: ${{ steps.validate.outcome }} · exit ${code:-n/a} · ${verdict}"
echo
echo '```'
cat verapdf-report.txt 2>/dev/null || echo "(no report produced)"
echo '```'
if [ -s verapdf-stderr.txt ]; then
echo
echo '<details><summary>validator stderr</summary>'
echo
echo '```'
cat verapdf-stderr.txt
echo '```'
echo '</details>'
fi
} >> "$GITHUB_STEP_SUMMARY"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ test-output/

# Sample output (generated PDFs — not committed)
samples/output/
samples/batch/manifest/out/
output.pdf

# Environment files — never commit secrets
Expand Down
Loading
Loading