No github
This commit is contained in:
parent
1cdc202731
commit
283df32afe
2 changed files with 101 additions and 0 deletions
|
|
@ -35,6 +35,7 @@ jobs:
|
|||
npm run typecheck
|
||||
npm run qa:no-em-dashes
|
||||
npm run qa:spelling
|
||||
npm run qa:no-github-links
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
|
|
|||
100
scripts/check-no-github-links.mjs
Normal file
100
scripts/check-no-github-links.mjs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
// cspell:ignore githubusercontent streetsidesoftware forgejo
|
||||
import { readFile, stat } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { walk } from './lib/walk.mjs';
|
||||
|
||||
const root = process.cwd();
|
||||
|
||||
// GitHub is no longer linked from anywhere on the site: project source links,
|
||||
// the social links, and the repo metadata all point at the self-hosted git
|
||||
// host instead. Matching the host substrings (rather than the bare word
|
||||
// "github") keeps the Shiki `github-light`/`github-dark` themes and the
|
||||
// Forgejo Actions `github.*` context variables from tripping the check.
|
||||
const bannedHosts = [/github\.com/i, /github\.io/i, /githubusercontent\.com/i];
|
||||
|
||||
// Upstream tooling URLs that genuinely live on GitHub and have no self-hosted
|
||||
// equivalent (e.g. the cspell config JSON Schema). A flagged URL is permitted
|
||||
// only when it exactly equals one of these.
|
||||
const permitted = new Set([
|
||||
'https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json',
|
||||
]);
|
||||
|
||||
const textExtensions = new Set([
|
||||
'.astro',
|
||||
'.css',
|
||||
'.html',
|
||||
'.js',
|
||||
'.json',
|
||||
'.md',
|
||||
'.mjs',
|
||||
'.ts',
|
||||
'.txt',
|
||||
'.vtt',
|
||||
'.webmanifest',
|
||||
'.xml',
|
||||
]);
|
||||
|
||||
const roots = [
|
||||
'src',
|
||||
'public',
|
||||
'scripts',
|
||||
'README.md',
|
||||
'package.json',
|
||||
'astro.config.mjs',
|
||||
'cspell.json',
|
||||
].map((entry) => path.resolve(root, entry));
|
||||
|
||||
async function exists(filePath) {
|
||||
try {
|
||||
await stat(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Grows a match outward to the whole whitespace/quote-delimited URL token so it
|
||||
// can be compared against the permitted set.
|
||||
function urlToken(line, index, length) {
|
||||
const boundary = /[\s"'`<>()[\]{}]/;
|
||||
let start = index;
|
||||
while (start > 0 && !boundary.test(line[start - 1])) start -= 1;
|
||||
let end = index + length;
|
||||
while (end < line.length && !boundary.test(line[end])) end += 1;
|
||||
return line.slice(start, end);
|
||||
}
|
||||
|
||||
const files = [];
|
||||
|
||||
for (const entry of roots) {
|
||||
if (!(await exists(entry))) continue;
|
||||
files.push(...(await walk(entry)));
|
||||
}
|
||||
|
||||
const textFiles = files.filter((file) => textExtensions.has(path.extname(file)));
|
||||
|
||||
const failures = [];
|
||||
|
||||
for (const file of textFiles) {
|
||||
const lines = (await readFile(file, 'utf8')).split('\n');
|
||||
|
||||
lines.forEach((line, lineIndex) => {
|
||||
for (const pattern of bannedHosts) {
|
||||
for (const match of line.matchAll(new RegExp(pattern, 'gi'))) {
|
||||
const token = urlToken(line, match.index, match[0].length);
|
||||
if (permitted.has(token)) continue;
|
||||
const location = `${path.relative(root, file)}:${lineIndex + 1}:${match.index + 1}`;
|
||||
failures.push(location);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error(
|
||||
`GitHub links are not allowed; link the self-hosted git host instead:\n${failures.join('\n')}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('No GitHub links found.');
|
||||
Loading…
Add table
Add a link
Reference in a new issue