Skip to content
Merged

Dev #16

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions .github/workflows/publish.yml

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ coverage/

# TypeScript
*.tsbuildinfo

# Local monorepo fixture for testing the workspace feature
test-monorepo/
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## [1.10.0] — 2026-05-21

### Added
- **Monorepo support** — auto-detects npm/yarn/pnpm workspaces and adds a workspace switcher to scope the dashboard to a single workspace or the whole repository
- **Duplicate version detection** — dashboard card that flags dependencies pinned to different versions across workspaces, with a color-coded distribution bar and per-version workspace breakdown
- **Download trend chart** — the Package Detail page shows an interactive area chart of weekly npm downloads over the last 12 months, with hover tooltips and 12-month total / weekly average / peak-week summaries
- New `GET /api/registry/:name/downloads` endpoint backed by the npm downloads API with a 6-hour server-side cache

### Changed
- **Package Detail page redesigned** — package type and dependency health now appear as inline tags beside the package name; the health score is shown as a visual radial gauge; version comparison is condensed into a single compact strip; registry metadata uses an icon-based grid; and the "Choose version" picker is available directly from the page
- **Duplicate Versions card redesigned** — replaced the flat text list with color-coded version lanes, a distribution bar, and workspace chips for clearer at-a-glance reading

---

## [1.9.2] — 2026-05-20

### Added
Expand Down
113 changes: 82 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Packy
<h1 align="center">Packy</h1>

<p align="center">
<img src="https://raw.githubusercontent.com/njevric/packy-dev/main/public/banner.svg" alt="Packy — modern browser UI for npm package management" width="900"/>
Expand All @@ -12,65 +12,108 @@
<a href="https://www.npmjs.com/package/packydash"><img src="https://img.shields.io/node/v/packydash" alt="Node version"/></a>
</p>

Modern browser UI for managing npm dependencies and packages.
<p align="center">
<strong>Run one command. See — and fix — everything about your project's dependencies.</strong>
</p>

Your dependencies live in a `package.json` and surface as scattered, plain-text CLI output. Packy turns them into a **live dashboard in your browser** — what's outdated, what's vulnerable, what's bloated, what's unused, what's unmaintained — and lets you fix all of it without ever leaving the page.

```bash
npx packydash
```

That's the whole setup. No install, no config, no account — Packy reads your project and opens a dashboard.

---

## Why Packy?

Run `packy` in any Node.js project to open a fully responsive dashboard for installing, removing, and updating packages — with real-time output streaming and a built-in security auditor.
Staying on top of dependencies usually means juggling `npm outdated`, `npm audit`, `npm ls`, a tab on npmjs.com for every package, and a changelog hunt before each upgrade. The answers are out there — just scattered across six places and printed as text.

Packy puts them in **one place you actually want to look at**:

- 🔎 **One glance, full picture** — outdated, vulnerable, unused, and unhealthy packages, all on one screen.
- 🛟 **Upgrade with confidence** — see the changelog and breaking changes *before* you install.
- 🖱️ **Fix it in place** — update, remove, audit-fix, and run scripts straight from the browser, with live output.
- 🔒 **Stays on your machine** — a local server, the npm registry, and nothing else.

## Features

- **Dashboard** — stats bar showing total packages, outdated count, vulnerability summary, node_modules install size, and last audit time with sparkline
- **Package management** — view all dependencies with current vs. latest versions, install, remove, and update individual packages; pick any specific version from the full registry history before installing
- **Update All** — one-click bulk update for all outdated packages with live progress
- **Package detail** — per-package view with npm registry info: version history, license, weekly downloads, homepage, repository, and keywords
- **Dependency health** — per-package health score (0–100) with maintenance status (healthy / maintained / aging / unmaintained / deprecated), last published date, and deprecation warnings
- **Changelog & breaking change detection** — when picking a version, fetches the changelog between your current and selected version from GitHub and flags any breaking changes before you install
- **Security audit** — run `npm audit` from the browser with severity breakdown (critical / high / moderate / low / info)
- **Audit fixes** — apply fixes individually or all at once, with automatic force-fix for breaking major version changes
- **Audit & fix history** — persistent logs of every audit run and fix applied
- **Activity feed** — timeline of recent installs, updates, removals, and audits across sessions
- **Real-time streaming** — command output streamed live to the browser via SSE
- **Smart filtering** — search packages by name, filter by type (all / outdated / dependencies / devDependencies)
- **Unused dependency detection** — powered by `depcheck`; highlights packages declared in `package.json` but not imported in the project
- **Smart filters panel** — contextual filter chips that surface the most relevant package subsets at a glance
- **Script runner** — run any `package.json` script from the browser with live output streaming and per-script run history
- **Dark mode** — system-aware theme with a manual toggle; preference persisted across sessions
- **Fully responsive** — works on mobile, tablet, and desktop
### 📊 A dashboard for your dependencies
- Stats bar: total packages, outdated count, vulnerability summary, `node_modules` install size, and last audit — with a trend sparkline.
- An activity feed of every install, update, removal, and audit across sessions.
- The packages that need attention, surfaced first.

### 📦 Package management, without the CLI
- Install, update, and remove packages from the browser.
- **Update All** outdated packages in a single click, with live progress.
- Pin **any version** — pick from the full npm release history before installing.
- Command output streams to the browser in real time over SSE.

### 🔍 Know before you upgrade
- **Changelog & breaking-change detection** — Packy fetches the changelog between your installed version and the target, and flags breaking changes before you hit install.
- **Dependency health score (0–100)** — shown as a visual gauge, with maintenance status (healthy → deprecated), last-published date, and deprecation warnings.
- **Download trends** — an interactive chart of weekly npm downloads over the last 12 months.

### 🛡️ Security, built in
- Run `npm audit` from the browser with a clear severity breakdown — critical, high, moderate, low, info.
- Apply fixes individually or all at once, including force-fixes for breaking major bumps.
- Persistent audit & fix history, so you can see exactly what changed and when.

