Skip to content

Commit 3be02d5

Browse files
committed
Meta: Add a warning to PR previews
1 parent 9e9299f commit 3be02d5

File tree

4 files changed

+154
-2
lines changed

4 files changed

+154
-2
lines changed

.github/workflows/publish-pr.yml

+21-1
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,31 @@ jobs:
5757
id: extract-pr-number
5858
run: |
5959
cd result
60-
awk -vok=1 \
60+
awk -v ok=1 \
6161
'{ print; if(!match($0, /^[1-9][0-9]*$/)) ok=0; } END { exit !(NR==1 && ok); }' \
6262
pr-number.txt
6363
echo "number=$(cat pr-number.txt)" >> $GITHUB_OUTPUT
6464
rm pr-number.txt
65+
- name: Insert preview warning
66+
env:
67+
PR: ${{ steps.extract-pr-number.outputs.number }}
68+
run: |
69+
tmp="$(mktemp -u XXXXXXXX.json)"
70+
repo_url="https://github.com/$GITHUB_REPOSITORY"
71+
commit="$(git rev-parse --verify HEAD)"
72+
jq -n --arg repo_url "$repo_url" --arg PR "$PR" --arg commit "$commit" '
73+
def repo_link($args): $args as [$path, $contents]
74+
| ($repo_url + ($path // "")) as $url
75+
| "<a href=\"\($url | @html)\">\($contents // $url)</a>";
76+
{
77+
SUMMARY: "PR #\($PR)",
78+
REPO_LINK: repo_link([]),
79+
PR_LINK: repo_link(["/pull/" + $PR, "PR #\($PR)"]),
80+
COMMIT_LINK: ("commit " + repo_link(["/commit/" + $commit, "<code>\($commit)</code>"])),
81+
}
82+
' > "$tmp"
83+
find result -name '*.html' -exec \
84+
node scripts/insert_warning.mjs --strict scripts/pr_preview_warning.html "$tmp" '{}' '+'
6585
- name: Publish to gh-pages
6686
uses: JamesIves/[email protected]
6787
with:

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"license": "MIT",
1818
"devDependencies": {
1919
"@tc39/ecma262-biblio": "^2.1.2775",
20-
"ecmarkup": "^20.0.0"
20+
"ecmarkup": "^20.0.0",
21+
"jsdom": "^25.0.1"
2122
},
2223
"engines": {
2324
"node": ">= 12"

scripts/insert_warning.mjs

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import fs from 'node:fs';
2+
import pathlib from 'node:path';
3+
import { parseArgs } from 'node:util';
4+
import { JSDOM, VirtualConsole } from 'jsdom';
5+
6+
const { positionals: cliArgs, values: cliOpts } = parseArgs({
7+
allowPositionals: true,
8+
options: {
9+
strict: { type: 'boolean' },
10+
},
11+
});
12+
if (cliArgs.length < 3) {
13+
const self = pathlib.relative(process.cwd(), process.argv[1]);
14+
console.error(`Usage: node ${self} [--strict] <template.html> <data.json> <file.html>...
15+
16+
{{identifier}} substrings in template.html are replaced from data.json, then
17+
the result is inserted at the start of the body element in each file.html.`);
18+
process.exit(64);
19+
}
20+
21+
const main = async (args, options) => {
22+
const [templateFile, dataFile, ...files] = args;
23+
const { strict } = options;
24+
25+
// Evaluate the template and parse it into nodes for inserting.
26+
// Everything will be prepended to body elements except metadata elements,
27+
// which will be appended to head elements.
28+
// https://html.spec.whatwg.org/multipage/dom.html#metadata-content-2
29+
const metadataNames =
30+
'base, link, meta, noscript, script, style, template, title'
31+
.toUpperCase()
32+
.split(', ');
33+
const template = fs.readFileSync(templateFile, 'utf8');
34+
const { default: data } =
35+
await import(pathlib.resolve(dataFile), { with: { type: 'json' } });
36+
const namePatt = /[{][{]([\p{ID_Start}$_][\p{ID_Continue}$]*)[}][}]/gu;
37+
const resolved = template.replaceAll(namePatt, (_, name) => {
38+
if (Object.hasOwn(data, name)) return data[name];
39+
if (strict) throw Error(`no data for {{${name}}}`);
40+
return '';
41+
});
42+
const headInserts = [], bodyInserts = [];
43+
let insertDom = JSDOM.fragment(resolved);
44+
for (const node of insertDom.childNodes) {
45+
if (metadataNames.includes(node.nodeName)) headInserts.push(node);
46+
else bodyInserts.push(node);
47+
}
48+
49+
// Perform the insertions, suppressing JSDOM warnings from e.g. unsupported
50+
// CSS features.
51+
const virtualConsole = new VirtualConsole();
52+
virtualConsole.on('error', () => {});
53+
const jsdomOpts = { contentType: 'text/html; charset=utf-8', virtualConsole };
54+
const getInserts =
55+
files.length > 1 ? nodes => nodes.map(n => n.cloneNode(true)) : x => x;
56+
const results = await Promise.allSettled(files.map(async file => {
57+
let dom = await JSDOM.fromFile(file, jsdomOpts);
58+
const { head, body } = dom.window.document;
59+
if (headInserts.length > 0) head.append(...getInserts(headInserts));
60+
if (bodyInserts.length > 0) body.prepend(...getInserts(bodyInserts));
61+
fs.writeFileSync(file, dom.serialize(), 'utf8');
62+
}));
63+
64+
const failures = results.flatMap(result =>
65+
result.status === 'fulfilled' ? [] : [result.reason],
66+
);
67+
if (failures.length > 0) throw AggregateError(failures);
68+
};
69+
70+
main(cliArgs, cliOpts).catch(err => {
71+
console.error(err);
72+
process.exit(1);
73+
});

scripts/pr_preview_warning.html

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<style>
2+
details.annoying-warning {
3+
position: fixed;
4+
top: 0;
5+
right: 0;
6+
z-index: 10;
7+
border: 2px solid white;
8+
background-color: #920800;
9+
background-image: linear-gradient(transparent 40%, rgba(255, 255, 255, 0.2));
10+
color: white;
11+
opacity: .95;
12+
}
13+
14+
details.annoying-warning[open] {
15+
top: 10%;
16+
top: calc(5vw + 5vh);
17+
left: 5%;
18+
right: 5%;
19+
margin: 0 auto;
20+
max-width: 800px;
21+
outline: solid 10000px rgba(255, 255, 255, 0.6);
22+
border-radius: 3px;
23+
border-width: 1px 1px 0 1px;
24+
box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.5);
25+
}
26+
27+
details.annoying-warning > summary {
28+
padding: 0.5ex 1ex;
29+
font-size: 0.875em;
30+
font-weight: bold;
31+
letter-spacing: 0.02em;
32+
text-align: center;
33+
text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.85);
34+
cursor: default;
35+
}
36+
37+
details.annoying-warning > p {
38+
line-height: 1.4;
39+
margin: 1em;
40+
text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.85);
41+
}
42+
43+
details.annoying-warning a {
44+
color: inherit;
45+
text-decoration: underline;
46+
}
47+
</style>
48+
49+
<details class="annoying-warning">
50+
<summary>{{SUMMARY}}</summary>
51+
<p>
52+
This document is a preview of merging {{PR_LINK}}, resulting in {{COMMIT_LINK}}.
53+
</p>
54+
<p>
55+
Do not reference it as authoritative in any way.
56+
Instead, see {{REPO_LINK}} for the living specification.
57+
</p>
58+
</details>

0 commit comments

Comments
 (0)