tanmi-dock 0.8.3 → 0.9.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 +6 -1
- package/dist/commands/check.d.ts +2 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +87 -12
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +36 -2
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/link.d.ts +1 -0
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +577 -632
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +89 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/core/codepac.d.ts +2 -0
- package/dist/core/codepac.d.ts.map +1 -1
- package/dist/core/codepac.js +27 -5
- package/dist/core/codepac.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +3 -1
- package/dist/core/config.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/git.d.ts +36 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +138 -0
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/progress.d.ts +8 -0
- package/dist/utils/progress.d.ts.map +1 -1
- package/dist/utils/progress.js +45 -5
- package/dist/utils/progress.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/link.js
CHANGED
|
@@ -13,13 +13,13 @@ import * as store from '../core/store.js';
|
|
|
13
13
|
import * as linker from '../core/linker.js';
|
|
14
14
|
import * as codepac from '../core/codepac.js';
|
|
15
15
|
import { setProxyConfig } from '../core/codepac.js';
|
|
16
|
-
import { resolvePath, getPlatformHelpText, GENERAL_PLATFORM, SHARED_PLATFORM, pathsEqual, isSparseOnlyCommon } from '../core/platform.js';
|
|
16
|
+
import { resolvePath, getPlatformHelpText, GENERAL_PLATFORM, SHARED_PLATFORM, pathsEqual, isSparseOnlyCommon, KNOWN_PLATFORM_VALUES } from '../core/platform.js';
|
|
17
17
|
import { Transaction } from '../core/transaction.js';
|
|
18
18
|
import { formatSize, checkDiskSpace } from '../utils/disk.js';
|
|
19
19
|
import { getDirSize } from '../utils/fs-utils.js';
|
|
20
20
|
import { ProgressTracker, DownloadMonitor, MultiBarManager } from '../utils/progress.js';
|
|
21
21
|
import { success, warn, error, info, hint, blank, separator, debug } from '../utils/logger.js';
|
|
22
|
-
import { verifyLocalCommit } from '../utils/git.js';
|
|
22
|
+
import { verifyLocalCommit, findSubmoduleConfigs } from '../utils/git.js';
|
|
23
23
|
import { DependencyStatus } from '../types/index.js';
|
|
24
24
|
import { withGlobalLock } from '../utils/global-lock.js';
|
|
25
25
|
import { selectPlatforms, parsePlatformArgs, selectOption, selectOptionalConfigs, PROMPT_CANCELLED } from '../utils/prompt.js';
|
|
@@ -37,6 +37,7 @@ export function createLinkCommand() {
|
|
|
37
37
|
.option('--no-download', '不自动下载缺失库')
|
|
38
38
|
.option('--dry-run', '只显示将要执行的操作')
|
|
39
39
|
.option('--config <configs...>', '指定可选配置文件 (如 codepac-dep-inner.json)')
|
|
40
|
+
.option('--no-submodules', '不检测 git submodule 依赖')
|
|
40
41
|
.addHelpText('after', `${getPlatformHelpText()}
|
|
41
42
|
|
|
42
43
|
示例:
|
|
@@ -46,7 +47,8 @@ export function createLinkCommand() {
|
|
|
46
47
|
td link -p mac android 链接多个平台
|
|
47
48
|
td link --dry-run 预览操作,不实际执行
|
|
48
49
|
td link -y 跳过确认,自动执行
|
|
49
|
-
td link --config codepac-dep-inner.json
|
|
50
|
+
td link --config codepac-dep-inner.json 使用指定的可选配置
|
|
51
|
+
td link --no-submodules 跳过 git submodule 检测`)
|
|
50
52
|
.action(async (projectPath, options) => {
|
|
51
53
|
await ensureInitialized();
|
|
52
54
|
try {
|
|
@@ -59,120 +61,69 @@ export function createLinkCommand() {
|
|
|
59
61
|
});
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
62
|
-
*
|
|
64
|
+
* 选择要链接的 submodule
|
|
63
65
|
*/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (cfg?.proxy) {
|
|
72
|
-
setProxyConfig(cfg.proxy);
|
|
66
|
+
async function selectSubmodules(configs, options, remembered) {
|
|
67
|
+
if (configs.length === 0)
|
|
68
|
+
return [];
|
|
69
|
+
let selected;
|
|
70
|
+
if (options.yes) {
|
|
71
|
+
// --yes 模式:自动包含所有
|
|
72
|
+
selected = configs;
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
info('已取消');
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
platforms = selectedPlatforms;
|
|
95
|
-
if (platforms.length === 0) {
|
|
96
|
-
error('至少需要选择一个平台');
|
|
97
|
-
process.exit(EXIT_CODES.MISUSE);
|
|
74
|
+
else if (process.stdout.isTTY) {
|
|
75
|
+
// 交互模式:checkbox 选择
|
|
76
|
+
info('检测到 git submodule 包含依赖配置:');
|
|
77
|
+
blank();
|
|
78
|
+
const { checkboxWithCancel, PROMPT_CANCELLED } = await import('../utils/prompt.js');
|
|
79
|
+
const choices = configs.map(c => ({
|
|
80
|
+
name: `${c.name} (${c.depCount} 个库)`,
|
|
81
|
+
value: c.relativePath,
|
|
82
|
+
// 首次全选,之后使用记忆的选择
|
|
83
|
+
checked: remembered ? remembered.includes(c.relativePath) : true,
|
|
84
|
+
}));
|
|
85
|
+
const selectedPaths = await checkboxWithCancel({
|
|
86
|
+
message: '选择要一并链接的子模块:',
|
|
87
|
+
choices,
|
|
88
|
+
});
|
|
89
|
+
if (selectedPaths === PROMPT_CANCELLED) {
|
|
90
|
+
return [];
|
|
98
91
|
}
|
|
92
|
+
selected = configs.filter(c => selectedPaths.includes(c.relativePath));
|
|
99
93
|
}
|
|
100
94
|
else {
|
|
101
|
-
//
|
|
102
|
-
error('
|
|
103
|
-
hint(
|
|
95
|
+
// 非 TTY 且没有 --yes
|
|
96
|
+
error('发现 git submodule 依赖,非交互模式下必须使用 --yes 或 --no-submodules');
|
|
97
|
+
hint(`检测到子模块: ${configs.map(c => c.name).join(', ')}`);
|
|
98
|
+
hint('示例: td link --yes -p mac (包含所有子模块)');
|
|
99
|
+
hint(' td link --no-submodules -p mac (跳过子模块)');
|
|
104
100
|
process.exit(EXIT_CODES.MISUSE);
|
|
105
101
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (configDiscovery && configDiscovery.optionalConfigs.length > 0) {
|
|
123
|
-
// 有可选配置
|
|
124
|
-
if (options.config && options.config.length > 0) {
|
|
125
|
-
// CLI 指定了配置文件名,按名称查找对应配置
|
|
126
|
-
for (const configName of options.config) {
|
|
127
|
-
const found = configDiscovery.optionalConfigs.find(c => c.name === configName);
|
|
128
|
-
if (!found) {
|
|
129
|
-
error(`找不到指定的配置: ${configName}`);
|
|
130
|
-
hint(`可用配置: ${configDiscovery.optionalConfigs.map(c => c.name).join(', ')}`);
|
|
131
|
-
process.exit(EXIT_CODES.MISUSE);
|
|
132
|
-
}
|
|
133
|
-
selectedOptionalConfigs.push(found);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
else if (options.yes) {
|
|
137
|
-
// --yes 模式:跳过可选配置选择(无论 TTY 还是非 TTY)
|
|
138
|
-
// selectedOptionalConfigs 保持为空
|
|
139
|
-
}
|
|
140
|
-
else if (process.stdout.isTTY) {
|
|
141
|
-
// TTY 交互模式:显示配置选择
|
|
142
|
-
const rememberedConfigs = existingProject?.optionalConfigs ?? [];
|
|
143
|
-
const selectOptions = {
|
|
144
|
-
isTTY: true,
|
|
145
|
-
specifiedConfigs: rememberedConfigs,
|
|
146
|
-
};
|
|
147
|
-
const selected = await selectOptionalConfigs(configDiscovery.optionalConfigs, selectOptions);
|
|
148
|
-
if (selected === PROMPT_CANCELLED) {
|
|
149
|
-
info('已取消');
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
selectedOptionalConfigs = selected;
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
// 非 TTY 模式且没有 --yes 或 --config:必须指定 --config
|
|
156
|
-
error('发现可选配置文件,非交互模式下必须使用 --config 或 --yes 参数');
|
|
157
|
-
hint(`可用配置: ${configDiscovery.optionalConfigs.map(c => c.name).join(', ')}`);
|
|
158
|
-
hint(`示例: td link --config ${configDiscovery.optionalConfigs[0].name}`);
|
|
159
|
-
hint('或使用 --yes 跳过可选配置');
|
|
160
|
-
process.exit(EXIT_CODES.MISUSE);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// 解析依赖
|
|
164
|
-
info(`分析 ${absolutePath}`);
|
|
102
|
+
return selected.map(s => ({ ...s }));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 单个 scope(主项目或 submodule)的链接处理
|
|
106
|
+
*
|
|
107
|
+
* 包含:解析依赖、分类、预扫描平台、下载确认、逐个处理、并行下载、嵌套 actions
|
|
108
|
+
*/
|
|
109
|
+
async function linkScope(params) {
|
|
110
|
+
const { scopeName, configPath, platforms, scanExtraPlatforms, registry, tx, projectHash, projectRoot, storePath, download, dryRun, yes, concurrency, optionalConfigs, scope, } = params;
|
|
111
|
+
let finalLinkPlatforms = [...params.finalLinkPlatforms];
|
|
112
|
+
// 记录 General 类型库
|
|
113
|
+
const generalLibs = new Set();
|
|
114
|
+
const downloadedLibs = [];
|
|
115
|
+
let savedBytes = 0;
|
|
116
|
+
// 1. 解析依赖
|
|
117
|
+
info(`分析 ${scopeName}: ${configPath}`);
|
|
165
118
|
let dependencies;
|
|
166
|
-
let configPath;
|
|
167
119
|
let configVars;
|
|
168
120
|
try {
|
|
169
|
-
const result = await parseProjectDependencies(
|
|
121
|
+
const result = await parseProjectDependencies(path.dirname(path.dirname(configPath)));
|
|
170
122
|
dependencies = result.dependencies;
|
|
171
|
-
configPath = result.configPath;
|
|
172
123
|
configVars = result.vars;
|
|
173
|
-
//
|
|
174
|
-
if (
|
|
175
|
-
for (const optionalConfig of
|
|
124
|
+
// 合并可选配置依赖
|
|
125
|
+
if (optionalConfigs && optionalConfigs.length > 0) {
|
|
126
|
+
for (const optionalConfig of optionalConfigs) {
|
|
176
127
|
try {
|
|
177
128
|
const optionalResult = await parseCodepacDep(optionalConfig.path);
|
|
178
129
|
const optionalDeps = extractDependencies(optionalResult);
|
|
@@ -189,61 +140,28 @@ export async function linkProject(projectPath, options) {
|
|
|
189
140
|
error(err.message);
|
|
190
141
|
process.exit(EXIT_CODES.DATAERR);
|
|
191
142
|
}
|
|
192
|
-
// 规范化项目根目录:确保始终登记在包含 3rdparty 的目录
|
|
193
|
-
const normalizedRoot = normalizeProjectRoot(absolutePath, configPath);
|
|
194
|
-
const wasNormalized = normalizedRoot !== absolutePath;
|
|
195
|
-
// 如果路径被规范化了(用户在 3rdparty 目录运行),需要处理迁移
|
|
196
|
-
if (wasNormalized) {
|
|
197
|
-
info(`项目根目录规范化: ${absolutePath} → ${normalizedRoot}`);
|
|
198
|
-
// 检查旧路径是否在 registry 中
|
|
199
|
-
const oldHash = registry.hashPath(absolutePath);
|
|
200
|
-
const oldProject = registry.getProject(oldHash);
|
|
201
|
-
if (oldProject) {
|
|
202
|
-
// 迁移旧登记到新路径
|
|
203
|
-
info('迁移旧的项目登记...');
|
|
204
|
-
registry.removeProject(oldHash);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
// 检查是否存在指向同一配置文件的其他登记(清理历史脏数据)
|
|
208
|
-
const absConfigPath = path.resolve(normalizedRoot, getRelativeConfigPath(normalizedRoot, configPath));
|
|
209
|
-
const allProjects = registry.listProjects();
|
|
210
|
-
for (const proj of allProjects) {
|
|
211
|
-
if (pathsEqual(proj.path, normalizedRoot))
|
|
212
|
-
continue;
|
|
213
|
-
const projAbsConfig = path.resolve(proj.path, proj.configPath);
|
|
214
|
-
if (pathsEqual(projAbsConfig, absConfigPath)) {
|
|
215
|
-
info(`清理重复登记: ${proj.path}`);
|
|
216
|
-
registry.removeProject(registry.hashPath(proj.path));
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
// 使用规范化后的路径继续
|
|
220
|
-
const finalPath = normalizedRoot;
|
|
221
143
|
info(`找到 ${dependencies.length} 个依赖,平台: ${platforms.join(', ')}`);
|
|
222
144
|
blank();
|
|
223
|
-
//
|
|
224
|
-
const
|
|
225
|
-
|
|
145
|
+
// 2. 分类依赖
|
|
146
|
+
const scopePath = path.dirname(path.dirname(configPath));
|
|
147
|
+
const classified = await classifyDependencies(dependencies, scopePath, configPath, platforms);
|
|
226
148
|
const stats = {
|
|
227
|
-
linked: 0,
|
|
228
|
-
relink: 0,
|
|
229
|
-
replace: 0,
|
|
230
|
-
absorb: 0,
|
|
231
|
-
missing: 0,
|
|
232
|
-
linkNew: 0,
|
|
149
|
+
linked: 0, relink: 0, replace: 0, absorb: 0, missing: 0, linkNew: 0,
|
|
233
150
|
};
|
|
234
151
|
for (const item of classified) {
|
|
235
152
|
stats[getStatusKey(item.status)]++;
|
|
236
153
|
}
|
|
237
|
-
//
|
|
238
|
-
if (
|
|
154
|
+
// 3. dry-run
|
|
155
|
+
if (dryRun) {
|
|
239
156
|
showDryRunInfo(classified, stats);
|
|
240
|
-
return
|
|
157
|
+
return {
|
|
158
|
+
linkedDeps: [], nestedLinkedDeps: [], generalLibs, downloadedLibs,
|
|
159
|
+
savedBytes: 0, finalLinkPlatforms, stats,
|
|
160
|
+
};
|
|
241
161
|
}
|
|
242
|
-
//
|
|
243
|
-
if (stats.missing > 0 &&
|
|
244
|
-
// 估算下载所需空间(每个库估算 500MB)
|
|
162
|
+
// 4. 磁盘空间预检
|
|
163
|
+
if (stats.missing > 0 && download) {
|
|
245
164
|
const estimatedSize = stats.missing * 500 * 1024 * 1024;
|
|
246
|
-
const storePath = await store.getStorePath();
|
|
247
165
|
const spaceCheck = await checkDiskSpace(storePath, estimatedSize);
|
|
248
166
|
if (!spaceCheck.sufficient) {
|
|
249
167
|
error(`磁盘空间不足: 预计需要 ${formatSize(spaceCheck.required)},可用 ${formatSize(spaceCheck.available)}(含 1GB 安全余量)`);
|
|
@@ -253,34 +171,9 @@ export async function linkProject(projectPath, options) {
|
|
|
253
171
|
warn('无法获取磁盘空间信息,继续执行');
|
|
254
172
|
}
|
|
255
173
|
}
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
if (pendingTx) {
|
|
259
|
-
warn(`发现未完成的事务 (${pendingTx.id.slice(0, 8)})`);
|
|
260
|
-
info('正在尝试回滚...');
|
|
261
|
-
try {
|
|
262
|
-
await pendingTx.rollback();
|
|
263
|
-
success('事务回滚完成');
|
|
264
|
-
}
|
|
265
|
-
catch (err) {
|
|
266
|
-
error(`回滚失败: ${err.message}`);
|
|
267
|
-
hint('请手动检查 Store 和项目目录状态');
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
// 执行链接(registry 已在前面加载)
|
|
271
|
-
let savedBytes = 0;
|
|
272
|
-
const storePath = await store.getStorePath();
|
|
273
|
-
const projectHash = registry.hashPath(finalPath);
|
|
274
|
-
// 创建事务
|
|
275
|
-
const tx = new Transaction(`link:${finalPath}`);
|
|
276
|
-
await tx.begin();
|
|
277
|
-
// 记录 General 类型库(用于最后生成 dependencies 时使用正确的 platform)
|
|
278
|
-
const generalLibs = new Set();
|
|
279
|
-
// 预扫描所有本地存在的依赖的额外平台,让用户选择要链接的平台
|
|
280
|
-
let finalLinkPlatforms = platforms; // 默认为用户请求的平台
|
|
281
|
-
if (!options.yes && process.stdout.isTTY) {
|
|
174
|
+
// 5. 预扫描额外平台(仅主项目 scope)
|
|
175
|
+
if (scanExtraPlatforms && !yes && process.stdout.isTTY) {
|
|
282
176
|
const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
|
|
283
|
-
// 收集所有本地存在的平台(去重)- 扫描所有有本地目录的依赖
|
|
284
177
|
const allLocalPlatforms = new Set();
|
|
285
178
|
for (const item of classified) {
|
|
286
179
|
try {
|
|
@@ -293,10 +186,9 @@ export async function linkProject(projectPath, options) {
|
|
|
293
186
|
.forEach(e => allLocalPlatforms.add(e.name));
|
|
294
187
|
}
|
|
295
188
|
catch {
|
|
296
|
-
//
|
|
189
|
+
// 读取失败,跳过
|
|
297
190
|
}
|
|
298
191
|
}
|
|
299
|
-
// 检测额外平台
|
|
300
192
|
const extraPlatforms = [...allLocalPlatforms].filter(p => !platforms.includes(p));
|
|
301
193
|
if (extraPlatforms.length > 0) {
|
|
302
194
|
info(`本地检测到额外平台: ${extraPlatforms.join(', ')}`);
|
|
@@ -306,15 +198,15 @@ export async function linkProject(projectPath, options) {
|
|
|
306
198
|
const selectedPlatforms = await checkboxWithCancel({
|
|
307
199
|
message: '选择要链接的平台 (未选择的将被删除):',
|
|
308
200
|
choices: allAvailable.map(p => ({
|
|
309
|
-
name: p,
|
|
310
|
-
value: p,
|
|
311
|
-
checked: platforms.includes(p), // 用户请求的默认勾选
|
|
201
|
+
name: p, value: p, checked: platforms.includes(p),
|
|
312
202
|
})),
|
|
313
203
|
});
|
|
314
|
-
// ESC 取消
|
|
315
204
|
if (selectedPlatforms === PROMPT_CANCELLED) {
|
|
316
205
|
info('已取消');
|
|
317
|
-
return
|
|
206
|
+
return {
|
|
207
|
+
linkedDeps: [], nestedLinkedDeps: [], generalLibs, downloadedLibs,
|
|
208
|
+
savedBytes: 0, finalLinkPlatforms, stats,
|
|
209
|
+
};
|
|
318
210
|
}
|
|
319
211
|
finalLinkPlatforms = selectedPlatforms;
|
|
320
212
|
if (finalLinkPlatforms.length === 0) {
|
|
@@ -324,27 +216,32 @@ export async function linkProject(projectPath, options) {
|
|
|
324
216
|
blank();
|
|
325
217
|
}
|
|
326
218
|
}
|
|
327
|
-
//
|
|
328
|
-
const downloadConfirmedLibs = new Set();
|
|
329
|
-
let skipAllDownloads = false;
|
|
330
|
-
if (
|
|
219
|
+
// 6. 预扫描下载确认
|
|
220
|
+
const downloadConfirmedLibs = new Set();
|
|
221
|
+
let skipAllDownloads = false;
|
|
222
|
+
if (download && !yes) {
|
|
331
223
|
const needDownloadItems = [];
|
|
332
224
|
for (const item of classified) {
|
|
333
225
|
const { dependency, status } = item;
|
|
334
226
|
if (status === DependencyStatus.MISSING) {
|
|
335
227
|
needDownloadItems.push({
|
|
336
|
-
libName: dependency.libName,
|
|
337
|
-
commit: dependency.commit,
|
|
338
|
-
reason: '缺失',
|
|
228
|
+
libName: dependency.libName, commit: dependency.commit, reason: '缺失',
|
|
339
229
|
});
|
|
340
230
|
}
|
|
231
|
+
else if (status === DependencyStatus.ABSORB) {
|
|
232
|
+
const { missing } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
|
|
233
|
+
if (missing.length > 0) {
|
|
234
|
+
needDownloadItems.push({
|
|
235
|
+
libName: dependency.libName, commit: dependency.commit,
|
|
236
|
+
reason: `补充平台 [${missing.join(', ')}]`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
341
240
|
else if (status === DependencyStatus.LINK_NEW) {
|
|
342
|
-
// 检查是否有缺失平台
|
|
343
241
|
const { missing } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
|
|
344
242
|
if (missing.length > 0) {
|
|
345
243
|
needDownloadItems.push({
|
|
346
|
-
libName: dependency.libName,
|
|
347
|
-
commit: dependency.commit,
|
|
244
|
+
libName: dependency.libName, commit: dependency.commit,
|
|
348
245
|
reason: `补充平台 [${missing.join(', ')}]`,
|
|
349
246
|
});
|
|
350
247
|
}
|
|
@@ -363,7 +260,6 @@ export async function linkProject(projectPath, options) {
|
|
|
363
260
|
skipAllDownloads = true;
|
|
364
261
|
}
|
|
365
262
|
else {
|
|
366
|
-
// 用户确认下载,记录所有需要下载的库
|
|
367
263
|
for (const item of needDownloadItems) {
|
|
368
264
|
downloadConfirmedLibs.add(`${item.libName}@${item.commit}`);
|
|
369
265
|
}
|
|
@@ -371,34 +267,28 @@ export async function linkProject(projectPath, options) {
|
|
|
371
267
|
blank();
|
|
372
268
|
}
|
|
373
269
|
}
|
|
374
|
-
else if (
|
|
375
|
-
// --yes 模式:标记所有库为已确认
|
|
270
|
+
else if (download && yes) {
|
|
376
271
|
for (const item of classified) {
|
|
377
272
|
downloadConfirmedLibs.add(`${item.dependency.libName}@${item.dependency.commit}`);
|
|
378
273
|
}
|
|
379
274
|
}
|
|
275
|
+
// 7. 逐个处理依赖(LINKED/RELINK/REPLACE/ABSORB/LINK_NEW)
|
|
380
276
|
try {
|
|
381
277
|
for (const item of classified) {
|
|
382
278
|
const { dependency, status, localPath } = item;
|
|
383
279
|
const libKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
384
|
-
// 检查 Store 版本兼容性(v0.5 旧结构会报错)
|
|
385
280
|
await store.ensureCompatibleStore(storePath, dependency.libName, dependency.commit);
|
|
386
281
|
switch (status) {
|
|
387
282
|
case DependencyStatus.LINKED: {
|
|
388
|
-
// 已链接,检查是否需要补充缺失平台
|
|
389
283
|
const isLinkedGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
|
|
390
284
|
if (!isLinkedGeneral) {
|
|
391
|
-
// 平台库:检查并补充缺失平台
|
|
392
285
|
const supplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
|
|
393
|
-
// 注册嵌套依赖
|
|
394
286
|
await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
|
|
395
287
|
if (supplementResult.downloaded.length > 0) {
|
|
396
|
-
// 有新平台下载,需要重新链接所有平台
|
|
397
288
|
const linkedCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
398
289
|
const { existing: allExisting } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
|
|
399
290
|
tx.recordOp('link', localPath, linkedCommitPath);
|
|
400
291
|
await linker.linkLib(localPath, linkedCommitPath, allExisting);
|
|
401
|
-
// 更新 StoreEntry 引用
|
|
402
292
|
for (const platform of allExisting) {
|
|
403
293
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
404
294
|
registry.addStoreReference(storeKey, projectHash);
|
|
@@ -409,29 +299,21 @@ export async function linkProject(projectPath, options) {
|
|
|
409
299
|
break;
|
|
410
300
|
}
|
|
411
301
|
case DependencyStatus.RELINK: {
|
|
412
|
-
// 重建链接(Store 已有)
|
|
413
302
|
const relinkCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
414
|
-
// 检查是否为 General 库
|
|
415
303
|
const isRelinkGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
|
|
416
304
|
if (isRelinkGeneral) {
|
|
417
305
|
tx.recordOp('unlink', localPath);
|
|
418
306
|
await linker.unlink(localPath);
|
|
419
|
-
// General 库:整目录链接到 _shared
|
|
420
307
|
const sharedPath = path.join(relinkCommitPath, '_shared');
|
|
421
308
|
tx.recordOp('link', localPath, sharedPath);
|
|
422
309
|
await linker.linkGeneral(localPath, sharedPath);
|
|
423
|
-
// 记录为 General 库
|
|
424
310
|
generalLibs.add(dependency.libName);
|
|
425
311
|
success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,重建链接`);
|
|
426
312
|
}
|
|
427
313
|
else {
|
|
428
|
-
// 平台库:先补充缺失平台
|
|
429
314
|
const relinkSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
|
|
430
|
-
// 注册嵌套依赖
|
|
431
315
|
await registerNestedLibraries(relinkSupplementResult.nestedLibraries, projectHash);
|
|
432
|
-
// 获取所有可用平台(原有 + 新下载)
|
|
433
316
|
const { existing: relinkExisting } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
|
|
434
|
-
// 检查:没有可用平台时警告并跳过(保持原链接状态)
|
|
435
317
|
if (relinkExisting.length === 0) {
|
|
436
318
|
const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
|
|
437
319
|
const relinkCommitEntries = await fs.readdir(relinkCommitPath, { withFileTypes: true });
|
|
@@ -439,15 +321,13 @@ export async function linkProject(projectPath, options) {
|
|
|
439
321
|
.filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
|
|
440
322
|
.map(e => e.name);
|
|
441
323
|
warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 不支持 ${platforms.join('/')} 平台 [可用: ${relinkAvailablePlatforms.join(', ')}]`);
|
|
442
|
-
// 记录到 unavailablePlatforms
|
|
443
324
|
const relinkLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
444
325
|
const relinkLib = registry.getLibrary(relinkLibKey);
|
|
445
326
|
if (relinkLib) {
|
|
446
327
|
const unavailable = relinkLib.unavailablePlatforms || [];
|
|
447
328
|
for (const p of platforms) {
|
|
448
|
-
if (!unavailable.includes(p) && !relinkAvailablePlatforms.includes(p))
|
|
329
|
+
if (!unavailable.includes(p) && !relinkAvailablePlatforms.includes(p))
|
|
449
330
|
unavailable.push(p);
|
|
450
|
-
}
|
|
451
331
|
}
|
|
452
332
|
registry.updateLibrary(relinkLibKey, { unavailablePlatforms: unavailable });
|
|
453
333
|
}
|
|
@@ -457,7 +337,6 @@ export async function linkProject(projectPath, options) {
|
|
|
457
337
|
await linker.unlink(localPath);
|
|
458
338
|
tx.recordOp('link', localPath, relinkCommitPath);
|
|
459
339
|
await linker.linkLib(localPath, relinkCommitPath, relinkExisting);
|
|
460
|
-
// 更新 StoreEntry 引用
|
|
461
340
|
for (const platform of relinkExisting) {
|
|
462
341
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
463
342
|
registry.addStoreReference(storeKey, projectHash);
|
|
@@ -472,29 +351,21 @@ export async function linkProject(projectPath, options) {
|
|
|
472
351
|
break;
|
|
473
352
|
}
|
|
474
353
|
case DependencyStatus.REPLACE: {
|
|
475
|
-
// Store 已有,删除本地目录,直接链接
|
|
476
354
|
const replaceSize = await getDirSize(localPath);
|
|
477
355
|
const replaceCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
478
|
-
// 检查是否为 General 库
|
|
479
356
|
const isReplaceGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
|
|
480
357
|
if (isReplaceGeneral) {
|
|
481
|
-
// General 库:整目录链接到 _shared
|
|
482
358
|
const sharedPath = path.join(replaceCommitPath, '_shared');
|
|
483
359
|
tx.recordOp('replace', localPath, sharedPath);
|
|
484
360
|
await linker.linkGeneral(localPath, sharedPath);
|
|
485
361
|
savedBytes += replaceSize;
|
|
486
|
-
// 记录为 General 库
|
|
487
362
|
generalLibs.add(dependency.libName);
|
|
488
363
|
success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,创建链接`);
|
|
489
364
|
}
|
|
490
365
|
else {
|
|
491
|
-
// 平台库:先补充缺失平台
|
|
492
366
|
const replaceSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
|
|
493
|
-
// 注册嵌套依赖
|
|
494
367
|
await registerNestedLibraries(replaceSupplementResult.nestedLibraries, projectHash);
|
|
495
|
-
// 获取所有可用平台(原有 + 新下载)
|
|
496
368
|
const { existing: replaceExisting } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
|
|
497
|
-
// 检查:没有可用平台时警告并跳过
|
|
498
369
|
if (replaceExisting.length === 0) {
|
|
499
370
|
const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
|
|
500
371
|
const replaceCommitEntries = await fs.readdir(replaceCommitPath, { withFileTypes: true });
|
|
@@ -502,15 +373,13 @@ export async function linkProject(projectPath, options) {
|
|
|
502
373
|
.filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
|
|
503
374
|
.map(e => e.name);
|
|
504
375
|
warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 不支持 ${platforms.join('/')} 平台 [可用: ${replaceAvailablePlatforms.join(', ')}]`);
|
|
505
|
-
// 记录到 unavailablePlatforms
|
|
506
376
|
const replaceLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
507
377
|
const replaceLib = registry.getLibrary(replaceLibKey);
|
|
508
378
|
if (replaceLib) {
|
|
509
379
|
const unavailable = replaceLib.unavailablePlatforms || [];
|
|
510
380
|
for (const p of platforms) {
|
|
511
|
-
if (!unavailable.includes(p) && !replaceAvailablePlatforms.includes(p))
|
|
381
|
+
if (!unavailable.includes(p) && !replaceAvailablePlatforms.includes(p))
|
|
512
382
|
unavailable.push(p);
|
|
513
|
-
}
|
|
514
383
|
}
|
|
515
384
|
registry.updateLibrary(replaceLibKey, { unavailablePlatforms: unavailable });
|
|
516
385
|
}
|
|
@@ -519,7 +388,6 @@ export async function linkProject(projectPath, options) {
|
|
|
519
388
|
tx.recordOp('replace', localPath, replaceCommitPath);
|
|
520
389
|
await linker.linkLib(localPath, replaceCommitPath, replaceExisting);
|
|
521
390
|
savedBytes += replaceSize;
|
|
522
|
-
// 更新 StoreEntry 引用
|
|
523
391
|
for (const platform of replaceExisting) {
|
|
524
392
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
525
393
|
registry.addStoreReference(storeKey, projectHash);
|
|
@@ -534,44 +402,33 @@ export async function linkProject(projectPath, options) {
|
|
|
534
402
|
break;
|
|
535
403
|
}
|
|
536
404
|
case DependencyStatus.ABSORB: {
|
|
537
|
-
// 移入 Store(吸收本地目录所有平台内容)
|
|
538
405
|
const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
539
|
-
// 1. 扫描本地平台目录
|
|
540
406
|
const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
|
|
541
407
|
const localDirEntries = await fs.readdir(localPath, { withFileTypes: true });
|
|
542
408
|
const localPlatforms = localDirEntries
|
|
543
409
|
.filter(entry => entry.isDirectory() && KNOWN_PLATFORM_VALUES.includes(entry.name))
|
|
544
410
|
.map(entry => entry.name);
|
|
545
|
-
// 2. 确定最终要吸收的平台(取本地存在的 ∩ 用户选择的)
|
|
546
411
|
const finalPlatforms = localPlatforms.filter(p => finalLinkPlatforms.includes(p));
|
|
547
|
-
// 2.5. 检查:本地有平台目录但没有用户请求的平台 → 警告并跳过
|
|
548
412
|
if (finalPlatforms.length === 0 && localPlatforms.length > 0) {
|
|
549
413
|
warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 不支持 ${platforms.join('/')} 平台 [本地有: ${localPlatforms.join(', ')}]`);
|
|
550
|
-
// 记录到 unavailablePlatforms
|
|
551
414
|
const absorbLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
552
415
|
const absorbLib = registry.getLibrary(absorbLibKey);
|
|
553
416
|
if (absorbLib) {
|
|
554
417
|
const unavailable = absorbLib.unavailablePlatforms || [];
|
|
555
418
|
for (const p of platforms) {
|
|
556
|
-
if (!unavailable.includes(p) && !localPlatforms.includes(p))
|
|
419
|
+
if (!unavailable.includes(p) && !localPlatforms.includes(p))
|
|
557
420
|
unavailable.push(p);
|
|
558
|
-
}
|
|
559
421
|
}
|
|
560
422
|
registry.updateLibrary(absorbLibKey, { unavailablePlatforms: unavailable });
|
|
561
423
|
}
|
|
562
424
|
break;
|
|
563
425
|
}
|
|
564
|
-
// 3. 计算大小并显示进度(只计算一次,用于进度条和 registry)
|
|
565
426
|
info(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 正在分析...`);
|
|
566
427
|
const absorbSize = await getDirSize(localPath);
|
|
567
|
-
// 4. 创建进度追踪器(用于跨文件系统复制时显示进度)
|
|
568
428
|
const progressTracker = new ProgressTracker({
|
|
569
|
-
name: ` 移入 Store`,
|
|
570
|
-
total: absorbSize,
|
|
571
|
-
showSpeed: true,
|
|
429
|
+
name: ` 移入 Store`, total: absorbSize, showSpeed: true,
|
|
572
430
|
});
|
|
573
431
|
tx.recordOp('absorb', storeCommitPath, localPath);
|
|
574
|
-
// 进度回调 - 只在跨文件系统时触发
|
|
575
432
|
let progressStarted = false;
|
|
576
433
|
const absorbResult = await store.absorbLib(localPath, finalPlatforms, dependency.libName, dependency.commit, {
|
|
577
434
|
totalSize: absorbSize,
|
|
@@ -583,62 +440,38 @@ export async function linkProject(projectPath, options) {
|
|
|
583
440
|
progressTracker.update(copied);
|
|
584
441
|
},
|
|
585
442
|
});
|
|
586
|
-
|
|
587
|
-
if (progressStarted) {
|
|
443
|
+
if (progressStarted)
|
|
588
444
|
progressTracker.stop();
|
|
589
|
-
}
|
|
590
|
-
// 获取所有可链接的平台(新吸收 + 已存在跳过的)
|
|
591
445
|
let absorbLinkPlatforms = [...Object.keys(absorbResult.platformPaths), ...absorbResult.skippedPlatforms];
|
|
592
|
-
// 兼容旧结构:先添加 LibraryInfo(供 supplementMissingPlatforms 记录 unavailablePlatforms)
|
|
593
446
|
const absorbLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
594
447
|
if (!registry.getLibrary(absorbLibKey)) {
|
|
595
448
|
registry.addLibrary({
|
|
596
|
-
libName: dependency.libName,
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
url: dependency.url,
|
|
600
|
-
platforms: absorbLinkPlatforms,
|
|
601
|
-
size: absorbSize,
|
|
602
|
-
referencedBy: [],
|
|
603
|
-
createdAt: new Date().toISOString(),
|
|
604
|
-
lastAccess: new Date().toISOString(),
|
|
449
|
+
libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
|
|
450
|
+
url: dependency.url, platforms: absorbLinkPlatforms, size: absorbSize,
|
|
451
|
+
referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
605
452
|
});
|
|
606
453
|
}
|
|
607
454
|
if (absorbLinkPlatforms.length > 0) {
|
|
608
|
-
// 为每个平台创建 StoreEntry
|
|
609
455
|
for (const platform of absorbLinkPlatforms) {
|
|
610
456
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
611
457
|
if (!registry.getStore(storeKey)) {
|
|
612
458
|
const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
|
|
613
459
|
registry.addStore({
|
|
614
|
-
libName: dependency.libName,
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
branch: dependency.branch,
|
|
618
|
-
url: dependency.url,
|
|
619
|
-
...integrity,
|
|
620
|
-
usedBy: [],
|
|
621
|
-
createdAt: new Date().toISOString(),
|
|
622
|
-
lastAccess: new Date().toISOString(),
|
|
460
|
+
libName: dependency.libName, commit: dependency.commit, platform,
|
|
461
|
+
branch: dependency.branch, url: dependency.url, ...integrity,
|
|
462
|
+
usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
623
463
|
});
|
|
624
464
|
}
|
|
625
465
|
}
|
|
626
|
-
// 注册 _shared 目录(如果存在)
|
|
627
466
|
await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
|
|
628
|
-
// 注册嵌套依赖
|
|
629
467
|
await registerNestedLibraries(absorbResult.nestedLibraries, projectHash);
|
|
630
|
-
// 补充缺失平台(本地没有但用户需要的)
|
|
631
468
|
const absorbSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
|
|
632
|
-
// 注册嵌套依赖
|
|
633
469
|
await registerNestedLibraries(absorbSupplementResult.nestedLibraries, projectHash);
|
|
634
|
-
// 合并所有可链接的平台
|
|
635
470
|
if (absorbSupplementResult.downloaded.length > 0) {
|
|
636
471
|
absorbLinkPlatforms = [...absorbLinkPlatforms, ...absorbSupplementResult.downloaded];
|
|
637
472
|
}
|
|
638
|
-
// 创建链接
|
|
639
473
|
tx.recordOp('link', localPath, storeCommitPath);
|
|
640
474
|
await linker.linkLib(localPath, storeCommitPath, absorbLinkPlatforms);
|
|
641
|
-
// 添加 StoreEntry 引用
|
|
642
475
|
for (const platform of absorbLinkPlatforms) {
|
|
643
476
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
644
477
|
registry.addStoreReference(storeKey, projectHash);
|
|
@@ -651,222 +484,159 @@ export async function linkProject(projectPath, options) {
|
|
|
651
484
|
}
|
|
652
485
|
}
|
|
653
486
|
else {
|
|
654
|
-
// 检测是否为 General 类型
|
|
655
487
|
const sharedPath = path.join(storeCommitPath, '_shared');
|
|
656
488
|
try {
|
|
657
489
|
await fs.access(sharedPath);
|
|
658
|
-
// 检查 _shared 目录是否有内容(防止空目录静默成功)
|
|
659
490
|
const sharedEntries = await fs.readdir(sharedPath);
|
|
660
491
|
if (sharedEntries.length === 0) {
|
|
661
492
|
warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - _shared 目录为空,请重新下载源文件后再 link`);
|
|
662
493
|
break;
|
|
663
494
|
}
|
|
664
|
-
// General 类型:整目录链接
|
|
665
495
|
const { GENERAL_PLATFORM } = await import('../core/platform.js');
|
|
666
496
|
tx.recordOp('link', localPath, sharedPath);
|
|
667
497
|
await linker.linkGeneral(localPath, sharedPath);
|
|
668
|
-
// Registry: StoreEntry 记录
|
|
669
498
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
670
499
|
if (!registry.getStore(storeKey)) {
|
|
671
500
|
const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
672
501
|
registry.addStore({
|
|
673
|
-
libName: dependency.libName,
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
branch: dependency.branch,
|
|
677
|
-
url: dependency.url,
|
|
678
|
-
...integrity,
|
|
679
|
-
usedBy: [],
|
|
680
|
-
createdAt: new Date().toISOString(),
|
|
681
|
-
lastAccess: new Date().toISOString(),
|
|
502
|
+
libName: dependency.libName, commit: dependency.commit, platform: GENERAL_PLATFORM,
|
|
503
|
+
branch: dependency.branch, url: dependency.url, ...integrity,
|
|
504
|
+
usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
682
505
|
});
|
|
683
506
|
}
|
|
684
507
|
registry.addStoreReference(storeKey, projectHash);
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
if (!registry.getLibrary(libKey)) {
|
|
508
|
+
const libKeyGen = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
509
|
+
if (!registry.getLibrary(libKeyGen)) {
|
|
688
510
|
const sharedSize = await getDirSize(sharedPath);
|
|
689
511
|
registry.addLibrary({
|
|
690
|
-
libName: dependency.libName,
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
url: dependency.url,
|
|
694
|
-
platforms: [GENERAL_PLATFORM],
|
|
695
|
-
size: sharedSize,
|
|
696
|
-
referencedBy: [],
|
|
697
|
-
createdAt: new Date().toISOString(),
|
|
698
|
-
lastAccess: new Date().toISOString(),
|
|
512
|
+
libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
|
|
513
|
+
url: dependency.url, platforms: [GENERAL_PLATFORM], size: sharedSize,
|
|
514
|
+
referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
699
515
|
});
|
|
700
516
|
}
|
|
701
|
-
// 记录为 General 库
|
|
702
517
|
generalLibs.add(dependency.libName);
|
|
703
518
|
hint(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,整目录链接`);
|
|
704
519
|
}
|
|
705
520
|
catch {
|
|
706
|
-
// _shared 也不存在
|
|
707
521
|
warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 本地目录不含任何内容,跳过`);
|
|
708
522
|
}
|
|
709
523
|
}
|
|
710
524
|
break;
|
|
711
525
|
}
|
|
712
526
|
case DependencyStatus.MISSING:
|
|
713
|
-
// 跳过,后续并行处理
|
|
714
527
|
break;
|
|
715
528
|
case DependencyStatus.LINK_NEW: {
|
|
716
|
-
// Store 已有(至少一个平台),本地无,检查平台完整性并补充缺失平台
|
|
717
529
|
const linkNewCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
718
|
-
// 1. 检查平台完整性
|
|
719
530
|
const { missing } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
|
|
720
|
-
// 2. 如果有缺失平台,检查是否已确认下载
|
|
721
531
|
const linkNewLibId = `${dependency.libName}@${dependency.commit}`;
|
|
722
532
|
if (missing.length > 0 && !skipAllDownloads && downloadConfirmedLibs.has(linkNewLibId)) {
|
|
723
533
|
info(`${dependency.libName} 缺少平台 [${missing.join(', ')}],开始下载...`);
|
|
724
|
-
// 查找历史记录中的大小估算
|
|
725
534
|
const linkNewLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
726
535
|
const linkNewHistoryLib = registry.getLibrary(linkNewLibKey);
|
|
727
|
-
// 创建下载进度监控器
|
|
728
536
|
const linkNewMonitor = new DownloadMonitor({
|
|
729
|
-
name: ` ${dependency.libName}`,
|
|
730
|
-
estimatedSize: linkNewHistoryLib?.size,
|
|
731
|
-
getDirSize,
|
|
537
|
+
name: ` ${dependency.libName}`, estimatedSize: linkNewHistoryLib?.size, getDirSize,
|
|
732
538
|
});
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}
|
|
745
|
-
// 停止进度监控
|
|
746
|
-
await linkNewMonitor.stop();
|
|
747
|
-
// 提示清理的平台(如果有)
|
|
539
|
+
let downloadResult;
|
|
540
|
+
try {
|
|
541
|
+
downloadResult = await codepac.downloadToTemp({
|
|
542
|
+
url: dependency.url, commit: dependency.commit, branch: dependency.branch,
|
|
543
|
+
libName: dependency.libName, platforms: missing, sparse: dependency.sparse, vars: configVars,
|
|
544
|
+
onTempDirCreated: (_tempDir, libDir) => { linkNewMonitor.start(libDir); },
|
|
545
|
+
onHeartbeat: (message) => { linkNewMonitor.heartbeat(message); },
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
finally {
|
|
549
|
+
await linkNewMonitor.stop();
|
|
550
|
+
}
|
|
748
551
|
if (downloadResult.cleanedPlatforms.length > 0) {
|
|
749
552
|
hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
|
|
750
553
|
}
|
|
751
554
|
try {
|
|
752
|
-
// 过滤:只保留实际下载的且在 missing 列表中的平台
|
|
753
555
|
const filteredDownloaded = downloadResult.platformDirs.filter(p => missing.includes(p));
|
|
754
556
|
if (filteredDownloaded.length > 0) {
|
|
755
557
|
tx.recordOp('absorb', linkNewCommitPath, downloadResult.libDir);
|
|
756
558
|
const linkNewAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
|
|
757
|
-
// 注册嵌套依赖
|
|
758
559
|
await registerNestedLibraries(linkNewAbsorbResult.nestedLibraries, projectHash);
|
|
759
560
|
}
|
|
760
561
|
}
|
|
761
562
|
finally {
|
|
762
|
-
// 清理临时目录
|
|
763
563
|
await fs.rm(downloadResult.tempDir, { recursive: true, force: true }).catch(() => { });
|
|
764
564
|
}
|
|
765
565
|
}
|
|
766
|
-
// 3. 获取 Store 中实际存在的平台(下载后可能仍有缺失)
|
|
767
566
|
const { existing: linkNewExisting } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
|
|
768
|
-
// 4. 检测是否为 General 库
|
|
769
567
|
const isLinkNewGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
|
|
770
568
|
if (isLinkNewGeneral) {
|
|
771
|
-
// General 库:整目录链接
|
|
772
569
|
const sharedPath = path.join(linkNewCommitPath, '_shared');
|
|
773
570
|
tx.recordOp('link', localPath, sharedPath);
|
|
774
571
|
await linker.linkGeneral(localPath, sharedPath);
|
|
775
|
-
// StoreEntry 记录
|
|
776
572
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
777
573
|
const existingEntry = registry.getStore(storeKey);
|
|
778
574
|
if (!existingEntry) {
|
|
779
575
|
const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
780
576
|
registry.addStore({
|
|
781
|
-
libName: dependency.libName,
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
branch: dependency.branch,
|
|
785
|
-
url: dependency.url,
|
|
786
|
-
...integrity,
|
|
787
|
-
usedBy: [],
|
|
788
|
-
createdAt: new Date().toISOString(),
|
|
789
|
-
lastAccess: new Date().toISOString(),
|
|
577
|
+
libName: dependency.libName, commit: dependency.commit, platform: GENERAL_PLATFORM,
|
|
578
|
+
branch: dependency.branch, url: dependency.url, ...integrity,
|
|
579
|
+
usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
790
580
|
});
|
|
791
581
|
}
|
|
792
582
|
else if (existingEntry.fileCount == null) {
|
|
793
|
-
// 旧数据回填:升级后首次 link 时顺便记录完整性数据
|
|
794
583
|
const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
795
584
|
registry.updateStore(storeKey, integrity);
|
|
796
585
|
}
|
|
797
586
|
registry.addStoreReference(storeKey, projectHash);
|
|
798
|
-
// 记录为 General 库
|
|
799
587
|
generalLibs.add(dependency.libName);
|
|
800
588
|
success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,创建链接`);
|
|
801
589
|
}
|
|
802
590
|
else if (linkNewExisting.length === 0) {
|
|
803
|
-
// 非 General 库但没有请求的平台可用 - 警告并跳过
|
|
804
|
-
// 获取 Store 中该库的所有可用平台
|
|
805
591
|
const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
|
|
806
592
|
const commitEntries = await fs.readdir(linkNewCommitPath, { withFileTypes: true });
|
|
807
593
|
const availablePlatforms = commitEntries
|
|
808
594
|
.filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
|
|
809
595
|
.map(e => e.name);
|
|
810
596
|
warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 不支持 ${platforms.join('/')} 平台 [可用: ${availablePlatforms.join(', ')}]`);
|
|
811
|
-
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const unavailable = lib.unavailablePlatforms || [];
|
|
597
|
+
const lnLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
598
|
+
const lnLib = registry.getLibrary(lnLibKey);
|
|
599
|
+
if (lnLib) {
|
|
600
|
+
const unavailable = lnLib.unavailablePlatforms || [];
|
|
816
601
|
for (const p of platforms) {
|
|
817
|
-
if (!unavailable.includes(p) && !availablePlatforms.includes(p))
|
|
602
|
+
if (!unavailable.includes(p) && !availablePlatforms.includes(p))
|
|
818
603
|
unavailable.push(p);
|
|
819
|
-
}
|
|
820
604
|
}
|
|
821
|
-
registry.updateLibrary(
|
|
605
|
+
registry.updateLibrary(lnLibKey, { unavailablePlatforms: unavailable });
|
|
822
606
|
}
|
|
823
607
|
break;
|
|
824
608
|
}
|
|
825
609
|
else {
|
|
826
|
-
// 普通库:linkLib 实际存在的平台
|
|
827
610
|
tx.recordOp('link', localPath, linkNewCommitPath);
|
|
828
611
|
await linker.linkLib(localPath, linkNewCommitPath, linkNewExisting);
|
|
829
|
-
// 5. 为每个实际存在的平台添加 StoreReference
|
|
830
612
|
for (const platform of linkNewExisting) {
|
|
831
613
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
832
614
|
const existingPlatformEntry = registry.getStore(storeKey);
|
|
833
|
-
// 如果 StoreEntry 不存在,创建它
|
|
834
615
|
if (!existingPlatformEntry) {
|
|
835
616
|
const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
|
|
836
617
|
registry.addStore({
|
|
837
|
-
libName: dependency.libName,
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
branch: dependency.branch,
|
|
841
|
-
url: dependency.url,
|
|
842
|
-
...integrity,
|
|
843
|
-
usedBy: [],
|
|
844
|
-
createdAt: new Date().toISOString(),
|
|
845
|
-
lastAccess: new Date().toISOString(),
|
|
618
|
+
libName: dependency.libName, commit: dependency.commit, platform,
|
|
619
|
+
branch: dependency.branch, url: dependency.url, ...integrity,
|
|
620
|
+
usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
846
621
|
});
|
|
847
622
|
}
|
|
848
623
|
else if (existingPlatformEntry.fileCount == null) {
|
|
849
|
-
// 旧数据回填:升级后首次 link 时顺便记录完整性数据
|
|
850
624
|
const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
|
|
851
625
|
registry.updateStore(storeKey, integrity);
|
|
852
626
|
}
|
|
853
627
|
registry.addStoreReference(storeKey, projectHash);
|
|
854
628
|
}
|
|
855
|
-
// 注册 _shared 目录(如果存在)
|
|
856
629
|
await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
|
|
857
630
|
success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 创建链接 [${linkNewExisting.join(', ')}]`);
|
|
858
631
|
}
|
|
859
632
|
break;
|
|
860
633
|
}
|
|
861
634
|
}
|
|
862
|
-
// 保存事务进度
|
|
863
635
|
await tx.save();
|
|
864
636
|
}
|
|
865
|
-
// 并行处理 MISSING 依赖
|
|
637
|
+
// 8. 并行处理 MISSING 依赖
|
|
866
638
|
const missingItems = classified.filter((c) => c.status === DependencyStatus.MISSING);
|
|
867
|
-
|
|
868
|
-
if (missingItems.length > 0 && options.download && !skipAllDownloads) {
|
|
869
|
-
// 根据预扫描阶段的确认结果筛选要下载的库
|
|
639
|
+
if (missingItems.length > 0 && download && !skipAllDownloads) {
|
|
870
640
|
const toDownload = missingItems.filter((item) => {
|
|
871
641
|
const libId = `${item.dependency.libName}@${item.dependency.commit}`;
|
|
872
642
|
return downloadConfirmedLibs.has(libId);
|
|
@@ -874,15 +644,11 @@ export async function linkProject(projectPath, options) {
|
|
|
874
644
|
if (toDownload.length > 0) {
|
|
875
645
|
info(`开始并行下载 ${toDownload.length} 个库 (最多 ${concurrency} 个并发)...`);
|
|
876
646
|
blank();
|
|
877
|
-
// TTY 模式下使用 MultiBarManager 统一管理并行进度条
|
|
878
647
|
const isTTY = process.stdout.isTTY ?? false;
|
|
879
648
|
const multiBarManager = isTTY ? new MultiBarManager() : null;
|
|
880
|
-
// 并行控制器
|
|
881
649
|
const downloadLimit = pLimit(concurrency);
|
|
882
|
-
// 为每个库下载所有选中的平台(使用 downloadToTemp + absorbLib + linkLib 新流程)
|
|
883
650
|
const downloadTasks = toDownload.map((item) => downloadLimit(async () => {
|
|
884
651
|
const { dependency, localPath } = item;
|
|
885
|
-
// 并行下载日志代理:通过 multibar.log() 安全输出,避免干扰进度条
|
|
886
652
|
const pLog = {
|
|
887
653
|
info: (msg) => multiBarManager ? multiBarManager.log(`[info] ${msg}`) : info(msg),
|
|
888
654
|
success: (msg) => multiBarManager ? multiBarManager.log(`[ok] ${msg}`) : success(msg),
|
|
@@ -892,318 +658,190 @@ export async function linkProject(projectPath, options) {
|
|
|
892
658
|
};
|
|
893
659
|
try {
|
|
894
660
|
const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
895
|
-
// 0. 检查平台完整性:避免重复下载已存在的平台
|
|
896
661
|
const { existing, missing } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
|
|
897
|
-
// 如果全部平台已存在,直接 linkLib,无需下载
|
|
898
662
|
if (missing.length === 0) {
|
|
899
663
|
pLog.info(`${dependency.libName} 所有平台已存在,直接链接...`);
|
|
900
664
|
tx.recordOp('link', localPath, storeCommitPath);
|
|
901
665
|
await linker.linkLib(localPath, storeCommitPath, platforms);
|
|
902
|
-
// 为每个平台创建 StoreEntry 并添加引用
|
|
903
666
|
for (const platform of platforms) {
|
|
904
667
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
905
668
|
if (!registry.getStore(storeKey)) {
|
|
906
669
|
const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
|
|
907
670
|
registry.addStore({
|
|
908
|
-
libName: dependency.libName,
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
branch: dependency.branch,
|
|
912
|
-
url: dependency.url,
|
|
913
|
-
...integrity,
|
|
914
|
-
usedBy: [],
|
|
915
|
-
createdAt: new Date().toISOString(),
|
|
916
|
-
lastAccess: new Date().toISOString(),
|
|
671
|
+
libName: dependency.libName, commit: dependency.commit, platform,
|
|
672
|
+
branch: dependency.branch, url: dependency.url, ...integrity,
|
|
673
|
+
usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
917
674
|
});
|
|
918
675
|
}
|
|
919
676
|
registry.addStoreReference(storeKey, projectHash);
|
|
920
677
|
}
|
|
921
|
-
// 注册 _shared 目录(如果存在)
|
|
922
678
|
await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
if (!registry.getLibrary(libKey)) {
|
|
679
|
+
const dlLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
680
|
+
if (!registry.getLibrary(dlLibKey)) {
|
|
926
681
|
let totalSize = 0;
|
|
927
682
|
for (const platform of platforms) {
|
|
928
683
|
totalSize += await store.getSize(dependency.libName, dependency.commit, platform);
|
|
929
684
|
}
|
|
930
685
|
registry.addLibrary({
|
|
931
|
-
libName: dependency.libName,
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
url: dependency.url,
|
|
935
|
-
platforms,
|
|
936
|
-
size: totalSize,
|
|
937
|
-
referencedBy: [],
|
|
938
|
-
createdAt: new Date().toISOString(),
|
|
939
|
-
lastAccess: new Date().toISOString(),
|
|
686
|
+
libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
|
|
687
|
+
url: dependency.url, platforms, size: totalSize, referencedBy: [],
|
|
688
|
+
createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
940
689
|
});
|
|
941
690
|
}
|
|
942
691
|
pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 链接完成 [${platforms.join(', ')}]`);
|
|
943
|
-
return {
|
|
944
|
-
success: true,
|
|
945
|
-
name: dependency.libName,
|
|
946
|
-
downloadedPlatforms: platforms,
|
|
947
|
-
skippedPlatforms: [],
|
|
948
|
-
};
|
|
692
|
+
return { success: true, name: dependency.libName, downloadedPlatforms: platforms, skippedPlatforms: [] };
|
|
949
693
|
}
|
|
950
|
-
|
|
951
|
-
const
|
|
952
|
-
const historyLib = registry.getLibrary(libKey);
|
|
694
|
+
const dlLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
695
|
+
const historyLib = registry.getLibrary(dlLibKey);
|
|
953
696
|
const unavailablePlatforms = historyLib?.unavailablePlatforms || [];
|
|
954
|
-
|
|
955
|
-
const toDownload = missing.filter(p => !unavailablePlatforms.includes(p));
|
|
697
|
+
const dlToDownload = missing.filter(p => !unavailablePlatforms.includes(p));
|
|
956
698
|
const knownUnavailable = missing.filter(p => unavailablePlatforms.includes(p));
|
|
957
|
-
|
|
958
|
-
if (toDownload.length === 0) {
|
|
699
|
+
if (dlToDownload.length === 0) {
|
|
959
700
|
if (knownUnavailable.length > 0) {
|
|
960
701
|
pLog.warn(`${dependency.libName} 平台 [${knownUnavailable.join(', ')}] 不支持(远程不存在)`);
|
|
961
702
|
}
|
|
962
|
-
return {
|
|
963
|
-
success: false,
|
|
964
|
-
name: dependency.libName,
|
|
965
|
-
skipped: true,
|
|
966
|
-
skippedPlatforms: missing,
|
|
967
|
-
unsupported: true,
|
|
968
|
-
};
|
|
703
|
+
return { success: false, name: dependency.libName, skipped: true, skippedPlatforms: missing, unsupported: true };
|
|
969
704
|
}
|
|
970
|
-
|
|
971
|
-
pLog.info(`下载 ${dependency.libName} [${toDownload.join(', ')}]...`);
|
|
705
|
+
pLog.info(`下载 ${dependency.libName} [${dlToDownload.join(', ')}]...`);
|
|
972
706
|
const estimatedSize = historyLib?.size;
|
|
973
|
-
// 创建下载进度监控器
|
|
974
707
|
const downloadMonitor = new DownloadMonitor({
|
|
975
|
-
name: ` ${dependency.libName}`,
|
|
976
|
-
estimatedSize,
|
|
977
|
-
getDirSize,
|
|
708
|
+
name: ` ${dependency.libName}`, estimatedSize, getDirSize,
|
|
978
709
|
manager: multiBarManager ?? undefined,
|
|
979
710
|
});
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
});
|
|
994
|
-
// 停止进度监控
|
|
995
|
-
await downloadMonitor.stop();
|
|
996
|
-
// 提示清理的平台(如果有)
|
|
711
|
+
let downloadResult;
|
|
712
|
+
try {
|
|
713
|
+
downloadResult = await codepac.downloadToTemp({
|
|
714
|
+
url: dependency.url, commit: dependency.commit, branch: dependency.branch,
|
|
715
|
+
libName: dependency.libName, platforms: dlToDownload, sparse: dependency.sparse,
|
|
716
|
+
vars: configVars,
|
|
717
|
+
onTempDirCreated: (_tempDir, libDir) => { downloadMonitor.start(libDir); },
|
|
718
|
+
onHeartbeat: (message) => { downloadMonitor.heartbeat(message); },
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
finally {
|
|
722
|
+
await downloadMonitor.stop();
|
|
723
|
+
}
|
|
997
724
|
if (downloadResult.cleanedPlatforms.length > 0) {
|
|
998
725
|
pLog.hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
|
|
999
726
|
}
|
|
1000
727
|
try {
|
|
1001
|
-
// 2. 检测是否为 General 库(没有 sparse 配置,或 sparse 只有 common,且没有平台目录)
|
|
1002
728
|
const isNewGeneral = (!dependency.sparse || isSparseOnlyCommon(dependency.sparse)) && downloadResult.platformDirs.length === 0;
|
|
1003
729
|
if (isNewGeneral) {
|
|
1004
|
-
// General 库:把整个下载内容移到 _shared
|
|
1005
730
|
tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
1006
731
|
await store.absorbGeneral(downloadResult.libDir, dependency.libName, dependency.commit);
|
|
1007
|
-
// 创建链接
|
|
1008
732
|
const sharedPath = path.join(storeCommitPath, '_shared');
|
|
1009
733
|
tx.recordOp('link', localPath, sharedPath);
|
|
1010
734
|
await linker.linkGeneral(localPath, sharedPath);
|
|
1011
|
-
// StoreEntry 记录
|
|
1012
735
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
1013
736
|
if (!registry.getStore(storeKey)) {
|
|
1014
737
|
const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
1015
738
|
registry.addStore({
|
|
1016
|
-
libName: dependency.libName,
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
branch: dependency.branch,
|
|
1020
|
-
url: dependency.url,
|
|
1021
|
-
...integrity,
|
|
1022
|
-
usedBy: [],
|
|
1023
|
-
createdAt: new Date().toISOString(),
|
|
1024
|
-
lastAccess: new Date().toISOString(),
|
|
739
|
+
libName: dependency.libName, commit: dependency.commit, platform: GENERAL_PLATFORM,
|
|
740
|
+
branch: dependency.branch, url: dependency.url, ...integrity,
|
|
741
|
+
usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
1025
742
|
});
|
|
1026
743
|
}
|
|
1027
744
|
registry.addStoreReference(storeKey, projectHash);
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
if (!registry.getLibrary(libKey)) {
|
|
745
|
+
const genLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
746
|
+
if (!registry.getLibrary(genLibKey)) {
|
|
1031
747
|
const sharedSize = await getDirSize(sharedPath);
|
|
1032
748
|
registry.addLibrary({
|
|
1033
|
-
libName: dependency.libName,
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
url: dependency.url,
|
|
1037
|
-
platforms: [GENERAL_PLATFORM],
|
|
1038
|
-
size: sharedSize,
|
|
1039
|
-
referencedBy: [],
|
|
1040
|
-
createdAt: new Date().toISOString(),
|
|
1041
|
-
lastAccess: new Date().toISOString(),
|
|
749
|
+
libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
|
|
750
|
+
url: dependency.url, platforms: [GENERAL_PLATFORM], size: sharedSize,
|
|
751
|
+
referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
1042
752
|
});
|
|
1043
753
|
}
|
|
1044
|
-
// 记录为 General 库
|
|
1045
754
|
generalLibs.add(dependency.libName);
|
|
1046
755
|
pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,下载完成`);
|
|
1047
|
-
return {
|
|
1048
|
-
success: true,
|
|
1049
|
-
name: dependency.libName,
|
|
1050
|
-
downloadedPlatforms: [GENERAL_PLATFORM],
|
|
1051
|
-
skippedPlatforms: [],
|
|
1052
|
-
isGeneral: true,
|
|
1053
|
-
};
|
|
756
|
+
return { success: true, name: dependency.libName, downloadedPlatforms: [GENERAL_PLATFORM], skippedPlatforms: [], isGeneral: true };
|
|
1054
757
|
}
|
|
1055
|
-
|
|
1056
|
-
const
|
|
1057
|
-
// 4. 检查并记录新发现的不可用平台
|
|
1058
|
-
const newUnavailable = toDownload.filter(p => !filteredDownloaded.includes(p));
|
|
758
|
+
const filteredDownloaded = downloadResult.platformDirs.filter(p => dlToDownload.includes(p));
|
|
759
|
+
const newUnavailable = dlToDownload.filter(p => !filteredDownloaded.includes(p));
|
|
1059
760
|
if (newUnavailable.length > 0) {
|
|
1060
|
-
// 更新 LibraryInfo 中的 unavailablePlatforms
|
|
1061
761
|
const updateLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
1062
762
|
if (historyLib) {
|
|
1063
763
|
const updatedUnavailable = [...new Set([...unavailablePlatforms, ...newUnavailable])];
|
|
1064
764
|
registry.updateLibrary(updateLibKey, { unavailablePlatforms: updatedUnavailable });
|
|
1065
765
|
}
|
|
1066
766
|
else {
|
|
1067
|
-
// 如果 LibraryInfo 不存在,先创建
|
|
1068
767
|
registry.addLibrary({
|
|
1069
|
-
libName: dependency.libName,
|
|
1070
|
-
|
|
1071
|
-
branch: dependency.branch,
|
|
1072
|
-
url: dependency.url,
|
|
1073
|
-
platforms: [],
|
|
1074
|
-
size: 0,
|
|
1075
|
-
referencedBy: [],
|
|
768
|
+
libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
|
|
769
|
+
url: dependency.url, platforms: [], size: 0, referencedBy: [],
|
|
1076
770
|
unavailablePlatforms: newUnavailable,
|
|
1077
|
-
createdAt: new Date().toISOString(),
|
|
1078
|
-
lastAccess: new Date().toISOString(),
|
|
771
|
+
createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
1079
772
|
});
|
|
1080
773
|
}
|
|
1081
774
|
pLog.warn(`${dependency.libName} 平台 [${newUnavailable.join(', ')}] 远程不存在,已记录`);
|
|
1082
775
|
}
|
|
1083
|
-
// 5. 调用 absorbLib 将临时目录内容移入 Store(如果有下载成功的平台)
|
|
1084
776
|
if (filteredDownloaded.length > 0) {
|
|
1085
777
|
tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
1086
778
|
const downloadAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
|
|
1087
|
-
// 注册嵌套依赖
|
|
1088
779
|
await registerNestedLibraries(downloadAbsorbResult.nestedLibraries, projectHash);
|
|
1089
780
|
}
|
|
1090
|
-
// 5. 获取所有可链接的平台(已存在 + 新下载成功的)
|
|
1091
781
|
const linkPlatforms = [...existing, ...filteredDownloaded];
|
|
1092
|
-
// 6. 检测是否为 General 库(Store 中已有 _shared)
|
|
1093
782
|
const isDownloadGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
|
|
1094
783
|
if (isDownloadGeneral) {
|
|
1095
|
-
// General 库:整目录链接
|
|
1096
784
|
const sharedPath = path.join(storeCommitPath, '_shared');
|
|
1097
785
|
tx.recordOp('link', localPath, sharedPath);
|
|
1098
786
|
await linker.linkGeneral(localPath, sharedPath);
|
|
1099
|
-
// StoreEntry 记录
|
|
1100
787
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
1101
788
|
if (!registry.getStore(storeKey)) {
|
|
1102
789
|
const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
1103
790
|
registry.addStore({
|
|
1104
|
-
libName: dependency.libName,
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
branch: dependency.branch,
|
|
1108
|
-
url: dependency.url,
|
|
1109
|
-
...integrity,
|
|
1110
|
-
usedBy: [],
|
|
1111
|
-
createdAt: new Date().toISOString(),
|
|
1112
|
-
lastAccess: new Date().toISOString(),
|
|
791
|
+
libName: dependency.libName, commit: dependency.commit, platform: GENERAL_PLATFORM,
|
|
792
|
+
branch: dependency.branch, url: dependency.url, ...integrity,
|
|
793
|
+
usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
1113
794
|
});
|
|
1114
795
|
}
|
|
1115
796
|
registry.addStoreReference(storeKey, projectHash);
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
if (!registry.getLibrary(libKey)) {
|
|
797
|
+
const genLibKey2 = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
798
|
+
if (!registry.getLibrary(genLibKey2)) {
|
|
1119
799
|
const sharedSize = await getDirSize(sharedPath);
|
|
1120
800
|
registry.addLibrary({
|
|
1121
|
-
libName: dependency.libName,
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
url: dependency.url,
|
|
1125
|
-
platforms: [GENERAL_PLATFORM],
|
|
1126
|
-
size: sharedSize,
|
|
1127
|
-
referencedBy: [],
|
|
1128
|
-
createdAt: new Date().toISOString(),
|
|
1129
|
-
lastAccess: new Date().toISOString(),
|
|
801
|
+
libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
|
|
802
|
+
url: dependency.url, platforms: [GENERAL_PLATFORM], size: sharedSize,
|
|
803
|
+
referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
1130
804
|
});
|
|
1131
805
|
}
|
|
1132
|
-
// 记录为 General 库
|
|
1133
806
|
generalLibs.add(dependency.libName);
|
|
1134
807
|
pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,下载完成`);
|
|
1135
|
-
return {
|
|
1136
|
-
success: true,
|
|
1137
|
-
name: dependency.libName,
|
|
1138
|
-
downloadedPlatforms: [GENERAL_PLATFORM],
|
|
1139
|
-
skippedPlatforms: [],
|
|
1140
|
-
isGeneral: true,
|
|
1141
|
-
};
|
|
808
|
+
return { success: true, name: dependency.libName, downloadedPlatforms: [GENERAL_PLATFORM], skippedPlatforms: [], isGeneral: true };
|
|
1142
809
|
}
|
|
1143
|
-
// 普通库:无平台可链接则跳过
|
|
1144
810
|
if (linkPlatforms.length === 0) {
|
|
1145
|
-
return {
|
|
1146
|
-
success: false,
|
|
1147
|
-
name: dependency.libName,
|
|
1148
|
-
skipped: true,
|
|
1149
|
-
skippedPlatforms: platforms,
|
|
1150
|
-
};
|
|
811
|
+
return { success: false, name: dependency.libName, skipped: true, skippedPlatforms: platforms };
|
|
1151
812
|
}
|
|
1152
|
-
// 6. 调用 linkLib 创建符号链接并复制共享文件
|
|
1153
813
|
tx.recordOp('link', localPath, storeCommitPath);
|
|
1154
814
|
await linker.linkLib(localPath, storeCommitPath, linkPlatforms);
|
|
1155
|
-
// 7. 为每个平台创建 StoreEntry 并添加引用
|
|
1156
815
|
for (const platform of linkPlatforms) {
|
|
1157
816
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
1158
817
|
if (!registry.getStore(storeKey)) {
|
|
1159
818
|
const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
|
|
1160
819
|
registry.addStore({
|
|
1161
|
-
libName: dependency.libName,
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
branch: dependency.branch,
|
|
1165
|
-
url: dependency.url,
|
|
1166
|
-
...integrity,
|
|
1167
|
-
usedBy: [],
|
|
1168
|
-
createdAt: new Date().toISOString(),
|
|
1169
|
-
lastAccess: new Date().toISOString(),
|
|
820
|
+
libName: dependency.libName, commit: dependency.commit, platform,
|
|
821
|
+
branch: dependency.branch, url: dependency.url, ...integrity,
|
|
822
|
+
usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
1170
823
|
});
|
|
1171
824
|
}
|
|
1172
825
|
registry.addStoreReference(storeKey, projectHash);
|
|
1173
826
|
}
|
|
1174
|
-
// 注册 _shared 目录(如果存在)
|
|
1175
827
|
await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
if (!registry.getLibrary(libKey)) {
|
|
828
|
+
const finalLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
829
|
+
if (!registry.getLibrary(finalLibKey)) {
|
|
1179
830
|
let totalSize = 0;
|
|
1180
831
|
for (const platform of linkPlatforms) {
|
|
1181
832
|
totalSize += await store.getSize(dependency.libName, dependency.commit, platform);
|
|
1182
833
|
}
|
|
1183
834
|
registry.addLibrary({
|
|
1184
|
-
libName: dependency.libName,
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
url: dependency.url,
|
|
1188
|
-
platforms: linkPlatforms,
|
|
1189
|
-
size: totalSize,
|
|
1190
|
-
referencedBy: [],
|
|
1191
|
-
createdAt: new Date().toISOString(),
|
|
1192
|
-
lastAccess: new Date().toISOString(),
|
|
835
|
+
libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
|
|
836
|
+
url: dependency.url, platforms: linkPlatforms, size: totalSize,
|
|
837
|
+
referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
|
|
1193
838
|
});
|
|
1194
839
|
}
|
|
1195
|
-
// 计算未能链接的平台(用户请求但未下载也未在 Store 中的)
|
|
1196
840
|
const notLinkedPlatforms = platforms.filter((p) => !linkPlatforms.includes(p));
|
|
1197
841
|
pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 下载完成 [${linkPlatforms.join(', ')}]`);
|
|
1198
|
-
return {
|
|
1199
|
-
success: true,
|
|
1200
|
-
name: dependency.libName,
|
|
1201
|
-
downloadedPlatforms: linkPlatforms,
|
|
1202
|
-
skippedPlatforms: notLinkedPlatforms,
|
|
1203
|
-
};
|
|
842
|
+
return { success: true, name: dependency.libName, downloadedPlatforms: linkPlatforms, skippedPlatforms: notLinkedPlatforms };
|
|
1204
843
|
}
|
|
1205
844
|
finally {
|
|
1206
|
-
// 清理临时目录(无论成功还是失败)
|
|
1207
845
|
await fs.rm(downloadResult.tempDir, { recursive: true, force: true }).catch(() => { });
|
|
1208
846
|
}
|
|
1209
847
|
}
|
|
@@ -1213,16 +851,13 @@ export async function linkProject(projectPath, options) {
|
|
|
1213
851
|
}
|
|
1214
852
|
}));
|
|
1215
853
|
const results = await Promise.all(downloadTasks);
|
|
1216
|
-
// 停止多进度条管理器
|
|
1217
854
|
multiBarManager?.stop();
|
|
1218
855
|
const succeeded = results.filter((r) => r.success);
|
|
1219
856
|
const failed = results.filter((r) => !r.success && !('skipped' in r && r.skipped));
|
|
1220
857
|
blank();
|
|
1221
858
|
info(`下载完成: ${succeeded.length}/${toDownload.length} 个库`);
|
|
1222
|
-
if (failed.length > 0)
|
|
859
|
+
if (failed.length > 0)
|
|
1223
860
|
warn(`${failed.length} 个库下载失败`);
|
|
1224
|
-
}
|
|
1225
|
-
// 汇总提示跳过的平台
|
|
1226
861
|
const allSkipped = [];
|
|
1227
862
|
for (const r of results) {
|
|
1228
863
|
if ('skippedPlatforms' in r && r.skippedPlatforms && r.skippedPlatforms.length > 0) {
|
|
@@ -1236,116 +871,365 @@ export async function linkProject(projectPath, options) {
|
|
|
1236
871
|
warn(` - ${item.name} / ${item.platforms.join(', ')}`);
|
|
1237
872
|
}
|
|
1238
873
|
}
|
|
1239
|
-
|
|
1240
|
-
for (const r of succeeded) {
|
|
874
|
+
for (const r of succeeded)
|
|
1241
875
|
downloadedLibs.push(r.name);
|
|
1242
|
-
}
|
|
1243
876
|
}
|
|
1244
877
|
}
|
|
1245
|
-
else if (missingItems.length > 0 && !
|
|
878
|
+
else if (missingItems.length > 0 && !download) {
|
|
1246
879
|
for (const item of missingItems) {
|
|
1247
880
|
warn(`${item.dependency.libName} (${item.dependency.commit.slice(0, 7)}) - 缺失 (跳过下载)`);
|
|
1248
881
|
}
|
|
1249
882
|
}
|
|
1250
|
-
//
|
|
883
|
+
// 9. 处理嵌套依赖 (actions)
|
|
1251
884
|
const topLevelConfig = await parseCodepacDep(configPath);
|
|
1252
885
|
const actions = extractActions(topLevelConfig);
|
|
1253
|
-
// 嵌套依赖记录(用于 registry)
|
|
1254
886
|
const nestedLinkedDeps = [];
|
|
1255
887
|
if (actions.length > 0) {
|
|
1256
888
|
blank();
|
|
1257
889
|
separator();
|
|
1258
890
|
info(`发现 ${actions.length} 个嵌套依赖配置`);
|
|
1259
891
|
const nestedContext = {
|
|
1260
|
-
depth: 0,
|
|
1261
|
-
processedConfigs: new Set([configPath]),
|
|
1262
|
-
platforms,
|
|
1263
|
-
vars: configVars,
|
|
892
|
+
depth: 0, processedConfigs: new Set([configPath]), platforms, vars: configVars,
|
|
1264
893
|
};
|
|
1265
894
|
const thirdPartyDir = path.dirname(configPath);
|
|
1266
|
-
// 依次处理每个 action
|
|
1267
895
|
for (const action of actions) {
|
|
1268
896
|
await processAction(action, nestedContext, thirdPartyDir, {
|
|
1269
|
-
tx,
|
|
1270
|
-
|
|
1271
|
-
projectHash,
|
|
1272
|
-
projectRoot: finalPath,
|
|
1273
|
-
dryRun: options.dryRun,
|
|
1274
|
-
download: options.download,
|
|
1275
|
-
yes: options.yes,
|
|
1276
|
-
generalLibs,
|
|
1277
|
-
downloadedLibs,
|
|
1278
|
-
nestedLinkedDeps,
|
|
897
|
+
tx, registry, projectHash, projectRoot, dryRun, download, yes,
|
|
898
|
+
generalLibs, downloadedLibs, nestedLinkedDeps,
|
|
1279
899
|
});
|
|
1280
900
|
}
|
|
1281
901
|
}
|
|
1282
|
-
//
|
|
1283
|
-
|
|
1284
|
-
//
|
|
1285
|
-
const relConfigPath = getRelativeConfigPath(finalPath, configPath);
|
|
1286
|
-
// 使用主平台作为依赖的 platform 字段(兼容旧结构)
|
|
902
|
+
// 10. 同步 cache 文件
|
|
903
|
+
await syncCacheFile(configPath);
|
|
904
|
+
// 11. 构建返回结果
|
|
1287
905
|
const primaryPlatform = platforms[0];
|
|
1288
|
-
const
|
|
906
|
+
const linkedDeps = classified
|
|
1289
907
|
.filter((c) => {
|
|
1290
|
-
if (c.status === DependencyStatus.MISSING)
|
|
1291
|
-
// 只包含成功下载的库
|
|
908
|
+
if (c.status === DependencyStatus.MISSING)
|
|
1292
909
|
return downloadedLibs.includes(c.dependency.libName);
|
|
1293
|
-
}
|
|
1294
910
|
return true;
|
|
1295
911
|
})
|
|
1296
912
|
.map((c) => ({
|
|
1297
913
|
libName: c.dependency.libName,
|
|
1298
914
|
commit: c.dependency.commit,
|
|
1299
|
-
// General 库使用 'general' 平台,普通库使用主平台
|
|
1300
915
|
platform: generalLibs.has(c.dependency.libName) ? GENERAL_PLATFORM : primaryPlatform,
|
|
1301
|
-
linkedPath: path.relative(
|
|
916
|
+
linkedPath: path.relative(projectRoot, c.localPath),
|
|
917
|
+
scope,
|
|
1302
918
|
}));
|
|
1303
|
-
|
|
1304
|
-
|
|
919
|
+
return {
|
|
920
|
+
linkedDeps, nestedLinkedDeps, generalLibs, downloadedLibs, savedBytes, finalLinkPlatforms, stats,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
catch (err) {
|
|
924
|
+
// 将异常传播给调用者(linkProject 的 try-catch 会处理回滚)
|
|
925
|
+
throw err;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* 执行链接操作
|
|
930
|
+
*/
|
|
931
|
+
export async function linkProject(projectPath, options) {
|
|
932
|
+
const absolutePath = resolvePath(projectPath);
|
|
933
|
+
// === 阶段 1: 初始化 ===
|
|
934
|
+
const cfg = await config.load();
|
|
935
|
+
if (cfg?.logLevel)
|
|
936
|
+
setLogLevel(cfg.logLevel);
|
|
937
|
+
if (cfg?.proxy)
|
|
938
|
+
setProxyConfig(cfg.proxy);
|
|
939
|
+
const concurrency = cfg?.concurrency ?? 5;
|
|
940
|
+
const registry = getRegistry();
|
|
941
|
+
await registry.load();
|
|
942
|
+
const existingProject = registry.getProjectByPath(absolutePath);
|
|
943
|
+
const rememberedPlatforms = existingProject?.platforms;
|
|
944
|
+
// 确定平台列表
|
|
945
|
+
let platforms;
|
|
946
|
+
if (options.platform && options.platform.length > 0) {
|
|
947
|
+
platforms = parsePlatformArgs(options.platform);
|
|
948
|
+
}
|
|
949
|
+
else if (!options.yes && process.stdout.isTTY) {
|
|
950
|
+
const selectedPlatforms = await selectPlatforms(rememberedPlatforms);
|
|
951
|
+
if (selectedPlatforms === PROMPT_CANCELLED) {
|
|
952
|
+
info('已取消');
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
platforms = selectedPlatforms;
|
|
956
|
+
if (platforms.length === 0) {
|
|
957
|
+
error('至少需要选择一个平台');
|
|
958
|
+
process.exit(EXIT_CODES.MISUSE);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
error('非交互模式下必须使用 -p 指定平台');
|
|
963
|
+
hint('示例: tanmi-dock link -p mac ios');
|
|
964
|
+
process.exit(EXIT_CODES.MISUSE);
|
|
965
|
+
}
|
|
966
|
+
// 检查项目路径
|
|
967
|
+
try {
|
|
968
|
+
const stat = await fs.stat(absolutePath);
|
|
969
|
+
if (!stat.isDirectory()) {
|
|
970
|
+
error(`路径不是目录: ${absolutePath}`);
|
|
971
|
+
process.exit(EXIT_CODES.NOINPUT);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
catch {
|
|
975
|
+
error(`路径不存在: ${absolutePath}`);
|
|
976
|
+
process.exit(EXIT_CODES.NOINPUT);
|
|
977
|
+
}
|
|
978
|
+
// 发现可选配置文件
|
|
979
|
+
const thirdpartyDir = path.join(absolutePath, '3rdparty');
|
|
980
|
+
const configDiscovery = await findAllCodepacConfigs(thirdpartyDir);
|
|
981
|
+
let selectedOptionalConfigs = [];
|
|
982
|
+
if (configDiscovery && configDiscovery.optionalConfigs.length > 0) {
|
|
983
|
+
if (options.config && options.config.length > 0) {
|
|
984
|
+
for (const configName of options.config) {
|
|
985
|
+
const found = configDiscovery.optionalConfigs.find(c => c.name === configName);
|
|
986
|
+
if (!found) {
|
|
987
|
+
error(`找不到指定的配置: ${configName}`);
|
|
988
|
+
hint(`可用配置: ${configDiscovery.optionalConfigs.map(c => c.name).join(', ')}`);
|
|
989
|
+
process.exit(EXIT_CODES.MISUSE);
|
|
990
|
+
}
|
|
991
|
+
selectedOptionalConfigs.push(found);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
else if (options.yes) {
|
|
995
|
+
// --yes 模式:跳过可选配置选择
|
|
996
|
+
}
|
|
997
|
+
else if (process.stdout.isTTY) {
|
|
998
|
+
const rememberedConfigs = existingProject?.optionalConfigs ?? [];
|
|
999
|
+
const selectOptions = {
|
|
1000
|
+
isTTY: true,
|
|
1001
|
+
specifiedConfigs: rememberedConfigs,
|
|
1002
|
+
};
|
|
1003
|
+
const selected = await selectOptionalConfigs(configDiscovery.optionalConfigs, selectOptions);
|
|
1004
|
+
if (selected === PROMPT_CANCELLED) {
|
|
1005
|
+
info('已取消');
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
selectedOptionalConfigs = selected;
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
error('发现可选配置文件,非交互模式下必须使用 --config 或 --yes 参数');
|
|
1012
|
+
hint(`可用配置: ${configDiscovery.optionalConfigs.map(c => c.name).join(', ')}`);
|
|
1013
|
+
hint(`示例: td link --config ${configDiscovery.optionalConfigs[0].name}`);
|
|
1014
|
+
hint('或使用 --yes 跳过可选配置');
|
|
1015
|
+
process.exit(EXIT_CODES.MISUSE);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// 查找主配置文件
|
|
1019
|
+
const { findCodepacConfig } = await import('../core/parser.js');
|
|
1020
|
+
const mainConfigPath = await findCodepacConfig(absolutePath);
|
|
1021
|
+
if (!mainConfigPath) {
|
|
1022
|
+
error(`找不到 codepac-dep.json 配置文件,已搜索: 3rdparty, .`);
|
|
1023
|
+
process.exit(EXIT_CODES.DATAERR);
|
|
1024
|
+
}
|
|
1025
|
+
// === 阶段 2: Submodule 检测 ===
|
|
1026
|
+
let selectedSubmodules = [];
|
|
1027
|
+
if (options.submodules !== false) {
|
|
1028
|
+
const submoduleConfigs = await findSubmoduleConfigs(absolutePath);
|
|
1029
|
+
if (submoduleConfigs.length > 0) {
|
|
1030
|
+
selectedSubmodules = await selectSubmodules(submoduleConfigs, options, existingProject?.submodules);
|
|
1031
|
+
// 为选中的 submodule 处理可选配置
|
|
1032
|
+
for (const sub of selectedSubmodules) {
|
|
1033
|
+
if (sub.optionalConfigs.length > 0 && !options.yes && process.stdout.isTTY) {
|
|
1034
|
+
info(`${sub.name} 发现可选配置:`);
|
|
1035
|
+
const selected = await selectOptionalConfigs(sub.optionalConfigs, { isTTY: true, specifiedConfigs: [] });
|
|
1036
|
+
if (selected !== PROMPT_CANCELLED) {
|
|
1037
|
+
sub.selectedOptionalConfigs = selected;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
// === 阶段 3: 规范化 + 事务准备 ===
|
|
1044
|
+
const normalizedRoot = normalizeProjectRoot(absolutePath, mainConfigPath);
|
|
1045
|
+
const wasNormalized = normalizedRoot !== absolutePath;
|
|
1046
|
+
if (wasNormalized) {
|
|
1047
|
+
info(`项目根目录规范化: ${absolutePath} → ${normalizedRoot}`);
|
|
1048
|
+
const oldHash = registry.hashPath(absolutePath);
|
|
1049
|
+
const oldProject = registry.getProject(oldHash);
|
|
1050
|
+
if (oldProject) {
|
|
1051
|
+
info('迁移旧的项目登记...');
|
|
1052
|
+
registry.removeProject(oldHash);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
const absConfigPath = path.resolve(normalizedRoot, getRelativeConfigPath(normalizedRoot, mainConfigPath));
|
|
1056
|
+
const allProjects = registry.listProjects();
|
|
1057
|
+
for (const proj of allProjects) {
|
|
1058
|
+
if (pathsEqual(proj.path, normalizedRoot))
|
|
1059
|
+
continue;
|
|
1060
|
+
const projAbsConfig = path.resolve(proj.path, proj.configPath);
|
|
1061
|
+
if (pathsEqual(projAbsConfig, absConfigPath)) {
|
|
1062
|
+
info(`清理重复登记: ${proj.path}`);
|
|
1063
|
+
registry.removeProject(registry.hashPath(proj.path));
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
const finalPath = normalizedRoot;
|
|
1067
|
+
// 检查是否有未完成的事务需要恢复
|
|
1068
|
+
const pendingTx = await Transaction.findPending();
|
|
1069
|
+
if (pendingTx) {
|
|
1070
|
+
warn(`发现未完成的事务 (${pendingTx.id.slice(0, 8)})`);
|
|
1071
|
+
info('正在尝试回滚...');
|
|
1072
|
+
try {
|
|
1073
|
+
await pendingTx.rollback();
|
|
1074
|
+
success('事务回滚完成');
|
|
1075
|
+
}
|
|
1076
|
+
catch (err) {
|
|
1077
|
+
error(`回滚失败: ${err.message}`);
|
|
1078
|
+
hint('请手动检查 Store 和项目目录状态');
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
const storePath = await store.getStorePath();
|
|
1082
|
+
const projectHash = registry.hashPath(finalPath);
|
|
1083
|
+
const tx = new Transaction(`link:${finalPath}`);
|
|
1084
|
+
await tx.begin();
|
|
1085
|
+
// === 阶段 4: 依次执行 linkScope ===
|
|
1086
|
+
try {
|
|
1087
|
+
// 主项目
|
|
1088
|
+
const mainResult = await linkScope({
|
|
1089
|
+
scopeName: '主项目',
|
|
1090
|
+
configPath: mainConfigPath,
|
|
1091
|
+
platforms,
|
|
1092
|
+
finalLinkPlatforms: platforms,
|
|
1093
|
+
scanExtraPlatforms: true,
|
|
1094
|
+
registry, tx, projectHash,
|
|
1095
|
+
projectRoot: finalPath,
|
|
1096
|
+
storePath,
|
|
1097
|
+
download: options.download,
|
|
1098
|
+
dryRun: options.dryRun,
|
|
1099
|
+
yes: options.yes,
|
|
1100
|
+
concurrency,
|
|
1101
|
+
optionalConfigs: selectedOptionalConfigs,
|
|
1102
|
+
});
|
|
1103
|
+
const finalLinkPlatforms = mainResult.finalLinkPlatforms;
|
|
1104
|
+
// 如果 dry-run 模式,linkScope 内已显示信息
|
|
1105
|
+
if (options.dryRun) {
|
|
1106
|
+
// submodule 也需要 dry-run 显示
|
|
1107
|
+
for (const sub of selectedSubmodules) {
|
|
1108
|
+
blank();
|
|
1109
|
+
separator();
|
|
1110
|
+
info(`子模块: ${sub.name}`);
|
|
1111
|
+
await linkScope({
|
|
1112
|
+
scopeName: sub.name,
|
|
1113
|
+
configPath: sub.configPath,
|
|
1114
|
+
platforms,
|
|
1115
|
+
finalLinkPlatforms,
|
|
1116
|
+
scanExtraPlatforms: false,
|
|
1117
|
+
registry, tx, projectHash,
|
|
1118
|
+
projectRoot: finalPath,
|
|
1119
|
+
storePath,
|
|
1120
|
+
download: options.download,
|
|
1121
|
+
dryRun: true,
|
|
1122
|
+
yes: options.yes,
|
|
1123
|
+
concurrency,
|
|
1124
|
+
optionalConfigs: sub.selectedOptionalConfigs,
|
|
1125
|
+
scope: sub.relativePath,
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
// Submodule scopes
|
|
1131
|
+
const subResults = [];
|
|
1132
|
+
for (const sub of selectedSubmodules) {
|
|
1133
|
+
blank();
|
|
1134
|
+
separator();
|
|
1135
|
+
info(`链接子模块: ${sub.name}`);
|
|
1136
|
+
const subResult = await linkScope({
|
|
1137
|
+
scopeName: sub.name,
|
|
1138
|
+
configPath: sub.configPath,
|
|
1139
|
+
platforms,
|
|
1140
|
+
finalLinkPlatforms,
|
|
1141
|
+
scanExtraPlatforms: false,
|
|
1142
|
+
registry, tx, projectHash,
|
|
1143
|
+
projectRoot: finalPath,
|
|
1144
|
+
storePath,
|
|
1145
|
+
download: options.download,
|
|
1146
|
+
dryRun: options.dryRun,
|
|
1147
|
+
yes: options.yes,
|
|
1148
|
+
concurrency,
|
|
1149
|
+
optionalConfigs: sub.selectedOptionalConfigs,
|
|
1150
|
+
scope: sub.relativePath,
|
|
1151
|
+
});
|
|
1152
|
+
subResults.push(subResult);
|
|
1153
|
+
}
|
|
1154
|
+
// === 阶段 5: 合并结果、注册项目 ===
|
|
1155
|
+
const allGeneralLibs = new Set([
|
|
1156
|
+
...mainResult.generalLibs,
|
|
1157
|
+
...subResults.flatMap(r => [...r.generalLibs]),
|
|
1158
|
+
]);
|
|
1159
|
+
const allDownloadedLibs = [
|
|
1160
|
+
...mainResult.downloadedLibs,
|
|
1161
|
+
...subResults.flatMap(r => r.downloadedLibs),
|
|
1162
|
+
];
|
|
1163
|
+
// 合并所有依赖
|
|
1164
|
+
const allLinkedDeps = [
|
|
1165
|
+
...mainResult.linkedDeps,
|
|
1166
|
+
...mainResult.nestedLinkedDeps,
|
|
1167
|
+
...subResults.flatMap(r => [...r.linkedDeps, ...r.nestedLinkedDeps]),
|
|
1168
|
+
];
|
|
1169
|
+
// 获取旧引用
|
|
1170
|
+
const oldStoreKeys = registry.getProjectStoreKeys(projectHash);
|
|
1171
|
+
// 更新项目信息
|
|
1172
|
+
const relConfigPath = getRelativeConfigPath(finalPath, mainConfigPath);
|
|
1305
1173
|
registry.addProject({
|
|
1306
1174
|
path: finalPath,
|
|
1307
1175
|
configPath: relConfigPath,
|
|
1308
1176
|
lastLinked: new Date().toISOString(),
|
|
1309
|
-
platforms: finalLinkPlatforms,
|
|
1310
|
-
dependencies:
|
|
1311
|
-
optionalConfigs: selectedOptionalConfigs.length > 0
|
|
1177
|
+
platforms: finalLinkPlatforms,
|
|
1178
|
+
dependencies: allLinkedDeps,
|
|
1179
|
+
optionalConfigs: selectedOptionalConfigs.length > 0
|
|
1180
|
+
? selectedOptionalConfigs.map(c => c.name) : undefined,
|
|
1181
|
+
submodules: selectedSubmodules.length > 0
|
|
1182
|
+
? selectedSubmodules.map(s => s.relativePath) : undefined,
|
|
1312
1183
|
});
|
|
1313
1184
|
// 更新 Store 引用关系
|
|
1314
|
-
const newStoreKeys =
|
|
1315
|
-
// 移除不再使用的引用(设置 unlinkedAt)
|
|
1185
|
+
const newStoreKeys = allLinkedDeps.map((d) => registry.getStoreKey(d.libName, d.commit, d.platform));
|
|
1316
1186
|
for (const key of oldStoreKeys) {
|
|
1317
1187
|
if (!newStoreKeys.includes(key)) {
|
|
1318
1188
|
registry.removeStoreReference(key, projectHash);
|
|
1319
1189
|
}
|
|
1320
1190
|
}
|
|
1321
|
-
// 添加新引用(清除 unlinkedAt)
|
|
1322
1191
|
for (const key of newStoreKeys) {
|
|
1323
1192
|
registry.addStoreReference(key, projectHash);
|
|
1324
1193
|
}
|
|
1325
1194
|
await registry.save();
|
|
1326
|
-
// 事务提交成功
|
|
1327
1195
|
await tx.commit();
|
|
1328
|
-
//
|
|
1329
|
-
|
|
1330
|
-
// 显示统计
|
|
1196
|
+
// === 阶段 6: 统计报告 ===
|
|
1197
|
+
const allSavedBytes = mainResult.savedBytes + subResults.reduce((sum, r) => sum + r.savedBytes, 0);
|
|
1331
1198
|
blank();
|
|
1332
1199
|
separator();
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
stats.replace +
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1200
|
+
if (selectedSubmodules.length > 0) {
|
|
1201
|
+
// 分组显示
|
|
1202
|
+
const mainLinked = mainResult.stats.linked + mainResult.stats.relink + mainResult.stats.replace +
|
|
1203
|
+
mainResult.stats.absorb + mainResult.stats.linkNew + mainResult.downloadedLibs.length;
|
|
1204
|
+
const mainNested = mainResult.nestedLinkedDeps.length;
|
|
1205
|
+
info(`主项目: ${mainLinked + mainNested} 个库` +
|
|
1206
|
+
(mainNested > 0 ? ` (顶层 ${mainLinked}, 嵌套 ${mainNested})` : ''));
|
|
1207
|
+
let totalLinked = mainLinked + mainNested;
|
|
1208
|
+
for (let i = 0; i < subResults.length; i++) {
|
|
1209
|
+
const subR = subResults[i];
|
|
1210
|
+
const subLinked = subR.stats.linked + subR.stats.relink + subR.stats.replace +
|
|
1211
|
+
subR.stats.absorb + subR.stats.linkNew + subR.downloadedLibs.length;
|
|
1212
|
+
const subNested = subR.nestedLinkedDeps.length;
|
|
1213
|
+
info(`${selectedSubmodules[i].name}: ${subLinked + subNested} 个库` +
|
|
1214
|
+
(subNested > 0 ? ` (顶层 ${subLinked}, 嵌套 ${subNested})` : ''));
|
|
1215
|
+
totalLinked += subLinked + subNested;
|
|
1216
|
+
}
|
|
1217
|
+
info(`完成: 共链接 ${totalLinked} 个库`);
|
|
1343
1218
|
}
|
|
1344
1219
|
else {
|
|
1345
|
-
|
|
1220
|
+
const mainLinked = mainResult.stats.linked + mainResult.stats.relink + mainResult.stats.replace +
|
|
1221
|
+
mainResult.stats.absorb + mainResult.stats.linkNew + mainResult.downloadedLibs.length;
|
|
1222
|
+
const mainNested = mainResult.nestedLinkedDeps.length;
|
|
1223
|
+
const totalLinked = mainLinked + mainNested;
|
|
1224
|
+
if (mainNested > 0) {
|
|
1225
|
+
info(`完成: 链接 ${totalLinked} 个库 (顶层 ${mainLinked}, 嵌套 ${mainNested})`);
|
|
1226
|
+
}
|
|
1227
|
+
else {
|
|
1228
|
+
info(`完成: 链接 ${totalLinked} 个库`);
|
|
1229
|
+
}
|
|
1346
1230
|
}
|
|
1347
|
-
if (
|
|
1348
|
-
info(`本次节省: ${formatSize(
|
|
1231
|
+
if (allSavedBytes > 0) {
|
|
1232
|
+
info(`本次节省: ${formatSize(allSavedBytes)}`);
|
|
1349
1233
|
}
|
|
1350
1234
|
const totalSize = await store.getTotalSize();
|
|
1351
1235
|
info(`Store 总计: ${formatSize(totalSize)}`);
|
|
@@ -1409,6 +1293,7 @@ export async function linkProject(projectPath, options) {
|
|
|
1409
1293
|
catch (err) {
|
|
1410
1294
|
// 链接过程出错,回滚事务
|
|
1411
1295
|
error(`链接失败: ${err.message}`);
|
|
1296
|
+
hintStoreRepairCommand(err);
|
|
1412
1297
|
warn('正在回滚事务...');
|
|
1413
1298
|
try {
|
|
1414
1299
|
await tx.rollback();
|
|
@@ -1421,6 +1306,17 @@ export async function linkProject(projectPath, options) {
|
|
|
1421
1306
|
process.exit(1);
|
|
1422
1307
|
}
|
|
1423
1308
|
}
|
|
1309
|
+
function hintStoreRepairCommand(err) {
|
|
1310
|
+
const message = err.message;
|
|
1311
|
+
const isSharedPlatformConflict = message.includes('EEXIST') &&
|
|
1312
|
+
message.includes('_shared') &&
|
|
1313
|
+
KNOWN_PLATFORM_VALUES.some((platform) => message.includes(`${path.sep}_shared${path.sep}${platform}`) || message.includes(`/_shared/${platform}`));
|
|
1314
|
+
if (!isSharedPlatformConflict) {
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
hint('检测到 Store 结构冲突,可能是 _shared 中混入了平台目录。');
|
|
1318
|
+
hint('请运行 `td check --fix` 清理损坏的 Store 条目后再重试。');
|
|
1319
|
+
}
|
|
1424
1320
|
/**
|
|
1425
1321
|
* 注册 _shared 目录的 StoreEntry(如果存在)
|
|
1426
1322
|
* 用于追踪平台库的共享内容大小
|
|
@@ -2092,6 +1988,40 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2092
1988
|
await registerNestedLibraries(resolveResult.absorbResult.nestedLibraries, projectHash);
|
|
2093
1989
|
}
|
|
2094
1990
|
storeHas = true;
|
|
1991
|
+
// 吸收为 General 后,检查是否实际需要平台内容
|
|
1992
|
+
// 本地可能只有部分文件(如只有 _shared 内容),被误分类为 General
|
|
1993
|
+
if (resolveResult.isGeneral && download) {
|
|
1994
|
+
const hasPlatformSparse = dep.sparse && !isSparseOnlyCommon(dep.sparse);
|
|
1995
|
+
if (hasPlatformSparse && availablePlatforms.length > 0) {
|
|
1996
|
+
try {
|
|
1997
|
+
const downloadResult = await codepac.downloadToTemp({
|
|
1998
|
+
url: dep.url,
|
|
1999
|
+
commit: dep.commit,
|
|
2000
|
+
branch: dep.branch,
|
|
2001
|
+
libName: dep.libName,
|
|
2002
|
+
platforms: availablePlatforms,
|
|
2003
|
+
sparse: dep.sparse,
|
|
2004
|
+
vars,
|
|
2005
|
+
});
|
|
2006
|
+
if (downloadResult.platformDirs.length > 0) {
|
|
2007
|
+
// 有平台内容 → 吸收平台目录,重新分类为平台库
|
|
2008
|
+
tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
2009
|
+
const nestedAbsorbResult = await store.absorbLib(downloadResult.libDir, downloadResult.platformDirs, dep.libName, dep.commit);
|
|
2010
|
+
await registerNestedLibraries(nestedAbsorbResult.nestedLibraries, projectHash);
|
|
2011
|
+
existingPlatforms.push(...downloadResult.platformDirs);
|
|
2012
|
+
isGeneral = false;
|
|
2013
|
+
info(`${indent} ${dep.libName} - 补充平台内容 [${downloadResult.platformDirs.join(', ')}]`);
|
|
2014
|
+
}
|
|
2015
|
+
if (downloadResult.cleanedPlatforms.length > 0) {
|
|
2016
|
+
hint(`${indent} 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
catch {
|
|
2020
|
+
// 下载失败,保持 General 分类
|
|
2021
|
+
warn(`${indent} ${dep.libName} - 平台内容下载失败,保持 General 分类`);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2095
2025
|
}
|
|
2096
2026
|
// 更新 localExists 状态(可能已被删除或 absorb)
|
|
2097
2027
|
try {
|
|
@@ -2207,15 +2137,30 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2207
2137
|
continue;
|
|
2208
2138
|
}
|
|
2209
2139
|
try {
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
sparse: dep.sparse,
|
|
2217
|
-
vars,
|
|
2140
|
+
const nestedLibKey = registry.getLibraryKey(dep.libName, dep.commit);
|
|
2141
|
+
const nestedHistoryLib = registry.getLibrary(nestedLibKey);
|
|
2142
|
+
const downloadMonitor = new DownloadMonitor({
|
|
2143
|
+
name: `${indent} ${dep.libName}`,
|
|
2144
|
+
estimatedSize: nestedHistoryLib?.size,
|
|
2145
|
+
getDirSize,
|
|
2218
2146
|
});
|
|
2147
|
+
let downloadResult;
|
|
2148
|
+
try {
|
|
2149
|
+
downloadResult = await codepac.downloadToTemp({
|
|
2150
|
+
url: dep.url,
|
|
2151
|
+
commit: dep.commit,
|
|
2152
|
+
branch: dep.branch,
|
|
2153
|
+
libName: dep.libName,
|
|
2154
|
+
platforms: availablePlatforms,
|
|
2155
|
+
sparse: dep.sparse,
|
|
2156
|
+
vars,
|
|
2157
|
+
onTempDirCreated: (_tempDir, libDir) => { downloadMonitor.start(libDir); },
|
|
2158
|
+
onHeartbeat: (message) => { downloadMonitor.heartbeat(message); },
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
finally {
|
|
2162
|
+
await downloadMonitor.stop();
|
|
2163
|
+
}
|
|
2219
2164
|
// 提示清理的平台(如果有)
|
|
2220
2165
|
if (downloadResult.cleanedPlatforms.length > 0) {
|
|
2221
2166
|
hint(`${indent} 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
|