Skip to content
Open
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
1 change: 1 addition & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"scripts": {
"clean": "git clean -xdf .cache .turbo dist dist-electron release node_modules",
"start": "electron-vite preview",
"postinstall": "bun run scripts/patch-trpc-electron.ts",
Copy link

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:

find . -name "package.json" -path "*/apps/desktop/*" | head -5

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

cat apps/desktop/package.json | grep -A2 -B2 '"postinstall"'
cat apps/desktop/package.json | grep -A2 -B2 '"trpc-electron"'
find . -name "patch-trpc-electron.ts" -type f

Repository: superset-sh/superset

Length of output: 582


🏁 Script executed:

#!/bin/bash
echo "=== Checking postinstall script ===" 
cat apps/desktop/package.json | grep -A1 '"postinstall"'

echo -e "\n=== Checking trpc-electron version ===" 
cat apps/desktop/package.json | grep -A1 '"trpc-electron"'

echo -e "\n=== Finding patch script ===" 
find . -name "patch-trpc-electron.ts" -type f

Repository: superset-sh/superset

Length of output: 407


🏁 Script executed:

#!/bin/bash
# Find and display the patch script
if [ -f "scripts/patch-trpc-electron.ts" ]; then
  echo "=== Patch script content ===" 
  cat scripts/patch-trpc-electron.ts
else
  echo "Finding patch script..."
  find . -name "*patch*trpc*" -type f 2>/dev/null | head -10
fi

Repository: superset-sh/superset

Length of output: 133


🏁 Script executed:

cat apps/desktop/package.json | jq '.scripts.postinstall, .dependencies."trpc-electron", .devDependencies."trpc-electron"' | head -20

Repository: superset-sh/superset

Length of output: 118


🏁 Script executed:

cat scripts/patch-trpc-electron.ts | head -100

Repository: superset-sh/superset

Length of output: 126


🏁 Script executed:

cat apps/desktop/scripts/patch-trpc-electron.ts

Repository: superset-sh/superset

Length of output: 3076


🏁 Script executed:

cat ./apps/desktop/scripts/patch-trpc-electron.ts

Repository: superset-sh/superset

Length of output: 3076


🏁 Script executed:

head -150 apps/desktop/scripts/patch-trpc-electron.ts

Repository: superset-sh/superset

Length of output: 3076


Pin trpc-electron to an exact version to prevent silent patch failures.

The postinstall script patches trpc-electron using fragile regex patterns that depend on specific code structure and minification artifacts (e.g., function names like Fe, ke). These patterns target three locations: the source TypeScript file, ESM dist, and minified CJS dist. With caret versioning (^0.1.2), any patch/minor release of trpc-electron could change its code structure or minification scheme, causing the patches to silently fail to apply (the script only logs but doesn't error on failed matches). This leaves the app running with an unpatched dependency that will break on Node.js 22+ or Electron 39+.

Change to exact version pinning:

"trpc-electron": "0.1.2"

Revert to caret versioning only after trpc-electron provides an upstream fix for Symbol.asyncDispose compatibility.

🤖 Prompt for AI Agents
In apps/desktop/package.json around line 19, the trpc-electron dependency is
caret-pinned which allows minor/patch upgrades that can change minified output
and cause the fragile postinstall patch script to silently fail; change the
dependency entry to an exact version string "0.1.2" (remove the ^) and update
the project lockfile (bun.lockb / lockfile if present) so installs are
deterministic; keep this exact pin until trpc-electron publishes an upstream
compatibility fix for Symbol.asyncDispose, then revert to caret if desired.

"predev": "bun run clean:dev && bun run scripts/patch-dev-protocol.ts",
"dev": "cross-env NODE_ENV=development electron-vite dev --watch",
"compile:app": "cross-env NODE_OPTIONS=--max-old-space-size=4096 electron-vite build",
Expand Down
77 changes: 77 additions & 0 deletions apps/desktop/scripts/patch-trpc-electron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env bun
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should possibly fork electron-trpc and add it as a package in our repo - it's no longer maintained so we probably will have to do similar stuff to make it work :/

/**
* Patches trpc-electron to fix compatibility with Electron 39+ / Node.js 22+
*
* Node.js 22+ has Symbol.asyncDispose built-in on AsyncIterators, which causes
* trpc-electron to throw "Symbol.asyncDispose already exists" when trying to
* add its own dispose function.
*
* This patch removes the existence check and allows overwriting the symbol.
*/

import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { join } from "node:path";

const nodeModulesPath = join(import.meta.dir, "..", "node_modules");
const trpcElectronPath = join(nodeModulesPath, "trpc-electron");

if (!existsSync(trpcElectronPath)) {
console.log("trpc-electron not found, skipping patch");
process.exit(0);
}

// Patch the source TypeScript file
const utilsPath = join(trpcElectronPath, "src", "main", "utils.ts");
if (existsSync(utilsPath)) {
let content = readFileSync(utilsPath, "utf-8");
const originalContent = content;

// Remove the check that throws when Symbol.asyncDispose exists
content = content.replace(
/\/\/ eslint-disable-next-line no-restricted-syntax\s*\n\s*if \(it\[Symbol\.asyncDispose\]\) \{\s*\n\s*throw new Error\('Symbol\.asyncDispose already exists'\);\s*\n\s*\}\s*\n/,
"// Node.js 22+ / Electron 39+ has Symbol.asyncDispose built-in on AsyncIterators\n // We overwrite it with our custom dispose function\n"
);

if (content !== originalContent) {
writeFileSync(utilsPath, content);
console.log("Patched trpc-electron/src/main/utils.ts");
}
}
Comment on lines +23 to +39
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add validation to detect when patches fail to apply.

The regex pattern is extremely brittle and depends on exact formatting, variable names (it), comment text, and error messages. If trpc-electron updates its code, the regex will silently fail to match, and the runtime error will reoccur.

🔎 Suggested improvements

Add validation to ensure the patch was applied or was already present:

 const utilsPath = join(trpcElectronPath, "src", "main", "utils.ts");
 if (existsSync(utilsPath)) {
 	let content = readFileSync(utilsPath, "utf-8");
 	const originalContent = content;
 
+	// Check if already patched
+	const isAlreadyPatched = !content.includes("Symbol.asyncDispose already exists");
+	const needsPatch = content.includes("Symbol.asyncDispose already exists");
+
 	// Remove the check that throws when Symbol.asyncDispose exists
 	content = content.replace(
 		/\/\/ eslint-disable-next-line no-restricted-syntax\s*\n\s*if \(it\[Symbol\.asyncDispose\]\) \{\s*\n\s*throw new Error\('Symbol\.asyncDispose already exists'\);\s*\n\s*\}\s*\n/,
 		"// Node.js 22+ / Electron 39+ has Symbol.asyncDispose built-in on AsyncIterators\n  // We overwrite it with our custom dispose function\n"
 	);
 
 	if (content !== originalContent) {
 		writeFileSync(utilsPath, content);
 		console.log("Patched trpc-electron/src/main/utils.ts");
+	} else if (needsPatch) {
+		console.error("ERROR: Failed to patch trpc-electron/src/main/utils.ts - regex pattern may be outdated");
+		process.exit(1);
 	}
 }

Apply similar validation to the dist file patches (lines 41-75).

📝 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
// Patch the source TypeScript file
const utilsPath = join(trpcElectronPath, "src", "main", "utils.ts");
if (existsSync(utilsPath)) {
let content = readFileSync(utilsPath, "utf-8");
const originalContent = content;
// Remove the check that throws when Symbol.asyncDispose exists
content = content.replace(
/\/\/ eslint-disable-next-line no-restricted-syntax\s*\n\s*if \(it\[Symbol\.asyncDispose\]\) \{\s*\n\s*throw new Error\('Symbol\.asyncDispose already exists'\);\s*\n\s*\}\s*\n/,
"// Node.js 22+ / Electron 39+ has Symbol.asyncDispose built-in on AsyncIterators\n // We overwrite it with our custom dispose function\n"
);
if (content !== originalContent) {
writeFileSync(utilsPath, content);
console.log("Patched trpc-electron/src/main/utils.ts");
}
}
// Patch the source TypeScript file
const utilsPath = join(trpcElectronPath, "src", "main", "utils.ts");
if (existsSync(utilsPath)) {
let content = readFileSync(utilsPath, "utf-8");
const originalContent = content;
// Check if already patched
const isAlreadyPatched = !content.includes("Symbol.asyncDispose already exists");
const needsPatch = content.includes("Symbol.asyncDispose already exists");
// Remove the check that throws when Symbol.asyncDispose exists
content = content.replace(
/\/\/ eslint-disable-next-line no-restricted-syntax\s*\n\s*if \(it\[Symbol\.asyncDispose\]\) \{\s*\n\s*throw new Error\('Symbol\.asyncDispose already exists'\);\s*\n\s*\}\s*\n/,
"// Node.js 22+ / Electron 39+ has Symbol.asyncDispose built-in on AsyncIterators\n // We overwrite it with our custom dispose function\n"
);
if (content !== originalContent) {
writeFileSync(utilsPath, content);
console.log("Patched trpc-electron/src/main/utils.ts");
} else if (needsPatch) {
console.error("ERROR: Failed to patch trpc-electron/src/main/utils.ts - regex pattern may be outdated");
process.exit(1);
}
}
🤖 Prompt for AI Agents
In apps/desktop/scripts/patch-trpc-electron.ts around lines 23 to 39, the
brittle regex replacement may silently fail if upstream formatting or names
change; add explicit validation after attempting the replace: detect whether the
content changed or already contains the intended replacement, and if neither is
true throw or log a failing error and exit (or fail the CI) so the developer is
alerted; apply the same pattern of validation to the subsequent dist file
patches (lines ~41-75) to ensure each patch either applied or was already
present before writing files.


// Patch the ESM dist file
const mainMjsPath = join(trpcElectronPath, "dist", "main.mjs");
if (existsSync(mainMjsPath)) {
let content = readFileSync(mainMjsPath, "utf-8");
const originalContent = content;

// Remove the check in the Fe function
content = content.replace(
/function Fe\(r, e\) \{\s*\n\s*const t = r;\s*\n\s*if \(t\[Symbol\.asyncDispose\]\)\s*\n\s*throw new Error\("Symbol\.asyncDispose already exists"\);\s*\n\s*return t\[Symbol\.asyncDispose\] = e, t;\s*\n\}/,
'function Fe(r, e) {\n const t = r;\n // Allow overwriting Symbol.asyncDispose for Node.js 22+ / Electron 39+ compatibility\n return t[Symbol.asyncDispose] = e, t;\n}'
);

if (content !== originalContent) {
writeFileSync(mainMjsPath, content);
console.log("Patched trpc-electron/dist/main.mjs");
}
}

// Patch the CJS dist file (minified)
const mainCjsPath = join(trpcElectronPath, "dist", "main.cjs");
if (existsSync(mainCjsPath)) {
let content = readFileSync(mainCjsPath, "utf-8");
const originalContent = content;

// Remove the check in the minified ke function
content = content.replace(
/function ke\(r,e\)\{const t=r;if\(t\[Symbol\.asyncDispose\]\)throw new Error\("Symbol\.asyncDispose already exists"\);return t\[Symbol\.asyncDispose\]=e,t\}/,
"function ke(r,e){const t=r;return t[Symbol.asyncDispose]=e,t}"
);

if (content !== originalContent) {
writeFileSync(mainCjsPath, content);
console.log("Patched trpc-electron/dist/main.cjs");
}
}

console.log("trpc-electron patch complete");
Loading