### 🧹 Find the dead weight
- **Unused dependency detection** (powered by [`depcheck`](https://www.npmjs.com/package/depcheck)) — packages declared in `package.json` that nothing actually imports.
- Smart filter chips that surface outdated, unused, or dev-only packages instantly.

### 🗂️ Monorepo-ready
- Auto-detects npm / yarn / pnpm workspaces.
- Switch the dashboard between any single workspace and the whole repository.
- **Duplicate version detection** — catches the same dependency pinned to different versions across workspaces, with a color-coded breakdown of who uses what.

### ⚡ Run your scripts
- Every `package.json` script, runnable from the browser with live streaming output and per-script run history.

### ✨ And the details that matter
- System-aware **dark mode** with a manual toggle, remembered across sessions.
- **Fully responsive** — works on phone, tablet, and desktop.

## How it works

1. Run `npx packydash` inside any Node.js project.
2. Packy starts a local server, reads your `package.json`, and opens your browser.
3. Browse, audit, upgrade, and run scripts — every command streams its output back live.

## Requirements

Node.js >= 18.0.0

## Installation
## Usage

Run it on demand with no install:

```bash
npm install -g packydash
cd your-project
npx packydash
```

## Usage

Navigate to any Node.js project directory and run:
Or install it globally if you reach for it often:

```bash
cd your-project
npm install -g packydash
packy
```

Packy starts a local server, opens your browser, and serves the dashboard pointed at your project's `package.json`.

### Options

```
--port <number> Port to listen on (default: auto-selects from 5173–5177)
--help Show help
```

Example with a custom port:

```bash
packy --port 4000
```

## Supported Package Managers
## Supported package managers

Packy auto-detects the package manager from your project:

Expand All @@ -80,6 +123,14 @@ Packy auto-detects the package manager from your project:
| `yarn.lock` | yarn |
| `package-lock.json` or none | npm |

## Local and private

Packy runs entirely on your machine. It serves the dashboard from a local server, reads your project's `package.json`, and queries the public npm registry for version, health, and download data. There are no accounts, no config files, and no telemetry — and the API only accepts requests from `localhost`.

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for the full release history.

## License

MIT — [Nikola Jevric](https://github.com/NJevric)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "packydash",
"version": "1.9.2",
"version": "1.10.0",
"description": "Modern UI for managing npm dependencies and packages",
"type": "module",
"main": "dist/server/index.js",
Expand Down
2 changes: 2 additions & 0 deletions src/client/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from 'radix-vue'
import { useTheme } from '@/composables/useTheme'
import { Button } from '@/components/ui/button'
import WorkspaceSwitcher from '@/components/WorkspaceSwitcher.vue'

const { theme, setTheme } = useTheme()

Expand All @@ -31,6 +32,7 @@ const themeOptions = [
<div class="container mx-auto flex h-14 items-center justify-between px-4 sm:px-6">
<div class="flex items-center gap-2.5">
<span class="text-xl font-bold tracking-tight">Packy</span>
<WorkspaceSwitcher />
</div>
<nav class="flex items-center gap-1">
<RouterLink
Expand Down
94 changes: 94 additions & 0 deletions src/client/components/WorkspaceSwitcher.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<script setup lang="ts">
import { computed, watch } from 'vue'
import { ChevronsUpDown, Check, FolderTree, Home } from 'lucide-vue-next'
import {
DropdownMenuRoot,
DropdownMenuTrigger,
DropdownMenuPortal,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
} from 'radix-vue'
import { useProject } from '@/composables/useProject'
import { useWorkspaceStore } from '@/stores/workspace'
import { Button } from '@/components/ui/button'

const { data: project } = useProject()
const ws = useWorkspaceStore()

const workspaces = computed(() => project.value?.workspaces ?? [])
const isMonorepo = computed(() => project.value?.isMonorepo ?? false)
const children = computed(() => workspaces.value.filter((w) => !w.isRoot))
const rootWorkspace = computed(() => workspaces.value.find((w) => w.isRoot))

// Drop a persisted selection that does not belong to the current project
watch(
workspaces,
(list) => {
if (ws.selected && !list.some((w) => !w.isRoot && w.name === ws.selected)) {
ws.select(null)
}
},
{ immediate: true }
)

const currentLabel = computed(() => {
if (ws.selected) return ws.selected
return rootWorkspace.value?.name ?? 'Root'
})
</script>

<template>
<DropdownMenuRoot v-if="isMonorepo">
<DropdownMenuTrigger as-child>
<Button
variant="outline"
size="sm"
class="h-8 gap-1.5 max-w-[200px]"
>
<FolderTree class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span class="truncate text-xs font-medium">{{ currentLabel }}</span>
<ChevronsUpDown class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent
align="start"
:side-offset="6"
class="z-50 min-w-[220px] max-h-[70vh] overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 p-1"
>
<DropdownMenuLabel class="px-2 py-1.5 text-[11px] font-semibold uppercase tracking-widest text-muted-foreground">
Workspace
</DropdownMenuLabel>

<DropdownMenuItem
class="flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none cursor-default select-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
@click="ws.select(null)"
>
<Home class="h-4 w-4 shrink-0 text-muted-foreground" />
<span class="flex-1 truncate">{{ rootWorkspace?.name ?? 'Root' }}</span>
<span class="text-[10px] text-muted-foreground">root</span>
<Check
v-if="!ws.selected"
class="h-3.5 w-3.5 shrink-0"
/>
</DropdownMenuItem>

<DropdownMenuItem
v-for="child in children"
:key="child.relativePath"
class="flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none cursor-default select-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
@click="ws.select(child.name)"
>
<FolderTree class="h-4 w-4 shrink-0 text-muted-foreground" />
<span class="flex-1 truncate">{{ child.name }}</span>
<span class="text-[10px] text-muted-foreground truncate max-w-[90px]">{{ child.relativePath }}</span>
<Check
v-if="ws.selected === child.name"
class="h-3.5 w-3.5 shrink-0"
/>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>
Loading
Loading