disk-space-saviour 0.0.0-stage → 0.11.30

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.
package/README.md CHANGED
@@ -1,3 +1,537 @@
1
- # Temporary Holding Version
1
+ # disk-space-saviour
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `dss` finds and safely deletes regenerable disk usage: package manager
4
+ caches, build outputs, forgotten dependency folders, old toolchain versions,
5
+ agent and browser caches, system logs, and Docker data. That includes caches
6
+ inside running containers and in nested Docker-in-Docker daemons. It is a
7
+ command line tool and a JavaScript library (`scan()` → report,
8
+ `clean(report)` → audit log).
9
+
10
+ ## Why
11
+
12
+ Build machines and AI agent hosts fill their disks with data that can be
13
+ rebuilt: `node_modules`, `target/`, `~/.cache`, Docker layers and caches left
14
+ inside long-lived containers. Deleting this data by hand is risky, because
15
+ the same folder may belong to a build that is running right now or to a
16
+ container holding unpushed work. `dss` only touches regenerable data. It
17
+ checks liveness and Git state again right before each deletion, and it never
18
+ deletes anything unless you ask it to.
19
+
20
+ ## Quick Start
21
+
22
+ ```bash
23
+ npm install -g disk-space-saviour # or: npx disk-space-saviour scan
24
+
25
+ dss scan # report only, never deletes
26
+ dss clean # plan for the safe tier (dry run)
27
+ dss clean --yes # delete the safe tier
28
+ dss clean --tier moderate --yes
29
+ dss emergency --free 20G --yes # escalate tiers until 20 GiB is free
30
+ dss docker scan --recursive # containers and nested daemons
31
+ ```
32
+
33
+ Example (`dss scan ~/work` on two projects untouched for 40 days):
34
+
35
+ ```
36
+ disk-space-saviour scan of host (build-01) in 3.7s (report only, nothing was deleted)
37
+ /: 75.8 GiB free of 193 GiB (61% used)
38
+
39
+ MODERATE 2 items, 7.6 MiB
40
+ 4.8 MiB cargo-target /home/me/work/api/target
41
+ 2.9 MiB node-modules /home/me/work/web/node_modules
42
+
43
+ Reclaimable (cumulative): safe 0 B · moderate 7.6 MiB · aggressive 7.6 MiB; blocked 0 B
44
+ Audit log: /home/me/.local/state/disk-space-saviour/audit/dss-scan-2026-09-26T00-31-05-432Z-232874.json
45
+ Run `dss clean --tier safe` to see the plan, add --yes to delete.
46
+ ```
47
+
48
+ `dss clean` without `--yes` asks on a terminal. When nobody can answer
49
+ (CI, agents, pipes), it prints the plan, deletes nothing, and says
50
+ `Dry run: pass --yes to delete non-interactively.` Add `--json` to any
51
+ command to get the report or audit log as JSON (`dss scan --json`).
52
+
53
+ ## Commands
54
+
55
+ ```
56
+ dss scan [paths...] [--docker] [--depth N] [--json] # report only
57
+ dss clean [paths...] --tier safe|moderate [--yes] [--older-than 1h]
58
+ dss emergency --free 20G | --until 80% [--path /] [--yes]
59
+ dss docker scan|clean [--container ID] [--recursive]
60
+ ```
61
+
62
+ | Exit code | Meaning |
63
+ | --------- | ---------------------------------------- |
64
+ | 0 | success (a dry run counts as success) |
65
+ | 1 | error, or at least one deletion failed |
66
+ | 2 | usage error |
67
+ | 3 | `dss emergency` could not reach its goal |
68
+
69
+ Run `dss --help` for every option. The most useful ones:
70
+
71
+ - `--only NAME` limits a run to an ecosystem, rule or kind (`--only rust`,
72
+ `--only npm-cache`, `--only docker-stopped-container`).
73
+ - `--exclude PATH|GLOB` makes paths untouchable.
74
+ - `--scanner NAME` restricts scanning to `projects`, `global`, `versions`,
75
+ `agents` or `system`.
76
+ - `--report FILE` limits cleaning to items in a saved `dss scan --json`
77
+ report. The current rules rescan those items before deletion.
78
+
79
+ ## Tiers
80
+
81
+ Tiers are cumulative: `moderate` includes `safe`, and `aggressive` includes
82
+ both.
83
+
84
+ | Tier | What it removes |
85
+ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
86
+ | `safe` | Download caches (npm, pnpm, yarn, pip, uv, Cargo registry, Gradle, Go, NuGet, and many more), dangling Docker images and build cache. Includes these caches inside running containers. |
87
+ | `moderate` | Whole `node_modules`, `target/`, `.venv`, `build/` and similar folders of projects inactive for `--inactive` (30 days); toolchain versions nothing uses; unused tagged images, each one only with `--remove-image REF` or an interactive yes; stopped containers (`--remove-stopped-containers` or an interactive yes). |
88
+ | `aggressive` | For emergencies: everything regenerable, including build outputs of projects in active use. Only `dss emergency` reaches it. |
89
+
90
+ Covered ecosystems: JavaScript/TypeScript (npm, yarn, pnpm, bun, deno,
91
+ framework build caches), Python, Rust, JVM (Gradle, Maven, Kotlin, Android),
92
+ Go, C/C++ (CMake, ccache, vcpkg, Conan), .NET, PHP, Ruby, Swift/Xcode, Dart,
93
+ Haskell, Scala, Elixir, OCaml, Lean, Julia, R, Zig, and scripting languages.
94
+ Also covered: version managers (nvm, pyenv, rbenv, SDKMAN!, rustup, elan,
95
+ ghcup, opam, swiftly, VS Code Server), Playwright, playwright-go, Puppeteer
96
+ and Cypress browsers, Chrome, Yandex Browser and Firefox HTTP caches, IDE
97
+ caches (JetBrains, VS Code, Cursor, Windsurf, Qoder), AI agent caches and logs,
98
+ the Discord cache, macOS app updates staged by Sparkle and Squirrel, package
99
+ manager archives, trash, crash reports, core dumps and journald logs.
100
+
101
+ ## Safety
102
+
103
+ - **Dry run by default.** `scan` never deletes. `clean` and `emergency`
104
+ delete only with `--yes` or an interactive yes, and never with
105
+ `--dry-run`.
106
+ - **Regenerable data only.** Every rule describes data that a tool
107
+ recreates: a cache, a build output or a dependency install.
108
+ - **Liveness checks, repeated right before each path is deleted.** Paths
109
+ that a process holds open (`/proc/*/fd`, `cwd`, `exe`, or `lsof` on
110
+ macOS) are skipped. So are projects with a running `cargo`, `gradle`,
111
+ `npm`, `node` or similar tool, and anything modified within
112
+ `--older-than` (1h).
113
+ - **Rust builds are pruned in two parts.** `cargo-superseded` removes
114
+ older hashes of a unit (`.rlib`, `.rmeta`, `.so`/`.dylib`/`.a`,
115
+ `build/*`, `.fingerprint`) that a newer build replaced, but never while
116
+ `cargo` or `rustc` runs in the project: a running build may have judged
117
+ them fresh. `cargo-superseded-leaf` removes incremental session
118
+ directories idle for 3 hours and test/example binaries older than 3
119
+ hours that a newer build replaced. Nothing links against them, so this
120
+ part stays eligible while cargo runs; each file a process runs or holds
121
+ open is skipped.
122
+ - **Running tasks must survive.** Before its first deletion in an
123
+ environment (the host or a container), `clean` records PID 1, agents
124
+ (`claude`, `codex`, `solve`, ...) and build tools (`cargo`, `gradle`,
125
+ ...) with their start times. It checks them again after every removed
126
+ path and item. If one is gone or its PID was reused, cleaning in that
127
+ environment stops at once, and the audit log and summary say which
128
+ process was lost. A build that simply finishes during the cleanup also
129
+ stops it: an exit cannot be told apart from a crash.
130
+ - **Git awareness.** A project or container is not touched while it has
131
+ uncommitted changes, unpushed commits or stashes. The blocker is shown in
132
+ the report. Stopped containers with Git work require an exact
133
+ `--allow-dirty-container ID` override.
134
+ - **Audit log on every run.** Each run, dry runs included, writes
135
+ `dss-<command>-<time>-<pid>.json` with every decision, the bytes freed
136
+ and left per environment, and disk usage before and after.
137
+
138
+ ## Docker
139
+
140
+ With `--docker`, or automatically when a daemon is reachable, `dss` handles
141
+ three kinds of Docker data:
142
+
143
+ - **Daemon data:** `docker system df`, dangling images and build cache.
144
+ `--include-volumes` lists unattached volumes for manual inspection; it
145
+ does not delete them.
146
+ - **Unused tagged images** are listed with their size, creation date and
147
+ the container that used them last (from the `docker inspect` backups
148
+ of containers dss removed), when known. No tier removes them, emergency
149
+ mode included: approve each one with `--remove-image REF` (`repo:tag`,
150
+ `repo` for `:latest`, or an id prefix of at least 12 characters) or an
151
+ interactive yes for that image.
152
+ - **Running containers** are never stopped, restarted or removed. They are
153
+ scanned and cleaned from the inside through `docker exec`, with the same
154
+ rules and liveness checks as the host.
155
+ - **Stopped containers** are listed with their size, image, command, owner
156
+ session (`HIVE_MIND_PARENT_SESSION_ID` and other session labels or
157
+ environment variables, else a session id in the container name), task
158
+ URL, exit code and reason, OOM flag and end time, and the Git state of
159
+ repositories in their writable layer. The layer is copied out with
160
+ `docker cp` and checked with Git. A container is removed only with
161
+ `--remove-stopped-containers` or after an interactive yes per container,
162
+ and only when its Git state is clean. Before `docker rm`, its logs and
163
+ `docker inspect` output are saved to `--backup-dir`.
164
+ - **Containers kept for investigation** (a non-zero exit code or OOM
165
+ killed) are never removed by a tier or by `--remove-stopped-containers`,
166
+ in emergency mode too. Name each one with `--remove-container ID|NAME`
167
+ (a full name, or an id prefix of at least 12 characters) to remove it.
168
+
169
+ `--recursive` follows Docker-in-Docker: a container that runs its own daemon
170
+ is scanned as a new environment, down to `--depth` levels (default 3). The
171
+ CI job `Docker-in-Docker Integration` builds a host → l1 → l2 chain and runs
172
+ `tests/integration/dind.mjs` against it (`npm run test:dind`).
173
+
174
+ ## Emergency mode
175
+
176
+ `dss emergency --free 20G` (at least 20 GiB available) or
177
+ `dss emergency --until 80%` (at most 80% used) works through the tiers in
178
+ order. Within a tier it removes caches first, then Docker objects, and
179
+ stopped containers last, only when approved. It re-reads the disk after
180
+ every deletion and stops as soon as the goal is met. It exits with 3 when
181
+ even the aggressive tier cannot reach the goal. `--path` chooses the volume
182
+ (default `/`); items on other host volumes are skipped.
183
+
184
+ ## Library
185
+
186
+ ```js
187
+ import { scan, clean, emergency, formatReport } from 'disk-space-saviour';
188
+
189
+ const report = await scan({ roots: ['/work'], docker: false });
190
+ console.log(formatReport(report));
191
+
192
+ // Library calls delete unless dryRun is set.
193
+ const audit = await clean(report, { tier: 'safe', dryRun: true });
194
+ console.log(audit.plannedBytes, audit.file);
195
+
196
+ await emergency({ free: '20G', removeStoppedContainers: false });
197
+ ```
198
+
199
+ Types ship in `src/index.d.ts`. See `examples/basic-usage.js` for a runnable
200
+ example and `examples/universal-app` for a report viewer.
201
+
202
+ ## Configuration
203
+
204
+ | Option / variable | Default | Purpose |
205
+ | -------------------------------- | ------------------------------------------ | ------------------------------------------- |
206
+ | `--audit-dir`, `DSS_AUDIT_DIR` | `$XDG_STATE_HOME/disk-space-saviour/audit` | Where audit logs go |
207
+ | `--backup-dir`, `DSS_BACKUP_DIR` | `…/disk-space-saviour/backups` | Container log backups before `docker rm` |
208
+ | `--older-than` | `1h` | Activity window; newer files are kept |
209
+ | `--inactive` | `30d` | Project inactivity before `moderate` |
210
+ | `--min-size` | `1M` | Smaller items are not reported |
211
+ | `--depth` | `3` | Docker nesting depth |
212
+ | `--journal-keep` | `512M` | journald size to keep |
213
+ | `--verbose`, `DSS_DEBUG=1` | off | List everything, trace commands and timings |
214
+
215
+ `$XDG_STATE_HOME` falls back to `~/.local/state`. Linux and macOS are
216
+ supported (liveness uses `/proc` on Linux and `lsof` on macOS).
217
+
218
+ ### Repository settings
219
+
220
+ #### Protected-Branch Release Pull Requests
221
+
222
+ If `main` requires pull requests and the `Pipeline Status` check, configure a
223
+ repository secret named `RELEASE_PR_TOKEN`. It must be a fine-grained PAT for
224
+ an automation actor other than the workflow's built-in `GITHUB_TOKEN`, scoped
225
+ to this repository with Contents and Pull requests write access and Checks read
226
+ access. The release and generated-preview fallbacks use it to open the PR, wait
227
+ for the PR's own checks, and merge only after they pass. The manual
228
+ changeset-PR mode uses it for the same reason. Teams that generate short-lived
229
+ GitHub App installation tokens can wire that action output to the same workflow
230
+ inputs instead of storing a PAT.
231
+
232
+ Repositories that allow the release workflow to push directly to `main` do not
233
+ exercise the fallback, but manual changeset PR creation still requires this
234
+ secret.
235
+
236
+ #### Optional Docker Hub Publishing
237
+
238
+ Docker publishing is disabled by default. To enable it for a project that ships
239
+ a Docker image, add a `Dockerfile` and configure these GitHub Actions settings:
240
+
241
+ | Setting | Type | Description |
242
+ | -------------------- | ------------------ | ------------------------------------------------------------------------------------- |
243
+ | `DOCKERHUB_IMAGE` | Variable | Docker Hub image name, for example `namespace/image`. This enables Docker publishing. |
244
+ | `DOCKERHUB_USERNAME` | Variable | Docker Hub username used by `docker/login-action`. |
245
+ | `DOCKERHUB_TOKEN` | Secret | Docker Hub access token used for registry authentication. |
246
+ | `DOCKER_CONTEXT` | Variable, optional | Docker build context. Defaults to `.`. |
247
+ | `DOCKERFILE` | Variable, optional | Dockerfile path. Defaults to `./Dockerfile`. |
248
+
249
+ When enabled, the release workflow waits until the exact published npm version
250
+ is visible in the npm registry, then publishes Docker Hub tags for `latest` and
251
+ that same version. The Docker build also receives `NPM_PACKAGE_VERSION` as a
252
+ build argument so Dockerfiles can install the matching published package.
253
+
254
+ #### ESLint Rules
255
+
256
+ Customize ESLint in `eslint.config.js`. Current configuration:
257
+
258
+ - ES Modules support
259
+ - Prettier integration
260
+ - No console restrictions (common in CLI tools)
261
+ - Strict equality enforcement
262
+ - Async/await best practices
263
+ - **Strict unused variables rule**: No exceptions - all unused variables, arguments, and caught errors must be removed (no `_` prefix exceptions)
264
+
265
+ #### Prettier Options
266
+
267
+ Configured in `.prettierrc`:
268
+
269
+ - Single quotes
270
+ - Semicolons
271
+ - 2-space indentation
272
+ - 80-character line width
273
+ - ES5 trailing commas
274
+ - LF line endings
275
+
276
+ ## Development
277
+
278
+ ```bash
279
+ npm install
280
+ npm test # Node.js, 30s per test
281
+ bun test --timeout 30000 # Bun
282
+ deno test --allow-read # Deno (read-only tests)
283
+ npm run check # lint + format + duplication
284
+ npm run test:dind # Docker-in-Docker integration (needs --privileged)
285
+ node bin/dss.js scan --verbose
286
+ ```
287
+
288
+ ### Project Structure
289
+
290
+ ```
291
+ .
292
+ ├── .changeset/ # Changeset configuration
293
+ ├── .github/workflows/ # GitHub Actions CI/CD
294
+ ├── .husky/ # Git hooks (pre-commit)
295
+ ├── examples/ # Usage examples
296
+ │ └── universal-app/ # React + GitHub Pages + Electron + Capacitor app
297
+ ├── scripts/ # Build and release scripts
298
+ ├── src/ # Source code
299
+ │ ├── index.js # Main entry point
300
+ │ └── index.d.ts # TypeScript definitions
301
+ ├── tests/ # Test files
302
+ ├── .eslintrc.js # ESLint configuration
303
+ ├── .prettierrc # Prettier configuration
304
+ ├── bunfig.toml # Bun configuration
305
+ ├── deno.json # Deno configuration
306
+ └── package.json # Node.js package manifest
307
+ ```
308
+
309
+ ### Multi-Runtime Support
310
+
311
+ This template is designed to work seamlessly with all major JavaScript runtimes:
312
+
313
+ - **Bun**: Primary runtime with highest performance, uses native test support (`bun test`)
314
+ - **Node.js**: Alternative runtime, uses built-in test runner (`node --test`)
315
+ - **Deno**: Secure runtime with built-in TypeScript support (`deno test`)
316
+
317
+ The [test-anywhere](https://github.com/link-foundation/test-anywhere) framework provides a unified testing API that works identically across all runtimes.
318
+
319
+ ### Package Manager Agnostic
320
+
321
+ While `package.json` is the source of truth for dependencies, the template supports:
322
+
323
+ - **bun**: Primary choice, uses `bun.lockb`
324
+ - **npm**: Uses `package-lock.json`
325
+ - **yarn**: Uses `yarn.lock`
326
+ - **pnpm**: Uses `pnpm-lock.yaml`
327
+ - **deno**: Uses `deno.json` for configuration
328
+
329
+ Note: `package-lock.json` is not committed by default to allow any package manager.
330
+
331
+ ### Universal App Example
332
+
333
+ The template includes `examples/universal-app`, a Vite React app that imports
334
+ `add` and `multiply` from `src/index.js` and renders a visual calculator UI.
335
+ The same static build is used by:
336
+
337
+ - GitHub Pages (`npm run example:web:build`)
338
+ - Electron desktop packaging (`npm run example:desktop:package`)
339
+ - Capacitor Android/iOS sync (`npm run example:mobile:sync`)
340
+
341
+ The example app has its own `package.json` and lockfile so template users can
342
+ opt into the frontend stack without adding React, Electron, or Capacitor to the
343
+ library package itself.
344
+
345
+ See [examples/universal-app/README.md](examples/universal-app/README.md) for
346
+ local web, desktop, Android, and iOS testing instructions.
347
+
348
+ ### Code Quality
349
+
350
+ - **ESLint**: Configured with recommended rules + Prettier integration
351
+ - **Prettier**: Consistent code formatting
352
+ - **Husky + lint-staged**: Pre-commit hooks ensure code quality
353
+ - **File size limit**: Files must stay under 1500 lines for maintainability (enforced via ESLint and CI)
354
+
355
+ ### Release Workflow
356
+
357
+ The release workflow uses [Changesets](https://github.com/changesets/changesets) for version management:
358
+
359
+ 1. **Creating a changeset**: Run `bun run changeset` to document changes
360
+ 2. **PR validation**: CI checks for valid changeset in each PR
361
+ 3. **Automated versioning**: Merging to `main` triggers version bump
362
+ 4. **npm publishing**: Automated via OIDC trusted publishing (no tokens needed)
363
+ 5. **Optional Docker Hub publishing**: When configured, waits for the exact npm version and tags the Docker image with that version
364
+ 6. **GitHub releases**: Auto-created with formatted release notes
365
+
366
+ > **First release of a brand-new package**: OIDC trusted publishing cannot
367
+ > create a package that does not exist yet (the first publish fails with
368
+ > `E404`, because a trusted publisher can only be configured for an existing
369
+ > package). To bootstrap, add a repository secret named `NPM_TOKEN` (a
370
+ > granular/automation token with publish access). The release workflow passes
371
+ > it as `NODE_AUTH_TOKEN` automatically. Once the package exists and OIDC
372
+ > trusted publishing is configured on npmjs.com, the token becomes optional and
373
+ > can be removed.
374
+
375
+ #### Manual Releases
376
+
377
+ Two manual release modes are available via GitHub Actions:
378
+
379
+ - **Instant release**: Immediately bump version and publish
380
+ - **Changeset PR**: Create a PR with changeset for review
381
+
382
+ ### CI/CD Pipeline
383
+
384
+ The GitHub Actions workflow (`.github/workflows/release.yml`) implements a fast-fail pipeline:
385
+
386
+ **Fast checks** (~7-30s each, run first for fastest feedback):
387
+
388
+ 1. **Test compilation**: Syntax-checks all `.mjs` files with `node --check`
389
+ 2. **Lint, format & secrets scan**: ESLint, Prettier, jscpd, and [secretlint](https://github.com/secretlint/secretlint) for credential leak detection
390
+ 3. **File line limits**: Enforces the 1500-line limit on JavaScript (`.js`, `.mjs`, `.cjs`) and Markdown (`.md`) files plus `release.yml`
391
+ 4. **Changeset check**: Validates PR has exactly one changeset (added by that PR)
392
+ 5. **Version check**: Blocks manual version changes in `package.json`
393
+ 6. **Documentation validation**: Checks required doc files (doc line limits are enforced by the file line limits check)
394
+
395
+ **Slow checks** (only run after all fast checks pass):
396
+
397
+ 7. **Test matrix**: 3 runtimes × 3 OS = 9 test combinations
398
+ 8. **Broken link checks**: Validates all links in Markdown/HTML files (separate workflow)
399
+
400
+ **Release** (on merge to main):
401
+
402
+ 9. **Changeset merge**: Combines multiple pending changesets at release time
403
+ 10. **Release**: Automated versioning and npm publishing
404
+ 11. **Optional Docker publish**: Publishes Docker Hub `latest` and npm-version tags after the npm package is visible
405
+
406
+ #### Reasonable Timeouts
407
+
408
+ Every CI job declares an explicit `timeout-minutes` so hung steps fail
409
+ in minutes instead of reaching the GitHub Actions default of six hours.
410
+ Fast checks use 5-10 minute caps, release jobs use 30 minutes, and the
411
+ link checker uses 10 minutes for external network variance.
412
+
413
+ That cap is a backstop, never the deadline: GitHub reports a job it
414
+ kills as **cancelled**, not **failed**. Long steps therefore own an
415
+ explicit budget via `scripts/run-with-budget-warning.sh`, which warns at
416
+ 70% of the budget and fails the step with exit code 124 when it expires.
417
+ See [CI-TIMEOUT-BUDGETS.md](docs/CI-TIMEOUT-BUDGETS.md).
418
+
419
+ Individual tests are also capped inside supported runners:
420
+ `npm test` runs `node --test --test-timeout=30000`, and the CI Bun
421
+ runner uses `bun test --timeout 30000`. Both bound a _single test_, not
422
+ the suite, which is why the suite budget above exists. Deno does not
423
+ provide a single global per-test timeout flag, so Deno tests are
424
+ protected by their step budget and the matrix job backstop.
425
+
426
+ See [BEST-PRACTICES.md](docs/BEST-PRACTICES.md) for detailed explanations of each practice.
427
+
428
+ #### Robust Changeset Handling
429
+
430
+ The CI/CD pipeline is designed to handle concurrent PRs gracefully:
431
+
432
+ - **PR Validation**: Only validates changesets **added by the current PR**, not pre-existing ones from other merged PRs. This prevents false failures when multiple PRs merge before a release cycle completes.
433
+
434
+ - **Release-time Merging**: If multiple changesets exist when releasing, they are automatically merged into a single changeset with:
435
+ - The highest version bump type (major > minor > patch)
436
+ - All descriptions preserved in chronological order
437
+
438
+ This design decouples PR validation from the need to pull changes from the default branch, reducing conflicts and ensuring that even if CI/CD fails, all unpublished changesets will still get published when the error is resolved.
439
+
440
+ ### Deploying the example app
441
+
442
+ The `example-app.yml` workflow deploys the universal example app to GitHub
443
+ Pages on every push to `main`. Before the first run on `main` in a new
444
+ repository created from this template, open **Settings → Pages** and set
445
+ **Source = GitHub Actions**. This is a one-time manual step and cannot be
446
+ configured from a workflow because the Pages source defaults to
447
+ _Deploy from a branch_. Without it, the `pages-deploy` job fails on
448
+ `actions/deploy-pages` with `Get Pages site failed` /
449
+ `Failed to create deployment`. After flipping the source, the workflow
450
+ provisions the Pages site on its first run.
451
+
452
+ ### Auto-regenerated preview screenshots
453
+
454
+ The same `example-app.yml` workflow contains a `preview-regen` job that boots
455
+ the built example app in a headless Chromium via
456
+ [`browser-commander`](https://www.npmjs.com/package/browser-commander) +
457
+ Playwright and writes fresh screenshots to
458
+ `docs/screenshots/example-app/example-app-{locale}-{theme}.png` on every
459
+ push to `main` (and on `workflow_dispatch`). Any drift is committed back to
460
+ `main` so README/site images never go stale between releases. Screenshot-only
461
+ pushes do not match this workflow's path filter, while a protected-branch
462
+ fallback PR remains eligible for its required checks. The job runs in the
463
+ official Playwright container with the browser already installed, avoiding CI
464
+ stalls from live Chromium downloads.
465
+
466
+ The same script is available locally:
467
+
468
+ ```bash
469
+ npm install --prefix examples/universal-app
470
+ npm run example:web:preview-images
471
+ # Verbose probe of <html data-theme>, <html lang>, and PNG signatures:
472
+ PREVIEW_VERBOSE=1 npm run example:web:preview-images
473
+ ```
474
+
475
+ The matrix defaults to `{en, ru} × {light, dark}`. The shipped example app
476
+ has no localization or theme toggle yet, so every cell currently renders
477
+ the same UI — when a fork adds either, the matrix produces real per-cell
478
+ variants without script edits.
479
+
480
+ ### Broken Link Checker
481
+
482
+ The link checker workflow (`.github/workflows/links.yml`) validates all links in Markdown and HTML files:
483
+
484
+ 1. **Detection**: Uses [lychee](https://github.com/lycheeverse/lychee-action) to scan all `*.md` and `*.html` files
485
+ 2. **Web Archive fallback**: For any broken links found, automatically checks the [Wayback Machine](https://web.archive.org) for archived versions
486
+ 3. **Actionable suggestions**: Reports one of three outcomes for each broken link:
487
+ - **Archived**: Suggests the Web Archive URL as a replacement
488
+ - **Not archived**: Clearly reports the link is unrecoverable
489
+ 4. **Scheduled checks**: Runs weekly to catch links that break over time (even if no files changed)
490
+ 5. **Issue creation**: On scheduled runs, creates a GitHub Issue with the full broken links report
491
+
492
+ Add regex patterns to `.lycheeignore` to exclude URLs from checks (e.g., local dev URLs, example.com, known rate-limited sites).
493
+
494
+ ### Scripts Reference
495
+
496
+ | Script | Description |
497
+ | ------------------------------------ | ----------------------------------------------------- |
498
+ | `bun test --timeout 30000` | Run tests with Bun and a 30s per-test cap |
499
+ | `npm test` | Run tests with Node.js and a 30s per-test cap |
500
+ | `bun run lint` | Check code with ESLint |
501
+ | `bun run lint:fix` | Fix ESLint issues automatically |
502
+ | `bun run format` | Format code with Prettier |
503
+ | `bun run format:check` | Check formatting without changing files |
504
+ | `bun run check` | Run all checks (lint + format) |
505
+ | `npm run example:web:dev` | Start the universal app Vite dev server |
506
+ | `npm run example:web:build` | Build the universal app static web bundle |
507
+ | `npm run example:web:preview-images` | Regenerate preview screenshots via browser-commander |
508
+ | `npm run example:desktop:package` | Package the Electron desktop app locally |
509
+ | `npm run example:mobile:sync` | Build and sync the app bundle into Capacitor projects |
510
+ | `bun run changeset` | Create a new changeset |
511
+
512
+ ### Best Practices
513
+
514
+ The repository implements CI/CD best practices for AI-driven development. See [BEST-PRACTICES.md](docs/BEST-PRACTICES.md) for details on:
515
+
516
+ - File size limits for AI readability
517
+ - Automated formatting and linting
518
+ - Multi-runtime and cross-platform testing
519
+ - Changeset-based versioning
520
+ - Concurrency control for CI/CD pipelines
521
+
522
+ ## Contributing
523
+
524
+ See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for detailed contribution guidelines.
525
+
526
+ Quick steps:
527
+
528
+ 1. Fork the repository
529
+ 2. Create a feature branch: `git checkout -b feature/my-feature`
530
+ 3. Make your changes
531
+ 4. Create a changeset: `bun run changeset`
532
+ 5. Commit your changes (pre-commit hooks will run automatically)
533
+ 6. Push and create a Pull Request
534
+
535
+ ## License
536
+
537
+ [Unlicense](LICENSE) - Public Domain
package/bin/dss.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runCli } from '../src/cli.js';
4
+
5
+ process.exitCode = await runCli(process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,98 @@
1
1
  {
2
2
  "name": "disk-space-saviour",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.11.30",
4
+ "description": "Safely reclaims disk space: caches and build outputs of 20+ ecosystems, forgotten projects, and Docker (running, stopped and nested containers), with an emergency mode",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "types": "src/index.d.ts",
8
+ "bin": {
9
+ "dss": "./bin/dss.js",
10
+ "disk-space-saviour": "./bin/dss.js"
11
+ },
12
+ "files": [
13
+ "bin/",
14
+ "src/",
15
+ "CHANGELOG.md",
16
+ "LICENSE",
17
+ "README.md"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "types": "./src/index.d.ts",
22
+ "import": "./src/index.js"
23
+ }
24
+ },
25
+ "scripts": {
26
+ "test": "node --test --test-timeout=30000 tests/*.test.js",
27
+ "test:dind": "node tests/integration/dind.mjs",
28
+ "lint": "node scripts/lint.mjs",
29
+ "lint:all": "eslint .",
30
+ "lint:fix": "eslint . --fix",
31
+ "format": "prettier --write .",
32
+ "format:check": "prettier --check .",
33
+ "check:duplication": "jscpd .",
34
+ "check": "npm run lint && npm run format:check && npm run check:duplication",
35
+ "example:web:dev": "npm --prefix examples/universal-app run dev",
36
+ "example:web:build": "npm --prefix examples/universal-app run build",
37
+ "example:web:preview": "npm --prefix examples/universal-app run preview",
38
+ "example:web:preview-images": "node scripts/update-preview-images.mjs",
39
+ "example:desktop:package": "npm --prefix examples/universal-app run desktop:package",
40
+ "example:desktop:make": "npm --prefix examples/universal-app run desktop:make",
41
+ "example:mobile:sync": "npm --prefix examples/universal-app run mobile:sync",
42
+ "prepare": "node scripts/install-git-hooks.mjs",
43
+ "changeset": "changeset",
44
+ "changeset:version": "node scripts/changeset-version.mjs",
45
+ "changeset:publish": "changeset publish",
46
+ "changeset:status": "changeset status --since=origin/main"
47
+ },
48
+ "keywords": [
49
+ "disk-space",
50
+ "cleanup",
51
+ "cache",
52
+ "docker",
53
+ "docker-in-docker",
54
+ "node_modules",
55
+ "cargo",
56
+ "emergency",
57
+ "cli"
58
+ ],
59
+ "author": "",
60
+ "license": "Unlicense",
61
+ "devEngines": {
62
+ "packageManager": {
63
+ "name": "npm"
64
+ }
65
+ },
66
+ "publishConfig": {
67
+ "access": "public"
68
+ },
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "git+https://github.com/konard/disk-space-saviour.git"
72
+ },
73
+ "engines": {
74
+ "node": ">=20.0.0"
75
+ },
76
+ "devDependencies": {
77
+ "@changesets/cli": "^2.29.7",
78
+ "eslint": "^9.38.0",
79
+ "eslint-config-prettier": "^10.1.8",
80
+ "eslint-plugin-prettier": "^5.5.4",
81
+ "husky": "^9.1.7",
82
+ "jscpd": "^4.0.5",
83
+ "lint-staged": "^16.2.6",
84
+ "prettier": "^3.6.2",
85
+ "test-anywhere": "^0.8.48"
86
+ },
87
+ "lint-staged": {
88
+ "*.{js,mjs,cjs}": [
89
+ "eslint --fix --max-warnings 0 --no-warn-ignored",
90
+ "prettier --write",
91
+ "prettier --check"
92
+ ],
93
+ "*.md": [
94
+ "prettier --write",
95
+ "prettier --check"
96
+ ]
97
+ }
98
+ }