Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Fixes

- Homebrew: make the tap formula fail clearly on Linux instead of installing a macOS binary, and add generator/test coverage for the macOS-only guard (#147, thanks @steipete).
- Firecrawl: reject `--firecrawl always` for YouTube URLs with an explicit guidance error instead of silently skipping Firecrawl on the transcript-first path (#145, thanks @steipete).
- YouTube: keep Gemini-only no-caption runs on the transcription path by forwarding the Google API key from the top-level URL flow into link-preview transcription config (#148, thanks @bytrangle).
- Daemon tests: expand CORS allowlist edge-case coverage for localhost variants, extension-origin casing, spoofed localhost domains, and full trusted response headers (#142, thanks @sebastiondev).
Expand Down
2 changes: 1 addition & 1 deletion docs/chrome-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Quickstart:

- Install summarize (choose one):
- `npm i -g @steipete/summarize`
- `brew install steipete/tap/summarize` (macOS arm64)
- `brew install steipete/tap/summarize` (macOS only)
- Build/load extension: `apps/chrome-extension/README.md`
- Firefox sidebar build: `pnpm -C apps/chrome-extension build:firefox` (load via `about:debugging` → temporary add-on)
- Open side panel → copy token install command → run:
Expand Down
10 changes: 6 additions & 4 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ summary: "Release checklist + Homebrew tap update."

- Ship npm packages (core first, then CLI).
- Tag + GitHub release.
- Update Homebrew tap so `brew install steipete/tap/summarize` matches latest tag.
- Update the macOS-only Homebrew tap so `brew install steipete/tap/summarize` matches latest tag.

## Checklist

Expand All @@ -17,10 +17,12 @@ summary: "Release checklist + Homebrew tap update."
3. If you didn’t run `tap` in the script, update the Homebrew tap formula for `summarize`:
- Bump version to the new tag.
- Update tarball URL + SHA256 for the new release.
- Keep the formula guarded as macOS-only; Linux installs must fail clearly and point users to npm until Linux artifacts exist.
4. Verify Homebrew install reflects the new version:
- `brew install steipete/tap/summarize`
- `summarize --version` matches tag.
- Run a feature added in the release (e.g. `summarize daemon install` for v0.8.2).
- macOS: `brew install steipete/tap/summarize`
- macOS: `summarize --version` matches tag.
- macOS: run a feature added in the release (e.g. `summarize daemon install` for v0.8.2).
- Linux: `brew install steipete/tap/summarize` fails with the explicit macOS-only / npm guidance.
5. If anything fails, fix and re-cut the release (no partials).

## Common failure
Expand Down
126 changes: 92 additions & 34 deletions scripts/release-formula.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,107 @@
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";

function replaceOnce(input, pattern, replacer) {
const next = input.replace(pattern, replacer);
if (next === input) {
throw new Error(`failed to update formula using pattern: ${pattern}`);
export const LINUX_HOMEBREW_MESSAGE =
"summarize Homebrew formula is macOS-only; use npm install -g @steipete/summarize on Linux";

function skipDoBlock(lines, start) {
let depth = 0;
for (let index = start; index < lines.length; index += 1) {
const trimmed = lines[index].trim();
if (trimmed.endsWith(" do")) {
depth += 1;
continue;
}
if (trimmed === "end") {
depth -= 1;
Comment on lines +11 to +16

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Count non-do Ruby blocks when skipping platform sections

skipDoBlock increases depth only for lines ending in do but decreases depth for every end, so an on_macos/on_linux block containing if/unless/case can terminate early at an inner end. In that case stripExistingPlatformConfig leaves the outer end in place, and updateFormulaForMacArtifacts writes malformed Ruby with an extra end, breaking formula generation for that release.

Useful? React with 👍 / 👎.

if (depth === 0) {
return index + 1;
}
}
}
return next;
throw new Error(`failed to find matching end for block starting at line ${start + 1}`);
}

export function updateFormulaForMacArtifacts(data, { urlArm, shaArm, urlX64, shaX64 }) {
if (data.includes("on_arm do") && data.includes("on_intel do")) {
let next = data;
next = replaceOnce(next, /(on_arm do\s*\n\s*url ")(.*?)(")/s, `$1${urlArm}$3`);
next = replaceOnce(next, /(on_arm do.*?\n\s*sha256 ")(.*?)(")/s, `$1${shaArm}$3`);
next = replaceOnce(next, /(on_intel do\s*\n\s*url ")(.*?)(")/s, `$1${urlX64}$3`);
next = replaceOnce(next, /(on_intel do.*?\n\s*sha256 ")(.*?)(")/s, `$1${shaX64}$3`);
return next;
}
function stripExistingPlatformConfig(data) {
const lines = data.split("\n");
const next = [];

if (data.includes("depends_on arch: :arm64")) {
const dualBlock = [
" on_arm do",
` url "${urlArm}"`,
` sha256 "${shaArm}"`,
" end",
"",
" on_intel do",
` url "${urlX64}"`,
` sha256 "${shaX64}"`,
" end",
].join("\n");

let next = data;
next = replaceOnce(next, / url "[^"\n]+"\n sha256 "[^"\n]+"/, dualBlock);
next = replaceOnce(next, /^ depends_on arch: :arm64\s*\n?/m, "");
return next;
for (let index = 0; index < lines.length; ) {
const line = lines[index];
if (/^ on_(macos|linux|arm|intel) do$/.test(line)) {
index = skipDoBlock(lines, index);
continue;
}
if (
/^ url "[^"\n]+"$/.test(line) ||
/^ sha256 "[^"\n]+"$/.test(line) ||
/^ depends_on arch: :arm64$/.test(line)
) {
index += 1;
continue;
}
next.push(line);
index += 1;
}

let next = data;
next = replaceOnce(next, /^ url ".*"$/m, ` url "${urlArm}"`);
next = replaceOnce(next, /^ sha256 ".*"$/m, ` sha256 "${shaArm}"`);
return next;
}

function findPlatformInsertIndex(lines) {
let insertIndex = 1;
for (let index = 1; index < lines.length; index += 1) {
if (/^ (desc|homepage|version|license) /.test(lines[index])) {
insertIndex = index + 1;
continue;
}
if (lines[index].trim() === "") {
continue;
}
break;
}
return insertIndex;
}

function buildPlatformBlock({ urlArm, shaArm, urlX64, shaX64 }) {
return [
" on_macos do",
" on_arm do",
` url "${urlArm}"`,
` sha256 "${shaArm}"`,
" end",
"",
" on_intel do",
` url "${urlX64}"`,
` sha256 "${shaX64}"`,
" end",
" end",
"",
" on_linux do",
` odie "${LINUX_HOMEBREW_MESSAGE}"`,
" end",
];
}

export function updateFormulaForMacArtifacts(data, { urlArm, shaArm, urlX64, shaX64 }) {
const lines = stripExistingPlatformConfig(data);
const insertIndex = findPlatformInsertIndex(lines);
const output = [
...lines.slice(0, insertIndex),
"",
...buildPlatformBlock({ urlArm, shaArm, urlX64, shaX64 }),
"",
...lines.slice(insertIndex).filter((line, index, array) => {
if (line.trim() !== "") return true;
const previous = index > 0 ? array[index - 1] : "";
return previous.trim() !== "";
}),
];
return `${output
.join("\n")
.replace(/\n{3,}/g, "\n\n")
.trimEnd()}\n`;
}

export function updateFormulaFile(path, args) {
const data = readFileSync(path, "utf8");
writeFileSync(path, updateFormulaForMacArtifacts(data, args));
Expand Down
21 changes: 16 additions & 5 deletions tests/release.formula.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { describe, expect, it } from "vitest";
import { updateFormulaForMacArtifacts } from "../scripts/release-formula.js";
import {
LINUX_HOMEBREW_MESSAGE,
updateFormulaForMacArtifacts,
} from "../scripts/release-formula.js";

const urls = {
urlArm: "https://example.com/summarize-macos-arm64.tar.gz",
Expand All @@ -25,15 +28,18 @@ end

const output = updateFormulaForMacArtifacts(input, urls);

expect(output).toContain("on_macos do");
expect(output).toContain("on_linux do");
expect(output).toContain(`url "${urls.urlArm}"`);
expect(output).toContain(`sha256 "${urls.shaArm}"`);
expect(output).toContain(`url "${urls.urlX64}"`);
expect(output).toContain(`sha256 "${urls.shaX64}"`);
expect(output).toContain(`odie "${LINUX_HOMEBREW_MESSAGE}"`);
expect(output).not.toContain("old-arm");
expect(output).not.toContain("old-x64");
});

it("converts arm64-only formulas to on_arm/on_intel blocks", () => {
it("converts arm64-only formulas to macOS-only dual-arch blocks", () => {
const input = `class Summarize < Formula
desc "summarize"
homepage "https://example.com"
Expand All @@ -45,16 +51,18 @@ end

const output = updateFormulaForMacArtifacts(input, urls);

expect(output).toContain("on_macos do");
expect(output).toContain("on_arm do");
expect(output).toContain("on_intel do");
expect(output).toContain("on_linux do");
expect(output).toContain(`url "${urls.urlArm}"`);
expect(output).toContain(`sha256 "${urls.shaArm}"`);
expect(output).toContain(`url "${urls.urlX64}"`);
expect(output).toContain(`sha256 "${urls.shaX64}"`);
expect(output).not.toContain("depends_on arch: :arm64");
});

it("keeps fallback formulas arm64-only when no arch blocks exist", () => {
it("rewrites fallback formulas to a canonical macOS-only shape", () => {
const input = `class Summarize < Formula
url "https://old.example/default.tgz"
sha256 "old-default"
Expand All @@ -63,9 +71,12 @@ end

const output = updateFormulaForMacArtifacts(input, urls);

expect(output).toContain("on_macos do");
expect(output).toContain("on_linux do");
expect(output).toContain(`url "${urls.urlArm}"`);
expect(output).toContain(`sha256 "${urls.shaArm}"`);
expect(output).not.toContain(urls.urlX64);
expect(output).not.toContain(urls.shaX64);
expect(output).toContain(`url "${urls.urlX64}"`);
expect(output).toContain(`sha256 "${urls.shaX64}"`);
expect(output).toContain(`odie "${LINUX_HOMEBREW_MESSAGE}"`);
});
});