dev-prune 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +164 -45
  2. package/bin/dev-prune.js +36 -8
  3. package/package.json +8 -8
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  <div align="center">
2
2
 
3
- <img src="assets/readme-banner.png" alt="dev-prune — get the gigabytes back, lose nothing" width="820" />
3
+ <img src="assets/github-readme-banner.png" alt="dev-prune — gigabytes back, nothing you can't rebuild" width="820" />
4
4
 
5
5
  # `dev-prune`
6
6
 
7
- ### Get the gigabytes back. Lose nothing.
7
+ ### Gigabytes back. Nothing you can't rebuild.
8
8
 
9
9
  **Reclaim the disk space your idle repositories are sitting on — without ever deleting
10
10
  something a lockfile cannot put back.**
@@ -83,6 +83,10 @@ All three download the prebuilt binary for your platform, verify its published S
83
83
  put it on `PATH`, and run `dev-prune setup`. Pass `--no-auto-setup` / `-NoAutoSetup` to
84
84
  skip that last step.
85
85
 
86
+ Re-run any of them whenever you like. An install that is already current, complete and on
87
+ `PATH` is left exactly as it is and exits `0` without downloading; an older one is updated
88
+ in place; a newer one is not downgraded. `--force` / `-Force` writes it again regardless.
89
+
86
90
  The Command Prompt form installs identically to the PowerShell one, but `cmd` cannot
87
91
  inherit the `PATH` the installer sets in its own process, so `devp` resolves in the *next*
88
92
  Command Prompt you open rather than the current one. PowerShell does not have that
@@ -91,6 +95,7 @@ problem.
91
95
  ### From a package manager
92
96
 
93
97
  ```bash
98
+ npm install -g dev-prune # or: npx dev-prune status
94
99
  uv tool install dev-prune # or: uvx dev-prune status
95
100
  pipx install dev-prune
96
101
  pip install dev-prune
@@ -98,9 +103,15 @@ cargo binstall dev-prune # fetches the prebuilt release archive
98
103
  cargo install dev-prune # builds from source, needs Rust 1.88+
99
104
  ```
100
105
 
101
- The PyPI packages **contain the binary** — there is no download step at install time,
102
- so they work behind a registry mirror and offline. Everything but `cargo install` ships
103
- a prebuilt executable.
106
+ The npm and PyPI packages **contain the binary** — there is no download step at install
107
+ time, so they work under `npm ci --ignore-scripts`, behind a registry mirror and offline.
108
+ Everything but `cargo install` ships a prebuilt executable.
109
+
110
+ npm delivers it the way esbuild and Biome do: one small `dev-prune` package that lists
111
+ seven platform packages as optional dependencies, of which npm installs exactly the one
112
+ matching your machine. That is why there is no download step to block. Windows works from
113
+ 1.8.0 onwards — earlier versions installed and then reported no binary to run, so a
114
+ machine still holding `dev-prune@1.7.0` needs `npm install -g dev-prune@latest`.
104
115
 
105
116
  crates.io stores source and nothing else, so `cargo install` has no binary to fetch and
