-
Notifications
You must be signed in to change notification settings - Fork 188
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add custom script for checking lit.dev redirects
- Loading branch information
Showing
4 changed files
with
153 additions
and
3 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
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,143 @@ | ||
/** | ||
* @license | ||
* Copyright 2021 Google LLC | ||
* SPDX-License-Identifier: BSD-3-Clause | ||
*/ | ||
|
||
import * as pathLib from 'path'; | ||
import * as fs from 'fs/promises'; | ||
import ansi from 'ansi-escape-sequences'; | ||
|
||
/** | ||
* We're a CommonJS module that needs to import some ES modules. | ||
* | ||
* We're a CommonJS module because Eleventy doesn't support ESM | ||
* (https://github.com/11ty/eleventy/issues/836), and most of the tools in this | ||
* package are for Eleventy. | ||
* | ||
* Node supports `await import(<ESM>)` for importing ESM from CommonJS. However, | ||
* TypeScript doesn't support emitting `import` statements -- it will always | ||
* transpile them to `require`, which breaks interop | ||
* (https://github.com/microsoft/TypeScript/issues/43329). | ||
* | ||
* This is a wacky eval hack until either TypeScript supports CommonJS -> ESM | ||
* interop, or we can rewrite this package as ESM, or we split the tools package | ||
* into two packages: one for CommonJS, one for ESM. | ||
*/ | ||
const transpileSafeImport = async (specifier: string) => | ||
eval(`import("${specifier}")`); | ||
|
||
const fetchImportPromise = transpileSafeImport('node-fetch'); | ||
|
||
const {red, green, yellow, bold, reset} = ansi.style; | ||
|
||
const OK = Symbol(); | ||
type ErrorMessage = string; | ||
|
||
const isUrl = (str: string) => { | ||
try { | ||
new URL(str); | ||
return true; | ||
} catch { | ||
return false; | ||
} | ||
}; | ||
|
||
const trimTrailingSlash = (str: string) => | ||
str.endsWith('/') ? str.slice(0, str.length - 1) : str; | ||
|
||
const siteOutputDir = pathLib.resolve( | ||
__dirname, | ||
'../', | ||
'../', | ||
'lit-dev-content', | ||
'_site' | ||
); | ||
|
||
const checkRedirect = async ( | ||
redirect: string | ||
): Promise<ErrorMessage | typeof OK> => { | ||
const {default: fetch} = await fetchImportPromise; | ||
if (isUrl(redirect)) { | ||
// Remote URLs. | ||
let res; | ||
try { | ||
res = await fetch(redirect); | ||
} catch (e) { | ||
return `Fetch error: ${(e as Error).message}`; | ||
} | ||
if (res.status !== 200) { | ||
return `HTTP ${res.status} error`; | ||
} | ||
} else { | ||
// Local paths. A bit hacky, but since we know how Eleventy works, we don't | ||
// need to actually run the server, we can just look directly in the built | ||
// HTML output directory. | ||
const {pathname, hash} = new URL(redirect, 'http://lit.dev'); | ||
const diskPath = pathLib.relative( | ||
process.cwd(), | ||
pathLib.join(siteOutputDir, trimTrailingSlash(pathname), 'index.html') | ||
); | ||
let data; | ||
try { | ||
data = await fs.readFile(diskPath, {encoding: 'utf8'}); | ||
} catch { | ||
return `Could not find file matching path ${pathname} | ||
Searched for file ${diskPath}`; | ||
} | ||
if (hash) { | ||
// Another hack. Just do a regexp search for e.g. id="somesection" instead | ||
// of DOM parsing. Should be good enough, especially given how regular our | ||
// Markdown generated HTML is. | ||
const idAttrRegExp = new RegExp(`\\sid=["']?${hash.slice(1)}["']?[\\s>]`); | ||
if (data.match(idAttrRegExp) === null) { | ||
return `Could not find section matching hash ${hash}. | ||
Searched in file ${diskPath}`; | ||
} | ||
} | ||
} | ||
return OK; | ||
}; | ||
|
||
const checkAllRedirects = async () => { | ||
console.log('=========================='); | ||
console.log('Checking lit.dev redirects'); | ||
console.log('=========================='); | ||
console.log(); | ||
|
||
const {pageRedirects} = await transpileSafeImport( | ||
'lit-dev-server/redirects.js' | ||
); | ||
let fail = false; | ||
const promises = []; | ||
for (const [from, to] of pageRedirects) { | ||
promises.push( | ||
(async () => { | ||
const result = await checkRedirect(to); | ||
if (result === OK) { | ||
console.log(`${bold + green}OK${reset} ${from} -> ${to}`); | ||
} else { | ||
console.log(); | ||
console.log( | ||
`${bold + red}BROKEN REDIRECT${reset} ${from} -> ${ | ||
yellow + to + reset | ||
}` | ||
); | ||
console.log(result); | ||
console.log(); | ||
fail = true; | ||
} | ||
})() | ||
); | ||
} | ||
await Promise.all(promises); | ||
console.log(); | ||
if (fail) { | ||
console.log('Redirects were broken!'); | ||
process.exit(1); | ||
} else { | ||
console.error('All redirects OK!'); | ||
} | ||
}; | ||
|
||
checkAllRedirects(); |