diff --git a/.repository-projection.json b/.repository-projection.json index 9c19b3e..ffbc537 100644 --- a/.repository-projection.json +++ b/.repository-projection.json @@ -3,11 +3,11 @@ "projection": "plugins", "projectionSchemaVersion": 1, "sourceRepository": "dx-corp/mono", - "sourceSha": "a971433d12b40d9042b3a64c2c074fa8fc398e69", + "sourceSha": "c97cbb033be2d815b33abd51b9e1a9a37968066c", "destinationRepository": "dx-corp/plugins", - "priorProjectedBase": "750156a6ea714eab0b6011e5482fb0e5efbaccea", + "priorProjectedBase": "35a76e98ab8d7db40d8fc3cedd7389810037d0b1", "definitionDigest": "b705140f4a8c5318b8a64dbeaa441df76d7cfb137de6940ae79004107d9a2b2e", - "toolDigest": "ff02edcbb40b0028af10ab01a101e918341f157a", - "contentDigest": "b1748f369e9a04281f189ac58733f011c7ae40ebff1956864ad8419bef91f8df", + "toolDigest": "0aae6000dbd0940f5a0af380463ccb5a83285eda", + "contentDigest": "a9a3dc09bc74d321a48634eac446182b5861547d711c97062d9fb6e64da84579", "publicationEligible": true } diff --git a/scripts/distribution-validation.mjs b/scripts/distribution-validation.mjs index 2fbd4e1..d308123 100644 --- a/scripts/distribution-validation.mjs +++ b/scripts/distribution-validation.mjs @@ -2,7 +2,7 @@ import { execFileSync, spawnSync } from "node:child_process"; import { lstat, mkdtemp, readFile, readdir, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; -import { join, relative, resolve } from "node:path"; +import { dirname, join, relative, resolve } from "node:path"; import { pathToFileURL } from "node:url"; import { parseArgs } from "node:util"; @@ -75,6 +75,24 @@ export async function validateSafeTree(name, root) { return files; } +export async function validateApiDocLinks(root, files) { + for (const file of files.filter(path => path.endsWith(".md"))) { + const source = await readFile(join(root, file), "utf8"); + const links = [...source.matchAll(/\]\(([^\s)]+)(?:\s+"[^"]*")?\)/g)] + .map(match => ({ target: match[1], base: dirname(join(root, file)) })); + const sourcePaths = [...source.matchAll(/(? ({ target: match[0], base: root })); + for (const { target, base } of [...links, ...sourcePaths]) { + if (/^(?:[a-z][a-z0-9+.-]*:|\/|#)/i.test(target)) continue; + const pathname = decodeURIComponent(target.split(/[?#]/, 1)[0]); + const resolved = resolve(base, pathname); + const path = relative(root, resolved).split("\\").join("/"); + requireValue(path && !path.startsWith("../") && path !== ".." && files.includes(path), + `broken API documentation link: ${file} -> ${target}`); + } + } +} + export async function validateProvenance(name, root) { const provenance = JSON.parse(await readFile(join(root, ".repository-projection.json"), "utf8")); exactKeys(provenance, [ @@ -127,6 +145,7 @@ async function validateDeployment(root, files) { } async function validateApi(root, files) { + await validateApiDocLinks(root, files); requireValue(files.includes("scripts/contracts/normalize-openapi-generated.py"), "API OpenAPI normalizer is missing"); const actualProto = files.filter(path => path.startsWith("proto/") && path.endsWith(".proto")).map(path => path.slice(6)).sort(); requireValue(JSON.stringify(actualProto) === JSON.stringify(PROTO), "API protobuf allowlist differs from the public SDK closure");