inup 1.6.6 → 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.
Files changed (109) hide show
  1. package/README.md +16 -0
  2. package/dist/{interactive-ui.js → app/interactive-ui.js} +32 -21
  3. package/dist/{core → app}/upgrade-runner.js +12 -11
  4. package/dist/cli.js +12 -10
  5. package/dist/{services → features/audit}/background-audit.js +2 -2
  6. package/dist/features/{headless/vulnerability-audit.js → audit/headless-audit.js} +9 -5
  7. package/dist/features/audit/index.js +36 -0
  8. package/dist/{ui/presenters/vulnerability.js → features/audit/presenter.js} +1 -1
  9. package/dist/{ui/controllers → features/audit}/vulnerability-audit-controller.js +12 -6
  10. package/dist/{services → features/audit}/vulnerability-checker.js +11 -8
  11. package/dist/features/changelog/clients/github-client.js +35 -17
  12. package/dist/features/changelog/clients/npm-registry-client.js +16 -2
  13. package/dist/features/changelog/index.js +1 -1
  14. package/dist/features/changelog/services/package-metadata-service.js +6 -2
  15. package/dist/features/changelog/services/release-notes-service.js +6 -10
  16. package/dist/features/changelog/types.js +3 -0
  17. package/dist/features/debug/index.js +3 -4
  18. package/dist/features/debug/{services/perf-logger.js → perf-logger.js} +1 -1
  19. package/dist/features/debug/types.js +3 -0
  20. package/dist/features/headless/headless-runner.js +11 -11
  21. package/dist/features/headless/index.js +1 -4
  22. package/dist/features/headless/report.js +2 -0
  23. package/dist/features/interactive/controllers/index.js +6 -0
  24. package/dist/{ui → features/interactive}/controllers/package-info-modal-controller.js +3 -3
  25. package/dist/features/interactive/index.js +21 -0
  26. package/dist/{ui → features/interactive}/input-handler.js +3 -3
  27. package/dist/{ui → features/interactive}/modal/layout.js +2 -2
  28. package/dist/{ui/renderer/package-list.js → features/interactive/modal/package-info-sections/index.js} +3 -3
  29. package/dist/{ui → features/interactive}/modal/package-info-sections/sections.js +53 -22
  30. package/dist/{ui → features/interactive}/modal/package-info-sections/text.js +5 -5
  31. package/dist/{ui → features/interactive}/modal/package-info.js +31 -13
  32. package/dist/features/interactive/modal/types.js +3 -0
  33. package/dist/{ui → features/interactive}/presenters/health.js +2 -2
  34. package/dist/{ui → features/interactive}/renderer/index.js +0 -1
  35. package/dist/{types.js → features/interactive/renderer/package-list/index.js} +3 -4
  36. package/dist/{ui → features/interactive}/renderer/package-list/interface.js +9 -10
  37. package/dist/{ui → features/interactive}/renderer/package-list/rows.js +24 -17
  38. package/dist/features/{debug → interactive}/renderer/performance-modal.js +1 -1
  39. package/dist/{ui/utils/version.js → features/interactive/renderer/version-format.js} +6 -10
  40. package/dist/{ui → features/interactive}/session/index.js +2 -1
  41. package/dist/{ui → features/interactive}/session/interactive-session.js +39 -19
  42. package/dist/{ui → features/interactive}/session/selection-state-builder.js +20 -5
  43. package/dist/{ui → features/interactive}/state/filter-manager.js +2 -2
  44. package/dist/{ui → features/interactive}/state/state-manager.js +6 -4
  45. package/dist/{ui → features/interactive}/state/theme-manager.js +8 -4
  46. package/dist/{ui → features/interactive}/themes-colors.js +5 -2
  47. package/dist/features/upgrade/index.js +8 -0
  48. package/dist/{core → features/upgrade}/package-detector.js +74 -33
  49. package/dist/{core → features/upgrade}/upgrader.js +72 -14
  50. package/dist/index.js +2 -2
  51. package/dist/{config → shared/config}/package-meta.js +1 -1
  52. package/dist/{config → shared/config}/project-config.js +4 -1
  53. package/dist/{utils/config.js → shared/config/user-config.js} +3 -3
  54. package/dist/{ui/modal/package-info-sections.js → shared/fs/index.js} +4 -3
  55. package/dist/{utils/filesystem → shared/fs}/paths.js +3 -3
  56. package/dist/{utils/filesystem → shared/fs}/scan.js +5 -2
  57. package/dist/{services → shared}/http/etag-store.js +56 -4
  58. package/dist/{utils → shared}/local-env.js +1 -1
  59. package/dist/{services/package-manager-detector.js → shared/package-manager.js} +1 -1
  60. package/dist/shared/pnpm-catalogs.js +138 -0
  61. package/dist/{services → shared/registry}/npm-registry.js +48 -29
  62. package/dist/shared/registry/registry-config.js +76 -0
  63. package/dist/{services → shared/registry}/version-checker.js +24 -25
  64. package/dist/{ui/utils → shared/terminal}/index.js +4 -6
  65. package/dist/{ui/utils → shared/terminal}/terminal-input.js +2 -1
  66. package/dist/shared/terminal/text.js +48 -0
  67. package/dist/shared/types/index.js +20 -0
  68. package/dist/{utils/version.js → shared/versions.js} +12 -1
  69. package/package.json +15 -6
  70. package/dist/core/index.js +0 -23
  71. package/dist/features/changelog/types/changelog.types.js +0 -3
  72. package/dist/features/debug/types/debug.types.js +0 -3
  73. package/dist/services/index.js +0 -25
  74. package/dist/ui/controllers/index.js +0 -8
  75. package/dist/ui/index.js +0 -27
  76. package/dist/ui/presenters/index.js +0 -11
  77. package/dist/ui/utils/text.js +0 -76
  78. package/dist/utils/filesystem.js +0 -20
  79. package/dist/utils/index.js +0 -34
  80. /package/dist/{ui/modal → features/audit}/types.js +0 -0
  81. /package/dist/features/debug/{services/performance-tracker.js → performance-tracker.js} +0 -0
  82. /package/dist/{ui → features/interactive}/keymap.js +0 -0
  83. /package/dist/{ui → features/interactive}/modal/index.js +0 -0
  84. /package/dist/{ui → features/interactive}/modal/package-info-sections/release-notes.js +0 -0
  85. /package/dist/{ui → features/interactive}/modal/theme-selector.js +0 -0
  86. /package/dist/{ui → features/interactive}/renderer/confirmation.js +0 -0
  87. /package/dist/{ui → features/interactive}/renderer/help-modal.js +0 -0
  88. /package/dist/{ui → features/interactive}/session/action-dispatcher.js +0 -0
  89. /package/dist/{ui → features/interactive}/state/index.js +0 -0
  90. /package/dist/{ui → features/interactive}/state/modal-manager.js +0 -0
  91. /package/dist/{ui → features/interactive}/state/navigation-manager.js +0 -0
  92. /package/dist/{ui → features/interactive}/themes.js +0 -0
  93. /package/dist/{config → shared/config}/constants.js +0 -0
  94. /package/dist/{config → shared/config}/index.js +0 -0
  95. /package/dist/{utils → shared}/debug-logger.js +0 -0
  96. /package/dist/{utils → shared}/engines.js +0 -0
  97. /package/dist/{utils → shared}/exec.js +0 -0
  98. /package/dist/{utils/filesystem → shared/fs}/io.js +0 -0
  99. /package/dist/{utils → shared}/git.js +0 -0
  100. /package/dist/{services → shared}/http/adaptive-controller.js +0 -0
  101. /package/dist/{services → shared}/http/inflight.js +0 -0
  102. /package/dist/{services → shared}/http/resizable-semaphore.js +0 -0
  103. /package/dist/{services → shared}/http/retry.js +0 -0
  104. /package/dist/{utils → shared}/manifest.js +0 -0
  105. /package/dist/{utils → shared/terminal}/color.js +0 -0
  106. /package/dist/{ui/utils → shared/terminal}/cursor.js +0 -0
  107. /package/dist/{types → shared/types}/domain.js +0 -0
  108. /package/dist/{types → shared/types}/streaming.js +0 -0
  109. /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 version_1 = require("../utils/version");
13
- const retry_1 = require("./http/retry");
14
- const inflight_1 = require("./http/inflight");
15
- const resizable_semaphore_1 = require("./http/resizable-semaphore");
16
- const adaptive_controller_1 = require("./http/adaptive-controller");
17
- const etag_store_1 = require("./http/etag-store");
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
- const registryOrigin = new URL(config_1.NPM_REGISTRY_URL).origin;
20
- const registryPathPrefix = new URL(config_1.NPM_REGISTRY_URL).pathname.replace(/\/$/, '');
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 registryPool = new undici_1.Pool(registryOrigin, {
32
- connections: config_1.POOL_CONNECTIONS,
33
- pipelining: 1,
34
- keepAliveTimeout: 30_000,
35
- keepAliveMaxTimeout: 600_000,
36
- headersTimeout: 30_000,
37
- bodyTimeout: 0,
38
- connectTimeout: 15_000,
39
- allowH2: false,
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 `${registryPathPrefix}/${encodedName}`;
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
- const cached = (0, etag_store_1.readEtag)(path);
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 registryPool.request({
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, version_1.parseVersions)(decoded.toString('utf8'));
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)(path, etag.toString(), data);
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
- const path = encodeRegistryPath(packageName);
158
- const outcome = await fetchFromRegistryWithRetries(path, onAttempt);
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
- // Use npm view to get the latest version from registry
47
- const result = (0, child_process_1.execSync)(`npm view ${packageName} version`, {
48
- encoding: 'utf-8',
49
- timeout: config_1.REQUEST_TIMEOUT,
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
- const latestVersion = result.trim();
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
- * Resolves immediately, result available via promise
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 new Promise((resolve) => {
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.wrapPlainText = exports.truncatePlainText = exports.stripAnsi = exports.getVisualLength = exports.TerminalInput = exports.RAW_SHOW_CURSOR = exports.RAW_EXIT_ALT_SCREEN = exports.ConsoleUtils = exports.CursorUtils = exports.formatVersionDiff = exports.truncateMiddle = exports.applyVersionPrefix = exports.VersionUtils = void 0;
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
- let cleanup = () => { };
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
- //# sourceMappingURL=version.js.map
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.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
- "undici": "^8.5.0"
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
- "docs:keys": "UPDATE_README=1 vitest run keymap-readme"
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
  }
@@ -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
@@ -1,3 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=changelog.types.js.map
@@ -1,3 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=debug.types.js.map