inup 1.4.12 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +39 -30
- package/dist/cli.js +29 -14
- package/dist/config/project-config.js +6 -0
- package/dist/core/package-detector.js +3 -2
- package/dist/core/upgrade-runner.js +6 -3
- package/dist/features/changelog/clients/github-client.js +134 -0
- package/dist/features/changelog/clients/npm-registry-client.js +53 -0
- package/dist/features/changelog/index.js +19 -0
- package/dist/features/changelog/parsers/changelog-parser.js +68 -0
- package/dist/features/changelog/parsers/github-release-html-parser.js +61 -0
- package/dist/features/changelog/parsers/package-metadata.js +34 -0
- package/dist/features/changelog/parsers/repository-ref.js +26 -0
- package/dist/features/changelog/services/changelog-service.js +30 -0
- package/dist/features/changelog/services/package-metadata-service.js +108 -0
- package/dist/features/changelog/services/release-notes-service.js +180 -0
- package/dist/features/changelog/types/changelog.types.js +3 -0
- package/dist/interactive-ui.js +242 -114
- package/dist/services/background-audit.js +60 -0
- package/dist/services/index.js +3 -1
- package/dist/services/vulnerability-checker.js +133 -0
- package/dist/ui/controllers/index.js +8 -0
- package/dist/ui/controllers/package-info-modal-controller.js +237 -0
- package/dist/ui/controllers/vulnerability-audit-controller.js +82 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/input-handler.js +40 -9
- package/dist/ui/modal/index.js +22 -0
- package/dist/ui/modal/layout.js +84 -0
- package/dist/ui/modal/package-info-sections.js +327 -0
- package/dist/ui/modal/package-info.js +147 -0
- package/dist/ui/modal/theme-selector.js +46 -0
- package/dist/ui/modal/types.js +3 -0
- package/dist/ui/presenters/index.js +11 -0
- package/dist/ui/presenters/vulnerability.js +76 -0
- package/dist/ui/renderer/index.js +9 -11
- package/dist/ui/renderer/package-list.js +135 -62
- package/dist/ui/state/filter-manager.js +17 -2
- package/dist/ui/state/modal-manager.js +48 -6
- package/dist/ui/state/state-manager.js +42 -7
- package/dist/ui/utils/cursor.js +18 -0
- package/dist/ui/utils/index.js +8 -1
- package/dist/ui/utils/terminal-input.js +125 -0
- package/dist/ui/utils/text.js +75 -0
- package/dist/ui/utils/version.js +3 -2
- package/dist/utils/git.js +33 -0
- package/dist/utils/index.js +1 -0
- package/package.json +22 -19
- package/dist/services/changelog-fetcher.js +0 -215
- package/dist/ui/renderer/modal.js +0 -190
- package/dist/ui/renderer/theme-selector.js +0 -83
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchVulnerabilities = fetchVulnerabilities;
|
|
4
|
+
const config_1 = require("../config");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const SEVERITY_ORDER = {
|
|
7
|
+
info: 0,
|
|
8
|
+
low: 1,
|
|
9
|
+
moderate: 2,
|
|
10
|
+
high: 3,
|
|
11
|
+
critical: 4,
|
|
12
|
+
};
|
|
13
|
+
function isKnownSeverity(severity) {
|
|
14
|
+
return severity in SEVERITY_ORDER;
|
|
15
|
+
}
|
|
16
|
+
function getHighestSeverity(vulnerabilities) {
|
|
17
|
+
if (vulnerabilities.length === 0)
|
|
18
|
+
return null;
|
|
19
|
+
let highest = 'info';
|
|
20
|
+
for (const vuln of vulnerabilities) {
|
|
21
|
+
if (SEVERITY_ORDER[vuln.severity] > SEVERITY_ORDER[highest]) {
|
|
22
|
+
highest = vuln.severity;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return highest;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Fetches security advisories for a set of packages using the npm bulk advisory API.
|
|
29
|
+
* This is the same endpoint that `npm audit` uses under the hood.
|
|
30
|
+
*
|
|
31
|
+
* POST https://registry.npmjs.org/-/npm/v1/security/advisories/bulk
|
|
32
|
+
* Body: { "package-name": ["version1"], ... }
|
|
33
|
+
* Response: { "package-name": [{ id, title, severity, url, vulnerable_versions }], ... }
|
|
34
|
+
*/
|
|
35
|
+
async function fetchVulnerabilities(packages) {
|
|
36
|
+
const results = new Map();
|
|
37
|
+
if (packages.size === 0) {
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
// Build the request body: { "package-name": ["version"], ... }
|
|
41
|
+
const body = {};
|
|
42
|
+
for (const [name, version] of packages) {
|
|
43
|
+
const cleanVersion = version.replace(/^[^0-9]*/, '');
|
|
44
|
+
if (cleanVersion) {
|
|
45
|
+
body[name] = [cleanVersion];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (Object.keys(body).length === 0) {
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
const url = `${config_1.NPM_REGISTRY_URL}/-/npm/v1/security/advisories/bulk`;
|
|
52
|
+
utils_1.debugLog.info('vulnerability-checker', `checking ${Object.keys(body).length} packages`);
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
const timeoutId = setTimeout(() => controller.abort(), config_1.REQUEST_TIMEOUT);
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: {
|
|
59
|
+
'content-type': 'application/json',
|
|
60
|
+
accept: 'application/json',
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify(body),
|
|
63
|
+
signal: controller.signal,
|
|
64
|
+
});
|
|
65
|
+
clearTimeout(timeoutId);
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
utils_1.debugLog.warn('vulnerability-checker', `API returned HTTP ${response.status}`);
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
// Response is keyed by package name, values are arrays of advisories
|
|
71
|
+
const data = (await response.json());
|
|
72
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
73
|
+
utils_1.debugLog.warn('vulnerability-checker', 'unexpected API payload shape');
|
|
74
|
+
return results;
|
|
75
|
+
}
|
|
76
|
+
for (const [packageName, advisories] of Object.entries(data)) {
|
|
77
|
+
if (!Array.isArray(advisories) || advisories.length === 0)
|
|
78
|
+
continue;
|
|
79
|
+
const seen = new Set();
|
|
80
|
+
const unique = [];
|
|
81
|
+
for (const advisory of advisories) {
|
|
82
|
+
if (!advisory ||
|
|
83
|
+
typeof advisory.id !== 'number' ||
|
|
84
|
+
typeof advisory.title !== 'string' ||
|
|
85
|
+
typeof advisory.url !== 'string' ||
|
|
86
|
+
typeof advisory.vulnerable_versions !== 'string' ||
|
|
87
|
+
typeof advisory.severity !== 'string' ||
|
|
88
|
+
!isKnownSeverity(advisory.severity)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (seen.has(advisory.id))
|
|
92
|
+
continue;
|
|
93
|
+
seen.add(advisory.id);
|
|
94
|
+
unique.push({
|
|
95
|
+
id: advisory.id,
|
|
96
|
+
title: advisory.title,
|
|
97
|
+
severity: advisory.severity,
|
|
98
|
+
url: advisory.url,
|
|
99
|
+
vulnerable_versions: advisory.vulnerable_versions,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
unique.sort((left, right) => {
|
|
103
|
+
const severityDelta = SEVERITY_ORDER[right.severity] - SEVERITY_ORDER[left.severity];
|
|
104
|
+
if (severityDelta !== 0) {
|
|
105
|
+
return severityDelta;
|
|
106
|
+
}
|
|
107
|
+
return left.id - right.id;
|
|
108
|
+
});
|
|
109
|
+
if (unique.length === 0)
|
|
110
|
+
continue;
|
|
111
|
+
results.set(packageName, {
|
|
112
|
+
packageName,
|
|
113
|
+
vulnerabilities: unique,
|
|
114
|
+
highestSeverity: getHighestSeverity(unique),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
utils_1.debugLog.info('vulnerability-checker', `found ${results.size} packages with vulnerabilities`);
|
|
118
|
+
return results;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
122
|
+
utils_1.debugLog.warn('vulnerability-checker', 'request timed out');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
utils_1.debugLog.warn('vulnerability-checker', 'failed to fetch vulnerabilities', error);
|
|
126
|
+
}
|
|
127
|
+
return results;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
clearTimeout(timeoutId);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=vulnerability-checker.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PackageInfoModalController = exports.VulnerabilityAuditController = void 0;
|
|
4
|
+
var vulnerability_audit_controller_1 = require("./vulnerability-audit-controller");
|
|
5
|
+
Object.defineProperty(exports, "VulnerabilityAuditController", { enumerable: true, get: function () { return vulnerability_audit_controller_1.VulnerabilityAuditController; } });
|
|
6
|
+
var package_info_modal_controller_1 = require("./package-info-modal-controller");
|
|
7
|
+
Object.defineProperty(exports, "PackageInfoModalController", { enumerable: true, get: function () { return package_info_modal_controller_1.PackageInfoModalController; } });
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,237 @@
|
|
|
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.PackageInfoModalController = void 0;
|
|
37
|
+
const semver = __importStar(require("semver"));
|
|
38
|
+
const services_1 = require("../../services");
|
|
39
|
+
const RELEASE_NOTES_LOAD_DEBOUNCE_MS = 120;
|
|
40
|
+
class PackageInfoModalController {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.abortController = null;
|
|
43
|
+
this.pendingReleaseNotesVersion = null;
|
|
44
|
+
this.releaseNotesDebounceTimer = null;
|
|
45
|
+
this.resolveDebouncedLoad = null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Cancel any in-flight hydrate/release-notes fetches.
|
|
49
|
+
* Safe to call multiple times or when nothing is in flight.
|
|
50
|
+
*/
|
|
51
|
+
cancel() {
|
|
52
|
+
this.abortController?.abort();
|
|
53
|
+
this.abortController = null;
|
|
54
|
+
this.pendingReleaseNotesVersion = null;
|
|
55
|
+
if (this.releaseNotesDebounceTimer) {
|
|
56
|
+
clearTimeout(this.releaseNotesDebounceTimer);
|
|
57
|
+
this.releaseNotesDebounceTimer = null;
|
|
58
|
+
}
|
|
59
|
+
this.resolveDebouncedLoad?.(false);
|
|
60
|
+
this.resolveDebouncedLoad = null;
|
|
61
|
+
}
|
|
62
|
+
async hydrate(state) {
|
|
63
|
+
// Abort any previous session
|
|
64
|
+
this.cancel();
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
this.abortController = controller;
|
|
67
|
+
const metadata = await services_1.changelogFetcher.fetchPackageMetadata(state.name, state.latestVersion, controller.signal);
|
|
68
|
+
if (!metadata) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const result = {
|
|
72
|
+
description: metadata.description,
|
|
73
|
+
homepage: metadata.homepage,
|
|
74
|
+
repository: metadata.releaseNotes,
|
|
75
|
+
weeklyDownloads: metadata.weeklyDownloads,
|
|
76
|
+
author: metadata.author,
|
|
77
|
+
license: metadata.license,
|
|
78
|
+
};
|
|
79
|
+
state.description = result.description;
|
|
80
|
+
state.homepage = result.homepage;
|
|
81
|
+
state.repository = result.repository;
|
|
82
|
+
state.weeklyDownloads = result.weeklyDownloads;
|
|
83
|
+
state.author = result.author;
|
|
84
|
+
state.license = result.license;
|
|
85
|
+
// Compute the version range for release notes
|
|
86
|
+
const targetVersion = state.selectedOption === 'range' ? state.rangeVersion : state.latestVersion;
|
|
87
|
+
if (state.allVersions && state.allVersions.length > 0) {
|
|
88
|
+
state.releaseNotesVersions = this.buildReleaseNotesVersionQueue(state.allVersions, state.currentVersion, targetVersion);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// No allVersions available — just show the target version
|
|
92
|
+
state.releaseNotesVersions = [targetVersion];
|
|
93
|
+
}
|
|
94
|
+
state.releaseNotesLoaded = new Map();
|
|
95
|
+
state.releaseNotesViewIndex = 0;
|
|
96
|
+
state.releaseNotesLoadingVersion = undefined;
|
|
97
|
+
this.pendingReleaseNotesVersion = null;
|
|
98
|
+
if (this.releaseNotesDebounceTimer) {
|
|
99
|
+
clearTimeout(this.releaseNotesDebounceTimer);
|
|
100
|
+
this.releaseNotesDebounceTimer = null;
|
|
101
|
+
}
|
|
102
|
+
this.resolveDebouncedLoad?.(false);
|
|
103
|
+
this.resolveDebouncedLoad = null;
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Load release notes for a specific version by index.
|
|
108
|
+
* Returns true if a load was triggered, false if nothing to load.
|
|
109
|
+
*/
|
|
110
|
+
async loadVersionAtIndex(state, index, onLoaded) {
|
|
111
|
+
if (!state.releaseNotesVersions || !state.releaseNotesLoaded)
|
|
112
|
+
return false;
|
|
113
|
+
if (index < 0 || index >= state.releaseNotesVersions.length)
|
|
114
|
+
return false;
|
|
115
|
+
const version = state.releaseNotesVersions[index];
|
|
116
|
+
// Already loaded
|
|
117
|
+
if (state.releaseNotesLoaded.has(version))
|
|
118
|
+
return false;
|
|
119
|
+
if (state.releaseNotesLoadingVersion) {
|
|
120
|
+
this.pendingReleaseNotesVersion = version;
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return await this.scheduleVersionLoad(state, version, onLoaded);
|
|
124
|
+
}
|
|
125
|
+
scheduleVersionLoad(state, version, onLoaded) {
|
|
126
|
+
if (this.releaseNotesDebounceTimer) {
|
|
127
|
+
clearTimeout(this.releaseNotesDebounceTimer);
|
|
128
|
+
this.releaseNotesDebounceTimer = null;
|
|
129
|
+
}
|
|
130
|
+
this.resolveDebouncedLoad?.(false);
|
|
131
|
+
this.pendingReleaseNotesVersion = version;
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
this.resolveDebouncedLoad = resolve;
|
|
134
|
+
this.releaseNotesDebounceTimer = setTimeout(() => {
|
|
135
|
+
this.releaseNotesDebounceTimer = null;
|
|
136
|
+
this.resolveDebouncedLoad = null;
|
|
137
|
+
const nextVersion = this.pendingReleaseNotesVersion;
|
|
138
|
+
this.pendingReleaseNotesVersion = null;
|
|
139
|
+
if (!nextVersion || state.releaseNotesLoadingVersion) {
|
|
140
|
+
resolve(false);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
void this.loadVersion(state, nextVersion, onLoaded).then(resolve);
|
|
144
|
+
}, RELEASE_NOTES_LOAD_DEBOUNCE_MS);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
async loadVersion(state, version, onLoaded) {
|
|
148
|
+
const loadedNotes = state.releaseNotesLoaded;
|
|
149
|
+
if (!loadedNotes)
|
|
150
|
+
return false;
|
|
151
|
+
state.releaseNotesLoadingVersion = version;
|
|
152
|
+
onLoaded(); // Re-render to show loading indicator
|
|
153
|
+
try {
|
|
154
|
+
const notes = await services_1.changelogFetcher.fetchReleaseNotesForVersion(state.name, version, this.abortController?.signal);
|
|
155
|
+
loadedNotes.set(version, notes);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
loadedNotes.set(version, null);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
state.releaseNotesLoadingVersion = undefined;
|
|
167
|
+
onLoaded(); // Re-render with new content or recovered state
|
|
168
|
+
const pendingVersion = this.pendingReleaseNotesVersion;
|
|
169
|
+
this.pendingReleaseNotesVersion = null;
|
|
170
|
+
if (pendingVersion &&
|
|
171
|
+
pendingVersion !== version &&
|
|
172
|
+
loadedNotes &&
|
|
173
|
+
!loadedNotes.has(pendingVersion)) {
|
|
174
|
+
void this.loadVersion(state, pendingVersion, onLoaded);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Navigate to the next or previous version in the release notes list.
|
|
180
|
+
* Returns the new view index, or -1 if navigation is not possible.
|
|
181
|
+
*/
|
|
182
|
+
navigateVersion(state, direction) {
|
|
183
|
+
if (!state.releaseNotesVersions || state.releaseNotesVersions.length === 0)
|
|
184
|
+
return -1;
|
|
185
|
+
const currentIndex = state.releaseNotesViewIndex ?? 0;
|
|
186
|
+
const newIndex = direction === 'older' ? currentIndex + 1 : currentIndex - 1;
|
|
187
|
+
if (newIndex < 0 || newIndex >= state.releaseNotesVersions.length)
|
|
188
|
+
return -1;
|
|
189
|
+
state.releaseNotesViewIndex = newIndex;
|
|
190
|
+
return newIndex;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Check if the version at the given index is already loaded.
|
|
194
|
+
*/
|
|
195
|
+
isVersionLoaded(state, index) {
|
|
196
|
+
if (!state.releaseNotesVersions || !state.releaseNotesLoaded)
|
|
197
|
+
return false;
|
|
198
|
+
if (index < 0 || index >= state.releaseNotesVersions.length)
|
|
199
|
+
return false;
|
|
200
|
+
return state.releaseNotesLoaded.has(state.releaseNotesVersions[index]);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get the total number of versions available.
|
|
204
|
+
*/
|
|
205
|
+
getVersionCount(state) {
|
|
206
|
+
return state.releaseNotesVersions?.length ?? 0;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Check if navigation in a direction is possible.
|
|
210
|
+
*/
|
|
211
|
+
canNavigate(state, direction) {
|
|
212
|
+
if (!state.releaseNotesVersions || state.releaseNotesVersions.length === 0)
|
|
213
|
+
return false;
|
|
214
|
+
const currentIndex = state.releaseNotesViewIndex ?? 0;
|
|
215
|
+
if (direction === 'newer')
|
|
216
|
+
return currentIndex > 0;
|
|
217
|
+
return currentIndex < state.releaseNotesVersions.length - 1;
|
|
218
|
+
}
|
|
219
|
+
buildReleaseNotesVersionQueue(allVersions, currentVersion, targetVersion) {
|
|
220
|
+
const cleanTarget = semver.clean(targetVersion);
|
|
221
|
+
if (!cleanTarget) {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
const cleanCurrent = semver.clean(currentVersion);
|
|
225
|
+
const versionsAtOrBelowTarget = Array.from(new Set(allVersions
|
|
226
|
+
.map((version) => semver.clean(version))
|
|
227
|
+
.filter((version) => version !== null)
|
|
228
|
+
.filter((version) => semver.lte(version, cleanTarget)))).sort(semver.rcompare);
|
|
229
|
+
if (!cleanCurrent) {
|
|
230
|
+
return versionsAtOrBelowTarget;
|
|
231
|
+
}
|
|
232
|
+
const relevantVersions = versionsAtOrBelowTarget.filter((version) => semver.gte(version, cleanCurrent));
|
|
233
|
+
return relevantVersions;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
exports.PackageInfoModalController = PackageInfoModalController;
|
|
237
|
+
//# sourceMappingURL=package-info-modal-controller.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VulnerabilityAuditController = void 0;
|
|
4
|
+
const services_1 = require("../../services");
|
|
5
|
+
const vulnerability_1 = require("../presenters/vulnerability");
|
|
6
|
+
class VulnerabilityAuditController {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.tracker = new services_1.BackgroundAuditTracker();
|
|
9
|
+
this.cache = new Map();
|
|
10
|
+
this.drainPromise = null;
|
|
11
|
+
}
|
|
12
|
+
getCachedSummary(packageName, currentVersionSpecifier, type) {
|
|
13
|
+
return this.cache.get(this.getCacheKey(packageName, currentVersionSpecifier, type));
|
|
14
|
+
}
|
|
15
|
+
getProgress() {
|
|
16
|
+
return this.tracker.getProgress();
|
|
17
|
+
}
|
|
18
|
+
enqueueStates(selectionStates, onUpdate) {
|
|
19
|
+
const packages = selectionStates.map((state) => ({
|
|
20
|
+
name: state.name,
|
|
21
|
+
version: state.currentVersionSpecifier,
|
|
22
|
+
}));
|
|
23
|
+
const added = this.tracker.enqueue(packages);
|
|
24
|
+
if (added > 0) {
|
|
25
|
+
this.drain(selectionStates, onUpdate);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
drain(selectionStates, onUpdate) {
|
|
29
|
+
if (this.drainPromise) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.drainPromise = (async () => {
|
|
33
|
+
while (true) {
|
|
34
|
+
const batch = this.tracker.reserveNextBatch(20);
|
|
35
|
+
if (batch.packageNames.length === 0) {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const vulnerabilityData = await (0, services_1.fetchVulnerabilities)(batch.packages);
|
|
40
|
+
const batchNames = new Set(batch.packageNames);
|
|
41
|
+
for (const state of selectionStates) {
|
|
42
|
+
if (!batchNames.has(state.name)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const vulnerability = vulnerabilityData.get(state.name);
|
|
46
|
+
if (!vulnerability ||
|
|
47
|
+
vulnerability.vulnerabilities.length === 0 ||
|
|
48
|
+
!vulnerability.highestSeverity) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const summary = (0, vulnerability_1.createVulnerabilitySummary)(state.vulnerability, vulnerability.vulnerabilities.map((item) => ({
|
|
52
|
+
id: item.id,
|
|
53
|
+
title: item.title,
|
|
54
|
+
severity: item.severity,
|
|
55
|
+
url: item.url,
|
|
56
|
+
})), vulnerability.highestSeverity);
|
|
57
|
+
const merged = (0, vulnerability_1.mergeVulnerabilitySummary)(state.vulnerability, summary);
|
|
58
|
+
state.vulnerability = merged;
|
|
59
|
+
this.cache.set(this.getCacheKey(state.name, state.currentVersionSpecifier, state.type), merged);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Best-effort enrichment only. Keep the UI responsive on audit failures.
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
this.tracker.markCompleted(batch.packageNames);
|
|
67
|
+
onUpdate?.();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
})().finally(() => {
|
|
71
|
+
this.drainPromise = null;
|
|
72
|
+
if (this.tracker.getProgress().isRunning) {
|
|
73
|
+
this.drain(selectionStates, onUpdate);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
getCacheKey(packageName, currentVersionSpecifier, type) {
|
|
78
|
+
return `${packageName}@${currentVersionSpecifier}@${type}`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.VulnerabilityAuditController = VulnerabilityAuditController;
|
|
82
|
+
//# sourceMappingURL=vulnerability-audit-controller.js.map
|
package/dist/ui/index.js
CHANGED
|
@@ -17,5 +17,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./utils"), exports);
|
|
18
18
|
__exportStar(require("./state"), exports);
|
|
19
19
|
__exportStar(require("./renderer"), exports);
|
|
20
|
+
__exportStar(require("./modal"), exports);
|
|
21
|
+
__exportStar(require("./presenters"), exports);
|
|
22
|
+
__exportStar(require("./controllers"), exports);
|
|
20
23
|
__exportStar(require("./input-handler"), exports);
|
|
21
24
|
//# sourceMappingURL=index.js.map
|
package/dist/ui/input-handler.js
CHANGED
|
@@ -47,8 +47,37 @@ class InputHandler {
|
|
|
47
47
|
}
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
+
// Handle info modal input (scroll and close)
|
|
51
|
+
if (uiState.showInfoModal) {
|
|
52
|
+
if (key) {
|
|
53
|
+
switch (key.name) {
|
|
54
|
+
case 'escape':
|
|
55
|
+
this.onAction({ type: 'toggle_info_modal' });
|
|
56
|
+
return;
|
|
57
|
+
case 'i':
|
|
58
|
+
case 'I':
|
|
59
|
+
this.onAction({ type: 'toggle_info_modal' });
|
|
60
|
+
return;
|
|
61
|
+
case 'up':
|
|
62
|
+
this.onAction({ type: 'scroll_info_modal_up' });
|
|
63
|
+
return;
|
|
64
|
+
case 'down':
|
|
65
|
+
this.onAction({ type: 'scroll_info_modal_down' });
|
|
66
|
+
return;
|
|
67
|
+
case 'left':
|
|
68
|
+
this.onAction({ type: 'navigate_info_modal_version', direction: 'newer' });
|
|
69
|
+
return;
|
|
70
|
+
case 'right':
|
|
71
|
+
this.onAction({ type: 'navigate_info_modal_version', direction: 'older' });
|
|
72
|
+
return;
|
|
73
|
+
default:
|
|
74
|
+
return; // Consume all other keys while modal is open
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
50
79
|
// Check for '/' character to handle filter mode (only when not in modal)
|
|
51
|
-
if (str === '/'
|
|
80
|
+
if (str === '/') {
|
|
52
81
|
if (uiState.filterMode) {
|
|
53
82
|
// Apply search (exit filter mode but keep the filter)
|
|
54
83
|
this.onAction({ type: 'exit_filter_mode' });
|
|
@@ -146,19 +175,19 @@ class InputHandler {
|
|
|
146
175
|
break;
|
|
147
176
|
case 'd':
|
|
148
177
|
case 'D':
|
|
149
|
-
if (!uiState.
|
|
178
|
+
if (!uiState.showThemeModal && !uiState.filterMode) {
|
|
150
179
|
this.onAction({ type: 'toggle_dep_type_filter', depType: 'devDependencies' });
|
|
151
180
|
}
|
|
152
181
|
break;
|
|
153
182
|
case 'p':
|
|
154
183
|
case 'P':
|
|
155
|
-
if (!uiState.
|
|
184
|
+
if (!uiState.showThemeModal && !uiState.filterMode) {
|
|
156
185
|
this.onAction({ type: 'toggle_dep_type_filter', depType: 'peerDependencies' });
|
|
157
186
|
}
|
|
158
187
|
break;
|
|
159
188
|
case 'o':
|
|
160
189
|
case 'O':
|
|
161
|
-
if (!uiState.
|
|
190
|
+
if (!uiState.showThemeModal && !uiState.filterMode) {
|
|
162
191
|
this.onAction({ type: 'toggle_dep_type_filter', depType: 'optionalDependencies' });
|
|
163
192
|
}
|
|
164
193
|
break;
|
|
@@ -166,16 +195,18 @@ class InputHandler {
|
|
|
166
195
|
case 'I':
|
|
167
196
|
this.onAction({ type: 'toggle_info_modal' });
|
|
168
197
|
break;
|
|
198
|
+
case 's':
|
|
199
|
+
case 'S':
|
|
200
|
+
if (!uiState.showThemeModal) {
|
|
201
|
+
this.onAction({ type: 'trigger_audit_scan' });
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
169
204
|
case 't':
|
|
170
205
|
case 'T':
|
|
171
206
|
this.onAction({ type: 'toggle_theme_modal' });
|
|
172
207
|
break;
|
|
173
208
|
case 'escape':
|
|
174
|
-
|
|
175
|
-
if (uiState.showInfoModal) {
|
|
176
|
-
this.onAction({ type: 'toggle_info_modal' });
|
|
177
|
-
}
|
|
178
|
-
else if (uiState.filterQuery) {
|
|
209
|
+
if (uiState.filterQuery) {
|
|
179
210
|
// Clear filter if one is applied
|
|
180
211
|
this.onAction({ type: 'exit_filter_mode', clearQuery: true });
|
|
181
212
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./layout"), exports);
|
|
19
|
+
__exportStar(require("./package-info"), exports);
|
|
20
|
+
__exportStar(require("./package-info-sections"), exports);
|
|
21
|
+
__exportStar(require("./theme-selector"), exports);
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,84 @@
|
|
|
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.getModalWidth = getModalWidth;
|
|
7
|
+
exports.renderModalRow = renderModalRow;
|
|
8
|
+
exports.renderModalSeparator = renderModalSeparator;
|
|
9
|
+
exports.fitModalSections = fitModalSections;
|
|
10
|
+
exports.getModalSectionRowCount = getModalSectionRowCount;
|
|
11
|
+
exports.getModalFrameHeight = getModalFrameHeight;
|
|
12
|
+
exports.renderModalFrame = renderModalFrame;
|
|
13
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
14
|
+
const utils_1 = require("../utils");
|
|
15
|
+
function getModalWidth(terminalWidth, minWidth, maxWidth) {
|
|
16
|
+
return Math.min(Math.max(minWidth, terminalWidth - 6), maxWidth);
|
|
17
|
+
}
|
|
18
|
+
function renderModalRow(padding, modalWidth, text) {
|
|
19
|
+
const rowLength = (0, utils_1.getVisualLength)(text);
|
|
20
|
+
const rowPadding = Math.max(0, modalWidth - 4 - rowLength);
|
|
21
|
+
return (' '.repeat(padding) +
|
|
22
|
+
chalk_1.default.gray('│') +
|
|
23
|
+
' ' +
|
|
24
|
+
text +
|
|
25
|
+
' '.repeat(rowPadding) +
|
|
26
|
+
' ' +
|
|
27
|
+
chalk_1.default.gray('│'));
|
|
28
|
+
}
|
|
29
|
+
function renderModalSeparator(padding, modalWidth) {
|
|
30
|
+
return ' '.repeat(padding) + chalk_1.default.gray('├' + '─'.repeat(modalWidth - 2) + '┤');
|
|
31
|
+
}
|
|
32
|
+
function fitModalSections(sections, maxHeight, trimOrder) {
|
|
33
|
+
const activeSections = sections.map((section) => ({ ...section, rows: [...section.rows] }));
|
|
34
|
+
while (getModalFrameHeight(activeSections) > maxHeight) {
|
|
35
|
+
let trimmed = false;
|
|
36
|
+
for (const key of trimOrder) {
|
|
37
|
+
const section = activeSections.find((candidate) => candidate.key === key && candidate.rows.length > 0 && !candidate.required);
|
|
38
|
+
if (!section) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (section.rows.length === 1) {
|
|
42
|
+
section.rows = [];
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
section.rows = section.rows.slice(0, -1);
|
|
46
|
+
}
|
|
47
|
+
trimmed = true;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (!trimmed) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return activeSections.filter((section) => section.rows.length > 0);
|
|
55
|
+
}
|
|
56
|
+
function getModalSectionRowCount(sections) {
|
|
57
|
+
const visible = sections.filter((section) => section.rows.length > 0);
|
|
58
|
+
return visible.reduce((sum, section, index) => sum + section.rows.length + (index > 0 ? 1 : 0), 0);
|
|
59
|
+
}
|
|
60
|
+
function getModalFrameHeight(sections) {
|
|
61
|
+
return 2 + getModalSectionRowCount(sections);
|
|
62
|
+
}
|
|
63
|
+
function renderModalFrame(sections, options) {
|
|
64
|
+
const modalWidth = getModalWidth(options.terminalWidth, options.minWidth, options.maxWidth);
|
|
65
|
+
const padding = Math.floor((options.terminalWidth - modalWidth) / 2);
|
|
66
|
+
const contentHeight = getModalFrameHeight(sections);
|
|
67
|
+
const topPadding = Math.max(0, Math.floor((options.terminalHeight - contentHeight) / 2));
|
|
68
|
+
const lines = [];
|
|
69
|
+
for (let i = 0; i < topPadding; i++) {
|
|
70
|
+
lines.push('');
|
|
71
|
+
}
|
|
72
|
+
lines.push(' '.repeat(padding) + chalk_1.default.gray('╭' + '─'.repeat(modalWidth - 2) + '╮'));
|
|
73
|
+
sections.forEach((section, index) => {
|
|
74
|
+
if (index > 0) {
|
|
75
|
+
lines.push(renderModalSeparator(padding, modalWidth));
|
|
76
|
+
}
|
|
77
|
+
section.rows.forEach((row) => {
|
|
78
|
+
lines.push(renderModalRow(padding, modalWidth, row));
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
lines.push(' '.repeat(padding) + chalk_1.default.gray('╰' + '─'.repeat(modalWidth - 2) + '╯'));
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=layout.js.map
|