Skip to content

feat(desktop): add Windows build to CI/CD pipeline#1617

Closed
Sandman-Ren wants to merge 14 commits into
superset-sh:mainfrom
Sandman-Ren:feat/windows-desktop-build
Closed

feat(desktop): add Windows build to CI/CD pipeline#1617
Sandman-Ren wants to merge 14 commits into
superset-sh:mainfrom
Sandman-Ren:feat/windows-desktop-build

Conversation

@Sandman-Ren
Copy link
Copy Markdown

@Sandman-Ren Sandman-Ren commented Feb 20, 2026

Hey Superset team,

I develop natively on Windows and would like to have a Windows installable for the purpose. I spent some time with Claude Code today and worked out a working installable on Windows and added a Windows build pipeline to the Actions workflows.

I'd be really happy to have Windows support for Superset. Please take a look at the PR and leave any feedback and I'd be happy to make changes as needed.

System Information

- OS: Windows 11 Pro
- Build: 10.0.26100
- Architecture: x64

Summary

  • Add a build-windows job to the desktop CI/CD pipeline (build-desktop.yml) targeting windows-latest with NSIS installer output
  • Update release workflows (release-desktop.yml, release-desktop-canary.yml) to handle Windows .exe artifacts and auto-update manifests
  • Fix cross-platform compatibility issues uncovered during Windows CI builds:
    • Replace bash postinstall.sh with cross-platform scripts/postinstall.mjs
    • Handle Windows junctions in copy-native-modules.ts (rmSync with { recursive: true, force: true })
    • Correct Windows auto-update manifest filename (latest.yml, not *-win.yml)
  • Fix black screen in packaged Windows builds:
    • Strip Vite's crossorigin attribute from script/link tags (incompatible with Electron's ASAR file:// handler)
    • Use || instead of ?? in defineEnv so empty strings from unresolved CI secrets fall back to defaults
    • Disable GPU hardware acceleration on Windows (same as existing Linux behavior)
  • Add renderer console message forwarding to main process stdout for production debugging

Test plan

  • Windows build succeeds on windows-latest CI runner
  • .exe installer produced and installs correctly
  • App launches and renders UI correctly after installation
  • Linux build still succeeds
  • macOS build succeeds (currently fails on fork due to missing code signing secrets — expected to work on upstream)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Windows desktop application build and release support with automated packaging and artifact management.
  • Bug Fixes

    • Disabled hardware acceleration on Windows to resolve GPU driver compatibility issues.
    • Fixed cross-origin attribute handling on Windows for improved network request compatibility.
  • Chores

    • Improved installation process for cross-platform compatibility.

Add a `build-windows` job to the reusable build workflow targeting
`windows-latest` with NSIS installer output. Update both stable and
canary release workflows to handle `.exe` artifacts and `*-win.yml`
auto-update manifests alongside existing macOS and Linux artifacts.
The root postinstall script is a bash .sh file that Bun on Windows
cannot execute directly. Use --ignore-scripts and manually run the
desktop native deps install step instead.
Replace the bash-only postinstall.sh with a Node script
(postinstall.mjs) so bun install works on Windows without needing
--ignore-scripts. The script does the same two things: runs sherif
for workspace validation and installs native desktop deps.
Bun creates junctions instead of symlinks on Windows. rmSync()
without recursive/force flags fails with EFAULT on junctions.
electron-builder names the Windows auto-update manifest latest.yml
(no -win suffix), unlike macOS (latest-mac.yml) and Linux
(latest-linux.yml). Update verify, upload, and release steps to
match the actual filename.
… black screen

GPU driver incompatibilities with Chromium's compositor can cause a completely
black window on Windows. Extend the existing Linux GPU disable to Windows.
Also add missing NEXT_PUBLIC_ELECTRIC_URL env var to macOS and Windows builds.
Vite adds `crossorigin` to script/link tags by default. Electron's ASAR
file:// handler doesn't support CORS, so this causes the main JS bundle
and CSS to silently fail to load, resulting in a black screen when the
app is packaged. Add a post-order Vite plugin that strips the attribute.
- Forward renderer console messages to main process stdout
- Add step-by-step logging to renderer entry point
- Auto-open DevTools in production (temporary)
…back

GitHub Actions resolves unset secrets to empty strings. The nullish
coalescing operator (??) doesn't treat "" as missing, so env var
defaults like "https://api.superset.sh" never kick in. This causes
Zod URL validation to fail on empty strings, crashing the renderer
before React mounts (black screen).

Also removes temporary debug logging added in previous commit.
Copilot AI review requested due to automatic review settings February 20, 2026 00:52
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 20, 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

This pull request adds Windows support to the desktop application build pipeline, including GitHub Actions workflow jobs for building and releasing Windows executables, Windows-specific Vite configuration to strip cross-origin attributes, platform-aware native module handling, hardware acceleration management, renderer console debugging, and a cross-platform postinstall script replacing the existing shell-based setup.

Changes

Cohort / File(s) Summary
GitHub Actions Workflows
.github/workflows/build-desktop.yml, .github/workflows/release-desktop-canary.yml, .github/workflows/release-desktop.yml
Added Windows build job with checkout, Bun setup, dependency caching, electron-vite compilation, Windows packaging, and artifact uploads. Propagated NEXT_PUBLIC_ELECTRIC_URL environment variable to build steps. Added Windows .exe artifact handling for canary (Superset-Canary-${arch}.exe) and stable (Superset-${arch}.exe) releases alongside existing manifest uploads.
Electron Vite Configuration
apps/desktop/vite/helpers.ts, apps/desktop/electron.vite.config.ts
Added stripCrossOriginPlugin that removes crossorigin attributes from script/link tags during renderer build on Windows (win32) via regex replacement. Plugin imported and integrated into electron.vite.config.ts renderer plugins array.
Platform-Specific Setup
apps/desktop/scripts/copy-native-modules.ts, apps/desktop/src/lib/electron-app/factories/app/setup.ts
Modified native module copying on Windows to use rmSync with recursive flag for junction handling. Extended hardware acceleration disabling to Windows alongside Linux with comments explaining GPU driver incompatibilities; macOS handling remains separate.
Windows Debugging & Installation
apps/desktop/src/main/windows/main.ts, scripts/postinstall.mjs, package.json
Added Windows-only renderer console message forwarding to main process stdout with level-based filtering and [renderer:] formatting for debugging. Replaced bash postinstall script with cross-platform Node.js module for Bun installation on Windows, macOS, and Linux with recursion guards via SUPERSET_POSTINSTALL_RUNNING environment variable.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hops through code with delight so bright,
Windows support now shines in sight!
Cross-origin stripped, hardware blessed,
Desktop dreams from east to west!
From Linux roots to Windows' call,
The rabbit built a build for all! 🏗️

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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(desktop): add Windows build to CI/CD pipeline' accurately and concisely summarizes the main change: adding Windows build support to the desktop CI/CD workflows.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering system information, detailed summary of changes, explicit test results, and addressing feedback. It closely follows the template structure with clear sections for description, type of change, and testing details.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds Windows support to the desktop application's CI/CD pipeline, addressing platform-specific build issues and fixing renderer loading problems that caused black screens in packaged builds.

