inup 1.6.5 → 1.6.7
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 +16 -0
- package/dist/{interactive-ui.js → app/interactive-ui.js} +32 -21
- package/dist/{core → app}/upgrade-runner.js +12 -11
- package/dist/cli.js +12 -10
- package/dist/{services → features/audit}/background-audit.js +2 -2
- package/dist/features/{headless/vulnerability-audit.js → audit/headless-audit.js} +9 -5
- package/dist/features/audit/index.js +36 -0
- package/dist/{ui/presenters/vulnerability.js → features/audit/presenter.js} +1 -1
- package/dist/{ui/controllers → features/audit}/vulnerability-audit-controller.js +12 -6
- package/dist/{services → features/audit}/vulnerability-checker.js +11 -8
- package/dist/features/changelog/clients/github-client.js +35 -17
- package/dist/features/changelog/clients/npm-registry-client.js +16 -2
- package/dist/features/changelog/index.js +1 -1
- package/dist/features/changelog/services/package-metadata-service.js +6 -2
- package/dist/features/changelog/services/release-notes-service.js +6 -10
- package/dist/features/changelog/types.js +3 -0
- package/dist/features/debug/index.js +3 -4
- package/dist/features/debug/{services/perf-logger.js → perf-logger.js} +1 -1
- package/dist/features/debug/types.js +3 -0
- package/dist/features/headless/headless-runner.js +11 -11
- package/dist/features/headless/index.js +1 -4
- package/dist/features/headless/report.js +2 -0
- package/dist/features/interactive/controllers/index.js +6 -0
- package/dist/{ui → features/interactive}/controllers/package-info-modal-controller.js +3 -3
- package/dist/features/interactive/index.js +21 -0
- package/dist/{ui → features/interactive}/input-handler.js +3 -3
- package/dist/{ui → features/interactive}/modal/layout.js +2 -2
- package/dist/{ui/renderer/package-list.js → features/interactive/modal/package-info-sections/index.js} +3 -3
- package/dist/{ui → features/interactive}/modal/package-info-sections/sections.js +53 -22
- package/dist/{ui → features/interactive}/modal/package-info-sections/text.js +5 -5
- package/dist/{ui → features/interactive}/modal/package-info.js +31 -13
- package/dist/features/interactive/modal/types.js +3 -0
- package/dist/{ui → features/interactive}/presenters/health.js +2 -2
- package/dist/{ui → features/interactive}/renderer/index.js +0 -1
- package/dist/{types.js → features/interactive/renderer/package-list/index.js} +3 -4
- package/dist/{ui → features/interactive}/renderer/package-list/interface.js +9 -10
- package/dist/{ui → features/interactive}/renderer/package-list/rows.js +24 -17
- package/dist/features/{debug → interactive}/renderer/performance-modal.js +1 -1
- package/dist/{ui/utils/version.js → features/interactive/renderer/version-format.js} +6 -10
- package/dist/{ui → features/interactive}/session/index.js +2 -1
- package/dist/{ui → features/interactive}/session/interactive-session.js +39 -19
- package/dist/{ui → features/interactive}/session/selection-state-builder.js +20 -5
- package/dist/{ui → features/interactive}/state/filter-manager.js +2 -2
- package/dist/{ui → features/interactive}/state/state-manager.js +6 -4
- package/dist/{ui → features/interactive}/state/theme-manager.js +8 -4
- package/dist/{ui → features/interactive}/themes-colors.js +5 -2
- package/dist/features/upgrade/index.js +8 -0
- package/dist/{core → features/upgrade}/package-detector.js +74 -33
- package/dist/{core → features/upgrade}/upgrader.js +72 -14
- package/dist/index.js +2 -2
- package/dist/{config → shared/config}/package-meta.js +1 -1
- package/dist/{config → shared/config}/project-config.js +4 -1
- package/dist/{utils/config.js → shared/config/user-config.js} +3 -3
- package/dist/{ui/modal/package-info-sections.js → shared/fs/index.js} +4 -3
- package/dist/{utils/filesystem → shared/fs}/paths.js +3 -3
- package/dist/{utils/filesystem → shared/fs}/scan.js +5 -2
- package/dist/{services → shared}/http/etag-store.js +56 -4
- package/dist/{utils → shared}/local-env.js +1 -1
- package/dist/{services/package-manager-detector.js → shared/package-manager.js} +1 -1
- package/dist/shared/pnpm-catalogs.js +138 -0
- package/dist/{services → shared/registry}/npm-registry.js +48 -29
- package/dist/shared/registry/registry-config.js +76 -0
- package/dist/{services → shared/registry}/version-checker.js +24 -25
- package/dist/{ui/utils → shared/terminal}/index.js +4 -6
- package/dist/{ui/utils → shared/terminal}/terminal-input.js +2 -1
- package/dist/shared/terminal/text.js +48 -0
- package/dist/shared/types/index.js +20 -0
- package/dist/{utils/version.js → shared/versions.js} +12 -1
- package/package.json +15 -6
- package/dist/core/index.js +0 -23
- package/dist/features/changelog/types/changelog.types.js +0 -3
- package/dist/features/debug/types/debug.types.js +0 -3
- package/dist/services/index.js +0 -25
- package/dist/ui/controllers/index.js +0 -8
- package/dist/ui/index.js +0 -27
- package/dist/ui/presenters/index.js +0 -11
- package/dist/ui/utils/text.js +0 -76
- package/dist/utils/filesystem.js +0 -20
- package/dist/utils/index.js +0 -34
- /package/dist/{ui/modal → features/audit}/types.js +0 -0
- /package/dist/features/debug/{services/performance-tracker.js → performance-tracker.js} +0 -0
- /package/dist/{ui → features/interactive}/keymap.js +0 -0
- /package/dist/{ui → features/interactive}/modal/index.js +0 -0
- /package/dist/{ui → features/interactive}/modal/package-info-sections/release-notes.js +0 -0
- /package/dist/{ui → features/interactive}/modal/theme-selector.js +0 -0
- /package/dist/{ui → features/interactive}/renderer/confirmation.js +0 -0
- /package/dist/{ui → features/interactive}/renderer/help-modal.js +0 -0
- /package/dist/{ui → features/interactive}/session/action-dispatcher.js +0 -0
- /package/dist/{ui → features/interactive}/state/index.js +0 -0
- /package/dist/{ui → features/interactive}/state/modal-manager.js +0 -0
- /package/dist/{ui → features/interactive}/state/navigation-manager.js +0 -0
- /package/dist/{ui → features/interactive}/themes.js +0 -0
- /package/dist/{config → shared/config}/constants.js +0 -0
- /package/dist/{config → shared/config}/index.js +0 -0
- /package/dist/{utils → shared}/debug-logger.js +0 -0
- /package/dist/{utils → shared}/engines.js +0 -0
- /package/dist/{utils → shared}/exec.js +0 -0
- /package/dist/{utils/filesystem → shared/fs}/io.js +0 -0
- /package/dist/{utils → shared}/git.js +0 -0
- /package/dist/{services → shared}/http/adaptive-controller.js +0 -0
- /package/dist/{services → shared}/http/inflight.js +0 -0
- /package/dist/{services → shared}/http/resizable-semaphore.js +0 -0
- /package/dist/{services → shared}/http/retry.js +0 -0
- /package/dist/{utils → shared}/manifest.js +0 -0
- /package/dist/{utils → shared/terminal}/color.js +0 -0
- /package/dist/{ui/utils → shared/terminal}/cursor.js +0 -0
- /package/dist/{types → shared/types}/domain.js +0 -0
- /package/dist/{types → shared/types}/streaming.js +0 -0
- /package/dist/{types → shared/types}/ui.js +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PnpmCatalogs = exports.DEFAULT_CATALOG = exports.PNPM_WORKSPACE_FILE = void 0;
|
|
4
|
+
exports.isCatalogReference = isCatalogReference;
|
|
5
|
+
exports.writeCatalogUpdates = writeCatalogUpdates;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const yaml_1 = require("yaml");
|
|
9
|
+
const debug_logger_1 = require("./debug-logger");
|
|
10
|
+
exports.PNPM_WORKSPACE_FILE = 'pnpm-workspace.yaml';
|
|
11
|
+
/** Name pnpm gives the top-level `catalog:` map (also addressable as `catalogs.default`). */
|
|
12
|
+
exports.DEFAULT_CATALOG = 'default';
|
|
13
|
+
/** `catalog:` (default) or `catalog:<name>` — the pnpm catalog protocol. */
|
|
14
|
+
function isCatalogReference(version) {
|
|
15
|
+
return version.startsWith('catalog:');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The catalog definitions of the nearest `pnpm-workspace.yaml`.
|
|
19
|
+
*
|
|
20
|
+
* Catalogs (pnpm ≥ 9.5) hoist shared version ranges out of the member
|
|
21
|
+
* package.json files: a dependency declared as `"react": "catalog:"` gets its
|
|
22
|
+
* actual range from this file's `catalog:` map (or `catalogs.<name>` for
|
|
23
|
+
* `"catalog:<name>"` references). Upgrading such a dependency therefore means
|
|
24
|
+
* editing pnpm-workspace.yaml, not the referencing package.json.
|
|
25
|
+
*/
|
|
26
|
+
class PnpmCatalogs {
|
|
27
|
+
path;
|
|
28
|
+
catalogs;
|
|
29
|
+
constructor(
|
|
30
|
+
/** Absolute path of the pnpm-workspace.yaml the catalogs were read from. */
|
|
31
|
+
path,
|
|
32
|
+
/** catalog name → (package name → range). */
|
|
33
|
+
catalogs) {
|
|
34
|
+
this.path = path;
|
|
35
|
+
this.catalogs = catalogs;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Load catalogs from the nearest pnpm-workspace.yaml at or above `startDir`.
|
|
39
|
+
* Returns null when there is no workspace file, it is unreadable, or it
|
|
40
|
+
* defines no catalogs — a broken file must never break a scan.
|
|
41
|
+
*/
|
|
42
|
+
static load(startDir) {
|
|
43
|
+
const path = findPnpmWorkspaceFile(startDir);
|
|
44
|
+
if (!path)
|
|
45
|
+
return null;
|
|
46
|
+
try {
|
|
47
|
+
const raw = (0, yaml_1.parse)((0, node_fs_1.readFileSync)(path, 'utf8'));
|
|
48
|
+
if (!raw || typeof raw !== 'object')
|
|
49
|
+
return null;
|
|
50
|
+
const catalogs = new Map();
|
|
51
|
+
const addCatalog = (name, value) => {
|
|
52
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
53
|
+
return;
|
|
54
|
+
const entries = new Map();
|
|
55
|
+
for (const [pkg, range] of Object.entries(value)) {
|
|
56
|
+
if (typeof range === 'string')
|
|
57
|
+
entries.set(pkg, range);
|
|
58
|
+
}
|
|
59
|
+
if (entries.size > 0)
|
|
60
|
+
catalogs.set(name, entries);
|
|
61
|
+
};
|
|
62
|
+
addCatalog(exports.DEFAULT_CATALOG, raw.catalog);
|
|
63
|
+
if (raw.catalogs && typeof raw.catalogs === 'object' && !Array.isArray(raw.catalogs)) {
|
|
64
|
+
for (const [name, value] of Object.entries(raw.catalogs)) {
|
|
65
|
+
addCatalog(name, value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (catalogs.size === 0)
|
|
69
|
+
return null;
|
|
70
|
+
return new PnpmCatalogs(path, catalogs);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
debug_logger_1.debugLog.warn('PnpmCatalogs', `failed to parse ${path}: ${error}`);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Resolve a `catalog:` / `catalog:<name>` version specifier for `packageName`
|
|
79
|
+
* to the range declared in the workspace file, or null when the reference
|
|
80
|
+
* points at a catalog (or entry) that does not exist.
|
|
81
|
+
*/
|
|
82
|
+
resolve(packageName, versionSpecifier) {
|
|
83
|
+
if (!isCatalogReference(versionSpecifier))
|
|
84
|
+
return null;
|
|
85
|
+
const name = versionSpecifier.slice('catalog:'.length).trim() || exports.DEFAULT_CATALOG;
|
|
86
|
+
const range = this.catalogs.get(name)?.get(packageName);
|
|
87
|
+
return range === undefined ? null : { catalog: name, range };
|
|
88
|
+
}
|
|
89
|
+
/** Every entry declared in the given catalog (empty for unknown catalogs). */
|
|
90
|
+
entriesOf(catalogName) {
|
|
91
|
+
const entries = this.catalogs.get(catalogName);
|
|
92
|
+
if (!entries)
|
|
93
|
+
return [];
|
|
94
|
+
return Array.from(entries, ([name, range]) => ({ name, range }));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.PnpmCatalogs = PnpmCatalogs;
|
|
98
|
+
/**
|
|
99
|
+
* Write new ranges for catalog entries back into pnpm-workspace.yaml.
|
|
100
|
+
*
|
|
101
|
+
* Edits go through the yaml Document API so comments, key order, and the
|
|
102
|
+
* formatting of untouched nodes survive the round-trip. Entries that no longer
|
|
103
|
+
* exist in the file (edited since the scan) are skipped, never invented.
|
|
104
|
+
*/
|
|
105
|
+
function writeCatalogUpdates(workspaceFilePath, updates) {
|
|
106
|
+
const raw = (0, node_fs_1.readFileSync)(workspaceFilePath, 'utf8');
|
|
107
|
+
const doc = (0, yaml_1.parseDocument)(raw);
|
|
108
|
+
for (const update of updates) {
|
|
109
|
+
// The default catalog may live under `catalog:` or `catalogs.default:`.
|
|
110
|
+
const candidatePaths = update.catalog === exports.DEFAULT_CATALOG
|
|
111
|
+
? [
|
|
112
|
+
['catalog', update.name],
|
|
113
|
+
['catalogs', exports.DEFAULT_CATALOG, update.name],
|
|
114
|
+
]
|
|
115
|
+
: [['catalogs', update.catalog, update.name]];
|
|
116
|
+
const keyPath = candidatePaths.find((path) => doc.hasIn(path));
|
|
117
|
+
if (!keyPath) {
|
|
118
|
+
debug_logger_1.debugLog.warn('PnpmCatalogs', `catalog entry ${update.catalog}:${update.name} no longer exists in ${workspaceFilePath} — skipping`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
doc.setIn(keyPath, update.range);
|
|
122
|
+
}
|
|
123
|
+
(0, node_fs_1.writeFileSync)(workspaceFilePath, doc.toString());
|
|
124
|
+
}
|
|
125
|
+
/** Nearest pnpm-workspace.yaml at or above `startDir`, or null. */
|
|
126
|
+
function findPnpmWorkspaceFile(startDir) {
|
|
127
|
+
let dir = (0, node_path_1.resolve)(startDir);
|
|
128
|
+
for (;;) {
|
|
129
|
+
const candidate = (0, node_path_1.join)(dir, exports.PNPM_WORKSPACE_FILE);
|
|
130
|
+
if ((0, node_fs_1.existsSync)(candidate))
|
|
131
|
+
return candidate;
|
|
132
|
+
const parent = (0, node_path_1.dirname)(dir);
|
|
133
|
+
if (parent === dir)
|
|
134
|
+
return null;
|
|
135
|
+
dir = parent;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=pnpm-catalogs.js.map
|
|
@@ -9,15 +9,18 @@ const gunzipAsync = (0, node_util_1.promisify)(node_zlib_1.gunzip);
|
|
|
9
9
|
const inflateAsync = (0, node_util_1.promisify)(node_zlib_1.inflate);
|
|
10
10
|
const brotliDecompressAsync = (0, node_util_1.promisify)(node_zlib_1.brotliDecompress);
|
|
11
11
|
const config_1 = require("../config");
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
12
|
+
const registry_config_1 = require("./registry-config");
|
|
13
|
+
const versions_1 = require("../versions");
|
|
14
|
+
const retry_1 = require("../http/retry");
|
|
15
|
+
const inflight_1 = require("../http/inflight");
|
|
16
|
+
const resizable_semaphore_1 = require("../http/resizable-semaphore");
|
|
17
|
+
const adaptive_controller_1 = require("../http/adaptive-controller");
|
|
18
|
+
const etag_store_1 = require("../http/etag-store");
|
|
18
19
|
const inFlightLookups = new inflight_1.InflightMap();
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
// One pool per registry origin: scoped packages may resolve to different
|
|
21
|
+
// registries (`@scope:registry` in .npmrc), and each origin keeps its own
|
|
22
|
+
// keep-alive connections. Most runs still touch a single origin.
|
|
23
|
+
//
|
|
21
24
|
// Connection count is kept == the adaptive controller's ceiling (POOL_CONNECTIONS)
|
|
22
25
|
// so the controller is never silently throttled below its chosen limit. Idle
|
|
23
26
|
// keep-alive connections are cheap.
|
|
@@ -28,16 +31,24 @@ const registryPathPrefix = new URL(config_1.NPM_REGISTRY_URL).pathname.replace(/
|
|
|
28
31
|
// controller. With a headers timeout, a stall surfaces as a transient error the
|
|
29
32
|
// controller can react to (and retry handles). `bodyTimeout` stays 0 — large
|
|
30
33
|
// packuments legitimately stream slowly.
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
const poolByOrigin = new Map();
|
|
35
|
+
function poolFor(origin) {
|
|
36
|
+
let pool = poolByOrigin.get(origin);
|
|
37
|
+
if (!pool) {
|
|
38
|
+
pool = new undici_1.Pool(origin, {
|
|
39
|
+
connections: config_1.POOL_CONNECTIONS,
|
|
40
|
+
pipelining: 1,
|
|
41
|
+
keepAliveTimeout: 30_000,
|
|
42
|
+
keepAliveMaxTimeout: 600_000,
|
|
43
|
+
headersTimeout: 30_000,
|
|
44
|
+
bodyTimeout: 0,
|
|
45
|
+
connectTimeout: 15_000,
|
|
46
|
+
allowH2: false,
|
|
47
|
+
});
|
|
48
|
+
poolByOrigin.set(origin, pool);
|
|
49
|
+
}
|
|
50
|
+
return pool;
|
|
51
|
+
}
|
|
41
52
|
const MAX_REGISTRY_ATTEMPTS = 3;
|
|
42
53
|
const RETRY_BACKOFF_MS = [500, 1500, 3000];
|
|
43
54
|
// Fixed concurrency used when adaptive is disabled (INUP_ADAPTIVE=0, the A/B
|
|
@@ -48,27 +59,32 @@ async function getFreshPackageData(packageName, currentVersion, onAttempt) {
|
|
|
48
59
|
const cacheKey = `${packageName}@${currentVersion ?? ''}`;
|
|
49
60
|
return inFlightLookups.dedupe(cacheKey, () => fetchPackageFromRegistry(packageName, onAttempt));
|
|
50
61
|
}
|
|
51
|
-
const encodeRegistryPath = (packageName) => {
|
|
62
|
+
const encodeRegistryPath = (packageName, pathPrefix) => {
|
|
52
63
|
const encodedName = packageName.startsWith('@')
|
|
53
64
|
? `@${encodeURIComponent(packageName.slice(1).split('/')[0])}/${encodeURIComponent(packageName.slice(packageName.indexOf('/') + 1))}`
|
|
54
65
|
: encodeURIComponent(packageName);
|
|
55
|
-
return `${
|
|
66
|
+
return `${pathPrefix}/${encodedName}`;
|
|
56
67
|
};
|
|
57
|
-
async function attemptRegistryFetch(path) {
|
|
68
|
+
async function attemptRegistryFetch(target, path) {
|
|
58
69
|
const startedAt = Date.now();
|
|
59
70
|
// Conditional request: if we have a stored ETag for this packument, ask the
|
|
60
71
|
// registry to validate it. Unchanged → 304 (no body) and we reuse stored data.
|
|
61
72
|
// This still hits the registry every run, so data is never served stale.
|
|
62
|
-
|
|
73
|
+
// Keys are origin-qualified so two registries can never collide on a path.
|
|
74
|
+
const cacheKey = `${target.origin}${path}`;
|
|
75
|
+
const cached = (0, etag_store_1.readEtag)(cacheKey);
|
|
63
76
|
try {
|
|
64
77
|
const requestHeaders = {
|
|
65
78
|
accept: 'application/vnd.npm.install-v1+json',
|
|
66
79
|
'accept-encoding': 'gzip, deflate, br',
|
|
67
80
|
};
|
|
81
|
+
if (target.authHeader) {
|
|
82
|
+
requestHeaders['authorization'] = target.authHeader;
|
|
83
|
+
}
|
|
68
84
|
if (cached) {
|
|
69
85
|
requestHeaders['if-none-match'] = cached.etag;
|
|
70
86
|
}
|
|
71
|
-
const { statusCode, headers, body } = await
|
|
87
|
+
const { statusCode, headers, body } = await poolFor(target.origin).request({
|
|
72
88
|
path,
|
|
73
89
|
method: 'GET',
|
|
74
90
|
headers: requestHeaders,
|
|
@@ -112,12 +128,12 @@ async function attemptRegistryFetch(path) {
|
|
|
112
128
|
else {
|
|
113
129
|
decoded = raw;
|
|
114
130
|
}
|
|
115
|
-
const data = (0,
|
|
131
|
+
const data = (0, versions_1.parseVersions)(decoded.toString('utf8'));
|
|
116
132
|
// Persist the ETag for next run's conditional request.
|
|
117
133
|
const etagHeader = headers['etag'];
|
|
118
134
|
const etag = Array.isArray(etagHeader) ? etagHeader[0] : etagHeader;
|
|
119
135
|
if (etag) {
|
|
120
|
-
(0, etag_store_1.writeEtag)(
|
|
136
|
+
(0, etag_store_1.writeEtag)(cacheKey, etag.toString(), data);
|
|
121
137
|
}
|
|
122
138
|
return {
|
|
123
139
|
kind: 'success',
|
|
@@ -134,10 +150,10 @@ async function attemptRegistryFetch(path) {
|
|
|
134
150
|
return { kind: 'transient' };
|
|
135
151
|
}
|
|
136
152
|
}
|
|
137
|
-
async function fetchFromRegistryWithRetries(path, onAttempt) {
|
|
153
|
+
async function fetchFromRegistryWithRetries(target, path, onAttempt) {
|
|
138
154
|
let lastOutcome = { kind: 'transient' };
|
|
139
155
|
for (let attempt = 0; attempt < MAX_REGISTRY_ATTEMPTS; attempt++) {
|
|
140
|
-
const outcome = await attemptRegistryFetch(path);
|
|
156
|
+
const outcome = await attemptRegistryFetch(target, path);
|
|
141
157
|
onAttempt?.(outcome);
|
|
142
158
|
if (outcome.kind === 'success' || outcome.kind === 'not-found') {
|
|
143
159
|
return outcome;
|
|
@@ -154,8 +170,11 @@ async function fetchFromRegistryWithRetries(path, onAttempt) {
|
|
|
154
170
|
return lastOutcome;
|
|
155
171
|
}
|
|
156
172
|
async function fetchPackageFromRegistry(packageName, onAttempt) {
|
|
157
|
-
|
|
158
|
-
|
|
173
|
+
// Scoped packages may live on a different registry (with credentials) than
|
|
174
|
+
// unscoped ones — resolved from the npm config chain, memoized per scope.
|
|
175
|
+
const target = (0, registry_config_1.registryTargetFor)(packageName);
|
|
176
|
+
const path = encodeRegistryPath(packageName, target.pathPrefix);
|
|
177
|
+
const outcome = await fetchFromRegistryWithRetries(target, path, onAttempt);
|
|
159
178
|
if (outcome.kind === 'success') {
|
|
160
179
|
return outcome.data;
|
|
161
180
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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.scopeOfPackage = scopeOfPackage;
|
|
7
|
+
exports.registryTargetFor = registryTargetFor;
|
|
8
|
+
exports.clearRegistryTargetCache = clearRegistryTargetCache;
|
|
9
|
+
const registry_auth_token_1 = __importDefault(require("registry-auth-token"));
|
|
10
|
+
const registry_url_1 = __importDefault(require("registry-auth-token/registry-url"));
|
|
11
|
+
const config_1 = require("../config");
|
|
12
|
+
// The submodule's .d.ts declares the second parameter as `{ npmrc?: … }`, but the
|
|
13
|
+
// implementation consumes the plain npmrc key/value record directly.
|
|
14
|
+
const getRegistryUrl = registry_url_1.default;
|
|
15
|
+
/** Scope of a package name (`@scope/pkg` → `@scope`), or undefined when unscoped. */
|
|
16
|
+
function scopeOfPackage(packageName) {
|
|
17
|
+
if (!packageName.startsWith('@'))
|
|
18
|
+
return undefined;
|
|
19
|
+
const slash = packageName.indexOf('/');
|
|
20
|
+
return slash > 0 ? packageName.slice(0, slash) : undefined;
|
|
21
|
+
}
|
|
22
|
+
// Config files are re-read on every resolution inside registry-auth-token, so
|
|
23
|
+
// memoize per scope: one resolution per distinct scope per process.
|
|
24
|
+
const targetByScope = new Map();
|
|
25
|
+
function registryTargetFor(packageName, npmrc) {
|
|
26
|
+
const scope = scopeOfPackage(packageName) ?? '';
|
|
27
|
+
if (!npmrc) {
|
|
28
|
+
const cached = targetByScope.get(scope);
|
|
29
|
+
if (cached)
|
|
30
|
+
return cached;
|
|
31
|
+
}
|
|
32
|
+
const target = resolveTarget(scope || undefined, npmrc);
|
|
33
|
+
if (!npmrc) {
|
|
34
|
+
targetByScope.set(scope, target);
|
|
35
|
+
}
|
|
36
|
+
return target;
|
|
37
|
+
}
|
|
38
|
+
function resolveTarget(scope, npmrc) {
|
|
39
|
+
let registryHref = config_1.NPM_REGISTRY_URL;
|
|
40
|
+
try {
|
|
41
|
+
registryHref = getRegistryUrl(scope, npmrc);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Unreadable npm config must never break a run — fall back to the public registry.
|
|
45
|
+
}
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = new URL(registryHref);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
parsed = new URL(config_1.NPM_REGISTRY_URL);
|
|
52
|
+
}
|
|
53
|
+
let authHeader;
|
|
54
|
+
try {
|
|
55
|
+
const auth = (0, registry_auth_token_1.default)(parsed.href, { recursive: true, ...(npmrc ? { npmrc } : {}) });
|
|
56
|
+
// registry-auth-token stringifies an unset ${ENV_VAR} reference into the
|
|
57
|
+
// literal "undefined". Sending `Bearer undefined` guarantees a 401 where an
|
|
58
|
+
// anonymous request might succeed, so treat it as no credentials.
|
|
59
|
+
if (auth?.token && auth.token !== 'undefined') {
|
|
60
|
+
authHeader = `${auth.type} ${auth.token}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// No credentials (or unreadable config) → anonymous requests, matching npm.
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
origin: parsed.origin,
|
|
68
|
+
pathPrefix: parsed.pathname.replace(/\/$/, ''),
|
|
69
|
+
authHeader,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Test helper: forget memoized resolutions so npmrc overrides take effect. */
|
|
73
|
+
function clearRegistryTargetCache() {
|
|
74
|
+
targetByScope.clear();
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=registry-config.js.map
|
|
@@ -35,21 +35,32 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.checkForUpdate = checkForUpdate;
|
|
37
37
|
exports.checkForUpdateAsync = checkForUpdateAsync;
|
|
38
|
-
const child_process_1 = require("child_process");
|
|
39
38
|
const semver = __importStar(require("semver"));
|
|
40
39
|
const config_1 = require("../config");
|
|
40
|
+
/** Cap for the self-update check; it must never hold up an interactive session. */
|
|
41
|
+
const UPDATE_CHECK_TIMEOUT_MS = 5000;
|
|
41
42
|
/**
|
|
42
|
-
* Check if the current package version is outdated compared to npm registry
|
|
43
|
+
* Check if the current package version is outdated compared to npm registry.
|
|
44
|
+
*
|
|
45
|
+
* One small registry request (`{registry}/{name}/latest`) instead of spawning
|
|
46
|
+
* the npm CLI — `npm view` paid a few hundred ms of process startup for the
|
|
47
|
+
* same answer.
|
|
43
48
|
*/
|
|
44
49
|
async function checkForUpdate(packageName, currentVersion) {
|
|
45
50
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
51
|
+
const response = await fetch(`${config_1.NPM_REGISTRY_URL}/${encodeURIComponent(packageName)}/latest`, {
|
|
52
|
+
method: 'GET',
|
|
53
|
+
headers: { accept: 'application/json' },
|
|
54
|
+
signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS),
|
|
51
55
|
});
|
|
52
|
-
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const manifest = (await response.json());
|
|
60
|
+
const latestVersion = typeof manifest.version === 'string' ? manifest.version.trim() : '';
|
|
61
|
+
if (!semver.valid(latestVersion)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
53
64
|
// Compare versions
|
|
54
65
|
const isOutdated = semver.lt(currentVersion, latestVersion);
|
|
55
66
|
// Determine update command based on how the tool was likely invoked
|
|
@@ -69,24 +80,12 @@ async function checkForUpdate(packageName, currentVersion) {
|
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
82
|
/**
|
|
72
|
-
* Check for updates in the background without blocking
|
|
73
|
-
*
|
|
83
|
+
* Check for updates in the background without blocking.
|
|
84
|
+
*
|
|
85
|
+
* `checkForUpdate` self-caps at UPDATE_CHECK_TIMEOUT_MS and never rejects
|
|
86
|
+
* (every failure resolves to null), so no extra timeout race is needed here.
|
|
74
87
|
*/
|
|
75
88
|
function checkForUpdateAsync(packageName, currentVersion) {
|
|
76
|
-
return
|
|
77
|
-
// Set a timeout to prevent hanging
|
|
78
|
-
const timeout = setTimeout(() => {
|
|
79
|
-
resolve(null);
|
|
80
|
-
}, 5000);
|
|
81
|
-
checkForUpdate(packageName, currentVersion)
|
|
82
|
-
.then((result) => {
|
|
83
|
-
clearTimeout(timeout);
|
|
84
|
-
resolve(result);
|
|
85
|
-
})
|
|
86
|
-
.catch(() => {
|
|
87
|
-
clearTimeout(timeout);
|
|
88
|
-
resolve(null);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
89
|
+
return checkForUpdate(packageName, currentVersion);
|
|
91
90
|
}
|
|
92
91
|
//# sourceMappingURL=version-checker.js.map
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
var version_1 = require("./version");
|
|
5
|
-
Object.defineProperty(exports, "VersionUtils", { enumerable: true, get: function () { return version_1.VersionUtils; } });
|
|
6
|
-
Object.defineProperty(exports, "applyVersionPrefix", { enumerable: true, get: function () { return version_1.applyVersionPrefix; } });
|
|
7
|
-
Object.defineProperty(exports, "truncateMiddle", { enumerable: true, get: function () { return version_1.truncateMiddle; } });
|
|
8
|
-
Object.defineProperty(exports, "formatVersionDiff", { enumerable: true, get: function () { return version_1.formatVersionDiff; } });
|
|
3
|
+
exports.shouldDisableColor = exports.applyColorSetting = exports.wrapPlainText = exports.truncatePlainText = exports.stripAnsi = exports.getVisualLength = exports.TerminalInput = exports.RAW_SHOW_CURSOR = exports.RAW_EXIT_ALT_SCREEN = exports.ConsoleUtils = exports.CursorUtils = void 0;
|
|
9
4
|
var cursor_1 = require("./cursor");
|
|
10
5
|
Object.defineProperty(exports, "CursorUtils", { enumerable: true, get: function () { return cursor_1.CursorUtils; } });
|
|
11
6
|
Object.defineProperty(exports, "ConsoleUtils", { enumerable: true, get: function () { return cursor_1.ConsoleUtils; } });
|
|
@@ -18,4 +13,7 @@ Object.defineProperty(exports, "getVisualLength", { enumerable: true, get: funct
|
|
|
18
13
|
Object.defineProperty(exports, "stripAnsi", { enumerable: true, get: function () { return text_1.stripAnsi; } });
|
|
19
14
|
Object.defineProperty(exports, "truncatePlainText", { enumerable: true, get: function () { return text_1.truncatePlainText; } });
|
|
20
15
|
Object.defineProperty(exports, "wrapPlainText", { enumerable: true, get: function () { return text_1.wrapPlainText; } });
|
|
16
|
+
var color_1 = require("./color");
|
|
17
|
+
Object.defineProperty(exports, "applyColorSetting", { enumerable: true, get: function () { return color_1.applyColorSetting; } });
|
|
18
|
+
Object.defineProperty(exports, "shouldDisableColor", { enumerable: true, get: function () { return color_1.shouldDisableColor; } });
|
|
21
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -85,7 +85,8 @@ exports.TerminalInput = {
|
|
|
85
85
|
promptForImmediateConfirmation(prompt, defaultValue = true) {
|
|
86
86
|
return new Promise((resolve) => {
|
|
87
87
|
process.stdout.write(prompt);
|
|
88
|
-
|
|
88
|
+
// Replaced synchronously below before any keypress can call finish().
|
|
89
|
+
let cleanup = /* v8 ignore next */ () => { };
|
|
89
90
|
const finish = (value) => {
|
|
90
91
|
cleanup();
|
|
91
92
|
process.stdout.write('\n');
|
|
@@ -0,0 +1,48 @@
|
|
|
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.stripAnsi = stripAnsi;
|
|
7
|
+
exports.getVisualLength = getVisualLength;
|
|
8
|
+
exports.truncatePlainText = truncatePlainText;
|
|
9
|
+
exports.wrapPlainText = wrapPlainText;
|
|
10
|
+
const string_width_1 = __importDefault(require("string-width"));
|
|
11
|
+
const strip_ansi_1 = __importDefault(require("strip-ansi"));
|
|
12
|
+
const wrap_ansi_1 = __importDefault(require("wrap-ansi"));
|
|
13
|
+
const cli_truncate_1 = __importDefault(require("cli-truncate"));
|
|
14
|
+
// Thin wrappers over the battle-tested terminal-string stack (string-width,
|
|
15
|
+
// strip-ansi, wrap-ansi, cli-truncate). The previous hand-rolled versions
|
|
16
|
+
// handled emoji but had no East Asian Width tables, so CJK text was counted at
|
|
17
|
+
// width 1 and misaligned every column that contained it.
|
|
18
|
+
function stripAnsi(text) {
|
|
19
|
+
return (0, strip_ansi_1.default)(text);
|
|
20
|
+
}
|
|
21
|
+
/** Terminal columns `text` occupies: ANSI-aware, emoji- and CJK-correct. */
|
|
22
|
+
function getVisualLength(text) {
|
|
23
|
+
return (0, string_width_1.default)(text);
|
|
24
|
+
}
|
|
25
|
+
function truncatePlainText(text, maxWidth) {
|
|
26
|
+
if (maxWidth <= 0) {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
if (getVisualLength(text) <= maxWidth) {
|
|
30
|
+
return text;
|
|
31
|
+
}
|
|
32
|
+
if (maxWidth <= 3) {
|
|
33
|
+
return '.'.repeat(maxWidth);
|
|
34
|
+
}
|
|
35
|
+
return (0, cli_truncate_1.default)(text, maxWidth, { truncationCharacter: '...' });
|
|
36
|
+
}
|
|
37
|
+
function wrapPlainText(text, maxWidth) {
|
|
38
|
+
if (!text) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
if (maxWidth <= 0 || getVisualLength(text) <= maxWidth) {
|
|
42
|
+
return [text];
|
|
43
|
+
}
|
|
44
|
+
// Soft wrap: words longer than maxWidth get their own (overflowing) line,
|
|
45
|
+
// matching the previous behavior. ANSI codes are re-balanced per line.
|
|
46
|
+
return (0, wrap_ansi_1.default)(text, maxWidth).split('\n');
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
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("./domain"), exports);
|
|
18
|
+
__exportStar(require("./ui"), exports);
|
|
19
|
+
__exportStar(require("./streaming"), exports);
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -40,6 +40,7 @@ exports.parseVersions = parseVersions;
|
|
|
40
40
|
exports.isVersionOutdated = isVersionOutdated;
|
|
41
41
|
exports.getOptimizedRangeVersion = getOptimizedRangeVersion;
|
|
42
42
|
exports.findClosestMinorVersion = findClosestMinorVersion;
|
|
43
|
+
exports.applyVersionPrefix = applyVersionPrefix;
|
|
43
44
|
const semver = __importStar(require("semver"));
|
|
44
45
|
const manifest_1 = require("./manifest");
|
|
45
46
|
function extractMajorVersion(version) {
|
|
@@ -98,10 +99,14 @@ function getOptimizedRangeVersion(_packageName, currentRange, allVersions, lates
|
|
|
98
99
|
const satisfyingVersions = allVersions.filter((version) => {
|
|
99
100
|
try {
|
|
100
101
|
return semver.satisfies(version, currentRange);
|
|
102
|
+
// semver.satisfies swallows invalid input internally (returns false);
|
|
103
|
+
// safety net for future semver behavior changes only.
|
|
104
|
+
/* v8 ignore start */
|
|
101
105
|
}
|
|
102
106
|
catch {
|
|
103
107
|
return false;
|
|
104
108
|
}
|
|
109
|
+
/* v8 ignore stop */
|
|
105
110
|
});
|
|
106
111
|
if (satisfyingVersions.length === 0) {
|
|
107
112
|
return latestVersion;
|
|
@@ -169,4 +174,10 @@ function findClosestMinorVersion(installedVersion, allVersions) {
|
|
|
169
174
|
return null;
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
|
-
|
|
177
|
+
/** Re-apply the original specifier's range prefix (^, ~, >=, …) to a new version. */
|
|
178
|
+
function applyVersionPrefix(originalSpecifier, targetVersion) {
|
|
179
|
+
const prefixMatch = originalSpecifier.match(/^([^\d]+)/);
|
|
180
|
+
const prefix = prefixMatch ? prefixMatch[1] : '';
|
|
181
|
+
return prefix + targetVersion;
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=versions.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inup",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.7",
|
|
4
4
|
"description": "Interactive dependency upgrader for npm, yarn, pnpm & bun. Zero-config, monorepo-ready. Upgrade-interactive for every package manager.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -46,17 +46,24 @@
|
|
|
46
46
|
"@types/node": "^25.9.4",
|
|
47
47
|
"@types/semver": "^7.7.1",
|
|
48
48
|
"@vitest/coverage-v8": "^4.1.9",
|
|
49
|
+
"dependency-cruiser": "^18.0.0",
|
|
49
50
|
"prettier": "^3.9.4",
|
|
50
51
|
"typescript": "^6.0.3",
|
|
51
52
|
"vitest": "^4.1.9"
|
|
52
53
|
},
|
|
53
54
|
"dependencies": {
|
|
54
55
|
"chalk": "^5.6.2",
|
|
56
|
+
"cli-truncate": "^6.1.0",
|
|
55
57
|
"commander": "^15.0.0",
|
|
56
58
|
"env-paths": "^4.0.0",
|
|
57
59
|
"nanospinner": "^1.2.2",
|
|
60
|
+
"registry-auth-token": "^5.1.1",
|
|
58
61
|
"semver": "^7.8.5",
|
|
59
|
-
"
|
|
62
|
+
"string-width": "^8.2.1",
|
|
63
|
+
"strip-ansi": "^7.2.0",
|
|
64
|
+
"undici": "^8.6.0",
|
|
65
|
+
"wrap-ansi": "^10.0.0",
|
|
66
|
+
"yaml": "^2.9.0"
|
|
60
67
|
},
|
|
61
68
|
"engines": {
|
|
62
69
|
"node": ">=22.19.0"
|
|
@@ -66,13 +73,15 @@
|
|
|
66
73
|
"dev": "tsc --watch",
|
|
67
74
|
"start": "node dist/cli.js",
|
|
68
75
|
"link": "pnpm build && pnpm add -g .",
|
|
69
|
-
"format": "prettier --write src/**/*.ts",
|
|
70
|
-
"format:check": "prettier --check src/**/*.ts",
|
|
76
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
77
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
71
78
|
"demo:record": "bash docs/demo/record-demo.sh",
|
|
72
79
|
"demo:setup": "cd docs/demo-project && pnpm install",
|
|
73
80
|
"test": "vitest run",
|
|
74
81
|
"test:watch": "vitest",
|
|
75
|
-
"test:coverage": "vitest run --coverage",
|
|
76
|
-
"
|
|
82
|
+
"test:coverage": "vitest run --coverage --reporter=default --reporter=json --outputFile.json=coverage/test-results.json",
|
|
83
|
+
"lint:deps": "depcruise src --config .dependency-cruiser.cjs",
|
|
84
|
+
"docs:keys": "UPDATE_README=1 vitest run keymap-readme",
|
|
85
|
+
"docs:badges": "pnpm test:coverage && UPDATE_README=1 vitest run badges-readme"
|
|
77
86
|
}
|
|
78
87
|
}
|
package/dist/core/index.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Core business logic
|
|
4
|
-
*/
|
|
5
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k;
|
|
7
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
-
}
|
|
11
|
-
Object.defineProperty(o, k2, desc);
|
|
12
|
-
}) : (function(o, m, k, k2) {
|
|
13
|
-
if (k2 === undefined) k2 = k;
|
|
14
|
-
o[k2] = m[k];
|
|
15
|
-
}));
|
|
16
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
-
};
|
|
19
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
__exportStar(require("./upgrade-runner"), exports);
|
|
21
|
-
__exportStar(require("./package-detector"), exports);
|
|
22
|
-
__exportStar(require("./upgrader"), exports);
|
|
23
|
-
//# sourceMappingURL=index.js.map
|