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
4 changes: 4 additions & 0 deletions .github/workflows/fork-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
Expand All @@ -37,6 +39,8 @@ jobs:
with:
key: fork-${{ matrix.target }}
- run: npm ci
- name: Bundle fork release notes
run: node scripts/fork-release.mjs prepare "$FORK_VERSION"
- name: Build and sign fork update
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.updater-private-key }}
Expand Down
3 changes: 3 additions & 0 deletions docs/fork-updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ or agent request is needed after merging a change.
`package.json`, Cargo, and the base Tauri config; the fork config overrides it.
- Release tags use `fork-v0.2.<CI run number>`, so upstream `v*` tags stay separate.
- Both `darwin-aarch64` and `darwin-x86_64` packages must finish successfully.
- Packaging adds a changelog section for the fork version from changes on main
since the previous fork release, so the post-update **What's new** view works.
This generated section is bundled in the app without committing version bumps.
- The workflow checks bundle identity, version, architecture, and code signature.
It stages a DMG, signed `.app.tar.gz`, and signature for each architecture.
- A single publisher validates artifact hashes and commit/version consistency,
Expand Down
63 changes: 61 additions & 2 deletions scripts/fork-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,64 @@ function gitSha() {
}).trim();
}

export function forkChangelogSection(version, messages, upstreamVersion, date) {
releaseTag(version);
const entries = messages.map((message) => {
const paragraphs = message.trim().split(/\n\s*\n/);
const title =
/^Merge /.test(paragraphs[0]) && paragraphs[1]
? paragraphs[1].split("\n")[0]
: paragraphs[0].split("\n")[0];
return `- ${title}`;
});
return `## [${version}] - ${date}\n\nBased on upstream MonoCode ${upstreamVersion}.\n\n${entries.join("\n")}\n\n[Full release notes](https://github.com/${repository}/releases/tag/${releaseTag(version)})\n`;
}

function prepare(version) {
releaseTag(version);
let previous;
try {
previous = execFileSync(
"git",
[
"describe",
"--tags",
"--first-parent",
"--match",
"fork-v0.2.*",
"--abbrev=0",
"HEAD^",
],
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
).trim();
} catch {
// The first release has no preceding fork tag; show recent main changes.
}
const args = ["log", "--first-parent", "--format=%B%x00"];
if (previous) args.push(`${previous}..HEAD`);
else args.push("-10");
const messages = execFileSync("git", args, { encoding: "utf8" })
.split("\0")
.map((message) => message.trim())
.filter(Boolean);
const section = forkChangelogSection(
version,
messages,
json("package.json").version,
new Date().toISOString().slice(0, 10),
);
const changelog = readFileSync("CHANGELOG.md", "utf8");
writeFileSync(
"CHANGELOG.md",
changelog.replace(/^(# [^\n]+\n)/, (heading) => `${heading}\n${section}\n`),
);
if (!readFileSync("CHANGELOG.md", "utf8").includes(`## [${version}]`)) {
throw new Error(
"Could not add fork release notes to the bundled changelog",
);
}
}

export function stage(version, target, root = process.cwd()) {
releaseTag(version);
const platform = platforms[target];
Expand Down Expand Up @@ -243,10 +301,11 @@ if (
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
) {
const [command, version, target] = process.argv.slice(2);
if (command === "stage") stage(version, target);
if (command === "prepare") prepare(version);
else if (command === "stage") stage(version, target);
else if (command === "publish") publish(version);
else
throw new Error(
"Usage: node scripts/fork-release.mjs <stage|publish> <version> [target]",
"Usage: node scripts/fork-release.mjs <prepare|stage|publish> <version> [target]",
);
}
23 changes: 22 additions & 1 deletion scripts/fork-release.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,28 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
import assert from "node:assert/strict";
import { manifest, shouldPublish } from "./fork-release.mjs";
import {
forkChangelogSection,
manifest,
shouldPublish,
} from "./fork-release.mjs";

test("bundled notes use the fork version and human-readable merge titles", () => {
const section = forkChangelogSection(
"0.2.7",
[
"Merge pull request #8 from owner/updates\n\nEnable in-app updates",
"Fix session recovery\n\nPreserve archived conversations.",
],
"0.1.46",
"2026-09-14",
);
assert.match(section, /^## \[0\.2\.7\] - 2026-09-14\n/m);
assert.match(section, /- Enable in-app updates\n- Fix session recovery/);
assert.match(section, /upstream MonoCode 0\.1\.46/);
assert.ok(!section.includes("Merge pull request"));
assert.match(section, /releases\/tag\/fork-v0\.2\.7/);
});

function fixture(t) {
const dir = mkdtempSync(join(tmpdir(), "fork-release-test-"));
Expand Down
Loading