Merge origin/main into issue-151-ssh-remote-port-proxying
This commit is contained in:
commit
c179ee74ea
202 changed files with 54781 additions and 2314 deletions
|
|
@ -2,10 +2,50 @@
|
|||
set -euo pipefail
|
||||
|
||||
# Build, sign, notarize, create DMG, generate appcast, and upload to GitHub release.
|
||||
# Usage: ./scripts/build-sign-upload.sh <tag>
|
||||
# Usage: ./scripts/build-sign-upload.sh <tag> [--allow-overwrite]
|
||||
# Requires: source ~/.secrets/cmuxterm.env && export SPARKLE_PRIVATE_KEY
|
||||
|
||||
TAG="${1:?Usage: $0 <tag>}"
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./scripts/build-sign-upload.sh <tag> [--allow-overwrite]
|
||||
|
||||
Options:
|
||||
--allow-overwrite Permit replacing existing release assets for the same tag.
|
||||
Use only for emergency rerolls.
|
||||
EOF
|
||||
}
|
||||
|
||||
ALLOW_OVERWRITE="false"
|
||||
POSITIONAL=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--allow-overwrite)
|
||||
ALLOW_OVERWRITE="true"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
POSITIONAL+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG="$1"
|
||||
SIGN_HASH="A050CC7E193C8221BDBA204E731B046CDCCC1B30"
|
||||
ENTITLEMENTS="cmux.entitlements"
|
||||
APP_PATH="build/Build/Products/Release/cmux.app"
|
||||
|
|
@ -81,8 +121,29 @@ echo "Generating appcast..."
|
|||
|
||||
# --- Create GitHub release (if needed) and upload ---
|
||||
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||
echo "Uploading to existing release $TAG..."
|
||||
gh release upload "$TAG" cmux-macos.dmg appcast.xml --clobber
|
||||
echo "Release $TAG already exists"
|
||||
EXISTING_ASSETS="$(gh release view "$TAG" --json assets --jq '.assets[].name' || true)"
|
||||
HAS_CONFLICTING_ASSET="false"
|
||||
for asset in cmux-macos.dmg appcast.xml; do
|
||||
if printf '%s\n' "$EXISTING_ASSETS" | grep -Fxq "$asset"; then
|
||||
HAS_CONFLICTING_ASSET="true"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$HAS_CONFLICTING_ASSET" == "true" && "$ALLOW_OVERWRITE" != "true" ]]; then
|
||||
echo "ERROR: Refusing to overwrite signed release assets for existing tag $TAG." >&2
|
||||
echo "Use a new tag, or rerun with --allow-overwrite for an emergency reroll." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$ALLOW_OVERWRITE" == "true" ]]; then
|
||||
echo "Uploading with overwrite enabled for existing release $TAG..."
|
||||
gh release upload "$TAG" cmux-macos.dmg appcast.xml --clobber
|
||||
else
|
||||
echo "Uploading to existing release $TAG..."
|
||||
gh release upload "$TAG" cmux-macos.dmg appcast.xml
|
||||
fi
|
||||
else
|
||||
echo "Creating release $TAG and uploading..."
|
||||
gh release create "$TAG" cmux-macos.dmg appcast.xml --title "$TAG" --notes "See CHANGELOG.md for details"
|
||||
|
|
|
|||
37
scripts/release_asset_guard.js
Normal file
37
scripts/release_asset_guard.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"use strict";
|
||||
|
||||
const IMMUTABLE_RELEASE_ASSETS = ["cmux-macos.dmg", "appcast.xml"];
|
||||
const RELEASE_ASSET_GUARD_STATE = Object.freeze({
|
||||
CLEAR: "clear",
|
||||
PARTIAL: "partial",
|
||||
COMPLETE: "complete",
|
||||
});
|
||||
|
||||
function evaluateReleaseAssetGuard({ existingAssetNames, immutableAssetNames = IMMUTABLE_RELEASE_ASSETS }) {
|
||||
const immutableAssets = immutableAssetNames || IMMUTABLE_RELEASE_ASSETS;
|
||||
const existing = new Set(existingAssetNames || []);
|
||||
const conflicts = immutableAssets.filter((assetName) => existing.has(assetName));
|
||||
const missingImmutableAssets = immutableAssets.filter((assetName) => !existing.has(assetName));
|
||||
|
||||
let guardState = RELEASE_ASSET_GUARD_STATE.CLEAR;
|
||||
if (conflicts.length === immutableAssets.length && immutableAssets.length > 0) {
|
||||
guardState = RELEASE_ASSET_GUARD_STATE.COMPLETE;
|
||||
} else if (conflicts.length > 0) {
|
||||
guardState = RELEASE_ASSET_GUARD_STATE.PARTIAL;
|
||||
}
|
||||
|
||||
return {
|
||||
conflicts,
|
||||
missingImmutableAssets,
|
||||
guardState,
|
||||
hasPartialConflict: guardState === RELEASE_ASSET_GUARD_STATE.PARTIAL,
|
||||
shouldSkipBuildAndUpload: guardState === RELEASE_ASSET_GUARD_STATE.COMPLETE,
|
||||
shouldSkipUpload: guardState === RELEASE_ASSET_GUARD_STATE.COMPLETE,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
IMMUTABLE_RELEASE_ASSETS,
|
||||
RELEASE_ASSET_GUARD_STATE,
|
||||
evaluateReleaseAssetGuard,
|
||||
};
|
||||
49
scripts/release_asset_guard.test.js
Normal file
49
scripts/release_asset_guard.test.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"use strict";
|
||||
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {
|
||||
IMMUTABLE_RELEASE_ASSETS,
|
||||
RELEASE_ASSET_GUARD_STATE,
|
||||
evaluateReleaseAssetGuard,
|
||||
} = require("./release_asset_guard");
|
||||
|
||||
test("marks guard as complete and skips build/upload when all immutable assets already exist", () => {
|
||||
const result = evaluateReleaseAssetGuard({
|
||||
existingAssetNames: ["cmux-macos.dmg", "appcast.xml", "notes.txt"],
|
||||
});
|
||||
|
||||
assert.deepEqual(result.conflicts, IMMUTABLE_RELEASE_ASSETS);
|
||||
assert.deepEqual(result.missingImmutableAssets, []);
|
||||
assert.equal(result.guardState, RELEASE_ASSET_GUARD_STATE.COMPLETE);
|
||||
assert.equal(result.hasPartialConflict, false);
|
||||
assert.equal(result.shouldSkipBuildAndUpload, true);
|
||||
assert.equal(result.shouldSkipUpload, true);
|
||||
});
|
||||
|
||||
test("marks guard as clear when immutable assets are not present", () => {
|
||||
const result = evaluateReleaseAssetGuard({
|
||||
existingAssetNames: ["notes.txt", "checksums.txt"],
|
||||
});
|
||||
|
||||
assert.deepEqual(result.conflicts, []);
|
||||
assert.deepEqual(result.missingImmutableAssets, IMMUTABLE_RELEASE_ASSETS);
|
||||
assert.equal(result.guardState, RELEASE_ASSET_GUARD_STATE.CLEAR);
|
||||
assert.equal(result.hasPartialConflict, false);
|
||||
assert.equal(result.shouldSkipBuildAndUpload, false);
|
||||
assert.equal(result.shouldSkipUpload, false);
|
||||
});
|
||||
|
||||
test("marks guard as partial when only some immutable assets exist", () => {
|
||||
const result = evaluateReleaseAssetGuard({
|
||||
existingAssetNames: ["appcast.xml"],
|
||||
});
|
||||
|
||||
assert.deepEqual(result.conflicts, ["appcast.xml"]);
|
||||
assert.deepEqual(result.missingImmutableAssets, ["cmux-macos.dmg"]);
|
||||
assert.equal(result.guardState, RELEASE_ASSET_GUARD_STATE.PARTIAL);
|
||||
assert.equal(result.hasPartialConflict, true);
|
||||
assert.equal(result.shouldSkipBuildAndUpload, false);
|
||||
assert.equal(result.shouldSkipUpload, false);
|
||||
});
|
||||
|
|
@ -13,6 +13,7 @@ cd "$(dirname "$0")/.."
|
|||
|
||||
DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData/cmux-tests-v1"
|
||||
APP="$DERIVED_DATA_PATH/Build/Products/Debug/cmux DEV.app"
|
||||
RUN_TAG="tests-v1"
|
||||
|
||||
echo "== build =="
|
||||
# Work around stale explicit-module cache artifacts (notably Sentry headers) that can
|
||||
|
|
@ -51,7 +52,7 @@ launch_and_wait() {
|
|||
defaults write com.cmuxterm.app.debug socketControlMode -string full >/dev/null 2>&1 || true
|
||||
|
||||
# Launch directly with UI test mode enabled so startup follows deterministic test codepaths.
|
||||
CMUX_UI_TEST_MODE=1 "$APP/Contents/MacOS/cmux DEV" >/dev/null 2>&1 &
|
||||
CMUX_TAG="$RUN_TAG" CMUX_UI_TEST_MODE=1 "$APP/Contents/MacOS/cmux DEV" >/dev/null 2>&1 &
|
||||
|
||||
SOCK=""
|
||||
for _ in {1..120}; do
|
||||
|
|
@ -70,7 +71,7 @@ launch_and_wait() {
|
|||
export CMUX_SOCKET="$SOCK"
|
||||
|
||||
# Ensure LaunchServices has a visible/main window attached for rendering checks.
|
||||
open "$APP" >/dev/null 2>&1 || true
|
||||
CMUX_TAG="$RUN_TAG" open "$APP" >/dev/null 2>&1 || true
|
||||
sleep 0.5
|
||||
|
||||
echo "== wait ready =="
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ cd "$(dirname "$0")/.."
|
|||
|
||||
DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData/cmux-tests-v2"
|
||||
APP="$DERIVED_DATA_PATH/Build/Products/Debug/cmux DEV.app"
|
||||
RUN_TAG="tests-v2"
|
||||
|
||||
echo "== build =="
|
||||
# Work around stale explicit-module cache artifacts (notably Sentry headers) that can
|
||||
|
|
@ -51,7 +52,7 @@ launch_and_wait() {
|
|||
defaults write com.cmuxterm.app.debug socketControlMode -string full >/dev/null 2>&1 || true
|
||||
|
||||
# Launch directly with UI test mode enabled so startup follows deterministic test codepaths.
|
||||
CMUX_UI_TEST_MODE=1 "$APP/Contents/MacOS/cmux DEV" >/dev/null 2>&1 &
|
||||
CMUX_TAG="$RUN_TAG" CMUX_UI_TEST_MODE=1 "$APP/Contents/MacOS/cmux DEV" >/dev/null 2>&1 &
|
||||
|
||||
SOCK=""
|
||||
for _ in {1..120}; do
|
||||
|
|
@ -70,7 +71,7 @@ launch_and_wait() {
|
|||
export CMUX_SOCKET="$SOCK"
|
||||
|
||||
# Ensure LaunchServices has a visible/main window attached for rendering checks.
|
||||
open "$APP" >/dev/null 2>&1 || true
|
||||
CMUX_TAG="$RUN_TAG" open "$APP" >/dev/null 2>&1 || true
|
||||
sleep 0.5
|
||||
|
||||
echo "== wait ready =="
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue