A GitHub Action to generate PDF diffs for Typst documents.
Warning
This project is in early development. The API may change in future releases.
- Builds Typst documents from separate base and head revisions.
- Generates diff PDFs with
diff-pdf. - Uploads head PDFs and diff PDFs as workflow artifacts.
- Optionally creates or updates a pull request comment.
The following workflow runs on pull requests, compares the PR head against the merge-base (the commit where the PR branched off the base branch), uploads the generated PDFs, and updates a PR comment.
name: Typst PDF Diff
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
typst-pdf-diff:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Generate Typst PDF diff
uses: conjikidow/typst-pdf-diff-action@v0.3.0
with:
target-files: main.typThe examples reference actions by tag for readability. For production workflows, consider pinning each action to a full-length commit SHA, as GitHub recommends. Releases of this action are immutable, so its own tags are already locked to a single commit.
If your Typst project uses submodules, set submodules: 'recursive' and pass a
token that can access those submodules.
name: Typst PDF Diff
on:
pull_request:
types: [opened, synchronize, reopened]
env:
TYPST_TARGET_FILES: paper/main.typ slides/main.typ
jobs:
typst-pdf-diff:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/create-github-app-token@v3
id: generate-token
with:
client-id: ${{ vars.GH_APP_CLIENT_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Generate Typst PDF diff
uses: conjikidow/typst-pdf-diff-action@v0.3.0
with:
target-files: ${{ env.TYPST_TARGET_FILES }}
github-token: ${{ steps.generate-token.outputs.token }}
submodules: 'recursive'Important
A GitHub App installation token is scoped to a single account,
so it cannot read submodules owned by another user or organization.
actions/checkout fails the whole job when any submodule cannot be fetched.
If your submodules span several owners, prepare the working trees yourself as described in
Bring Your Own Working Trees.
For non-PR events, set head-ref and base-ref explicitly if you do not want
to rely on the action's automatic revision resolution.
| Name | Description | Required | Default |
|---|---|---|---|
target-files |
Space-separated Typst entrypoint files to compile. | Yes | - |
typst-version |
The Typst version to install. | No | 'latest' |
github-token |
The GitHub Token for checkout, artifact upload, and comments. | No | ${{ github.token }} |
submodules |
actions/checkout submodule mode: false, true, or recursive. |
No | 'false' |
head-ref |
Head revision to compare. If empty, uses the PR head SHA or github.sha. |
No | '' |
base-ref |
Base revision to compare. If empty, uses the merge-base or github.event.before. |
No | '' |
post-comment |
Whether to update a pull request comment with diff results. | No | 'true' |
comment-mode |
Comment update mode: replace or append. |
No | 'replace' |
fail-on-comment-error |
Whether to fail the action when PR comment updates fail. | No | 'false' |
upload-artifacts |
Whether to upload head and diff PDFs as workflow artifacts. | No | 'true' |
target-files is interpreted as a space-separated list, for example main.typ appendix.typ.
Note
post-comment: 'true' is intended for pull_request events.
On other events, the action skips pull request comment updates.
These are only needed when the action cannot check out the sources itself. See Bring Your Own Working Trees.
| Name | Description | Required | Default |
|---|---|---|---|
head-dir |
Existing working tree to build the head revision from. Disables checkout when set. | No | '' |
base-dir |
Existing working tree to build the base revision from. Requires head-dir. |
No | '' |
Both must be set together, and submodules, head-ref, and base-ref must stay at their defaults,
because the action checks out nothing in this mode.
Any other combination fails immediately.
| Name | Description |
|---|---|
has-diff |
true when at least one target file produces a diff PDF. |
head-artifact-url |
The uploaded head PDF artifact URL when artifact upload is enabled. |
diff-artifact-url |
The uploaded diff PDF artifact URL when a diff artifact is uploaded. |
Set head-dir and base-dir when the action cannot check out the sources itself,
for example when your submodules live under more than one owner and therefore need separate tokens.
The action then builds and compares the directories you provide, and checks out nothing.
Resolving the base revision is then up to you. For a pull request, compare against the merge-base rather than the base branch tip, or the diff will also contain unrelated changes merged into the base branch in the meantime.
- uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ vars.GH_APP_CLIENT_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
repositories: private-submodule
- name: Resolve the merge-base
id: merge-base
env:
GH_TOKEN: ${{ github.token }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
sha=$(gh api "repos/${GITHUB_REPOSITORY}/compare/${BASE_SHA}...${HEAD_SHA}" --jq '.merge_base_commit.sha')
echo "sha=${sha}" >>"$GITHUB_OUTPUT"
- uses: actions/checkout@v7
with:
path: head-src
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- uses: actions/checkout@v7
with:
path: base-src
ref: ${{ steps.merge-base.outputs.sha }}
persist-credentials: false
- name: Check out the required submodules
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
git config --global --add url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf git@github.com:
git config --global --add url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf https://github.com/
for dir in head-src base-src; do
git -C "${dir}" submodule update --init --depth 1 private-submodule
done
- name: Generate Typst PDF diff
uses: conjikidow/typst-pdf-diff-action@v0.3.0
with:
target-files: paper/main.typ
head-dir: head-src
base-dir: base-srcThat example uses a single token. When your submodules span several owners, qualify each rewrite with the owner so that the longest match wins. Identical prefixes resolve to whichever token was configured first, which silently sends one owner's token to another owner.
git config --global --add \
url."https://x-access-token:${TOKEN_A}@github.com/owner-a/".insteadOf git@github.com:owner-a/
git config --global --add \
url."https://x-access-token:${TOKEN_B}@github.com/owner-b/".insteadOf git@github.com:owner-b/The action writes its build output to build/ in the workspace,
so do not point head-dir or base-dir inside that directory.
Caution
The action cannot verify that the directories you supply hold the revisions you intended.
- Resolves the base and head revisions, and checks them out into separate directories.
Both steps are skipped when
head-dirandbase-dirare provided. - Installs Typst and
diff-pdf. - Builds PDFs for all
target-filesfrom both revisions. - Generates diff PDFs with
diff-pdf. - Uploads head PDFs and diff PDFs as artifacts when enabled.
- Builds a Markdown summary and optionally updates a PR comment.
Contributions, bug reports, and feedback are always welcome! Thank you for helping improve this project for everyone!