Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
28 changes: 26 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
SOURCE_GITHUB_REPOSITORY: ${{ github.repository }}
REPOPROMPT_CONTROL_PLANE_SCRIPTS_DIR: ${{ github.workspace }}/trusted-control-plane/Scripts
REPOPROMPT_RELEASE_SOURCE_ROOT: ${{ github.workspace }}/release-source
# Link Sentry only in the official release binary. The protected DSN is
# Link the Sentry SDK into the official release binary. The protected DSN is
# injected later in the signing job, so the secret-free stage stays inert.
REPOPROMPT_ENABLE_SENTRY: "1"
run: ./trusted-control-plane/Scripts/release.sh stage-publish
Expand Down Expand Up @@ -184,6 +184,27 @@ jobs:
echo "REPOPROMPT_PROVISIONING_PROFILE=$SECRETS_DIR/repoprompt-ce.provisionprofile" >> "$GITHUB_ENV"
echo "NOTARYTOOL_PRIVATE_KEY=$SECRETS_DIR/notarytool-private-key.p8" >> "$GITHUB_ENV"

- name: Install Sentry CLI when symbol upload is configured
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
set -euo pipefail
if [[ -n "${SENTRY_AUTH_TOKEN:-}" ]] && ! command -v sentry-cli >/dev/null 2>&1; then
brew install getsentry/tools/sentry-cli
fi

- name: Prepare Sentry symbol upload token file
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
set -euo pipefail
if [[ -n "${SENTRY_AUTH_TOKEN:-}" ]]; then
umask 077
SENTRY_TOKEN_FILE="$RUNNER_TEMP/repoprompt-release-secrets/sentry-auth-token"
printf '%s' "$SENTRY_AUTH_TOKEN" > "$SENTRY_TOKEN_FILE"
echo "REPOPROMPT_SENTRY_AUTH_TOKEN_FILE=$SENTRY_TOKEN_FILE" >> "$GITHUB_ENV"
fi

- name: Sign, notarize, and create draft release
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -199,8 +220,11 @@ jobs:
NOTARYTOOL_KEY_ID: ${{ secrets.NOTARYTOOL_KEY_ID }}
NOTARYTOOL_ISSUER_ID: ${{ secrets.NOTARYTOOL_ISSUER_ID }}
# Protected release environment secret. sign_staged_release.sh bakes it
# into the signed bundle's Info.plist as RepoPromptSentryDSN.
# into the signed bundle's Info.plist as RepoPromptSentryDSN. Optional: if
# unset, telemetry stays off (fail-safe).
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
REPOPROMPT_SENTRY_ORG: ${{ vars.SENTRY_ORG }}
REPOPROMPT_SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
run: ./trusted-control-plane/Scripts/release.sh publish-staged

- name: Remove ephemeral keychain
Expand Down
17 changes: 13 additions & 4 deletions Scripts/build_swiftpm_release_products.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ fail() {
exit 1
}

sentry_linking_enabled() {
[[ "${REPOPROMPT_ENABLE_SENTRY:-}" == "1" ]]
}