Changes:

  • Added Windows x64 build job to CI/CD pipeline with NSIS installer output and auto-update manifest handling
  • Replaced bash-only postinstall.sh with cross-platform scripts/postinstall.mjs for Windows compatibility
  • Fixed black screen issues by stripping Vite's crossorigin attribute, using || operator for empty string handling, and disabling GPU acceleration on Windows
  • Enhanced Windows compatibility in native module copying (junction handling) and added renderer console forwarding for production debugging

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
File Description
scripts/postinstall.mjs New cross-platform postinstall script replacing bash-only version, with recursion guard and workspace validation
package.json Updated postinstall command to use Node.js instead of bash
apps/desktop/vite/helpers.ts Added stripCrossOriginPlugin() to fix ASAR loading issues and changed defineEnv() to use || for empty string fallback
apps/desktop/electron.vite.config.ts Integrated stripCrossOriginPlugin into renderer build pipeline
apps/desktop/src/main/windows/main.ts Added console message forwarding from renderer to main process for production debugging
apps/desktop/src/lib/electron-app/factories/app/setup.ts Extended GPU hardware acceleration disabling to Windows (previously Linux-only)
apps/desktop/scripts/copy-native-modules.ts Added recursive: true, force: true to rmSync() for Windows junction compatibility
.github/workflows/build-desktop.yml Added complete Windows build job with installer verification and artifact uploads
.github/workflows/release-desktop.yml Added Windows .exe artifact handling and update manifest logic
.github/workflows/release-desktop-canary.yml Added Windows canary build artifact handling

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@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 (6)
apps/desktop/src/lib/electron-app/factories/app/setup.ts (1)

73-76: Consider if statement for side-effect call.

Using && for a void side effect is pre-existing here, but an if block communicates intent more clearly for readers.

