Skip to content
| Marketplace
Sign in
Visual Studio Code>Other>NPM Scripts ExplorerNew to Visual Studio Code? Get it now.
NPM Scripts Explorer

NPM Scripts Explorer

adardour.npm-scripts-explorer

| (0) | Free
Detect, browse, and run package.json scripts from a dedicated sidebar — with pnpm/yarn/bun auto-detection, favorites, categories, and live run status.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

NPM Scripts Explorer

A VS Code extension that discovers package.json scripts and gives them a dedicated sidebar view: run with one click, filter/search, pin favorites, group by category, see live running status, and stop a running script — all while auto-detecting whether the project uses npm, pnpm, yarn, or bun.

Getting started

npm install
npm run build     # bundles src/extension.ts -> dist/extension.js

Then press F5 in VS Code (with this folder open) to launch an Extension Development Host with the extension loaded. Open any project with a package.json and the "NPM Scripts" icon will appear in the Activity Bar.

npm run watch rebuilds on save during development. npm run compile type-checks without emitting (used in CI/pre-commit).

Architecture

The code is organized in layers, each with one reason to change:

src/
├── extension.ts            # composition root: wires everything together
├── models/types.ts         # domain types — zero vscode imports
├── services/
│   ├── PackageManagerDetector.ts   # lockfile -> package manager
│   ├── ScriptCategoryClassifier.ts # script name/command -> category
│   ├── PackageJsonParser.ts        # file -> ParsedPackageFile (+ cache)
│   ├── WorkspaceScanner.ts         # locates nearest package.json per folder
│   ├── ScriptRepository.ts         # aggregates scanner + parser results
│   ├── TerminalManager.ts          # owns Terminal creation/reuse
│   └── ScriptRunner.ts             # ScriptDefinition -> executed command
├── state/
│   ├── FavoritesStore.ts           # persisted pinned scripts (workspaceState)
│   └── RunningScriptsTracker.ts    # in-memory "what's running now"
├── providers/
│   ├── ScriptTreeItem.ts           # TreeNode -> vscode.TreeItem
│   └── ScriptsTreeDataProvider.ts  # tree shape + refresh semantics
├── watchers/
│   └── FileWatcherService.ts       # fs/workspace events -> repository refresh
└── commands/
    └── commands.ts                 # binds command IDs to service calls

Why this shape (SOLID, applied concretely)

  • Single Responsibility. Each service does exactly one job: PackageManagerDetector only detects a PM, ScriptRunner only turns a script into an executed command, ScriptsTreeDataProvider only decides tree structure. None of them know how to parse JSON, run processes, and render UI — that would make every change ripple across concerns.

  • Open/Closed. Package managers are data (PACKAGE_MANAGERS table), not a switch statement threaded through multiple files. Adding a fifth package manager means adding one table entry in PackageManagerDetector.ts. The same applies to category rules in ScriptCategoryClassifier.ts — an ordered rule table instead of scattered if/else.

  • Liskov / Interface Segregation. ScriptsTreeDataProvider depends on ScriptRepository, FavoritesStore, and RunningScriptsTracker through their public surface only (a handful of methods and one event each), not on vscode.ExtensionContext or the filesystem directly. Each of those collaborators could be replaced by a test double implementing the same narrow surface.

  • Dependency Inversion. extension.ts is the only file that calls new X(...) and decides concrete wiring (constructor injection throughout). Every other file receives its dependencies as constructor arguments. This is what makes the object graph visible in one place and each class unit-testable without booting VS Code.

  • Domain vs. VS Code API. models/types.ts has zero import * as vscode. Business concepts (a script, a package manager, a category) are defined independently of how the editor happens to render or store them, so the domain logic isn't accidentally coupled to vscode.TreeItem shapes.

Performance: avoiding redundant parsing

PackageJsonParser keeps an in-memory cache keyed by file path, storing the mtimeMs seen at last parse. On every request it first calls vscode.workspace.fs.stat (cheap) and only reads + JSON.parses + re-classifies scripts when the mtime has actually changed. Combined with FileWatcherService (which calls refreshFile() only for the file that actually changed, not a full rescan), this means:

  • Expanding/collapsing tree nodes, toggling grouping, or typing in the search box never touches the filesystem — they only re-render from already-parsed data.
  • Editing one package.json in a multi-root workspace re-parses that file only; every other project's cache entry is untouched.
  • A full ScriptRepository.refresh() (re-scan + re-parse everything) only happens on the "Refresh" button, workspace folder add/remove, or a lockfile change (since that can change which package manager applies to every script in that folder).

TreeView design

Tree rows are modeled as a plain discriminated union (TreeNode in ScriptTreeItem.ts) rather than subclasses of vscode.TreeItem. This separates what the tree contains (folders → favorites → categories → scripts) from how a row is drawn (icon, tooltip, contextValue). contextValue is built as a colon-joined set of flags (script:running:pinned), and package.json's view/item/context menu entries key off viewItem =~ /running/ style regexes — so new context menu actions or new flags don't require any TypeScript changes, only new when clauses.

Terminal & running-status design

RunningScriptsTracker is the single source of truth for "is script X running", exposed as a plain event emitter so the tree can refresh icons reactively. TerminalManager is the only class that touches vscode.window.createTerminal / Terminal, and implements two policies:

  • Run reuses one terminal per script id (so re-running "dev" reuses the same terminal, matching how most developers work).
  • Run in New Terminal always spawns a fresh terminal, useful for running the same script twice or comparing output side by side.
  • Stop sends Ctrl+C (\u0003) to the script's terminal — the portable way to interrupt an arbitrary shell command, since the API has no generic "kill this npm script" primitive.
  • Closing a terminal (vscode.window.onDidCloseTerminal) automatically marks the corresponding script as stopped, so status never gets stuck.

Multi-root workspaces

WorkspaceScanner iterates vscode.workspace.workspaceFolders and resolves the nearest package.json per folder (root first, then a bounded, node_modules-excluded glob search as a fallback for monorepo layouts). ScriptsTreeDataProvider only inserts a "folder" grouping level when more than one workspace folder actually contributes scripts, so a single-project workspace doesn't grow an unnecessary extra tree level.

Features implemented

  • Nearest package.json discovery, per workspace folder
  • npm / pnpm / yarn / bun auto-detection via lockfiles, npm as fallback
  • Tree view with icon-per-category, command as description + tooltip
  • Click-to-run, using the correct package manager's run syntax
  • Auto-refresh on package.json edits, lockfile changes, and workspace folder changes, without re-parsing unrelated files
  • Search/filter (by name or command)
  • Pin/favorite scripts (persisted per workspace)
  • Group by category (toggle) — dev, build, test, lint, format, database, docker, release, clean, other
  • Live running-status icon, with a Stop action
  • Multi-root workspace support (folder grouping)
  • Run in current (reused) terminal vs. always-new terminal
  • Context menu: Run, Run in New Terminal, Stop, Copy Command, Reveal in package.json, Toggle Pin

Extending it

  • New package manager: add one entry to PACKAGE_MANAGERS and LOCKFILE_MARKERS in PackageManagerDetector.ts.
  • New category: add a value to ScriptCategory in models/types.ts, a rule in ScriptCategoryClassifier.ts, a label in ScriptTreeItem.ts's CATEGORY_LABELS, an icon in utils/icons.ts, and an entry in CATEGORY_ORDER in ScriptsTreeDataProvider.ts.
  • New context menu action: register a command in commands/commands.ts and add a view/item/context entry in package.json, keyed off the existing contextValue flags (or a new flag added in ScriptTreeItem.ts).
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft