d4c-pool 0.2.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 +116 -0
  2. package/dist/index.js +1432 -0
  3. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # d4c
2
+
3
+ **Dependency Deduplication Done Dirt Cheap.**
4
+
5
+ Your `node_modules` are scattered across dozens of copies. Pool them.
6
+
7
+ ```bash
8
+ npx d4c-pool dedupe # make identical files share storage — deletes nothing
9
+ npx d4c-pool gc # delete trees nobody has touched in a month
10
+ npx d4c-pool history # what was removed, and how to restore it
11
+ ```
12
+
13
+ Both are dry-run by default.
14
+
15
+ ## Why
16
+
17
+ On the machine this was built for:
18
+
19
+ - 976 `node_modules` trees, holding more than the disk had free
20
+ - `typescript/lib/typescript.js` existed in **89 identical copies**
21
+ - one Next.js SWC binary appeared **38 times**, 4.3 GB of the same bytes
22
+
23
+ Nothing had gone wrong. That is just what happens after a year of
24
+ worktrees, benchmark runs, and abandoned branches.
25
+
26
+ ## `dedupe` — reclaim without losing anything
27
+
28
+ Finds files that are byte-identical across trees and makes them share
29
+ storage through APFS clones. Every copy stays where it is and keeps
30
+ working; they simply stop occupying separate blocks until one is written
31
+ to, at which point that one diverges on its own.
32
+
33
+ Matching is by content, not by lockfile. Twenty projects pinning
34
+ `react@18.3.1` have twenty different lockfiles and one identical
35
+ `react/index.js`. Measured on a real tree, content matching found **30.0
36
+ GB** where lockfile grouping found **10.9 GB**.
37
+
38
+ Because nothing is deleted, `dedupe` does not require a lockfile — which
39
+ makes it the only option for trees `gc` will not touch.
40
+
41
+ ## `gc` — delete what can be rebuilt
42
+
43
+ Removes trees that have seen no activity past a threshold. Refuses
44
+ anything it cannot restore.
45
+
46
+ ## What neither will touch
47
+
48
+ - Trees with recent activity, measured from `node_modules` mtime and git
49
+ index mtime, whichever is more recent
50
+ - Files any process currently has open (checked with `lsof`; if `lsof`
51
+ cannot run, `dedupe` does nothing rather than guess)
52
+ - Symlinked `node_modules` — the real tree lives elsewhere
53
+ - Hardlinked files, where replacing one link frees nothing
54
+ - Files whose permissions differ, where replacing would drop the exec bit
55
+ - Anything crossing a mount boundary, or matched by `--exclude`
56
+ - For `gc` only: trees with no lockfile able to rebuild them. A parent
57
+ lockfile counts only if that directory declares the tree as a workspace
58
+ member
59
+
60
+ Every check is re-run immediately before the destructive step, never
61
+ trusted from the earlier scan.
62
+
63
+ ## Options
64
+
65
+ ```
66
+ d4c dedupe [options]
67
+
68
+ --days N Idle threshold in days (default 30, minimum 1)
69
+ --min-file SIZE Ignore files smaller than this (default 64K)
70
+ --skip-open-check Proceed without checking for open files
71
+ --exclude PATTERN Protect matching paths. Repeatable, full match
72
+ --yes Apply. Without it nothing changes
73
+ --json Machine-readable output on stdout only
74
+
75
+ d4c gc [options]
76
+
77
+ --days N Idle threshold in days (default 30, minimum 1)
78
+ --min-size SIZE Ignore trees smaller than this (e.g. 10M, 1G)
79
+ --exclude PATTERN Protect matching paths. Repeatable, full match
80
+ --depth N How deep to search (default 8)
81
+ --yes Actually delete
82
+ --json Machine-readable output on stdout only
83
+
84
+ d4c history [--limit N] [--json]
85
+ ```
86
+
87
+ ## Read the numbers as a lower bound
88
+
89
+ Idle age comes from mtime, which records the last install or git
90
+ operation — not the last read. **A project you still use can look idle,
91
+ and a long-running process leaves no trace at all.** Check the list before
92
+ passing `--yes`.
93
+
94
+ Sizes come from `st_blocks`, tracking what deletion actually frees, within
95
+ about 3% of measured freed space. Files already sharing blocks through
96
+ APFS clones are over-reported, since that sharing is invisible to
97
+ `st_blocks`.
98
+
99
+ ## Recovery
100
+
101
+ `gc` appends every deletion to `~/.local/state/d4c/deletions.jsonl` as it
102
+ happens, so an interrupted run is still fully recorded. `d4c history`
103
+ prints each project with the install command that rebuilds it, matched to
104
+ the lockfile that was there.
105
+
106
+ `dedupe` records its replacements to the same log. It has nothing to
107
+ recover from — the files are still there.
108
+
109
+ ## Requirements
110
+
111
+ macOS with APFS for `dedupe`, which uses `clonefile(2)` through FFI. `gc`
112
+ runs anywhere. Node 20 or newer.
113
+
114
+ ## License
115
+
116
+ MIT