Skip to content

Commit 59cd2a2

Browse files
authored
Merge branch 'main' into feat/env-list-remote-installed-markers
2 parents 0e3621a + f2ee2df commit 59cd2a2

222 files changed

Lines changed: 8916 additions & 2600 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/renovate.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
"/^oxc-.*/",
2222
"@oxc-node/*",
2323
"@oxc-project/*",
24+
"@tsdown/css",
25+
"@tsdown/exe",
2426
"@vitejs/devtools",
27+
"lightningcss",
2528
"oxfmt",
2629
"oxlint",
2730
"oxlint-tsgolint",
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/scripts/upgrade-deps.ts

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ type LatestTag = {
2222
tag: string;
2323
};
2424

25+
type LatestTagOptions = {
26+
stableOnly?: boolean;
27+
};
28+
2529
type NpmLatestResponse = {
2630
version?: unknown;
31+
dependencies?: Record<string, string>;
2732
};
2833

2934
type UpstreamVersions = {
@@ -38,6 +43,7 @@ type UpstreamVersions = {
3843
type PnpmWorkspaceVersions = {
3944
vitest: string;
4045
tsdown: string;
46+
lightningcss: string;
4147
oxcNodeCli: string;
4248
oxcNodeCore: string;
4349
oxfmt: string;
@@ -62,6 +68,8 @@ type PackageJson = {
6268
peerDependencies?: Record<string, string>;
6369
};
6470

71+
const STABLE_SEMVER_TAG_RE = /^v?\d+\.\d+\.\d+$/;
72+
6573
const isFullSha = (s: string): boolean => /^[0-9a-f]{40}$/.test(s);
6674

6775
const changes = new Map<string, Change>();
@@ -89,21 +97,34 @@ function recordChange(
8997
}
9098

9199
// ============ GitHub API ============
92-
async function getLatestTag(owner: string, repo: string): Promise<LatestTag> {
93-
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/tags?per_page=1`, {
94-
headers: {
95-
Authorization: `token ${process.env.GITHUB_TOKEN}`,
96-
Accept: 'application/vnd.github.v3+json',
100+
async function getLatestTag(
101+
owner: string,
102+
repo: string,
103+
options: LatestTagOptions = {},
104+
): Promise<LatestTag> {
105+
const perPage = options.stableOnly ? 100 : 1;
106+
const res = await fetch(
107+
`https://api.github.com/repos/${owner}/${repo}/tags?per_page=${perPage}`,
108+
{
109+
headers: {
110+
Authorization: `token ${process.env.GITHUB_TOKEN}`,
111+
Accept: 'application/vnd.github.v3+json',
112+
},
97113
},
98-
});
114+
);
99115
if (!res.ok) {
100116
throw new Error(`Failed to fetch tags for ${owner}/${repo}: ${res.status} ${res.statusText}`);
101117
}
102118
const tags = (await res.json()) as GitHubTag[];
103119
if (!Array.isArray(tags) || !tags.length) {
104120
throw new Error(`No tags found for ${owner}/${repo}`);
105121
}
106-
const [latest] = tags;
122+
const latest = options.stableOnly
123+
? tags.find((tag) => typeof tag.name === 'string' && STABLE_SEMVER_TAG_RE.test(tag.name))
124+
: tags[0];
125+
if (!latest) {
126+
throw new Error(`No stable semver tags found for ${owner}/${repo}`);
127+
}
107128
if (typeof latest?.commit?.sha !== 'string' || typeof latest.name !== 'string') {
108129
throw new Error(`Invalid tag structure for ${owner}/${repo}: missing SHA or name`);
109130
}
@@ -112,20 +133,37 @@ async function getLatestTag(owner: string, repo: string): Promise<LatestTag> {
112133
}
113134

114135
// ============ npm Registry ============
115-
async function getLatestNpmVersion(packageName: string): Promise<string> {
136+
async function fetchNpmLatest(packageName: string): Promise<NpmLatestResponse> {
116137
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
117138
if (!res.ok) {
118139
throw new Error(
119-
`Failed to fetch npm version for ${packageName}: ${res.status} ${res.statusText}`,
140+
`Failed to fetch npm metadata for ${packageName}: ${res.status} ${res.statusText}`,
120141
);
121142
}
122-
const data = (await res.json()) as NpmLatestResponse;
143+
return (await res.json()) as NpmLatestResponse;
144+
}
145+
146+
async function getLatestNpmVersion(packageName: string): Promise<string> {
147+
const data = await fetchNpmLatest(packageName);
123148
if (typeof data.version !== 'string') {
124149
throw new Error(`Invalid npm response for ${packageName}: missing version field`);
125150
}
126151
return data.version;
127152
}
128153

154+
// Read a dependency range from the latest published version of `packageName`,
155+
// e.g. the `lightningcss` range that the bundled `@tsdown/css` depends on.
156+
async function getNpmDependencyRange(packageName: string, dependencyName: string): Promise<string> {
157+
const data = await fetchNpmLatest(packageName);
158+
const range = data.dependencies?.[dependencyName];
159+
if (typeof range !== 'string') {
160+
throw new Error(
161+
`Invalid npm response for ${packageName}: missing dependencies.${dependencyName}`,
162+
);
163+
}
164+
return range;
165+
}
166+
129167
// ============ Update .upstream-versions.json ============
130168
async function updateUpstreamVersions(): Promise<void> {
131169
const filePath = path.join(ROOT, 'packages/tools/.upstream-versions.json');
@@ -135,7 +173,7 @@ async function updateUpstreamVersions(): Promise<void> {
135173
const oldViteHash = data.vite.hash;
136174
const [rolldown, vite] = await Promise.all([
137175
getLatestTag('rolldown', 'rolldown'),
138-
getLatestTag('vitejs', 'vite'),
176+
getLatestTag('vitejs', 'vite', { stableOnly: true }),
139177
]);
140178
data.rolldown.hash = rolldown.sha;
141179
data.vite.hash = vite.sha;
@@ -193,6 +231,32 @@ async function updatePnpmWorkspace(versions: PnpmWorkspaceVersions): Promise<voi
193231
replacement: `tsdown: ^${versions.tsdown}`,
194232
newVersion: versions.tsdown,
195233
},
234+
// `@tsdown/css` and `@tsdown/exe` are bundled into core and published in
235+
// lockstep with tsdown (they exact-peer-depend on the same tsdown version),
236+
// so pin both catalog entries to the tsdown version to avoid drift.
237+
{
238+
name: '@tsdown/css',
239+
pattern: /'@tsdown\/css': \^([\d.]+(?:-[\w.]+)?)/,
240+
replacement: `'@tsdown/css': ^${versions.tsdown}`,
241+
newVersion: versions.tsdown,
242+
},
243+
{
244+
name: '@tsdown/exe',
245+
pattern: /'@tsdown\/exe': \^([\d.]+(?:-[\w.]+)?)/,
246+
replacement: `'@tsdown/exe': ^${versions.tsdown}`,
247+
newVersion: versions.tsdown,
248+
},
249+
// `lightningcss` is a core dependency consumed by the bundled `@tsdown/css`.
250+
// Track exactly what `@tsdown/css` requires (already an `^x.y.z` range) so a
251+
// tsdown upgrade that bumps lightningcss is mirrored here.
252+
{
253+
name: 'lightningcss',
254+
// Match any range value (not just `^x.y.z`) so the pattern can re-match
255+
// whatever `@tsdown/css` declares (`~`, `>=`, compound ranges) on the next run.
256+
pattern: /\n {2}lightningcss: ([^\n]+)\n/,
257+
replacement: `\n lightningcss: ${versions.lightningcss}\n`,
258+
newVersion: versions.lightningcss,
259+
},
196260
{
197261
name: '@oxc-node/cli',
198262
pattern: /'@oxc-node\/cli': \^([\d.]+(?:-[\w.]+)?)/,
@@ -497,6 +561,7 @@ console.log('Fetching latest versions…');
497561
const [
498562
vitestVersion,
499563
tsdownVersion,
564+
lightningcssVersion,
500565
devtoolsVersion,
501566
oxcNodeCliVersion,
502567
oxcNodeCoreVersion,
@@ -511,6 +576,8 @@ const [
511576
] = await Promise.all([
512577
getLatestNpmVersion('vitest'),
513578
getLatestNpmVersion('tsdown'),
579+
// Mirror exactly what the bundled @tsdown/css depends on.
580+
getNpmDependencyRange('@tsdown/css', 'lightningcss'),
514581
getLatestNpmVersion('@vitejs/devtools'),
515582
getLatestNpmVersion('@oxc-node/cli'),
516583
getLatestNpmVersion('@oxc-node/core'),
@@ -526,6 +593,7 @@ const [
526593

527594
console.log(`vitest: ${vitestVersion}`);
528595
console.log(`tsdown: ${tsdownVersion}`);
596+
console.log(`lightningcss (from @tsdown/css): ${lightningcssVersion}`);
529597
console.log(`@vitejs/devtools: ${devtoolsVersion}`);
530598
console.log(`@oxc-node/cli: ${oxcNodeCliVersion}`);
531599
console.log(`@oxc-node/core: ${oxcNodeCoreVersion}`);
@@ -542,6 +610,7 @@ await updateUpstreamVersions();
542610
await updatePnpmWorkspace({
543611
vitest: vitestVersion,
544612
tsdown: tsdownVersion,
613+
lightningcss: lightningcssVersion,
545614
oxcNodeCli: oxcNodeCliVersion,
546615
oxcNodeCore: oxcNodeCoreVersion,
547616
oxfmt: oxfmtVersion,

.github/workflows/ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ jobs:
294294
Get-Command tsc
295295
296296
# Test 2: Verify the package was installed correctly
297-
Get-ChildItem (Join-Path $vpHome "packages/typescript")
298297
Get-ChildItem $vpBin
299298
300299
# Test 3: Uninstall
@@ -386,7 +385,6 @@ jobs:
386385
which tsc
387386
388387
# Test 2: Verify the package was installed correctly
389-
ls -la ~/.vite-plus/packages/typescript/
390388
ls -la ~/.vite-plus/bin/
391389
392390
# Test 3: Uninstall

.github/workflows/publish-to-pkg.pr.new.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
6868
- uses: ./.github/actions/clone
6969

70-
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
70+
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
7171

7272
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
7373
with:

0 commit comments

Comments
 (0)