Skip to content

feat: add intel mac desktop packaging flow#550

Merged
mrcfps merged 5 commits intomainfrom
feat/intel-mac-packaging
Mar 26, 2026
Merged

feat: add intel mac desktop packaging flow#550
mrcfps merged 5 commits intomainfrom
feat/intel-mac-packaging

Conversation

@mrcfps
Copy link
Copy Markdown
Contributor

@mrcfps mrcfps commented Mar 25, 2026

Closes #584

Summary

  • add explicit arm64/x64 desktop packaging entrypoints and fail fast when a mac build runs on the wrong host architecture
  • publish desktop build, release, and update-feed artifacts to arch-specific paths so Intel and Apple Silicon apps resolve the right packages
  • update CI/docs to cover Intel mac packaging commands, verification paths, and updater expectations

Testing

  • pnpm typecheck
  • pnpm exec vitest run tests/desktop/update-manager.test.ts
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Architecture-specific macOS releases (arm64 & x64): arch-tagged artifacts, per-arch checksums, and architecture-scoped update feeds with legacy arm64 compatibility.
  • Documentation

    • Packaging and build guidance updated with arch-specific scripts and examples for arm64/x64; update feed behavior documented.
  • Chores

    • CI/release pipelines now build, name, upload, and publish artifacts per-architecture; update feed URLs include arch path segments.
  • Bug Fixes

    • More robust build output parsing and installer discovery; update feed resolution now validates mac architecture.
  • Tests

    • Added/updated tests for feed resolution, mac architecture validation, and noisy JSON extraction.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds macOS architecture-specific build and release flows: CI job matrices, arch-tagged artifacts/checksums, R2/Cloudflare upload prefixes, local build scripts and npm scripts for arm64/x64, updater feed URLs including arch, and JSON-extraction tooling for polluted electron-builder output.

Changes

Cohort / File(s) Summary
GitHub Actions workflows
/.github/workflows/desktop-build.yml, /.github/workflows/desktop-ci-dist.yml, /.github/workflows/desktop-release.yml
Introduce macOS arch matrix, wire matrix.archARCH/NEXU_DESKTOP_TARGET_ARCH, make artifacts/checksums/logs arch-specific, update artifact names and R2/Cloudflare upload prefixes to include /${ARCH}, add legacy arm64 compatibility uploads.
Packaging & build scripts
apps/desktop/scripts/dist-mac.mjs, apps/desktop/package.json, package.json
Compute/validate target arch from flag/env/host, reject cross-arch packaging, derive dmg-builder arch from target, ensure update channel present in build-config, prepend a Node preload for JSON extraction, pass explicit --<arch> to electron-builder, expand mac bundle dir discovery, and add arch-specific npm scripts.
Updater feed resolution
apps/desktop/main/updater/update-manager.ts, tests/desktop/update-manager.test.ts
Remove static feed map; add getMacFeedArch and getDefaultR2FeedUrl(channel, arch), make resolveUpdateFeedUrl accept optional arch, update tests to expect /channel/{arch} paths and to validate unsupported-arch errors.
CI artifact discovery & logs
/.github/.../desktop-ci-dist.yml
Use nullglob search for apps/desktop/release/mac*/Nexu.app, fail if none, select first match, and upload per-arch artifacts/logs with strict missing-artifact errors.
Electron-builder JSON handling
apps/desktop/scripts/electron-builder-json-extractor.cjs, apps/desktop/scripts/electron-builder-pnpm-json-preload.cjs, tests/desktop/electron-builder-json-extractor.test.ts
Add robust JSON extractor for polluted builder output, preload patch to override builder collector parsing, and unit tests validating extraction and error behavior.
Docs & examples
AGENTS.md, apps/desktop/.env.example, specs/current/...
Document new arch-specific dist scripts, update env guidance and local verification commands, and note arch-scoped update feeds.
Tests
tests/desktop/update-manager.test.ts, tests/desktop/electron-builder-json-extractor.test.ts
Extend update-manager tests for arch-aware feed URLs and unsupported-arch error; add tests for JSON extractor success/failure cases.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer / CI
  participant Runner as macOS Runner (arm64 / x64)
  participant Builder as dist-mac.mjs / electron-builder
  participant ArtifactStore as R2 / Cloudflare
  participant Updater as Desktop App Updater

  Dev->>Runner: Start matrix build (ARCH=arm64 or x64)
  Runner->>Builder: Run dist-mac.mjs with target ARCH
  Builder->>Builder: produce DMG/ZIP + write build-config with /{channel}/{ARCH}
  Builder->>ArtifactStore: Upload artifacts and `desktop-{ARCH}-sha256.txt` under /{channel}/{ARCH}/
  ArtifactStore-->>Builder: Return upload URLs
  Updater->>ArtifactStore: Fetch `latest-mac.yml` from /{channel}/{ARCH}/
  ArtifactStore-->>Updater: Return latest file + download URL
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • lefarcen
  • Siri-Ray

Poem

🐰 Two builds hop down the build-line track,
Arm and Intel bundled, each snug in its pack,
Feeds split by arch, artifacts neatly named,
CI hums and uploads, each prefix framed,
A carrot-sweet release — binaries intact.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add intel mac desktop packaging flow' directly and clearly describes the main change: adding support for Intel Mac desktop packaging alongside the existing ARM64 support.
Description check ✅ Passed The PR description covers all essential sections (Summary and Testing), clearly explains the motivation and implementation approach, and provides verification steps. Though it deviates from the template structure, it contains sufficient detail for reviewers to understand the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/intel-mac-packaging

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: db55e3a1ac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/scripts/dist-mac.mjs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/desktop/scripts/dist-mac.mjs (1)

343-410: ⚠️ Potential issue | 🟠 Major

Bake NEXU_DESKTOP_UPDATE_CHANNEL into build-config.json.

This manifest still omits the packaged update channel. Downstream, UpdateManager falls back to "stable" when no channel is supplied, so packaged beta/nightly apps can still poll the wrong /<channel>/<arch> feed unless the channel is embedded here.

🔧 Proposed fix
   const config = {
+    NEXU_DESKTOP_UPDATE_CHANNEL:
+      merged.NEXU_DESKTOP_UPDATE_CHANNEL ??
+      existingConfig.NEXU_DESKTOP_UPDATE_CHANNEL ??
+      "stable",
     ...((merged.NEXU_SENTRY_ENV ?? existingConfig.NEXU_SENTRY_ENV)
       ? {
           NEXU_SENTRY_ENV:
             merged.NEXU_SENTRY_ENV ?? existingConfig.NEXU_SENTRY_ENV,
         }
Based on learnings, "Packaged builds should embed `NEXU_DESKTOP_UPDATE_CHANNEL` (`stable` / `beta` / `nightly`) so the updater checks the matching feed."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/scripts/dist-mac.mjs` around lines 343 - 410, The build-config
generation omits NEXU_DESKTOP_UPDATE_CHANNEL so packaged apps may poll the wrong
feed; update the config object (the same area that sets
NEXU_DESKTOP_APP_VERSION, NEXU_DESKTOP_BUILD_SOURCE, etc.) to include a
NEXU_DESKTOP_UPDATE_CHANNEL entry that prefers
merged.NEXU_DESKTOP_UPDATE_CHANNEL, falls back to
existingConfig.NEXU_DESKTOP_UPDATE_CHANNEL, then
defaultMetadata.NEXU_DESKTOP_UPDATE_CHANNEL (or a sensible default like
"stable"), following the same conditional/ternary pattern used for other keys,
so the value is written by writeFile(configPath, JSON.stringify(config,...)).
.github/workflows/desktop-release.yml (1)

261-270: ⚠️ Potential issue | 🟡 Minor

Update release body to reflect architecture-specific checksum filenames.

The release body mentions desktop-sha256.txt but the actual checksum files are now architecture-specific (desktop-arm64-sha256.txt, desktop-x64-sha256.txt).

Proposed fix
           body: |
             Desktop release for `${{ steps.meta.outputs.desktop_version }}`.

-            Assets include signed macOS `.dmg` and `.zip` packages plus `desktop-sha256.txt`.
+            Assets include signed macOS `.dmg` and `.zip` packages plus architecture-specific SHA-256 checksums.

             The packaged `Nexu.app` bundle is notarized and stapled during the build before the archives are published.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/desktop-release.yml around lines 261 - 270, Update the
release body text to reflect that checksum filenames are architecture-specific
rather than the single `desktop-sha256.txt`; either reference the actual emitted
artifact variable `${{ steps.artifacts.outputs.checksum_file }}` (used in the
files list) or explicitly list both `desktop-arm64-sha256.txt` and
`desktop-x64-sha256.txt` in the descriptive paragraph so the body matches the
files under `apps/desktop/release-artifacts/` and the existing outputs
(`steps.artifacts.outputs.checksum_file`,
`steps.artifacts.outputs.versioned_dmg`,
`steps.artifacts.outputs.versioned_zip`, and
`steps.meta.outputs.desktop_version`).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/desktop-build.yml:
- Around line 56-64: Replace the deprecated runner value in the workflow matrix:
for the include entry currently using "runner: macos-13" paired with "arch:
x64", update the runner value to "macos-15-intel" so the matrix entry becomes
the new Intel macOS runner; ensure the other entry "runner: macos-14" with
"arch: arm64" remains unchanged and keep runs-on using ${{ matrix.runner }}.

In @.github/workflows/desktop-ci-dist.yml:
- Around line 46-54: Replace the retired macos-13 runner entry in the matrix
include with the supported macos-15-intel label so x64 builds run on an
available runner; update the matrix include block where runner: macos-13 and
arch: x64 are declared to use runner: macos-15-intel instead, keeping the arch:
x64 entry and the existing strategy/matrix configuration intact.

In @.github/workflows/desktop-release.yml:
- Around line 24-32: Update the matrix entry that currently targets "runner:
macos-13" for the x64 build to use "macos-15-intel" instead: locate the matrix
include block (the entries with "runner" and "arch") and replace the tuple where
runner is "macos-13" and arch is "x64" with runner "macos-15-intel" keeping the
arch value as "x64"; leave the runs-on: ${{ matrix.runner }} usage unchanged.

In `@apps/desktop/main/updater/update-manager.ts`:
- Around line 21-23: getMacFeedArch currently maps any non-"x64" to "arm64",
which masks typos/unknown arches; update getMacFeedArch(arch: string =
process.arch) to explicitly return "x64" when arch === "x64" and "arm64" when
arch === "arm64", and throw a descriptive Error (or assert) for any other arch
value so the updater fails fast on unexpected runtime architectures; adjust any
callers if they relied on the old fallback behavior.

---

Outside diff comments:
In @.github/workflows/desktop-release.yml:
- Around line 261-270: Update the release body text to reflect that checksum
filenames are architecture-specific rather than the single `desktop-sha256.txt`;
either reference the actual emitted artifact variable `${{
steps.artifacts.outputs.checksum_file }}` (used in the files list) or explicitly
list both `desktop-arm64-sha256.txt` and `desktop-x64-sha256.txt` in the
descriptive paragraph so the body matches the files under
`apps/desktop/release-artifacts/` and the existing outputs
(`steps.artifacts.outputs.checksum_file`,
`steps.artifacts.outputs.versioned_dmg`,
`steps.artifacts.outputs.versioned_zip`, and
`steps.meta.outputs.desktop_version`).

In `@apps/desktop/scripts/dist-mac.mjs`:
- Around line 343-410: The build-config generation omits
NEXU_DESKTOP_UPDATE_CHANNEL so packaged apps may poll the wrong feed; update the
config object (the same area that sets NEXU_DESKTOP_APP_VERSION,
NEXU_DESKTOP_BUILD_SOURCE, etc.) to include a NEXU_DESKTOP_UPDATE_CHANNEL entry
that prefers merged.NEXU_DESKTOP_UPDATE_CHANNEL, falls back to
existingConfig.NEXU_DESKTOP_UPDATE_CHANNEL, then
defaultMetadata.NEXU_DESKTOP_UPDATE_CHANNEL (or a sensible default like
"stable"), following the same conditional/ternary pattern used for other keys,
so the value is written by writeFile(configPath, JSON.stringify(config,...)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 313f03aa-055b-43d7-a62f-fe15fa7c4144

📥 Commits

Reviewing files that changed from the base of the PR and between 12d2153 and db55e3a.

📒 Files selected for processing (12)
  • .github/workflows/desktop-build.yml
  • .github/workflows/desktop-ci-dist.yml
  • .github/workflows/desktop-release.yml
  • AGENTS.md
  • apps/desktop/.env.example
  • apps/desktop/main/updater/update-manager.ts
  • apps/desktop/package.json
  • apps/desktop/scripts/dist-mac.mjs
  • package.json
  • specs/current/environment.md
  • specs/current/sentry/SENTRY.md
  • tests/desktop/update-manager.test.ts

Comment thread .github/workflows/desktop-build.yml
Comment thread .github/workflows/desktop-ci-dist.yml
Comment thread .github/workflows/desktop-release.yml
Comment thread apps/desktop/main/updater/update-manager.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d045a4e447

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/desktop-build.yml Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/desktop-build.yml:
- Line 349: The shell assignment to r2_prefix embeds a node -e snippet that uses
JS template-backticks which Bash treats as command substitution; change the node
snippet to remove backticks so the shell won't interpret them (e.g., replace the
template literal with concatenation: process.stdout.write(path + '/' +
process.env.ARCH) or escape the backticks), leaving the rest of the logic intact
so r2_prefix is computed correctly.

In `@apps/desktop/scripts/dist-mac.mjs`:
- Around line 350-353: The code copies NEXU_DESKTOP_UPDATE_CHANNEL (and
similarly NEXU_UPDATE_FEED_URL) verbatim which allows an explicit override to
bypass the arch-scoped feed; in ensureBuildConfig() ensure that when taking
merged.NEXU_DESKTOP_UPDATE_CHANNEL or existingConfig.NEXU_DESKTOP_UPDATE_CHANNEL
(and NEXU_UPDATE_FEED_URL) you enforce architecture scoping by appending
`/${arch}` if the URL/path does not already include the arch segment (use the
same arch variable used elsewhere in ensureBuildConfig/packaging logic), so
explicit overrides become architecture-specific; adjust the assignments that use
merged, existingConfig, and defaultMetadata to perform this check-and-append
behavior (same change for the other occurrence around lines 376-380).
- Around line 445-449: The current guard only checks process.arch against
targetMacArch and should also fail early on non-mac hosts; update the check
around the existing if that references process.arch and targetMacArch in
dist-mac.mjs to include process.platform !== 'darwin' (e.g. if (process.platform
!== 'darwin' || process.arch !== targetMacArch) throw new Error(...)) so the
script immediately errors on Windows/Linux hosts as well as mismatched
architectures, keeping the existing descriptive message but adding platform
context.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed409711-47c2-4212-9412-5eadf005d1d4

📥 Commits

Reviewing files that changed from the base of the PR and between db55e3a and d045a4e.

📒 Files selected for processing (6)
  • .github/workflows/desktop-build.yml
  • .github/workflows/desktop-ci-dist.yml
  • .github/workflows/desktop-release.yml
  • apps/desktop/main/updater/update-manager.ts
  • apps/desktop/scripts/dist-mac.mjs
  • tests/desktop/update-manager.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/desktop-release.yml
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/desktop/update-manager.test.ts
  • .github/workflows/desktop-ci-dist.yml
  • apps/desktop/main/updater/update-manager.ts

Comment thread .github/workflows/desktop-build.yml Outdated
Comment thread apps/desktop/scripts/dist-mac.mjs
Comment thread apps/desktop/scripts/dist-mac.mjs
Patch electron-builder's pnpm dependency-tree parsing so the Intel desktop packaging job can finish reliably. Upload DMG and ZIP artifacts from CI so PR builds are downloadable for testing.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/desktop/scripts/electron-builder-pnpm-json-preload.cjs (1)

20-22: Silent return may obscure electron-builder internal changes.

If NodeModulesCollector is not found (e.g., after an electron-builder upgrade changes internals), the patch silently does nothing and the original parsing issue will resurface without clear indication. Consider logging a warning to aid debugging.

💡 Optional: Add a debug warning
   if (!collectorPrototype) {
+    if (process.env.DEBUG) {
+      console.warn("[nexu] NodeModulesCollector prototype not found; JSON extractor patch skipped");
+    }
     return;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/scripts/electron-builder-pnpm-json-preload.cjs` around lines 20
- 22, The silent return when collectorPrototype is falsy hides failures; update
the block that checks NodeModulesCollector/collectorPrototype to emit a clear
warning instead of returning silently—e.g., log with console.warn or the
project's logger (mentioning "NodeModulesCollector not found" and suggesting an
electron-builder upgrade) and then return to preserve behavior; modify the check
around collectorPrototype so the warning is emitted before the early return to
aid debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/desktop/scripts/electron-builder-pnpm-json-preload.cjs`:
- Around line 20-22: The silent return when collectorPrototype is falsy hides
failures; update the block that checks NodeModulesCollector/collectorPrototype
to emit a clear warning instead of returning silently—e.g., log with
console.warn or the project's logger (mentioning "NodeModulesCollector not
found" and suggesting an electron-builder upgrade) and then return to preserve
behavior; modify the check around collectorPrototype so the warning is emitted
before the early return to aid debugging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24340e02-655c-4cd3-ab4d-61d30e793e04

📥 Commits

Reviewing files that changed from the base of the PR and between d045a4e and ac43e55.

📒 Files selected for processing (5)
  • .github/workflows/desktop-ci-dist.yml
  • apps/desktop/scripts/dist-mac.mjs
  • apps/desktop/scripts/electron-builder-json-extractor.cjs
  • apps/desktop/scripts/electron-builder-pnpm-json-preload.cjs
  • tests/desktop/electron-builder-json-extractor.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/desktop-ci-dist.yml
  • apps/desktop/scripts/dist-mac.mjs

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac43e55e41

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/desktop-release.yml Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f61f4c1e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/scripts/dist-mac.mjs Outdated
Comment thread .github/workflows/desktop-build.yml
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/scripts/dist-mac.mjs (1)

220-239: ⚠️ Potential issue | 🟠 Major

Avoid carrying the preload through NODE_OPTIONS — it will fail if the checkout path contains spaces.

NODE_OPTIONS is parsed by Node.js itself (not the shell), and it splits on unescaped spaces. If electronBuilderPreload is an absolute path from resolve() and the checkout directory contains spaces, --require=/path with spaces/... will be split into separate arguments before electron-builder starts, causing the preload to fail. Move the preload to the process.execPath command line as a direct --require argument and leave NODE_OPTIONS only for inherited options like NODE_ENV.

Proposed fix
   const targetOpenFiles = process.env.NEXU_DESKTOP_MAX_OPEN_FILES ?? "8192";
   const baseEnv = options.env ?? process.env;
   const existingNodeOptions = baseEnv.NODE_OPTIONS?.trim();
-  const nodeOptions = [
-    existingNodeOptions,
-    `--require=${electronBuilderPreload}`,
-  ]
-    .filter(Boolean)
-    .join(" ");
   const command = [
     `target=${shellEscape(targetOpenFiles)}`,
-    `export NODE_OPTIONS=${shellEscape(nodeOptions)}`,
+    ...(existingNodeOptions
+      ? [`export NODE_OPTIONS=${shellEscape(existingNodeOptions)}`]
+      : []),
     'hard_limit=$(ulimit -Hn 2>/dev/null || printf %s "$target")',
     'if [ "$hard_limit" != "unlimited" ] && [ "$hard_limit" -lt "$target" ]; then target="$hard_limit"; fi',
     'ulimit -n "$target" 2>/dev/null || true',
-    `exec ${shellEscape(process.execPath)} ${shellEscape(electronBuilderCli)} ${args.map(shellEscape).join(" ")}`,
+    `exec ${shellEscape(process.execPath)} --require ${shellEscape(electronBuilderPreload)} ${shellEscape(electronBuilderCli)} ${args.map(shellEscape).join(" ")}`,
   ].join("; ");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/scripts/dist-mac.mjs` around lines 220 - 239, The NODE_OPTIONS
construction currently injects --require=${electronBuilderPreload} into
nodeOptions which will be parsed by Node and broken if the resolved path
contains spaces; change the logic so nodeOptions only includes the inherited
existingNodeOptions (leave NODE_OPTIONS for things like NODE_ENV) and remove
electronBuilderPreload from nodeOptions, then add a direct --require argument
referencing electronBuilderPreload to the exec command array so the preload is
passed on process.execPath invocation (update the variables nodeOptions, the
NODE_OPTIONS export string, and the final exec element that builds the command
with process.execPath and electronBuilderCli to include
--require=${electronBuilderPreload} using shellEscape).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/desktop-build.yml:
- Around line 358-382: The R2 object keys in upload_prefix() (and the similar
block later) always prepend "${prefix}/..." which produces a leading slash when
channel_prefix is empty; change the key construction to omit the extra slash for
an empty prefix (e.g., build the target key as either "${prefix}/${filename}"
only when prefix is non-empty, otherwise just "${filename}"), and apply the same
conditional logic to every npx wrangler r2 object put invocation in
upload_prefix() and the matching upload block so public URLs and R2 keys remain
consistent.

---

Outside diff comments:
In `@apps/desktop/scripts/dist-mac.mjs`:
- Around line 220-239: The NODE_OPTIONS construction currently injects
--require=${electronBuilderPreload} into nodeOptions which will be parsed by
Node and broken if the resolved path contains spaces; change the logic so
nodeOptions only includes the inherited existingNodeOptions (leave NODE_OPTIONS
for things like NODE_ENV) and remove electronBuilderPreload from nodeOptions,
then add a direct --require argument referencing electronBuilderPreload to the
exec command array so the preload is passed on process.execPath invocation
(update the variables nodeOptions, the NODE_OPTIONS export string, and the final
exec element that builds the command with process.execPath and
electronBuilderCli to include --require=${electronBuilderPreload} using
shellEscape).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7aa597b6-4d15-43e4-a43e-fa76e544eaee

📥 Commits

Reviewing files that changed from the base of the PR and between ac43e55 and 2f61f4c.

📒 Files selected for processing (5)
  • .github/workflows/desktop-build.yml
  • .github/workflows/desktop-ci-dist.yml
  • .github/workflows/desktop-release.yml
  • apps/desktop/scripts/dist-mac.mjs
  • apps/desktop/scripts/electron-builder-pnpm-json-preload.cjs
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/desktop-release.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/desktop-ci-dist.yml
  • apps/desktop/scripts/electron-builder-pnpm-json-preload.cjs

Comment thread .github/workflows/desktop-build.yml
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/scripts/dist-mac.mjs (1)

375-393: ⚠️ Potential issue | 🟠 Major

Validate the embedded update channel before writing it.

These assignments still accept "" and arbitrary typos verbatim because they only use nullish coalescing. That can silently package a build with an invalid channel and send the updater to the wrong feed; normalize once and throw unless the final value is stable, beta, or nightly.

🛠️ Suggested fix
+function resolveUpdateChannel(rawChannel) {
+  if (rawChannel == null) {
+    return "stable";
+  }
+
+  const channel = rawChannel.trim();
+  if (channel === "stable" || channel === "beta" || channel === "nightly") {
+    return channel;
+  }
+
+  throw new Error(
+    `[dist:mac] Unsupported update channel "${rawChannel}". Expected "stable", "beta", or "nightly".`,
+  );
+}
+
 async function ensureBuildConfig() {
   const configPath = resolve(electronRoot, "build-config.json");
@@
   const merged = { ...fileEnv, ...process.env };
   const gitBranch = getGitValue(["rev-parse", "--abbrev-ref", "HEAD"]);
   const gitCommit = getGitValue(["rev-parse", "HEAD"]);
+  const updateChannel = resolveUpdateChannel(
+    merged.NEXU_DESKTOP_UPDATE_CHANNEL ??
+      existingConfig.NEXU_DESKTOP_UPDATE_CHANNEL,
+  );
 
   const defaultMetadata = {
-    NEXU_DESKTOP_UPDATE_CHANNEL:
-      merged.NEXU_DESKTOP_UPDATE_CHANNEL ??
-      existingConfig.NEXU_DESKTOP_UPDATE_CHANNEL ??
-      "stable",
+    NEXU_DESKTOP_UPDATE_CHANNEL: updateChannel,
@@
   const config = {
-    NEXU_DESKTOP_UPDATE_CHANNEL:
-      merged.NEXU_DESKTOP_UPDATE_CHANNEL ??
-      existingConfig.NEXU_DESKTOP_UPDATE_CHANNEL ??
-      defaultMetadata.NEXU_DESKTOP_UPDATE_CHANNEL,
+    NEXU_DESKTOP_UPDATE_CHANNEL: updateChannel,

Based on learnings, packaged builds should embed NEXU_DESKTOP_UPDATE_CHANNEL (stable / beta / nightly) so the updater checks the matching feed.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/scripts/dist-mac.mjs`:
- Around line 62-83: The helper ensureArchScopedFeedUrl currently appends
targetMacArch and can produce double-suffixes like ".../x64/arm64"; update it to
detect and replace any existing trailing arch segment (e.g. "x64" or "arm64")
with targetMacArch instead of appending. In the URL branch (inside
ensureArchScopedFeedUrl) inspect url.pathname, strip a final "/<arch>" if
present (match known mac arches) then set url.pathname to the cleaned path plus
`/${targetMacArch}` before returning; do the same in the catch branch by
normalizing trimmedFeedUrl to remove any trailing "/<arch>" and then append
`/${targetMacArch}` only once. Ensure you reference the function name
ensureArchScopedFeedUrl and the variable targetMacArch when making changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 976b6ea0-9bbc-4532-af3f-cd21c9514fad

📥 Commits

Reviewing files that changed from the base of the PR and between 2f61f4c and e44e222.

📒 Files selected for processing (2)
  • .github/workflows/desktop-build.yml
  • apps/desktop/scripts/dist-mac.mjs
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/desktop-build.yml

Comment thread apps/desktop/scripts/dist-mac.mjs
@mrcfps mrcfps merged commit d069d50 into main Mar 26, 2026
8 checks passed
@mrcfps mrcfps deleted the feat/intel-mac-packaging branch March 26, 2026 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Intel (x64) macOS build

2 participants