inup 1.5.6 → 1.6.2

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 (47) hide show
  1. package/README.md +30 -1
  2. package/dist/cli.js +42 -11
  3. package/dist/config/constants.js +6 -7
  4. package/dist/config/project-config.js +5 -0
  5. package/dist/core/package-detector.js +49 -1
  6. package/dist/core/upgrade-runner.js +9 -0
  7. package/dist/core/upgrader.js +9 -3
  8. package/dist/features/changelog/services/package-metadata-service.js +2 -13
  9. package/dist/features/changelog/services/release-notes-service.js +0 -27
  10. package/dist/features/debug/index.js +1 -0
  11. package/dist/features/debug/renderer/performance-modal.js +17 -1
  12. package/dist/features/debug/services/perf-logger.js +129 -0
  13. package/dist/features/debug/services/performance-tracker.js +15 -3
  14. package/dist/features/headless/headless-runner.js +69 -0
  15. package/dist/features/headless/index.js +27 -0
  16. package/dist/features/headless/report.js +64 -0
  17. package/dist/features/headless/types.js +6 -0
  18. package/dist/features/headless/vulnerability-audit.js +106 -0
  19. package/dist/interactive-ui.js +4 -2
  20. package/dist/services/http/adaptive-controller.js +153 -0
  21. package/dist/services/http/etag-store.js +91 -0
  22. package/dist/services/http/resizable-semaphore.js +71 -0
  23. package/dist/services/http/retry.js +23 -1
  24. package/dist/services/index.js +0 -1
  25. package/dist/services/npm-registry.js +198 -97
  26. package/dist/services/package-manager-detector.js +6 -3
  27. package/dist/ui/modal/package-info-sections/sections.js +24 -0
  28. package/dist/ui/presenters/health.js +24 -0
  29. package/dist/ui/renderer/package-list/rows.js +8 -3
  30. package/dist/ui/session/selection-state-builder.js +7 -2
  31. package/dist/ui/themes-colors.js +12 -3
  32. package/dist/ui/utils/cursor.js +9 -3
  33. package/dist/utils/color.js +38 -0
  34. package/dist/utils/debug-logger.js +8 -1
  35. package/dist/utils/engines.js +63 -0
  36. package/dist/utils/filesystem/io.js +16 -0
  37. package/dist/utils/filesystem/scan.js +82 -7
  38. package/dist/utils/index.js +4 -0
  39. package/dist/utils/local-env.js +81 -0
  40. package/dist/utils/manifest.js +35 -0
  41. package/dist/utils/version.js +9 -2
  42. package/package.json +8 -8
  43. package/dist/services/cache-manager.js +0 -95
  44. package/dist/services/jsdelivr/client.js +0 -191
  45. package/dist/services/jsdelivr/manifest.js +0 -136
  46. package/dist/services/jsdelivr-registry.js +0 -9
  47. package/dist/services/persistent-cache.js +0 -237
@@ -85,6 +85,8 @@ function createSelectionStates(packages, getCachedSummary, previousSelections, i
85
85
  hasRangeUpdate: pkg.hasRangeUpdate,
86
86
  hasMajorUpdate: pkg.hasMajorUpdate,
87
87
  type: pkg.type,
88
+ deprecated: pkg.deprecated,
89
+ enginesNode: pkg.enginesNode,
88
90
  vulnerability: getCachedSummary(pkg.name, pkg.currentVersion, pkg.type),
89
91
  allVersions: pkg.allVersions,
90
92
  };
@@ -120,13 +122,16 @@ function createPendingSelectionStates(packages, getCachedSummary, previousSelect
120
122
  };
121
123
  });
122
124
  }
