nx-selector-cli
v1.0.0
Published
Target-agnostic interactive runner for Nx monorepos. Personal labels, fuzzy selection, streaming output.
Downloads
30
Readme
nxs
Target-agnostic interactive runner for Nx monorepos.
Pick projects with a fuzzy-searchable, grouped checkbox UI — or save them under personal labels and run them with two words. Works with any Nx target (serve, build, test, lint, e2e, storybook, anything), in any workspace, with configuration stored per user — never inside the repository.
nxs serve # interactive selector, "default" label pre-checked
nxs serve backend # nx run-many -t serve --projects=<backend projects>
nxs build frontend # any target works
nxs test default
nxs set backend # pick projects, save the label
nxs list # show your labelsInstall
pnpm install
pnpm build
pnpm add -g . # exposes `nxs` (and the legacy `nx-serve` shim)If pnpm complains that its global bin directory is not in PATH, run pnpm setup once and open a new terminal (or source ~/.bashrc).
Requires Node ≥ 18.17 and an nx binary reachable from the workspace.
Why labels live in your config, not the repo
The old nx-serve.json lived in the workspace root: it polluted repositories and forced every developer to share one selection. nxs stores everything in a user-level file keyed by workspace path, so each developer keeps their own labels per repository:
| Platform | Config location |
|---|---|
| Linux | ~/.config/nxs/config.json (or $XDG_CONFIG_HOME/nxs; fallback ~/.cache/nxs) |
| macOS | ~/Library/Application Support/nxs/config.json |
| Windows | %APPDATA%\nxs\config.json |
Override with NXS_CONFIG_DIR / NXS_CACHE_DIR.
{
"version": 1,
"workspaces": {
"/home/user/projects/alpha": {
"labels": {
"backend": ["identity-server", "project-service", "audit-service"],
"frontend": ["portal-ui", "workflow-ui"],
"default": ["portal-ui", "identity-server"]
},
"recent": ["portal-ui", "identity-server"],
"lastRun": { "serve": ["portal-ui"] }
}
}
}Commands
Run any target
nxs <target> [labels...] [options] [-- nx-args...]nxs serve # interactive picker
nxs serve backend # one label
nxs serve backend frontend # merge multiple labels
nxs e2e default # "default" is just a label
nxs storybook # any target a project defines
nxs test --projects=a,b,c # explicit projects, no label needed
nxs serve --changed # only affected projects (nx affected)
nxs serve --last # re-run the last selection for this target
nxs serve --no-cache # refresh the project graph first
nxs serve backend --parallel=4 # explicit nx parallelism
nxs build backend -- --verbose --configuration=production # forwarded to nxThe target is validated against the workspace before anything runs; selections are automatically filtered to projects that actually define the target (with a warning for anything skipped).
Manage labels
Labels are dynamic — nothing is hardcoded:
nxs set backend # checkbox picker, saves under "backend"
nxs set api
nxs set scheduler
nxs set default # the label preselected by bare `nxs <target>`
nxs list # backend, default, frontend, ...
nxs remove backend
nxs rename backend api
nxs config # pretty-print this workspace's config + recents + last runsThe selector
Select projects to serve
↑↓ move · space toggle · ←→ fold · a all · n none · i invert · type to search · enter run
search: (type to filter)
❯ [~] ▾ Recent (2)
[x] portal-ui
[ ] identity-server
[ ] ▸ apps/ (4)
[~] ▾ services/ (3)
[x] identity-server
[ ] project-service
[ ] audit-service
2 selected- Auto-grouping by top-level folder (
apps/,libs/,services/, …), with a Recent section on top built from your execution history. - Collapsible groups:
←folds,→expands. - Group selection:
spaceon a group row toggles all of its (visible) children;[~]marks partial selection. - Bulk keys:
aselect all,nnone,iinvert — scoped to whatever is currently visible/filtered. - Fuzzy search: just type (
identity, or scattered likeidsrv) — filters instantly, best matches first. Need to search for a literala/n/i? Press/first.escclears. enterruns,ctrl+ccancels.
Execution
Selections run through nx run-many via spawn with inherited stdio — output streams live, colors and interactive logs are preserved, and Ctrl+C / SIGTERM are forwarded to nx (the CLI mirrors nx's exit code). --parallel=N is a first-class option; when omitted it defaults to the number of selected projects (so serve runs everything at once).
Discovery & caching
Projects come from nx graph --file and are cached per workspace. The cache fingerprints nx.json, package.json, workspace.json, pnpm-workspace.yaml, and every project.json in the tree (path + mtime + size), so adding, removing, or editing a project invalidates it automatically. --no-cache forces a refresh.
Migrating from nx-serve
Automatic. The first time you run nxs in a workspace that still has an nx-serve.json (or nx-serve.config.json) and you have no labels there yet, it imports:
defaultProjects→ labeldefault- each
groupsentry → a lowercase label
…into your personal config and tells you the repo file can be deleted. Your existing labels are never overwritten.
The legacy nx-serve command keeps working as a deprecation shim:
| Old | Runs |
|---|---|
| nx-serve | nxs serve |
| nx-serve --default | nxs serve default |
| nx-serve --set-default | nxs set default |
| nx-serve --clear-default | nxs remove default |
| nx-serve --group=Apps | nxs serve apps |
| nx-serve --groups=A,B | nxs serve a b |
Architecture
src/
├── cli/
│ ├── parse.ts argv → Command (any target, labels, flags, passthrough)
│ ├── legacy.ts nx-serve flag translation
│ └── help.ts
├── commands/
│ ├── run.ts selection resolution + execution
│ ├── set.ts list.ts remove.ts rename.ts config.ts
│ └── context.ts workspace root + store + one-time migration
├── nx/
│ ├── discover.ts nx graph → projects with all their targets
│ ├── cache.ts fingerprinted project-graph cache
│ ├── targets.ts target validation/filtering
│ └── runner.ts spawn-based streaming execution, signal forwarding
├── config/
│ ├── paths.ts platform-aware config/cache dirs
│ ├── store.ts user-level config load/normalize/atomic-save
│ ├── workspace.ts per-workspace store (labels, recents, lastRun)
│ └── migrate.ts nx-serve.json import
├── ui/
│ ├── selector.ts raw-mode TUI (rendering + keys)
│ ├── model.ts pure selection state (fully unit-tested)
│ ├── groups.ts auto-grouping + Recent
│ ├── fuzzy.ts fuzzy matcher
│ └── ansi.ts
└── index.ts dispatch + exit codesZero runtime dependencies — the selector is built on node:readline raw mode.
Development
pnpm build # tsc → dist/
pnpm dev # tsc --watch
pnpm test # vitest (70 tests across all modules)Exit codes: 0 success/empty selection, 1 runtime failure (mirrors nx), 2 usage error, 130 cancelled.
