GitHub - DrDustinEdwards/devkit: Shared Renovate configuration for the DrDustinEdwards repositories. · GitHub
Skip to content
DrDustinEdwardsPublic

About

Shared Renovate configuration for the DrDustinEdwards repositories.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

devkit

Shared tooling for the Dustin Edwards repositories, public so private repositories can use it.

  • default.json: the Renovate policy. Each repository's renovate.json extends it; each setting's reason is in its description.
  • .github/workflows/: reusable workflows, each called, never run on its own. A caller pins a full 40-character devkit sha, and scripts/check-callers.mjs (run by the caller on its own workflow files) fails on a branch, a tag or secrets: inherit. examples/ holds the shape of each caller.
    • ai-author.yml: refuses an AI attribution trailer on new commits, and on a pull request a commit authored or committed as Claude (devkit-check-trailers). It installs devkit from the tag in the caller's package.json, or from the devkit input.
    • pr-footer.yml: removes the "Generated by Claude Code" footer from pull request descriptions. The script is inline, so a caller needs only the sha.
    • no-bloat.yml: the Knip and jscpd ratchet (devkit-no-bloat). Public repositories call it on every pull request; private ones weekly.
    • worker-size.yml: the Worker bundle size budget (devkit-worker-size).
    • worker-deploy.yml: deploy, verify and roll back a Cloudflare Worker. See "The shared deploy".
    • code-map.yml: the code map for the explorer at capsid.dustinedwards.info. See "The code map".
    • bump-consumers.yml: a package repository calls it on a tag push; it starts a Renovate run in every repository whose package.json depends on that package. It needs the RENOVATE_TRIGGER_TOKEN secret described in its header.
  • testing/ and bin/: the package (@dustinedwards/devkit): shared test helpers and shared checks. See below.
  • test/: CI runs these (npm test).

The shared checks

Bins in the package, so a check runs the same locally (npx <bin>) as in the workflow that calls it. The workflow runs the bin from the caller's own install, so the caller lists @dustinedwards/devkit in its devDependencies at a tag.

  • devkit-no-bloat: Knip and jscpd, each count compared with the baseline in the repository's .no-bloat.json. A count above its baseline fails (exit 1, an ::error:: line each); a count at or below passes, and one that went down is named, with the way to lower the baseline: npx devkit-no-bloat --write-baseline, then commit .no-bloat.json. A deliberate raise is the same command, in a reviewed change. A tool that cannot run fails, naming its error, and so does a config that is not JSON. Tools run from node_modules/.bin when installed, otherwise through npx at the config's tools (default knip@6, jscpd@4). The config's fields are in the header of bin/no-bloat.mjs.
  • devkit-worker-size: runs wrangler deploy --dry-run and fails when its "Total Upload" (raw or gzip, the size Cloudflare counts) is over the budget in the repository's .worker-size.json: { "raw_kib": 1700, "gzip_kib": 400 }, with optional args for the dry run and wrangler (npx spec when wrangler is not installed, default wrangler@4). Cloudflare's upload limits are far higher; the budget is about startup, which must finish inside 1 s. No budget file, a failed dry run or no Total Upload line fails. The workflow's prepare input gives the dry run a config (cp wrangler.jsonc.example wrangler.jsonc, or a build) and working-directory names where it lives. No secret is needed.
  • devkit-check-trailers: with no arguments it reads the range from the GitHub event (EVENT, PR_BASE, PR_HEAD, PUSH_BEFORE, PUSH_AFTER, set by ai-author.yml); locally, npx devkit-check-trailers <base> <head> [--authors]. Exit 1 names each commit and how to fix it; exit 2 means it could not read the commits, which is a failure, not a pass.