run() {
printf '+ '
printf '%q ' "$@"
Expand Down Expand Up @@ -74,6 +78,11 @@ esac
run mkdir -p "$SCRATCH_ROOT"
printf 'RepoPrompt CE universal public SwiftPM scratch\n' > "$SCRATCH_ROOT/$SCRATCH_SENTINEL_NAME"

SWIFT_BUILD_ARGS=(-c release)
if sentry_linking_enabled; then
SWIFT_BUILD_ARGS+=(-debug-info-format dwarf)
fi

ARM64_BIN_DIR=""
X86_64_BIN_DIR=""
for arch in arm64 x86_64; do
Expand All @@ -83,18 +92,18 @@ for arch in arm64 x86_64; do
REPOPROMPT_SWIFTPM_SCRATCH_PATH="$scratch" \
"$KEYBOARD_SHORTCUTS_PATCH_HELPER" "$ROOT_DIR"
run "$RUN_WITHOUT_GITHUB_TOKENS" swift build \
-c release \
"${SWIFT_BUILD_ARGS[@]}" \
--arch "$arch" \
--scratch-path "$scratch" \
--product RepoPrompt
run "$RUN_WITHOUT_GITHUB_TOKENS" swift build \
-c release \
"${SWIFT_BUILD_ARGS[@]}" \
--arch "$arch" \
--scratch-path "$scratch" \
--product repoprompt-mcp
printf '+ %q ' "$RUN_WITHOUT_GITHUB_TOKENS" swift build -c release --arch "$arch" --scratch-path "$scratch" --show-bin-path
printf '+ %q ' "$RUN_WITHOUT_GITHUB_TOKENS" swift build "${SWIFT_BUILD_ARGS[@]}" --arch "$arch" --scratch-path "$scratch" --show-bin-path
printf '\n'
bin_dir="$("$RUN_WITHOUT_GITHUB_TOKENS" swift build -c release --arch "$arch" --scratch-path "$scratch" --show-bin-path)"
bin_dir="$("$RUN_WITHOUT_GITHUB_TOKENS" swift build "${SWIFT_BUILD_ARGS[@]}" --arch "$arch" --scratch-path "$scratch" --show-bin-path)"
if [[ "$arch" == "arm64" ]]; then
ARM64_BIN_DIR="$bin_dir"
else
Expand Down
19 changes: 18 additions & 1 deletion Scripts/conductor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1030,8 +1030,25 @@ class OperationRegistry:
"RPCE_RUN_CODEMAP_E2E",
"RPCE_RUN_SCALE_TESTS",
]
TELEMETRY_ENV_KEYS = [
"REPOPROMPT_ENABLE_SENTRY",
"REPOPROMPT_SENTRY_DSN",
"REPOPROMPT_UPLOAD_SENTRY_SYMBOLS",
"REPOPROMPT_SENTRY_AUTH_TOKEN_FILE",
"REPOPROMPT_SENTRY_ORG",
"REPOPROMPT_SENTRY_PROJECT",
"REPOPROMPT_SENTRY_UPLOAD_WAIT",
"SENTRY_URL",
]
PASSTHROUGH_ENV_KEYS = sorted(
set(SIGNING_ENV_KEYS + DEBUG_ENV_KEYS + BUILD_ENV_KEYS + STYLE_ENV_KEYS + TEST_ENV_KEYS)
set(
SIGNING_ENV_KEYS
+ DEBUG_ENV_KEYS
+ BUILD_ENV_KEYS
+ STYLE_ENV_KEYS
+ TEST_ENV_KEYS
+ TELEMETRY_ENV_KEYS
)
)

