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

@nage-api/cli

v1.0.0-beta.4

Published

The nage CLI — scaffold and manage a @nage-api workspace, its apps, packages and resources

Readme

@nage-api/cli

The nage command (PLAN.md §10): scaffolding that produces a production-shaped workspace, and workspace management that keeps it coherent as it grows.

The legacy generator wrote a project and then let it drift: --noSpec was the habit, wiring a new module was a manual edit in three files, and nothing checked that the result still made sense. Here every command is a transaction over the whole workspace, every generator that produces behaviour also produces its test, and nage doctor reports through the same audit that blocks an insecure production boot.

Commands

| Command | What it does | | --------------------------- | --------------------------------------------------------------------------- | | nage create <name> | Scaffold a workspace seeded with one app | | nage new app <name> | Add an app — its own preset, port and features | | nage new package <name> | Add a shared local package and wire its alias | | nage g resource <name> | Entity, DTOs, service, controller, module, tests and a migration | | nage g <schematic> <name> | module, service, controller, entity, dto, migration, seed | | nage add <feature> | Enable auth/cache/queue/storage/realtime/notify/observability | | nage remove <feature> | Disable one, dependency and all | | nage remove app <name> | Unregister and archive the directory as <name>.removed | | nage remove package <n> | Unregister and drop the alias; the directory is left where it is | | nage list / nage info | What exists; what versions a bug report needs | | nage doctor | Workspace integrity + the secret audit, exit 1 when blocking |

Flags: --app <name>, --all, --dry-run, --db, --first-app, --preset, --port, --fields, --route, --package, --no-migration, --no-spec, --legacy, -h, -v. parseArgs needs the whole option set declared up front, so every flag parses on every command and a command ignores the ones it does not read. --app is usually unnecessary for g — the current directory says which app is meant — and doctor accepts it but does not act on it.

g entity, g dto, g migration and g seed emit no spec of their own: they produce a declaration, and the tests belong to the service and controller that use it, which g resource, g service and g controller do emit.

Every command, every flag with its type and default, every file each one writes, and each doctor check individually: docs/cli-reference.md.

Four ideas

Every command is a plan. A command builds a FileTree and returns it; the caller commits it. That is what makes --dry-run exact rather than a best-effort preview, and what lets the whole surface be tested without spawning a process:

import { commitFileTree, loadWorkspace, planGenerate } from '@nage-api/cli';

const { root, manifest } = await loadWorkspace(process.cwd());

const plan = await planGenerate({
  root,
  manifest,
  schematic: 'resource',
  name: 'product',
  appName: 'api',
});

const willBeWritten: readonly string[] = plan.tree.paths; // exactly what lands
await commitFileTree(plan.tree, { root, dryRun: false });

