disk-space-saviour 0.0.0-stage → 0.12.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.
package/README.md CHANGED
@@ -1,3 +1,539 @@
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. The summary line counts them, and
152
+ stopped containers that need consent, as `needs explicit consent`, so
153
+ the tier totals match what `clean --tier … --yes` removes.
154
+ - **Running containers** are never stopped, restarted or removed. They are
155
+ scanned and cleaned from the inside through `docker exec`, with the same
156
+ rules and liveness checks as the host.
157
+ - **Stopped containers** are listed with their size, image, command, owner
158
+ session (`HIVE_MIND_PARENT_SESSION_ID` and other session labels or
159
+ environment variables, else a session id in the container name), task
160
+ URL, exit code and reason, OOM flag and end time, and the Git state of
161
+ repositories in their writable layer. The layer is copied out with
162
+ `docker cp` and checked with Git. A container is removed only with
163
+ `--remove-stopped-containers` or after an interactive yes per container,
164
+ and only when its Git state is clean. Before `docker rm`, its logs and
165
+ `docker inspect` output are saved to `--backup-dir`.
166
+ - **Containers kept for investigation** (a non-zero exit code or OOM
167
+ killed) are never removed by a tier or by `--remove-stopped-containers`,
168
+ in emergency mode too. Name each one with `--remove-container ID|NAME`
169
+ (a full name, or an id prefix of at least 12 characters) to remove it.
170
+
171
+ `--recursive` follows Docker-in-Docker: a container that runs its own daemon
172
+ is scanned as a new environment, down to `--depth` levels (default 3). The
173
+ CI job `Docker-in-Docker Integration` builds a host → l1 → l2 chain and runs
174
+ `tests/integration/dind.mjs` against it (`npm run test:dind`).
175
+
176
+ ## Emergency mode
177
+
178
+ `dss emergency --free 20G` (at least 20 GiB available) or
179
+ `dss emergency --until 80%` (at most 80% used) works through the tiers in
180
+ order. Within a tier it removes caches first, then Docker objects, and
181
+ stopped containers last, only when approved. It re-reads the disk after
182
+ every deletion and stops as soon as the goal is met. It exits with 3 when
183
+ even the aggressive tier cannot reach the goal. `--path` chooses the volume
184
+ (default `/`); items on other host volumes are skipped.
185
+
186
+ ## Library
187
+
188
+ ```js
189
+ import { scan, clean, emergency, formatReport } from 'disk-space-saviour';
190
+
191
+ const report = await scan({ roots: ['/work'], docker: false });
192
+ console.log(formatReport(report));
193
+
194
+ // Library calls delete unless dryRun is set.
195
+ const audit = await clean(report, { tier: 'safe', dryRun: true });
196
+ console.log(audit.plannedBytes, audit.file);
197
+
198
+ await emergency({ free: '20G', removeStoppedContainers: false });
199
+ ```
200
+
201
+ Types ship in `src/index.d.ts`. See `examples/basic-usage.js` for a runnable
202
+ example and `examples/universal-app` for a report viewer.
203
+
204
+ ## Configuration
205
+
206
+ | Option / variable | Default | Purpose |
207
+ | -------------------------------- | ------------------------------------------ | ------------------------------------------- |
208
+ | `--audit-dir`, `DSS_AUDIT_DIR` | `$XDG_STATE_HOME/disk-space-saviour/audit` | Where audit logs go |
209
+ | `--backup-dir`, `DSS_BACKUP_DIR` | `…/disk-space-saviour/backups` | Container log backups before `docker rm` |
210
+ | `--older-than` | `1h` | Activity window; newer files are kept |
211
+ | `--inactive` | `30d` | Project inactivity before `moderate` |
212
+ | `--min-size` | `1M` | Smaller items are not reported |
213
+ | `--depth` | `3` | Docker nesting depth |
214
+ | `--journal-keep` | `512M` | journald size to keep |
215
+ | `--verbose`, `DSS_DEBUG=1` | off | List everything, trace commands and timings |
216
+
217
+ `$XDG_STATE_HOME` falls back to `~/.local/state`. Linux and macOS are
218
+ supported (liveness uses `/proc` on Linux and `lsof` on macOS).
219
+
220
+ ### Repository settings
221
+
222
+ #### Protected-Branch Release Pull Requests
223
+
224
+ If `main` requires pull requests and the `Pipeline Status` check, configure a
225
+ repository secret named `RELEASE_PR_TOKEN`. It must be a fine-grained PAT for
226
+ an automation actor other than the workflow's built-in `GITHUB_TOKEN`, scoped
227
+ to this repository with Contents and Pull requests write access and Checks read
228
+ access. The release and generated-preview fallbacks use it to open the PR, wait
229
+ for the PR's own checks, and merge only after they pass. The manual
230
+ changeset-PR mode uses it for the same reason. Teams that generate short-lived
231
+ GitHub App installation tokens can wire that action output to the same workflow
232
+ inputs instead of storing a PAT.
233
+
234
+ Repositories that allow the release workflow to push directly to `main` do not
235
+ exercise the fallback, but manual changeset PR creation still requires this
236
+ secret.
237
+
238
+ #### Optional Docker Hub Publishing
239
+
240
+ Docker publishing is disabled by default. To enable it for a project that ships
241
+ a Docker image, add a `Dockerfile` and configure these GitHub Actions settings:
242
+
243
+ | Setting | Type | Description |
244
+ | -------------------- | ------------------ | ------------------------------------------------------------------------------------- |
245
+ | `DOCKERHUB_IMAGE` | Variable | Docker Hub image name, for example `namespace/image`. This enables Docker publishing. |
246
+ | `DOCKERHUB_USERNAME` | Variable | Docker Hub username used by `docker/login-action`. |
247
+ | `DOCKERHUB_TOKEN` | Secret | Docker Hub access token used for registry authentication. |
248
+ | `DOCKER_CONTEXT` | Variable, optional | Docker build context. Defaults to `.`. |
249
+ | `DOCKERFILE` | Variable, optional | Dockerfile path. Defaults to `./Dockerfile`. |
250
+
251
+ When enabled, the release workflow waits until the exact published npm version
252
+ is visible in the npm registry, then publishes Docker Hub tags for `latest` and
253
+ that same version. The Docker build also receives `NPM_PACKAGE_VERSION` as a
254
+ build argument so Dockerfiles can install the matching published package.
255
+
256
+ #### ESLint Rules
257
+
258
+ Customize ESLint in `eslint.config.js`. Current configuration:
259
+
260
+ - ES Modules support
261
+ - Prettier integration
262
+ - No console restrictions (common in CLI tools)
263
+ - Strict equality enforcement
264
+ - Async/await best practices
265
+ - **Strict unused variables rule**: No exceptions - all unused variables, arguments, and caught errors must be removed (no `_` prefix exceptions)
266
+
267
+ #### Prettier Options
268
+
269
+ Configured in `.prettierrc`:
270
+
271
+ - Single quotes
272
+ - Semicolons
273
+ - 2-space indentation
274
+ - 80-character line width
275
+ - ES5 trailing commas
276
+ - LF line endings
277
+
278
+ ## Development
279
+
280
+ ```bash
281
+ npm install
282
+ npm test # Node.js, 30s per test
283
+ bun test --timeout 30000 # Bun
284
+ deno test --allow-read # Deno (read-only tests)
285
+ npm run check # lint + format + duplication
286
+ npm run test:dind # Docker-in-Docker integration (needs --privileged)
287
+ node bin/dss.js scan --verbose
288
+ ```
289
+
290
+ ### Project Structure
291
+
292
+ ```
293
+ .
294
+ ├── .changeset/ # Changeset configuration
295
+ ├── .github/workflows/ # GitHub Actions CI/CD
296
+ ├── .husky/ # Git hooks (pre-commit)
297
+ ├── examples/ # Usage examples
298
+ │ └── universal-app/ # React + GitHub Pages + Electron + Capacitor app
299
+ ├── scripts/ # Build and release scripts
300
+ ├── src/ # Source code
301
+ │ ├── index.js # Main entry point
302
+ │ └── index.d.ts # TypeScript definitions
303
+ ├── tests/ # Test files
304
+ ├── .eslintrc.js # ESLint configuration
305
+ ├── .prettierrc # Prettier configuration
306
+ ├── bunfig.toml # Bun configuration
307
+ ├── deno.json # Deno configuration
308
+ └── package.json # Node.js package manifest
309
+ ```
310
+
311
+ ### Multi-Runtime Support
312
+
313
+ This template is designed to work seamlessly with all major JavaScript runtimes:
314
+
315
+ - **Bun**: Primary runtime with highest performance, uses native test support (`bun test`)
316
+ - **Node.js**: Alternative runtime, uses built-in test runner (`node --test`)
317
+ - **Deno**: Secure runtime with built-in TypeScript support (`deno test`)
318
+
319
+ The [test-anywhere](https://github.com/link-foundation/test-anywhere) framework provides a unified testing API that works identically across all runtimes.
320
+
321
+ ### Package Manager Agnostic
322
+
323
+ While `package.json` is the source of truth for dependencies, the template supports:
324
+
325
+ - **bun**: Primary choice, uses `bun.lockb`
326
+ - **npm**: Uses `package-lock.json`
327
+ - **yarn**: Uses `yarn.lock`
328
+ - **pnpm**: Uses `pnpm-lock.yaml`
329
+ - **deno**: Uses `deno.json` for configuration
330
+
331
+ Note: `package-lock.json` is not committed by default to allow any package manager.
332
+
333
+ ### Universal App Example
334
+
335
+ The template includes `examples/universal-app`, a Vite React app that imports
336
+ `add` and `multiply` from `src/index.js` and renders a visual calculator UI.
337
+ The same static build is used by:
338
+
339
+ - GitHub Pages (`npm run example:web:build`)
340
+ - Electron desktop packaging (`npm run example:desktop:package`)
341
+ - Capacitor Android/iOS sync (`npm run example:mobile:sync`)
342
+
343
+ The example app has its own `package.json` and lockfile so template users can
344
+ opt into the frontend stack without adding React, Electron, or Capacitor to the
345
+ library package itself.
346
+
347
+ See [examples/universal-app/README.md](examples/universal-app/README.md) for
348
+ local web, desktop, Android, and iOS testing instructions.
349
+
350
+ ### Code Quality
351
+
352
+ - **ESLint**: Configured with recommended rules + Prettier integration
353
+ - **Prettier**: Consistent code formatting
354
+ - **Husky + lint-staged**: Pre-commit hooks ensure code quality
355
+ - **File size limit**: Files must stay under 1500 lines for maintainability (enforced via ESLint and CI)
356
+
357
+ ### Release Workflow
358
+
359
+ The release workflow uses [Changesets](https://github.com/changesets/changesets) for version management:
360
+
361
+ 1. **Creating a changeset**: Run `bun run changeset` to document changes
362
+ 2. **PR validation**: CI checks for valid changeset in each PR
363
+ 3. **Automated versioning**: Merging to `main` triggers version bump
364
+ 4. **npm publishing**: Automated via OIDC trusted publishing (no tokens needed)
365
+ 5. **Optional Docker Hub publishing**: When configured, waits for the exact npm version and tags the Docker image with that version
366
+ 6. **GitHub releases**: Auto-created with formatted release notes
367
+
368
+ > **First release of a brand-new package**: OIDC trusted publishing cannot
369
+ > create a package that does not exist yet (the first publish fails with
370
+ > `E404`, because a trusted publisher can only be configured for an existing
371
+ > package). To bootstrap, add a repository secret named `NPM_TOKEN` (a
372
+ > granular/automation token with publish access). The release workflow passes
373
+ > it as `NODE_AUTH_TOKEN` automatically. Once the package exists and OIDC
374
+ > trusted publishing is configured on npmjs.com, the token becomes optional and
375
+ > can be removed.
376
+
377
+ #### Manual Releases
378
+
379
+ Two manual release modes are available via GitHub Actions:
380
+
381
+ - **Instant release**: Immediately bump version and publish
382
+ - **Changeset PR**: Create a PR with changeset for review
383
+
384
+ ### CI/CD Pipeline
385
+
386
+ The GitHub Actions workflow (`.github/workflows/release.yml`) implements a fast-fail pipeline:
387
+
388
+ **Fast checks** (~7-30s each, run first for fastest feedback):
389
+
390
+ 1. **Test compilation**: Syntax-checks all `.mjs` files with `node --check`
391
+ 2. **Lint, format & secrets scan**: ESLint, Prettier, jscpd, and [secretlint](https://github.com/secretlint/secretlint) for credential leak detection
392
+ 3. **File line limits**: Enforces the 1500-line limit on JavaScript (`.js`, `.mjs`, `.cjs`) and Markdown (`.md`) files plus `release.yml`
393
+ 4. **Changeset check**: Validates PR has exactly one changeset (added by that PR)
394
+ 5. **Version check**: Blocks manual version changes in `package.json`
395
+ 6. **Documentation validation**: Checks required doc files (doc line limits are enforced by the file line limits check)
396
+
397
+ **Slow checks** (only run after all fast checks pass):
398
+
399
+ 7. **Test matrix**: 3 runtimes × 3 OS = 9 test combinations
400
+ 8. **Broken link checks**: Validates all links in Markdown/HTML files (separate workflow)
401
+
402
+ **Release** (on merge to main):
403
+
404
+ 9. **Changeset merge**: Combines multiple pending changesets at release time
405
+ 10. **Release**: Automated versioning and npm publishing
406
+ 11. **Optional Docker publish**: Publishes Docker Hub `latest` and npm-version tags after the npm package is visible
407
+
408
+ #### Reasonable Timeouts
409
+
410
+ Every CI job declares an explicit `timeout-minutes` so hung steps fail
411
+ in minutes instead of reaching the GitHub Actions default of six hours.
412
+ Fast checks use 5-10 minute caps, release jobs use 30 minutes, and the
413
+ link checker uses 10 minutes for external network variance.
414
+
415
+ That cap is a backstop, never the deadline: GitHub reports a job it
416
+ kills as **cancelled**, not **failed**. Long steps therefore own an
417
+ explicit budget via `scripts/run-with-budget-warning.sh`, which warns at
418
+ 70% of the budget and fails the step with exit code 124 when it expires.
419
+ See [CI-TIMEOUT-BUDGETS.md](docs/CI-TIMEOUT-BUDGETS.md).
420
+
421
+ Individual tests are also capped inside supported runners:
422
+ `npm test` runs `node --test --test-timeout=30000`, and the CI Bun
423
+ runner uses `bun test --timeout 30000`. Both bound a _single test_, not
424
+ the suite, which is why the suite budget above exists. Deno does not
425
+ provide a single global per-test timeout flag, so Deno tests are
426
+ protected by their step budget and the matrix job backstop.
427
+
428
+ See [BEST-PRACTICES.md](docs/BEST-PRACTICES.md) for detailed explanations of each practice.
429
+
430
+ #### Robust Changeset Handling
431
+
432
+ The CI/CD pipeline is designed to handle concurrent PRs gracefully:
433
+
434
+ - **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.
435
+
436
+ - **Release-time Merging**: If multiple changesets exist when releasing, they are automatically merged into a single changeset with:
437
+ - The highest version bump type (major > minor > patch)
438
+ - All descriptions preserved in chronological order
439
+
440
+ 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.
441
+
442
+ ### Deploying the example app
443
+
444
+ The `example-app.yml` workflow deploys the universal example app to GitHub
445
+ Pages on every push to `main`. Before the first run on `main` in a new
446
+ repository created from this template, open **Settings → Pages** and set
447
+ **Source = GitHub Actions**. This is a one-time manual step and cannot be
448
+ configured from a workflow because the Pages source defaults to
449
+ _Deploy from a branch_. Without it, the `pages-deploy` job fails on
450
+ `actions/deploy-pages` with `Get Pages site failed` /
451
+ `Failed to create deployment`. After flipping the source, the workflow
452
+ provisions the Pages site on its first run.
453
+
454
+ ### Auto-regenerated preview screenshots
455
+
456
+ The same `example-app.yml` workflow contains a `preview-regen` job that boots
457
+ the built example app in a headless Chromium via
458
+ [`browser-commander`](https://www.npmjs.com/package/browser-commander) +
459
+ Playwright and writes fresh screenshots to
460
+ `docs/screenshots/example-app/example-app-{locale}-{theme}.png` on every
461
+ push to `main` (and on `workflow_dispatch`). Any drift is committed back to
462
+ `main` so README/site images never go stale between releases. Screenshot-only
463
+ pushes do not match this workflow's path filter, while a protected-branch
464
+ fallback PR remains eligible for its required checks. The job runs in the
465
+ official Playwright container with the browser already installed, avoiding CI
466
+ stalls from live Chromium downloads.
467
+
468
+ The same script is available locally:
469
+
470
+ ```bash
471
+ npm install --prefix examples/universal-app
472
+ npm run example:web:preview-images
473
+ # Verbose probe of <html data-theme>, <html lang>, and PNG signatures:
474
+ PREVIEW_VERBOSE=1 npm run example:web:preview-images
475
+ ```
476
+
477
+ The matrix defaults to `{en, ru} × {light, dark}`. The shipped example app
478
+ has no localization or theme toggle yet, so every cell currently renders
479
+ the same UI — when a fork adds either, the matrix produces real per-cell
480
+ variants without script edits.
481
+
482
+ ### Broken Link Checker
483
+
484
+ The link checker workflow (`.github/workflows/links.yml`) validates all links in Markdown and HTML files:
485
+
486
+ 1. **Detection**: Uses [lychee](https://github.com/lycheeverse/lychee-action) to scan all `*.md` and `*.html` files
487
+ 2. **Web Archive fallback**: For any broken links found, automatically checks the [Wayback Machine](https://web.archive.org) for archived versions
488
+ 3. **Actionable suggestions**: Reports one of three outcomes for each broken link:
489
+ - **Archived**: Suggests the Web Archive URL as a replacement
490
+ - **Not archived**: Clearly reports the link is unrecoverable
491
+ 4. **Scheduled checks**: Runs weekly to catch links that break over time (even if no files changed)
492
+ 5. **Issue creation**: On scheduled runs, creates a GitHub Issue with the full broken links report
493
+
494
+ Add regex patterns to `.lycheeignore` to exclude URLs from checks (e.g., local dev URLs, example.com, known rate-limited sites).
495
+
496
+ ### Scripts Reference
497
+
498
+ | Script | Description |
499
+ | ------------------------------------ | ----------------------------------------------------- |
500
+ | `bun test --timeout 30000` | Run tests with Bun and a 30s per-test cap |
501
+ | `npm test` | Run tests with Node.js and a 30s per-test cap |
502
+ | `bun run lint` | Check code with ESLint |
503
+ | `bun run lint:fix` | Fix ESLint issues automatically |
504
+ | `bun run format` | Format code with Prettier |
505
+ | `bun run format:check` | Check formatting without changing files |
506
+ | `bun run check` | Run all checks (lint + format) |
507
+ | `npm run example:web:dev` | Start the universal app Vite dev server |
508
+ | `npm run example:web:build` | Build the universal app static web bundle |
509
+ | `npm run example:web:preview-images` | Regenerate preview screenshots via browser-commander |
510
+ | `npm run example:desktop:package` | Package the Electron desktop app locally |
511
+ | `npm run example:mobile:sync` | Build and sync the app bundle into Capacitor projects |
512
+ | `bun run changeset` | Create a new changeset |
513
+
514
+ ### Best Practices
515
+
516
+ The repository implements CI/CD best practices for AI-driven development. See [BEST-PRACTICES.md](docs/BEST-PRACTICES.md) for details on:
517
+
518
+ - File size limits for AI readability
519
+ - Automated formatting and linting
520
+ - Multi-runtime and cross-platform testing
521
+ - Changeset-based versioning
522
+ - Concurrency control for CI/CD pipelines
523
+
524
+ ## Contributing
525
+
526
+ See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for detailed contribution guidelines.
527
+
528
+ Quick steps:
529
+
530
+ 1. Fork the repository
531
+ 2. Create a feature branch: `git checkout -b feature/my-feature`
532
+ 3. Make your changes
533
+ 4. Create a changeset: `bun run changeset`
534
+ 5. Commit your changes (pre-commit hooks will run automatically)
535
+ 6. Push and create a Pull Request
536
+
537
+ ## License
538
+
539
+ [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.12.0",
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
+ }