This commit is contained in:
Miguel Ángel
2026-08-30 02:16:15 -07:00
committed by GitHub
3 changed files with 61 additions and 9 deletions
@@ -78,6 +78,19 @@ describe("upgradeProjectPins", () => {
expect(pkg.scripts.render).toBe("npx --yes hyperframes@0.7.55 render");
});
it("rewrites project-root shell wrapper pins alongside package scripts", async () => {
const d = project({ render: "npx --yes hyperframes@0.7.48 render" });
const fullCpu = join(d, "hyperframes-full-cpu.sh");
const publish = join(d, "publish-wrapper.sh");
writeFileSync(fullCpu, "npx --yes hyperframes@0.7.48 render --workers 2\n");
writeFileSync(publish, "./publish.sh 0.7.48 hyperframes@0.7.48\n");
await upgradeProjectPins(d, { json: false, check: false });
expect(readFileSync(fullCpu, "utf8")).toContain("hyperframes@0.7.55");
expect(readFileSync(publish, "utf8")).toContain("hyperframes@0.7.55");
});
it("--check reports without writing", async () => {
const d = project({ render: "npx --yes hyperframes@0.7.48 render" });
const before = readFileSync(join(d, "package.json"), "utf-8");
+20 -5
View File
@@ -3,10 +3,10 @@ import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import * as clack from "@clack/prompts";
import { execFileSync } from "node:child_process";
import { existsSync, readFileSync, writeFileSync, renameSync } from "node:fs";
import { existsSync, readFileSync, writeFileSync, renameSync, readdirSync } from "node:fs";
import { resolve } from "node:path";
import { c } from "../ui/colors.js";
import { rewriteProjectPinnedScripts } from "../utils/projectPin.js";
import { rewritePinnedHyperframesText, rewriteProjectPinnedScripts } from "../utils/projectPin.js";
export const examples: Example[] = [
["Check for updates interactively", "hyperframes upgrade"],
@@ -197,13 +197,28 @@ export async function upgradeProjectPins(
const { latest } = await checkForUpdate(true);
if (!isSafeVersion(latest)) return { changed: false, from: [], to: latest, path: pkgPath };
const rewrite = rewriteProjectPinnedScripts(scripts, latest);
const wrapperRewrites = readdirSync(dir, { withFileTypes: true })
.filter((entry) => entry.isFile() && entry.name.endsWith(".sh"))
.map((entry) => {
const path = resolve(dir, entry.name);
return { path, rewrite: rewritePinnedHyperframesText(readFileSync(path, "utf-8"), latest) };
});
if (rewrite.changed && !opts.check) {
raw.scripts = rewrite.scripts;
const tmp = `${pkgPath}.tmp`;
writeFileSync(tmp, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
renameSync(tmp, pkgPath);
}
return { changed: rewrite.changed, from: rewrite.fromVersions, to: latest, path: pkgPath };
if (!opts.check) {
for (const wrapper of wrapperRewrites) {
if (wrapper.rewrite.changed) writeFileSync(wrapper.path, wrapper.rewrite.text, "utf-8");
}
}
const from = new Set(rewrite.fromVersions);
for (const wrapper of wrapperRewrites) {
for (const version of wrapper.rewrite.fromVersions) from.add(version);
}
return { changed: from.size > 0, from: [...from].sort(), to: latest, path: pkgPath };
}
function printProjectPinResult(
@@ -215,12 +230,12 @@ function printProjectPinResult(
return;
}
if (!res.changed) {
console.log(` ${c.success("◇")} Project scripts already on hyperframes@${res.to}`);
console.log(` ${c.success("◇")} Project pins already on hyperframes@${res.to}`);
return;
}
const verb = checkOnly ? "would bump" : "bumped";
console.log(
` ${c.success("◇")} ${verb} project scripts ${res.from.join(", ")}${c.accent(res.to)}`,
` ${c.success("◇")} ${verb} project pins ${res.from.join(", ")}${c.accent(res.to)}`,
);
if (checkOnly)
console.log(` ${c.dim("Run `npx hyperframes@latest upgrade --project` to apply.")}`);
+28 -4
View File
@@ -12,6 +12,31 @@ export interface PinRewriteResult {
fromVersions: string[];
}
export interface TextPinRewriteResult {
changed: boolean;
text: string;
fromVersions: string[];
}
export function rewritePinnedHyperframesText(
text: string,
targetVersion: string,
): TextPinRewriteResult {
if (!isSafeVersion(targetVersion)) {
return { changed: false, text, fromVersions: [] };
}
const fromVersions = new Set<string>();
const next = text.replace(HYPERFRAMES_PIN_RE, (_full, version: string) => {
if (version !== targetVersion) fromVersions.add(version);
return `hyperframes@${targetVersion}`;
});
return {
changed: fromVersions.size > 0,
text: next,
fromVersions: [...fromVersions].sort(),
};
}
export function readPinnedHyperframesVersions(scripts: Record<string, string>): string[] {
const found = new Set<string>();
for (const cmd of Object.values(scripts ?? {})) {
@@ -31,10 +56,9 @@ export function rewriteProjectPinnedScripts(
const fromVersions = new Set<string>();
const next: Record<string, string> = {};
for (const [name, cmd] of Object.entries(scripts ?? {})) {
next[name] = cmd.replace(HYPERFRAMES_PIN_RE, (_full, version: string) => {
if (version !== targetVersion) fromVersions.add(version);
return `hyperframes@${targetVersion}`;
});
const rewrite = rewritePinnedHyperframesText(cmd, targetVersion);
next[name] = rewrite.text;
for (const version of rewrite.fromVersions) fromVersions.add(version);
}
return {
changed: [...fromVersions].length > 0,