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

@actionforge/git

v0.1.0

Published

Standalone git helpers — pure ref/refspec parsing plus promise-based git command wrappers built on node:child_process, with no GitHub Actions runtime required

Readme

@actionforge/git

Git helpers with two halves: a set of pure ref/refspec parsers with no I/O at all, and promise-based command wrappers built directly on node:child_process.

Why this exists

This started as a GitHub Actions helper library, which meant every git call went through @actions/exec and every log line through @actions/core. Both of those only behave correctly inside an Actions runner — which made the ref parsing (the genuinely useful, genuinely tricky part) unusable from a plain script.

So the Actions coupling is gone:

  • No @actions/exec. Commands run through execFile with a real argv array — never a shell string — so arguments containing spaces, quotes, or $(...) need no escaping and cannot inject extra commands.
  • No @actions/core. Logging goes to an injectable Logger that defaults to silent. Pass consoleLogger, the global console, or a core-shaped object if you want the Actions annotations back.
  • No implicit environment. Nothing reads GITHUB_TOKEN, GITHUB_ACTOR, or GITHUB_REPOSITORY. Credentials are ordinary function parameters.

The result works the same from a laptop script, a Docker build, or CI.

Install

npm install @actionforge/git

Ref parsing (pure, no I/O)

Every parser accepts a RefLike — a ref string, or any object with a ref string property (a webhook payload, an Octokit response).

import { getBranch, getRefspec, getTag, isPrRef } from '@actionforge/git'

getBranch('refs/heads/main')            // 'main'
getBranch('refs/remotes/origin/main')   // 'main'
getBranch('refs/tags/v1.2.3')           // ''
getBranch('refs/tags/v1.2.3', false)    // 'tags/v1.2.3'

getTag('refs/tags/v1.2.3')              // 'v1.2.3'
isPrRef('refs/pull/123/merge')          // true

getRefspec('refs/heads/main')
// 'refs/heads/main:refs/remotes/origin/main'
getRefspec('refs/tags/v1.2.3')
// 'refs/tags/v1.2.3:refs/tags/v1.2.3'
getRefspec('refs/pull/123/merge')
// 'refs/pull/123/merge:refs/remotes/origin/pull/123/merge'

| Function | Purpose | | --- | --- | | getRef(ref) | Normalise a RefLike to its string | | isRef / isBranch / isRemoteBranch / isTagRef / isPrRef | Classification | | normalizeRef(ref) | Fully qualify a bare name (assumes a branch) | | getRefForUpdate(ref) | Strip the leading refs/ | | trimRef(ref) | Strip refs/heads/, refs/tags/, or refs/pull/ | | getBranch(ref, defaultIsEmpty?) | Branch name, or '' | | getTag(ref) | Tag name, or '' | | saveTarget(ref, origin) | Local namespace a ref belongs in | | getRemoteRefspec / getLocalRefspec / getRefspec | Refspec construction |

Running git

import { allGitTags, gitTagExists, revParse, runGit } from '@actionforge/git'

await revParse()                 // HEAD sha
await revParse('v1.0.0')         // tag sha
await revParse('nope')           // '' — unresolvable refs are not an exception

await allGitTags()               // ['v1.0.0', 'v1.1.0', ...]
await gitTagExists('v1.0.0')     // true

// Escape hatch for anything not wrapped here:
const { stdout } = await runGit(['log', '-1', '--format=%H'], { check: true })

Every command helper accepts GitExecOptions: cwd, env, timeout, logger, gitPath. runGit additionally takes check — by default a non-zero exit is returned as data ({ exitCode, stdout, stderr }), because most callers want to branch on it; check: true throws a GitCommandError carrying the exit code, both streams, and the argv.

Identity

import { setGitIdentity } from '@actionforge/git'

await setGitIdentity({
  name: 'release-bot',
  email: '[email protected]',
  cwd: workDir,
})

The original took an Octokit client and looked the name and email up from a GitHub login. That lookup is your business now — do it in your own code and pass the result in, so this function stays usable without a network call or a GitHub account.

Remotes and authentication

import {
  addOrigin,
  buildAuthenticatedRemoteUrl,
  fetchOrigin,
  remoteExists,
  setRemoteUrl,
} from '@actionforge/git'

const url = buildAuthenticatedRemoteUrl({
  owner: 'octocat',
  repo: 'hello-world',
  token: myToken,        // plain parameter — nothing is read from the env
})

if (await remoteExists({ cwd: workDir })) {
  await setRemoteUrl({ cwd: workDir, url })
} else {
  await addOrigin({ cwd: workDir, url })
}

await fetchOrigin({
  cwd: workDir,
  args: ['--depth=1', '--no-tags'],
  refspec: [getRefspec('refs/heads/main')],
})

username defaults to x-access-token (what GitHub expects for an app installation token or an Actions-issued token); pass a real login for a classic PAT. host defaults to github.com for GitHub Enterprise. Both the username and token are percent-encoded, so a credential containing :, /, or @ cannot corrupt the URL.

Security note: a token embedded in a remote URL and written into .git/config stays readable on disk for the lifetime of the checkout. On an ephemeral CI runner that is usually acceptable; anywhere else, prefer a git credential helper.

Logging

import { consoleLogger, runGit } from '@actionforge/git'

await runGit(['status'], { logger: consoleLogger })

Logger is a structural interface (debug / info / warn / error), so the global console satisfies it directly, as does @actions/core. The default is silentLogger — a library should not print unless asked.

License

MIT © Andrew Cates