npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@titan-design/github

v0.10.0

Published

GitHub REST port over the gh CLI: validated paths, required checks from branch rules, and an in-memory fake

Readme

@titan-design/github

GitHub REST port over the gh CLI: validated paths, required checks from branch rules, and an in-memory fake

Tier 1 of the titan-platform DAG. May import only packages in the same tier or below; the package-layers rule in .codewatch/check.json enforces this in CI.

GitHubWire is one GitHub call per method, unconditional, the way GitHub itself behaves. ghCliWire() implements it by running gh api with an argv array, never a shell, on the caller's existing gh login; nothing here reads or passes a token. githubPort(wire) puts check-then-act on top: every write reads first and reports { done: false, skipped } when its effect is already in place, so a step that repeats after a crash repeats no effect.

| Write | Reads first | Guard GitHub enforces | | --- | --- | --- | | ensureBranch | the ref | none | | deleteRef | the head's repo, the default branch, then the ref; a fork head or the default branch skips | none | | putFile | the file on the branch; identical content skips | sha = expected blob | | openPr | open, then merged, PRs for the head | none | | updateBranch | the PR: merged, head moved or not behind skips | expected_head_sha | | pushEmptyCommit | the branch's ref: absent or moved past the expected head skips | the ref moves only as a fast-forward | | merge | the PR: merged returns the stored merge SHA; a moved head skips | sha = the approved head | | rerunFailed | the Actions run; not completed skips | none | | upsertComment | the PR's comments; one containing the marker skips | none |

githubPort validates every argument before any wire call, because each one becomes part of a gh api path. It checks five things. The repo is owner/name of [A-Za-z0-9._-], and neither part is . or ... A branch or ref follows git's ref-name rules and has no ?, # or %. A sha is 40 lower-case hex characters. A PR number or run id is a positive safe integer. A content path is relative, with no ., .. or empty segment and no ? or #. A bad value throws GitHubInputError naming the field, and gh never runs.

requiredChecks reads the branch's active rulesets (rules/branches/<base>), never a hardcoded list. latestCheckRuns keeps the newest run per check name, because one head can carry a success and a later superseded cancelled run. behind comes from the compare API. mergeReadiness (and headCheckFindings under it) is the one rule for whether a required check passed: a required name needs a completed success run, so a skipped or neutral run, or no run at all, never passes.

