Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/api/src/app/api/agent/[transport]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ async function verifyToken(req: Request, bearerToken?: string) {
// 1. Try session auth
const session = await auth.api.getSession({ headers: req.headers });
if (session?.session) {
if (!session.user) {
console.error("[mcp/auth] Session missing user");
return undefined;
}
const extendedSession = session.session as {
activeOrganizationId?: string;
};
Expand Down
38 changes: 28 additions & 10 deletions apps/desktop/electron-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import pkg from "./package.json";
const currentYear = new Date().getFullYear();
const author = pkg.author?.name ?? pkg.author;
const productName = pkg.productName;
const disableWinSigning = process.env.SUPERSET_DISABLE_WIN_SIGNING === "1";

const config: Configuration = {
appId: "com.superset.desktop",
Expand Down Expand Up @@ -56,32 +57,34 @@ const config: Configuration = {
to: "resources/migrations",
filter: ["**/*"],
},
// App icons used by Windows shortcuts (keep outside asar)
{
from: join(pkg.resources, "build/icons"),
to: "build/icons",
filter: ["**/*"],
},
],

files: [
"dist/**/*",
"package.json",
{
filter: ["dist/**/*", "!dist/resources/migrations/**", "package.json"],
},
{
from: pkg.resources,
to: "resources",
filter: ["**/*"],
filter: ["**/*", "!build/**"],
},
// Native modules that can't be bundled by Vite.
// bun creates symlinks for direct deps in workspace node_modules.
// The copy:native-modules script replaces symlinks with real files
// before building (required for Bun 1.3+ isolated installs).
// Native modules rebuilt for Electron
{
from: "node_modules/better-sqlite3",
to: "node_modules/better-sqlite3",
filter: ["**/*"],
},
// better-sqlite3 uses `bindings` package to locate its native .node file
{
from: "node_modules/bindings",
to: "node_modules/bindings",
filter: ["**/*"],
},
// `bindings` requires `file-uri-to-path` for file:// URL handling
{
from: "node_modules/file-uri-to-path",
to: "node_modules/file-uri-to-path",
Expand All @@ -101,7 +104,7 @@ const config: Configuration = {
"!**/.DS_Store",
],

// Rebuild native modules for Electron's Node.js version
// Rebuild native modules for Electron platform during packaging
npmRebuild: true,

// macOS
Expand Down Expand Up @@ -153,12 +156,27 @@ const config: Configuration = {
},
],
artifactName: `${productName}-${pkg.version}-\${arch}.\${ext}`,
signAndEditExecutable: disableWinSigning ? false : undefined,
asarUnpack: ["**/node_modules/@lydell/node-pty-win32-x64/**/*"],
files: [
{
from: "node_modules/@lydell/node-pty-win32-x64",
to: "node_modules/@lydell/node-pty-win32-x64",
filter: ["**/*"],
},
],
},

// NSIS installer (Windows)
nsis: {
oneClick: false,
allowToChangeInstallationDirectory: true,
createDesktopShortcut: true,
createStartMenuShortcut: true,
shortcutName: productName,
installerIcon: join(pkg.resources, "build/icons/icon.ico"),
uninstallerIcon: join(pkg.resources, "build/icons/icon.ico"),
include: join(pkg.resources, "build/installer.nsh"),
},
};

Expand Down
11 changes: 5 additions & 6 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@superset/desktop",
"productName": "Superset",
"description": "The last developer tool you'll ever need",
"version": "0.0.63",
"version": "0.0.64",
"main": "./dist/main/index.js",
"resources": "src/resources",
"repository": {
Expand All @@ -21,11 +21,10 @@
"compile:app": "cross-env NODE_OPTIONS=--max-old-space-size=8192 electron-vite build",
"copy:native-modules": "bun run scripts/copy-native-modules.ts",
"prebuild": "bun run clean:dev && bun run compile:app && bun run copy:native-modules",
"build": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --publish never",
"prepackage": "bun run copy:native-modules",
"package": "electron-builder --config electron-builder.ts",
"build": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --publish never --config electron-builder.ts",
"package": "bun run copy:native-modules && electron-builder --config electron-builder.ts",
"install:deps": "electron-builder install-app-deps",
"release": "electron-builder --publish always",
"release": "bun run copy:native-modules && electron-builder --publish always --config electron-builder.ts",
"clean:dev": "rimraf ./node_modules/.dev",
"generate:routes": "tsr generate",
"pretypecheck": "bun run generate:routes",
Expand Down Expand Up @@ -132,7 +131,7 @@
"monaco-editor": "^0.55.1",
"nanoid": "^5.1.6",
"node-addon-api": "^7.1.0",
"node-pty": "1.1.0-beta30",
"node-pty": "npm:@lydell/node-pty@^1.0.1",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@AXGZ21 what's the reason for using this package? I think i had to do this for onlook as well but can't recall exactly

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good question! The @lydell/node-pty package is a fork of the original node-pty library. Based on the change, there are a few likely reasons why this fork was used:

Possible reasons:

  • Bug fixes - The lydell fork may contain fixes for specific issues that haven't been merged into the main package yet
  • Platform-specific compatibility - Since node-pty deals with pseudo-terminals, this fork might have better cross-platform support (Windows/macOS/Linux)
  • Electron compatibility - The fork could have better compatibility with our specific Electron version
  • Maintenance - The main package might have had slower maintenance when this change was made

To confirm the exact reason, it would be helpful to:

  • Check the original PR description or commit message for context
  • Review the lydell/node-pty repository to see what differences exist
  • Look for any related issues that prompted this change

Did you encounter this same issue with Onlook, or are you just reviewing the change for context?

AI Generated by PierrunoYT

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@Kitenite We use npm:@lydell/node-pty@^1.0.1 because it provides reliable prebuilt binaries (especially for Windows) for Electron, so terminal PTY spawning works and installer builds don’t fail on native compilation.

Also, keep only one node-pty entry in package.json (remove the duplicate).

"os-locale": "^6.0.2",
"pidtree": "^0.6.0",
"posthog-js": "1.310.1",
Expand Down
98 changes: 87 additions & 11 deletions apps/desktop/scripts/copy-native-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,48 @@
* This is safe because bun install will recreate the symlinks on next install.
*/

import { cpSync, existsSync, lstatSync, realpathSync, rmSync } from "node:fs";
import { dirname, join } from "node:path";
import {
cpSync,
existsSync,
lstatSync,
mkdirSync,
realpathSync,
readdirSync,
rmSync,
} from "node:fs";
import { dirname, join, resolve } from "node:path";

// Native modules that must exist for the app to work
// Made optional for Windows builds where native compilation may fail
const NATIVE_MODULES = ["better-sqlite3", "node-pty"] as const;

// Dependencies of native modules that need to be copied (may be hoisted or symlinked)
const NATIVE_MODULE_DEPS = ["bindings", "file-uri-to-path"] as const;

const desktopDir = dirname(import.meta.dirname);
const desktopNodeModulesDir = join(desktopDir, "node_modules");
const repoRootDir = resolve(desktopDir, "..", "..");
const repoNodeModulesDir = join(repoRootDir, "node_modules");
const bunStoreDir = join(repoNodeModulesDir, ".bun");

const OPTIONAL_PLATFORM_MODULES = ["@lydell/node-pty-win32-x64"] as const;

function findBunModulePath(moduleName: string): string | null {
if (!existsSync(bunStoreDir)) return null;

const bunPrefix = moduleName.startsWith("@")
? moduleName.replace("/", "+")
: moduleName;
const matches = readdirSync(bunStoreDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && entry.name.startsWith(`${bunPrefix}@`))
.map((entry) => entry.name)
.sort((a, b) => b.localeCompare(a));

if (matches.length === 0) return null;

return join(bunStoreDir, matches[0], "node_modules", moduleName);
}

function copyModuleIfSymlink(
nodeModulesDir: string,
moduleName: string,
Expand All @@ -31,8 +64,9 @@ function copyModuleIfSymlink(

if (!existsSync(modulePath)) {
if (required) {
console.error(` [ERROR] ${moduleName} not found at ${modulePath}`);
process.exit(1);
// On Windows, native modules may not compile - warn but don't fail
console.warn(` [WARN] ${moduleName} not found at ${modulePath} - continuing without it`);
return false;
}
console.log(` ${moduleName}: not found (skipping)`);
return false;
Expand All @@ -46,8 +80,8 @@ function copyModuleIfSymlink(
console.log(` ${moduleName}: symlink -> replacing with real files`);
console.log(` Real path: ${realPath}`);

// Remove the symlink
rmSync(modulePath);
// Remove the symlink/junction safely on Windows
rmSync(modulePath, { recursive: true, force: true });

// Copy the actual files
cpSync(realPath, modulePath, { recursive: true });
Expand All @@ -63,18 +97,60 @@ function copyModuleIfSymlink(
function prepareNativeModules() {
console.log("Preparing native modules for electron-builder...");

// bun creates symlinks for direct dependencies in the workspace's node_modules
const nodeModulesDir = join(dirname(import.meta.dirname), "node_modules");
// bun creates symlinks for direct dependencies in the workspace's node_modules.
// If the workspace doesn't have its own node_modules, fall back to the repo root.
if (!existsSync(desktopNodeModulesDir)) {
mkdirSync(desktopNodeModulesDir, { recursive: true });
}

function ensureLocalModuleCopy(moduleName: string, required: boolean) {
const desktopModulePath = join(desktopNodeModulesDir, moduleName);
if (existsSync(desktopModulePath)) {
return copyModuleIfSymlink(desktopNodeModulesDir, moduleName, required);
}

const repoModulePath = join(repoNodeModulesDir, moduleName);
if (!existsSync(repoModulePath)) {
const bunModulePath = findBunModulePath(moduleName);
if (bunModulePath && existsSync(bunModulePath)) {
console.log(` ${moduleName}: copying from bun store`);
mkdirSync(dirname(desktopModulePath), { recursive: true });
cpSync(bunModulePath, desktopModulePath, { recursive: true });
return true;
}
if (required) {
console.warn(
` [WARN] ${moduleName} not found in desktop or repo node_modules - continuing without it`,
);
return false;
}
console.log(` ${moduleName}: not found (skipping)`);
return false;
}

const repoStats = lstatSync(repoModulePath);
const sourcePath = repoStats.isSymbolicLink()
? realpathSync(repoModulePath)
: repoModulePath;
console.log(` ${moduleName}: copying from repo node_modules`);
cpSync(sourcePath, desktopModulePath, { recursive: true });
return true;
}

// Copy required native modules
// Copy native modules (not required on Windows if compilation failed)
for (const moduleName of NATIVE_MODULES) {
copyModuleIfSymlink(nodeModulesDir, moduleName, true);
ensureLocalModuleCopy(moduleName, false);
}

// Copy native module dependencies (not required but needed if present)
console.log("\nPreparing native module dependencies...");
for (const moduleName of NATIVE_MODULE_DEPS) {
copyModuleIfSymlink(nodeModulesDir, moduleName, false);
ensureLocalModuleCopy(moduleName, false);
}

console.log("\nPreparing platform-specific optional modules...");
for (const moduleName of OPTIONAL_PLATFORM_MODULES) {
ensureLocalModuleCopy(moduleName, false);
}

console.log("\nDone!");
Expand Down
31 changes: 29 additions & 2 deletions apps/desktop/src/lib/electron-app/factories/windows/create.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import { BrowserWindow, shell } from "electron";
import { app, BrowserWindow, shell } from "electron";
import { registerRoute } from "lib/window-loader";
import type { WindowProps } from "shared/types";

function getDefaultWindowIcon(): string | undefined {
if (process.platform !== "win32") {
return undefined;
}

const candidates = app.isPackaged
? [
join(process.resourcesPath, "build", "icons", "icon.ico"),
]
: [
join(app.getAppPath(), "src", "resources", "build", "icons", "icon.ico"),
];
Comment thread
coderabbitai[bot] marked this conversation as resolved.

for (const candidate of candidates) {
if (existsSync(candidate)) {
return candidate;
}
}

return undefined;
}

export function createWindow({ id, ...settings }: WindowProps) {
const window = new BrowserWindow(settings);
const icon = settings.icon ?? getDefaultWindowIcon();
const window = new BrowserWindow({
...settings,
...(icon ? { icon } : {}),
});

// Open external URLs in the system browser instead of Electron
window.webContents.setWindowOpenHandler(({ url }) => {
Expand Down
68 changes: 67 additions & 1 deletion apps/desktop/src/lib/trpc/routers/external/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { getAppCommand, resolvePath, stripPathWrappers } from "./helpers";

describe("getAppCommand", () => {
const isDarwin = process.platform === "darwin";
const isWindows = process.platform === "win32";

const describeDarwin = isDarwin ? describe : describe.skip;
const describeWindows = isWindows ? describe : describe.skip;

describeDarwin("getAppCommand (darwin)", () => {
test("returns null for finder (handled specially)", () => {
expect(getAppCommand("finder", "/path/to/file")).toBeNull();
});
Expand Down Expand Up @@ -123,6 +130,65 @@ describe("getAppCommand", () => {
});
});

describeWindows("getAppCommand (windows)", () => {
const originalEnv = {
LOCALAPPDATA: process.env.LOCALAPPDATA,
ProgramFiles: process.env.ProgramFiles,
ProgramFilesX86: process.env["ProgramFiles(x86)"],
};
let tempRoot: string;

beforeEach(() => {
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "superset-win-"));
process.env.LOCALAPPDATA = path.join(tempRoot, "LocalAppData");
process.env.ProgramFiles = path.join(tempRoot, "ProgramFiles");
process.env["ProgramFiles(x86)"] = path.join(tempRoot, "ProgramFilesX86");
fs.mkdirSync(process.env.LOCALAPPDATA, { recursive: true });
fs.mkdirSync(process.env.ProgramFiles, { recursive: true });
fs.mkdirSync(process.env["ProgramFiles(x86)"], { recursive: true });
});

afterEach(() => {
process.env.LOCALAPPDATA = originalEnv.LOCALAPPDATA;
process.env.ProgramFiles = originalEnv.ProgramFiles;
process.env["ProgramFiles(x86)"] = originalEnv.ProgramFilesX86;
fs.rmSync(tempRoot, { recursive: true, force: true });
});

test("resolves install path for VS Code", () => {
const exePath = path.join(
process.env.LOCALAPPDATA ?? "",
"Programs",
"Microsoft VS Code",
"Code.exe",
);
fs.mkdirSync(path.dirname(exePath), { recursive: true });
fs.writeFileSync(exePath, "");
const result = getAppCommand("vscode", "C:\\path\\file.ts");
expect(result).toEqual({
command: exePath,
args: ["C:\\path\\file.ts"],
});
});

test("resolves JetBrains install path for IntelliJ", () => {
const exePath = path.join(
process.env.ProgramFiles ?? "",
"JetBrains",
"IntelliJ IDEA 2024.1",
"bin",
"idea64.exe",
);
fs.mkdirSync(path.dirname(exePath), { recursive: true });
fs.writeFileSync(exePath, "");
const result = getAppCommand("intellij", "C:\\path\\project");
expect(result).toEqual({
command: exePath,
args: ["C:\\path\\project"],
});
});
});

describe("resolvePath", () => {
const homedir = os.homedir();
const originalHome = process.env.HOME;
Expand Down
Loading