tanmi-dock 0.9.0 → 0.9.3

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 (65) hide show
  1. package/dist/commands/link.d.ts +27 -1
  2. package/dist/commands/link.d.ts.map +1 -1
  3. package/dist/commands/link.js +470 -297
  4. package/dist/commands/link.js.map +1 -1
  5. package/dist/commands/reset.d.ts +9 -0
  6. package/dist/commands/reset.d.ts.map +1 -0
  7. package/dist/commands/reset.js +280 -0
  8. package/dist/commands/reset.js.map +1 -0
  9. package/dist/commands/status.d.ts.map +1 -1
  10. package/dist/commands/status.js +34 -14
  11. package/dist/commands/status.js.map +1 -1
  12. package/dist/commands/unavailable.d.ts +7 -0
  13. package/dist/commands/unavailable.d.ts.map +1 -0
  14. package/dist/commands/unavailable.js +202 -0
  15. package/dist/commands/unavailable.js.map +1 -0
  16. package/dist/commands/unlink.d.ts.map +1 -1
  17. package/dist/commands/unlink.js +8 -4
  18. package/dist/commands/unlink.js.map +1 -1
  19. package/dist/core/codepac.d.ts +3 -1
  20. package/dist/core/codepac.d.ts.map +1 -1
  21. package/dist/core/codepac.js +5 -4
  22. package/dist/core/codepac.js.map +1 -1
  23. package/dist/core/linker.d.ts +6 -0
  24. package/dist/core/linker.d.ts.map +1 -1
  25. package/dist/core/linker.js +26 -1
  26. package/dist/core/linker.js.map +1 -1
  27. package/dist/core/parser.d.ts +10 -0
  28. package/dist/core/parser.d.ts.map +1 -1
  29. package/dist/core/parser.js +18 -0
  30. package/dist/core/parser.js.map +1 -1
  31. package/dist/core/platform.d.ts +4 -0
  32. package/dist/core/platform.d.ts.map +1 -1
  33. package/dist/core/platform.js +52 -0
  34. package/dist/core/platform.js.map +1 -1
  35. package/dist/core/registry.d.ts +16 -0
  36. package/dist/core/registry.d.ts.map +1 -1
  37. package/dist/core/registry.js +66 -1
  38. package/dist/core/registry.js.map +1 -1
  39. package/dist/index.js +4 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/types/index.d.ts +3 -0
  42. package/dist/types/index.d.ts.map +1 -1
  43. package/dist/types/index.js +1 -0
  44. package/dist/types/index.js.map +1 -1
  45. package/dist/utils/prompt.d.ts +12 -0
  46. package/dist/utils/prompt.d.ts.map +1 -1
  47. package/dist/utils/prompt.js +102 -0
  48. package/dist/utils/prompt.js.map +1 -1
  49. package/package.json +1 -1
  50. package/dist/commands/doctor.d.ts +0 -7
  51. package/dist/commands/doctor.d.ts.map +0 -1
  52. package/dist/commands/doctor.js +0 -178
  53. package/dist/commands/doctor.js.map +0 -1
  54. package/dist/commands/projects.d.ts +0 -7
  55. package/dist/commands/projects.d.ts.map +0 -1
  56. package/dist/commands/projects.js +0 -126
  57. package/dist/commands/projects.js.map +0 -1
  58. package/dist/commands/repair.d.ts +0 -16
  59. package/dist/commands/repair.d.ts.map +0 -1
  60. package/dist/commands/repair.js +0 -364
  61. package/dist/commands/repair.js.map +0 -1
  62. package/dist/commands/verify.d.ts +0 -11
  63. package/dist/commands/verify.d.ts.map +0 -1
  64. package/dist/commands/verify.js +0 -266
  65. package/dist/commands/verify.js.map +0 -1
@@ -7,13 +7,13 @@ import { Command } from 'commander';
7
7
  import { ensureInitialized } from '../core/guard.js';
8
8
  import * as config from '../core/config.js';
9
9
  import { setLogLevel } from '../utils/logger.js';
10
- import { parseProjectDependencies, getRelativeConfigPath, parseCodepacDep, extractActions, parseActionCommand, extractNestedDependencies, normalizeProjectRoot, findAllCodepacConfigs, extractDependencies, } from '../core/parser.js';
10
+ import { parseProjectDependencies, getRelativeConfigPath, parseCodepacDep, extractActions, parseActionCommand, extractNestedDependencies, findAllCodepacConfigs, extractDependencies, resolveProjectRootPath, } from '../core/parser.js';
11
11
  import { getRegistry } from '../core/registry.js';
12
12
  import * as store from '../core/store.js';
13
13
  import * as linker from '../core/linker.js';
14
14
  import * as codepac from '../core/codepac.js';
15
15
  import { setProxyConfig } from '../core/codepac.js';
16
- import { resolvePath, getPlatformHelpText, GENERAL_PLATFORM, SHARED_PLATFORM, pathsEqual, isSparseOnlyCommon, KNOWN_PLATFORM_VALUES } from '../core/platform.js';
16
+ import { getPlatformHelpText, GENERAL_PLATFORM, SHARED_PLATFORM, pathsEqual, isSparseOnlyCommon, KNOWN_PLATFORM_VALUES, getRequestedPlatformTargets, } from '../core/platform.js';
17
17
  import { Transaction } from '../core/transaction.js';
18
18
  import { formatSize, checkDiskSpace } from '../utils/disk.js';
19
19
  import { getDirSize } from '../utils/fs-utils.js';
@@ -60,6 +60,181 @@ export function createLinkCommand() {
60
60
  }
61
61
  });
62
62
  }
63
+ function resolveRequestedPlatformsByActual(requestedPlatforms, sparse, actualPlatforms, vars) {
64
+ const actualExisting = [...new Set(actualPlatforms)];
65
+ const actualPlatformSet = new Set(actualExisting);
66
+ const satisfiedRequested = requestedPlatforms.filter((requestedPlatform) => {
67
+ const targets = getRequestedPlatformTargets(requestedPlatform, sparse, vars);
68
+ return targets.some((target) => actualPlatformSet.has(target));
69
+ });
70
+ return {
71
+ actualExisting,
72
+ satisfiedRequested,
73
+ missingRequested: requestedPlatforms.filter((platform) => !satisfiedRequested.includes(platform)),
74
+ };
75
+ }
76
+ export function assessDownloadResult(requestedPlatforms, sparse, downloadResult, vars) {
77
+ const downloadedActual = new Set(downloadResult.platformDirs);
78
+ const { satisfiedRequested, missingRequested, } = resolveRequestedPlatformsByActual(requestedPlatforms, sparse, downloadResult.platformDirs, vars);
79
+ const downloadedRequested = [...new Set(satisfiedRequested.flatMap((requestedPlatform) => getRequestedPlatformTargets(requestedPlatform, sparse, vars)
80
+ .filter((target) => downloadedActual.has(target))))];
81
+ const unavailableRequested = missingRequested;
82
+ const hasAnyPlatformArtifacts = downloadResult.allPlatformDirs.length > 0;
83
+ const isPureGeneral = Boolean((!sparse || isSparseOnlyCommon(sparse)) &&
84
+ !hasAnyPlatformArtifacts);
85
+ return {
86
+ downloadedRequested,
87
+ satisfiedRequested,
88
+ unavailableRequested,
89
+ hasAnyPlatformArtifacts,
90
+ isPureGeneral,
91
+ };
92
+ }
93
+ async function resolveRequestedStoreState(dependency, requestedPlatforms, vars) {
94
+ const resolvedExisting = [];
95
+ const candidatePlatforms = [...new Set(requestedPlatforms.flatMap((platform) => getRequestedPlatformTargets(platform, dependency.sparse, vars)))];
96
+ for (const platform of candidatePlatforms) {
97
+ if (await store.exists(dependency.libName, dependency.commit, platform)) {
98
+ resolvedExisting.push(platform);
99
+ }
100
+ }
101
+ return resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, resolvedExisting, vars);
102
+ }
103
+ function getBlockedRequestedPlatforms(registry, dependency, requestedPlatforms, vars) {
104
+ const { manualPlatforms, autoPlatforms } = resolveUnavailablePlatforms(registry, dependency);
105
+ const manualBlocked = requestedPlatforms.filter((platform) => manualPlatforms.includes(platform));
106
+ const autoBlocked = requestedPlatforms.filter((platform) => {
107
+ if (!autoPlatforms.includes(platform))
108
+ return false;
109
+ const targets = getRequestedPlatformTargets(platform, dependency.sparse, vars);
110
+ return targets.length === 1 && targets[0] === platform;
111
+ });
112
+ return [...new Set([...manualBlocked, ...autoBlocked])];
113
+ }
114
+ function clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested) {
115
+ if (satisfiedRequested.length === 0)
116
+ return;
117
+ const libKey = registry.getLibraryKey(dependency.libName, dependency.commit);
118
+ const library = registry.getLibrary(libKey);
119
+ if (!library?.unavailablePlatforms || library.unavailablePlatforms.length === 0)
120
+ return;
121
+ const nextUnavailable = library.unavailablePlatforms.filter((platform) => !satisfiedRequested.includes(platform));
122
+ if (nextUnavailable.length === library.unavailablePlatforms.length) {
123
+ return;
124
+ }
125
+ registry.updateLibrary(libKey, {
126
+ unavailablePlatforms: nextUnavailable,
127
+ lastAccess: new Date().toISOString(),
128
+ });
129
+ }
130
+ function upsertLibraryAvailability(dependency, updates) {
131
+ const registry = getRegistry();
132
+ const libKey = registry.getLibraryKey(dependency.libName, dependency.commit);
133
+ const existing = registry.getLibrary(libKey);
134
+ if (existing) {
135
+ registry.updateLibrary(libKey, {
136
+ unavailablePlatforms: updates.unavailablePlatforms ?? existing.unavailablePlatforms,
137
+ platforms: updates.platforms ?? existing.platforms,
138
+ isGeneral: updates.isGeneral ?? existing.isGeneral,
139
+ lastAccess: new Date().toISOString(),
140
+ });
141
+ return;
142
+ }
143
+ registry.addLibrary({
144
+ libName: dependency.libName,
145
+ commit: dependency.commit,
146
+ branch: dependency.branch,
147
+ url: dependency.url,
148
+ platforms: updates.platforms ?? [],
149
+ size: 0,
150
+ isGeneral: updates.isGeneral,
151
+ referencedBy: [],
152
+ unavailablePlatforms: updates.unavailablePlatforms,
153
+ createdAt: new Date().toISOString(),
154
+ lastAccess: new Date().toISOString(),
155
+ });
156
+ }
157
+ export async function resolveLibraryGeneralState(dependency) {
158
+ const registry = getRegistry();
159
+ const libKey = registry.getLibraryKey(dependency.libName, dependency.commit);
160
+ const library = registry.getLibrary(libKey);
161
+ if (typeof library?.isGeneral === 'boolean') {
162
+ return library.isGeneral;
163
+ }
164
+ return store.isGeneralLib(dependency.libName, dependency.commit);
165
+ }
166
+ export function resolveUnavailablePlatforms(registry, dependency) {
167
+ const libKey = registry.getLibraryKey(dependency.libName, dependency.commit);
168
+ const autoPlatforms = registry.getLibrary(libKey)?.unavailablePlatforms || [];
169
+ const manualPlatforms = registry.getManualUnavailablePlatforms(dependency.libName, dependency.commit);
170
+ return {
171
+ platforms: [...new Set([...manualPlatforms, ...autoPlatforms])],
172
+ manualPlatforms,
173
+ autoPlatforms,
174
+ };
175
+ }
176
+ export async function collectProjectDependencyGraph(projectRoot) {
177
+ const { normalizedPath, configPath } = await resolveProjectRootPath(projectRoot);
178
+ if (!configPath) {
179
+ throw new Error('只能在项目目录下使用');
180
+ }
181
+ const results = [];
182
+ const seenDeps = new Set();
183
+ const visitedConfigs = new Set();
184
+ const queue = [
185
+ { configPath, source: 'direct' },
186
+ ];
187
+ const submodules = await findSubmoduleConfigs(normalizedPath);
188
+ for (const submodule of submodules) {
189
+ queue.push({ configPath: submodule.configPath, scope: submodule.relativePath, source: 'submodule' });
190
+ }
191
+ while (queue.length > 0) {
192
+ const current = queue.shift();
193
+ if (visitedConfigs.has(current.configPath))
194
+ continue;
195
+ visitedConfigs.add(current.configPath);
196
+ let parsedConfig;
197
+ try {
198
+ parsedConfig = await parseCodepacDep(current.configPath);
199
+ }
200
+ catch {
201
+ continue;
202
+ }
203
+ for (const dependency of extractDependencies(parsedConfig)) {
204
+ const key = `${dependency.libName}:${dependency.commit}:${current.scope ?? ''}`;
205
+ if (!seenDeps.has(key)) {
206
+ seenDeps.add(key);
207
+ results.push({ ...dependency, scope: current.scope, source: current.source });
208
+ }
209
+ }
210
+ for (const action of extractActions(parsedConfig)) {
211
+ let parsedAction;
212
+ try {
213
+ parsedAction = parseActionCommand(action.command);
214
+ }
215
+ catch {
216
+ continue;
217
+ }
218
+ const nestedConfigPath = path.resolve(path.dirname(current.configPath), parsedAction.configDir, 'codepac-dep.json');
219
+ let nested;
220
+ try {
221
+ nested = await extractNestedDependencies(nestedConfigPath, parsedAction.libraries);
222
+ }
223
+ catch {
224
+ continue;
225
+ }
226
+ for (const dependency of nested.dependencies) {
227
+ const key = `${dependency.libName}:${dependency.commit}:${current.scope ?? ''}`;
228
+ if (!seenDeps.has(key)) {
229
+ seenDeps.add(key);
230
+ results.push({ ...dependency, scope: current.scope, source: 'nested' });
231
+ }
232
+ }
233
+ queue.push({ configPath: nestedConfigPath, scope: current.scope, source: 'nested' });
234
+ }
235
+ }
236
+ return results;
237
+ }
63
238
  /**
64
239
  * 选择要链接的 submodule
65
240
  */
@@ -144,7 +319,7 @@ async function linkScope(params) {
144
319
  blank();
145
320
  // 2. 分类依赖
146
321
  const scopePath = path.dirname(path.dirname(configPath));
147
- const classified = await classifyDependencies(dependencies, scopePath, configPath, platforms);
322
+ const classified = await classifyDependencies(dependencies, scopePath, configPath, platforms, configVars);
148
323
  const stats = {
149
324
  linked: 0, relink: 0, replace: 0, absorb: 0, missing: 0, linkNew: 0,
150
325
  };
@@ -223,13 +398,21 @@ async function linkScope(params) {
223
398
  const needDownloadItems = [];
224
399
  for (const item of classified) {
225
400
  const { dependency, status } = item;
401
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
402
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
226
403
  if (status === DependencyStatus.MISSING) {
227
- needDownloadItems.push({
228
- libName: dependency.libName, commit: dependency.commit, reason: '缺失',
229
- });
404
+ if (requestedPlatforms.length > 0) {
405
+ const { missingRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
406
+ if (missingRequested.length === 0) {
407
+ continue;
408
+ }
409
+ needDownloadItems.push({
410
+ libName: dependency.libName, commit: dependency.commit, reason: '缺失',
411
+ });
412
+ }
230
413
  }
231
414
  else if (status === DependencyStatus.ABSORB) {
232
- const { missing } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
415
+ const { missingRequested: missing } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
233
416
  if (missing.length > 0) {
234
417
  needDownloadItems.push({
235
418
  libName: dependency.libName, commit: dependency.commit,
@@ -238,7 +421,7 @@ async function linkScope(params) {
238
421
  }
239
422
  }
240
423
  else if (status === DependencyStatus.LINK_NEW) {
241
- const { missing } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
424
+ const { missingRequested: missing } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
242
425
  if (missing.length > 0) {
243
426
  needDownloadItems.push({
244
427
  libName: dependency.libName, commit: dependency.commit,
@@ -280,54 +463,62 @@ async function linkScope(params) {
280
463
  await store.ensureCompatibleStore(storePath, dependency.libName, dependency.commit);
281
464
  switch (status) {
282
465
  case DependencyStatus.LINKED: {
283
- const isLinkedGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
466
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
467
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
468
+ const isLinkedGeneral = await resolveLibraryGeneralState(dependency);
284
469
  if (!isLinkedGeneral) {
285
470
  const supplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
286
471
  await registerNestedLibraries(supplementResult.nestedLibraries, projectHash);
287
472
  if (supplementResult.downloaded.length > 0) {
288
473
  const linkedCommitPath = path.join(storePath, dependency.libName, dependency.commit);
289
- const { existing: allExisting } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
474
+ const { actualExisting: allExisting, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
475
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
290
476
  tx.recordOp('link', localPath, linkedCommitPath);
291
477
  await linker.linkLib(localPath, linkedCommitPath, allExisting);
292
- for (const platform of allExisting) {
293
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
294
- registry.addStoreReference(storeKey, projectHash);
295
- }
478
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, allExisting, projectHash, false);
296
479
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已补充平台 [${supplementResult.downloaded.join(', ')}]`);
297
480
  }
298
481
  }
482
+ const { actualExisting: linkedExisting, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
483
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
484
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, isLinkedGeneral ? [GENERAL_PLATFORM] : linkedExisting, projectHash, isLinkedGeneral);
299
485
  break;
300
486
  }
301
487
  case DependencyStatus.RELINK: {
488
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
489
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
302
490
  const relinkCommitPath = path.join(storePath, dependency.libName, dependency.commit);
303
- const isRelinkGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
491
+ const isRelinkGeneral = await resolveLibraryGeneralState(dependency);
304
492
  if (isRelinkGeneral) {
305
493
  tx.recordOp('unlink', localPath);
306
494
  await linker.unlink(localPath);
307
495
  const sharedPath = path.join(relinkCommitPath, '_shared');
308
496
  tx.recordOp('link', localPath, sharedPath);
309
497
  await linker.linkGeneral(localPath, sharedPath);
498
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
310
499
  generalLibs.add(dependency.libName);
311
500
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,重建链接`);
312
501
  }
313
502
  else {
314
503
  const relinkSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
315
504
  await registerNestedLibraries(relinkSupplementResult.nestedLibraries, projectHash);
316
- const { existing: relinkExisting } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
505
+ const { actualExisting: relinkExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
506
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
317
507
  if (relinkExisting.length === 0) {
318
508
  const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
319
509
  const relinkCommitEntries = await fs.readdir(relinkCommitPath, { withFileTypes: true });
320
510
  const relinkAvailablePlatforms = relinkCommitEntries
321
511
  .filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
322
512
  .map(e => e.name);
323
- warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 不支持 ${platforms.join('/')} 平台 [可用: ${relinkAvailablePlatforms.join(', ')}]`);
513
+ warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已按规则跳过平台 [${platforms.join('/')}], 可用平台 [${relinkAvailablePlatforms.join(', ')}]`);
324
514
  const relinkLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
325
515
  const relinkLib = registry.getLibrary(relinkLibKey);
326
516
  if (relinkLib) {
327
517
  const unavailable = relinkLib.unavailablePlatforms || [];
328
- for (const p of platforms) {
329
- if (!unavailable.includes(p) && !relinkAvailablePlatforms.includes(p))
330
- unavailable.push(p);
518
+ const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, relinkAvailablePlatforms, configVars).missingRequested;
519
+ for (const requestedPlatform of missingRequested) {
520
+ if (!unavailable.includes(requestedPlatform))
521
+ unavailable.push(requestedPlatform);
331
522
  }
332
523
  registry.updateLibrary(relinkLibKey, { unavailablePlatforms: unavailable });
333
524
  }
@@ -337,10 +528,7 @@ async function linkScope(params) {
337
528
  await linker.unlink(localPath);
338
529
  tx.recordOp('link', localPath, relinkCommitPath);
339
530
  await linker.linkLib(localPath, relinkCommitPath, relinkExisting);
340
- for (const platform of relinkExisting) {
341
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
342
- registry.addStoreReference(storeKey, projectHash);
343
- }
531
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, relinkExisting, projectHash, false);
344
532
  if (relinkSupplementResult.downloaded.length > 0) {
345
533
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 重建链接并补充平台 [${relinkExisting.join(', ')}]`);
346
534
  }
@@ -351,35 +539,40 @@ async function linkScope(params) {
351
539
  break;
352
540
  }
353
541
  case DependencyStatus.REPLACE: {
542
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
543
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
354
544
  const replaceSize = await getDirSize(localPath);
355
545
  const replaceCommitPath = path.join(storePath, dependency.libName, dependency.commit);
356
- const isReplaceGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
546
+ const isReplaceGeneral = await resolveLibraryGeneralState(dependency);
357
547
  if (isReplaceGeneral) {
358
548
  const sharedPath = path.join(replaceCommitPath, '_shared');
359
549
  tx.recordOp('replace', localPath, sharedPath);
360
550
  await linker.linkGeneral(localPath, sharedPath);
361
551
  savedBytes += replaceSize;
552
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
362
553
  generalLibs.add(dependency.libName);
363
554
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,创建链接`);
364
555
  }
365
556
  else {
366
557
  const replaceSupplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, { vars: configVars });
367
558
  await registerNestedLibraries(replaceSupplementResult.nestedLibraries, projectHash);
368
- const { existing: replaceExisting } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
559
+ const { actualExisting: replaceExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
560
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
369
561
  if (replaceExisting.length === 0) {
370
562
  const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
371
563
  const replaceCommitEntries = await fs.readdir(replaceCommitPath, { withFileTypes: true });
372
564
  const replaceAvailablePlatforms = replaceCommitEntries
373
565
  .filter(e => e.isDirectory() && e.name !== '_shared' && KNOWN_PLATFORM_VALUES.includes(e.name))
374
566
  .map(e => e.name);
375
- warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 不支持 ${platforms.join('/')} 平台 [可用: ${replaceAvailablePlatforms.join(', ')}]`);
567
+ warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已按规则跳过平台 [${platforms.join('/')}], 可用平台 [${replaceAvailablePlatforms.join(', ')}]`);
376
568
  const replaceLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
377
569
  const replaceLib = registry.getLibrary(replaceLibKey);
378
570
  if (replaceLib) {
379
571
  const unavailable = replaceLib.unavailablePlatforms || [];
380
- for (const p of platforms) {
381
- if (!unavailable.includes(p) && !replaceAvailablePlatforms.includes(p))
382
- unavailable.push(p);
572
+ const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, replaceAvailablePlatforms, configVars).missingRequested;
573
+ for (const requestedPlatform of missingRequested) {
574
+ if (!unavailable.includes(requestedPlatform))
575
+ unavailable.push(requestedPlatform);
383
576
  }
384
577
  registry.updateLibrary(replaceLibKey, { unavailablePlatforms: unavailable });
385
578
  }
@@ -388,10 +581,7 @@ async function linkScope(params) {
388
581
  tx.recordOp('replace', localPath, replaceCommitPath);
389
582
  await linker.linkLib(localPath, replaceCommitPath, replaceExisting);
390
583
  savedBytes += replaceSize;
391
- for (const platform of replaceExisting) {
392
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
393
- registry.addStoreReference(storeKey, projectHash);
394
- }
584
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, replaceExisting, projectHash, false);
395
585
  if (replaceSupplementResult.downloaded.length > 0) {
396
586
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - Store 已有,创建链接并补充平台 [${replaceExisting.join(', ')}]`);
397
587
  }
@@ -402,22 +592,26 @@ async function linkScope(params) {
402
592
  break;
403
593
  }
404
594
  case DependencyStatus.ABSORB: {
595
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
596
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
405
597
  const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
406
598
  const { KNOWN_PLATFORM_VALUES } = await import('../core/platform.js');
407
599
  const localDirEntries = await fs.readdir(localPath, { withFileTypes: true });
408
600
  const localPlatforms = localDirEntries
409
601
  .filter(entry => entry.isDirectory() && KNOWN_PLATFORM_VALUES.includes(entry.name))
410
602
  .map(entry => entry.name);
411
- const finalPlatforms = localPlatforms.filter(p => finalLinkPlatforms.includes(p));
603
+ const requestedTargets = [...new Set(requestedPlatforms.flatMap((platform) => getRequestedPlatformTargets(platform, dependency.sparse, configVars)))];
604
+ const finalPlatforms = localPlatforms.filter((platform) => requestedTargets.includes(platform));
412
605
  if (finalPlatforms.length === 0 && localPlatforms.length > 0) {
413
- warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 不支持 ${platforms.join('/')} 平台 [本地有: ${localPlatforms.join(', ')}]`);
606
+ warn(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已按规则跳过平台 [${platforms.join('/')}], 本地有 [${localPlatforms.join(', ')}]`);
414
607
  const absorbLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
415
608
  const absorbLib = registry.getLibrary(absorbLibKey);
416
609
  if (absorbLib) {
417
610
  const unavailable = absorbLib.unavailablePlatforms || [];
418
- for (const p of platforms) {
419
- if (!unavailable.includes(p) && !localPlatforms.includes(p))
420
- unavailable.push(p);
611
+ const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, localPlatforms, configVars).missingRequested;
612
+ for (const requestedPlatform of missingRequested) {
613
+ if (!unavailable.includes(requestedPlatform))
614
+ unavailable.push(requestedPlatform);
421
615
  }
422
616
  registry.updateLibrary(absorbLibKey, { unavailablePlatforms: unavailable });
423
617
  }
@@ -448,6 +642,7 @@ async function linkScope(params) {
448
642
  registry.addLibrary({
449
643
  libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
450
644
  url: dependency.url, platforms: absorbLinkPlatforms, size: absorbSize,
645
+ isGeneral: false,
451
646
  referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
452
647
  });
453
648
  }
@@ -511,6 +706,7 @@ async function linkScope(params) {
511
706
  registry.addLibrary({
512
707
  libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
513
708
  url: dependency.url, platforms: [GENERAL_PLATFORM], size: sharedSize,
709
+ isGeneral: true,
514
710
  referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
515
711
  });
516
712
  }
@@ -527,7 +723,9 @@ async function linkScope(params) {
527
723
  break;
528
724
  case DependencyStatus.LINK_NEW: {
529
725
  const linkNewCommitPath = path.join(storePath, dependency.libName, dependency.commit);
530
- const { missing } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
726
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
727
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
728
+ const { missingRequested: missing } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
531
729
  const linkNewLibId = `${dependency.libName}@${dependency.commit}`;
532
730
  if (missing.length > 0 && !skipAllDownloads && downloadConfirmedLibs.has(linkNewLibId)) {
533
731
  info(`${dependency.libName} 缺少平台 [${missing.join(', ')}],开始下载...`);
@@ -552,7 +750,9 @@ async function linkScope(params) {
552
750
  hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
553
751
  }
554
752
  try {
555
- const filteredDownloaded = downloadResult.platformDirs.filter(p => missing.includes(p));
753
+ const assessment = assessDownloadResult(missing, dependency.sparse, downloadResult, configVars);
754
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, assessment.satisfiedRequested);
755
+ const filteredDownloaded = assessment.downloadedRequested;
556
756
  if (filteredDownloaded.length > 0) {
557
757
  tx.recordOp('absorb', linkNewCommitPath, downloadResult.libDir);
558
758
  const linkNewAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
@@ -563,27 +763,14 @@ async function linkScope(params) {
563
763
  await fs.rm(downloadResult.tempDir, { recursive: true, force: true }).catch(() => { });
564
764
  }
565
765
  }
566
- const { existing: linkNewExisting } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
567
- const isLinkNewGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
766
+ const { actualExisting: linkNewExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
767
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
768
+ const isLinkNewGeneral = await resolveLibraryGeneralState(dependency);
568
769
  if (isLinkNewGeneral) {
569
770
  const sharedPath = path.join(linkNewCommitPath, '_shared');
570
771
  tx.recordOp('link', localPath, sharedPath);
571
772
  await linker.linkGeneral(localPath, sharedPath);
572
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
573
- const existingEntry = registry.getStore(storeKey);
574
- if (!existingEntry) {
575
- const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
576
- registry.addStore({
577
- libName: dependency.libName, commit: dependency.commit, platform: GENERAL_PLATFORM,
578
- branch: dependency.branch, url: dependency.url, ...integrity,
579
- usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
580
- });
581
- }
582
- else if (existingEntry.fileCount == null) {
583
- const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
584
- registry.updateStore(storeKey, integrity);
585
- }
586
- registry.addStoreReference(storeKey, projectHash);
773
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
587
774
  generalLibs.add(dependency.libName);
588
775
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,创建链接`);
589
776
  }
@@ -598,9 +785,10 @@ async function linkScope(params) {
598
785
  const lnLib = registry.getLibrary(lnLibKey);
599
786
  if (lnLib) {
600
787
  const unavailable = lnLib.unavailablePlatforms || [];
601
- for (const p of platforms) {
602
- if (!unavailable.includes(p) && !availablePlatforms.includes(p))
603
- unavailable.push(p);
788
+ const missingRequested = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, availablePlatforms, configVars).missingRequested;
789
+ for (const requestedPlatform of missingRequested) {
790
+ if (!unavailable.includes(requestedPlatform))
791
+ unavailable.push(requestedPlatform);
604
792
  }
605
793
  registry.updateLibrary(lnLibKey, { unavailablePlatforms: unavailable });
606
794
  }
@@ -609,29 +797,15 @@ async function linkScope(params) {
609
797
  else {
610
798
  tx.recordOp('link', localPath, linkNewCommitPath);
611
799
  await linker.linkLib(localPath, linkNewCommitPath, linkNewExisting);
612
- for (const platform of linkNewExisting) {
613
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
614
- const existingPlatformEntry = registry.getStore(storeKey);
615
- if (!existingPlatformEntry) {
616
- const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
617
- registry.addStore({
618
- libName: dependency.libName, commit: dependency.commit, platform,
619
- branch: dependency.branch, url: dependency.url, ...integrity,
620
- usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
621
- });
622
- }
623
- else if (existingPlatformEntry.fileCount == null) {
624
- const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
625
- registry.updateStore(storeKey, integrity);
626
- }
627
- registry.addStoreReference(storeKey, projectHash);
628
- }
629
- await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
800
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkNewExisting, projectHash, false);
630
801
  success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 创建链接 [${linkNewExisting.join(', ')}]`);
631
802
  }
632
803
  break;
633
804
  }
634
805
  }
806
+ // 无论当前是跳过、补平台还是重建链接,都要把本地残留的平台目录清理到最终平台集合
807
+ const desiredLocalPlatforms = await resolveStoredPlatforms(dependency.libName, dependency.commit, finalLinkPlatforms);
808
+ await linker.cleanupLocalExtraPlatforms(localPath, desiredLocalPlatforms);
635
809
  await tx.save();
636
810
  }
637
811
  // 8. 并行处理 MISSING 依赖
@@ -658,47 +832,26 @@ async function linkScope(params) {
658
832
  };
659
833
  try {
660
834
  const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
661
- const { existing, missing } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
835
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
836
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
837
+ const { actualExisting: existing, missingRequested: missing, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
838
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
662
839
  if (missing.length === 0) {
663
840
  pLog.info(`${dependency.libName} 所有平台已存在,直接链接...`);
664
841
  tx.recordOp('link', localPath, storeCommitPath);
665
- await linker.linkLib(localPath, storeCommitPath, platforms);
666
- for (const platform of platforms) {
667
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
668
- if (!registry.getStore(storeKey)) {
669
- const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
670
- registry.addStore({
671
- libName: dependency.libName, commit: dependency.commit, platform,
672
- branch: dependency.branch, url: dependency.url, ...integrity,
673
- usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
674
- });
675
- }
676
- registry.addStoreReference(storeKey, projectHash);
677
- }
678
- await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
679
- const dlLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
680
- if (!registry.getLibrary(dlLibKey)) {
681
- let totalSize = 0;
682
- for (const platform of platforms) {
683
- totalSize += await store.getSize(dependency.libName, dependency.commit, platform);
684
- }
685
- registry.addLibrary({
686
- libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
687
- url: dependency.url, platforms, size: totalSize, referencedBy: [],
688
- createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
689
- });
690
- }
691
- pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 链接完成 [${platforms.join(', ')}]`);
692
- return { success: true, name: dependency.libName, downloadedPlatforms: platforms, skippedPlatforms: [] };
842
+ await linker.linkLib(localPath, storeCommitPath, existing);
843
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, existing, projectHash, false);
844
+ pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 链接完成 [${existing.join(', ')}]`);
845
+ return { success: true, name: dependency.libName, downloadedPlatforms: existing, skippedPlatforms: [] };
693
846
  }
694
847
  const dlLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
695
848
  const historyLib = registry.getLibrary(dlLibKey);
696
- const unavailablePlatforms = historyLib?.unavailablePlatforms || [];
849
+ const unavailablePlatforms = blockedPlatforms;
697
850
  const dlToDownload = missing.filter(p => !unavailablePlatforms.includes(p));
698
851
  const knownUnavailable = missing.filter(p => unavailablePlatforms.includes(p));
699
852
  if (dlToDownload.length === 0) {
700
853
  if (knownUnavailable.length > 0) {
701
- pLog.warn(`${dependency.libName} 平台 [${knownUnavailable.join(', ')}] 不支持(远程不存在)`);
854
+ pLog.warn(`${dependency.libName} 平台 [${knownUnavailable.join(', ')}] 已按规则跳过`);
702
855
  }
703
856
  return { success: false, name: dependency.libName, skipped: true, skippedPlatforms: missing, unsupported: true };
704
857
  }
@@ -725,52 +878,31 @@ async function linkScope(params) {
725
878
  pLog.hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
726
879
  }
727
880
  try {
728
- const isNewGeneral = (!dependency.sparse || isSparseOnlyCommon(dependency.sparse)) && downloadResult.platformDirs.length === 0;
729
- if (isNewGeneral) {
881
+ const assessment = assessDownloadResult(dlToDownload, dependency.sparse, downloadResult, configVars);
882
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, assessment.satisfiedRequested);
883
+ if (assessment.isPureGeneral) {
884
+ upsertLibraryAvailability(dependency, {
885
+ platforms: [GENERAL_PLATFORM],
886
+ isGeneral: true,
887
+ });
730
888
  tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
731
889
  await store.absorbGeneral(downloadResult.libDir, dependency.libName, dependency.commit);
732
890
  const sharedPath = path.join(storeCommitPath, '_shared');
733
891
  tx.recordOp('link', localPath, sharedPath);
734
892
  await linker.linkGeneral(localPath, sharedPath);
735
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
736
- if (!registry.getStore(storeKey)) {
737
- const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
738
- registry.addStore({
739
- libName: dependency.libName, commit: dependency.commit, platform: GENERAL_PLATFORM,
740
- branch: dependency.branch, url: dependency.url, ...integrity,
741
- usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
742
- });
743
- }
744
- registry.addStoreReference(storeKey, projectHash);
745
- const genLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
746
- if (!registry.getLibrary(genLibKey)) {
747
- const sharedSize = await getDirSize(sharedPath);
748
- registry.addLibrary({
749
- libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
750
- url: dependency.url, platforms: [GENERAL_PLATFORM], size: sharedSize,
751
- referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
752
- });
753
- }
893
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
754
894
  generalLibs.add(dependency.libName);
755
895
  pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,下载完成`);
756
896
  return { success: true, name: dependency.libName, downloadedPlatforms: [GENERAL_PLATFORM], skippedPlatforms: [], isGeneral: true };
757
897
  }
758
- const filteredDownloaded = downloadResult.platformDirs.filter(p => dlToDownload.includes(p));
759
- const newUnavailable = dlToDownload.filter(p => !filteredDownloaded.includes(p));
898
+ const filteredDownloaded = assessment.downloadedRequested;
899
+ const newUnavailable = assessment.unavailableRequested;
760
900
  if (newUnavailable.length > 0) {
761
- const updateLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
762
- if (historyLib) {
763
- const updatedUnavailable = [...new Set([...unavailablePlatforms, ...newUnavailable])];
764
- registry.updateLibrary(updateLibKey, { unavailablePlatforms: updatedUnavailable });
765
- }
766
- else {
767
- registry.addLibrary({
768
- libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
769
- url: dependency.url, platforms: [], size: 0, referencedBy: [],
770
- unavailablePlatforms: newUnavailable,
771
- createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
772
- });
773
- }
901
+ const updatedUnavailable = [...new Set([...unavailablePlatforms, ...newUnavailable])];
902
+ upsertLibraryAvailability(dependency, {
903
+ unavailablePlatforms: updatedUnavailable,
904
+ isGeneral: false,
905
+ });
774
906
  pLog.warn(`${dependency.libName} 平台 [${newUnavailable.join(', ')}] 远程不存在,已记录`);
775
907
  }
776
908
  if (filteredDownloaded.length > 0) {
@@ -779,65 +911,18 @@ async function linkScope(params) {
779
911
  await registerNestedLibraries(downloadAbsorbResult.nestedLibraries, projectHash);
780
912
  }
781
913
  const linkPlatforms = [...existing, ...filteredDownloaded];
782
- const isDownloadGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
783
- if (isDownloadGeneral) {
784
- const sharedPath = path.join(storeCommitPath, '_shared');
785
- tx.recordOp('link', localPath, sharedPath);
786
- await linker.linkGeneral(localPath, sharedPath);
787
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
788
- if (!registry.getStore(storeKey)) {
789
- const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, GENERAL_PLATFORM);
790
- registry.addStore({
791
- libName: dependency.libName, commit: dependency.commit, platform: GENERAL_PLATFORM,
792
- branch: dependency.branch, url: dependency.url, ...integrity,
793
- usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
794
- });
795
- }
796
- registry.addStoreReference(storeKey, projectHash);
797
- const genLibKey2 = registry.getLibraryKey(dependency.libName, dependency.commit);
798
- if (!registry.getLibrary(genLibKey2)) {
799
- const sharedSize = await getDirSize(sharedPath);
800
- registry.addLibrary({
801
- libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
802
- url: dependency.url, platforms: [GENERAL_PLATFORM], size: sharedSize,
803
- referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
804
- });
805
- }
806
- generalLibs.add(dependency.libName);
807
- pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - General 库,下载完成`);
808
- return { success: true, name: dependency.libName, downloadedPlatforms: [GENERAL_PLATFORM], skippedPlatforms: [], isGeneral: true };
809
- }
810
914
  if (linkPlatforms.length === 0) {
811
- return { success: false, name: dependency.libName, skipped: true, skippedPlatforms: platforms };
915
+ return {
916
+ success: false,
917
+ name: dependency.libName,
918
+ skipped: true,
919
+ skippedPlatforms: requestedPlatforms,
920
+ };
812
921
  }
813
922
  tx.recordOp('link', localPath, storeCommitPath);
814
923
  await linker.linkLib(localPath, storeCommitPath, linkPlatforms);
815
- for (const platform of linkPlatforms) {
816
- const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
817
- if (!registry.getStore(storeKey)) {
818
- const integrity = await store.captureIntegrity(dependency.libName, dependency.commit, platform);
819
- registry.addStore({
820
- libName: dependency.libName, commit: dependency.commit, platform,
821
- branch: dependency.branch, url: dependency.url, ...integrity,
822
- usedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
823
- });
824
- }
825
- registry.addStoreReference(storeKey, projectHash);
826
- }
827
- await registerSharedStore(dependency.libName, dependency.commit, dependency.branch, dependency.url);
828
- const finalLibKey = registry.getLibraryKey(dependency.libName, dependency.commit);
829
- if (!registry.getLibrary(finalLibKey)) {
830
- let totalSize = 0;
831
- for (const platform of linkPlatforms) {
832
- totalSize += await store.getSize(dependency.libName, dependency.commit, platform);
833
- }
834
- registry.addLibrary({
835
- libName: dependency.libName, commit: dependency.commit, branch: dependency.branch,
836
- url: dependency.url, platforms: linkPlatforms, size: totalSize,
837
- referencedBy: [], createdAt: new Date().toISOString(), lastAccess: new Date().toISOString(),
838
- });
839
- }
840
- const notLinkedPlatforms = platforms.filter((p) => !linkPlatforms.includes(p));
924
+ await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkPlatforms, projectHash, false);
925
+ const notLinkedPlatforms = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, linkPlatforms, configVars).missingRequested;
841
926
  pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 下载完成 [${linkPlatforms.join(', ')}]`);
842
927
  return { success: true, name: dependency.libName, downloadedPlatforms: linkPlatforms, skippedPlatforms: notLinkedPlatforms };
843
928
  }
@@ -902,20 +987,22 @@ async function linkScope(params) {
902
987
  // 10. 同步 cache 文件
903
988
  await syncCacheFile(configPath);
904
989
  // 11. 构建返回结果
905
- const primaryPlatform = platforms[0];
906
- const linkedDeps = classified
907
- .filter((c) => {
908
- if (c.status === DependencyStatus.MISSING)
909
- return downloadedLibs.includes(c.dependency.libName);
910
- return true;
911
- })
912
- .map((c) => ({
913
- libName: c.dependency.libName,
914
- commit: c.dependency.commit,
915
- platform: generalLibs.has(c.dependency.libName) ? GENERAL_PLATFORM : primaryPlatform,
916
- linkedPath: path.relative(projectRoot, c.localPath),
917
- scope,
918
- }));
990
+ const linkedDeps = [];
991
+ for (const item of classified) {
992
+ if (item.status === DependencyStatus.MISSING && !downloadedLibs.includes(item.dependency.libName)) {
993
+ continue;
994
+ }
995
+ const actualPlatforms = generalLibs.has(item.dependency.libName)
996
+ ? [GENERAL_PLATFORM]
997
+ : await resolveStoredPlatforms(item.dependency.libName, item.dependency.commit, platforms);
998
+ linkedDeps.push({
999
+ libName: item.dependency.libName,
1000
+ commit: item.dependency.commit,
1001
+ platform: actualPlatforms[0] ?? platforms[0],
1002
+ linkedPath: path.relative(projectRoot, item.localPath),
1003
+ scope,
1004
+ });
1005
+ }
919
1006
  return {
920
1007
  linkedDeps, nestedLinkedDeps, generalLibs, downloadedLibs, savedBytes, finalLinkPlatforms, stats,
921
1008
  };
@@ -929,7 +1016,7 @@ async function linkScope(params) {
929
1016
  * 执行链接操作
930
1017
  */
931
1018
  export async function linkProject(projectPath, options) {
932
- const absolutePath = resolvePath(projectPath);
1019
+ const { absolutePath, normalizedPath, configPath: discoveredConfigPath, } = await resolveProjectRootPath(projectPath);
933
1020
  // === 阶段 1: 初始化 ===
934
1021
  const cfg = await config.load();
935
1022
  if (cfg?.logLevel)
@@ -939,7 +1026,7 @@ export async function linkProject(projectPath, options) {
939
1026
  const concurrency = cfg?.concurrency ?? 5;
940
1027
  const registry = getRegistry();
941
1028
  await registry.load();
942
- const existingProject = registry.getProjectByPath(absolutePath);
1029
+ const existingProject = registry.getProjectByPath(normalizedPath);
943
1030
  const rememberedPlatforms = existingProject?.platforms;
944
1031
  // 确定平台列表
945
1032
  let platforms;
@@ -976,7 +1063,7 @@ export async function linkProject(projectPath, options) {
976
1063
  process.exit(EXIT_CODES.NOINPUT);
977
1064
  }
978
1065
  // 发现可选配置文件
979
- const thirdpartyDir = path.join(absolutePath, '3rdparty');
1066
+ const thirdpartyDir = path.join(normalizedPath, '3rdparty');
980
1067
  const configDiscovery = await findAllCodepacConfigs(thirdpartyDir);
981
1068
  let selectedOptionalConfigs = [];
982
1069
  if (configDiscovery && configDiscovery.optionalConfigs.length > 0) {
@@ -1016,8 +1103,7 @@ export async function linkProject(projectPath, options) {
1016
1103
  }
1017
1104
  }
1018
1105
  // 查找主配置文件
1019
- const { findCodepacConfig } = await import('../core/parser.js');
1020
- const mainConfigPath = await findCodepacConfig(absolutePath);
1106
+ const mainConfigPath = discoveredConfigPath;
1021
1107
  if (!mainConfigPath) {
1022
1108
  error(`找不到 codepac-dep.json 配置文件,已搜索: 3rdparty, .`);
1023
1109
  process.exit(EXIT_CODES.DATAERR);
@@ -1025,7 +1111,7 @@ export async function linkProject(projectPath, options) {
1025
1111
  // === 阶段 2: Submodule 检测 ===
1026
1112
  let selectedSubmodules = [];
1027
1113
  if (options.submodules !== false) {
1028
- const submoduleConfigs = await findSubmoduleConfigs(absolutePath);
1114
+ const submoduleConfigs = await findSubmoduleConfigs(normalizedPath);
1029
1115
  if (submoduleConfigs.length > 0) {
1030
1116
  selectedSubmodules = await selectSubmodules(submoduleConfigs, options, existingProject?.submodules);
1031
1117
  // 为选中的 submodule 处理可选配置
@@ -1041,7 +1127,7 @@ export async function linkProject(projectPath, options) {
1041
1127
  }
1042
1128
  }
1043
1129
  // === 阶段 3: 规范化 + 事务准备 ===
1044
- const normalizedRoot = normalizeProjectRoot(absolutePath, mainConfigPath);
1130
+ const normalizedRoot = normalizedPath;
1045
1131
  const wasNormalized = normalizedRoot !== absolutePath;
1046
1132
  if (wasNormalized) {
1047
1133
  info(`项目根目录规范化: ${absolutePath} → ${normalizedRoot}`);
@@ -1365,6 +1451,95 @@ async function registerSharedStore(libName, commit, branch = '', url = '') {
1365
1451
  debug(`注册 _shared: ${libName}:${commit.slice(0, 8)} (${formatSize(integrity.size)})`);
1366
1452
  return true;
1367
1453
  }
1454
+ async function ensureStoreInfo(libName, commit, platform, branch, url, projectHash) {
1455
+ const registry = getRegistry();
1456
+ const storeKey = registry.getStoreKey(libName, commit, platform);
1457
+ const existingEntry = registry.getStore(storeKey);
1458
+ if (!existingEntry) {
1459
+ const integrity = await store.captureIntegrity(libName, commit, platform);
1460
+ registry.addStore({
1461
+ libName,
1462
+ commit,
1463
+ platform,
1464
+ branch,
1465
+ url,
1466
+ ...integrity,
1467
+ usedBy: [],
1468
+ createdAt: new Date().toISOString(),
1469
+ lastAccess: new Date().toISOString(),
1470
+ });
1471
+ }
1472
+ else if (existingEntry.fileCount == null) {
1473
+ const integrity = await store.captureIntegrity(libName, commit, platform);
1474
+ registry.updateStore(storeKey, integrity);
1475
+ }
1476
+ if (projectHash) {
1477
+ registry.addStoreReference(storeKey, projectHash);
1478
+ }
1479
+ }
1480
+ async function syncLibraryInfo(libName, commit, branch, url, platforms, isGeneral) {
1481
+ const registry = getRegistry();
1482
+ const libKey = registry.getLibraryKey(libName, commit);
1483
+ const existingLibrary = registry.getLibrary(libKey);
1484
+ const normalizedPlatforms = [...new Set(platforms)];
1485
+ let totalSize = 0;
1486
+ for (const platform of normalizedPlatforms) {
1487
+ totalSize += await store.getSize(libName, commit, platform);
1488
+ }
1489
+ if (!existingLibrary) {
1490
+ registry.addLibrary({
1491
+ libName,
1492
+ commit,
1493
+ branch,
1494
+ url,
1495
+ platforms: normalizedPlatforms,
1496
+ size: totalSize,
1497
+ isGeneral,
1498
+ referencedBy: [],
1499
+ createdAt: new Date().toISOString(),
1500
+ lastAccess: new Date().toISOString(),
1501
+ });
1502
+ return;
1503
+ }
1504
+ registry.updateLibrary(libKey, {
1505
+ branch: branch || existingLibrary.branch,
1506
+ url: url || existingLibrary.url,
1507
+ platforms: normalizedPlatforms,
1508
+ size: totalSize,
1509
+ isGeneral,
1510
+ lastAccess: new Date().toISOString(),
1511
+ });
1512
+ }
1513
+ async function resolveStoredPlatforms(libName, commit, fallbackPlatforms) {
1514
+ const storePath = await config.getStorePath();
1515
+ if (!storePath) {
1516
+ return [...new Set(fallbackPlatforms)];
1517
+ }
1518
+ const commitPath = path.join(storePath, libName, commit);
1519
+ try {
1520
+ const entries = await fs.readdir(commitPath, { withFileTypes: true });
1521
+ const storedPlatforms = entries
1522
+ .filter(entry => entry.isDirectory() && KNOWN_PLATFORM_VALUES.includes(entry.name))
1523
+ .map(entry => entry.name);
1524
+ return storedPlatforms.length > 0 ? storedPlatforms : [...new Set(fallbackPlatforms)];
1525
+ }
1526
+ catch {
1527
+ return [...new Set(fallbackPlatforms)];
1528
+ }
1529
+ }
1530
+ async function ensureLinkedRegistryState(libName, commit, branch, url, platforms, projectHash, isGeneral) {
1531
+ const linkedPlatforms = isGeneral ? [GENERAL_PLATFORM] : [...new Set(platforms)];
1532
+ const libraryPlatforms = isGeneral
1533
+ ? [GENERAL_PLATFORM]
1534
+ : await resolveStoredPlatforms(libName, commit, linkedPlatforms);
1535
+ for (const platform of linkedPlatforms) {
1536
+ await ensureStoreInfo(libName, commit, platform, branch, url, projectHash);
1537
+ }
1538
+ if (!isGeneral) {
1539
+ await registerSharedStore(libName, commit, branch, url);
1540
+ }
1541
+ await syncLibraryInfo(libName, commit, branch, url, libraryPlatforms, isGeneral);
1542
+ }
1368
1543
  /**
1369
1544
  * 注册嵌套依赖到 Registry
1370
1545
  * absorbLib 递归吸收嵌套依赖时只做文件操作,此函数负责 Registry 注册
@@ -1405,6 +1580,7 @@ async function registerNestedLibraries(nestedLibraries, projectHash) {
1405
1580
  url: '',
1406
1581
  platforms: [GENERAL_PLATFORM],
1407
1582
  size: nested.size,
1583
+ isGeneral: true,
1408
1584
  referencedBy: [],
1409
1585
  createdAt: new Date().toISOString(),
1410
1586
  lastAccess: new Date().toISOString(),
@@ -1443,6 +1619,7 @@ async function registerNestedLibraries(nestedLibraries, projectHash) {
1443
1619
  url: '',
1444
1620
  platforms: nested.platforms,
1445
1621
  size: nested.size,
1622
+ isGeneral: false,
1446
1623
  referencedBy: [],
1447
1624
  createdAt: new Date().toISOString(),
1448
1625
  lastAccess: new Date().toISOString(),
@@ -1507,7 +1684,7 @@ export async function classifyLinkStatus(localPath, storeCommitPath, isGeneral,
1507
1684
  * @param configPath 配置文件路径
1508
1685
  * @param platforms 请求的平台列表
1509
1686
  */
1510
- async function classifyDependencies(dependencies, projectPath, configPath, platforms) {
1687
+ async function classifyDependencies(dependencies, projectPath, configPath, platforms, vars) {
1511
1688
  const result = [];
1512
1689
  const thirdPartyDir = path.dirname(configPath);
1513
1690
  const storePath = await store.getStorePath();
@@ -1518,8 +1695,10 @@ async function classifyDependencies(dependencies, projectPath, configPath, platf
1518
1695
  for (const dep of dependencies) {
1519
1696
  const localPath = path.join(thirdPartyDir, dep.libName);
1520
1697
  const storeLibPath = store.getLibraryPath(storePath, dep.libName, dep.commit, primaryPlatform);
1521
- // 检查 Store 中是否有任意请求的平台(而非只检查主平台)
1522
- const { existing: rawExisting } = await store.checkPlatformCompleteness(dep.libName, dep.commit, platforms);
1698
+ const blockedPlatforms = getBlockedRequestedPlatforms(getRegistry(), dep, platforms, vars);
1699
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
1700
+ // 检查 Store 中是否有任意请求的平台(支持 sparse 承载目录)
1701
+ const { actualExisting: rawExisting, satisfiedRequested, } = await resolveRequestedStoreState(dep, requestedPlatforms, vars);
1523
1702
  // 完整性校验:验证 existing 平台的文件完整性
1524
1703
  const { valid: verifiedExisting, corrupted } = await verifyExistingPlatforms(dep.libName, dep.commit, rawExisting);
1525
1704
  // 清除损坏的平台数据
@@ -1534,8 +1713,11 @@ async function classifyDependencies(dependencies, projectPath, configPath, platf
1534
1713
  }
1535
1714
  const existing = verifiedExisting;
1536
1715
  // 也检查是否为 General 库(有 _shared 且有内容)
1537
- const isGeneral = await store.isGeneralLib(dep.libName, dep.commit);
1716
+ const isGeneral = await resolveLibraryGeneralState(dep);
1538
1717
  const inStore = existing.length > 0 || isGeneral;
1718
+ if (satisfiedRequested.length > 0) {
1719
+ clearSatisfiedAutoUnavailablePlatforms(getRegistry(), dep, satisfiedRequested);
1720
+ }
1539
1721
  // 用于非 inStore 情况的本地路径状态检查
1540
1722
  const expectedTarget = isGeneral
1541
1723
  ? path.join(storePath, dep.libName, dep.commit, '_shared')
@@ -1617,15 +1799,17 @@ async function classifyDependencies(dependencies, projectPath, configPath, platf
1617
1799
  }
1618
1800
  async function supplementMissingPlatforms(dependency, platforms, registry, tx, options = {}) {
1619
1801
  const result = { downloaded: [], unavailable: [], nestedLibraries: [] };
1802
+ const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, options.vars);
1803
+ const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
1620
1804
  // 1. 检查平台完整性
1621
- const { missing } = await store.checkPlatformCompleteness(dependency.libName, dependency.commit, platforms);
1805
+ const { missingRequested, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, options.vars);
1806
+ const missing = missingRequested;
1622
1807
  if (missing.length === 0) {
1808
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
1623
1809
  return result;
1624
1810
  }
1625
1811
  // 2. 获取已知不可用平台
1626
- const libKey = registry.getLibraryKey(dependency.libName, dependency.commit);
1627
- const libInfo = registry.getLibrary(libKey);
1628
- const unavailablePlatforms = libInfo?.unavailablePlatforms || [];
1812
+ const unavailablePlatforms = blockedPlatforms;
1629
1813
  // 3. 计算需要下载的平台(排除已知不可用的,除非强制下载)
1630
1814
  const toDownload = options.forceDownload
1631
1815
  ? missing
@@ -1633,7 +1817,7 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
1633
1817
  if (toDownload.length === 0) {
1634
1818
  // 所有缺失平台都是已知不可用的
1635
1819
  if (missing.length > 0) {
1636
- hint(`${dependency.libName} 平台 [${missing.join(', ')}] 远程不存在(已记录)`);
1820
+ hint(`${dependency.libName} 平台 [${missing.join(', ')}] 已按规则跳过`);
1637
1821
  }
1638
1822
  result.unavailable = missing.filter((p) => unavailablePlatforms.includes(p));
1639
1823
  return result;
@@ -1655,18 +1839,20 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
1655
1839
  hint(` 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
1656
1840
  }
1657
1841
  try {
1658
- // 5. 检查实际下载了什么
1659
- const downloaded = downloadResult.platformDirs.filter((p) => toDownload.includes(p));
1842
+ // 5. 解释下载结果
1843
+ const assessment = assessDownloadResult(toDownload, dependency.sparse, downloadResult, options.vars);
1844
+ const downloaded = assessment.downloadedRequested;
1660
1845
  result.downloaded = downloaded;
1846
+ clearSatisfiedAutoUnavailablePlatforms(registry, dependency, assessment.satisfiedRequested);
1661
1847
  // 6. 记录新发现的不可用平台
1662
- const notFound = toDownload.filter((p) => !downloaded.includes(p));
1848
+ const notFound = assessment.unavailableRequested;
1663
1849
  if (notFound.length > 0) {
1664
1850
  result.unavailable = notFound;
1665
- // 更新 LibraryInfo(如果存在)
1666
- if (libInfo) {
1667
- const newUnavailable = [...new Set([...unavailablePlatforms, ...notFound])];
1668
- registry.updateLibrary(libKey, { unavailablePlatforms: newUnavailable });
1669
- }
1851
+ const newUnavailable = [...new Set([...unavailablePlatforms, ...notFound])];
1852
+ upsertLibraryAvailability(dependency, {
1853
+ unavailablePlatforms: newUnavailable,
1854
+ isGeneral: false,
1855
+ });
1670
1856
  warn(`${dependency.libName} 平台 [${notFound.join(', ')}] 远程不存在,已记录`);
1671
1857
  }
1672
1858
  // 7. 吸收下载的内容到 Store(不做链接,由调用者负责)
@@ -1919,7 +2105,6 @@ async function linkNestedDependencies(dependencies, params) {
1919
2105
  const nestedTargetDir = path.join(thirdPartyDir, targetDir);
1920
2106
  const { tx, registry, projectHash, projectRoot, dryRun, download, generalLibs, downloadedLibs, nestedLinkedDeps } = options;
1921
2107
  const { platforms, vars } = context;
1922
- const primaryPlatform = platforms[0];
1923
2108
  for (const dep of dependencies) {
1924
2109
  const localPath = path.join(nestedTargetDir, dep.libName);
1925
2110
  const storePath = await store.getStorePath();
@@ -1938,18 +2123,18 @@ async function linkNestedDependencies(dependencies, params) {
1938
2123
  // 检查 Store 状态和历史记录
1939
2124
  const libKey = registry.getLibraryKey(dep.libName, dep.commit);
1940
2125
  const historyLib = registry.getLibrary(libKey);
1941
- const unavailablePlatforms = historyLib?.unavailablePlatforms || [];
2126
+ const unavailablePlatforms = getBlockedRequestedPlatforms(registry, dep, platforms, vars);
1942
2127
  // 过滤掉已知不可用的平台
1943
2128
  const availablePlatforms = platforms.filter(p => !unavailablePlatforms.includes(p));
1944
2129
  const knownUnavailable = platforms.filter(p => unavailablePlatforms.includes(p));
1945
2130
  let storeHas = false;
1946
2131
  const existingPlatforms = [];
1947
- for (const p of availablePlatforms) {
1948
- if (await store.exists(dep.libName, dep.commit, p)) {
1949
- existingPlatforms.push(p);
1950
- storeHas = true;
1951
- }
2132
+ const initialStoreState = await resolveRequestedStoreState(dep, availablePlatforms, vars);
2133
+ existingPlatforms.push(...initialStoreState.actualExisting);
2134
+ if (initialStoreState.satisfiedRequested.length > 0) {
2135
+ clearSatisfiedAutoUnavailablePlatforms(registry, dep, initialStoreState.satisfiedRequested);
1952
2136
  }
2137
+ storeHas = existingPlatforms.length > 0;
1953
2138
  // 完整性校验:验证 existing 平台的文件完整性(与 classifyDependencies 同逻辑)
1954
2139
  if (existingPlatforms.length > 0) {
1955
2140
  const { valid: verifiedPlatforms, corrupted } = await verifyExistingPlatforms(dep.libName, dep.commit, existingPlatforms);
@@ -1963,10 +2148,10 @@ async function linkNestedDependencies(dependencies, params) {
1963
2148
  storeHas = verifiedPlatforms.length > 0;
1964
2149
  }
1965
2150
  }
1966
- let isGeneral = await store.isGeneralLib(dep.libName, dep.commit);
2151
+ let isGeneral = await resolveLibraryGeneralState(dep);
1967
2152
  // 如果所有平台都已知不可用,跳过
1968
2153
  if (availablePlatforms.length === 0 && knownUnavailable.length > 0 && !isGeneral) {
1969
- warn(`${indent} ${dep.libName} - 平台 [${knownUnavailable.join(', ')}] 不支持(远程不存在)`);
2154
+ warn(`${indent} ${dep.libName} - 平台 [${knownUnavailable.join(', ')}] 已按规则跳过`);
1970
2155
  continue;
1971
2156
  }
1972
2157
  // Store 没有,但本地是目录时,验证 commit 并尝试 absorb
@@ -2003,14 +2188,16 @@ async function linkNestedDependencies(dependencies, params) {
2003
2188
  sparse: dep.sparse,
2004
2189
  vars,
2005
2190
  });
2006
- if (downloadResult.platformDirs.length > 0) {
2191
+ const assessment = assessDownloadResult(availablePlatforms, dep.sparse, downloadResult, vars);
2192
+ clearSatisfiedAutoUnavailablePlatforms(registry, dep, assessment.satisfiedRequested);
2193
+ if (assessment.downloadedRequested.length > 0) {
2007
2194
  // 有平台内容 → 吸收平台目录,重新分类为平台库
2008
2195
  tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
2009
- const nestedAbsorbResult = await store.absorbLib(downloadResult.libDir, downloadResult.platformDirs, dep.libName, dep.commit);
2196
+ const nestedAbsorbResult = await store.absorbLib(downloadResult.libDir, assessment.downloadedRequested, dep.libName, dep.commit);
2010
2197
  await registerNestedLibraries(nestedAbsorbResult.nestedLibraries, projectHash);
2011
- existingPlatforms.push(...downloadResult.platformDirs);
2198
+ existingPlatforms.push(...assessment.downloadedRequested);
2012
2199
  isGeneral = false;
2013
- info(`${indent} ${dep.libName} - 补充平台内容 [${downloadResult.platformDirs.join(', ')}]`);
2200
+ info(`${indent} ${dep.libName} - 补充平台内容 [${assessment.downloadedRequested.join(', ')}]`);
2014
2201
  }
2015
2202
  if (downloadResult.cleanedPlatforms.length > 0) {
2016
2203
  hint(`${indent} 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
@@ -2036,6 +2223,7 @@ async function linkNestedDependencies(dependencies, params) {
2036
2223
  if (localExists && (storeHas || isGeneral)) {
2037
2224
  const linkStatus = await classifyLinkStatus(localPath, storeCommitPath, isGeneral, existingPlatforms);
2038
2225
  if (linkStatus === 'linked') {
2226
+ let linkedPlatforms = isGeneral ? [GENERAL_PLATFORM] : [...existingPlatforms];
2039
2227
  // 已链接,但需要检查是否需要补充缺失平台(与顶层依赖逻辑一致)
2040
2228
  if (!isGeneral) {
2041
2229
  const supplementResult = await supplementMissingPlatforms(dep, platforms, registry, tx, { vars });
@@ -2052,6 +2240,7 @@ async function linkNestedDependencies(dependencies, params) {
2052
2240
  }
2053
2241
  // 合并所有平台并重新链接
2054
2242
  const allPlatforms = [...existingPlatforms, ...supplementResult.downloaded];
2243
+ linkedPlatforms = allPlatforms;
2055
2244
  if (!dryRun) {
2056
2245
  tx.recordOp('link', localPath, storeCommitPath);
2057
2246
  await linker.linkLib(localPath, storeCommitPath, allPlatforms);
@@ -2059,10 +2248,11 @@ async function linkNestedDependencies(dependencies, params) {
2059
2248
  success(`${indent} ${dep.libName} - 重新链接完成 [${allPlatforms.join(', ')}]`);
2060
2249
  }
2061
2250
  }
2251
+ await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, linkedPlatforms, projectHash, isGeneral);
2062
2252
  nestedLinkedDeps.push({
2063
2253
  libName: dep.libName,
2064
2254
  commit: dep.commit,
2065
- platform: isGeneral ? GENERAL_PLATFORM : primaryPlatform,
2255
+ platform: isGeneral ? GENERAL_PLATFORM : (linkedPlatforms[0] ?? availablePlatforms[0]),
2066
2256
  linkedPath: path.relative(projectRoot, localPath),
2067
2257
  });
2068
2258
  if (!isGeneral) {
@@ -2094,6 +2284,7 @@ async function linkNestedDependencies(dependencies, params) {
2094
2284
  const sharedPath = path.join(storeCommitPath, '_shared');
2095
2285
  tx.recordOp('link', localPath, sharedPath);
2096
2286
  await linker.linkGeneral(localPath, sharedPath);
2287
+ await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, [GENERAL_PLATFORM], projectHash, true);
2097
2288
  generalLibs.add(dep.libName);
2098
2289
  // 记录到 nestedLinkedDeps
2099
2290
  nestedLinkedDeps.push({
@@ -2119,11 +2310,12 @@ async function linkNestedDependencies(dependencies, params) {
2119
2310
  }
2120
2311
  tx.recordOp('link', localPath, storeCommitPath);
2121
2312
  await linker.linkLib(localPath, storeCommitPath, allPlatforms);
2313
+ await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, allPlatforms, projectHash, false);
2122
2314
  // 记录到 nestedLinkedDeps
2123
2315
  nestedLinkedDeps.push({
2124
2316
  libName: dep.libName,
2125
2317
  commit: dep.commit,
2126
- platform: primaryPlatform,
2318
+ platform: allPlatforms[0],
2127
2319
  linkedPath: path.relative(projectRoot, localPath),
2128
2320
  });
2129
2321
  success(`${indent} ${dep.libName} - 链接完成 [${allPlatforms.join(', ')}]`);
@@ -2165,15 +2357,20 @@ async function linkNestedDependencies(dependencies, params) {
2165
2357
  if (downloadResult.cleanedPlatforms.length > 0) {
2166
2358
  hint(`${indent} 已过滤: ${downloadResult.cleanedPlatforms.join(', ')}`);
2167
2359
  }
2168
- // 检测是否为 General 库(没有 sparse 配置,或 sparse 只有 common,且没有平台目录)
2169
- const isNewGeneral = (!dep.sparse || isSparseOnlyCommon(dep.sparse)) && downloadResult.platformDirs.length === 0;
2170
- if (isNewGeneral) {
2360
+ const assessment = assessDownloadResult(availablePlatforms, dep.sparse, downloadResult, vars);
2361
+ clearSatisfiedAutoUnavailablePlatforms(registry, dep, assessment.satisfiedRequested);
2362
+ if (assessment.isPureGeneral) {
2171
2363
  // General 库处理
2364
+ upsertLibraryAvailability(dep, {
2365
+ platforms: [GENERAL_PLATFORM],
2366
+ isGeneral: true,
2367
+ });
2172
2368
  tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
2173
2369
  await store.absorbGeneral(downloadResult.libDir, dep.libName, dep.commit);
2174
2370
  const sharedPath = path.join(storeCommitPath, '_shared');
2175
2371
  tx.recordOp('link', localPath, sharedPath);
2176
2372
  await linker.linkGeneral(localPath, sharedPath);
2373
+ await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, [GENERAL_PLATFORM], projectHash, true);
2177
2374
  generalLibs.add(dep.libName);
2178
2375
  downloadedLibs.push(dep.libName);
2179
2376
  // 记录到 nestedLinkedDeps
@@ -2185,66 +2382,42 @@ async function linkNestedDependencies(dependencies, params) {
2185
2382
  });
2186
2383
  success(`${indent} ${dep.libName} - 下载完成 (General)`);
2187
2384
  }
2188
- else if (downloadResult.platformDirs.length > 0) {
2385
+ else if (assessment.downloadedRequested.length > 0) {
2189
2386
  // 平台库处理
2190
2387
  tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
2191
- const nestedAbsorbResult = await store.absorbLib(downloadResult.libDir, downloadResult.platformDirs, dep.libName, dep.commit);
2388
+ const nestedAbsorbResult = await store.absorbLib(downloadResult.libDir, assessment.downloadedRequested, dep.libName, dep.commit);
2192
2389
  await registerNestedLibraries(nestedAbsorbResult.nestedLibraries, projectHash);
2193
2390
  tx.recordOp('link', localPath, storeCommitPath);
2194
- await linker.linkLib(localPath, storeCommitPath, downloadResult.platformDirs);
2391
+ await linker.linkLib(localPath, storeCommitPath, assessment.downloadedRequested);
2392
+ await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, assessment.downloadedRequested, projectHash, false);
2195
2393
  downloadedLibs.push(dep.libName);
2196
2394
  // 记录到 nestedLinkedDeps
2197
2395
  nestedLinkedDeps.push({
2198
2396
  libName: dep.libName,
2199
2397
  commit: dep.commit,
2200
- platform: primaryPlatform,
2398
+ platform: assessment.downloadedRequested[0],
2201
2399
  linkedPath: path.relative(projectRoot, localPath),
2202
2400
  });
2203
2401
  // 检查并记录新发现的不可用平台
2204
- const newUnavailable = availablePlatforms.filter(p => !downloadResult.platformDirs.includes(p));
2402
+ const newUnavailable = assessment.unavailableRequested;
2205
2403
  if (newUnavailable.length > 0) {
2206
2404
  const updatedUnavailable = [...new Set([...unavailablePlatforms, ...newUnavailable])];
2207
- if (historyLib) {
2208
- registry.updateLibrary(libKey, { unavailablePlatforms: updatedUnavailable });
2209
- }
2210
- else {
2211
- registry.addLibrary({
2212
- libName: dep.libName,
2213
- commit: dep.commit,
2214
- branch: dep.branch,
2215
- url: dep.url,
2216
- platforms: downloadResult.platformDirs,
2217
- size: 0,
2218
- referencedBy: [],
2219
- unavailablePlatforms: newUnavailable,
2220
- createdAt: new Date().toISOString(),
2221
- lastAccess: new Date().toISOString(),
2222
- });
2223
- }
2405
+ upsertLibraryAvailability(dep, {
2406
+ unavailablePlatforms: updatedUnavailable,
2407
+ platforms: assessment.downloadedRequested,
2408
+ isGeneral: false,
2409
+ });
2224
2410
  warn(`${indent} ${dep.libName} 平台 [${newUnavailable.join(', ')}] 远程不存在,已记录`);
2225
2411
  }
2226
- success(`${indent} ${dep.libName} - 下载完成 [${downloadResult.platformDirs.join(', ')}]`);
2412
+ success(`${indent} ${dep.libName} - 下载完成 [${assessment.downloadedRequested.join(', ')}]`);
2227
2413
  }
2228
2414
  else {
2229
2415
  // 所有请求的平台都不可用,记录到 registry
2230
2416
  const newUnavailable = [...new Set([...unavailablePlatforms, ...availablePlatforms])];
2231
- if (historyLib) {
2232
- registry.updateLibrary(libKey, { unavailablePlatforms: newUnavailable });
2233
- }
2234
- else {
2235
- registry.addLibrary({
2236
- libName: dep.libName,
2237
- commit: dep.commit,
2238
- branch: dep.branch,
2239
- url: dep.url,
2240
- platforms: [],
2241
- size: 0,
2242
- referencedBy: [],
2243
- unavailablePlatforms: newUnavailable,
2244
- createdAt: new Date().toISOString(),
2245
- lastAccess: new Date().toISOString(),
2246
- });
2247
- }
2417
+ upsertLibraryAvailability(dep, {
2418
+ unavailablePlatforms: newUnavailable,
2419
+ isGeneral: false,
2420
+ });
2248
2421
  warn(`${indent} ${dep.libName} - 下载成功但平台 [${availablePlatforms.join(', ')}] 不可用,已记录`);
2249
2422
  }
2250
2423
  // 清理临时目录