Files
wehub-resource-sync 70bf21e064
Handle Changesets / Handle Changesets (push) Waiting to run
Semgrep OSS scan / semgrep-oss (push) Waiting to run
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
chore: import upstream snapshot with attribution
2026-07-13 12:30:11 +08:00

132 lines
3.2 KiB
TypeScript

/**
* Validates that workspace packages use `catalog:` references for any dependency
* that exists in the pnpm catalog. Exceptions: `workspace:` refs, `npm:` aliases,
* and packages under `templates/` (scaffolded for end users).
*/
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { glob } from "tinyglobby";
const ROOT = resolve(__dirname, "../..");
// Deps that are deliberately pinned outside the catalog (e.g. workerd is
// bumped in coordinated PRs with its own automation).
const IGNORED_DEPS = new Set(["workerd"]);
/**
* Parses the `catalog:` block of a pnpm-workspace.yaml into a map of
* dependency name -> version specifier.
*/
export function parseCatalog(workspaceYaml: string): Map<string, string> {
const catalog = new Map<string, string>();
let inCatalog = false;
for (const line of workspaceYaml.split("\n")) {
if (line.startsWith("catalog:")) {
inCatalog = true;
continue;
}
if (inCatalog && /^\S/.test(line)) {
break;
}
if (!inCatalog) {
continue;
}
const trimmed = line.trim();
if (trimmed === "" || trimmed.startsWith("#")) {
continue;
}
const match = trimmed.match(
/^["']?(@?[^"':]+)["']?\s*:\s*["']?([^"'#\s]+)["']?/
);
if (match) {
catalog.set(match[1].trim(), match[2]);
}
}
return catalog;
}
/**
* Loads the pnpm catalog as a map of dependency name -> version specifier.
*/
export function loadCatalog(): Map<string, string> {
const content = readFileSync(resolve(ROOT, "pnpm-workspace.yaml"), "utf-8");
return parseCatalog(content);
}
function loadCatalogDeps(): Set<string> {
return new Set(loadCatalog().keys());
}
async function main(): Promise<void> {
console.log("::group::Checking catalog usage");
const catalogDeps = loadCatalogDeps();
const errors: string[] = [];
const packageJsonPaths = await glob(
[
"package.json",
"packages/*/package.json",
"packages/vite-plugin-cloudflare/playground/*/package.json",
"packages/vite-plugin-cloudflare/playground/package.json",
"fixtures/*/package.json",
"tools/package.json",
],
{ cwd: ROOT, absolute: true }
);
for (const pkgPath of packageJsonPaths) {
if (pkgPath.includes("/templates/")) {
continue;
}
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
const rel = pkgPath.replace(ROOT + "/", "");
for (const section of ["dependencies", "devDependencies"] as const) {
for (const [name, version] of Object.entries(
(pkg[section] ?? {}) as Record<string, string>
)) {
if (
!catalogDeps.has(name) ||
IGNORED_DEPS.has(name) ||
version.startsWith("catalog:") ||
version.startsWith("workspace:") ||
version.startsWith("npm:")
) {
continue;
}
errors.push(
`${rel}: "${name}" uses "${version}" in ${section} but should use "catalog:default"`
);
}
}
}
if (errors.length > 0) {
console.error(
"::error::Catalog usage violations:" +
errors.map((e) => `\n- ${e}`).join("")
);
} else {
console.log("All catalog dependencies are correctly referenced.");
}
console.log("::endgroup::");
process.exit(errors.length > 0 ? 1 : 0);
}
if (require.main === module) {
main().catch((error) => {
console.log("::endgroup::");
console.error("An unexpected error occurred", error);
process.exit(1);
});
}