From 4b5718d37ea821214ecfb37ad50e7c23ca55eca2 Mon Sep 17 00:00:00 2001 From: Shane Murphy Date: Thu, 11 Jun 2026 19:51:31 +0200 Subject: [PATCH 1/3] feat(ci): auto-submit altacv to Typst Universe on release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Append a `submit-to-typst-universe` job that fires whenever release-please cuts a new tag (gated on `release-please.outputs.release_created == 'true'`, downstream of `attach-release-artifacts` so the tarball exists). It opens a PR on typst/packages from a maintainer-owned fork (smur89/packages), branched off the current upstream main so the diff is exactly the new `packages/preview/altacv//` directory. The job is idempotent — a re-run for an already-submitted version detects the existing open PR and exits cleanly. The PR body mirrors the manual submission template (metadata + submission checklist). Prerequisites — documented in the workflow comment block — are a fork of typst/packages owned by the same account, plus a fine-grained PAT (TYPST_UNIVERSE_TOKEN secret) scoped to the fork with contents + pull-requests write. --- .github/workflows/build.yml | 110 ++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 229042c..8c49a37 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -188,3 +188,113 @@ jobs: examples/example.pdf examples/preview.png make_latest: 'true' + + # When release-please cuts a new tag, automatically open a PR on + # typst/packages submitting altacv: to Typst Universe. The + # PR is created from a maintainer-owned fork (smur89/packages), + # branched off the current typst/packages main so the diff is just + # the new package directory. + # + # Prerequisites (one-time setup): + # 1. Fork github.com/typst/packages to smur89/packages. + # 2. Create a fine-grained PAT at github.com/settings/personal-access-tokens + # scoped to smur89/packages, with: + # - Contents: Read and write (push the branch) + # - Pull requests: Read and write (open the cross-repo PR) + # Cross-repo PRs into public typst/packages do not require any + # permission on typst/packages itself — read is implicit. + # 3. Store the PAT as a repository secret named TYPST_UNIVERSE_TOKEN + # on smur89/alta-typst. + # + # The job is idempotent: re-running for an already-submitted version + # detects the existing PR and exits cleanly. To re-submit after a + # maintainer rejection, close the previous PR first. + submit-to-typst-universe: + needs: [release-please, attach-release-artifacts] + if: needs.release-please.outputs.release_created == 'true' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + env: + VERSION: ${{ needs.release-please.outputs.version }} + TAG: ${{ needs.release-please.outputs.tag_name }} + FORK_REPO: smur89/packages + UPSTREAM_REPO: typst/packages + GH_TOKEN: ${{ secrets.TYPST_UNIVERSE_TOKEN }} + steps: + - name: Skip if a PR for this version is already open + id: check + run: | + existing=$(gh pr list \ + --repo "$UPSTREAM_REPO" \ + --search "altacv:${VERSION} in:title is:open" \ + --json url --jq '.[0].url // empty') + if [ -n "$existing" ]; then + echo "PR already open: $existing — skipping" + echo "skip=true" >> "$GITHUB_OUTPUT" + fi + + - name: Configure git + if: steps.check.outputs.skip != 'true' + run: | + gh auth setup-git + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Clone fork, fetch upstream, branch off upstream/main + if: steps.check.outputs.skip != 'true' + run: | + gh repo clone "$FORK_REPO" pkg -- --no-tags + cd pkg + git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" + git fetch upstream main --depth=1 + git checkout -b "altacv-${VERSION}" upstream/main + + - name: Add package files from the release tarball + if: steps.check.outputs.skip != 'true' + run: | + cd pkg + PKG_DIR="packages/preview/altacv/${VERSION}" + mkdir -p "$PKG_DIR" + curl -sSfL \ + "https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/altacv-${VERSION}.tar.gz" \ + | tar xz -C "$PKG_DIR" + git add "$PKG_DIR" + git commit -m "altacv:${VERSION}" + + - name: Push branch to fork + if: steps.check.outputs.skip != 'true' + run: | + cd pkg + git push origin "altacv-${VERSION}" + + - name: Open PR upstream + if: steps.check.outputs.skip != 'true' + run: | + cd pkg + gh pr create \ + --repo "$UPSTREAM_REPO" \ + --base main \ + --head "${FORK_REPO%%/*}:altacv-${VERSION}" \ + --title "altacv:${VERSION}" \ + --body "$(cat < Date: Thu, 11 Jun 2026 19:54:11 +0200 Subject: [PATCH 2/3] fix(ci): use --head filter for idempotency check + tighten job perms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `--search "altacv:${VERSION} in:title is:open"` form treats `altacv:` as a GitHub-search filter qualifier (not a title substring), returning zero results and breaking the intended skip-on-existing-PR behaviour. Replaced with `--head :altacv-${VERSION}` which matches PRs by branch name — one branch per version, reliable. Also dropped the job's `contents: read` permission. The submission job writes only to external repos (smur89/packages + typst/packages) via the TYPST_UNIVERSE_TOKEN secret; it never reads the workflow repo, so `permissions: {}` documents that. --- .github/workflows/build.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c49a37..6fc10f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -214,8 +214,10 @@ jobs: if: needs.release-please.outputs.release_created == 'true' runs-on: ubuntu-latest timeout-minutes: 5 - permissions: - contents: read + # All write operations target external repos (smur89/packages and + # typst/packages) via TYPST_UNIVERSE_TOKEN, so no permissions on + # the workflow repo itself are needed. + permissions: {} env: VERSION: ${{ needs.release-please.outputs.version }} TAG: ${{ needs.release-please.outputs.tag_name }} @@ -226,9 +228,14 @@ jobs: - name: Skip if a PR for this version is already open id: check run: | + # Filter by head branch (one branch per version) rather than + # title-substring search — GitHub's search syntax interprets + # `altacv:` as an unknown filter qualifier and returns nothing + # useful for a title containing a colon. existing=$(gh pr list \ --repo "$UPSTREAM_REPO" \ - --search "altacv:${VERSION} in:title is:open" \ + --head "${FORK_REPO%%/*}:altacv-${VERSION}" \ + --state open \ --json url --jq '.[0].url // empty') if [ -n "$existing" ]; then echo "PR already open: $existing — skipping" From d9f1d1b43a129aadb4e6b9a39820bfff50948710 Mon Sep 17 00:00:00 2001 From: Shane Murphy Date: Thu, 11 Jun 2026 19:56:38 +0200 Subject: [PATCH 3/3] refactor(ci): extract Typst Universe PR body to a template file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inline heredoc body inside the workflow's `gh pr create` step was long and tangled bash-escaping (backticks in markdown vs command substitution) with markdown structure. Moved to `.github/typst-universe-pr-body.md` and substituted at submission time via `envsubst` restricted to the three expected variables — incidental $-references in the body cannot accidentally expand. The job now sparse-checks-out only that single file, so we don't pull lib.typ + icons we don't need. The PR body itself is now editable in any markdown tool, with no shell-escape gotchas. --- .github/typst-universe-pr-body.md | 17 ++++++++++++ .github/workflows/build.yml | 46 ++++++++++++++----------------- 2 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 .github/typst-universe-pr-body.md diff --git a/.github/typst-universe-pr-body.md b/.github/typst-universe-pr-body.md new file mode 100644 index 0000000..3e727b9 --- /dev/null +++ b/.github/typst-universe-pr-body.md @@ -0,0 +1,17 @@ +altacv ${VERSION} — see https://github.com/${GITHUB_REPOSITORY}/releases/tag/${TAG} for the release notes, source tag, and bundled artefacts. + +## Package metadata + +- **Source repository:** https://github.com/${GITHUB_REPOSITORY} +- **Release:** https://github.com/${GITHUB_REPOSITORY}/releases/tag/${TAG} +- **License:** MIT +- **Category:** `cv` + +## Submission checklist + +- [x] Package compiles without errors. +- [x] Name follows the [naming rules](https://github.com/typst/packages/blob/main/docs/manifest.md#naming-rules). +- [x] README documents the public API, schema, and configuration, with examples pinned to ${VERSION}. +- [x] Manifest's `license` field matches the contents of `LICENSE`. +- [x] Bundle excludes generated artefacts — only `typst.toml`, `lib.typ`, `icons/`, `LICENSE`, and `README.md` are included. +- [x] Submission opened automatically by alta-typst's release pipeline. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6fc10f0..93c1288 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -242,6 +242,16 @@ jobs: echo "skip=true" >> "$GITHUB_OUTPUT" fi + # Sparse-checkout the single PR-body template; full checkout + # would pull lib.typ and icons we don't need in this job. + - name: Checkout PR body template + if: steps.check.outputs.skip != 'true' + uses: actions/checkout@v4 + with: + persist-credentials: false + sparse-checkout: .github/typst-universe-pr-body.md + sparse-checkout-cone-mode: false + - name: Configure git if: steps.check.outputs.skip != 'true' run: | @@ -276,32 +286,18 @@ jobs: cd pkg git push origin "altacv-${VERSION}" + # envsubst is restricted to the expected variable names so any + # incidental $-references in the template (e.g. a stray $PATH) + # are not silently expanded. - name: Open PR upstream if: steps.check.outputs.skip != 'true' run: | cd pkg - gh pr create \ - --repo "$UPSTREAM_REPO" \ - --base main \ - --head "${FORK_REPO%%/*}:altacv-${VERSION}" \ - --title "altacv:${VERSION}" \ - --body "$(cat <