tanmi-dock 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/check.d.ts +30 -4
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +453 -84
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +109 -35
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +9 -3
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/unlink.d.ts.map +1 -1
- package/dist/commands/unlink.js +9 -0
- package/dist/commands/unlink.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +4 -3
- package/dist/core/config.js.map +1 -1
- package/dist/core/space/calibration-executor.d.ts +2 -1
- package/dist/core/space/calibration-executor.d.ts.map +1 -1
- package/dist/core/space/calibration-executor.js +2 -0
- package/dist/core/space/calibration-executor.js.map +1 -1
- package/dist/core/space/inventory.d.ts +2 -1
- package/dist/core/space/inventory.d.ts.map +1 -1
- package/dist/core/space/inventory.js +23 -0
- package/dist/core/space/inventory.js.map +1 -1
- package/dist/core/space/service.d.ts +2 -2
- package/dist/core/space/service.d.ts.map +1 -1
- package/dist/core/space/service.js +2 -2
- package/dist/core/space/service.js.map +1 -1
- package/dist/core/space/types.d.ts +7 -0
- package/dist/core/space/types.d.ts.map +1 -1
- package/dist/core/space/types.js.map +1 -1
- package/dist/core/store-write-protection.d.ts +30 -0
- package/dist/core/store-write-protection.d.ts.map +1 -0
- package/dist/core/store-write-protection.js +320 -0
- package/dist/core/store-write-protection.js.map +1 -0
- package/dist/core/transaction.d.ts +12 -3
- package/dist/core/transaction.d.ts.map +1 -1
- package/dist/core/transaction.js +160 -16
- package/dist/core/transaction.js.map +1 -1
- package/dist/utils/fs-utils.d.ts.map +1 -1
- package/dist/utils/fs-utils.js +46 -9
- package/dist/utils/fs-utils.js.map +1 -1
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +43 -3
- package/dist/utils/logger.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/link.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import fs from 'fs/promises';
|
|
6
|
+
import crypto from 'crypto';
|
|
6
7
|
import { Command } from 'commander';
|
|
7
8
|
import { ensureInitialized } from '../core/guard.js';
|
|
8
9
|
import * as config from '../core/config.js';
|
|
@@ -26,6 +27,7 @@ import { withGlobalLock } from '../utils/global-lock.js';
|
|
|
26
27
|
import { selectPlatforms, parsePlatformArgs, selectOption, selectOptionalConfigs, PROMPT_CANCELLED } from '../utils/prompt.js';
|
|
27
28
|
import pLimit from 'p-limit';
|
|
28
29
|
import { EXIT_CODES } from '../utils/exit-codes.js';
|
|
30
|
+
import { assertPathOutsideStore, isMaterializedStoreDirectory, prepareWritableDirectory, } from '../core/store-write-protection.js';
|
|
29
31
|
/**
|
|
30
32
|
* 创建 link 命令
|
|
31
33
|
*/
|
|
@@ -719,7 +721,7 @@ async function linkScope(params) {
|
|
|
719
721
|
const linkedCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
720
722
|
const { actualExisting: allExisting, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
|
|
721
723
|
clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
|
|
722
|
-
tx.recordOp('link', localPath, linkedCommitPath);
|
|
724
|
+
await tx.recordOp('link', localPath, linkedCommitPath);
|
|
723
725
|
await linker.linkLib(localPath, linkedCommitPath, allExisting);
|
|
724
726
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, allExisting, projectHash, false);
|
|
725
727
|
success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 已补充平台 [${supplementResult.downloaded.join(', ')}]`);
|
|
@@ -728,6 +730,9 @@ async function linkScope(params) {
|
|
|
728
730
|
const { actualExisting: linkedExisting, satisfiedRequested } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
|
|
729
731
|
clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
|
|
730
732
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, isLinkedGeneral ? [GENERAL_PLATFORM] : linkedExisting, projectHash, isLinkedGeneral);
|
|
733
|
+
if (isLinkedGeneral) {
|
|
734
|
+
generalLibs.add(dependency.libName);
|
|
735
|
+
}
|
|
731
736
|
break;
|
|
732
737
|
}
|
|
733
738
|
case DependencyStatus.RELINK: {
|
|
@@ -736,10 +741,10 @@ async function linkScope(params) {
|
|
|
736
741
|
const relinkCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
737
742
|
const isRelinkGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
|
|
738
743
|
if (isRelinkGeneral) {
|
|
739
|
-
tx.recordOp('unlink', localPath);
|
|
744
|
+
await tx.recordOp('unlink', localPath);
|
|
740
745
|
await linker.unlink(localPath);
|
|
741
746
|
const sharedPath = path.join(relinkCommitPath, '_shared');
|
|
742
|
-
tx.recordOp('link', localPath, sharedPath);
|
|
747
|
+
await tx.recordOp('link', localPath, sharedPath);
|
|
743
748
|
await linker.linkGeneral(localPath, sharedPath);
|
|
744
749
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
|
|
745
750
|
generalLibs.add(dependency.libName);
|
|
@@ -774,9 +779,9 @@ async function linkScope(params) {
|
|
|
774
779
|
}
|
|
775
780
|
break;
|
|
776
781
|
}
|
|
777
|
-
tx.recordOp('unlink', localPath);
|
|
782
|
+
await tx.recordOp('unlink', localPath);
|
|
778
783
|
await linker.unlink(localPath);
|
|
779
|
-
tx.recordOp('link', localPath, relinkCommitPath);
|
|
784
|
+
await tx.recordOp('link', localPath, relinkCommitPath);
|
|
780
785
|
await linker.linkLib(localPath, relinkCommitPath, relinkExisting);
|
|
781
786
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, relinkExisting, projectHash, false);
|
|
782
787
|
if (relinkSupplementResult.downloaded.length > 0) {
|
|
@@ -796,7 +801,7 @@ async function linkScope(params) {
|
|
|
796
801
|
const isReplaceGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
|
|
797
802
|
if (isReplaceGeneral) {
|
|
798
803
|
const sharedPath = path.join(replaceCommitPath, '_shared');
|
|
799
|
-
tx.recordOp('replace', localPath, sharedPath);
|
|
804
|
+
await tx.recordOp('replace', localPath, sharedPath);
|
|
800
805
|
await linker.linkGeneral(localPath, sharedPath);
|
|
801
806
|
savedBytes += replaceSize;
|
|
802
807
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
|
|
@@ -832,7 +837,7 @@ async function linkScope(params) {
|
|
|
832
837
|
}
|
|
833
838
|
break;
|
|
834
839
|
}
|
|
835
|
-
tx.recordOp('replace', localPath, replaceCommitPath);
|
|
840
|
+
await tx.recordOp('replace', localPath, replaceCommitPath);
|
|
836
841
|
await linker.linkLib(localPath, replaceCommitPath, replaceExisting);
|
|
837
842
|
savedBytes += replaceSize;
|
|
838
843
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, replaceExisting, projectHash, false);
|
|
@@ -876,7 +881,7 @@ async function linkScope(params) {
|
|
|
876
881
|
const progressTracker = new ProgressTracker({
|
|
877
882
|
name: ` 移入 Store`, total: absorbSize, showSpeed: true,
|
|
878
883
|
});
|
|
879
|
-
tx.recordOp('absorb', storeCommitPath, localPath);
|
|
884
|
+
await tx.recordOp('absorb', storeCommitPath, localPath);
|
|
880
885
|
let progressStarted = false;
|
|
881
886
|
const absorbResult = await store.absorbLib(localPath, finalPlatforms, dependency.libName, dependency.commit, {
|
|
882
887
|
totalSize: absorbSize,
|
|
@@ -923,7 +928,7 @@ async function linkScope(params) {
|
|
|
923
928
|
if (absorbSupplementResult.downloaded.length > 0) {
|
|
924
929
|
absorbLinkPlatforms = [...absorbLinkPlatforms, ...absorbSupplementResult.downloaded];
|
|
925
930
|
}
|
|
926
|
-
tx.recordOp('link', localPath, storeCommitPath);
|
|
931
|
+
await tx.recordOp('link', localPath, storeCommitPath);
|
|
927
932
|
await linker.linkLib(localPath, storeCommitPath, absorbLinkPlatforms);
|
|
928
933
|
for (const platform of absorbLinkPlatforms) {
|
|
929
934
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
@@ -946,7 +951,7 @@ async function linkScope(params) {
|
|
|
946
951
|
break;
|
|
947
952
|
}
|
|
948
953
|
const { GENERAL_PLATFORM } = await import('../core/platform.js');
|
|
949
|
-
tx.recordOp('link', localPath, sharedPath);
|
|
954
|
+
await tx.recordOp('link', localPath, sharedPath);
|
|
950
955
|
await linker.linkGeneral(localPath, sharedPath);
|
|
951
956
|
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
952
957
|
if (!registry.getStore(storeKey)) {
|
|
@@ -1035,7 +1040,7 @@ async function linkScope(params) {
|
|
|
1035
1040
|
clearSatisfiedAutoUnavailablePlatforms(registry, dependency, assessment.satisfiedRequested);
|
|
1036
1041
|
const filteredDownloaded = assessment.downloadedRequested;
|
|
1037
1042
|
if (filteredDownloaded.length > 0) {
|
|
1038
|
-
tx.recordOp('absorb', linkNewCommitPath, downloadResult.libDir);
|
|
1043
|
+
await tx.recordOp('absorb', linkNewCommitPath, downloadResult.libDir);
|
|
1039
1044
|
const linkNewAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
|
|
1040
1045
|
await registerNestedLibraries(linkNewAbsorbResult.nestedLibraries, projectHash);
|
|
1041
1046
|
const repaired = filteredDownloaded.filter((platform) => repairRequiredSet.has(platform));
|
|
@@ -1053,7 +1058,7 @@ async function linkScope(params) {
|
|
|
1053
1058
|
const isLinkNewGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
|
|
1054
1059
|
if (isLinkNewGeneral) {
|
|
1055
1060
|
const sharedPath = path.join(linkNewCommitPath, '_shared');
|
|
1056
|
-
tx.recordOp('link', localPath, sharedPath);
|
|
1061
|
+
await tx.recordOp('link', localPath, sharedPath);
|
|
1057
1062
|
await linker.linkGeneral(localPath, sharedPath);
|
|
1058
1063
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
|
|
1059
1064
|
generalLibs.add(dependency.libName);
|
|
@@ -1085,7 +1090,7 @@ async function linkScope(params) {
|
|
|
1085
1090
|
break;
|
|
1086
1091
|
}
|
|
1087
1092
|
else {
|
|
1088
|
-
tx.recordOp('link', localPath, linkNewCommitPath);
|
|
1093
|
+
await tx.recordOp('link', localPath, linkNewCommitPath);
|
|
1089
1094
|
await linker.linkLib(localPath, linkNewCommitPath, linkNewExisting);
|
|
1090
1095
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkNewExisting, projectHash, false);
|
|
1091
1096
|
success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 创建链接 [${linkNewExisting.join(', ')}]`);
|
|
@@ -1154,7 +1159,7 @@ async function linkScope(params) {
|
|
|
1154
1159
|
};
|
|
1155
1160
|
}
|
|
1156
1161
|
pLog.info(`${dependency.libName} 所有平台已存在,直接链接...`);
|
|
1157
|
-
tx.recordOp('link', localPath, storeCommitPath);
|
|
1162
|
+
await tx.recordOp('link', localPath, storeCommitPath);
|
|
1158
1163
|
await linker.linkLib(localPath, storeCommitPath, existing);
|
|
1159
1164
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, existing, projectHash, false);
|
|
1160
1165
|
pLog.success(`${dependency.libName} (${dependency.commit.slice(0, 7)}) - 链接完成 [${existing.join(', ')}]`);
|
|
@@ -1215,10 +1220,10 @@ async function linkScope(params) {
|
|
|
1215
1220
|
platforms: [GENERAL_PLATFORM],
|
|
1216
1221
|
isGeneral: true,
|
|
1217
1222
|
});
|
|
1218
|
-
tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
1223
|
+
await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
1219
1224
|
await store.absorbGeneral(downloadResult.libDir, dependency.libName, dependency.commit);
|
|
1220
1225
|
const sharedPath = path.join(storeCommitPath, '_shared');
|
|
1221
|
-
tx.recordOp('link', localPath, sharedPath);
|
|
1226
|
+
await tx.recordOp('link', localPath, sharedPath);
|
|
1222
1227
|
await linker.linkGeneral(localPath, sharedPath);
|
|
1223
1228
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, [GENERAL_PLATFORM], projectHash, true);
|
|
1224
1229
|
generalLibs.add(dependency.libName);
|
|
@@ -1244,7 +1249,7 @@ async function linkScope(params) {
|
|
|
1244
1249
|
pLog.warn(`${dependency.libName} 平台 [${newUnavailable.join(', ')}] 远程不存在,已记录`);
|
|
1245
1250
|
}
|
|
1246
1251
|
if (filteredDownloaded.length > 0) {
|
|
1247
|
-
tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
1252
|
+
await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
1248
1253
|
const downloadAbsorbResult = await store.absorbLib(downloadResult.libDir, filteredDownloaded, dependency.libName, dependency.commit);
|
|
1249
1254
|
await registerNestedLibraries(downloadAbsorbResult.nestedLibraries, projectHash);
|
|
1250
1255
|
}
|
|
@@ -1261,7 +1266,7 @@ async function linkScope(params) {
|
|
|
1261
1266
|
skippedPlatforms: requestedPlatforms,
|
|
1262
1267
|
};
|
|
1263
1268
|
}
|
|
1264
|
-
tx.recordOp('link', localPath, storeCommitPath);
|
|
1269
|
+
await tx.recordOp('link', localPath, storeCommitPath);
|
|
1265
1270
|
await linker.linkLib(localPath, storeCommitPath, linkPlatforms);
|
|
1266
1271
|
await ensureLinkedRegistryState(dependency.libName, dependency.commit, dependency.branch, dependency.url, linkPlatforms, projectHash, false);
|
|
1267
1272
|
const notLinkedPlatforms = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, linkPlatforms, configVars).missingRequested;
|
|
@@ -1350,7 +1355,7 @@ async function linkScope(params) {
|
|
|
1350
1355
|
const thirdPartyDir = path.dirname(configPath);
|
|
1351
1356
|
for (const action of actions) {
|
|
1352
1357
|
await processAction(action, nestedContext, configPath, thirdPartyDir, {
|
|
1353
|
-
tx, registry, projectHash, projectRoot, dryRun, download, yes,
|
|
1358
|
+
tx, registry, projectHash, projectRoot, storePath, dryRun, download, yes,
|
|
1354
1359
|
gitLightweightDownload, skipActions: new Set(params.skipActions ?? []),
|
|
1355
1360
|
generalLibs, downloadedLibs, nestedLinkedDeps,
|
|
1356
1361
|
});
|
|
@@ -1358,7 +1363,7 @@ async function linkScope(params) {
|
|
|
1358
1363
|
}
|
|
1359
1364
|
// 10. 同步 cache 文件
|
|
1360
1365
|
for (const cacheConfigPath of cacheConfigPaths) {
|
|
1361
|
-
await syncCacheFile(cacheConfigPath);
|
|
1366
|
+
await syncCacheFile(cacheConfigPath, storePath, dryRun);
|
|
1362
1367
|
}
|
|
1363
1368
|
// 11. 构建返回结果
|
|
1364
1369
|
const linkedDeps = [];
|
|
@@ -2041,7 +2046,10 @@ export async function classifyLinkStatus(localPath, storeCommitPath, isGeneral,
|
|
|
2041
2046
|
case 'linked': return 'linked';
|
|
2042
2047
|
case 'wrong_link':
|
|
2043
2048
|
case 'broken_link': return 'relink';
|
|
2044
|
-
case 'directory':
|
|
2049
|
+
case 'directory':
|
|
2050
|
+
return (await isMaterializedStoreDirectory(localPath, expectedTarget))
|
|
2051
|
+
? 'linked'
|
|
2052
|
+
: 'replace';
|
|
2045
2053
|
case 'missing':
|
|
2046
2054
|
default: return 'link_new';
|
|
2047
2055
|
}
|
|
@@ -2279,7 +2287,7 @@ async function supplementMissingPlatforms(dependency, platforms, registry, tx, o
|
|
|
2279
2287
|
if (downloaded.length > 0) {
|
|
2280
2288
|
const storePath = await store.getStorePath();
|
|
2281
2289
|
const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
2282
|
-
tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
2290
|
+
await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
2283
2291
|
const supplementAbsorbResult = await store.absorbLib(downloadResult.libDir, downloaded, dependency.libName, dependency.commit);
|
|
2284
2292
|
// 收集嵌套依赖(由调用者负责注册)
|
|
2285
2293
|
result.nestedLibraries.push(...supplementAbsorbResult.nestedLibraries);
|
|
@@ -2377,24 +2385,69 @@ function getStatusKey(status) {
|
|
|
2377
2385
|
* 同步配置文件到 cache 目录
|
|
2378
2386
|
* 使 cmake 的 checkValid.js 检测能够通过
|
|
2379
2387
|
*/
|
|
2380
|
-
async function syncCacheFile(configPath) {
|
|
2388
|
+
async function syncCacheFile(configPath, storePath, dryRun = false) {
|
|
2381
2389
|
const configDir = path.dirname(configPath);
|
|
2382
2390
|
const cacheDir = path.join(configDir, '.cache');
|
|
2383
2391
|
const configFileName = path.basename(configPath);
|
|
2384
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
|
+
}
|
|
2385
2400
|
try {
|
|
2401
|
+
await assertPathOutsideStore(cacheDir, storePath, 'sync-action-cache-directory');
|
|
2386
2402
|
// 确保 .cache 目录存在
|
|
2387
2403
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
2388
|
-
//
|
|
2389
|
-
|
|
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
|
+
}
|
|
2390
2435
|
if (process.env.VERBOSE) {
|
|
2391
2436
|
info(`已同步 cache: ${configFileName} -> ${path.relative(configDir, cachePath)}`);
|
|
2392
2437
|
}
|
|
2438
|
+
debug(`[link-cache] 同步完成: config=${configPath}, cache=${cachePath}, state=written`);
|
|
2393
2439
|
}
|
|
2394
2440
|
catch (err) {
|
|
2395
2441
|
// cache 同步失败不应阻塞主流程,仅警告
|
|
2396
2442
|
warn(`cache 同步失败: ${configFileName} -> ${path.relative(configDir, cachePath)}: ${err.message}`);
|
|
2397
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
|
+
}
|
|
2398
2451
|
}
|
|
2399
2452
|
/**
|
|
2400
2453
|
* 处理单个 action(嵌套依赖)
|
|
@@ -2463,7 +2516,21 @@ async function processAction(action, context, parentConfigPath, parentTargetDir,
|
|
|
2463
2516
|
info(`${indent} 嵌套 action 命中 ${dependencies.length} 个依赖,继承平台: ${formatActionPlatforms(plan.inheritedPlatforms)}, 生效平台: ${formatActionPlatforms(plan.effectiveCodepacPlatforms)}`);
|
|
2464
2517
|
// 5. 合并变量
|
|
2465
2518
|
const mergedVars = { ...context.vars, ...vars };
|
|
2466
|
-
// 6.
|
|
2519
|
+
// 6. 写入前分别保护配置目录和目标目录。
|
|
2520
|
+
const configPreparation = await prepareWritableDirectory(path.dirname(plan.nestedConfigPath), {
|
|
2521
|
+
projectRoot: options.projectRoot,
|
|
2522
|
+
storePath: options.storePath,
|
|
2523
|
+
reason: `action-config:${plan.actionName ?? 'unnamed'}:depth-${context.depth}`,
|
|
2524
|
+
transaction: options.tx,
|
|
2525
|
+
dryRun: options.dryRun,
|
|
2526
|
+
});
|
|
2527
|
+
const targetPreparation = await prepareWritableDirectory(plan.nestedTargetDir, {
|
|
2528
|
+
projectRoot: options.projectRoot,
|
|
2529
|
+
storePath: options.storePath,
|
|
2530
|
+
reason: `action-target:${plan.actionName ?? 'unnamed'}:depth-${context.depth}`,
|
|
2531
|
+
transaction: options.tx,
|
|
2532
|
+
dryRun: options.dryRun,
|
|
2533
|
+
});
|
|
2467
2534
|
await linkNestedDependencies(dependencies, {
|
|
2468
2535
|
targetDir: plan.nestedTargetDir,
|
|
2469
2536
|
nestedConfigPath: plan.nestedConfigPath,
|
|
@@ -2472,7 +2539,7 @@ async function processAction(action, context, parentConfigPath, parentTargetDir,
|
|
|
2472
2539
|
indent,
|
|
2473
2540
|
});
|
|
2474
2541
|
// 同步嵌套配置文件的 cache(兼容 checkValid.js 检测)
|
|
2475
|
-
await syncCacheFile(plan.nestedConfigPath);
|
|
2542
|
+
await syncCacheFile(plan.nestedConfigPath, options.storePath, options.dryRun);
|
|
2476
2543
|
// 7. 递归处理嵌套 actions(如果没有 disable_action)
|
|
2477
2544
|
if (!plan.parsed.disableAction && nestedActions.length > 0) {
|
|
2478
2545
|
const nestedContext = {
|
|
@@ -2489,6 +2556,10 @@ async function processAction(action, context, parentConfigPath, parentTargetDir,
|
|
|
2489
2556
|
else if (plan.parsed.disableAction && nestedActions.length > 0) {
|
|
2490
2557
|
info(`${indent} 跳过后续嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=disable_action`);
|
|
2491
2558
|
}
|
|
2559
|
+
const materializedPaths = [
|
|
2560
|
+
...new Set([...configPreparation.materializedPaths, ...targetPreparation.materializedPaths]),
|
|
2561
|
+
];
|
|
2562
|
+
debug(`${indent}CodePac action 完成: name=${plan.actionName ?? '(未命名)'}, dependencies=${dependencies.length}, nestedActions=${nestedActions.length}, materialized=${materializedPaths.join(',') || 'none'}, output=${plan.nestedTargetDir}`);
|
|
2492
2563
|
}
|
|
2493
2564
|
function formatActionPlatforms(platforms) {
|
|
2494
2565
|
return platforms && platforms.length > 0 ? platforms.join(',') : 'all';
|
|
@@ -2555,6 +2626,9 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2555
2626
|
const { targetDir: nestedTargetDir, context, options, indent } = params;
|
|
2556
2627
|
const { tx, registry, projectHash, projectRoot, dryRun, download, generalLibs, downloadedLibs, nestedLinkedDeps } = options;
|
|
2557
2628
|
const { platforms, vars } = context;
|
|
2629
|
+
if (!dryRun) {
|
|
2630
|
+
await assertPathOutsideStore(nestedTargetDir, options.storePath, 'link-nested-dependencies');
|
|
2631
|
+
}
|
|
2558
2632
|
for (const dep of dependencies) {
|
|
2559
2633
|
const localPath = path.join(nestedTargetDir, dep.libName);
|
|
2560
2634
|
const storePath = await store.getStorePath();
|
|
@@ -2666,7 +2740,7 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2666
2740
|
clearSatisfiedAutoUnavailablePlatforms(registry, dep, assessment.satisfiedRequested);
|
|
2667
2741
|
if (assessment.downloadedRequested.length > 0) {
|
|
2668
2742
|
// 有平台内容 → 吸收平台目录,重新分类为平台库
|
|
2669
|
-
tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
2743
|
+
await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
2670
2744
|
const nestedAbsorbResult = await store.absorbLib(downloadResult.libDir, assessment.downloadedRequested, dep.libName, dep.commit);
|
|
2671
2745
|
await registerNestedLibraries(nestedAbsorbResult.nestedLibraries, projectHash);
|
|
2672
2746
|
existingPlatforms.push(...assessment.downloadedRequested);
|
|
@@ -2716,7 +2790,7 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2716
2790
|
const allPlatforms = [...existingPlatforms, ...supplementResult.downloaded];
|
|
2717
2791
|
linkedPlatforms = allPlatforms;
|
|
2718
2792
|
if (!dryRun) {
|
|
2719
|
-
tx.recordOp('link', localPath, storeCommitPath);
|
|
2793
|
+
await tx.recordOp('link', localPath, storeCommitPath);
|
|
2720
2794
|
await linker.linkLib(localPath, storeCommitPath, allPlatforms);
|
|
2721
2795
|
}
|
|
2722
2796
|
success(`${indent} ${dep.libName} - 重新链接完成 [${allPlatforms.join(', ')}]`);
|
|
@@ -2756,7 +2830,7 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2756
2830
|
}
|
|
2757
2831
|
if (isGeneral) {
|
|
2758
2832
|
const sharedPath = path.join(storeCommitPath, '_shared');
|
|
2759
|
-
tx.recordOp('link', localPath, sharedPath);
|
|
2833
|
+
await tx.recordOp('link', localPath, sharedPath);
|
|
2760
2834
|
await linker.linkGeneral(localPath, sharedPath);
|
|
2761
2835
|
await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, [GENERAL_PLATFORM], projectHash, true);
|
|
2762
2836
|
generalLibs.add(dep.libName);
|
|
@@ -2782,7 +2856,7 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2782
2856
|
if (supplementResult.downloaded.length > 0) {
|
|
2783
2857
|
success(`${indent} ${dep.libName} - 补充平台 [${supplementResult.downloaded.join(', ')}]`);
|
|
2784
2858
|
}
|
|
2785
|
-
tx.recordOp('link', localPath, storeCommitPath);
|
|
2859
|
+
await tx.recordOp('link', localPath, storeCommitPath);
|
|
2786
2860
|
await linker.linkLib(localPath, storeCommitPath, allPlatforms);
|
|
2787
2861
|
await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, allPlatforms, projectHash, false);
|
|
2788
2862
|
// 记录到 nestedLinkedDeps
|
|
@@ -2846,10 +2920,10 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2846
2920
|
platforms: [GENERAL_PLATFORM],
|
|
2847
2921
|
isGeneral: true,
|
|
2848
2922
|
});
|
|
2849
|
-
tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
2923
|
+
await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
2850
2924
|
await store.absorbGeneral(downloadResult.libDir, dep.libName, dep.commit);
|
|
2851
2925
|
const sharedPath = path.join(storeCommitPath, '_shared');
|
|
2852
|
-
tx.recordOp('link', localPath, sharedPath);
|
|
2926
|
+
await tx.recordOp('link', localPath, sharedPath);
|
|
2853
2927
|
await linker.linkGeneral(localPath, sharedPath);
|
|
2854
2928
|
await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, [GENERAL_PLATFORM], projectHash, true);
|
|
2855
2929
|
generalLibs.add(dep.libName);
|
|
@@ -2870,10 +2944,10 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2870
2944
|
}
|
|
2871
2945
|
else if (assessment.downloadedRequested.length > 0) {
|
|
2872
2946
|
// 平台库处理
|
|
2873
|
-
tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
2947
|
+
await tx.recordOp('absorb', storeCommitPath, downloadResult.libDir);
|
|
2874
2948
|
const nestedAbsorbResult = await store.absorbLib(downloadResult.libDir, assessment.downloadedRequested, dep.libName, dep.commit);
|
|
2875
2949
|
await registerNestedLibraries(nestedAbsorbResult.nestedLibraries, projectHash);
|
|
2876
|
-
tx.recordOp('link', localPath, storeCommitPath);
|
|
2950
|
+
await tx.recordOp('link', localPath, storeCommitPath);
|
|
2877
2951
|
await linker.linkLib(localPath, storeCommitPath, assessment.downloadedRequested);
|
|
2878
2952
|
await ensureLinkedRegistryState(dep.libName, dep.commit, dep.branch, dep.url, assessment.downloadedRequested, projectHash, false);
|
|
2879
2953
|
downloadedLibs.push(dep.libName);
|