Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
156 commits
Select commit Hold shift + click to select a range
30d2a1e
Fix default for `validateStatus`
chgeo Apr 28, 2026
97e4a18
Update SQLite Decimal precision limitation (#2524)
samyuktaprabhu Apr 28, 2026
95c6479
CAP Java 5.x Migration Guide: removed repackaged `Olingo` (#2528)
eugene-andreev Apr 28, 2026
50ea6c3
Java protocols (#2523)
agoerler Apr 28, 2026
e25bd3d
chore: Update Java Properties (#2543)
github-actions[bot] Apr 29, 2026
429e551
chore: Update CLI texts (#2544)
github-actions[bot] Apr 29, 2026
477c4b8
docs: re-add paragraph about `ON DELETE CASCADE` (#2513)
patricebender Apr 29, 2026
12e7269
leaf elements can not be looped (#2389)
Akatuoro Apr 29, 2026
46e560a
remove ai review action
renejeglinsky Apr 29, 2026
80b6016
cds-services-archetype: Changed default JDK version to 25 (#2548)
mofterdinger May 2, 2026
378abaf
Vector embeddings: Use `OrchestrationEmbeddingRequest` (#2547)
MattSchur May 4, 2026
ad8b40b
req.subject ignores query options (#2003)
sjvans May 4, 2026
e085926
admin plan warning (#2551)
ecklie May 5, 2026
592d84e
Update dependency globals to v17.6.0 (#2549)
renovate[bot] May 5, 2026
9a0ef00
Update dependency @typescript-eslint/parser to v8.59.2 (#2540)
renovate[bot] May 5, 2026
5d81a5b
Update dependency vite-plugin-cds to ^0.3.0 (#2542)
renovate[bot] May 5, 2026
820605c
chore: Update CLI texts (#2555)
github-actions[bot] May 6, 2026
0590bcd
Revert "Document tenant-specific IAS host injection in RequestContext…
StefanHenke May 7, 2026
2a58fe3
Update dependency vite to v7.3.3 (#2558)
renovate[bot] May 7, 2026
452e976
Update dependency vite-plugin-cds to v0.3.3 (#2554)
renovate[bot] May 7, 2026
0da1990
Discourage `i18n.folders` config (#2559)
daogrady May 7, 2026
e5466ca
Vector functions with local MTXS (#2553)
MattSchur May 7, 2026
2ab6ebe
Troubleshooting empty HDI if subscribed on Windows (#2550)
vkozyura May 8, 2026
4a96c26
Auto-toggled code groups (#2538)
chgeo May 11, 2026
27b7a76
fix(java): update OpenRewrite recipe namespaces to com.sap.cds.servic…
rjayasinghe May 12, 2026
bcce364
Use latest versions for ESLint playground
chgeo May 12, 2026
0474538
Updated fiori draft docs
danjoa May 13, 2026
2cc5cf3
Adjust links to changed Fiori docs
chgeo May 18, 2026
eddd1c3
chore: Update CLI texts (#2576)
github-actions[bot] May 18, 2026
78b4688
fix: broken links detected with new blc (#2572)
swaldmann May 18, 2026
97d2162
chore: remove cspell + mdlint (#2579)
swaldmann May 18, 2026
3809880
Update dependency com.sap.cloud.security.ams.client:cap-ams-support t…
renovate[bot] May 18, 2026
2600851
Update dependency fflate to v0.8.3 (#2578)
renovate[bot] May 18, 2026
53080b7
Update dependency @typescript-eslint/parser to v8.59.3 (#2570)
renovate[bot] May 18, 2026
1567b8d
Update dependency @cap-js/cds-types to v0.17.0 (#2567)
renovate[bot] May 18, 2026
827c6d1
Learn More: Add new blog post series on local-first dev with CAP Node…
qmacro May 19, 2026
4974c4e
Update dependency @cap-js/cds-typer to v0.39.0 (#2566)
renovate[bot] May 19, 2026
0e3e7e8
chore: Update CLI texts (#2584)
github-actions[bot] May 20, 2026
80050f2
Update shiki monorepo to v4.1.0 (#2583)
renovate[bot] May 20, 2026
0a66f9a
Update dependency @typescript-eslint/parser to v8.59.4 (#2581)
renovate[bot] May 20, 2026
1f51c5d
Add `httpc` language alias for php (#2580)
renejeglinsky May 21, 2026
530f24f
Update dependency @typescript-eslint/parser to v8.60.0 (#2591)
renovate[bot] May 26, 2026
6112e98
Update dependency sass to v1.100.0 (#2590)
renovate[bot] May 26, 2026
d856c7a
Minor tweaks to the CAP-level Authorization topic page (#2568)
qmacro May 28, 2026
99d7473
Update fiori.md (#2595)
danjoa May 28, 2026
2966880
cds debug: Add warning for HTTP health check scenarios (#2589)
swaldmann May 28, 2026
d0b01ee
chore: Update CLI texts (#2594)
github-actions[bot] May 29, 2026
2551bc5
chore: remove PR-SAP workflow (#2599)
chgeo Jun 1, 2026
c7fa27a
Remove console docs (#2602)
chgeo Jun 1, 2026
6179bc8
chore: fetch Java artifacts w/o credentials (#2600)
chgeo Jun 1, 2026
6dd8b92
Update dependency vite to v7.3.5 (#2601)
renovate[bot] Jun 1, 2026
9e1c035
Update package.json
danjoa Jun 2, 2026
08b80ca
Update package.json
danjoa Jun 2, 2026
b31ddf3
Hint on `cds-dk` as design-time only package. (#2603)
chgeo Jun 2, 2026
fb4ed1c
Remove vector dimension from examples (#2609)
MattSchur Jun 2, 2026
5ebb932
Document codegen module migration in 5.0 (#2574)
vmikhailenko Jun 4, 2026
510391b
New no-escaped-anno-brackets eslint rule (#2565)
schiwekM Jun 4, 2026
3029cc4
Minor fixes to event-queues.md (#2615)
qmacro Jun 4, 2026
3191da6
Fixed broken link to hex-arch illustration
danjoa Jun 5, 2026
27f8e83
.
danjoa Jun 5, 2026
3965aeb
polished
danjoa Jun 5, 2026
3ea0f9a
Update compiler error for redirection target in cdl.md (#2617)
qmacro Jun 8, 2026
6397b21
Java Migration Guide: Set Min Maven version to `3.9.15` (#2618)
MattSchur Jun 8, 2026
b363540
Adding yellow
danjoa Jun 9, 2026
3c797da
Review Serving UIs > Fiori (#2592)
renejeglinsky Jun 9, 2026
a3d48c4
Update get-help.md (#2622)
daogrady Jun 10, 2026
f8fd9e5
add docu snipped for draftPrepare with =DraftMessages (#2621)
rjayasinghe Jun 10, 2026
41ecff7
Add `req.req` and `req.res` sections (#1603)
schwma Jun 10, 2026
dcd24ec
Add concrete logger example for application.yaml (#2614)
Akatuoro Jun 11, 2026
29f8e03
fix: fiori docs to reflect cds10 reality (#2628)
johannes-vogel Jun 16, 2026
3d5d73f
fix local binding registry filename in reuse-and-compose.md (#2632)
qmacro Jun 17, 2026
c0cb602
Update dependency sass to v1.101.0 (#2624)
renovate[bot] Jun 17, 2026
6876aeb
Remove obsolete migration menu entry (#2636)
chgeo Jun 17, 2026
3fee251
fix: support redirects ending with `/` (#2634)
chgeo Jun 17, 2026
6868883
fix: drop Node.js 20 references, recommend 22/24 (#2635)
DanSchlachter Jun 18, 2026
7ce20cc
add data privacy plugin to plugins list (#2637)
maxieckert-sap Jun 18, 2026
c4bc04e
Update min. Maven version to 3.9.14 in Java Migration Guide (#2627)
mofterdinger Jun 18, 2026
f7288fe
data-inspector first draft (#2629)
mayankgupta-01 Jun 18, 2026
4bf05a7
Bump min. required JDK version of CAP Java to 21 (#2587)
mofterdinger Jun 19, 2026
6a9fd9e
Document `generateClasses` (#2633)
vmikhailenko Jun 19, 2026
8309cc0
Temporary remove the links to @cap-js/process (#2638)
danjoa Jun 19, 2026
10aeacf
Update property defaults and deprecate old properties (#2495)
StefanHenke Jun 19, 2026
5cb82cc
Update remote-services.md (#2630)
davidhunglam Jun 19, 2026
3219459
Fix minor typo in schema-evolution.md (#2645)
qmacro Jun 22, 2026
31f0639
Update CAP Java SDK to v4.9.1 (#2644)
renovate[bot] Jun 22, 2026
22ddfd8
Update shiki monorepo to v4.2.0 (#2612)
renovate[bot] Jun 22, 2026
8f5641f
chore: Update CLI texts (#2611)
github-actions[bot] Jun 22, 2026
2e2a13e
Update ESLint to v8.61.1 (#2607)
renovate[bot] Jun 22, 2026
8ecf9ad
Fix links (#2646)
chgeo Jun 22, 2026
2489b2a
Update dependency @cap-js/cds-typer to v0.40.0 (#2652)
renovate[bot] Jun 22, 2026
e0c67a8
Update CAP Java SDK (#2651)
renovate[bot] Jun 22, 2026
e74d6e0
Update dependency @cap-js/cds-types to v0.18.0 (#2653)
renovate[bot] Jun 22, 2026
b4d94f2
Exclude old MTX apis+migration from sitemap (#2649)
chgeo Jun 22, 2026
c9747f4
Update build.md (#2626)
eric-pSAP Jun 23, 2026
acbf7ef
Node.js: Consistent Return Values (#2642)
sjvans Jun 23, 2026
4f0ee04
Hook for TMS CMK usage (#2660)
ecklie Jun 24, 2026
7cafb2d
add more hints how to set the database_id (#2656)
ecklie Jun 24, 2026
0780fff
chore: Update CLI texts (#2663)
github-actions[bot] Jun 24, 2026
e4074f9
chore: Update Java Properties (#2662)
github-actions[bot] Jun 24, 2026
485d25a
Document annotation processors change in Java 23+ (#2575)
vmikhailenko Jun 24, 2026
50352b7
Add AI plugin to external plugin section (#2650)
lisajulia Jun 25, 2026
5016088
vitepress2 + vite8 (#2657)
chgeo Jun 25, 2026
5f21765
add logger group for outbox (#2666)
rjayasinghe Jun 25, 2026
97f60b7
Node.js: Support for Event Mesh in SAP Integration Suite (#2631)
sjvans Jun 25, 2026
42a0965
Update shiki monorepo to v4.3.0 (#2668)
renovate[bot] Jun 25, 2026
303fa5f
Add CAP Java 5.0 OpenRewrite recipes to migration table (#2647)
rjayasinghe Jun 25, 2026
e594510
Move `cds up --overlay` section (#2669)
swaldmann Jun 25, 2026
161fdb8
fiori: add warning to protect partial updates (#2641)
johannes-vogel Jun 25, 2026
1bdcdb7
Fix link in Java Migration guide (#2670)
renejeglinsky Jun 25, 2026
1e5e9f4
use concrete version number for open rewrite call (#2671)
rjayasinghe Jun 25, 2026
058835d
Update dependency com.sap.cds:cds4j-api to v5 (#2675)
renovate[bot] Jun 25, 2026
8100d21
Update dependency com.sap.cds:cds-services-api to v5 (#2677)
renovate[bot] Jun 25, 2026
538539e
Docs for `cds upgrade` (#2676)
chgeo Jun 26, 2026
a4f8136
Minor fixes to constraints.md (#2673)
qmacro Jun 26, 2026
b48ef72
Add Node.js part to Outbound Authentication (#2596)
vkozyura Jun 26, 2026
b582bda
Fix HTML syntax for deprecated properties section in migration.md (#2…
renejeglinsky Jun 26, 2026
5517cee
Fix config inspector (#2682)
chgeo Jun 26, 2026
2b13759
chore: Update Java Properties (#2679)
github-actions[bot] Jun 26, 2026
cf5127c
chore: Update CLI texts (#2680)
github-actions[bot] Jun 26, 2026
336649e
Reflect cds-compiler 7 changes in capire (#2605)
stewsk Jun 26, 2026
e474720
Clarification on using directive (#2678)
stewsk Jun 26, 2026
c60b63f
Clarify maven version (#2683)
agoerler Jun 26, 2026
73d04ca
Add further reading section for CAP application deployment automation…
KoblerS Jun 26, 2026
a496512
CAP Operator (Deploy to Kyma) section updated (#2684)
Pavan-SAP Jun 29, 2026
9191c34
node.js: string decimals in best practices (#2640)
johannes-vogel Jun 29, 2026
f92c353
docs: node streaming API documentation (#2514)
BobdenOs Jun 29, 2026
33e5cfd
remove beta tag from cds export section (#2687)
smahati Jun 29, 2026
d808de1
Cosmetics in Java properties table (#2672)
chgeo Jun 29, 2026
5e9c731
Check links in external content (#2686)
chgeo Jun 29, 2026
7540969
[Fix] typo in CAP Operator link corrected (#2689)
Pavan-SAP Jun 29, 2026
89bcee6
Event Queues (#2608)
sjvans Jun 29, 2026
44ba873
add information for draft w/ localized data (#2552)
renejeglinsky Jun 29, 2026
f8518ea
Add native fetch client section for remote service consumption (#2519)
schwma Jun 29, 2026
219d7ce
Update dependency @sap/cds to v9.9.2 (#2691)
renovate[bot] Jun 29, 2026
afac5e0
Update dependency adm-zip to v0.5.18 (#2688)
renovate[bot] Jun 29, 2026
61691a7
CAP Java: date/time functions (#2527)
agoerler Jun 30, 2026
8f93042
Hierarchies with Aggregations (#2692)
eugene-andreev Jun 30, 2026
5333816
chore(renovate): use cooldown time of 3 days (#2694)
chgeo Jun 30, 2026
e204a16
Java migration: language edit of 4 to 5 section (#2697)
renejeglinsky Jul 1, 2026
b48c5a9
Remove deprecated properties/commands/features (#2699)
renejeglinsky Jul 1, 2026
ca51d65
Performance degradations in H2 `2.4` (#2664)
MattSchur Jul 1, 2026
a02450a
Node.js: Clarify Consistent Return Values (#2690)
sjvans Jul 1, 2026
2287bde
update config for draft updates (#2698)
smahati Jul 1, 2026
329f4d6
Disable `cds upgrade` for now (#2703)
chgeo Jul 2, 2026
e75ec1b
move project explorer docs from release notes (#2702)
renejeglinsky Jul 2, 2026
7ff7575
Streamlining docs on cds upgrade (#2704)
danjoa Jul 2, 2026
9d7e4e7
Update dependency @mdit/plugin-dl to v1 (#2693)
renovate[bot] Jul 2, 2026
3c84098
Update dependency @typescript-eslint/parser to v8.62.1 (#2655)
renovate[bot] Jul 2, 2026
d28d1ec
Add code owners for `tools/` (#2707)
chgeo Jul 2, 2026
08f6dc1
chore: Update CLI texts (#2706)
github-actions[bot] Jul 2, 2026
11b0016
Fix wrong Maven version in windows installation guide (#2709)
mofterdinger Jul 3, 2026
b4b7166
Add warning about cds-services-archetype and JDK 26 (#2708)
mofterdinger Jul 3, 2026
1f26c97
bumped different versions in snippets (#2705)
renejeglinsky Jul 3, 2026
577a220
Updates on migration topics Java/Node (#2696)
renejeglinsky Jul 3, 2026
fee6053
MCP going public (#2710)
danjoa Jul 3, 2026
fcc1696
fix: update links for @cap-js/mcp to external repository (#2714)
stefanrudi Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
# We start with defining ownership globally and later on can get more granular.

# General content
* @renejeglinsky
* @renejeglinsky @danjoa

node.js/ @smahati @renejeglinsky
java/ @smahati @renejeglinsky
node.js/ @smahati @renejeglinsky @danjoa
java/ @smahati @renejeglinsky @danjoa
tools/ @chgeo @swaldmann @renejeglinsky

# Infra
.github/ @chgeo @swaldmann
.vitepress/ @chgeo @swaldmann
public/ @chgeo @swaldmann
.vitepress/ @chgeo @swaldmann @danjoa
public/ @chgeo @swaldmann @danjoa

# allow dependencies updates through renovate w/o code owners
package.json
Expand Down
331 changes: 331 additions & 0 deletions .github/etc/blc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
#!/usr/bin/env node

import { parseDocument } from 'htmlparser2'
import { spawn } from 'child_process'
import { readdirSync, existsSync } from 'fs'
import { join } from 'path'
import { parseArgs } from 'node:util'

const { Bright,Dim,Reset, foreground:{
Red, Yellow, Green
}} = {

Reset: "\x1b[0m",
Bright: "\x1b[1m",
Dim: "\x1b[2m",
Underscore: "\x1b[4m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
Hidden: "\x1b[8m",

foreground: {
Black: "\x1b[30m",
Red: "\x1b[31m",
Green: "\x1b[32m",
Yellow: "\x1b[33m",
Blue: "\x1b[34m",
Magenta: "\x1b[35m",
Cyan: "\x1b[36m",
White: "\x1b[37m",
},
background: {
Black: "\x1b[40m",
Red: "\x1b[41m",
Green: "\x1b[42m",
Yellow: "\x1b[43m",
Blue: "\x1b[44m",
Magenta: "\x1b[45m",
Cyan: "\x1b[46m",
White: "\x1b[47m",
}
}

const urlExcludesBase = [
/\/java\/assets\/cds-maven-plugin-site\//,
/\/java\/custom-logic\//,
/\/releases\/changelog\//,
/\/releases\/latest/,
/\/releases\/current/,
/\/tools\/lint/,
]

// extended set of excludes because public content refers to internal content in some places
const urlExcludesPublicRepo = [
...urlExcludesBase,
/\/guides\/security\//,
/\/releases/,
/\/resources/,
/cds\/compiler\/messages/,
/mcp/
]

const { values, positionals } = parseArgs({
args: process.argv.slice(2),
options: {
x: { type: 'boolean', short: 'x', default: false },
public: { type: 'boolean', default: false },
},
allowPositionals: true,
})

const urlExcludes = values.public ? urlExcludesPublicRepo : urlExcludesBase

let [base] = positionals

// Build a set of URL paths that are directory-backed (have index.html)
// so we know when to add trailing slash for correct relative URL resolution.
const distDir = '.vitepress/dist'
const dirPages = new Set()
function scanDirs(dir, prefix) {
try {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (entry.isDirectory()) {
const sub = join(dir, entry.name)
const subPrefix = prefix + entry.name + '/'
if (existsSync(join(sub, 'index.html'))) dirPages.add(subPrefix)
scanDirs(sub, subPrefix)
}
}
} catch {}
}
scanDirs(distDir, '/')

let server
if (!base) {
// Auto-start a vitepress preview server
const port = 4173 + Math.floor(Math.random() * 1000)
base = `http://localhost:${port}/docs/`
server = spawn('npx', ['vitepress', 'preview', '.', '--port', port], {
stdio: 'ignore',
detached: true,
})
// Wait for server to be ready
for (let i = 0; i < 30; i++) {
try {
const res = await fetch(base)
if (res.ok) break
} catch {}
await new Promise(r => setTimeout(r, 500))
}
}

try {
if (values.x) { console.log (`Checking external links on ${base}...`); await check ({ excludeInternalLinks:true }) }
else { console.log (`Checking internal links on ${base}...`); await check ({ excludeExternalLinks:true }) }
} finally {
if (server) {
process.kill(-server.pid)
await new Promise(r => server.on('close', r))
}
}

async function check (options={}) {

let N=0, all=new Set, broken={}, errors=[], pages={}
const visited = new Set()
const failedUrls = new Set()
const queue = [base]
const incomingLinks = {} // url -> [{ from: page, original: href }]

function record (link, reason, p) {
++N
if (broken.page !== p) errors.push (broken = {page:p,links:[]})
broken.links.push ({ link, reason, toString() { return reason +': '+ link } })
}

// Phase 1: Crawl all reachable internal pages
while (queue.length > 0) {
const batch = queue.splice(0, 10)
await Promise.all(batch.map(crawlPage))
}

async function crawlPage (url) {
let cleanUrl = url.split('#')[0]
// Normalize /index URLs to directory form (e.g. /releases/index -> /releases/)
if (cleanUrl.endsWith('/index')) cleanUrl = cleanUrl.slice(0, -5)
if (visited.has(cleanUrl)) return
visited.add(cleanUrl)
// Also mark the counterpart (with/without trailing slash) as visited
// to avoid crawling the same page twice with different URL resolution
if (cleanUrl.endsWith('/')) visited.add(cleanUrl.slice(0,-1))
else visited.add(cleanUrl + '/')

const path = cleanUrl.replace(base,'/')
if (path.startsWith('/assets')) return
if (urlExcludes.find(l => l.test(path))) return

let html, finalUrl, resolveBase
try {
const res = await fetch(cleanUrl)
if (!res.ok) {
failedUrls.add(cleanUrl)
return
}
const ct = res.headers.get('content-type') || ''
if (!ct.includes('text/html')) return
finalUrl = res.url // after redirects
// Use filesystem knowledge to determine if this page is directory-backed.
// Directory pages (served from dir/index.html) need trailing slash for
// correct relative URL resolution (e.g. ./foo resolves within the directory).
const urlPath = path.endsWith('/') ? path : path + '/'
resolveBase = dirPages.has(urlPath) ? cleanUrl.replace(/\/?$/, '/') : finalUrl
html = await res.text()
} catch(e) {
failedUrls.add(cleanUrl)
console.error(`Error fetching ${cleanUrl}: ${e.message}`)
return
}

const doc = parseDocument(html)
const p = {
url: cleanUrl, path,
doc,
anchors: {},
hashed: [],
}
pages[path] = p

console.log (Dim+path, Reset)

for (let hash of fetchLocalIn(doc)) p.hashed.push ({ hash })

walkLinks(doc, (href) => {
if (!href || href.startsWith('mailto:') || href.startsWith('javascript:') || href.startsWith('data:') || href.startsWith('vbscript:') || href.startsWith('tel:')) return
let resolved
try { resolved = new URL(href, resolveBase).href } catch { return }

all.add(resolved)
const [resolvedBase] = resolved.split('#')
const [,hash] = href.split('#')
const isInternal = resolvedBase.startsWith(base)

if (hash && isInternal) {
p.hashed.push ({ url: resolvedBase.replace(base,'/'), hash })
}

if (isInternal && !options.excludeInternalLinks) {
if (!incomingLinks[resolvedBase]) incomingLinks[resolvedBase] = []
incomingLinks[resolvedBase].push({ from: p, original: href })
if (!visited.has(resolvedBase)) {
queue.push(resolvedBase)
}
}
})
}

// Phase 2: Check for broken internal links (pages that failed to load)
if (!options.excludeInternalLinks) {
for (const [url, links] of Object.entries(incomingLinks)) {
if (failedUrls.has(url)) {
const linkRel = url.replace(base,'/')
if (urlExcludes.find(l => l.test(linkRel))) continue
for (const { from, original } of links) {
record(original, 'Not found', from)
}
}
}
}

// Phase 3: Check external links (if -x mode)
if (!options.excludeExternalLinks) {
const externalLinks = new Map()
for (const p of Object.values(pages)) {
walkLinks(p.doc, href => {
if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('javascript:') || href.startsWith('data:') || href.startsWith('vbscript:') || href.startsWith('tel:')) return
let resolved
try { resolved = new URL(href, p.url).href } catch { return }
if (!resolved.startsWith(base)) {
if (!externalLinks.has(resolved)) externalLinks.set(resolved, [])
externalLinks.get(resolved).push({ from: p, original: href })
}
})
}

console.log(`\nChecking ${externalLinks.size} external links...`)
const entries = [...externalLinks.entries()]
for (let i = 0; i < entries.length; i += 10) {
await Promise.all(entries.slice(i, i + 10).map(async ([url, links]) => {
try {
const res = await fetch(url, {
method: 'HEAD',
signal: AbortSignal.timeout(10000),
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; LinkChecker)' },
redirect: 'follow',
})
if (!res.ok) {
for (const { from, original } of links) record(original, `HTTP ${res.status}`, from)
}
} catch (e) {
for (const { from, original } of links) record(original, e.message || 'Connection error', from)
}
}))
}
}

// Phase 4: Check hash/anchor links across pages
for (let p of Object.values(pages)) {
for (let {url,hash} of p.hashed) try {
if (url) {
if (urlExcludes.find(l => l.test(url))) continue
const page = pages[url] || pages[url.replace(/\/$/, '')]
if (!page) continue
checkLocal (page.doc,hash) || record (url+' #'+hash, 'Unresolved hash link', p)
}
else if (hash) {
if (urlExcludes.find(l => l.test(p.path))) continue
checkLocal (p.doc,hash) || record ('#'+hash, 'Unresolved local link', p)
}
} catch(e) { record(url+' #'+hash, 'Unresolved hash link', p) }
}

// Phase 5: Report results
console.log (`\n-----------------------------------------------------------------`)
if (Object.keys(pages).length === 0) {
console.log (Bright+Red+`Could not fetch any pages from ${base}\n`, Reset)
process.exitCode = 1
} else if (broken.links) {
console.log (Bright+Red+`Found ${N} broken link(s) to internal targets in ${errors.length} source(s):`, Reset)
for (let broken of errors) {
console.log ('in:', broken.page.path)
for (let each of broken.links) console.log (Bright+Red+ each)
console.log (Reset)
}
if (N > 0) process.exitCode = 1
} else {
console.log (Bright+Green+`It's all fine in ${all.size} links, no broken links found\n`, Reset)
}
}

function checkLocal (doc, id) {
return doc._anchors?.[id] ?? ((doc._anchors ??= {})[id] = findById(doc, id))
}

function findById (node, id) {
for (let each of (node.children || [])) {
if (each.attribs?.id === id) return each
if (each.children) {
const found = findById (each, id)
if (found) return found
}
}
}

function fetchLocalIn (node, all=new Set) {
for (let each of (node.children || [])) {
if (each.name === 'a') {
const href = each.attribs?.href
if (href && href[0]==='#') all.add (href.slice(1))
}
if (each.children) fetchLocalIn (each,all)
}
return all
}

function walkLinks (node, callback) {
for (let each of (node.children || [])) {
if (each.name === 'a' && each.attribs?.href) {
callback(each.attribs.href)
}
if (each.children) walkLinks(each, callback)
}
}
Loading
Loading