Build1 publisherNot yet confirmed elsewhere3 min readPublished
webpack stopped hand-writing its API docs, and the hard part was the credential
A GSoC project rebuilt webpack's API docs as a pipeline that regenerates from TypeScript declarations on release. The awkward part was getting one repo to trigger another.
The Engineer · Build desk
What happened
- webpack's API docs were updated by hand for every API change, and stale pages surfaced only when a reader hit one.
- webpack-doc-kit regenerates the site from webpack's TypeScript declarations via TypeDoc, with nodejs/doc-kit handling linking and UI.
- All six operational deliverables are merged, including versioned output folders and CI validation before merge.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Any docs pipeline that spans two repos has to carry a credential the default workflow token cannot replace, so the only real decision left is what kind of credential and how long it lives.
- decision Generated docs still stop at a draft PR with a preview attached, which keeps the publish decision with a maintainer instead of handing it to the release job.
- exposure While the pipeline ran on a PAT, one contributor's account and scope was the blast radius of the docs deployment, and it stayed that way until somebody rotated it.
- cost The expensive input in this pattern is reviewer attention rather than authoring time: six of the described decisions were reshaped in review, one of them by deleting code that had already merged.
The load-bearing piece is not TypeDoc. It is the handoff between two repositories: releases fire in webpack/webpack, the docs live in webpack/webpack-doc-kit, and `secrets.GITHUB_TOKEN` cannot reach across [5][15]. That constraint dictates the shape of everything downstream. A job in the core repo runs after release, finds `webpack` in the published-packages list, and calls `createWorkflowDispatch` against the docs repo's `release.yml` with the tag it just published [11]. Nikhil Kumar Rajak, who took the operational third of the split while teammates took AST parsing and UI, describes the resulting chain as publish, dispatch, update `versions.json`, open a PR, Vercel preview, maintainer merges [22][17].
The `versions.json` design is the part worth stealing. His mentor proposed an object per entry carrying `latest`, `label`, `major`, `exactVersion`, `commit` and `frozen`; review cut it to a flat array of tag strings, because the semver string and its position in the array already carry all of that, and `unshift()` at the front is what "latest" means [7]. Rajak writes that it was the right call and that he did not see it. State you can derive is state that cannot drift out of sync with itself, which is the same disease the docs had in the first place [2].
The credential is where the story stops being a student project. Two weeks after the dispatch job merged, mentor Aviv Keller replaced the `DISPATCH_TOKEN` PAT with a GitHub App installation token minted in a preceding step from `BOT_APP_ID` and `BOT_PRIVATE_KEY` [13]. The reasoning in the write-up is operational rather than aesthetic: a PAT is one person's account and one person's scope, sitting in the store until somebody remembers to rotate it, while the app token is org-owned and expires inside the hour [14]. So the version of this pipeline running today is not the version the deliverable shipped, and the delta is entirely about blast radius [25].
The largest single moving part is the README importer: 612 lines added and 968 removed across 10 files, a net loss of 356 lines [18][23]. It pages the org's public repos following `rel="next"` in the `Link` header, skips archived ones, and writes each README with a generated `site.json` for the sidebar [19]. Those generated files were not picked up at all until the root site config stopped being JSON and became an `.mjs` module that imports and concatenates them [21].
Counting the changes the write-up credits to review rather than authorship, the total is six, including a PR whose only content was deleting an input he had added on a wrong assumption [24][10]. That is the real cost line for anyone copying this pattern. The generator is cheap; the cross-repo trigger, the token that crosses with it, and the reviewers who catch a GitHub Actions expression interpolated into JavaScript are not [12].
What to watch
- Whether the first webpack release after the app-token swap produces a docs PR with no human touching the dispatch.
- Whether the draft-PR gate survives routine releases, or maintainers move it to auto-merge.
- Whether webpack.js.org content migrates onto webpack-doc-kit or the two sites run in parallel.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence46
- Adoption38
- Hype gap+12
- Incentives62
- Confidence52
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
The six operational deliverables were PR-based doc sync, release-aware doc generation, versioned output folders, a deployment pipeline, CI validation before merge, and README fetch automation; all six shipped and are merged into main with nothing open or pending.
- [2]
webpack's docs lived at webpack.js.org and every API change meant somebody updating them by hand; pages go stale and nobody notices until a reader does.
- [3]
webpack-doc-kit takes webpack's TypeScript declarations, runs them through TypeDoc, hands the output to nodejs/doc-kit for linking and UI, and produces a site that regenerates itself.
- [4]
The project ran 25 May to 17 August 2026 as GSoC work for webpack, contributed by Nikhil Kumar Rajak with mentors Aviv Keller, Claudio Wunder and Sebastian Beltran.
- [5]
webpack releases happen in webpack/webpack while the docs live in webpack/webpack-doc-kit, so a release in one has to produce updated docs in the other with nobody doing anything.
- [6]
PR #110 set up versions.json as the single source of truth that everything downstream reads, plus the script that maintains it and the workflow that runs it.
- [7]
The mentor proposed an object schema with latest, label, major, exactVersion, commit and frozen per entry; review cut it to a flat array of tag strings because everything else is derivable from the semver string, and position [0] with unshift() already tells you which is latest.
- [8]
The maintenance script validates with semver, finds any entry with the same major and replaces it in place or unshifts to the front; the workflow runs on workflow_dispatch and opens a draft PR, with every action SHA pinned.
- [9]
The workflow_dispatch trigger was itself a review correction; the author had reached for something else before realising releases fire in a different repo entirely.
- [10]
PR #116 removed a commit input the author had added on the belief that cross-repo triggering needed it; review asked why, and it did not.
- [11]
webpack/webpack#21074 is the dispatching half in the core repo: it runs after release, finds webpack in the published-packages list, and calls createWorkflowDispatch against webpack-doc-kit's release.yml with tag "v" + pkg.version.
- [12]
Review of the dispatch job caught a GitHub Actions ${{ }} expression interpolated straight into JavaScript, which moved to an env value parsed with JSON.parse, and the published package being picked by index [0] instead of by name.
- [13]
Two weeks after the dispatch job merged, Aviv Keller replaced the credential in #21082: the DISPATCH_TOKEN PAT became a GitHub App installation token minted by actions/create-github-app-token from BOT_APP_ID and BOT_PRIVATE_KEY and handed to github-script as steps.app-token.outputs.token.
- [14]
A PAT is one person's account and one person's scope, sitting in the secret store until someone remembers to rotate it; an app token is org-owned, scoped to the app's installation, and expires within the hour.
- [15]
secrets.GITHUB_TOKEN was never an option because it cannot reach another repository, which is the entire reason a second credential exists.
- [16]
The job was also renamed from trigger-webpack-doc-kit to trigger-documentation-update, with the script itself left alone and both review fixes included.
- [17]
The full chain is: webpack publishes, core dispatches, it updates versions.json, opens a PR, Vercel builds a preview, and a maintainer merges.
- [18]
PR #122 automated pulling loaders and plugins READMEs from across the org, at +612/-968 across 10 files, the author's second largest change.
- [19]
The importer pages through the org's public repos following rel="next" in the Link header, skips archived ones, sorts the rest by name suffix, then fetches, cleans and writes each README with a generated site.json for the sidebar.
- [20]
Review made GH_TOKEN optional in the README importer, since it threw when unset and would have broken local builds.
- [21]
The generated site.json files were not picked up at all; the fix was converting the root site config from JSON to an .mjs module that imports and concatenates them.
- [22]
The work was split three ways: Shams took AST parsing and content, Tushar took routing, navigation and UI, and Rajak took the operational side of generation, versioning and deployment.
- [23]
PR #122 is a net reduction of 356 lines of code.
- [24]
At least six of the design decisions described in the write-up were changed by review rather than by the author.
- [25]
The dispatch job running in production is not the one that shipped as the deliverable; its credential was replaced after merge.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toMaking webpack's Docs Update Themselves | GSoC 2026, wrapped
1 article · August 23, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- CI Credential SecurityFollow
- Open Source MentorshipFollow
- CI/CD pipelinesFollow
- JavaScript Build ToolingFollow
- Documentation AutomationFollow