Skip to content

Commit 65e12fc

Browse files
committed
Add custom script for checking lit.dev redirects
1 parent 4d623e0 commit 65e12fc

File tree

4 files changed

+153
-3
lines changed

4 files changed

+153
-3
lines changed

.github/workflows/build.yml

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ jobs:
2222
- name: Build
2323
run: npm run build
2424

25+
- name: Check for broken redirects
26+
run: npm run test:links:redirects
27+
2528
- name: Check for broken links (internal)
2629
run: npm run test:links:internal
2730

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"format": "prettier \"**/*.{ts,js,json,html,css,md}\" --write",
1212
"nuke": "rm -rf node_modules package-lock.json packages/*/node_modules packages/*/package-lock.json && npm install && npx lerna bootstrap",
1313
"test": "npm run test:links",
14-
"test:links": "npm run test:links:internal && npm run test:links:external",
14+
"test:links": "npm run test:links:redirects && npm run test:links:internal && npm run test:links:external",
15+
"test:links:redirects": "node packages/lit-dev-tools/lib/check-redirects.js",
1516
"test:links:internal": "run-p -r start check-links:internal",
1617
"test:links:external": "run-p -r start check-links:external",
1718
"check-links:internal": "wait-on tcp:8080 && blc http://localhost:8080 --recursive --exclude-external --ordered",

packages/lit-dev-tools/package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,26 @@
1212
"test": "npm run build && uvu ./lib \".spec.js$\""
1313
},
1414
"dependencies": {
15+
"@types/jsdom": "^16.2.13",
1516
"@types/markdown-it": "^12.0.1",
1617
"@types/source-map": "^0.5.7",
18+
"@types/strip-comments": "^2.0.1",
1719
"@web/dev-server": "^0.1.6",
1820
"@web/dev-server-core": "^0.3.5",
21+
"ansi-escape-sequences": "^6.2.0",
1922
"fast-glob": "^3.2.5",
2023
"jsdom": "^16.6.0",
24+
"lit-dev-server": "^0.0.0",
2125
"markdown-it-anchor": "^7.1.0",
2226
"minisearch": "^3.0.4",
27+
"node-fetch": "^3.0.0",
2328
"outdent": "^0.8.0",
2429
"playground-elements": "^0.12.1",
2530
"playwright": "^1.8.0",
2631
"source-map": "^0.7.3",
2732
"strip-comments": "^2.0.1",
2833
"striptags": "^3.2.0",
2934
"typedoc": "^0.20.30",
30-
"@types/jsdom": "^16.2.13",
31-
"@types/strip-comments": "^2.0.1",
3235
"uvu": "^0.5.1"
3336
}
3437
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
import * as pathLib from 'path';
8+
import * as fs from 'fs/promises';
9+
import ansi from 'ansi-escape-sequences';
10+
11+
/**
12+
* We're a CommonJS module that needs to import some ES modules.
13+
*
14+
* We're a CommonJS module because Eleventy doesn't support ESM
15+
* (https://github.com/11ty/eleventy/issues/836), and most of the tools in this
16+
* package are for Eleventy.
17+
*
18+
* Node supports `await import(<ESM>)` for importing ESM from CommonJS. However,
19+
* TypeScript doesn't support emitting `import` statements -- it will always
20+
* transpile them to `require`, which breaks interop
21+
* (https://github.com/microsoft/TypeScript/issues/43329).
22+
*
23+
* This is a wacky eval hack until either TypeScript supports CommonJS -> ESM
24+
* interop, or we can rewrite this package as ESM, or we split the tools package
25+
* into two packages: one for CommonJS, one for ESM.
26+
*/
27+
const transpileSafeImport = async (specifier: string) =>
28+
eval(`import("${specifier}")`);
29+
30+
const fetchImportPromise = transpileSafeImport('node-fetch');
31+
32+
const {red, green, yellow, bold, reset} = ansi.style;
33+
34+
const OK = Symbol();
35+
type ErrorMessage = string;
36+
37+
const isUrl = (str: string) => {
38+
try {
39+
new URL(str);
40+
return true;
41+
} catch {
42+
return false;
43+
}
44+
};
45+
46+
const trimTrailingSlash = (str: string) =>
47+
str.endsWith('/') ? str.slice(0, str.length - 1) : str;
48+
49+
const siteOutputDir = pathLib.resolve(
50+
__dirname,
51+
'../',
52+
'../',
53+
'lit-dev-content',
54+
'_site'
55+
);
56+
57+
const checkRedirect = async (
58+
redirect: string
59+
): Promise<ErrorMessage | typeof OK> => {
60+
const {default: fetch} = await fetchImportPromise;
61+
if (isUrl(redirect)) {
62+
// Remote URLs.
63+
let res;
64+
try {
65+
res = await fetch(redirect);
66+
} catch (e) {
67+
return `Fetch error: ${(e as Error).message}`;
68+
}
69+
if (res.status !== 200) {
70+
return `HTTP ${res.status} error`;
71+
}
72+
} else {
73+
// Local paths. A bit hacky, but since we know how Eleventy works, we don't
74+
// need to actually run the server, we can just look directly in the built
75+
// HTML output directory.
76+
const {pathname, hash} = new URL(redirect, 'http://lit.dev');
77+
const diskPath = pathLib.relative(
78+
process.cwd(),
79+
pathLib.join(siteOutputDir, trimTrailingSlash(pathname), 'index.html')
80+
);
81+
let data;
82+
try {
83+
data = await fs.readFile(diskPath, {encoding: 'utf8'});
84+
} catch {
85+
return `Could not find file matching path ${pathname}
86+
Searched for file ${diskPath}`;
87+
}
88+
if (hash) {
89+
// Another hack. Just do a regexp search for e.g. id="somesection" instead
90+
// of DOM parsing. Should be good enough, especially given how regular our
91+
// Markdown generated HTML is.
92+
const idAttrRegExp = new RegExp(`\\sid=["']?${hash.slice(1)}["']?[\\s>]`);
93+
if (data.match(idAttrRegExp) === null) {
94+
return `Could not find section matching hash ${hash}.
95+
Searched in file ${diskPath}`;
96+
}
97+
}
98+
}
99+
return OK;
100+
};
101+
102+
const checkAllRedirects = async () => {
103+
console.log('==========================');
104+
console.log('Checking lit.dev redirects');
105+
console.log('==========================');
106+
console.log();
107+
108+
const {pageRedirects} = await transpileSafeImport(
109+
'lit-dev-server/redirects.js'
110+
);
111+
let fail = false;
112+
const promises = [];
113+
for (const [from, to] of pageRedirects) {
114+
promises.push(
115+
(async () => {
116+
const result = await checkRedirect(to);
117+
if (result === OK) {
118+
console.log(`${bold + green}OK${reset} ${from} -> ${to}`);
119+
} else {
120+
console.log();
121+
console.log(
122+
`${bold + red}BROKEN REDIRECT${reset} ${from} -> ${
123+
yellow + to + reset
124+
}`
125+
);
126+
console.log(result);
127+
console.log();
128+
fail = true;
129+
}
130+
})()
131+
);
132+
}
133+
await Promise.all(promises);
134+
console.log();
135+
if (fail) {
136+
console.log('Redirects were broken!');
137+
process.exit(1);
138+
} else {
139+
console.error('All redirects OK!');
140+
}
141+
};
142+
143+
checkAllRedirects();

0 commit comments

Comments
 (0)