@crbroughton/recul 0.1.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/LICENSE +21 -0
- package/README.md +132 -0
- package/dist/bin/recul.d.ts +3 -0
- package/dist/bin/recul.d.ts.map +1 -0
- package/dist/bin/recul.js +96 -0
- package/dist/bin/recul.js.map +1 -0
- package/dist/src/adapters/npm.d.ts +10 -0
- package/dist/src/adapters/npm.d.ts.map +1 -0
- package/dist/src/adapters/npm.js +26 -0
- package/dist/src/adapters/npm.js.map +1 -0
- package/dist/src/adapters/pnpm.d.ts +34 -0
- package/dist/src/adapters/pnpm.d.ts.map +1 -0
- package/dist/src/adapters/pnpm.js +97 -0
- package/dist/src/adapters/pnpm.js.map +1 -0
- package/dist/src/audit.d.ts +19 -0
- package/dist/src/audit.d.ts.map +1 -0
- package/dist/src/audit.js +65 -0
- package/dist/src/audit.js.map +1 -0
- package/dist/src/config.d.ts +23 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +67 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/init.d.ts +2 -0
- package/dist/src/init.d.ts.map +1 -0
- package/dist/src/init.js +113 -0
- package/dist/src/init.js.map +1 -0
- package/dist/src/lockfile.d.ts +24 -0
- package/dist/src/lockfile.d.ts.map +1 -0
- package/dist/src/lockfile.js +54 -0
- package/dist/src/lockfile.js.map +1 -0
- package/dist/src/output.d.ts +13 -0
- package/dist/src/output.d.ts.map +1 -0
- package/dist/src/output.js +198 -0
- package/dist/src/output.js.map +1 -0
- package/dist/src/resolve.d.ts +25 -0
- package/dist/src/resolve.d.ts.map +1 -0
- package/dist/src/resolve.js +55 -0
- package/dist/src/resolve.js.map +1 -0
- package/dist/src/semver.d.ts +29 -0
- package/dist/src/semver.d.ts.map +1 -0
- package/dist/src/semver.js +39 -0
- package/dist/src/semver.js.map +1 -0
- package/dist/src/types.d.ts +70 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Craig R Broughton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# recul
|
|
2
|
+
|
|
3
|
+
Stay N versions behind the latest published release of your npm dependencies to avoid supply chain attacks.
|
|
4
|
+
|
|
5
|
+
recul is not a replacement for typical auditing via `npm audit` or third party security tools; it is a complementary layer that reduces the attack surface without requiring active effort on every release cycle.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
Given a lag of `N`, the target version is `versions[latest_index - N]`. Only stable releases are counted; pre-release versions (configurable, defaults to `-alpha`, `-beta`, `-rc`, `-next`, `-canary`) are excluded. If a package has fewer releases than the lag value, recul pins to the oldest available stable version.
|
|
10
|
+
|
|
11
|
+
Packages already older than the lag target are left alone by default. The invariant is "never be too new", not "be exactly N behind".
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- Node.js 18 or later
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm i -D recul
|
|
21
|
+
# or
|
|
22
|
+
pnpm add -D recul
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
# Create a config file in the current directory
|
|
29
|
+
recul init
|
|
30
|
+
|
|
31
|
+
# Audit your dependencies
|
|
32
|
+
recul
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
Commit a `recul.config.jsonc` to standardise settings across the team.
|
|
38
|
+
|
|
39
|
+
```jsonc
|
|
40
|
+
{
|
|
41
|
+
// How many versions to stay behind the latest published release.
|
|
42
|
+
// Counted in releases, not semver increments.
|
|
43
|
+
//
|
|
44
|
+
// 1 → days to weeks (fast-moving projects, minimal buffer)
|
|
45
|
+
// 2 → weeks (balanced default, recommended)
|
|
46
|
+
// 3 → weeks to months (cautious teams, slower release cadences)
|
|
47
|
+
// 5+ → months (regulated environments, high-security contexts)
|
|
48
|
+
"lag": 2,
|
|
49
|
+
|
|
50
|
+
// Package manager: "npm" | "pnpm"
|
|
51
|
+
"packageManager": "pnpm",
|
|
52
|
+
|
|
53
|
+
// Path to the package.json to audit, relative to this config file.
|
|
54
|
+
"packageFile": "package.json",
|
|
55
|
+
|
|
56
|
+
// How to handle packages already older than the lag target.
|
|
57
|
+
// "ignore" → treat as ok, no output (default)
|
|
58
|
+
// "report" → surface them with a safe upgrade-to-target command
|
|
59
|
+
"behindBehavior": "ignore",
|
|
60
|
+
|
|
61
|
+
// Version prefix used in generated install commands.
|
|
62
|
+
// "exact" → 1.3.4 (recommended; audits are reliable)
|
|
63
|
+
// "caret" → ^1.3.4 (allows minor/patch drift)
|
|
64
|
+
// "tilde" → ~1.3.4 (allows patch drift only)
|
|
65
|
+
//
|
|
66
|
+
// Per-package map also supported:
|
|
67
|
+
// { "default": "exact", "react": "tilde" }
|
|
68
|
+
"rangeSpecifier": "exact",
|
|
69
|
+
|
|
70
|
+
// Packages to skip entirely.
|
|
71
|
+
"ignore": [],
|
|
72
|
+
|
|
73
|
+
// Minimum days a version must have been published before it is eligible
|
|
74
|
+
// as a lag target. Combines with "lag" for defence-in-depth.
|
|
75
|
+
// Omit or set to 0 to disable.
|
|
76
|
+
"minimumReleaseAge": 3,
|
|
77
|
+
|
|
78
|
+
// Version strings containing any of these substrings are treated as
|
|
79
|
+
// pre-releases and excluded from the candidate list.
|
|
80
|
+
"preReleaseFilter": ["-alpha", "-beta", "-rc", "-next", "-canary"]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A config file is required; run `recul init` if you do not have one.
|
|
85
|
+
|
|
86
|
+
## Output
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
recul staying 2 versions behind latest
|
|
90
|
+
|
|
91
|
+
settings
|
|
92
|
+
lag 2 ; stay 2 versions behind latest
|
|
93
|
+
pm pnpm ; the chosen package manager
|
|
94
|
+
behind ignore ; ignore packages behind target
|
|
95
|
+
range exact ; pin exact versions
|
|
96
|
+
minAge 3 ; skip versions published within the last 3 days
|
|
97
|
+
|
|
98
|
+
package declared → target latest status
|
|
99
|
+
────────────────────────────────────────────────────────────────────────────────
|
|
100
|
+
express ^4.19.2 4.17.3 4.19.2 ↓ will pin back
|
|
101
|
+
react ^18.3.1 18.1.0 18.3.1 ↓ will pin back
|
|
102
|
+
typescript 5.4.5 5.4.5 5.4.5 ✓ ok
|
|
103
|
+
|
|
104
|
+
to pin back:
|
|
105
|
+
pnpm add express@4.17.3 react@18.1.0
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Status values
|
|
109
|
+
|
|
110
|
+
| Status | Meaning |
|
|
111
|
+
|--------|---------|
|
|
112
|
+
| `✓ ok` | At or behind the lag target |
|
|
113
|
+
| `↓ will pin back` | Ahead of the lag target; install command shown |
|
|
114
|
+
| `↑ safe to upgrade` | Behind the target (only shown when `behindBehavior: report`) |
|
|
115
|
+
| `✗ unresolved` | Registry fetch failed or no stable versions found |
|
|
116
|
+
|
|
117
|
+
A `⚠ declared <specifier>` warning is appended when a package is declared with a different range prefix than `rangeSpecifier`; this means the audited version may differ from what is actually installed.
|
|
118
|
+
|
|
119
|
+
## pnpm catalog support
|
|
120
|
+
|
|
121
|
+
When using pnpm workspaces with a `catalogs` block in `pnpm-workspace.yaml`, recul reads catalog entries to resolve `catalog:` and `catalog:<name>` references in `package.json`.
|
|
122
|
+
|
|
123
|
+
Violations in catalog-managed packages are reported with the catalog entry to update rather than an install command. Pass `--fix` to apply the updates directly to `pnpm-workspace.yaml`.
|
|
124
|
+
|
|
125
|
+
## Lockfile support
|
|
126
|
+
|
|
127
|
+
When a lockfile is present, recul reads the installed version from it and uses that for comparison rather than the declared range. This gives accurate results for packages declared with `^` or `~`.
|
|
128
|
+
|
|
129
|
+
| Package manager | Lockfile |
|
|
130
|
+
|----------------|----------|
|
|
131
|
+
| npm | `package-lock.json` (v3) |
|
|
132
|
+
| pnpm | `pnpm-lock.yaml` (v6+) |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recul.d.ts","sourceRoot":"","sources":["../../bin/recul.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { defineCommand, runMain } from 'citty';
|
|
5
|
+
import { defu } from 'defu';
|
|
6
|
+
import { destr } from 'destr';
|
|
7
|
+
import { auditDeps } from '../src/audit.js';
|
|
8
|
+
import { DEFAULTS, loadConfigFile, resolveConfigDir } from '../src/config.js';
|
|
9
|
+
import { runInit } from '../src/init.js';
|
|
10
|
+
import { detectPackageManager, loadLockfile, loadPnpmCatalog, npmAdapter, pnpmAdapter, resolveCatalogRefs, updatePnpmCatalog } from '../src/lockfile.js';
|
|
11
|
+
import { printResults } from '../src/output.js';
|
|
12
|
+
const initCommand = defineCommand({
|
|
13
|
+
meta: { name: 'init', description: 'Create recul.config.jsonc with recommended settings' },
|
|
14
|
+
run() {
|
|
15
|
+
runInit(process.cwd());
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const main = defineCommand({
|
|
19
|
+
meta: { name: 'recul', description: 'Stay N versions behind latest' },
|
|
20
|
+
args: {
|
|
21
|
+
file: { type: 'string', alias: 'f', description: 'Path to package.json (default: package.json)' },
|
|
22
|
+
fix: { type: 'boolean', description: 'Apply catalog fixes directly to pnpm-workspace.yaml' },
|
|
23
|
+
},
|
|
24
|
+
subCommands: { init: initCommand },
|
|
25
|
+
async run({ args }) {
|
|
26
|
+
const configDir = resolveConfigDir({ ...(args.file !== undefined ? { file: args.file } : {}), cwd: process.cwd() });
|
|
27
|
+
const fileConfig = loadConfigFile(configDir);
|
|
28
|
+
if (fileConfig === null) {
|
|
29
|
+
console.error('no config file found or config could not be parsed.\n');
|
|
30
|
+
console.error('run "recul init" to create recul.config.jsonc with recommended settings.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const detectedPm = detectPackageManager(configDir);
|
|
34
|
+
const config = defu({ file: args.file }, { lag: fileConfig.lag, file: fileConfig.packageFile, pm: fileConfig.packageManager, behindBehavior: fileConfig.behindBehavior, rangeSpecifier: fileConfig.rangeSpecifier, ignore: fileConfig.ignore, minimumReleaseAge: fileConfig.minimumReleaseAge, preReleaseFilter: fileConfig.preReleaseFilter }, { pm: detectedPm ?? undefined }, DEFAULTS);
|
|
35
|
+
const { lag, file, pm, behindBehavior, rangeSpecifier, ignore, preReleaseFilter } = config;
|
|
36
|
+
const minimumReleaseAge = config.minimumReleaseAge !== null ? config.minimumReleaseAge : undefined;
|
|
37
|
+
const pkgPath = resolve(process.cwd(), file);
|
|
38
|
+
let raw;
|
|
39
|
+
try {
|
|
40
|
+
raw = destr(readFileSync(pkgPath, 'utf8'));
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
44
|
+
console.error(`error: could not read ${pkgPath}: ${message}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
if (raw === null || typeof raw !== 'object') {
|
|
48
|
+
console.error(`error: ${pkgPath} is not a JSON object`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const pkgDir = dirname(pkgPath);
|
|
52
|
+
const base = raw;
|
|
53
|
+
const catalogs = loadPnpmCatalog(pkgDir);
|
|
54
|
+
const pkgJson = catalogs !== null
|
|
55
|
+
? {
|
|
56
|
+
...(base.dependencies !== undefined ? { dependencies: resolveCatalogRefs(base.dependencies, catalogs) } : {}),
|
|
57
|
+
...(base.devDependencies !== undefined ? { devDependencies: resolveCatalogRefs(base.devDependencies, catalogs) } : {}),
|
|
58
|
+
}
|
|
59
|
+
: base;
|
|
60
|
+
const catalogPackages = catalogs !== null
|
|
61
|
+
? new Set(Object.values(catalogs).flatMap(Object.keys))
|
|
62
|
+
: undefined;
|
|
63
|
+
const installedMap = loadLockfile({ dir: pkgDir, adapters: [npmAdapter, pnpmAdapter] });
|
|
64
|
+
const results = await auditDeps({
|
|
65
|
+
pkgJson,
|
|
66
|
+
lag,
|
|
67
|
+
...(minimumReleaseAge !== undefined ? { minimumReleaseAge } : {}),
|
|
68
|
+
preReleaseFilter,
|
|
69
|
+
rangeSpecifier,
|
|
70
|
+
ignore,
|
|
71
|
+
...(installedMap !== null ? { installed: installedMap } : {}),
|
|
72
|
+
...(catalogPackages !== undefined ? { catalogPackages } : {}),
|
|
73
|
+
});
|
|
74
|
+
let fixed;
|
|
75
|
+
if (args.fix && catalogPackages !== undefined) {
|
|
76
|
+
const updates = {};
|
|
77
|
+
for (const r of results) {
|
|
78
|
+
if (!r.fromCatalog)
|
|
79
|
+
continue;
|
|
80
|
+
if (r.status === 'pin' && r.target !== null)
|
|
81
|
+
updates[r.name] = r.target;
|
|
82
|
+
else if (r.status === 'behind' && behindBehavior === 'report' && r.target !== null)
|
|
83
|
+
updates[r.name] = r.target;
|
|
84
|
+
else if (r.specifierMismatch && r.status !== 'pin')
|
|
85
|
+
updates[r.name] = r.current;
|
|
86
|
+
}
|
|
87
|
+
if (Object.keys(updates).length > 0) {
|
|
88
|
+
updatePnpmCatalog(pkgDir, updates);
|
|
89
|
+
fixed = Object.keys(updates);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
printResults({ results, lag, pm, behindBehavior, rangeSpecifier, ...(minimumReleaseAge !== undefined ? { minimumReleaseAge } : {}), ...(catalogPackages !== undefined ? { workspaceFile: 'pnpm-workspace.yaml' } : {}), ...(fixed !== undefined ? { fixed } : {}) });
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
runMain(main);
|
|
96
|
+
//# sourceMappingURL=recul.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recul.js","sourceRoot":"","sources":["../../bin/recul.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxJ,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,MAAM,WAAW,GAAG,aAAa,CAAC;IAChC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,qDAAqD,EAAE;IAC1F,GAAG;QACD,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACxB,CAAC;CACF,CAAC,CAAA;AAEF,MAAM,IAAI,GAAG,aAAa,CAAC;IACzB,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,+BAA+B,EAAE;IACrE,IAAI,EAAE;QACJ,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,8CAA8C,EAAE;QACjG,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,qDAAqD,EAAE;KAC7F;IACD,WAAW,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;IAClC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE;QAChB,MAAM,SAAS,GAAG,gBAAgB,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;QACnH,MAAM,UAAU,GAAG,cAAc,CAAC,SAAS,CAAC,CAAA;QAE5C,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,OAAO,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAA;YACtE,OAAO,CAAC,KAAK,CAAC,0EAA0E,CAAC,CAAA;YACzF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,MAAM,UAAU,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAA;QAElD,MAAM,MAAM,GAAG,IAAI,CACjB,EAAE,IAAI,EAAE,IAAI,CAAC,IAA0B,EAAE,EACzC,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,WAAW,EAAE,EAAE,EAAE,UAAU,CAAC,cAA4C,EAAE,cAAc,EAAE,UAAU,CAAC,cAAc,EAAE,cAAc,EAAE,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,iBAAiB,EAAE,UAAU,CAAC,iBAAiB,EAAE,gBAAgB,EAAE,UAAU,CAAC,gBAAgB,EAAE,EACnU,EAAE,EAAE,EAAE,UAAU,IAAI,SAAS,EAAE,EAC/B,QAAQ,CACT,CAAA;QAED,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAAA;QAC1F,MAAM,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAA;QAElG,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAA;QAE5C,IAAI,GAAuB,CAAA;QAC3B,IAAI,CAAC;YACH,GAAG,GAAG,KAAK,CAAqB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;QAChE,CAAC;QACD,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAChE,OAAO,CAAC,KAAK,CAAC,yBAAyB,OAAO,KAAK,OAAO,EAAE,CAAC,CAAA;YAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QACD,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC5C,OAAO,CAAC,KAAK,CAAC,UAAU,OAAO,uBAAuB,CAAC,CAAA;YACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;QAC/B,MAAM,IAAI,GAAG,GAAG,CAAA;QAChB,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,CAAA;QACxC,MAAM,OAAO,GAAgB,QAAQ,KAAK,IAAI;YAC5C,CAAC,CAAC;gBACE,GAAG,CAAC,IAAI,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,kBAAkB,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7G,GAAG,CAAC,IAAI,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,kBAAkB,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACvH;YACH,CAAC,CAAC,IAAI,CAAA;QACR,MAAM,eAAe,GAAG,QAAQ,KAAK,IAAI;YACvC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACvD,CAAC,CAAC,SAAS,CAAA;QACb,MAAM,YAAY,GAAG,YAAY,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE,CAAC,CAAA;QACvF,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC;YAC9B,OAAO;YACP,GAAG;YACH,GAAG,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,gBAAgB;YAChB,cAAc;YACd,MAAM;YACN,GAAG,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,GAAG,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC,CAAA;QACF,IAAI,KAA2B,CAAA;QAC/B,IAAI,IAAI,CAAC,GAAG,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;YAC9C,MAAM,OAAO,GAA2B,EAAE,CAAA;YAC1C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxB,IAAI,CAAC,CAAC,CAAC,WAAW;oBAChB,SAAQ;gBACV,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI;oBACzC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;qBACvB,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,cAAc,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI;oBAChF,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;qBACvB,IAAI,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK;oBAChD,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAA;YAC/B,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpC,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;gBAClC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAC9B,CAAC;QACH,CAAC;QAED,YAAY,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,GAAG,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IACtQ,CAAC;CACF,CAAC,CAAA;AAEF,OAAO,CAAC,IAAI,CAAC,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LockfileAdapter } from '../lockfile.js';
|
|
2
|
+
import type { InstalledVersionMap } from '../types.js';
|
|
3
|
+
interface NpmLockPackageEntry {
|
|
4
|
+
version?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function parsePackagesBlock(packages: Record<string, NpmLockPackageEntry>): InstalledVersionMap;
|
|
7
|
+
export declare function parseNpmLock(content: string): InstalledVersionMap;
|
|
8
|
+
export declare const npmAdapter: LockfileAdapter;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=npm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm.d.ts","sourceRoot":"","sources":["../../../src/adapters/npm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAGtD,UAAU,mBAAmB;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAMD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAAG,mBAAmB,CAarG;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAKjE;AAED,eAAO,MAAM,UAAU,EAAE,eAGxB,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { destr } from 'destr';
|
|
2
|
+
export function parsePackagesBlock(packages) {
|
|
3
|
+
const result = {};
|
|
4
|
+
for (const [key, entry] of Object.entries(packages)) {
|
|
5
|
+
if (!key.startsWith('node_modules/'))
|
|
6
|
+
continue;
|
|
7
|
+
const name = key.slice('node_modules/'.length);
|
|
8
|
+
// Skip nested installs like "foo/node_modules/bar"
|
|
9
|
+
if (name.includes('node_modules'))
|
|
10
|
+
continue;
|
|
11
|
+
if (typeof entry.version === 'string')
|
|
12
|
+
result[name] = entry.version;
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
export function parseNpmLock(content) {
|
|
17
|
+
const raw = destr(content);
|
|
18
|
+
if (raw === null || typeof raw !== 'object')
|
|
19
|
+
return {};
|
|
20
|
+
return raw.packages ? parsePackagesBlock(raw.packages) : {};
|
|
21
|
+
}
|
|
22
|
+
export const npmAdapter = {
|
|
23
|
+
filename: 'package-lock.json',
|
|
24
|
+
parse: parseNpmLock,
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=npm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm.js","sourceRoot":"","sources":["../../../src/adapters/npm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAU7B,MAAM,UAAU,kBAAkB,CAAC,QAA6C;IAC9E,MAAM,MAAM,GAAwB,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,eAAe,CAAC;YAClC,SAAQ;QACV,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAA;QAC9C,mDAAmD;QACnD,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC;YAC/B,SAAQ;QACV,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;YACnC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAA;IAChC,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,GAAG,GAAG,KAAK,CAAiB,OAAO,CAAC,CAAA;IAC1C,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ;QACzC,OAAO,EAAE,CAAA;IACX,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC7D,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAoB;IACzC,QAAQ,EAAE,mBAAmB;IAC7B,KAAK,EAAE,YAAY;CACpB,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { LockfileAdapter } from '../lockfile.js';
|
|
2
|
+
import type { InstalledVersionMap } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse a pnpm-lock.yaml (v6–v9) into an installed version map.
|
|
5
|
+
*
|
|
6
|
+
* Reads only the root importer (`.`) from the `importers` block.
|
|
7
|
+
* Peer-dependency suffixes like `1.2.3(@types/node@22.0.0)` are stripped
|
|
8
|
+
* to leave only the bare version number.
|
|
9
|
+
*/
|
|
10
|
+
export declare function parsePnpmLock(content: string): InstalledVersionMap;
|
|
11
|
+
export declare const pnpmAdapter: LockfileAdapter;
|
|
12
|
+
/**
|
|
13
|
+
* Read all catalogs from pnpm-workspace.yaml and return a
|
|
14
|
+
* catalogName → (packageName → specifier) map.
|
|
15
|
+
*
|
|
16
|
+
* `catalog:` references use the `"default"` key.
|
|
17
|
+
* Named references like `catalog:testing` use the catalog name as key.
|
|
18
|
+
*
|
|
19
|
+
* Returns null if the file is absent or has no catalogs section.
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadPnpmCatalog(dir: string): Record<string, Record<string, string>> | null;
|
|
22
|
+
/**
|
|
23
|
+
* Substitute `catalog:` and `catalog:<name>` references in a deps map with
|
|
24
|
+
* the real specifier from the workspace catalogs.
|
|
25
|
+
* Entries not found in the catalog are left as-is.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveCatalogRefs(deps: Record<string, string>, catalogs: Record<string, Record<string, string>>): Record<string, string>;
|
|
28
|
+
/**
|
|
29
|
+
* Update catalog entries in pnpm-workspace.yaml in place.
|
|
30
|
+
* `updates` is a map of package name → new specifier (e.g. `"1.2.3"`).
|
|
31
|
+
* Only lines within the file that match a catalog entry for the given name are rewritten.
|
|
32
|
+
*/
|
|
33
|
+
export declare function updatePnpmCatalog(dir: string, updates: Record<string, string>): void;
|
|
34
|
+
//# sourceMappingURL=pnpm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pnpm.d.ts","sourceRoot":"","sources":["../../../src/adapters/pnpm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAiBtD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAkBlE;AAED,eAAO,MAAM,WAAW,EAAE,eAGzB,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAmB1F;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC/C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAexB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CASpF"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { parseYAML } from 'confbox';
|
|
4
|
+
/**
|
|
5
|
+
* Parse a pnpm-lock.yaml (v6–v9) into an installed version map.
|
|
6
|
+
*
|
|
7
|
+
* Reads only the root importer (`.`) from the `importers` block.
|
|
8
|
+
* Peer-dependency suffixes like `1.2.3(@types/node@22.0.0)` are stripped
|
|
9
|
+
* to leave only the bare version number.
|
|
10
|
+
*/
|
|
11
|
+
export function parsePnpmLock(content) {
|
|
12
|
+
const raw = parseYAML(content);
|
|
13
|
+
const result = {};
|
|
14
|
+
const root = raw?.importers?.['.'];
|
|
15
|
+
if (!root)
|
|
16
|
+
return result;
|
|
17
|
+
for (const block of [root.dependencies, root.devDependencies]) {
|
|
18
|
+
if (!block)
|
|
19
|
+
continue;
|
|
20
|
+
for (const [name, entry] of Object.entries(block)) {
|
|
21
|
+
if (entry.version !== undefined) {
|
|
22
|
+
result[name] = entry.version.split('(')[0].trim();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
export const pnpmAdapter = {
|
|
29
|
+
filename: 'pnpm-lock.yaml',
|
|
30
|
+
parse: parsePnpmLock,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Read all catalogs from pnpm-workspace.yaml and return a
|
|
34
|
+
* catalogName → (packageName → specifier) map.
|
|
35
|
+
*
|
|
36
|
+
* `catalog:` references use the `"default"` key.
|
|
37
|
+
* Named references like `catalog:testing` use the catalog name as key.
|
|
38
|
+
*
|
|
39
|
+
* Returns null if the file is absent or has no catalogs section.
|
|
40
|
+
*/
|
|
41
|
+
export function loadPnpmCatalog(dir) {
|
|
42
|
+
const path = resolve(dir, 'pnpm-workspace.yaml');
|
|
43
|
+
if (!existsSync(path))
|
|
44
|
+
return null;
|
|
45
|
+
try {
|
|
46
|
+
const raw = parseYAML(readFileSync(path, 'utf8'));
|
|
47
|
+
if (!raw?.catalogs)
|
|
48
|
+
return null;
|
|
49
|
+
const result = {};
|
|
50
|
+
for (const [catalogName, section] of Object.entries(raw.catalogs)) {
|
|
51
|
+
result[catalogName] = { ...section };
|
|
52
|
+
}
|
|
53
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
57
|
+
console.error(`error: could not parse ${path}: ${message}`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Substitute `catalog:` and `catalog:<name>` references in a deps map with
|
|
63
|
+
* the real specifier from the workspace catalogs.
|
|
64
|
+
* Entries not found in the catalog are left as-is.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveCatalogRefs(deps, catalogs) {
|
|
67
|
+
const result = {};
|
|
68
|
+
for (const [name, ver] of Object.entries(deps)) {
|
|
69
|
+
if (ver === 'catalog:') {
|
|
70
|
+
result[name] = catalogs.default?.[name] ?? ver;
|
|
71
|
+
}
|
|
72
|
+
else if (ver.startsWith('catalog:')) {
|
|
73
|
+
const catalogName = ver.slice('catalog:'.length);
|
|
74
|
+
result[name] = catalogs[catalogName]?.[name] ?? ver;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
result[name] = ver;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Update catalog entries in pnpm-workspace.yaml in place.
|
|
84
|
+
* `updates` is a map of package name → new specifier (e.g. `"1.2.3"`).
|
|
85
|
+
* Only lines within the file that match a catalog entry for the given name are rewritten.
|
|
86
|
+
*/
|
|
87
|
+
export function updatePnpmCatalog(dir, updates) {
|
|
88
|
+
const path = resolve(dir, 'pnpm-workspace.yaml');
|
|
89
|
+
let content = readFileSync(path, 'utf8');
|
|
90
|
+
for (const [name, version] of Object.entries(updates)) {
|
|
91
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
92
|
+
const pattern = new RegExp(`^(\\s+(?:'${escaped}'|"${escaped}"|${escaped}):\\s*)(.+)$`, 'gm');
|
|
93
|
+
content = content.replace(pattern, `$1${version}`);
|
|
94
|
+
}
|
|
95
|
+
writeFileSync(path, content, 'utf8');
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=pnpm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pnpm.js","sourceRoot":"","sources":["../../../src/adapters/pnpm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAcnC;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,GAAG,GAAG,SAAS,CAAW,OAAO,CAAC,CAAA;IACxC,MAAM,MAAM,GAAwB,EAAE,CAAA;IACtC,MAAM,IAAI,GAAG,GAAG,EAAE,SAAS,EAAE,CAAC,GAAG,CAAC,CAAA;IAClC,IAAI,CAAC,IAAI;QACP,OAAO,MAAM,CAAA;IAEf,KAAK,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;QAC9D,IAAI,CAAC,KAAK;YACR,SAAQ;QACV,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAClD,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBAChC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAA;YACpD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAoB;IAC1C,QAAQ,EAAE,gBAAgB;IAC1B,KAAK,EAAE,aAAa;CACrB,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAA;IAChD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QACnB,OAAO,IAAI,CAAA;IACb,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,SAAS,CAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;QAChE,IAAI,CAAC,GAAG,EAAE,QAAQ;YAChB,OAAO,IAAI,CAAA;QACb,MAAM,MAAM,GAA2C,EAAE,CAAA;QACzD,KAAK,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClE,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAA;QACtC,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;IACvD,CAAC;IACD,OAAO,GAAG,EAAE,CAAC;QACX,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAChE,OAAO,CAAC,KAAK,CAAC,0BAA0B,IAAI,KAAK,OAAO,EAAE,CAAC,CAAA;QAC3D,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAA4B,EAC5B,QAAgD;IAEhD,MAAM,MAAM,GAA2B,EAAE,CAAA;IACzC,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/C,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,CAAA;QAChD,CAAC;aACI,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YACpC,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;YAChD,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,CAAA;QACrD,CAAC;aACI,CAAC;YACJ,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAA;QACpB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,OAA+B;IAC5E,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAA;IAChD,IAAI,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACxC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACtD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;QAC3D,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,aAAa,OAAO,MAAM,OAAO,KAAK,OAAO,cAAc,EAAE,IAAI,CAAC,CAAA;QAC7F,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC,CAAA;IACpD,CAAC;IACD,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAA;AACtC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AuditResult, InstalledVersionMap, PackageJson, RangeSpecifierConfig } from './types.js';
|
|
2
|
+
export interface AuditDepsOptions {
|
|
3
|
+
pkgJson: PackageJson;
|
|
4
|
+
lag: number;
|
|
5
|
+
minimumReleaseAge?: number;
|
|
6
|
+
preReleaseFilter?: string[];
|
|
7
|
+
rangeSpecifier: RangeSpecifierConfig;
|
|
8
|
+
ignore?: string[];
|
|
9
|
+
installed?: InstalledVersionMap;
|
|
10
|
+
catalogPackages?: ReadonlySet<string>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Audit all dependencies in a package.json object.
|
|
14
|
+
* - `ignore`: skip these package names entirely.
|
|
15
|
+
* - `installed`: lockfile-resolved version map; used for comparison when present.
|
|
16
|
+
* - `rangeSpecifier`: global string or per-package record.
|
|
17
|
+
*/
|
|
18
|
+
export declare function auditDeps({ pkgJson, lag, minimumReleaseAge, preReleaseFilter, rangeSpecifier, ignore, installed, catalogPackages }: AuditDepsOptions): Promise<AuditResult[]>;
|
|
19
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,mBAAmB,EAAE,WAAW,EAAkB,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAyDlI,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,WAAW,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,cAAc,EAAE,oBAAoB,CAAA;IACpC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,SAAS,CAAC,EAAE,mBAAmB,CAAA;IAC/B,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CACtC;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,gBAAqB,EAAE,cAAc,EAAE,MAAW,EAAE,SAAS,EAAE,eAAe,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA0B7L"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { resolveRangeSpecifier } from './config.js';
|
|
2
|
+
import { resolvePackage } from './resolve.js';
|
|
3
|
+
import { bareVersion, detectSpecifier, semverCompareForSpecifier } from './semver.js';
|
|
4
|
+
async function auditOne({ name, rawVersion, lag, minimumReleaseAge, preReleaseFilter, rangeSpecifier, installedVersion, fromCatalog }) {
|
|
5
|
+
const declared = rawVersion;
|
|
6
|
+
const isCatalogRef = rawVersion.startsWith('catalog:');
|
|
7
|
+
// catalog: entries have no usable version in package.json — the lockfile is required
|
|
8
|
+
if (isCatalogRef && installedVersion === null) {
|
|
9
|
+
return { name, declared, current: '', installed: null, target: null, latest: null, status: 'unresolved', rangeSpecifier, declaredSpecifier: 'other', specifierMismatch: false, fromCatalog, error: 'catalog reference; pnpm lockfile required' };
|
|
10
|
+
}
|
|
11
|
+
const current = isCatalogRef ? installedVersion : bareVersion(rawVersion);
|
|
12
|
+
const installed = installedVersion;
|
|
13
|
+
const declaredSpecifier = isCatalogRef ? 'other' : detectSpecifier(rawVersion);
|
|
14
|
+
const specifierMismatch = declaredSpecifier !== 'other' && declaredSpecifier !== rangeSpecifier;
|
|
15
|
+
let resolved;
|
|
16
|
+
try {
|
|
17
|
+
resolved = await resolvePackage({ name, lag, preReleaseFilter, ...(minimumReleaseAge !== undefined ? { minimumReleaseAge } : {}) });
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
21
|
+
return { name, declared, current, installed, target: null, latest: null, status: 'unresolved', rangeSpecifier, declaredSpecifier, specifierMismatch, fromCatalog, error: message };
|
|
22
|
+
}
|
|
23
|
+
const { target, latest } = resolved;
|
|
24
|
+
if (target === null) {
|
|
25
|
+
return { name, declared, current, installed, target: null, latest, status: 'unresolved', rangeSpecifier, declaredSpecifier, specifierMismatch, fromCatalog, error: 'no stable versions found' };
|
|
26
|
+
}
|
|
27
|
+
const compareVersion = installed ?? current;
|
|
28
|
+
const cmp = semverCompareForSpecifier({ versionA: compareVersion, versionB: target, specifier: rangeSpecifier });
|
|
29
|
+
let status;
|
|
30
|
+
if (cmp > 0)
|
|
31
|
+
status = 'pin';
|
|
32
|
+
else if (cmp === 0)
|
|
33
|
+
status = 'ok';
|
|
34
|
+
else
|
|
35
|
+
status = 'behind';
|
|
36
|
+
return { name, declared, current, installed, target, latest, status, rangeSpecifier, declaredSpecifier, specifierMismatch, fromCatalog };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Audit all dependencies in a package.json object.
|
|
40
|
+
* - `ignore`: skip these package names entirely.
|
|
41
|
+
* - `installed`: lockfile-resolved version map; used for comparison when present.
|
|
42
|
+
* - `rangeSpecifier`: global string or per-package record.
|
|
43
|
+
*/
|
|
44
|
+
export async function auditDeps({ pkgJson, lag, minimumReleaseAge, preReleaseFilter = [], rangeSpecifier, ignore = [], installed, catalogPackages }) {
|
|
45
|
+
const deps = {
|
|
46
|
+
...(pkgJson.dependencies ?? {}),
|
|
47
|
+
...(pkgJson.devDependencies ?? {}),
|
|
48
|
+
};
|
|
49
|
+
let names = Object.keys(deps);
|
|
50
|
+
if (ignore.length > 0) {
|
|
51
|
+
const ignoreSet = new Set(ignore);
|
|
52
|
+
names = names.filter(n => !ignoreSet.has(n));
|
|
53
|
+
}
|
|
54
|
+
return Promise.all(names.map(n => auditOne({
|
|
55
|
+
name: n,
|
|
56
|
+
rawVersion: deps[n] ?? '',
|
|
57
|
+
lag,
|
|
58
|
+
...(minimumReleaseAge !== undefined ? { minimumReleaseAge } : {}),
|
|
59
|
+
preReleaseFilter,
|
|
60
|
+
rangeSpecifier: resolveRangeSpecifier({ config: rangeSpecifier, name: n }),
|
|
61
|
+
installedVersion: installed?.[n] ?? null,
|
|
62
|
+
fromCatalog: catalogPackages?.has(n) ?? false,
|
|
63
|
+
})));
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Config, ConfigFile, RangeSpecifier, RangeSpecifierConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load the config file from `dir`.
|
|
4
|
+
* Returns the parsed config, or null if no config file was found.
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadConfigFile(dir: string): ConfigFile | null;
|
|
7
|
+
/** Resolve the directory to search for the config file, based on --file if provided. */
|
|
8
|
+
export declare function resolveConfigDir({ file, cwd }: {
|
|
9
|
+
file?: string;
|
|
10
|
+
cwd: string;
|
|
11
|
+
}): string;
|
|
12
|
+
export declare const DEFAULTS: Config;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the effective RangeSpecifier for a specific package.
|
|
15
|
+
* When config is a string it applies to all packages.
|
|
16
|
+
* When config is a record, looks up by name, then "default", then falls back to "exact".
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveRangeSpecifier({ config, name }: {
|
|
19
|
+
config: RangeSpecifierConfig;
|
|
20
|
+
name: string;
|
|
21
|
+
}): RangeSpecifier;
|
|
22
|
+
export declare function rangePrefix(specifier: RangeSpecifier): string;
|
|
23
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAa1F;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAiB7D;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEtF;AAED,eAAO,MAAM,QAAQ,EAAE,MAQtB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;IAAE,MAAM,EAAE,oBAAoB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,cAAc,CAUtH;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,cAAc,GAAG,MAAM,CAM7D"}
|