From 0608e6f220b6f21b063815f69d13dfb592dba6e5 Mon Sep 17 00:00:00 2001 From: JonathanLab Date: Tue, 14 Apr 2026 18:57:08 +0200 Subject: [PATCH] refactor: initial decoupling of electron --- .github/workflows/build.yml | 3 +++ .github/workflows/code-release.yml | 6 +++++ .github/workflows/test.yml | 1 + apps/code/package.json | 1 + apps/code/scripts/postinstall.sh | 13 ++++++++++ apps/code/src/main/di/container.ts | 3 +++ apps/code/src/main/di/tokens.ts | 3 +++ .../electron-url-launcher.ts | 10 +++++++ .../services/github-integration/service.ts | 11 +++++--- mprocs.yaml | 4 +++ packages/platform/package.json | 26 +++++++++++++++++++ packages/platform/src/url-launcher.ts | 3 +++ packages/platform/tsconfig.json | 17 ++++++++++++ packages/platform/tsup.config.ts | 12 +++++++++ pnpm-lock.yaml | 12 +++++++++ 15 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 apps/code/src/main/platform-adapters/electron-url-launcher.ts create mode 100644 packages/platform/package.json create mode 100644 packages/platform/src/url-launcher.ts create mode 100644 packages/platform/tsconfig.json create mode 100644 packages/platform/tsup.config.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea00ec045..a9eed0d58 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,6 +33,9 @@ jobs: - name: Build electron-trpc run: pnpm --filter @posthog/electron-trpc build + - name: Build platform + run: pnpm --filter @posthog/platform build + - name: Build shared run: pnpm --filter @posthog/shared build diff --git a/.github/workflows/code-release.yml b/.github/workflows/code-release.yml index 8a20432e8..e582d21c2 100644 --- a/.github/workflows/code-release.yml +++ b/.github/workflows/code-release.yml @@ -85,6 +85,9 @@ jobs: - name: Build electron-trpc package run: pnpm --filter @posthog/electron-trpc run build + - name: Build platform package + run: pnpm --filter @posthog/platform run build + - name: Build shared package run: pnpm --filter @posthog/shared run build @@ -182,6 +185,9 @@ jobs: - name: Build electron-trpc package run: pnpm --filter @posthog/electron-trpc run build + - name: Build platform package + run: pnpm --filter @posthog/platform run build + - name: Build shared package run: pnpm --filter @posthog/shared run build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ea1cc7038..db5b51cbe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,6 +78,7 @@ jobs: - name: Build packages run: | pnpm --filter @posthog/electron-trpc build & + pnpm --filter @posthog/platform build & pnpm --filter @posthog/shared build pnpm --filter @posthog/git build pnpm --filter agent build & diff --git a/apps/code/package.json b/apps/code/package.json index be52c179f..ef6fc96e9 100644 --- a/apps/code/package.json +++ b/apps/code/package.json @@ -131,6 +131,7 @@ "@posthog/git": "workspace:*", "@posthog/hedgehog-mode": "^0.0.48", "@posthog/quill": "0.1.0-alpha.7", + "@posthog/platform": "workspace:*", "@posthog/shared": "workspace:*", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-icons": "^1.3.2", diff --git a/apps/code/scripts/postinstall.sh b/apps/code/scripts/postinstall.sh index 3be076571..d96063fef 100755 --- a/apps/code/scripts/postinstall.sh +++ b/apps/code/scripts/postinstall.sh @@ -8,6 +8,19 @@ set -e REPO_ROOT="$(cd ../.. && pwd)" SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)" +# Self-heal missing Electron binary. +# pnpm skips package-level postinstall scripts when the lockfile is already +# satisfied, so if node_modules/electron/dist gets wiped (interrupted download, +# cache eviction, arch change, manual cleanup), `pnpm install` won't notice — +# and `electron-forge start` then fails with "Electron failed to install +# correctly, please delete node_modules/electron and try installing again". +# Detect the missing binary and invoke Electron's own install script to fetch it. +ELECTRON_DIST="$REPO_ROOT/node_modules/electron/dist" +if [ ! -d "$ELECTRON_DIST" ] || [ -z "$(ls -A "$ELECTRON_DIST" 2>/dev/null)" ]; then + echo "Electron binary missing at $ELECTRON_DIST — downloading..." + node "$REPO_ROOT/node_modules/electron/install.js" +fi + echo "Rebuilding native modules for Electron..." cd "$REPO_ROOT" diff --git a/apps/code/src/main/di/container.ts b/apps/code/src/main/di/container.ts index ba30b8fa3..bb08c3752 100644 --- a/apps/code/src/main/di/container.ts +++ b/apps/code/src/main/di/container.ts @@ -9,6 +9,7 @@ import { SuspensionRepositoryImpl } from "../db/repositories/suspension-reposito import { WorkspaceRepository } from "../db/repositories/workspace-repository"; import { WorktreeRepository } from "../db/repositories/worktree-repository"; import { DatabaseService } from "../db/service"; +import { ElectronUrlLauncher } from "../platform-adapters/electron-url-launcher"; import { AgentAuthAdapter } from "../services/agent/auth-adapter"; import { AgentService } from "../services/agent/service"; import { AppLifecycleService } from "../services/app-lifecycle/service"; @@ -53,6 +54,8 @@ export const container = new Container({ defaultScope: "Singleton", }); +container.bind(MAIN_TOKENS.UrlLauncher).to(ElectronUrlLauncher); + container.bind(MAIN_TOKENS.DatabaseService).to(DatabaseService); container .bind(MAIN_TOKENS.AuthPreferenceRepository) diff --git a/apps/code/src/main/di/tokens.ts b/apps/code/src/main/di/tokens.ts index 1b980855b..8dfe4afcd 100644 --- a/apps/code/src/main/di/tokens.ts +++ b/apps/code/src/main/di/tokens.ts @@ -5,6 +5,9 @@ * Never import this file from renderer code. */ export const MAIN_TOKENS = Object.freeze({ + // Platform ports (host-agnostic interfaces from @posthog/platform) + UrlLauncher: Symbol.for("Platform.UrlLauncher"), + // Stores SettingsStore: Symbol.for("Main.SettingsStore"), diff --git a/apps/code/src/main/platform-adapters/electron-url-launcher.ts b/apps/code/src/main/platform-adapters/electron-url-launcher.ts new file mode 100644 index 000000000..d89ac835d --- /dev/null +++ b/apps/code/src/main/platform-adapters/electron-url-launcher.ts @@ -0,0 +1,10 @@ +import type { IUrlLauncher } from "@posthog/platform/url-launcher"; +import { shell } from "electron"; +import { injectable } from "inversify"; + +@injectable() +export class ElectronUrlLauncher implements IUrlLauncher { + public async launch(url: string): Promise { + await shell.openExternal(url); + } +} diff --git a/apps/code/src/main/services/github-integration/service.ts b/apps/code/src/main/services/github-integration/service.ts index 2c3071049..dc8714c35 100644 --- a/apps/code/src/main/services/github-integration/service.ts +++ b/apps/code/src/main/services/github-integration/service.ts @@ -1,6 +1,7 @@ +import type { IUrlLauncher } from "@posthog/platform/url-launcher"; import { getCloudUrlFromRegion } from "@shared/utils/urls"; -import { shell } from "electron"; -import { injectable } from "inversify"; +import { inject, injectable } from "inversify"; +import { MAIN_TOKENS } from "../../di/tokens"; import { logger } from "../../utils/logger"; import type { CloudRegion, StartGitHubFlowOutput } from "./schemas"; @@ -8,6 +9,10 @@ const log = logger.scope("github-integration-service"); @injectable() export class GitHubIntegrationService { + constructor( + @inject(MAIN_TOKENS.UrlLauncher) private readonly urlLauncher: IUrlLauncher, + ) {} + public async startFlow( region: CloudRegion, projectId: number, @@ -18,7 +23,7 @@ export class GitHubIntegrationService { const authorizeUrl = `${cloudUrl}/api/environments/${projectId}/integrations/authorize/?kind=github&next=${encodeURIComponent(next)}`; log.info("Opening GitHub authorization URL in browser"); - await shell.openExternal(authorizeUrl); + await this.urlLauncher.launch(authorizeUrl); return { success: true }; } catch (error) { diff --git a/mprocs.yaml b/mprocs.yaml index e54206273..da56ee4d2 100644 --- a/mprocs.yaml +++ b/mprocs.yaml @@ -7,6 +7,7 @@ procs: - agent - git - enricher + - platform 2-agent: shell: 'node scripts/pnpm-run.mjs --filter agent run dev' @@ -14,6 +15,9 @@ procs: 3-git: shell: 'node scripts/pnpm-run.mjs --filter @posthog/git run dev' + platform: + shell: 'node scripts/pnpm-run.mjs --filter @posthog/platform run dev' + 4-enricher: shell: 'node scripts/pnpm-run.mjs --filter @posthog/enricher run dev' diff --git a/packages/platform/package.json b/packages/platform/package.json new file mode 100644 index 000000000..1d15e7f56 --- /dev/null +++ b/packages/platform/package.json @@ -0,0 +1,26 @@ +{ + "name": "@posthog/platform", + "version": "1.0.0", + "description": "Host-agnostic platform port interfaces. Zero runtime deps; implemented by per-host adapters (Electron, Node server, React Native, web).", + "type": "module", + "exports": { + "./url-launcher": { + "types": "./dist/url-launcher.d.ts", + "import": "./dist/url-launcher.js" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "typecheck": "tsc --noEmit", + "clean": "node ../../scripts/rimraf.mjs dist .turbo" + }, + "devDependencies": { + "tsup": "^8.5.1", + "typescript": "^5.5.0" + }, + "files": [ + "dist/**/*", + "src/**/*" + ] +} diff --git a/packages/platform/src/url-launcher.ts b/packages/platform/src/url-launcher.ts new file mode 100644 index 000000000..16edc5142 --- /dev/null +++ b/packages/platform/src/url-launcher.ts @@ -0,0 +1,3 @@ +export interface IUrlLauncher { + launch(url: string): Promise; +} diff --git a/packages/platform/tsconfig.json b/packages/platform/tsconfig.json new file mode 100644 index 000000000..c1475909f --- /dev/null +++ b/packages/platform/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "target": "ES2022", + "module": "ESNext", + "moduleDetection": "force", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/platform/tsup.config.ts b/packages/platform/tsup.config.ts new file mode 100644 index 000000000..e5fad5642 --- /dev/null +++ b/packages/platform/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/url-launcher.ts"], + format: ["esm"], + dts: true, + sourcemap: true, + clean: true, + splitting: false, + outDir: "dist", + target: "es2022", +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e9af6d86..cc3e99d73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -172,6 +172,9 @@ importers: '@posthog/quill': specifier: 0.1.0-alpha.7 version: 0.1.0-alpha.7(@types/react-dom@19.2.3(@types/react@19.2.11))(@types/react@19.2.11)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tailwindcss@4.2.2) + '@posthog/platform': + specifier: workspace:* + version: link:../../packages/platform '@posthog/shared': specifier: workspace:* version: link:../../packages/shared @@ -787,6 +790,15 @@ importers: specifier: ^2.1.8 version: 2.1.9(@types/node@25.2.0)(jsdom@26.1.0)(lightningcss@1.32.0)(msw@2.12.8(@types/node@25.2.0)(typescript@5.9.3))(terser@5.46.0) + packages/platform: + devDependencies: + tsup: + specifier: ^8.5.1 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + typescript: + specifier: ^5.5.0 + version: 5.9.3 + packages/shared: devDependencies: '@agentclientprotocol/sdk':