106
117
  always compiles. [`cargo binstall`](https://github.com/cargo-bins/cargo-binstall) is the
@@ -132,7 +143,7 @@ brew install https://raw.githubusercontent.com/Life-Experimentalist/dev-prune/ma
132
143
  scoop install https://raw.githubusercontent.com/Life-Experimentalist/dev-prune/main/packaging/scoop/dev-prune.json
133
144
  ```
134
145
 
135
- WinGet is [submitted and in review](https://github.com/microsoft/winget-pkgs/pull/422665);
146
+ WinGet is [submitted and in review](https://github.com/microsoft/winget-pkgs/pull/422809);
136
147
  `winget install VKrishna04.dev-prune` starts resolving when that pull request merges.
137
148
 
138
149
  The two Python entry points differ in where they land. `pip install` follows whichever
@@ -252,6 +263,7 @@ A few more worth knowing on day one:
252
263
  ```bash
253
264
  devp stats # how much has been reclaimed so far, and by which repositories
254
265
  devp caches # every package manager cache, sized. The report deletes nothing
266
+ devp caches docker # what Docker holds, and the prune commands. Read-only, permanently
255
267
  devp status --drift # anything installed that the lockfiles don't record?
256
268
  devp doctor . # why is this repository not being pruned?
257
269
  devp doctor --fix # repair a broken integration — never a first-time install
@@ -321,31 +333,108 @@ $ devp caches
321
333
 
322
334
  Package manager caches
323
335
 
324
- uv cache 16.25 GiB ~/.cache/uv
325
- clear: uv cache prune
336
+ npm cache 9.98 GiB ~/.npm
337
+ clear: npm cache clean --force
338
+ npm is used by 18 of 46 registered repositories · 567.75 MiB each
326
339
 
327
- npm cache 8.97 GiB ~/.npm
328
- clear: npm cache clean --force
340
+ uv cache 6.22 GiB ~/.cache/uv
341
+ clear: uv cache prune
342
+ uv is used by 7 of 46 registered repositories · 909.77 MiB each
329
343
 
330
- go build cache 371.75 MiB ~/.cache/go-build
331
- clear: go clean -cache
332
- compiled build artifacts; clearing them means the next build is a cold one
344
+ pnpm store 2.07 GiB /mnt/data/.pnpm-store
345
+ clear: pnpm store prune --store-dir /mnt/data/.pnpm-store
346
+ one store per filesystem, because a hardlink into node_modules cannot cross one
347
+ pnpm is used by 1 of 46 registered repositories · 2.09 GiB each
333
348
 
334
- cargo registry sources 104.75 MiB ~/.cargo/registry/src
335
- clear: rm -rf ~/.cargo/registry/src
336
- unpacked copies of the archives above; cargo re-extracts these offline
349
+ nuget global packages 1.44 GiB ~/.nuget/packages
350
+ clear: dotnet nuget locals global-packages --clear
351
+ every .NET project on the machine restores from here; re-downloaded on the next restore
337
352
 
338
- (pip, bun, pnpm, go module cache, cargo registry cache)
353
+ (pip, bun, both go caches, gradle wrapper distributions, both cargo
354
+ registry caches, and the pnpm store beside your home directory)
339
355
 
340
- Total 27.26 GiB across 9 caches
356
+ Total 21.99 GiB across 12 caches
341
357
 
342
358
  → Nothing above was deleted, and dev-prune never deletes any of it.
359
+
360
+ Container engines
361
+
362
+ docker 47.64 GiB 36.69 GiB reclaimable · devp caches docker
363
+
364
+ Container images, volumes and build cache are not package manager caches and are not in
365
+ the total above — dev-prune reports them and never deletes them.
343
366
  ```
344
367
 
345
368
  A cache lives outside every repository and is shared by all of them, so no single
346
369
  lockfile can prove it recoverable — and it is what makes `devp restore` fast. `devp
347
370
  caches` reports and prints the clear command; running it is your decision.
348
371
 
372
+ pnpm gets a row per filesystem rather than a single row. It hardlinks its store into
373
+ every `node_modules` it fills, and a hardlink cannot cross a filesystem, so projects
374
+ kept off the system disk have a store of their own at the root of *that* filesystem —
375
+ `V:\.pnpm-store` on a second Windows drive, `/mnt/data/.pnpm-store` on Linux,
376
+ `/Volumes/Work/.pnpm-store` on macOS. `pnpm store path` only ever answers for the
377
+ filesystem it is run on, so dev-prune looks at the root of every filesystem that holds
378
+ a registered repository, and each such row names its store in the command it prints.
379
+
380
+ </details>
381
+
382
+ <details>
383
+ <summary><b><code>devp caches docker</code></b> — the engine that is usually bigger than all of them</summary>
384
+
385
+ ```console
386
+ $ devp caches docker
387
+
388
+ Container engines
389
+
390
+ docker
391
+
392
+ Images 37.36 GiB 29.28 GiB reclaimable 41 items, 9 in use
393
+ Containers 1.19 GiB 1.12 GiB reclaimable 12 items, 3 in use
394
+ Local Volumes 2.79 GiB 0 B reclaimable 5 items, 2 in use
395
+ Build Cache 6.29 GiB 6.29 GiB reclaimable 41 items, 0 in use
396
+
397
+ Total 47.64 GiB 36.69 GiB reclaimable
398
+
399
+ Reclaim it yourself what it takes with it
400
+ docker builder prune the build cache; costs a slower next build
401
+ docker image prune dangling images no tag points at any more
402
+ docker container prune stopped containers and each writable layer
403
+ docker system prune the three above at once; volumes untouched
404
+ docker system prune --volumes adds unused volumes — the one that deletes data
405
+
406
+ Nothing above was deleted, and nothing dev-prune runs on a schedule will ever delete it.
407
+ An image has no lockfile to prove it can be rebuilt, and a named volume is the one thing
408
+ here that cannot be rebuilt at all — so this command measures, prints the commands, and
409
+ leaves the decision with you.
410
+ ```
411
+
412
+ Clearing 6 GiB of npm cache while a Docker install nobody has looked at in a year sits on
413
+ 40 GiB is the mistake this exists to prevent. `devp caches podman` and `devp caches
414
+ nerdctl` are the same report for those engines, and `devp caches containers` runs every
415
+ one it finds and lists any local Kubernetes clusters (kind, k3d, minikube) by name.
416
+
417
+ **It is read-only, permanently.** There is no flag and no `clear` verb that makes
418
+ dev-prune run one of those commands — `devp caches clear docker` is a usage error that
419
+ says so — and no scheduler or Git hook reaches this code at all. That is the same rule
420
+ as everywhere else rather than extra caution: an image has no lockfile to prove it can be
421
+ rebuilt, the Dockerfile that built it may not be on this disk, and a named volume is the
422
+ one thing on the machine that cannot be rebuilt at all.
423
+
424
+ The figures come from the engine's own `system df`, not a walk of the disk. On Docker
425
+ Desktop and Podman the store lives inside a VM disk image the host filesystem cannot see,
426
+ so a size taken off the disk would be wrong by orders of magnitude in the reassuring
427
+ direction — and asking is the only way to learn what is *reclaimable*, which is the
428
+ figure that decides anything. 40 GB of images with 38 GB dangling is a different
429
+ situation from 40 GB with 2 GB dangling.
430
+
431
+ An engine that is installed with its daemon stopped is reported as exactly that, quoting
432
+ the engine's own first line, and contributes no figures: a blank, not a zero. An engine
433
+ that is not installed is absent from the report rather than listed as missing. Local
434
+ Kubernetes clusters are named and deliberately not sized — their nodes are containers
435
+ belonging to an engine already in the table, so a figure beside the cluster name would be
436
+ the same gigabytes counted twice.
437
+
349
438
  </details>
350
439
 
351
440
  <details>
@@ -421,7 +510,8 @@ process that leaves a dirty working tree is a surprise.
421
510
  | 🧩 **Any number of ecosystems per repository** | uv, npm and cargo in one root, or spread across `frontend/`, `services/api/` and `tools/cli/` — each discovered, verified and pruned on its own terms |
422
511
  | ↩️ **One-command restore** | `devp restore .` reinstalls a tree; `devp restore --last-run` puts back exactly what the most recent pass deleted, across every repository it touched |
423
512
  | 🕒 **Activity-aware** | Combines `git log` timestamps with source-file `mtime`, so uncommitted work protects a repository just as a commit does |
424
- | 📊 **Cache report** | `devp caches` sizes every package manager cache and store on the machine npm to cargo to Maven, Gradle, NuGet, vcpkg, Conan, Composer, CocoaPods and Hex and prints the command that clears each. The report is read-only; `devp caches clear <manager>` runs that command for you, after asking. Nothing on a schedule ever touches a cache |
513
+ | 🐳 **Container report** | `devp caches docker` (also `podman`, `nerdctl`, or `containers` for all of them plus local Kubernetes clusters) breaks a container engine's disk into images, containers, local volumes and build cache, each with what the engine itself calls reclaimable, and prints the prune commands narrowest first with what each takes with it. **Read-only, permanently** — `devp caches clear docker` is a usage error, and no scheduler or hook reaches this code: an image has no lockfile to prove it can be rebuilt, and a named volume cannot be rebuilt at all. `devp caches` carries a one-line summary per engine, outside its own total |
514
+ | 📊 **Cache report** | `devp caches` sizes every package manager cache and store on the machine — npm to cargo to conda, Maven, Gradle, NuGet, vcpkg, Conan, Composer, CocoaPods and Hex — and prints the command that clears each. The report is read-only; `devp caches clear <manager>` runs that command for you, after asking. `devp config set cache_max_gb uv=10,npm=10` says how big is too big, per manager, and marks the ones past it — `devp caches clear --over-cap all` then empties exactly those, still only when you type it. Each manager also says how many of your registered repositories use it and what that works out to per repository, and `devp caches clear --unused all` empties the ones nothing uses at all. pnpm is reported once per filesystem, because a store it hardlinks into `node_modules` cannot cross one and projects kept off the system disk get a store of their own. Nothing on a schedule ever touches a cache, and Maven's `~/.m2/repository` is never cleared at all — it holds artifacts `mvn install:install-file` put there that no remote can hand back |
425
515
  | 🩺 **`devp doctor`** | One read-only pass that ends by naming the *single* reason a repository would or would not be pruned. Runs no package manager, repairs nothing, safe to run twice. `devp doctor --fix` then mends what it found — installed-but-broken only |
426
516
  | 🤖 **Self-installing automation** | OS-native scheduler (Task Scheduler, LaunchAgent, systemd user timer) and non-blocking Git hooks, installed at install time and restored after an upgrade. `auto_setup`, `auto_hooks`, `auto_daemon` or `DEV_PRUNE_NO_AUTO_SETUP=1` turn it off |
427
517
  | ⚡ **0ms opt-out** | An `ignore.devprune.json` in a repository root is honoured by file presence alone — no read, no parse |
@@ -436,27 +526,28 @@ process that leaves a dirty working tree is a surprise.
436
526
 
437
527
  ## Commands
438
528
 
439
- | Command | Also | What it does |
440
- | :--------------------- | :------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
441
- | `devp init [PATHS]` | `scan`, `onboard` | Crawls directory trees for Git repositories and registers them, then runs the `setup` integration pass |
442
- | `devp link [PATH]` | | Registers one repository |
443
- | `devp unlink [PATH]` | `--missing` | Unregisters one; `--missing` drops every entry whose directory is gone, in one pass |
444
- | `devp undo` | | Reverts the most recent `init` or `link` |
445
- | `devp run [PATH]` | `--dry-run`, `--only`, `--skip`, `--except`, `--min-size`, `--json` | Prunes every registered repository, or one target |
446
- | `devp status` | `--top N`, `--drift`, `--json` | Interactive dashboard; a plain table when there is no TTY. `--top N` shows only the N biggest repositories; `--drift` lists every environment holding packages its lockfile never recorded |
447
- | `devp stats` | `--json` | What has already been reclaimed: lifetime total, prune passes, the last pass, and the biggest contributors |
448
- | `devp completions` | `bash`, `zsh`, `fish`, `powershell`, `elvish` | Prints a shell completion script to stdout, generated from the same argument definitions the binary parses with |
449
- | `devp caches` | `clear <manager\|all>`, `--json` | Sizes every package manager cache on the machine and prints the command that clears each. The report deletes nothing and nothing on a schedule ever will; `clear` empties one when you type it, after asking |
450
- | `devp trust` | `--json` | What dev-prune may do on this machine: the guarantees the code enforces, then the scheduler, hooks and settings read live. Read-only |
451
- | `devp restore [PATH]` | `--last-run` | Reinstalls dependencies for every project in a tree; `--last-run` undoes the last prune pass |
452
- | `devp doctor [PATH]` | `--fix` | Diagnoses the installation, or one repository — ending with the single reason a pass would or would not touch it. `--fix` repairs what the checks found; it never performs a first-time install |
453
- | `devp config [ACTION]` | `get`, `set`, `show`, `wizard`, `project`, `daemon`, `hook`, `icon` | Global settings, per-repository `.devprune.json`, scheduler, Git hooks, file manager icons |
454
- | `devp setup` | `--status` | Installs any missing integration; `--status` only reports |
455
- | `devp update` | `--offline`, `--install` | Prints the installed version, checks GitHub for a newer release, shows the upgrade command for your install channel; `--install` runs that upgrade through the channel that owns this copy |
456
- | `devp skill` | `--agent <editor>` | Exports `SKILL.md` and prints AI agent onboarding prompts; `--agent` writes per-repository rules for 15 editors — Cursor, Windsurf, Antigravity, Cline, Roo, Kilo Code, Continue, Amazon Q, Kiro, Trae, Junie, Gemini CLI, Zed, Copilot or `AGENTS.md` |
457
- | `devp man` | `--dir <dir>` | The manual as man pages, generated from the same argument definitions `--help` prints; alone it emits `devp(1)` to stdout, `--dir` writes the full set |
458
- | `devp uninstall` | `--deep` | Removes the scheduler, hooks, both binaries and every other installed copy it can find on the machine; `--deep` also clears configuration |
459
- | `devp -V` | | Version plus an environment audit: OS, architecture, config path, PATH activation |
529
+ | Command | Also | What it does |
530
+ | :--------------------- | :------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
531
+ | `devp init [PATHS]` | `scan`, `onboard` | Crawls directory trees for Git repositories and registers them, then runs the `setup` integration pass |
532
+ | `devp link [PATH]` | :------------------------------------------------------------------ | Registers one repository |
533
+ | `devp unlink [PATH]` | `--missing` | Unregisters one; `--missing` drops every entry whose directory is gone, in one pass |
534
+ | `devp undo` | :------------------------------------------------------------------ | Reverts the most recent `init` or `link` |
535
+ | `devp run [PATH]` | `--dry-run`, `--only`, `--skip`, `--except`, `--min-size`, `--json` | Prunes every registered repository, or one target |
536
+ | `devp status` | `--top N`, `--drift`, `--json` | Interactive dashboard; a plain table when there is no TTY. `--top N` shows only the N biggest repositories; `--drift` lists every environment holding packages its lockfile never recorded |
537
+ | `devp stats` | `--json` | What has already been reclaimed: lifetime total from pruning, a separate lifetime total from `devp caches clear`, prune passes, the last pass, and the biggest contributors |
538
+ | `devp completions` | `bash`, `zsh`, `fish`, `powershell`, `elvish` | Prints a shell completion script to stdout, generated from the same argument definitions the binary parses with |
539
+ | `devp caches` | `clear <manager\|all>`, `--json` | Sizes every package manager cache on the machine and prints the command that clears each. The report deletes nothing and nothing on a schedule ever will; `clear` empties one when you type it, after asking |
540
+ | `devp caches docker` | `podman`, `nerdctl`, `containers [ENGINE]`, `--json` | What a container engine holds images, containers, volumes, build cache — each sized, with what the engine calls reclaimable, then the prune commands. Read-only permanently: it prints them, you run them |
541
+ | `devp trust` | `--json` | What dev-prune may do on this machine: the guarantees the code enforces, then the scheduler, hooks and settings read live. Read-only |
542
+ | `devp restore [PATH]` | `--last-run` | Reinstalls dependencies for every project in a tree; `--last-run` undoes the last prune pass |
543
+ | `devp doctor [PATH]` | `--fix` | Diagnoses the installation, or one repository ending with the single reason a pass would or would not touch it. `--fix` repairs what the checks found; it never performs a first-time install |
544
+ | `devp config [ACTION]` | `get`, `set`, `show`, `wizard`, `project`, `daemon`, `hook`, `icon` | Global settings, per-repository `.devprune.json`, scheduler, Git hooks, file manager icons |
545
+ | `devp setup` | `--status` | Installs any missing integration; `--status` only reports |
546
+ | `devp update` | `--offline`, `--install` | Prints the installed version, checks GitHub for a newer release, shows the upgrade command for your install channel; `--install` runs that upgrade through the channel that owns this copy |
547
+ | `devp skill` | `--agent <editor>` | Exports `SKILL.md` and prints AI agent onboarding prompts; `--agent` writes per-repository rules for 16 editors — Cursor, Windsurf, Antigravity, Cline, Roo, Kilo Code, Continue, Amazon Q, Kiro, Trae, Junie, Gemini CLI, Zed, Copilot, Aider or `AGENTS.md` |
548
+ | `devp man` | `--dir <dir>` | The manual as man pages, generated from the same argument definitions `--help` prints; alone it emits `devp(1)` to stdout, `--dir` writes the full set |
549
+ | `devp uninstall` | `--deep` | Removes the scheduler, hooks, both binaries and every other installed copy it can find on the machine; `--deep` also clears configuration |
550
+ | `devp -V` | :------------------------------------------------------------------ | Version plus an environment audit: OS, architecture, config path, PATH activation |
460
551
 
461
552
  `devp hook`, `devp daemon` and `devp icon` are shorthands for the `config` subcommands of
462
553
  the same name, and `install` / `uninstall` / `on` / `off` work wherever `enable` /
@@ -499,16 +590,19 @@ Adapters detect the project, verify the lockfile, and own the bloat directories:
499
590
  | **SwiftPM** *(opt-in)* | `Package.swift` | `.build` | `Package.swift` declares a `Package(` — the rebuild-from-source proof | *(rebuilt by the next `swift build`)* |
500
591
  | **Dart / Flutter** *(opt-in)* | `pubspec.yaml` | `.dart_tool` | `pubspec.lock` has a `packages:` section and is no older than `pubspec.yaml` | `dart pub get` / `flutter pub get` |
501
592
  | **Mix `_build/`** *(opt-in)* | `mix.exs` | `_build` | `mix.exs` and `mix.lock` both present — the rebuild-from-source proof | *(rebuilt by the next `mix compile`)* |
593
+ | **vcpkg** (C/C++) *(opt-in)* | `vcpkg.json` | `vcpkg_installed` | `vcpkg.json` declares a non-empty `dependencies` list | *(rebuilt by the next `vcpkg install`)* |
594
+ | **CMake** (C/C++) *(opt-in)* | `CMakeLists.txt` | any tree holding a `CMakeCache.txt` | the tree's own `CMakeCache.txt` names a source directory inside this repository | *(rebuilt by the next `cmake --build`)* |
502
595
 
503
596
  A required binary that is missing is a reason to skip, never a reason to delete: if `npm`
504
597
  is not on `PATH`, the `node_modules` it owns is left exactly where it is.
505
598
 
506
- The six build-tool adapters ship **disabled**, because a build tree is regenerated
599
+ The eight build-tool adapters ship **disabled**, because a build tree is regenerated
507
600
  by recompiling, not downloading — it costs more to get back. `devp config set
508
601
  enable_cargo true` / `enable_gradle true` / `enable_maven true` / `enable_swift true` /
509
- `enable_dart true` / `enable_mix_build true` switches them on, and their candidates wait for `build_idle_days` (45 by default),
510
- applied as the *maximum* of it and `idle_days` the build-tool gate only ever makes
511
- pruning later, never earlier.
602
+ `enable_dart true` / `enable_mix_build true` / `enable_vcpkg true` /
603
+ `enable_cmake_build true` switches them on, and their candidates wait for
604
+ `build_idle_days` (45 by default), applied as the *maximum* of it and `idle_days` — the
605
+ build-tool gate only ever makes pruning later, never earlier.
512
606
 
513
607
  Any one adapter can be made to wait longer than the rest: `devp config set
514
608
  adapter_idle_days cargo=90,npm=30` sets a per-adapter floor, applied as
@@ -516,6 +610,30 @@ adapter_idle_days cargo=90,npm=30` sets a per-adapter floor, applied as
516
610
  it beside the adapter checklist, grouped by language, where one heading sets the same
517
611
  window for every adapter under it.
518
612
 
613
+ A download cache is a bet that re-downloading costs less than the disk it occupies,
614
+ and somewhere the bet stops paying. `devp config set cache_max_gb uv=10,npm=10` is
615
+ where you say where: a ceiling in gibibytes, per manager, measured against that
616
+ manager's whole footprint. A manager over its cap is **marked** in `devp caches` —
617
+ setting one deletes nothing — and `devp caches clear --over-cap all` empties exactly
618
+ what is marked, when you type it. It is keyed by the names `devp caches clear` takes
619
+ (`npm`, `pnpm`, `uv`, `pip`, `cargo`, `go`, `nuget`, …) rather than by adapter name,
620
+ because the two lists overlap without either containing the other. Empty by default:
621
+ no cache is too big until you say what too big is. The wizard sets caps as a third
622
+ column on the same checklist.
623
+
624
+ The report answers one more question the disk cannot: *who still needs this*. Beside each
625
+ manager it says how many of your registered repositories use it, and what its cache works
626
+ out to per repository — two repositories sharing a 12 GiB cache is 6 GiB each and worth a
627
+ look, forty sharing the same 12 GiB is 300 MiB each and is the cache doing its job. A
628
+ manager **no** registered repository uses is the one case where a count is enough to act
629
+ on: everything in it was downloaded for projects that are not on this disk any more, so
630
+ `devp caches clear --unused all` costs no re-download for anything you still have. The
631
+ count ignores whether an adapter is switched on, because the question is which managers
632
+ your projects *use*, not which ones a prune pass would touch, and it is shown only for the
633
+ managers that are also adapter names — `pip`, `conda`, `nuget`, `conan` and `hex` get
634
+ no number rather than a guess. With nothing registered, nothing is counted and
635
+ `--unused` refuses to run.
636
+
519
637
  Bundler and Pipenv claim only the install that lives *inside* the repository: the
520
638
  `vendor/bundle` a project gets from `bundle config set path vendor/bundle`, and the
521
639
  `.venv` that appears when `PIPENV_VENV_IN_PROJECT` is set. Both tools default to a
@@ -600,9 +718,10 @@ inherited — and again after an upgrade adds a setting you have never been show
600
718
  | `check_interval_days` | `2` | How often the OS scheduler runs a pass |
601
719
  | `update_check` | `true` | Whether the periodic release check runs |
602
720
  | `update_check_interval_days` · `update_check_timeout_secs` | `7` · `5` | Minimum gap between checks, and how long one may hang |
603
- | `enable_cargo` · `enable_gradle` · `enable_maven` · `enable_swift` · `enable_dart` · `enable_mix_build` | `false` | Turn on an opt-in build-tool adapter; `build_idle_days` (`45`) gates all six |
721
+ | `enable_cargo` `enable_cmake_build` | `false` | Turn on an opt-in build-tool adapter; `build_idle_days` (`45`) gates all eight |
604
722
  | `adapter_idle_days` | *(none)* | Per-adapter idle floors, as `cargo=90,npm=30` — each raises only its own window |
605
723
  | `disabled_adapters` | *(none)* | Adapters to leave alone entirely, by name — as if that ecosystem were not installed |
724
+ | `cache_max_gb` | *(none)* | Per-manager cache caps in GiB, as `uv=10,npm=10` — the cap itself deletes nothing |
606
725
 
607
726
  Three of them — `idle_days` (as `override_idle_days`), `min_size_mb` and `scan_depth` —
608
727
  also take a per-repository form in that project's `.devprune.json`, where they win for
package/bin/dev-prune.js CHANGED
@@ -22,7 +22,16 @@ const os = require('os');
22
22
 
23
23
  const isWin = process.platform === 'win32';
24
24
  const exeName = isWin ? 'dev-prune.exe' : 'dev-prune';
25
- const platformPackage = `dev-prune-${process.platform}-${process.arch}`;
25
+
26
+ // npm refused every `dev-prune-win32-*` name with `E403 - Package name triggered spam
27
+ // detection`, while the identically-shaped linux and darwin names went through, so the
28
+ // refusal tracks the name rather than the payload. The Windows packages are named after
29
+ // the release assets instead. Each one still declares npm's own `win32`/`ia32` values in
30
+ // `os` and `cpu`, so resolution is unchanged - only the name it resolves to differs.
31
+ const WINDOWS_ARCH = { x64: 'x64', arm64: 'arm64', ia32: 'x86' };
32
+ const platformPackage = isWin
33
+ ? `dev-prune-windows-${WINDOWS_ARCH[process.arch] || process.arch}`
34
+ : `dev-prune-${process.platform}-${process.arch}`;
26
35
 
27
36
  // Must mirror Registry::config_dir() in src/config.rs.
28
37
  function configBinDir() {
@@ -57,7 +66,18 @@ const fallbacks = [
57
66
  const binaryPath =
58
67
  bundledBinary() || fallbacks.find((c) => fs.existsSync(c)) || exeName;
59
68
 
60
- const child = spawn(binaryPath, process.argv.slice(2), { stdio: 'inherit' });
69
+ let child;
70
+ try {
71
+ child = spawn(binaryPath, process.argv.slice(2), { stdio: 'inherit' });
72
+ } catch (err) {
73
+ // Windows throws out of `spawn` itself when the loader refuses the image — a
74
+ // wrong-architecture or truncated binary — because there is no child to emit an
75
+ // 'error' event on. Everywhere else the same failure arrives asynchronously. Without
76
+ // this branch the message written for exactly that case never runs, and the user gets
77
+ // a raw Node stack trace instead. `reportLaunchFailure` is a hoisted function
78
+ // declaration, so it is callable from above its definition; it never returns.
79
+ reportLaunchFailure(err);
80
+ }
61
81
 
62
82
  // Forward termination to the child. Ctrl+C already reaches it through the shared
63
83
  // process group, but a bare `kill` of this wrapper — process managers, CI timeouts —
@@ -76,11 +96,19 @@ for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
76
96
 
77
97
  // Without this, a missing binary surfaces as an unhandled 'error' event and a Node
78
98
  // stack trace instead of an actionable message.
79
- child.on('error', (err) => {
80
- if (err.code === 'ENOENT' && binaryPath !== exeName && fs.existsSync(binaryPath)) {
81
- // The file is right there, yet the loader said "no such file" — the kernel's
82
- // report for a binary it cannot load, typically one built for a different
83
- // architecture. "Install what you already installed" would loop the user.
99
+ child.on('error', reportLaunchFailure);
100
+
101
+ function reportLaunchFailure(err) {
102
+ // ENOENT on a file that is demonstrably there, or Windows' UNKNOWN: both are a loader
103
+ // refusing the image rather than a file that is missing.
104
+ if (
105
+ (err.code === 'ENOENT' || err.code === 'UNKNOWN') &&
106
+ binaryPath !== exeName &&
107
+ fs.existsSync(binaryPath)
108
+ ) {
109
+ // The file is right there, yet the loader would not take it — typically a binary
110
+ // built for a different architecture. "Install what you already installed" would
111
+ // loop the user.
84
112
  console.error(
85
113
  `dev-prune: '${binaryPath}' exists but could not be executed.\n\n` +
86
114
  `It may be built for a different architecture than ${process.platform}-${process.arch},\n` +
@@ -107,7 +135,7 @@ child.on('error', (err) => {
107
135
  console.error(`dev-prune: failed to launch ${binaryPath}: ${err.message}`);
108
136
  }
109
137
  process.exit(127);
110
- });
138
+ }
111
139
 
112
140
  // `code` is null when the child was killed by a signal; `code || 0` reported success
113
141
  // in that case. Mirror the shell convention of 128 + signal number instead.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dev-prune",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Universal, lockfile-safe workspace pruner. Reclaims disk space from idle Git repositories by deleting only dependency and build directories a lockfile can rebuild.",
5
5
  "bin": {
6
6
  "dev-prune": "./bin/dev-prune.js",
@@ -12,13 +12,13 @@
12
12
  "LICENSE.md"
13
13
  ],
14
14
  "optionalDependencies": {
15
- "dev-prune-darwin-arm64": "1.7.0",
16
- "dev-prune-darwin-x64": "1.7.0",
17
- "dev-prune-linux-arm64": "1.7.0",
18
- "dev-prune-linux-x64": "1.7.0",
19
- "dev-prune-win32-arm64": "1.7.0",
20
- "dev-prune-win32-ia32": "1.7.0",
21
- "dev-prune-win32-x64": "1.7.0"
15
+ "dev-prune-darwin-arm64": "1.9.0",
16
+ "dev-prune-darwin-x64": "1.9.0",
17
+ "dev-prune-linux-arm64": "1.9.0",
18
+ "dev-prune-linux-x64": "1.9.0",
19
+ "dev-prune-windows-arm64": "1.9.0",
20
+ "dev-prune-windows-x64": "1.9.0",
21
+ "dev-prune-windows-x86": "1.9.0"
22
22
  },
23
23
  "engines": {
24
24
  "node": ">=20"