♻️ Optional readability improvement
-// Disable GPU hardware acceleration on Linux and Windows to prevent black/blank
-// screens caused by GPU driver incompatibilities with Chromium's compositor.
-// macOS uses a separate set of GPU workarounds (see below and MainWindow).
-(PLATFORM.IS_LINUX || PLATFORM.IS_WINDOWS) && app.disableHardwareAcceleration();
+// Disable GPU hardware acceleration on Linux and Windows to prevent black/blank
+// screens caused by GPU driver incompatibilities with Chromium's compositor.
+// macOS uses a separate set of GPU workarounds (see below and MainWindow).
+if (PLATFORM.IS_LINUX || PLATFORM.IS_WINDOWS) {
+	app.disableHardwareAcceleration();
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/electron-app/factories/app/setup.ts` around lines 73 -
76, Replace the terse logical-and side-effect with an explicit conditional:
check PLATFORM.IS_LINUX || PLATFORM.IS_WINDOWS in an if statement and call
app.disableHardwareAcceleration() inside its block (referencing
PLATFORM.IS_LINUX, PLATFORM.IS_WINDOWS and app.disableHardwareAcceleration) so
the intent is clear and readable.
apps/desktop/src/main/windows/main.ts (1)

225-229: Preserve renderer log severity — use console.warn/console.error for levels ≥ 2.

Every renderer log level (verbose, info, warning, error) is currently forwarded via console.log. Errors won't surface as red entries in the terminal, and any log-level filtering in the main process or external log aggregators will miss them.

♻️ Proposed fix
-	window.webContents.on("console-message", (_event, level, message, line, sourceId) => {
-		const levelStr = ["verbose", "info", "warning", "error"][level] ?? "unknown";
-		const source = sourceId ? ` (${sourceId}:${line})` : "";
-		console.log(`[renderer:${levelStr}] ${message}${source}`);
-	});
+	window.webContents.on("console-message", (_event, level, message, line, sourceId) => {
+		const levelStr = ["verbose", "info", "warning", "error"][level] ?? "unknown";
+		const source = sourceId ? ` (${sourceId}:${line})` : "";
+		const formatted = `[renderer:${levelStr}] ${message}${source}`;
+		if (level === 3) console.error(formatted);
+		else if (level === 2) console.warn(formatted);
+		else console.log(formatted);
+	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/windows/main.ts` around lines 225 - 229, The renderer
console handler in window.webContents.on("console-message") currently routes all
messages through console.log; update the handler to preserve severity by
switching on the numeric level (0: verbose, 1: info, 2: warning, 3: error) and
call console.log for levels 0–1, console.warn for level 2, and console.error for
level 3, keeping the existing levelStr and source formatting logic so the
message string construction (levelStr, message, source) is reused but emitted
with the appropriate console method.
scripts/postinstall.mjs (1)

22-28: Minor: SUPERSET_POSTINSTALL_RUNNING in run() env is redundant.

process.env.SUPERSET_POSTINSTALL_RUNNING is already set on line 20, so spreading { ...process.env, SUPERSET_POSTINSTALL_RUNNING: "1" } duplicates it. It's harmless and defensive, but worth knowing it adds no extra protection.

♻️ Simplified env spread
 function run(cmd) {
 	execSync(cmd, {
 		stdio: "inherit",
-		env: { ...process.env, SUPERSET_POSTINSTALL_RUNNING: "1" },
+		env: process.env,
 	});
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/postinstall.mjs` around lines 22 - 28, The run function currently
passes env: { ...process.env, SUPERSET_POSTINSTALL_RUNNING: "1" } which
redundantly duplicates the env var already set on process.env; update the
run(cmd) implementation (the run function) to remove the spread and either omit
the env option entirely (let child inherit process.env) or pass env: process.env
so it relies on the existing SUPERSET_POSTINSTALL_RUNNING value instead of
recreating it.
apps/desktop/vite/helpers.ts (1)

23-25: defineEnv("") with no fallback now produces undefined in the define map — verify Vite handles it gracefully.

JSON.stringify(undefined) returns the JS value undefined (not the string "undefined"), which violates the declared string return type. When both value is "" and no fallback is given, the define entry becomes undefined. Vite's documented contract for define values is string; behaviour with undefined is implementation-defined.

This is a pre-existing gap (same for value = undefined), but || widens the window to empty-string inputs. CI passes, so Vite is tolerating it, but consider making the return type explicit:

♻️ Type-safe alternative
 export function defineEnv(
 	value: string | undefined,
 	fallback?: string,
-): string {
-	// Use || instead of ?? so empty strings (from unresolved GitHub Actions
-	// secrets) also fall through to the default value.
-	return JSON.stringify(value || fallback);
+): string | undefined {
+	// Use || instead of ?? so empty strings (from unresolved GitHub Actions
+	// secrets) also fall through to the default value.
+	const resolved = value || fallback;
+	return resolved !== undefined ? JSON.stringify(resolved) : undefined;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/vite/helpers.ts` around lines 23 - 25, The current defineEnv
helper can return the JS value undefined (via JSON.stringify(undefined)) when
both value is "" or undefined and fallback is omitted; update defineEnv in
helpers.ts so it always returns a JSON-stringified string: choose value when it
is a non-empty string, otherwise use fallback if provided, and if neither is
present fall back to the empty string, then JSON.stringify that resolved string
before returning. Ensure you reference the defineEnv function and replace the
current return logic so the function's declared string return type is always
satisfied.
.github/workflows/build-desktop.yml (2)

266-280: Version suffix step: minor shell-injection surface via inputs.version_suffix.

The ${{ inputs.version_suffix }} expression on line 272 is interpolated directly into the bash script. Since this is a workflow_call input (not user-controlled workflow_dispatch freetext from an external fork), the risk is low — callers are trusted internal workflows. However, if this workflow is ever exposed to less-trusted callers, a crafted suffix could break out of the string assignment.

The macOS and Linux jobs have the same pattern (lines 69, 175), so this is pre-existing. Just flagging for awareness — if you ever open this to workflow_dispatch, sanitize or quote the input.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-desktop.yml around lines 266 - 280, The step
interpolates `${{ inputs.version_suffix }}` directly into the bash script which
can allow shell-injection if untrusted; change the approach to avoid direct
interpolation by passing the suffix via a safe environment variable or by
properly quoting/sanitizing it before use: e.g., set an env var like
VERSION_SUFFIX from the workflow input and use bash variable expansion (or
printf %q) to build NEW_VERSION, or have the Node snippet read
process.env.VERSION_SUFFIX instead of embedding '$NEW_VERSION' into the inline
script (update references to CURRENT_VERSION, NEW_VERSION, and the inline node
-e writer accordingly) so arbitrary characters cannot break out of the command
string.

333-341: Windows manifest upload uses a broader glob than macOS/Linux — consider tightening.

macOS uploads *-mac.yml (line 140) and Linux uploads *-linux.yml (line 235), both targeting the specific auto-update manifest. The Windows upload uses *.yml with only builder-debug.yml excluded. This could accidentally capture other electron-builder output files (e.g., builder-effective-config.yml).

Since the verify step on line 320 already confirms latest.yml exists, you could target it directly:

🔧 Proposed tighter glob for Windows manifest
       - name: Upload Windows auto-update manifest
         uses: actions/upload-artifact@v4
         with:
           name: ${{ inputs.artifact_prefix }}-win-update-manifest
-          path: |
-            apps/desktop/release/*.yml
-            !apps/desktop/release/builder-debug.yml
+          path: apps/desktop/release/latest.yml
           retention-days: ${{ inputs.artifact_retention_days }}
           if-no-files-found: error
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-desktop.yml around lines 333 - 341, The Windows
artifact upload step ("Upload Windows auto-update manifest") currently uses a
broad glob (apps/desktop/release/*.yml) which can pick up unrelated
electron-builder outputs; change the with.path to target the exact auto-update
manifest confirmed earlier (apps/desktop/release/latest.yml) instead of the
glob, remove the unnecessary exclusion of builder-debug.yml, and keep the
existing with.name (using inputs.artifact_prefix) and with.retention-days
(inputs.artifact_retention_days) and if-no-files-found behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/build-desktop.yml:
- Around line 266-280: The step interpolates `${{ inputs.version_suffix }}`
directly into the bash script which can allow shell-injection if untrusted;
change the approach to avoid direct interpolation by passing the suffix via a
safe environment variable or by properly quoting/sanitizing it before use: e.g.,
set an env var like VERSION_SUFFIX from the workflow input and use bash variable
expansion (or printf %q) to build NEW_VERSION, or have the Node snippet read
process.env.VERSION_SUFFIX instead of embedding '$NEW_VERSION' into the inline
script (update references to CURRENT_VERSION, NEW_VERSION, and the inline node
-e writer accordingly) so arbitrary characters cannot break out of the command
string.
- Around line 333-341: The Windows artifact upload step ("Upload Windows
auto-update manifest") currently uses a broad glob (apps/desktop/release/*.yml)
which can pick up unrelated electron-builder outputs; change the with.path to
target the exact auto-update manifest confirmed earlier
(apps/desktop/release/latest.yml) instead of the glob, remove the unnecessary
exclusion of builder-debug.yml, and keep the existing with.name (using
inputs.artifact_prefix) and with.retention-days (inputs.artifact_retention_days)
and if-no-files-found behavior unchanged.

In `@apps/desktop/src/lib/electron-app/factories/app/setup.ts`:
- Around line 73-76: Replace the terse logical-and side-effect with an explicit
conditional: check PLATFORM.IS_LINUX || PLATFORM.IS_WINDOWS in an if statement
and call app.disableHardwareAcceleration() inside its block (referencing
PLATFORM.IS_LINUX, PLATFORM.IS_WINDOWS and app.disableHardwareAcceleration) so
the intent is clear and readable.

In `@apps/desktop/src/main/windows/main.ts`:
- Around line 225-229: The renderer console handler in
window.webContents.on("console-message") currently routes all messages through
console.log; update the handler to preserve severity by switching on the numeric
level (0: verbose, 1: info, 2: warning, 3: error) and call console.log for
levels 0–1, console.warn for level 2, and console.error for level 3, keeping the
existing levelStr and source formatting logic so the message string construction
(levelStr, message, source) is reused but emitted with the appropriate console
method.

In `@apps/desktop/vite/helpers.ts`:
- Around line 23-25: The current defineEnv helper can return the JS value
undefined (via JSON.stringify(undefined)) when both value is "" or undefined and
fallback is omitted; update defineEnv in helpers.ts so it always returns a
JSON-stringified string: choose value when it is a non-empty string, otherwise
use fallback if provided, and if neither is present fall back to the empty
string, then JSON.stringify that resolved string before returning. Ensure you
reference the defineEnv function and replace the current return logic so the
function's declared string return type is always satisfied.

In `@scripts/postinstall.mjs`:
- Around line 22-28: The run function currently passes env: { ...process.env,
SUPERSET_POSTINSTALL_RUNNING: "1" } which redundantly duplicates the env var
already set on process.env; update the run(cmd) implementation (the run
function) to remove the spread and either omit the env option entirely (let
child inherit process.env) or pass env: process.env so it relies on the existing
SUPERSET_POSTINSTALL_RUNNING value instead of recreating it.

@PwccaCode
Copy link
Copy Markdown

updates?

@Sandman-Ren
Copy link
Copy Markdown
Author

Heya, I'll circle back in a day or two to get to the comments, in the mean time I wish I could get some feedback from the devs to see if they'd like to include Windows support.

Copy link
Copy Markdown
Contributor

@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/electron.vite.config.ts (1)

18-18: Regex in stripCrossOriginPlugin is fragile for valued crossorigin attributes

The implementation at apps/desktop/vite/helpers.ts lines 77–80 uses:

html.replace(/ crossorigin/g, "")

This is safe for Vite's current output, which injects crossorigin as a boolean attribute on <script type="module"> and <link rel="modulepreload"> tags. However, if the HTML template (or any plugin) ever emits a valued form such as crossorigin="anonymous", the replacement leaves a dangling ="anonymous" token and produces malformed HTML.

Consider tightening the regex to also consume any optional value:

♻️ Suggested fix in apps/desktop/vite/helpers.ts
-				return html.replace(/ crossorigin/g, "");
+				return html.replace(/ crossorigin(?:="[^"]*")?/g, "");

Also applies to: 220-220

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/electron.vite.config.ts` at line 18, stripCrossOriginPlugin
currently removes only the literal " crossorigin" token which breaks when an
attribute has a value (e.g. crossorigin="anonymous"); update the plugin's HTML
replacement to remove the entire attribute including any optional value and its
preceding whitespace by using a regex that matches the attribute plus optional
=value (supporting double-quoted, single-quoted, or unquoted values) and replace
that whole match, and apply the same fix to the other occurrence of the same
replacement in the helper so both places remove the full crossorigin attribute.
🤖 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/electron.vite.config.ts`:
- Line 18: stripCrossOriginPlugin currently removes only the literal "
crossorigin" token which breaks when an attribute has a value (e.g.
crossorigin="anonymous"); update the plugin's HTML replacement to remove the
entire attribute including any optional value and its preceding whitespace by
using a regex that matches the attribute plus optional =value (supporting
double-quoted, single-quoted, or unquoted values) and replace that whole match,
and apply the same fix to the other occurrence of the same replacement in the
helper so both places remove the full crossorigin attribute.

- Delete orphaned scripts/postinstall.sh (replaced by postinstall.mjs)
- Filter console-message forwarder to warnings/errors only
- Use console.warn/console.error to preserve log severity
- Simplify postinstall.mjs run() by removing redundant env spread
- Make defineEnv return type-safe when both args are undefined
- Tighten Windows manifest upload glob to latest.yml
@Sandman-Ren
Copy link
Copy Markdown
Author

Addressed the review feedback in e0071d4. Here's a summary of what was changed and what was intentionally skipped:

Changes made

  • Delete orphaned scripts/postinstall.sh — replaced by postinstall.mjs, no longer referenced
  • Filter console-message forwarder to warnings/errors only (main.ts) — reduces unnecessary I/O in production. Also uses console.warn/console.error to preserve log severity (CodeRabbit nitpick Bump lucide-react from 0.468.0 to 0.546.0 #2)
  • Simplify postinstall.mjs run() env — removed redundant { ...process.env, SUPERSET_POSTINSTALL_RUNNING: "1" } spread since process.env is already mutated on line 20 (CodeRabbit nitpick Bump react from 18.3.1 to 19.2.0 #3)
  • Type-safe defineEnv (helpers.ts) — return type changed to string | undefined to avoid JSON.stringify(undefined) when both args are empty/undefined (CodeRabbit nitpick Bump tailwind-merge from 2.6.0 to 3.3.1 #4)
  • Tighter Windows manifest glob (build-desktop.yml) — targets apps/desktop/release/latest.yml directly instead of broad *.yml glob, matching the verify step (CodeRabbit nitpick Feat/tabs #6)

Intentionally skipped

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (4)
.github/workflows/build-desktop.yml (3)

305-308: Windows installer will be unsigned — SmartScreen warning on every install.

Unlike the macOS job, which passes code-signing credentials via CSC_LINK / CSC_KEY_PASSWORD, this step has no signing env vars. electron-builder supports Windows code signing via WIN_CSC_LINK / WIN_CSC_KEY_PASSWORD (PFX certificate) or the WINDOWS_CERTIFICATE_THUMBPRINT + Azure Trusted Signing approach. Without signing, every user who installs the .exe will encounter a Windows Defender SmartScreen "Unknown Publisher" dialog.

If signing is intentionally deferred, document it and consider adding a #TODO: add WIN_CSC_LINK comment. If secrets already exist (e.g., re-using the same PFX as the macOS CSC_LINK), they can be wired in directly:

🔑 Proposed env block for Windows signing
      - name: Build Electron app
        shell: bash
        working-directory: apps/desktop
+       env:
+         WIN_CSC_LINK: ${{ secrets.WIN_CERTIFICATE }}
+         WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CERTIFICATE_PASSWORD }}
        run: bun run package -- --publish never --config ${{ inputs.electron_builder_config }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-desktop.yml around lines 305 - 308, The Windows
build step "Build Electron app" currently omits code-signing env vars, causing
unsigned installers and SmartScreen warnings; fix by adding Windows signing
environment variables to that step (e.g., set WIN_CSC_LINK and
WIN_CSC_KEY_PASSWORD for a PFX or set WINDOWS_CERTIFICATE_THUMBPRINT for Azure
Trusted Signing) or, if signing is intentionally deferred, add a clear TODO
comment and documentation in the step indicating signing is not configured;
update the workflow step that runs "bun run package" in the Build Electron app
job to export the chosen env vars (or a TODO) so electron-builder can sign the
.exe.

239-339: High duplication across all three build jobs — consider a reusable composite action.

The new Windows job replicates the version-suffix mutation step, the 10-variable compile:app env block, and the build/verify/upload structure almost verbatim from the Linux job. With all three jobs now aligned, this is a good time to consider extracting the shared logic into a composite action (e.g., .github/actions/build-desktop-platform/action.yml) parameterised by os, arch, and platform-specific signing secrets. This would reduce the workflow from ~340 lines to roughly ~100 lines and ensure future env-var additions (like NEXT_PUBLIC_ELECTRIC_URL in this PR) don't need to be applied in three places.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-desktop.yml around lines 239 - 339, The Windows job
duplicates the same version mutation, compile env block, build/verify/upload
steps found in the other platform jobs; refactor by creating a reusable
composite action (e.g., .github/actions/build-desktop-platform/action.yml) that
encapsulates the steps "Set version suffix", "Compile app with electron-vite",
"Build Electron app", "Verify Windows installer + update manifest", "Upload EXE
artifact", and "Upload Windows auto-update manifest"; parameterize the action
with inputs for version_suffix, electron_builder_config, artifact_prefix,
artifact_retention_days, os/arch, and any platform-specific signing secrets or
env vars (like NEXT_PUBLIC_* and SENTRY_*), then replace the platform-specific
jobs (including build-windows) to call the composite action with appropriate
inputs to remove duplication.

253-260: Cache key based on github.sha is ineffective — applies to all three jobs.

The primary key ${{ runner.os }}-bun-${{ github.sha }} changes on every commit, so it almost never produces an exact hit. Every run falls back to the restore-keys prefix, writes a new cache entry, and accumulates stale entries over time. A lockfile-based key would only invalidate when dependencies actually change.

This pattern is shared by all three build jobs; swapping the key in one place fixes all of them:

♻️ Proposed cache key fix (apply to macOS, Linux, and Windows jobs)
-         key: ${{ runner.os }}-bun-${{ github.sha }}
+         key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
          restore-keys: |
            ${{ runner.os }}-bun-
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-desktop.yml around lines 253 - 260, The cache step
"Cache dependencies" currently uses the per-commit key `${{ runner.os }}-bun-${{
github.sha }}` which invalidates the cache every commit; replace that key with a
lockfile-based key (e.g., hash of bun.lockb or your package lockfile) so the
cache only changes when dependencies change (update the value used in the `key`
field in the "Cache dependencies" step and do the same change for the other two
build jobs), and keep a broad `restore-keys` prefix like `${{ runner.os }}-bun-`
so partial restores still work.
apps/desktop/vite/helpers.ts (1)

73-83: Consider a more defensive regex for future Vite output compatibility.

Vite's standard production build emits crossorigin as a valueless boolean attribute (e.g., <script type="module" crossorigin src="..."> and <link rel="modulepreload" crossorigin href="...">), so the current regex works correctly for all known Vite output. However, if a future Vite version emits crossorigin="anonymous" (which is semantically equivalent), the regex would leave an orphaned ="anonymous" token, producing invalid HTML.

♻️ Proposed defensive regex
-			return html.replace(/ crossorigin/g, "");
+			return html.replace(/ crossorigin(?:="[^"]*")?/g, "");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/vite/helpers.ts` around lines 73 - 83, The current
stripCrossOriginPlugin.handler uses html.replace(/ crossorigin/g, "") which will
leave orphaned =... tokens if Vite emits crossorigin="anonymous"; update the
regex in stripCrossOriginPlugin (inside transformIndexHtml.handler) to
defensively remove the attribute and any optional value and surrounding
whitespace (use a pattern that matches optional =value with
single/double/unquoted values and use the global flag) so it strips both
valueless crossorigin and crossorigin="..." occurrences without leaving stray
tokens.
🤖 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/vite/helpers.ts`:
- Around line 22-27: The defineEnv helper now returns string | undefined but
callers assign it directly into Vite's define object, causing undefined entries
to violate Vite's Record<string,string>; update the call sites that use
defineEnv (e.g., the entries for "process.env.SENTRY_DSN_DESKTOP",
"process.env.NEXT_PUBLIC_POSTHOG_KEY", "process.env.DESKTOP_VITE_PORT") to
either supply a fallback when calling defineEnv or filter out undefined values
before merging into define (for example compute const val = defineEnv(...); then
spread ...(val !== undefined && { "process.env.X": val }) so only string values
are included).

---

Nitpick comments:
In @.github/workflows/build-desktop.yml:
- Around line 305-308: The Windows build step "Build Electron app" currently
omits code-signing env vars, causing unsigned installers and SmartScreen
warnings; fix by adding Windows signing environment variables to that step
(e.g., set WIN_CSC_LINK and WIN_CSC_KEY_PASSWORD for a PFX or set
WINDOWS_CERTIFICATE_THUMBPRINT for Azure Trusted Signing) or, if signing is
intentionally deferred, add a clear TODO comment and documentation in the step
indicating signing is not configured; update the workflow step that runs "bun
run package" in the Build Electron app job to export the chosen env vars (or a
TODO) so electron-builder can sign the .exe.
- Around line 239-339: The Windows job duplicates the same version mutation,
compile env block, build/verify/upload steps found in the other platform jobs;
refactor by creating a reusable composite action (e.g.,
.github/actions/build-desktop-platform/action.yml) that encapsulates the steps
"Set version suffix", "Compile app with electron-vite", "Build Electron app",
"Verify Windows installer + update manifest", "Upload EXE artifact", and "Upload
Windows auto-update manifest"; parameterize the action with inputs for
version_suffix, electron_builder_config, artifact_prefix,
artifact_retention_days, os/arch, and any platform-specific signing secrets or
env vars (like NEXT_PUBLIC_* and SENTRY_*), then replace the platform-specific
jobs (including build-windows) to call the composite action with appropriate
inputs to remove duplication.
- Around line 253-260: The cache step "Cache dependencies" currently uses the
per-commit key `${{ runner.os }}-bun-${{ github.sha }}` which invalidates the
cache every commit; replace that key with a lockfile-based key (e.g., hash of
bun.lockb or your package lockfile) so the cache only changes when dependencies
change (update the value used in the `key` field in the "Cache dependencies"
step and do the same change for the other two build jobs), and keep a broad
`restore-keys` prefix like `${{ runner.os }}-bun-` so partial restores still
work.

In `@apps/desktop/vite/helpers.ts`:
- Around line 73-83: The current stripCrossOriginPlugin.handler uses
html.replace(/ crossorigin/g, "") which will leave orphaned =... tokens if Vite
emits crossorigin="anonymous"; update the regex in stripCrossOriginPlugin
(inside transformIndexHtml.handler) to defensively remove the attribute and any
optional value and surrounding whitespace (use a pattern that matches optional
=value with single/double/unquoted values and use the global flag) so it strips
both valueless crossorigin and crossorigin="..." occurrences without leaving
stray tokens.

Comment thread apps/desktop/vite/helpers.ts Outdated
Comment on lines 22 to 27
): string | undefined {
// Use || instead of ?? so empty strings (from unresolved GitHub Actions
// secrets) also fall through to the default value.
const resolved = value || fallback;
return resolved !== undefined ? JSON.stringify(resolved) : undefined;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n --type=ts -B2 -A2 'defineEnv\s*\(' --glob='**/*.ts' --glob='**/*.tsx'

Repository: superset-sh/superset

Length of output: 7752


🏁 Script executed:

head -100 apps/desktop/electron.vite.config.ts

Repository: superset-sh/superset

Length of output: 3432


🏁 Script executed:

cat -n apps/desktop/tsconfig.json

Repository: superset-sh/superset

Length of output: 646


🏁 Script executed:

find . -path "*typescript/electron.json" -o -path "*typescript/electron*" | head -20

Repository: superset-sh/superset

Length of output: 98


🏁 Script executed:

cat -n ./tooling/typescript/electron.json

Repository: superset-sh/superset

Length of output: 524


🏁 Script executed:

cat -n ./tooling/typescript/base.json

Repository: superset-sh/superset

Length of output: 867


🏁 Script executed:

# Check if there's a build configuration or CI that would compile TypeScript
find . -name "package.json" -path "*/apps/desktop/*" | xargs grep -l "typescript\|tsc" 2>/dev/null | head -5

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

cat -n apps/desktop/package.json | head -50

Repository: superset-sh/superset

Length of output: 2459


🏁 Script executed:

# Check the actual Vite/electron-vite type definitions for define property
# First, let's see what version of electron-vite is being used
cat apps/desktop/package.json | grep -A 2 -B 2 "electron-vite"

Repository: superset-sh/superset

Length of output: 880


🏁 Script executed:

# Let's check if there are any type assertions or workarounds in the electron.vite.config.ts file
# Get the full file to see if there are any type casts
wc -l apps/desktop/electron.vite.config.ts

Repository: superset-sh/superset

Length of output: 104


🏁 Script executed:

tail -160 apps/desktop/electron.vite.config.ts

Repository: superset-sh/superset

Length of output: 4213


🏁 Script executed:

# Let's check the actual type definitions for electron-vite's define config
# by looking at node_modules or checking what types are expected
find . -path "*node_modules/electron-vite*" -name "index.d.ts" 2>/dev/null | head -1

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

# Let's look at the defineEnv function implementation in detail
sed -n '19,27p' apps/desktop/vite/helpers.ts

Repository: superset-sh/superset

Length of output: 399


🏁 Script executed:

# Let's try to run typecheck on the desktop app to see if there are actual TypeScript errors
cd apps/desktop && npm run typecheck 2>&1 | head -100

Repository: superset-sh/superset

Length of output: 530


🏁 Script executed:

# Check if there are any type assertions or casts in the config
grep -n "as\|as const\|@ts-" apps/desktop/electron.vite.config.ts

Repository: superset-sh/superset

Length of output: 192


🏁 Script executed:

# Let's check what Vite's type definition for define config actually accepts
# by searching for how it's typed in node_modules (if available)
find . -path "*node_modules/vite*" -name "*.d.ts" | xargs grep -l "define" | head -3

Repository: superset-sh/superset

Length of output: 46


Update defineEnv return type handling—callers directly assign string | undefined to Vite's define config without filtering undefined.

The return type changed from string to string | undefined, but the call sites in electron.vite.config.ts assign the result directly to the define object. Many calls lack a fallback, meaning when environment variables are unset, defineEnv returns undefined. This breaks Vite's define type contract, which expects Record<string, string>.

Examples without fallback:

  • "process.env.SENTRY_DSN_DESKTOP": defineEnv(process.env.SENTRY_DSN_DESKTOP) (line 70)
  • "process.env.NEXT_PUBLIC_POSTHOG_KEY": defineEnv(process.env.NEXT_PUBLIC_POSTHOG_KEY) (line 74)
  • "process.env.DESKTOP_VITE_PORT": defineEnv(process.env.DESKTOP_VITE_PORT) (line 84)

Filter out undefined values at the call sites: ...(val !== undefined && { KEY: val }) or provide fallbacks for all calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/vite/helpers.ts` around lines 22 - 27, The defineEnv helper now
returns string | undefined but callers assign it directly into Vite's define
object, causing undefined entries to violate Vite's Record<string,string>;
update the call sites that use defineEnv (e.g., the entries for
"process.env.SENTRY_DSN_DESKTOP", "process.env.NEXT_PUBLIC_POSTHOG_KEY",
"process.env.DESKTOP_VITE_PORT") to either supply a fallback when calling
defineEnv or filter out undefined values before merging into define (for example
compute const val = defineEnv(...); then spread ...(val !== undefined && {
"process.env.X": val }) so only string values are included).

Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution @Sandman-Ren — appreciate the effort to get Windows builds working. However, Windows isn't currently a priority for us, and our primary concern is that none of these changes jeopardize the macOS or Linux builds. Several changes in this PR touch shared code paths that run on all platforms, and we'd need those tightened up before considering a merge.

Here's a detailed review:


1. defineEnv: ??|| and return type change (vite/helpers.ts)

This is the highest-risk change in the PR. It affects every env var substitution across all platform builds.

  • Changing ?? to || means empty strings ("") now fall through to the fallback. While the intent is to handle unresolved GitHub Actions secrets, this is a semantic change on macOS and Linux too. If any env var is intentionally set to "" in our CI or local dev, it would now silently be replaced by the fallback value.
  • The return type changing from string to string | undefined is technically more correct (the old code could already return undefined from JSON.stringify(undefined)), but this is still a behavioral change.

Suggestion: If this is only needed for the Windows CI runner, guard it there (e.g., set explicit defaults in the workflow env block) rather than changing the shared utility. Or at minimum, only apply the || fallback for specific variables known to be problematic, not globally.


2. stripCrossOriginPlugin runs on ALL platforms

This strips crossorigin from every <script> and <link> tag in the renderer HTML. While ASAR file:// on Windows may need this, the plugin is unconditionally applied on macOS and Linux too.

  • On macOS, where the build is stable and working, removing crossorigin could subtly change script loading behavior (e.g., error reporting, CORS handling).
  • The regex / crossorigin/g is also fragile — it would match crossorigin anywhere in the HTML, including inside attribute values or comments.

Suggestion: Either make this plugin Windows-only (if (process.platform !== 'win32') return html;), or explain why it's safe/necessary on macOS and Linux. If it doesn't cause issues on Mac, that's fine — but it needs to be validated, not assumed.


3. postinstall.shpostinstall.mjs

This replaces the bash postinstall script with a Node.js equivalent. This runs on every bun install across all platforms.

  • The logic looks equivalent, but process.env.SUPERSET_POSTINSTALL_RUNNING = "1" in Node.js sets it on the current process's env, which is inherited by execSync children. This should work, but it's a change to a critical install-time script.
  • Deleting postinstall.sh means there's no fallback if the .mjs script has issues.

Concern: This needs thorough testing on macOS before merge. We run bun install constantly, and a broken postinstall would block all development.


4. rmSync change in copy-native-modules.ts

Changing rmSync(modulePath) to rmSync(modulePath, { recursive: true, force: true }) to handle Windows junctions.

  • On macOS/Linux, rmSync on a symlink without recursive just removes the symlink. With { recursive: true }, it should still just remove the symlink (not follow it), but this is a change to behavior that's been stable.
  • The force: true flag suppresses errors if the path doesn't exist, which could mask real issues.

Suggestion: Consider guarding the { recursive: true, force: true } options behind a process.platform === 'win32' check, since Windows junctions are the only reason for the change.


5. Console message forwarding (main.ts)

Adding console-message listener and extra URL logging runs on all platforms. Low risk but adds noise to the main process stdout on Mac/Linux.

Suggestion: Gate this behind an env var or --verbose flag, or at minimum a process.platform === 'win32' check if it's only needed for Windows debugging.


6. CI workflow changes

The new build-windows job in build-desktop.yml is additive and doesn't affect Mac/Linux jobs — this part is fine. Adding NEXT_PUBLIC_ELECTRIC_URL to the existing Mac build env is also fine (looks like it was missing).

The release workflow changes (release-desktop.yml, release-desktop-canary.yml) for .exe artifact handling look correct and are guarded by file existence checks.


Summary

The CI/workflow changes are fine, but the shared code changes (defineEnv, stripCrossOriginPlugin, postinstall.mjs, rmSync options, console logging) all affect macOS and Linux builds without platform guards. We'd need either:

  1. Platform guards on Windows-specific fixes so Mac/Linux paths are untouched, or
  2. Proof that the Mac build is unaffected (e.g., a successful macOS CI run with these changes)

We're happy to reconsider once those concerns are addressed. Thanks again for the initiative!

@Sandman-Ren
Copy link
Copy Markdown
Author

Hi @AviPeltz, thanks for the detailed review and carefully pointing out the issues in my PR. I understand that Windows builds are not priority at the moment. I'd be happy to contribute to the Windows build effort in a way that does not tamper with the MacOS/Linux builds.

I'll spend some time with your comments and make changes accordingly.

Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (1)
apps/desktop/vite/helpers.ts (1)

19-24: defineEnv return type mismatch — already tracked.

JSON.stringify(value ?? fallback) returns the JS undefined value (not the string) when both value and fallback are undefined, violating the declared : string return type and breaking Vite's define contract at the affected call sites.

— this concern is covered by the existing review comment on this function.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/vite/helpers.ts` around lines 19 - 24, The function defineEnv
currently returns JSON.stringify(value ?? fallback) which yields the JS
undefined (not a string) when both are undefined; update defineEnv to always
produce a string by using a final default (e.g., JSON.stringify(value ??
fallback ?? null)) so it returns a valid string literal (like "null") instead of
raw undefined; modify the implementation of defineEnv to use that
ternary/default expression and keep the declared : string return type.
🤖 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/vite/helpers.ts`:
- Around line 71-82: The current stripCrossOriginPlugin uses process.platform
inside transformIndexHtml (handler) which is evaluated at build time and can be
wrong for cross-compilation; change the check to use an explicit build-target
env var (e.g., process.env.TARGET_PLATFORM or VITE_TARGET_PLATFORM) instead of
process.platform in the stripCrossOriginPlugin/transformIndexHtml handler, and
document the intent in a comment so CI can pass TARGET_PLATFORM=win32 for
Windows artifacts; ensure the handler falls back safely (e.g., treat undefined
as non-Windows) so behavior is deterministic and not tied to the build host.

---

Duplicate comments:
In `@apps/desktop/vite/helpers.ts`:
- Around line 19-24: The function defineEnv currently returns
JSON.stringify(value ?? fallback) which yields the JS undefined (not a string)
when both are undefined; update defineEnv to always produce a string by using a
final default (e.g., JSON.stringify(value ?? fallback ?? null)) so it returns a
valid string literal (like "null") instead of raw undefined; modify the
implementation of defineEnv to use that ternary/default expression and keep the
declared : string return type.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9669a90 and 23b1ea1.

📒 Files selected for processing (3)
  • apps/desktop/scripts/copy-native-modules.ts
  • apps/desktop/src/main/windows/main.ts
  • apps/desktop/vite/helpers.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/main/windows/main.ts
  • apps/desktop/scripts/copy-native-modules.ts

Comment on lines +71 to +82
export function stripCrossOriginPlugin(): Plugin {
return {
name: "strip-crossorigin",
transformIndexHtml: {
order: "post",
handler(html) {
if (process.platform !== "win32") return html;
return html.replace(/ crossorigin(?:="[^"]*")?/g, "");
},
},
};
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

process.platform is evaluated at build time — cross-compilation silently skips the fix.

process.platform !== "win32" runs in the Vite/Node.js process during the build, not inside the packaged Electron app. This works correctly when the CI runner is windows-latest, but if a Windows artifact is ever produced from a macOS or Linux host (cross-compilation), the crossorigin stripping will be silently skipped, causing the black-screen regression to reappear with no indication.

Consider asserting the intent in a comment, or pass the target platform explicitly via an env var so the build graph doesn't couple correctness to the build host:

💡 Proposed hardening
 export function stripCrossOriginPlugin(): Plugin {
 	return {
 		name: "strip-crossorigin",
 		transformIndexHtml: {
 			order: "post",
 			handler(html) {
-				if (process.platform !== "win32") return html;
+				// NOTE: evaluated at Vite build time, not Electron runtime.
+				// For cross-compilation, set TARGET_PLATFORM=win32 explicitly.
+				const platform = process.env.TARGET_PLATFORM ?? process.platform;
+				if (platform !== "win32") return html;
 				return html.replace(/ crossorigin(?:="[^"]*")?/g, "");
 			},
 		},
 	};
 }

The CI workflow would then pass TARGET_PLATFORM=win32 when invoking the Windows build step, making the condition explicit and immune to the build-host assumption.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function stripCrossOriginPlugin(): Plugin {
return {
name: "strip-crossorigin",
transformIndexHtml: {
order: "post",
handler(html) {
if (process.platform !== "win32") return html;
return html.replace(/ crossorigin(?:="[^"]*")?/g, "");
},
},
};
}
export function stripCrossOriginPlugin(): Plugin {
return {
name: "strip-crossorigin",
transformIndexHtml: {
order: "post",
handler(html) {
// NOTE: evaluated at Vite build time, not Electron runtime.
// For cross-compilation, set TARGET_PLATFORM=win32 explicitly.
const platform = process.env.TARGET_PLATFORM ?? process.platform;
if (platform !== "win32") return html;
return html.replace(/ crossorigin(?:="[^"]*")?/g, "");
},
},
};
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/vite/helpers.ts` around lines 71 - 82, The current
stripCrossOriginPlugin uses process.platform inside transformIndexHtml (handler)
which is evaluated at build time and can be wrong for cross-compilation; change
the check to use an explicit build-target env var (e.g.,
process.env.TARGET_PLATFORM or VITE_TARGET_PLATFORM) instead of process.platform
in the stripCrossOriginPlugin/transformIndexHtml handler, and document the
intent in a comment so CI can pass TARGET_PLATFORM=win32 for Windows artifacts;
ensure the handler falls back safely (e.g., treat undefined as non-Windows) so
behavior is deterministic and not tied to the build host.

Address PR review feedback by ensuring shared code paths are untouched
on macOS and Linux:

- Revert defineEnv back to ?? (|| was only needed for fork CI secrets)
- Gate stripCrossOriginPlugin to Windows only, tighten regex
- Gate rmSync({ recursive }) to Windows only, drop force flag
- Gate renderer console-message forwarding to Windows only
- Remove extra URL log line from did-finish-load
- Restore postinstall.sh alongside postinstall.mjs as fallback
@Sandman-Ren Sandman-Ren force-pushed the feat/windows-desktop-build branch from 23b1ea1 to bf257f4 Compare February 25, 2026 02:07
@Sandman-Ren
Copy link
Copy Markdown
Author

Sandman-Ren commented Feb 25, 2026

Thanks again for the detailed review @AviPeltz.

I've made changes in response to your comments. Notably my changes in 1. was supposed to be a temporary workaround for not having some repository secrets while trying to replicate your CI GitHub Actions workflow in my fork repository for build validation. I have reverted 1. to its original state.

I've added Windows gates for stripCrossOriginPlugin, rmSync and console logs. Upon review I also realized that the Windows log lines were added for my own debugging purposes and I felt that it may be helpful down the line, so I've kept it for now.

For 3. I've restored the postinstall.sh so that we will have a fallback. I've replicated the CI GitHub Actions workflows in my fork and looks like they build successfully for Linux and Windows. For MacOS the build passes. The workflow is marked as "failed" because I do not have the Apple developer secrets to sign the built package.

See below for details of the changes made. Let me know once you and the team get a chance to review :)


Changes made

1. defineEnv: reverted to ??
The || change was a workaround for our fork's CI not having the repository secrets (GitHub Actions resolves missing secrets to "", which bypassed the ?? fallback). Since the upstream CI has all secrets configured, this change adds no value here. Reverted to the original implementation — zero diff from main.

2. stripCrossOriginPlugin: gated to Windows only
Now returns html unchanged on macOS and Linux (if (process.platform !== "win32") return html). Also tightened the regex from / crossorigin/g to / crossorigin(?:="[^"]*")?/g to properly match the attribute form only.

3. postinstall.sh: restored alongside postinstall.mjs
The .mjs script is needed for Windows (bash scripts don't execute natively), but the original postinstall.sh is now restored in the repo as a fallback. package.json uses the .mjs version (functionally equivalent, works cross-platform). Reverting to .sh is a one-line change if needed.

4. rmSync: gated { recursive } to Windows only
Removed force: true entirely. On macOS/Linux the call is now plain rmSync(modulePath) — identical to main. The { recursive: true } option only applies on Windows where Bun creates junctions instead of symlinks.

5. Console message forwarding: gated to Windows only
Wrapped the console-message handler in if (PLATFORM.IS_WINDOWS). Removed the extra console.log("[main-window] URL:", ...) line. Neither runs on macOS or Linux.

6. CI/workflow changes: additive only. Also added git config --system core.longPaths true before checkout in the Windows build job — some upstream file paths exceed Windows' 260-character MAX_PATH limit.


CI evidence

Fork CI (lint, typecheck, test, build on Linux) — all passing:
https://github.com/Sandman-Ren/superset/actions/runs/22379001867

Check Result
Sherif Pass
Lint Pass
Test Pass
Typecheck Pass
Build (bun turbo run build --filter=@superset/desktop) Pass

Fork canary build — latest commit (56bf5f6, all three platforms):
https://github.com/Sandman-Ren/superset/actions/runs/22382955161

Platform Compile (Vite) Package (electron-builder) Notes
Linux Pass Pass Full end-to-end
Windows Pass Pass With core.longPaths fix
macOS Pass Fail Missing Apple code signing certs (expected on forks; CSC_LINK empty)

The macOS Vite compile passes all three stages, confirming all shared code paths (defineEnv, stripCrossOriginPlugin, postinstall.mjs) work on macOS. The packaging failure is solely due to Apple code signing certificates not being available on forks.

Every shared code path is now either identical to main or gated behind process.platform === "win32" / PLATFORM.IS_WINDOWS.

Windows has a 260-character MAX_PATH limit. Some upstream file paths
(e.g. deeply nested component directories) exceed this, causing
`git checkout` to fail on Windows runners. Setting core.longPaths
before checkout resolves this.
@Sandman-Ren
Copy link
Copy Markdown
Author

just wanted to give this a soft nudge and see if we can get any reviews. Many thanks for the time :)

@Sandman-Ren Sandman-Ren requested a review from AviPeltz March 11, 2026 03:31
@Kitenite
Copy link
Copy Markdown
Collaborator

Closing: this Windows CI/CD PR from Feb 20 has been superseded by newer Windows support efforts (#2100, #2196, #2322).

@Kitenite Kitenite closed this Mar 13, 2026
@Kitenite Kitenite reopened this Mar 13, 2026
@Kitenite
Copy link
Copy Markdown
Collaborator

Hey sorry that got into the cleaning loop. I don't want to dismiss the good work done here but we're just not able to support or maintain a Windows version. I'd be happy to give a windows fork our proverbial blessing and eventually converge back when we have more resources to properly test and give a good windows experience

@Sandman-Ren
Copy link
Copy Markdown
Author

No worries. Happy to keep a windows fork since I use this for my own dev work. Looking forward to getting back and work on windows support again in the future. Cheers!

@Sandman-Ren Sandman-Ren closed this Apr 1, 2026
@Sandman-Ren Sandman-Ren deleted the feat/windows-desktop-build branch April 1, 2026 05:15
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.

5 participants