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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ TCPVIEWER_R2_PUBLIC_BASE_URL=https://downloads.example.com
TCPVIEWER_PUBLISH_RELEASE_TO_BACKEND=false
TCPVIEWER_RELEASE_BACKEND_URL=
TCPVIEWER_RELEASE_BACKEND_SCRIPT_SECRET=

# Required only when a production release also creates a Homebrew Cask update PR.
TCPVIEWER_HOMEBREW_CASK_REPO=/path/to/homebrew-cask
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ For production releases, add a matching entry to `ReleaseNote.json`, then run:
npm run release
```

Choose `beta` or `production` when prompted. Production releases also create the Sparkle appcast, push the `v<version>` tag, and publish the GitHub release. Artifacts are written under:
Choose `beta` or `production` when prompted. Production releases also create the Sparkle appcast, push the `v<version>` tag, and publish the GitHub release. They can optionally update `tcpviewer.rb` in the configured `TCPVIEWER_HOMEBREW_CASK_REPO` checkout and open a pull request from `ProxymanApp/homebrew-cask` to `Homebrew/homebrew-cask`. The checkout must be clean, on `master`, and in sync with `origin/master` before the release starts.

For non-interactive production releases, pass `--bump-homebrew` to enable the cask PR or `--no-bump-homebrew` to skip it. `--yes` skips the Homebrew update unless `--bump-homebrew` is also present.

Artifacts are written under:

```bash
~/Desktop/tcpviewer-production/
Expand Down
7 changes: 7 additions & 0 deletions scripts/nodejs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ function parseArgs(argv) {
args.betaName = arg.slice("--beta-name=".length).trim();
} else if (arg === "--yes" || arg === "-y") {
args.yes = true;
} else if (arg === "--bump-homebrew") {
args.bumpHomebrew = true;
} else if (arg === "--no-bump-homebrew") {
args.bumpHomebrew = false;
}
}
return args;
Expand Down Expand Up @@ -103,6 +107,9 @@ function buildReleaseArgs(type, args) {
if (args.yes) {
releaseArgs.push("--yes");
}
if (typeof args.bumpHomebrew === "boolean") {
releaseArgs.push(args.bumpHomebrew ? "--bump-homebrew" : "--no-bump-homebrew");
}
return releaseArgs;
}

Expand Down
36 changes: 36 additions & 0 deletions scripts/release-lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,42 @@ export function normalizeBetaDMGCustomName(value) {
return normalizeFileNameSegment(normalized, "Beta DMG custom name");
}

export function makeHomebrewCaskBranchName({ version, buildNumber }) {
const safeVersion = normalizeFileNameSegment(version, "Homebrew Cask version");
const safeBuildNumber = normalizeFileNameSegment(buildNumber, "Homebrew Cask build number");
return `tcpviewer-${safeVersion}-${safeBuildNumber}`;
}

export function parseHomebrewCaskVersion(content) {
const match = String(content).match(/^ version "([^"]+),([^"]+)"$/m);
if (!match) {
throw new Error("TCP Viewer Homebrew Cask must contain one version with a version and build number.");
}

return { version: match[1], buildNumber: match[2] };
}

export function updateHomebrewCaskContent(content, { version, buildNumber, sha256 }) {
const current = String(content);
parseHomebrewCaskVersion(current);
const safeVersion = normalizeFileNameSegment(version, "Homebrew Cask version");
const safeBuildNumber = normalizeFileNameSegment(buildNumber, "Homebrew Cask build number");
const safeSHA256 = String(sha256 ?? "").trim().toLowerCase();
if (!/^[a-f0-9]{64}$/.test(safeSHA256)) {
throw new Error("Homebrew Cask SHA-256 must contain 64 hexadecimal characters.");
}

const versionMatches = current.match(/^ version ".*"$/gm) ?? [];
const shaMatches = current.match(/^ sha256 ".*"$/gm) ?? [];
if (versionMatches.length !== 1 || shaMatches.length !== 1) {
throw new Error("TCP Viewer Homebrew Cask must contain exactly one version and one SHA-256 stanza.");
}

return current
.replace(versionMatches[0], ` version "${safeVersion},${safeBuildNumber}"`)
.replace(shaMatches[0], ` sha256 "${safeSHA256}"`);
}

export function makeR2ObjectKey({ releaseType, version, buildNumber, timestamp, fileName }) {
const safeFileName = validateDMGFileName(fileName ?? makeDMGFileName({ version, buildNumber }));

Expand Down
Loading