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

@devdogsuga/backstage

v0.1.14

Published

Officer and production CLI for DevDogsUGA: deploys, env sync with Bitwarden and GitHub, the migration planner role, club graphics and QR codes, GitHub rulesets and settings, and the newsletter. Ships built JS; run it anywhere with `pnpm dlx @devdogsuga/ba

Readme

@devdogsuga/backstage

The officer and production CLI: everything that needs production secrets and is used only by the two people who hold them (and CI), plus the tools that need only the user's own login. Run it anywhere:

pnpm dlx --config.minimum-release-age=0 --config.dlx-cache-max-age=0 @devdogsuga/backstage

It always runs the latest publish, CI included, so local runs and CI runs match. The two flags matter outside a DevDogsUGA checkout, where the workspace's minimumReleaseAgeExclude and dlxCacheMaxAge: 0 do not apply (inside one, pnpm backstage is the script).

It starts without a checkout. Help, version, completions, the tools below that need no secrets (graphics, qr, github, newsletter, creds), import, export and anything run with --no-env never look for one; a command that reads a checkout says "run this from inside a DevDogsUGA clone" and exits 1. The DevDogsUGA libraries it reads (@devdogsuga/env, @devdogsuga/db) are optional peers, resolved through the checkout by the commands that need them.

pnpm backstage                                   # no arguments: a menu
pnpm backstage env pull --target production      # Bitwarden → .env.production
pnpm backstage env audit --target production     # every store, plus orphaned Worker secrets
pnpm backstage env audit --target production --prune
pnpm backstage deploy platform --tier staging    # token check, secrets file, wrangler deploy
pnpm backstage --no-env deploy write-env         # the CI steps that supply their own environment
pnpm backstage --no-env planner status           # the preflight credential, from DB_URL
pnpm backstage graphics 'event/*' --out ~/images # club images, no checkout
pnpm backstage qr https://devdogsuga.org --format svg,png,webp --logo acm
pnpm backstage newsletter send 3.0.1 --to [email protected]   # asks first; --yes with no terminal
pnpm backstage creds                             # share a club login with officers, as a Bitwarden Send
pnpm backstage creds renew                       # extend every Send 30 days
pnpm backstage import involvement --file OrganizationRoster.csv  # verify members; previews, then asks
pnpm backstage import attendance --meeting 2026-09-09 --file sign-in.csv
pnpm backstage export stars --from 2026-08-17   # audited under your gh login
pnpm backstage export attendance --meeting 2026-09-09 --format bevy,involvement

Commands

| Command | What it does | | -------------------------------------------------- | -------------------------------------------------------------------------------------- | | deploy <app> --tier <t> | Checks CLOUDFLARE_API_TOKEN, writes the Worker's secrets file, deploys, removes it. | | deploy write-env | Composes .env.<DEPLOY_ENV> from the GitHub environment. | | deploy preflight | Classifies the project: paused (skip) or broken (fail). | | deploy plan, deploy migrate [--include-seed] | Dry-run the migrations (and new seed files) into the job summary; apply them. | | deploy avatars | Uploads seeded headshots the bucket lacks; never replaces one (SECRET_KEY). | | deploy smoke --tier <t> [--app] | Public routes answer 200, the auth redirect works, this deploy's Sentry release shows. | | deploy reconcile --tier <t> | The platform's config reconcile, after the deploy (CRON_SECRET). | | deploy prune-monitors --tier <t> [--app] | Deletes the app's Sentry Crons monitors it no longer declares (production only). | | env pull\|push\|audit --target <t> | One env file per target, synced to Bitwarden and GitHub. audit lists orphans. | | planner status\|create\|reset-password\|drop | The migration_planner role the preflight tier holds. | | graphics [graphic…] | Club images from @devdogsuga/brand: brand/*, app/*, event/*. | | qr <text> | QR codes with every option of /console/qr. | | github rulesets\|settings | Diff (and with --apply write) GitHub config, through gh. | | newsletter render\|draft\|send <issue…> | Changelog issues as files, mailbox drafts, or a send. | | creds send\|add\|renew\|list\|report | Club logins from Bitwarden as email-verified Sends, and the Linear report. | | import involvement --file <csv> | Verifies the members on the Involvement Network roster, unverifies everyone else. | | import attendance --meeting <day> --file <csv> | Records a meeting's attendance from a sign-in sheet (method import). | | export stars\|attendance\|reflections\|responses | Member data as CSV, each export audited under the officer's gh login. |

smoke and reconcile replace DevDogsUGA's packages/deploy-checks. The per-app data (hosts, public paths, the protected path and its redirect) stays in DevDogsUGA, as a smoke field on each app's entry in workers.json:

[
  {
    "path": "apps/platform",
    "smoke": {
      "hosts": {
        "staging": "staging.devdogsuga.org",
        "production": "devdogsuga.org"
      },
      "publicPaths": ["/", "/events"],
      "protectedPath": "/console/permissions",
      "protectedRedirectPrefix": "/auth"
    }
  }
]

Entries may be bare paths or objects. While the field is absent, the table deploy-checks carried answers for platform and schedule-builder.

The tools that need no production secrets

None of these reads an env file or needs a checkout, so the launcher skips env entry for them (envFree in the command tree).

  • graphics renders the brand, app and event images with @devdogsuga/brand/render, reading meetings from the published @devdogsuga/events. Files go to --out or the current directory, flat, as <name>-<format>.png. There is no page/* group (the platform renders page cards per request) and no --default-out.
  • qr takes its options from qrRequestSchema in @devdogsuga/brand/qr, the same schema /console/qr parses, so a new option reaches both. Every schema field has a flag (qr/options.ts is typed over the schema, and a test checks it): content, --size, --margin, --color, --background, --gradient, --shape, --error-level, --qr-version, --logo (a preset, none, or an image file) with --logo-size, --logo-padding and --logo-crop x,y,w,h, and --format with any of svg, png, jpg, webp, avif and tiff at once. It prints the same scannability warning as the page. /console/qr stays, deprecated; new options are CLI-only.
  • github rulesets|settings keep GitHub's rulesets and repository settings as code, through gh and your own login. They must keep matching what the platform's server/github/rulesets.ts and teamSync.ts assume.
  • newsletter render|draft|send are described in the newsletter package. Drafts and sends use the club mailbox ([email protected]) with the officer's own Microsoft sign-in; there is no --mailbox. send needs --to and always asks first, naming the issue and every recipient; with no terminal, --yes answers it. The sign-in borrows Thunderbird's public client ID (see src/newsletter/oauth.ts); if Microsoft or UGA's tenant blocks it, sending needs a club-owned app registration.

Shared logins: creds

The club's shared logins (Instagram, Canva, ArchPass, Linktree, later project API keys) live in the DevDogs Bitwarden organization, in whatever collection suits them. creds hands any login or secure note there to officers as a Bitwarden Send. Personal items, other organizations' items and production secrets (which are in Secrets Manager, not the vault) are out of its reach.

  • send picks items and recipients (officers by name or by role, read live from production: everyone holding a role other than Member, by UGA MyID email), shows a preview with the password masked as ••••••••, then creates or updates one Send per item, restricted by email verification to its recipients and deleted 30 days out. Removing an address revokes it. Addresses off the roster need --allow-email. Non-interactive: --item, --to, --role, --yes.
  • add saves a new login to the organization, then sends it. It goes in the one collection you can see, or the one you pick (--collection with no terminal). The password is typed at a hidden prompt, or read with --password-stdin; never argv.
  • renew pushes each Send's deletion 30 days out in place (the link stays) and re-syncs its recipients and body from the item. It asks first for any Send with more than 7 days left.
  • list prints items, recipients and expiry (--json for scripts).
  • report regenerates the Shared Accounts Linear document (Platform & DevOps initiative), which send, add and renew also do. It lists only items that have been shared, and is generated whole; edits there are overwritten.

Each item's custom fields are the only access record: Recipients, Owner, Send ID, Send link, Send expires, Send account. Sends belong to the Bitwarden account that creates them, so creds runs on your own bw session (signing in and unlocking after asking, or BW_SESSION), never on CI; renewing a Send another officer made creates a new one, with a new link, under yours.

The roster needs production's DB_URL: --db-url, else .env.production in a checkout, else Secrets Manager. The Linear key: --linear-token, then LINEAR_API_KEY, then the item "DevDogs Linear API key (backstage)" in your personal vault, then a prompt, which offers to save it there. Use your own key, never one shared through the DevDogs organization: Linear records every edit under the key's owner, and creds ignores organization items of that name.

Member data: import and export

These replace the platform's /console/verification upload and /console/exports downloads. They read and write production only: DB_URL, API_URL and SECRET_KEY come from .env.production in a checkout, else Secrets Manager. There is no --db-url, since a database override would leave account creation pointed at production. Days (--meeting 2026-09-09, --from, --to) are Eastern, and --to includes the whole day.

import involvement

Replaces the platform's /console/verification upload. Export the roster from the UGA Involvement Network (Roster → Export → Organization Roster) and pass the file as it downloads: the title lines above the header, the (Hidden) columns and the one-row-per-position layout are all expected. A roster for another organization is refused.

The roster is the whole truth: everyone on it is verified, and everyone verified who is not on it loses that status. A roster email finds its account by the profile's UGA email first, then the sign-in address; with no account, one is created (confirmed, no invite email) so the member is verified the first time they sign in. Every run previews the counts, who is newly verified, who loses verification, and whose preferred name differs from the roster, then asks; --dry-run stops there, --yes answers with no terminal. Preferred names are never changed. Rerunning the same export changes nothing.

import attendance

Records a meeting's attendance from a sign-in sheet, for members who could not check in on the platform. The sheet is a Google or Microsoft Forms response export or a hand-made spreadsheet, saved as CSV; one sheet is one meeting, named with --meeting (its day, slug or id; a day with two meetings asks for the slug). The header and columns are found, not assumed: the email column is the one holding UGA addresses, the name is "First/Last Name" or a column mentioning "name", and --email-column/--name-column override either. A sheet with no header is read as every address in it. Only UGA addresses count; anything else is listed and skipped.

Check-ins are written with method import, stamped with the meeting's start, and count for stars like any other. A member's own check-in is never touched. Someone with no account gets one (confirmed, no invite), named from the sheet; with no name on the sheet they are listed and skipped. Cancelled and future meetings are refused. Rerunning a sheet records nothing new; --replace makes the sheet the meeting's whole imported set, removing earlier imported rows it no longer lists. Every run previews, then asks (--dry-run, --yes).

export stars|attendance|reflections|responses

The platform's three CSVs, columns unchanged: stars (one row per earned star), attendance (one row per check-in; --meeting for one night) and reflections (each member's current text). responses is the check-in survey: one row per answer, worded with each question's current labels beside the stored value. With --meeting it is that meeting's answers plus its attendees' member answers as they stood when it ended (from the answer history); --format wide writes one meeting as one row per person and one column per question. Each export writes a platform.exportAudit row before reading anything, attributed to the platform account linked to the GitHub login gh is signed in as, and refuses to run when there is none.

attendance can also be written as a Bevy attendee import (GDG's gdg.community.dev; one row per person, checked in, with a survey: column for each question mapped to one in questions.json that anyone answered) and an Involvement Network list (one MyID email per line), both for one --meeting. --format platform,bevy,involvement picks any of them, written from one read with an audit row each; at a terminal it is asked. At a terminal each file's destination is asked for too, with path completion (Tab completes the highlighted suggestion; a folder means the suggested name inside it). Otherwise files go to ./<kind>…[-<format>].<ext>, or --out (a file, a folder for several, or - for stdout). Files are owner-readable only and never replace an existing file without --yes or a yes at the prompt.

No secret value reaches stdout, stderr, the failure log, Sentry, an error message or argv: values go to bw as base64 JSON on stdin, every error is scrubbed of every value read so far (src/creds/secrets.ts), and the clipboard only ever gets the link. src/creds/commands.test.ts runs every path against a fake bw with a sentinel password and looks for it in all of those places.

Tiers, --no-env, CI

Nothing is ever asked at launch. --tier <t> (anywhere in argv) or DEPLOY_ENV names the session's tier and so the env file loaded; with neither the session is plain development (where BWS_ACCESS_TOKEN lives). deploy refuses to run without one of the two.

--no-env loads no env files and does not look for a checkout: the caller's environment is the environment. It is for deploy write-env, which creates the file tier resolution would otherwise insist on reading, and for CI steps that hold one narrow credential in the job's env: block.

With no terminal, or CI=true: no menu, no banner, plain lines, errors on stderr, and every confirmation needs --yes. Against staging or production a command that is not read-only asks once first (--yes answers it).

Each command checks for the secrets it uses up front: deploy <app> for CLOUDFLARE_API_TOKEN, deploy reconcile for CRON_SECRET, deploy prune-monitors for SENTRY_MONITORS_TOKEN (skipped without it), env for the Secrets Manager token (flag, environment, then your personal Bitwarden vault, which env signs in to and unlocks itself, then asking). The token is each person's own: the vault read skips organization items, and a save sets no organization, so one person's token can be revoked without the rest.

Layout

Like devtools: src/<domain>/catalog.ts (inert data) and commands.ts; src/catalog.ts composes the tree, src/cli.ts maps names to handlers, src/launch-core.ts settles the tier before any command is imported. The shared core is the private @devdogsuga/cli-core, which tsdown inlines; every other import must be declared here, and the build fails when one is not.

env.ts is the operator manifest (BWS_ACCESS_TOKEN, CLOUDFLARE_API_TOKEN and friends). It is a copy of devtools' own, kept identical by a test, because each CLI loads the one beside it.

Contract tests (pnpm test:contract) pack the real tarball, install it with install scripts off outside any repo (and check the Bitwarden native module loads), and run it again inside the fixture repo. They gate publishing.

Crash reporting is the same as devtools' (Sentry, scrubbed, off with DEVTOOLS_TELEMETRY=0); the release is named backstage@<version>.