diff --git a/.repository-projection.json b/.repository-projection.json index 54cdc21..ff24c71 100644 --- a/.repository-projection.json +++ b/.repository-projection.json @@ -3,11 +3,11 @@ "projection": "examples", "projectionSchemaVersion": 1, "sourceRepository": "dx-corp/mono", - "sourceSha": "a971433d12b40d9042b3a64c2c074fa8fc398e69", + "sourceSha": "c97cbb033be2d815b33abd51b9e1a9a37968066c", "destinationRepository": "dx-corp/examples", - "priorProjectedBase": "c4df6306c20a8670d6b0046a960f0c5aa020f8bb", + "priorProjectedBase": "7f301097ed2440e6b08031736b7fe96b0fb09818", "definitionDigest": "ec2a926dbb322ffc1c2329c0b1f56b2b8bc0528c918473ebbcea0ef8234ff2b5", - "toolDigest": "ff02edcbb40b0028af10ab01a101e918341f157a", - "contentDigest": "dd893c827fbf83693aa832b8932dd452e30876e77e70477fe8ebae40ed92d678", + "toolDigest": "0aae6000dbd0940f5a0af380463ccb5a83285eda", + "contentDigest": "ca021474379e0919db17d0fbf92f40813531c108959892a8c00d27e8db5b4ea3", "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");