mergeReadiness({ pr, rules, runs, requiredApps, approvedHead }) is pure. It is ready only when the PR is open, not a draft, not conflicting, up to date when the rules are strict, at exactly the approved head, the rules name at least one required context, and every required context has a completed run concluding success from an app in requiredApps (GITHUB_ACTIONS_APP_ID, 15368, on this owner's repos) at pr.headSha. A run from any other app, or at any other sha, never counts. Every counted run must be green (success, neutral or skipped), superseded or not, required or not, so an older red run of a re-run check still blocks and so does one still queued or in progress. Pass it every run, from checkRuns, never latestCheckRuns. Every not-ready result lists blockers, each with a reason and a detail.

Every call runs gh api -i, so the wire sees the status line and headers. A GET sends the ETag of the same request's last 200 as If-None-Match, and a 304 answers with that cached body. Lists follow Link: rel="next" page by page, each page conditional on its own ETag, and only to api.github.com. One RateBudget per process (sharedRateBudget, or pass budget) reads x-ratelimit-*; below 500 remaining core calls, each call waits the time left until reset divided by the calls left. jobLogTail(repo, jobId, lines) reads an Actions job log. listOpenPrs(repo, headPrefix?) lists open PRs; list rows carry no behind or mergeableState.

listPrFiles(repo, pr) returns every changed file of a PR, all pages, with previousPath on a rename. GitHub stops that list at 3,000 files without saying so, so the port compares the count with the PR's own changed_files and throws FileListTruncatedError when fewer came back. A caller must treat that error as "cannot decide" (for example, hold the PR for a human), never as an empty or partial list. compareFiles(repo, base, head) returns { mergeBaseSha, files, truncated }. GitHub caps compare at 300 files and 250 commits, also silently; truncated is true when files reaches 300 or the commits returned are fewer than total_commits. When it is true, files may be missing paths: fall back to listPrFiles for a PR, or treat the result as unknown. listPrCommits(repo, pr) returns the PR's commit shas oldest first; GitHub stops at the first 250 (PR_COMMITS_CAP), so a list whose last sha is not the head is short. listDefaultBranchCommits(repo, since) returns { sha, message } for each default-branch commit committed at or after since, newest first, all pages. listForcePushes(repo, pr) returns the PR's head force-pushes oldest first, each as { before, after }: the head it replaced (null once GitHub no longer has that commit) and the new head. REST timeline events name only the new head, so this one read posts to GraphQL (HeadRefForcePushedEvent) on the same gh login. It reads one page of FORCE_PUSHES_CAP (100) and throws ForcePushesTruncated when there are more, never a short list. The fake reads fake.forcePushes, a map of PR number to pushes. upsertComment(repo, pr, marker, body) lists the PR's comments first and posts only when none by the authenticated gh user has marker (an HTML comment the caller builds, also put in body) alone on a line; trailing whitespace on that line still counts. Another author's comment or a longer marker never counts. Under a GitHub App installation token, GET /user is 403, so pass the app's bot login: githubPort(wire, { login: "my-app[bot]" }) (GET /app needs an App JWT, so config is the only path that works with the installation token). Two concurrent callers can both post; there is no lock. Each read carries the same ETag cache and rate budget as the others.

formatSquashMessage({ title, body, prNumber, taskIds, commits }) is pure and builds a squash commit message: a subject of the PR title, any task id the title does not already name, and (#<pr>); a body of ## Summary (the PR body), ## Changes (one - **<subject>.** <body> bullet per commit) and Refs: <task ids>, #<pr>. Author text passes through as written: only a plain paragraph in a commit body is joined onto one line, and headings, setext underlines, lists, blockquotes, tables, HTML and code keep their own lines. It drops Co-authored-by and Signed-off-by trailers, the "Generated with Claude Code" line, standalone email addresses and any commit merging main into the branch, but never touches code (fenced, indented or inline) or an address inside a URL or ssh remote. GitHub's own squash text is recognised only in a commit whose subject ends (#<n>), the one place GitHub writes it: there a --------- line followed only by trailers is dropped and a * <subject> header standing alone loses its star, so an author's lists and rules pass through everywhere else. An empty PR body omits ## Summary, and an empty title leaves the subject as (#<pr>). The same input always gives byte-identical output, and formatting its own output again (as title and body) changes nothing: only the exact Changes and Refs tail it would write is cut from the PR body, so an author's own ## Changes or Refs: stays. The titan-squash-message bin reads the same input as JSON on stdin and prints the subject, a blank line and the body, or { subject, body } with --json; bad input exits 2 naming the field.

ciVerdict({ rules, runs, statuses }) is pure and folds a sha's check-runs and commit statuses into { verdict: "green" | "red" | "pending", reasons }. With required contexts, each must be success from its pinned app, else from GitHub Actions (15368) or a commit status; a skipped or neutral required context is red, and runs that are not required are ignored. With none, every run and status must pass and at least one check-run must exist. loadRequiredContexts(port, repo, branch) reads rulesets, then classic protection; both reads treat a free-plan 403 ("Upgrade to GitHub Pro") as no rules and throw on any other 403. resolveCommit(repo, sha) expands an abbreviation (null when GitHub has no such commit) and commitStatuses(repo, sha) reads the combined status. ciWait(port, options) polls those until green, red or the deadline, reading the rules once and retrying a failed read. The titan-gh bin wraps it:

titan-gh ci-wait <owner/repo> <sha> [--base <branch>] [--timeout <seconds>] [--interval <seconds>]

It prints GREEN <repo> <sha>, RED <repo> <sha>: <name>=<conclusion>; ... or TIMEOUT <repo> <sha> after <n>s: pending <names>, and exits 0 green, 1 red, 2 timeout (run it again) and 64 on bad arguments or a sha GitHub does not have. Defaults: 540 second timeout, 30 second interval, the repo's default branch.

Its pr-status and pr-context verbs read one PR over the caller's gh login, in place of gh pr view --json piped to jq. titan-gh pr-status <owner/repo#N> prints one line: state (open, draft, closed or merged), the head's first seven characters, mergeableState, how many required checks passed with each non-passing one as name:state, and ready or the mergeReadiness blocker reasons. Required checks come from the base's rulesets, or from classic protection when the rulesets require none. Approval lives in Shepherd, not on GitHub, so it never reports unapproved. titan-gh pr-context <owner/repo#N> prints the title, base, head sha, changed files and body for a reviewer; past GitHub's 3,000-file cap it gives the count and lists none. --json prints either as one object; an unknown verb or a ref that is not owner/repo#N prints usage and exits 64.

fakeGitHub() is an in-memory GitHubWire with effect counters, for tests only.

Reference: site/reference/github.md.