diff --git a/.repository-projection.json b/.repository-projection.json index 059ed4b..94d5c44 100644 --- a/.repository-projection.json +++ b/.repository-projection.json @@ -3,11 +3,11 @@ "projection": "endpoint", "projectionSchemaVersion": 1, "sourceRepository": "dx-corp/mono", - "sourceSha": "a971433d12b40d9042b3a64c2c074fa8fc398e69", + "sourceSha": "c97cbb033be2d815b33abd51b9e1a9a37968066c", "destinationRepository": "dx-corp/endpoint", - "priorProjectedBase": "142c973027e30b912a4855968b8ca51544f827ec", + "priorProjectedBase": "9bfd6e9f9d3495d28dc9eb44475251f420a2da97", "definitionDigest": "8068fb5528eff3a9256419584bb34a9722ea322c288ee4cfda088ff93fb60ec6", - "toolDigest": "ff02edcbb40b0028af10ab01a101e918341f157a", - "contentDigest": "f0fc7c5d517b63b7ca6d48bebe668120061843cbb5a657e27dd19aa1e80eea55", + "toolDigest": "0aae6000dbd0940f5a0af380463ccb5a83285eda", + "contentDigest": "6805af47e6eb5ba9309346562bdc67ac643315fcc4605f43653d66c5c1be844d", "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");