123
- function createUpgradeChoices(selectedStates) {
125
+ function createUpgradeChoices(selectedStates, saveExact = false) {
124
126
  const choices = [];
125
127
  selectedStates
126
128
  .filter((state) => state.loadState === 'ready' && state.selectedOption !== 'none')
127
129
  .forEach((state) => {
128
130
  const targetVersion = state.selectedOption === 'range' ? state.rangeVersion : state.latestVersion;
129
- const targetVersionWithPrefix = (0, utils_1.applyVersionPrefix)(state.currentVersionSpecifier, targetVersion);
131
+ // Preserve the original range prefix (^/~) by default; --save-exact writes the bare version.
132
+ const targetVersionWithPrefix = saveExact
133
+ ? targetVersion
134
+ : (0, utils_1.applyVersionPrefix)(state.currentVersionSpecifier, targetVersion);
130
135
  const pathsToUpdate = state.packageJsonPaths || [state.packageJsonPath];
131
136
  pathsToUpdate.forEach((packageJsonPath) => {
132
137
  choices.push({
@@ -182,18 +182,27 @@ function hexToRgb(hex) {
182
182
  : { r: 0, g: 0, b: 0 };
183
183
  }
184
184
  /**
185
- * Get ANSI escape code to set terminal background color
185
+ * Get ANSI escape code to set terminal background color.
186
+ *
187
+ * Returns an empty string when color output is disabled (`--no-color`/`NO_COLOR`
188
+ * set `chalk.level` to 0), so the TUI inherits the user's own terminal
189
+ * background instead of painting over it. This escape is raw (not produced by
190
+ * chalk), so it must be gated explicitly.
186
191
  */
187
192
  function getTerminalBgColorCode() {
193
+ if (chalk_1.default.level === 0) {
194
+ return '';
195
+ }
188
196
  const hex = getThemeBgColor();
189
197
  const rgb = hexToRgb(hex);
190
198
  return `\x1b[48;2;${rgb.r};${rgb.g};${rgb.b}m`;
191
199
  }
192
200
  /**
193
- * Get ANSI escape code to reset terminal colors
201
+ * Get ANSI escape code to reset terminal colors. Empty when color is disabled,
202
+ * so a `--no-color` run emits no escape sequences at all.
194
203
  */
195
204
  function getTerminalResetCode() {
196
- return '\x1b[0m';
205
+ return chalk_1.default.level === 0 ? '' : '\x1b[0m';
197
206
  }
198
207
  const BRAND_COLORS = [chalk_1.default.red, chalk_1.default.yellow, chalk_1.default.blue, chalk_1.default.magenta];
199
208
  function coloredInupLogo() {
@@ -67,16 +67,22 @@ exports.ConsoleUtils = {
67
67
  */
68
68
  LINE_WIDTH: 80,
69
69
  /**
70
- * Show a progress message on the current line (overwrites previous content)
70
+ * Show a progress message on the current line (overwrites previous content).
71
+ * Written to stderr so stdout stays clean for --json / piped output, and only
72
+ * when stderr is a TTY — the \r animation is just noise in a redirected log.
71
73
  */
72
74
  showProgress(message) {
73
- process.stdout.write(`\r${' '.repeat(exports.ConsoleUtils.LINE_WIDTH)}\r${message}`);
75
+ if (!process.stderr.isTTY)
76
+ return;
77
+ process.stderr.write(`\r${' '.repeat(exports.ConsoleUtils.LINE_WIDTH)}\r${message}`);
74
78
  },
75
79
  /**
76
80
  * Clear the current progress line
77
81
  */
78
82
  clearProgress() {
79
- process.stdout.write('\r' + ' '.repeat(exports.ConsoleUtils.LINE_WIDTH) + '\r');
83
+ if (!process.stderr.isTTY)
84
+ return;
85
+ process.stderr.write('\r' + ' '.repeat(exports.ConsoleUtils.LINE_WIDTH) + '\r');
80
86
  },
81
87
  };
82
88
  //# sourceMappingURL=cursor.js.map
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.shouldDisableColor = shouldDisableColor;
7
+ exports.applyColorSetting = applyColorSetting;
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ /**
10
+ * Decide whether colored output should be disabled.
11
+ *
12
+ * Precedence (highest first):
13
+ * 1. An explicit `--no-color` flag (`colorFlag === false`) always wins.
14
+ * 2. `FORCE_COLOR` keeps colors on.
15
+ * 3. `NO_COLOR` (any non-empty value) disables them — the de-facto standard.
16
+ *
17
+ * chalk v5 already honors the env vars on its own, but the explicit flag does
18
+ * not flow through automatically, so we resolve the final intent here.
19
+ */
20
+ function shouldDisableColor(colorFlag, env = process.env) {
21
+ if (colorFlag === false) {
22
+ return true;
23
+ }
24
+ if (env.FORCE_COLOR) {
25
+ return false;
26
+ }
27
+ return Boolean(env.NO_COLOR);
28
+ }
29
+ /**
30
+ * Apply the resolved color intent to chalk's global level. Call once at startup
31
+ * before anything renders.
32
+ */
33
+ function applyColorSetting(colorFlag, env = process.env) {
34
+ if (shouldDisableColor(colorFlag, env)) {
35
+ chalk_1.default.level = 0;
36
+ }
37
+ }
38
+ //# sourceMappingURL=color.js.map
@@ -5,6 +5,7 @@ exports.enableDebugLogging = enableDebugLogging;
5
5
  exports.isDebugEnabled = isDebugEnabled;
6
6
  exports.getDebugLogPath = getDebugLogPath;
7
7
  const fs_1 = require("fs");
8
+ const os_1 = require("os");
8
9
  const path_1 = require("path");
9
10
  let _enabled = false;
10
11
  let _logFile = null;
@@ -18,7 +19,13 @@ function getLogFile() {
18
19
  if (!_logFile) {
19
20
  const d = new Date();
20
21
  const dateStr = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
21
- _logFile = (0, path_1.join)(`inup-debug-${dateStr}.log`);
22
+ // Write to the OS temp dir (matches the path advertised in --help), not the
23
+ // current working directory — a debug log must never litter the user's repo.
24
+ const dir = (0, path_1.join)((0, os_1.tmpdir)(), 'inup');
25
+ if (!(0, fs_1.existsSync)(dir)) {
26
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
27
+ }
28
+ _logFile = (0, path_1.join)(dir, `inup-debug-${dateStr}.log`);
22
29
  // Write a header so the file is easy to identify
23
30
  (0, fs_1.writeFileSync)(_logFile, `=== inup debug log started at ${timestamp()} ===\n`, { flag: 'a' });
24
31
  }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.checkNodeEngineCompatibility = checkNodeEngineCompatibility;
37
+ const semver = __importStar(require("semver"));
38
+ /**
39
+ * Check whether the running Node version satisfies a package's declared
40
+ * `engines.node` range. Returns a short human-readable warning when it does
41
+ * not, or `null` when compatible (or when the inputs are unusable).
42
+ *
43
+ * Best-effort: an unparseable range is treated as "no opinion" (null) rather
44
+ * than a false warning.
45
+ */
46
+ function checkNodeEngineCompatibility(requiredRange, currentNodeVersion = process.versions.node) {
47
+ if (!requiredRange) {
48
+ return null;
49
+ }
50
+ const range = semver.validRange(requiredRange, { loose: true });
51
+ if (!range) {
52
+ return null;
53
+ }
54
+ const current = semver.coerce(currentNodeVersion);
55
+ if (!current) {
56
+ return null;
57
+ }
58
+ if (semver.satisfies(current, range, { includePrerelease: true })) {
59
+ return null;
60
+ }
61
+ return `requires Node ${requiredRange}, you're on ${current.version}`;
62
+ }
63
+ //# sourceMappingURL=engines.js.map
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readPackageJson = readPackageJson;
4
+ exports.detectJsonFormat = detectJsonFormat;
4
5
  exports.readPackageJsonAsync = readPackageJsonAsync;
5
6
  exports.collectAllDependencies = collectAllDependencies;
6
7
  exports.collectAllDependenciesAsync = collectAllDependenciesAsync;
@@ -15,6 +16,21 @@ function readPackageJson(path) {
15
16
  throw new Error(`Failed to read package.json: ${error}`);
16
17
  }
17
18
  }
19
+ /**
20
+ * Detect the indentation and trailing-newline style of a raw JSON document so a
21
+ * re-serialized version can preserve the original formatting instead of normalizing it.
22
+ *
23
+ * The first indented line's leading whitespace is exactly one indent unit; using it verbatim
24
+ * as the JSON.stringify indent round-trips tabs, 2-space, and 4-space without branching on type.
25
+ * Minified/single-line files (no indented line) fall back to 2 spaces, matching prior behavior.
26
+ */
27
+ function detectJsonFormat(raw) {
28
+ const match = raw.match(/\n([ \t]+)\S/);
29
+ return {
30
+ indent: match ? match[1] : 2,
31
+ trailingNewline: raw.endsWith('\n'),
32
+ };
33
+ }
18
34
  async function readPackageJsonAsync(path) {
19
35
  try {
20
36
  const content = await fs_2.promises.readFile(path, 'utf-8');
@@ -16,15 +16,85 @@ const SKIP_DIRS = new Set([
16
16
  'esm',
17
17
  'cjs',
18
18
  ]);
19
- function shouldSkipDirectory(name) {
20
- return name.startsWith('.') || SKIP_DIRS.has(name);
19
+ /**
20
+ * Skip dirs that are ambiguous source-vs-build directories where a real package may legitimately
21
+ * live. Only these trigger the "silently skipped a package" warning — node_modules and build-output
22
+ * dirs (dist/build/coverage/out) routinely contain package.json files and would be pure noise.
23
+ */
24
+ const WARN_SKIP_DIRS = new Set(['lib', 'es', 'esm', 'cjs']);
25
+ /** Effective skip set: the defaults minus any directory the caller opted back into via `scanDirs`. */
26
+ function buildSkipSet(scanDirs) {
27
+ if (!scanDirs || scanDirs.length === 0) {
28
+ return SKIP_DIRS;
29
+ }
30
+ const skip = new Set(SKIP_DIRS);
31
+ for (const dir of scanDirs) {
32
+ skip.delete(dir);
33
+ }
34
+ return skip;
35
+ }
36
+ function classifyDirectory(name, skipSet) {
37
+ if (name.startsWith('.'))
38
+ return 'hidden';
39
+ if (skipSet.has(name))
40
+ return 'skip-dir';
41
+ return null;
21
42
  }
22
- function findAllPackageJsonFiles(rootDir = process.cwd(), excludePatterns = [], maxDepth = 10, onProgress) {
43
+ /**
44
+ * Cheaply decide whether a pruned directory looks like it holds a real package — a package.json
45
+ * directly inside it, or inside any immediate child (the common `lib/<pkg>/package.json` monorepo
46
+ * layout). Stays shallow (depth 1) so detecting a skip doesn't re-walk the subtree we just pruned.
47
+ */
48
+ function prunedDirHoldsPackage(dir) {
49
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, 'package.json'))) {
50
+ return true;
51
+ }
52
+ let entries;
53
+ try {
54
+ entries = (0, fs_1.readdirSync)(dir);
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ for (const entry of entries) {
60
+ if (entry.startsWith('.'))
61
+ continue;
62
+ const child = (0, path_1.join)(dir, entry);
63
+ try {
64
+ if ((0, fs_1.statSync)(child).isDirectory() && (0, fs_1.existsSync)((0, path_1.join)(child, 'package.json'))) {
65
+ return true;
66
+ }
67
+ }
68
+ catch {
69
+ // Skip children we can't stat
70
+ }
71
+ }
72
+ return false;
73
+ }
74
+ /**
75
+ * Decide whether to descend into a directory, and notify when one is pruned by the default skip
76
+ * list despite containing a package.json (so the caller can surface a "silently skipped" warning).
77
+ */
78
+ function shouldTraverse(name, fullPath, relativePath, skipSet, onSkippedPackageDir) {
79
+ const reason = classifyDirectory(name, skipSet);
80
+ if (reason === null) {
81
+ return true;
82
+ }
83
+ if (reason === 'skip-dir' &&
84
+ WARN_SKIP_DIRS.has(name) &&
85
+ onSkippedPackageDir &&
86
+ prunedDirHoldsPackage(fullPath)) {
87
+ onSkippedPackageDir(relativePath);
88
+ }
89
+ return false;
90
+ }
91
+ function findAllPackageJsonFiles(rootDir = process.cwd(), excludePatterns = [], maxDepth = 10, onProgress, options = {}) {
23
92
  const packageJsonFiles = [];
24
93
  const visitedPaths = new Set();
25
94
  let directoriesScanned = 0;
26
95
  let lastProgressAt = 0;
27
96
  const progressIntervalMs = 250;
97
+ const skipSet = buildSkipSet(options.scanDirs);
28
98
  const excludeRegexes = excludePatterns.map((pattern) => new RegExp(pattern, 'i'));
29
99
  function shouldExcludePath(relativePath) {
30
100
  return excludeRegexes.some((regex) => regex.test(relativePath));
@@ -72,8 +142,10 @@ function findAllPackageJsonFiles(rootDir = process.cwd(), excludePatterns = [],
72
142
  // Skip files/dirs we can't stat (broken symlinks, permission issues)
73
143
  continue;
74
144
  }
75
- if (stat.isDirectory() && !shouldSkipDirectory(file)) {
76
- traverseDirectory(fullPath, depth + 1);
145
+ if (stat.isDirectory()) {
146
+ if (shouldTraverse(file, fullPath, relativePath, skipSet, options.onSkippedPackageDir)) {
147
+ traverseDirectory(fullPath, depth + 1);
148
+ }
77
149
  }
78
150
  else if (file === 'package.json' && stat.isFile()) {
79
151
  packageJsonFiles.push(fullPath);
@@ -94,6 +166,7 @@ async function findAllPackageJsonFilesAsync(rootDir = process.cwd(), excludePatt
94
166
  let lastProgressAt = 0;
95
167
  const progressIntervalMs = 250;
96
168
  const concurrency = Math.max(1, Math.min(options.concurrency ?? 16, 64));
169
+ const skipSet = buildSkipSet(options.scanDirs);
97
170
  const excludeRegexes = excludePatterns.map((pattern) => new RegExp(pattern, 'i'));
98
171
  function shouldExcludePath(relativePath) {
99
172
  return excludeRegexes.some((regex) => regex.test(relativePath));
@@ -167,8 +240,10 @@ async function findAllPackageJsonFilesAsync(rootDir = process.cwd(), excludePatt
167
240
  catch {
168
241
  continue;
169
242
  }
170
- if (stat.isDirectory() && !shouldSkipDirectory(file)) {
171
- schedule(fullPath, depth + 1);
243
+ if (stat.isDirectory()) {
244
+ if (shouldTraverse(file, fullPath, relativePath, skipSet, options.onSkippedPackageDir)) {
245
+ schedule(fullPath, depth + 1);
246
+ }
172
247
  }
173
248
  else if (file === 'package.json' && stat.isFile()) {
174
249
  packageJsonFiles.push(fullPath);
@@ -23,6 +23,10 @@ __exportStar(require("./exec"), exports);
23
23
  __exportStar(require("./git"), exports);
24
24
  __exportStar(require("./version"), exports);
25
25
  __exportStar(require("./debug-logger"), exports);
26
+ __exportStar(require("./local-env"), exports);
27
+ __exportStar(require("./color"), exports);
28
+ __exportStar(require("./engines"), exports);
29
+ __exportStar(require("./manifest"), exports);
26
30
  // Re-export async functions for convenience
27
31
  var filesystem_1 = require("./filesystem");
28
32
  Object.defineProperty(exports, "readPackageJsonAsync", { enumerable: true, get: function () { return filesystem_1.readPackageJsonAsync; } });
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadInupLocalEnv = loadInupLocalEnv;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ /**
7
+ * Loads a gitignored `.env.local` from the inup repo itself (NOT the cwd), so
8
+ * developer-only toggles can be "set once" and apply to every `inup` run in any
9
+ * project — without committing anything or touching the shell profile.
10
+ *
11
+ * The repo is located by walking UP from this file's own directory until a
12
+ * `.env.local` is found, so it works regardless of nesting depth (src/utils when
13
+ * type-stripped, dist/utils when compiled) and resolves the inup repo even when
14
+ * the binary is linked and invoked from a different project directory.
15
+ *
16
+ * Fully optional and best-effort: if the file is absent or unreadable, nothing
17
+ * happens and the run proceeds exactly as before. Existing process env always
18
+ * wins, so a one-off `INUP_PERF=0 inup` still overrides the file.
19
+ */
20
+ const ENV_FILE_NAME = '.env.local';
21
+ /** Walk upward from this file's dir to find the .env.local; null if none. */
22
+ function findEnvFile() {
23
+ let dir = __dirname;
24
+ const root = (0, path_1.parse)(dir).root;
25
+ // Cap the walk so a missing file can never loop unbounded.
26
+ for (let i = 0; i < 12; i++) {
27
+ const candidate = (0, path_1.join)(dir, ENV_FILE_NAME);
28
+ if ((0, fs_1.existsSync)(candidate))
29
+ return candidate;
30
+ if (dir === root)
31
+ break;
32
+ dir = (0, path_1.dirname)(dir);
33
+ }
34
+ return null;
35
+ }
36
+ /** Parse a minimal KEY=VALUE env file. Ignores blanks and `#` comments. */
37
+ function parseEnv(contents) {
38
+ const out = {};
39
+ for (const rawLine of contents.split(/\r?\n/)) {
40
+ const line = rawLine.trim();
41
+ if (!line || line.startsWith('#'))
42
+ continue;
43
+ const eq = line.indexOf('=');
44
+ if (eq === -1)
45
+ continue;
46
+ const key = line.slice(0, eq).trim();
47
+ if (!key)
48
+ continue;
49
+ let value = line.slice(eq + 1).trim();
50
+ // Strip a single layer of matching quotes.
51
+ if ((value.startsWith('"') && value.endsWith('"')) ||
52
+ (value.startsWith("'") && value.endsWith("'"))) {
53
+ value = value.slice(1, -1);
54
+ }
55
+ out[key] = value;
56
+ }
57
+ return out;
58
+ }
59
+ /**
60
+ * Apply `<inup-repo>/.env.local` into process.env. Existing values are never
61
+ * overwritten (real env / one-off overrides win). Returns the path if loaded.
62
+ */
63
+ function loadInupLocalEnv() {
64
+ try {
65
+ const file = findEnvFile();
66
+ if (!file)
67
+ return null;
68
+ const parsed = parseEnv((0, fs_1.readFileSync)(file, 'utf8'));
69
+ for (const [key, value] of Object.entries(parsed)) {
70
+ if (process.env[key] === undefined) {
71
+ process.env[key] = value;
72
+ }
73
+ }
74
+ return file;
75
+ }
76
+ catch {
77
+ // Best-effort: never let a dev convenience break a real run.
78
+ return null;
79
+ }
80
+ }
81
+ //# sourceMappingURL=local-env.js.map
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * Helpers for reading optional health signals out of an npm version manifest
4
+ * (the per-version object inside an abbreviated packument, or a full version
5
+ * document). Both fields are advisory and frequently absent.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.normalizeDeprecatedMessage = normalizeDeprecatedMessage;
9
+ exports.extractEnginesNode = extractEnginesNode;
10
+ /**
11
+ * npm represents deprecation as either a string message or the boolean `true`.
12
+ * Normalize both into a displayable message, or `undefined` when not deprecated.
13
+ */
14
+ function normalizeDeprecatedMessage(value) {
15
+ if (typeof value === 'string' && value.trim()) {
16
+ return value;
17
+ }
18
+ if (value === true) {
19
+ return 'This version is deprecated.';
20
+ }
21
+ return undefined;
22
+ }
23
+ /**
24
+ * Extract the `engines.node` range from a manifest's `engines` object, if any.
25
+ */
26
+ function extractEnginesNode(engines) {
27
+ if (typeof engines === 'object' && engines !== null) {
28
+ const node = engines.node;
29
+ if (typeof node === 'string' && node.trim()) {
30
+ return node;
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+ //# sourceMappingURL=manifest.js.map
@@ -41,6 +41,7 @@ exports.isVersionOutdated = isVersionOutdated;
41
41
  exports.getOptimizedRangeVersion = getOptimizedRangeVersion;
42
42
  exports.findClosestMinorVersion = findClosestMinorVersion;
43
43
  const semver = __importStar(require("semver"));
44
+ const manifest_1 = require("./manifest");
44
45
  function extractMajorVersion(version) {
45
46
  if (!version)
46
47
  return null;
@@ -62,10 +63,16 @@ function versionIdentity(version) {
62
63
  }
63
64
  function parseVersions(raw) {
64
65
  const data = JSON.parse(raw);
65
- const allVersions = Object.keys(data.versions || {}).filter((v) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(v));
66
+ const versions = data.versions || {};
67
+ const allVersions = Object.keys(versions).filter((v) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(v));
66
68
  const sortedVersions = allVersions.sort(semver.rcompare);
67
69
  const latestVersion = sortedVersions.length > 0 ? sortedVersions[0] : 'unknown';
68
- return { latestVersion, allVersions };
70
+ // Surface health signals for the latest version straight from the abbreviated
71
+ // packument we already fetched — no extra request. Both fields are optional.
72
+ const latestManifest = versions[latestVersion];
73
+ const deprecated = (0, manifest_1.normalizeDeprecatedMessage)(latestManifest?.deprecated);
74
+ const enginesNode = (0, manifest_1.extractEnginesNode)(latestManifest?.engines);
75
+ return { latestVersion, allVersions, deprecated, enginesNode };
69
76
  }
70
77
  /**
71
78
  * Checks if a version is outdated compared to the latest version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inup",
3
- "version": "1.5.6",
3
+ "version": "1.6.2",
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": {
@@ -43,20 +43,20 @@
43
43
  "README.md"
44
44
  ],
45
45
  "devDependencies": {
46
- "@types/node": "^24.12.4",
46
+ "@types/node": "^25.9.4",
47
47
  "@types/semver": "^7.7.1",
48
- "@vitest/coverage-v8": "^4.1.7",
49
- "prettier": "^3.8.3",
48
+ "@vitest/coverage-v8": "^4.1.9",
49
+ "prettier": "^3.9.4",
50
50
  "typescript": "^6.0.3",
51
- "vitest": "^4.1.7"
51
+ "vitest": "^4.1.9"
52
52
  },
53
53
  "dependencies": {
54
54
  "chalk": "^5.6.2",
55
- "commander": "^14.0.3",
55
+ "commander": "^15.0.0",
56
56
  "env-paths": "^4.0.0",
57
57
  "nanospinner": "^1.2.2",
58
- "semver": "^7.8.1",
59
- "undici": "^8.3.0"
58
+ "semver": "^7.8.5",
59
+ "undici": "^8.5.0"
60
60
  },
61
61
  "engines": {
62
62
  "node": ">=20.0.0"
@@ -1,95 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.packageCache = exports.CacheManager = void 0;
4
- const config_1 = require("../config");
5
- const persistent_cache_1 = require("./persistent-cache");
6
- // Single TTL policy for both memory and disk.
7
- class CacheManager {
8
- memoryCache = new Map();
9
- ttl;
10
- constructor(ttl = config_1.CACHE_TTL) {
11
- this.ttl = ttl;
12
- }
13
- /**
14
- * Get cached data for a key, checking memory first, then disk.
15
- * Returns null if not found or expired.
16
- */
17
- get(key) {
18
- // Check in-memory cache first (fastest)
19
- const memoryCached = this.memoryCache.get(key);
20
- if (memoryCached && Date.now() - memoryCached.timestamp < this.ttl) {
21
- return memoryCached.data;
22
- }
23
- // Check persistent disk cache (survives restarts)
24
- const diskCached = persistent_cache_1.persistentCache.get(key);
25
- if (diskCached && Date.now() - diskCached.timestamp < this.ttl) {
26
- // Populate in-memory cache for subsequent accesses
27
- this.memoryCache.set(key, {
28
- data: diskCached,
29
- timestamp: diskCached.timestamp,
30
- });
31
- return diskCached;
32
- }
33
- return null;
34
- }
35
- /**
36
- * Store data in both memory and disk cache.
37
- */
38
- set(key, data) {
39
- // Cache in memory
40
- this.memoryCache.set(key, {
41
- data,
42
- timestamp: Date.now(),
43
- });
44
- // Cache to disk for persistence
45
- persistent_cache_1.persistentCache.set(key, data);
46
- }
47
- /**
48
- * Get data from cache or fetch it using the provided fetcher function.
49
- * This is the main entry point for cache-aside pattern.
50
- */
51
- async getOrFetch(key, fetcher) {
52
- // Try cache first
53
- const cached = this.get(key);
54
- if (cached) {
55
- return cached;
56
- }
57
- // Fetch fresh data
58
- const data = await fetcher();
59
- if (data) {
60
- this.set(key, data);
61
- }
62
- return data;
63
- }
64
- /**
65
- * Check if a key exists and is not expired in cache.
66
- */
67
- has(key) {
68
- return this.get(key) !== null;
69
- }
70
- /**
71
- * Clear in-memory cache (useful for testing).
72
- */
73
- clear() {
74
- this.memoryCache.clear();
75
- }
76
- /**
77
- * Flush pending disk cache writes.
78
- */
79
- flush() {
80
- persistent_cache_1.persistentCache.flush();
81
- }
82
- /**
83
- * Get cache statistics.
84
- */
85
- getStats() {
86
- return {
87
- memoryEntries: this.memoryCache.size,
88
- diskStats: persistent_cache_1.persistentCache.getStats(),
89
- };
90
- }
91
- }
92
- exports.CacheManager = CacheManager;
93
- // Default package version cache instance
94
- exports.packageCache = new CacheManager();
95
- //# sourceMappingURL=cache-manager.js.map