deps-conflict-resolver 1.0.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 +248 -0
- package/dist/config.types-CN7glPbA.d.cts +237 -0
- package/dist/config.types-CN7glPbA.d.ts +237 -0
- package/dist/index.cjs +2219 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +162 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.js +2210 -0
- package/dist/index.js.map +1 -0
- package/dist/resolver-BjbDmC24.d.ts +67 -0
- package/dist/resolver-Dew5GU0y.d.cts +67 -0
- package/dist/vite.cjs +2376 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +35 -0
- package/dist/vite.d.ts +35 -0
- package/dist/vite.js +2366 -0
- package/dist/vite.js.map +1 -0
- package/dist/webpack.cjs +2380 -0
- package/dist/webpack.cjs.map +1 -0
- package/dist/webpack.d.cts +58 -0
- package/dist/webpack.d.ts +58 -0
- package/dist/webpack.js +2370 -0
- package/dist/webpack.js.map +1 -0
- package/package.json +116 -0
package/dist/webpack.js
ADDED
|
@@ -0,0 +1,2370 @@
|
|
|
1
|
+
import { promises, existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join, dirname, relative } from 'path';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
import { pathToFileURL } from 'url';
|
|
5
|
+
import { parse } from 'yaml';
|
|
6
|
+
import semver from 'semver';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
|
|
9
|
+
// src/constants.ts
|
|
10
|
+
var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
|
|
11
|
+
var LOCK_FILE_MAP = {
|
|
12
|
+
"pnpm-lock.yaml": "pnpm",
|
|
13
|
+
"yarn.lock": "yarn",
|
|
14
|
+
"package-lock.json": "npm",
|
|
15
|
+
"npm-shrinkwrap.json": "npm"
|
|
16
|
+
};
|
|
17
|
+
var DEFAULT_NPM_REGISTRY_TIMEOUT_MS = 1e4;
|
|
18
|
+
|
|
19
|
+
// src/utils/logger.ts
|
|
20
|
+
var DEFAULT_GLOBAL_LEVEL = 2 /* INFO */ | 4 /* WARN */ | 8 /* ERROR */;
|
|
21
|
+
var __GLOBAL_LEVEL__ = DEFAULT_GLOBAL_LEVEL;
|
|
22
|
+
var Logger = class {
|
|
23
|
+
prefix;
|
|
24
|
+
levelOverride;
|
|
25
|
+
constructor(config = {}) {
|
|
26
|
+
this.prefix = config.prefix ?? "[deps-conflict-resolver]";
|
|
27
|
+
this.levelOverride = config.level;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 设置日志级别
|
|
31
|
+
*/
|
|
32
|
+
setLevel(level) {
|
|
33
|
+
__GLOBAL_LEVEL__ = level;
|
|
34
|
+
this.levelOverride = void 0;
|
|
35
|
+
}
|
|
36
|
+
getEffectiveLevel() {
|
|
37
|
+
return this.levelOverride ?? __GLOBAL_LEVEL__;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 调试日志
|
|
41
|
+
*/
|
|
42
|
+
debug(...args) {
|
|
43
|
+
if (this.getEffectiveLevel() & 1 /* DEBUG */) {
|
|
44
|
+
console.debug(this.formatPrefix("DEBUG"), ...args);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 信息日志
|
|
49
|
+
*/
|
|
50
|
+
info(...args) {
|
|
51
|
+
if (this.getEffectiveLevel() & 2 /* INFO */) {
|
|
52
|
+
console.info(this.formatPrefix("INFO"), ...args);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 警告日志
|
|
57
|
+
*/
|
|
58
|
+
warn(...args) {
|
|
59
|
+
if (this.getEffectiveLevel() & 4 /* WARN */) {
|
|
60
|
+
console.warn(this.formatPrefix("WARN"), ...args);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 错误日志
|
|
65
|
+
*/
|
|
66
|
+
error(...args) {
|
|
67
|
+
if (this.getEffectiveLevel() & 8 /* ERROR */) {
|
|
68
|
+
console.error(this.formatPrefix("ERROR"), ...args);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 格式化前缀
|
|
73
|
+
*/
|
|
74
|
+
formatPrefix(level) {
|
|
75
|
+
return `${this.prefix} [${level}]`;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function createLogger(subPrefix, debug = false) {
|
|
79
|
+
return new Logger({
|
|
80
|
+
prefix: `[deps-conflict-resolver:${subPrefix}]`,
|
|
81
|
+
// debug=true 时强制当前 logger 输出所有日志;否则使用全局级别(默认 INFO/WARN/ERROR)
|
|
82
|
+
level: debug ? 15 /* ALL */ : void 0
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/utils/lru-cache.ts
|
|
87
|
+
var LruCache = class {
|
|
88
|
+
map = /* @__PURE__ */ new Map();
|
|
89
|
+
maxEntries;
|
|
90
|
+
constructor(options) {
|
|
91
|
+
this.maxEntries = options.maxEntries;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 当前缓存条目数
|
|
95
|
+
*/
|
|
96
|
+
get size() {
|
|
97
|
+
return this.map.size;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 获取最大条目数
|
|
101
|
+
*/
|
|
102
|
+
getMaxEntries() {
|
|
103
|
+
return this.maxEntries;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 动态调整最大条目数
|
|
107
|
+
* - 调小会立即触发淘汰
|
|
108
|
+
*/
|
|
109
|
+
setMaxEntries(maxEntries) {
|
|
110
|
+
this.maxEntries = maxEntries;
|
|
111
|
+
if (this.maxEntries <= 0) {
|
|
112
|
+
this.map.clear();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
this.trim();
|
|
116
|
+
}
|
|
117
|
+
clear() {
|
|
118
|
+
this.map.clear();
|
|
119
|
+
}
|
|
120
|
+
has(key) {
|
|
121
|
+
return this.map.has(key);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 获取并刷新 LRU 顺序(命中后会变成“最近使用”)
|
|
125
|
+
*/
|
|
126
|
+
get(key) {
|
|
127
|
+
if (this.maxEntries <= 0) {
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
if (!this.map.has(key)) {
|
|
131
|
+
return void 0;
|
|
132
|
+
}
|
|
133
|
+
const value = this.map.get(key);
|
|
134
|
+
this.map.delete(key);
|
|
135
|
+
this.map.set(key, value);
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 写入并刷新 LRU 顺序
|
|
140
|
+
*/
|
|
141
|
+
set(key, value) {
|
|
142
|
+
if (this.maxEntries <= 0) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (this.map.has(key)) {
|
|
146
|
+
this.map.delete(key);
|
|
147
|
+
}
|
|
148
|
+
this.map.set(key, value);
|
|
149
|
+
this.trim();
|
|
150
|
+
}
|
|
151
|
+
delete(key) {
|
|
152
|
+
return this.map.delete(key);
|
|
153
|
+
}
|
|
154
|
+
trim() {
|
|
155
|
+
if (this.maxEntries <= 0) return;
|
|
156
|
+
while (this.map.size > this.maxEntries) {
|
|
157
|
+
const iter = this.map.keys().next();
|
|
158
|
+
if (iter.done) break;
|
|
159
|
+
this.map.delete(iter.value);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// src/utils/fs.ts
|
|
165
|
+
function* iterateParentDirs(startDir) {
|
|
166
|
+
let currentDir = startDir;
|
|
167
|
+
while (true) {
|
|
168
|
+
yield currentDir;
|
|
169
|
+
const parentDir = dirname(currentDir);
|
|
170
|
+
if (parentDir === currentDir) {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
currentDir = parentDir;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
var DEFAULT_PACKAGE_JSON_CACHE_MAX_ENTRIES = 5e3;
|
|
177
|
+
var packageJsonCache = new LruCache({
|
|
178
|
+
maxEntries: DEFAULT_PACKAGE_JSON_CACHE_MAX_ENTRIES
|
|
179
|
+
});
|
|
180
|
+
var DEFAULT_REQUIRE_CACHE_MAX_ENTRIES = 200;
|
|
181
|
+
var requireCache = new LruCache({
|
|
182
|
+
maxEntries: DEFAULT_REQUIRE_CACHE_MAX_ENTRIES
|
|
183
|
+
});
|
|
184
|
+
var DEFAULT_PACKAGE_PATH_CACHE_MAX_ENTRIES = 2e4;
|
|
185
|
+
var packagePathCache = new LruCache({
|
|
186
|
+
maxEntries: DEFAULT_PACKAGE_PATH_CACHE_MAX_ENTRIES
|
|
187
|
+
});
|
|
188
|
+
function clearPackageJsonCache() {
|
|
189
|
+
packageJsonCache.clear();
|
|
190
|
+
packagePathCache.clear();
|
|
191
|
+
requireCache.clear();
|
|
192
|
+
}
|
|
193
|
+
function readJsonFileSync(filePath) {
|
|
194
|
+
try {
|
|
195
|
+
const content = readFileSync(filePath, "utf-8");
|
|
196
|
+
return JSON.parse(content);
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function fileExists(filePath) {
|
|
202
|
+
return existsSync(filePath);
|
|
203
|
+
}
|
|
204
|
+
function readPackageJsonCached(dir) {
|
|
205
|
+
const pkgPath = join(dir, "package.json");
|
|
206
|
+
const cached = packageJsonCache.get(pkgPath);
|
|
207
|
+
if (cached !== void 0) {
|
|
208
|
+
return cached;
|
|
209
|
+
}
|
|
210
|
+
const result = readJsonFileSync(pkgPath);
|
|
211
|
+
packageJsonCache.set(pkgPath, result);
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
function findProjectRoot(startDir) {
|
|
215
|
+
for (const dir of iterateParentDirs(startDir)) {
|
|
216
|
+
if (fileExists(join(dir, "package.json"))) {
|
|
217
|
+
return dir;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
function findPackagePath(packageName, baseDir) {
|
|
223
|
+
const cacheKey = `${baseDir}\0${packageName}`;
|
|
224
|
+
const cached = packagePathCache.get(cacheKey);
|
|
225
|
+
if (cached !== void 0) {
|
|
226
|
+
return cached;
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
let require2 = requireCache.get(baseDir);
|
|
230
|
+
if (!require2) {
|
|
231
|
+
const baseUrl = pathToFileURL(join(baseDir, "package.json")).href;
|
|
232
|
+
require2 = createRequire(baseUrl);
|
|
233
|
+
requireCache.set(baseDir, require2);
|
|
234
|
+
}
|
|
235
|
+
const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
|
|
236
|
+
const resolved = dirname(pkgJsonPath);
|
|
237
|
+
packagePathCache.set(cacheKey, resolved);
|
|
238
|
+
return resolved;
|
|
239
|
+
} catch {
|
|
240
|
+
const resolved = findPackagePathFallback(packageName, baseDir);
|
|
241
|
+
packagePathCache.set(cacheKey, resolved);
|
|
242
|
+
return resolved;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function findPackagePathFallback(packageName, baseDir) {
|
|
246
|
+
const packagePath = join(baseDir, "node_modules", packageName);
|
|
247
|
+
if (fileExists(packagePath)) {
|
|
248
|
+
return packagePath;
|
|
249
|
+
}
|
|
250
|
+
for (const dir of iterateParentDirs(baseDir)) {
|
|
251
|
+
const candidatePath = join(dir, "node_modules", packageName);
|
|
252
|
+
if (fileExists(candidatePath)) {
|
|
253
|
+
return candidatePath;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
var logger = createLogger("environment-detector");
|
|
259
|
+
var EnvironmentDetector = class {
|
|
260
|
+
projectRoot;
|
|
261
|
+
detectionResult = null;
|
|
262
|
+
registryCache = null;
|
|
263
|
+
constructor(projectRoot) {
|
|
264
|
+
this.projectRoot = projectRoot;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* 获取检测到的包管理器类型
|
|
268
|
+
*/
|
|
269
|
+
async getPackageManager() {
|
|
270
|
+
const result = await this.detect();
|
|
271
|
+
return result.packageManager;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 获取检测到的 registry
|
|
275
|
+
*/
|
|
276
|
+
async getRegistry() {
|
|
277
|
+
if (this.registryCache) {
|
|
278
|
+
return this.registryCache;
|
|
279
|
+
}
|
|
280
|
+
const result = await this.detect();
|
|
281
|
+
this.registryCache = await this.detectRegistry(result.packageManager);
|
|
282
|
+
return this.registryCache;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* 获取指定包管理器的 registry(兼容旧 API)
|
|
286
|
+
*/
|
|
287
|
+
async getRegistryForPackageManager(packageManager) {
|
|
288
|
+
return this.detectRegistry(packageManager);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* 获取完整的检测结果
|
|
292
|
+
*/
|
|
293
|
+
async getDetectionResult() {
|
|
294
|
+
return this.detect();
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* 重置缓存,强制重新检测
|
|
298
|
+
*/
|
|
299
|
+
reset() {
|
|
300
|
+
this.detectionResult = null;
|
|
301
|
+
this.registryCache = null;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* 更新项目根目录
|
|
305
|
+
*/
|
|
306
|
+
setProjectRoot(projectRoot) {
|
|
307
|
+
this.projectRoot = projectRoot;
|
|
308
|
+
this.reset();
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* 检测项目使用的包管理器
|
|
312
|
+
*
|
|
313
|
+
* 检测优先级:
|
|
314
|
+
* 1. package.json 中的 packageManager 字段(如 "pnpm@8.0.0")- 向上遍历查找
|
|
315
|
+
* 2. lock 文件存在 - 向上遍历查找(支持 monorepo)
|
|
316
|
+
* 3. 默认为 npm
|
|
317
|
+
*/
|
|
318
|
+
detect() {
|
|
319
|
+
if (this.detectionResult) {
|
|
320
|
+
return Promise.resolve(this.detectionResult);
|
|
321
|
+
}
|
|
322
|
+
for (const currentDir of iterateParentDirs(this.projectRoot)) {
|
|
323
|
+
const pkgJsonPath = join(currentDir, "package.json");
|
|
324
|
+
if (fileExists(pkgJsonPath)) {
|
|
325
|
+
const pkgJson = readPackageJsonCached(currentDir);
|
|
326
|
+
const pmField = pkgJson?.packageManager;
|
|
327
|
+
if (pmField) {
|
|
328
|
+
const pm = this.parsePackageManagerField(pmField);
|
|
329
|
+
if (pm) {
|
|
330
|
+
logger.debug(`Detected package manager from package.json: ${pm} (at ${currentDir})`);
|
|
331
|
+
this.detectionResult = {
|
|
332
|
+
packageManager: pm,
|
|
333
|
+
detectedFrom: "packageJson",
|
|
334
|
+
rootDir: currentDir
|
|
335
|
+
};
|
|
336
|
+
return Promise.resolve(this.detectionResult);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
for (const [lockFile, pm] of Object.entries(LOCK_FILE_MAP)) {
|
|
341
|
+
if (fileExists(join(currentDir, lockFile))) {
|
|
342
|
+
logger.debug(
|
|
343
|
+
`Detected package manager from lock file: ${pm} (${lockFile} at ${currentDir})`
|
|
344
|
+
);
|
|
345
|
+
this.detectionResult = {
|
|
346
|
+
packageManager: pm,
|
|
347
|
+
detectedFrom: "lockfile",
|
|
348
|
+
rootDir: currentDir
|
|
349
|
+
};
|
|
350
|
+
return Promise.resolve(this.detectionResult);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
logger.debug("No lock file found, defaulting to npm");
|
|
355
|
+
this.detectionResult = {
|
|
356
|
+
packageManager: "npm",
|
|
357
|
+
detectedFrom: "default"
|
|
358
|
+
};
|
|
359
|
+
return Promise.resolve(this.detectionResult);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 解析 package.json 中的 packageManager 字段
|
|
363
|
+
* 格式如:pnpm@8.0.0, yarn@3.2.0, npm@9.0.0
|
|
364
|
+
*/
|
|
365
|
+
parsePackageManagerField(value) {
|
|
366
|
+
const match = value.match(/^(npm|yarn|pnpm)(@|$)/);
|
|
367
|
+
if (match) {
|
|
368
|
+
return match[1];
|
|
369
|
+
}
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* 检测项目使用的 registry
|
|
374
|
+
*
|
|
375
|
+
* 检测优先级:
|
|
376
|
+
* 1. 项目级 .npmrc(向上遍历,覆盖 monorepo 子包场景)
|
|
377
|
+
* 2. 用户级 .npmrc
|
|
378
|
+
* 3. 包管理器特定配置
|
|
379
|
+
* 4. 默认 registry
|
|
380
|
+
*/
|
|
381
|
+
async detectRegistry(packageManager) {
|
|
382
|
+
const defaultRegistry = DEFAULT_NPM_REGISTRY;
|
|
383
|
+
for (const dir of iterateParentDirs(this.projectRoot)) {
|
|
384
|
+
const npmrcPath = join(dir, ".npmrc");
|
|
385
|
+
if (fileExists(npmrcPath)) {
|
|
386
|
+
const registry = await this.parseRegistryFromNpmrc(npmrcPath);
|
|
387
|
+
if (registry) {
|
|
388
|
+
logger.debug(`Detected registry from .npmrc: ${registry} (at ${dir})`);
|
|
389
|
+
return registry;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
394
|
+
if (homeDir) {
|
|
395
|
+
const userNpmrc = join(homeDir, ".npmrc");
|
|
396
|
+
if (fileExists(userNpmrc)) {
|
|
397
|
+
const registry = await this.parseRegistryFromNpmrc(userNpmrc);
|
|
398
|
+
if (registry) {
|
|
399
|
+
logger.debug(`Detected registry from user .npmrc: ${registry}`);
|
|
400
|
+
return registry;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (packageManager === "pnpm") {
|
|
404
|
+
const pnpmConfig = join(homeDir, ".pnpmrc");
|
|
405
|
+
if (fileExists(pnpmConfig)) {
|
|
406
|
+
const registry = await this.parseRegistryFromNpmrc(pnpmConfig);
|
|
407
|
+
if (registry) {
|
|
408
|
+
logger.debug(`Detected registry from .pnpmrc: ${registry}`);
|
|
409
|
+
return registry;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (packageManager === "yarn") {
|
|
414
|
+
const yarnrc = join(this.projectRoot, ".yarnrc.yml");
|
|
415
|
+
if (fileExists(yarnrc)) {
|
|
416
|
+
const registry = await this.parseRegistryFromYarnrc(yarnrc);
|
|
417
|
+
if (registry) {
|
|
418
|
+
logger.debug(`Detected registry from .yarnrc.yml: ${registry}`);
|
|
419
|
+
return registry;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
logger.debug(`Using default registry: ${defaultRegistry}`);
|
|
425
|
+
return defaultRegistry;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* 从 .npmrc 文件解析 registry
|
|
429
|
+
*/
|
|
430
|
+
async parseRegistryFromNpmrc(filePath) {
|
|
431
|
+
try {
|
|
432
|
+
const content = await promises.readFile(filePath, "utf-8");
|
|
433
|
+
const lines = content.split("\n");
|
|
434
|
+
for (const line of lines) {
|
|
435
|
+
const trimmed = line.trim();
|
|
436
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(";")) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const match = trimmed.match(/^registry\s*=\s*(.+)$/i);
|
|
440
|
+
if (match?.[1]) {
|
|
441
|
+
return match[1].trim().replace(/["']/g, "");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} catch {
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 从 .yarnrc.yml 文件解析 registry
|
|
450
|
+
*/
|
|
451
|
+
async parseRegistryFromYarnrc(filePath) {
|
|
452
|
+
try {
|
|
453
|
+
const content = await promises.readFile(filePath, "utf-8");
|
|
454
|
+
const lines = content.split("\n");
|
|
455
|
+
for (const line of lines) {
|
|
456
|
+
const trimmed = line.trim();
|
|
457
|
+
const match = trimmed.match(/^npmRegistryServer:\s*["']?(.+?)["']?\s*$/);
|
|
458
|
+
if (match?.[1]) {
|
|
459
|
+
return match[1];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
function createEnvironmentDetector(projectRoot) {
|
|
468
|
+
return new EnvironmentDetector(projectRoot);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/utils/version-spec.ts
|
|
472
|
+
var NPM_ALIAS_PATTERN = /^npm:(@?[^@]+)@(.+)$/;
|
|
473
|
+
function parseNpmAlias(versionSpec) {
|
|
474
|
+
const match = versionSpec.match(NPM_ALIAS_PATTERN);
|
|
475
|
+
if (match?.[1] && match?.[2]) {
|
|
476
|
+
return [match[1], match[2]];
|
|
477
|
+
}
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/core/workspace-detector.ts
|
|
482
|
+
var logger2 = createLogger("workspace-detector");
|
|
483
|
+
var PROTOCOL_PREFIXES = {
|
|
484
|
+
"catalog:": "catalog",
|
|
485
|
+
"workspace:": "workspace",
|
|
486
|
+
"npm:": "npm",
|
|
487
|
+
"file:": "file",
|
|
488
|
+
"link:": "link",
|
|
489
|
+
"portal:": "portal"
|
|
490
|
+
};
|
|
491
|
+
function extractAliasesFromDeps(deps, targetPackage, definedIn, existingAliases) {
|
|
492
|
+
if (!deps) return [];
|
|
493
|
+
const aliases = [];
|
|
494
|
+
for (const [aliasName, versionSpec] of Object.entries(deps)) {
|
|
495
|
+
if (typeof versionSpec !== "string") continue;
|
|
496
|
+
if (existingAliases.has(aliasName)) continue;
|
|
497
|
+
const parsed = parseNpmAlias(versionSpec);
|
|
498
|
+
if (parsed && parsed[0] === targetPackage) {
|
|
499
|
+
aliases.push({
|
|
500
|
+
aliasName,
|
|
501
|
+
targetPackage,
|
|
502
|
+
versionSpec: parsed[1],
|
|
503
|
+
definedIn,
|
|
504
|
+
isWorkspaceRoot: true
|
|
505
|
+
});
|
|
506
|
+
existingAliases.add(aliasName);
|
|
507
|
+
logger2.debug(`Found alias: ${aliasName} -> ${targetPackage}@${parsed[1]} (in ${definedIn})`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return aliases;
|
|
511
|
+
}
|
|
512
|
+
var WorkspaceDetector = class {
|
|
513
|
+
projectRoot;
|
|
514
|
+
detectionResult = null;
|
|
515
|
+
constructor(projectRoot) {
|
|
516
|
+
this.projectRoot = projectRoot;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* 获取 workspace 检测结果
|
|
520
|
+
*/
|
|
521
|
+
detect() {
|
|
522
|
+
if (this.detectionResult) {
|
|
523
|
+
return Promise.resolve(this.detectionResult);
|
|
524
|
+
}
|
|
525
|
+
this.detectionResult = this.detectWorkspace();
|
|
526
|
+
return Promise.resolve(this.detectionResult);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* 重置缓存
|
|
530
|
+
*/
|
|
531
|
+
reset() {
|
|
532
|
+
this.detectionResult = null;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* 解析 catalog: 协议的版本
|
|
536
|
+
* @param packageName 包名
|
|
537
|
+
* @param versionSpec 版本规格(如 "catalog:" 或 "catalog:react17")
|
|
538
|
+
*/
|
|
539
|
+
async resolveCatalogVersion(packageName, versionSpec) {
|
|
540
|
+
const result = {
|
|
541
|
+
original: versionSpec,
|
|
542
|
+
resolved: null,
|
|
543
|
+
catalogName: "default",
|
|
544
|
+
success: false
|
|
545
|
+
};
|
|
546
|
+
if (versionSpec === "catalog:" || versionSpec === "catalog:default") {
|
|
547
|
+
result.catalogName = "default";
|
|
548
|
+
} else if (versionSpec.startsWith("catalog:")) {
|
|
549
|
+
result.catalogName = versionSpec.slice("catalog:".length);
|
|
550
|
+
} else {
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
const workspace = await this.detect();
|
|
554
|
+
if (!workspace.isMonorepo || !workspace.workspaceRoot) {
|
|
555
|
+
logger2.warn(`Cannot resolve catalog: protocol - not in a monorepo`);
|
|
556
|
+
return result;
|
|
557
|
+
}
|
|
558
|
+
if (result.catalogName === "default") {
|
|
559
|
+
const version = workspace.catalog?.[packageName];
|
|
560
|
+
if (version) {
|
|
561
|
+
result.resolved = version;
|
|
562
|
+
result.success = true;
|
|
563
|
+
logger2.debug(`Resolved ${packageName} from default catalog: ${version}`);
|
|
564
|
+
}
|
|
565
|
+
} else {
|
|
566
|
+
const namedCatalog = workspace.catalogs?.[result.catalogName];
|
|
567
|
+
if (namedCatalog) {
|
|
568
|
+
const version = namedCatalog[packageName];
|
|
569
|
+
if (version) {
|
|
570
|
+
result.resolved = version;
|
|
571
|
+
result.success = true;
|
|
572
|
+
logger2.debug(`Resolved ${packageName} from catalog:${result.catalogName}: ${version}`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (!result.success) {
|
|
577
|
+
logger2.warn(`Package "${packageName}" not found in catalog:${result.catalogName}`);
|
|
578
|
+
}
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* 解析版本规格(支持各种特殊协议)
|
|
583
|
+
* @param packageName 包名
|
|
584
|
+
* @param versionSpec 版本规格
|
|
585
|
+
* @returns 解析后的普通版本范围,或 null(如无法解析)
|
|
586
|
+
*/
|
|
587
|
+
async resolveVersionSpec(packageName, versionSpec) {
|
|
588
|
+
const protocol = this.getVersionProtocol(versionSpec);
|
|
589
|
+
switch (protocol) {
|
|
590
|
+
case "catalog": {
|
|
591
|
+
const resolution = await this.resolveCatalogVersion(packageName, versionSpec);
|
|
592
|
+
return resolution.resolved;
|
|
593
|
+
}
|
|
594
|
+
case "workspace":
|
|
595
|
+
case "file":
|
|
596
|
+
case "link":
|
|
597
|
+
case "portal":
|
|
598
|
+
return "*";
|
|
599
|
+
case "npm": {
|
|
600
|
+
const parsed = parseNpmAlias(versionSpec);
|
|
601
|
+
return parsed?.[1] ?? null;
|
|
602
|
+
}
|
|
603
|
+
default:
|
|
604
|
+
return versionSpec;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* 判断版本规格的协议类型
|
|
609
|
+
*/
|
|
610
|
+
getVersionProtocol(versionSpec) {
|
|
611
|
+
for (const [prefix, protocol] of Object.entries(PROTOCOL_PREFIXES)) {
|
|
612
|
+
if (versionSpec.startsWith(prefix)) {
|
|
613
|
+
return protocol;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return "normal";
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* 从 workspace 根目录和 catalog 中查找已存在的别名
|
|
620
|
+
*/
|
|
621
|
+
async findWorkspaceAliases(targetPackage) {
|
|
622
|
+
const workspace = await this.detect();
|
|
623
|
+
if (!workspace.isMonorepo || !workspace.workspaceRoot) {
|
|
624
|
+
return [];
|
|
625
|
+
}
|
|
626
|
+
const existingAliases = /* @__PURE__ */ new Set();
|
|
627
|
+
const aliases = [];
|
|
628
|
+
const catalogPath = join(workspace.workspaceRoot, "pnpm-workspace.yaml");
|
|
629
|
+
if (workspace.workspaceType === "pnpm") {
|
|
630
|
+
aliases.push(
|
|
631
|
+
...extractAliasesFromDeps(workspace.catalog, targetPackage, catalogPath, existingAliases)
|
|
632
|
+
);
|
|
633
|
+
if (workspace.catalogs) {
|
|
634
|
+
for (const catalog of Object.values(workspace.catalogs)) {
|
|
635
|
+
aliases.push(
|
|
636
|
+
...extractAliasesFromDeps(catalog, targetPackage, catalogPath, existingAliases)
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const rootPkgPath = join(workspace.workspaceRoot, "package.json");
|
|
642
|
+
const rootPkgJson = readPackageJsonCached(workspace.workspaceRoot);
|
|
643
|
+
if (rootPkgJson) {
|
|
644
|
+
aliases.push(
|
|
645
|
+
...extractAliasesFromDeps(
|
|
646
|
+
rootPkgJson.dependencies,
|
|
647
|
+
targetPackage,
|
|
648
|
+
rootPkgPath,
|
|
649
|
+
existingAliases
|
|
650
|
+
),
|
|
651
|
+
...extractAliasesFromDeps(
|
|
652
|
+
rootPkgJson.devDependencies,
|
|
653
|
+
targetPackage,
|
|
654
|
+
rootPkgPath,
|
|
655
|
+
existingAliases
|
|
656
|
+
)
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
return aliases;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* 获取 workspace 根目录
|
|
663
|
+
*/
|
|
664
|
+
async getWorkspaceRoot() {
|
|
665
|
+
const result = await this.detect();
|
|
666
|
+
return result.workspaceRoot;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* 检测 workspace
|
|
670
|
+
*/
|
|
671
|
+
detectWorkspace() {
|
|
672
|
+
for (const dir of iterateParentDirs(this.projectRoot)) {
|
|
673
|
+
const result = this.tryDetectWorkspaceAt(dir);
|
|
674
|
+
if (result) {
|
|
675
|
+
return result;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
isMonorepo: false,
|
|
680
|
+
workspaceRoot: null,
|
|
681
|
+
workspaceType: "none"
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* 尝试在指定目录检测 workspace
|
|
686
|
+
*/
|
|
687
|
+
tryDetectWorkspaceAt(dir) {
|
|
688
|
+
const pnpmWorkspacePath = join(dir, "pnpm-workspace.yaml");
|
|
689
|
+
if (fileExists(pnpmWorkspacePath)) {
|
|
690
|
+
const config = this.parsePnpmWorkspaceYaml(pnpmWorkspacePath);
|
|
691
|
+
logger2.debug(`Detected pnpm workspace at: ${dir}`);
|
|
692
|
+
return {
|
|
693
|
+
isMonorepo: true,
|
|
694
|
+
workspaceRoot: dir,
|
|
695
|
+
currentProjectPath: relative(dir, this.projectRoot) || ".",
|
|
696
|
+
workspaceType: "pnpm",
|
|
697
|
+
catalog: config.catalog,
|
|
698
|
+
catalogs: config.catalogs
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
const pkgJson = readPackageJsonCached(dir);
|
|
702
|
+
if (pkgJson?.workspaces) {
|
|
703
|
+
const isYarn = fileExists(join(dir, "yarn.lock"));
|
|
704
|
+
const workspaceType = isYarn ? "yarn" : "npm";
|
|
705
|
+
logger2.debug(`Detected ${workspaceType} workspace at: ${dir}`);
|
|
706
|
+
return {
|
|
707
|
+
isMonorepo: true,
|
|
708
|
+
workspaceRoot: dir,
|
|
709
|
+
currentProjectPath: relative(dir, this.projectRoot) || ".",
|
|
710
|
+
workspaceType
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
if (fileExists(join(dir, "lerna.json"))) {
|
|
714
|
+
logger2.debug(`Detected lerna workspace at: ${dir}`);
|
|
715
|
+
return {
|
|
716
|
+
isMonorepo: true,
|
|
717
|
+
workspaceRoot: dir,
|
|
718
|
+
currentProjectPath: relative(dir, this.projectRoot) || ".",
|
|
719
|
+
workspaceType: "lerna"
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* 解析 pnpm-workspace.yaml
|
|
726
|
+
*/
|
|
727
|
+
parsePnpmWorkspaceYaml(filePath) {
|
|
728
|
+
try {
|
|
729
|
+
const content = readFileSync(filePath, "utf-8");
|
|
730
|
+
const parsed = parse(content);
|
|
731
|
+
return parsed ?? {};
|
|
732
|
+
} catch (error) {
|
|
733
|
+
logger2.warn(`Failed to parse pnpm-workspace.yaml: ${String(error)}`);
|
|
734
|
+
return {};
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
function createWorkspaceDetector(projectRoot) {
|
|
739
|
+
return new WorkspaceDetector(projectRoot);
|
|
740
|
+
}
|
|
741
|
+
function satisfies(version, range) {
|
|
742
|
+
try {
|
|
743
|
+
return semver.satisfies(version, range, { includePrerelease: false });
|
|
744
|
+
} catch {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function compare(v1, v2) {
|
|
749
|
+
try {
|
|
750
|
+
return semver.compare(v1, v2);
|
|
751
|
+
} catch {
|
|
752
|
+
return 0;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
function findIntersection(versions, ranges) {
|
|
756
|
+
if (ranges.length === 0) return versions;
|
|
757
|
+
return versions.filter((version) => {
|
|
758
|
+
return ranges.every((range) => satisfies(version, range));
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
function findBestVersion(versions, ranges) {
|
|
762
|
+
const satisfyingVersions = findIntersection(versions, ranges);
|
|
763
|
+
if (satisfyingVersions.length === 0) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
const sorted = [...satisfyingVersions].sort((a, b) => compare(b, a));
|
|
767
|
+
return sorted[0] ?? null;
|
|
768
|
+
}
|
|
769
|
+
function rangesIntersect(range1, range2) {
|
|
770
|
+
try {
|
|
771
|
+
const intersection = semver.intersects(range1, range2);
|
|
772
|
+
return intersection;
|
|
773
|
+
} catch {
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
function createAliasInstallSpec(alias, packageName, version) {
|
|
778
|
+
return `${alias}@npm:${packageName}@${version}`;
|
|
779
|
+
}
|
|
780
|
+
function generateAliasName(packageName, version) {
|
|
781
|
+
let major;
|
|
782
|
+
try {
|
|
783
|
+
major = semver.major(version);
|
|
784
|
+
} catch {
|
|
785
|
+
const m = version.match(/^(\d+)/);
|
|
786
|
+
major = m ? Number(m[1]) : 0;
|
|
787
|
+
}
|
|
788
|
+
const cleanName = packageName.replace(/^@/, "").replace(/\//g, "-");
|
|
789
|
+
return `${cleanName}${major}`;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/core/npm-registry.ts
|
|
793
|
+
var logger3 = createLogger("npm-registry");
|
|
794
|
+
var packageCache = /* @__PURE__ */ new Map();
|
|
795
|
+
function clearNpmRegistryCache() {
|
|
796
|
+
packageCache.clear();
|
|
797
|
+
}
|
|
798
|
+
async function fetchWithTimeout(url, options = {}, timeout = DEFAULT_NPM_REGISTRY_TIMEOUT_MS) {
|
|
799
|
+
const controller = new AbortController();
|
|
800
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
801
|
+
try {
|
|
802
|
+
const response = await fetch(url, {
|
|
803
|
+
...options,
|
|
804
|
+
signal: controller.signal
|
|
805
|
+
});
|
|
806
|
+
return response;
|
|
807
|
+
} finally {
|
|
808
|
+
clearTimeout(timeoutId);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
async function fetchPackageInfo(packageName, registry = DEFAULT_NPM_REGISTRY) {
|
|
812
|
+
const cacheKey = `${registry}:${packageName}`;
|
|
813
|
+
const cached = packageCache.get(cacheKey);
|
|
814
|
+
if (cached) {
|
|
815
|
+
return cached;
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
const encodedName = packageName.startsWith("@") ? `@${encodeURIComponent(packageName.slice(1))}` : packageName;
|
|
819
|
+
const url = `${registry}/${encodedName}`;
|
|
820
|
+
logger3.debug(`Fetching package info from: ${url}`);
|
|
821
|
+
const response = await fetchWithTimeout(url, {
|
|
822
|
+
headers: {
|
|
823
|
+
Accept: "application/json"
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
if (!response.ok) {
|
|
827
|
+
if (response.status === 404) {
|
|
828
|
+
logger3.warn(`Package not found: ${packageName}`);
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
832
|
+
}
|
|
833
|
+
const data = await response.json();
|
|
834
|
+
const packageInfo = {
|
|
835
|
+
name: data.name,
|
|
836
|
+
versions: Object.keys(data.versions),
|
|
837
|
+
versionDetails: data.versions,
|
|
838
|
+
distTags: data["dist-tags"]
|
|
839
|
+
};
|
|
840
|
+
const cacheEntry = {
|
|
841
|
+
name: packageInfo.name,
|
|
842
|
+
versions: packageInfo.versions,
|
|
843
|
+
versionDetails: {},
|
|
844
|
+
distTags: packageInfo.distTags
|
|
845
|
+
};
|
|
846
|
+
packageCache.set(cacheKey, cacheEntry);
|
|
847
|
+
return packageInfo;
|
|
848
|
+
} catch (error) {
|
|
849
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
850
|
+
logger3.error(
|
|
851
|
+
`Timeout fetching package info for ${packageName} (>${DEFAULT_NPM_REGISTRY_TIMEOUT_MS}ms)`
|
|
852
|
+
);
|
|
853
|
+
} else {
|
|
854
|
+
logger3.error(`Failed to fetch package info for ${packageName}:`, error);
|
|
855
|
+
}
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
async function fetchAllVersions(packageName, registry = DEFAULT_NPM_REGISTRY) {
|
|
860
|
+
const info = await fetchPackageInfo(packageName, registry);
|
|
861
|
+
return info?.versions ?? [];
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/core/dependency-analyzer.ts
|
|
865
|
+
var logger4 = createLogger("dependency-analyzer");
|
|
866
|
+
var DependencyAnalyzer = class {
|
|
867
|
+
options;
|
|
868
|
+
/** 主项目声明的依赖(解析后的版本,catalog: 等协议已转换) */
|
|
869
|
+
declaredDeps;
|
|
870
|
+
/** 主项目声明的原始依赖(未解析,保留 catalog: 等协议) */
|
|
871
|
+
rawDeclaredDeps;
|
|
872
|
+
/** 分析结果:存储有 peer 依赖的包信息 */
|
|
873
|
+
analyzedDependencies = /* @__PURE__ */ new Map();
|
|
874
|
+
/** 已访问的包名(使用包名,同一个包只分析一次) */
|
|
875
|
+
visitedPackages = /* @__PURE__ */ new Set();
|
|
876
|
+
/** 已安装包的信息缓存(按需填充,避免扫描整个 node_modules) */
|
|
877
|
+
installedPackageCache = /* @__PURE__ */ new Map();
|
|
878
|
+
/**
|
|
879
|
+
* 已存在的别名映射:真实包名 -> 别名信息列表
|
|
880
|
+
* 从 package.json 的 npm:package@version 格式解析得到
|
|
881
|
+
* 这是最准确的别名识别方式
|
|
882
|
+
*/
|
|
883
|
+
existingAliasesMap = /* @__PURE__ */ new Map();
|
|
884
|
+
/** Workspace 检测器(用于 catalog 协议解析和 workspace 别名查找) */
|
|
885
|
+
workspaceDetector;
|
|
886
|
+
/** Workspace 级别的别名缓存 */
|
|
887
|
+
workspaceAliasesCache = /* @__PURE__ */ new Map();
|
|
888
|
+
/** workspace 根目录 package.json 声明的依赖(dependencies/devDependencies),用于“声明判断”合并 */
|
|
889
|
+
workspaceRootDeclaredDeps = {};
|
|
890
|
+
/** 是否已加载 workspaceRootDeclaredDeps */
|
|
891
|
+
workspaceRootDeclaredLoaded = false;
|
|
892
|
+
constructor(options, mainPackageJson, installedDeps) {
|
|
893
|
+
this.options = options;
|
|
894
|
+
this.rawDeclaredDeps = {
|
|
895
|
+
...mainPackageJson.dependencies,
|
|
896
|
+
...mainPackageJson.devDependencies
|
|
897
|
+
};
|
|
898
|
+
this.declaredDeps = { ...this.rawDeclaredDeps };
|
|
899
|
+
this.workspaceDetector = createWorkspaceDetector(options.projectRoot);
|
|
900
|
+
this.parseExistingAliases();
|
|
901
|
+
if (installedDeps) {
|
|
902
|
+
for (const [name, info] of installedDeps) {
|
|
903
|
+
this.installedPackageCache.set(name, info);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* 统一封装:在指定 baseDir 下解析包安装路径并读取 package.json
|
|
909
|
+
* - 用于收敛 findPackagePath + readPackageJsonCached 的重复逻辑
|
|
910
|
+
* - 返回 { packagePath: null } 表示无法 resolve 到该包
|
|
911
|
+
* - 返回 { packagePath, pkgJson: null } 表示找到了目录但 package.json 无法读取/解析
|
|
912
|
+
*/
|
|
913
|
+
resolvePackageJson(packageName, baseDir) {
|
|
914
|
+
const packagePath = findPackagePath(packageName, baseDir);
|
|
915
|
+
if (!packagePath) {
|
|
916
|
+
return { packagePath: null, pkgJson: null };
|
|
917
|
+
}
|
|
918
|
+
const pkgJson = readPackageJsonCached(packagePath);
|
|
919
|
+
return { packagePath, pkgJson };
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* 加载 workspace 根目录 package.json 声明的依赖(dependencies/devDependencies)
|
|
923
|
+
*
|
|
924
|
+
* 目的:在 monorepo/workspace 场景下,“主工程声明”需要合并当前包与 workspace 根的声明,
|
|
925
|
+
* 与 WorkspaceDetector 的 workspaceRoot 判断逻辑保持一致。
|
|
926
|
+
*/
|
|
927
|
+
async ensureWorkspaceRootDeclaredDepsLoaded() {
|
|
928
|
+
if (this.workspaceRootDeclaredLoaded) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
this.workspaceRootDeclaredLoaded = true;
|
|
932
|
+
const workspaceRoot = await this.workspaceDetector.getWorkspaceRoot();
|
|
933
|
+
if (!workspaceRoot || workspaceRoot === this.options.projectRoot) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const rootPkgJson = readPackageJsonCached(workspaceRoot);
|
|
937
|
+
if (!rootPkgJson) {
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
this.workspaceRootDeclaredDeps = {
|
|
941
|
+
...rootPkgJson.dependencies,
|
|
942
|
+
...rootPkgJson.devDependencies
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* 判断某个包是否在“主工程”声明(dependencies/devDependencies)
|
|
947
|
+
*
|
|
948
|
+
* 默认行为(与 workspace 判断逻辑一致):
|
|
949
|
+
* - 非 workspace:只看当前 projectRoot 的 package.json
|
|
950
|
+
* - workspace/monorepo:合并 projectRoot + workspaceRoot 两处 package.json 的声明
|
|
951
|
+
*/
|
|
952
|
+
isDeclaredInMainProject(packageName) {
|
|
953
|
+
return Object.prototype.hasOwnProperty.call(this.declaredDeps, packageName) || Object.prototype.hasOwnProperty.call(this.workspaceRootDeclaredDeps, packageName);
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* 解析 package.json 中的 npm: 协议别名
|
|
957
|
+
* 格式:aliasName: "npm:realPackage@version"
|
|
958
|
+
* 这是识别别名最准确的方式
|
|
959
|
+
*/
|
|
960
|
+
parseExistingAliases() {
|
|
961
|
+
for (const [aliasName, versionSpec] of Object.entries(this.declaredDeps)) {
|
|
962
|
+
if (typeof versionSpec !== "string") continue;
|
|
963
|
+
const parsed = parseNpmAlias(versionSpec);
|
|
964
|
+
if (parsed) {
|
|
965
|
+
const [targetPackage, version] = parsed;
|
|
966
|
+
const aliasInfo = {
|
|
967
|
+
aliasName,
|
|
968
|
+
targetPackage,
|
|
969
|
+
versionSpec: version,
|
|
970
|
+
installedVersion: null
|
|
971
|
+
// 稍后在需要时填充
|
|
972
|
+
};
|
|
973
|
+
const existing = this.existingAliasesMap.get(targetPackage) ?? [];
|
|
974
|
+
existing.push(aliasInfo);
|
|
975
|
+
this.existingAliasesMap.set(targetPackage, existing);
|
|
976
|
+
logger4.debug(`Found npm alias: ${aliasName} -> ${targetPackage}@${version}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (this.existingAliasesMap.size > 0) {
|
|
980
|
+
logger4.debug(`Found ${this.existingAliasesMap.size} npm: protocol aliases`);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* 获取别名的已安装版本(懒加载)
|
|
985
|
+
*/
|
|
986
|
+
getAliasInstalledVersion(aliasName) {
|
|
987
|
+
const cached = this.installedPackageCache.get(aliasName);
|
|
988
|
+
if (cached) {
|
|
989
|
+
return cached.version;
|
|
990
|
+
}
|
|
991
|
+
const { packagePath, pkgJson } = this.resolvePackageJson(aliasName, this.options.projectRoot);
|
|
992
|
+
if (packagePath && pkgJson?.version) {
|
|
993
|
+
this.installedPackageCache.set(aliasName, {
|
|
994
|
+
name: aliasName,
|
|
995
|
+
version: pkgJson.version,
|
|
996
|
+
path: packagePath,
|
|
997
|
+
isAlias: true,
|
|
998
|
+
realName: pkgJson.name
|
|
999
|
+
});
|
|
1000
|
+
return pkgJson.version;
|
|
1001
|
+
}
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* 解析所有 catalog: 协议的依赖版本
|
|
1006
|
+
* 将 catalog:xxx 转换为实际的版本范围
|
|
1007
|
+
*/
|
|
1008
|
+
async resolveCatalogDependencies() {
|
|
1009
|
+
for (const [pkgName, versionSpec] of Object.entries(this.rawDeclaredDeps)) {
|
|
1010
|
+
if (typeof versionSpec !== "string") continue;
|
|
1011
|
+
if (versionSpec.startsWith("catalog:")) {
|
|
1012
|
+
const resolved = await this.workspaceDetector.resolveVersionSpec(pkgName, versionSpec);
|
|
1013
|
+
if (resolved) {
|
|
1014
|
+
this.declaredDeps[pkgName] = resolved;
|
|
1015
|
+
logger4.debug(`Resolved catalog dependency: ${pkgName} "${versionSpec}" -> "${resolved}"`);
|
|
1016
|
+
} else {
|
|
1017
|
+
logger4.warn(`Failed to resolve catalog dependency: ${pkgName}@${versionSpec}`);
|
|
1018
|
+
}
|
|
1019
|
+
} else if (versionSpec.startsWith("workspace:")) {
|
|
1020
|
+
const resolved = await this.workspaceDetector.resolveVersionSpec(pkgName, versionSpec);
|
|
1021
|
+
if (resolved) {
|
|
1022
|
+
this.declaredDeps[pkgName] = resolved;
|
|
1023
|
+
logger4.debug(
|
|
1024
|
+
`Resolved workspace dependency: ${pkgName} "${versionSpec}" -> "${resolved}"`
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* 分析依赖并找出冲突
|
|
1032
|
+
*/
|
|
1033
|
+
async analyze() {
|
|
1034
|
+
logger4.info("Starting dependency analysis...");
|
|
1035
|
+
try {
|
|
1036
|
+
await this.resolveCatalogDependencies();
|
|
1037
|
+
await this.ensureWorkspaceRootDeclaredDepsLoaded();
|
|
1038
|
+
for (const depName of this.options.dependencies) {
|
|
1039
|
+
this.analyzeDependencyRecursive(depName, []);
|
|
1040
|
+
}
|
|
1041
|
+
const peerDepsMap = this.collectFirstLevelPeerDependencies();
|
|
1042
|
+
const peerConflicts = await this.analyzePeerConflicts(peerDepsMap);
|
|
1043
|
+
const aliasMappings = await this.generateAliasMappings(peerConflicts);
|
|
1044
|
+
const missingFirstLevelPeers = this.findMissingFirstLevelPeers(peerDepsMap);
|
|
1045
|
+
const conflictsNeedingAlias = peerConflicts.filter((c) => c.needsAlias).length;
|
|
1046
|
+
logger4.info(
|
|
1047
|
+
`Analysis complete. Found ${conflictsNeedingAlias} first-level peer conflicts needing alias, ${missingFirstLevelPeers.length} missing peers (not auto-installing). Analyzed ${this.analyzedDependencies.size} packages, visited ${this.visitedPackages.size} packages total.`
|
|
1048
|
+
);
|
|
1049
|
+
return {
|
|
1050
|
+
analyzedDependencies: this.analyzedDependencies,
|
|
1051
|
+
peerConflicts,
|
|
1052
|
+
aliasMappings,
|
|
1053
|
+
missingFirstLevelPeers
|
|
1054
|
+
};
|
|
1055
|
+
} finally {
|
|
1056
|
+
clearPackageJsonCache();
|
|
1057
|
+
clearNpmRegistryCache();
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* 递归分析单个依赖及其所有子依赖
|
|
1062
|
+
*/
|
|
1063
|
+
analyzeDependencyRecursive(packageName, dependencyPath) {
|
|
1064
|
+
if (this.visitedPackages.has(packageName)) {
|
|
1065
|
+
logger4.debug(`Skipping already visited package: ${packageName}`);
|
|
1066
|
+
return this.analyzedDependencies.get(packageName) ?? null;
|
|
1067
|
+
}
|
|
1068
|
+
let rootInfo = null;
|
|
1069
|
+
const stack = [
|
|
1070
|
+
{ name: packageName, path: dependencyPath }
|
|
1071
|
+
];
|
|
1072
|
+
while (stack.length > 0) {
|
|
1073
|
+
const item = stack.pop();
|
|
1074
|
+
if (!item) break;
|
|
1075
|
+
const name = item.name;
|
|
1076
|
+
const path = item.path;
|
|
1077
|
+
if (this.visitedPackages.has(name)) {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
this.visitedPackages.add(name);
|
|
1081
|
+
const { packagePath, pkgJson } = this.resolvePackageJson(name, this.options.projectRoot);
|
|
1082
|
+
if (!packagePath) {
|
|
1083
|
+
logger4.debug(`Package not found: ${name}`);
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
if (!pkgJson) {
|
|
1087
|
+
logger4.debug(`Could not read package.json for: ${name}`);
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
this.cacheInstalledPackageInfo(name, packagePath, pkgJson);
|
|
1091
|
+
logger4.debug(`Analyzing ${name}@${pkgJson.version}`);
|
|
1092
|
+
const depInfo = {
|
|
1093
|
+
name,
|
|
1094
|
+
version: pkgJson.version ?? "unknown",
|
|
1095
|
+
dependencyPath: [...path],
|
|
1096
|
+
dependencies: pkgJson.dependencies ?? {},
|
|
1097
|
+
peerDependencies: pkgJson.peerDependencies ?? {},
|
|
1098
|
+
peerDependenciesMeta: pkgJson.peerDependenciesMeta
|
|
1099
|
+
};
|
|
1100
|
+
this.analyzedDependencies.set(name, depInfo);
|
|
1101
|
+
if (name === packageName) {
|
|
1102
|
+
rootInfo = depInfo;
|
|
1103
|
+
}
|
|
1104
|
+
const currentPath = [...path, name];
|
|
1105
|
+
const depsToCheck = [
|
|
1106
|
+
...Object.keys(pkgJson.dependencies ?? {}),
|
|
1107
|
+
...Object.keys(pkgJson.peerDependencies ?? {})
|
|
1108
|
+
];
|
|
1109
|
+
for (const depName of depsToCheck) {
|
|
1110
|
+
if (this.visitedPackages.has(depName)) {
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
stack.push({ name: depName, path: currentPath });
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return rootInfo;
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* 缓存已安装包的信息(用于后续查找别名等)
|
|
1120
|
+
*/
|
|
1121
|
+
cacheInstalledPackageInfo(packageName, packagePath, pkgJson) {
|
|
1122
|
+
if (this.installedPackageCache.has(packageName)) {
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
const info = {
|
|
1126
|
+
name: packageName,
|
|
1127
|
+
version: pkgJson.version ?? "unknown",
|
|
1128
|
+
path: packagePath
|
|
1129
|
+
};
|
|
1130
|
+
if (pkgJson.name && pkgJson.name !== packageName) {
|
|
1131
|
+
info.isAlias = true;
|
|
1132
|
+
info.realName = pkgJson.name;
|
|
1133
|
+
}
|
|
1134
|
+
const declaredSpec = this.declaredDeps[packageName];
|
|
1135
|
+
if (typeof declaredSpec === "string" && declaredSpec.startsWith("npm:")) {
|
|
1136
|
+
info.isAlias = true;
|
|
1137
|
+
const parsed = parseNpmAlias(declaredSpec);
|
|
1138
|
+
if (parsed) {
|
|
1139
|
+
info.realName = parsed[0];
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
this.installedPackageCache.set(packageName, info);
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* 收集第一层依赖的 peer 依赖
|
|
1146
|
+
* 只分析用户指定的 dependencies 的直接 peerDependencies,不分析子依赖的 peerDependencies
|
|
1147
|
+
*
|
|
1148
|
+
* 注意:虽然只收集第一层的 peer 依赖用于冲突检测和别名安装,
|
|
1149
|
+
* 但依赖树仍然会被完整构建,用于后续的重定向判断
|
|
1150
|
+
*/
|
|
1151
|
+
collectFirstLevelPeerDependencies() {
|
|
1152
|
+
const peerDepsMap = /* @__PURE__ */ new Map();
|
|
1153
|
+
for (const depName of this.options.dependencies) {
|
|
1154
|
+
const depInfo = this.analyzedDependencies.get(depName);
|
|
1155
|
+
if (!depInfo) continue;
|
|
1156
|
+
const peerDeps = depInfo.peerDependencies;
|
|
1157
|
+
const peerMeta = depInfo.peerDependenciesMeta ?? {};
|
|
1158
|
+
for (const [peerName, peerRange] of Object.entries(peerDeps)) {
|
|
1159
|
+
const isOptional = peerMeta[peerName]?.optional === true;
|
|
1160
|
+
const existing = peerDepsMap.get(peerName) ?? [];
|
|
1161
|
+
existing.push({
|
|
1162
|
+
path: [depName],
|
|
1163
|
+
requiredRange: peerRange
|
|
1164
|
+
});
|
|
1165
|
+
peerDepsMap.set(peerName, existing);
|
|
1166
|
+
logger4.debug(
|
|
1167
|
+
`Found first-level peer dependency: ${peerName}@${peerRange} required by ${depName}${isOptional ? " (optional)" : ""}`
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return peerDepsMap;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* 分析 peer 依赖冲突
|
|
1175
|
+
* 只有当主工程已安装某个依赖,且 peerDeps 要求的版本不兼容时,才需要别名
|
|
1176
|
+
*/
|
|
1177
|
+
async analyzePeerConflicts(peerDepsMap) {
|
|
1178
|
+
const conflicts = [];
|
|
1179
|
+
for (const [packageName, requestedBy] of peerDepsMap) {
|
|
1180
|
+
const isDeclared = this.isDeclaredInMainProject(packageName);
|
|
1181
|
+
const mainVersion = isDeclared ? this.getMainProjectVersion(packageName) : null;
|
|
1182
|
+
const isInstalled = mainVersion !== null;
|
|
1183
|
+
let hasConflict = false;
|
|
1184
|
+
const conflictingRanges = [];
|
|
1185
|
+
for (const request of requestedBy) {
|
|
1186
|
+
const resolvedRange = await this.workspaceDetector.resolveVersionSpec(packageName, request.requiredRange) ?? request.requiredRange;
|
|
1187
|
+
if (mainVersion && satisfies(mainVersion, resolvedRange)) ; else {
|
|
1188
|
+
conflictingRanges.push({
|
|
1189
|
+
...request,
|
|
1190
|
+
requiredRange: resolvedRange
|
|
1191
|
+
// 使用解析后的版本范围
|
|
1192
|
+
});
|
|
1193
|
+
if (isDeclared && isInstalled) {
|
|
1194
|
+
hasConflict = true;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
const needsAlias = isDeclared && isInstalled && hasConflict && conflictingRanges.length > 0;
|
|
1199
|
+
if (needsAlias) {
|
|
1200
|
+
logger4.info(
|
|
1201
|
+
`Peer conflict: ${packageName}@${mainVersion} - ${conflictingRanges.length} incompatible ranges`
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
conflicts.push({
|
|
1205
|
+
packageName,
|
|
1206
|
+
mainProjectVersion: mainVersion,
|
|
1207
|
+
requiredRange: this.mergeRequiredRanges(requestedBy),
|
|
1208
|
+
requestedBy,
|
|
1209
|
+
conflictingRanges,
|
|
1210
|
+
// 只记录冲突的范围
|
|
1211
|
+
hasConflict,
|
|
1212
|
+
needsAlias
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
return conflicts;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* 获取已安装的包版本(从 node_modules 读取实际版本)
|
|
1219
|
+
* 返回 null 表示包未安装
|
|
1220
|
+
*/
|
|
1221
|
+
getMainProjectVersion(packageName) {
|
|
1222
|
+
const cached = this.installedPackageCache.get(packageName);
|
|
1223
|
+
if (cached) {
|
|
1224
|
+
return cached.version;
|
|
1225
|
+
}
|
|
1226
|
+
const { packagePath, pkgJson } = this.resolvePackageJson(packageName, this.options.projectRoot);
|
|
1227
|
+
if (packagePath && pkgJson?.version) {
|
|
1228
|
+
this.installedPackageCache.set(packageName, {
|
|
1229
|
+
name: packageName,
|
|
1230
|
+
version: pkgJson.version,
|
|
1231
|
+
path: packagePath
|
|
1232
|
+
});
|
|
1233
|
+
return pkgJson.version;
|
|
1234
|
+
}
|
|
1235
|
+
return null;
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* 合并多个版本范围为描述字符串
|
|
1239
|
+
*/
|
|
1240
|
+
mergeRequiredRanges(requests) {
|
|
1241
|
+
const ranges = [...new Set(requests.map((r) => r.requiredRange))];
|
|
1242
|
+
return ranges.join(" || ");
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* 生成别名映射
|
|
1246
|
+
*/
|
|
1247
|
+
async generateAliasMappings(conflicts) {
|
|
1248
|
+
const aliasMappings = [];
|
|
1249
|
+
const aliasNeeded = conflicts.filter((c) => c.needsAlias);
|
|
1250
|
+
const allConflictPackageNames = aliasNeeded.map((c) => c.packageName);
|
|
1251
|
+
for (const conflict of aliasNeeded) {
|
|
1252
|
+
const mappings = await this.createAliasMappingsForConflict(conflict, allConflictPackageNames);
|
|
1253
|
+
aliasMappings.push(...mappings);
|
|
1254
|
+
}
|
|
1255
|
+
return aliasMappings;
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* 为单个冲突创建别名映射
|
|
1259
|
+
*/
|
|
1260
|
+
async createAliasMappingsForConflict(conflict, allConflictPackageNames) {
|
|
1261
|
+
const { packageName, conflictingRanges } = conflict;
|
|
1262
|
+
const rangeGroups = this.groupRangesByCompatibility(conflictingRanges);
|
|
1263
|
+
logger4.info(`Creating alias for "${packageName}": ${rangeGroups.length} version groups`);
|
|
1264
|
+
const workspaceRoot = await this.workspaceDetector.getWorkspaceRoot();
|
|
1265
|
+
if (workspaceRoot) {
|
|
1266
|
+
logger4.info(` Workspace root detected: ${workspaceRoot}`);
|
|
1267
|
+
const wsAliases = await this.findWorkspaceAliasesForPackage(packageName);
|
|
1268
|
+
if (wsAliases.length > 0) {
|
|
1269
|
+
logger4.info(` Found ${wsAliases.length} workspace alias(es) for ${packageName}:`);
|
|
1270
|
+
for (const a of wsAliases) {
|
|
1271
|
+
logger4.info(
|
|
1272
|
+
` - ${a.aliasName}: npm:${packageName}@${a.versionSpec} (defined in: ${a.definedIn})`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
} else {
|
|
1276
|
+
logger4.info(
|
|
1277
|
+
` No workspace aliases found for ${packageName} (expected npm:${packageName}@version format in root package.json)`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
} else {
|
|
1281
|
+
logger4.info(` Not in a monorepo workspace`);
|
|
1282
|
+
}
|
|
1283
|
+
const localAliases = this.existingAliasesMap.get(packageName);
|
|
1284
|
+
if (localAliases && localAliases.length > 0) {
|
|
1285
|
+
logger4.info(` Found ${localAliases.length} local alias(es) for ${packageName}:`);
|
|
1286
|
+
for (const a of localAliases) {
|
|
1287
|
+
logger4.info(
|
|
1288
|
+
` - ${a.aliasName}: npm:${packageName}@${a.versionSpec} (installed: ${a.installedVersion ?? "not installed"})`
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
const versions = await fetchAllVersions(packageName, this.options.registry);
|
|
1293
|
+
if (versions.length === 0) {
|
|
1294
|
+
logger4.warn(`No versions found for ${packageName}`);
|
|
1295
|
+
return [];
|
|
1296
|
+
}
|
|
1297
|
+
const existingAliasNames = await this.getExistingAliasNames(packageName);
|
|
1298
|
+
const usedAliasNames = /* @__PURE__ */ new Set();
|
|
1299
|
+
const mappings = [];
|
|
1300
|
+
for (let i = 0; i < rangeGroups.length; i++) {
|
|
1301
|
+
const group = rangeGroups[i];
|
|
1302
|
+
if (!group || group.length === 0) continue;
|
|
1303
|
+
const groupRanges = [...new Set(group.map((r) => r.requiredRange))];
|
|
1304
|
+
logger4.debug(` Group ${i + 1}: ${groupRanges.join(", ")}`);
|
|
1305
|
+
const mapping = await this.createAliasMappingForGroup(
|
|
1306
|
+
packageName,
|
|
1307
|
+
group,
|
|
1308
|
+
groupRanges,
|
|
1309
|
+
versions,
|
|
1310
|
+
existingAliasNames,
|
|
1311
|
+
usedAliasNames,
|
|
1312
|
+
allConflictPackageNames
|
|
1313
|
+
);
|
|
1314
|
+
if (mapping) {
|
|
1315
|
+
mappings.push(mapping);
|
|
1316
|
+
usedAliasNames.add(mapping.aliasName);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return mappings;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* 按范围兼容性分组
|
|
1323
|
+
* 有交集的范围放在一组,互斥的分开
|
|
1324
|
+
*/
|
|
1325
|
+
groupRangesByCompatibility(requests) {
|
|
1326
|
+
const groups = [];
|
|
1327
|
+
for (const request of requests) {
|
|
1328
|
+
let added = false;
|
|
1329
|
+
for (const group of groups) {
|
|
1330
|
+
const canJoin = group.every(
|
|
1331
|
+
(existing) => rangesIntersect(existing.requiredRange, request.requiredRange)
|
|
1332
|
+
);
|
|
1333
|
+
if (canJoin) {
|
|
1334
|
+
group.push(request);
|
|
1335
|
+
added = true;
|
|
1336
|
+
break;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (!added) {
|
|
1340
|
+
groups.push([request]);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
return groups;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* 为一个兼容组创建别名映射
|
|
1347
|
+
*/
|
|
1348
|
+
async createAliasMappingForGroup(packageName, group, groupRanges, versions, existingAliasNames, usedAliasNames, allConflictPackageNames) {
|
|
1349
|
+
const existingAlias = await this.findExistingAliasForRanges(
|
|
1350
|
+
packageName,
|
|
1351
|
+
groupRanges,
|
|
1352
|
+
usedAliasNames
|
|
1353
|
+
);
|
|
1354
|
+
if (existingAlias) {
|
|
1355
|
+
logger4.debug(
|
|
1356
|
+
` Reusing: ${existingAlias.name}@${existingAlias.version}${existingAlias.isWorkspaceAlias ? " (workspace)" : ""}`
|
|
1357
|
+
);
|
|
1358
|
+
return this.buildAliasMapping(
|
|
1359
|
+
packageName,
|
|
1360
|
+
existingAlias.name,
|
|
1361
|
+
existingAlias.version,
|
|
1362
|
+
"",
|
|
1363
|
+
// 不需要安装
|
|
1364
|
+
group,
|
|
1365
|
+
allConflictPackageNames
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
const bestVersion = findBestVersion(versions, groupRanges);
|
|
1369
|
+
if (!bestVersion) {
|
|
1370
|
+
logger4.warn(
|
|
1371
|
+
` No version satisfies all ranges [${groupRanges.join(", ")}] for ${packageName}`
|
|
1372
|
+
);
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
const combinedExisting = /* @__PURE__ */ new Set([...existingAliasNames, ...usedAliasNames]);
|
|
1376
|
+
const aliasName = this.generateUniqueAliasName(packageName, bestVersion, combinedExisting);
|
|
1377
|
+
const installSpec = createAliasInstallSpec(aliasName, packageName, bestVersion);
|
|
1378
|
+
logger4.info(` New alias: ${aliasName}@${bestVersion}`);
|
|
1379
|
+
return this.buildAliasMapping(
|
|
1380
|
+
packageName,
|
|
1381
|
+
aliasName,
|
|
1382
|
+
bestVersion,
|
|
1383
|
+
installSpec,
|
|
1384
|
+
group,
|
|
1385
|
+
allConflictPackageNames
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* 构建 AliasMapping 对象
|
|
1390
|
+
*/
|
|
1391
|
+
buildAliasMapping(packageName, aliasName, version, installSpec, group, allConflictPackageNames) {
|
|
1392
|
+
const usedBy = group.map((r) => r.path.join(">"));
|
|
1393
|
+
const usedByRoots = [
|
|
1394
|
+
...new Set(group.map((r) => r.path[0]).filter((p) => p !== void 0))
|
|
1395
|
+
];
|
|
1396
|
+
const allDependents = this.collectAllRelatedPackages(usedByRoots, allConflictPackageNames);
|
|
1397
|
+
return {
|
|
1398
|
+
originalName: packageName,
|
|
1399
|
+
aliasName,
|
|
1400
|
+
installSpec,
|
|
1401
|
+
resolvedVersion: version,
|
|
1402
|
+
usedBy,
|
|
1403
|
+
allDependents
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* 查找满足指定范围的现有别名(排除已使用的)
|
|
1408
|
+
* 优先查找当前项目的别名,然后查找 workspace 级别的别名
|
|
1409
|
+
*/
|
|
1410
|
+
async findExistingAliasForRanges(packageName, ranges, excludeAliases) {
|
|
1411
|
+
logger4.debug(
|
|
1412
|
+
` Searching for existing alias for ${packageName} satisfying ranges: [${ranges.join(", ")}]`
|
|
1413
|
+
);
|
|
1414
|
+
const npmAliases = this.existingAliasesMap.get(packageName);
|
|
1415
|
+
if (npmAliases && npmAliases.length > 0) {
|
|
1416
|
+
logger4.debug(` Checking ${npmAliases.length} local alias(es)...`);
|
|
1417
|
+
for (const alias of npmAliases) {
|
|
1418
|
+
if (excludeAliases.has(alias.aliasName)) {
|
|
1419
|
+
logger4.debug(` Skip ${alias.aliasName}: already used`);
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
if (alias.installedVersion === null) {
|
|
1423
|
+
alias.installedVersion = this.getAliasInstalledVersion(alias.aliasName);
|
|
1424
|
+
}
|
|
1425
|
+
if (!alias.installedVersion) {
|
|
1426
|
+
logger4.debug(` Skip ${alias.aliasName}: not installed`);
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
const allSatisfied = ranges.every((range) => satisfies(alias.installedVersion, range));
|
|
1430
|
+
if (allSatisfied) {
|
|
1431
|
+
logger4.info(` Reusing local alias: ${alias.aliasName}@${alias.installedVersion}`);
|
|
1432
|
+
return { name: alias.aliasName, version: alias.installedVersion };
|
|
1433
|
+
} else {
|
|
1434
|
+
logger4.debug(
|
|
1435
|
+
` Skip ${alias.aliasName}@${alias.installedVersion}: version doesn't satisfy all ranges`
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
const workspaceAliases = await this.findWorkspaceAliasesForPackage(packageName);
|
|
1441
|
+
const workspaceRoot = await this.workspaceDetector.getWorkspaceRoot();
|
|
1442
|
+
if (workspaceAliases.length > 0) {
|
|
1443
|
+
logger4.debug(` Checking ${workspaceAliases.length} workspace alias(es)...`);
|
|
1444
|
+
}
|
|
1445
|
+
for (const wsAlias of workspaceAliases) {
|
|
1446
|
+
if (excludeAliases.has(wsAlias.aliasName)) {
|
|
1447
|
+
logger4.debug(` Skip ${wsAlias.aliasName}: already used`);
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
let installedVersion = null;
|
|
1451
|
+
if (workspaceRoot) {
|
|
1452
|
+
const { packagePath, pkgJson } = this.resolvePackageJson(wsAlias.aliasName, workspaceRoot);
|
|
1453
|
+
if (packagePath) {
|
|
1454
|
+
installedVersion = pkgJson?.version ?? null;
|
|
1455
|
+
logger4.debug(
|
|
1456
|
+
` Found ${wsAlias.aliasName} installed at workspace root: ${installedVersion}`
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
if (!installedVersion) {
|
|
1461
|
+
installedVersion = this.getAliasInstalledVersion(wsAlias.aliasName);
|
|
1462
|
+
if (installedVersion) {
|
|
1463
|
+
logger4.debug(` Found ${wsAlias.aliasName} installed at project: ${installedVersion}`);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (installedVersion) {
|
|
1467
|
+
const allSatisfied = ranges.every((range) => satisfies(installedVersion, range));
|
|
1468
|
+
if (allSatisfied) {
|
|
1469
|
+
logger4.info(` Reusing workspace alias: ${wsAlias.aliasName}@${installedVersion}`);
|
|
1470
|
+
return {
|
|
1471
|
+
name: wsAlias.aliasName,
|
|
1472
|
+
version: installedVersion,
|
|
1473
|
+
isWorkspaceAlias: true
|
|
1474
|
+
};
|
|
1475
|
+
} else {
|
|
1476
|
+
logger4.debug(
|
|
1477
|
+
` Skip ${wsAlias.aliasName}@${installedVersion}: version doesn't satisfy all ranges`
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
const declaredSpec = wsAlias.versionSpec;
|
|
1483
|
+
if (declaredSpec) {
|
|
1484
|
+
logger4.debug(
|
|
1485
|
+
` Checking ${wsAlias.aliasName} (declared: ${declaredSpec}) - not installed yet`
|
|
1486
|
+
);
|
|
1487
|
+
const hasIntersection = ranges.every((range) => rangesIntersect(declaredSpec, range));
|
|
1488
|
+
if (hasIntersection) {
|
|
1489
|
+
logger4.info(
|
|
1490
|
+
` Can reuse workspace alias: ${wsAlias.aliasName} (declared: ${declaredSpec})`
|
|
1491
|
+
);
|
|
1492
|
+
return {
|
|
1493
|
+
name: wsAlias.aliasName,
|
|
1494
|
+
version: declaredSpec,
|
|
1495
|
+
isWorkspaceAlias: true
|
|
1496
|
+
};
|
|
1497
|
+
} else {
|
|
1498
|
+
logger4.debug(
|
|
1499
|
+
` Skip ${wsAlias.aliasName}: declared range ${declaredSpec} doesn't intersect with [${ranges.join(", ")}]`
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
logger4.debug(` No existing alias found for ${packageName}`);
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* 查找 workspace 级别的别名(带缓存)
|
|
1509
|
+
*/
|
|
1510
|
+
async findWorkspaceAliasesForPackage(packageName) {
|
|
1511
|
+
if (this.workspaceAliasesCache.has(packageName)) {
|
|
1512
|
+
return this.workspaceAliasesCache.get(packageName);
|
|
1513
|
+
}
|
|
1514
|
+
const aliases = await this.workspaceDetector.findWorkspaceAliases(packageName);
|
|
1515
|
+
this.workspaceAliasesCache.set(packageName, aliases);
|
|
1516
|
+
return aliases;
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* 生成唯一的别名名称,避免与已存在的别名冲突
|
|
1520
|
+
*/
|
|
1521
|
+
generateUniqueAliasName(packageName, version, existingNames) {
|
|
1522
|
+
const baseName = `${this.options.aliasPrefix}${generateAliasName(packageName, version)}`;
|
|
1523
|
+
if (!existingNames.has(baseName)) {
|
|
1524
|
+
return baseName;
|
|
1525
|
+
}
|
|
1526
|
+
const versionSuffix = version.replace(/\./g, "-");
|
|
1527
|
+
const nameWithVersion = `${this.options.aliasPrefix}${packageName.replace(/^@/, "").replace(/\//g, "-")}-${versionSuffix}`;
|
|
1528
|
+
if (!existingNames.has(nameWithVersion)) {
|
|
1529
|
+
return nameWithVersion;
|
|
1530
|
+
}
|
|
1531
|
+
let counter = 2;
|
|
1532
|
+
while (existingNames.has(`${baseName}-${counter}`)) {
|
|
1533
|
+
counter++;
|
|
1534
|
+
}
|
|
1535
|
+
return `${baseName}-${counter}`;
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* 获取包的所有已存在的别名名称(不管版本是否符合)
|
|
1539
|
+
* 用于生成新别名时避免命名冲突
|
|
1540
|
+
*
|
|
1541
|
+
* 包含当前项目和 workspace 级别的别名
|
|
1542
|
+
*/
|
|
1543
|
+
async getExistingAliasNames(packageName) {
|
|
1544
|
+
const names = /* @__PURE__ */ new Set();
|
|
1545
|
+
const npmAliases = this.existingAliasesMap.get(packageName);
|
|
1546
|
+
if (npmAliases) {
|
|
1547
|
+
for (const alias of npmAliases) {
|
|
1548
|
+
names.add(alias.aliasName);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
const workspaceAliases = await this.findWorkspaceAliasesForPackage(packageName);
|
|
1552
|
+
for (const wsAlias of workspaceAliases) {
|
|
1553
|
+
names.add(wsAlias.aliasName);
|
|
1554
|
+
}
|
|
1555
|
+
return names;
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* 递归收集指定包的所有子依赖(dependencies + peerDependencies)
|
|
1559
|
+
*
|
|
1560
|
+
* 用于构建 allDependents 列表,这些包引用冲突依赖时都需要做重定向解析
|
|
1561
|
+
*
|
|
1562
|
+
* 逻辑说明:
|
|
1563
|
+
* - 冲突检测:只检测第一层依赖的 peerDependencies 与主工程的冲突
|
|
1564
|
+
* - 重定向范围:声明冲突 peerDep 的包及其所有子依赖都需要重定向
|
|
1565
|
+
* 因为这些子依赖内部引用冲突包时,也需要解析到别名版本
|
|
1566
|
+
*
|
|
1567
|
+
* 性能优化:优先从 analyzedDependencies 内存数据中读取,避免重复磁盘 I/O
|
|
1568
|
+
*/
|
|
1569
|
+
collectAllSubDependenciesRecursive(packageName, visited, collected, baseDir) {
|
|
1570
|
+
const stack = [{ name: packageName, baseDir }];
|
|
1571
|
+
while (stack.length > 0) {
|
|
1572
|
+
const item = stack.pop();
|
|
1573
|
+
if (!item) break;
|
|
1574
|
+
const name = item.name;
|
|
1575
|
+
const currentBaseDir = item.baseDir;
|
|
1576
|
+
collected.add(name);
|
|
1577
|
+
const analyzed = this.analyzedDependencies.get(name);
|
|
1578
|
+
if (analyzed) {
|
|
1579
|
+
if (visited.has(name)) {
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1582
|
+
visited.add(name);
|
|
1583
|
+
for (const depName of Object.keys(analyzed.dependencies)) {
|
|
1584
|
+
stack.push({ name: depName, baseDir: currentBaseDir });
|
|
1585
|
+
}
|
|
1586
|
+
for (const peerName of Object.keys(analyzed.peerDependencies)) {
|
|
1587
|
+
stack.push({ name: peerName, baseDir: currentBaseDir });
|
|
1588
|
+
}
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
const { packagePath, pkgJson } = this.resolvePackageJson(name, currentBaseDir);
|
|
1592
|
+
if (!packagePath) {
|
|
1593
|
+
logger4.debug(
|
|
1594
|
+
`Package not found for dependency collection: ${name} (from ${currentBaseDir})`
|
|
1595
|
+
);
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
if (visited.has(packagePath)) {
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
visited.add(packagePath);
|
|
1602
|
+
if (!pkgJson) {
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
const deps = pkgJson.dependencies ?? {};
|
|
1606
|
+
const peerDeps = pkgJson.peerDependencies ?? {};
|
|
1607
|
+
for (const depName of Object.keys(deps)) {
|
|
1608
|
+
stack.push({ name: depName, baseDir: packagePath });
|
|
1609
|
+
}
|
|
1610
|
+
for (const peerName of Object.keys(peerDeps)) {
|
|
1611
|
+
stack.push({ name: peerName, baseDir: packagePath });
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* 收集多个包的所有子依赖(用于 allDependents)
|
|
1617
|
+
* 包括 dependencies 和 peerDependencies 递归下去的所有包
|
|
1618
|
+
*/
|
|
1619
|
+
collectAllRelatedPackages(packageNames, excludePackages = []) {
|
|
1620
|
+
const collected = /* @__PURE__ */ new Set();
|
|
1621
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1622
|
+
const excludeSet = new Set(excludePackages);
|
|
1623
|
+
for (const pkgName of packageNames) {
|
|
1624
|
+
this.collectAllSubDependenciesRecursive(
|
|
1625
|
+
pkgName,
|
|
1626
|
+
visited,
|
|
1627
|
+
collected,
|
|
1628
|
+
this.options.projectRoot
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
for (const excludePkg of excludeSet) {
|
|
1632
|
+
collected.delete(excludePkg);
|
|
1633
|
+
}
|
|
1634
|
+
return Array.from(collected);
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* 找出第一层依赖中缺失的 peer 依赖(无冲突)
|
|
1638
|
+
* 复用 collectFirstLevelPeerDependencies 已收集的 peerDepsMap,避免重复遍历
|
|
1639
|
+
*/
|
|
1640
|
+
findMissingFirstLevelPeers(peerDepsMap) {
|
|
1641
|
+
const missing = [];
|
|
1642
|
+
for (const [peerName, requests] of peerDepsMap) {
|
|
1643
|
+
if (this.isDeclaredInMainProject(peerName)) {
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
for (const request of requests) {
|
|
1647
|
+
const requestedBy = request.path[0];
|
|
1648
|
+
if (!requestedBy) continue;
|
|
1649
|
+
const depInfo = this.analyzedDependencies.get(requestedBy);
|
|
1650
|
+
const isOptional = depInfo?.peerDependenciesMeta?.[peerName]?.optional === true;
|
|
1651
|
+
if (isOptional) {
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
missing.push({
|
|
1655
|
+
packageName: peerName,
|
|
1656
|
+
requiredRange: request.requiredRange,
|
|
1657
|
+
requestedBy
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return missing;
|
|
1662
|
+
}
|
|
1663
|
+
};
|
|
1664
|
+
function createDependencyAnalyzer(options) {
|
|
1665
|
+
const mainPackageJson = readPackageJsonCached(options.projectRoot);
|
|
1666
|
+
if (!mainPackageJson) {
|
|
1667
|
+
throw new Error(`Could not read package.json at ${options.projectRoot}`);
|
|
1668
|
+
}
|
|
1669
|
+
return new DependencyAnalyzer(options, mainPackageJson);
|
|
1670
|
+
}
|
|
1671
|
+
var logger5 = createLogger("alias-manager");
|
|
1672
|
+
var AliasManager = class {
|
|
1673
|
+
options;
|
|
1674
|
+
aliasMappings = [];
|
|
1675
|
+
/**
|
|
1676
|
+
* 规则索引:originalName -> 相关规则列表
|
|
1677
|
+
* 用于在 resolveModule 时避免全量遍历所有规则,提升 dev server/HMR 场景下性能。
|
|
1678
|
+
*/
|
|
1679
|
+
rulesMap = /* @__PURE__ */ new Map();
|
|
1680
|
+
constructor(options) {
|
|
1681
|
+
this.options = options;
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* 从分析结果初始化别名映射
|
|
1685
|
+
*/
|
|
1686
|
+
initFromAnalysisResult(result) {
|
|
1687
|
+
this.aliasMappings = result.aliasMappings;
|
|
1688
|
+
this.buildRules();
|
|
1689
|
+
logger5.info(`Initialized ${this.aliasMappings.length} alias mappings`);
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* 基于 allDependents 构建规则(用 Set 做成员判断,避免超大正则导致的性能与长度问题)
|
|
1693
|
+
*/
|
|
1694
|
+
buildRules() {
|
|
1695
|
+
this.rulesMap = /* @__PURE__ */ new Map();
|
|
1696
|
+
for (const mapping of this.aliasMappings) {
|
|
1697
|
+
let dependents = mapping.allDependents ?? [];
|
|
1698
|
+
const excludeList = this.options.excludeRedirects[mapping.originalName] ?? [];
|
|
1699
|
+
if (excludeList.length > 0) {
|
|
1700
|
+
dependents = dependents.filter((dep) => !excludeList.includes(dep));
|
|
1701
|
+
logger5.debug(`Excluded redirects for ${mapping.originalName}: ${excludeList.join(", ")}`);
|
|
1702
|
+
}
|
|
1703
|
+
const rule = {
|
|
1704
|
+
originalName: mapping.originalName,
|
|
1705
|
+
aliasName: mapping.aliasName,
|
|
1706
|
+
dependents: new Set(dependents)
|
|
1707
|
+
};
|
|
1708
|
+
const list = this.rulesMap.get(rule.originalName);
|
|
1709
|
+
if (list) {
|
|
1710
|
+
list.push(rule);
|
|
1711
|
+
} else {
|
|
1712
|
+
this.rulesMap.set(rule.originalName, [rule]);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* 从模块请求中提取“包名部分”,用于索引命中:
|
|
1718
|
+
* - vue/compiler-sfc -> vue
|
|
1719
|
+
* - @scope/pkg/subpath -> @scope/pkg
|
|
1720
|
+
* - @scope/pkg/subpath?raw -> @scope/pkg
|
|
1721
|
+
* - virtual:xxx -> virtual:xxx
|
|
1722
|
+
* - C:\path\to\file (Windows) -> ''(视为非包请求)
|
|
1723
|
+
*/
|
|
1724
|
+
getRequestPackageName(request) {
|
|
1725
|
+
if (!request) return "";
|
|
1726
|
+
const base = request.split(/[?#]/, 1)[0] ?? "";
|
|
1727
|
+
if (!base) return "";
|
|
1728
|
+
if (/^[a-zA-Z]:[\\/]/.test(base)) {
|
|
1729
|
+
return "";
|
|
1730
|
+
}
|
|
1731
|
+
if (base.startsWith("@")) {
|
|
1732
|
+
const firstSlash = base.indexOf("/");
|
|
1733
|
+
if (firstSlash === -1) return base;
|
|
1734
|
+
const secondSlash = base.indexOf("/", firstSlash + 1);
|
|
1735
|
+
return secondSlash === -1 ? base : base.slice(0, secondSlash);
|
|
1736
|
+
}
|
|
1737
|
+
const slash = base.indexOf("/");
|
|
1738
|
+
return slash === -1 ? base : base.slice(0, slash);
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* 从 importer 路径中提取所有 node_modules 里出现过的包名(包含嵌套 node_modules)。
|
|
1742
|
+
*
|
|
1743
|
+
* 示例:
|
|
1744
|
+
* - /p/node_modules/a/index.js -> ["a"]
|
|
1745
|
+
* - /p/node_modules/a/node_modules/b/x.js -> ["a","b"]
|
|
1746
|
+
* - /p/node_modules/@s/a/dist/index.js -> ["@s/a"]
|
|
1747
|
+
* - /p/node_modules/.pnpm/a@1/node_modules/a/ -> ["a"](会跳过 ".pnpm")
|
|
1748
|
+
*/
|
|
1749
|
+
extractImporterPackages(importer) {
|
|
1750
|
+
const normalized = importer.replace(/\\/g, "/");
|
|
1751
|
+
const parts = normalized.split("/");
|
|
1752
|
+
const packages = [];
|
|
1753
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1754
|
+
if (parts[i] !== "node_modules") continue;
|
|
1755
|
+
const next = parts[i + 1];
|
|
1756
|
+
if (!next || next.startsWith(".")) {
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
if (next.startsWith("@")) {
|
|
1760
|
+
const name = parts[i + 2];
|
|
1761
|
+
if (name) {
|
|
1762
|
+
packages.push(`${next}/${name}`);
|
|
1763
|
+
}
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
packages.push(next);
|
|
1767
|
+
}
|
|
1768
|
+
return packages;
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* 解析模块请求
|
|
1772
|
+
* @returns 解析后的模块名,如果不需要重定向则返回 null
|
|
1773
|
+
*/
|
|
1774
|
+
resolveModule(context) {
|
|
1775
|
+
const { request, importer } = context;
|
|
1776
|
+
if (!importer) {
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
const importerPackages = this.extractImporterPackages(importer);
|
|
1780
|
+
if (importerPackages.length === 0) {
|
|
1781
|
+
return null;
|
|
1782
|
+
}
|
|
1783
|
+
const requestPackageName = this.getRequestPackageName(request);
|
|
1784
|
+
if (!requestPackageName) {
|
|
1785
|
+
return null;
|
|
1786
|
+
}
|
|
1787
|
+
const candidateRules = this.rulesMap.get(requestPackageName);
|
|
1788
|
+
if (!candidateRules || candidateRules.length === 0) {
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
for (const rule of candidateRules) {
|
|
1792
|
+
let shouldRedirect = false;
|
|
1793
|
+
for (const pkg of importerPackages) {
|
|
1794
|
+
if (rule.dependents.has(pkg)) {
|
|
1795
|
+
shouldRedirect = true;
|
|
1796
|
+
break;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
if (!shouldRedirect) {
|
|
1800
|
+
continue;
|
|
1801
|
+
}
|
|
1802
|
+
if (request === rule.originalName) {
|
|
1803
|
+
logger5.debug(`Resolving ${request} -> ${rule.aliasName} (from ${importer})`);
|
|
1804
|
+
return rule.aliasName;
|
|
1805
|
+
}
|
|
1806
|
+
const prefix = `${rule.originalName}/`;
|
|
1807
|
+
if (request.startsWith(prefix)) {
|
|
1808
|
+
const resolved = `${rule.aliasName}${request.slice(rule.originalName.length)}`;
|
|
1809
|
+
logger5.debug(`Resolving ${request} -> ${resolved} (from ${importer})`);
|
|
1810
|
+
return resolved;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return null;
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1816
|
+
* 获取别名路径映射
|
|
1817
|
+
* 返回别名名称到实际路径的映射,供构建工具插件转换为各自的格式
|
|
1818
|
+
*/
|
|
1819
|
+
getAliasPathMappings() {
|
|
1820
|
+
const mappings = [];
|
|
1821
|
+
for (const mapping of this.aliasMappings) {
|
|
1822
|
+
const aliasPath = findPackagePath(mapping.aliasName, this.options.projectRoot);
|
|
1823
|
+
const resolvedPath = aliasPath ?? join(this.options.projectRoot, "node_modules", mapping.aliasName);
|
|
1824
|
+
mappings.push({
|
|
1825
|
+
aliasName: mapping.aliasName,
|
|
1826
|
+
originalName: mapping.originalName,
|
|
1827
|
+
path: resolvedPath
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
return mappings;
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
function createAliasManager(options) {
|
|
1834
|
+
return new AliasManager(options);
|
|
1835
|
+
}
|
|
1836
|
+
var logger6 = createLogger("package-installer");
|
|
1837
|
+
var INSTALL_TIMEOUT = 12e4;
|
|
1838
|
+
var PackageInstaller = class {
|
|
1839
|
+
options;
|
|
1840
|
+
constructor(options) {
|
|
1841
|
+
this.options = options;
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* 安装别名依赖
|
|
1845
|
+
*/
|
|
1846
|
+
async installAliases(mappings) {
|
|
1847
|
+
const result = {
|
|
1848
|
+
success: true,
|
|
1849
|
+
installed: [],
|
|
1850
|
+
failed: [],
|
|
1851
|
+
errors: []
|
|
1852
|
+
};
|
|
1853
|
+
if (mappings.length === 0) {
|
|
1854
|
+
logger6.info("No aliases to install");
|
|
1855
|
+
return result;
|
|
1856
|
+
}
|
|
1857
|
+
const toInstall = mappings.filter((m) => {
|
|
1858
|
+
if (!m.installSpec) {
|
|
1859
|
+
logger6.debug(`Skipping ${m.aliasName}: reusing existing installation`);
|
|
1860
|
+
return false;
|
|
1861
|
+
}
|
|
1862
|
+
const aliasPath = join(this.options.projectRoot, "node_modules", m.aliasName);
|
|
1863
|
+
if (!fileExists(aliasPath)) {
|
|
1864
|
+
return true;
|
|
1865
|
+
}
|
|
1866
|
+
const versionMatch = this.verifyInstalledVersion(
|
|
1867
|
+
aliasPath,
|
|
1868
|
+
m.originalName,
|
|
1869
|
+
m.resolvedVersion
|
|
1870
|
+
);
|
|
1871
|
+
if (!versionMatch) {
|
|
1872
|
+
logger6.info(`${m.aliasName} exists but version mismatch, will reinstall`);
|
|
1873
|
+
return true;
|
|
1874
|
+
}
|
|
1875
|
+
logger6.debug(`${m.aliasName} already installed with correct version`);
|
|
1876
|
+
return false;
|
|
1877
|
+
});
|
|
1878
|
+
if (toInstall.length === 0) {
|
|
1879
|
+
logger6.info("All aliases already installed with correct versions");
|
|
1880
|
+
return result;
|
|
1881
|
+
}
|
|
1882
|
+
logger6.info(`Installing ${toInstall.length} alias packages...`);
|
|
1883
|
+
const installSpecs = toInstall.map((m) => m.installSpec);
|
|
1884
|
+
const installResult = await this.runInstall(installSpecs);
|
|
1885
|
+
if (installResult.success) {
|
|
1886
|
+
result.installed = toInstall.map((m) => m.aliasName);
|
|
1887
|
+
logger6.info(`Successfully installed: ${result.installed.join(", ")}`);
|
|
1888
|
+
} else {
|
|
1889
|
+
result.success = false;
|
|
1890
|
+
result.failed = toInstall.map((m) => m.aliasName);
|
|
1891
|
+
result.errors = installResult.errors;
|
|
1892
|
+
logger6.error(`Failed to install aliases: ${installResult.errors.join(", ")}`);
|
|
1893
|
+
}
|
|
1894
|
+
return result;
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* 验证已安装的别名版本是否正确
|
|
1898
|
+
* @returns true 如果版本匹配
|
|
1899
|
+
*/
|
|
1900
|
+
verifyInstalledVersion(aliasPath, expectedPackageName, expectedVersion) {
|
|
1901
|
+
try {
|
|
1902
|
+
const pkgJsonPath = join(aliasPath, "package.json");
|
|
1903
|
+
if (!fileExists(pkgJsonPath)) {
|
|
1904
|
+
return false;
|
|
1905
|
+
}
|
|
1906
|
+
const pkgJson = readPackageJsonCached(aliasPath);
|
|
1907
|
+
if (!pkgJson) {
|
|
1908
|
+
logger6.debug(`Failed to verify version: unable to read/parse ${pkgJsonPath}`);
|
|
1909
|
+
return false;
|
|
1910
|
+
}
|
|
1911
|
+
if (pkgJson.name !== expectedPackageName) {
|
|
1912
|
+
logger6.debug(
|
|
1913
|
+
`Package name mismatch: expected ${expectedPackageName}, got ${pkgJson.name ?? "unknown"}`
|
|
1914
|
+
);
|
|
1915
|
+
return false;
|
|
1916
|
+
}
|
|
1917
|
+
const installedClean = semver.clean(pkgJson.version ?? "");
|
|
1918
|
+
const expectedClean = semver.clean(expectedVersion);
|
|
1919
|
+
if (!installedClean || !expectedClean || installedClean !== expectedClean) {
|
|
1920
|
+
logger6.debug(
|
|
1921
|
+
`Version mismatch: expected ${expectedVersion}, got ${pkgJson.version ?? "unknown"}`
|
|
1922
|
+
);
|
|
1923
|
+
return false;
|
|
1924
|
+
}
|
|
1925
|
+
return true;
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
logger6.debug(`Failed to verify version: ${String(error)}`);
|
|
1928
|
+
return false;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* 执行安装命令
|
|
1933
|
+
*/
|
|
1934
|
+
async runInstall(packages) {
|
|
1935
|
+
if (packages.length === 0) {
|
|
1936
|
+
return { success: true, errors: [] };
|
|
1937
|
+
}
|
|
1938
|
+
const { packageManager, projectRoot } = this.options;
|
|
1939
|
+
let command;
|
|
1940
|
+
let args;
|
|
1941
|
+
switch (packageManager) {
|
|
1942
|
+
case "yarn":
|
|
1943
|
+
command = "yarn";
|
|
1944
|
+
args = ["add", ...packages];
|
|
1945
|
+
break;
|
|
1946
|
+
case "pnpm":
|
|
1947
|
+
command = "pnpm";
|
|
1948
|
+
args = ["add", ...packages];
|
|
1949
|
+
break;
|
|
1950
|
+
case "npm":
|
|
1951
|
+
default:
|
|
1952
|
+
command = "npm";
|
|
1953
|
+
args = ["install", "--legacy-peer-deps", ...packages];
|
|
1954
|
+
break;
|
|
1955
|
+
}
|
|
1956
|
+
const fullCommand = `${command} ${args.join(" ")}`;
|
|
1957
|
+
logger6.info(`Running: ${fullCommand}`);
|
|
1958
|
+
const startTime = Date.now();
|
|
1959
|
+
return new Promise((resolve) => {
|
|
1960
|
+
let resolved = false;
|
|
1961
|
+
const child = spawn(command, args, {
|
|
1962
|
+
cwd: projectRoot,
|
|
1963
|
+
shell: true,
|
|
1964
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1965
|
+
});
|
|
1966
|
+
let stdout = "";
|
|
1967
|
+
let stderr = "";
|
|
1968
|
+
const timeoutId = setTimeout(() => {
|
|
1969
|
+
if (!resolved) {
|
|
1970
|
+
resolved = true;
|
|
1971
|
+
logger6.error(`Install timeout after ${INSTALL_TIMEOUT / 1e3}s`);
|
|
1972
|
+
child.kill("SIGTERM");
|
|
1973
|
+
setTimeout(() => {
|
|
1974
|
+
if (!child.killed) {
|
|
1975
|
+
child.kill("SIGKILL");
|
|
1976
|
+
}
|
|
1977
|
+
}, 5e3);
|
|
1978
|
+
resolve({
|
|
1979
|
+
success: false,
|
|
1980
|
+
errors: [`Install timeout after ${INSTALL_TIMEOUT / 1e3}s`]
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
}, INSTALL_TIMEOUT);
|
|
1984
|
+
child.stdout?.on("data", (data) => {
|
|
1985
|
+
stdout += data.toString();
|
|
1986
|
+
});
|
|
1987
|
+
child.stderr?.on("data", (data) => {
|
|
1988
|
+
stderr += data.toString();
|
|
1989
|
+
});
|
|
1990
|
+
child.on("error", (error) => {
|
|
1991
|
+
if (!resolved) {
|
|
1992
|
+
resolved = true;
|
|
1993
|
+
clearTimeout(timeoutId);
|
|
1994
|
+
logger6.error(`Install error: ${error.message}`);
|
|
1995
|
+
resolve({
|
|
1996
|
+
success: false,
|
|
1997
|
+
errors: [error.message]
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
child.on("close", (code) => {
|
|
2002
|
+
if (!resolved) {
|
|
2003
|
+
resolved = true;
|
|
2004
|
+
clearTimeout(timeoutId);
|
|
2005
|
+
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
2006
|
+
if (code === 0) {
|
|
2007
|
+
if (stdout) {
|
|
2008
|
+
logger6.debug(`stdout: ${stdout}`);
|
|
2009
|
+
}
|
|
2010
|
+
logger6.info(`Install completed in ${elapsed}s`);
|
|
2011
|
+
resolve({ success: true, errors: [] });
|
|
2012
|
+
} else {
|
|
2013
|
+
logger6.error(`Install failed with exit code ${code}`);
|
|
2014
|
+
if (stdout) {
|
|
2015
|
+
logger6.debug(`stdout: ${stdout}`);
|
|
2016
|
+
}
|
|
2017
|
+
if (stderr) {
|
|
2018
|
+
logger6.debug(`stderr: ${stderr}`);
|
|
2019
|
+
}
|
|
2020
|
+
resolve({
|
|
2021
|
+
success: false,
|
|
2022
|
+
errors: stderr ? [stderr] : [`Process exited with code ${code}`]
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
});
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
};
|
|
2030
|
+
function createPackageInstaller(options) {
|
|
2031
|
+
return new PackageInstaller(options);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// src/core/resolver.ts
|
|
2035
|
+
var logger7 = createLogger("resolver");
|
|
2036
|
+
var DEFAULT_OPTIONS = {
|
|
2037
|
+
autoInstall: true,
|
|
2038
|
+
debug: false,
|
|
2039
|
+
aliasPrefix: "aliased-"
|
|
2040
|
+
};
|
|
2041
|
+
var DepsConflictResolver = class {
|
|
2042
|
+
options;
|
|
2043
|
+
userOptions;
|
|
2044
|
+
hooks;
|
|
2045
|
+
environmentDetector = null;
|
|
2046
|
+
analyzer = null;
|
|
2047
|
+
aliasManager;
|
|
2048
|
+
installer;
|
|
2049
|
+
analysisResult = null;
|
|
2050
|
+
initialized = false;
|
|
2051
|
+
constructor(userOptions, hooks = {}) {
|
|
2052
|
+
this.userOptions = userOptions;
|
|
2053
|
+
this.hooks = hooks;
|
|
2054
|
+
if (userOptions.debug) {
|
|
2055
|
+
logger7.setLevel(1 /* DEBUG */);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* 解析配置,填充默认值(异步,支持自动检测)
|
|
2060
|
+
*/
|
|
2061
|
+
async resolveOptions(userOptions, projectRoot) {
|
|
2062
|
+
this.environmentDetector = createEnvironmentDetector(projectRoot);
|
|
2063
|
+
let packageManager;
|
|
2064
|
+
if (!userOptions.packageManager || userOptions.packageManager === "auto") {
|
|
2065
|
+
const detected = await this.environmentDetector.getDetectionResult();
|
|
2066
|
+
packageManager = detected.packageManager;
|
|
2067
|
+
logger7.info(
|
|
2068
|
+
`Auto-detected package manager: ${packageManager} (from ${detected.detectedFrom})`
|
|
2069
|
+
);
|
|
2070
|
+
} else {
|
|
2071
|
+
packageManager = userOptions.packageManager;
|
|
2072
|
+
}
|
|
2073
|
+
let registry;
|
|
2074
|
+
if (!userOptions.registry) {
|
|
2075
|
+
registry = await this.environmentDetector.getRegistryForPackageManager(packageManager);
|
|
2076
|
+
if (registry !== DEFAULT_NPM_REGISTRY) {
|
|
2077
|
+
logger7.info(`Auto-detected registry: ${registry}`);
|
|
2078
|
+
}
|
|
2079
|
+
} else {
|
|
2080
|
+
registry = userOptions.registry;
|
|
2081
|
+
}
|
|
2082
|
+
return {
|
|
2083
|
+
...DEFAULT_OPTIONS,
|
|
2084
|
+
dependencies: userOptions.dependencies,
|
|
2085
|
+
projectRoot,
|
|
2086
|
+
packageManager,
|
|
2087
|
+
registry,
|
|
2088
|
+
autoInstall: userOptions.autoInstall ?? DEFAULT_OPTIONS.autoInstall,
|
|
2089
|
+
debug: userOptions.debug ?? DEFAULT_OPTIONS.debug,
|
|
2090
|
+
aliasPrefix: userOptions.aliasPrefix ?? DEFAULT_OPTIONS.aliasPrefix,
|
|
2091
|
+
excludeRedirects: userOptions.excludeRedirects ?? {}
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
/**
|
|
2095
|
+
* 初始化解析器
|
|
2096
|
+
*/
|
|
2097
|
+
async initialize() {
|
|
2098
|
+
if (this.initialized) {
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
logger7.info("Initializing dependencies resolver...");
|
|
2102
|
+
const projectRoot = this.userOptions.projectRoot ?? process.cwd();
|
|
2103
|
+
const validRoot = findProjectRoot(projectRoot);
|
|
2104
|
+
if (!validRoot) {
|
|
2105
|
+
throw new Error(`Could not find package.json in ${projectRoot} or parent directories`);
|
|
2106
|
+
}
|
|
2107
|
+
if (validRoot !== projectRoot) {
|
|
2108
|
+
logger7.debug(`Adjusted project root to: ${validRoot}`);
|
|
2109
|
+
}
|
|
2110
|
+
this.options = await this.resolveOptions(this.userOptions, validRoot);
|
|
2111
|
+
if (this.options.debug) {
|
|
2112
|
+
logger7.setLevel(1 /* DEBUG */);
|
|
2113
|
+
}
|
|
2114
|
+
this.aliasManager = createAliasManager(this.options);
|
|
2115
|
+
this.installer = createPackageInstaller(this.options);
|
|
2116
|
+
this.analyzer = createDependencyAnalyzer(this.options);
|
|
2117
|
+
this.analysisResult = await this.analyzer.analyze();
|
|
2118
|
+
this.logMissingFirstLevelPeers(this.analysisResult);
|
|
2119
|
+
this.aliasManager.initFromAnalysisResult(this.analysisResult);
|
|
2120
|
+
if (this.hooks.onAnalysisComplete) {
|
|
2121
|
+
await this.hooks.onAnalysisComplete(this.analysisResult);
|
|
2122
|
+
}
|
|
2123
|
+
if (this.options.autoInstall) {
|
|
2124
|
+
await this.installDependencies();
|
|
2125
|
+
}
|
|
2126
|
+
this.initialized = true;
|
|
2127
|
+
logger7.info("Dependencies resolver initialized");
|
|
2128
|
+
}
|
|
2129
|
+
/**
|
|
2130
|
+
* 输出第一层依赖中缺失的 peer 依赖警告(不自动安装,仅提示)
|
|
2131
|
+
*
|
|
2132
|
+
* 放在 initialize 阶段统一输出,避免各插件重复打印,且在 autoInstall=false 时也能看到提示。
|
|
2133
|
+
*/
|
|
2134
|
+
logMissingFirstLevelPeers(result) {
|
|
2135
|
+
const missing = result.missingFirstLevelPeers;
|
|
2136
|
+
if (!missing || missing.length === 0) {
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
logger7.warn(`Found ${missing.length} unsatisfied peer dependencies (not auto-installing):`);
|
|
2140
|
+
for (const peer of missing) {
|
|
2141
|
+
logger7.warn(
|
|
2142
|
+
` - ${peer.packageName}@${peer.requiredRange} (required by ${peer.requestedBy})`
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
/**
|
|
2147
|
+
* 安装所需依赖
|
|
2148
|
+
*
|
|
2149
|
+
* 安装策略(按用户需求):
|
|
2150
|
+
* 1. 只自动安装「主工程已声明的依赖 & peerDeps 要求版本与主工程版本冲突」的别名版本
|
|
2151
|
+
* 2. 缺失的 peer 依赖(主工程未声明):不自动安装,只警告
|
|
2152
|
+
* 3. 内部依赖冲突(子依赖之间的版本冲突):不自动安装,只做编译时重定向
|
|
2153
|
+
*
|
|
2154
|
+
* 这样可以避免"什么依赖都自动安装"的问题,用户可以根据警告自行决定是否安装缺失的依赖
|
|
2155
|
+
*/
|
|
2156
|
+
async installDependencies() {
|
|
2157
|
+
if (!this.analysisResult) {
|
|
2158
|
+
throw new Error("Resolver not initialized. Call initialize() first.");
|
|
2159
|
+
}
|
|
2160
|
+
const { aliasMappings } = this.analysisResult;
|
|
2161
|
+
const aliasResult = await this.installer.installAliases(aliasMappings);
|
|
2162
|
+
if (!aliasResult.success) {
|
|
2163
|
+
logger7.warn(`Some alias installations failed: ${aliasResult.errors.join(", ")}`);
|
|
2164
|
+
}
|
|
2165
|
+
if (this.hooks.onInstallComplete) {
|
|
2166
|
+
await this.hooks.onInstallComplete(aliasMappings);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* 解析模块请求
|
|
2171
|
+
* @returns 解析后的模块名,如果不需要重定向则返回 null
|
|
2172
|
+
*/
|
|
2173
|
+
resolveModule(request, importer) {
|
|
2174
|
+
if (this.hooks.beforeResolve) {
|
|
2175
|
+
const hookResult = this.hooks.beforeResolve(request, importer);
|
|
2176
|
+
if (hookResult !== void 0) {
|
|
2177
|
+
return hookResult;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
return this.aliasManager.resolveModule({ request, importer });
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2183
|
+
* 获取别名路径映射
|
|
2184
|
+
* 返回别名名称到实际路径的映射,供构建工具插件转换为各自的格式
|
|
2185
|
+
*/
|
|
2186
|
+
getAliasPathMappings() {
|
|
2187
|
+
return this.aliasManager.getAliasPathMappings();
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* 获取分析结果
|
|
2191
|
+
*/
|
|
2192
|
+
getAnalysisResult() {
|
|
2193
|
+
return this.analysisResult;
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* 获取配置
|
|
2197
|
+
*/
|
|
2198
|
+
getOptions() {
|
|
2199
|
+
return { ...this.options };
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
|
|
2203
|
+
// src/utils/module-request.ts
|
|
2204
|
+
function isPathLikeRequest(request) {
|
|
2205
|
+
if (!request) return false;
|
|
2206
|
+
if (request.startsWith(".")) {
|
|
2207
|
+
return true;
|
|
2208
|
+
}
|
|
2209
|
+
if (request.startsWith("/")) {
|
|
2210
|
+
return true;
|
|
2211
|
+
}
|
|
2212
|
+
if (/^[a-zA-Z]:[\\/]/.test(request)) {
|
|
2213
|
+
return true;
|
|
2214
|
+
}
|
|
2215
|
+
if (request.startsWith("\\\\")) {
|
|
2216
|
+
return true;
|
|
2217
|
+
}
|
|
2218
|
+
return false;
|
|
2219
|
+
}
|
|
2220
|
+
function isWebpackInternalRequest(request) {
|
|
2221
|
+
return request.startsWith("webpack/") || request.startsWith("!");
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
// src/plugins/webpack/index.ts
|
|
2225
|
+
var logger8 = createLogger("webpack-plugin");
|
|
2226
|
+
var PLUGIN_NAME = "DepsConflictResolverWebpackPlugin";
|
|
2227
|
+
var DepsConflictResolverWebpackPlugin = class {
|
|
2228
|
+
config;
|
|
2229
|
+
resolver = null;
|
|
2230
|
+
analysisResult = null;
|
|
2231
|
+
initPromise = null;
|
|
2232
|
+
initialized = false;
|
|
2233
|
+
constructor(config) {
|
|
2234
|
+
this.config = config;
|
|
2235
|
+
if (config.debug) {
|
|
2236
|
+
logger8.setLevel(1 /* DEBUG */);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* 初始化解析器
|
|
2241
|
+
*/
|
|
2242
|
+
async initialize() {
|
|
2243
|
+
if (this.initialized) return;
|
|
2244
|
+
logger8.info("Initializing dependencies resolver...");
|
|
2245
|
+
const { hooks, ...resolverOptions } = this.config;
|
|
2246
|
+
this.resolver = new DepsConflictResolver(resolverOptions, hooks);
|
|
2247
|
+
await this.resolver.initialize();
|
|
2248
|
+
this.analysisResult = this.resolver.getAnalysisResult();
|
|
2249
|
+
if (this.analysisResult && this.analysisResult.aliasMappings.length > 0) {
|
|
2250
|
+
logger8.info(`Detected ${this.analysisResult.aliasMappings.length} peer dependency conflicts`);
|
|
2251
|
+
if (this.config.debug) {
|
|
2252
|
+
logger8.debug(
|
|
2253
|
+
"Alias mappings:",
|
|
2254
|
+
this.analysisResult.aliasMappings.map((m) => ({
|
|
2255
|
+
original: m.originalName,
|
|
2256
|
+
alias: m.aliasName,
|
|
2257
|
+
usedBy: m.usedBy,
|
|
2258
|
+
allDependents: m.allDependents
|
|
2259
|
+
}))
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2262
|
+
} else {
|
|
2263
|
+
logger8.info("No peer dependency conflicts detected");
|
|
2264
|
+
}
|
|
2265
|
+
this.initialized = true;
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* 处理模块解析请求
|
|
2269
|
+
* 直接修改 resolveData.request 来实现重定向
|
|
2270
|
+
*
|
|
2271
|
+
* 注意:这里直接使用别名包名作为新的请求,而不是转换为文件系统路径
|
|
2272
|
+
* 因为 webpack 5 的 ESM 模式需要模块请求而不是目录路径
|
|
2273
|
+
*/
|
|
2274
|
+
handleResolve(resolveData) {
|
|
2275
|
+
const { request, contextInfo } = resolveData;
|
|
2276
|
+
if (!request || isPathLikeRequest(request)) {
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
if (isWebpackInternalRequest(request)) {
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
if (!this.resolver) {
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
const importer = contextInfo.issuer || resolveData.context;
|
|
2286
|
+
if (!importer) {
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
const resolvedModule = this.resolver.resolveModule(request, importer);
|
|
2290
|
+
if (!resolvedModule) {
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
if (this.config.debug) {
|
|
2294
|
+
logger8.debug(`Redirecting ${request} -> ${resolvedModule} (importer: ${importer})`);
|
|
2295
|
+
}
|
|
2296
|
+
resolveData.request = resolvedModule;
|
|
2297
|
+
}
|
|
2298
|
+
apply(compiler) {
|
|
2299
|
+
logger8.info("Applying DepsConflictResolverWebpackPlugin...");
|
|
2300
|
+
this.initPromise = this.initialize();
|
|
2301
|
+
compiler.hooks.beforeRun.tapPromise(PLUGIN_NAME, async () => {
|
|
2302
|
+
await this.initPromise;
|
|
2303
|
+
});
|
|
2304
|
+
compiler.hooks.watchRun.tapPromise(PLUGIN_NAME, async () => {
|
|
2305
|
+
await this.initPromise;
|
|
2306
|
+
});
|
|
2307
|
+
compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, (nmf) => {
|
|
2308
|
+
nmf.hooks.beforeResolve.tapAsync(PLUGIN_NAME, (resolveData, callback) => {
|
|
2309
|
+
this.initPromise.then(() => {
|
|
2310
|
+
this.handleResolve(resolveData);
|
|
2311
|
+
callback();
|
|
2312
|
+
}).catch((err) => {
|
|
2313
|
+
logger8.error("Error during module resolution:", err);
|
|
2314
|
+
callback();
|
|
2315
|
+
});
|
|
2316
|
+
});
|
|
2317
|
+
});
|
|
2318
|
+
compiler.hooks.done.tap(PLUGIN_NAME, (stats) => {
|
|
2319
|
+
if (stats.hasErrors()) {
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2322
|
+
if (this.analysisResult && this.analysisResult.aliasMappings.length > 0) {
|
|
2323
|
+
logger8.info(
|
|
2324
|
+
`Resolved ${this.analysisResult.aliasMappings.length} peer dependency conflicts`
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* 获取解析器实例
|
|
2331
|
+
*/
|
|
2332
|
+
getResolver() {
|
|
2333
|
+
return this.resolver;
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* 获取分析结果
|
|
2337
|
+
*/
|
|
2338
|
+
getAnalysisResult() {
|
|
2339
|
+
return this.analysisResult;
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* 获取别名配置
|
|
2343
|
+
* 返回 webpack 格式的别名映射
|
|
2344
|
+
*/
|
|
2345
|
+
getAliasConfig() {
|
|
2346
|
+
if (!this.resolver) {
|
|
2347
|
+
return {};
|
|
2348
|
+
}
|
|
2349
|
+
const mappings = this.resolver.getAliasPathMappings();
|
|
2350
|
+
const aliases = {};
|
|
2351
|
+
for (const mapping of mappings) {
|
|
2352
|
+
aliases[mapping.aliasName] = mapping.path;
|
|
2353
|
+
}
|
|
2354
|
+
return aliases;
|
|
2355
|
+
}
|
|
2356
|
+
/**
|
|
2357
|
+
* 检查是否已初始化
|
|
2358
|
+
*/
|
|
2359
|
+
isInitialized() {
|
|
2360
|
+
return this.initialized;
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
function createWebpackPlugin(config) {
|
|
2364
|
+
return new DepsConflictResolverWebpackPlugin(config);
|
|
2365
|
+
}
|
|
2366
|
+
var webpack_default = DepsConflictResolverWebpackPlugin;
|
|
2367
|
+
|
|
2368
|
+
export { DepsConflictResolverWebpackPlugin, createWebpackPlugin, webpack_default as default };
|
|
2369
|
+
//# sourceMappingURL=webpack.js.map
|
|
2370
|
+
//# sourceMappingURL=webpack.js.map
|