depfresh 0.11.0 → 1.0.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
@@ -5,129 +5,76 @@
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-3178c6)](https://www.typescriptlang.org/)
6
6
  [![Node.js](https://img.shields.io/badge/Node.js-24+-339933)](https://nodejs.org/)
7
7
 
8
- Keep your npm dependencies fresh. Fast, correct, zero-config. Your AI agent already knows how to use this. You don't even need to read this README -- it did.
9
-
10
- Spiritual successor to [taze](https://github.com/antfu/taze) by Anthony Fu -- a tool that did the job well until maintenance slowed and issues piled up. I took the best ideas, rewrote everything from scratch, fixed the bugs that sat open for years, and made it work for humans and AI agents alike. Credit where it's due.
11
-
12
- ## Features
13
-
14
- - **Zero-config dependency checking** -- run `depfresh` and it tells you what's outdated. No YAML. No PhD.
15
- - **Monorepo & workspace support** -- pnpm, bun, yarn, npm. Auto-detected. Catalog deps included.
16
- - **7 range modes** -- `default`, `major`, `minor`, `patch`, `latest`, `newest`, `next`. One flag, total control.
17
- - **Interactive cherry-picking** -- grouped multiselect with colour-coded severity. Pick what you want, ignore the rest.
18
- - **Per-package modes** -- `packageMode` lets you set exact, glob, or regex patterns per dependency.
19
- - **Write safely** -- `--write` updates files. `--verify-command` tests each dep individually and reverts failures.
20
- - **Post-write hooks** -- `--execute`, `--install`, `--update`. Chain commands after writing.
21
- - **Global packages** -- `--global` checks one detected manager, `--global-all` scans npm + pnpm + bun with deduped package names.
22
- - **Private registries** -- full `.npmrc` support. Scoped registries, auth tokens, env vars. Fixed from day one.
23
- - **JSON output** -- structured envelope for scripts and AI agents. No ANSI noise.
24
- - **CI mode** -- `--fail-on-outdated` exits with code 1. Plug it into your pipeline.
25
- - **SQLite cache** -- WAL mode, 30min TTL, auto-fallback to memory. Fast repeat runs.
26
- - **Provenance tracking** -- warnings for unsigned or downgraded attestations.
27
- - **Node engine compat** -- flags updates that don't match your Node version.
28
- - **Cooldown filter** -- skip versions published less than N days ago. Let the early adopters find the bugs.
29
- - **Sorting** -- 6 strategies: by diff severity, publish time, or name.
30
- - **CRLF preservation** -- Windows line endings survive the write. You're welcome.
31
- - **Nested workspace detection** -- auto-skips monorepos inside monorepos.
32
- - **Programmatic API** -- lifecycle callbacks + addon system for custom workflows.
33
-
34
- ## Why
35
-
36
- Because `npm outdated` gives you a table and then abandons you. Because Renovate requires a PhD in YAML. Because your AI coding assistant should be able to update your deps without you holding its hand.
37
-
38
- depfresh checks every package manifest (`package.json`, `package.yaml`) in your project, tells you what's outdated, and optionally writes the updates. Monorepos, workspace catalogs, private registries - it handles all of it without a config file.
39
-
40
- If both `package.yaml` and `package.json` exist in the same directory, depfresh uses `package.yaml` and skips the sibling `package.json` to avoid duplicate package entries.
8
+ Keep your dependencies fresh. Zero config, fast, monorepo-ready. Your AI agent already knows how to use this.
41
9
 
42
10
  ## Install
43
11
 
44
12
  ```bash
45
- npm install -g depfresh
46
- ```
47
-
48
- Or don't install globally. I'm not your parent.
49
-
50
- ```bash
13
+ # One-off run (no install)
51
14
  npx depfresh
52
15
  pnpm dlx depfresh
53
16
  bunx depfresh
17
+
18
+ # Global install
19
+ npm install -g depfresh
20
+
21
+ # Local devDependency (recommended for team + CI)
22
+ pnpm add -D depfresh
54
23
  ```
55
24
 
56
- Lost? `depfresh help` prints every flag and mode. `depfresh --help-json` spits out the full CLI contract as JSON for the robots. Between the two of them, there's no excuse for not knowing what this thing does.
25
+ | If you want... | Use | Example |
26
+ | --- | --- | --- |
27
+ | Run once in any repo | One-off | `npx depfresh` |
28
+ | Always available on your machine | Global | `pnpm add -g depfresh` |
29
+ | Pinned for team/CI consistency | Local devDep | `npm install -D depfresh` |
57
30
 
58
- ## Usage
31
+ ## Quick Start
59
32
 
60
33
  ```bash
61
- # Check for outdated dependencies
34
+ # What's outdated?
62
35
  depfresh
63
36
 
64
- # Lost? This prints everything.
65
- depfresh help
66
-
67
- # Same thing but for machines and AI agents who can't read tables
68
- depfresh --help-json
69
-
70
- # Actually update them
37
+ # Update everything
71
38
  depfresh --write
72
39
 
73
- # Interactive mode -- pick what to update like a civilised person
74
- depfresh --interactive
75
-
76
- # Only minor/patch updates (living cautiously)
77
- depfresh minor -w
40
+ # Interactive -- pick what to update
41
+ depfresh -I
78
42
 
79
43
  # JSON output for scripts and AI agents
80
44
  depfresh --output json
81
45
 
82
- # Filter specific packages
83
- depfresh --include "react,vue" --exclude "eslint"
84
-
85
- # Verify each dep individually, revert failures
86
- depfresh -w --verify-command "pnpm test"
46
+ # Only minor/patch (living cautiously)
47
+ depfresh minor -w
87
48
 
88
49
  # CI: fail if anything is outdated
89
50
  depfresh --fail-on-outdated
51
+ ```
90
52
 
91
- # Skip specific directories from recursive scan
92
- depfresh --ignore-paths "apps/legacy/**,examples/**"
53
+ ## Features
93
54
 
94
- # Bypass cache for one run (same behavior)
95
- depfresh --refresh-cache
96
- depfresh --no-cache
55
+ - **Zero config** -- run `depfresh` and it works. No YAML. No PhD.
56
+ - **Monorepo & workspace support** -- pnpm, bun, yarn, npm. Auto-detected. Catalogs included.
57
+ - **7 range modes** -- `default`, `major`, `minor`, `patch`, `latest`, `newest`, `next`
58
+ - **Interactive cherry-picking** -- grouped multiselect with colour-coded severity
59
+ - **Per-package modes** -- `packageMode` with exact, glob, or regex patterns per dependency
60
+ - **Write safely** -- `--write` updates files. `--verify-command` tests each dep and reverts failures.
61
+ - **Post-write hooks** -- `--execute`, `--install`, `--update`. Chain commands after writing.
62
+ - **Global packages** -- `--global` for one manager, `--global-all` scans npm + pnpm + bun (deduped)
63
+ - **Private registries** -- full `.npmrc` support. Scoped registries, auth tokens, env vars.
64
+ - **GitHub dependencies** -- `github:owner/repo#tag` with protocol-preserving writes
65
+ - **JSON output** -- structured envelope for scripts and AI agents. No ANSI noise.
66
+ - **CI mode** -- `--fail-on-outdated` exits with code 1. Plug it into your pipeline.
67
+ - **SQLite cache** -- WAL mode, 30min TTL, auto-fallback to memory
68
+ - **Provenance tracking** -- warnings for unsigned or downgraded attestations
69
+ - **Node engine compat** -- flags updates that don't match your Node version
70
+ - **Cooldown filter** -- skip versions published less than N days ago
71
+ - **Programmatic API** -- lifecycle callbacks + addon system for custom workflows
97
72
 
98
- # Check globals across npm + pnpm + bun (deduped names)
99
- depfresh --global-all
100
- ```
73
+ Full CLI reference: **[docs/cli/](docs/cli/)**
74
+
75
+ ## Configuration
101
76
 
102
- ## CLI Flags
103
-
104
- The top flags to get you started. Full reference with all 27+ flags: **[docs/cli/](docs/cli/)**
105
-
106
- | Flag | Alias | Default | Description |
107
- |------|-------|---------|-------------|
108
- | `--recursive` | `-r` | `true` | Recursively search for package manifests (`package.json`, `package.yaml`) |
109
- | `--write` | `-w` | `false` | Write updated versions to package files |
110
- | `--interactive` | `-I` | `false` | Select which deps to update |
111
- | `--mode` | `-m` | `default` | Range mode: `default` `major` `minor` `patch` `latest` `newest` `next` |
112
- | `--include` | `-n` | -- | Only include packages matching regex (comma-separated) |
113
- | `--exclude` | `-x` | -- | Exclude packages matching regex (comma-separated) |
114
- | `--ignore-paths` | -- | -- | Extra ignore globs (comma-separated), merged with default ignored paths |
115
- | `--force` | `-f` | `false` | Force update even if version is satisfied (does not bypass cache) |
116
- | `--refresh-cache` | -- | `false` | Bypass cache reads and fetch fresh metadata for this run |
117
- | `--no-cache` | -- | `false` | Alias for `--refresh-cache` |
118
- | `--global` | `-g` | `false` | Check global packages for one detected package manager |
119
- | `--global-all` | -- | `false` | Check global packages across npm, pnpm, and bun with deduped package names |
120
- | `--output` | `-o` | `table` | Output format: `table` `json` |
121
- | `--execute` | `-e` | -- | Run command after writing (e.g. `"pnpm test"`) |
122
- | `--verify-command` | `-V` | -- | Run command per dep, revert on failure |
123
- | `--install` | `-i` | `false` | Run package manager install after writing |
124
- | `--fail-on-outdated` | -- | `false` | Exit code 1 when outdated deps found (CI mode) |
125
- | `--cooldown` | -- | `0` | Skip versions published less than N days ago |
126
- | `--sort` | `-s` | `diff-asc` | Sort: `diff-asc` `diff-desc` `time-asc` `time-desc` `name-asc` `name-desc` |
127
-
128
- ## Config File
129
-
130
- Zero config works. But if you want it, create `depfresh.config.ts` (or `.depfreshrc`, or add a `depfresh` key to `package.json`):
77
+ Zero config works. But if you want it:
131
78
 
132
79
  ```typescript
133
80
  import { defineConfig } from 'depfresh'
@@ -142,237 +89,37 @@ export default defineConfig({
142
89
  })
143
90
  ```
144
91
 
145
- Addon example (programmatic API):
146
-
147
- ```typescript
148
- import { check, resolveConfig, type depfreshAddon } from 'depfresh'
149
-
150
- const addon: depfreshAddon = {
151
- name: 'audit-log',
152
- afterPackageWrite(_ctx, pkg, changes) {
153
- console.log(`updated ${pkg.name}: ${changes.length} changes`)
154
- },
155
- }
156
-
157
- const options = await resolveConfig({ write: true, addons: [addon] })
158
- await check(options)
159
- ```
160
-
161
- Full options reference: **[docs/configuration/](docs/configuration/)**
162
-
163
- ## JSON Output
164
-
165
- `--output json` emits a single structured envelope. No log noise. No ANSI codes. Just clean JSON that machines can parse without having an existential crisis.
166
-
167
- ```json
168
- {
169
- "packages": [
170
- {
171
- "name": "my-app",
172
- "updates": [
173
- {
174
- "name": "react",
175
- "current": "^18.2.0",
176
- "target": "^19.1.0",
177
- "diff": "major",
178
- "source": "dependencies"
179
- }
180
- ]
181
- }
182
- ],
183
- "errors": [
184
- {
185
- "name": "some-private-pkg",
186
- "source": "dependencies",
187
- "currentVersion": "^1.0.0",
188
- "message": "Failed to resolve from registry"
189
- }
190
- ],
191
- "summary": {
192
- "total": 12,
193
- "major": 1,
194
- "minor": 7,
195
- "patch": 4,
196
- "packages": 3,
197
- "scannedPackages": 3,
198
- "packagesWithUpdates": 3,
199
- "plannedUpdates": 0,
200
- "appliedUpdates": 0,
201
- "revertedUpdates": 0
202
- },
203
- "meta": {
204
- "schemaVersion": 1,
205
- "cwd": "/path/to/project",
206
- "mode": "default",
207
- "timestamp": "2026-02-22T12:00:00.000Z",
208
- "noPackagesFound": false,
209
- "didWrite": false
210
- }
211
- }
212
- ```
213
-
214
- Full schema and field reference: **[docs/output-formats/](docs/output-formats/)**
215
-
216
- ## AI Agent Usage
217
-
218
- depfresh was designed to work with AI coding assistants out of the box. No special configuration needed. Run it blind and it tells you what to do next.
219
-
220
- **Auto-discovery** -- when stdout isn't a TTY (piped, captured by an agent), depfresh prints a hint to stderr: `Tip: Use --output json for structured output. Run --help-json for CLI capabilities.` Agents are stateless. They don't remember your last hint.
221
-
222
- **`--help-json`** returns a full machine-readable contract: version, flags, enums, exit codes, plus:
223
- - `workflows` -- 4 copy-paste agent recipes (`checkOnly`, `safeUpdate`, `fullUpdate`, `selective`)
224
- - `flagRelationships` -- which flags require or conflict with others
225
- - `configFiles` -- every supported config file pattern
226
- - `jsonOutputSchema` -- field descriptions of the JSON envelope
227
-
228
- ```bash
229
- # First run -- just see what happens (agents get the stderr hint)
230
- depfresh
231
-
232
- # Discover the full CLI contract
233
- depfresh --help-json
234
-
235
- # Check for updates, get structured output
236
- depfresh --output json
237
-
238
- # Apply only safe updates
239
- depfresh --write --mode minor --output json
240
-
241
- # Selective update
242
- depfresh --write --include "typescript,vitest" --output json
243
-
244
- # Full send
245
- depfresh --write --mode latest --output json
246
- ```
247
-
248
- **Exit codes are semantic:**
249
- - `0` -- all deps up to date (or updates were written)
250
- - `1` -- updates available (with `--fail-on-outdated`)
251
- - `2` -- error (structured JSON error envelope when `--output json` is active)
252
-
253
- **Structured errors** -- when `--output json` is active and something fails, you get a JSON error envelope with `error.code`, `error.message`, and `error.retryable` instead of plaintext stderr. Resolution failures for individual deps appear in the `errors[]` array of the normal envelope.
254
-
255
- **TTY detection** -- when stdout isn't a terminal, depfresh automatically suppresses spinners and interactive prompts. `NO_COLOR` is respected.
256
-
257
- ## Programmatic API
258
-
259
- ```typescript
260
- import { check, resolveConfig } from 'depfresh'
261
-
262
- const options = await resolveConfig({
263
- cwd: process.cwd(),
264
- mode: 'minor',
265
- write: true,
266
- onDependencyResolved: (pkg, dep) => {
267
- if (dep.diff === 'major') {
268
- console.log(`Major update: ${dep.name} ${dep.currentVersion} -> ${dep.targetVersion}`)
269
- }
270
- },
271
- beforePackageWrite: (pkg) => {
272
- return true // return false to skip
273
- },
274
- })
275
-
276
- const exitCode = await check(options)
277
- ```
278
-
279
- Programmatic API with lifecycle callbacks, addon plugins, and full typed exports. Full reference: **[docs/api/](docs/api/)**
92
+ Supports `depfresh.config.ts`, `.depfreshrc`, or a `depfresh` key in `package.json`. Full reference: **[docs/configuration/](docs/configuration/)**
280
93
 
281
94
  ## Monorepo Support
282
95
 
283
- depfresh auto-detects workspace structures. No config needed.
96
+ depfresh auto-detects pnpm, bun, yarn, and npm workspaces -- no config needed. Workspace catalogs (`pnpm-workspace.yaml`, bun catalogs, yarn `.yarnrc.yml` catalogs) are resolved and updated in-place alongside your package manifests.
284
97
 
285
- | Package Manager | Workspaces | Catalogs |
286
- |----------------|------------|----------|
287
- | pnpm | `pnpm-workspace.yaml` | `catalog:` protocol |
288
- | Bun | `workspaces` in `package.json` or `package.yaml` | `workspaces.catalog` |
289
- | Yarn | `workspaces` in `package.json` or `package.yaml` | `.yarnrc.yml` catalogs |
290
- | npm | `workspaces` in `package.json` or `package.yaml` | -- |
98
+ Details: **[docs/configuration/workspaces.md](docs/configuration/workspaces.md)**
291
99
 
292
- Workspace catalogs are resolved and updated in-place. Your `pnpm-workspace.yaml` catalog entries get depfreshaded alongside your manifest deps (`package.json` / `package.yaml`). No manual sync needed.
100
+ ## AI Agent Friendly
293
101
 
294
- ## Private Registries
102
+ depfresh was built for humans and machines. `--output json` emits a structured envelope. `--help-json` returns the full CLI contract (flags, enums, exit codes, agent workflows). Exit codes are semantic: `0` = up to date, `1` = updates available, `2` = error. Non-TTY environments automatically suppress spinners and interactive prompts.
295
103
 
296
- depfresh reads `.npmrc` from your project and home directory. Scoped registries, auth tokens, proxies -- all respected.
104
+ Details: **[docs/agents/README.md](docs/agents/README.md)**
297
105
 
298
- ```ini
299
- # .npmrc
300
- @mycompany:registry=https://npm.mycompany.com/
301
- //npm.mycompany.com/:_authToken=${NPM_TOKEN}
302
- ```
106
+ ## Coming from taze?
303
107
 
304
- This was broken in taze for 4+ years. I fixed it on day one. You're welcome.
305
-
306
- ## depfresh vs taze
307
-
308
- Verified against taze v19.9.2 (commit `31c6fe8`, 2026-01-20). Not marketing. Real code inspection, runtime test runs, CLI smoke checks on actual repos.
309
-
310
- ### Feature parity (both have it)
311
-
312
- | Feature | taze | depfresh | Notes |
313
- |---------|------|----------|-------|
314
- | 7 range modes | yes | yes | |
315
- | Include/exclude filters | yes | yes | depfresh adds glob patterns alongside regex |
316
- | Interactive TUI | yes | yes | Both have vim keys + per-version selection |
317
- | `--cwd` | yes | yes | |
318
- | `--fail-on-outdated` | yes | yes | |
319
- | `package.yaml` support | yes | yes | |
320
- | Addon/plugin API | yes | yes | |
321
- | pnpm catalogs | yes | yes | |
322
- | Yarn catalogs | yes | yes | |
323
- | CRLF preservation | yes | yes | |
324
- | CJK width handling | yes | yes | |
325
-
326
- ### Where depfresh is ahead
327
-
328
- | Feature | taze | depfresh |
329
- |---------|------|----------|
330
- | JSON output envelope | no ([#201](https://github.com/antfu-collective/taze/issues/201) open) | Structured envelope with schema version |
331
- | Machine-readable CLI contract | no | `--help-json` with workflows, flag relationships, schema |
332
- | `--deps-only` / `--dev-only` | no ([#101](https://github.com/antfu-collective/taze/issues/101) open) | yes |
333
- | `packageMode` precedence | buggy ([#91](https://github.com/antfu-collective/taze/issues/91) open) | Deterministic |
334
- | Global package breadth | npm + pnpm | npm + pnpm + bun (`--global-all`) |
335
- | Bun catalog writes | Bug history, data loss risk | Single-writer architecture, tested |
336
- | `.npmrc` / private registries | Ignored for years | Full support from day one |
337
- | `.npmrc` transport (proxy/TLS/CA) | Parsed, not applied | Applied via `undici` transport adapter |
338
- | Network retry | None | Exponential backoff, non-transient errors fail fast |
339
- | Cache | JSON file (race conditions) | SQLite WAL mode, memory fallback |
340
- | Verify + rollback | no | `--verify-command` tests each dep, reverts failures |
341
- | Typed error hierarchy | Limited | Structured subclasses with `.code` and `.cause` |
342
- | Structured JSON errors | no | JSON error envelope with `error.code`, `error.retryable` |
343
- | Explicit cache bypass | no | `--refresh-cache` / `--no-cache` |
344
-
345
- ### Where taze is ahead
346
-
347
- | Area | Why |
348
- |------|-----|
349
- | Ecosystem adoption | 4,061 stars, years of trust, larger user base |
350
- | npm config edge cases | `@npmcli/config` may cover obscure auth patterns we haven't hit yet |
351
-
352
- ### Numbers
353
-
354
- | Metric | taze v19.9.2 | depfresh v0.11.0 |
355
- |--------|-------------:|------------------:|
356
- | Test files | 13 | 77 |
357
- | Passing tests | 55 | 598 |
358
- | CLI flags | 24 | 36 |
108
+ depfresh is a spiritual successor to [taze](https://github.com/antfu/taze) by Anthony Fu -- a tool that did the job well until maintenance slowed and issues piled up. depfresh rewrites everything from scratch, fixes long-standing bugs (private registries, bun catalogs, packageMode precedence), and adds structured JSON output, verify-and-rollback, SQLite caching, and proper AI agent support.
359
109
 
360
- ## Documentation
110
+ Migration guide: **[docs/compare/from-taze.md](docs/compare/from-taze.md)** | Full comparison: **[docs/compare/](docs/compare/)**
361
111
 
362
- The full docs, for people who read manuals before assembling furniture.
112
+ ## Documentation
363
113
 
364
- - **[CLI Reference](docs/cli/)** -- all 27+ flags, modes, sorting, filtering, hooks, interactive, CI, workspaces
365
- - **[Configuration](docs/configuration/)** -- config files, every option, packageMode, depFields, private registries, cache
366
- - **[Programmatic API](docs/api/)** -- exported functions, lifecycle callbacks, addon plugins, types, workflow examples
367
- - **[Output Formats](docs/output-formats/)** -- table, JSON, exit codes, AI agent integration
368
- - **[Agent Workflows](docs/agents/README.md)** -- copy-paste quickstarts for Codex, Claude Code, and Gemini CLI
369
- - **[Integrations](docs/integrations/README.md)** -- GitHub Actions and thin MCP wrapper guidance
114
+ - **[CLI Reference](docs/cli/)** -- flags, modes, sorting, filtering, hooks, interactive, CI
115
+ - **[Configuration](docs/configuration/)** -- config files, options, packageMode, private registries, cache
116
+ - **[Programmatic API](docs/api/)** -- functions, lifecycle callbacks, addon plugins, types
117
+ - **[Output Formats](docs/output-formats/)** -- table, JSON, exit codes
118
+ - **[Agent Workflows](docs/agents/README.md)** -- quickstarts for AI coding assistants
119
+ - **[Integrations](docs/integrations/README.md)** -- GitHub Actions and MCP wrapper guidance
120
+ - **[Compare](docs/compare/)** -- coverage matrix, migration guide, solved issues
370
121
  - **[Troubleshooting](docs/troubleshooting.md)** -- common issues, workspace gotchas, known limitations
371
122
 
372
- ## Requirements
373
-
374
- - Node.js >= 24
375
-
376
123
  ## Standing on the Shoulders of People Who Actually Did the Work
377
124
 
378
125
  depfresh wouldn't exist without [taze](https://github.com/antfu/taze). I rewrote everything from scratch, yes, but "from scratch" is easy when someone else already figured out what the thing should do. Every bug report, every feature PR, every typo fix in the taze repo was a free lesson in what users actually need. I just took notes and built a new house on someone else's blueprint.
@@ -1,5 +1,5 @@
1
- import { c as check } from '../shared/depfresh.C-me_t4a.mjs';
2
- export { d as detectPackageManager } from '../shared/depfresh.C-me_t4a.mjs';
1
+ import { c as check } from '../shared/depfresh.CTatBHRg.mjs';
2
+ export { d as detectPackageManager } from '../shared/depfresh.CTatBHRg.mjs';
3
3
  import 'ansis';
4
4
  import '../shared/depfresh.Dxgqypc6.mjs';
5
5
  import 'node:fs';
@@ -1,6 +1,6 @@
1
1
  import * as readline from 'node:readline';
2
2
  import * as semver from 'semver';
3
- import { g as getVersionPrefix, f as applyVersionPrefix, h as getDiff, t as timeDifference, s as stripAnsi, i as truncate, j as padEnd, b as colorizeVersionDiff, a as arrow, e as colorDiff } from '../shared/depfresh.C-me_t4a.mjs';
3
+ import { g as getVersionPrefix, f as applyVersionPrefix, h as getDiff, t as timeDifference, s as stripAnsi, i as truncate, j as padEnd, b as colorizeVersionDiff, a as arrow, e as colorDiff } from '../shared/depfresh.CTatBHRg.mjs';
4
4
  import c from 'ansis';
5
5
  import '../shared/depfresh.Dxgqypc6.mjs';
6
6
  import 'node:fs';
@@ -2,7 +2,7 @@ import './config.mjs';
2
2
  import '../shared/depfresh.CHHNqHXD.mjs';
3
3
  import { execSync } from 'node:child_process';
4
4
  import { c as createLogger } from '../shared/depfresh.Dxgqypc6.mjs';
5
- import '../shared/depfresh.C-me_t4a.mjs';
5
+ import '../shared/depfresh.CTatBHRg.mjs';
6
6
 
7
7
  function addonVSCode(pkg, changes) {
8
8
  const vscodeChange = changes.find((c) => c.name === "@types/vscode");
@@ -1,6 +1,6 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import c from 'ansis';
3
- import { a as arrow, b as colorizeVersionDiff, e as colorDiff } from '../shared/depfresh.C-me_t4a.mjs';
3
+ import { a as arrow, b as colorizeVersionDiff, e as colorDiff } from '../shared/depfresh.CTatBHRg.mjs';
4
4
  import '../shared/depfresh.Dxgqypc6.mjs';
5
5
  import 'node:fs';
6
6
  import 'node:os';
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { defineCommand, runMain } from 'citty';
2
+ import { renderUsage, defineCommand, runMain } from 'citty';
3
3
 
4
- const version = "0.11.0";
4
+ const version = "1.0.0";
5
5
 
6
6
  const migrationParityArgs = {
7
7
  "ignore-paths": {
@@ -58,12 +58,12 @@ const args = {
58
58
  include: {
59
59
  type: "string",
60
60
  alias: "n",
61
- description: "Only include packages matching this regex (comma-separated)"
61
+ description: "Only include packages matching these regex/glob patterns (comma-separated)"
62
62
  },
63
63
  exclude: {
64
64
  type: "string",
65
65
  alias: "x",
66
- description: "Exclude packages matching this regex (comma-separated)"
66
+ description: "Exclude packages matching these regex/glob patterns (comma-separated)"
67
67
  },
68
68
  force: {
69
69
  type: "boolean",
@@ -219,6 +219,24 @@ function normalizeCliRawArgs(rawArgs) {
219
219
  return ["--help", ...rawArgs.slice(1)];
220
220
  }
221
221
 
222
+ const DOCUMENTATION_URL = "https://github.com/vcode-sh/depfresh/tree/main/docs";
223
+ const REPOSITORY_URL = "https://github.com/vcode-sh/depfresh";
224
+ function withHelpLinks(usage) {
225
+ return `${usage}
226
+
227
+ Docs: ${DOCUMENTATION_URL}
228
+ GitHub: ${REPOSITORY_URL}
229
+ `;
230
+ }
231
+ async function showUsageWithLinks(cmd, parent) {
232
+ try {
233
+ const usage = await renderUsage(cmd, parent);
234
+ console.log(withHelpLinks(usage));
235
+ } catch (error) {
236
+ console.error(error);
237
+ }
238
+ }
239
+
222
240
  const main = defineCommand({
223
241
  meta: {
224
242
  name: "depfresh",
@@ -228,6 +246,11 @@ const main = defineCommand({
228
246
  args,
229
247
  async run({ args: args2 }) {
230
248
  try {
249
+ if (args2.mode_arg === "help") {
250
+ const { showUsage } = await import('citty');
251
+ await showUsage(main);
252
+ process.exit(0);
253
+ }
231
254
  if (args2["help-json"]) {
232
255
  const { outputCliCapabilities } = await import('./chunks/capabilities.mjs');
233
256
  outputCliCapabilities();
@@ -261,7 +284,8 @@ const main = defineCommand({
261
284
  }
262
285
  });
263
286
  runMain(main, {
264
- rawArgs: normalizeCliRawArgs(process.argv.slice(2))
287
+ rawArgs: normalizeCliRawArgs(process.argv.slice(2)),
288
+ showUsage: showUsageWithLinks
265
289
  });
266
290
 
267
291
  export { args as a, version as v };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  export { D as DEFAULT_OPTIONS, r as resolveConfig } from './chunks/config.mjs';
2
2
  export { A as AddonError, a as CacheError, C as ConfigError, R as RegistryError, b as ResolveError, W as WriteError, d as depfreshError } from './shared/depfresh.CHHNqHXD.mjs';
3
3
  export { a as addonVSCode, c as createVSCodeAddon, d as defineConfig, l as loadGlobalPackages, b as loadGlobalPackagesAll, w as writeGlobalPackage } from './chunks/index3.mjs';
4
- export { c as check, l as loadPackages, p as parseDependencies, r as resolvePackage, w as writePackage } from './shared/depfresh.C-me_t4a.mjs';
4
+ export { c as check, l as loadPackages, p as parseDependencies, r as resolvePackage, w as writePackage } from './shared/depfresh.CTatBHRg.mjs';
5
5
  import 'node:fs/promises';
6
6
  import 'node:url';
7
7
  import 'defu';
@@ -390,14 +390,51 @@ function resolveTargetVersion(currentVersion, versions, distTags, mode) {
390
390
  }
391
391
  }
392
392
 
393
+ function parseGithubSpec(version) {
394
+ const githubMatch = version.match(/^github:([^#]+)#(.+)$/);
395
+ if (!githubMatch) {
396
+ return null;
397
+ }
398
+ const repository = githubMatch[1]?.trim();
399
+ const ref = githubMatch[2]?.trim();
400
+ if (!(repository && ref)) {
401
+ return null;
402
+ }
403
+ const withoutTagPrefix = ref.startsWith("refs/tags/") ? ref.slice("refs/tags/".length) : ref;
404
+ const normalized = withoutTagPrefix.startsWith("v") ? withoutTagPrefix.slice(1) : withoutTagPrefix;
405
+ const valid = semver.valid(normalized);
406
+ if (!valid) {
407
+ return null;
408
+ }
409
+ return {
410
+ aliasName: `github:${repository}`,
411
+ currentVersion: valid
412
+ };
413
+ }
393
414
  function parseProtocol(version) {
394
415
  const npmMatch = version.match(/^npm:(.+)@(.+)$/);
395
416
  if (npmMatch) {
396
- return { protocol: "npm", currentVersion: npmMatch[2] };
417
+ return {
418
+ protocol: "npm",
419
+ aliasName: npmMatch[1],
420
+ currentVersion: npmMatch[2]
421
+ };
397
422
  }
398
423
  const jsrMatch = version.match(/^jsr:(.+)@(.+)$/);
399
424
  if (jsrMatch) {
400
- return { protocol: "jsr", currentVersion: jsrMatch[2] };
425
+ return {
426
+ protocol: "jsr",
427
+ aliasName: `jsr:${jsrMatch[1]}`,
428
+ currentVersion: jsrMatch[2]
429
+ };
430
+ }
431
+ const githubSpec = parseGithubSpec(version);
432
+ if (githubSpec) {
433
+ return {
434
+ protocol: "github",
435
+ aliasName: githubSpec.aliasName,
436
+ currentVersion: githubSpec.currentVersion
437
+ };
401
438
  }
402
439
  return { currentVersion: version };
403
440
  }
@@ -434,6 +471,7 @@ function flattenOverrides(obj, source, deps, options, parents, includePatterns,
434
471
  deps.push({
435
472
  name,
436
473
  currentVersion: protocol.currentVersion,
474
+ aliasName: protocol.aliasName,
437
475
  source,
438
476
  update: !isLocked(protocol.currentVersion) || options.includeLocked,
439
477
  parents: [...parents, key],
@@ -495,7 +533,10 @@ function isDepFieldEnabled(field, options) {
495
533
  function shouldSkipDependency(name, version, options, includePatterns = [], excludePatterns = []) {
496
534
  if (version.startsWith("workspace:") && !options.includeWorkspace) return true;
497
535
  if (version.startsWith("catalog:")) return true;
498
- if (/^(link|file|git|github|https?):/.test(version)) return true;
536
+ if (version.startsWith("github:")) {
537
+ return !parseGithubSpec(version);
538
+ }
539
+ if (/^(link|file|git|https?):/.test(version)) return true;
499
540
  if (includePatterns.length && !includePatterns.some((re) => re.test(name))) {
500
541
  return true;
501
542
  }
@@ -520,6 +561,7 @@ function parseDependencies(raw, options) {
520
561
  deps.push({
521
562
  name,
522
563
  currentVersion: protocol.currentVersion,
564
+ aliasName: protocol.aliasName,
523
565
  source: field,
524
566
  update: !isLocked(protocol.currentVersion) || options.includeLocked,
525
567
  parents: [],
@@ -997,6 +1039,9 @@ function loadCaBundle(cafilePath) {
997
1039
 
998
1040
  async function fetchPackageData(name, options) {
999
1041
  const registry = getRegistryForPackage(name, options.npmrc);
1042
+ if (name.startsWith("github:")) {
1043
+ return fetchGithubPackage(name.slice("github:".length), options);
1044
+ }
1000
1045
  if (name.startsWith("jsr:")) {
1001
1046
  return fetchJsrPackage(name.slice(4), options);
1002
1047
  }
@@ -1011,7 +1056,11 @@ async function fetchNpmPackage(name, registry, options) {
1011
1056
  if (registry.token) {
1012
1057
  headers.authorization = registry.authType === "basic" ? `Basic ${registry.token}` : `Bearer ${registry.token}`;
1013
1058
  }
1014
- const json = await fetchWithRetry(url, headers, options);
1059
+ const payload = await fetchWithRetry(url, headers, options);
1060
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
1061
+ throw new ResolveError(`Unexpected npm registry payload shape for ${url}`);
1062
+ }
1063
+ const json = payload;
1015
1064
  const versionsObj = json.versions ?? {};
1016
1065
  const versions = Object.keys(versionsObj).filter((v) => semver.valid(v));
1017
1066
  const distTags = json["dist-tags"] ?? {};
@@ -1046,7 +1095,11 @@ async function fetchNpmPackage(name, registry, options) {
1046
1095
  }
1047
1096
  async function fetchJsrPackage(name, options) {
1048
1097
  const url = `https://jsr.io/${name}/meta.json`;
1049
- const json = await fetchWithRetry(url, {}, options);
1098
+ const payload = await fetchWithRetry(url, {}, options);
1099
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
1100
+ throw new ResolveError(`Unexpected JSR payload shape for ${url}`);
1101
+ }
1102
+ const json = payload;
1050
1103
  const versionsObj = json.versions ?? {};
1051
1104
  const versions = Object.keys(versionsObj);
1052
1105
  const latest = json.latest ?? versions[versions.length - 1] ?? "";
@@ -1056,6 +1109,59 @@ async function fetchJsrPackage(name, options) {
1056
1109
  distTags: { latest }
1057
1110
  };
1058
1111
  }
1112
+ async function fetchGithubPackage(repository, options) {
1113
+ const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
1114
+ const headers = {
1115
+ accept: "application/vnd.github+json",
1116
+ "user-agent": "depfresh"
1117
+ };
1118
+ if (token) {
1119
+ headers.authorization = `Bearer ${token}`;
1120
+ }
1121
+ const versions = /* @__PURE__ */ new Set();
1122
+ for (let page = 1; page <= 10; page++) {
1123
+ const url = `https://api.github.com/repos/${repository}/tags?per_page=100&page=${page}`;
1124
+ const payload = await fetchWithRetry(url, headers, options);
1125
+ if (!Array.isArray(payload)) {
1126
+ throw new ResolveError(`Unexpected GitHub tags payload shape for ${url}`);
1127
+ }
1128
+ if (payload.length === 0) {
1129
+ break;
1130
+ }
1131
+ for (const item of payload) {
1132
+ if (!item || typeof item !== "object") continue;
1133
+ const tagName = item.name;
1134
+ if (typeof tagName !== "string") continue;
1135
+ const normalized = normalizeGithubTag(tagName);
1136
+ if (normalized) {
1137
+ versions.add(normalized);
1138
+ }
1139
+ }
1140
+ if (payload.length < 100) {
1141
+ break;
1142
+ }
1143
+ }
1144
+ const sorted = Array.from(versions).sort(semver.compare);
1145
+ const latest = sorted[sorted.length - 1];
1146
+ if (!latest) {
1147
+ throw new ResolveError(`No semver tags found for github:${repository}`);
1148
+ }
1149
+ const repositoryUrl = `https://github.com/${repository}`;
1150
+ return {
1151
+ name: `github:${repository}`,
1152
+ versions: sorted,
1153
+ distTags: { latest },
1154
+ repository: repositoryUrl,
1155
+ homepage: repositoryUrl
1156
+ };
1157
+ }
1158
+ function normalizeGithubTag(tag) {
1159
+ const trimmed = tag.trim();
1160
+ if (!trimmed) return null;
1161
+ const withoutTagPrefix = trimmed.startsWith("refs/tags/") ? trimmed.slice("refs/tags/".length) : trimmed;
1162
+ const withoutVPrefix = withoutTagPrefix.startsWith("v") ? withoutTagPrefix.slice(1) : withoutTagPrefix;
1163
+ return semver.valid(withoutVPrefix);
1164
+ }
1059
1165
  async function fetchWithRetry(url, headers, options, attempt = 0) {
1060
1166
  const controller = new AbortController();
1061
1167
  const timer = setTimeout(() => controller.abort(), options.timeout);
@@ -1067,6 +1173,9 @@ async function fetchWithRetry(url, headers, options, attempt = 0) {
1067
1173
  ...transportInit
1068
1174
  });
1069
1175
  if (!response.ok) {
1176
+ if (isGithubRateLimit(response, url)) {
1177
+ throw createGithubRateLimitError(response, url);
1178
+ }
1070
1179
  throw new RegistryError(
1071
1180
  `HTTP ${response.status}: ${response.statusText} for ${url}`,
1072
1181
  response.status,
@@ -1106,6 +1215,30 @@ function isAbortError(error) {
1106
1215
  const named = error;
1107
1216
  return named.name === "AbortError";
1108
1217
  }
1218
+ function isGithubRateLimit(response, url) {
1219
+ if (!url.includes("api.github.com")) return false;
1220
+ if (response.status !== 403 && response.status !== 429) return false;
1221
+ return response.headers.get("x-ratelimit-remaining") === "0";
1222
+ }
1223
+ function createGithubRateLimitError(response, url) {
1224
+ const resetRaw = response.headers.get("x-ratelimit-reset");
1225
+ let resetHint = "";
1226
+ if (resetRaw) {
1227
+ const resetSeconds = Number.parseInt(resetRaw, 10);
1228
+ if (Number.isFinite(resetSeconds)) {
1229
+ resetHint = ` Resets at ${new Date(resetSeconds * 1e3).toISOString()}.`;
1230
+ }
1231
+ }
1232
+ return new ResolveError(
1233
+ `GitHub API rate limit exceeded for ${url}.${resetHint} Set GITHUB_TOKEN or GH_TOKEN.`,
1234
+ {
1235
+ cause: {
1236
+ status: response.status,
1237
+ statusText: response.statusText
1238
+ }
1239
+ }
1240
+ );
1241
+ }
1109
1242
  function sleep(ms) {
1110
1243
  return new Promise((resolve) => setTimeout(resolve, ms));
1111
1244
  }
@@ -1258,6 +1391,9 @@ async function resolvePackage(pkg, options, externalCache, externalNpmrc, privat
1258
1391
  }
1259
1392
  }
1260
1393
 
1394
+ function isPeerScopedCatalog$1(name) {
1395
+ return name.trim().toLowerCase() === "peers";
1396
+ }
1261
1397
  function parseCatalogDeps(catalog, parentPath, options) {
1262
1398
  const deps = [];
1263
1399
  for (const [name, version] of Object.entries(catalog)) {
@@ -1302,6 +1438,9 @@ const bunCatalogLoader = {
1302
1438
  if (raw.workspaces?.catalogs) {
1303
1439
  const catalogs = raw.workspaces.catalogs;
1304
1440
  for (const [catalogName, catalog] of Object.entries(catalogs)) {
1441
+ if (!options.peer && isPeerScopedCatalog$1(catalogName)) {
1442
+ continue;
1443
+ }
1305
1444
  sources.push({
1306
1445
  type: "bun",
1307
1446
  name: catalogName,
@@ -1344,6 +1483,9 @@ const bun = {
1344
1483
  bunCatalogLoader: bunCatalogLoader
1345
1484
  };
1346
1485
 
1486
+ function isPeerScopedCatalog(name) {
1487
+ return name.trim().toLowerCase() === "peers";
1488
+ }
1347
1489
  const pnpmCatalogLoader = {
1348
1490
  async detect(cwd) {
1349
1491
  return !!findUpSync("pnpm-workspace.yaml", { cwd });
@@ -1360,6 +1502,9 @@ const pnpmCatalogLoader = {
1360
1502
  }
1361
1503
  if (schema.catalogs && typeof schema.catalogs === "object") {
1362
1504
  for (const [name, deps] of Object.entries(schema.catalogs)) {
1505
+ if (!options.peer && isPeerScopedCatalog(name)) {
1506
+ continue;
1507
+ }
1363
1508
  catalogs.push(parseCatalogSection(deps, name, filepath, options, content));
1364
1509
  }
1365
1510
  }
@@ -1493,6 +1638,11 @@ function rebuildVersion(original, newVersion) {
1493
1638
  if (npmMatch) return `${npmMatch[1]}${newVersion}`;
1494
1639
  const jsrMatch = original.match(/^(jsr:.+@)/);
1495
1640
  if (jsrMatch) return `${jsrMatch[1]}${newVersion}`;
1641
+ const githubMatch = original.match(/^(github:[^#]+#)(refs\/tags\/)?(v?)(.+)$/);
1642
+ if (githubMatch) {
1643
+ const [, prefix, tagPrefix = "", vPrefix = ""] = githubMatch;
1644
+ return `${prefix}${tagPrefix}${vPrefix}${newVersion}`;
1645
+ }
1496
1646
  return newVersion;
1497
1647
  }
1498
1648
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "depfresh",
3
- "version": "0.11.0",
3
+ "version": "1.0.0",
4
4
  "description": "Keep your npm dependencies fresh. Fast, correct, zero-config.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -55,6 +55,7 @@
55
55
  "lint:fix": "biome check --write .",
56
56
  "format": "biome format --write .",
57
57
  "typecheck": "tsc --noEmit",
58
+ "audit:coverage": "node scripts/audit/update-taze-coverage.mjs",
58
59
  "prepublishOnly": "pnpm run build"
59
60
  },
60
61
  "dependencies": {