inup 1.6.2 → 1.6.4
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
|
@@ -51,6 +51,8 @@ inup [options]
|
|
|
51
51
|
--package-manager <name> Force package manager (npm, yarn, pnpm, bun)
|
|
52
52
|
--json Print a machine-readable JSON report and exit (read-only)
|
|
53
53
|
-c, --check Exit non-zero if updates exist, without writing (for CI)
|
|
54
|
+
--apply Non-interactively write upgrades + install (for CI/automation)
|
|
55
|
+
--target <level> With --apply: how far to bump — minor (default) | patch | latest
|
|
54
56
|
--debug Write verbose debug logs
|
|
55
57
|
```
|
|
56
58
|
|
|
@@ -61,11 +63,19 @@ pipeline waiting on the interactive UI. Both `--json` and `--check` are **read-o
|
|
|
61
63
|
they never edit `package.json` or install.
|
|
62
64
|
|
|
63
65
|
```bash
|
|
64
|
-
inup --check
|
|
65
|
-
inup --json | jq
|
|
66
|
-
inup | cat
|
|
66
|
+
inup --check # exit 1 if anything is outdated → fails the build
|
|
67
|
+
inup --json | jq # structured drift report for dashboards/bots
|
|
68
|
+
inup | cat # plain line-based report when piped to a log
|
|
69
|
+
inup --apply # write safe in-range bumps + install (non-interactive)
|
|
70
|
+
inup --apply --target latest # include major bumps; --json to also emit the report
|
|
67
71
|
```
|
|
68
72
|
|
|
73
|
+
Unlike `--json` and `--check`, **`--apply` writes**: it bumps `package.json` and runs your package
|
|
74
|
+
manager's install to update the lockfile. By default (`--target minor`) it only applies **in-range**
|
|
75
|
+
updates and leaves majors for you to review; `--target latest` includes majors. It honors `.inuprc`
|
|
76
|
+
(`ignore`, `exclude`, `scanDirs`) exactly as the report does — a package the config excludes is
|
|
77
|
+
never written. With `--apply --json`, the install output goes to stderr so stdout stays pure JSON.
|
|
78
|
+
|
|
69
79
|
Each reported package carries its health signals: `deprecated` (npm deprecation message), `enginesNode`
|
|
70
80
|
(declared `engines.node`), and `vulnerability` (known advisories on the currently-installed version,
|
|
71
81
|
from one bulk `npm audit`-style request). Every advisory is **cross-referenced against the upgrade
|
|
@@ -81,6 +91,73 @@ agents can pin to a known shape.
|
|
|
81
91
|
Output hygiene: with `--json`, stdout carries **only** the JSON document; all progress and warnings go
|
|
82
92
|
to stderr. Exit codes: `0` up to date, `1` updates exist (`--check`), `2` error.
|
|
83
93
|
|
|
94
|
+
## GitHub Action — one rolling upgrade PR
|
|
95
|
+
|
|
96
|
+
Run inup on a schedule and get **one rolling pull request** with safe upgrades applied. Re-runs update
|
|
97
|
+
the same PR instead of opening new ones.
|
|
98
|
+
|
|
99
|
+
Add this workflow to **your** repo:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
# .github/workflows/inup.yml
|
|
103
|
+
name: inup
|
|
104
|
+
on:
|
|
105
|
+
schedule:
|
|
106
|
+
- cron: '0 6 * * *' # daily at 06:00 UTC
|
|
107
|
+
workflow_dispatch: {}
|
|
108
|
+
|
|
109
|
+
permissions:
|
|
110
|
+
contents: write
|
|
111
|
+
pull-requests: write
|
|
112
|
+
|
|
113
|
+
jobs:
|
|
114
|
+
upgrade:
|
|
115
|
+
runs-on: ubuntu-latest
|
|
116
|
+
steps:
|
|
117
|
+
- uses: actions/checkout@v4
|
|
118
|
+
- uses: donfear/inup@v1
|
|
119
|
+
with:
|
|
120
|
+
target: minor # minor (default) | patch | latest
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
That's it. Also enable Settings → Actions → General → **Workflow permissions** →
|
|
124
|
+
**"Allow GitHub Actions to create and approve pull requests"** so the action can open the PR.
|
|
125
|
+
|
|
126
|
+
### Commit as you, not the bot
|
|
127
|
+
|
|
128
|
+
By default the upgrade commit is authored by `github-actions[bot]`. To attribute it to **you**, store
|
|
129
|
+
your name/email as repo secrets (Settings → Secrets and variables → Actions) and pass `committer`/`author`:
|
|
130
|
+
|
|
131
|
+
```yaml
|
|
132
|
+
- uses: donfear/inup@v1
|
|
133
|
+
with:
|
|
134
|
+
committer: ${{ secrets.GH_USERNAME }} <${{ secrets.GH_EMAIL }}>
|
|
135
|
+
author: ${{ secrets.GH_USERNAME }} <${{ secrets.GH_EMAIL }}>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### All inputs
|
|
139
|
+
|
|
140
|
+
| Input | Default | Description |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `target` | `minor` | How far to bump: `minor` (in-range), `patch`, or `latest` (includes majors). |
|
|
143
|
+
| `directory` | `.` | Directory to run in. |
|
|
144
|
+
| `package-manager` | _(auto)_ | Force `npm`/`yarn`/`pnpm`/`bun`; empty auto-detects from the lockfile. |
|
|
145
|
+
| `node-version` | `22` | Node.js version for the run (minimum `22.19`). |
|
|
146
|
+
| `inup-version` | `latest` | inup version to run (pin for reproducible runs). |
|
|
147
|
+
| `pr-branch` | `inup/dependency-upgrades` | Branch for the rolling PR. |
|
|
148
|
+
| `pr-title` | `chore(deps): dependency upgrades` | PR title. |
|
|
149
|
+
| `commit-message` | `chore(deps): upgrade dependencies via inup` | Commit message. |
|
|
150
|
+
| `base` | _(default branch)_ | Base branch the PR targets. |
|
|
151
|
+
| `labels` | `dependencies` | Labels to apply to the PR. |
|
|
152
|
+
| `token` | `${{ github.token }}` | Token to push + open the PR. Pass a PAT to also trigger CI on the PR. |
|
|
153
|
+
| `committer` | `github-actions[bot]` | Commit committer, as `Name <email>`. |
|
|
154
|
+
| `author` | _(triggering user)_ | Commit author, as `Name <email>`. |
|
|
155
|
+
|
|
156
|
+
Outputs: `outdated`, `vulnerable`, `pull-request-number`.
|
|
157
|
+
|
|
158
|
+
> `@v1` floats to the latest `1.x` release; pin to `@v1.6.2` or a SHA for reproducible runs. inup
|
|
159
|
+
> honors your `.inuprc` (`ignore`, `exclude`, `scanDirs`).
|
|
160
|
+
|
|
84
161
|
## Keyboard Shortcuts
|
|
85
162
|
|
|
86
163
|
<!-- KEYS:START -->
|
package/dist/cli.js
CHANGED
|
@@ -28,8 +28,14 @@ async function runCli(options) {
|
|
|
28
28
|
(0, utils_1.enableDebugLogging)();
|
|
29
29
|
}
|
|
30
30
|
// Headless when piped, in CI, or when a non-interactive flag is set. The TUI only renders in
|
|
31
|
-
// interactive mode; everything else routes through the read-only
|
|
32
|
-
const interactive = !!process.stdout.isTTY && !process.env.CI && !options.json && !options.check;
|
|
31
|
+
// interactive mode; everything else routes through the headless path (read-only, unless --apply).
|
|
32
|
+
const interactive = !!process.stdout.isTTY && !process.env.CI && !options.json && !options.check && !options.apply;
|
|
33
|
+
// Validate --target early so a typo fails fast instead of silently defaulting.
|
|
34
|
+
if (options.target && !['minor', 'patch', 'latest'].includes(options.target)) {
|
|
35
|
+
console.error(chalk_1.default.red(`Invalid target: ${options.target}`));
|
|
36
|
+
console.error(chalk_1.default.yellow('Valid options: minor, patch, latest'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
33
39
|
// The dirty-tree prompt would hang without a TTY; headless is read-only anyway, so skip it.
|
|
34
40
|
if (interactive) {
|
|
35
41
|
const gitState = (0, git_1.getGitWorkingTreeState)(cwd);
|
|
@@ -99,7 +105,12 @@ async function runCli(options) {
|
|
|
99
105
|
// Non-interactive (piped / CI / --json / --check) routes to the read-only headless feature;
|
|
100
106
|
// only the interactive path builds the full TUI runner.
|
|
101
107
|
if (!interactive) {
|
|
102
|
-
await new headless_1.HeadlessRunner(runnerOptions).run({
|
|
108
|
+
await new headless_1.HeadlessRunner(runnerOptions).run({
|
|
109
|
+
json: options.json,
|
|
110
|
+
check: options.check,
|
|
111
|
+
apply: options.apply,
|
|
112
|
+
target: options.target || 'minor',
|
|
113
|
+
});
|
|
103
114
|
return;
|
|
104
115
|
}
|
|
105
116
|
await new index_1.UpgradeRunner(runnerOptions).run();
|
|
@@ -140,6 +151,8 @@ program
|
|
|
140
151
|
.option('--save-exact', 'write exact versions instead of preserving the range prefix (^/~)')
|
|
141
152
|
.option('--json', 'print a machine-readable JSON report and exit (non-interactive, read-only)')
|
|
142
153
|
.option('-c, --check', 'exit non-zero if updates exist, without writing (for CI; read-only)')
|
|
154
|
+
.option('--apply', 'non-interactively write upgrades to package.json and run install (honors .inuprc ignore/exclude)')
|
|
155
|
+
.option('--target <level>', 'with --apply: how far to bump — minor | patch | latest (default: minor, in-range only)', 'minor')
|
|
143
156
|
.action(runCli);
|
|
144
157
|
// Handle uncaught errors gracefully
|
|
145
158
|
process.on('uncaughtException', (error) => {
|
package/dist/core/upgrader.js
CHANGED
|
@@ -12,12 +12,17 @@ const child_process_1 = require("child_process");
|
|
|
12
12
|
const utils_1 = require("../utils");
|
|
13
13
|
class PackageUpgrader {
|
|
14
14
|
packageManager;
|
|
15
|
-
|
|
15
|
+
quiet;
|
|
16
|
+
/** When quiet, send our own progress to stderr so stdout stays reserved for the JSON document. */
|
|
17
|
+
log;
|
|
18
|
+
constructor(packageManager, options) {
|
|
16
19
|
this.packageManager = packageManager;
|
|
20
|
+
this.quiet = options?.quiet ?? false;
|
|
21
|
+
this.log = this.quiet ? (msg) => console.error(msg) : (msg) => console.log(msg);
|
|
17
22
|
}
|
|
18
23
|
async upgradePackages(choices, _packageInfos) {
|
|
19
24
|
if (choices.length === 0) {
|
|
20
|
-
|
|
25
|
+
this.log(chalk_1.default.yellow('No packages to upgrade.'));
|
|
21
26
|
return;
|
|
22
27
|
}
|
|
23
28
|
// Group choices by package.json path and dependency type
|
|
@@ -26,12 +31,12 @@ class PackageUpgrader {
|
|
|
26
31
|
if (choiceList.length === 0)
|
|
27
32
|
continue;
|
|
28
33
|
const [packageJsonPath, type] = fileAndType.split('|');
|
|
29
|
-
|
|
34
|
+
this.log(`Processing ${type} in ${packageJsonPath}`);
|
|
30
35
|
await this.upgradeChoiceGroup(choiceList, packageJsonPath, type);
|
|
31
36
|
}
|
|
32
37
|
// Count unique packages upgraded
|
|
33
38
|
const uniquePackages = new Set(choices.map((c) => c.name));
|
|
34
|
-
|
|
39
|
+
this.log(chalk_1.default.green(`\n✅ Successfully upgraded ${uniquePackages.size} package(s)!`));
|
|
35
40
|
// Execute package manager install after all upgrades are complete
|
|
36
41
|
await this.runInstall(choices);
|
|
37
42
|
}
|
|
@@ -50,16 +55,23 @@ class PackageUpgrader {
|
|
|
50
55
|
(0, utils_1.executeCommand)(`${this.packageManager.name} --version`, installDir);
|
|
51
56
|
}
|
|
52
57
|
catch (error) {
|
|
53
|
-
|
|
58
|
+
this.log(chalk_1.default.yellow(`\n⚠️ ${this.packageManager.displayName} is detected but not installed on your system.\n` +
|
|
54
59
|
`Please run the install command manually:\n` +
|
|
55
60
|
` cd ${installDir}\n` +
|
|
56
61
|
` ${this.packageManager.installCommand}\n`));
|
|
57
62
|
return; // Skip install, let user do it manually
|
|
58
63
|
}
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
// We just rewrote package.json, so the install must be allowed to regenerate the lockfile.
|
|
65
|
+
// pnpm/yarn default to frozen/immutable installs under CI; writeInstallCommand opts out.
|
|
66
|
+
const installCommand = this.packageManager.writeInstallCommand ?? this.packageManager.installCommand;
|
|
67
|
+
this.log(chalk_1.default.cyan(`\n📦 Running ${installCommand}...\n`));
|
|
68
|
+
// In quiet mode, send the install child's stdout to *our* stderr (fd 2). The child uses
|
|
69
|
+
// inherited fds, so its progress output bypasses any JS shim — redirecting at spawn time is
|
|
70
|
+
// the only reliable way to keep stdout reserved for the --json document. stderr stays inherited.
|
|
71
|
+
const stdio = this.quiet ? ['inherit', 2, 'inherit'] : 'inherit';
|
|
72
|
+
const result = (0, child_process_1.spawnSync)(installCommand, {
|
|
61
73
|
cwd: installDir,
|
|
62
|
-
stdio
|
|
74
|
+
stdio,
|
|
63
75
|
shell: true,
|
|
64
76
|
});
|
|
65
77
|
if (result.error) {
|
|
@@ -67,9 +79,9 @@ class PackageUpgrader {
|
|
|
67
79
|
}
|
|
68
80
|
if (result.status !== 0) {
|
|
69
81
|
if (result.signal) {
|
|
70
|
-
throw new Error(`${
|
|
82
|
+
throw new Error(`${installCommand} terminated by signal ${result.signal}`);
|
|
71
83
|
}
|
|
72
|
-
throw new Error(`${
|
|
84
|
+
throw new Error(`${installCommand} exited with code ${result.status}`);
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
groupChoicesByFileAndType(choices) {
|
|
@@ -86,11 +98,14 @@ class PackageUpgrader {
|
|
|
86
98
|
async upgradeChoiceGroup(choices, packageJsonPath, type) {
|
|
87
99
|
// Validate that package.json exists
|
|
88
100
|
if (!(0, fs_1.existsSync)(packageJsonPath)) {
|
|
89
|
-
|
|
101
|
+
this.log(chalk_1.default.yellow(`⚠️ Skipping ${type} in ${packageJsonPath} - package.json file not found`));
|
|
90
102
|
return;
|
|
91
103
|
}
|
|
92
104
|
const packageDir = packageJsonPath.replace('/package.json', '');
|
|
93
|
-
|
|
105
|
+
// The spinner animates on stdout; skip it in quiet mode so the --json document stays clean.
|
|
106
|
+
const spinner = this.quiet
|
|
107
|
+
? null
|
|
108
|
+
: (0, nanospinner_1.createSpinner)(`Upgrading ${type} in ${packageDir}...`).start();
|
|
94
109
|
try {
|
|
95
110
|
// Read the current package.json — keep the raw text so we can round-trip its formatting
|
|
96
111
|
const rawContent = (0, fs_1.readFileSync)(packageJsonPath, 'utf-8');
|
|
@@ -128,15 +143,21 @@ class PackageUpgrader {
|
|
|
128
143
|
(0, fs_1.writeFileSync)(packageJsonPath, nextContent);
|
|
129
144
|
}
|
|
130
145
|
}
|
|
131
|
-
spinner
|
|
146
|
+
if (spinner)
|
|
147
|
+
spinner.success({ text: `Upgraded ${choices.length} ${type} in ${packageDir}` });
|
|
148
|
+
else
|
|
149
|
+
this.log(chalk_1.default.green(`✔ Upgraded ${choices.length} ${type} in ${packageDir}`));
|
|
132
150
|
// Show which packages were upgraded
|
|
133
151
|
choices.forEach((choice) => {
|
|
134
152
|
const upgradeTypeColor = choice.upgradeType === 'range' ? chalk_1.default.yellow : chalk_1.default.red;
|
|
135
|
-
|
|
153
|
+
this.log(` ${chalk_1.default.green('✓')} ${chalk_1.default.cyan(choice.name)} → ${upgradeTypeColor(choice.targetVersion)}`);
|
|
136
154
|
});
|
|
137
155
|
}
|
|
138
156
|
catch (error) {
|
|
139
|
-
spinner
|
|
157
|
+
if (spinner)
|
|
158
|
+
spinner.error({ text: `Failed to upgrade ${type} in ${packageDir}` });
|
|
159
|
+
else
|
|
160
|
+
this.log(chalk_1.default.red(`✖ Failed to upgrade ${type} in ${packageDir}`));
|
|
140
161
|
console.error(chalk_1.default.red(`Error: ${error}`));
|
|
141
162
|
throw error;
|
|
142
163
|
}
|
|
@@ -6,20 +6,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.HeadlessRunner = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const package_detector_1 = require("../../core/package-detector");
|
|
9
|
+
const upgrader_1 = require("../../core/upgrader");
|
|
10
|
+
const package_manager_detector_1 = require("../../services/package-manager-detector");
|
|
11
|
+
const version_1 = require("../../ui/utils/version");
|
|
9
12
|
const vulnerability_audit_1 = require("./vulnerability-audit");
|
|
10
13
|
const report_1 = require("./report");
|
|
11
14
|
const debug_1 = require("../../features/debug");
|
|
12
15
|
/**
|
|
13
16
|
* Non-interactive entry point. Resolves the outdated list without rendering the TUI, then either
|
|
14
17
|
* emits a JSON report (--json) or a plain line-based report. With --check, sets a non-zero exit
|
|
15
|
-
* code when updates exist so CI can gate on it.
|
|
18
|
+
* code when updates exist so CI can gate on it. With --apply, writes the bumps to package.json and
|
|
19
|
+
* runs install — the only non-interactive write path.
|
|
16
20
|
*
|
|
17
|
-
* This is the headless counterpart to the interactive `UpgradeRunner`; it shares
|
|
18
|
-
* `PackageDetector` (
|
|
21
|
+
* This is the headless counterpart to the interactive `UpgradeRunner`; it shares the
|
|
22
|
+
* `PackageDetector` (scan/resolve) and, for --apply, the `PackageUpgrader` (write + install).
|
|
19
23
|
*/
|
|
20
24
|
class HeadlessRunner {
|
|
21
25
|
detector;
|
|
26
|
+
options;
|
|
22
27
|
constructor(options) {
|
|
28
|
+
this.options = options;
|
|
23
29
|
this.detector = new package_detector_1.PackageDetector(options);
|
|
24
30
|
}
|
|
25
31
|
async run(options) {
|
|
@@ -47,11 +53,18 @@ class HeadlessRunner {
|
|
|
47
53
|
// Audit the current versions (one bulk request, best-effort) and cross-reference each
|
|
48
54
|
// advisory against the upgrade targets, so the report says whether upgrading *fixes* it.
|
|
49
55
|
const vulnerabilities = await (0, vulnerability_audit_1.auditVulnerabilities)(outdated);
|
|
56
|
+
// Build the report from the *pre-apply* outdated set: it describes what this run addressed.
|
|
57
|
+
const report = (0, report_1.buildHeadlessReport)(packages, outdated, vulnerabilities);
|
|
58
|
+
// --apply writes the bumps + lockfile. The scan above already honored .inuprc
|
|
59
|
+
// (ignore/exclude/scanDirs), so the set we write is exactly the set we report — never more.
|
|
60
|
+
if (options.apply) {
|
|
61
|
+
await this.applyUpgrades(outdated, options.target ?? 'minor', !!options.json);
|
|
62
|
+
}
|
|
50
63
|
if (options.json) {
|
|
51
64
|
// stdout is reserved for the JSON document only.
|
|
52
|
-
console.log(JSON.stringify(
|
|
65
|
+
console.log(JSON.stringify(report, null, 2));
|
|
53
66
|
}
|
|
54
|
-
else {
|
|
67
|
+
else if (!options.apply) {
|
|
55
68
|
console.log((0, report_1.renderPlainReport)(outdated, vulnerabilities));
|
|
56
69
|
}
|
|
57
70
|
// Exit 1 only means "updates exist" (like `prettier --check`); 2 is reserved for errors.
|
|
@@ -64,6 +77,61 @@ class HeadlessRunner {
|
|
|
64
77
|
process.exit(2);
|
|
65
78
|
}
|
|
66
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Apply upgrades to the already-filtered outdated set, by version policy. Reuses the interactive
|
|
82
|
+
* write path (`PackageUpgrader`) verbatim — we only build the choices programmatically instead of
|
|
83
|
+
* from a TUI selection. No path/package filtering happens here: `outdated` is config-filtered.
|
|
84
|
+
*/
|
|
85
|
+
async applyUpgrades(outdated, target, json) {
|
|
86
|
+
const choices = this.buildChoices(outdated, target);
|
|
87
|
+
if (choices.length === 0)
|
|
88
|
+
return;
|
|
89
|
+
const packageManager = this.resolvePackageManager();
|
|
90
|
+
// With --json, run the upgrader quietly: its own progress + the install child's stdout go to
|
|
91
|
+
// stderr, leaving stdout for the JSON document only.
|
|
92
|
+
const upgrader = new upgrader_1.PackageUpgrader(packageManager, { quiet: json });
|
|
93
|
+
await upgrader.upgradePackages(choices, outdated);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build `PackageUpgradeChoice[]` from the outdated set per the version policy. Mirrors
|
|
97
|
+
* `createUpgradeChoices` in the TUI: preserves the original range prefix (^/~) unless --save-exact.
|
|
98
|
+
*
|
|
99
|
+
* - minor/patch: take the in-range target (`rangeVersion`); skip packages whose only update is a
|
|
100
|
+
* major (no in-range bump). Uses upgradeType 'range'.
|
|
101
|
+
* - latest: take `latestVersion`; uses upgradeType 'latest' (majors included).
|
|
102
|
+
*/
|
|
103
|
+
buildChoices(outdated, target) {
|
|
104
|
+
const saveExact = this.options?.saveExact ?? false;
|
|
105
|
+
const choices = [];
|
|
106
|
+
for (const pkg of outdated) {
|
|
107
|
+
const useLatest = target === 'latest';
|
|
108
|
+
// minor/patch only act on packages with an in-range bump; major-only updates are skipped.
|
|
109
|
+
if (!useLatest && !pkg.hasRangeUpdate)
|
|
110
|
+
continue;
|
|
111
|
+
const targetVersion = useLatest ? pkg.latestVersion : pkg.rangeVersion;
|
|
112
|
+
if (!targetVersion)
|
|
113
|
+
continue;
|
|
114
|
+
const targetVersionWithPrefix = saveExact
|
|
115
|
+
? targetVersion
|
|
116
|
+
: (0, version_1.applyVersionPrefix)(pkg.currentVersion, targetVersion);
|
|
117
|
+
choices.push({
|
|
118
|
+
name: pkg.name,
|
|
119
|
+
packageJsonPath: pkg.packageJsonPath,
|
|
120
|
+
dependencyType: pkg.type,
|
|
121
|
+
upgradeType: useLatest ? 'latest' : 'range',
|
|
122
|
+
targetVersion: targetVersionWithPrefix,
|
|
123
|
+
currentVersionSpecifier: pkg.currentVersion,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return choices;
|
|
127
|
+
}
|
|
128
|
+
/** Resolve the package manager the same way the interactive runner does. */
|
|
129
|
+
resolvePackageManager() {
|
|
130
|
+
const cwd = this.options?.cwd || process.cwd();
|
|
131
|
+
return this.options?.packageManager
|
|
132
|
+
? package_manager_detector_1.PackageManagerDetector.getInfo(this.options.packageManager)
|
|
133
|
+
: package_manager_detector_1.PackageManagerDetector.detect(cwd);
|
|
134
|
+
}
|
|
67
135
|
}
|
|
68
136
|
exports.HeadlessRunner = HeadlessRunner;
|
|
69
137
|
//# sourceMappingURL=headless-runner.js.map
|
|
@@ -22,6 +22,8 @@ const PACKAGE_MANAGERS = {
|
|
|
22
22
|
lockFile: 'yarn.lock',
|
|
23
23
|
workspaceFile: null, // Uses package.json workspaces field
|
|
24
24
|
installCommand: 'yarn install',
|
|
25
|
+
// Yarn Berry defaults to immutable installs in CI; --no-immutable lets the lockfile update.
|
|
26
|
+
writeInstallCommand: 'yarn install --no-immutable',
|
|
25
27
|
color: chalk_1.default.blue,
|
|
26
28
|
},
|
|
27
29
|
pnpm: {
|
|
@@ -30,6 +32,8 @@ const PACKAGE_MANAGERS = {
|
|
|
30
32
|
lockFile: 'pnpm-lock.yaml',
|
|
31
33
|
workspaceFile: 'pnpm-workspace.yaml',
|
|
32
34
|
installCommand: 'pnpm install',
|
|
35
|
+
// pnpm defaults to --frozen-lockfile in CI; --no-frozen-lockfile lets the lockfile update.
|
|
36
|
+
writeInstallCommand: 'pnpm install --no-frozen-lockfile',
|
|
33
37
|
color: chalk_1.default.yellow,
|
|
34
38
|
},
|
|
35
39
|
bun: {
|
|
@@ -5,6 +5,15 @@ exports.findAllPackageJsonFilesAsync = findAllPackageJsonFilesAsync;
|
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const fs_2 = require("fs");
|
|
7
7
|
const path_1 = require("path");
|
|
8
|
+
/**
|
|
9
|
+
* Normalize a relative path to forward slashes before matching `.inuprc` exclude patterns.
|
|
10
|
+
* On Windows `path.relative` yields backslash separators (e.g. `packages\skipme`), but users write
|
|
11
|
+
* exclude regexes with `/` (e.g. `^packages/skipme(?:/|$)`). Without this, excludes silently fail
|
|
12
|
+
* on Windows and a path the user meant to skip gets scanned and upgraded.
|
|
13
|
+
*/
|
|
14
|
+
function toPosixPath(p) {
|
|
15
|
+
return p.replace(/\\/g, '/');
|
|
16
|
+
}
|
|
8
17
|
const SKIP_DIRS = new Set([
|
|
9
18
|
'node_modules',
|
|
10
19
|
'dist',
|
|
@@ -97,7 +106,8 @@ function findAllPackageJsonFiles(rootDir = process.cwd(), excludePatterns = [],
|
|
|
97
106
|
const skipSet = buildSkipSet(options.scanDirs);
|
|
98
107
|
const excludeRegexes = excludePatterns.map((pattern) => new RegExp(pattern, 'i'));
|
|
99
108
|
function shouldExcludePath(relativePath) {
|
|
100
|
-
|
|
109
|
+
const posix = toPosixPath(relativePath);
|
|
110
|
+
return excludeRegexes.some((regex) => regex.test(posix));
|
|
101
111
|
}
|
|
102
112
|
function reportProgress(currentDir, force = false) {
|
|
103
113
|
if (!onProgress)
|
|
@@ -169,7 +179,8 @@ async function findAllPackageJsonFilesAsync(rootDir = process.cwd(), excludePatt
|
|
|
169
179
|
const skipSet = buildSkipSet(options.scanDirs);
|
|
170
180
|
const excludeRegexes = excludePatterns.map((pattern) => new RegExp(pattern, 'i'));
|
|
171
181
|
function shouldExcludePath(relativePath) {
|
|
172
|
-
|
|
182
|
+
const posix = toPosixPath(relativePath);
|
|
183
|
+
return excludeRegexes.some((regex) => regex.test(posix));
|
|
173
184
|
}
|
|
174
185
|
function reportProgress(currentDir, force = false) {
|
|
175
186
|
if (!onProgress)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inup",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
4
4
|
"description": "Interactive dependency upgrader for npm, yarn, pnpm & bun. Zero-config, monorepo-ready. Upgrade-interactive for every package manager.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^25.9.4",
|
|
47
47
|
"@types/semver": "^7.7.1",
|
|
48
|
-
"@vitest/coverage-v8": "^4.1.
|
|
48
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
49
49
|
"prettier": "^3.9.4",
|
|
50
50
|
"typescript": "^6.0.3",
|
|
51
|
-
"vitest": "^4.1.
|
|
51
|
+
"vitest": "^4.1.7"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"chalk": "^5.6.2",
|
|
@@ -59,16 +59,13 @@
|
|
|
59
59
|
"undici": "^8.5.0"
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
62
|
-
"node": ">=
|
|
62
|
+
"node": ">=22.19.0"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "rm -rf dist && tsc",
|
|
66
66
|
"dev": "tsc --watch",
|
|
67
67
|
"start": "node dist/cli.js",
|
|
68
|
-
"link": "pnpm build && pnpm
|
|
69
|
-
"version:patch": "pnpm version patch && git push && git push --tags",
|
|
70
|
-
"version:minor": "pnpm version minor && git push && git push --tags",
|
|
71
|
-
"version:major": "pnpm version major && git push && git push --tags",
|
|
68
|
+
"link": "pnpm build && pnpm add -g .",
|
|
72
69
|
"format": "prettier --write src/**/*.ts",
|
|
73
70
|
"format:check": "prettier --check src/**/*.ts",
|
|
74
71
|
"demo:record": "bash docs/demo/record-demo.sh",
|