tanmi-dock 1.0.5 → 1.0.6-beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/commands/check.d.ts.map +1 -1
  2. package/dist/commands/check.js +121 -22
  3. package/dist/commands/check.js.map +1 -1
  4. package/dist/commands/clean.d.ts +3 -0
  5. package/dist/commands/clean.d.ts.map +1 -1
  6. package/dist/commands/clean.js +11 -17
  7. package/dist/commands/clean.js.map +1 -1
  8. package/dist/commands/dashboard.d.ts.map +1 -1
  9. package/dist/commands/dashboard.js +28 -10
  10. package/dist/commands/dashboard.js.map +1 -1
  11. package/dist/commands/link.d.ts +21 -0
  12. package/dist/commands/link.d.ts.map +1 -1
  13. package/dist/commands/link.js +739 -344
  14. package/dist/commands/link.js.map +1 -1
  15. package/dist/commands/status.js +3 -3
  16. package/dist/commands/status.js.map +1 -1
  17. package/dist/commands/unavailable.d.ts.map +1 -1
  18. package/dist/commands/unavailable.js +4 -3
  19. package/dist/commands/unavailable.js.map +1 -1
  20. package/dist/core/codepac-cache.d.ts +22 -0
  21. package/dist/core/codepac-cache.d.ts.map +1 -0
  22. package/dist/core/codepac-cache.js +122 -0
  23. package/dist/core/codepac-cache.js.map +1 -0
  24. package/dist/core/linker.d.ts +1 -1
  25. package/dist/core/linker.d.ts.map +1 -1
  26. package/dist/core/linker.js +93 -39
  27. package/dist/core/linker.js.map +1 -1
  28. package/dist/core/registry.d.ts +27 -2
  29. package/dist/core/registry.d.ts.map +1 -1
  30. package/dist/core/registry.js +278 -23
  31. package/dist/core/registry.js.map +1 -1
  32. package/dist/core/space/cleanup-executor.d.ts +1 -0
  33. package/dist/core/space/cleanup-executor.d.ts.map +1 -1
  34. package/dist/core/space/cleanup-executor.js +43 -3
  35. package/dist/core/space/cleanup-executor.js.map +1 -1
  36. package/dist/core/space/cleanup-planner.d.ts.map +1 -1
  37. package/dist/core/space/cleanup-planner.js +62 -19
  38. package/dist/core/space/cleanup-planner.js.map +1 -1
  39. package/dist/core/space/cleanup-selection.d.ts.map +1 -1
  40. package/dist/core/space/cleanup-selection.js +2 -2
  41. package/dist/core/space/cleanup-selection.js.map +1 -1
  42. package/dist/core/space/inventory.d.ts +11 -0
  43. package/dist/core/space/inventory.d.ts.map +1 -1
  44. package/dist/core/space/inventory.js +135 -9
  45. package/dist/core/space/inventory.js.map +1 -1
  46. package/dist/core/space/meter.d.ts.map +1 -1
  47. package/dist/core/space/meter.js +9 -5
  48. package/dist/core/space/meter.js.map +1 -1
  49. package/dist/core/space/service.d.ts +1 -0
  50. package/dist/core/space/service.d.ts.map +1 -1
  51. package/dist/core/space/service.js +4 -1
  52. package/dist/core/space/service.js.map +1 -1
  53. package/dist/core/space/types.d.ts +2 -1
  54. package/dist/core/space/types.d.ts.map +1 -1
  55. package/dist/core/space/types.js.map +1 -1
  56. package/dist/core/store-import-journal.d.ts +58 -0
  57. package/dist/core/store-import-journal.d.ts.map +1 -0
  58. package/dist/core/store-import-journal.js +304 -0
  59. package/dist/core/store-import-journal.js.map +1 -0
  60. package/dist/core/store-import.d.ts +52 -0
  61. package/dist/core/store-import.d.ts.map +1 -0
  62. package/dist/core/store-import.js +770 -0
  63. package/dist/core/store-import.js.map +1 -0
  64. package/dist/core/store-write-protection.d.ts +2 -0
  65. package/dist/core/store-write-protection.d.ts.map +1 -1
  66. package/dist/core/store-write-protection.js +6 -5
  67. package/dist/core/store-write-protection.js.map +1 -1
  68. package/dist/core/transaction.d.ts +39 -6
  69. package/dist/core/transaction.d.ts.map +1 -1
  70. package/dist/core/transaction.js +536 -73
  71. package/dist/core/transaction.js.map +1 -1
  72. package/dist/index.js +2 -19
  73. package/dist/index.js.map +1 -1
  74. package/dist/types/index.d.ts +7 -0
  75. package/dist/types/index.d.ts.map +1 -1
  76. package/dist/types/index.js.map +1 -1
  77. package/dist/utils/fs-utils.d.ts +2 -0
  78. package/dist/utils/fs-utils.d.ts.map +1 -1
  79. package/dist/utils/fs-utils.js +45 -13
  80. package/dist/utils/fs-utils.js.map +1 -1
  81. package/package.json +1 -1
@@ -3,7 +3,6 @@
3
3
  */
4
4
  import path from 'path';
5
5
  import fs from 'fs/promises';
6
- import crypto from 'crypto';
7
6
  import { Command } from 'commander';
8
7
  import { ensureInitialized } from '../core/guard.js';
9
8
  import * as config from '../core/config.js';
@@ -14,10 +13,12 @@ import * as store from '../core/store.js';
14
13
  import * as linker from '../core/linker.js';
15
14
  import * as codepac from '../core/codepac.js';
16
15
  import { setProxyConfig } from '../core/codepac.js';
17
- import { getPlatformHelpText, GENERAL_PLATFORM, SHARED_PLATFORM, pathsEqual, isSparseOnlyCommon, KNOWN_PLATFORM_VALUES, getRequestedPlatformTargets, resolveSparseConfig, getBaseKeyForCodepac, } from '../core/platform.js';
16
+ import { getPlatformHelpText, GENERAL_PLATFORM, SHARED_PLATFORM, pathsEqual, isSparseOnlyCommon, KNOWN_PLATFORM_VALUES, getRequestedPlatformTargets, resolveSparseConfig, getBaseKeyForCodepac, getRegistryPath, } from '../core/platform.js';
18
17
  import { Transaction } from '../core/transaction.js';
18
+ import { syncCodepacCache } from '../core/codepac-cache.js';
19
+ import { StoreImportService } from '../core/store-import.js';
19
20
  import { formatSize, checkDiskSpace } from '../utils/disk.js';
20
- import { getDirSize } from '../utils/fs-utils.js';
21
+ import { getDirSize, isPathMissingError } from '../utils/fs-utils.js';
21
22
  import { ProgressTracker, DownloadMonitor, MultiBarManager } from '../utils/progress.js';
22
23
  import { success, warn, error, info, hint, blank, separator, debug } from '../utils/logger.js';
23
24
  import { verifyLocalCommit, findSubmoduleConfigs } from '../utils/git.js';
@@ -65,6 +66,181 @@ export function createLinkCommand() {
65
66
  }
66
67
  });
67
68
  }
69
+ /** 下载产物完成导入后仅清理临时目录;清理失败不会撤销已经验证并登记的 Store。 */
70
+ export async function cleanupDownloadTempDir(tempDir, context) {
71
+ debug(`[link-download] 临时目录清理开始: stage=${context.stage}, lib=${context.libName}, commit=${context.commit}, temp=${tempDir}`);
72
+ try {
73
+ await fs.rm(tempDir, { recursive: true, force: true });
74
+ debug(`[link-download] 临时目录清理完成: stage=${context.stage}, lib=${context.libName}, commit=${context.commit}, temp=${tempDir}, output=removed`);
75
+ }
76
+ catch (err) {
77
+ warn(`[link-download] 临时目录清理失败,Store 缓存保持有效: stage=${context.stage}, lib=${context.libName}, commit=${context.commit}, temp=${tempDir}, message=${err.message}`);
78
+ }
79
+ }
80
+ function dependencyIdentityKey(dependency, localPath) {
81
+ return JSON.stringify([dependency.libName, dependency.commit, path.resolve(localPath)]);
82
+ }
83
+ function implicitReferenceOwner(dependency, projectRoot, localPath, scope) {
84
+ return {
85
+ libName: dependency.libName,
86
+ commit: dependency.commit,
87
+ linkedPath: path.relative(projectRoot, localPath),
88
+ scope,
89
+ };
90
+ }
91
+ function implicitReferenceOwnerKey(owner) {
92
+ return JSON.stringify([
93
+ owner.libName,
94
+ owner.commit,
95
+ owner.linkedPath,
96
+ owner.scope ?? '',
97
+ ]);
98
+ }
99
+ function serializeImplicitStoreReferences(references) {
100
+ return [...references.values()].map(({ owner, storeKeys }) => ({
101
+ owner,
102
+ storeKeys: [...storeKeys],
103
+ }));
104
+ }
105
+ function addImplicitStoreReference(references, owner, storeKey) {
106
+ const ownerKey = implicitReferenceOwnerKey(owner);
107
+ let entry = references.get(ownerKey);
108
+ if (!entry) {
109
+ entry = { owner, storeKeys: new Set() };
110
+ references.set(ownerKey, entry);
111
+ }
112
+ entry.storeKeys.add(storeKey);
113
+ }
114
+ function errorCauseHasCode(errorValue, codes) {
115
+ let current = errorValue;
116
+ while (current instanceof Error) {
117
+ const code = current.code;
118
+ if (code && codes.has(code))
119
+ return true;
120
+ current = current.cause;
121
+ }
122
+ return false;
123
+ }
124
+ async function retainExistingLinkedDependencies(project, currentDependencies, projectRoot, storePath) {
125
+ if (!project)
126
+ return [];
127
+ const currentOwners = new Set(currentDependencies.map((dependency) => implicitReferenceOwnerKey({
128
+ libName: dependency.libName,
129
+ commit: dependency.commit,
130
+ linkedPath: dependency.linkedPath,
131
+ scope: dependency.scope,
132
+ })));
133
+ const groups = new Map();
134
+ for (const dependency of project.dependencies) {
135
+ const ownerKey = implicitReferenceOwnerKey({
136
+ libName: dependency.libName,
137
+ commit: dependency.commit,
138
+ linkedPath: dependency.linkedPath,
139
+ scope: dependency.scope,
140
+ });
141
+ if (currentOwners.has(ownerKey))
142
+ continue;
143
+ const group = groups.get(ownerKey) ?? [];
144
+ group.push(dependency);
145
+ groups.set(ownerKey, group);
146
+ }
147
+ const retained = [];
148
+ for (const dependencies of groups.values()) {
149
+ const template = dependencies[0];
150
+ const localPath = path.resolve(projectRoot, template.linkedPath);
151
+ const storeCommitPath = path.join(storePath, template.libName, template.commit);
152
+ const isGeneral = dependencies.some((dependency) => dependency.platform === GENERAL_PLATFORM) &&
153
+ dependencies.every((dependency) => dependency.platform === GENERAL_PLATFORM);
154
+ try {
155
+ await recordLinkedDependency(retained, template, localPath, storeCommitPath, projectRoot, isGeneral, template.scope);
156
+ debug(`[link-registry] 保留现有真实链接: lib=${template.libName}, commit=${template.commit}, ` +
157
+ `local=${localPath}, source=previous-project`);
158
+ }
159
+ catch (err) {
160
+ if (errorCauseHasCode(err, new Set(['EACCES', 'EPERM', 'EIO'])))
161
+ throw err;
162
+ debug(`[link-registry] 现有依赖不再指向当前 Store,移除登记: lib=${template.libName}, ` +
163
+ `commit=${template.commit}, local=${localPath}, detail=${err.message}`);
164
+ }
165
+ }
166
+ return retained;
167
+ }
168
+ function mergeProjectImplicitStoreReferences(project, projectHash, activeDependencies, discovered, registry) {
169
+ const activeOwners = new Set(activeDependencies.map((dependency) => implicitReferenceOwnerKey({
170
+ libName: dependency.libName,
171
+ commit: dependency.commit,
172
+ linkedPath: dependency.linkedPath,
173
+ scope: dependency.scope,
174
+ })));
175
+ const merged = new Map();
176
+ const legacyStoreKeys = new Set();
177
+ const knownPreviousImplicitKeys = new Set((project?.implicitStoreReferences ?? []).flatMap((reference) => reference.storeKeys));
178
+ const previousDependencyKeys = new Set((project?.dependencies ?? []).map((dependency) => registry.getStoreKey(dependency.libName, dependency.commit, dependency.platform)));
179
+ if (project) {
180
+ for (const storeKey of registry.getProjectStoreKeys(projectHash)) {
181
+ if (!previousDependencyKeys.has(storeKey) && !knownPreviousImplicitKeys.has(storeKey)) {
182
+ legacyStoreKeys.add(storeKey);
183
+ }
184
+ }
185
+ }
186
+ for (const reference of project?.implicitStoreReferences ?? []) {
187
+ if (!reference.owner) {
188
+ for (const storeKey of reference.storeKeys)
189
+ legacyStoreKeys.add(storeKey);
190
+ continue;
191
+ }
192
+ if (!activeOwners.has(implicitReferenceOwnerKey(reference.owner)))
193
+ continue;
194
+ for (const storeKey of reference.storeKeys) {
195
+ if (registry.getStore(storeKey))
196
+ addImplicitStoreReference(merged, reference.owner, storeKey);
197
+ }
198
+ }
199
+ for (const reference of discovered) {
200
+ if (!reference.owner || !activeOwners.has(implicitReferenceOwnerKey(reference.owner)))
201
+ continue;
202
+ for (const storeKey of reference.storeKeys) {
203
+ if (registry.getStore(storeKey))
204
+ addImplicitStoreReference(merged, reference.owner, storeKey);
205
+ }
206
+ }
207
+ const result = serializeImplicitStoreReferences(merged);
208
+ const validLegacyKeys = [...legacyStoreKeys].filter((storeKey) => registry.getStore(storeKey));
209
+ if (validLegacyKeys.length > 0)
210
+ result.push({ storeKeys: validLegacyKeys });
211
+ debug(`[link-registry] 隐式引用合并完成: project=${projectHash}, activeOwners=${activeOwners.size}, ` +
212
+ `discovered=${discovered.length}, previous=${project?.implicitStoreReferences?.length ?? 0}, ` +
213
+ `legacy=${validLegacyKeys.length}, groups=${result.length}`);
214
+ return result;
215
+ }
216
+ function buildDependencyRefs(dependency, platforms) {
217
+ return [...new Set(platforms)].map((platform) => ({ ...dependency, platform }));
218
+ }
219
+ function deduplicateDependencyRefs(dependencies) {
220
+ const seen = new Set();
221
+ return dependencies.filter((dependency) => {
222
+ const key = JSON.stringify([
223
+ dependency.libName,
224
+ dependency.commit,
225
+ dependency.platform,
226
+ dependency.linkedPath,
227
+ dependency.scope ?? '',
228
+ ]);
229
+ if (seen.has(key))
230
+ return false;
231
+ seen.add(key);
232
+ return true;
233
+ });
234
+ }
235
+ /** 并行下载批次统一管理进度组件生命周期,任务创建或汇总失败时同样恢复终端状态。 */
236
+ export async function runParallelDownloadBatch(createTasks, manager) {
237
+ try {
238
+ return await Promise.all(createTasks());
239
+ }
240
+ finally {
241
+ manager?.stop();
242
+ }
243
+ }
68
244
  function resolveRequestedPlatformsByActual(requestedPlatforms, sparse, actualPlatforms, vars) {
69
245
  const actualExisting = [...new Set(actualPlatforms)];
70
246
  const actualPlatformSet = new Set(actualExisting);
@@ -130,8 +306,12 @@ async function prepareSharedStoreRepair(dependency, requestedPlatforms, repairRe
130
306
  try {
131
307
  await fs.access(sharedPath);
132
308
  }
133
- catch {
134
- return [];
309
+ catch (err) {
310
+ if (isPathMissingError(err))
311
+ return [];
312
+ error(`[link-store] shared 目录访问失败: lib=${dependency.libName}, commit=${dependency.commit}, ` +
313
+ `path=${sharedPath}, code=${err.code ?? 'unknown'}, message=${err.message}`);
314
+ throw err;
135
315
  }
136
316
  const result = await verifyExistingPlatforms(dependency.libName, dependency.commit, [SHARED_PLATFORM]);
137
317
  if (result.valid.length > 0 && result.corrupted.length === 0 && result.repairRequired.length === 0) {
@@ -167,8 +347,12 @@ async function hasRequestedPlatformContent(dependency, requestedPlatforms, vars)
167
347
  }
168
348
  }
169
349
  }
170
- catch {
171
- // Store 目录不存在时继续使用历史记录或后续 General 检测
350
+ catch (err) {
351
+ if (!isPathMissingError(err)) {
352
+ error(`[link-store] 请求平台目录读取失败: lib=${dependency.libName}, commit=${dependency.commit}, ` +
353
+ `path=${commitPath}, code=${err.code ?? 'unknown'}, message=${err.message}`);
354
+ throw err;
355
+ }
172
356
  }
173
357
  return false;
174
358
  }
@@ -489,18 +673,29 @@ function buildSubmoduleOptionalConfigState(submodules) {
489
673
  }
490
674
  return Object.keys(state).length > 0 ? state : undefined;
491
675
  }
676
+ async function linkPlatformDirectory(tx, localPath, storeCommitPath, platforms) {
677
+ await tx.preparePathReplacement(localPath, storeCommitPath);
678
+ await linker.linkLib(localPath, storeCommitPath, platforms);
679
+ }
680
+ async function linkGeneralDirectory(tx, localPath, storeSharedPath) {
681
+ await tx.preparePathReplacement(localPath, storeSharedPath);
682
+ await linker.linkGeneral(localPath, storeSharedPath);
683
+ }
492
684
  /**
493
685
  * 单个 scope(主项目或 submodule)的链接处理
494
686
  *
495
687
  * 包含:解析依赖、分类、预扫描平台、下载确认、逐个处理、并行下载、嵌套 actions
496
688
  */
497
689
  async function linkScope(params) {
498
- const { scopeName, configPath, platforms, scanExtraPlatforms, registry, tx, projectHash, projectRoot, storePath, download, dryRun, yes, concurrency, gitLightweightDownload, optionalConfigs, scope, } = params;
690
+ const { scopeName, configPath, platforms, scanExtraPlatforms, registry, tx, projectRoot, storePath, download, dryRun, yes, concurrency, gitLightweightDownload, optionalConfigs, scope, } = params;
499
691
  let finalLinkPlatforms = [...params.finalLinkPlatforms];
500
692
  const codepacPlatforms = [...new Set(platforms.map((platform) => getBaseKeyForCodepac(platform)))];
501
- // 记录 General 类型库
502
- const generalLibs = new Set();
693
+ // General 身份包含版本和项目位置,避免同名的其他依赖被误判为 General
694
+ const generalDependencyKeys = new Set();
695
+ // 吸收过程中发现、但项目配置没有显式 dependency 的嵌套库仍需保留项目引用。
696
+ const implicitStoreReferences = new Map();
503
697
  const downloadedLibs = [];
698
+ const linkedMissingDependencyKeys = new Set();
504
699
  let savedBytes = 0;
505
700
  // 1. 解析依赖
506
701
  info(`分析 ${scopeName}: ${configPath}`);
@@ -559,7 +754,7 @@ async function linkScope(params) {
559
754
  if (dryRun) {
560
755
  showDryRunInfo(classified, stats);
561
756
  return {
562
- linkedDeps: [], nestedLinkedDeps: [], generalLibs, downloadedLibs,
757
+ linkedDeps: [], nestedLinkedDeps: [], implicitStoreReferences: [], downloadedLibs,
563
758
  savedBytes: 0, finalLinkPlatforms, stats,
564
759
  };
565
760
  }
@@ -606,8 +801,12 @@ async function linkScope(params) {
606
801
  .filter(e => e.isDirectory() && KNOWN_PLATFORM_VALUES.includes(e.name))
607
802
  .forEach(e => allLocalPlatforms.add(e.name));
608
803
  }
609
- catch {
610
- // 读取失败,跳过
804
+ catch (err) {
805
+ if (isPathMissingError(err))
806
+ continue;
807
+ error(`[link] 本地平台预扫描失败: lib=${item.dependency.libName}, commit=${item.dependency.commit}, ` +
808
+ `path=${item.localPath}, code=${err.code ?? 'unknown'}, message=${err.message}`);
809
+ throw err;
611
810
  }
612
811
  }
613
812
  const extraPlatforms = [...allLocalPlatforms].filter(p => !platforms.includes(p));
@@ -625,7 +824,7 @@ async function linkScope(params) {
625
824
  if (selectedPlatforms === PROMPT_CANCELLED) {
626
825
  info('已取消');
627
826
  return {
628
- linkedDeps: [], nestedLinkedDeps: [], generalLibs, downloadedLibs,
827
+ linkedDeps: [], nestedLinkedDeps: [], implicitStoreReferences: [], downloadedLibs,
629
828
  savedBytes: 0, finalLinkPlatforms, stats,
630
829
  };
631
830
  }
@@ -704,6 +903,7 @@ async function linkScope(params) {
704
903
  // 7. 逐个处理依赖(LINKED/RELINK/REPLACE/ABSORB/LINK_NEW)
705
904
  for (const item of classified) {
706
905
  const { dependency, status, localPath, repairRequiredPlatforms } = item;
906
+ const implicitOwner = implicitReferenceOwner(dependency, projectRoot, localPath, scope);
707
907
  await store.ensureCompatibleStore(storePath, dependency.libName, dependency.commit);
708
908
  switch (status) {
709
909
  case DependencyStatus.LINKED: {
@@ -716,22 +916,21 @@ async function linkScope(params) {
716
916
  useGitLightweightDownload: gitLightweightDownload,
717
917
  repairRequiredPlatforms,
718
918
  });
719
- await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
919
+ await registerNestedLibraries(supplementResult.nestedLibraries, implicitStoreReferences, implicitOwner);
720
920
  if (supplementResult.downloaded.length > 0) {
721
921
  const linkedCommitPath = path.join(storePath, dependency.libName, dependency.commit);
722
922
  const { actualExisting: allExisting, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
723
923
  clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
724
- await tx.recordOp('link', localPath, linkedCommitPath);
725
- await linker.linkLib(localPath, linkedCommitPath, allExisting);
726
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, allExisting, projectHash, false);
924
+ await linkPlatformDirectory(tx, localPath, linkedCommitPath, allExisting);
925
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, allExisting, false);
727
926
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已补充平台 [${supplementResult.downloaded.join(', ')}]`);
728
927
  }
729
928
  }
730
929
  const { actualExisting: linkedExisting, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
731
930
  clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
732
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, isLinkedGeneral ? [GENERAL_PLATFORM] : linkedExisting, projectHash, isLinkedGeneral);
931
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, isLinkedGeneral ? [GENERAL_PLATFORM] : linkedExisting, isLinkedGeneral);
733
932
  if (isLinkedGeneral) {
734
- generalLibs.add(dependency.libName);
933
+ generalDependencyKeys.add(dependencyIdentityKey(dependency, localPath));
735
934
  }
736
935
  break;
737
936
  }
@@ -741,13 +940,10 @@ async function linkScope(params) {
741
940
  const relinkCommitPath = path.join(storePath, dependency.libName, dependency.commit);
742
941
  const isRelinkGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
743
942
  if (isRelinkGeneral) {
744
- await tx.recordOp('unlink', localPath);
745
- await linker.unlink(localPath);
746
943
  const sharedPath = path.join(relinkCommitPath, '_shared');
747
- await tx.recordOp('link', localPath, sharedPath);
748
- await linker.linkGeneral(localPath, sharedPath);
749
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
750
- generalLibs.add(dependency.libName);
944
+ await linkGeneralDirectory(tx, localPath, sharedPath);
945
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], true);
946
+ generalDependencyKeys.add(dependencyIdentityKey(dependency, localPath));
751
947
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,重建链接`);
752
948
  }
753
949
  else {
@@ -756,7 +952,7 @@ async function linkScope(params) {
756
952
  useGitLightweightDownload: gitLightweightDownload,
757
953
  repairRequiredPlatforms,
758
954
  });
759
- await registerNestedLibraries(relinkSupplementResult.nestedLibraries, projectHash);
955
+ await registerNestedLibraries(relinkSupplementResult.nestedLibraries, implicitStoreReferences, implicitOwner);
760
956
  const { actualExisting: relinkExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
761
957
  clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
762
958
  if (relinkExisting.length === 0) {
@@ -779,11 +975,8 @@ async function linkScope(params) {
779
975
  }
780
976
  break;
781
977
  }
782
- await tx.recordOp('unlink', localPath);
783
- await linker.unlink(localPath);
784
- await tx.recordOp('link', localPath, relinkCommitPath);
785
- await linker.linkLib(localPath, relinkCommitPath, relinkExisting);
786
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, relinkExisting, projectHash, false);
978
+ await linkPlatformDirectory(tx, localPath, relinkCommitPath, relinkExisting);
979
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, relinkExisting, false);
787
980
  if (relinkSupplementResult.downloaded.length > 0) {
788
981
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 重建链接并补充平台 [${relinkExisting.join(', ')}]`);
789
982
  }
@@ -801,11 +994,10 @@ async function linkScope(params) {
801
994
  const isReplaceGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
802
995
  if (isReplaceGeneral) {
803
996
  const sharedPath = path.join(replaceCommitPath, '_shared');
804
- await tx.recordOp('replace', localPath, sharedPath);
805
- await linker.linkGeneral(localPath, sharedPath);
997
+ await linkGeneralDirectory(tx, localPath, sharedPath);
806
998
  savedBytes += replaceSize;
807
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
808
- generalLibs.add(dependency.libName);
999
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], true);
1000
+ generalDependencyKeys.add(dependencyIdentityKey(dependency, localPath));
809
1001
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,创建链接`);
810
1002
  }
811
1003
  else {
@@ -814,7 +1006,7 @@ async function linkScope(params) {
814
1006
  useGitLightweightDownload: gitLightweightDownload,
815
1007
  repairRequiredPlatforms,
816
1008
  });
817
- await registerNestedLibraries(replaceSupplementResult.nestedLibraries, projectHash);
1009
+ await registerNestedLibraries(replaceSupplementResult.nestedLibraries, implicitStoreReferences, implicitOwner);
818
1010
  const { actualExisting: replaceExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
819
1011
  clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
820
1012
  if (replaceExisting.length === 0) {
@@ -837,10 +1029,9 @@ async function linkScope(params) {
837
1029
  }
838
1030
  break;
839
1031
  }
840
- await tx.recordOp('replace', localPath, replaceCommitPath);
841
- await linker.linkLib(localPath, replaceCommitPath, replaceExisting);
1032
+ await linkPlatformDirectory(tx, localPath, replaceCommitPath, replaceExisting);
842
1033
  savedBytes += replaceSize;
843
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, replaceExisting, projectHash, false);
1034
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, replaceExisting, false);
844
1035
  if (replaceSupplementResult.downloaded.length > 0) {
845
1036
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - Store 已有,创建链接并补充平台 [${replaceExisting.join(', ')}]`);
846
1037
  }
@@ -881,20 +1072,31 @@ async function linkScope(params) {
881
1072
  const progressTracker = new ProgressTracker({
882
1073
  name: ` 移入 Store`, total: absorbSize, showSpeed: true,
883
1074
  });
884
- await tx.recordOp('absorb', storeCommitPath, localPath);
885
1075
  let progressStarted = false;
886
- const absorbResult = await store.absorbLib(localPath, finalPlatforms, dependency.libName, dependency.commit, {
887
- totalSize: absorbSize,
888
- onProgress: (copied, _total) => {
889
- if (!progressStarted) {
890
- progressStarted = true;
891
- progressTracker.start();
892
- }
893
- progressTracker.update(copied);
894
- },
895
- });
896
- if (progressStarted)
897
- progressTracker.stop();
1076
+ let absorbResult;
1077
+ try {
1078
+ absorbResult = await new StoreImportService(registry, storePath).importLibrary({
1079
+ libDir: localPath,
1080
+ platforms: finalPlatforms,
1081
+ libName: dependency.libName,
1082
+ commit: dependency.commit,
1083
+ branch: dependency.branch,
1084
+ url: dependency.url,
1085
+ sourceKind: 'local',
1086
+ totalSize: absorbSize,
1087
+ onProgress: (copied, _total) => {
1088
+ if (!progressStarted) {
1089
+ progressStarted = true;
1090
+ progressTracker.start();
1091
+ }
1092
+ progressTracker.update(copied);
1093
+ },
1094
+ });
1095
+ }
1096
+ finally {
1097
+ if (progressStarted)
1098
+ progressTracker.stop();
1099
+ }
898
1100
  let absorbLinkPlatforms = [...Object.keys(absorbResult.platformPaths), ...absorbResult.skippedPlatforms];
899
1101
  const absorbLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
900
1102
  if (!registry.getLibrary(absorbLibKey)) {
@@ -918,22 +1120,17 @@ async function linkScope(params) {
918
1120
  }
919
1121
  }
920
1122
  await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
921
- await registerNestedLibraries(absorbResult.nestedLibraries, projectHash);
1123
+ await registerNestedLibraries(absorbResult.nestedLibraries, implicitStoreReferences, implicitOwner);
922
1124
  const absorbSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, {
923
1125
  vars: configVars,
924
1126
  useGitLightweightDownload: gitLightweightDownload,
925
1127
  repairRequiredPlatforms,
926
1128
  });
927
- await registerNestedLibraries(absorbSupplementResult.nestedLibraries, projectHash);
1129
+ await registerNestedLibraries(absorbSupplementResult.nestedLibraries, implicitStoreReferences, implicitOwner);
928
1130
  if (absorbSupplementResult.downloaded.length > 0) {
929
1131
  absorbLinkPlatforms = [...absorbLinkPlatforms, ...absorbSupplementResult.downloaded];
930
1132
  }
931
- await tx.recordOp('link', localPath, storeCommitPath);
932
- await linker.linkLib(localPath, storeCommitPath, absorbLinkPlatforms);
933
- for (const platform of absorbLinkPlatforms) {
934
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
935
- registry.addStoreReference(storeKey, projectHash);
936
- }
1133
+ await linkPlatformDirectory(tx, localPath, storeCommitPath, absorbLinkPlatforms);
937
1134
  if (absorbSupplementResult.downloaded.length > 0) {
938
1135
  hint(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 本地已有,移入 Store 并补充平台 [${absorbLinkPlatforms.join(', ')}]`);
939
1136
  }
@@ -951,8 +1148,7 @@ async function linkScope(params) {
951
1148
  break;
952
1149
  }
953
1150
  const { GENERAL_PLATFORM } = await import('../core/platform.js');
954
- await tx.recordOp('link', localPath, sharedPath);
955
- await linker.linkGeneral(localPath, sharedPath);
1151
+ await linkGeneralDirectory(tx, localPath, sharedPath);
956
1152
  const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
957
1153
  if (!registry.getStore(storeKey)) {
958
1154
  const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
@@ -962,7 +1158,6 @@ async function linkScope(params) {
962
1158
  usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
963
1159
  });
964
1160
  }
965
- registry.addStoreReference(storeKey, projectHash);
966
1161
  const libKeyGen = registry.getLibraryKey(dependency.libName, dependency.commit);
967
1162
  if (!registry.getLibrary(libKeyGen)) {
968
1163
  const sharedSize = await getDirSize(sharedPath);
@@ -973,7 +1168,7 @@ async function linkScope(params) {
973
1168
  referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
974
1169
  });
975
1170
  }
976
- generalLibs.add(dependency.libName);
1171
+ generalDependencyKeys.add(dependencyIdentityKey(dependency, localPath));
977
1172
  hint(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,整目录链接`);
978
1173
  }
979
1174
  catch {
@@ -1040,9 +1235,16 @@ async function linkScope(params) {
1040
1235
  clearSatisfiedAutoUnavailablePlatforms(registry, dependency, assessment.satisfiedRequested);
1041
1236
  const filteredDownloaded = assessment.downloadedRequested;
1042
1237
  if (filteredDownloaded.length > 0) {
1043
- await tx.recordOp('absorb', linkNewCommitPath, downloadResult.libDir);
1044
- const linkNewAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
1045
- await registerNestedLibraries(linkNewAbsorbResult.nestedLibraries, projectHash);
1238
+ const linkNewAbsorbResult = await new StoreImportService(registry, storePath).importLibrary({
1239
+ libDir: downloadResult.libDir,
1240
+ platforms: filteredDownloaded,
1241
+ libName: dependency.libName,
1242
+ commit: dependency.commit,
1243
+ branch: dependency.branch,
1244
+ url: dependency.url,
1245
+ sourceKind: 'download',
1246
+ });
1247
+ await registerNestedLibraries(linkNewAbsorbResult.nestedLibraries, implicitStoreReferences, implicitOwner);
1046
1248
  const repaired = filteredDownloaded.filter((platform) => repairRequiredSet.has(platform));
1047
1249
  if (repaired.length > 0) {
1048
1250
  warn(`${dependency.libName} ${repaired.join(', ')} 本地缓存不完整,已重新下载`);
@@ -1050,7 +1252,11 @@ async function linkScope(params) {
1050
1252
  }
1051
1253
  }
1052
1254
  finally {
1053
- await fs.rm(downloadResult.tempDir, { recursive: true, force: true }).catch(() => { });
1255
+ await cleanupDownloadTempDir(downloadResult.tempDir, {
1256
+ libName: dependency.libName,
1257
+ commit: dependency.commit,
1258
+ stage: 'link-new-import',
1259
+ });
1054
1260
  }
1055
1261
  }
1056
1262
  const { actualExisting: linkNewExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
@@ -1058,10 +1264,9 @@ async function linkScope(params) {
1058
1264
  const isLinkNewGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
1059
1265
  if (isLinkNewGeneral) {
1060
1266
  const sharedPath = path.join(linkNewCommitPath, '_shared');
1061
- await tx.recordOp('link', localPath, sharedPath);
1062
- await linker.linkGeneral(localPath, sharedPath);
1063
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
1064
- generalLibs.add(dependency.libName);
1267
+ await linkGeneralDirectory(tx, localPath, sharedPath);
1268
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], true);
1269
+ generalDependencyKeys.add(dependencyIdentityKey(dependency, localPath));
1065
1270
  if (isRepairingGeneral) {
1066
1271
  warn(`${dependency.libName} ${GENERAL_PLATFORM} 本地缓存不完整,已重新下载`);
1067
1272
  }
@@ -1090,17 +1295,18 @@ async function linkScope(params) {
1090
1295
  break;
1091
1296
  }
1092
1297
  else {
1093
- await tx.recordOp('link', localPath, linkNewCommitPath);
1094
- await linker.linkLib(localPath, linkNewCommitPath, linkNewExisting);
1095
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkNewExisting, projectHash, false);
1298
+ await linkPlatformDirectory(tx, localPath, linkNewCommitPath, linkNewExisting);
1299
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkNewExisting, false);
1096
1300
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 创建链接 [${linkNewExisting.join(', ')}]`);
1097
1301
  }
1098
1302
  break;
1099
1303
  }
1100
1304
  }
1101
- // 无论当前是跳过、补平台还是重建链接,都要把本地残留的平台目录清理到最终平台集合
1102
- const desiredLocalPlatforms = await resolveStoredPlatforms(dependency.libName, dependency.commit, finalLinkPlatforms);
1103
- await linker.cleanupLocalExtraPlatforms(localPath, desiredLocalPlatforms);
1305
+ // 本地链接集合由本次请求及 sparse 映射决定,不能使用 Store 中全部可用平台。
1306
+ const { actualExisting: desiredLocalPlatforms } = await resolveRequestedStoreState(dependency, finalLinkPlatforms, configVars);
1307
+ debug(`[link-registry] 本地平台清理准备: lib=${dependency.libName}, commit=${dependency.commit}, ` +
1308
+ `requested=${finalLinkPlatforms.join(',')}, desired=${desiredLocalPlatforms.join(',')}, local=${localPath}`);
1309
+ await linker.cleanupLocalExtraPlatforms(localPath, desiredLocalPlatforms, (entryPath) => tx.preparePathReplacement(entryPath, 'extra-platform-cleanup'));
1104
1310
  await tx.save();
1105
1311
  }
1106
1312
  // 8. 并行处理 MISSING 依赖
