Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/cli/src/commands/upgrade.project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
25 changes: 20 additions & 5 deletions packages/cli/src/commands/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down Expand Up @@ -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(
Expand All @@ -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.")}`);
Expand Down
32 changes: 28 additions & 4 deletions packages/cli/src/utils/projectPin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? {})) {
Expand All @@ -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,
Expand Down
Loading