Writes are atomic. commitFileTree refuses the whole batch before writing anything, backs up what it replaces, and rolls back on any failure. Adding a shared package touches four files at once — nage.workspace.json, the alias in tsconfig.base.json, the packages/* glob in pnpm-workspace.yaml and the project reference in the root tsconfig.json — and a workspace where three of the four landed is worse than a command that failed cleanly: the alias resolves in the editor but not in pnpm install, and nothing says why.

The rollback covers a failed write, not a killed process: there is no signal handler, so Ctrl-C mid-write leaves what had landed. And remove app archives the directory after the commit rather than inside it, so an archive that fails leaves the app unregistered.

Generators are idempotent. Re-running one writes only the files that are missing (onConflict: 'skip'); edited code is reported as left alone, never clobbered. The module-wiring edit to app.module.ts is deliberately conservative: when the file no longer matches the shape the CLI emits, it prints an instruction rather than mangling it.

The one exception is the migration. Its filename carries a fresh timestamp, so it can never collide with the previous one, and re-running g resource product leaves two migrations that both create the products table. Pass --no-migration when re-running a generator over a resource that already exists.

Sub-generators cannot drift. g service is a filter over the file set g resource would produce, so the two are byte-identical by construction — a property the suite asserts rather than a convention to remember.

nage.workspace.json

The manifest is the source of truth, serialised deterministically so a rewrite produces an empty diff:

{
  "version": 1,
  "name": "shop",
  "engine": "postgres",
  "frameworkVersion": "^0.1.0",
  "apps": [{ "name": "api", "preset": "api", "port": 3000, "features": ["cache"] }],
  "packages": [{ "name": "domain", "alias": "@app/domain" }]
}

One engine per workspace (§10.2): apps share packages/domain entities and database/migrations, which only stays coherent with a single engine. The driver package is chosen at scaffold time — a Postgres workspace never installs the Mongo driver.

What the generated workspace looks like

nage create shop writes a monorepo that is ready to deploy, not ready to configure: strict TypeScript with project references, Turborepo, ESLint, Prettier, a CI workflow, a multi-stage non-root Dockerfile, a .env.example, and an app whose main.ts validates its environment before binding a port. The .env.example covers NODE_ENV, CORS_ORIGINS, DATABASE_URL and a <APP>_PORT line per app; it does not cover PORT, which is the variable the generated env schema actually reads.

Presets: minimal, api, api-realtime, worker. A worker has no HTTP surface — no port, no CORS, no health controller — because a preset that stays half-applicable is how insecure defaults spread. minimal, api and api-realtime currently emit identical output; the preset is recorded in the manifest so a later version can act on it.

nage doctor

Two kinds of check, one report and one exit code:

  • workspace integrity — duplicate app names or ports, aliases that collide, apps registered but missing from disk (and the reverse), shared packages nothing imports;
  • secrets — delegated to auditSecurity in @nage-api/core, so the rule that fails a weak secret here is the same code that refuses a production boot, rather than two lists that drift.

Anything critical or high exits 1, which is what makes it usable as a CI gate. --legacy additionally scans sources for the insecure patterns of §23.3.

Secret strength is judged from the workspace's own .env, deliberately not from process.env: the shell a developer or a runner invokes doctor in is full of tokens whose names match any secret heuristic, and a report about the machine rather than the project is a report nobody reads.

What doctor does not yet see is the app's own nage.config.ts. It calls auditSecurity with a minimal production config, so the config-shaped findings that audit can produce — a wildcard CORS list, ssl: 'no-verify', throttling switched off — are not reported by the CLI today. Reaching them means executing the app's TypeScript config, which the CLI does not do. Until it does, assertSecureConfig at boot is where those are caught (§12).

Not yet implemented

  • nage upgrade — codemods across framework versions (§10.1).
  • nage db migrate|seed|rollback — needs a driver resolved at runtime inside a generated workspace. The generated package.json ships db:migrate and db:seed scripts that invoke it anyway, so pnpm db:migrate fails with "db" is not a nage command, as do the two generated notes that recommend it.
  • --standalone and nage workspace init (§9.3). The flag parses and is accepted; planCreate ignores it, so you get a workspace either way.
  • --force. It parses, but no command reads it — a conflicting file still fails the whole batch, whatever the hint in that error says.
  • doctor --app <name>. It parses and reaches runDoctor, which never reads it: the report always covers the whole workspace, and an app name that does not exist is not even reported.
  • Validation of --db, --preset and --port. All three are cast rather than checked, so --db cassandra writes that engine into the manifest and --port abc records "port": null.
  • --package <name> beyond placing the entity file. The service is pointed at @app/<name>, but the controller, the service spec and the query DTO still import ./entities/<name>.entity.js, and the package's index.ts is not updated to export the entity — so a resource generated this way does not typecheck until three imports and one export are fixed by hand. The same applies to g module, g controller and g dto used on their own: each emits files that import siblings it does not emit.
  • Dropping the root project reference in remove package. The manifest entry and the alias go; { "path": "packages/<name>" } stays in tsconfig.json and breaks tsc -b once you delete the directory. remove app does remove its reference.
  • The security half of doctor reading the app's config, as above.
  • Interactive prompts; every command is flag-driven for now.
  • The "generated workspace installs from a registry and passes its own CI" acceptance test (§24 Phase 6). The suite does build, lint, typecheck and run the generated workspace's own tests, but against symlinked local packages: it cannot tell you whether the version ranges in the generated package.json resolve, because @nage-api/* is not published yet.

The deeper guide is docs/packages/cli.md.