inup 1.4.6 → 1.4.8
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/dist/cli.js +6 -0
- package/dist/config/constants.js +4 -1
- package/dist/core/package-detector.js +43 -1
- package/dist/core/upgrader.js +9 -9
- package/dist/interactive-ui.js +3 -2
- package/dist/services/jsdelivr-registry.js +348 -115
- package/dist/ui/state/navigation-manager.js +2 -2
- package/dist/ui/state/state-manager.js +6 -4
- package/dist/utils/debug-logger.js +81 -0
- package/dist/utils/index.js +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ const path_1 = require("path");
|
|
|
11
11
|
const index_1 = require("./index");
|
|
12
12
|
const services_1 = require("./services");
|
|
13
13
|
const config_1 = require("./config");
|
|
14
|
+
const utils_1 = require("./utils");
|
|
14
15
|
const packageJson = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../package.json'), 'utf-8'));
|
|
15
16
|
const program = new commander_1.Command();
|
|
16
17
|
program
|
|
@@ -21,11 +22,15 @@ program
|
|
|
21
22
|
.option('-e, --exclude <patterns>', 'exclude paths matching regex patterns (comma-separated)', '')
|
|
22
23
|
.option('-i, --ignore <packages>', 'ignore packages (comma-separated, supports glob patterns like @babel/*)')
|
|
23
24
|
.option('--package-manager <name>', 'manually specify package manager (npm, yarn, pnpm, bun)')
|
|
25
|
+
.option('--debug', 'write verbose debug log to /tmp/inup-debug-YYYY-MM-DD.log')
|
|
24
26
|
.action(async (options) => {
|
|
25
27
|
console.log(chalk_1.default.bold.blue(`🚀 `) + chalk_1.default.bold.red(`i`) + chalk_1.default.bold.yellow(`n`) + chalk_1.default.bold.blue(`u`) + chalk_1.default.bold.magenta(`p`) + `\n`);
|
|
26
28
|
// Check for updates in the background (non-blocking)
|
|
27
29
|
const updateCheckPromise = (0, services_1.checkForUpdateAsync)('inup', packageJson.version);
|
|
28
30
|
const cwd = (0, path_1.resolve)(options.dir);
|
|
31
|
+
if (options.debug || process.env.INUP_DEBUG === '1') {
|
|
32
|
+
(0, utils_1.enableDebugLogging)();
|
|
33
|
+
}
|
|
29
34
|
// Load project config from .inuprc
|
|
30
35
|
const projectConfig = (0, config_1.loadProjectConfig)(cwd);
|
|
31
36
|
// Merge CLI exclude patterns with config
|
|
@@ -60,6 +65,7 @@ program
|
|
|
60
65
|
excludePatterns,
|
|
61
66
|
ignorePackages,
|
|
62
67
|
packageManager,
|
|
68
|
+
debug: options.debug || process.env.INUP_DEBUG === '1',
|
|
63
69
|
});
|
|
64
70
|
await upgrader.run();
|
|
65
71
|
// After the main flow completes, check if there's an update available
|
package/dist/config/constants.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_REGISTRY = exports.REQUEST_TIMEOUT = exports.CACHE_TTL = exports.MAX_CONCURRENT_REQUESTS = exports.JSDELIVR_CDN_URL = exports.NPM_REGISTRY_URL = exports.PACKAGE_NAME = void 0;
|
|
3
|
+
exports.DEFAULT_REGISTRY = exports.JSDELIVR_POOL_TIMEOUT = exports.JSDELIVR_RETRY_DELAYS = exports.JSDELIVR_RETRY_TIMEOUTS = exports.REQUEST_TIMEOUT = exports.CACHE_TTL = exports.MAX_CONCURRENT_REQUESTS = exports.JSDELIVR_CDN_URL = exports.NPM_REGISTRY_URL = exports.PACKAGE_NAME = void 0;
|
|
4
4
|
exports.PACKAGE_NAME = 'inup';
|
|
5
5
|
exports.NPM_REGISTRY_URL = 'https://registry.npmjs.org';
|
|
6
6
|
exports.JSDELIVR_CDN_URL = 'https://cdn.jsdelivr.net/npm';
|
|
7
7
|
exports.MAX_CONCURRENT_REQUESTS = 150;
|
|
8
8
|
exports.CACHE_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
9
9
|
exports.REQUEST_TIMEOUT = 60000; // 60 seconds in milliseconds
|
|
10
|
+
exports.JSDELIVR_RETRY_TIMEOUTS = [2000, 3500]; // short retry budget to keep fallback fast
|
|
11
|
+
exports.JSDELIVR_RETRY_DELAYS = [150]; // tiny backoff between jsDelivr retries in ms
|
|
12
|
+
exports.JSDELIVR_POOL_TIMEOUT = 60000; // keep-alive/connect lifecycle should be looser than per-request timeouts
|
|
10
13
|
exports.DEFAULT_REGISTRY = 'jsdelivr';
|
|
11
14
|
//# sourceMappingURL=constants.js.map
|
|
@@ -39,6 +39,7 @@ const utils_1 = require("../utils");
|
|
|
39
39
|
const services_1 = require("../services");
|
|
40
40
|
const config_1 = require("../config");
|
|
41
41
|
const utils_2 = require("../ui/utils");
|
|
42
|
+
const utils_3 = require("../utils");
|
|
42
43
|
class PackageDetector {
|
|
43
44
|
constructor(options) {
|
|
44
45
|
this.packageJsonPath = null;
|
|
@@ -59,27 +60,46 @@ class PackageDetector {
|
|
|
59
60
|
throw new Error('No package.json found in current directory');
|
|
60
61
|
}
|
|
61
62
|
const packages = [];
|
|
63
|
+
const t0 = Date.now();
|
|
64
|
+
utils_3.debugLog.info('PackageDetector', `Starting scan in ${this.cwd}`);
|
|
62
65
|
// Always check all package.json files recursively with timeout protection
|
|
63
66
|
this.showProgress('🔍 Scanning repository for package.json files...');
|
|
67
|
+
const tScan = Date.now();
|
|
64
68
|
const allPackageJsonFiles = this.findPackageJsonFilesWithTimeout(30000); // 30 second timeout
|
|
69
|
+
utils_3.debugLog.perf('PackageDetector', `file scan (${allPackageJsonFiles.length} files)`, tScan, {
|
|
70
|
+
files: allPackageJsonFiles,
|
|
71
|
+
});
|
|
65
72
|
this.showProgress(`🔍 Found ${allPackageJsonFiles.length} package.json file${allPackageJsonFiles.length === 1 ? '' : 's'}`);
|
|
66
73
|
// Step 2: Collect all dependencies from package.json files (parallelized)
|
|
67
74
|
this.showProgress('🔍 Reading dependencies from package.json files...');
|
|
75
|
+
const tDeps = Date.now();
|
|
68
76
|
const allDepsRaw = await (0, utils_1.collectAllDependenciesAsync)(allPackageJsonFiles, {
|
|
69
77
|
includePeerDeps: true,
|
|
70
78
|
includeOptionalDeps: true,
|
|
71
79
|
});
|
|
80
|
+
utils_3.debugLog.perf('PackageDetector', `dependency collection (${allDepsRaw.length} raw deps)`, tDeps);
|
|
72
81
|
// Step 3: Get unique package names while filtering out workspace references and ignored packages
|
|
73
82
|
this.showProgress('🔍 Identifying unique packages...');
|
|
74
83
|
const uniquePackageNames = new Set();
|
|
75
84
|
const allDeps = [];
|
|
76
85
|
let ignoredCount = 0;
|
|
86
|
+
const seenWorkspaceRefs = new Set();
|
|
87
|
+
const seenIgnored = new Set();
|
|
77
88
|
for (const dep of allDepsRaw) {
|
|
78
89
|
if (this.isWorkspaceReference(dep.version)) {
|
|
90
|
+
const key = `${dep.name}@${dep.version}`;
|
|
91
|
+
if (!seenWorkspaceRefs.has(key)) {
|
|
92
|
+
seenWorkspaceRefs.add(key);
|
|
93
|
+
utils_3.debugLog.info('PackageDetector', `skipping workspace ref: ${key}`);
|
|
94
|
+
}
|
|
79
95
|
continue;
|
|
80
96
|
}
|
|
81
97
|
if (this.ignorePackages.length > 0 && (0, config_1.isPackageIgnored)(dep.name, this.ignorePackages)) {
|
|
82
98
|
ignoredCount++;
|
|
99
|
+
if (!seenIgnored.has(dep.name)) {
|
|
100
|
+
seenIgnored.add(dep.name);
|
|
101
|
+
utils_3.debugLog.info('PackageDetector', `ignoring package: ${dep.name}`);
|
|
102
|
+
}
|
|
83
103
|
continue;
|
|
84
104
|
}
|
|
85
105
|
allDeps.push(dep);
|
|
@@ -89,6 +109,7 @@ class PackageDetector {
|
|
|
89
109
|
this.showProgress(`🔍 Skipped ${ignoredCount} ignored package(s)`);
|
|
90
110
|
}
|
|
91
111
|
const packageNames = Array.from(uniquePackageNames);
|
|
112
|
+
utils_3.debugLog.info('PackageDetector', `${packageNames.length} unique packages to check, ${ignoredCount} ignored`);
|
|
92
113
|
// Step 4: Fetch all package data in one call per package
|
|
93
114
|
// Create a map of package names to their current versions for major version optimization
|
|
94
115
|
const currentVersions = new Map();
|
|
@@ -98,6 +119,8 @@ class PackageDetector {
|
|
|
98
119
|
currentVersions.set(dep.name, dep.version);
|
|
99
120
|
}
|
|
100
121
|
}
|
|
122
|
+
const tFetch = Date.now();
|
|
123
|
+
utils_3.debugLog.info('PackageDetector', `fetching version data via ${config_1.DEFAULT_REGISTRY}`);
|
|
101
124
|
const allPackageData = config_1.DEFAULT_REGISTRY === 'jsdelivr'
|
|
102
125
|
? await (0, services_1.getAllPackageDataFromJsdelivr)(packageNames, currentVersions, (_currentPackage, completed, total) => {
|
|
103
126
|
this.showProgress(`🌐 Checking versions... (${completed}/${total} packages)`);
|
|
@@ -105,12 +128,20 @@ class PackageDetector {
|
|
|
105
128
|
: await (0, services_1.getAllPackageData)(packageNames, (_currentPackage, completed, total) => {
|
|
106
129
|
this.showProgress(`🌐 Checking versions... (${completed}/${total} packages)`);
|
|
107
130
|
});
|
|
131
|
+
utils_3.debugLog.perf('PackageDetector', `registry fetch (${allPackageData.size}/${packageNames.length} resolved)`, tFetch);
|
|
132
|
+
const loggedOutdated = new Set();
|
|
133
|
+
const loggedNoData = new Set();
|
|
108
134
|
try {
|
|
109
135
|
for (const dep of allDeps) {
|
|
110
136
|
try {
|
|
111
137
|
const packageData = allPackageData.get(dep.name);
|
|
112
|
-
if (!packageData)
|
|
138
|
+
if (!packageData) {
|
|
139
|
+
if (!loggedNoData.has(dep.name)) {
|
|
140
|
+
loggedNoData.add(dep.name);
|
|
141
|
+
utils_3.debugLog.warn('PackageDetector', `no data returned for ${dep.name} — skipping`);
|
|
142
|
+
}
|
|
113
143
|
continue;
|
|
144
|
+
}
|
|
114
145
|
const { latestVersion, allVersions } = packageData;
|
|
115
146
|
// Find closest minor version (same major, higher minor) that satisfies the current range
|
|
116
147
|
// Falls back to patch updates if no minor updates are available
|
|
@@ -123,6 +154,13 @@ class PackageDetector {
|
|
|
123
154
|
const hasRangeUpdate = minorClean !== null && minorClean !== installedClean;
|
|
124
155
|
const hasMajorUpdate = semver.major(latestClean) > semver.major(installedClean);
|
|
125
156
|
const isOutdated = hasRangeUpdate || hasMajorUpdate;
|
|
157
|
+
if (isOutdated) {
|
|
158
|
+
const outdatedKey = `${dep.name}@${dep.version}`;
|
|
159
|
+
if (!loggedOutdated.has(outdatedKey)) {
|
|
160
|
+
loggedOutdated.add(outdatedKey);
|
|
161
|
+
utils_3.debugLog.info('PackageDetector', `outdated: ${dep.name} ${dep.version} → range:${closestMinorVersion ?? '-'} latest:${latestVersion}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
126
164
|
packages.push({
|
|
127
165
|
name: dep.name,
|
|
128
166
|
currentVersion: dep.version, // Keep original version specifier with prefix
|
|
@@ -136,6 +174,7 @@ class PackageDetector {
|
|
|
136
174
|
});
|
|
137
175
|
}
|
|
138
176
|
catch (error) {
|
|
177
|
+
utils_3.debugLog.error('PackageDetector', `error processing ${dep.name}`, error);
|
|
139
178
|
// Skip packages that can't be checked (private packages, etc.)
|
|
140
179
|
packages.push({
|
|
141
180
|
name: dep.name,
|
|
@@ -150,10 +189,13 @@ class PackageDetector {
|
|
|
150
189
|
});
|
|
151
190
|
}
|
|
152
191
|
}
|
|
192
|
+
const outdatedCount = packages.filter((p) => p.isOutdated).length;
|
|
193
|
+
utils_3.debugLog.perf('PackageDetector', `total scan complete (${outdatedCount} outdated of ${packages.length} deps)`, t0);
|
|
153
194
|
return packages;
|
|
154
195
|
}
|
|
155
196
|
catch (error) {
|
|
156
197
|
this.showProgress('❌ Failed to check packages\n');
|
|
198
|
+
utils_3.debugLog.error('PackageDetector', 'fatal error during package check', error);
|
|
157
199
|
throw error;
|
|
158
200
|
}
|
|
159
201
|
}
|
package/dist/core/upgrader.js
CHANGED
|
@@ -8,6 +8,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
8
8
|
const nanospinner_1 = require("nanospinner");
|
|
9
9
|
const fs_1 = require("fs");
|
|
10
10
|
const path_1 = require("path");
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
11
12
|
const utils_1 = require("../utils");
|
|
12
13
|
class PackageUpgrader {
|
|
13
14
|
constructor(packageManager) {
|
|
@@ -54,15 +55,14 @@ class PackageUpgrader {
|
|
|
54
55
|
` ${this.packageManager.installCommand}\n`));
|
|
55
56
|
return; // Skip install, let user do it manually
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
throw error;
|
|
58
|
+
console.log(chalk_1.default.cyan(`\n📦 Running ${this.packageManager.installCommand}...\n`));
|
|
59
|
+
const [cmd, ...args] = this.packageManager.installCommand.split(' ');
|
|
60
|
+
const result = (0, child_process_1.spawnSync)(cmd, args, {
|
|
61
|
+
cwd: installDir,
|
|
62
|
+
stdio: 'inherit',
|
|
63
|
+
});
|
|
64
|
+
if (result.status !== 0) {
|
|
65
|
+
throw new Error(`${this.packageManager.installCommand} exited with code ${result.status}`);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
groupChoicesByFileAndType(choices, packageInfos) {
|
package/dist/interactive-ui.js
CHANGED
|
@@ -237,7 +237,7 @@ class InteractiveUI {
|
|
|
237
237
|
case 'resize':
|
|
238
238
|
const heightChanged = stateManager.updateTerminalHeight(action.height);
|
|
239
239
|
if (heightChanged) {
|
|
240
|
-
stateManager.resetForResize();
|
|
240
|
+
stateManager.resetForResize(filteredStates.length);
|
|
241
241
|
}
|
|
242
242
|
else {
|
|
243
243
|
// Even if height didn't change, width might have changed
|
|
@@ -400,7 +400,8 @@ class InteractiveUI {
|
|
|
400
400
|
// This handles cases where process.stdout.rows might not be accurate at startup
|
|
401
401
|
const currentHeight = this.getTerminalHeight();
|
|
402
402
|
if (stateManager.updateTerminalHeight(currentHeight)) {
|
|
403
|
-
stateManager.
|
|
403
|
+
const initialFiltered = stateManager.getFilteredStates(states);
|
|
404
|
+
stateManager.resetForResize(initialFiltered.length);
|
|
404
405
|
}
|
|
405
406
|
// Initial render
|
|
406
407
|
renderInterface();
|
|
@@ -42,50 +42,250 @@ const config_1 = require("../config");
|
|
|
42
42
|
const npm_registry_1 = require("./npm-registry");
|
|
43
43
|
const cache_manager_1 = require("./cache-manager");
|
|
44
44
|
const utils_1 = require("../ui/utils");
|
|
45
|
+
const utils_2 = require("../utils");
|
|
46
|
+
// Batch configuration for progressive loading
|
|
47
|
+
const BATCH_SIZE = 5;
|
|
48
|
+
const BATCH_TIMEOUT_MS = 500;
|
|
49
|
+
const DEFAULT_JSDELIVR_RETRY_TIMEOUT_MS = 2000;
|
|
50
|
+
const DEFAULT_JSDELIVR_POOL_TIMEOUT_MS = 60000;
|
|
51
|
+
const MIN_JSDELIVR_CONNECT_TIMEOUT_MS = 500;
|
|
52
|
+
const toPositiveInteger = (value) => {
|
|
53
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const normalized = Math.floor(value);
|
|
57
|
+
return normalized > 0 ? normalized : null;
|
|
58
|
+
};
|
|
59
|
+
const RETRY_TIMEOUTS = (() => {
|
|
60
|
+
const configured = Array.from(new Set(config_1.JSDELIVR_RETRY_TIMEOUTS.map(toPositiveInteger).filter((value) => value !== null))).sort((a, b) => a - b);
|
|
61
|
+
return configured.length > 0 ? configured : [DEFAULT_JSDELIVR_RETRY_TIMEOUT_MS];
|
|
62
|
+
})();
|
|
63
|
+
const RETRY_DELAYS = config_1.JSDELIVR_RETRY_DELAYS.map(toPositiveInteger).filter((value) => value !== null);
|
|
64
|
+
const MAX_RETRY_AFTER_DELAY_MS = RETRY_TIMEOUTS[RETRY_TIMEOUTS.length - 1];
|
|
65
|
+
const RETRY_AFTER_HEADER = 'retry-after';
|
|
66
|
+
const parseRetryAfterMs = (value) => {
|
|
67
|
+
const trimmed = value.trim();
|
|
68
|
+
if (!trimmed) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const seconds = Number(trimmed);
|
|
72
|
+
if (Number.isFinite(seconds)) {
|
|
73
|
+
if (seconds <= 0) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const delayMs = Math.floor(seconds * 1000);
|
|
77
|
+
return delayMs > 0 ? delayMs : null;
|
|
78
|
+
}
|
|
79
|
+
const dateMs = Date.parse(trimmed);
|
|
80
|
+
if (Number.isNaN(dateMs)) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const delayMs = dateMs - Date.now();
|
|
84
|
+
return delayMs > 0 ? delayMs : null;
|
|
85
|
+
};
|
|
86
|
+
const getHeaderValue = (headers, name) => {
|
|
87
|
+
if (!headers) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const direct = headers[name];
|
|
91
|
+
if (typeof direct === 'string') {
|
|
92
|
+
return direct;
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(direct)) {
|
|
95
|
+
return direct.find((value) => typeof value === 'string') ?? null;
|
|
96
|
+
}
|
|
97
|
+
const headerEntry = Object.entries(headers).find(([headerName]) => headerName.toLowerCase() === name);
|
|
98
|
+
if (!headerEntry) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const [, rawValue] = headerEntry;
|
|
102
|
+
if (typeof rawValue === 'string') {
|
|
103
|
+
return rawValue;
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(rawValue)) {
|
|
106
|
+
return rawValue.find((value) => typeof value === 'string') ?? null;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
};
|
|
110
|
+
const getRetryAfterDelay = (headers) => {
|
|
111
|
+
const retryAfterValue = getHeaderValue(headers, RETRY_AFTER_HEADER);
|
|
112
|
+
if (!retryAfterValue) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const parsedDelay = parseRetryAfterMs(retryAfterValue);
|
|
116
|
+
if (parsedDelay === null) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return Math.min(parsedDelay, MAX_RETRY_AFTER_DELAY_MS);
|
|
120
|
+
};
|
|
121
|
+
const getRetryDelay = (attempt, headers) => {
|
|
122
|
+
const configuredDelay = RETRY_DELAYS.length === 0 ? 0 : RETRY_DELAYS[Math.min(attempt, RETRY_DELAYS.length - 1)];
|
|
123
|
+
const retryAfterDelay = getRetryAfterDelay(headers);
|
|
124
|
+
return retryAfterDelay === null ? configuredDelay : Math.max(configuredDelay, retryAfterDelay);
|
|
125
|
+
};
|
|
126
|
+
// Keep connection setup bounded by retry budget so fallback stays responsive.
|
|
127
|
+
const JSDELIVR_CONNECT_TIMEOUT_MS = Math.max(RETRY_TIMEOUTS[0], MIN_JSDELIVR_CONNECT_TIMEOUT_MS);
|
|
128
|
+
const JSDELIVR_POOL_TIMEOUT_MS = toPositiveInteger(config_1.JSDELIVR_POOL_TIMEOUT) ?? DEFAULT_JSDELIVR_POOL_TIMEOUT_MS;
|
|
129
|
+
const JSDELIVR_CONNECTIONS = toPositiveInteger(config_1.MAX_CONCURRENT_REQUESTS) ?? 1;
|
|
45
130
|
// Create a persistent connection pool for jsDelivr CDN with optimal settings
|
|
46
131
|
// This enables connection reuse and HTTP/1.1 keep-alive for blazing fast requests
|
|
47
132
|
const jsdelivrPool = new undici_1.Pool('https://cdn.jsdelivr.net', {
|
|
48
|
-
connections:
|
|
49
|
-
pipelining: 10,
|
|
50
|
-
keepAliveTimeout:
|
|
51
|
-
keepAliveMaxTimeout:
|
|
52
|
-
connectTimeout:
|
|
133
|
+
connections: JSDELIVR_CONNECTIONS,
|
|
134
|
+
pipelining: 10,
|
|
135
|
+
keepAliveTimeout: JSDELIVR_POOL_TIMEOUT_MS,
|
|
136
|
+
keepAliveMaxTimeout: JSDELIVR_POOL_TIMEOUT_MS,
|
|
137
|
+
connectTimeout: JSDELIVR_CONNECT_TIMEOUT_MS,
|
|
53
138
|
});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
139
|
+
const isTimeoutError = (error) => {
|
|
140
|
+
if (!(error instanceof Error)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const maybeCode = error.code;
|
|
144
|
+
const message = error.message.toLowerCase();
|
|
145
|
+
return (maybeCode === 'UND_ERR_HEADERS_TIMEOUT' ||
|
|
146
|
+
maybeCode === 'UND_ERR_BODY_TIMEOUT' ||
|
|
147
|
+
maybeCode === 'UND_ERR_CONNECT_TIMEOUT' ||
|
|
148
|
+
error.name === 'HeadersTimeoutError' ||
|
|
149
|
+
error.name === 'BodyTimeoutError' ||
|
|
150
|
+
error.name === 'ConnectTimeoutError' ||
|
|
151
|
+
message.includes('timeout'));
|
|
152
|
+
};
|
|
153
|
+
const isTransientNetworkError = (error) => {
|
|
154
|
+
if (!(error instanceof Error)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const maybeCode = error.code;
|
|
158
|
+
return (maybeCode === 'UND_ERR_SOCKET' ||
|
|
159
|
+
maybeCode === 'ENOTFOUND' ||
|
|
160
|
+
maybeCode === 'EAI_AGAIN' ||
|
|
161
|
+
maybeCode === 'ECONNRESET' ||
|
|
162
|
+
maybeCode === 'ECONNREFUSED' ||
|
|
163
|
+
maybeCode === 'ETIMEDOUT' ||
|
|
164
|
+
maybeCode === 'EPIPE');
|
|
165
|
+
};
|
|
166
|
+
const isRetryableStatus = (statusCode) => statusCode === 408 || statusCode === 429 || statusCode >= 500;
|
|
167
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
168
|
+
const consumeBodySafely = async (body) => {
|
|
169
|
+
try {
|
|
170
|
+
await body.text();
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Ignore body read errors on non-200 responses because request will be retried/fallback.
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const extractMajorVersion = (version) => {
|
|
177
|
+
if (!version) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const coerced = semver.coerce(version);
|
|
181
|
+
if (!coerced) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return semver.major(coerced).toString();
|
|
185
|
+
};
|
|
186
|
+
const toComparableVersion = (version) => {
|
|
187
|
+
const validVersion = semver.valid(version);
|
|
188
|
+
if (validVersion) {
|
|
189
|
+
return validVersion;
|
|
190
|
+
}
|
|
191
|
+
const coerced = semver.coerce(version);
|
|
192
|
+
return coerced ? coerced.version : null;
|
|
193
|
+
};
|
|
194
|
+
const versionIdentity = (version) => {
|
|
195
|
+
const comparable = toComparableVersion(version);
|
|
196
|
+
return comparable ?? `raw:${version}`;
|
|
197
|
+
};
|
|
198
|
+
const sortVersionsDescending = (versions) => {
|
|
199
|
+
const uniqueVersions = [];
|
|
200
|
+
const seenVersions = new Set();
|
|
201
|
+
for (const version of versions) {
|
|
202
|
+
const identity = versionIdentity(version);
|
|
203
|
+
if (!seenVersions.has(identity)) {
|
|
204
|
+
seenVersions.add(identity);
|
|
205
|
+
uniqueVersions.push(version);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return uniqueVersions.sort((a, b) => {
|
|
209
|
+
const comparableA = toComparableVersion(a);
|
|
210
|
+
const comparableB = toComparableVersion(b);
|
|
211
|
+
if (comparableA && comparableB) {
|
|
212
|
+
return semver.rcompare(comparableA, comparableB);
|
|
213
|
+
}
|
|
214
|
+
if (comparableA) {
|
|
215
|
+
return -1;
|
|
216
|
+
}
|
|
217
|
+
if (comparableB) {
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
return b.localeCompare(a);
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
const isExpectedTransientError = (error) => isTimeoutError(error) || isTransientNetworkError(error);
|
|
57
224
|
/**
|
|
58
225
|
* Fetches package.json from jsdelivr CDN for a specific version tag using undici pool.
|
|
59
226
|
* Uses connection pooling and keep-alive for maximum performance.
|
|
227
|
+
* Retries on transient failures while keeping a short fallback budget.
|
|
60
228
|
* @param packageName - The npm package name
|
|
61
229
|
* @param versionTag - The version tag (e.g., '14', 'latest')
|
|
62
230
|
* @returns The package.json content or null if not found
|
|
63
231
|
*/
|
|
64
232
|
async function fetchPackageJsonFromJsdelivr(packageName, versionTag) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
headers
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
233
|
+
const url = `${config_1.JSDELIVR_CDN_URL}/${encodeURIComponent(packageName)}@${versionTag}/package.json`;
|
|
234
|
+
for (let attempt = 0; attempt < RETRY_TIMEOUTS.length; attempt++) {
|
|
235
|
+
const timeout = RETRY_TIMEOUTS[attempt];
|
|
236
|
+
const tReq = Date.now();
|
|
237
|
+
try {
|
|
238
|
+
const { statusCode, headers, body } = await (0, undici_1.request)(url, {
|
|
239
|
+
dispatcher: jsdelivrPool,
|
|
240
|
+
method: 'GET',
|
|
241
|
+
headers: {
|
|
242
|
+
accept: 'application/json',
|
|
243
|
+
},
|
|
244
|
+
headersTimeout: timeout,
|
|
245
|
+
bodyTimeout: timeout,
|
|
246
|
+
});
|
|
247
|
+
if (statusCode !== 200) {
|
|
248
|
+
// Consume body to prevent memory leaks
|
|
249
|
+
await consumeBodySafely(body);
|
|
250
|
+
if (isRetryableStatus(statusCode) && attempt < RETRY_TIMEOUTS.length - 1) {
|
|
251
|
+
const delay = getRetryDelay(attempt, headers);
|
|
252
|
+
utils_2.debugLog.warn('jsdelivr', `${packageName}@${versionTag} HTTP ${statusCode}, retry ${attempt + 1} in ${delay}ms`);
|
|
253
|
+
if (delay > 0) {
|
|
254
|
+
await sleep(delay);
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
utils_2.debugLog.warn('jsdelivr', `${packageName}@${versionTag} HTTP ${statusCode}, no more retries`);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const text = await body.text();
|
|
262
|
+
const data = JSON.parse(text);
|
|
263
|
+
const version = typeof data.version === 'string' ? data.version.trim() : '';
|
|
264
|
+
utils_2.debugLog.perf('jsdelivr', `fetch ${packageName}@${versionTag} → ${version || 'no version'}`, tReq);
|
|
265
|
+
return version ? { version } : null;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
if ((isTimeoutError(error) || isTransientNetworkError(error)) &&
|
|
269
|
+
attempt < RETRY_TIMEOUTS.length - 1) {
|
|
270
|
+
const delay = getRetryDelay(attempt);
|
|
271
|
+
utils_2.debugLog.warn('jsdelivr', `${packageName}@${versionTag} transient error on attempt ${attempt + 1}, retry in ${delay}ms`, error);
|
|
272
|
+
if (delay > 0) {
|
|
273
|
+
await sleep(delay);
|
|
274
|
+
}
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (!isExpectedTransientError(error)) {
|
|
278
|
+
// Unexpected errors are logged for observability.
|
|
279
|
+
console.error(`jsDelivr fetch failed for ${packageName}@${versionTag} on attempt ${attempt + 1}/${RETRY_TIMEOUTS.length}`, error);
|
|
280
|
+
utils_2.debugLog.error('jsdelivr', `unexpected error for ${packageName}@${versionTag} attempt ${attempt + 1}`, error);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
utils_2.debugLog.warn('jsdelivr', `${packageName}@${versionTag} exhausted retries`, error);
|
|
284
|
+
}
|
|
79
285
|
return null;
|
|
80
286
|
}
|
|
81
|
-
const text = await body.text();
|
|
82
|
-
const data = JSON.parse(text);
|
|
83
|
-
return data.version ? { version: data.version } : null;
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
console.error(`Error fetching from jsdelivr for package: ${packageName}@${versionTag}`, error);
|
|
87
|
-
return null;
|
|
88
287
|
}
|
|
288
|
+
return null;
|
|
89
289
|
}
|
|
90
290
|
/**
|
|
91
291
|
* Fetches package version data from jsdelivr CDN for multiple packages.
|
|
@@ -105,14 +305,41 @@ async function getAllPackageDataFromJsdelivr(packageNames, currentVersions, onPr
|
|
|
105
305
|
}
|
|
106
306
|
const total = packageNames.length;
|
|
107
307
|
let completedCount = 0;
|
|
308
|
+
let progressCallback = onProgress;
|
|
309
|
+
let batchReadyCallback = onBatchReady;
|
|
108
310
|
// Batch buffer for progressive updates
|
|
109
311
|
let batchBuffer = [];
|
|
110
312
|
let batchTimer = null;
|
|
313
|
+
const emitProgress = (packageName, completed, packageTotal) => {
|
|
314
|
+
if (!progressCallback) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
progressCallback(packageName, completed, packageTotal);
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
console.error('Progress callback failed, disabling progress updates for this run.', error);
|
|
322
|
+
progressCallback = undefined;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
const emitBatch = (batch) => {
|
|
326
|
+
if (!batchReadyCallback) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
batchReadyCallback(batch);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
console.error('Batch callback failed, disabling batch updates for this run.', error);
|
|
334
|
+
batchReadyCallback = undefined;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
111
337
|
// Helper to flush the current batch
|
|
112
338
|
const flushBatch = () => {
|
|
113
|
-
if (batchBuffer.length > 0
|
|
114
|
-
|
|
339
|
+
if (batchBuffer.length > 0) {
|
|
340
|
+
const batch = [...batchBuffer];
|
|
115
341
|
batchBuffer = [];
|
|
342
|
+
emitBatch(batch);
|
|
116
343
|
}
|
|
117
344
|
if (batchTimer) {
|
|
118
345
|
clearTimeout(batchTimer);
|
|
@@ -121,113 +348,119 @@ async function getAllPackageDataFromJsdelivr(packageNames, currentVersions, onPr
|
|
|
121
348
|
};
|
|
122
349
|
// Helper to add package to batch and flush if needed
|
|
123
350
|
const addToBatch = (packageName, data) => {
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
351
|
+
if (!batchReadyCallback) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
batchBuffer.push({ name: packageName, data });
|
|
355
|
+
// Flush if batch is full
|
|
356
|
+
if (batchBuffer.length >= BATCH_SIZE) {
|
|
357
|
+
flushBatch();
|
|
358
|
+
}
|
|
359
|
+
else if (!batchTimer) {
|
|
360
|
+
// Set timer to flush batch after timeout
|
|
361
|
+
batchTimer = setTimeout(flushBatch, BATCH_TIMEOUT_MS);
|
|
134
362
|
}
|
|
135
363
|
};
|
|
136
364
|
// Process individual package fetch with immediate npm fallback on failure
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
|
|
365
|
+
const inFlightLookups = new Map();
|
|
366
|
+
const fetchFromNpmFallback = async (packageName) => {
|
|
367
|
+
const tFallback = Date.now();
|
|
368
|
+
utils_2.debugLog.info('jsdelivr', `falling back to npm registry for ${packageName}`);
|
|
369
|
+
try {
|
|
370
|
+
const npmData = await (0, npm_registry_1.getAllPackageData)([packageName]);
|
|
371
|
+
const result = npmData.get(packageName) ?? null;
|
|
372
|
+
if (result) {
|
|
373
|
+
cache_manager_1.packageCache.set(packageName, result);
|
|
374
|
+
utils_2.debugLog.perf('jsdelivr', `npm fallback resolved ${packageName} → ${result.latestVersion}`, tFallback);
|
|
146
375
|
}
|
|
147
|
-
|
|
148
|
-
|
|
376
|
+
else {
|
|
377
|
+
utils_2.debugLog.warn('jsdelivr', `npm fallback returned no data for ${packageName}`);
|
|
378
|
+
}
|
|
379
|
+
return result;
|
|
149
380
|
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
utils_2.debugLog.error('jsdelivr', `npm fallback failed for ${packageName}`, error);
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
const fetchFreshPackageData = async (packageName, currentVersion) => {
|
|
150
387
|
try {
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
? semver.major(semver.coerce(currentVersion) || '0.0.0').toString()
|
|
154
|
-
: null;
|
|
155
|
-
// Prepare requests: always fetch @latest, @major if we have a current version
|
|
156
|
-
const requests = [
|
|
157
|
-
fetchPackageJsonFromJsdelivr(packageName, 'latest'),
|
|
158
|
-
];
|
|
159
|
-
if (majorVersion) {
|
|
160
|
-
requests.push(fetchPackageJsonFromJsdelivr(packageName, majorVersion));
|
|
161
|
-
}
|
|
162
|
-
// Execute all requests simultaneously
|
|
163
|
-
const results = await Promise.all(requests);
|
|
164
|
-
const latestResult = results[0];
|
|
165
|
-
const majorResult = results[1];
|
|
388
|
+
const majorVersion = extractMajorVersion(currentVersion);
|
|
389
|
+
const latestResult = await fetchPackageJsonFromJsdelivr(packageName, 'latest');
|
|
166
390
|
if (!latestResult) {
|
|
167
|
-
|
|
168
|
-
const npmData = await (0, npm_registry_1.getAllPackageData)([packageName]);
|
|
169
|
-
const result = npmData.get(packageName);
|
|
170
|
-
if (result) {
|
|
171
|
-
packageData.set(packageName, result);
|
|
172
|
-
// CacheManager handles both memory and disk caching
|
|
173
|
-
cache_manager_1.packageCache.set(packageName, result);
|
|
174
|
-
addToBatch(packageName, result);
|
|
175
|
-
}
|
|
176
|
-
completedCount++;
|
|
177
|
-
if (onProgress) {
|
|
178
|
-
onProgress(packageName, completedCount, total);
|
|
179
|
-
}
|
|
180
|
-
return;
|
|
391
|
+
return await fetchFromNpmFallback(packageName);
|
|
181
392
|
}
|
|
182
393
|
const latestVersion = latestResult.version;
|
|
394
|
+
const latestMajorVersion = extractMajorVersion(latestVersion);
|
|
395
|
+
const shouldFetchMajorVersion = Boolean(majorVersion && (latestMajorVersion === null || majorVersion !== latestMajorVersion));
|
|
396
|
+
const majorResult = shouldFetchMajorVersion
|
|
397
|
+
? await fetchPackageJsonFromJsdelivr(packageName, majorVersion)
|
|
398
|
+
: null;
|
|
183
399
|
const allVersions = [latestVersion];
|
|
184
|
-
// Add the major version result if different from latest
|
|
185
400
|
if (majorResult && majorResult.version !== latestVersion) {
|
|
186
401
|
allVersions.push(majorResult.version);
|
|
187
402
|
}
|
|
403
|
+
const sortedVersions = sortVersionsDescending(allVersions);
|
|
404
|
+
const orderedVersions = sortedVersions[0] === latestVersion
|
|
405
|
+
? sortedVersions
|
|
406
|
+
: [latestVersion, ...sortedVersions.filter((version) => version !== latestVersion)];
|
|
188
407
|
const result = {
|
|
189
408
|
latestVersion,
|
|
190
|
-
allVersions:
|
|
409
|
+
allVersions: orderedVersions,
|
|
191
410
|
};
|
|
192
|
-
// Cache the result using CacheManager (handles both memory and disk)
|
|
193
411
|
cache_manager_1.packageCache.set(packageName, result);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return await fetchFromNpmFallback(packageName);
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
const getPackageData = async (packageName, currentVersion) => {
|
|
419
|
+
const cached = cache_manager_1.packageCache.get(packageName);
|
|
420
|
+
if (cached) {
|
|
421
|
+
utils_2.debugLog.info('jsdelivr', `cache hit: ${packageName} → ${cached.latestVersion}`);
|
|
422
|
+
return cached;
|
|
423
|
+
}
|
|
424
|
+
const inFlight = inFlightLookups.get(packageName);
|
|
425
|
+
if (inFlight) {
|
|
426
|
+
return await inFlight;
|
|
427
|
+
}
|
|
428
|
+
const lookupPromise = fetchFreshPackageData(packageName, currentVersion).finally(() => {
|
|
429
|
+
inFlightLookups.delete(packageName);
|
|
430
|
+
});
|
|
431
|
+
inFlightLookups.set(packageName, lookupPromise);
|
|
432
|
+
return await lookupPromise;
|
|
433
|
+
};
|
|
434
|
+
const fetchPackageWithFallback = async (packageName) => {
|
|
435
|
+
try {
|
|
436
|
+
const currentVersion = currentVersions?.get(packageName);
|
|
437
|
+
const result = await getPackageData(packageName, currentVersion);
|
|
438
|
+
if (result) {
|
|
439
|
+
packageData.set(packageName, result);
|
|
440
|
+
addToBatch(packageName, result);
|
|
198
441
|
}
|
|
199
|
-
addToBatch(packageName, result);
|
|
200
442
|
}
|
|
201
443
|
catch (error) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const result = npmData.get(packageName);
|
|
206
|
-
if (result) {
|
|
207
|
-
packageData.set(packageName, result);
|
|
208
|
-
// CacheManager handles both memory and disk caching
|
|
209
|
-
cache_manager_1.packageCache.set(packageName, result);
|
|
210
|
-
addToBatch(packageName, result);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
catch (npmError) {
|
|
214
|
-
// If both fail, just continue
|
|
215
|
-
}
|
|
444
|
+
console.error(`Failed to resolve package data for ${packageName}; continuing with others.`, error);
|
|
445
|
+
}
|
|
446
|
+
finally {
|
|
216
447
|
completedCount++;
|
|
217
|
-
|
|
218
|
-
onProgress(packageName, completedCount, total);
|
|
219
|
-
}
|
|
448
|
+
emitProgress(packageName, completedCount, total);
|
|
220
449
|
}
|
|
221
450
|
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
451
|
+
try {
|
|
452
|
+
// Fire all requests simultaneously - each request internally handles retries/fallback.
|
|
453
|
+
await Promise.all(packageNames.map(fetchPackageWithFallback));
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
// Flush any remaining batch items
|
|
457
|
+
flushBatch();
|
|
458
|
+
// Flush persistent cache to disk
|
|
459
|
+
cache_manager_1.packageCache.flush();
|
|
460
|
+
// Clear the progress line if no custom progress handler
|
|
461
|
+
if (!onProgress) {
|
|
462
|
+
utils_1.ConsoleUtils.clearProgress();
|
|
463
|
+
}
|
|
231
464
|
}
|
|
232
465
|
return packageData;
|
|
233
466
|
}
|
|
@@ -129,9 +129,9 @@ class NavigationManager {
|
|
|
129
129
|
if (targetVisualIndex < this.state.scrollOffset) {
|
|
130
130
|
this.state.scrollOffset = targetVisualIndex;
|
|
131
131
|
}
|
|
132
|
-
// Scrolling down: scroll
|
|
132
|
+
// Scrolling down: adjust scroll to keep item visible
|
|
133
133
|
else if (visualIndex >= this.state.scrollOffset + this.maxVisibleItems) {
|
|
134
|
-
this.state.scrollOffset
|
|
134
|
+
this.state.scrollOffset = visualIndex - this.maxVisibleItems + 1;
|
|
135
135
|
}
|
|
136
136
|
// Ensure scrollOffset doesn't go negative or beyond bounds
|
|
137
137
|
const maxScroll = Math.max(0, totalVisualItems - this.maxVisibleItems);
|
|
@@ -167,8 +167,10 @@ class StateManager {
|
|
|
167
167
|
}
|
|
168
168
|
exitFilterMode(clearQuery = false) {
|
|
169
169
|
this.filterManager.exitFilterMode(clearQuery);
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
if (clearQuery) {
|
|
171
|
+
this.navigationManager.setCurrentRow(0);
|
|
172
|
+
this.navigationManager.setScrollOffset(0);
|
|
173
|
+
}
|
|
172
174
|
// Use incremental render for search mode toggle (no blink)
|
|
173
175
|
}
|
|
174
176
|
updateFilterQuery(query) {
|
|
@@ -218,8 +220,8 @@ class StateManager {
|
|
|
218
220
|
setInitialRender(isInitial) {
|
|
219
221
|
this.renderState.forceFullRender = isInitial;
|
|
220
222
|
}
|
|
221
|
-
resetForResize() {
|
|
222
|
-
const totalItems = this.renderState.renderableItems.length || this.displayState.maxVisibleItems;
|
|
223
|
+
resetForResize(totalFilteredItems) {
|
|
224
|
+
const totalItems = totalFilteredItems || this.renderState.renderableItems.length || this.displayState.maxVisibleItems;
|
|
223
225
|
this.navigationManager.resetForResize(totalItems);
|
|
224
226
|
this.renderState.forceFullRender = true;
|
|
225
227
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.debugLog = void 0;
|
|
4
|
+
exports.enableDebugLogging = enableDebugLogging;
|
|
5
|
+
exports.isDebugEnabled = isDebugEnabled;
|
|
6
|
+
exports.getDebugLogPath = getDebugLogPath;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
let _enabled = false;
|
|
10
|
+
let _logFile = null;
|
|
11
|
+
const pad = (n, width = 2) => String(n).padStart(width, '0');
|
|
12
|
+
function timestamp() {
|
|
13
|
+
const d = new Date();
|
|
14
|
+
return (`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
|
|
15
|
+
`${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`);
|
|
16
|
+
}
|
|
17
|
+
function getLogFile() {
|
|
18
|
+
if (!_logFile) {
|
|
19
|
+
const d = new Date();
|
|
20
|
+
const dateStr = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
21
|
+
_logFile = (0, path_1.join)(`inup-debug-${dateStr}.log`);
|
|
22
|
+
// Write a header so the file is easy to identify
|
|
23
|
+
(0, fs_1.writeFileSync)(_logFile, `=== inup debug log started at ${timestamp()} ===\n`, { flag: 'a' });
|
|
24
|
+
}
|
|
25
|
+
return _logFile;
|
|
26
|
+
}
|
|
27
|
+
function enableDebugLogging() {
|
|
28
|
+
_enabled = true;
|
|
29
|
+
const file = getLogFile();
|
|
30
|
+
// Print the path so the user knows where to look
|
|
31
|
+
process.stderr.write(`[inup] debug logging enabled → ${file}\n`);
|
|
32
|
+
}
|
|
33
|
+
function isDebugEnabled() {
|
|
34
|
+
return _enabled;
|
|
35
|
+
}
|
|
36
|
+
function getDebugLogPath() {
|
|
37
|
+
return _logFile;
|
|
38
|
+
}
|
|
39
|
+
function write(level, context, message, extra) {
|
|
40
|
+
if (!_enabled)
|
|
41
|
+
return;
|
|
42
|
+
let line = `[${timestamp()}] [${level}] [${context}] ${message}`;
|
|
43
|
+
if (extra !== undefined) {
|
|
44
|
+
if (extra instanceof Error) {
|
|
45
|
+
line += ` | ${extra.name}: ${extra.message}`;
|
|
46
|
+
if (extra.stack) {
|
|
47
|
+
const stackLines = extra.stack.split('\n').slice(1, 4).join(' | ');
|
|
48
|
+
line += ` | ${stackLines}`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (typeof extra === 'object') {
|
|
52
|
+
try {
|
|
53
|
+
line += ` | ${JSON.stringify(extra)}`;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
line += ` | [unserializable]`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
line += ` | ${extra}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
line += '\n';
|
|
64
|
+
try {
|
|
65
|
+
(0, fs_1.appendFileSync)(getLogFile(), line);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Never crash the app because of debug logging
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.debugLog = {
|
|
72
|
+
info: (context, message, extra) => write('INFO', context, message, extra),
|
|
73
|
+
warn: (context, message, extra) => write('WARN', context, message, extra),
|
|
74
|
+
error: (context, message, extra) => write('ERROR', context, message, extra),
|
|
75
|
+
/** Log elapsed time since a start timestamp obtained via Date.now() */
|
|
76
|
+
perf: (context, label, startMs, extra) => {
|
|
77
|
+
const elapsed = Date.now() - startMs;
|
|
78
|
+
write('PERF', context, `${label} — ${elapsed}ms`, extra);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=debug-logger.js.map
|
package/dist/utils/index.js
CHANGED
|
@@ -21,6 +21,7 @@ exports.collectAllDependenciesAsync = exports.readPackageJsonAsync = void 0;
|
|
|
21
21
|
__exportStar(require("./filesystem"), exports);
|
|
22
22
|
__exportStar(require("./exec"), exports);
|
|
23
23
|
__exportStar(require("./version"), exports);
|
|
24
|
+
__exportStar(require("./debug-logger"), exports);
|
|
24
25
|
// Re-export async functions for convenience
|
|
25
26
|
var filesystem_1 = require("./filesystem");
|
|
26
27
|
Object.defineProperty(exports, "readPackageJsonAsync", { enumerable: true, get: function () { return filesystem_1.readPackageJsonAsync; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inup",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.8",
|
|
4
4
|
"description": "Interactive CLI tool for upgrading dependencies with ease. Auto-detects and works with npm, yarn, pnpm, and bun. Inspired by yarn upgrade-interactive. Supports monorepos, workspaces, and batch upgrades.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|