inup 1.5.6 → 1.6.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 +29 -0
- package/dist/cli.js +35 -11
- package/dist/config/constants.js +1 -5
- package/dist/config/project-config.js +5 -0
- package/dist/core/package-detector.js +29 -1
- package/dist/core/upgrade-runner.js +1 -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/headless/headless-runner.js +53 -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/index.js +0 -1
- package/dist/services/npm-registry.js +9 -17
- 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/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 +3 -0
- package/dist/utils/manifest.js +35 -0
- package/dist/utils/version.js +9 -2
- package/package.json +3 -3
- 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/interactive-ui.js
CHANGED
|
@@ -20,6 +20,7 @@ class InteractiveUI {
|
|
|
20
20
|
renderer;
|
|
21
21
|
packageManager;
|
|
22
22
|
options;
|
|
23
|
+
saveExact;
|
|
23
24
|
vulnerabilityAuditController = new controllers_1.VulnerabilityAuditController();
|
|
24
25
|
packageInfoModalController = new controllers_1.PackageInfoModalController();
|
|
25
26
|
refreshView;
|
|
@@ -27,6 +28,7 @@ class InteractiveUI {
|
|
|
27
28
|
this.renderer = new ui_1.UIRenderer();
|
|
28
29
|
this.packageManager = packageManager;
|
|
29
30
|
this.options = normalizeVulnerabilityDisplayOptions(options);
|
|
31
|
+
this.saveExact = options?.saveExact ?? false;
|
|
30
32
|
}
|
|
31
33
|
async displayPackagesTable(packages) {
|
|
32
34
|
console.log(this.renderer.renderPackagesTable(packages));
|
|
@@ -37,7 +39,7 @@ class InteractiveUI {
|
|
|
37
39
|
return [];
|
|
38
40
|
}
|
|
39
41
|
const selectedStates = await (0, session_1.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => { this.refreshView = refresh; });
|
|
40
|
-
return (0, session_1.createUpgradeChoices)(selectedStates);
|
|
42
|
+
return (0, session_1.createUpgradeChoices)(selectedStates, this.saveExact);
|
|
41
43
|
}
|
|
42
44
|
createSelectionStates(packages, previousSelections, includeUpToDate = true) {
|
|
43
45
|
return (0, session_1.createSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections, includeUpToDate);
|
|
@@ -63,7 +65,7 @@ class InteractiveUI {
|
|
|
63
65
|
async selectPackagesToUpgradeProgressive(selectionStates, progress, attachRefresh) {
|
|
64
66
|
this.enqueueSecurityAudit(selectionStates);
|
|
65
67
|
const selectedStates = await (0, session_1.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => { this.refreshView = refresh; }, progress, attachRefresh);
|
|
66
|
-
return (0, session_1.createUpgradeChoices)(selectedStates);
|
|
68
|
+
return (0, session_1.createUpgradeChoices)(selectedStates, this.saveExact);
|
|
67
69
|
}
|
|
68
70
|
enqueueSecurityAudit(selectionStates) {
|
|
69
71
|
this.vulnerabilityAuditController.enqueueStates(selectionStates, () => this.refreshView?.());
|
package/dist/services/index.js
CHANGED
|
@@ -18,7 +18,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
__exportStar(require("./npm-registry"), exports);
|
|
21
|
-
__exportStar(require("./jsdelivr-registry"), exports);
|
|
22
21
|
__exportStar(require("../features/changelog"), exports);
|
|
23
22
|
__exportStar(require("./version-checker"), exports);
|
|
24
23
|
__exportStar(require("./vulnerability-checker"), exports);
|
|
@@ -10,7 +10,6 @@ const gunzipAsync = (0, node_util_1.promisify)(node_zlib_1.gunzip);
|
|
|
10
10
|
const inflateAsync = (0, node_util_1.promisify)(node_zlib_1.inflate);
|
|
11
11
|
const brotliDecompressAsync = (0, node_util_1.promisify)(node_zlib_1.brotliDecompress);
|
|
12
12
|
const config_1 = require("../config");
|
|
13
|
-
const jsdelivr_registry_1 = require("./jsdelivr-registry");
|
|
14
13
|
const version_1 = require("../utils/version");
|
|
15
14
|
const retry_1 = require("./http/retry");
|
|
16
15
|
const inflight_1 = require("./http/inflight");
|
|
@@ -32,13 +31,9 @@ const registryPool = new undici_1.Pool(registryOrigin, {
|
|
|
32
31
|
});
|
|
33
32
|
const MAX_REGISTRY_ATTEMPTS = 3;
|
|
34
33
|
const RETRY_BACKOFF_MS = [500, 1500, 3000];
|
|
35
|
-
const fetchFromJsdelivrFallback = async (packageName, currentVersion) => {
|
|
36
|
-
const jsdelivrData = await (0, jsdelivr_registry_1.getAllPackageDataFromJsdelivr)([packageName], currentVersion ? new Map([[packageName, currentVersion]]) : undefined);
|
|
37
|
-
return jsdelivrData.get(packageName) ?? { latestVersion: 'unknown', allVersions: [] };
|
|
38
|
-
};
|
|
39
34
|
async function getFreshPackageData(packageName, currentVersion) {
|
|
40
35
|
const cacheKey = `${packageName}@${currentVersion ?? ''}`;
|
|
41
|
-
return inFlightLookups.dedupe(cacheKey, () =>
|
|
36
|
+
return inFlightLookups.dedupe(cacheKey, () => fetchPackageFromRegistry(packageName));
|
|
42
37
|
}
|
|
43
38
|
const encodeRegistryPath = (packageName) => {
|
|
44
39
|
const encodedName = packageName.startsWith('@')
|
|
@@ -110,19 +105,15 @@ async function fetchFromRegistryWithRetries(path) {
|
|
|
110
105
|
}
|
|
111
106
|
return lastOutcome;
|
|
112
107
|
}
|
|
113
|
-
async function
|
|
108
|
+
async function fetchPackageFromRegistry(packageName) {
|
|
114
109
|
const path = encodeRegistryPath(packageName);
|
|
115
110
|
const outcome = await fetchFromRegistryWithRetries(path);
|
|
116
111
|
if (outcome.kind === 'success') {
|
|
117
112
|
return outcome.data;
|
|
118
113
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
// Only reach here after exhausted retries against real errors — try jsdelivr
|
|
123
|
-
// as last-resort safety net so we don't silently return 'unknown'.
|
|
124
|
-
const fallback = await fetchFromJsdelivrFallback(packageName, currentVersion).catch(() => null);
|
|
125
|
-
return fallback ?? { latestVersion: 'unknown', allVersions: [] };
|
|
114
|
+
// Not found, or exhausted retries against real errors: report unavailable.
|
|
115
|
+
// The registry is the single source of truth — there is no secondary fetch.
|
|
116
|
+
return { latestVersion: 'unknown', allVersions: [] };
|
|
126
117
|
}
|
|
127
118
|
/**
|
|
128
119
|
* Fetches version data for a list of packages from the npm registry.
|
|
@@ -132,9 +123,10 @@ async function fetchPackageFromRegistryWithFallback(packageName, currentVersion)
|
|
|
132
123
|
* It doesn't interact with batch size — batches exist only to group
|
|
133
124
|
* emissions for the UI.
|
|
134
125
|
* - No per-request timeouts: slow responses are allowed to finish. Real
|
|
135
|
-
* network errors are retried with exponential backoff; after
|
|
136
|
-
*
|
|
137
|
-
* dropped due to
|
|
126
|
+
* network errors are retried with exponential backoff; after the retry
|
|
127
|
+
* budget is exhausted the package is reported as unavailable
|
|
128
|
+
* (`latestVersion: 'unknown'`). A result is never silently dropped due to
|
|
129
|
+
* slowness.
|
|
138
130
|
*
|
|
139
131
|
* Callbacks:
|
|
140
132
|
* - `onBatchReady` fires once a whole emission batch has resolved, in
|
|
@@ -56,8 +56,8 @@ class PackageManagerDetector {
|
|
|
56
56
|
if (fromLockFile) {
|
|
57
57
|
return fromLockFile;
|
|
58
58
|
}
|
|
59
|
-
// 3. Fallback to npm
|
|
60
|
-
console.
|
|
59
|
+
// 3. Fallback to npm. Warn on stderr so it never corrupts --json output on stdout.
|
|
60
|
+
console.error(chalk_1.default.yellow('⚠️ No package manager detected. Defaulting to npm. Consider adding a "packageManager" field to your package.json.'));
|
|
61
61
|
return PACKAGE_MANAGERS.npm;
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
@@ -92,6 +92,8 @@ class PackageManagerDetector {
|
|
|
92
92
|
const lockFileChecks = [
|
|
93
93
|
{ pm: PACKAGE_MANAGERS.pnpm, path: (0, path_1.join)(cwd, 'pnpm-lock.yaml') },
|
|
94
94
|
{ pm: PACKAGE_MANAGERS.bun, path: (0, path_1.join)(cwd, 'bun.lockb') },
|
|
95
|
+
// Bun >= 1.2 writes a text `bun.lock` instead of the binary `bun.lockb`.
|
|
96
|
+
{ pm: PACKAGE_MANAGERS.bun, path: (0, path_1.join)(cwd, 'bun.lock') },
|
|
95
97
|
{ pm: PACKAGE_MANAGERS.yarn, path: (0, path_1.join)(cwd, 'yarn.lock') },
|
|
96
98
|
{ pm: PACKAGE_MANAGERS.npm, path: (0, path_1.join)(cwd, 'package-lock.json') },
|
|
97
99
|
];
|
|
@@ -107,7 +109,8 @@ class PackageManagerDetector {
|
|
|
107
109
|
}
|
|
108
110
|
// If multiple lock files, use most recently modified
|
|
109
111
|
if (existingLocks.length > 1) {
|
|
110
|
-
|
|
112
|
+
// stderr so it never corrupts --json output on stdout.
|
|
113
|
+
console.error(chalk_1.default.yellow('⚠️ Multiple lock files detected. Using most recently modified. Consider cleaning up unused lock files.'));
|
|
111
114
|
existingLocks.sort((a, b) => b.mtime - a.mtime);
|
|
112
115
|
}
|
|
113
116
|
return existingLocks[0].pm;
|
|
@@ -10,6 +10,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
10
10
|
const themes_colors_1 = require("../../themes-colors");
|
|
11
11
|
const vulnerability_1 = require("../../presenters/vulnerability");
|
|
12
12
|
const utils_1 = require("../../utils");
|
|
13
|
+
const utils_2 = require("../../../utils");
|
|
13
14
|
const release_notes_1 = require("./release-notes");
|
|
14
15
|
function formatNumber(num) {
|
|
15
16
|
if (!num)
|
|
@@ -88,6 +89,29 @@ function buildPackageInfoSections(state, modalWidth, activeTab) {
|
|
|
88
89
|
required: true,
|
|
89
90
|
behavior: 'pinned',
|
|
90
91
|
});
|
|
92
|
+
const warningRows = [];
|
|
93
|
+
const warningContentWidth = Math.max(10, modalWidth - 4);
|
|
94
|
+
if (state.deprecated) {
|
|
95
|
+
// Wrap (don't truncate) so a deprecation URL stays whole and clickable —
|
|
96
|
+
// truncation with "..." produces a dead link. `wrapPlainText` breaks on
|
|
97
|
+
// spaces only, so the URL keeps its own intact line. No emoji marker:
|
|
98
|
+
// `getVisualLength` scores glyphs like ⚠ as width 2 while many terminals
|
|
99
|
+
// render them as width 1, which throws off the modal's border alignment.
|
|
100
|
+
for (const line of (0, utils_1.wrapPlainText)(`Deprecated: ${state.deprecated}`, warningContentWidth)) {
|
|
101
|
+
warningRows.push((0, themes_colors_1.getThemeColor)('warning')(line));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const engineWarning = (0, utils_2.checkNodeEngineCompatibility)(state.enginesNode);
|
|
105
|
+
if (engineWarning) {
|
|
106
|
+
warningRows.push((0, themes_colors_1.getThemeColor)('warning')(`Hold: ${engineWarning}`));
|
|
107
|
+
}
|
|
108
|
+
if (warningRows.length > 0) {
|
|
109
|
+
sections.push({
|
|
110
|
+
key: 'warnings',
|
|
111
|
+
rows: warningRows,
|
|
112
|
+
behavior: 'pinned',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
91
115
|
if (state.homepage) {
|
|
92
116
|
sections.push({
|
|
93
117
|
key: 'homepage',
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getHealthBadge = getHealthBadge;
|
|
4
|
+
const utils_1 = require("../../utils");
|
|
5
|
+
const themes_colors_1 = require("../themes-colors");
|
|
6
|
+
/**
|
|
7
|
+
* A compact badge flagging a package's health in the list:
|
|
8
|
+
* - `[DEPR]` when the latest version is deprecated (highest priority), or
|
|
9
|
+
* - `[ENG]` when its `engines.node` is incompatible with the running Node.
|
|
10
|
+
*
|
|
11
|
+
* Returns an empty string when neither applies. Deprecation wins because an
|
|
12
|
+
* engines mismatch on an abandoned package is moot. Both render in the theme's
|
|
13
|
+
* (amber) warning color — a caution signal, not an alarm.
|
|
14
|
+
*/
|
|
15
|
+
function getHealthBadge(state) {
|
|
16
|
+
if (state.deprecated) {
|
|
17
|
+
return (0, themes_colors_1.getThemeColor)('warning')('[DEPR]');
|
|
18
|
+
}
|
|
19
|
+
if ((0, utils_1.checkNodeEngineCompatibility)(state.enginesNode)) {
|
|
20
|
+
return (0, themes_colors_1.getThemeColor)('warning')('[ENG]');
|
|
21
|
+
}
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -11,6 +11,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
11
11
|
const utils_1 = require("../../utils");
|
|
12
12
|
const themes_colors_1 = require("../../themes-colors");
|
|
13
13
|
const vulnerability_1 = require("../../presenters/vulnerability");
|
|
14
|
+
const health_1 = require("../../presenters/health");
|
|
14
15
|
function padLineToWidth(line, terminalWidth) {
|
|
15
16
|
const padding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(line));
|
|
16
17
|
return line + ' '.repeat(padding);
|
|
@@ -118,15 +119,19 @@ function renderPackageLine(state, _index, isCurrentRow, terminalWidth = 80, opti
|
|
|
118
119
|
const shouldShowVulnerability = (0, vulnerability_1.shouldDisplayVulnerabilityForDependency)(state.type, options);
|
|
119
120
|
const vulnBadge = shouldShowVulnerability ? (0, vulnerability_1.getVulnerabilityBadge)(state.vulnerability) : '';
|
|
120
121
|
const vulnBadgeWidth = vulnBadge ? utils_1.VersionUtils.getVisualLength(vulnBadge) + 1 : 0;
|
|
122
|
+
// Deprecation / engines-incompatibility marker (independent of dep type).
|
|
123
|
+
const healthBadge = (0, health_1.getHealthBadge)(state);
|
|
124
|
+
const healthBadgeWidth = healthBadge ? utils_1.VersionUtils.getVisualLength(healthBadge) + 1 : 0;
|
|
121
125
|
const nameLength = utils_1.VersionUtils.getVisualLength(truncatedName);
|
|
122
|
-
const namePadding = Math.max(0, packageNameWidth - nameLength - 1 - badgeWidth - vulnBadgeWidth);
|
|
126
|
+
const namePadding = Math.max(0, packageNameWidth - nameLength - 1 - badgeWidth - vulnBadgeWidth - healthBadgeWidth);
|
|
123
127
|
const nameDashes = shouldShowDashes(namePadding)
|
|
124
128
|
? dashColor('-').repeat(namePadding)
|
|
125
129
|
: ' '.repeat(namePadding);
|
|
126
130
|
const vulnSuffix = vulnBadge ? ` ${vulnBadge}` : '';
|
|
131
|
+
const healthSuffix = healthBadge ? ` ${healthBadge}` : '';
|
|
127
132
|
const packageNameSection = typeBadge
|
|
128
|
-
? `${displayName} ${nameDashes}${vulnSuffix}${typeBadge}`
|
|
129
|
-
: `${displayName} ${nameDashes}${vulnSuffix}`;
|
|
133
|
+
? `${displayName} ${nameDashes}${vulnSuffix}${healthSuffix}${typeBadge}`
|
|
134
|
+
: `${displayName} ${nameDashes}${vulnSuffix}${healthSuffix}`;
|
|
130
135
|
const currentSection = `${currentDot} ${currentVersion}`;
|
|
131
136
|
const currentSectionLength = utils_1.VersionUtils.getVisualLength(currentSection) + 1;
|
|
132
137
|
const currentPadding = Math.max(0, currentColumnWidth - currentSectionLength);
|
|
@@ -85,6 +85,8 @@ function createSelectionStates(packages, getCachedSummary, previousSelections, i
|
|
|
85
85
|
hasRangeUpdate: pkg.hasRangeUpdate,
|
|
86
86
|
hasMajorUpdate: pkg.hasMajorUpdate,
|
|
87
87
|
type: pkg.type,
|
|
88
|
+
deprecated: pkg.deprecated,
|
|
89
|
+
enginesNode: pkg.enginesNode,
|
|
88
90
|
vulnerability: getCachedSummary(pkg.name, pkg.currentVersion, pkg.type),
|
|
89
91
|
allVersions: pkg.allVersions,
|
|
90
92
|
};
|
|
@@ -120,13 +122,16 @@ function createPendingSelectionStates(packages, getCachedSummary, previousSelect
|
|
|
120
122
|
};
|
|
121
123
|
});
|
|
122
124
|
}
|
|
123
|
-
function createUpgradeChoices(selectedStates) {
|
|
125
|
+
function createUpgradeChoices(selectedStates, saveExact = false) {
|
|
124
126
|
const choices = [];
|
|
125
127
|
selectedStates
|
|
126
128
|
.filter((state) => state.loadState === 'ready' && state.selectedOption !== 'none')
|
|
127
129
|
.forEach((state) => {
|
|
128
130
|
const targetVersion = state.selectedOption === 'range' ? state.rangeVersion : state.latestVersion;
|
|
129
|
-
|
|
131
|
+
// Preserve the original range prefix (^/~) by default; --save-exact writes the bare version.
|
|
132
|
+
const targetVersionWithPrefix = saveExact
|
|
133
|
+
? targetVersion
|
|
134
|
+
: (0, utils_1.applyVersionPrefix)(state.currentVersionSpecifier, targetVersion);
|
|
130
135
|
const pathsToUpdate = state.packageJsonPaths || [state.packageJsonPath];
|
|
131
136
|
pathsToUpdate.forEach((packageJsonPath) => {
|
|
132
137
|
choices.push({
|
package/dist/ui/themes-colors.js
CHANGED
|
@@ -182,18 +182,27 @@ function hexToRgb(hex) {
|
|
|
182
182
|
: { r: 0, g: 0, b: 0 };
|
|
183
183
|
}
|
|
184
184
|
/**
|
|
185
|
-
* Get ANSI escape code to set terminal background color
|
|
185
|
+
* Get ANSI escape code to set terminal background color.
|
|
186
|
+
*
|
|
187
|
+
* Returns an empty string when color output is disabled (`--no-color`/`NO_COLOR`
|
|
188
|
+
* set `chalk.level` to 0), so the TUI inherits the user's own terminal
|
|
189
|
+
* background instead of painting over it. This escape is raw (not produced by
|
|
190
|
+
* chalk), so it must be gated explicitly.
|
|
186
191
|
*/
|
|
187
192
|
function getTerminalBgColorCode() {
|
|
193
|
+
if (chalk_1.default.level === 0) {
|
|
194
|
+
return '';
|
|
195
|
+
}
|
|
188
196
|
const hex = getThemeBgColor();
|
|
189
197
|
const rgb = hexToRgb(hex);
|
|
190
198
|
return `\x1b[48;2;${rgb.r};${rgb.g};${rgb.b}m`;
|
|
191
199
|
}
|
|
192
200
|
/**
|
|
193
|
-
* Get ANSI escape code to reset terminal colors
|
|
201
|
+
* Get ANSI escape code to reset terminal colors. Empty when color is disabled,
|
|
202
|
+
* so a `--no-color` run emits no escape sequences at all.
|
|
194
203
|
*/
|
|
195
204
|
function getTerminalResetCode() {
|
|
196
|
-
return '\x1b[0m';
|
|
205
|
+
return chalk_1.default.level === 0 ? '' : '\x1b[0m';
|
|
197
206
|
}
|
|
198
207
|
const BRAND_COLORS = [chalk_1.default.red, chalk_1.default.yellow, chalk_1.default.blue, chalk_1.default.magenta];
|
|
199
208
|
function coloredInupLogo() {
|
package/dist/ui/utils/cursor.js
CHANGED
|
@@ -67,16 +67,22 @@ exports.ConsoleUtils = {
|
|
|
67
67
|
*/
|
|
68
68
|
LINE_WIDTH: 80,
|
|
69
69
|
/**
|
|
70
|
-
* Show a progress message on the current line (overwrites previous content)
|
|
70
|
+
* Show a progress message on the current line (overwrites previous content).
|
|
71
|
+
* Written to stderr so stdout stays clean for --json / piped output, and only
|
|
72
|
+
* when stderr is a TTY — the \r animation is just noise in a redirected log.
|
|
71
73
|
*/
|
|
72
74
|
showProgress(message) {
|
|
73
|
-
|
|
75
|
+
if (!process.stderr.isTTY)
|
|
76
|
+
return;
|
|
77
|
+
process.stderr.write(`\r${' '.repeat(exports.ConsoleUtils.LINE_WIDTH)}\r${message}`);
|
|
74
78
|
},
|
|
75
79
|
/**
|
|
76
80
|
* Clear the current progress line
|
|
77
81
|
*/
|
|
78
82
|
clearProgress() {
|
|
79
|
-
|
|
83
|
+
if (!process.stderr.isTTY)
|
|
84
|
+
return;
|
|
85
|
+
process.stderr.write('\r' + ' '.repeat(exports.ConsoleUtils.LINE_WIDTH) + '\r');
|
|
80
86
|
},
|
|
81
87
|
};
|
|
82
88
|
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.shouldDisableColor = shouldDisableColor;
|
|
7
|
+
exports.applyColorSetting = applyColorSetting;
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
/**
|
|
10
|
+
* Decide whether colored output should be disabled.
|
|
11
|
+
*
|
|
12
|
+
* Precedence (highest first):
|
|
13
|
+
* 1. An explicit `--no-color` flag (`colorFlag === false`) always wins.
|
|
14
|
+
* 2. `FORCE_COLOR` keeps colors on.
|
|
15
|
+
* 3. `NO_COLOR` (any non-empty value) disables them — the de-facto standard.
|
|
16
|
+
*
|
|
17
|
+
* chalk v5 already honors the env vars on its own, but the explicit flag does
|
|
18
|
+
* not flow through automatically, so we resolve the final intent here.
|
|
19
|
+
*/
|
|
20
|
+
function shouldDisableColor(colorFlag, env = process.env) {
|
|
21
|
+
if (colorFlag === false) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (env.FORCE_COLOR) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return Boolean(env.NO_COLOR);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Apply the resolved color intent to chalk's global level. Call once at startup
|
|
31
|
+
* before anything renders.
|
|
32
|
+
*/
|
|
33
|
+
function applyColorSetting(colorFlag, env = process.env) {
|
|
34
|
+
if (shouldDisableColor(colorFlag, env)) {
|
|
35
|
+
chalk_1.default.level = 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=color.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.checkNodeEngineCompatibility = checkNodeEngineCompatibility;
|
|
37
|
+
const semver = __importStar(require("semver"));
|
|
38
|
+
/**
|
|
39
|
+
* Check whether the running Node version satisfies a package's declared
|
|
40
|
+
* `engines.node` range. Returns a short human-readable warning when it does
|
|
41
|
+
* not, or `null` when compatible (or when the inputs are unusable).
|
|
42
|
+
*
|
|
43
|
+
* Best-effort: an unparseable range is treated as "no opinion" (null) rather
|
|
44
|
+
* than a false warning.
|
|
45
|
+
*/
|
|
46
|
+
function checkNodeEngineCompatibility(requiredRange, currentNodeVersion = process.versions.node) {
|
|
47
|
+
if (!requiredRange) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const range = semver.validRange(requiredRange, { loose: true });
|
|
51
|
+
if (!range) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const current = semver.coerce(currentNodeVersion);
|
|
55
|
+
if (!current) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (semver.satisfies(current, range, { includePrerelease: true })) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return `requires Node ${requiredRange}, you're on ${current.version}`;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=engines.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.readPackageJson = readPackageJson;
|
|
4
|
+
exports.detectJsonFormat = detectJsonFormat;
|
|
4
5
|
exports.readPackageJsonAsync = readPackageJsonAsync;
|
|
5
6
|
exports.collectAllDependencies = collectAllDependencies;
|
|
6
7
|
exports.collectAllDependenciesAsync = collectAllDependenciesAsync;
|
|
@@ -15,6 +16,21 @@ function readPackageJson(path) {
|
|
|
15
16
|
throw new Error(`Failed to read package.json: ${error}`);
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Detect the indentation and trailing-newline style of a raw JSON document so a
|
|
21
|
+
* re-serialized version can preserve the original formatting instead of normalizing it.
|
|
22
|
+
*
|
|
23
|
+
* The first indented line's leading whitespace is exactly one indent unit; using it verbatim
|
|
24
|
+
* as the JSON.stringify indent round-trips tabs, 2-space, and 4-space without branching on type.
|
|
25
|
+
* Minified/single-line files (no indented line) fall back to 2 spaces, matching prior behavior.
|
|
26
|
+
*/
|
|
27
|
+
function detectJsonFormat(raw) {
|
|
28
|
+
const match = raw.match(/\n([ \t]+)\S/);
|
|
29
|
+
return {
|
|
30
|
+
indent: match ? match[1] : 2,
|
|
31
|
+
trailingNewline: raw.endsWith('\n'),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
18
34
|
async function readPackageJsonAsync(path) {
|
|
19
35
|
try {
|
|
20
36
|
const content = await fs_2.promises.readFile(path, 'utf-8');
|
|
@@ -16,15 +16,85 @@ const SKIP_DIRS = new Set([
|
|
|
16
16
|
'esm',
|
|
17
17
|
'cjs',
|
|
18
18
|
]);
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Skip dirs that are ambiguous source-vs-build directories where a real package may legitimately
|
|
21
|
+
* live. Only these trigger the "silently skipped a package" warning — node_modules and build-output
|
|
22
|
+
* dirs (dist/build/coverage/out) routinely contain package.json files and would be pure noise.
|
|
23
|
+
*/
|
|
24
|
+
const WARN_SKIP_DIRS = new Set(['lib', 'es', 'esm', 'cjs']);
|
|
25
|
+
/** Effective skip set: the defaults minus any directory the caller opted back into via `scanDirs`. */
|
|
26
|
+
function buildSkipSet(scanDirs) {
|
|
27
|
+
if (!scanDirs || scanDirs.length === 0) {
|
|
28
|
+
return SKIP_DIRS;
|
|
29
|
+
}
|
|
30
|
+
const skip = new Set(SKIP_DIRS);
|
|
31
|
+
for (const dir of scanDirs) {
|
|
32
|
+
skip.delete(dir);
|
|
33
|
+
}
|
|
34
|
+
return skip;
|
|
35
|
+
}
|
|
36
|
+
function classifyDirectory(name, skipSet) {
|
|
37
|
+
if (name.startsWith('.'))
|
|
38
|
+
return 'hidden';
|
|
39
|
+
if (skipSet.has(name))
|
|
40
|
+
return 'skip-dir';
|
|
41
|
+
return null;
|
|
21
42
|
}
|
|
22
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Cheaply decide whether a pruned directory looks like it holds a real package — a package.json
|
|
45
|
+
* directly inside it, or inside any immediate child (the common `lib/<pkg>/package.json` monorepo
|
|
46
|
+
* layout). Stays shallow (depth 1) so detecting a skip doesn't re-walk the subtree we just pruned.
|
|
47
|
+
*/
|
|
48
|
+
function prunedDirHoldsPackage(dir) {
|
|
49
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(dir, 'package.json'))) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
let entries;
|
|
53
|
+
try {
|
|
54
|
+
entries = (0, fs_1.readdirSync)(dir);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (entry.startsWith('.'))
|
|
61
|
+
continue;
|
|
62
|
+
const child = (0, path_1.join)(dir, entry);
|
|
63
|
+
try {
|
|
64
|
+
if ((0, fs_1.statSync)(child).isDirectory() && (0, fs_1.existsSync)((0, path_1.join)(child, 'package.json'))) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Skip children we can't stat
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Decide whether to descend into a directory, and notify when one is pruned by the default skip
|
|
76
|
+
* list despite containing a package.json (so the caller can surface a "silently skipped" warning).
|
|
77
|
+
*/
|
|
78
|
+
function shouldTraverse(name, fullPath, relativePath, skipSet, onSkippedPackageDir) {
|
|
79
|
+
const reason = classifyDirectory(name, skipSet);
|
|
80
|
+
if (reason === null) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (reason === 'skip-dir' &&
|
|
84
|
+
WARN_SKIP_DIRS.has(name) &&
|
|
85
|
+
onSkippedPackageDir &&
|
|
86
|
+
prunedDirHoldsPackage(fullPath)) {
|
|
87
|
+
onSkippedPackageDir(relativePath);
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function findAllPackageJsonFiles(rootDir = process.cwd(), excludePatterns = [], maxDepth = 10, onProgress, options = {}) {
|
|
23
92
|
const packageJsonFiles = [];
|
|
24
93
|
const visitedPaths = new Set();
|
|
25
94
|
let directoriesScanned = 0;
|
|
26
95
|
let lastProgressAt = 0;
|
|
27
96
|
const progressIntervalMs = 250;
|
|
97
|
+
const skipSet = buildSkipSet(options.scanDirs);
|
|
28
98
|
const excludeRegexes = excludePatterns.map((pattern) => new RegExp(pattern, 'i'));
|
|
29
99
|
function shouldExcludePath(relativePath) {
|
|
30
100
|
return excludeRegexes.some((regex) => regex.test(relativePath));
|
|
@@ -72,8 +142,10 @@ function findAllPackageJsonFiles(rootDir = process.cwd(), excludePatterns = [],
|
|
|
72
142
|
// Skip files/dirs we can't stat (broken symlinks, permission issues)
|
|
73
143
|
continue;
|
|
74
144
|
}
|
|
75
|
-
if (stat.isDirectory()
|
|
76
|
-
|
|
145
|
+
if (stat.isDirectory()) {
|
|
146
|
+
if (shouldTraverse(file, fullPath, relativePath, skipSet, options.onSkippedPackageDir)) {
|
|
147
|
+
traverseDirectory(fullPath, depth + 1);
|
|
148
|
+
}
|
|
77
149
|
}
|
|
78
150
|
else if (file === 'package.json' && stat.isFile()) {
|
|
79
151
|
packageJsonFiles.push(fullPath);
|
|
@@ -94,6 +166,7 @@ async function findAllPackageJsonFilesAsync(rootDir = process.cwd(), excludePatt
|
|
|
94
166
|
let lastProgressAt = 0;
|
|
95
167
|
const progressIntervalMs = 250;
|
|
96
168
|
const concurrency = Math.max(1, Math.min(options.concurrency ?? 16, 64));
|
|
169
|
+
const skipSet = buildSkipSet(options.scanDirs);
|
|
97
170
|
const excludeRegexes = excludePatterns.map((pattern) => new RegExp(pattern, 'i'));
|
|
98
171
|
function shouldExcludePath(relativePath) {
|
|
99
172
|
return excludeRegexes.some((regex) => regex.test(relativePath));
|
|
@@ -167,8 +240,10 @@ async function findAllPackageJsonFilesAsync(rootDir = process.cwd(), excludePatt
|
|
|
167
240
|
catch {
|
|
168
241
|
continue;
|
|
169
242
|
}
|
|
170
|
-
if (stat.isDirectory()
|
|
171
|
-
|
|
243
|
+
if (stat.isDirectory()) {
|
|
244
|
+
if (shouldTraverse(file, fullPath, relativePath, skipSet, options.onSkippedPackageDir)) {
|
|
245
|
+
schedule(fullPath, depth + 1);
|
|
246
|
+
}
|
|
172
247
|
}
|
|
173
248
|
else if (file === 'package.json' && stat.isFile()) {
|
|
174
249
|
packageJsonFiles.push(fullPath);
|
package/dist/utils/index.js
CHANGED
|
@@ -23,6 +23,9 @@ __exportStar(require("./exec"), exports);
|
|
|
23
23
|
__exportStar(require("./git"), exports);
|
|
24
24
|
__exportStar(require("./version"), exports);
|
|
25
25
|
__exportStar(require("./debug-logger"), exports);
|
|
26
|
+
__exportStar(require("./color"), exports);
|
|
27
|
+
__exportStar(require("./engines"), exports);
|
|
28
|
+
__exportStar(require("./manifest"), exports);
|
|
26
29
|
// Re-export async functions for convenience
|
|
27
30
|
var filesystem_1 = require("./filesystem");
|
|
28
31
|
Object.defineProperty(exports, "readPackageJsonAsync", { enumerable: true, get: function () { return filesystem_1.readPackageJsonAsync; } });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Helpers for reading optional health signals out of an npm version manifest
|
|
4
|
+
* (the per-version object inside an abbreviated packument, or a full version
|
|
5
|
+
* document). Both fields are advisory and frequently absent.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.normalizeDeprecatedMessage = normalizeDeprecatedMessage;
|
|
9
|
+
exports.extractEnginesNode = extractEnginesNode;
|
|
10
|
+
/**
|
|
11
|
+
* npm represents deprecation as either a string message or the boolean `true`.
|
|
12
|
+
* Normalize both into a displayable message, or `undefined` when not deprecated.
|
|
13
|
+
*/
|
|
14
|
+
function normalizeDeprecatedMessage(value) {
|
|
15
|
+
if (typeof value === 'string' && value.trim()) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
if (value === true) {
|
|
19
|
+
return 'This version is deprecated.';
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extract the `engines.node` range from a manifest's `engines` object, if any.
|
|
25
|
+
*/
|
|
26
|
+
function extractEnginesNode(engines) {
|
|
27
|
+
if (typeof engines === 'object' && engines !== null) {
|
|
28
|
+
const node = engines.node;
|
|
29
|
+
if (typeof node === 'string' && node.trim()) {
|
|
30
|
+
return node;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=manifest.js.map
|
package/dist/utils/version.js
CHANGED
|
@@ -41,6 +41,7 @@ exports.isVersionOutdated = isVersionOutdated;
|
|
|
41
41
|
exports.getOptimizedRangeVersion = getOptimizedRangeVersion;
|
|
42
42
|
exports.findClosestMinorVersion = findClosestMinorVersion;
|
|
43
43
|
const semver = __importStar(require("semver"));
|
|
44
|
+
const manifest_1 = require("./manifest");
|
|
44
45
|
function extractMajorVersion(version) {
|
|
45
46
|
if (!version)
|
|
46
47
|
return null;
|
|
@@ -62,10 +63,16 @@ function versionIdentity(version) {
|
|
|
62
63
|
}
|
|
63
64
|
function parseVersions(raw) {
|
|
64
65
|
const data = JSON.parse(raw);
|
|
65
|
-
const
|
|
66
|
+
const versions = data.versions || {};
|
|
67
|
+
const allVersions = Object.keys(versions).filter((v) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(v));
|
|
66
68
|
const sortedVersions = allVersions.sort(semver.rcompare);
|
|
67
69
|
const latestVersion = sortedVersions.length > 0 ? sortedVersions[0] : 'unknown';
|
|
68
|
-
|
|
70
|
+
// Surface health signals for the latest version straight from the abbreviated
|
|
71
|
+
// packument we already fetched — no extra request. Both fields are optional.
|
|
72
|
+
const latestManifest = versions[latestVersion];
|
|
73
|
+
const deprecated = (0, manifest_1.normalizeDeprecatedMessage)(latestManifest?.deprecated);
|
|
74
|
+
const enginesNode = (0, manifest_1.extractEnginesNode)(latestManifest?.engines);
|
|
75
|
+
return { latestVersion, allVersions, deprecated, enginesNode };
|
|
69
76
|
}
|
|
70
77
|
/**
|
|
71
78
|
* Checks if a version is outdated compared to the latest version.
|