release-skill 0.2.6 → 0.2.8
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +42 -0
- package/INSTALL.md +12 -12
- package/INSTALL.zh-CN.md +10 -10
- package/README.md +26 -11
- package/README.zh-CN.md +22 -11
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +2619 -1728
- package/adapters/claude/schemas/.render-manifest.json +2 -2
- package/adapters/claude/schemas/release-project.schema.json +60 -0
- package/adapters/claude/skills/release-prepare/SKILL.md +5 -0
- package/adapters/claude/skills/release-setup/SKILL.md +10 -1
- package/adapters/claude/skills/release-verify/SKILL.md +4 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +2619 -1728
- package/adapters/codex/schemas/.render-manifest.json +2 -2
- package/adapters/codex/schemas/release-project.schema.json +60 -0
- package/adapters/codex/skills/release-prepare/SKILL.md +5 -0
- package/adapters/codex/skills/release-setup/SKILL.md +10 -1
- package/adapters/codex/skills/release-verify/SKILL.md +4 -2
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +2619 -1728
- package/adapters/kimi/schemas/.render-manifest.json +2 -2
- package/adapters/kimi/schemas/release-project.schema.json +60 -0
- package/adapters/kimi/skills/release-prepare/SKILL.md +5 -0
- package/adapters/kimi/skills/release-setup/SKILL.md +10 -1
- package/adapters/kimi/skills/release-verify/SKILL.md +4 -2
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +2619 -1728
- package/adapters/workbuddy/schemas/.render-manifest.json +2 -2
- package/adapters/workbuddy/schemas/release-project.schema.json +60 -0
- package/adapters/workbuddy/skills/release-prepare/SKILL.md +5 -0
- package/adapters/workbuddy/skills/release-setup/SKILL.md +10 -1
- package/adapters/workbuddy/skills/release-verify/SKILL.md +4 -2
- package/bin/release-skill.bundle.mjs +2619 -1728
- package/package.json +1 -1
- package/schemas/.render-manifest.json +2 -2
- package/schemas/release-project.schema.json +60 -0
- package/skills/release-prepare/SKILL.md +5 -0
- package/skills/release-setup/SKILL.md +10 -1
- package/skills/release-verify/SKILL.md +4 -2
- package/skills-src/release-prepare/SKILL.md +5 -0
- package/skills-src/release-setup/SKILL.md +10 -1
- package/skills-src/release-verify/SKILL.md +4 -2
- package/src/adapters/npm.mjs +54 -2
- package/src/adapters/plugin-marketplace.mjs +4 -29
- package/src/commands/prepare.mjs +49 -3
- package/src/commands/publish.mjs +2 -2
- package/src/commands/reconcile.mjs +39 -0
- package/src/commands/setup.mjs +168 -0
- package/src/commands/verify.mjs +28 -1
- package/src/core/config.mjs +48 -0
- package/src/core/public-surface.mjs +509 -0
- package/src/npm/npm-entry-closure.mjs +195 -0
- package/src/platforms/codebuddy.mjs +19 -11
- package/src/platforms/codex.mjs +18 -10
- package/src/platforms/kimi.mjs +26 -39
- package/src/snapshot/frozen.mjs +51 -19
package/src/commands/setup.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import addFormats from 'ajv-formats';
|
|
|
24
24
|
import { canonicalJson, sha256Hex } from '../core/digest.mjs';
|
|
25
25
|
import { acquireProjectLock } from '../artifacts/project-lock.mjs';
|
|
26
26
|
import { getPlatform, PLATFORMS } from '../platforms/registry.mjs';
|
|
27
|
+
import { checkNpmEntryClosure } from '../npm/npm-entry-closure.mjs';
|
|
27
28
|
import {
|
|
28
29
|
CONFIG_EXISTS,
|
|
29
30
|
CONFIG_INVALID,
|
|
@@ -201,6 +202,8 @@ function summarizeLegacyReleaseConfig(value, path) {
|
|
|
201
202
|
tagPrefix: optionalString(repo.tagPrefix),
|
|
202
203
|
npmPackage: optionalString(repo.npmPackage),
|
|
203
204
|
npmPackageDeclared: Object.hasOwn(repo, 'npmPackage'),
|
|
205
|
+
npmRequiredPathCandidates: stringList(repo.npmRequiredPackagePaths),
|
|
206
|
+
npmRequiredPathsDeclared: Object.hasOwn(repo, 'npmRequiredPackagePaths'),
|
|
204
207
|
docsSource: optionalString(repo.docsSource),
|
|
205
208
|
requiredPathCandidates: stringList(repo.requiredPackagePaths),
|
|
206
209
|
snapshotCommands: Array.isArray(repo.snapshotCommands) ? repo.snapshotCommands : [],
|
|
@@ -223,6 +226,8 @@ function summarizeLegacyReleaseConfig(value, path) {
|
|
|
223
226
|
npmPackageDeclared: plugins.some((plugin) => (
|
|
224
227
|
plugin && typeof plugin === 'object' && !Array.isArray(plugin) && Object.hasOwn(plugin, 'npmPackage')
|
|
225
228
|
)),
|
|
229
|
+
npmRequiredPathCandidates: stringList(value.npmRequiredPackagePaths),
|
|
230
|
+
npmRequiredPathsDeclared: Object.hasOwn(value, 'npmRequiredPackagePaths'),
|
|
226
231
|
docsSource: null,
|
|
227
232
|
requiredPathCandidates: stringList(value.requiredPaths),
|
|
228
233
|
snapshotCommands: Array.isArray(value.snapshotCommands) ? value.snapshotCommands : [],
|
|
@@ -467,6 +472,111 @@ async function discoverUnitGit(unitAbsDir, parentRoot) {
|
|
|
467
472
|
};
|
|
468
473
|
}
|
|
469
474
|
|
|
475
|
+
async function pathIsGitIgnored(unitAbsDir, target) {
|
|
476
|
+
// --no-index also classifies generated paths that do not exist yet.
|
|
477
|
+
try {
|
|
478
|
+
await execFile('git', ['check-ignore', '--no-index', '--quiet', '--', target], {
|
|
479
|
+
cwd: unitAbsDir,
|
|
480
|
+
shell: false,
|
|
481
|
+
timeout: 5000,
|
|
482
|
+
});
|
|
483
|
+
return true;
|
|
484
|
+
} catch {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function classifyNpmEntryCandidate(unitAbsDir, target, trackedFiles) {
|
|
490
|
+
const tracked = trackedFiles.includes(target);
|
|
491
|
+
const ignored = await pathIsGitIgnored(unitAbsDir, target);
|
|
492
|
+
let stat = null;
|
|
493
|
+
try {
|
|
494
|
+
stat = await lstat(join(unitAbsDir, target));
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (error.code !== 'ENOENT') throw error;
|
|
497
|
+
}
|
|
498
|
+
const exists = stat !== null;
|
|
499
|
+
const regular = stat?.isFile() === true && stat?.isSymbolicLink() !== true;
|
|
500
|
+
const state = exists && !regular
|
|
501
|
+
? 'NON_REGULAR'
|
|
502
|
+
: exists && tracked
|
|
503
|
+
? 'TRACKED_PRESENT'
|
|
504
|
+
: exists && ignored
|
|
505
|
+
? 'IGNORED_PRESENT'
|
|
506
|
+
: exists
|
|
507
|
+
? 'UNTRACKED_PRESENT'
|
|
508
|
+
: ignored
|
|
509
|
+
? 'IGNORED_MISSING'
|
|
510
|
+
: 'MISSING';
|
|
511
|
+
return { path: target, state, exists, tracked, ignored };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async function discoverNpmEntryCandidates(root, pkg, matchingLegacyUnits, unitGitEntry) {
|
|
515
|
+
const emptyIndex = new Map();
|
|
516
|
+
const semantic = checkNpmEntryClosure(pkg.entryManifest, emptyIndex);
|
|
517
|
+
const byPath = new Map();
|
|
518
|
+
const add = (target, source) => {
|
|
519
|
+
const current = byPath.get(target) ?? { path: target, sources: new Set() };
|
|
520
|
+
current.sources.add(source);
|
|
521
|
+
byPath.set(target, current);
|
|
522
|
+
};
|
|
523
|
+
for (const entry of semantic.entries) add(entry.target, `package.json:${entry.field}`);
|
|
524
|
+
|
|
525
|
+
const diagnostics = [
|
|
526
|
+
...semantic.errors
|
|
527
|
+
.filter((error) => error.reason !== 'entry_missing')
|
|
528
|
+
.map((error) => ({ code: 'NPM_ENTRY_DECLARATION_INVALID', ...error })),
|
|
529
|
+
...semantic.diagnostics.map((diagnostic) => ({
|
|
530
|
+
code: 'NPM_ENTRY_WILDCARD_REQUIRES_REVIEW',
|
|
531
|
+
...diagnostic,
|
|
532
|
+
})),
|
|
533
|
+
];
|
|
534
|
+
const declaredLegacyPaths = new Set();
|
|
535
|
+
const legacyCoverageDeclared = matchingLegacyUnits.some((unit) => unit.npmRequiredPathsDeclared);
|
|
536
|
+
for (const legacy of matchingLegacyUnits) {
|
|
537
|
+
for (const target of legacy.npmRequiredPathCandidates) {
|
|
538
|
+
const checked = checkNpmEntryClosure({ main: target }, emptyIndex);
|
|
539
|
+
const normalized = checked.entries[0]?.target;
|
|
540
|
+
if (!normalized) {
|
|
541
|
+
diagnostics.push({
|
|
542
|
+
code: 'LEGACY_NPM_REQUIRED_PATH_INVALID',
|
|
543
|
+
path: target,
|
|
544
|
+
reason: checked.errors[0]?.reason ?? 'invalid_path',
|
|
545
|
+
});
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
declaredLegacyPaths.add(normalized);
|
|
549
|
+
add(normalized, 'public-release.json:npmRequiredPackagePaths');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (legacyCoverageDeclared) {
|
|
553
|
+
for (const entry of semantic.entries) {
|
|
554
|
+
if (!declaredLegacyPaths.has(entry.target)) {
|
|
555
|
+
diagnostics.push({
|
|
556
|
+
code: 'LEGACY_NPM_ENTRY_COVERAGE_MISSING',
|
|
557
|
+
path: entry.target,
|
|
558
|
+
field: entry.field,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const unitAbsDir = resolve(root, pkg.directory);
|
|
565
|
+
const candidates = [];
|
|
566
|
+
for (const item of [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
567
|
+
candidates.push({
|
|
568
|
+
...await classifyNpmEntryCandidate(
|
|
569
|
+
unitAbsDir,
|
|
570
|
+
item.path,
|
|
571
|
+
unitGitEntry?.trackedFiles ?? [],
|
|
572
|
+
),
|
|
573
|
+
sources: [...item.sources].sort(),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
diagnostics.sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b)));
|
|
577
|
+
return { candidates, diagnostics };
|
|
578
|
+
}
|
|
579
|
+
|
|
470
580
|
async function discoverFacts(root) {
|
|
471
581
|
const files = await walkDiscoveryFiles(root);
|
|
472
582
|
const packageFiles = files.filter((path) => (
|
|
@@ -494,6 +604,11 @@ async function discoverFacts(root) {
|
|
|
494
604
|
private: pkg.private === true,
|
|
495
605
|
repository: parseGithubRepo(pkg.repository),
|
|
496
606
|
publishRegistry: typeof pkg.publishConfig?.registry === 'string' ? pkg.publishConfig.registry : null,
|
|
607
|
+
entryManifest: Object.fromEntries(
|
|
608
|
+
['bin', 'main', 'module', 'types', 'typings', 'exports']
|
|
609
|
+
.filter((field) => Object.hasOwn(pkg, field))
|
|
610
|
+
.map((field) => [field, pkg[field]]),
|
|
611
|
+
),
|
|
497
612
|
files: Array.isArray(pkg.files) ? pkg.files.filter((item) => typeof item === 'string').sort() : [],
|
|
498
613
|
scripts: Object.fromEntries(Object.entries(pkg.scripts ?? {})
|
|
499
614
|
.filter(([, value]) => typeof value === 'string')
|
|
@@ -557,6 +672,22 @@ async function discoverFacts(root) {
|
|
|
557
672
|
for (const pkg of packages) {
|
|
558
673
|
const unitAbsDir = resolve(root, pkg.directory);
|
|
559
674
|
unitGit[pkg.directory] = await discoverUnitGit(unitAbsDir, root);
|
|
675
|
+
const inferredId = safeUnitId(pkg, pkg.directory);
|
|
676
|
+
const matchingLegacyUnits = legacyReleaseConfigs
|
|
677
|
+
.flatMap((config) => config.releaseUnits)
|
|
678
|
+
.filter((unit) => (
|
|
679
|
+
unit.id === inferredId ||
|
|
680
|
+
unit.source === pkg.directory ||
|
|
681
|
+
unit.source === dirname(pkg.path)
|
|
682
|
+
));
|
|
683
|
+
const npmDiscovery = await discoverNpmEntryCandidates(
|
|
684
|
+
root,
|
|
685
|
+
pkg,
|
|
686
|
+
matchingLegacyUnits,
|
|
687
|
+
unitGit[pkg.directory],
|
|
688
|
+
);
|
|
689
|
+
pkg.npmEntryCandidates = npmDiscovery.candidates;
|
|
690
|
+
pkg.npmEntryDiagnostics = npmDiscovery.diagnostics;
|
|
560
691
|
}
|
|
561
692
|
|
|
562
693
|
return { git, packages, manifests, skills, legacyReleaseConfigs, fileDigests, unitGit };
|
|
@@ -871,6 +1002,8 @@ function buildCandidates(facts) {
|
|
|
871
1002
|
publicFileMappingCandidates: mappingResult.mappings,
|
|
872
1003
|
publicFileMappingConflicts: mappingResult.conflicts,
|
|
873
1004
|
requiredPublicFileCandidates,
|
|
1005
|
+
npmEntryCandidates: pkg.npmEntryCandidates ?? [],
|
|
1006
|
+
npmEntryDiagnostics: pkg.npmEntryDiagnostics ?? [],
|
|
874
1007
|
entrySkillCandidates: entrySkillCandidatesForUnit(facts, pkg, id),
|
|
875
1008
|
...(hasAuthorityConflict ? {
|
|
876
1009
|
authorityConflict: {
|
|
@@ -1027,6 +1160,24 @@ function buildRecommendedProposal(facts, candidates) {
|
|
|
1027
1160
|
assumptions,
|
|
1028
1161
|
};
|
|
1029
1162
|
}
|
|
1163
|
+
if (
|
|
1164
|
+
unit.distributionCandidates.includes('npm') &&
|
|
1165
|
+
(
|
|
1166
|
+
(unit.npmEntryCandidates ?? []).some((candidate) => candidate.state !== 'TRACKED_PRESENT') ||
|
|
1167
|
+
(unit.npmEntryDiagnostics ?? []).length > 0
|
|
1168
|
+
)
|
|
1169
|
+
) {
|
|
1170
|
+
return {
|
|
1171
|
+
answers: null,
|
|
1172
|
+
conflicts: [{
|
|
1173
|
+
code: 'NPM_ENTRY_REVIEW_REQUIRED',
|
|
1174
|
+
unit: unit.id,
|
|
1175
|
+
candidates: unit.npmEntryCandidates,
|
|
1176
|
+
diagnostics: unit.npmEntryDiagnostics,
|
|
1177
|
+
}],
|
|
1178
|
+
assumptions,
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1030
1181
|
|
|
1031
1182
|
// Infer npm publisher: prefer legacy owner, then parse from repo slug
|
|
1032
1183
|
const repoOwner = publicRepo.includes('/') ? publicRepo.split('/')[0] : null;
|
|
@@ -1245,6 +1396,15 @@ function buildDecisionsRequired(candidates, localOnly) {
|
|
|
1245
1396
|
id: `unit:${unit.id}:distributions-and-files`,
|
|
1246
1397
|
description: `逐项确认渠道 ${JSON.stringify(unit.distributionCandidates)}、公开文件边界和 requiredPublicFiles;候选不是授权。`,
|
|
1247
1398
|
});
|
|
1399
|
+
if (
|
|
1400
|
+
(unit.npmEntryCandidates ?? []).length > 0 ||
|
|
1401
|
+
(unit.npmEntryDiagnostics ?? []).length > 0
|
|
1402
|
+
) {
|
|
1403
|
+
decisions.push({
|
|
1404
|
+
id: `unit:${unit.id}:npm-entry-closure`,
|
|
1405
|
+
description: '审阅 package.json 入口及旧 npmRequiredPackagePaths 的 tracked/untracked/ignored/missing 状态;setup 只报告候选,不自动写入 publicFiles 或 requiredPublicFiles。',
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1248
1408
|
}
|
|
1249
1409
|
decisions.push({
|
|
1250
1410
|
id: 'verification-gates',
|
|
@@ -1271,6 +1431,8 @@ function buildCompactSummary(report) {
|
|
|
1271
1431
|
distributionCandidates: [...unit.distributionCandidates].sort(),
|
|
1272
1432
|
publicFileMappingCount: (unit.publicFileMappingCandidates ?? []).length,
|
|
1273
1433
|
requiredPublicFileCount: (unit.requiredPublicFileCandidates ?? []).length,
|
|
1434
|
+
npmEntryCandidates: unit.npmEntryCandidates ?? [],
|
|
1435
|
+
npmEntryDiagnostics: unit.npmEntryDiagnostics ?? [],
|
|
1274
1436
|
})),
|
|
1275
1437
|
gateCandidates: (report.gateCandidates ?? []).map((gate) => ({
|
|
1276
1438
|
id: gate.id,
|
|
@@ -1519,6 +1681,12 @@ export async function setupProject({ root, answersPath, write = false, confirmSe
|
|
|
1519
1681
|
...(unconfiguredGateCandidateIds.length > 0
|
|
1520
1682
|
? ['存在未配置的验证候选;逐项审阅副作用后决定是否人工注册。']
|
|
1521
1683
|
: []),
|
|
1684
|
+
...(candidates.units.some((unit) => (
|
|
1685
|
+
(unit.npmEntryCandidates ?? []).some((candidate) => candidate.state !== 'TRACKED_PRESENT') ||
|
|
1686
|
+
(unit.npmEntryDiagnostics ?? []).length > 0
|
|
1687
|
+
))
|
|
1688
|
+
? ['npm 入口候选存在未跟踪、被忽略、缺失、非普通文件或声明覆盖疑点;人工增量修复配置或构建流程,setup 不自动改写。']
|
|
1689
|
+
: []),
|
|
1522
1690
|
],
|
|
1523
1691
|
},
|
|
1524
1692
|
recommendedGateIds: [],
|
package/src/commands/verify.mjs
CHANGED
|
@@ -63,6 +63,10 @@ import {
|
|
|
63
63
|
shouldSkipVerification,
|
|
64
64
|
INSTALLATION_CONTRACT_ALGORITHM_VERSION,
|
|
65
65
|
} from '../core/installation-contract.mjs';
|
|
66
|
+
import {
|
|
67
|
+
buildDirectoryFileIndex,
|
|
68
|
+
checkNpmEntryClosure,
|
|
69
|
+
} from '../npm/npm-entry-closure.mjs';
|
|
66
70
|
|
|
67
71
|
// ---------------------------------------------------------------------------
|
|
68
72
|
// Constants
|
|
@@ -294,6 +298,30 @@ export async function runSmokeTest(plan, root, options = {}) {
|
|
|
294
298
|
}
|
|
295
299
|
|
|
296
300
|
const pkgRoot = join(installDir, 'node_modules', pkgName);
|
|
301
|
+
|
|
302
|
+
// Entry closure check: verify all declared entry points (bin, main,
|
|
303
|
+
// module, types, typings, exports) exist as regular files in the
|
|
304
|
+
// installed package. This catches incident-class tarballs where
|
|
305
|
+
// name/version are correct but distribution files are missing.
|
|
306
|
+
{
|
|
307
|
+
const dirIndex = await buildDirectoryFileIndex(pkgRoot);
|
|
308
|
+
const closureResult = checkNpmEntryClosure(installedPkg, dirIndex);
|
|
309
|
+
if (closureResult.errors.length > 0) {
|
|
310
|
+
return {
|
|
311
|
+
passed: false,
|
|
312
|
+
details: {
|
|
313
|
+
gate: 'npm-entry-closure',
|
|
314
|
+
error: `npm entry closure check failed for ${packageAtVersion}: ${closureResult.errors.map((e) => e.message).join('; ')}`,
|
|
315
|
+
packageAtVersion,
|
|
316
|
+
unitId,
|
|
317
|
+
entries: closureResult.entries,
|
|
318
|
+
errors: closureResult.errors,
|
|
319
|
+
diagnostics: closureResult.diagnostics,
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
297
325
|
if (plan.skillResourceClosure) {
|
|
298
326
|
const expectedUnitReceipt = plan.skillResourceClosure.unitReceipts
|
|
299
327
|
.find((item) => item.unitId === unitId);
|
|
@@ -1225,7 +1253,6 @@ export async function verifyRelease(options) {
|
|
|
1225
1253
|
}
|
|
1226
1254
|
: {
|
|
1227
1255
|
HOME: resolve(runDir, 'consumers', `kimi-${action.parameters.plugin}`),
|
|
1228
|
-
KIMI_CODE_HOME: resolve(runDir, 'consumers', `kimi-${action.parameters.plugin}`),
|
|
1229
1256
|
},
|
|
1230
1257
|
}));
|
|
1231
1258
|
} else {
|
package/src/core/config.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import Ajv from 'ajv';
|
|
|
23
23
|
import addFormats from 'ajv-formats';
|
|
24
24
|
import { canonicalJson, sha256Hex } from './digest.mjs';
|
|
25
25
|
import { ReleaseError, CONFIG_INVALID } from './errors.mjs';
|
|
26
|
+
import { validatePublicSurfaceGlob } from './public-surface.mjs';
|
|
26
27
|
import { canonicalPublicPath, publicPathCollisionKey } from '../snapshot/public-path.mjs';
|
|
27
28
|
import { isReservedReleaseControlPath } from './baseline.mjs';
|
|
28
29
|
import { readTrustedPackageResource } from './trusted-resource.mjs';
|
|
@@ -294,6 +295,53 @@ export async function loadProjectConfig({ root, configPath } = {}) {
|
|
|
294
295
|
);
|
|
295
296
|
}
|
|
296
297
|
|
|
298
|
+
// Validate expected public-surface roots and the deliberately small glob
|
|
299
|
+
// language before generic schema validation so diagnostics identify the
|
|
300
|
+
// exact unit, scan root, and pattern.
|
|
301
|
+
const surface = unit.expectedPublicSurface;
|
|
302
|
+
if (surface && typeof surface === 'object' && Array.isArray(surface.scanRoots)) {
|
|
303
|
+
for (const [rootIndex, scanRoot] of surface.scanRoots.entries()) {
|
|
304
|
+
if (!scanRoot || typeof scanRoot !== 'object') continue;
|
|
305
|
+
if (typeof scanRoot.root === 'string') {
|
|
306
|
+
try {
|
|
307
|
+
canonicalPublicPath(scanRoot.root, { allowDot: true });
|
|
308
|
+
} catch (err) {
|
|
309
|
+
throw new ReleaseError(
|
|
310
|
+
CONFIG_INVALID,
|
|
311
|
+
`unit "${unit.id}" has invalid expectedPublicSurface scan root: ${err.message}`,
|
|
312
|
+
{
|
|
313
|
+
unitId: unit.id,
|
|
314
|
+
rootIndex,
|
|
315
|
+
root: scanRoot.root,
|
|
316
|
+
field: 'expectedPublicSurface.scanRoots[].root',
|
|
317
|
+
},
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (const field of ['include', 'exclude']) {
|
|
322
|
+
if (!Array.isArray(scanRoot[field])) continue;
|
|
323
|
+
for (const pattern of scanRoot[field]) {
|
|
324
|
+
if (typeof pattern !== 'string') continue;
|
|
325
|
+
try {
|
|
326
|
+
validatePublicSurfaceGlob(pattern);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
throw new ReleaseError(
|
|
329
|
+
CONFIG_INVALID,
|
|
330
|
+
`unit "${unit.id}" has invalid expectedPublicSurface ${field} glob: ${err.message}`,
|
|
331
|
+
{
|
|
332
|
+
unitId: unit.id,
|
|
333
|
+
rootIndex,
|
|
334
|
+
pattern,
|
|
335
|
+
field: `expectedPublicSurface.scanRoots[].${field}`,
|
|
336
|
+
reason: err.details?.reason ?? 'PUBLIC_SURFACE_GLOB_INVALID',
|
|
337
|
+
},
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
297
345
|
// Validate publicFiles[].from and .to
|
|
298
346
|
if (Array.isArray(unit.publicFiles)) {
|
|
299
347
|
for (const mapping of unit.publicFiles) {
|