@@ -1116,8 +1322,9 @@ async function linkScope(params) {
1116
1322
  const isTTY = process.stdout.isTTY ?? false;
1117
1323
  const multiBarManager = isTTY ? new MultiBarManager() : null;
1118
1324
  const downloadLimit = pLimit(concurrency);
1119
- const downloadTasks = toDownload.map((item) => downloadLimit(async () => {
1325
+ const results = await runParallelDownloadBatch(() => toDownload.map((item) => downloadLimit(async () => {
1120
1326
  const { dependency, localPath, repairRequiredPlatforms = [] } = item;
1327
+ const implicitOwner = implicitReferenceOwner(dependency, projectRoot, localPath, scope);
1121
1328
  const repairRequiredSet = new Set(repairRequiredPlatforms);
1122
1329
  const pLog = {
1123
1330
  info: (msg) => multiBarManager ? multiBarManager.log(`[info] ${msg}`) : info(msg),
@@ -1126,6 +1333,7 @@ async function linkScope(params) {
1126
1333
  warn: (msg) => multiBarManager ? multiBarManager.log(`[warn] ${msg}`) : warn(msg),
1127
1334
  error: (msg) => multiBarManager ? multiBarManager.log(`[err] ${msg}`) : error(msg),
1128
1335
  };
1336
+ let downloadCompleted = false;
1129
1337
  try {
1130
1338
  const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
1131
1339
  const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
@@ -1159,10 +1367,10 @@ async function linkScope(params) {
1159
1367
  };
1160
1368
  }
1161
1369
  pLog.info(`${dependency.libName} 所有平台已存在,直接链接...`);
1162
- await tx.recordOp('link', localPath, storeCommitPath);
1163
- await linker.linkLib(localPath, storeCommitPath, existing);
1164
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, existing, projectHash, false);
1370
+ await linkPlatformDirectory(tx, localPath, storeCommitPath, existing);
1371
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, existing, false);
1165
1372
  pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 链接完成 [${existing.join(', ')}]`);
1373
+ linkedMissingDependencyKeys.add(dependencyIdentityKey(dependency, localPath));
1166
1374
  return { success: true, name: dependency.libName, downloadedPlatforms: existing, skippedPlatforms: [] };
1167
1375
  }
1168
1376
  const dlLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
@@ -1209,6 +1417,7 @@ async function linkScope(params) {
1209
1417
  finally {
1210
1418
  await downloadMonitor.stop();
1211
1419
  }
1420
+ downloadCompleted = true;
1212
1421
  if (downloadResult.cleanedPlatforms.length > 0) {
1213
1422
  pLog.hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
1214
1423
  }
@@ -1220,13 +1429,18 @@ async function linkScope(params) {
1220
1429
  platforms: [GENERAL_PLATFORM],
1221
1430
  isGeneral: true,
1222
1431
  });
1223
- await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
1224
- await store.absorbGeneral(downloadResult.libDir, dependency.libName, dependency.commit);
1432
+ await new StoreImportService(registry, storePath).importGeneral({
1433
+ libDir: downloadResult.libDir,
1434
+ libName: dependency.libName,
1435
+ commit: dependency.commit,
1436
+ branch: dependency.branch,
1437
+ url: dependency.url,
1438
+ sourceKind: 'download',
1439
+ });
1225
1440
  const sharedPath = path.join(storeCommitPath, '_shared');
1226
- await tx.recordOp('link', localPath, sharedPath);
1227
- await linker.linkGeneral(localPath, sharedPath);
1228
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
1229
- generalLibs.add(dependency.libName);
1441
+ await linkGeneralDirectory(tx, localPath, sharedPath);
1442
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], true);
1443
+ generalDependencyKeys.add(dependencyIdentityKey(dependency, localPath));
1230
1444
  if (isRepairingGeneral) {
1231
1445
  pLog.warn(`${dependency.libName} ${GENERAL_PLATFORM} 本地缓存不完整,已重新下载`);
1232
1446
  }
@@ -1236,6 +1450,7 @@ async function linkScope(params) {
1236
1450
  else {
1237
1451
  pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,下载完成`);
1238
1452
  }
1453
+ linkedMissingDependencyKeys.add(dependencyIdentityKey(dependency, localPath));
1239
1454
  return { success: true, name: dependency.libName, downloadedPlatforms: [GENERAL_PLATFORM], skippedPlatforms: [], isGeneral: true };
1240
1455
  }
1241
1456
  const filteredDownloaded = assessment.downloadedRequested;
@@ -1249,9 +1464,16 @@ async function linkScope(params) {
1249
1464
  pLog.warn(`${dependency.libName} 平台 [${newUnavailable.join(', ')}] 远程不存在,已记录`);
1250
1465
  }
1251
1466
  if (filteredDownloaded.length > 0) {
1252
- await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
1253
- const downloadAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
1254
- await registerNestedLibraries(downloadAbsorbResult.nestedLibraries, projectHash);
1467
+ const downloadAbsorbResult = await new StoreImportService(registry, storePath).importLibrary({
1468
+ libDir: downloadResult.libDir,
1469
+ platforms: filteredDownloaded,
1470
+ libName: dependency.libName,
1471
+ commit: dependency.commit,
1472
+ branch: dependency.branch,
1473
+ url: dependency.url,
1474
+ sourceKind: 'download',
1475
+ });
1476
+ await registerNestedLibraries(downloadAbsorbResult.nestedLibraries, implicitStoreReferences, implicitOwner);
1255
1477
  }
1256
1478
  const linkPlatforms = [...existing, ...filteredDownloaded];
1257
1479
  if (linkPlatforms.length === 0) {
@@ -1266,9 +1488,8 @@ async function linkScope(params) {
1266
1488
  skippedPlatforms: requestedPlatforms,
1267
1489
  };
1268
1490
  }
1269
- await tx.recordOp('link', localPath, storeCommitPath);
1270
- await linker.linkLib(localPath, storeCommitPath, linkPlatforms);
1271
- await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkPlatforms, projectHash, false);
1491
+ await linkPlatformDirectory(tx, localPath, storeCommitPath, linkPlatforms);
1492
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkPlatforms, false);
1272
1493
  const notLinkedPlatforms = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, linkPlatforms, configVars).missingRequested;
1273
1494
  const repaired = filteredDownloaded.filter((platform) => repairRequiredSet.has(platform));
1274
1495
  if (repaired.length > 0) {
@@ -1277,13 +1498,27 @@ async function linkScope(params) {
1277
1498
  else {
1278
1499
  pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 下载完成 [${linkPlatforms.join(', ')}]`);
1279
1500
  }
1501
+ linkedMissingDependencyKeys.add(dependencyIdentityKey(dependency, localPath));
1280
1502
  return { success: true, name: dependency.libName, downloadedPlatforms: linkPlatforms, skippedPlatforms: notLinkedPlatforms };
1281
1503
  }
1282
1504
  finally {
1283
- await fs.rm(downloadResult.tempDir, { recursive: true, force: true }).catch(() => { });
1505
+ await cleanupDownloadTempDir(downloadResult.tempDir, {
1506
+ libName: dependency.libName,
1507
+ commit: dependency.commit,
1508
+ stage: 'parallel-download-import',
1509
+ });
1284
1510
  }
1285
1511
  }
1286
1512
  catch (err) {
1513
+ if (downloadCompleted) {
1514
+ pLog.error(`${dependency.libName} Store 导入或项目处理失败: ${err.message}`);
1515
+ return {
1516
+ success: false,
1517
+ name: dependency.libName,
1518
+ error: err.message,
1519
+ fatal: true,
1520
+ };
1521
+ }
1287
1522
  const repairTargets = repairRequiredPlatforms.filter((platform) => platforms.includes(platform) || platform === GENERAL_PLATFORM);
1288
1523
  const repairFailure = repairTargets.length > 0;
1289
1524
  if (repairTargets.length > 0) {
@@ -1301,9 +1536,15 @@ async function linkScope(params) {
1301
1536
  repairFailure,
1302
1537
  };
1303
1538
  }
1304
- }));
1305
- const results = await Promise.all(downloadTasks);
1306
- multiBarManager?.stop();
1539
+ })), multiBarManager);
1540
+ const fatalResults = results.filter((result) => 'fatal' in result && result.fatal);
1541
+ if (fatalResults.length > 0) {
1542
+ const details = fatalResults
1543
+ .map((result) => `${result.name}: ${'error' in result ? result.error : '未知错误'}`)
1544
+ .join(' | ');
1545
+ debug(`[link] 并行任务全部结束后中止项目事务: failures=${details}`);
1546
+ throw new Error(`Store 导入或项目处理失败: ${details}`);
1547
+ }
1307
1548
  const succeeded = results.filter((r) => r.success);
1308
1549
  const failed = results.filter((r) => !r.success && !('skipped' in r && r.skipped));
1309
1550
  blank();
@@ -1355,35 +1596,40 @@ async function linkScope(params) {
1355
1596
  const thirdPartyDir = path.dirname(configPath);
1356
1597
  for (const action of actions) {
1357
1598
  await processAction(action, nestedContext, configPath, thirdPartyDir, {
1358
- tx, registry, projectHash, projectRoot, storePath, dryRun, download, yes,
1599
+ tx, registry, projectRoot, storePath, dryRun, download, yes,
1359
1600
  gitLightweightDownload, skipActions: new Set(params.skipActions ?? []),
1360
- generalLibs, downloadedLibs, nestedLinkedDeps,
1601
+ scope, generalDependencyKeys, implicitStoreReferences, downloadedLibs, nestedLinkedDeps,
1361
1602
  });
1362
1603
  }
1363
1604
  }
1364
1605
  // 10. 同步 cache 文件
1365
1606
  for (const cacheConfigPath of cacheConfigPaths) {
1366
- await syncCacheFile(cacheConfigPath, storePath, dryRun);
1607
+ await syncCodepacCache(cacheConfigPath, {
1608
+ projectRoot,
1609
+ storePath,
1610
+ transaction: tx,
1611
+ reason: `scope-cache:${scopeName}:${path.basename(cacheConfigPath)}`,
1612
+ dryRun,
1613
+ });
1367
1614
  }
1368
1615
  // 11. 构建返回结果
1369
1616
  const linkedDeps = [];
1370
1617
  for (const item of classified) {
1371
- if (item.status === DependencyStatus.MISSING && !downloadedLibs.includes(item.dependency.libName)) {
1618
+ if (item.status === DependencyStatus.MISSING &&
1619
+ !linkedMissingDependencyKeys.has(dependencyIdentityKey(item.dependency, item.localPath))) {
1372
1620
  continue;
1373
1621
  }
1374
- const actualPlatforms = generalLibs.has(item.dependency.libName)
1375
- ? [GENERAL_PLATFORM]
1376
- : await resolveStoredPlatforms(item.dependency.libName, item.dependency.commit, platforms);
1377
- linkedDeps.push({
1378
- libName: item.dependency.libName,
1379
- commit: item.dependency.commit,
1380
- platform: actualPlatforms[0] ?? platforms[0],
1381
- linkedPath: path.relative(projectRoot, item.localPath),
1382
- scope,
1383
- });
1622
+ const storeCommitPath = path.join(storePath, item.dependency.libName, item.dependency.commit);
1623
+ await recordLinkedDependency(linkedDeps, item.dependency, item.localPath, storeCommitPath, projectRoot, generalDependencyKeys.has(dependencyIdentityKey(item.dependency, item.localPath)), scope);
1384
1624
  }
1385
1625
  return {
1386
- linkedDeps, nestedLinkedDeps, generalLibs, downloadedLibs, savedBytes, finalLinkPlatforms, stats,
1626
+ linkedDeps,
1627
+ nestedLinkedDeps,
1628
+ implicitStoreReferences: serializeImplicitStoreReferences(implicitStoreReferences),
1629
+ downloadedLibs,
1630
+ savedBytes,
1631
+ finalLinkPlatforms,
1632
+ stats,
1387
1633
  };
1388
1634
  }
1389
1635
  /**
@@ -1402,6 +1648,39 @@ export async function linkProject(projectPath, options) {
1402
1648
  setProxyConfig(cfg.proxy);
1403
1649
  const concurrency = cfg?.concurrency ?? 5;
1404
1650
  const gitLightweightDownload = cfg?.gitLightweightDownload ?? true;
1651
+ // 必须先恢复 Registry 文件和项目目录,再加载内存登记并据此选择平台与配置。
1652
+ if (!options.dryRun) {
1653
+ let recoveryInProgress = false;
1654
+ try {
1655
+ await Transaction.recoverPendingTransactions({
1656
+ onRecoveryStart: (transaction) => {
1657
+ recoveryInProgress = true;
1658
+ warn(`发现未完成的事务 (${transaction.id.slice(0, 8)})`);
1659
+ info('正在尝试回滚...');
1660
+ },
1661
+ onRecoveryComplete: () => {
1662
+ recoveryInProgress = false;
1663
+ success('事务回滚完成');
1664
+ },
1665
+ onRecoveryFailure: (transaction, rollbackErrors) => {
1666
+ for (const rollbackError of rollbackErrors) {
1667
+ error(`[transaction] 历史事务恢复失败: transaction=${transaction.id}, detail=${rollbackError}`);
1668
+ }
1669
+ },
1670
+ });
1671
+ }
1672
+ catch (err) {
1673
+ if (recoveryInProgress) {
1674
+ error(`回滚失败: ${err.message}`);
1675
+ hint('请手动检查 Store 和项目目录状态');
1676
+ }
1677
+ throw err;
1678
+ }
1679
+ }
1680
+ else {
1681
+ const pendingTransactions = await Transaction.getPendingTransactions();
1682
+ debug(`[link] dry-run 跳过事务恢复与事务日志创建: pending=${pendingTransactions.length}`);
1683
+ }
1405
1684
  const registry = getRegistry();
1406
1685
  await registry.load({ migrate: !options.dryRun });
1407
1686
  const existingProject = registry.getProjectByPath(normalizedPath);
@@ -1436,8 +1715,13 @@ export async function linkProject(projectPath, options) {
1436
1715
  process.exit(EXIT_CODES.NOINPUT);
1437
1716
  }
1438
1717
  }
1439
- catch {
1440
- error(`路径不存在: ${absolutePath}`);
1718
+ catch (err) {
1719
+ if (isPathMissingError(err))
1720
+ error(`路径不存在: ${absolutePath}`);
1721
+ else {
1722
+ error(`项目路径无法访问: path=${absolutePath}, code=${err.code ?? 'unknown'}, ` +
1723
+ `message=${err.message}`);
1724
+ }
1441
1725
  process.exit(EXIT_CODES.NOINPUT);
1442
1726
  }
1443
1727
  // 发现可选配置文件,扫描位置跟随实际主配置所在目录。
@@ -1536,26 +1820,6 @@ export async function linkProject(projectPath, options) {
1536
1820
  }
1537
1821
  }
1538
1822
  const finalPath = normalizedRoot;
1539
- // 预演模式只读取现状,不恢复历史事务,也不创建新的持久事务日志。
1540
- if (!options.dryRun) {
1541
- const pendingTx = await Transaction.findPending();
1542
- if (pendingTx) {
1543
- warn(`发现未完成的事务 (${pendingTx.id.slice(0, 8)})`);
1544
- info('正在尝试回滚...');
1545
- try {
1546
- await pendingTx.rollback();
1547
- success('事务回滚完成');
1548
- }
1549
- catch (err) {
1550
- error(`回滚失败: ${err.message}`);
1551
- hint('请手动检查 Store 和项目目录状态');
1552
- }
1553
- }
1554
- }
1555
- else {
1556
- const pendingTransactions = await Transaction.getPendingTransactions();
1557
- debug(`[link] dry-run 跳过事务恢复与事务日志创建: pending=${pendingTransactions.length}`);
1558
- }
1559
1823
  const storePath = await store.getStorePath();
1560
1824
  if (!options.dryRun) {
1561
1825
  const recovered = await new SpaceService(registry, storePath)
@@ -1564,9 +1828,12 @@ export async function linkProject(projectPath, options) {
1564
1828
  debug(`[link] 清理日志恢复完成: recovered=${recovered}, store=${storePath}`);
1565
1829
  }
1566
1830
  const projectHash = registry.hashPath(finalPath);
1567
- const tx = new Transaction(`link:${finalPath}`);
1831
+ const tx = new Transaction(finalPath, storePath);
1568
1832
  if (!options.dryRun)
1569
1833
  await tx.begin();
1834
+ let transactionCommitted = false;
1835
+ let projectRegistrySnapshot;
1836
+ let projectRegistryStage = 'linking';
1570
1837
  // === 阶段 4: 依次执行 linkScope ===
1571
1838
  try {
1572
1839
  // 主项目
@@ -1576,7 +1843,7 @@ export async function linkProject(projectPath, options) {
1576
1843
  platforms,
1577
1844
  finalLinkPlatforms: platforms,
1578
1845
  scanExtraPlatforms: true,
1579
- registry, tx, projectHash,
1846
+ registry, tx,
1580
1847
  projectRoot: finalPath,
1581
1848
  storePath,
1582
1849
  download: options.download,
@@ -1601,7 +1868,7 @@ export async function linkProject(projectPath, options) {
1601
1868
  platforms,
1602
1869
  finalLinkPlatforms,
1603
1870
  scanExtraPlatforms: false,
1604
- registry, tx, projectHash,
1871
+ registry, tx,
1605
1872
  projectRoot: finalPath,
1606
1873
  storePath,
1607
1874
  download: options.download,
@@ -1628,7 +1895,7 @@ export async function linkProject(projectPath, options) {
1628
1895
  platforms,
1629
1896
  finalLinkPlatforms,
1630
1897
  scanExtraPlatforms: false,
1631
- registry, tx, projectHash,
1898
+ registry, tx,
1632
1899
  projectRoot: finalPath,
1633
1900
  storePath,
1634
1901
  download: options.download,
@@ -1643,14 +1910,23 @@ export async function linkProject(projectPath, options) {
1643
1910
  subResults.push(subResult);
1644
1911
  }
1645
1912
  // === 阶段 5: 合并结果、注册项目 ===
1913
+ projectRegistrySnapshot = registry.snapshotState();
1914
+ projectRegistryStage = 'assembling-project-references';
1646
1915
  // 合并所有依赖
1647
- const allLinkedDeps = [
1916
+ const currentLinkedDeps = deduplicateDependencyRefs([
1648
1917
  ...mainResult.linkedDeps,
1649
1918
  ...mainResult.nestedLinkedDeps,
1650
1919
  ...subResults.flatMap(r => [...r.linkedDeps, ...r.nestedLinkedDeps]),
1651
- ];
1652
- // 获取旧引用
1653
- const oldStoreKeys = registry.getProjectStoreKeys(projectHash);
1920
+ ]);
1921
+ const retainedLinkedDeps = await retainExistingLinkedDependencies(existingProject, currentLinkedDeps, finalPath, storePath);
1922
+ const allLinkedDeps = deduplicateDependencyRefs([
1923
+ ...currentLinkedDeps,
1924
+ ...retainedLinkedDeps,
1925
+ ]);
1926
+ const implicitStoreReferences = mergeProjectImplicitStoreReferences(existingProject, projectHash, allLinkedDeps, [
1927
+ ...mainResult.implicitStoreReferences,
1928
+ ...subResults.flatMap((result) => result.implicitStoreReferences),
1929
+ ], registry);
1654
1930
  // 更新项目信息
1655
1931
  const relConfigPath = getRelativeConfigPath(finalPath, mainConfigPath);
1656
1932
  registry.addProject({
@@ -1664,19 +1940,26 @@ export async function linkProject(projectPath, options) {
1664
1940
  submodules: selectedSubmodules.length > 0
1665
1941
  ? selectedSubmodules.map(s => s.relativePath) : undefined,
1666
1942
  submoduleOptionalConfigs: buildSubmoduleOptionalConfigState(selectedSubmodules),
1943
+ implicitStoreReferences: implicitStoreReferences.length > 0 ? implicitStoreReferences : undefined,
1667
1944
  });
1668
- // 更新 Store 引用关系
1669
- const newStoreKeys = allLinkedDeps.map((d) => registry.getStoreKey(d.libName, d.commit, d.platform));
1670
- for (const key of oldStoreKeys) {
1671
- if (!newStoreKeys.includes(key)) {
1672
- registry.removeStoreReference(key, projectHash);
1673
- }
1674
- }
1675
- for (const key of newStoreKeys) {
1676
- registry.addStoreReference(key, projectHash);
1677
- }
1945
+ // dependency usedBy 使用同一份真实链接结果;隐式嵌套库由吸收结果补充。
1946
+ const newStoreKeys = new Set([
1947
+ ...allLinkedDeps.map((dependency) => registry.getStoreKey(dependency.libName, dependency.commit, dependency.platform)),
1948
+ ...implicitStoreReferences.flatMap((reference) => reference.storeKeys),
1949
+ ]);
1950
+ debug(`[link-registry] 项目引用提交准备: project=${projectHash}, dependencies=${allLinkedDeps.length}, ` +
1951
+ `retainedDependencies=${retainedLinkedDeps.length}, implicit=${implicitStoreReferences.length}, ` +
1952
+ `storeKeys=${[...newStoreKeys].join(',') || 'none'}`);
1953
+ const referenceUpdate = registry.replaceProjectReferences(projectHash, newStoreKeys);
1954
+ debug(`[link-registry] 项目引用提交结果: project=${projectHash}, ` +
1955
+ `added=${referenceUpdate.added.length}, removed=${referenceUpdate.removed.length}, ` +
1956
+ `retained=${referenceUpdate.retained.length}`);
1957
+ await tx.prepareFileReplacement(getRegistryPath(), 'registry-save');
1958
+ projectRegistryStage = 'saving-registry';
1678
1959
  await registry.save();
1960
+ projectRegistryStage = 'committing-project-transaction';
1679
1961
  await tx.commit();
1962
+ transactionCommitted = true;
1680
1963
  // === 阶段 6: 统计报告 ===
1681
1964
  const allSavedBytes = mainResult.savedBytes + subResults.reduce((sum, r) => sum + r.savedBytes, 0);
1682
1965
  blank();
@@ -1767,20 +2050,43 @@ export async function linkProject(projectPath, options) {
1767
2050
  await checkForUpdates();
1768
2051
  }
1769
2052
  catch (err) {
2053
+ if (transactionCommitted) {
2054
+ error(`[link] 链接事务提交后的处理失败: transaction=${tx.id}, state=committed, message=${err.message}`);
2055
+ hint('依赖链接和项目登记已经完成,本次不会回滚;后续运行会继续清理事务产物');
2056
+ process.exit(1);
2057
+ }
1770
2058
  // 链接过程出错,回滚事务
1771
2059
  error(`链接失败: ${err.message}`);
2060
+ if (projectRegistrySnapshot) {
2061
+ error(`[link-registry] 项目登记失败: transaction=${tx.id}, stage=${projectRegistryStage}, ` +
2062
+ `project=${projectHash}, message=${err.message}`);
2063
+ }
1772
2064
  hintStoreRepairCommand(err);
1773
2065
  if (!options.dryRun) {
1774
2066
  warn('正在回滚事务...');
1775
2067
  try {
1776
- await tx.rollback();
1777
- success('事务已回滚');
2068
+ const rollbackErrors = await tx.rollback();
2069
+ if (rollbackErrors.length === 0) {
2070
+ success('事务已回滚');
2071
+ }
2072
+ else {
2073
+ error(`事务回滚未完成: transaction=${tx.id}, failedOperations=${rollbackErrors.length}`);
2074
+ for (const rollbackError of rollbackErrors) {
2075
+ error(`[transaction] 当前事务恢复失败: transaction=${tx.id}, detail=${rollbackError}`);
2076
+ }
2077
+ hint('事务日志已保留,请停止修改项目并重新运行 td link 触发恢复');
2078
+ }
1778
2079
  }
1779
2080
  catch (rollbackErr) {
1780
2081
  error(`回滚失败: ${rollbackErr.message}`);
1781
2082
  hint('请手动检查 Store 和项目目录状态');
1782
2083
  }
1783
2084
  }
2085
+ if (projectRegistrySnapshot) {
2086
+ registry.restoreState(projectRegistrySnapshot);
2087
+ debug(`[link-registry] 项目登记内存恢复完成: transaction=${tx.id}, project=${projectHash}, ` +
2088
+ `stage=${projectRegistryStage}`);
2089
+ }
1784
2090
  process.exit(1);
1785
2091
  }
1786
2092
  }
@@ -1821,8 +2127,12 @@ async function registerSharedStore(libName, commit, branch = '', url = '') {
1821
2127
  return false; // 空目录不注册
1822
2128
  }
1823
2129
  }
1824
- catch {
1825
- return false; // 目录不存在
2130
+ catch (err) {
2131
+ if (isPathMissingError(err))
2132
+ return false;
2133
+ error(`[link-registry] shared 登记读取失败: lib=${libName}, commit=${commit}, path=${sharedPath}, ` +
2134
+ `code=${err.code ?? 'unknown'}, message=${err.message}`);
2135
+ throw err;
1826
2136
  }
1827
2137
  // 计算 _shared 完整性信息并注册
1828
2138
  const integrity = await store.captureIntegrity(libName, commit, SHARED_PLATFORM);
@@ -1850,7 +2160,7 @@ async function registerSharedStore(libName, commit, branch = '', url = '') {
1850
2160
  debug(`注册 _shared: ${libName}:${commit.slice(0, 8)} (${formatSize(integrity.size)})`);
1851
2161
  return true;
1852
2162
  }
1853
- async function ensureStoreInfo(libName, commit, platform, branch, url, projectHash) {
2163
+ async function ensureStoreInfo(libName, commit, platform, branch, url) {
1854
2164
  const registry = getRegistry();
1855
2165
  const storeKey = registry.getStoreKey(libName, commit, platform);
1856
2166
  const existingEntry = registry.getStore(storeKey);
@@ -1876,9 +2186,6 @@ async function ensureStoreInfo(libName, commit, platform, branch, url, projectHa
1876
2186
  lastAccess: new Date().toISOString(),
1877
2187
  });
1878
2188
  }
1879
- if (projectHash) {
1880
- registry.addStoreReference(storeKey, projectHash);
1881
- }
1882
2189
  }
1883
2190
  async function syncLibraryInfo(libName, commit, branch, url, platforms, isGeneral) {
1884
2191
  const registry = getRegistry();
@@ -1926,17 +2233,101 @@ async function resolveStoredPlatforms(libName, commit, fallbackPlatforms) {
1926
2233
  .map(entry => entry.name);
1927
2234
  return storedPlatforms.length > 0 ? storedPlatforms : [...new Set(fallbackPlatforms)];
1928
2235
  }
1929
- catch {
1930
- return [...new Set(fallbackPlatforms)];
2236
+ catch (err) {
2237
+ if (isPathMissingError(err))
2238
+ return [...new Set(fallbackPlatforms)];
2239
+ error(`[link-registry] Store 平台枚举失败: lib=${libName}, commit=${commit}, path=${commitPath}, ` +
2240
+ `code=${err.code ?? 'unknown'}, message=${err.message}`);
2241
+ throw err;
1931
2242
  }
1932
2243
  }
1933
- async function ensureLinkedRegistryState(libName, commit, branch, url, platforms, projectHash, isGeneral) {
2244
+ /**
2245
+ * 项目引用登记以当前项目中的真实链接为准。任何无法证明的目录形态都会终止项目事务,
2246
+ * 避免根据请求平台写入推测的 dependency 和 Store usedBy。
2247
+ */
2248
+ export async function resolveLinkedPlatforms(input) {
2249
+ const { libName, commit, localPath, storeCommitPath } = input;
2250
+ debug(`[link-registry] 真实平台核对开始: lib=${libName}, commit=${commit}, local=${localPath}, store=${storeCommitPath}`);
2251
+ try {
2252
+ const stat = await fs.lstat(localPath);
2253
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
2254
+ throw new Error(`项目库应为包含平台链接的物理目录: isDirectory=${stat.isDirectory()}, isSymbolicLink=${stat.isSymbolicLink()}`);
2255
+ }
2256
+ const entries = await fs.readdir(localPath, { withFileTypes: true });
2257
+ const linkedPlatforms = [];
2258
+ for (const entry of entries) {
2259
+ if (!KNOWN_PLATFORM_VALUES.includes(entry.name))
2260
+ continue;
2261
+ const entryPath = path.join(localPath, entry.name);
2262
+ if (!entry.isSymbolicLink()) {
2263
+ throw new Error(`平台项不是符号链接: platform=${entry.name}, path=${entryPath}`);
2264
+ }
2265
+ const rawTarget = await fs.readlink(entryPath);
2266
+ const target = path.resolve(localPath, rawTarget);
2267
+ const expectedTarget = path.join(storeCommitPath, entry.name);
2268
+ if (!pathsEqual(target, expectedTarget)) {
2269
+ throw new Error(`平台链接目标不属于当前 Store 版本: platform=${entry.name}, path=${entryPath}, target=${target}, expected=${expectedTarget}`);
2270
+ }
2271
+ linkedPlatforms.push(entry.name);
2272
+ debug(`[link-registry] 真实平台链接确认: lib=${libName}, commit=${commit}, platform=${entry.name}, path=${entryPath}, target=${target}`);
2273
+ }
2274
+ if (linkedPlatforms.length === 0) {
2275
+ throw new Error('项目目录中没有指向当前 Store 版本的平台链接');
2276
+ }
2277
+ const resolved = [...new Set(linkedPlatforms)];
2278
+ debug(`[link-registry] 真实平台核对完成: lib=${libName}, commit=${commit}, platforms=${resolved.join(',')}`);
2279
+ return resolved;
2280
+ }
2281
+ catch (err) {
2282
+ const message = `无法确认项目真实平台: lib=${libName}, commit=${commit}, local=${localPath}, ` +
2283
+ `store=${storeCommitPath}, detail=${err.message}`;
2284
+ error(`[link-registry] 真实平台核对失败: ${message}`);
2285
+ throw new Error(message, { cause: err });
2286
+ }
2287
+ }
2288
+ async function resolveLinkedDependencyPlatforms(input) {
2289
+ if (!input.isGeneral)
2290
+ return resolveLinkedPlatforms(input);
2291
+ const expectedTarget = path.join(input.storeCommitPath, SHARED_PLATFORM);
2292
+ debug(`[link-registry] General 链接核对开始: lib=${input.libName}, commit=${input.commit}, ` +
2293
+ `local=${input.localPath}, expected=${expectedTarget}`);
2294
+ const status = await linker.getPathStatus(input.localPath, expectedTarget);
2295
+ const materialized = status === 'directory' &&
2296
+ (await isMaterializedStoreDirectory(input.localPath, expectedTarget));
2297
+ if (status !== 'linked' && !materialized) {
2298
+ const message = `无法确认项目 General 引用: lib=${input.libName}, commit=${input.commit}, ` +
2299
+ `local=${input.localPath}, expected=${expectedTarget}, status=${status}`;
2300
+ error(`[link-registry] General 链接核对失败: ${message}`);
2301
+ throw new Error(message);
2302
+ }
2303
+ debug(`[link-registry] General 链接核对完成: lib=${input.libName}, commit=${input.commit}, ` +
2304
+ `local=${input.localPath}, status=${status}, materialized=${materialized}, platform=${GENERAL_PLATFORM}`);
2305
+ return [GENERAL_PLATFORM];
2306
+ }
2307
+ async function recordLinkedDependency(dependencies, dependency, localPath, storeCommitPath, projectRoot, isGeneral, scope) {
2308
+ const actualPlatforms = await resolveLinkedDependencyPlatforms({
2309
+ libName: dependency.libName,
2310
+ commit: dependency.commit,
2311
+ localPath,
2312
+ storeCommitPath,
2313
+ isGeneral,
2314
+ });
2315
+ dependencies.push(...buildDependencyRefs({
2316
+ libName: dependency.libName,
2317
+ commit: dependency.commit,
2318
+ linkedPath: path.relative(projectRoot, localPath),
2319
+ scope,
2320
+ }, actualPlatforms));
2321
+ debug(`[link-registry] dependency 登记完成: lib=${dependency.libName}, commit=${dependency.commit}, ` +
2322
+ `local=${localPath}, scope=${scope ?? 'main'}, platforms=${actualPlatforms.join(',')}`);
2323
+ }
2324
+ async function ensureLinkedRegistryState(libName, commit, branch, url, platforms, isGeneral) {
1934
2325
  const linkedPlatforms = isGeneral ? [GENERAL_PLATFORM] : [...new Set(platforms)];
1935
2326
  const libraryPlatforms = isGeneral
1936
2327
  ? [GENERAL_PLATFORM]
1937
2328
  : await resolveStoredPlatforms(libName, commit, linkedPlatforms);
1938
2329
  for (const platform of linkedPlatforms) {
1939
- await ensureStoreInfo(libName, commit, platform, branch, url, projectHash);
2330
+ await ensureStoreInfo(libName, commit, platform, branch, url);
1940
2331
  }
1941
2332
  if (!isGeneral) {
1942
2333
  await registerSharedStore(libName, commit, branch, url);
@@ -1947,7 +2338,7 @@ async function ensureLinkedRegistryState(libName, commit, branch, url, platforms
1947
2338
  * 注册嵌套依赖到 Registry
1948
2339
  * absorbLib 递归吸收嵌套依赖时只做文件操作,此函数负责 Registry 注册
1949
2340
  */
1950
- async function registerNestedLibraries(nestedLibraries, projectHash) {
2341
+ async function registerNestedLibraries(nestedLibraries, references, owner) {
1951
2342
  if (nestedLibraries.length === 0)
1952
2343
  return;
1953
2344
  debug(`注册 ${nestedLibraries.length} 个嵌套依赖: ${nestedLibraries.map(n => n.libName).join(', ')}`);
@@ -1972,7 +2363,7 @@ async function registerNestedLibraries(nestedLibraries, projectHash) {
1972
2363
  lastAccess: new Date().toISOString(),
1973
2364
  });
1974
2365
  }
1975
- registry.addStoreReference(storeKey, projectHash);
2366
+ addImplicitStoreReference(references, owner, storeKey);
1976
2367
  // 兼容旧结构:LibraryInfo
1977
2368
  const libKey = registry.getLibraryKey(nested.libName, nested.commit);
1978
2369
  if (!registry.getLibrary(libKey)) {
@@ -2008,7 +2399,7 @@ async function registerNestedLibraries(nestedLibraries, projectHash) {
2008
2399
  lastAccess: new Date().toISOString(),
2009
2400
  });
2010
2401
  }
2011
- registry.addStoreReference(storeKey, projectHash);
2402
+ addImplicitStoreReference(references, owner, storeKey);
2012
2403
  }
2013
2404
  // 注册 _shared 目录(如果存在)
2014
2405
  await registerSharedStore(nested.libName, nested.commit, '', '');
@@ -2030,6 +2421,11 @@ async function registerNestedLibraries(nestedLibraries, projectHash) {
2030
2421
  }
2031
2422
  }
2032
2423
  }
2424
+ debug(`[link-registry] 隐式嵌套库登记完成: libraries=${nestedLibraries.length}, ` +
2425
+ `owner=${implicitReferenceOwnerKey(owner)}, ` +
2426
+ `storeKeys=${serializeImplicitStoreReferences(references)
2427
+ .flatMap((reference) => reference.storeKeys)
2428
+ .join(',') || 'none'}`);
2033
2429
  }
2034
2430
  /**
2035
2431
  * 分类依赖的链接状态
@@ -2079,8 +2475,10 @@ export async function classifyLinkStatus(localPath, storeCommitPath, isGeneral,
2079
2475
  await fs.lstat(localPath);
2080
2476
  return 'replace';
2081
2477
  }
2082
- catch {
2083
- return 'link_new';
2478
+ catch (err) {
2479
+ if (isPathMissingError(err))
2480
+ return 'link_new';
2481
+ throw err;
2084
2482
  }
2085
2483
  }
2086
2484
  /**
@@ -2247,6 +2645,7 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
2247
2645
  else {
2248
2646
  info(`${dependency.libName} 缺少平台 [${toDownload.join(', ')}],尝试下载...`);
2249
2647
  }
2648
+ let downloadCompleted = false;
2250
2649
  try {
2251
2650
  const downloadResult = await codepac.downloadToTemp({
2252
2651
  url: dependency.url,
@@ -2258,6 +2657,7 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
2258
2657
  vars: options.vars,
2259
2658
  useGitLightweightDownload: options.useGitLightweightDownload,
2260
2659
  });
2660
+ downloadCompleted = true;
2261
2661
  // 提示清理的平台(如果有)
2262
2662
  if (downloadResult.cleanedPlatforms.length > 0) {
2263
2663
  hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
@@ -2286,9 +2686,15 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
2286
2686
  // 7. 吸收下载的内容到 Store(不做链接,由调用者负责)
2287
2687
  if (downloaded.length > 0) {
2288
2688
  const storePath = await store.getStorePath();
2289
- const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
2290
- await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
2291
- const supplementAbsorbResult = await store.absorbLib(downloadResult.libDir, downloaded, dependency.libName, dependency.commit);
2689
+ const supplementAbsorbResult = await new StoreImportService(registry, storePath).importLibrary({
2690
+ libDir: downloadResult.libDir,
2691
+ platforms: downloaded,
2692
+ libName: dependency.libName,
2693
+ commit: dependency.commit,
2694
+ branch: dependency.branch,
2695
+ url: dependency.url,
2696
+ sourceKind: 'download',
2697
+ });
2292
2698
  // 收集嵌套依赖(由调用者负责注册)
2293
2699
  result.nestedLibraries.push(...supplementAbsorbResult.nestedLibraries);
2294
2700
  // 8. 更新 Registry StoreEntry
@@ -2317,11 +2723,17 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
2317
2723
  }
2318
2724
  }
2319
2725
  finally {
2320
- // 清理临时目录
2321
- await fs.rm(downloadResult.tempDir, { recursive: true, force: true }).catch(() => { });
2726
+ await cleanupDownloadTempDir(downloadResult.tempDir, {
2727
+ libName: dependency.libName,
2728
+ commit: dependency.commit,
2729
+ stage: 'supplement-import',
2730
+ });
2322
2731
  }
2323
2732
  }
2324
2733
  catch (err) {
2734
+ if (downloadCompleted) {
2735
+ throw err;
2736
+ }
2325
2737
  const repairTargets = toDownload.filter((platform) => repairRequiredPlatforms.has(platform));
2326
2738
  if (repairTargets.length > 0) {
2327
2739
  error(`${dependency.libName} ${repairTargets.join(', ')} 本地缓存不完整,自动重新下载失败`);
@@ -2381,74 +2793,6 @@ function getStatusKey(status) {
2381
2793
  };
2382
2794
  return map[status];
2383
2795
  }
2384
- /**
2385
- * 同步配置文件到 cache 目录
2386
- * 使 cmake 的 checkValid.js 检测能够通过
2387
- */
2388
- async function syncCacheFile(configPath, storePath, dryRun = false) {
2389
- const configDir = path.dirname(configPath);
2390
- const cacheDir = path.join(configDir, '.cache');
2391
- const configFileName = path.basename(configPath);
2392
- const cachePath = path.join(cacheDir, configFileName);
2393
- const tempCachePath = path.join(cacheDir, `.${configFileName}.tanmi-dock-${process.pid}-${crypto.randomBytes(4).toString('hex')}.tmp`);
2394
- let tempCacheCreated = false;
2395
- debug(`[link-cache] 同步检查: config=${configPath}, cache=${cachePath}, store=${storePath}, dryRun=${dryRun}`);
2396
- if (dryRun) {
2397
- debug(`[link-cache] 预演跳过写入: config=${configPath}, cache=${cachePath}`);
2398
- return;
2399
- }
2400
- try {
2401
- await assertPathOutsideStore(cacheDir, storePath, 'sync-action-cache-directory');
2402
- // 确保 .cache 目录存在
2403
- await fs.mkdir(cacheDir, { recursive: true });
2404
- // 文件级符号链接必须先删除链接本身,避免 copyFile 跟随链接覆盖 Store。
2405
- try {
2406
- const cacheStat = await fs.lstat(cachePath);
2407
- if (cacheStat.isDirectory()) {
2408
- throw new Error(`cache 目标是目录: ${cachePath}`);
2409
- }
2410
- if (cacheStat.isSymbolicLink()) {
2411
- const previousTarget = await fs.readlink(cachePath).catch(() => '(unreadable)');
2412
- await fs.unlink(cachePath);
2413
- debug(`[link-cache] 已移除旧 cache 符号链接: cache=${cachePath}, previousTarget=${previousTarget}`);
2414
- }
2415
- }
2416
- catch (err) {
2417
- if (err.code !== 'ENOENT')
2418
- throw err;
2419
- }
2420
- await assertPathOutsideStore(cachePath, storePath, 'sync-action-cache-file');
2421
- const tempHandle = await fs.open(tempCachePath, 'wx');
2422
- await tempHandle.close();
2423
- tempCacheCreated = true;
2424
- await fs.copyFile(configPath, tempCachePath);
2425
- try {
2426
- await fs.rename(tempCachePath, cachePath);
2427
- }
2428
- catch (err) {
2429
- const code = err.code;
2430
- if (code !== 'EEXIST' && code !== 'EPERM')
2431
- throw err;
2432
- await fs.rm(cachePath, { force: true });
2433
- await fs.rename(tempCachePath, cachePath);
2434
- }
2435
- if (process.env.VERBOSE) {
2436
- info(`已同步 cache: ${configFileName} -> ${path.relative(configDir, cachePath)}`);
2437
- }
2438
- debug(`[link-cache] 同步完成: config=${configPath}, cache=${cachePath}, state=written`);
2439
- }
2440
- catch (err) {
2441
- // cache 同步失败不应阻塞主流程,仅警告
2442
- warn(`cache 同步失败: ${configFileName} -> ${path.relative(configDir, cachePath)}: ${err.message}`);
2443
- }
2444
- finally {
2445
- if (tempCacheCreated) {
2446
- await fs.rm(tempCachePath, { force: true }).catch((err) => {
2447
- debug(`[link-cache] 临时文件清理失败: temp=${tempCachePath}, message=${err.message}`);
2448
- });
2449
- }
2450
- }
2451
- }
2452
2796
  /**
2453
2797
  * 处理单个 action(嵌套依赖)
2454
2798
  * @param action action 配置
@@ -2491,10 +2835,16 @@ async function processAction(action, context, parentConfigPath, parentTargetDir,
2491
2835
  try {
2492
2836
  await fs.access(plan.nestedConfigPath);
2493
2837
  }
2494
- catch {
2495
- warn(`${indent} 跳过嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=配置不存在, config=${plan.nestedConfigPath}`);
2496
- hint(`${indent} 请确保 ${plan.configDir} 库已被下载`);
2497
- return;
2838
+ catch (err) {
2839
+ if (isPathMissingError(err)) {
2840
+ warn(`${indent} 跳过嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=配置不存在, config=${plan.nestedConfigPath}`);
2841
+ hint(`${indent} 请确保 ${plan.configDir} 库已被下载`);
2842
+ return;
2843
+ }
2844
+ error(`${indent}嵌套 action 配置访问失败: name=${plan.actionName ?? '(未命名)'}, ` +
2845
+ `config=${plan.nestedConfigPath}, code=${err.code ?? 'unknown'}, ` +
2846
+ `message=${err.message}`);
2847
+ throw err;
2498
2848
  }
2499
2849
  // 4. 提取指定库的依赖
2500
2850
  let nestedResult;
@@ -2539,7 +2889,13 @@ async function processAction(action, context, parentConfigPath, parentTargetDir,
2539
2889
  indent,
2540
2890
  });
2541
2891
  // 同步嵌套配置文件的 cache(兼容 checkValid.js 检测)
2542
- await syncCacheFile(plan.nestedConfigPath, options.storePath, options.dryRun);
2892
+ const cacheSync = await syncCodepacCache(plan.nestedConfigPath, {
2893
+ projectRoot: options.projectRoot,
2894
+ storePath: options.storePath,
2895
+ transaction: options.tx,
2896
+ reason: `action-cache:${plan.actionName ?? 'unnamed'}:depth-${context.depth}`,
2897
+ dryRun: options.dryRun,
2898
+ });
2543
2899
  // 7. 递归处理嵌套 actions(如果没有 disable_action)
2544
2900
  if (!plan.parsed.disableAction && nestedActions.length > 0) {
2545
2901
  const nestedContext = {
@@ -2557,7 +2913,11 @@ async function processAction(action, context, parentConfigPath, parentTargetDir,
2557
2913
  info(`${indent} 跳过后续嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=disable_action`);
2558
2914
  }
2559
2915
  const materializedPaths = [
2560
- ...new Set([...configPreparation.materializedPaths, ...targetPreparation.materializedPaths]),
2916
+ ...new Set([
2917
+ ...configPreparation.materializedPaths,
2918
+ ...targetPreparation.materializedPaths,
2919
+ ...cacheSync.directoryPreparation.materializedPaths,
2920
+ ]),
2561
2921
  ];
2562
2922
  debug(`${indent}CodePac action 完成: name=${plan.actionName ?? '(未命名)'}, dependencies=${dependencies.length}, nestedActions=${nestedActions.length}, materialized=${materializedPaths.join(',') || 'none'}, output=${plan.nestedTargetDir}`);
2563
2923
  }
@@ -2585,8 +2945,14 @@ async function resolveLocalAndAbsorb(localPath, libName, commit, options) {
2585
2945
  case 'match': {
2586
2946
  info(`${indent} ${libName} - 本地 commit 匹配,吸收到 Store`);
2587
2947
  if (!dryRun) {
2588
- await options.onMatch?.();
2589
- const absorbResult = await store.absorbLocalLib(localPath, libName, commit);
2948
+ const absorbResult = await options.storeImporter.importLocalLibrary({
2949
+ libDir: localPath,
2950
+ libName,
2951
+ commit,
2952
+ branch: options.branch,
2953
+ url: options.url,
2954
+ sourceKind: 'local',
2955
+ });
2590
2956
  return { action: 'absorbed', isGeneral: absorbResult.isGeneral, absorbResult };
2591
2957
  }
2592
2958
  return { action: 'absorbed' };
@@ -2595,6 +2961,7 @@ async function resolveLocalAndAbsorb(localPath, libName, commit, options) {
2595
2961
  warn(`${indent} ${libName}: 本地 commit (${verifyResult.actualCommit?.slice(0, 7)}) 与预期 (${commit.slice(0, 7)}) 不匹配,将重新下载`);
2596
2962
  if (!dryRun) {
2597
2963
  await options.onMismatch?.(verifyResult.actualCommit);
2964
+ await options.onDelete?.();
2598
2965
  await fs.rm(localPath, { recursive: true, force: true });
2599
2966
  }
2600
2967
  return { action: 'deleted' };
@@ -2603,8 +2970,14 @@ async function resolveLocalAndAbsorb(localPath, libName, commit, options) {
2603
2970
  if (unverifiedStrategy === 'absorb') {
2604
2971
  info(`${indent} ${libName}: 本地无 .git 目录,按配置直接吸收`);
2605
2972
  if (!dryRun) {
2606
- await options.onNoGit?.();
2607
- const absorbResult = await store.absorbLocalLib(localPath, libName, commit);
2973
+ const absorbResult = await options.storeImporter.importLocalLibrary({
2974
+ libDir: localPath,
2975
+ libName,
2976
+ commit,
2977
+ branch: options.branch,
2978
+ url: options.url,
2979
+ sourceKind: 'local',
2980
+ });
2608
2981
  return { action: 'absorbed', isGeneral: absorbResult.isGeneral, absorbResult };
2609
2982
  }
2610
2983
  return { action: 'absorbed' };
@@ -2612,6 +2985,7 @@ async function resolveLocalAndAbsorb(localPath, libName, commit, options) {
2612
2985
  else {
2613
2986
  warn(`${indent} ${libName}: 本地无 .git 目录,无法验证 commit,将重新下载`);
2614
2987
  if (!dryRun) {
2988
+ await options.onDelete?.();
2615
2989
  await fs.rm(localPath, { recursive: true, force: true });
2616
2990
  }
2617
2991
  return { action: 'deleted' };
@@ -2624,7 +2998,7 @@ async function resolveLocalAndAbsorb(localPath, libName, commit, options) {
2624
2998
  */
2625
2999
  async function linkNestedDependencies(dependencies, params) {
2626
3000
  const { targetDir: nestedTargetDir, context, options, indent } = params;
2627
- const { tx, registry, projectHash, projectRoot, dryRun, download, generalLibs, downloadedLibs, nestedLinkedDeps } = options;
3001
+ const { tx, registry, projectRoot, dryRun, download, generalDependencyKeys, implicitStoreReferences, downloadedLibs, nestedLinkedDeps, } = options;
2628
3002
  const { platforms, vars } = context;
2629
3003
  if (!dryRun) {
2630
3004
  await assertPathOutsideStore(nestedTargetDir, options.storePath, 'link-nested-dependencies');
@@ -2633,6 +3007,7 @@ async function linkNestedDependencies(dependencies, params) {
2633
3007
  const localPath = path.join(nestedTargetDir, dep.libName);
2634
3008
  const storePath = await store.getStorePath();
2635
3009
  const storeCommitPath = path.join(storePath, dep.libName, dep.commit);
3010
+ const implicitOwner = implicitReferenceOwner(dep, projectRoot, localPath, options.scope);
2636
3011
  // 检查本地状态
2637
3012
  let localExists = false;
2638
3013
  let localIsSymlink = false;
@@ -2641,8 +3016,12 @@ async function linkNestedDependencies(dependencies, params) {
2641
3016
  localExists = true;
2642
3017
  localIsSymlink = stat.isSymbolicLink();
2643
3018
  }
2644
- catch {
2645
- // 不存在
3019
+ catch (err) {
3020
+ if (!isPathMissingError(err)) {
3021
+ error(`[link-action] 本地依赖状态读取失败: lib=${dep.libName}, commit=${dep.commit}, ` +
3022
+ `path=${localPath}, code=${err.code ?? 'unknown'}, message=${err.message}`);
3023
+ throw err;
3024
+ }
2646
3025
  }
2647
3026
  // 检查 Store 状态和历史记录
2648
3027
  const unavailablePlatforms = getBlockedRequestedPlatforms(registry, dep, platforms, vars);
@@ -2698,8 +3077,10 @@ async function linkNestedDependencies(dependencies, params) {
2698
3077
  unverifiedStrategy: strategy,
2699
3078
  dryRun,
2700
3079
  indent,
2701
- onMatch: () => tx.recordOp('absorb', storeCommitPath, localPath),
2702
- onNoGit: () => tx.recordOp('absorb', storeCommitPath, localPath),
3080
+ storeImporter: new StoreImportService(registry, storePath),
3081
+ branch: dep.branch,
3082
+ url: dep.url,
3083
+ onDelete: () => tx.preparePathReplacement(localPath, storeCommitPath),
2703
3084
  });
2704
3085
  if (resolveResult.action === 'absorbed' && !dryRun) {
2705
3086
  if (resolveResult.isGeneral) {
@@ -2717,7 +3098,7 @@ async function linkNestedDependencies(dependencies, params) {
2717
3098
  debug(`${indent} ${dep.libName} - 本地吸收后平台回填: requested=${availablePlatforms.join(', ') || '无'}, absorbed=${absorbedPlatforms.join(', ') || '无'}, linkable=${existingPlatforms.join(', ') || '无'}`);
2718
3099
  }
2719
3100
  if (resolveResult.absorbResult?.nestedLibraries.length) {
2720
- await registerNestedLibraries(resolveResult.absorbResult.nestedLibraries, projectHash);
3101
+ await registerNestedLibraries(resolveResult.absorbResult.nestedLibraries, implicitStoreReferences, implicitOwner);
2721
3102
  }
2722
3103
  storeHas = isGeneral || existingPlatforms.length > 0;
2723
3104
  // 吸收为 General 后,检查是否实际需要平台内容
@@ -2725,8 +3106,9 @@ async function linkNestedDependencies(dependencies, params) {
2725
3106
  if (resolveResult.isGeneral && download) {
2726
3107
  const hasPlatformSparse = dep.sparse && !isSparseOnlyCommon(dep.sparse);
2727
3108
  if (hasPlatformSparse && availablePlatforms.length > 0) {
3109
+ let platformDownload;
2728
3110
  try {
2729
- const downloadResult = await codepac.downloadToTemp({
3111
+ platformDownload = await codepac.downloadToTemp({
2730
3112
  url: dep.url,
2731
3113
  commit: dep.commit,
2732
3114
  branch: dep.branch,
@@ -2736,25 +3118,43 @@ async function linkNestedDependencies(dependencies, params) {
2736
3118
  vars,
2737
3119
  useGitLightweightDownload: options.gitLightweightDownload,
2738
3120
  });
2739
- const assessment = assessDownloadResult(availablePlatforms, dep.sparse, downloadResult, vars);
3121
+ const assessment = assessDownloadResult(availablePlatforms, dep.sparse, platformDownload, vars);
2740
3122
  clearSatisfiedAutoUnavailablePlatforms(registry, dep, assessment.satisfiedRequested);
2741
3123
  if (assessment.downloadedRequested.length > 0) {
2742
3124
  // 有平台内容 → 吸收平台目录,重新分类为平台库
2743
- await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
2744
- const nestedAbsorbResult = await store.absorbLib(downloadResult.libDir, assessment.downloadedRequested, dep.libName, dep.commit);
2745
- await registerNestedLibraries(nestedAbsorbResult.nestedLibraries, projectHash);
3125
+ const nestedAbsorbResult = await new StoreImportService(registry, storePath).importLibrary({
3126
+ libDir: platformDownload.libDir,
3127
+ platforms: assessment.downloadedRequested,
3128
+ libName: dep.libName,
3129
+ commit: dep.commit,
3130
+ branch: dep.branch,
3131
+ url: dep.url,
3132
+ sourceKind: 'download',
3133
+ });
3134
+ await registerNestedLibraries(nestedAbsorbResult.nestedLibraries, implicitStoreReferences, implicitOwner);
2746
3135
  existingPlatforms.push(...assessment.downloadedRequested);
2747
3136
  isGeneral = false;
2748
3137
  info(`${indent} ${dep.libName} - 补充平台内容 [${assessment.downloadedRequested.join(', ')}]`);
2749
3138
  }
2750
- if (downloadResult.cleanedPlatforms.length > 0) {
2751
- hint(`${indent} 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
3139
+ if (platformDownload.cleanedPlatforms.length > 0) {
3140
+ hint(`${indent} 已过滤: ${platformDownload.cleanedPlatforms.join(', ')}`);
2752
3141
  }
2753
3142
  }
2754
- catch {
3143
+ catch (err) {
3144
+ if (platformDownload)
3145
+ throw err;
2755
3146
  // 下载失败,保持 General 分类
2756
3147
  warn(`${indent} ${dep.libName} - 平台内容下载失败,保持 General 分类`);
2757
3148
  }
3149
+ finally {
3150
+ if (platformDownload) {
3151
+ await cleanupDownloadTempDir(platformDownload.tempDir, {
3152
+ libName: dep.libName,
3153
+ commit: dep.commit,
3154
+ stage: 'nested-general-platform-import',
3155
+ });
3156
+ }
3157
+ }
2758
3158
  }
2759
3159
  }
2760
3160
  }
@@ -2763,7 +3163,12 @@ async function linkNestedDependencies(dependencies, params) {
2763
3163
  await fs.lstat(localPath);
2764
3164
  localExists = true;
2765
3165
  }
2766
- catch {
3166
+ catch (err) {
3167
+ if (!isPathMissingError(err)) {
3168
+ error(`[link-action] 本地依赖更新后状态读取失败: lib=${dep.libName}, commit=${dep.commit}, ` +
3169
+ `path=${localPath}, code=${err.code ?? 'unknown'}, message=${err.message}`);
3170
+ throw err;
3171
+ }
2767
3172
  localExists = false;
2768
3173
  }
2769
3174
  }
@@ -2777,32 +3182,32 @@ async function linkNestedDependencies(dependencies, params) {
2777
3182
  const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars, useGitLightweightDownload: options.gitLightweightDownload, repairRequiredPlatforms });
2778
3183
  // 注册嵌套依赖
2779
3184
  if (supplementResult.nestedLibraries.length > 0) {
2780
- await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
3185
+ await registerNestedLibraries(supplementResult.nestedLibraries, implicitStoreReferences, implicitOwner);
2781
3186
  }
2782
3187
  // 如果补充了新平台,需要重新链接
2783
3188
  if (supplementResult.downloaded.length > 0) {
2784
3189
  success(`${indent} ${dep.libName} - 补充平台 [${supplementResult.downloaded.join(', ')}]`);
2785
- // 删除现有链接
2786
- if (!dryRun) {
2787
- await fs.rm(localPath, { recursive: true, force: true });
2788
- }
2789
3190
  // 合并所有平台并重新链接
2790
3191
  const allPlatforms = [...existingPlatforms, ...supplementResult.downloaded];
2791
3192
  linkedPlatforms = allPlatforms;
2792
3193
  if (!dryRun) {
2793
- await tx.recordOp('link', localPath, storeCommitPath);
2794
- await linker.linkLib(localPath, storeCommitPath, allPlatforms);
3194
+ await linkPlatformDirectory(tx, localPath, storeCommitPath, allPlatforms);
2795
3195
  }
2796
3196
  success(`${indent} ${dep.libName} - 重新链接完成 [${allPlatforms.join(', ')}]`);
2797
3197
  }
2798
3198
  }
2799
- await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, linkedPlatforms, projectHash, isGeneral);
2800
- nestedLinkedDeps.push({
2801
- libName: dep.libName,
2802
- commit: dep.commit,
2803
- platform: isGeneral ? GENERAL_PLATFORM : (linkedPlatforms[0] ?? availablePlatforms[0]),
2804
- linkedPath: path.relative(projectRoot, localPath),
2805
- });
3199
+ await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, linkedPlatforms, isGeneral);
3200
+ if (!isGeneral) {
3201
+ const { actualExisting: desiredNestedPlatforms } = await resolveRequestedStoreState(dep, platforms, vars);
3202
+ debug(`[link-registry] 嵌套平台清理准备: lib=${dep.libName}, commit=${dep.commit}, ` +
3203
+ `requested=${platforms.join(',')}, desired=${desiredNestedPlatforms.join(',')}, local=${localPath}`);
3204
+ await linker.cleanupLocalExtraPlatforms(localPath, desiredNestedPlatforms, (entryPath) => tx.preparePathReplacement(entryPath, 'nested-extra-platform-cleanup'));
3205
+ await tx.save();
3206
+ }
3207
+ if (isGeneral) {
3208
+ generalDependencyKeys.add(dependencyIdentityKey(dep, localPath));
3209
+ }
3210
+ await recordLinkedDependency(nestedLinkedDeps, dep, localPath, storeCommitPath, projectRoot, isGeneral);
2806
3211
  if (!isGeneral) {
2807
3212
  success(`${indent} ${dep.libName} - 已链接`);
2808
3213
  }
@@ -2810,9 +3215,6 @@ async function linkNestedDependencies(dependencies, params) {
2810
3215
  }
2811
3216
  if (linkStatus === 'relink') {
2812
3217
  warn(`${indent} ${dep.libName} - 链接异常(断链或指向错误),将重新链接`);
2813
- if (!dryRun) {
2814
- await fs.rm(localPath, { recursive: true, force: true });
2815
- }
2816
3218
  localExists = false;
2817
3219
  localIsSymlink = false;
2818
3220
  }
@@ -2824,23 +3226,12 @@ async function linkNestedDependencies(dependencies, params) {
2824
3226
  info(`${indent} ${dep.libName} - 将链接到 Store`);
2825
3227
  continue;
2826
3228
  }
2827
- if (localExists && !localIsSymlink) {
2828
- // 本地是目录,先删除
2829
- await fs.rm(localPath, { recursive: true, force: true });
2830
- }
2831
3229
  if (isGeneral) {
2832
3230
  const sharedPath = path.join(storeCommitPath, '_shared');
2833
- await tx.recordOp('link', localPath, sharedPath);
2834
- await linker.linkGeneral(localPath, sharedPath);
2835
- await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, [GENERAL_PLATFORM], projectHash, true);
2836
- generalLibs.add(dep.libName);
2837
- // 记录到 nestedLinkedDeps
2838
- nestedLinkedDeps.push({
2839
- libName: dep.libName,
2840
- commit: dep.commit,
2841
- platform: GENERAL_PLATFORM,
2842
- linkedPath: path.relative(projectRoot, localPath),
2843
- });
3231
+ await linkGeneralDirectory(tx, localPath, sharedPath);
3232
+ await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, [GENERAL_PLATFORM], true);
3233
+ generalDependencyKeys.add(dependencyIdentityKey(dep, localPath));
3234
+ await recordLinkedDependency(nestedLinkedDeps, dep, localPath, storeCommitPath, projectRoot, true);
2844
3235
  success(`${indent} ${dep.libName} - 链接完成 (General)`);
2845
3236
  }
2846
3237
  else {
@@ -2848,7 +3239,7 @@ async function linkNestedDependencies(dependencies, params) {
2848
3239
  const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars, useGitLightweightDownload: options.gitLightweightDownload, repairRequiredPlatforms });
2849
3240
  // 注册嵌套依赖
2850
3241
  if (supplementResult.nestedLibraries.length > 0) {
2851
- await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
3242
+ await registerNestedLibraries(supplementResult.nestedLibraries, implicitStoreReferences, implicitOwner);
2852
3243
  }
2853
3244
  // 合并所有可链接的平台(已有 + 新下载)
2854
3245
  const allPlatforms = [...existingPlatforms, ...supplementResult.downloaded];
@@ -2856,16 +3247,9 @@ async function linkNestedDependencies(dependencies, params) {
2856
3247
  if (supplementResult.downloaded.length > 0) {
2857
3248
  success(`${indent} ${dep.libName} - 补充平台 [${supplementResult.downloaded.join(', ')}]`);
2858
3249
  }
2859
- await tx.recordOp('link', localPath, storeCommitPath);
2860
- await linker.linkLib(localPath, storeCommitPath, allPlatforms);
2861
- await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, allPlatforms, projectHash, false);
2862
- // 记录到 nestedLinkedDeps
2863
- nestedLinkedDeps.push({
2864
- libName: dep.libName,
2865
- commit: dep.commit,
2866
- platform: allPlatforms[0],
2867
- linkedPath: path.relative(projectRoot, localPath),
2868
- });
3250
+ await linkPlatformDirectory(tx, localPath, storeCommitPath, allPlatforms);
3251
+ await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, allPlatforms, false);
3252
+ await recordLinkedDependency(nestedLinkedDeps, dep, localPath, storeCommitPath, projectRoot, false);
2869
3253
  success(`${indent} ${dep.libName} - 链接完成 [${allPlatforms.join(', ')}]`);
2870
3254
  }
2871
3255
  }
@@ -2882,6 +3266,8 @@ async function linkNestedDependencies(dependencies, params) {
2882
3266
  info(`${indent} ${dep.libName} - 将下载`);
2883
3267
  continue;
2884
3268
  }
3269
+ let downloadResult;
3270
+ let downloadTempDir;
2885
3271
  try {
2886
3272
  const nestedLibKey = registry.getLibraryKey(dep.libName, dep.commit);
2887
3273
  const nestedHistoryLib = registry.getLibrary(nestedLibKey);
@@ -2890,7 +3276,6 @@ async function linkNestedDependencies(dependencies, params) {
2890
3276
  estimatedSize: nestedHistoryLib?.size,
2891
3277
  getDirSize,
2892
3278
  });
2893
- let downloadResult;
2894
3279
  try {
2895
3280
  downloadResult = await codepac.downloadToTemp({
2896
3281
  url: dep.url,
@@ -2901,13 +3286,18 @@ async function linkNestedDependencies(dependencies, params) {
2901
3286
  sparse: dep.sparse,
2902
3287
  vars,
2903
3288
  useGitLightweightDownload: options.gitLightweightDownload,
2904
- onTempDirCreated: (_tempDir, libDir) => { downloadMonitor.start(libDir); },
3289
+ onTempDirCreated: (tempDir, libDir) => {
3290
+ downloadTempDir = tempDir;
3291
+ downloadMonitor.start(libDir);
3292
+ },
2905
3293
  onHeartbeat: (message) => { downloadMonitor.heartbeat(message); },
2906
3294
  });
2907
3295
  }
2908
3296
  finally {
2909
3297
  await downloadMonitor.stop();
2910
3298
  }
3299
+ if (!downloadResult)
3300
+ throw new Error(`${dep.libName} 下载未返回结果`);
2911
3301
  // 提示清理的平台(如果有)
2912
3302
  if (downloadResult.cleanedPlatforms.length > 0) {
2913
3303
  hint(`${indent} 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
@@ -2920,21 +3310,20 @@ async function linkNestedDependencies(dependencies, params) {
2920
3310
  platforms: [GENERAL_PLATFORM],
2921
3311
  isGeneral: true,
2922
3312
  });
2923
- await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
2924
- await store.absorbGeneral(downloadResult.libDir, dep.libName, dep.commit);
2925
- const sharedPath = path.join(storeCommitPath, '_shared');
2926
- await tx.recordOp('link', localPath, sharedPath);
2927
- await linker.linkGeneral(localPath, sharedPath);
2928
- await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, [GENERAL_PLATFORM], projectHash, true);
2929
- generalLibs.add(dep.libName);
2930
- downloadedLibs.push(dep.libName);
2931
- // 记录到 nestedLinkedDeps
2932
- nestedLinkedDeps.push({
3313
+ await new StoreImportService(registry, storePath).importGeneral({
3314
+ libDir: downloadResult.libDir,
2933
3315
  libName: dep.libName,
2934
3316
  commit: dep.commit,
2935
- platform: GENERAL_PLATFORM,
2936
- linkedPath: path.relative(projectRoot, localPath),
3317
+ branch: dep.branch,
3318
+ url: dep.url,
3319
+ sourceKind: 'download',
2937
3320
  });
3321
+ const sharedPath = path.join(storeCommitPath, '_shared');
3322
+ await linkGeneralDirectory(tx, localPath, sharedPath);
3323
+ await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, [GENERAL_PLATFORM], true);
3324
+ generalDependencyKeys.add(dependencyIdentityKey(dep, localPath));
3325
+ downloadedLibs.push(dep.libName);
3326
+ await recordLinkedDependency(nestedLinkedDeps, dep, localPath, storeCommitPath, projectRoot, true);
2938
3327
  if (repairTargets.includes(GENERAL_PLATFORM)) {
2939
3328
  warn(`${indent} ${dep.libName} ${GENERAL_PLATFORM} 本地缓存不完整,已重新下载`);
2940
3329
  }
@@ -2944,20 +3333,20 @@ async function linkNestedDependencies(dependencies, params) {
2944
3333
  }
2945
3334
  else if (assessment.downloadedRequested.length > 0) {
2946
3335
  // 平台库处理
2947
- await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
2948
- const nestedAbsorbResult = await store.absorbLib(downloadResult.libDir, assessment.downloadedRequested, dep.libName, dep.commit);
2949
- await registerNestedLibraries(nestedAbsorbResult.nestedLibraries, projectHash);
2950
- await tx.recordOp('link', localPath, storeCommitPath);
2951
- await linker.linkLib(localPath, storeCommitPath, assessment.downloadedRequested);
2952
- await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, assessment.downloadedRequested, projectHash, false);
2953
- downloadedLibs.push(dep.libName);
2954
- // 记录到 nestedLinkedDeps
2955
- nestedLinkedDeps.push({
3336
+ const nestedAbsorbResult = await new StoreImportService(registry, storePath).importLibrary({
3337
+ libDir: downloadResult.libDir,
3338
+ platforms: assessment.downloadedRequested,
2956
3339
  libName: dep.libName,
2957
3340
  commit: dep.commit,
2958
- platform: assessment.downloadedRequested[0],
2959
- linkedPath: path.relative(projectRoot, localPath),
3341
+ branch: dep.branch,
3342
+ url: dep.url,
3343
+ sourceKind: 'download',
2960
3344
  });
3345
+ await registerNestedLibraries(nestedAbsorbResult.nestedLibraries, implicitStoreReferences, implicitOwner);
3346
+ await linkPlatformDirectory(tx, localPath, storeCommitPath, assessment.downloadedRequested);
3347
+ await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, assessment.downloadedRequested, false);
3348
+ downloadedLibs.push(dep.libName);
3349
+ await recordLinkedDependency(nestedLinkedDeps, dep, localPath, storeCommitPath, projectRoot, false);
2961
3350
  // 检查并记录新发现的不可用平台
2962
3351
  const newUnavailable = assessment.unavailableRequested;
2963
3352
  if (newUnavailable.length > 0) {
@@ -2986,15 +3375,11 @@ async function linkNestedDependencies(dependencies, params) {
2986
3375
  });
2987
3376
  warn(`${indent} ${dep.libName} - 下载成功但平台 [${availablePlatforms.join(', ')}] 不可用,已记录`);
2988
3377
  }
2989
- // 清理临时目录
2990
- try {
2991
- await fs.rm(downloadResult.tempDir, { recursive: true, force: true });
2992
- }
2993
- catch {
2994
- // 忽略清理错误
2995
- }
2996
3378
  }
2997
3379
  catch (err) {
3380
+ if (downloadResult) {
3381
+ throw err;
3382
+ }
2998
3383
  if (repairTargets.length > 0) {
2999
3384
  error(`${indent} ${dep.libName} ${repairTargets.join(', ')} 本地缓存不完整,自动重新下载失败`);
3000
3385
  hint(`${indent} 可运行 td reset ${dep.libName} 后重试`);
@@ -3005,6 +3390,16 @@ async function linkNestedDependencies(dependencies, params) {
3005
3390
  warn(`${indent} ${dep.libName} - 下载失败: ${err.message}`);
3006
3391
  }
3007
3392
  }
3393
+ finally {
3394
+ const tempDir = downloadResult?.tempDir ?? downloadTempDir;
3395
+ if (tempDir) {
3396
+ await cleanupDownloadTempDir(tempDir, {
3397
+ libName: dep.libName,
3398
+ commit: dep.commit,
3399
+ stage: 'nested-download-import',
3400
+ });
3401
+ }
3402
+ }
3008
3403
  }
3009
3404
  else {
3010
3405
  warn(`${indent} ${dep.libName} - 缺失 (跳过下载)`);