inup 1.5.5 → 1.6.0
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 +53 -10
- package/dist/cli.js +53 -31
- package/dist/config/constants.js +4 -6
- package/dist/config/package-meta.js +10 -0
- package/dist/config/project-config.js +11 -1
- package/dist/core/package-detector.js +37 -5
- package/dist/core/upgrade-runner.js +8 -10
- package/dist/core/upgrader.js +15 -11
- package/dist/features/changelog/clients/github-client.js +5 -6
- package/dist/features/changelog/services/changelog-service.js +2 -4
- package/dist/features/changelog/services/package-metadata-service.js +7 -23
- package/dist/features/changelog/services/release-notes-service.js +4 -29
- package/dist/features/debug/services/performance-tracker.js +6 -8
- package/dist/features/headless/headless-runner.js +53 -0
- package/dist/features/headless/index.js +27 -0
- package/dist/features/headless/report.js +64 -0
- package/dist/features/headless/types.js +6 -0
- package/dist/features/headless/vulnerability-audit.js +106 -0
- package/dist/index.js +1 -2
- package/dist/interactive-ui.js +15 -606
- package/dist/services/background-audit.js +3 -5
- package/dist/services/cache-manager.js +5 -7
- package/dist/services/http/inflight.js +22 -0
- package/dist/services/http/retry.js +30 -0
- package/dist/services/index.js +0 -1
- package/dist/services/npm-registry.js +20 -97
- package/dist/services/package-manager-detector.js +6 -3
- package/dist/services/persistent-cache.js +8 -13
- package/dist/types/domain.js +3 -0
- package/dist/types/streaming.js +3 -0
- package/dist/types/ui.js +3 -0
- package/dist/types.js +17 -0
- package/dist/ui/controllers/package-info-modal-controller.js +22 -31
- package/dist/ui/controllers/vulnerability-audit-controller.js +17 -8
- package/dist/ui/index.js +3 -0
- package/dist/ui/input-handler.js +58 -75
- package/dist/ui/keymap.js +208 -0
- package/dist/ui/modal/package-info-sections/release-notes.js +161 -0
- package/dist/ui/modal/package-info-sections/sections.js +174 -0
- package/dist/ui/modal/package-info-sections/text.js +75 -0
- package/dist/ui/modal/package-info-sections.js +15 -369
- package/dist/ui/modal/package-info.js +1 -1
- package/dist/ui/presenters/health.js +24 -0
- package/dist/ui/renderer/help-modal.js +75 -0
- package/dist/ui/renderer/index.js +2 -5
- package/dist/ui/renderer/package-list/interface.js +184 -0
- package/dist/ui/renderer/package-list/rows.js +177 -0
- package/dist/ui/renderer/package-list.js +15 -455
- package/dist/ui/session/action-dispatcher.js +233 -0
- package/dist/ui/session/index.js +13 -0
- package/dist/ui/session/interactive-session.js +280 -0
- package/dist/ui/session/selection-state-builder.js +149 -0
- package/dist/ui/state/filter-manager.js +17 -6
- package/dist/ui/state/modal-manager.js +35 -0
- package/dist/ui/state/navigation-manager.js +33 -4
- package/dist/ui/state/state-manager.js +68 -3
- package/dist/ui/state/theme-manager.js +1 -0
- package/dist/ui/themes-colors.js +16 -4
- package/dist/ui/themes.js +11 -19
- package/dist/ui/utils/cursor.js +12 -7
- package/dist/ui/utils/index.js +6 -1
- package/dist/ui/utils/text.js +3 -2
- package/dist/ui/utils/version.js +60 -86
- package/dist/utils/color.js +38 -0
- package/dist/utils/config.js +13 -1
- package/dist/utils/engines.js +63 -0
- package/dist/utils/filesystem/io.js +103 -0
- package/dist/utils/filesystem/paths.js +19 -0
- package/dist/utils/filesystem/scan.js +280 -0
- package/dist/utils/filesystem.js +17 -335
- package/dist/utils/index.js +3 -0
- package/dist/utils/manifest.js +35 -0
- package/dist/utils/version.js +38 -1
- package/package.json +8 -7
- package/dist/services/jsdelivr-registry.js +0 -395
package/dist/utils/config.js
CHANGED
|
@@ -7,11 +7,14 @@ exports.configManager = void 0;
|
|
|
7
7
|
const fs_1 = require("fs");
|
|
8
8
|
const path_1 = require("path");
|
|
9
9
|
const env_paths_1 = __importDefault(require("env-paths"));
|
|
10
|
+
const config_1 = require("../config");
|
|
10
11
|
class ConfigManager {
|
|
12
|
+
configDir;
|
|
13
|
+
configPath;
|
|
11
14
|
constructor() {
|
|
12
15
|
// Use env-paths for cross-platform config directory support
|
|
13
16
|
// Mac/Linux: ~/.config/inup, Windows: %APPDATA%/inup
|
|
14
|
-
const paths = (0, env_paths_1.default)(
|
|
17
|
+
const paths = (0, env_paths_1.default)(config_1.PACKAGE_NAME);
|
|
15
18
|
this.configDir = paths.config;
|
|
16
19
|
this.configPath = (0, path_1.join)(this.configDir, 'config.json');
|
|
17
20
|
}
|
|
@@ -51,6 +54,15 @@ class ConfigManager {
|
|
|
51
54
|
config.theme = theme;
|
|
52
55
|
this.writeConfig(config);
|
|
53
56
|
}
|
|
57
|
+
getFilters() {
|
|
58
|
+
const config = this.readConfig();
|
|
59
|
+
return config.filters ?? null;
|
|
60
|
+
}
|
|
61
|
+
setFilters(filters) {
|
|
62
|
+
const config = this.readConfig();
|
|
63
|
+
config.filters = filters;
|
|
64
|
+
this.writeConfig(config);
|
|
65
|
+
}
|
|
54
66
|
}
|
|
55
67
|
// Export singleton instance
|
|
56
68
|
exports.configManager = new ConfigManager();
|
|
@@ -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
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readPackageJson = readPackageJson;
|
|
4
|
+
exports.detectJsonFormat = detectJsonFormat;
|
|
5
|
+
exports.readPackageJsonAsync = readPackageJsonAsync;
|
|
6
|
+
exports.collectAllDependencies = collectAllDependencies;
|
|
7
|
+
exports.collectAllDependenciesAsync = collectAllDependenciesAsync;
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const fs_2 = require("fs");
|
|
10
|
+
function readPackageJson(path) {
|
|
11
|
+
try {
|
|
12
|
+
const content = (0, fs_1.readFileSync)(path, 'utf-8');
|
|
13
|
+
return JSON.parse(content);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw new Error(`Failed to read package.json: ${error}`);
|
|
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
|
+
}
|
|
34
|
+
async function readPackageJsonAsync(path) {
|
|
35
|
+
try {
|
|
36
|
+
const content = await fs_2.promises.readFile(path, 'utf-8');
|
|
37
|
+
return JSON.parse(content);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new Error(`Failed to read package.json: ${error}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function collectAllDependencies(packageJsonFiles, _options = {}) {
|
|
44
|
+
const allDeps = [];
|
|
45
|
+
for (const packageJsonPath of packageJsonFiles) {
|
|
46
|
+
try {
|
|
47
|
+
const packageJson = readPackageJson(packageJsonPath);
|
|
48
|
+
const depTypes = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
49
|
+
for (const depType of depTypes) {
|
|
50
|
+
const deps = packageJson[depType];
|
|
51
|
+
if (deps && typeof deps === 'object') {
|
|
52
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
53
|
+
allDeps.push({
|
|
54
|
+
name,
|
|
55
|
+
version: version,
|
|
56
|
+
type: depType,
|
|
57
|
+
packageJsonPath,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Skip malformed package.json files
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return allDeps;
|
|
68
|
+
}
|
|
69
|
+
async function collectAllDependenciesAsync(packageJsonFiles, _options = {}) {
|
|
70
|
+
const packageJsonPromises = packageJsonFiles.map(async (packageJsonPath) => {
|
|
71
|
+
try {
|
|
72
|
+
const packageJson = await readPackageJsonAsync(packageJsonPath);
|
|
73
|
+
return { packageJson, packageJsonPath };
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Skip malformed package.json files
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const results = await Promise.all(packageJsonPromises);
|
|
81
|
+
const allDeps = [];
|
|
82
|
+
for (const result of results) {
|
|
83
|
+
if (!result)
|
|
84
|
+
continue;
|
|
85
|
+
const { packageJson, packageJsonPath } = result;
|
|
86
|
+
const depTypes = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
87
|
+
for (const depType of depTypes) {
|
|
88
|
+
const deps = packageJson[depType];
|
|
89
|
+
if (deps && typeof deps === 'object') {
|
|
90
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
91
|
+
allDeps.push({
|
|
92
|
+
name,
|
|
93
|
+
version: version,
|
|
94
|
+
type: depType,
|
|
95
|
+
packageJsonPath,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return allDeps;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=io.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findPackageJson = findPackageJson;
|
|
4
|
+
exports.findWorkspaceRoot = findWorkspaceRoot;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const package_manager_detector_1 = require("../../services/package-manager-detector");
|
|
8
|
+
function findPackageJson(cwd = process.cwd()) {
|
|
9
|
+
const packageJsonPath = (0, path_1.join)(cwd, 'package.json');
|
|
10
|
+
return (0, fs_1.existsSync)(packageJsonPath) ? packageJsonPath : null;
|
|
11
|
+
}
|
|
12
|
+
function findWorkspaceRoot(cwd = process.cwd(), packageManager) {
|
|
13
|
+
if (!packageManager) {
|
|
14
|
+
const detected = package_manager_detector_1.PackageManagerDetector.detect(cwd);
|
|
15
|
+
packageManager = detected.name;
|
|
16
|
+
}
|
|
17
|
+
return package_manager_detector_1.PackageManagerDetector.findWorkspaceRoot(cwd, packageManager);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findAllPackageJsonFiles = findAllPackageJsonFiles;
|
|
4
|
+
exports.findAllPackageJsonFilesAsync = findAllPackageJsonFilesAsync;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const fs_2 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const SKIP_DIRS = new Set([
|
|
9
|
+
'node_modules',
|
|
10
|
+
'dist',
|
|
11
|
+
'build',
|
|
12
|
+
'coverage',
|
|
13
|
+
'out',
|
|
14
|
+
'lib',
|
|
15
|
+
'es',
|
|
16
|
+
'esm',
|
|
17
|
+
'cjs',
|
|
18
|
+
]);
|
|
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;
|
|
42
|
+
}
|
|
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 = {}) {
|
|
92
|
+
const packageJsonFiles = [];
|
|
93
|
+
const visitedPaths = new Set();
|
|
94
|
+
let directoriesScanned = 0;
|
|
95
|
+
let lastProgressAt = 0;
|
|
96
|
+
const progressIntervalMs = 250;
|
|
97
|
+
const skipSet = buildSkipSet(options.scanDirs);
|
|
98
|
+
const excludeRegexes = excludePatterns.map((pattern) => new RegExp(pattern, 'i'));
|
|
99
|
+
function shouldExcludePath(relativePath) {
|
|
100
|
+
return excludeRegexes.some((regex) => regex.test(relativePath));
|
|
101
|
+
}
|
|
102
|
+
function reportProgress(currentDir, force = false) {
|
|
103
|
+
if (!onProgress)
|
|
104
|
+
return;
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
if (!force && now - lastProgressAt < progressIntervalMs) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
lastProgressAt = now;
|
|
110
|
+
const relativePath = (0, path_1.relative)(rootDir, currentDir) || '.';
|
|
111
|
+
onProgress(relativePath, packageJsonFiles.length);
|
|
112
|
+
}
|
|
113
|
+
function traverseDirectory(dir, depth = 0) {
|
|
114
|
+
if (depth > maxDepth) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
// Prevent symlink cycles by tracking visited real paths
|
|
119
|
+
const realPath = (0, fs_1.realpathSync)(dir);
|
|
120
|
+
if (visitedPaths.has(realPath)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
visitedPaths.add(realPath);
|
|
124
|
+
directoriesScanned++;
|
|
125
|
+
// Report progress every 10 directories or on first scan
|
|
126
|
+
if (onProgress && (directoriesScanned % 10 === 0 || directoriesScanned === 1)) {
|
|
127
|
+
reportProgress(dir, true);
|
|
128
|
+
}
|
|
129
|
+
const files = (0, fs_1.readdirSync)(dir);
|
|
130
|
+
for (const file of files) {
|
|
131
|
+
reportProgress(dir);
|
|
132
|
+
const fullPath = (0, path_1.join)(dir, file);
|
|
133
|
+
const relativePath = (0, path_1.relative)(rootDir, fullPath);
|
|
134
|
+
if (shouldExcludePath(relativePath)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
let stat;
|
|
138
|
+
try {
|
|
139
|
+
stat = (0, fs_1.statSync)(fullPath);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Skip files/dirs we can't stat (broken symlinks, permission issues)
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (stat.isDirectory()) {
|
|
146
|
+
if (shouldTraverse(file, fullPath, relativePath, skipSet, options.onSkippedPackageDir)) {
|
|
147
|
+
traverseDirectory(fullPath, depth + 1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (file === 'package.json' && stat.isFile()) {
|
|
151
|
+
packageJsonFiles.push(fullPath);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Skip directories that can't be read (permission issues, etc.)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
traverseDirectory(rootDir);
|
|
160
|
+
return packageJsonFiles;
|
|
161
|
+
}
|
|
162
|
+
async function findAllPackageJsonFilesAsync(rootDir = process.cwd(), excludePatterns = [], maxDepth = 10, onProgress, options = {}) {
|
|
163
|
+
const packageJsonFiles = [];
|
|
164
|
+
const visitedPaths = new Set();
|
|
165
|
+
let directoriesScanned = 0;
|
|
166
|
+
let lastProgressAt = 0;
|
|
167
|
+
const progressIntervalMs = 250;
|
|
168
|
+
const concurrency = Math.max(1, Math.min(options.concurrency ?? 16, 64));
|
|
169
|
+
const skipSet = buildSkipSet(options.scanDirs);
|
|
170
|
+
const excludeRegexes = excludePatterns.map((pattern) => new RegExp(pattern, 'i'));
|
|
171
|
+
function shouldExcludePath(relativePath) {
|
|
172
|
+
return excludeRegexes.some((regex) => regex.test(relativePath));
|
|
173
|
+
}
|
|
174
|
+
function reportProgress(currentDir, force = false) {
|
|
175
|
+
if (!onProgress)
|
|
176
|
+
return;
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
if (!force && now - lastProgressAt < progressIntervalMs) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
lastProgressAt = now;
|
|
182
|
+
const relativePath = (0, path_1.relative)(rootDir, currentDir) || '.';
|
|
183
|
+
onProgress(relativePath, packageJsonFiles.length);
|
|
184
|
+
}
|
|
185
|
+
const pending = [];
|
|
186
|
+
let activeTasks = 0;
|
|
187
|
+
let failedError = null;
|
|
188
|
+
let resolveDone = null;
|
|
189
|
+
let rejectDone = null;
|
|
190
|
+
const done = new Promise((resolve, reject) => {
|
|
191
|
+
resolveDone = resolve;
|
|
192
|
+
rejectDone = reject;
|
|
193
|
+
});
|
|
194
|
+
function finishIfIdle() {
|
|
195
|
+
if (pending.length === 0 && activeTasks === 0) {
|
|
196
|
+
resolveDone?.();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function schedule(dir, depth) {
|
|
200
|
+
pending.push({ dir, depth });
|
|
201
|
+
pump();
|
|
202
|
+
}
|
|
203
|
+
async function processDirectory(dir, depth) {
|
|
204
|
+
if (depth > maxDepth) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
let realPath;
|
|
208
|
+
try {
|
|
209
|
+
realPath = await fs_2.promises.realpath(dir);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (visitedPaths.has(realPath)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
visitedPaths.add(realPath);
|
|
218
|
+
directoriesScanned++;
|
|
219
|
+
if (directoriesScanned % 10 === 0 || directoriesScanned === 1) {
|
|
220
|
+
reportProgress(dir, true);
|
|
221
|
+
}
|
|
222
|
+
let files;
|
|
223
|
+
try {
|
|
224
|
+
files = await fs_2.promises.readdir(dir);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
for (const file of files) {
|
|
230
|
+
reportProgress(dir);
|
|
231
|
+
const fullPath = (0, path_1.join)(dir, file);
|
|
232
|
+
const relativePath = (0, path_1.relative)(rootDir, fullPath);
|
|
233
|
+
if (shouldExcludePath(relativePath)) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
let stat;
|
|
237
|
+
try {
|
|
238
|
+
stat = await fs_2.promises.stat(fullPath);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (stat.isDirectory()) {
|
|
244
|
+
if (shouldTraverse(file, fullPath, relativePath, skipSet, options.onSkippedPackageDir)) {
|
|
245
|
+
schedule(fullPath, depth + 1);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (file === 'package.json' && stat.isFile()) {
|
|
249
|
+
packageJsonFiles.push(fullPath);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function pump() {
|
|
254
|
+
while (activeTasks < concurrency && pending.length > 0 && !failedError) {
|
|
255
|
+
const next = pending.shift();
|
|
256
|
+
if (!next)
|
|
257
|
+
break;
|
|
258
|
+
activeTasks++;
|
|
259
|
+
void processDirectory(next.dir, next.depth)
|
|
260
|
+
.catch((error) => {
|
|
261
|
+
if (!failedError) {
|
|
262
|
+
failedError = error;
|
|
263
|
+
rejectDone?.(error);
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
.finally(() => {
|
|
267
|
+
activeTasks--;
|
|
268
|
+
if (failedError) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
pump();
|
|
272
|
+
finishIfIdle();
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
schedule(rootDir, 0);
|
|
277
|
+
await done;
|
|
278
|
+
return packageJsonFiles;
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=scan.js.map
|