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

@magnaboy/cli-repo

v0.0.2

Published

Repository script entrypoints, paths, argument rules and package-manager commands for Node.js CLIs.

Readme

@magnaboy/cli-repo

The runtime a repository's own scripts sit on: entrypoints that know the checkout, the three directories a path can be relative to, the argument rules Commander cannot own, and package-manager commands that survive Windows.

Install

npm i @magnaboy/cli-repo

Requires Node 25+ and ESM. commander is a peer dependency, needed only for runRepositoryCommand and parseCommand.

import { createRepository, runRepositoryCommand } from '@magnaboy/cli-repo';
import { packageManagerInvocation } from '@magnaboy/cli-repo/package-manager';

The repository

import { createRepository } from '@magnaboy/cli-repo';

const repository = createRepository(import.meta.url);
repository.root; // the checkout
repository.fromRoot('apps', 'web'); // a path the repository owns
repository.sibling('other-repo', 'src'); // a checkout beside this one

createRepository derives everything from the calling module's own location, never from process.cwd(). A script started from a package subdirectory, or through pnpm --filter, still resolves repository paths correctly. A module that lives in scripts/lib/ instead passes its own two locations:

export const repository = createRepository(import.meta.url, { root: '../..', scripts: '..' });

Three directories, not one

A script deals with three, and using the wrong one is the usual source of "works for me" bugs:

  • repoRoot — for anything the repository owns. Build outputs, config, checked-in fixtures.
  • workingDirectory — where the process is running. pnpm changes this for a filtered script, so it is the right base for package-local inputs and nothing else.
  • invocationDirectory — where the person was when they typed the command, recovered from INIT_CWD. A relative path in argv is relative to this, not to either of the others.
const apk = resolveWorkingPath(options.apk, context.invocationDirectory);

Entrypoints

import { createRepository, runRepositoryCommand } from '@magnaboy/cli-repo';
import { Command } from 'commander';

const repository = createRepository(import.meta.url);

await runRepositoryCommand(repository, ({ repoRoot, runner }) =>
	new Command('build').option('--fast').action(async (options: { fast?: boolean }) => {
		await runner.run({ command: 'echo', args: [repoRoot], output: 'tee' });
	})
);

runRepositoryScript is the same without Commander, for a script that parses its own arguments. runInteractiveRepositoryScript adds a Prompter that is closed even when the script throws.

All three delegate to runScript from @magnaboy/cli-core, so SIGINT exits 130, a CliError exits 2 with one line and no stack, and cleanup runs on every path. A Commander program built this way needs no usageHint: --help exits 0 and a usage error prints once.

Running a subcommand program with no arguments prints its help and exits 0. Commander's own behaviour there is to write help to stderr through a channel this suppresses, which would otherwise print nothing at all.

Argument rules

Commander should own argument parsing. These cover the cases where it cannot: a parser that forwards unrecognised arguments to another tool, such as passing everything it does not know to Gradle.

import { requireList, requireValue, usageRequested } from '@magnaboy/cli-repo/argv';

if (usageRequested(args, usage)) return 0;
const device = requireValue(args, index, '--device');
const suites = requireList(args, index, '--suites'); // "a, b c" -> ['a', 'b', 'c']

requireValue refuses the next argument when it is itself an option, which is what turns --device --verbose into a usage error instead of a device named --verbose. Set rejectAnyDash for parsers whose short flags would otherwise be swallowed, and allowEmpty for an option whose value may legitimately be blank.

parseCommand is for a parser that returns a value rather than dispatching. It stops Commander writing or exiting, returns true when help was requested, and reports a usage error as a CliError. It applies exitOverride to every subcommand, because Commander copies that setting when .command() creates a child rather than looking it up on the parent.

Package managers

import { createRecursiveScriptCommand, requirePackageManager } from '@magnaboy/cli-repo/package-manager';

const pnpm = await requirePackageManager(runner);
await runChecked(runner, createRecursiveScriptCommand({
	executable: pnpm,
	host: await detectHost(runner),
	cwd: repository.root,
	script: 'build',
	workspaceConcurrency: 4
}));

On Windows pnpm, npm, yarn and bun are all .cmd shims, which cannot be spawned directly; packageManagerInvocation reaches them through cmd /d /c. Arguments stay a list on every host, so a version range like left-pad@>=1.0.0 <2 arrives as one argument instead of being split by a shell. requirePackageManager reports a missing tool as a PrerequisiteError rather than letting it surface as a spawn failure.

createRecursiveScriptCommand requires workspaceConcurrency rather than defaulting it. The right number depends on what the script does, and pnpm's one-per-core default melts a machine running database or Gradle work under every package at once.

Transcript

import { Transcript } from '@magnaboy/cli-repo/transcript';
import { Workflow } from '@magnaboy/cli-core';

const transcript = new Transcript(repository.fromRoot('meta/build.log'));
transcript.reset();
const workflow = new Workflow(transcript);

A WorkflowOutput that writes to the terminal and appends the same text to a build log. The appends are synchronous: a build killed mid-step must still leave the log of everything before it, and an async write can be lost when the process exits. If the log becomes unwritable the build carries on and the transcript says so once, rather than failing the build over its own logging.

Run naming

import { runTimestamp, safeArtifactName } from '@magnaboy/cli-repo/naming';

const directory = `${runTimestamp()}-${safeArtifactName(label)}`; // 20260917T123456Z-wifi_rails

Stamps are UTC so two machines name the same run the same way. safeArtifactName reduces a label to characters every filesystem accepts.