Per-repository measurements (capsomer's page weight, dustinedwards-info's route weight, capsid's dashboard size budget) stay in the repository.

The shared deploy

.github/workflows/worker-deploy.yml deploys a Cloudflare Worker, checks it live, and rolls it back when the check refuses it. It is capsid's deploy and live gate jobs and scripts/rollback-guard.mjs, so every Worker site gets what capsid has. A caller (examples/worker-deploy-caller.yml) calls it on a push to its default branch, after its own checks, pinned to a full devkit sha, with a concurrency group that never cancels a deploy in flight.

Two jobs:

  • deploy checks out, installs, runs the caller's pre-deploy guard, runs wrangler deploy with the wrangler version pinned in the file, and publishes the run attempt it shipped in. Never on a pull request.
  • live gate polls the health URL (40 reads, 3 seconds apart) until its JSON sha is this run's commit, then runs the verify command. Exit 0 passes. Exit 1 is a refusal, and only a refusal runs "Roll back the deploy this run shipped": the rollback guard rolls back only when what is live is this run's commit (or /health is unreadable and the deploy ran in this same attempt), runs wrangler rollback, and reads /health until the sha moves. Exit 3, or a /health that never answered, is "could not run": the job fails and nothing rolls back.

capsid's watcher reads the job name live gate, the step name, and the step's three ::error:: sentences ("rolled back: was ...", "not rolling back. ...", "the rollback did not take effect: ...") to write deploy_outcomes, and the step summary carries rollback_from= and rollback_to=. test/worker-deploy-workflow.test.mjs fails on a change to any of them.

Inputs:

Input Required Default What it is
worker-name yes The Worker's name, for wrangler deploy --name and wrangler rollback --name.
health-url yes The live health route. It must answer JSON whose sha is the deployed commit (abbreviated to 7 or more hex characters is fine).
verify-command yes The live checks, run in the working directory with EXPECT_SHA and HEALTH_URL set and no install. 0 passes, 1 refuses, 3 could not run. It gets no Cloudflare secret.
node-version-file no .nvmrc As actions/setup-node reads it, from the repository root.
working-directory no . Where the Worker and its wrangler config live.
install no npm ci The deploy job's install.
pre-deploy no none The caller's guards before the deploy (a migration check, a config rebuild, a build), run with the Cloudflare secrets. A failure stops the deploy.
sha-var no none When set, the deploy passes --var <name>:<this run's commit>, so the Worker can report it on /health.

Secrets, passed by name (secrets: inherit is refused by check-callers.mjs): CLOUDFLARE_API_TOKEN, a token that can deploy and roll back the Worker (Workers Scripts: Edit), and CLOUDFLARE_ACCOUNT_ID. Both are required, and a missing one fails the deploy loudly rather than skipping it.

The limits. Cloudflare's rollback swaps the Worker's code and bindings only. It is refused when D1 or KV resources changed between the two versions, and it never undoes a D1 migration (Cloudflare Docs, "Rollbacks"). A deploy that ran a migration is not made safe by this workflow: keep migrations a separate, human step, and check them in pre-deploy. Data recovery is D1 Time Travel, by hand (wrangler d1 time-travel restore). The rollback goes to the previous version, whatever that is; the guard is what stops a rerun from moving production onto the refused commit.

The guard is inlined into the workflow so a caller's one pinned sha fixes both. After an edit to scripts/rollback-guard.mjs, run node scripts/inline-rollback-guard.mjs; the test fails while the two differ.

The code map

.github/workflows/code-map.yml runs on a push to a public repository's default branch and replaces code-map.json on that repository's orphan code-map branch. Capsid reads it there and the explorer draws it. The script, scripts/code-map.mjs, is inline in the workflow, so a caller pins one sha and needs no release (examples/code-map-caller.yml).

It reads the code through the caller's own TypeScript, so there is no new dependency: imports are resolved with the caller's compiler options, and cyclomatic complexity is counted on the syntax tree (one, plus each if, loop, case, catch, ?:, &&, || and ?? in a function's own body). Coverage is the istanbul JSON Vitest writes (--coverage.reporter=json) or the lcov file of node --test --experimental-test-coverage --test-reporter=lcov, and a Stryker mutation.json adds mutants and a mutation score; both are mapped onto the innermost function holding each statement or mutant. CRAP is CC² × (1 − coverage)³ + CC. A number nobody measured is null, never 0.

Mutation runs only when mutation-command is given, which a caller does on a weekly schedule: that run's Stryker reports (mutation-file, a glob for one report per chunk) are kept as mutation.json on the code-map branch beside the map. Every other run reads that file back and, from the source each report holds, carries a function's numbers forward only while its lines are unchanged; a function that changed or is new is stale: true with no numbers, and the map names the run as mutated: {sha, date}.

Inputs: coverage-command (empty for no coverage), coverage-file, mutation-command, mutation-artifacts (for a caller whose Stryker run is a matrix of earlier jobs: the pattern of the artifacts holding the reports, downloaded from that run into mutation-artifacts/<artifact>/), mutation-file, tsconfig (a repository without one is read as plain JavaScript modules), include (path prefixes to map, separated by spaces, default src/; . for every folder), exclude (prefixes to leave out), install (must install typescript; a JavaScript repository can add it with npm i --no-save typescript), npm-cache (false for a repository with no package-lock.json) and node-version-file. Locally: node scripts/code-map.mjs --out code-map.json [--coverage coverage/coverage-final.json] from the repository's root.

The shared test helpers

Test code that more than one repository needs, written once here. Installed by tag:

"@dustinedwards/devkit": "github:DrDustinEdwards/devkit#v0.7.0"

Only testing/, bin/ and this README are in the package. No dependencies and no test framework: they run in node and in workerd.

  • @dustinedwards/devkit/github: stubGitHub({ owner, repo, branch, files }), a fake of the GitHub contents and Git Data API at the outbound fetch. An unknown host or route throws, blob shas are real, a tree applies only when the ref moves, failNext plants 500s and history plants the commit listing. install: false hands back the fake as fetch instead of installing it globally. gitBlobSha and versionOf come with it.

  • @dustinedwards/devkit/network: refuseNetwork({ hint }), the global fetch that throws naming the URL, and installFetch, the swap under it and under the GitHub fake. Each returns its restore.

  • @dustinedwards/devkit/d1: resetDb(db, { keep }), which empties every table sqlite_master lists in one batch, so a table a migration adds is cleared without anyone adding it to a list. A full-text table is cleared through itself and its shadow tables are left to it; foreign keys are checked at commit, so table order does not matter; d1_migrations, SQLite's and D1's own tables are never touched. It returns the tables it cleared.

  • @dustinedwards/devkit/access: await stubAccess({ issuer, audience, email, authorizationUrl }), a fake of Cloudflare Access for SaaS for one sign-in. The token endpoint answers an RS256 ID token signed by a key made per stub, carrying the issuer, the audience, the email and the nonce from the authorization URL, and the JWKS serves its public half, so the code under test verifies it as it verifies Access's. claims adds to every token, token(extra) signs one directly (also what a test sends as Cf-Access-Jwt-Assertion, with jwksUrl set to the team's /cdn-cgi/access/certs), and tokenRequests holds what the token endpoint received. An unknown URL throws unless fallback answers it.

An entry point is added when a repository adopts it. A release is a tag; a consumer moves to it in its own pull request.

About

Shared Renovate configuration for the DrDustinEdwards repositories.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages