tanmi-dock 0.10.0-beta → 1.0.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.
- package/README.md +8 -5
- package/dist/commands/check.d.ts +36 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +61 -7
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/link.d.ts +3 -2
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +373 -98
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/reset.d.ts.map +1 -1
- package/dist/commands/reset.js +46 -17
- package/dist/commands/reset.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +145 -20
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/unavailable.d.ts.map +1 -1
- package/dist/commands/unavailable.js +7 -2
- package/dist/commands/unavailable.js.map +1 -1
- package/dist/core/codepac.d.ts +44 -0
- package/dist/core/codepac.d.ts.map +1 -1
- package/dist/core/codepac.js +287 -5
- package/dist/core/codepac.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +1 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/linker.d.ts +7 -0
- package/dist/core/linker.d.ts.map +1 -1
- package/dist/core/linker.js +47 -0
- package/dist/core/linker.js.map +1 -1
- package/dist/core/parser.d.ts +30 -5
- package/dist/core/parser.d.ts.map +1 -1
- package/dist/core/parser.js +375 -56
- package/dist/core/parser.js.map +1 -1
- package/dist/core/store.d.ts +2 -0
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +32 -6
- package/dist/core/store.js.map +1 -1
- package/dist/types/index.d.ts +61 -10
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/git.d.ts +20 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +181 -4
- package/dist/utils/git.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/link.js
CHANGED
|
@@ -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,
|
|
10
|
+
import { parseProjectDependencies, getRelativeConfigPath, parseCodepacDep, extractActions, buildActionExecutionPlan, 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 { getPlatformHelpText, GENERAL_PLATFORM, SHARED_PLATFORM, pathsEqual, isSparseOnlyCommon, KNOWN_PLATFORM_VALUES, getRequestedPlatformTargets, } from '../core/platform.js';
|
|
16
|
+
import { getPlatformHelpText, GENERAL_PLATFORM, SHARED_PLATFORM, pathsEqual, isSparseOnlyCommon, KNOWN_PLATFORM_VALUES, getRequestedPlatformTargets, resolveSparseConfig, getBaseKeyForCodepac, } 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';
|
|
@@ -37,6 +37,7 @@ export function createLinkCommand() {
|
|
|
37
37
|
.option('--no-download', '不自动下载缺失库')
|
|
38
38
|
.option('--dry-run', '只显示将要执行的操作')
|
|
39
39
|
.option('--config <configs...>', '指定可选配置文件 (如 codepac-dep-inner.json)')
|
|
40
|
+
.option('--skip-action, --skip_action, -sa <names...>', '按 action.name 跳过指定嵌套 action')
|
|
40
41
|
.option('--no-submodules', '不检测 git submodule 依赖')
|
|
41
42
|
.addHelpText('after', `${getPlatformHelpText()}
|
|
42
43
|
|
|
@@ -48,6 +49,7 @@ export function createLinkCommand() {
|
|
|
48
49
|
td link --dry-run 预览操作,不实际执行
|
|
49
50
|
td link -y 跳过确认,自动执行
|
|
50
51
|
td link --config codepac-dep-inner.json 使用指定的可选配置
|
|
52
|
+
td link --skip-action prepareNested 跳过指定 action.name
|
|
51
53
|
td link --no-submodules 跳过 git submodule 检测`)
|
|
52
54
|
.action(async (projectPath, options) => {
|
|
53
55
|
await ensureInitialized();
|
|
@@ -97,8 +99,20 @@ async function resolveRequestedStoreState(dependency, requestedPlatforms, vars)
|
|
|
97
99
|
if (await store.exists(dependency.libName, dependency.commit, platform)) {
|
|
98
100
|
resolvedExisting.push(platform);
|
|
99
101
|
}
|
|
102
|
+
else {
|
|
103
|
+
const registry = getRegistry();
|
|
104
|
+
const storeKey = registry.getStoreKey(dependency.libName, dependency.commit, platform);
|
|
105
|
+
if (registry.getStore(storeKey)) {
|
|
106
|
+
registry.removeStore(storeKey);
|
|
107
|
+
const storePath = await store.getStorePath();
|
|
108
|
+
const platformPath = store.getLibraryPath(storePath, dependency.libName, dependency.commit, platform);
|
|
109
|
+
await fs.rm(platformPath, { recursive: true, force: true }).catch(() => { });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
100
112
|
}
|
|
101
|
-
|
|
113
|
+
const result = resolveRequestedPlatformsByActual(requestedPlatforms, dependency.sparse, resolvedExisting, vars);
|
|
114
|
+
debug(`[link-store-state] ${dependency.libName}@${dependency.commit.slice(0, 7)} 请求平台=${requestedPlatforms.join(', ') || '空'}, Store候选=${candidatePlatforms.join(', ') || '空'}, 实际存在=${result.actualExisting.join(', ') || '无'}, 满足请求=${result.satisfiedRequested.join(', ') || '无'}, 缺失请求=${result.missingRequested.join(', ') || '无'}`);
|
|
115
|
+
return result;
|
|
102
116
|
}
|
|
103
117
|
function getBlockedRequestedPlatforms(registry, dependency, requestedPlatforms, vars) {
|
|
104
118
|
const { manualPlatforms, autoPlatforms } = resolveUnavailablePlatforms(registry, dependency);
|
|
@@ -109,7 +123,9 @@ function getBlockedRequestedPlatforms(registry, dependency, requestedPlatforms,
|
|
|
109
123
|
const targets = getRequestedPlatformTargets(platform, dependency.sparse, vars);
|
|
110
124
|
return targets.length === 1 && targets[0] === platform;
|
|
111
125
|
});
|
|
112
|
-
|
|
126
|
+
const blocked = [...new Set([...manualBlocked, ...autoBlocked])];
|
|
127
|
+
debug(`[link-platforms] ${dependency.libName}@${dependency.commit.slice(0, 7)} 请求平台=${requestedPlatforms.join(', ') || '空'}, 手动跳过=${manualBlocked.join(', ') || '无'}, 自动跳过=${autoBlocked.join(', ') || '无'}, 最终跳过=${blocked.join(', ') || '无'}`);
|
|
128
|
+
return blocked;
|
|
113
129
|
}
|
|
114
130
|
function clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested) {
|
|
115
131
|
if (satisfiedRequested.length === 0)
|
|
@@ -154,12 +170,93 @@ function upsertLibraryAvailability(dependency, updates) {
|
|
|
154
170
|
lastAccess: new Date().toISOString(),
|
|
155
171
|
});
|
|
156
172
|
}
|
|
157
|
-
export async function resolveLibraryGeneralState(dependency) {
|
|
173
|
+
export async function resolveLibraryGeneralState(dependency, requestedPlatforms = [], vars) {
|
|
158
174
|
const registry = getRegistry();
|
|
159
175
|
const libKey = registry.getLibraryKey(dependency.libName, dependency.commit);
|
|
160
176
|
const library = registry.getLibrary(libKey);
|
|
177
|
+
const sparseConfig = resolveSparseConfig(dependency.sparse, vars);
|
|
178
|
+
const hasPlatformSparse = sparseConfig
|
|
179
|
+
? Object.keys(sparseConfig).some((key) => key !== 'common')
|
|
180
|
+
: Boolean(dependency.sparse && !isSparseOnlyCommon(dependency.sparse));
|
|
181
|
+
const hasPlatformLibraryHistory = requestedPlatforms.length > 0
|
|
182
|
+
&& library?.isGeneral === false
|
|
183
|
+
&& library.platforms.some((platform) => platform !== GENERAL_PLATFORM);
|
|
184
|
+
if (requestedPlatforms.length > 0) {
|
|
185
|
+
const requestedState = await resolveRequestedStoreState(dependency, requestedPlatforms, vars);
|
|
186
|
+
if (requestedState.actualExisting.length > 0) {
|
|
187
|
+
if (library?.isGeneral === true) {
|
|
188
|
+
registry.updateLibrary(libKey, {
|
|
189
|
+
platforms: requestedState.actualExisting,
|
|
190
|
+
isGeneral: false,
|
|
191
|
+
lastAccess: new Date().toISOString(),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (requestedPlatforms.length > 0 && library?.isGeneral === true) {
|
|
198
|
+
const requestedTargets = [...new Set(requestedPlatforms.flatMap((platform) => getRequestedPlatformTargets(platform, dependency.sparse, vars)))];
|
|
199
|
+
const storePath = await store.getStorePath();
|
|
200
|
+
const commitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
201
|
+
try {
|
|
202
|
+
const entries = await fs.readdir(commitPath, { withFileTypes: true });
|
|
203
|
+
let hasRequestedPlatformDir = false;
|
|
204
|
+
for (const entry of entries) {
|
|
205
|
+
if (!entry.isDirectory() || !requestedTargets.includes(entry.name))
|
|
206
|
+
continue;
|
|
207
|
+
if (await store.exists(dependency.libName, dependency.commit, entry.name)) {
|
|
208
|
+
hasRequestedPlatformDir = true;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (hasRequestedPlatformDir) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Store 目录不存在时继续使用历史记录或后续 General 检测
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (requestedPlatforms.length > 0 && hasPlatformSparse) {
|
|
221
|
+
if (library?.isGeneral === true) {
|
|
222
|
+
registry.updateLibrary(libKey, {
|
|
223
|
+
isGeneral: false,
|
|
224
|
+
lastAccess: new Date().toISOString(),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
if (hasPlatformLibraryHistory) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
161
232
|
if (typeof library?.isGeneral === 'boolean') {
|
|
162
|
-
|
|
233
|
+
if (library.isGeneral) {
|
|
234
|
+
const hasGeneralContent = await store.exists(dependency.libName, dependency.commit, GENERAL_PLATFORM);
|
|
235
|
+
if (!hasGeneralContent) {
|
|
236
|
+
registry.updateLibrary(libKey, {
|
|
237
|
+
isGeneral: false,
|
|
238
|
+
lastAccess: new Date().toISOString(),
|
|
239
|
+
});
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
const actualGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
|
|
243
|
+
if (!actualGeneral) {
|
|
244
|
+
registry.updateLibrary(libKey, {
|
|
245
|
+
isGeneral: false,
|
|
246
|
+
lastAccess: new Date().toISOString(),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return actualGeneral;
|
|
250
|
+
}
|
|
251
|
+
const actualGeneral = await store.isGeneralLib(dependency.libName, dependency.commit);
|
|
252
|
+
if (actualGeneral) {
|
|
253
|
+
registry.updateLibrary(libKey, {
|
|
254
|
+
platforms: [GENERAL_PLATFORM],
|
|
255
|
+
isGeneral: true,
|
|
256
|
+
lastAccess: new Date().toISOString(),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return actualGeneral;
|
|
163
260
|
}
|
|
164
261
|
return store.isGeneralLib(dependency.libName, dependency.commit);
|
|
165
262
|
}
|
|
@@ -173,7 +270,7 @@ export function resolveUnavailablePlatforms(registry, dependency) {
|
|
|
173
270
|
autoPlatforms,
|
|
174
271
|
};
|
|
175
272
|
}
|
|
176
|
-
export async function collectProjectDependencyGraph(projectRoot) {
|
|
273
|
+
export async function collectProjectDependencyGraph(projectRoot, codepacPlatforms = []) {
|
|
177
274
|
const { normalizedPath, configPath } = await resolveProjectRootPath(projectRoot);
|
|
178
275
|
if (!configPath) {
|
|
179
276
|
throw new Error('只能在项目目录下使用');
|
|
@@ -182,11 +279,17 @@ export async function collectProjectDependencyGraph(projectRoot) {
|
|
|
182
279
|
const seenDeps = new Set();
|
|
183
280
|
const visitedConfigs = new Set();
|
|
184
281
|
const queue = [
|
|
185
|
-
{ configPath, source: 'direct' },
|
|
282
|
+
{ configPath, targetDir: path.dirname(configPath), codepacPlatforms, source: 'direct' },
|
|
186
283
|
];
|
|
187
|
-
const submodules = await findSubmoduleConfigs(normalizedPath);
|
|
284
|
+
const submodules = await findSubmoduleConfigs(normalizedPath, '', 0, codepacPlatforms);
|
|
188
285
|
for (const submodule of submodules) {
|
|
189
|
-
queue.push({
|
|
286
|
+
queue.push({
|
|
287
|
+
configPath: submodule.configPath,
|
|
288
|
+
targetDir: path.dirname(submodule.configPath),
|
|
289
|
+
codepacPlatforms,
|
|
290
|
+
scope: submodule.relativePath,
|
|
291
|
+
source: 'submodule',
|
|
292
|
+
});
|
|
190
293
|
}
|
|
191
294
|
while (queue.length > 0) {
|
|
192
295
|
const current = queue.shift();
|
|
@@ -200,25 +303,32 @@ export async function collectProjectDependencyGraph(projectRoot) {
|
|
|
200
303
|
catch {
|
|
201
304
|
continue;
|
|
202
305
|
}
|
|
203
|
-
|
|
306
|
+
const extractOptions = { platforms: current.codepacPlatforms, configPath: current.configPath };
|
|
307
|
+
for (const dependency of extractDependencies(parsedConfig, extractOptions)) {
|
|
204
308
|
const key = `${dependency.libName}:${dependency.commit}:${current.scope ?? ''}`;
|
|
205
309
|
if (!seenDeps.has(key)) {
|
|
206
310
|
seenDeps.add(key);
|
|
207
311
|
results.push({ ...dependency, scope: current.scope, source: current.source });
|
|
208
312
|
}
|
|
209
313
|
}
|
|
210
|
-
for (const action of extractActions(parsedConfig)) {
|
|
211
|
-
let
|
|
314
|
+
for (const action of extractActions(parsedConfig, extractOptions)) {
|
|
315
|
+
let plan;
|
|
212
316
|
try {
|
|
213
|
-
|
|
317
|
+
plan = buildActionExecutionPlan(action, {
|
|
318
|
+
parentConfigPath: current.configPath,
|
|
319
|
+
parentTargetDir: current.targetDir,
|
|
320
|
+
inheritedCodepacPlatforms: current.codepacPlatforms,
|
|
321
|
+
});
|
|
214
322
|
}
|
|
215
323
|
catch {
|
|
216
324
|
continue;
|
|
217
325
|
}
|
|
218
|
-
const nestedConfigPath = path.resolve(path.dirname(current.configPath), parsedAction.configDir, 'codepac-dep.json');
|
|
219
326
|
let nested;
|
|
220
327
|
try {
|
|
221
|
-
nested = await extractNestedDependencies(nestedConfigPath,
|
|
328
|
+
nested = await extractNestedDependencies(plan.nestedConfigPath, plan.libraries, {
|
|
329
|
+
platforms: plan.effectiveCodepacPlatforms,
|
|
330
|
+
configPath: plan.nestedConfigPath,
|
|
331
|
+
});
|
|
222
332
|
}
|
|
223
333
|
catch {
|
|
224
334
|
continue;
|
|
@@ -230,7 +340,15 @@ export async function collectProjectDependencyGraph(projectRoot) {
|
|
|
230
340
|
results.push({ ...dependency, scope: current.scope, source: 'nested' });
|
|
231
341
|
}
|
|
232
342
|
}
|
|
233
|
-
|
|
343
|
+
if (!plan.parsed.disableAction) {
|
|
344
|
+
queue.push({
|
|
345
|
+
configPath: plan.nestedConfigPath,
|
|
346
|
+
targetDir: plan.nestedTargetDir,
|
|
347
|
+
codepacPlatforms: plan.effectiveCodepacPlatforms,
|
|
348
|
+
scope: current.scope,
|
|
349
|
+
source: 'nested',
|
|
350
|
+
});
|
|
351
|
+
}
|
|
234
352
|
}
|
|
235
353
|
}
|
|
236
354
|
return results;
|
|
@@ -276,6 +394,16 @@ async function selectSubmodules(configs, options, remembered) {
|
|
|
276
394
|
}
|
|
277
395
|
return selected.map(s => ({ ...s }));
|
|
278
396
|
}
|
|
397
|
+
function buildSubmoduleOptionalConfigState(submodules) {
|
|
398
|
+
const state = {};
|
|
399
|
+
for (const submodule of submodules) {
|
|
400
|
+
const names = submodule.selectedOptionalConfigs?.map((config) => config.name) ?? [];
|
|
401
|
+
if (names.length > 0) {
|
|
402
|
+
state[submodule.relativePath] = names;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return Object.keys(state).length > 0 ? state : undefined;
|
|
406
|
+
}
|
|
279
407
|
/**
|
|
280
408
|
* 单个 scope(主项目或 submodule)的链接处理
|
|
281
409
|
*
|
|
@@ -284,6 +412,7 @@ async function selectSubmodules(configs, options, remembered) {
|
|
|
284
412
|
async function linkScope(params) {
|
|
285
413
|
const { scopeName, configPath, platforms, scanExtraPlatforms, registry, tx, projectHash, projectRoot, storePath, download, dryRun, yes, concurrency, gitLightweightDownload, optionalConfigs, scope, } = params;
|
|
286
414
|
let finalLinkPlatforms = [...params.finalLinkPlatforms];
|
|
415
|
+
const codepacPlatforms = [...new Set(platforms.map((platform) => getBaseKeyForCodepac(platform)))];
|
|
287
416
|
// 记录 General 类型库
|
|
288
417
|
const generalLibs = new Set();
|
|
289
418
|
const downloadedLibs = [];
|
|
@@ -292,17 +421,32 @@ async function linkScope(params) {
|
|
|
292
421
|
info(`分析 ${scopeName}: ${configPath}`);
|
|
293
422
|
let dependencies;
|
|
294
423
|
let configVars;
|
|
424
|
+
const selectedActions = [];
|
|
425
|
+
const cacheConfigPaths = [configPath];
|
|
295
426
|
try {
|
|
296
|
-
const result = await parseProjectDependencies(path.dirname(path.dirname(configPath))
|
|
427
|
+
const result = await parseProjectDependencies(path.dirname(path.dirname(configPath)), {
|
|
428
|
+
platforms: codepacPlatforms,
|
|
429
|
+
});
|
|
297
430
|
dependencies = result.dependencies;
|
|
298
431
|
configVars = result.vars;
|
|
432
|
+
const topLevelConfig = await parseCodepacDep(configPath);
|
|
433
|
+
selectedActions.push(...extractActions(topLevelConfig, { platforms: codepacPlatforms, configPath }));
|
|
299
434
|
// 合并可选配置依赖
|
|
300
435
|
if (optionalConfigs && optionalConfigs.length > 0) {
|
|
301
436
|
for (const optionalConfig of optionalConfigs) {
|
|
302
437
|
try {
|
|
303
438
|
const optionalResult = await parseCodepacDep(optionalConfig.path);
|
|
304
|
-
const optionalDeps = extractDependencies(optionalResult
|
|
439
|
+
const optionalDeps = extractDependencies(optionalResult, {
|
|
440
|
+
platforms: codepacPlatforms,
|
|
441
|
+
configPath: optionalConfig.path,
|
|
442
|
+
});
|
|
305
443
|
dependencies = mergeDepLists(dependencies, optionalDeps);
|
|
444
|
+
configVars = { ...configVars, ...optionalResult.vars };
|
|
445
|
+
selectedActions.push(...extractActions(optionalResult, {
|
|
446
|
+
platforms: codepacPlatforms,
|
|
447
|
+
configPath: optionalConfig.path,
|
|
448
|
+
}));
|
|
449
|
+
cacheConfigPaths.push(optionalConfig.path);
|
|
306
450
|
info(` + ${optionalConfig.name}: ${optionalDeps.length} 个依赖`);
|
|
307
451
|
}
|
|
308
452
|
catch (optErr) {
|
|
@@ -463,7 +607,7 @@ async function linkScope(params) {
|
|
|
463
607
|
case DependencyStatus.LINKED: {
|
|
464
608
|
const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
|
|
465
609
|
const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
|
|
466
|
-
const isLinkedGeneral = await resolveLibraryGeneralState(dependency);
|
|
610
|
+
const isLinkedGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
|
|
467
611
|
if (!isLinkedGeneral) {
|
|
468
612
|
const supplementResult = await supplementMissingPlatforms(dependency, platforms, registry, tx, {
|
|
469
613
|
vars: configVars,
|
|
@@ -489,7 +633,7 @@ async function linkScope(params) {
|
|
|
489
633
|
const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
|
|
490
634
|
const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
|
|
491
635
|
const relinkCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
492
|
-
const isRelinkGeneral = await resolveLibraryGeneralState(dependency);
|
|
636
|
+
const isRelinkGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
|
|
493
637
|
if (isRelinkGeneral) {
|
|
494
638
|
tx.recordOp('unlink', localPath);
|
|
495
639
|
await linker.unlink(localPath);
|
|
@@ -547,7 +691,7 @@ async function linkScope(params) {
|
|
|
547
691
|
const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
|
|
548
692
|
const replaceSize = await getDirSize(localPath);
|
|
549
693
|
const replaceCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
550
|
-
const isReplaceGeneral = await resolveLibraryGeneralState(dependency);
|
|
694
|
+
const isReplaceGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
|
|
551
695
|
if (isReplaceGeneral) {
|
|
552
696
|
const sharedPath = path.join(replaceCommitPath, '_shared');
|
|
553
697
|
tx.recordOp('replace', localPath, sharedPath);
|
|
@@ -776,7 +920,7 @@ async function linkScope(params) {
|
|
|
776
920
|
}
|
|
777
921
|
const { actualExisting: linkNewExisting, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
|
|
778
922
|
clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
|
|
779
|
-
const isLinkNewGeneral = await resolveLibraryGeneralState(dependency);
|
|
923
|
+
const isLinkNewGeneral = await resolveLibraryGeneralState(dependency, requestedPlatforms, configVars);
|
|
780
924
|
if (isLinkNewGeneral) {
|
|
781
925
|
const sharedPath = path.join(linkNewCommitPath, '_shared');
|
|
782
926
|
tx.recordOp('link', localPath, sharedPath);
|
|
@@ -845,9 +989,34 @@ async function linkScope(params) {
|
|
|
845
989
|
const storeCommitPath = path.join(storePath, dependency.libName, dependency.commit);
|
|
846
990
|
const blockedPlatforms = getBlockedRequestedPlatforms(registry, dependency, platforms, configVars);
|
|
847
991
|
const requestedPlatforms = platforms.filter((platform) => !blockedPlatforms.includes(platform));
|
|
992
|
+
if (requestedPlatforms.length === 0) {
|
|
993
|
+
pLog.warn(`${dependency.libName} 请求平台 [${platforms.join(', ')}] 均已按规则跳过,没有可链接产物`);
|
|
994
|
+
if (await linker.cleanupEmptyLocalDirectory(localPath)) {
|
|
995
|
+
pLog.warn(`${dependency.libName} 已清理历史遗留的空本地目录`);
|
|
996
|
+
}
|
|
997
|
+
return {
|
|
998
|
+
success: false,
|
|
999
|
+
name: dependency.libName,
|
|
1000
|
+
skipped: true,
|
|
1001
|
+
skippedPlatforms: blockedPlatforms,
|
|
1002
|
+
unsupported: true,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
848
1005
|
const { actualExisting: existing, missingRequested: missing, satisfiedRequested, } = await resolveRequestedStoreState(dependency, requestedPlatforms, configVars);
|
|
849
1006
|
clearSatisfiedAutoUnavailablePlatforms(registry, dependency, satisfiedRequested);
|
|
850
1007
|
if (missing.length === 0) {
|
|
1008
|
+
if (existing.length === 0) {
|
|
1009
|
+
pLog.warn(`${dependency.libName} 请求平台 [${requestedPlatforms.join(', ')}] 在 Store 中没有可链接产物,已拒绝创建空目录`);
|
|
1010
|
+
if (await linker.cleanupEmptyLocalDirectory(localPath)) {
|
|
1011
|
+
pLog.warn(`${dependency.libName} 已清理历史遗留的空本地目录`);
|
|
1012
|
+
}
|
|
1013
|
+
return {
|
|
1014
|
+
success: false,
|
|
1015
|
+
name: dependency.libName,
|
|
1016
|
+
skipped: true,
|
|
1017
|
+
skippedPlatforms: requestedPlatforms,
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
851
1020
|
pLog.info(`${dependency.libName} 所有平台已存在,直接链接...`);
|
|
852
1021
|
tx.recordOp('link', localPath, storeCommitPath);
|
|
853
1022
|
await linker.linkLib(localPath, storeCommitPath, existing);
|
|
@@ -864,6 +1033,9 @@ async function linkScope(params) {
|
|
|
864
1033
|
if (knownUnavailable.length > 0) {
|
|
865
1034
|
pLog.warn(`${dependency.libName} 平台 [${knownUnavailable.join(', ')}] 已按规则跳过`);
|
|
866
1035
|
}
|
|
1036
|
+
if (await linker.cleanupEmptyLocalDirectory(localPath)) {
|
|
1037
|
+
pLog.warn(`${dependency.libName} 已清理历史遗留的空本地目录`);
|
|
1038
|
+
}
|
|
867
1039
|
return { success: false, name: dependency.libName, skipped: true, skippedPlatforms: missing, unsupported: true };
|
|
868
1040
|
}
|
|
869
1041
|
pLog.info(`下载 ${dependency.libName} [${dlToDownload.join(', ')}]...`);
|
|
@@ -924,6 +1096,10 @@ async function linkScope(params) {
|
|
|
924
1096
|
}
|
|
925
1097
|
const linkPlatforms = [...existing, ...filteredDownloaded];
|
|
926
1098
|
if (linkPlatforms.length === 0) {
|
|
1099
|
+
pLog.warn(`${dependency.libName} 下载后没有生成请求平台 [${requestedPlatforms.join(', ')}] 的可链接产物,已拒绝创建空目录`);
|
|
1100
|
+
if (await linker.cleanupEmptyLocalDirectory(localPath)) {
|
|
1101
|
+
pLog.warn(`${dependency.libName} 已清理历史遗留的空本地目录`);
|
|
1102
|
+
}
|
|
927
1103
|
return {
|
|
928
1104
|
success: false,
|
|
929
1105
|
name: dependency.libName,
|
|
@@ -978,26 +1154,32 @@ async function linkScope(params) {
|
|
|
978
1154
|
}
|
|
979
1155
|
}
|
|
980
1156
|
// 9. 处理嵌套依赖 (actions)
|
|
981
|
-
const
|
|
982
|
-
const actions = extractActions(topLevelConfig);
|
|
1157
|
+
const actions = selectedActions;
|
|
983
1158
|
const nestedLinkedDeps = [];
|
|
984
1159
|
if (actions.length > 0) {
|
|
985
1160
|
blank();
|
|
986
1161
|
separator();
|
|
987
1162
|
info(`发现 ${actions.length} 个嵌套依赖配置`);
|
|
988
1163
|
const nestedContext = {
|
|
989
|
-
depth: 0,
|
|
1164
|
+
depth: 0,
|
|
1165
|
+
processedConfigs: new Set([buildActionVisitKey(configPath, path.dirname(configPath))]),
|
|
1166
|
+
platforms,
|
|
1167
|
+
codepacPlatforms,
|
|
1168
|
+
vars: configVars,
|
|
990
1169
|
};
|
|
991
1170
|
const thirdPartyDir = path.dirname(configPath);
|
|
992
1171
|
for (const action of actions) {
|
|
993
|
-
await processAction(action, nestedContext, thirdPartyDir, {
|
|
1172
|
+
await processAction(action, nestedContext, configPath, thirdPartyDir, {
|
|
994
1173
|
tx, registry, projectHash, projectRoot, dryRun, download, yes,
|
|
995
|
-
gitLightweightDownload,
|
|
1174
|
+
gitLightweightDownload, skipActions: new Set(params.skipActions ?? []),
|
|
1175
|
+
generalLibs, downloadedLibs, nestedLinkedDeps,
|
|
996
1176
|
});
|
|
997
1177
|
}
|
|
998
1178
|
}
|
|
999
1179
|
// 10. 同步 cache 文件
|
|
1000
|
-
|
|
1180
|
+
for (const cacheConfigPath of cacheConfigPaths) {
|
|
1181
|
+
await syncCacheFile(cacheConfigPath);
|
|
1182
|
+
}
|
|
1001
1183
|
// 11. 构建返回结果
|
|
1002
1184
|
const linkedDeps = [];
|
|
1003
1185
|
for (const item of classified) {
|
|
@@ -1070,9 +1252,10 @@ export async function linkProject(projectPath, options) {
|
|
|
1070
1252
|
error(`路径不存在: ${absolutePath}`);
|
|
1071
1253
|
process.exit(EXIT_CODES.NOINPUT);
|
|
1072
1254
|
}
|
|
1073
|
-
//
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1255
|
+
// 发现可选配置文件,扫描位置跟随实际主配置所在目录。
|
|
1256
|
+
const configDiscovery = discoveredConfigPath
|
|
1257
|
+
? await findAllCodepacConfigs(path.dirname(discoveredConfigPath))
|
|
1258
|
+
: null;
|
|
1076
1259
|
let selectedOptionalConfigs = [];
|
|
1077
1260
|
if (configDiscovery && configDiscovery.optionalConfigs.length > 0) {
|
|
1078
1261
|
if (options.config && options.config.length > 0) {
|
|
@@ -1119,14 +1302,21 @@ export async function linkProject(projectPath, options) {
|
|
|
1119
1302
|
// === 阶段 2: Submodule 检测 ===
|
|
1120
1303
|
let selectedSubmodules = [];
|
|
1121
1304
|
if (options.submodules !== false) {
|
|
1122
|
-
const
|
|
1305
|
+
const codepacPlatforms = [...new Set(platforms.map((platform) => getBaseKeyForCodepac(platform)))];
|
|
1306
|
+
const submoduleConfigs = await findSubmoduleConfigs(normalizedPath, '', 0, codepacPlatforms);
|
|
1123
1307
|
if (submoduleConfigs.length > 0) {
|
|
1124
1308
|
selectedSubmodules = await selectSubmodules(submoduleConfigs, options, existingProject?.submodules);
|
|
1125
1309
|
// 为选中的 submodule 处理可选配置
|
|
1126
1310
|
for (const sub of selectedSubmodules) {
|
|
1127
|
-
if (sub.optionalConfigs.length
|
|
1311
|
+
if (sub.optionalConfigs.length === 0)
|
|
1312
|
+
continue;
|
|
1313
|
+
const rememberedConfigs = existingProject?.submoduleOptionalConfigs?.[sub.relativePath] ?? [];
|
|
1314
|
+
if (options.yes) {
|
|
1315
|
+
sub.selectedOptionalConfigs = sub.optionalConfigs.filter((configInfo) => rememberedConfigs.includes(configInfo.name));
|
|
1316
|
+
}
|
|
1317
|
+
else if (process.stdout.isTTY) {
|
|
1128
1318
|
info(`${sub.name} 发现可选配置:`);
|
|
1129
|
-
const selected = await selectOptionalConfigs(sub.optionalConfigs, { isTTY: true, specifiedConfigs:
|
|
1319
|
+
const selected = await selectOptionalConfigs(sub.optionalConfigs, { isTTY: true, specifiedConfigs: rememberedConfigs });
|
|
1130
1320
|
if (selected !== PROMPT_CANCELLED) {
|
|
1131
1321
|
sub.selectedOptionalConfigs = selected;
|
|
1132
1322
|
}
|
|
@@ -1194,6 +1384,7 @@ export async function linkProject(projectPath, options) {
|
|
|
1194
1384
|
concurrency,
|
|
1195
1385
|
gitLightweightDownload,
|
|
1196
1386
|
optionalConfigs: selectedOptionalConfigs,
|
|
1387
|
+
skipActions: options.skipAction ?? [],
|
|
1197
1388
|
});
|
|
1198
1389
|
const finalLinkPlatforms = mainResult.finalLinkPlatforms;
|
|
1199
1390
|
// 如果 dry-run 模式,linkScope 内已显示信息
|
|
@@ -1219,6 +1410,7 @@ export async function linkProject(projectPath, options) {
|
|
|
1219
1410
|
gitLightweightDownload,
|
|
1220
1411
|
optionalConfigs: sub.selectedOptionalConfigs,
|
|
1221
1412
|
scope: sub.relativePath,
|
|
1413
|
+
skipActions: options.skipAction ?? [],
|
|
1222
1414
|
});
|
|
1223
1415
|
}
|
|
1224
1416
|
return;
|
|
@@ -1245,6 +1437,7 @@ export async function linkProject(projectPath, options) {
|
|
|
1245
1437
|
gitLightweightDownload,
|
|
1246
1438
|
optionalConfigs: sub.selectedOptionalConfigs,
|
|
1247
1439
|
scope: sub.relativePath,
|
|
1440
|
+
skipActions: options.skipAction ?? [],
|
|
1248
1441
|
});
|
|
1249
1442
|
subResults.push(subResult);
|
|
1250
1443
|
}
|
|
@@ -1269,6 +1462,7 @@ export async function linkProject(projectPath, options) {
|
|
|
1269
1462
|
? selectedOptionalConfigs.map(c => c.name) : undefined,
|
|
1270
1463
|
submodules: selectedSubmodules.length > 0
|
|
1271
1464
|
? selectedSubmodules.map(s => s.relativePath) : undefined,
|
|
1465
|
+
submoduleOptionalConfigs: buildSubmoduleOptionalConfigState(selectedSubmodules),
|
|
1272
1466
|
});
|
|
1273
1467
|
// 更新 Store 引用关系
|
|
1274
1468
|
const newStoreKeys = allLinkedDeps.map((d) => registry.getStoreKey(d.libName, d.commit, d.platform));
|
|
@@ -1419,10 +1613,7 @@ function hintStoreRepairCommand(err) {
|
|
|
1419
1613
|
async function registerSharedStore(libName, commit, branch = '', url = '') {
|
|
1420
1614
|
const registry = getRegistry();
|
|
1421
1615
|
const storeKey = registry.getStoreKey(libName, commit, SHARED_PLATFORM);
|
|
1422
|
-
|
|
1423
|
-
if (registry.getStore(storeKey)) {
|
|
1424
|
-
return true;
|
|
1425
|
-
}
|
|
1616
|
+
const existingEntry = registry.getStore(storeKey);
|
|
1426
1617
|
// 检查 _shared 目录是否存在
|
|
1427
1618
|
const storePath = await config.getStorePath();
|
|
1428
1619
|
if (!storePath)
|
|
@@ -1440,17 +1631,27 @@ async function registerSharedStore(libName, commit, branch = '', url = '') {
|
|
|
1440
1631
|
}
|
|
1441
1632
|
// 计算 _shared 完整性信息并注册
|
|
1442
1633
|
const integrity = await store.captureIntegrity(libName, commit, SHARED_PLATFORM);
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1634
|
+
if (existingEntry) {
|
|
1635
|
+
registry.updateStore(storeKey, {
|
|
1636
|
+
branch: existingEntry.branch || branch,
|
|
1637
|
+
url: existingEntry.url || url,
|
|
1638
|
+
...integrity,
|
|
1639
|
+
lastAccess: new Date().toISOString(),
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
else {
|
|
1643
|
+
registry.addStore({
|
|
1644
|
+
libName,
|
|
1645
|
+
commit,
|
|
1646
|
+
platform: SHARED_PLATFORM,
|
|
1647
|
+
branch,
|
|
1648
|
+
url,
|
|
1649
|
+
...integrity,
|
|
1650
|
+
usedBy: [], // _shared 不追踪引用,生命周期跟随平台
|
|
1651
|
+
createdAt: new Date().toISOString(),
|
|
1652
|
+
lastAccess: new Date().toISOString(),
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1454
1655
|
debug(`注册 _shared: ${libName}:${commit.slice(0, 8)} (${formatSize(integrity.size)})`);
|
|
1455
1656
|
return true;
|
|
1456
1657
|
}
|
|
@@ -1476,6 +1677,13 @@ async function ensureStoreInfo(libName, commit, platform, branch, url, projectHa
|
|
|
1476
1677
|
const integrity = await store.captureIntegrity(libName, commit, platform);
|
|
1477
1678
|
registry.updateStore(storeKey, integrity);
|
|
1478
1679
|
}
|
|
1680
|
+
if (existingEntry && ((!existingEntry.branch && branch) || (!existingEntry.url && url))) {
|
|
1681
|
+
registry.updateStore(storeKey, {
|
|
1682
|
+
branch: existingEntry.branch || branch,
|
|
1683
|
+
url: existingEntry.url || url,
|
|
1684
|
+
lastAccess: new Date().toISOString(),
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1479
1687
|
if (projectHash) {
|
|
1480
1688
|
registry.addStoreReference(storeKey, projectHash);
|
|
1481
1689
|
}
|
|
@@ -1716,7 +1924,7 @@ async function classifyDependencies(dependencies, projectPath, configPath, platf
|
|
|
1716
1924
|
}
|
|
1717
1925
|
const existing = verifiedExisting;
|
|
1718
1926
|
// 也检查是否为 General 库(有 _shared 且有内容)
|
|
1719
|
-
const isGeneral = await resolveLibraryGeneralState(dep);
|
|
1927
|
+
const isGeneral = await resolveLibraryGeneralState(dep, requestedPlatforms, vars);
|
|
1720
1928
|
const inStore = existing.length > 0 || isGeneral;
|
|
1721
1929
|
if (satisfiedRequested.length > 0) {
|
|
1722
1930
|
clearSatisfiedAutoUnavailablePlatforms(getRegistry(), dep, satisfiedRequested);
|
|
@@ -1953,19 +2161,20 @@ function getStatusKey(status) {
|
|
|
1953
2161
|
async function syncCacheFile(configPath) {
|
|
1954
2162
|
const configDir = path.dirname(configPath);
|
|
1955
2163
|
const cacheDir = path.join(configDir, '.cache');
|
|
1956
|
-
const
|
|
2164
|
+
const configFileName = path.basename(configPath);
|
|
2165
|
+
const cachePath = path.join(cacheDir, configFileName);
|
|
1957
2166
|
try {
|
|
1958
2167
|
// 确保 .cache 目录存在
|
|
1959
2168
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
1960
2169
|
// 复制配置文件到 cache
|
|
1961
2170
|
await fs.copyFile(configPath, cachePath);
|
|
1962
2171
|
if (process.env.VERBOSE) {
|
|
1963
|
-
info(`已同步 cache: ${path.
|
|
2172
|
+
info(`已同步 cache: ${configFileName} -> ${path.relative(configDir, cachePath)}`);
|
|
1964
2173
|
}
|
|
1965
2174
|
}
|
|
1966
2175
|
catch (err) {
|
|
1967
2176
|
// cache 同步失败不应阻塞主流程,仅警告
|
|
1968
|
-
warn(`cache 同步失败: ${err.message}`);
|
|
2177
|
+
warn(`cache 同步失败: ${configFileName} -> ${path.relative(configDir, cachePath)}: ${err.message}`);
|
|
1969
2178
|
}
|
|
1970
2179
|
}
|
|
1971
2180
|
/**
|
|
@@ -1975,78 +2184,98 @@ async function syncCacheFile(configPath) {
|
|
|
1975
2184
|
* @param thirdPartyDir 3rdparty 目录路径
|
|
1976
2185
|
* @param options 处理选项
|
|
1977
2186
|
*/
|
|
1978
|
-
async function processAction(action, context,
|
|
2187
|
+
async function processAction(action, context, parentConfigPath, parentTargetDir, options) {
|
|
1979
2188
|
const indent = ' '.repeat(context.depth);
|
|
1980
|
-
|
|
1981
|
-
|
|
2189
|
+
if (action.name && options.skipActions.has(action.name)) {
|
|
2190
|
+
info(`${indent}跳过嵌套 action: name=${action.name}, 原因=skip-action`);
|
|
2191
|
+
debug(`${indent}CodePac action 跳过: name=${action.name}, command=${action.command}, skipActions=${Array.from(options.skipActions).join(',')}`);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
// 1. 解析 action 命令和执行计划
|
|
2195
|
+
let plan;
|
|
1982
2196
|
try {
|
|
1983
|
-
|
|
2197
|
+
plan = buildActionExecutionPlan(action, {
|
|
2198
|
+
parentConfigPath,
|
|
2199
|
+
parentTargetDir,
|
|
2200
|
+
inheritedCodepacPlatforms: context.codepacPlatforms ?? context.platforms,
|
|
2201
|
+
});
|
|
1984
2202
|
}
|
|
1985
2203
|
catch (err) {
|
|
1986
|
-
warn(`${indent}
|
|
2204
|
+
warn(`${indent}跳过嵌套 action: name=${action.name ?? '(未命名)'}, 原因=无法解析命令, message=${err.message}`);
|
|
2205
|
+
debug(`${indent}CodePac action 解析失败: command=${action.command}`);
|
|
1987
2206
|
return;
|
|
1988
2207
|
}
|
|
1989
|
-
const libsDisplay =
|
|
1990
|
-
info(`${indent}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
if (context.processedConfigs.has(
|
|
1995
|
-
warn(`${indent}
|
|
2208
|
+
const libsDisplay = plan.libraries.length > 0 ? plan.libraries.join(', ') : '全部依赖';
|
|
2209
|
+
info(`${indent}处理嵌套 action: name=${plan.actionName ?? '(未命名)'}, config=${plan.nestedConfigPath}, target=${plan.nestedTargetDir}, libs=[${libsDisplay}]`);
|
|
2210
|
+
debug(`${indent}CodePac action 执行计划: command=${plan.command}, parentConfig=${parentConfigPath}, parentTarget=${parentTargetDir}, parsedConfigDir=${plan.configDir}, parsedTargetDir=${plan.targetDir}, resolvedConfig=${plan.nestedConfigPath}, resolvedTarget=${plan.nestedTargetDir}, inheritedPlatforms=${formatActionPlatforms(plan.inheritedPlatforms)}, effectivePlatforms=${formatActionPlatforms(plan.effectiveCodepacPlatforms)}, explicitPlatform=${plan.parsed.hasExplicitPlatform}, fullgit=${plan.parsed.hasExplicitFullGit}, unshallow=${plan.parsed.hasExplicitUnshallow}, disable_sparse=${plan.parsed.hasExplicitDisableSparse}`);
|
|
2211
|
+
// 2. 循环检测
|
|
2212
|
+
const visitKey = buildActionVisitKey(plan.nestedConfigPath, plan.nestedTargetDir);
|
|
2213
|
+
if (context.processedConfigs.has(visitKey)) {
|
|
2214
|
+
warn(`${indent} 跳过嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=循环配置, config=${plan.nestedConfigPath}, target=${plan.nestedTargetDir}`);
|
|
1996
2215
|
return;
|
|
1997
2216
|
}
|
|
1998
|
-
context.processedConfigs.add(
|
|
1999
|
-
//
|
|
2217
|
+
context.processedConfigs.add(visitKey);
|
|
2218
|
+
// 3. 检查配置文件是否存在
|
|
2000
2219
|
try {
|
|
2001
|
-
await fs.access(nestedConfigPath);
|
|
2220
|
+
await fs.access(plan.nestedConfigPath);
|
|
2002
2221
|
}
|
|
2003
2222
|
catch {
|
|
2004
|
-
warn(`${indent}
|
|
2005
|
-
hint(`${indent} 请确保 ${
|
|
2223
|
+
warn(`${indent} 跳过嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=配置不存在, config=${plan.nestedConfigPath}`);
|
|
2224
|
+
hint(`${indent} 请确保 ${plan.configDir} 库已被下载`);
|
|
2006
2225
|
return;
|
|
2007
2226
|
}
|
|
2008
|
-
//
|
|
2227
|
+
// 4. 提取指定库的依赖
|
|
2009
2228
|
let nestedResult;
|
|
2010
2229
|
try {
|
|
2011
|
-
nestedResult = await extractNestedDependencies(nestedConfigPath,
|
|
2230
|
+
nestedResult = await extractNestedDependencies(plan.nestedConfigPath, plan.libraries, {
|
|
2231
|
+
platforms: plan.effectiveCodepacPlatforms,
|
|
2232
|
+
configPath: plan.nestedConfigPath,
|
|
2233
|
+
});
|
|
2012
2234
|
}
|
|
2013
2235
|
catch (err) {
|
|
2014
|
-
warn(`${indent}
|
|
2236
|
+
warn(`${indent} 跳过嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=解析配置失败, message=${err.message}`);
|
|
2015
2237
|
return;
|
|
2016
2238
|
}
|
|
2017
2239
|
const { dependencies, vars, nestedActions } = nestedResult;
|
|
2018
2240
|
if (dependencies.length === 0) {
|
|
2019
|
-
warn(`${indent}
|
|
2241
|
+
warn(`${indent} 跳过嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=未找到依赖, libs=[${libsDisplay}], config=${plan.nestedConfigPath}`);
|
|
2020
2242
|
return;
|
|
2021
2243
|
}
|
|
2022
|
-
info(`${indent}
|
|
2023
|
-
//
|
|
2244
|
+
info(`${indent} 嵌套 action 命中 ${dependencies.length} 个依赖,继承平台: ${formatActionPlatforms(plan.inheritedPlatforms)}, 生效平台: ${formatActionPlatforms(plan.effectiveCodepacPlatforms)}`);
|
|
2245
|
+
// 5. 合并变量
|
|
2024
2246
|
const mergedVars = { ...context.vars, ...vars };
|
|
2025
|
-
//
|
|
2247
|
+
// 6. 处理这些依赖(targetDir 指定嵌套依赖的目标目录)
|
|
2026
2248
|
await linkNestedDependencies(dependencies, {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
context: { ...context, vars: mergedVars },
|
|
2249
|
+
targetDir: plan.nestedTargetDir,
|
|
2250
|
+
nestedConfigPath: plan.nestedConfigPath,
|
|
2251
|
+
context: { ...context, codepacPlatforms: plan.effectiveCodepacPlatforms, vars: mergedVars },
|
|
2031
2252
|
options,
|
|
2032
2253
|
indent,
|
|
2033
2254
|
});
|
|
2034
2255
|
// 同步嵌套配置文件的 cache(兼容 checkValid.js 检测)
|
|
2035
|
-
await syncCacheFile(nestedConfigPath);
|
|
2036
|
-
//
|
|
2037
|
-
|
|
2038
|
-
if (!parsed.disableAction && nestedActions.length > 0) {
|
|
2256
|
+
await syncCacheFile(plan.nestedConfigPath);
|
|
2257
|
+
// 7. 递归处理嵌套 actions(如果没有 disable_action)
|
|
2258
|
+
if (!plan.parsed.disableAction && nestedActions.length > 0) {
|
|
2039
2259
|
const nestedContext = {
|
|
2040
2260
|
depth: context.depth + 1,
|
|
2041
2261
|
processedConfigs: context.processedConfigs,
|
|
2042
2262
|
platforms: context.platforms,
|
|
2263
|
+
codepacPlatforms: plan.effectiveCodepacPlatforms,
|
|
2043
2264
|
vars: mergedVars,
|
|
2044
2265
|
};
|
|
2045
|
-
const nestedThirdPartyDir = path.join(thirdPartyDir, parsed.targetDir);
|
|
2046
2266
|
for (const nestedAction of nestedActions) {
|
|
2047
|
-
await processAction(nestedAction, nestedContext,
|
|
2267
|
+
await processAction(nestedAction, nestedContext, plan.nestedConfigPath, plan.nestedTargetDir, options);
|
|
2048
2268
|
}
|
|
2049
2269
|
}
|
|
2270
|
+
else if (plan.parsed.disableAction && nestedActions.length > 0) {
|
|
2271
|
+
info(`${indent} 跳过后续嵌套 action: name=${plan.actionName ?? '(未命名)'}, 原因=disable_action`);
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
function formatActionPlatforms(platforms) {
|
|
2275
|
+
return platforms && platforms.length > 0 ? platforms.join(',') : 'all';
|
|
2276
|
+
}
|
|
2277
|
+
function buildActionVisitKey(configPath, targetDir) {
|
|
2278
|
+
return `${configPath}::${targetDir}`;
|
|
2050
2279
|
}
|
|
2051
2280
|
/**
|
|
2052
2281
|
* 统一的本地目录验证+吸收流程
|
|
@@ -2104,9 +2333,7 @@ async function resolveLocalAndAbsorb(localPath, libName, commit, options) {
|
|
|
2104
2333
|
* 链接嵌套依赖
|
|
2105
2334
|
*/
|
|
2106
2335
|
async function linkNestedDependencies(dependencies, params) {
|
|
2107
|
-
const {
|
|
2108
|
-
// 计算嵌套依赖的实际目标目录
|
|
2109
|
-
const nestedTargetDir = path.join(thirdPartyDir, targetDir);
|
|
2336
|
+
const { targetDir: nestedTargetDir, context, options, indent } = params;
|
|
2110
2337
|
const { tx, registry, projectHash, projectRoot, dryRun, download, generalLibs, downloadedLibs, nestedLinkedDeps } = options;
|
|
2111
2338
|
const { platforms, vars } = context;
|
|
2112
2339
|
for (const dep of dependencies) {
|
|
@@ -2130,7 +2357,7 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2130
2357
|
const availablePlatforms = platforms.filter(p => !unavailablePlatforms.includes(p));
|
|
2131
2358
|
const knownUnavailable = platforms.filter(p => unavailablePlatforms.includes(p));
|
|
2132
2359
|
let storeHas = false;
|
|
2133
|
-
|
|
2360
|
+
let existingPlatforms = [];
|
|
2134
2361
|
const initialStoreState = await resolveRequestedStoreState(dep, availablePlatforms, vars);
|
|
2135
2362
|
existingPlatforms.push(...initialStoreState.actualExisting);
|
|
2136
2363
|
if (initialStoreState.satisfiedRequested.length > 0) {
|
|
@@ -2140,6 +2367,7 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2140
2367
|
// 完整性校验:验证 existing 平台的文件完整性(与 classifyDependencies 同逻辑)
|
|
2141
2368
|
if (existingPlatforms.length > 0) {
|
|
2142
2369
|
const { valid: verifiedPlatforms, corrupted } = await verifyExistingPlatforms(dep.libName, dep.commit, existingPlatforms);
|
|
2370
|
+
existingPlatforms = verifiedPlatforms;
|
|
2143
2371
|
if (corrupted.length > 0) {
|
|
2144
2372
|
for (const p of corrupted) {
|
|
2145
2373
|
const storeKey = registry.getStoreKey(dep.libName, dep.commit, p);
|
|
@@ -2147,10 +2375,10 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2147
2375
|
const platformPath = store.getLibraryPath(await store.getStorePath(), dep.libName, dep.commit, p);
|
|
2148
2376
|
await fs.rm(platformPath, { recursive: true, force: true }).catch(() => { });
|
|
2149
2377
|
}
|
|
2150
|
-
storeHas = verifiedPlatforms.length > 0;
|
|
2151
2378
|
}
|
|
2379
|
+
storeHas = existingPlatforms.length > 0;
|
|
2152
2380
|
}
|
|
2153
|
-
let isGeneral = await resolveLibraryGeneralState(dep);
|
|
2381
|
+
let isGeneral = await resolveLibraryGeneralState(dep, availablePlatforms, vars);
|
|
2154
2382
|
// 如果所有平台都已知不可用,跳过
|
|
2155
2383
|
if (availablePlatforms.length === 0 && knownUnavailable.length > 0 && !isGeneral) {
|
|
2156
2384
|
warn(`${indent} ${dep.libName} - 平台 [${knownUnavailable.join(', ')}] 已按规则跳过`);
|
|
@@ -2171,10 +2399,21 @@ async function linkNestedDependencies(dependencies, params) {
|
|
|
2171
2399
|
if (resolveResult.isGeneral) {
|
|
2172
2400
|
isGeneral = true;
|
|
2173
2401
|
}
|
|
2402
|
+
else if (resolveResult.absorbResult) {
|
|
2403
|
+
const absorbedPlatforms = resolveResult.absorbResult.platforms;
|
|
2404
|
+
const absorbedPlatformSet = new Set(absorbedPlatforms);
|
|
2405
|
+
const linkableAbsorbedPlatforms = [...new Set(availablePlatforms.flatMap((platform) => getRequestedPlatformTargets(platform, dep.sparse, vars)
|
|
2406
|
+
.filter((target) => absorbedPlatformSet.has(target))))];
|
|
2407
|
+
existingPlatforms = [...new Set([
|
|
2408
|
+
...existingPlatforms,
|
|
2409
|
+
...linkableAbsorbedPlatforms,
|
|
2410
|
+
])];
|
|
2411
|
+
debug(`${indent} ${dep.libName} - 本地吸收后平台回填: requested=${availablePlatforms.join(', ') || '无'}, absorbed=${absorbedPlatforms.join(', ') || '无'}, linkable=${existingPlatforms.join(', ') || '无'}`);
|
|
2412
|
+
}
|
|
2174
2413
|
if (resolveResult.absorbResult?.nestedLibraries.length) {
|
|
2175
2414
|
await registerNestedLibraries(resolveResult.absorbResult.nestedLibraries, projectHash);
|
|
2176
2415
|
}
|
|
2177
|
-
storeHas =
|
|
2416
|
+
storeHas = isGeneral || existingPlatforms.length > 0;
|
|
2178
2417
|
// 吸收为 General 后,检查是否实际需要平台内容
|
|
2179
2418
|
// 本地可能只有部分文件(如只有 _shared 内容),被误分类为 General
|
|
2180
2419
|
if (resolveResult.isGeneral && download) {
|
|
@@ -2481,26 +2720,62 @@ export async function verifyExistingPlatforms(libName, commit, existingPlatforms
|
|
|
2481
2720
|
for (const platform of existingPlatforms) {
|
|
2482
2721
|
const storeKey = registry.getStoreKey(libName, commit, platform);
|
|
2483
2722
|
const entry = registry.getStore(storeKey);
|
|
2484
|
-
// 没有 StoreEntry,跳过校验视为合法
|
|
2485
2723
|
if (!entry) {
|
|
2724
|
+
try {
|
|
2725
|
+
const integrity = await store.captureIntegrity(libName, commit, platform);
|
|
2726
|
+
if (integrity.fileCount === 0) {
|
|
2727
|
+
corrupted.push(platform);
|
|
2728
|
+
warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 目录为空`);
|
|
2729
|
+
continue;
|
|
2730
|
+
}
|
|
2731
|
+
registry.addStore({
|
|
2732
|
+
libName,
|
|
2733
|
+
commit,
|
|
2734
|
+
platform,
|
|
2735
|
+
branch: '',
|
|
2736
|
+
url: '',
|
|
2737
|
+
...integrity,
|
|
2738
|
+
usedBy: [],
|
|
2739
|
+
createdAt: new Date().toISOString(),
|
|
2740
|
+
lastAccess: new Date().toISOString(),
|
|
2741
|
+
});
|
|
2742
|
+
}
|
|
2743
|
+
catch {
|
|
2744
|
+
corrupted.push(platform);
|
|
2745
|
+
warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 完整性回填失败`);
|
|
2746
|
+
continue;
|
|
2747
|
+
}
|
|
2486
2748
|
valid.push(platform);
|
|
2487
2749
|
continue;
|
|
2488
2750
|
}
|
|
2751
|
+
if (entry.fileCount === 0) {
|
|
2752
|
+
corrupted.push(platform);
|
|
2753
|
+
warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 记录为空`);
|
|
2754
|
+
continue;
|
|
2755
|
+
}
|
|
2489
2756
|
// 旧数据回填:升级后首次遇到无 fileCount 的条目,顺便记录完整性数据
|
|
2490
2757
|
if (entry.fileCount == null) {
|
|
2491
2758
|
try {
|
|
2492
2759
|
const integrity = await store.captureIntegrity(libName, commit, platform);
|
|
2493
2760
|
registry.updateStore(storeKey, integrity);
|
|
2761
|
+
if (integrity.fileCount === 0) {
|
|
2762
|
+
corrupted.push(platform);
|
|
2763
|
+
warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 目录为空`);
|
|
2764
|
+
continue;
|
|
2765
|
+
}
|
|
2494
2766
|
}
|
|
2495
2767
|
catch {
|
|
2496
|
-
|
|
2768
|
+
corrupted.push(platform);
|
|
2769
|
+
warn(`${libName} (${commit.slice(0, 7)}) ${platform}: Store 完整性回填失败`);
|
|
2770
|
+
continue;
|
|
2497
2771
|
}
|
|
2498
2772
|
valid.push(platform);
|
|
2499
2773
|
continue;
|
|
2500
2774
|
}
|
|
2501
|
-
const result = await store.
|
|
2775
|
+
const result = await store.fullVerify(libName, commit, platform, {
|
|
2502
2776
|
size: entry.size,
|
|
2503
2777
|
fileCount: entry.fileCount,
|
|
2778
|
+
contentHash: entry.contentHash,
|
|
2504
2779
|
});
|
|
2505
2780
|
if (result.valid) {
|
|
2506
2781
|
valid.push(platform);
|