upgrade-interactive 1.0.0 → 1.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/README.md +82 -15
- package/package.json +5 -3
- package/src/cli.js +71 -7
- package/src/components/App.js +238 -98
- package/src/components/OverridePicker.js +44 -0
- package/src/components/Prompt.js +8 -2
- package/src/components/Row.js +80 -11
- package/src/links.js +17 -0
- package/src/lockfile.js +58 -0
- package/src/package-file.js +35 -5
- package/src/registry.js +49 -0
- package/src/vulnerabilities.js +260 -0
package/README.md
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# upgrade-interactive
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
An interactive dependency upgrader for npm projects, **inspired by** `yarn
|
|
4
|
+
upgrade-interactive` (the Yarn Berry / Yarn 4 version, built into Yarn since v4).
|
|
5
|
+
The three-column layout, keybindings, and version-suggestion logic follow yarn's
|
|
6
|
+
closely — they were built by reading Yarn's actual source
|
|
7
|
+
(`@yarnpkg/plugin-interactive-tools`) — but this tool also adds things yarn
|
|
8
|
+
doesn't have, notably built-in **vulnerability warnings** and one-key npm
|
|
9
|
+
**`overrides`**, so it deliberately diverges where that improves the experience.
|
|
7
10
|
|
|
8
11
|
## Install / run
|
|
9
12
|
|
|
@@ -16,11 +19,20 @@ nui
|
|
|
16
19
|
|
|
17
20
|
Requires Node 18+ and an interactive terminal.
|
|
18
21
|
|
|
19
|
-
### Using it inside a project
|
|
22
|
+
### Using it inside a project
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
`
|
|
23
|
-
|
|
24
|
+
Because this package ships a `bin`, you don't need `npm run` — or any
|
|
25
|
+
`package.json` change — to use it in a project. Install it and call it with npx:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm install -D upgrade-interactive
|
|
29
|
+
npx upgrade-interactive # runs the locally-installed copy (npx prefers node_modules/.bin)
|
|
30
|
+
npx nui # same thing, short name
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
npm has no plugin/subcommand system the way Yarn Berry does, so the literal
|
|
34
|
+
`npm upgrade-interactive` isn't possible — but `npx upgrade-interactive` is the
|
|
35
|
+
native, no-`run` equivalent. If you'd still rather have a named script, add:
|
|
24
36
|
|
|
25
37
|
```json
|
|
26
38
|
"scripts": {
|
|
@@ -28,7 +40,7 @@ and the standard way to wire up any custom npm command, is a script entry:
|
|
|
28
40
|
}
|
|
29
41
|
```
|
|
30
42
|
|
|
31
|
-
|
|
43
|
+
and run `npm run upgrade-interactive`.
|
|
32
44
|
|
|
33
45
|
## What it does
|
|
34
46
|
|
|
@@ -43,7 +55,26 @@ Then `npm run upgrade-interactive` works from that project, every time.
|
|
|
43
55
|
entirely — same as yarn.
|
|
44
56
|
3. Lets you pick, per package, whether to stay on **Current**, take the
|
|
45
57
|
**Range** upgrade, or take the **Latest** upgrade.
|
|
46
|
-
4.
|
|
58
|
+
4. **Checks for known vulnerabilities** (on by default) against npm's advisory
|
|
59
|
+
database, covering both your **direct and transitive** dependencies. A
|
|
60
|
+
flagged row shows a ⚠ icon, the severity (`low` / `moderate` / `high` /
|
|
61
|
+
`critical`), and the CVE id as a clickable link to the advisory — with the
|
|
62
|
+
plain URL printed alongside it when your terminal can't render clickable
|
|
63
|
+
links. The affected range and first fixed version are shown inline.
|
|
64
|
+
5. Lets you press `o` on a vulnerable package to **pin it to a safe version via
|
|
65
|
+
an npm `overrides` entry** — the main way to patch a *transitive* dependency
|
|
66
|
+
you don't directly control.
|
|
67
|
+
6. **Flags existing `overrides` that are no longer needed** — either because
|
|
68
|
+
nothing in the tree depends on that package anymore, or because your deps
|
|
69
|
+
would now resolve to a non-vulnerable version without the pin. Press `x` to
|
|
70
|
+
remove one. (It never removes an override that's still doing something, and
|
|
71
|
+
only ever removes one you explicitly select.)
|
|
72
|
+
7. Writes your choices (overrides added and removed) back into `package.json`
|
|
73
|
+
and runs `npm install`.
|
|
74
|
+
|
|
75
|
+
By default the list is grouped into **Dependencies**, **Dev dependencies**, and
|
|
76
|
+
**Overrides** (transitive packages you've flagged for an override) sections. Pass
|
|
77
|
+
`--no-section` for a single flat list.
|
|
47
78
|
|
|
48
79
|
## Controls
|
|
49
80
|
|
|
@@ -52,6 +83,8 @@ Then `npm run upgrade-interactive` works from that project, every time.
|
|
|
52
83
|
| `↑` / `↓` | Move between packages |
|
|
53
84
|
| `←` / `→` | Move between Current / Range / Latest for that package |
|
|
54
85
|
| `c` / `r` / `l` | Select **c**urrent / **r**ange / **l**atest for *every* package at once |
|
|
86
|
+
| `o` | Override the focused vulnerable package to a safe version (audit mode) |
|
|
87
|
+
| `x` | Remove the focused override when it's no longer needed (audit mode) |
|
|
55
88
|
| `Enter` | Apply the selected upgrades and run `npm install` |
|
|
56
89
|
| `Ctrl+C` / `Esc` | Abort — nothing is written |
|
|
57
90
|
|
|
@@ -62,11 +95,34 @@ changed highlighted — same idea as yarn's diff highlighting.
|
|
|
62
95
|
## Flags
|
|
63
96
|
|
|
64
97
|
- `--no-install` — update `package.json` only, skip the `npm install` step
|
|
98
|
+
- `--audit` / `--no-audit` — enable/disable the vulnerability check (default: on)
|
|
99
|
+
- `--section` / `--no-section` — grouped sections vs a flat list (default: on)
|
|
65
100
|
- `-h, --help`, `-v, --version`
|
|
66
101
|
|
|
102
|
+
### Persisting audit / section preferences
|
|
103
|
+
|
|
104
|
+
Audit and sectioning are both on by default. To change the default permanently,
|
|
105
|
+
set an environment variable or a `package.json` config block:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
"upgrade-interactive": { "audit": false, "section": true }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
NUI_AUDIT=0 npx upgrade-interactive # disable auditing for this run
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Precedence, highest first: command-line flag → `NUI_AUDIT` / `NUI_SECTION`
|
|
116
|
+
environment variable → `package.json` config → default (on).
|
|
117
|
+
|
|
118
|
+
> Vulnerability data comes from npm's advisory endpoint, so auditing needs
|
|
119
|
+
> network access. When it can't reach the network the tool says so
|
|
120
|
+
> (`no network — couldn't check for vulnerable packages`) rather than pretending
|
|
121
|
+
> everything is clean, and the upgrade flow works as normal.
|
|
122
|
+
|
|
67
123
|
## How closely does this match yarn?
|
|
68
124
|
|
|
69
|
-
|
|
125
|
+
Follows yarn closely:
|
|
70
126
|
- The three-column Current/Range/Latest layout and the help text wording
|
|
71
127
|
- The up/down/left/right navigation model (selection = which column is
|
|
72
128
|
highlighted per row, not a separate checkbox)
|
|
@@ -76,7 +132,14 @@ Matched exactly:
|
|
|
76
132
|
- The version-diff coloring algorithm (segment-by-segment: modifier → major
|
|
77
133
|
→ minor → patch)
|
|
78
134
|
|
|
79
|
-
|
|
135
|
+
Deliberate additions / differences (this is *inspired by* yarn, not a clone):
|
|
136
|
+
- **Vulnerability warnings + `overrides`** — flags vulnerable direct and
|
|
137
|
+
transitive packages, lets you pin a safe version via npm `overrides`, and
|
|
138
|
+
flags existing overrides that are no longer needed so you can remove them.
|
|
139
|
+
Yarn's command has no equivalent.
|
|
140
|
+
- **Sectioned layout** — the list is grouped into Dependencies / Dev
|
|
141
|
+
dependencies / Overrides by default (yarn shows one flat list; use
|
|
142
|
+
`--no-section` to match that).
|
|
80
143
|
- Only plain semver ranges are resolved (git/file/link/workspace ranges,
|
|
81
144
|
and compound ranges like `>=1.0.0 <2.0.0`, are skipped — yarn handles
|
|
82
145
|
these through its pluggable resolvers, which is out of scope here).
|
|
@@ -92,12 +155,16 @@ Intentional differences:
|
|
|
92
155
|
|
|
93
156
|
```
|
|
94
157
|
src/
|
|
95
|
-
cli.js entry point, arg parsing, apply + npm install
|
|
96
|
-
registry.js npm registry client
|
|
158
|
+
cli.js entry point, arg/flag parsing, apply + npm install
|
|
159
|
+
registry.js npm registry client + bulk advisory lookup
|
|
97
160
|
semver-suggest.js Current/Range/Latest suggestion + diff coloring
|
|
98
|
-
package-file.js package.json read/write
|
|
161
|
+
package-file.js package.json read/write (+ overrides)
|
|
162
|
+
lockfile.js installed versions from package-lock.json
|
|
163
|
+
vulnerabilities.js advisory orchestration + severity/safe-version logic
|
|
164
|
+
links.js OSC 8 terminal hyperlinks (with fallback)
|
|
99
165
|
components/
|
|
100
166
|
App.js state machine + keybindings
|
|
167
|
+
OverridePicker.js safe-version chooser overlay
|
|
101
168
|
Header.js, Prompt.js, Row.js presentation
|
|
102
169
|
test/
|
|
103
170
|
app.test.mjs simulated-keypress smoke tests (ink-testing-library)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "upgrade-interactive",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "An interactive dependency upgrader for npm projects, inspired by `yarn upgrade-interactive` (Yarn Berry / Yarn 4), with built-in vulnerability warnings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"upgrade-interactive": "./src/cli.js",
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
},
|
|
10
10
|
"main": "./src/cli.js",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"test": "node test/
|
|
12
|
+
"test": "node --test \"test/unit/**/*.test.mjs\"",
|
|
13
|
+
"test:integration": "node test/app.test.mjs",
|
|
14
|
+
"test:all": "npm run test && npm run test:integration"
|
|
13
15
|
},
|
|
14
16
|
"engines": {
|
|
15
17
|
"node": ">=18"
|
package/src/cli.js
CHANGED
|
@@ -15,24 +15,50 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
15
15
|
const HELP = `
|
|
16
16
|
upgrade-interactive (nui)
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
An interactive dependency upgrader for npm projects, inspired by yarn's
|
|
19
|
+
"upgrade-interactive" (Yarn Berry / Yarn 4).
|
|
19
20
|
|
|
20
21
|
Usage
|
|
21
22
|
$ npx upgrade-interactive [options]
|
|
22
23
|
|
|
23
24
|
Options
|
|
24
|
-
--no-install
|
|
25
|
-
|
|
26
|
-
-
|
|
25
|
+
--no-install Update package.json only, skip running "npm install" afterwards
|
|
26
|
+
--audit Flag vulnerable packages (default: on)
|
|
27
|
+
--no-audit Skip the vulnerability check (no advisory network calls)
|
|
28
|
+
--section Group the list into Dependencies / Dev dependencies / Overrides (default: on)
|
|
29
|
+
--no-section Show one flat list instead
|
|
30
|
+
-h, --help Show this help message
|
|
31
|
+
-v, --version Show the version number
|
|
32
|
+
|
|
33
|
+
Audit and sectioning are on by default. Persist a preference either way with the
|
|
34
|
+
NUI_AUDIT / NUI_SECTION environment variables, or a package.json config block:
|
|
35
|
+
|
|
36
|
+
"upgrade-interactive": { "audit": false, "section": true }
|
|
37
|
+
|
|
38
|
+
Precedence: command-line flag > environment variable > package.json config > default (on).
|
|
27
39
|
|
|
28
40
|
Controls (inside the interactive UI)
|
|
29
41
|
<up>/<down> select a package
|
|
30
42
|
<left>/<right> select which version to apply (Current / Range / Latest)
|
|
31
43
|
c / r / l select all packages' Current / Range / Latest column at once
|
|
44
|
+
o override a vulnerable package to a safe version (audit mode)
|
|
45
|
+
x remove an existing override that's no longer needed (audit mode)
|
|
32
46
|
<enter> apply the selected upgrades (and run npm install)
|
|
33
47
|
<ctrl+c> / esc abort without changing anything
|
|
34
48
|
`;
|
|
35
49
|
|
|
50
|
+
// Resolve a boolean toggle from flags > env var > package.json config > default(true).
|
|
51
|
+
function resolveToggle({ args, env, config, onFlag, offFlag, envVar, configKey }) {
|
|
52
|
+
if (args.includes(offFlag)) return false;
|
|
53
|
+
if (args.includes(onFlag)) return true;
|
|
54
|
+
const envVal = env[envVar];
|
|
55
|
+
if (envVal != null && envVal !== '') {
|
|
56
|
+
return !/^(0|false|no|off)$/i.test(envVal.trim());
|
|
57
|
+
}
|
|
58
|
+
if (config && typeof config[configKey] === 'boolean') return config[configKey];
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
36
62
|
async function main() {
|
|
37
63
|
const args = process.argv.slice(2);
|
|
38
64
|
|
|
@@ -65,11 +91,23 @@ async function main() {
|
|
|
65
91
|
return;
|
|
66
92
|
}
|
|
67
93
|
|
|
94
|
+
const config = manifest.json['upgrade-interactive'];
|
|
95
|
+
const audit = resolveToggle({
|
|
96
|
+
args, env: process.env, config, onFlag: '--audit', offFlag: '--no-audit', envVar: 'NUI_AUDIT', configKey: 'audit',
|
|
97
|
+
});
|
|
98
|
+
const section = resolveToggle({
|
|
99
|
+
args, env: process.env, config, onFlag: '--section', offFlag: '--no-section', envVar: 'NUI_SECTION', configKey: 'section',
|
|
100
|
+
});
|
|
101
|
+
|
|
68
102
|
const result = await new Promise((resolve) => {
|
|
69
103
|
const { waitUntilExit } = render(
|
|
70
104
|
e(App, {
|
|
71
105
|
descriptors: manifest.descriptors,
|
|
72
|
-
|
|
106
|
+
audit,
|
|
107
|
+
section,
|
|
108
|
+
cwd,
|
|
109
|
+
overrides: manifest.json.overrides || {},
|
|
110
|
+
onSubmit: (selections, overrides, removals) => resolve({ type: 'submit', selections, overrides, removals }),
|
|
73
111
|
onAbort: () => resolve({ type: 'abort' }),
|
|
74
112
|
}),
|
|
75
113
|
{ exitOnCtrlC: false }
|
|
@@ -83,12 +121,23 @@ async function main() {
|
|
|
83
121
|
return;
|
|
84
122
|
}
|
|
85
123
|
|
|
86
|
-
|
|
124
|
+
const overrideSelections = result.overrides || {};
|
|
125
|
+
const overrideRemovals = result.removals || [];
|
|
126
|
+
if (
|
|
127
|
+
result.selections.size === 0 &&
|
|
128
|
+
Object.keys(overrideSelections).length === 0 &&
|
|
129
|
+
overrideRemovals.length === 0
|
|
130
|
+
) {
|
|
87
131
|
process.stdout.write('\nNo changes selected.\n');
|
|
88
132
|
return;
|
|
89
133
|
}
|
|
90
134
|
|
|
91
|
-
const applied = await applyUpgrades(
|
|
135
|
+
const { applied, overrides, removed } = await applyUpgrades(
|
|
136
|
+
manifest,
|
|
137
|
+
result.selections,
|
|
138
|
+
overrideSelections,
|
|
139
|
+
overrideRemovals
|
|
140
|
+
);
|
|
92
141
|
|
|
93
142
|
process.stdout.write('\n');
|
|
94
143
|
const byField = { dependencies: [], devDependencies: [] };
|
|
@@ -102,6 +151,21 @@ async function main() {
|
|
|
102
151
|
}
|
|
103
152
|
}
|
|
104
153
|
|
|
154
|
+
if (overrides.length > 0 || removed.length > 0) {
|
|
155
|
+
process.stdout.write('overrides\n');
|
|
156
|
+
for (const change of overrides) {
|
|
157
|
+
process.stdout.write(` ${change.name} \u2192 ${change.to}\n`);
|
|
158
|
+
}
|
|
159
|
+
for (const change of removed) {
|
|
160
|
+
process.stdout.write(` ${change.name} removed\n`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (applied.length === 0 && overrides.length === 0 && removed.length === 0) {
|
|
165
|
+
process.stdout.write('No effective changes.\n');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
105
169
|
if (skipInstall) {
|
|
106
170
|
process.stdout.write('\nUpdated package.json. Run npm install to apply.\n');
|
|
107
171
|
return;
|