def __init__(self, repo_root: Path) -> None:
Expand Down
37 changes: 37 additions & 0 deletions Scripts/package_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ run(){
"$@"
}
fail(){ echo "ERROR: $*" >&2; exit 1; }
truthy(){
case "${1:-}" in
1|true|TRUE|yes|YES) return 0 ;;
*) return 1 ;;
esac
}
sentry_linking_enabled(){
[[ "${REPOPROMPT_ENABLE_SENTRY:-}" == "1" ]]
}
require_sentry_upload_credentials(){
if [[ -z "${SENTRY_AUTH_TOKEN:-}" && -z "${REPOPROMPT_SENTRY_AUTH_TOKEN_FILE:-}" && -z "${SENTRY_AUTH_TOKEN_FILE:-}" ]]; then
fail "REPOPROMPT_UPLOAD_SENTRY_SYMBOLS requires SENTRY_AUTH_TOKEN or REPOPROMPT_SENTRY_AUTH_TOKEN_FILE."
fi
}
remove_stale_artifact_manifests(){
local manifests=()
shopt -s nullglob
Expand Down Expand Up @@ -75,6 +89,7 @@ source "$CONTROL_PLANE_SCRIPTS_DIR/load_release_metadata.sh"
load_release_metadata "$ROOT_DIR"
APP_NAME="${APP_NAME:-RepoPrompt}"; DISPLAY_NAME="${DISPLAY_NAME:-RepoPrompt CE}"; BASE_BUNDLE_ID="${BUNDLE_ID:-com.pvncher.repoprompt.ce}"; MARKETING_VERSION="${MARKETING_VERSION:-0.1.0}"; BUILD_NUMBER="${BUILD_NUMBER:-1}"; SIGNING_TEAM_ID="${SIGNING_TEAM_ID:-648A27MST5}"
ARTIFACT_MANIFEST="$ROOT_DIR/.build/release/$APP_NAME-artifact-manifest.json"
SENTRY_SYMBOLS_DIR="$ROOT_DIR/.build/sentry-symbols/$CONF"

IS_RELEASE=0
[[ "$CONF" == "release" ]] && IS_RELEASE=1
Expand Down Expand Up @@ -188,6 +203,9 @@ printf 'Debug secure storage backend marker: %s\n' "$DEBUG_STORAGE_BACKEND_MARKE
printf 'Signing mode marker: %s\n' "$SIGNING_MODE_MARKER"

SWIFT_BUILD_ARGS=(-c "$CONF")
if sentry_linking_enabled; then
SWIFT_BUILD_ARGS+=(-debug-info-format dwarf)
fi
PUBLIC_UNIVERSAL_RELEASE=0
ARCHITECTURE_POLICY="matching"
if (( IS_RELEASE )) && (( ! USE_LOCAL_SELF_SIGNED_RELEASE )); then
Expand Down Expand Up @@ -230,6 +248,20 @@ COMPAT_APP_BUNDLE="$ROOT_DIR/.build/$CONF/$APP_NAME.app"
CLI_PATH="$BUILD_DIR/repoprompt-mcp"
printf 'BUILD_DIR=%s\nAPP_BUNDLE=%s\nCOMPAT_APP_BUNDLE=%s\nCLI_PATH=%s\nAD_HOC_SIGNING=%s\nARCHITECTURE_POLICY=%s\n' "$BUILD_DIR" "$APP_BUNDLE" "$COMPAT_APP_BUNDLE" "$CLI_PATH" "$USE_ADHOC_SIGNING" "$ARCHITECTURE_POLICY"

generate_sentry_debug_symbols(){
sentry_linking_enabled || return 0
phase "Generating Sentry debug symbols"
command -v xcrun >/dev/null 2>&1 || fail "xcrun is required to generate dSYMs."
run rm -rf "$SENTRY_SYMBOLS_DIR"
run mkdir -p "$SENTRY_SYMBOLS_DIR"
for exe in "$APP_NAME" repoprompt-mcp; do
[[ -f "$BUILD_DIR/$exe" ]] || fail "Missing built executable for dSYM generation: $BUILD_DIR/$exe"
run xcrun dsymutil "$BUILD_DIR/$exe" -o "$SENTRY_SYMBOLS_DIR/$exe.dSYM"
done
printf 'Sentry debug symbols: %s\n' "$SENTRY_SYMBOLS_DIR"
}
generate_sentry_debug_symbols

phase "Creating app bundle layout"
run rm -rf "$APP_BUNDLE"
APP_BUNDLE_MATCHES_COMPAT="$(paths_same "$APP_BUNDLE" "$COMPAT_APP_BUNDLE")"
Expand Down Expand Up @@ -397,6 +429,11 @@ if (( PUBLIC_UNIVERSAL_RELEASE )); then
fi
run "$CONTROL_PLANE_SCRIPTS_DIR/validate_embedded_mcp_helper_layout.sh" "$APP_BUNDLE" "Packaged app MCP helper layout"
run "$RUN_WITHOUT_GITHUB_TOKENS" "$CONTROL_PLANE_SCRIPTS_DIR/smoke_embedded_mcp_helper.sh" "$APP_BUNDLE" "Packaged app MCP helper"
if truthy "${REPOPROMPT_UPLOAD_SENTRY_SYMBOLS:-}"; then
sentry_linking_enabled || fail "REPOPROMPT_UPLOAD_SENTRY_SYMBOLS requires REPOPROMPT_ENABLE_SENTRY=1."
require_sentry_upload_credentials
run "$CONTROL_PLANE_SCRIPTS_DIR/upload_sentry_debug_symbols.sh" "$SENTRY_SYMBOLS_DIR"
fi
if [[ "$APP_BUNDLE_MATCHES_COMPAT" != "1" ]]; then
phase "Updating compatibility app bundle link"
run mkdir -p "$(dirname "$COMPAT_APP_BUNDLE")"
Expand Down
26 changes: 26 additions & 0 deletions Scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ DMG="$DIST_DIR/$ARCHIVE_BASENAME.dmg"
APPCAST="$DIST_DIR/appcast.xml"
CHECKSUMS="$DIST_DIR/SHA256SUMS"
BUILD_ARTIFACT_MANIFEST="$ROOT_DIR/.build/release/$APP_NAME-artifact-manifest.json"
SENTRY_SYMBOLS_DIR="$ROOT_DIR/.build/sentry-symbols/release"
FINAL_ARTIFACT_MANIFEST="$DIST_DIR/$ARCHIVE_BASENAME-artifact-manifest.json"
STAGE_ARCHIVE="$DIST_DIR/$ARCHIVE_BASENAME-stage.zip"
STAGE_ARCHIVE_CHECKSUM="$STAGE_ARCHIVE.sha256"
Expand Down Expand Up @@ -49,6 +50,10 @@ require_env() {
[[ -n "${!1:-}" ]] || fail "Missing required environment variable: $1"
}

sentry_linking_enabled() {
[[ "${REPOPROMPT_ENABLE_SENTRY:-}" == "1" ]]
}

require_release_tag_matches_metadata() {
[[ "$RELEASE_TAG" == "v$MARKETING_VERSION" ]] ||
fail "Release tag must match release metadata: expected v$MARKETING_VERSION, got ${RELEASE_TAG:-<missing>}"
Expand Down Expand Up @@ -77,6 +82,7 @@ run_preflight() {
require_file "$ROOT_DIR/Vendor/Sparkle/PROVENANCE.md"
require_file "$ROOT_DIR/Vendor/Sparkle/SHA256SUMS"
require_file "$CONTROL_PLANE_SCRIPTS_DIR/sign_staged_release.sh"
require_file "$CONTROL_PLANE_SCRIPTS_DIR/upload_sentry_debug_symbols.sh"
require_file "$CONTROL_PLANE_SCRIPTS_DIR/build_swiftpm_release_products.sh"
require_file "$CONTROL_PLANE_SCRIPTS_DIR/compare_swiftpm_release_resources.py"
require_file "$CONTROL_PLANE_SCRIPTS_DIR/smoke_embedded_mcp_helper.sh"
Expand Down Expand Up @@ -169,6 +175,16 @@ write_final_artifact_manifest() {
--expected-architectures "arm64,x86_64"
}

require_staged_sentry_symbols_when_enabled() {
if sentry_linking_enabled && [[ ! -d "$SENTRY_SYMBOLS_DIR" ]]; then
fail "Sentry-enabled release staging did not produce debug symbols at $SENTRY_SYMBOLS_DIR"
fi
}

upload_sentry_symbols_if_configured() {
"$CONTROL_PLANE_SCRIPTS_DIR/upload_sentry_debug_symbols.sh" "$SENTRY_SYMBOLS_DIR"
}

package_release_candidate() {
resolve_without_lockfile_drift
run_preflight
Expand Down Expand Up @@ -214,6 +230,10 @@ verify_publish_inputs() {
require_release_tag_matches_metadata
require_file "$REPOPROMPT_PROVISIONING_PROFILE"
require_file "$NOTARYTOOL_PRIVATE_KEY"
if [[ -n "${REPOPROMPT_SENTRY_AUTH_TOKEN_FILE:-${SENTRY_AUTH_TOKEN_FILE:-}}" || -n "${SENTRY_AUTH_TOKEN:-}" ]]; then
require_env REPOPROMPT_SENTRY_ORG
require_env REPOPROMPT_SENTRY_PROJECT
fi

"$CONTROL_PLANE_SCRIPTS_DIR/verify_remote_release_commit.sh" "$RELEASE_TAG" "$RELEASE_COMMIT"
}
Expand Down Expand Up @@ -246,11 +266,16 @@ stage_publish_release() {
run_preflight
validate_packaged_legal "$APP_BUNDLE"
validate_public_app "$APP_BUNDLE" "$BUILD_ARTIFACT_MANIFEST" "Release staging"
require_staged_sentry_symbols_when_enabled
TMP_DIR="$(mktemp -d)"
local stage_root="$TMP_DIR/release-stage"
mkdir -p "$stage_root/.build/release"
ditto "$APP_BUNDLE" "$stage_root/.build/release/$APP_NAME.app"
cp "$BUILD_ARTIFACT_MANIFEST" "$stage_root/.build/release/$APP_NAME-artifact-manifest.json"
if [[ -d "$SENTRY_SYMBOLS_DIR" ]]; then
mkdir -p "$stage_root/.build/sentry-symbols"
ditto "$SENTRY_SYMBOLS_DIR" "$stage_root/.build/sentry-symbols/release"
fi
cp "$ROOT_DIR/version.env" "$ROOT_DIR/LICENSE" "$ROOT_DIR/THIRD_PARTY_NOTICES.md" "$stage_root/"
cp -R "$ROOT_DIR/ThirdPartyLicenses" "$stage_root/"
printf '%s\n' "$RELEASE_COMMIT" > "$stage_root/RELEASE_COMMIT"
Expand Down Expand Up @@ -287,6 +312,7 @@ publish_staged_release() {
xcrun stapler validate "$APP_BUNDLE"
write_final_artifact_manifest
validate_public_app "$APP_BUNDLE" "$FINAL_ARTIFACT_MANIFEST" "Final Developer ID app"
upload_sentry_symbols_if_configured

local distribution_dir="$TMP_DIR/distribution"
mkdir -p "$distribution_dir"
Expand Down
5 changes: 3 additions & 2 deletions Scripts/sign_staged_release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ plutil -lint "$app_entitlements"
plutil -replace RepoPromptDebugSecureStorageBackend -string keychain "$APP_BUNDLE/Contents/Info.plist"
plutil -replace RepoPromptSigningMode -string developer-id "$APP_BUNDLE/Contents/Info.plist"

# Official telemetry is gated on the protected DSN. Keep unsigned staging and
# local release-candidate artifacts inert, and never echo or manifest the value.
# Telemetry is gated on DSN presence: only the official Developer ID publish job receives the
# protected SENTRY_DSN secret, and only here is it baked into the signed bundle. The value is never
# echoed or written to the artifact manifest.
if [[ -n "${SENTRY_DSN:-}" ]]; then
plutil -replace RepoPromptSentryDSN -string "$SENTRY_DSN" "$APP_BUNDLE/Contents/Info.plist"
fi
Expand Down
Loading
Loading