-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make changes to check for broken links (#4)
- Loading branch information
1 parent
83767e7
commit c03c54c
Showing
6 changed files
with
252 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
name: Check links | ||
|
||
on: | ||
# when someone makes a change directly to main branch | ||
push: | ||
branches: | ||
- main | ||
# when someone requests a change to main branch | ||
pull_request: | ||
branches: | ||
- main | ||
|
||
# run periodically | ||
schedule: | ||
- cron: "0 0 * * *" | ||
# run manually | ||
workflow_dispatch: | ||
|
||
jobs: | ||
check: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- if: runner.debug == '1' | ||
uses: mxschmitt/action-tmate@v3 | ||
|
||
- name: Get this repo's code | ||
uses: actions/checkout@v4 | ||
|
||
- name: Set up Bun | ||
uses: oven-sh/setup-bun@v1 | ||
|
||
- name: Install packages | ||
run: bun install glob@v9 yaml@v2 | ||
|
||
- name: Run check script | ||
run: bun ./check.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { addError, getList, onExit } from "./core"; | ||
|
||
onExit(); | ||
|
||
// check list of redirects for broken links | ||
async function checkList(list) { | ||
return await Promise.all( | ||
// for each redirect | ||
list.map(async ({ to }) => { | ||
try { | ||
// do simple request to target url | ||
const response = await fetch(to); | ||
if ( | ||
// only fail on certain status codes that might indicate link is "broken" | ||
// select as desired from https://en.wikipedia.org/wiki/List_of_HTTP_status_codes | ||
[ | ||
400, 404, 405, 406, 408, 409, 410, 421, 500, 501, 502, 503, 504, | ||
].includes(response.status) | ||
) | ||
throw Error(response.status); | ||
} catch (error) { | ||
addError(`"to: ${to}" may be a broken link\n(${error})`); | ||
} | ||
}) | ||
); | ||
} | ||
|
||
await checkList(getList()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { readFileSync } from "fs"; | ||
import { resolve } from "path"; | ||
import { globSync } from "glob"; | ||
import { parse } from "yaml"; | ||
|
||
// if running in github actions debug mode, do extra logging | ||
export const verbose = !!process.env.RUNNER_DEBUG; | ||
|
||
// get full list of redirects | ||
export function getList() { | ||
// get yaml files that match glob pattern | ||
const files = globSync("*.y?(a)ml", { cwd: __dirname }); | ||
|
||
log("Files", files.join(" ")); | ||
|
||
// start combined list of redirects | ||
const list = []; | ||
|
||
// keep track of duplicate entries | ||
const duplicates = {}; | ||
|
||
// go through each yaml file | ||
for (const file of files) { | ||
// load file contents | ||
const contents = readFileSync(resolve(__dirname, file), "utf8"); | ||
|
||
// try to parse as yaml | ||
let data; | ||
try { | ||
data = parse(contents); | ||
} catch (error) { | ||
addError(`Couldn't parse ${file}. Make sure it is valid YAML.`); | ||
continue; | ||
} | ||
|
||
// check if top level is list | ||
if (!Array.isArray(data)) { | ||
addError(`${file} is not a list`); | ||
continue; | ||
} | ||
|
||
// go through each entry | ||
for (let [index, entry] of Object.entries(data)) { | ||
index = Number(index) + 1; | ||
const trace = `${file} entry ${index}`; | ||
|
||
// check if dict | ||
if (typeof entry !== "object") { | ||
addError(`${trace} is not a dict`); | ||
continue; | ||
} | ||
|
||
// check "from" field | ||
if (!(typeof entry.from === "string" && entry.from.trim())) { | ||
addError(`${trace} "from" field invalid`); | ||
continue; | ||
} | ||
|
||
// check "to" field | ||
if (!(typeof entry.to === "string" && entry.to.trim())) | ||
addError(`${trace} "to" field invalid`); | ||
|
||
// normalize "from" field. lower case, remove leading slashes. | ||
entry.from = entry.from.toLowerCase().replace(/^(\/+)/, ""); | ||
|
||
// add to combined list | ||
list.push(entry); | ||
|
||
// add to duplicate list. record source file and entry number for logging. | ||
duplicates[entry.from] ??= []; | ||
duplicates[entry.from].push({ ...entry, file, index }); | ||
} | ||
} | ||
|
||
// check that any redirects exist | ||
if (!list.length) addError("No redirects"); | ||
|
||
if (verbose) log("Combined redirects list", list); | ||
|
||
// trigger errors for duplicates | ||
for (const [from, entries] of Object.entries(duplicates)) { | ||
const count = entries.length; | ||
if (count <= 1) continue; | ||
const duplicates = entries | ||
.map(({ file, index }) => `\n ${file} entry ${index}`) | ||
.join(""); | ||
addError(`"from: ${from}" appears ${count} time(s): ${duplicates}`); | ||
} | ||
|
||
return list; | ||
} | ||
|
||
// collect (caught) errors to report at end | ||
const errors = []; | ||
|
||
// add error | ||
export function addError(error) { | ||
errors.push(error); | ||
} | ||
|
||
// when script finished, report all errors together | ||
export function onExit() { | ||
process.on("exit", () => { | ||
if (errors.length) { | ||
errors.forEach(logError); | ||
logError(`${errors.length} error(s)`); | ||
process.exit(1); | ||
} else { | ||
process.exitCode = 0; | ||
log("No errors!"); | ||
} | ||
}); | ||
} | ||
|
||
// formatted normal log | ||
export function log(message, data) { | ||
console.info("\x1b[1m\x1b[96m" + message + "\x1b[0m"); | ||
if (data) console.log(data); | ||
} | ||
|
||
// formatted error log | ||
export function logError(message) { | ||
console.error("\x1b[1m\x1b[91m" + message + "\x1b[0m"); | ||
} |
Oops, something went wrong.