inup 1.5.6 → 1.6.2
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 +30 -1
- package/dist/cli.js +42 -11
- package/dist/config/constants.js +6 -7
- package/dist/config/project-config.js +5 -0
- package/dist/core/package-detector.js +49 -1
- package/dist/core/upgrade-runner.js +9 -0
- package/dist/core/upgrader.js +9 -3
- package/dist/features/changelog/services/package-metadata-service.js +2 -13
- package/dist/features/changelog/services/release-notes-service.js +0 -27
- package/dist/features/debug/index.js +1 -0
- package/dist/features/debug/renderer/performance-modal.js +17 -1
- package/dist/features/debug/services/perf-logger.js +129 -0
- package/dist/features/debug/services/performance-tracker.js +15 -3
- package/dist/features/headless/headless-runner.js +69 -0
- package/dist/features/headless/index.js +27 -0
- package/dist/features/headless/report.js +64 -0
- package/dist/features/headless/types.js +6 -0
- package/dist/features/headless/vulnerability-audit.js +106 -0
- package/dist/interactive-ui.js +4 -2
- package/dist/services/http/adaptive-controller.js +153 -0
- package/dist/services/http/etag-store.js +91 -0
- package/dist/services/http/resizable-semaphore.js +71 -0
- package/dist/services/http/retry.js +23 -1
- package/dist/services/index.js +0 -1
- package/dist/services/npm-registry.js +198 -97
- package/dist/services/package-manager-detector.js +6 -3
- package/dist/ui/modal/package-info-sections/sections.js +24 -0
- package/dist/ui/presenters/health.js +24 -0
- package/dist/ui/renderer/package-list/rows.js +8 -3
- package/dist/ui/session/selection-state-builder.js +7 -2
- package/dist/ui/themes-colors.js +12 -3
- package/dist/ui/utils/cursor.js +9 -3
- package/dist/utils/color.js +38 -0
- package/dist/utils/debug-logger.js +8 -1
- package/dist/utils/engines.js +63 -0
- package/dist/utils/filesystem/io.js +16 -0
- package/dist/utils/filesystem/scan.js +82 -7
- package/dist/utils/index.js +4 -0
- package/dist/utils/local-env.js +81 -0
- package/dist/utils/manifest.js +35 -0
- package/dist/utils/version.js +9 -2
- package/package.json +8 -8
- package/dist/services/cache-manager.js +0 -95
- package/dist/services/jsdelivr/client.js +0 -191
- package/dist/services/jsdelivr/manifest.js +0 -136
- package/dist/services/jsdelivr-registry.js +0 -9
- package/dist/services/persistent-cache.js +0 -237
package/README.md
CHANGED
|
@@ -49,9 +49,38 @@ inup [options]
|
|
|
49
49
|
-i, --ignore <packages> Ignore packages (comma-separated, glob supported)
|
|
50
50
|
--max-depth <number> Maximum scan depth for package discovery (default: 10)
|
|
51
51
|
--package-manager <name> Force package manager (npm, yarn, pnpm, bun)
|
|
52
|
+
--json Print a machine-readable JSON report and exit (read-only)
|
|
53
|
+
-c, --check Exit non-zero if updates exist, without writing (for CI)
|
|
52
54
|
--debug Write verbose debug logs
|
|
53
55
|
```
|
|
54
56
|
|
|
57
|
+
## CI & Scripting
|
|
58
|
+
|
|
59
|
+
`inup` runs headless automatically when stdout isn't a TTY or `$CI` is set, so it never hangs in a
|
|
60
|
+
pipeline waiting on the interactive UI. Both `--json` and `--check` are **read-only** — they report,
|
|
61
|
+
they never edit `package.json` or install.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
inup --check # exit 1 if anything is outdated → fails the build
|
|
65
|
+
inup --json | jq # structured drift report for dashboards/bots
|
|
66
|
+
inup | cat # plain line-based report when piped to a log
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Each reported package carries its health signals: `deprecated` (npm deprecation message), `enginesNode`
|
|
70
|
+
(declared `engines.node`), and `vulnerability` (known advisories on the currently-installed version,
|
|
71
|
+
from one bulk `npm audit`-style request). Every advisory is **cross-referenced against the upgrade
|
|
72
|
+
targets**, so you know whether the upgrade actually fixes it:
|
|
73
|
+
|
|
74
|
+
- `vulnerability.advisories[].fixedByRange` / `fixedByLatest` — does the in-range / latest target escape
|
|
75
|
+
this advisory's affected range?
|
|
76
|
+
- `vulnerability.fixedByRange` / `fixedByLatest` — does the target clear **every** advisory?
|
|
77
|
+
|
|
78
|
+
The summary includes a `vulnerable` count, and the payload carries a `schemaVersion` so scripts and
|
|
79
|
+
agents can pin to a known shape.
|
|
80
|
+
|
|
81
|
+
Output hygiene: with `--json`, stdout carries **only** the JSON document; all progress and warnings go
|
|
82
|
+
to stderr. Exit codes: `0` up to date, `1` updates exist (`--check`), `2` error.
|
|
83
|
+
|
|
55
84
|
## Keyboard Shortcuts
|
|
56
85
|
|
|
57
86
|
<!-- KEYS:START -->
|
|
@@ -83,7 +112,7 @@ inup [options]
|
|
|
83
112
|
|
|
84
113
|
## Privacy
|
|
85
114
|
|
|
86
|
-
No tracking, no telemetry, no data collection. Package metadata is fetched directly from the npm registry. Download counts come from the npm downloads API.
|
|
115
|
+
No tracking, no telemetry, no data collection. Package metadata is fetched directly from the npm registry. Download counts come from the npm downloads API. Changelog and release notes are fetched from GitHub.
|
|
87
116
|
|
|
88
117
|
## License
|
|
89
118
|
|
package/dist/cli.js
CHANGED
|
@@ -9,23 +9,36 @@ const commander_1 = require("commander");
|
|
|
9
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
10
10
|
const path_1 = require("path");
|
|
11
11
|
const index_1 = require("./index");
|
|
12
|
+
const headless_1 = require("./features/headless");
|
|
12
13
|
const services_1 = require("./services");
|
|
13
14
|
const config_1 = require("./config");
|
|
14
15
|
const utils_1 = require("./utils");
|
|
15
16
|
const git_1 = require("./utils/git");
|
|
16
17
|
const ui_1 = require("./ui");
|
|
18
|
+
// Load developer-only toggles from <inup-repo>/.env.local before anything reads
|
|
19
|
+
// env. Best-effort, gitignored, never overrides real env. Lets perf/debug be
|
|
20
|
+
// "set once" across every project without shell config.
|
|
21
|
+
(0, utils_1.loadInupLocalEnv)();
|
|
17
22
|
const program = new commander_1.Command();
|
|
18
23
|
async function runCli(options) {
|
|
24
|
+
// Resolve colored-output intent before anything renders.
|
|
25
|
+
(0, utils_1.applyColorSetting)(options.color);
|
|
19
26
|
const cwd = (0, path_1.resolve)(options.dir);
|
|
20
27
|
if (options.debug || process.env.INUP_DEBUG === '1') {
|
|
21
28
|
(0, utils_1.enableDebugLogging)();
|
|
22
29
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 headless report path.
|
|
32
|
+
const interactive = !!process.stdout.isTTY && !process.env.CI && !options.json && !options.check;
|
|
33
|
+
// The dirty-tree prompt would hang without a TTY; headless is read-only anyway, so skip it.
|
|
34
|
+
if (interactive) {
|
|
35
|
+
const gitState = (0, git_1.getGitWorkingTreeState)(cwd);
|
|
36
|
+
if (gitState.isRepo && gitState.isDirty) {
|
|
37
|
+
const shouldProceed = await ui_1.TerminalInput.promptForImmediateConfirmation(`${chalk_1.default.yellow('Warning:')} dirty working tree. Proceed anyway? ${chalk_1.default.dim('[y/N]')} `, false);
|
|
38
|
+
if (!shouldProceed) {
|
|
39
|
+
console.log(chalk_1.default.yellow('Upgrade cancelled.'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
29
42
|
}
|
|
30
43
|
}
|
|
31
44
|
// Load project config from .inuprc
|
|
@@ -52,8 +65,11 @@ async function runCli(options) {
|
|
|
52
65
|
console.error(chalk_1.default.yellow('Expected a non-negative integer, for example: --max-depth 10'));
|
|
53
66
|
process.exit(1);
|
|
54
67
|
}
|
|
55
|
-
// Check for updates in the background (non-blocking)
|
|
56
|
-
|
|
68
|
+
// Check for updates in the background (non-blocking). Interactive only — keeps headless stdout
|
|
69
|
+
// clean and avoids a lingering fetch handle in CI.
|
|
70
|
+
const updateCheckPromise = interactive
|
|
71
|
+
? (0, services_1.checkForUpdateAsync)(config_1.PACKAGE_NAME, config_1.PACKAGE_VERSION)
|
|
72
|
+
: undefined;
|
|
57
73
|
// Validate package manager if provided
|
|
58
74
|
let packageManager;
|
|
59
75
|
if (options.packageManager) {
|
|
@@ -65,17 +81,28 @@ async function runCli(options) {
|
|
|
65
81
|
}
|
|
66
82
|
packageManager = options.packageManager;
|
|
67
83
|
}
|
|
68
|
-
const
|
|
84
|
+
const runnerOptions = {
|
|
69
85
|
cwd,
|
|
70
86
|
excludePatterns,
|
|
87
|
+
scanDirs: projectConfig.scanDirs,
|
|
71
88
|
maxDepth,
|
|
72
89
|
ignorePackages,
|
|
73
90
|
packageManager,
|
|
74
91
|
showPeerDependencyVulnerabilities: projectConfig.showPeerDependencyVulnerabilities ?? false,
|
|
75
92
|
showOptionalDependencyVulnerabilities: projectConfig.showOptionalDependencyVulnerabilities ?? false,
|
|
76
93
|
debug: options.debug || process.env.INUP_DEBUG === '1',
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
saveExact: options.saveExact ?? false,
|
|
95
|
+
// Adaptive concurrency defaults ON; it's an internal/dev toggle with no public
|
|
96
|
+
// flag. Set INUP_ADAPTIVE=0 to disable (e.g. for A/B perf comparisons).
|
|
97
|
+
adaptive: process.env.INUP_ADAPTIVE !== '0',
|
|
98
|
+
};
|
|
99
|
+
// Non-interactive (piped / CI / --json / --check) routes to the read-only headless feature;
|
|
100
|
+
// only the interactive path builds the full TUI runner.
|
|
101
|
+
if (!interactive) {
|
|
102
|
+
await new headless_1.HeadlessRunner(runnerOptions).run({ json: options.json, check: options.check });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
await new index_1.UpgradeRunner(runnerOptions).run();
|
|
79
106
|
// After the main flow completes, check if there's an update available
|
|
80
107
|
const updateCheck = await updateCheckPromise;
|
|
81
108
|
if (updateCheck?.isOutdated) {
|
|
@@ -109,6 +136,10 @@ program
|
|
|
109
136
|
.option('--max-depth <number>', 'maximum directory depth for package.json discovery', '10')
|
|
110
137
|
.option('--package-manager <name>', 'manually specify package manager (npm, yarn, pnpm, bun)')
|
|
111
138
|
.option('--debug', 'write verbose debug log to /tmp/inup-debug-YYYY-MM-DD.log')
|
|
139
|
+
.option('--no-color', 'disable colored output (also respects NO_COLOR / FORCE_COLOR)')
|
|
140
|
+
.option('--save-exact', 'write exact versions instead of preserving the range prefix (^/~)')
|
|
141
|
+
.option('--json', 'print a machine-readable JSON report and exit (non-interactive, read-only)')
|
|
142
|
+
.option('-c, --check', 'exit non-zero if updates exist, without writing (for CI; read-only)')
|
|
112
143
|
.action(runCli);
|
|
113
144
|
// Handle uncaught errors gracefully
|
|
114
145
|
process.on('uncaughtException', (error) => {
|
package/dist/config/constants.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.POOL_CONNECTIONS = exports.REQUEST_TIMEOUT = exports.NPM_REGISTRY_URL = exports.PACKAGE_VERSION = exports.PACKAGE_NAME = void 0;
|
|
4
4
|
var package_meta_1 = require("./package-meta");
|
|
5
5
|
Object.defineProperty(exports, "PACKAGE_NAME", { enumerable: true, get: function () { return package_meta_1.PACKAGE_NAME; } });
|
|
6
6
|
Object.defineProperty(exports, "PACKAGE_VERSION", { enumerable: true, get: function () { return package_meta_1.PACKAGE_VERSION; } });
|
|
7
7
|
exports.NPM_REGISTRY_URL = 'https://registry.npmjs.org';
|
|
8
|
-
exports.JSDELIVR_CDN_URL = 'https://cdn.jsdelivr.net/npm';
|
|
9
|
-
exports.MAX_CONCURRENT_REQUESTS = 150;
|
|
10
|
-
exports.CACHE_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
11
8
|
exports.REQUEST_TIMEOUT = 60000; // 60 seconds in milliseconds
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
// Upper bound for both the undici Pool's connection count and the adaptive
|
|
10
|
+
// concurrency controller's ceiling, kept as one const so they never drift apart.
|
|
11
|
+
// With pipelining:1, effective in-flight requests are capped by the pool's
|
|
12
|
+
// connection count, so the controller must never ramp past it.
|
|
13
|
+
exports.POOL_CONNECTIONS = 24;
|
|
15
14
|
//# sourceMappingURL=constants.js.map
|
|
@@ -54,6 +54,11 @@ function normalizeConfig(config) {
|
|
|
54
54
|
normalized.exclude = config.exclude.filter((item) => typeof item === 'string');
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
if (config.scanDirs) {
|
|
58
|
+
if (Array.isArray(config.scanDirs)) {
|
|
59
|
+
normalized.scanDirs = config.scanDirs.filter((item) => typeof item === 'string');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
57
62
|
if (typeof config.showPeerDependencyVulnerabilities === 'boolean') {
|
|
58
63
|
normalized.showPeerDependencyVulnerabilities = config.showPeerDependencyVulnerabilities;
|
|
59
64
|
}
|
|
@@ -32,8 +32,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.PackageDetector = void 0;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
37
41
|
const semver = __importStar(require("semver"));
|
|
38
42
|
const utils_1 = require("../utils");
|
|
39
43
|
const services_1 = require("../services");
|
|
@@ -46,15 +50,19 @@ class PackageDetector {
|
|
|
46
50
|
packageJson = null;
|
|
47
51
|
cwd;
|
|
48
52
|
excludePatterns;
|
|
53
|
+
scanDirs;
|
|
49
54
|
ignorePackages;
|
|
50
55
|
maxDepth;
|
|
51
56
|
batchSize = 10;
|
|
52
57
|
maxConcurrency = 10;
|
|
58
|
+
adaptive;
|
|
53
59
|
constructor(options) {
|
|
54
60
|
this.cwd = options?.cwd || process.cwd();
|
|
55
61
|
this.excludePatterns = options?.excludePatterns || [];
|
|
62
|
+
this.scanDirs = options?.scanDirs || [];
|
|
56
63
|
this.ignorePackages = options?.ignorePackages || [];
|
|
57
64
|
this.maxDepth = options?.maxDepth ?? 10;
|
|
65
|
+
this.adaptive = options?.adaptive ?? true;
|
|
58
66
|
this.packageJsonPath = (0, utils_1.findPackageJson)(this.cwd);
|
|
59
67
|
if (this.packageJsonPath) {
|
|
60
68
|
this.packageJson = (0, utils_1.readPackageJson)(this.packageJsonPath);
|
|
@@ -63,6 +71,19 @@ class PackageDetector {
|
|
|
63
71
|
hasPackageJson() {
|
|
64
72
|
return this.packageJsonPath !== null && this.packageJson !== null;
|
|
65
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The resolved fetch configuration for this run, for perf logging. Exposes the
|
|
76
|
+
* exact values handed to the registry fetcher so a logged run is reproducible.
|
|
77
|
+
*/
|
|
78
|
+
getPerfConfig() {
|
|
79
|
+
return {
|
|
80
|
+
cwd: this.cwd,
|
|
81
|
+
adaptive: this.adaptive,
|
|
82
|
+
maxConcurrency: this.maxConcurrency,
|
|
83
|
+
batchSize: this.batchSize,
|
|
84
|
+
poolConnections: config_1.POOL_CONNECTIONS,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
66
87
|
async getOutdatedPackages() {
|
|
67
88
|
const packages = [];
|
|
68
89
|
await this.streamOutdatedPackages((event) => {
|
|
@@ -103,6 +124,11 @@ class PackageDetector {
|
|
|
103
124
|
currentVersions: prepared.currentVersions,
|
|
104
125
|
batchSize: this.batchSize,
|
|
105
126
|
maxConcurrency: this.maxConcurrency,
|
|
127
|
+
adaptive: this.adaptive,
|
|
128
|
+
onControlTick: (tick) => performanceTracker.recordControlTick(tick),
|
|
129
|
+
onPackageTiming: (0, debug_1.isPerfLoggingEnabled)()
|
|
130
|
+
? (name, latencyMs) => performanceTracker.recordPackageTiming({ name, latencyMs })
|
|
131
|
+
: undefined,
|
|
106
132
|
onBatchReady: (batch) => {
|
|
107
133
|
const batchStart = lastBatchEndAt;
|
|
108
134
|
let batchFailedCount = 0;
|
|
@@ -282,6 +308,8 @@ class PackageDetector {
|
|
|
282
308
|
hasRangeUpdate,
|
|
283
309
|
hasMajorUpdate,
|
|
284
310
|
allVersions,
|
|
311
|
+
deprecated: packageData.deprecated,
|
|
312
|
+
enginesNode: packageData.enginesNode,
|
|
285
313
|
};
|
|
286
314
|
}
|
|
287
315
|
catch (error) {
|
|
@@ -313,13 +341,17 @@ class PackageDetector {
|
|
|
313
341
|
};
|
|
314
342
|
}
|
|
315
343
|
async findPackageJsonFilesWithTimeout(timeoutMs) {
|
|
344
|
+
const skippedPackageDirs = new Set();
|
|
316
345
|
try {
|
|
317
346
|
let timeoutId;
|
|
318
347
|
try {
|
|
319
|
-
|
|
348
|
+
const files = await Promise.race([
|
|
320
349
|
(0, utils_1.findAllPackageJsonFilesAsync)(this.cwd, this.excludePatterns, this.maxDepth, (currentDir, foundCount) => {
|
|
321
350
|
const truncatedDir = currentDir.length > 50 ? '...' + currentDir.slice(-47) : currentDir;
|
|
322
351
|
this.showProgress(`🔍 Scanning ${truncatedDir} (found ${foundCount})`);
|
|
352
|
+
}, {
|
|
353
|
+
scanDirs: this.scanDirs,
|
|
354
|
+
onSkippedPackageDir: (relativePath) => skippedPackageDirs.add(relativePath),
|
|
323
355
|
}),
|
|
324
356
|
new Promise((_, reject) => {
|
|
325
357
|
timeoutId = setTimeout(() => {
|
|
@@ -328,6 +360,8 @@ class PackageDetector {
|
|
|
328
360
|
timeoutId.unref?.();
|
|
329
361
|
}),
|
|
330
362
|
]);
|
|
363
|
+
this.warnSkippedPackageDirs(skippedPackageDirs);
|
|
364
|
+
return files;
|
|
331
365
|
}
|
|
332
366
|
finally {
|
|
333
367
|
if (timeoutId) {
|
|
@@ -339,6 +373,20 @@ class PackageDetector {
|
|
|
339
373
|
throw new Error(`Failed to scan for package.json files: ${err}. Try using --exclude patterns to skip problematic directories.`);
|
|
340
374
|
}
|
|
341
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Warn (once per directory, after the scan) about package.json-bearing directories that the
|
|
378
|
+
* default skip list pruned, so the user can re-include them via `.inuprc`'s `scanDirs`.
|
|
379
|
+
* Emitted after scanning so it does not corrupt the progress spinner output.
|
|
380
|
+
*/
|
|
381
|
+
warnSkippedPackageDirs(skippedPackageDirs) {
|
|
382
|
+
if (skippedPackageDirs.size === 0) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const list = Array.from(skippedPackageDirs).sort();
|
|
386
|
+
console.warn(chalk_1.default.yellow(`⚠️ Skipped ${list.length} package.json-bearing director${list.length === 1 ? 'y' : 'ies'} matching the default ignore list:\n` +
|
|
387
|
+
list.map((dir) => ` - ${dir}`).join('\n') +
|
|
388
|
+
`\n Add the directory name(s) to "scanDirs" in .inuprc to include them.`));
|
|
389
|
+
}
|
|
342
390
|
isWorkspaceReference(version) {
|
|
343
391
|
return (version.includes('workspace:') ||
|
|
344
392
|
version === '*' ||
|
|
@@ -32,6 +32,7 @@ class UpgradeRunner {
|
|
|
32
32
|
this.ui = new interactive_ui_1.InteractiveUI(this.packageManager, {
|
|
33
33
|
showPeerDependencyVulnerabilities: options?.showPeerDependencyVulnerabilities ?? false,
|
|
34
34
|
showOptionalDependencyVulnerabilities: options?.showOptionalDependencyVulnerabilities ?? false,
|
|
35
|
+
saveExact: options?.saveExact ?? false,
|
|
35
36
|
});
|
|
36
37
|
this.upgrader = new upgrader_1.PackageUpgrader(this.packageManager);
|
|
37
38
|
}
|
|
@@ -91,6 +92,14 @@ class UpgradeRunner {
|
|
|
91
92
|
progress.isLoading = event.payload.progress.isLoading;
|
|
92
93
|
performanceTracker.mark('firstBatch');
|
|
93
94
|
performanceTracker.mark('allLoaded');
|
|
95
|
+
if ((0, debug_1.isPerfLoggingEnabled)()) {
|
|
96
|
+
(0, debug_1.writePerfLog)({
|
|
97
|
+
...this.detector.getPerfConfig(),
|
|
98
|
+
packageManager: this.packageManager.name,
|
|
99
|
+
mode: 'interactive',
|
|
100
|
+
env: (0, debug_1.perfEnv)(),
|
|
101
|
+
}, performanceTracker.snapshot());
|
|
102
|
+
}
|
|
94
103
|
refreshUI?.();
|
|
95
104
|
}
|
|
96
105
|
});
|
package/dist/core/upgrader.js
CHANGED
|
@@ -92,7 +92,8 @@ class PackageUpgrader {
|
|
|
92
92
|
const packageDir = packageJsonPath.replace('/package.json', '');
|
|
93
93
|
const spinner = (0, nanospinner_1.createSpinner)(`Upgrading ${type} in ${packageDir}...`).start();
|
|
94
94
|
try {
|
|
95
|
-
// Read the current package.json
|
|
95
|
+
// Read the current package.json — keep the raw text so we can round-trip its formatting
|
|
96
|
+
const rawContent = (0, fs_1.readFileSync)(packageJsonPath, 'utf-8');
|
|
96
97
|
const packageJson = (0, utils_1.readPackageJson)(packageJsonPath);
|
|
97
98
|
// Group by upgrade type (range vs latest)
|
|
98
99
|
const rangeChoices = choices.filter((c) => c.upgradeType === 'range');
|
|
@@ -118,9 +119,14 @@ class PackageUpgrader {
|
|
|
118
119
|
modified = true;
|
|
119
120
|
});
|
|
120
121
|
}
|
|
121
|
-
// Write back the modified package.json
|
|
122
|
+
// Write back the modified package.json, preserving the original indentation and
|
|
123
|
+
// trailing-newline style. Skip the write entirely when nothing actually changed.
|
|
122
124
|
if (modified) {
|
|
123
|
-
(0,
|
|
125
|
+
const format = (0, utils_1.detectJsonFormat)(rawContent);
|
|
126
|
+
const nextContent = JSON.stringify(packageJson, null, format.indent) + (format.trailingNewline ? '\n' : '');
|
|
127
|
+
if (nextContent !== rawContent) {
|
|
128
|
+
(0, fs_1.writeFileSync)(packageJsonPath, nextContent);
|
|
129
|
+
}
|
|
124
130
|
}
|
|
125
131
|
spinner.success({ text: `Upgraded ${choices.length} ${type} in ${packageDir}` });
|
|
126
132
|
// Show which packages were upgraded
|
|
@@ -3,16 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PackageMetadataService = void 0;
|
|
4
4
|
const npm_registry_client_1 = require("../clients/npm-registry-client");
|
|
5
5
|
const package_metadata_1 = require("../parsers/package-metadata");
|
|
6
|
-
const jsdelivr_registry_1 = require("../../../services/jsdelivr-registry");
|
|
7
6
|
const inflight_1 = require("../../../services/http/inflight");
|
|
8
7
|
class PackageMetadataService {
|
|
9
8
|
npmRegistryClient;
|
|
10
|
-
exactManifestFetcher;
|
|
11
9
|
cache = new Map();
|
|
12
10
|
inFlight = new inflight_1.InflightMap();
|
|
13
|
-
constructor(npmRegistryClient = new npm_registry_client_1.NpmRegistryClient()
|
|
11
|
+
constructor(npmRegistryClient = new npm_registry_client_1.NpmRegistryClient()) {
|
|
14
12
|
this.npmRegistryClient = npmRegistryClient;
|
|
15
|
-
this.exactManifestFetcher = exactManifestFetcher;
|
|
16
13
|
}
|
|
17
14
|
clearCache() {
|
|
18
15
|
this.cache.clear();
|
|
@@ -85,15 +82,7 @@ class PackageMetadataService {
|
|
|
85
82
|
}
|
|
86
83
|
async fetchPackageManifest(packageName, version, signal) {
|
|
87
84
|
signal?.throwIfAborted();
|
|
88
|
-
|
|
89
|
-
if (normalizedVersion) {
|
|
90
|
-
const jsdelivrManifest = await this.exactManifestFetcher(packageName, normalizedVersion);
|
|
91
|
-
if (jsdelivrManifest) {
|
|
92
|
-
return jsdelivrManifest;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
signal?.throwIfAborted();
|
|
96
|
-
return await this.npmRegistryClient.fetchPackageManifest(packageName, normalizedVersion || 'latest', signal);
|
|
85
|
+
return await this.npmRegistryClient.fetchPackageManifest(packageName, version?.trim() || 'latest', signal);
|
|
97
86
|
}
|
|
98
87
|
buildMetadata(packageName, rawData) {
|
|
99
88
|
return (0, package_metadata_1.mapPackageManifestToMetadata)(packageName, rawData);
|
|
@@ -38,7 +38,6 @@ const semver = __importStar(require("semver"));
|
|
|
38
38
|
const github_client_1 = require("../clients/github-client");
|
|
39
39
|
const changelog_parser_1 = require("../parsers/changelog-parser");
|
|
40
40
|
const github_release_html_parser_1 = require("../parsers/github-release-html-parser");
|
|
41
|
-
const constants_1 = require("../../../config/constants");
|
|
42
41
|
const RELEASE_NOTES_FETCH_TIMEOUT_MS = 5000;
|
|
43
42
|
const PREFER_GITHUB_RELEASE_PAGE = true;
|
|
44
43
|
class ReleaseNotesService {
|
|
@@ -89,9 +88,6 @@ class ReleaseNotesService {
|
|
|
89
88
|
return notes;
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
|
-
const changelogNotes = await this.fetchJsdelivrChangelog(packageName, version, signal);
|
|
93
|
-
if (changelogNotes)
|
|
94
|
-
return changelogNotes;
|
|
95
91
|
return null;
|
|
96
92
|
}
|
|
97
93
|
getGitHubSources(repoUrl, version, signal) {
|
|
@@ -154,29 +150,6 @@ class ReleaseNotesService {
|
|
|
154
150
|
return null;
|
|
155
151
|
return (0, changelog_parser_1.extractVersionSection)(fullText, version);
|
|
156
152
|
}
|
|
157
|
-
async fetchJsdelivrChangelog(packageName, version, signal) {
|
|
158
|
-
const fullText = await this.fetchPublishedPackageChangelog(packageName, version, signal);
|
|
159
|
-
if (!fullText)
|
|
160
|
-
return null;
|
|
161
|
-
return (0, changelog_parser_1.extractVersionSection)(fullText, version);
|
|
162
|
-
}
|
|
163
|
-
async fetchPublishedPackageChangelog(packageName, version, signal) {
|
|
164
|
-
try {
|
|
165
|
-
const response = await fetch(`${constants_1.JSDELIVR_CDN_URL}/${encodeURIComponent(packageName)}@${version}/CHANGELOG.md`, {
|
|
166
|
-
method: 'GET',
|
|
167
|
-
signal,
|
|
168
|
-
});
|
|
169
|
-
if (!response.ok)
|
|
170
|
-
return null;
|
|
171
|
-
return await response.text();
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
175
|
-
throw error;
|
|
176
|
-
}
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
153
|
}
|
|
181
154
|
exports.ReleaseNotesService = ReleaseNotesService;
|
|
182
155
|
//# sourceMappingURL=release-notes-service.js.map
|
|
@@ -16,5 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./types/debug.types"), exports);
|
|
18
18
|
__exportStar(require("./services/performance-tracker"), exports);
|
|
19
|
+
__exportStar(require("./services/perf-logger"), exports);
|
|
19
20
|
__exportStar(require("./renderer/performance-modal"), exports);
|
|
20
21
|
//# sourceMappingURL=index.js.map
|
|
@@ -21,7 +21,7 @@ function labelValue(label, value, labelWidth = 22) {
|
|
|
21
21
|
return `${chalk_1.default.white(padded)} ${value}`;
|
|
22
22
|
}
|
|
23
23
|
function buildSections(snapshot) {
|
|
24
|
-
const { phases, counts, batches, failedPackages, packageManager, totalMs } = snapshot;
|
|
24
|
+
const { phases, counts, batches, controlTicks, failedPackages, packageManager, totalMs } = snapshot;
|
|
25
25
|
const pinned = [
|
|
26
26
|
{
|
|
27
27
|
key: 'header',
|
|
@@ -67,6 +67,22 @@ function buildSections(snapshot) {
|
|
|
67
67
|
bodyRows.push(chalk_1.default.gray(' (no batches recorded)'));
|
|
68
68
|
}
|
|
69
69
|
bodyRows.push('');
|
|
70
|
+
bodyRows.push(chalk_1.default.bold('Concurrency'));
|
|
71
|
+
if (controlTicks.length > 0) {
|
|
72
|
+
const limits = controlTicks.map((t) => t.limit);
|
|
73
|
+
const finalTick = controlTicks[controlTicks.length - 1];
|
|
74
|
+
const hardDowns = controlTicks.filter((t) => t.reason === 'hard-down').length;
|
|
75
|
+
bodyRows.push(labelValue('Start limit', formatCount(controlTicks[0].limit)));
|
|
76
|
+
bodyRows.push(labelValue('Peak limit', formatCount(Math.max(...limits))));
|
|
77
|
+
bodyRows.push(labelValue('Final limit', formatCount(finalTick.limit)));
|
|
78
|
+
bodyRows.push(labelValue('Final EWMA', formatMs(finalTick.ewmaMs)));
|
|
79
|
+
bodyRows.push(labelValue('Control ticks', formatCount(controlTicks.length)));
|
|
80
|
+
bodyRows.push(labelValue('Hard back-offs', formatCount(hardDowns)));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
bodyRows.push(chalk_1.default.gray(' (fixed — adaptive off or run too small)'));
|
|
84
|
+
}
|
|
85
|
+
bodyRows.push('');
|
|
70
86
|
bodyRows.push(chalk_1.default.bold('Failures'));
|
|
71
87
|
if (failedPackages.length > 0) {
|
|
72
88
|
for (const name of failedPackages) {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isPerfLoggingEnabled = isPerfLoggingEnabled;
|
|
4
|
+
exports.perfEnv = perfEnv;
|
|
5
|
+
exports.getPerfDir = getPerfDir;
|
|
6
|
+
exports.writePerfLog = writePerfLog;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const adaptive_controller_1 = require("../../../services/http/adaptive-controller");
|
|
10
|
+
/**
|
|
11
|
+
* Performance debug logger.
|
|
12
|
+
*
|
|
13
|
+
* When INUP_PERF=1, every run writes ONE self-contained JSON file capturing the
|
|
14
|
+
* full configuration plus the performance snapshot (phases, batches, adaptive
|
|
15
|
+
* control ticks, counts). The files accumulate in a gitignored directory so a
|
|
16
|
+
* series of runs can be diffed to find the best-performing configuration.
|
|
17
|
+
*
|
|
18
|
+
* Output location:
|
|
19
|
+
* - Default: `<cwd>/.inup-perf` (logs next to the project being scanned).
|
|
20
|
+
* - INUP_PERF_DIR=<path>: a single centralized directory (e.g. the inup repo)
|
|
21
|
+
* so runs from many different projects collect in one place for analysis.
|
|
22
|
+
* The project name is encoded into each filename so they stay distinct.
|
|
23
|
+
*
|
|
24
|
+
* Each file is independent: it records every input that could affect timing, so
|
|
25
|
+
* a run can be understood (and reproduced) without external context.
|
|
26
|
+
*/
|
|
27
|
+
const PERF_DIR_NAME = '.inup-perf';
|
|
28
|
+
/** Filesystem-safe slug of a project name for use in filenames. */
|
|
29
|
+
function projectSlug(cwd) {
|
|
30
|
+
const name = (0, path_1.basename)((0, path_1.resolve)(cwd)) || 'root';
|
|
31
|
+
return name.replace(/[^a-zA-Z0-9._-]/g, '-').slice(0, 40);
|
|
32
|
+
}
|
|
33
|
+
function isPerfLoggingEnabled() {
|
|
34
|
+
return process.env.INUP_PERF === '1';
|
|
35
|
+
}
|
|
36
|
+
/** Capture the env toggles that influence a run, for reproducibility. */
|
|
37
|
+
function perfEnv() {
|
|
38
|
+
return {
|
|
39
|
+
INUP_ADAPTIVE: process.env.INUP_ADAPTIVE,
|
|
40
|
+
INUP_PERF: process.env.INUP_PERF,
|
|
41
|
+
INUP_DEBUG: process.env.INUP_DEBUG,
|
|
42
|
+
CI: process.env.CI,
|
|
43
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve (and lazily create) the perf-log directory.
|
|
48
|
+
*
|
|
49
|
+
* - INUP_PERF_DIR set → that directory verbatim (centralized collection point,
|
|
50
|
+
* e.g. the inup repo's .inup-perf). Relative paths resolve from cwd.
|
|
51
|
+
* - otherwise → `<cwd>/.inup-perf` (logs sit next to the scanned project).
|
|
52
|
+
*/
|
|
53
|
+
function getPerfDir(cwd = process.cwd()) {
|
|
54
|
+
const override = process.env.INUP_PERF_DIR;
|
|
55
|
+
const dir = override
|
|
56
|
+
? (0, path_1.isAbsolute)(override)
|
|
57
|
+
? override
|
|
58
|
+
: (0, path_1.resolve)(cwd, override)
|
|
59
|
+
: (0, path_1.join)(cwd, PERF_DIR_NAME);
|
|
60
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
61
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
return dir;
|
|
64
|
+
}
|
|
65
|
+
const pad = (n, width = 2) => String(n).padStart(width, '0');
|
|
66
|
+
function fileStamp(d) {
|
|
67
|
+
return (`${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}` +
|
|
68
|
+
`-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}` +
|
|
69
|
+
`-${pad(d.getMilliseconds(), 3)}`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Write one perf record to its own JSON file. Best-effort: never throws into the
|
|
73
|
+
* caller (a debugging aid must not break a real run). Returns the path written,
|
|
74
|
+
* or null if disabled or on failure.
|
|
75
|
+
*/
|
|
76
|
+
function writePerfLog(config, snapshot) {
|
|
77
|
+
if (!isPerfLoggingEnabled())
|
|
78
|
+
return null;
|
|
79
|
+
try {
|
|
80
|
+
const now = new Date();
|
|
81
|
+
const record = {
|
|
82
|
+
schemaVersion: 1,
|
|
83
|
+
timestamp: now.toISOString(),
|
|
84
|
+
wallMs: snapshot.totalMs,
|
|
85
|
+
config,
|
|
86
|
+
tuning: adaptive_controller_1.DEFAULT_TUNING,
|
|
87
|
+
snapshot,
|
|
88
|
+
};
|
|
89
|
+
const dir = getPerfDir(config.cwd);
|
|
90
|
+
const modeTag = config.mode;
|
|
91
|
+
const adaptiveTag = config.adaptive ? 'adaptive' : 'fixed';
|
|
92
|
+
// Project name in the filename keeps multi-project runs distinct when many
|
|
93
|
+
// collect into one centralized INUP_PERF_DIR.
|
|
94
|
+
const fileName = `run-${fileStamp(now)}-${projectSlug(config.cwd)}-${adaptiveTag}-${modeTag}.json`;
|
|
95
|
+
const filePath = (0, path_1.join)(dir, fileName);
|
|
96
|
+
(0, fs_1.writeFileSync)(filePath, JSON.stringify(record, null, 2));
|
|
97
|
+
// Maintain a stable pointer to the most recent run for quick reads.
|
|
98
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(dir, 'latest.json'), JSON.stringify(record, null, 2));
|
|
99
|
+
// And a cheap one-line-per-run index for fast scanning.
|
|
100
|
+
appendPerfIndex(dir, record);
|
|
101
|
+
if (process.env.INUP_DEBUG === '1') {
|
|
102
|
+
process.stderr.write(`[inup] perf log → ${filePath}\n`);
|
|
103
|
+
}
|
|
104
|
+
return filePath;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Never let perf logging affect the run.
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Append a single line to a NDJSON index for ultra-cheap scanning, best-effort. */
|
|
112
|
+
function appendPerfIndex(dir, record) {
|
|
113
|
+
try {
|
|
114
|
+
const line = JSON.stringify({
|
|
115
|
+
timestamp: record.timestamp,
|
|
116
|
+
wallMs: record.wallMs,
|
|
117
|
+
adaptive: record.config.adaptive,
|
|
118
|
+
mode: record.config.mode,
|
|
119
|
+
uniquePackages: record.snapshot.counts.uniquePackages,
|
|
120
|
+
registryFetch: record.snapshot.phases.registryFetch,
|
|
121
|
+
controlTicks: record.snapshot.controlTicks.length,
|
|
122
|
+
}) + '\n';
|
|
123
|
+
(0, fs_1.appendFileSync)((0, path_1.join)(dir, 'index.ndjson'), line);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* best-effort */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=perf-logger.js.map
|