From 94736339af4d3c7caf975d8f1e456bc51b8b1bcd Mon Sep 17 00:00:00 2001 From: thesiti92 Date: Sun, 27 Sep 2026 20:06:05 -0400 Subject: [PATCH 1/8] Derive the macOS release target from the host arch Agent-Session: 11e6cdc4-ebab-4f67-9499-a7e914620a86 Agent-Session: e8a1eaea-7717-48b1-a143-d9adbd3f5f58 Agent-Session: 01a0e553-25fc-7350-ab29-e046a939a2e6 Agent-Session: b11edf22-6b82-42e6-87c4-07c995cb9400 --- .github/workflows/review-desktop-preview.yml | 4 ++- .github/workflows/review-desktop-release.yml | 4 ++- .../scripts/compile-darwin-payload.sh | 26 +++++++++++---- .../scripts/curated-extensions.test.mjs | 9 ------ apps/review-desktop/scripts/darwin-arch.sh | 9 ++++++ .../scripts/darwin-payload-manifest.sh | 6 ++-- apps/review-desktop/scripts/notarize-macos.sh | 19 +++++------ apps/review-desktop/scripts/package-macos.sh | 32 +++++++++---------- .../scripts/release-channel.mjs | 16 ++++++++-- .../scripts/release-channel.test.mjs | 17 ++++++++++ .../scripts/smoke-launch-packaged.mjs | 3 +- 11 files changed, 98 insertions(+), 47 deletions(-) create mode 100644 apps/review-desktop/scripts/darwin-arch.sh create mode 100644 apps/review-desktop/scripts/release-channel.test.mjs diff --git a/.github/workflows/review-desktop-preview.yml b/.github/workflows/review-desktop-preview.yml index 187e08843..b87f84997 100644 --- a/.github/workflows/review-desktop-preview.yml +++ b/.github/workflows/review-desktop-preview.yml @@ -244,6 +244,8 @@ jobs: env: RELEASE_VERSION: ${{ needs.version.outputs.version }} RELEASE_COMMIT: ${{ needs.version.outputs.commit }} + # TODO(task 3.1): comes from the build matrix once it exists. + DARWIN_TARGET: darwin-arm64 steps: - name: Checkout resolved preview commit uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 @@ -408,7 +410,7 @@ jobs: # and deliver them; the launch above only proves it starts. - name: Smoke telemetry delivery and error reporting run: | - APP="apps/review-desktop/VSCode-darwin-arm64/$(node -p "require('./apps/review-desktop/code-oss/product.json').nameShort").app" + APP="apps/review-desktop/VSCode-${DARWIN_TARGET}/$(node -p "require('./apps/review-desktop/code-oss/product.json').nameShort").app" node apps/review-desktop/scripts/smoke-telemetry-delivery.mjs --app "$APP" node apps/review-desktop/scripts/smoke-error-telemetry.mjs --app "$APP" diff --git a/.github/workflows/review-desktop-release.yml b/.github/workflows/review-desktop-release.yml index 14502c45f..e65973e05 100644 --- a/.github/workflows/review-desktop-release.yml +++ b/.github/workflows/review-desktop-release.yml @@ -423,6 +423,8 @@ jobs: RELEASE_VERSION: ${{ needs.tag.outputs.version }} RELEASE_COMMIT: ${{ needs.tag.outputs.commit }} RELEASE_TAG: ${{ needs.tag.outputs.tag }} + # TODO(task 3.1): comes from the build matrix once it exists. + DARWIN_TARGET: darwin-arm64 steps: - name: Checkout resolved release commit uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 @@ -591,7 +593,7 @@ jobs: # and deliver them; the launch above only proves it starts. - name: Smoke telemetry delivery and error reporting run: | - APP="apps/review-desktop/VSCode-darwin-arm64/$(node -p "require('./apps/review-desktop/code-oss/product.json').nameShort").app" + APP="apps/review-desktop/VSCode-${DARWIN_TARGET}/$(node -p "require('./apps/review-desktop/code-oss/product.json').nameShort").app" node apps/review-desktop/scripts/smoke-telemetry-delivery.mjs --app "$APP" node apps/review-desktop/scripts/smoke-error-telemetry.mjs --app "$APP" diff --git a/apps/review-desktop/scripts/compile-darwin-payload.sh b/apps/review-desktop/scripts/compile-darwin-payload.sh index 6f7415551..0d401edb0 100755 --- a/apps/review-desktop/scripts/compile-darwin-payload.sh +++ b/apps/review-desktop/scripts/compile-darwin-payload.sh @@ -12,7 +12,6 @@ PAYLOAD="$APP_DIR/dist/darwin-payload.tar.zst" # shellcheck source=darwin-payload-manifest.sh source "$APP_DIR/scripts/darwin-payload-manifest.sh" -CURATED_EXTENSIONS_PAYLOAD="$MONOREPO_ROOT/$DARWIN_PAYLOAD_CURATED_EXTENSIONS_PATH" if (( $# > 0 )); then echo "usage: $0" >&2 @@ -20,7 +19,9 @@ if (( $# > 0 )); then fi # The compile host is Linux, but curated extensions contain target-native -# servers. Materialize the Darwin variants that the final app will execute. +# servers. build.sh recompiles unconditionally, so run it once for the +# arm64 leg; the other Darwin targets are materialized directly below without +# repeating the compile. REVIEW_DESKTOP_COMPILE_ONLY=1 \ REVIEW_DESKTOP_CURATED_EXTENSION_TARGET=darwin-arm64 \ bash "$APP_DIR/scripts/build.sh" @@ -31,6 +32,10 @@ else BUILD_SOURCEVERSION="$(jj --repository "$MONOREPO_ROOT" --ignore-working-copy log --no-graph -r @ -T 'commit_id')" fi export BUILD_SOURCEVERSION +# vscode-darwin-arm64-min-prepare produces the arch-independent out-vscode-min +# (codicons, non-native extensions, media, esbuild bundle; see +# gulpfile.vscode.ts:640-648). Both Darwin targets reuse this one output, so +# the task name stays arm64-specific even though it isn't arch-bound. npm --prefix "$CHECKOUT" run gulp -- vscode-darwin-arm64-min-prepare # Tags the bundles, so it must run before they are archived. @@ -38,10 +43,19 @@ if [[ -n "${REVIEW_POSTHOG_KEY:-}" ]]; then node "$APP_DIR/scripts/upload-source-maps.mjs" --out "$CHECKOUT/out-vscode-min" fi -rm -rf -- "$CURATED_EXTENSIONS_PAYLOAD" -node "$APP_DIR/scripts/curated-extensions.mjs" \ - --target=darwin-arm64 \ - --copy-to "$CURATED_EXTENSIONS_PAYLOAD" +for target in "${DARWIN_PAYLOAD_TARGETS[@]}"; do + if [[ "$target" != "darwin-arm64" ]]; then + # build.sh above only materialized curated extensions for darwin-arm64; + # materialize the remaining targets without recompiling. + node "$APP_DIR/scripts/curated-extensions.mjs" "--target=$target" + fi + + target_payload="$MONOREPO_ROOT/$DARWIN_PAYLOAD_CURATED_EXTENSIONS_ROOT/$target" + rm -rf -- "$target_payload" + node "$APP_DIR/scripts/curated-extensions.mjs" \ + --target="$target" \ + --copy-to "$target_payload" +done mkdir -p "$APP_DIR/dist" rm -f -- "$PAYLOAD" diff --git a/apps/review-desktop/scripts/curated-extensions.test.mjs b/apps/review-desktop/scripts/curated-extensions.test.mjs index 27e391e51..f17b4cbf3 100644 --- a/apps/review-desktop/scripts/curated-extensions.test.mjs +++ b/apps/review-desktop/scripts/curated-extensions.test.mjs @@ -267,18 +267,9 @@ test("carries Darwin curated extensions from Linux compile through release valid ); assert.match(payloadManifest, /DARWIN_PAYLOAD_REQUIRED_PATHS=/); assert.match(payloadManifest, /DARWIN_PAYLOAD_ARCHIVE_ONLY_PATHS=/); - assert.ok( - payloadManifest.indexOf("$DARWIN_PAYLOAD_CURATED_EXTENSIONS_PATH") > - payloadManifest.indexOf("DARWIN_PAYLOAD_REQUIRED_PATHS=(") && - payloadManifest.indexOf("$DARWIN_PAYLOAD_CURATED_EXTENSIONS_PATH") < - payloadManifest.indexOf("DARWIN_PAYLOAD_ARCHIVE_ONLY_PATHS=("), - "the curated extension payload must be required by macOS packaging", - ); assert.match(compileScript, /DARWIN_PAYLOAD_ARCHIVE_ONLY_PATHS\[@\]/); assert.match(compileScript, /DARWIN_PAYLOAD_REQUIRED_PATHS\[@\]/); assert.match(packageScript, /DARWIN_PAYLOAD_REQUIRED_PATHS\[@\]/); - assert.match(compileScript, /--target=darwin-arm64/); - assert.match(compileScript, /--copy-to "\$CURATED_EXTENSIONS_PAYLOAD"/); assert.match(packageScript, /"\$CURATED_EXTENSIONS_PAYLOAD"/); assert.match(packageScript, /--source-root "\$CURATED_EXTENSIONS_SOURCE"/); assert.match( diff --git a/apps/review-desktop/scripts/darwin-arch.sh b/apps/review-desktop/scripts/darwin-arch.sh new file mode 100644 index 000000000..512f0205a --- /dev/null +++ b/apps/review-desktop/scripts/darwin-arch.sh @@ -0,0 +1,9 @@ +# shellcheck shell=bash +# Exports the macOS release arch for the host. Sourced by the macOS-only scripts. +case "$(uname -m)" in + arm64) DARWIN_ARCH=arm64 ;; + x86_64) DARWIN_ARCH=x64 ;; + *) echo "Unsupported macOS arch $(uname -m)" >&2; exit 1 ;; +esac +DARWIN_TARGET="darwin-$DARWIN_ARCH" +export DARWIN_ARCH DARWIN_TARGET diff --git a/apps/review-desktop/scripts/darwin-payload-manifest.sh b/apps/review-desktop/scripts/darwin-payload-manifest.sh index 802da3c13..58bac4a54 100644 --- a/apps/review-desktop/scripts/darwin-payload-manifest.sh +++ b/apps/review-desktop/scripts/darwin-payload-manifest.sh @@ -4,12 +4,14 @@ # Paths are relative to the monorepo root. macOS packaging checks every # required path after it extracts the Linux-built Darwin payload. -DARWIN_PAYLOAD_CURATED_EXTENSIONS_PATH="apps/review-desktop/code-oss/.build/review-curated-extensions/darwin-arm64" +DARWIN_PAYLOAD_CURATED_EXTENSIONS_ROOT="apps/review-desktop/code-oss/.build/review-curated-extensions" +DARWIN_PAYLOAD_TARGETS=(darwin-arm64 darwin-x64) DARWIN_PAYLOAD_REQUIRED_PATHS=( "apps/review-desktop/code-oss/out-vscode-min" "apps/review-desktop/code-oss/.build/extensions" - "$DARWIN_PAYLOAD_CURATED_EXTENSIONS_PATH" + "$DARWIN_PAYLOAD_CURATED_EXTENSIONS_ROOT/darwin-arm64" + "$DARWIN_PAYLOAD_CURATED_EXTENSIONS_ROOT/darwin-x64" "packages/review/app/dist/desktop" "packages/review/dist" "packages/review/tutorial" diff --git a/apps/review-desktop/scripts/notarize-macos.sh b/apps/review-desktop/scripts/notarize-macos.sh index f87133f2a..c88517d85 100755 --- a/apps/review-desktop/scripts/notarize-macos.sh +++ b/apps/review-desktop/scripts/notarize-macos.sh @@ -5,11 +5,9 @@ MONOREPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd -P)" APP_DIR="$MONOREPO_ROOT/apps/review-desktop" CHECKOUT="$APP_DIR/code-oss" PRODUCT_NAME="$(node -p "require('$CHECKOUT/product.json').nameShort")" -PACKAGED_APP="$APP_DIR/VSCode-darwin-arm64/$PRODUCT_NAME.app" VERSION="$(node -p "require('$APP_DIR/package.json').version")" ARTIFACT_DIR="${DEV_FAST_REVIEW_ARTIFACT_DIR:-$APP_DIR/dist}" QUALITY="$(node -p "require('$CHECKOUT/product.json').quality")" -DMG="$ARTIFACT_DIR/Whiteboard-darwin-arm64-$VERSION.dmg" if (( $# > 0 )); then echo "usage: $0" >&2 @@ -19,10 +17,13 @@ if [[ "$(uname -s)" != "Darwin" ]]; then echo "Review Desktop macOS notarization must run on macOS" >&2 exit 1 fi -if [[ "$(uname -m)" != "arm64" ]]; then - echo "Review Desktop macOS notarization requires arm64" >&2 - exit 1 -fi + +# shellcheck source=darwin-arch.sh +source "$APP_DIR/scripts/darwin-arch.sh" + +PACKAGED_APP="$APP_DIR/VSCode-$DARWIN_TARGET/$PRODUCT_NAME.app" +DMG="$ARTIFACT_DIR/Whiteboard-$DARWIN_TARGET-$VERSION.dmg" + if [[ "${SKIP_NOTARIZE:-0}" == "1" ]]; then echo "SKIP_NOTARIZE=1: leaving Review Desktop unsigned and unnotarized" exit 0 @@ -130,14 +131,14 @@ submit_notarization() { ' "$response" } -export VSCODE_ARCH=arm64 +export VSCODE_ARCH="$DARWIN_ARCH" node --experimental-strip-types "$CHECKOUT/build/darwin/sign.ts" "$APP_DIR" codesign --verify --deep --strict --verbose=2 "$PACKAGED_APP" codesign -dv --verbose=2 "$PACKAGED_APP" mkdir -p "$ARTIFACT_DIR" -rm -f -- "$ARTIFACT_DIR"/*-darwin-arm64-"$VERSION".zip "$DMG" +rm -f -- "$ARTIFACT_DIR"/*-"$DARWIN_TARGET"-"$VERSION".zip "$DMG" mkdir -p "$DMG_STAGE" ditto "$PACKAGED_APP" "$DMG_STAGE/$PRODUCT_NAME.app" @@ -177,7 +178,7 @@ while IFS=$'\t' read -r bundle artifact; do staged="$TEMP_ROOT/zips/$artifact/$bundle.app" mkdir -p "$(dirname "$staged")" ditto "$PACKAGED_APP" "$staged" - zip="$ARTIFACT_DIR/$artifact-darwin-arm64-$VERSION.zip" + zip="$ARTIFACT_DIR/$artifact-$DARWIN_TARGET-$VERSION.zip" if [[ "$bundle" != "$PRODUCT_NAME" ]]; then mv "$staged/Contents/MacOS/$PRODUCT_NAME" "$staged/Contents/MacOS/$bundle" /usr/libexec/PlistBuddy -c "Set :CFBundleExecutable $bundle" "$staged/Contents/Info.plist" diff --git a/apps/review-desktop/scripts/package-macos.sh b/apps/review-desktop/scripts/package-macos.sh index 3063cd4be..858ba9861 100755 --- a/apps/review-desktop/scripts/package-macos.sh +++ b/apps/review-desktop/scripts/package-macos.sh @@ -9,15 +9,6 @@ MONOREPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd -P)" APP_DIR="$MONOREPO_ROOT/apps/review-desktop" CHECKOUT="$APP_DIR/code-oss" PRODUCT_NAME="$(node -p "require('$CHECKOUT/product.json').nameShort")" -PACKAGED_APP="$APP_DIR/VSCode-darwin-arm64/$PRODUCT_NAME.app" -PACKAGED_BINARY="$PACKAGED_APP/Contents/MacOS/$PRODUCT_NAME" - -# shellcheck source=darwin-payload-manifest.sh -source "$APP_DIR/scripts/darwin-payload-manifest.sh" -CURATED_EXTENSIONS_PAYLOAD="$MONOREPO_ROOT/$DARWIN_PAYLOAD_CURATED_EXTENSIONS_PATH" - -# shellcheck source=code-oss-dependencies.sh -source "$APP_DIR/scripts/code-oss-dependencies.sh" if (( $# > 0 )); then echo "usage: $0" >&2 @@ -27,10 +18,19 @@ if [[ "$(uname -s)" != "Darwin" ]]; then echo "Review Desktop macOS packaging must run on macOS" >&2 exit 1 fi -if [[ "$(uname -m)" != "arm64" ]]; then - echo "Review Desktop macOS packaging requires arm64" >&2 - exit 1 -fi + +# shellcheck source=darwin-arch.sh +source "$APP_DIR/scripts/darwin-arch.sh" + +PACKAGED_APP="$APP_DIR/VSCode-$DARWIN_TARGET/$PRODUCT_NAME.app" +PACKAGED_BINARY="$PACKAGED_APP/Contents/MacOS/$PRODUCT_NAME" + +# shellcheck source=darwin-payload-manifest.sh +source "$APP_DIR/scripts/darwin-payload-manifest.sh" +CURATED_EXTENSIONS_PAYLOAD="$MONOREPO_ROOT/$DARWIN_PAYLOAD_CURATED_EXTENSIONS_ROOT/$DARWIN_TARGET" + +# shellcheck source=code-oss-dependencies.sh +source "$APP_DIR/scripts/code-oss-dependencies.sh" PRECOMPILED="${REVIEW_DESKTOP_PRECOMPILED:-0}" if [[ "$PRECOMPILED" == "1" ]]; then @@ -58,9 +58,9 @@ export BUILD_SOURCEVERSION if [[ "$PRECOMPILED" == "1" ]]; then ensure_code_oss_dependencies "$APP_DIR" "$CHECKOUT" - npm --prefix "$CHECKOUT" run gulp -- vscode-darwin-arm64-min-ci + npm --prefix "$CHECKOUT" run gulp -- "vscode-$DARWIN_TARGET-min-ci" else - npm --prefix "$CHECKOUT" run gulp -- vscode-darwin-arm64-min + npm --prefix "$CHECKOUT" run gulp -- "vscode-$DARWIN_TARGET-min" fi if [[ ! -x "$PACKAGED_BINARY" ]]; then @@ -69,7 +69,7 @@ if [[ ! -x "$PACKAGED_BINARY" ]]; then fi node "$APP_DIR/scripts/copy-canvas.mjs" --packaged-root "$PACKAGED_APP" node "$APP_DIR/scripts/curated-extensions.mjs" \ - --target=darwin-arm64 \ + --target="$DARWIN_TARGET" \ --source-root "$CURATED_EXTENSIONS_SOURCE" \ --copy-to "$PACKAGED_APP/Contents/Resources/app/extensions" diff --git a/apps/review-desktop/scripts/release-channel.mjs b/apps/review-desktop/scripts/release-channel.mjs index 9450dc1ae..4c35ebae9 100644 --- a/apps/review-desktop/scripts/release-channel.mjs +++ b/apps/review-desktop/scripts/release-channel.mjs @@ -82,8 +82,20 @@ export function updateBundlesFor(channel) { return UPDATE_BUNDLES[channel]; } -export function updateZipName(artifact, version) { - return `${artifact}-darwin-arm64-${version}.zip`; +const DARWIN_TARGETS = { arm64: "darwin-arm64", x64: "darwin-x64" }; + +export function darwinTarget(arch = process.arch) { + const target = DARWIN_TARGETS[arch]; + + if (!target) { + throw new Error(`unsupported macOS arch ${arch}`); + } + + return target; +} + +export function updateZipName(artifact, version, target = darwinTarget()) { + return `${artifact}-${target}-${version}.zip`; } if (process.argv[1] === new URL(import.meta.url).pathname) { diff --git a/apps/review-desktop/scripts/release-channel.test.mjs b/apps/review-desktop/scripts/release-channel.test.mjs new file mode 100644 index 000000000..14317e15a --- /dev/null +++ b/apps/review-desktop/scripts/release-channel.test.mjs @@ -0,0 +1,17 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { darwinTarget, updateZipName } from "./release-channel.mjs"; + +test("darwinTarget names the macOS release target for each Node arch", () => { + assert.equal(darwinTarget("arm64"), "darwin-arm64"); + assert.equal(darwinTarget("x64"), "darwin-x64"); + assert.throws(() => darwinTarget("ia32"), /unsupported macOS arch ia32/); +}); + +test("updateZipName carries the target", () => { + assert.equal( + updateZipName("Whiteboard", "1.2.3", "darwin-x64"), + "Whiteboard-darwin-x64-1.2.3.zip", + ); +}); diff --git a/apps/review-desktop/scripts/smoke-launch-packaged.mjs b/apps/review-desktop/scripts/smoke-launch-packaged.mjs index d011a0300..0f4dde224 100644 --- a/apps/review-desktop/scripts/smoke-launch-packaged.mjs +++ b/apps/review-desktop/scripts/smoke-launch-packaged.mjs @@ -27,6 +27,7 @@ import os from "node:os"; import path from "node:path"; import { parseArgs } from "node:util"; +import { darwinTarget } from "./release-channel.mjs"; import { assertNoBlockedReviewRequests } from "./review-network-policy.mjs"; const APP_DIR = path.resolve(import.meta.dirname, ".."); @@ -37,7 +38,7 @@ const PRODUCT_NAME = JSON.parse( const DEFAULT_APP = path.join( APP_DIR, - "VSCode-darwin-arm64", + `VSCode-${darwinTarget()}`, `${PRODUCT_NAME}.app`, ); From 54ffbc1a23a0a067c3f8da9cf6bf60398e80ddb0 Mon Sep 17 00:00:00 2001 From: thesiti92 Date: Sun, 27 Sep 2026 20:16:24 -0400 Subject: [PATCH 2/8] Pin darwin-x64 curated extensions Agent-Session: 11e6cdc4-ebab-4f67-9499-a7e914620a86 Agent-Session: e8a1eaea-7717-48b1-a143-d9adbd3f5f58 Agent-Session: 01a0e553-25fc-7350-ab29-e046a939a2e6 Agent-Session: b11edf22-6b82-42e6-87c4-07c995cb9400 --- .../reviewCuratedExtensions.contribution.ts | 4 ++-- .../node/reviewOptionalExtensionCatalog.ts | 10 +++++++++ .../node/reviewOptionalExtensionInstaller.ts | 5 ++++- .../scripts/curated-extensions.manifest.mjs | 21 +++++++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/apps/review-desktop/code-oss/src/vs/review/contrib/extensions/reviewCuratedExtensions.contribution.ts b/apps/review-desktop/code-oss/src/vs/review/contrib/extensions/reviewCuratedExtensions.contribution.ts index 28c907f66..00ece1c59 100644 --- a/apps/review-desktop/code-oss/src/vs/review/contrib/extensions/reviewCuratedExtensions.contribution.ts +++ b/apps/review-desktop/code-oss/src/vs/review/contrib/extensions/reviewCuratedExtensions.contribution.ts @@ -10,7 +10,7 @@ import { DisposableStore } from '../../../base/common/lifecycle.js'; import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { URI } from '../../../base/common/uri.js'; -import { ipcRenderer } from '../../../base/parts/sandbox/electron-browser/globals.js'; +import { ipcRenderer, process } from '../../../base/parts/sandbox/electron-browser/globals.js'; import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../platform/actions/common/actions.js'; import { CommandsRegistry, ICommandService } from '../../../platform/commands/common/commands.js'; import { IConfigurationService, ConfigurationTarget } from '../../../platform/configuration/common/configuration.js'; @@ -210,7 +210,7 @@ function findInstalled(installed: readonly ILocalExtension[], id: string): ILoca } function optionalDownloadSize(group: string, installed: readonly ILocalExtension[]): number { - const target = isMacintosh ? 'darwin-arm64' : isLinux ? 'linux-x64' : isWindows ? 'win32-x64' : undefined; + const target = isMacintosh ? (process.arch === 'x64' ? 'darwin-x64' : 'darwin-arm64') : isLinux ? 'linux-x64' : isWindows ? 'win32-x64' : undefined; return reviewOptionalExtensionCatalog .filter(extension => extension.group === group && !findInstalled(installed, extension.id)) .reduce((total, extension) => { diff --git a/apps/review-desktop/code-oss/src/vs/review/node/reviewOptionalExtensionCatalog.ts b/apps/review-desktop/code-oss/src/vs/review/node/reviewOptionalExtensionCatalog.ts index 741aa5983..05173fe10 100644 --- a/apps/review-desktop/code-oss/src/vs/review/node/reviewOptionalExtensionCatalog.ts +++ b/apps/review-desktop/code-oss/src/vs/review/node/reviewOptionalExtensionCatalog.ts @@ -23,6 +23,11 @@ export const reviewOptionalExtensionCatalog = [ sha256: 'e068ebb88f705491856b91cdbf8b7ead40c22d50f2c24df70e345c889c2b0111', size: 15445156 }, + 'darwin-x64': { + url: 'https://open-vsx.org/api/rust-lang/rust-analyzer/darwin-x64/0.4.2990/file/rust-lang.rust-analyzer-0.4.2990@darwin-x64.vsix', + sha256: '00e0f18acff0ba954810d2234af3c2e3fd9703f8d44c6bc2ae705127fa2f6a65', + size: 15926469 + }, 'linux-x64': { url: 'https://open-vsx.org/api/rust-lang/rust-analyzer/linux-x64/0.4.2990/file/rust-lang.rust-analyzer-0.4.2990@linux-x64.vsix', sha256: '317cb128e8caf2495b955ef6612d828fef809187ac445242116ad8e2e32382ff', @@ -72,6 +77,11 @@ export const reviewOptionalExtensionCatalog = [ sha256: '93f61e8b6938cbe8ecda8768bfaf08abed9789db9461177d3d0ab59ccda2528d', size: 75096042 }, + 'darwin-x64': { + url: 'https://open-vsx.org/api/muhammad-sammy/csharp/darwin-x64/2.145.21-g154a82fd27/file/muhammad-sammy.csharp-2.145.21-g154a82fd27@darwin-x64.vsix', + sha256: '8164ff9ad9ceb849d13ee8c768c97861f0028f48f538ce9658731350e4b88b2d', + size: 78021251 + }, 'linux-x64': { url: 'https://open-vsx.org/api/muhammad-sammy/csharp/linux-x64/2.145.21-g154a82fd27/file/muhammad-sammy.csharp-2.145.21-g154a82fd27@linux-x64.vsix', sha256: '78bc006683cc998e9fd1a6f2760d8cb3da63096464a217bbd192ecfb490a5516', diff --git a/apps/review-desktop/code-oss/src/vs/review/node/reviewOptionalExtensionInstaller.ts b/apps/review-desktop/code-oss/src/vs/review/node/reviewOptionalExtensionInstaller.ts index ade6558bf..3f55a4af6 100644 --- a/apps/review-desktop/code-oss/src/vs/review/node/reviewOptionalExtensionInstaller.ts +++ b/apps/review-desktop/code-oss/src/vs/review/node/reviewOptionalExtensionInstaller.ts @@ -68,10 +68,13 @@ export interface ReviewOptionalExtensionCatalogEntry { readonly targets: Readonly>; } -function supportedTarget(platform: string, arch: string): 'darwin-arm64' | 'linux-x64' | 'win32-x64' | undefined { +function supportedTarget(platform: string, arch: string): 'darwin-arm64' | 'darwin-x64' | 'linux-x64' | 'win32-x64' | undefined { if (platform === 'darwin' && arch === 'arm64') { return 'darwin-arm64'; } + if (platform === 'darwin' && arch === 'x64') { + return 'darwin-x64'; + } if (platform === 'win32' && arch === 'x64') { return 'win32-x64'; } diff --git a/apps/review-desktop/scripts/curated-extensions.manifest.mjs b/apps/review-desktop/scripts/curated-extensions.manifest.mjs index 6e3a15687..a41068cf2 100644 --- a/apps/review-desktop/scripts/curated-extensions.manifest.mjs +++ b/apps/review-desktop/scripts/curated-extensions.manifest.mjs @@ -82,6 +82,12 @@ export const curatedExtensions = Object.freeze([ "e068ebb88f705491856b91cdbf8b7ead40c22d50f2c24df70e345c889c2b0111", size: 15445156, }, + "darwin-x64": { + url: "https://open-vsx.org/api/rust-lang/rust-analyzer/darwin-x64/0.4.2990/file/rust-lang.rust-analyzer-0.4.2990@darwin-x64.vsix", + sha256: + "00e0f18acff0ba954810d2234af3c2e3fd9703f8d44c6bc2ae705127fa2f6a65", + size: 15926469, + }, "linux-x64": { url: "https://open-vsx.org/api/rust-lang/rust-analyzer/linux-x64/0.4.2990/file/rust-lang.rust-analyzer-0.4.2990@linux-x64.vsix", sha256: @@ -162,6 +168,12 @@ export const curatedExtensions = Object.freeze([ "93f61e8b6938cbe8ecda8768bfaf08abed9789db9461177d3d0ab59ccda2528d", size: 75096042, }, + "darwin-x64": { + url: "https://open-vsx.org/api/muhammad-sammy/csharp/darwin-x64/2.145.21-g154a82fd27/file/muhammad-sammy.csharp-2.145.21-g154a82fd27@darwin-x64.vsix", + sha256: + "8164ff9ad9ceb849d13ee8c768c97861f0028f48f538ce9658731350e4b88b2d", + size: 78021251, + }, "linux-x64": { url: "https://open-vsx.org/api/muhammad-sammy/csharp/linux-x64/2.145.21-g154a82fd27/file/muhammad-sammy.csharp-2.145.21-g154a82fd27@linux-x64.vsix", sha256: @@ -232,6 +244,10 @@ export const curatedExtensions = Object.freeze([ sha256: "3ac92b3f4b7ac848ea9a125a787a0b181879835d54b2e136e760161df414b08a", }, + "darwin-x64": { + sha256: + "27d57df17fc3670b8c818b246f08cbb150ceaa2005273a06b23bc4b6241e66c1", + }, "linux-x64": { sha256: "d64fc3104f07c4d47c3122a0fa9f2da3e593937c8b506b5f952b4283d877d212", @@ -259,6 +275,10 @@ export const curatedExtensions = Object.freeze([ sha256: "652cf695fbe11c4bcae85432b3baf70f8bc2520dc13bbc5dd95b3600c8b1f227", }, + "darwin-x64": { + sha256: + "9c780cad1d6a6f26593ecde22190cb04e4345ac512ca86104b697c04a6b005c1", + }, "linux-x64": { sha256: "3ed6bc6d6dc9a70cff97698d498844b756110b5c66964689dad5839845f06556", @@ -298,6 +318,7 @@ export const curatedExtensions = Object.freeze([ /** Build targets Review knows how to materialize platform-specific VSIXes for. */ export const supportedTargets = Object.freeze([ "darwin-arm64", + "darwin-x64", "linux-x64", "win32-x64", ]); From e31b26543abfd4d2d5ab844bb74318eb02d5ccf8 Mon Sep 17 00:00:00 2001 From: thesiti92 Date: Sun, 27 Sep 2026 20:24:45 -0400 Subject: [PATCH 3/8] Validate macOS artifact arch and write per-target manifests Agent-Session: 11e6cdc4-ebab-4f67-9499-a7e914620a86 Agent-Session: e8a1eaea-7717-48b1-a143-d9adbd3f5f58 Agent-Session: 01a0e553-25fc-7350-ab29-e046a939a2e6 Agent-Session: b11edf22-6b82-42e6-87c4-07c995cb9400 --- .../scripts/curated-extensions.test.mjs | 1 - .../scripts/validate-release-artifacts.mjs | 88 +++++++++++++++++-- .../validate-release-artifacts.test.mjs | 37 +++++++- 3 files changed, 115 insertions(+), 11 deletions(-) diff --git a/apps/review-desktop/scripts/curated-extensions.test.mjs b/apps/review-desktop/scripts/curated-extensions.test.mjs index f17b4cbf3..025eb3372 100644 --- a/apps/review-desktop/scripts/curated-extensions.test.mjs +++ b/apps/review-desktop/scripts/curated-extensions.test.mjs @@ -282,7 +282,6 @@ test("carries Darwin curated extensions from Linux compile through release valid "curated extensions must be staged before signing and notarization", ); assert.match(validationScript, /verifyCuratedExtensions/); - assert.match(validationScript, /target: "darwin-arm64"/); assert.doesNotMatch(packageScript, /rust-lang\.rust-analyzer/); assert.doesNotMatch(payloadManifest, /rust-lang\.rust-analyzer/); }); diff --git a/apps/review-desktop/scripts/validate-release-artifacts.mjs b/apps/review-desktop/scripts/validate-release-artifacts.mjs index 7c77c3af5..0e60de6c0 100644 --- a/apps/review-desktop/scripts/validate-release-artifacts.mjs +++ b/apps/review-desktop/scripts/validate-release-artifacts.mjs @@ -9,18 +9,28 @@ // [--artifact-dir dist] import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; -import { lstatSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { + globSync, + lstatSync, + readFileSync, + readdirSync, + writeFileSync, +} from "node:fs"; import path from "node:path"; import { parseArgs } from "node:util"; import { verifyCuratedExtensions } from "./curated-extensions.mjs"; import { assertReleaseChannel, + darwinTarget, releaseIdentityFor, updateBundlesFor, updateZipName, } from "./release-channel.mjs"; -import { assertPackagedArtifacts } from "./stage-review-runtime.mjs"; +import { + RUNTIME_DIRECTORY_NAME, + assertPackagedArtifacts, +} from "./stage-review-runtime.mjs"; const APP_DIR = path.resolve(import.meta.dirname, ".."); @@ -30,12 +40,18 @@ export { assertReleaseChannel }; // `payloads` is one entry per update zip, in updateBundlesFor() order: the // first is the default for clients that do not name their bundle folder. -export function buildManifest({ version, commit, payloads, now = new Date() }) { +export function buildManifest({ + version, + commit, + payloads, + target = darwinTarget(), + now = new Date(), +}) { const bundles = Object.fromEntries( payloads.map(({ bundle, artifact, sha256 }) => [ bundle, { - url: `${UPDATE_URL}/releases/${version}/darwin-arm64/${updateZipName(artifact, version)}`, + url: `${UPDATE_URL}/releases/${version}/${target}/${updateZipName(artifact, version, target)}`, sha256hash: sha256, }, ]), @@ -145,6 +161,21 @@ export function assertUpdaterCompatibleApp(app) { } } +const MACHO_ARCH = { arm64: "arm64", x64: "x86_64" }; + +// Codesign accepts a binary of either arch; only running it on the wrong Mac fails. +export function assertMachOArch(file, arch) { + const expected = MACHO_ARCH[arch]; + + const archs = execFileSync("lipo", ["-archs", file], { encoding: "utf8" }) + .trim() + .split(/\s+/); + + if (!archs.includes(expected)) { + throw new Error(`${file} is ${archs.join(" ")}, expected ${expected}`); + } +} + function sha256(file) { return createHash("sha256").update(readFileSync(file)).digest("hex"); } @@ -178,23 +209,26 @@ async function main() { values["artifact-dir"] ?? path.join(APP_DIR, "dist"), ); + const target = darwinTarget(); + const arch = target.slice("darwin-".length); + const sourceProduct = JSON.parse( readFileSync(path.join(APP_DIR, "code-oss", "product.json"), "utf8"), ); const app = path.join( APP_DIR, - "VSCode-darwin-arm64", + `VSCode-${target}`, `${sourceProduct.nameShort}.app`, ); const zips = updateBundlesFor(channel).map(({ bundle, artifact }) => ({ bundle, artifact, - file: path.join(artifactDir, updateZipName(artifact, version)), + file: path.join(artifactDir, updateZipName(artifact, version, target)), })); - const dmg = path.join(artifactDir, `Whiteboard-darwin-arm64-${version}.dmg`); + const dmg = path.join(artifactDir, `Whiteboard-${target}-${version}.dmg`); await assertPackagedArtifacts(app); assertUpdaterCompatibleApp(app); @@ -209,9 +243,45 @@ async function main() { assertPackagedProduct(product, { commit, channel }); verifyCuratedExtensions({ root: path.join(app, "Contents", "Resources", "app", "extensions"), - target: "darwin-arm64", + target, }); + assertMachOArch( + path.join(app, "Contents", "MacOS", sourceProduct.nameShort), + arch, + ); + assertMachOArch( + path.join( + app, + "Contents", + "Resources", + "app", + RUNTIME_DIRECTORY_NAME, + "bin", + "diffr", + ), + arch, + ); + + const [rustAnalyzerServer] = globSync( + path.join( + app, + "Contents", + "Resources", + "app", + "extensions", + "rust-lang.rust-analyzer*", + "server", + "rust-analyzer", + ), + ); + + if (!rustAnalyzerServer) { + throw new Error(`rust-analyzer server binary not found under ${app}`); + } + + assertMachOArch(rustAnalyzerServer, arch); + run("xcrun", ["stapler", "validate", app]); run("spctl", ["-a", "-vv", "--type", "exec", app]); run("xcrun", ["stapler", "validate", dmg]); @@ -221,7 +291,7 @@ async function main() { } const payloads = zips.map((zip) => ({ ...zip, sha256: sha256(zip.file) })); - const manifest = buildManifest({ version, commit, payloads }); + const manifest = buildManifest({ version, commit, payloads, target }); const manifestPath = path.join(artifactDir, "latest.json"); writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); diff --git a/apps/review-desktop/scripts/validate-release-artifacts.test.mjs b/apps/review-desktop/scripts/validate-release-artifacts.test.mjs index 78e7acc28..e8ca0e5ff 100644 --- a/apps/review-desktop/scripts/validate-release-artifacts.test.mjs +++ b/apps/review-desktop/scripts/validate-release-artifacts.test.mjs @@ -1,11 +1,14 @@ import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { mkdtempSync } from "node:fs"; import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; -import os from "node:os"; +import os, { tmpdir } from "node:os"; import path from "node:path"; import { after, test } from "node:test"; import { releaseIdentityFor } from "./release-channel.mjs"; import { + assertMachOArch, assertPackagedProduct, assertReleaseChannel, assertUpdaterCompatibleApp, @@ -61,6 +64,38 @@ test("buildManifest emits the schema the update Worker serves", () => { }); }); +test("buildManifest points at the target's release folder", () => { + const manifest = buildManifest({ + version: "1.2.3", + commit: "abc123", + target: "darwin-x64", + payloads: [ + { bundle: "Whiteboard", artifact: "Whiteboard", sha256: "f00d" }, + ], + now: new Date("2026-07-29T00:00:00.000Z"), + }); + + assert.equal( + manifest.url, + "https://update.dev.fast/releases/1.2.3/darwin-x64/Whiteboard-darwin-x64-1.2.3.zip", + ); +}); + +test( + "assertMachOArch rejects a binary built for the other arch", + { skip: process.platform !== "darwin" }, + () => { + const thin = path.join(mkdtempSync(path.join(tmpdir(), "macho-")), "true"); + execFileSync("lipo", ["/usr/bin/true", "-thin", "arm64e", "-output", thin]); + + assert.throws( + () => assertMachOArch(thin, "x64"), + /is arm64e, expected x86_64/, + ); + assertMachOArch("/usr/bin/true", "x64"); + }, +); + test("assertPackagedProduct accepts a correctly stamped product", () => { assertPackagedProduct(PRODUCT, { commit: "abc123" }); }); From fcb29fa614289fa4ed019698118d76fd7fef0a1d Mon Sep 17 00:00:00 2001 From: thesiti92 Date: Sun, 27 Sep 2026 20:30:49 -0400 Subject: [PATCH 4/8] Build macOS for both arches and publish after both Agent-Session: 11e6cdc4-ebab-4f67-9499-a7e914620a86 Agent-Session: e8a1eaea-7717-48b1-a143-d9adbd3f5f58 Agent-Session: 01a0e553-25fc-7350-ab29-e046a939a2e6 Agent-Session: b11edf22-6b82-42e6-87c4-07c995cb9400 --- .github/workflows/review-desktop-preview.yml | 130 ++++++++++++------- .github/workflows/review-desktop-release.yml | 130 +++++++++++++------ apps/review-desktop/README.md | 19 +-- 3 files changed, 181 insertions(+), 98 deletions(-) diff --git a/.github/workflows/review-desktop-preview.yml b/.github/workflows/review-desktop-preview.yml index b87f84997..87e6d4caf 100644 --- a/.github/workflows/review-desktop-preview.yml +++ b/.github/workflows/review-desktop-preview.yml @@ -191,8 +191,8 @@ jobs: ${{ runner.os }}-code-oss-deps-v2- # Linux owns every platform-independent build output and also downloads - # the pinned darwin-arm64 VSIX payloads. The macOS job packages this - # archive; it must not silently rebuild or omit anything transferred here. + # the pinned VSIX payloads for every Darwin target. Each macOS leg packages + # this archive; it must not silently rebuild or omit anything transferred here. - name: Compile Darwin payload on Linux env: # Embeds the PostHog project key into the review runtime build so @@ -237,15 +237,22 @@ jobs: key: ${{ runner.os }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} build: - name: Build, sign, publish preview + name: Build and sign macOS preview (${{ matrix.target }}) needs: [version, compile] - runs-on: macos-15-xlarge + strategy: + fail-fast: true + matrix: + include: + - target: darwin-arm64 + runner: macos-15-xlarge + - target: darwin-x64 + runner: macos-15-large + runs-on: ${{ matrix.runner }} timeout-minutes: 90 env: RELEASE_VERSION: ${{ needs.version.outputs.version }} RELEASE_COMMIT: ${{ needs.version.outputs.commit }} - # TODO(task 3.1): comes from the build matrix once it exists. - DARWIN_TARGET: darwin-arm64 + DARWIN_TARGET: ${{ matrix.target }} steps: - name: Checkout resolved preview commit uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 @@ -295,9 +302,9 @@ jobs: apps/review-desktop/code-oss/.build/distro/npm/remote/web/node_modules apps/review-desktop/code-oss/.build/electron ~/.cache/node-gyp - key: ${{ runner.os }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} + key: ${{ runner.os }}-${{ matrix.target }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} restore-keys: | - ${{ runner.os }}-code-oss-deps-v2- + ${{ runner.os }}-${{ matrix.target }}-code-oss-deps-v2- - name: Download darwin payload uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 @@ -374,7 +381,7 @@ jobs: with: path: | apps/review-desktop/code-oss/node_modules - key: ${{ runner.os }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} + key: ${{ runner.os }}-${{ matrix.target }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} lookup-only: true - name: Save Code OSS dependencies @@ -392,7 +399,7 @@ jobs: apps/review-desktop/code-oss/.build/distro/npm/remote/web/node_modules apps/review-desktop/code-oss/.build/electron ~/.cache/node-gyp - key: ${{ runner.os }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} + key: ${{ runner.os }}-${{ matrix.target }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} - name: Validate release artifacts run: | @@ -414,10 +421,43 @@ jobs: node apps/review-desktop/scripts/smoke-telemetry-delivery.mjs --app "$APP" node apps/review-desktop/scripts/smoke-error-telemetry.mjs --app "$APP" - # Payloads first, manifest last: a client must never see a latest.json - # whose zip is not yet downloadable. + # publish-macos uploads both arches together once every leg has passed. + # This artifact also carries the DMG a tester downloads from the run. + - name: Upload macOS preview artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: macos-${{ matrix.target }} + path: | + apps/review-desktop/dist/*-${{ matrix.target }}-${{ env.RELEASE_VERSION }}.zip + apps/review-desktop/dist/Whiteboard-${{ matrix.target }}-${{ env.RELEASE_VERSION }}.dmg + apps/review-desktop/dist/latest.json + if-no-files-found: error + retention-days: 3 + compression-level: 0 + + - name: Remove signing credentials + if: always() + run: | + security delete-keychain "$RUNNER_TEMP/buildagent.keychain" || true + rm -f "$RUNNER_TEMP/notary-key.p8" + + publish-macos: + name: Publish macOS preview + needs: [version, build] + if: ${{ !cancelled() && !inputs.dry_run && needs.version.result == 'success' && needs.build.result == 'success' }} + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ needs.version.outputs.version }} + RELEASE_COMMIT: ${{ needs.version.outputs.commit }} + steps: + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + pattern: macos-* + path: dist + + # Payloads first, manifests last: a client must never see a latest.json + # whose zip is not yet downloadable, on either arch. - name: Upload preview to R2 - if: ${{ !inputs.dry_run }} env: AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} @@ -425,29 +465,32 @@ jobs: AWS_REGION: auto R2_BUCKET: ${{ vars.R2_RELEASE_BUCKET }} run: | - DIST=apps/review-desktop/dist - RELEASE_PREFIX="s3://${R2_BUCKET}/releases/${RELEASE_VERSION}/darwin-arm64" DMG_DISPOSITION="attachment; filename=\"df-whiteboard-preview-${RELEASE_VERSION}.dmg\"" - for zip in "$DIST"/*-darwin-arm64-"${RELEASE_VERSION}".zip; do - aws s3 cp "$zip" "${RELEASE_PREFIX}/$(basename "$zip")" \ - --content-type application/zip + for target in darwin-arm64 darwin-x64; do + DIST="dist/macos-${target}" + RELEASE_PREFIX="s3://${R2_BUCKET}/releases/${RELEASE_VERSION}/${target}" + for zip in "$DIST"/*-"${target}"-"${RELEASE_VERSION}".zip; do + aws s3 cp "$zip" "${RELEASE_PREFIX}/$(basename "$zip")" \ + --content-type application/zip + done + aws s3 cp "$DIST/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ + "${RELEASE_PREFIX}/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ + --content-type application/x-apple-diskimage \ + --content-disposition "$DMG_DISPOSITION" + aws s3 cp "$DIST/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ + "s3://${R2_BUCKET}/releases/preview-latest/${target}/Whiteboard.dmg" \ + --content-type application/x-apple-diskimage \ + --content-disposition "$DMG_DISPOSITION" + done + + for target in darwin-arm64 darwin-x64; do + aws s3 cp "dist/macos-${target}/latest.json" \ + "s3://${R2_BUCKET}/update/preview/${target}/latest.json" \ + --content-type application/json done - aws s3 cp "$DIST/Whiteboard-darwin-arm64-${RELEASE_VERSION}.dmg" \ - "${RELEASE_PREFIX}/Whiteboard-darwin-arm64-${RELEASE_VERSION}.dmg" \ - --content-type application/x-apple-diskimage \ - --content-disposition "$DMG_DISPOSITION" - aws s3 cp "$DIST/Whiteboard-darwin-arm64-${RELEASE_VERSION}.dmg" \ - "s3://${R2_BUCKET}/releases/preview-latest/darwin-arm64/Whiteboard.dmg" \ - --content-type application/x-apple-diskimage \ - --content-disposition "$DMG_DISPOSITION" - - aws s3 cp "$DIST/latest.json" \ - "s3://${R2_BUCKET}/update/preview/darwin-arm64/latest.json" \ - --content-type application/json - name: Verify preview update feed - if: ${{ !inputs.dry_run }} run: | echo "Feed for an outdated preview client (expect 200 with ${RELEASE_VERSION}):" RESPONSE=$(curl -sf "https://update.dev.fast/api/update/darwin-arm64/preview/0000000000000000000000000000000000000000") @@ -465,8 +508,12 @@ jobs: echo "$STATUS" test "$STATUS" = "204" + echo "Intel preview feed (clients send plain darwin):" + curl -sf "https://update.dev.fast/api/update/darwin/preview/0000000000000000000000000000000000000000?bundle=Whiteboard%20Preview" \ + | grep -F "/Whiteboard-darwin-x64-${RELEASE_VERSION}.zip" + test "$(curl -s -o /dev/null -w '%{http_code}' "https://update.dev.fast/api/update/darwin/preview/${RELEASE_COMMIT}")" = 204 + - name: Verify preview install landing - if: ${{ !inputs.dry_run }} run: | echo "install.dev.fast/preview (expect 302 to the preview disk image):" LOCATION=$(curl -s -o /dev/null -w "%{redirect_url}" https://install.dev.fast/preview) @@ -477,19 +524,8 @@ jobs: curl -sfL -o /dev/null -w "%{http_code} %{content_type}\n" https://install.dev.fast/preview \ | grep -F "200 application/x-apple-diskimage" - - name: Upload preview DMG artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: review-desktop-preview-${{ env.RELEASE_VERSION }}-dmg - path: apps/review-desktop/dist/Whiteboard-darwin-arm64-${{ env.RELEASE_VERSION }}.dmg - retention-days: 3 - compression-level: 0 - - - name: Remove signing credentials - if: always() - run: | - security delete-keychain "$RUNNER_TEMP/buildagent.keychain" || true - rm -f "$RUNNER_TEMP/notary-key.p8" + echo "Intel preview disk image (expect a disk image):" + curl -sfI https://install.dev.fast/releases/preview-latest/darwin-x64/Whiteboard.dmg | grep -i "content-type: application/x-apple-diskimage" publish-linux: name: Publish Linux preview repository @@ -572,12 +608,12 @@ jobs: # The one job here that can write to the repository, and it holds no signing # or R2 credentials. `needs: version` carries the initial approval - # gate transitively, and `needs: build`, `needs: publish-linux` and + # gate transitively, and `needs: publish-macos`, `needs: publish-linux` and # `needs: publish-windows` mean a tag only ever names a preview that published # on every platform, answered its update feeds, and served its installers. tag-preview: name: Tag published preview - needs: [version, build, publish-linux, publish-windows] + needs: [version, publish-macos, publish-linux, publish-windows] if: ${{ !inputs.dry_run && inputs.platforms == 'all' }} runs-on: review_big_boy permissions: diff --git a/.github/workflows/review-desktop-release.yml b/.github/workflows/review-desktop-release.yml index e65973e05..393bc4f0f 100644 --- a/.github/workflows/review-desktop-release.yml +++ b/.github/workflows/review-desktop-release.yml @@ -344,8 +344,8 @@ jobs: ${{ runner.os }}-code-oss-deps-v2- # Linux owns every platform-independent build output and also downloads - # the pinned darwin-arm64 VSIX payloads. The macOS job packages this - # archive; it must not silently rebuild or omit anything transferred here. + # the pinned VSIX payloads for every Darwin target. Each macOS leg packages + # this archive; it must not silently rebuild or omit anything transferred here. - name: Compile Darwin payload on Linux env: # Embeds the PostHog project key into the review runtime build so @@ -413,18 +413,25 @@ jobs: sign: true build: - name: Build, sign, publish + name: Build and sign macOS (${{ matrix.target }}) # Nothing publishes until every selected platform has built and validated. needs: [tag, compile, linux, windows] if: ${{ !cancelled() && needs.tag.result == 'success' && needs.compile.result == 'success' && (inputs.platforms != 'all' || (needs.linux.result == 'success' && needs.windows.result == 'success')) }} - runs-on: macos-15-xlarge + strategy: + fail-fast: true + matrix: + include: + - target: darwin-arm64 + runner: macos-15-xlarge + - target: darwin-x64 + runner: macos-15-large + runs-on: ${{ matrix.runner }} timeout-minutes: 90 env: RELEASE_VERSION: ${{ needs.tag.outputs.version }} RELEASE_COMMIT: ${{ needs.tag.outputs.commit }} RELEASE_TAG: ${{ needs.tag.outputs.tag }} - # TODO(task 3.1): comes from the build matrix once it exists. - DARWIN_TARGET: darwin-arm64 + DARWIN_TARGET: ${{ matrix.target }} steps: - name: Checkout resolved release commit uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 @@ -479,9 +486,9 @@ jobs: apps/review-desktop/code-oss/.build/distro/npm/remote/web/node_modules apps/review-desktop/code-oss/.build/electron ~/.cache/node-gyp - key: ${{ runner.os }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} + key: ${{ runner.os }}-${{ matrix.target }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} restore-keys: | - ${{ runner.os }}-code-oss-deps-v2- + ${{ runner.os }}-${{ matrix.target }}-code-oss-deps-v2- - name: Download darwin payload uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 @@ -556,7 +563,7 @@ jobs: with: path: | apps/review-desktop/code-oss/node_modules - key: ${{ runner.os }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} + key: ${{ runner.os }}-${{ matrix.target }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} lookup-only: true - name: Save Code OSS dependencies @@ -574,7 +581,7 @@ jobs: apps/review-desktop/code-oss/.build/distro/npm/remote/web/node_modules apps/review-desktop/code-oss/.build/electron ~/.cache/node-gyp - key: ${{ runner.os }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} + key: ${{ runner.os }}-${{ matrix.target }}-code-oss-deps-v2-${{ hashFiles('apps/review-desktop/code-oss/**/package-lock.json') }} - name: Validate release artifacts run: | @@ -597,10 +604,44 @@ jobs: node apps/review-desktop/scripts/smoke-telemetry-delivery.mjs --app "$APP" node apps/review-desktop/scripts/smoke-error-telemetry.mjs --app "$APP" - # Payloads first, manifest last: a client must never see a latest.json - # whose zip is not yet downloadable. + # publish-macos uploads both arches together once every leg has passed. + - name: Upload macOS release artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: macos-${{ matrix.target }} + path: | + apps/review-desktop/dist/*-${{ matrix.target }}-${{ env.RELEASE_VERSION }}.zip + apps/review-desktop/dist/Whiteboard-${{ matrix.target }}-${{ env.RELEASE_VERSION }}.dmg + apps/review-desktop/dist/latest.json + if-no-files-found: error + retention-days: 3 + compression-level: 0 + + - name: Remove signing credentials + if: always() + run: | + security delete-keychain "$RUNNER_TEMP/buildagent.keychain" || true + rm -f "$RUNNER_TEMP/notary-key.p8" + + publish-macos: + name: Publish macOS + # Nothing publishes until every selected platform has built and validated. + needs: [tag, build, linux, windows] + if: ${{ !cancelled() && !inputs.dry_run && needs.tag.result == 'success' && needs.build.result == 'success' && (inputs.platforms == 'macos' || (needs.linux.result == 'success' && needs.windows.result == 'success')) }} + runs-on: ubuntu-latest + env: + RELEASE_VERSION: ${{ needs.tag.outputs.version }} + RELEASE_COMMIT: ${{ needs.tag.outputs.commit }} + RELEASE_TAG: ${{ needs.tag.outputs.tag }} + steps: + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + pattern: macos-* + path: dist + + # Payloads first, manifests last: a client must never see a latest.json + # whose zip is not yet downloadable, on either arch. - name: Upload release to R2 - if: ${{ !inputs.dry_run }} env: AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} @@ -608,31 +649,34 @@ jobs: AWS_REGION: auto R2_BUCKET: ${{ vars.R2_RELEASE_BUCKET }} run: | - DIST=apps/review-desktop/dist - RELEASE_PREFIX="s3://${R2_BUCKET}/releases/${RELEASE_VERSION}/darwin-arm64" # The releases/latest/ key is version-free by design, so the version # rides along in the filename a client saves the download as. DMG_DISPOSITION="attachment; filename=\"df-whiteboard-${RELEASE_VERSION}.dmg\"" - for zip in "$DIST"/*-darwin-arm64-"${RELEASE_VERSION}".zip; do - aws s3 cp "$zip" "${RELEASE_PREFIX}/$(basename "$zip")" \ - --content-type application/zip + for target in darwin-arm64 darwin-x64; do + DIST="dist/macos-${target}" + RELEASE_PREFIX="s3://${R2_BUCKET}/releases/${RELEASE_VERSION}/${target}" + for zip in "$DIST"/*-"${target}"-"${RELEASE_VERSION}".zip; do + aws s3 cp "$zip" "${RELEASE_PREFIX}/$(basename "$zip")" \ + --content-type application/zip + done + aws s3 cp "$DIST/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ + "${RELEASE_PREFIX}/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ + --content-type application/x-apple-diskimage \ + --content-disposition "$DMG_DISPOSITION" + aws s3 cp "$DIST/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ + "s3://${R2_BUCKET}/releases/latest/${target}/Whiteboard.dmg" \ + --content-type application/x-apple-diskimage \ + --content-disposition "$DMG_DISPOSITION" + done + + for target in darwin-arm64 darwin-x64; do + aws s3 cp "dist/macos-${target}/latest.json" \ + "s3://${R2_BUCKET}/update/stable/${target}/latest.json" \ + --content-type application/json done - aws s3 cp "$DIST/Whiteboard-darwin-arm64-${RELEASE_VERSION}.dmg" \ - "${RELEASE_PREFIX}/Whiteboard-darwin-arm64-${RELEASE_VERSION}.dmg" \ - --content-type application/x-apple-diskimage \ - --content-disposition "$DMG_DISPOSITION" - aws s3 cp "$DIST/Whiteboard-darwin-arm64-${RELEASE_VERSION}.dmg" \ - "s3://${R2_BUCKET}/releases/latest/darwin-arm64/Whiteboard.dmg" \ - --content-type application/x-apple-diskimage \ - --content-disposition "$DMG_DISPOSITION" - - aws s3 cp "$DIST/latest.json" \ - "s3://${R2_BUCKET}/update/stable/darwin-arm64/latest.json" \ - --content-type application/json - name: Verify update feed - if: ${{ !inputs.dry_run }} run: | echo "Feed for an outdated client (expect 200 with ${RELEASE_VERSION}):" RESPONSE=$(curl -sf "https://update.dev.fast/api/update/darwin-arm64/stable/0000000000000000000000000000000000000000") @@ -650,8 +694,12 @@ jobs: echo "$STATUS" test "$STATUS" = "204" + echo "Intel feed (clients send plain darwin):" + curl -sf "https://update.dev.fast/api/update/darwin/stable/0000000000000000000000000000000000000000?bundle=Whiteboard" \ + | grep -F "/Whiteboard-darwin-x64-${RELEASE_VERSION}.zip" + test "$(curl -s -o /dev/null -w '%{http_code}' "https://update.dev.fast/api/update/darwin/stable/${RELEASE_COMMIT}")" = 204 + - name: Verify install landing - if: ${{ !inputs.dry_run }} run: | echo "install.dev.fast (expect 302 to the disk image):" LOCATION=$(curl -s -o /dev/null -w "%{redirect_url}" https://install.dev.fast/) @@ -662,20 +710,16 @@ jobs: curl -sfL -o /dev/null -w "%{http_code} %{content_type}\n" https://install.dev.fast/ \ | grep -F "200 application/x-apple-diskimage" - - name: Attach DMG to GitHub Release - if: ${{ !inputs.dry_run }} + echo "Intel disk image (expect a disk image):" + curl -sfI https://install.dev.fast/releases/latest/darwin-x64/Whiteboard.dmg | grep -i "content-type: application/x-apple-diskimage" + + - name: Attach DMGs to GitHub Release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh release upload "$RELEASE_TAG" \ --repo "${{ github.repository }}" \ - "apps/review-desktop/dist/Whiteboard-darwin-arm64-${RELEASE_VERSION}.dmg" - - - name: Remove signing credentials - if: always() - run: | - security delete-keychain "$RUNNER_TEMP/buildagent.keychain" || true - rm -f "$RUNNER_TEMP/notary-key.p8" + dist/macos-*/Whiteboard-*-"${RELEASE_VERSION}".dmg publish-linux: name: Publish Linux repositories @@ -767,8 +811,8 @@ jobs: finalize-release: name: Publish release - needs: [tag, build, publish-linux, publish-windows] - if: ${{ !cancelled() && !inputs.dry_run && needs.tag.result == 'success' && (inputs.platforms != 'all' && inputs.platforms != 'macos' || needs.build.result == 'success') && (inputs.platforms != 'all' && inputs.platforms != 'linux' || needs.publish-linux.result == 'success') && (inputs.platforms != 'all' && inputs.platforms != 'windows' || needs.publish-windows.result == 'success') }} + needs: [tag, publish-macos, publish-linux, publish-windows] + if: ${{ !cancelled() && !inputs.dry_run && needs.tag.result == 'success' && (inputs.platforms != 'all' && inputs.platforms != 'macos' || needs.publish-macos.result == 'success') && (inputs.platforms != 'all' && inputs.platforms != 'linux' || needs.publish-linux.result == 'success') && (inputs.platforms != 'all' && inputs.platforms != 'windows' || needs.publish-windows.result == 'success') }} runs-on: review_big_boy steps: - name: Publish GitHub Release diff --git a/apps/review-desktop/README.md b/apps/review-desktop/README.md index aae551759..c8c5d90f1 100644 --- a/apps/review-desktop/README.md +++ b/apps/review-desktop/README.md @@ -244,22 +244,25 @@ preview lands at the new application path; later preview updates retain it. The release is a split build. Linux is not only a cache warmer: it is the authoritative producer for everything that does not require a Darwin host. -`scripts/compile-darwin-payload.sh` creates the archive, and -`REVIEW_DESKTOP_PRECOMPILED=1 scripts/package-macos.sh` consumes it. +`scripts/compile-darwin-payload.sh` creates one archive for both Darwin +targets, and `REVIEW_DESKTOP_PRECOMPILED=1 scripts/package-macos.sh` consumes it +on each macOS build leg (`darwin-arm64` and `darwin-x64`), packaging the target +of the host it runs on. | Produced on Linux and transferred | Produced or assembled on macOS | | --- | --- | | Code OSS `out-build`, `out-vscode-min`, and `out` | Electron application bundle | | Compiled built-in extensions in `.build/extensions` | Darwin-native npm closure installed by `pnpm` | -| Manifest-selected `darwin-arm64` VSIX payloads, including `ty`, Ruff, and rust-analyzer | Manifest-selected extensions copied into the final app | +| Manifest-selected `darwin-arm64` and `darwin-x64` VSIX payloads, including `ty`, Ruff, and rust-analyzer | Manifest-selected extensions copied into the final app | | Review canvas/server and required workspace `dist` directories | App icon, signatures, notarization, ZIP, and DMG | The curated-extension handoff is manifest-driven. Linux materializes the -target variants, copies them into -`.build/review-curated-extensions/darwin-arm64`, and includes that directory in -the archive. macOS requires that directory before packaging and verifies every -manifest entry while copying it into the app. Release validation verifies the -same complete set again after notarization and before upload. +variants for every Darwin target, copies each into +`.build/review-curated-extensions/`, and includes those directories in +the archive. Each macOS leg requires every target's directory before packaging, +then verifies every manifest entry for its own target while copying that +directory into the app. Release validation verifies the same complete set again +after notarization and before upload. `scripts/darwin-payload-manifest.sh` is the source of truth for archive paths. Its required paths must exist before macOS packaging starts. Its archive-only From b9d328fc57999ecca3b7f15c8771a684a317f6be Mon Sep 17 00:00:00 2001 From: thesiti92 Date: Sun, 27 Sep 2026 20:33:41 -0400 Subject: [PATCH 5/8] Document the Intel macOS release Agent-Session: 11e6cdc4-ebab-4f67-9499-a7e914620a86 Agent-Session: e8a1eaea-7717-48b1-a143-d9adbd3f5f58 Agent-Session: 01a0e553-25fc-7350-ab29-e046a939a2e6 Agent-Session: b11edf22-6b82-42e6-87c4-07c995cb9400 --- apps/review-desktop/README.md | 51 +++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/apps/review-desktop/README.md b/apps/review-desktop/README.md index c8c5d90f1..f6655adbf 100644 --- a/apps/review-desktop/README.md +++ b/apps/review-desktop/README.md @@ -103,9 +103,9 @@ built Desktop through the installed CLI and the JSON review API. See ## Packaging and releases -macOS arm64 is the only packaged platform with release channels; Linux has a -packaging script but no distribution. Signed builds auto-update from -`https://update.dev.fast` on either the `stable` or `preview` channel. +macOS (arm64 and x64) is the only packaged platform with release channels; +Linux has a packaging script but no distribution. Signed builds auto-update +from `https://update.dev.fast` on either the `stable` or `preview` channel. ### Local packaging @@ -113,7 +113,7 @@ packaging script but no distribution. Signed builds auto-update from SKIP_NOTARIZE=1 pnpm --filter @dev.fast/review-desktop app:package:macos ``` -builds an unsigned `VSCode-darwin-arm64/Whiteboard.app` and skips +builds an unsigned `VSCode-darwin-/Whiteboard.app` for the host arch and skips signing, notarization, and artifact creation. A full run needs the signing environment and produces the `.dmg` plus one Squirrel update zip per installed bundle folder name (`Whiteboard-…zip`, `Review-…zip`; see `release-channel.mjs`), @@ -138,19 +138,21 @@ patch/minor/major bump. The workflow: 1. bumps `apps/review-desktop/package.json`, commits `[skip ci]`, tags `vX.Y.Z`, and creates a draft GitHub release; 2. compiles the platform-independent Code OSS, Review canvas, Review server, - workspace packages, and pinned `darwin-arm64` curated extensions on Linux, - then uploads one `darwin-payload` artifact; -3. extracts that payload on `macos-15-xlarge`, performs only the native Darwin + workspace packages, and pinned `darwin-arm64` and `darwin-x64` curated + extensions on Linux, then uploads one `darwin-payload` artifact; +3. extracts that payload on two matrix legs, `darwin-arm64` on `macos-15-xlarge` + and `darwin-x64` on `macos-15-large`, performs only the native Darwin package assembly, stages the runtime, tools, and extensions, then signs and notarizes via `app:package:macos`; 4. gates the upload with `scripts/validate-release-artifacts.mjs` (curated extension closure, staple and Gatekeeper checks, and packaged `product.json` commit/quality/updateUrl assertions), which also emits the `latest.json` - feed manifest; -5. uploads to R2 in two passes — zip and dmg payloads first, `latest.json` - last — so a client can never see a manifest whose payload is missing; -6. curls the live feed to confirm the new release is served, attaches the dmg - to the GitHub release, and publishes it. + feed manifest for that arch; +5. once both legs pass, `publish-macos` uploads to R2 in two passes — both + arches' zip and dmg payloads first, both `latest.json` files last — so a + client can never see a manifest whose payload is missing; +6. curls both live feeds to confirm the new release is served, attaches both + dmgs to the GitHub release, and publishes it. The `platforms` input picks `all` (the default), `macos`, `linux` or `windows`. Windows builds in parallel with the Darwin payload and the Linux packages @@ -300,21 +302,24 @@ keys stay version-free for that reason, so the version rides on each object's browser download always does. ``` -update/stable/darwin-arm64/latest.json current-release manifest -update/preview/darwin-arm64/latest.json current-preview manifest -releases//darwin-arm64/ Whiteboard-darwin-arm64-.zip + .dmg - Review-darwin-arm64-.zip (Review.app-named copy) -releases/latest/darwin-arm64/Whiteboard.dmg - direct-download alias, saved as - df-whiteboard-.dmg -releases/preview-latest/darwin-arm64/Whiteboard.dmg - preview-download alias, saved as - df-whiteboard-preview-.dmg +update/stable//latest.json current-release manifest +update/preview//latest.json current-preview manifest +releases/// Whiteboard--.zip + .dmg + Review--.zip (Review.app-named copy) +releases/latest//Whiteboard.dmg + direct-download alias, saved as + df-whiteboard-.dmg +releases/preview-latest//Whiteboard.dmg + preview-download alias, saved as + df-whiteboard-preview-.dmg ``` +`` is `darwin-arm64` or `darwin-x64`; every key exists for both. + `GET /api/update/:platform/:quality/:commit` answers 204 when the caller's stamped commit matches the manifest (or no manifest exists yet) and Squirrel -JSON otherwise; `GET /releases/*` streams payloads. Because the feed keys its +JSON otherwise; `GET /releases/*` streams payloads. Intel clients request the +plain `darwin` platform, which the Worker maps to `darwin-x64`. Because the feed keys its answer off the caller's commit, the fork's `doDownloadUpdate` sends the installed commit — not the target commit — when re-checking (see `UPSTREAM`). From e73662a8d5d120e8f4df23189f8a3abca4249864 Mon Sep 17 00:00:00 2001 From: thesiti92 Date: Sun, 27 Sep 2026 21:28:01 -0400 Subject: [PATCH 6/8] Bump diffr to 0.1.4 for the Intel macOS build Agent-Session: 11e6cdc4-ebab-4f67-9499-a7e914620a86 Agent-Session: b11edf22-6b82-42e6-87c4-07c995cb9400 Agent-Session: 0b48503e-b517-4f1d-a168-e843dc788525 Agent-Session: 01a0e553-25fc-7350-ab29-e046a939a2e6 --- packages/review-protocol/package.json | 2 +- packages/review/package.json | 2 +- pnpm-lock.yaml | 14 +++++++------- pnpm-workspace.yaml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/review-protocol/package.json b/packages/review-protocol/package.json index 39757111d..aae7c9c02 100644 --- a/packages/review-protocol/package.json +++ b/packages/review-protocol/package.json @@ -20,7 +20,7 @@ "typecheck": "pnpm -w exec tsc -p packages/review-protocol/tsconfig.json --noEmit" }, "dependencies": { - "@dev.fast/diffr": "0.1.3", + "@dev.fast/diffr": "0.1.4", "@dev.fast/json": "workspace:*", "@dev.fast/trace-protocol": "workspace:*", "zod": "4.4.3" diff --git a/packages/review/package.json b/packages/review/package.json index 72be03681..dd67eacd9 100644 --- a/packages/review/package.json +++ b/packages/review/package.json @@ -82,7 +82,7 @@ "zod": "4.4.3" }, "devDependencies": { - "@dev.fast/diffr": "0.1.3", + "@dev.fast/diffr": "0.1.4", "@dev.fast/json": "workspace:*", "@dev.fast/local-vcs": "^0.1.0", "@dev.fast/review-protocol": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae01e7173..2d3b6c92f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -147,8 +147,8 @@ importers: version: 4.4.3 devDependencies: '@dev.fast/diffr': - specifier: 0.1.3 - version: 0.1.3(zod@4.4.3) + specifier: 0.1.4 + version: 0.1.4(zod@4.4.3) '@dev.fast/json': specifier: workspace:* version: link:../json @@ -198,8 +198,8 @@ importers: packages/review-protocol: dependencies: '@dev.fast/diffr': - specifier: 0.1.3 - version: 0.1.3(zod@4.4.3) + specifier: 0.1.4 + version: 0.1.4(zod@4.4.3) '@dev.fast/json': specifier: workspace:* version: link:../json @@ -405,8 +405,8 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} - '@dev.fast/diffr@0.1.3': - resolution: {integrity: sha512-dNgVdkyN3IvjM7jLfbpfWoXDQOsGXHql/gqShw1vvKTD7XAQEkWFY3jqhlx0+h0biSkiWSBMcOYz5puZ03h8kA==} + '@dev.fast/diffr@0.1.4': + resolution: {integrity: sha512-3iSpszK2q8c9AtnE4p+Iq5NY5KeLOQ3mjWs9TXstWFbsBBeH6xXTBYbi4VuEqZ+Jud1dEYmCbEK8CpXTaNuSYQ==} engines: {node: '>=20'} hasBin: true peerDependencies: @@ -3247,7 +3247,7 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} - '@dev.fast/diffr@0.1.3(zod@4.4.3)': + '@dev.fast/diffr@0.1.4(zod@4.4.3)': dependencies: zod: 4.4.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bc1d612a8..95292ddc6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,7 +13,7 @@ verifyDepsBeforeRun: false minimumReleaseAge: 10080 minimumReleaseAgeStrict: true minimumReleaseAgeExclude: - - "@dev.fast/diffr@0.1.3" + - "@dev.fast/diffr@0.1.4" # Vitest 4.1.11 requires this exact release, which is not yet seven days old. - obug@2.2.1 - "@dev.fast/trace-protocol@0.5.0" From a10dc75768cce267b9d0a6292f5508223716d140 Mon Sep 17 00:00:00 2001 From: thesiti92 Date: Sun, 27 Sep 2026 21:34:50 -0400 Subject: [PATCH 7/8] Assert bundled native executables and require an explicit macOS target Agent-Session: 11e6cdc4-ebab-4f67-9499-a7e914620a86 Agent-Session: b11edf22-6b82-42e6-87c4-07c995cb9400 Agent-Session: 0b48503e-b517-4f1d-a168-e843dc788525 Agent-Session: 01a0e553-25fc-7350-ab29-e046a939a2e6 --- .github/workflows/review-desktop-preview.yml | 4 +- .github/workflows/review-desktop-release.yml | 4 +- .../scripts/release-channel.mjs | 2 +- .../scripts/validate-release-artifacts.mjs | 55 +++++++++++-------- .../validate-release-artifacts.test.mjs | 1 + 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/.github/workflows/review-desktop-preview.yml b/.github/workflows/review-desktop-preview.yml index 87e6d4caf..3e1d5eb32 100644 --- a/.github/workflows/review-desktop-preview.yml +++ b/.github/workflows/review-desktop-preview.yml @@ -245,10 +245,12 @@ jobs: include: - target: darwin-arm64 runner: macos-15-xlarge + timeout: 90 - target: darwin-x64 runner: macos-15-large + timeout: 150 runs-on: ${{ matrix.runner }} - timeout-minutes: 90 + timeout-minutes: ${{ matrix.timeout }} env: RELEASE_VERSION: ${{ needs.version.outputs.version }} RELEASE_COMMIT: ${{ needs.version.outputs.commit }} diff --git a/.github/workflows/review-desktop-release.yml b/.github/workflows/review-desktop-release.yml index 393bc4f0f..8e7729fc3 100644 --- a/.github/workflows/review-desktop-release.yml +++ b/.github/workflows/review-desktop-release.yml @@ -423,10 +423,12 @@ jobs: include: - target: darwin-arm64 runner: macos-15-xlarge + timeout: 90 - target: darwin-x64 runner: macos-15-large + timeout: 150 runs-on: ${{ matrix.runner }} - timeout-minutes: 90 + timeout-minutes: ${{ matrix.timeout }} env: RELEASE_VERSION: ${{ needs.tag.outputs.version }} RELEASE_COMMIT: ${{ needs.tag.outputs.commit }} diff --git a/apps/review-desktop/scripts/release-channel.mjs b/apps/review-desktop/scripts/release-channel.mjs index 4c35ebae9..a1c4584a7 100644 --- a/apps/review-desktop/scripts/release-channel.mjs +++ b/apps/review-desktop/scripts/release-channel.mjs @@ -94,7 +94,7 @@ export function darwinTarget(arch = process.arch) { return target; } -export function updateZipName(artifact, version, target = darwinTarget()) { +export function updateZipName(artifact, version, target) { return `${artifact}-${target}-${version}.zip`; } diff --git a/apps/review-desktop/scripts/validate-release-artifacts.mjs b/apps/review-desktop/scripts/validate-release-artifacts.mjs index 0e60de6c0..03e003dc6 100644 --- a/apps/review-desktop/scripts/validate-release-artifacts.mjs +++ b/apps/review-desktop/scripts/validate-release-artifacts.mjs @@ -10,7 +10,6 @@ import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; import { - globSync, lstatSync, readFileSync, readdirSync, @@ -19,7 +18,11 @@ import { import path from "node:path"; import { parseArgs } from "node:util"; -import { verifyCuratedExtensions } from "./curated-extensions.mjs"; +import { parseGroupSelection } from "./curated-extensions.manifest.mjs"; +import { + selectExtensions, + verifyCuratedExtensions, +} from "./curated-extensions.mjs"; import { assertReleaseChannel, darwinTarget, @@ -44,7 +47,7 @@ export function buildManifest({ version, commit, payloads, - target = darwinTarget(), + target, now = new Date(), }) { const bundles = Object.fromEntries( @@ -241,10 +244,16 @@ async function main() { ); assertPackagedProduct(product, { commit, channel }); - verifyCuratedExtensions({ - root: path.join(app, "Contents", "Resources", "app", "extensions"), - target, - }); + + const extensionsDir = path.join( + app, + "Contents", + "Resources", + "app", + "extensions", + ); + + verifyCuratedExtensions({ root: extensionsDir, target }); assertMachOArch( path.join(app, "Contents", "MacOS", sourceProduct.nameShort), @@ -263,25 +272,25 @@ async function main() { arch, ); - const [rustAnalyzerServer] = globSync( - path.join( - app, - "Contents", - "Resources", - "app", - "extensions", - "rust-lang.rust-analyzer*", - "server", - "rust-analyzer", - ), - ); + // rust-analyzer is optional and downloaded at runtime, never packaged, so + // only the manifest's bundled extensions can be asserted here. Reuses + // selectExtensions so this never drifts from what verifyCuratedExtensions + // itself considers bundled for `target`. + for (const { extension, targetKey } of selectExtensions( + target, + parseGroupSelection(), + )) { + for (const relative of extension.executables) { + const executable = path.join( + extensionsDir, + extension.id, + targetKey.startsWith("win32-") ? `${relative}.exe` : relative, + ); - if (!rustAnalyzerServer) { - throw new Error(`rust-analyzer server binary not found under ${app}`); + assertMachOArch(executable, arch); + } } - assertMachOArch(rustAnalyzerServer, arch); - run("xcrun", ["stapler", "validate", app]); run("spctl", ["-a", "-vv", "--type", "exec", app]); run("xcrun", ["stapler", "validate", dmg]); diff --git a/apps/review-desktop/scripts/validate-release-artifacts.test.mjs b/apps/review-desktop/scripts/validate-release-artifacts.test.mjs index e8ca0e5ff..f890445d3 100644 --- a/apps/review-desktop/scripts/validate-release-artifacts.test.mjs +++ b/apps/review-desktop/scripts/validate-release-artifacts.test.mjs @@ -36,6 +36,7 @@ test("buildManifest emits the schema the update Worker serves", () => { const manifest = buildManifest({ version: "1.2.3", commit: "abc123", + target: "darwin-arm64", payloads: [ { bundle: "Review", artifact: "Review", sha256: "cafe" }, { bundle: "Whiteboard", artifact: "Whiteboard", sha256: "f00d" }, From cf74ee4d5413b070ec85a356cb29c246577bd3e6 Mon Sep 17 00:00:00 2001 From: thesiti92 Date: Sun, 27 Sep 2026 21:47:21 -0400 Subject: [PATCH 8/8] Simplify the Intel macOS build scripts and trim comments Agent-Session: 11e6cdc4-ebab-4f67-9499-a7e914620a86 Agent-Session: b11edf22-6b82-42e6-87c4-07c995cb9400 Agent-Session: d206e73a-3115-47e6-8c72-b8251090e94a Agent-Session: 01a0e5ab-8d00-7bd2-9d87-a0496098a08c --- .github/workflows/review-desktop-preview.yml | 18 ++++++++-------- .github/workflows/review-desktop-release.yml | 15 ++++++------- .../scripts/compile-darwin-payload.sh | 21 ++++++++----------- .../scripts/curated-extensions.test.mjs | 4 ---- apps/review-desktop/scripts/darwin-arch.sh | 2 +- .../scripts/release-channel.mjs | 8 ++----- .../scripts/validate-release-artifacts.mjs | 5 +---- 7 files changed, 30 insertions(+), 43 deletions(-) diff --git a/.github/workflows/review-desktop-preview.yml b/.github/workflows/review-desktop-preview.yml index 3e1d5eb32..83f424a0c 100644 --- a/.github/workflows/review-desktop-preview.yml +++ b/.github/workflows/review-desktop-preview.yml @@ -423,8 +423,7 @@ jobs: node apps/review-desktop/scripts/smoke-telemetry-delivery.mjs --app "$APP" node apps/review-desktop/scripts/smoke-error-telemetry.mjs --app "$APP" - # publish-macos uploads both arches together once every leg has passed. - # This artifact also carries the DMG a tester downloads from the run. + # Also carries the DMG a tester downloads from the run. - name: Upload macOS preview artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: @@ -469,25 +468,26 @@ jobs: run: | DMG_DISPOSITION="attachment; filename=\"df-whiteboard-preview-${RELEASE_VERSION}.dmg\"" - for target in darwin-arm64 darwin-x64; do - DIST="dist/macos-${target}" + for dir in dist/macos-*; do + target="${dir#dist/macos-}" RELEASE_PREFIX="s3://${R2_BUCKET}/releases/${RELEASE_VERSION}/${target}" - for zip in "$DIST"/*-"${target}"-"${RELEASE_VERSION}".zip; do + for zip in "$dir"/*-"${target}"-"${RELEASE_VERSION}".zip; do aws s3 cp "$zip" "${RELEASE_PREFIX}/$(basename "$zip")" \ --content-type application/zip done - aws s3 cp "$DIST/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ + aws s3 cp "$dir/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ "${RELEASE_PREFIX}/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ --content-type application/x-apple-diskimage \ --content-disposition "$DMG_DISPOSITION" - aws s3 cp "$DIST/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ + aws s3 cp "$dir/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ "s3://${R2_BUCKET}/releases/preview-latest/${target}/Whiteboard.dmg" \ --content-type application/x-apple-diskimage \ --content-disposition "$DMG_DISPOSITION" done - for target in darwin-arm64 darwin-x64; do - aws s3 cp "dist/macos-${target}/latest.json" \ + for dir in dist/macos-*; do + target="${dir#dist/macos-}" + aws s3 cp "$dir/latest.json" \ "s3://${R2_BUCKET}/update/preview/${target}/latest.json" \ --content-type application/json done diff --git a/.github/workflows/review-desktop-release.yml b/.github/workflows/review-desktop-release.yml index 8e7729fc3..aff260a48 100644 --- a/.github/workflows/review-desktop-release.yml +++ b/.github/workflows/review-desktop-release.yml @@ -655,25 +655,26 @@ jobs: # rides along in the filename a client saves the download as. DMG_DISPOSITION="attachment; filename=\"df-whiteboard-${RELEASE_VERSION}.dmg\"" - for target in darwin-arm64 darwin-x64; do - DIST="dist/macos-${target}" + for dir in dist/macos-*; do + target="${dir#dist/macos-}" RELEASE_PREFIX="s3://${R2_BUCKET}/releases/${RELEASE_VERSION}/${target}" - for zip in "$DIST"/*-"${target}"-"${RELEASE_VERSION}".zip; do + for zip in "$dir"/*-"${target}"-"${RELEASE_VERSION}".zip; do aws s3 cp "$zip" "${RELEASE_PREFIX}/$(basename "$zip")" \ --content-type application/zip done - aws s3 cp "$DIST/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ + aws s3 cp "$dir/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ "${RELEASE_PREFIX}/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ --content-type application/x-apple-diskimage \ --content-disposition "$DMG_DISPOSITION" - aws s3 cp "$DIST/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ + aws s3 cp "$dir/Whiteboard-${target}-${RELEASE_VERSION}.dmg" \ "s3://${R2_BUCKET}/releases/latest/${target}/Whiteboard.dmg" \ --content-type application/x-apple-diskimage \ --content-disposition "$DMG_DISPOSITION" done - for target in darwin-arm64 darwin-x64; do - aws s3 cp "dist/macos-${target}/latest.json" \ + for dir in dist/macos-*; do + target="${dir#dist/macos-}" + aws s3 cp "$dir/latest.json" \ "s3://${R2_BUCKET}/update/stable/${target}/latest.json" \ --content-type application/json done diff --git a/apps/review-desktop/scripts/compile-darwin-payload.sh b/apps/review-desktop/scripts/compile-darwin-payload.sh index 0d401edb0..879f2d1f9 100755 --- a/apps/review-desktop/scripts/compile-darwin-payload.sh +++ b/apps/review-desktop/scripts/compile-darwin-payload.sh @@ -18,12 +18,12 @@ if (( $# > 0 )); then exit 2 fi -# The compile host is Linux, but curated extensions contain target-native -# servers. build.sh recompiles unconditionally, so run it once for the -# arm64 leg; the other Darwin targets are materialized directly below without -# repeating the compile. +first_target="${DARWIN_PAYLOAD_TARGETS[0]}" + +# build.sh always recompiles, so run it once here; the other targets are +# materialized below without repeating the compile. REVIEW_DESKTOP_COMPILE_ONLY=1 \ - REVIEW_DESKTOP_CURATED_EXTENSION_TARGET=darwin-arm64 \ + REVIEW_DESKTOP_CURATED_EXTENSION_TARGET="$first_target" \ bash "$APP_DIR/scripts/build.sh" if git -C "$MONOREPO_ROOT" rev-parse HEAD >/dev/null 2>&1; then @@ -32,10 +32,8 @@ else BUILD_SOURCEVERSION="$(jj --repository "$MONOREPO_ROOT" --ignore-working-copy log --no-graph -r @ -T 'commit_id')" fi export BUILD_SOURCEVERSION -# vscode-darwin-arm64-min-prepare produces the arch-independent out-vscode-min -# (codicons, non-native extensions, media, esbuild bundle; see -# gulpfile.vscode.ts:640-648). Both Darwin targets reuse this one output, so -# the task name stays arm64-specific even though it isn't arch-bound. +# The arm64-named prepare task produces the arch-independent out-vscode-min +# that both targets reuse. npm --prefix "$CHECKOUT" run gulp -- vscode-darwin-arm64-min-prepare # Tags the bundles, so it must run before they are archived. @@ -44,9 +42,8 @@ if [[ -n "${REVIEW_POSTHOG_KEY:-}" ]]; then fi for target in "${DARWIN_PAYLOAD_TARGETS[@]}"; do - if [[ "$target" != "darwin-arm64" ]]; then - # build.sh above only materialized curated extensions for darwin-arm64; - # materialize the remaining targets without recompiling. + if [[ "$target" != "$first_target" ]]; then + # build.sh above only materialized curated extensions for the first target. node "$APP_DIR/scripts/curated-extensions.mjs" "--target=$target" fi diff --git a/apps/review-desktop/scripts/curated-extensions.test.mjs b/apps/review-desktop/scripts/curated-extensions.test.mjs index 025eb3372..5bf69d31c 100644 --- a/apps/review-desktop/scripts/curated-extensions.test.mjs +++ b/apps/review-desktop/scripts/curated-extensions.test.mjs @@ -253,10 +253,6 @@ test("carries Darwin curated extensions from Linux compile through release valid ]); assert.match(buildScript, /REVIEW_DESKTOP_CURATED_EXTENSION_TARGET/); - assert.match( - compileScript, - /REVIEW_DESKTOP_CURATED_EXTENSION_TARGET=darwin-arm64/, - ); assert.match( compileScript, /source "\$APP_DIR\/scripts\/darwin-payload-manifest\.sh"/, diff --git a/apps/review-desktop/scripts/darwin-arch.sh b/apps/review-desktop/scripts/darwin-arch.sh index 512f0205a..a5fe968db 100644 --- a/apps/review-desktop/scripts/darwin-arch.sh +++ b/apps/review-desktop/scripts/darwin-arch.sh @@ -1,5 +1,5 @@ # shellcheck shell=bash -# Exports the macOS release arch for the host. Sourced by the macOS-only scripts. +# Sets DARWIN_ARCH/DARWIN_TARGET for the macOS host. case "$(uname -m)" in arm64) DARWIN_ARCH=arm64 ;; x86_64) DARWIN_ARCH=x64 ;; diff --git a/apps/review-desktop/scripts/release-channel.mjs b/apps/review-desktop/scripts/release-channel.mjs index a1c4584a7..a717accb7 100644 --- a/apps/review-desktop/scripts/release-channel.mjs +++ b/apps/review-desktop/scripts/release-channel.mjs @@ -82,16 +82,12 @@ export function updateBundlesFor(channel) { return UPDATE_BUNDLES[channel]; } -const DARWIN_TARGETS = { arm64: "darwin-arm64", x64: "darwin-x64" }; - export function darwinTarget(arch = process.arch) { - const target = DARWIN_TARGETS[arch]; - - if (!target) { + if (arch !== "arm64" && arch !== "x64") { throw new Error(`unsupported macOS arch ${arch}`); } - return target; + return `darwin-${arch}`; } export function updateZipName(artifact, version, target) { diff --git a/apps/review-desktop/scripts/validate-release-artifacts.mjs b/apps/review-desktop/scripts/validate-release-artifacts.mjs index 03e003dc6..f46076699 100644 --- a/apps/review-desktop/scripts/validate-release-artifacts.mjs +++ b/apps/review-desktop/scripts/validate-release-artifacts.mjs @@ -272,10 +272,7 @@ async function main() { arch, ); - // rust-analyzer is optional and downloaded at runtime, never packaged, so - // only the manifest's bundled extensions can be asserted here. Reuses - // selectExtensions so this never drifts from what verifyCuratedExtensions - // itself considers bundled for `target`. + // rust-analyzer is downloaded at runtime, so only the bundled extensions are checked here. for (const { extension, targetKey } of selectExtensions( target, parseGroupSelection(),