Skip to content

Commit 5cfcafa

Browse files
authored
Merge branch 'main' into feat/low-node-passthrough
2 parents ac565de + b386620 commit 5cfcafa

32 files changed

Lines changed: 5393 additions & 1596 deletions
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#!/usr/bin/env bash
2+
# Regenerate the vendored Node.js release signing keyring from the canonical
3+
# nodejs/release-keys repository.
4+
#
5+
# The keyring is the trust anchor used by `vite_js_runtime` to verify the PGP
6+
# signature on Node.js `SHASUMS256.txt` (see rfcs/verify-node-shasums-signature.md).
7+
# Run from the repository root:
8+
#
9+
# .github/scripts/update-node-release-keys.sh
10+
#
11+
# When KEYRING_SUMMARY_FILE is set, a Markdown pull-request body describing the
12+
# before/after key changes (added, removed, modified, by fingerprint) is written
13+
# to that path. The accompanying workflow
14+
# (.github/workflows/update-node-release-keys.yml) runs this weekly and opens a
15+
# PR when the keyring changes. The change must be reviewed by a human before
16+
# merging, since it alters which keys are trusted.
17+
set -euo pipefail
18+
19+
DEST="crates/vite_js_runtime/src/assets/node-release-keys.asc"
20+
SUMMARY_FILE="${KEYRING_SUMMARY_FILE:-}"
21+
22+
if [[ ! -f "$DEST" ]]; then
23+
echo "error: run from the repository root (missing $DEST)" >&2
24+
exit 1
25+
fi
26+
27+
TMP="$(mktemp -d)"
28+
trap 'rm -rf "$TMP"' EXIT
29+
30+
# Snapshot the current keyring so we can diff it after regenerating.
31+
cp "$DEST" "$TMP/before.asc"
32+
33+
git clone --depth 1 https://github.com/nodejs/release-keys.git "$TMP/release-keys"
34+
35+
# Concatenate every armored key block, sorted by filename and separated by a
36+
# single blank-free newline, so a key whose file lacks a trailing newline does
37+
# not get its END line merged with the next BEGIN line.
38+
python3 - "$TMP/release-keys/keys" "$DEST" <<'PY'
39+
import glob
40+
import os
41+
import sys
42+
43+
keys_dir, dest = sys.argv[1], sys.argv[2]
44+
files = sorted(glob.glob(os.path.join(keys_dir, "*.asc")))
45+
if not files:
46+
sys.exit("error: no key files found in nodejs/release-keys")
47+
48+
blocks = [
49+
open(path, encoding="utf-8").read().replace("\r\n", "\n").replace("\r", "\n").strip("\n")
50+
for path in files
51+
]
52+
with open(dest, "w", encoding="utf-8") as out:
53+
out.write("\n".join(blocks) + "\n")
54+
print(f"wrote {len(files)} release keys to {dest}")
55+
PY
56+
57+
# Sanity check: balanced, non-trivial keyring.
58+
begin=$(grep -c '^-----BEGIN PGP PUBLIC KEY BLOCK-----$' "$DEST" || true)
59+
end=$(grep -c '^-----END PGP PUBLIC KEY BLOCK-----$' "$DEST" || true)
60+
if [[ "$begin" != "$end" || "$begin" -lt 8 ]]; then
61+
echo "error: unexpected keyring (begin=$begin end=$end)" >&2
62+
exit 1
63+
fi
64+
echo "keyring has $begin key blocks"
65+
66+
# Write the PR body with a before/after comparison of the changed keys. Best
67+
# effort: if the diff can't be produced (e.g. gpg missing), fall back to a plain
68+
# body so the PR is still created.
69+
if [[ -n "$SUMMARY_FILE" ]]; then
70+
if ! python3 - "$TMP/before.asc" "$DEST" > "$SUMMARY_FILE" <<'PY'
71+
import hashlib
72+
import re
73+
import shutil
74+
import subprocess
75+
import sys
76+
import tempfile
77+
78+
PREAMBLE = (
79+
"Automated refresh of the vendored Node.js release signing keys from "
80+
"[nodejs/release-keys](https://github.com/nodejs/release-keys).\n\n"
81+
"This keyring is the trust anchor used to verify the PGP signature on "
82+
"Node.js `SHASUMS256.txt` (see `rfcs/verify-node-shasums-signature.md`).\n\n"
83+
"**Review the key changes below before merging.** PR CI runs the "
84+
"`vite_js_runtime` tests, which confirm every vendored key still parses; a "
85+
"new key that fails to parse will surface as a failing test.\n"
86+
)
87+
88+
89+
def unescape(value):
90+
# gpg --with-colons backslash-escapes field-conflicting bytes as \xHH
91+
# (e.g. a literal ':' in a uid becomes '\x3a'); decode them for display.
92+
return re.sub(r"\\x([0-9a-fA-F]{2})", lambda m: chr(int(m.group(1), 16)), value)
93+
94+
95+
def load_keys(asc_path):
96+
"""Map primary fingerprint -> {uid, digest of its colon record}."""
97+
home = tempfile.mkdtemp()
98+
try:
99+
out = subprocess.run(
100+
["gpg", "--homedir", home, "--quiet", "--batch",
101+
"--show-keys", "--with-colons", asc_path],
102+
capture_output=True, text=True, check=True,
103+
).stdout
104+
finally:
105+
shutil.rmtree(home, ignore_errors=True)
106+
107+
keys, fpr, pending = {}, None, []
108+
for line in out.splitlines():
109+
field = line.split(":")
110+
record = field[0]
111+
if record == "pub":
112+
fpr, pending = None, [line]
113+
elif record == "fpr" and fpr is None:
114+
fpr = field[9]
115+
keys[fpr] = {"uid": "", "lines": pending + [line]}
116+
pending = []
117+
elif fpr is not None:
118+
keys[fpr]["lines"].append(line)
119+
if record == "uid" and not keys[fpr]["uid"]:
120+
keys[fpr]["uid"] = unescape(field[9])
121+
else:
122+
pending.append(line)
123+
124+
for entry in keys.values():
125+
entry["digest"] = hashlib.sha256("\n".join(entry["lines"]).encode()).hexdigest()
126+
return keys
127+
128+
129+
before = load_keys(sys.argv[1])
130+
after = load_keys(sys.argv[2])
131+
132+
added = sorted(set(after) - set(before))
133+
removed = sorted(set(before) - set(after))
134+
modified = sorted(f for f in set(after) & set(before)
135+
if after[f]["digest"] != before[f]["digest"])
136+
137+
138+
def bullets(fingerprints, table):
139+
if not fingerprints:
140+
return ["- _none_"]
141+
out = []
142+
for fpr in fingerprints:
143+
uid = table[fpr]["uid"] or "(no user id)"
144+
out.append(f"- {uid} `{fpr}`")
145+
return out
146+
147+
148+
lines = [PREAMBLE, "## Key changes", ""]
149+
lines.append(f"Release keyring: **{len(before)} → {len(after)} keys**.")
150+
lines += ["", f"### Added ({len(added)})", *bullets(added, after)]
151+
lines += ["", f"### Removed ({len(removed)})", *bullets(removed, before)]
152+
lines += ["", f"### Modified ({len(modified)})", *bullets(modified, after)]
153+
print("\n".join(lines))
154+
PY
155+
then
156+
echo "warning: could not build key diff; writing a plain PR body" >&2
157+
cat > "$SUMMARY_FILE" <<'EOF'
158+
Automated refresh of the vendored Node.js release signing keys from
159+
[nodejs/release-keys](https://github.com/nodejs/release-keys).
160+
161+
This keyring is the trust anchor used to verify the PGP signature on Node.js
162+
`SHASUMS256.txt` (see `rfcs/verify-node-shasums-signature.md`). Review which keys
163+
changed (`git diff`) before merging.
164+
EOF
165+
fi
166+
echo "wrote PR body to $SUMMARY_FILE"
167+
fi

.github/workflows/security.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,4 @@ jobs:
4545
shell: bash
4646
run: git remote remove origin
4747

48-
- uses: oxc-project/security-action@cba83d3159bd19731697875c03613fd0fe389f27 # v1.0.7
48+
- uses: oxc-project/security-action@2ac862d109c9728f5bf439d249b69f68905a5de4 # v1.0.8

.github/workflows/test-vp-create.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ jobs:
189189
# Force full dependency rewriting so the library template's existing
190190
# vite-plus dep gets overridden with the local tgz
191191
VP_FORCE_MIGRATE: '1'
192+
# yarn 4 quarantines packages published within `npmMinimalAgeGate`
193+
# (default 1440 min / 24h). When an oxlint bump landed <24h ago, the
194+
# migration step's `vp dlx @oxlint/migrate@<bundled oxlint>` fails with
195+
# `YN0016 ... are quarantined`. The migrate tool is version-pinned to the
196+
# bundled oxlint, so disable the gate for this test (no-op for npm/pnpm/bun).
197+
YARN_NPM_MINIMAL_AGE_GATE: '0'
192198
steps:
193199
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
194200

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Update Node release keys
2+
3+
on:
4+
schedule:
5+
# Weekly: Monday 00:30 UTC
6+
- cron: '30 0 * * 1'
7+
workflow_dispatch:
8+
9+
permissions: {}
10+
11+
defaults:
12+
run:
13+
shell: bash
14+
15+
jobs:
16+
update:
17+
if: github.repository == 'voidzero-dev/vite-plus' && github.event.repository.fork == false
18+
runs-on: ubuntu-latest
19+
permissions:
20+
contents: read
21+
steps:
22+
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
23+
24+
- name: Regenerate vendored Node.js release keyring
25+
env:
26+
# The script writes a PR body with a before/after key diff here.
27+
KEYRING_SUMMARY_FILE: ${{ runner.temp }}/keyring-pr-body.md
28+
run: .github/scripts/update-node-release-keys.sh
29+
30+
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
31+
id: app-token
32+
with:
33+
client-id: ${{ secrets.APP_ID }}
34+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
35+
36+
- name: Create or update PR
37+
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
38+
with:
39+
base: main
40+
branch: chore/update-node-release-keys
41+
title: 'chore(runtime): refresh Node.js release keyring'
42+
sign-commits: true
43+
token: ${{ steps.app-token.outputs.token }}
44+
commit-message: 'chore(runtime): refresh Node.js release keyring'
45+
add-paths: |
46+
crates/vite_js_runtime/src/assets/node-release-keys.asc
47+
body-path: ${{ runner.temp }}/keyring-pr-body.md

.typos.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ ratatui = "ratatui"
33
PUNICODE = "PUNICODE"
44
Jod = "Jod" # Node.js v22 LTS codename
55
flate = "flate" # flate2 crate name (gzip/deflate compression)
6+
fpr = "fpr" # GPG colon-format record name for a key fingerprint
67

78
[default.extend-identifiers]
89
# Yarn Plug'n'Play — tokenizer splits `PnP` into the word `Pn`, which typos
@@ -16,4 +17,6 @@ extend-exclude = [
1617
"crates/fspy_detours_sys/detours",
1718
"crates/fspy_detours_sys/src/generated_bindings.rs",
1819
"packages/cli/src/oxfmt-config.ts",
20+
# Vendored PGP key/signature blobs: base64 armor trips the spell checker.
21+
"crates/vite_js_runtime/src/assets/**/*.asc",
1922
]

0 commit comments

Comments
 (0)