Skip to content

Commit 28938a5

Browse files
refactor(Sky): Simplify Electron workbench loading with Effect.runPromise
Refactor the Electron workbench (A3 approach) to use proper Effect-TS patterns and simplify script loading: - Update Bootstrap.ts and Mountain.astro to wrap runBootstrap() with Effect.runPromise() since runBootstrap() returns an Effect, not a Promise - Consolidate Layout.astro from 4 separate script tags into a single sequential await chain (Preload → Polyfills → Bootstrap → Workbench) - Simplify Workbench.ts to use direct /Static/Application/ import path with improved error handling for MIME errors - Add chatDebugTokenizer to Electron worker allowlist in Electron.astro - Add astro.config.ts build hooks for: 1. Patching Electron workbench.js baseUrl computation to use /Static/Application/ instead of vscode-file:// URIs 2. Injecting __name shim into extension host iframe HTML These changes enable the Electron workbench to function in Tauri by fixing asset path resolution and providing required globals.
1 parent 7210878 commit 28938a5

6 files changed

Lines changed: 165 additions & 124 deletions

File tree

Source/Workbench/Electron/Bootstrap.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
2-
* electron-bootstrap.ts - Effect-TS bootstrap script for Electron
2+
* Effect-TS bootstrap script for Electron workbench (A3)
33
*
4-
* This script runs the Wind Effect-TS bootstrap which initializes all
5-
* Wind services and verifies their health.
4+
* runBootstrap() returns an Effect, not a Promise.
5+
* Must be executed via Effect.runPromise().
66
*/
77

88
interface BootstrapStage {
@@ -23,35 +23,38 @@ console.log("[Electron] ===== Starting Wind Effect-TS bootstrap =====");
2323
try {
2424
const { runBootstrap } =
2525
await import("@codeeditorland/wind/Target/Effect/Bootstrap");
26+
const { Effect } = await import("effect");
2627

27-
console.log("[Electron] ✓ Bootstrap module loaded successfully");
28+
console.log("[Electron] Bootstrap module loaded successfully");
2829

29-
// Run the bootstrap with options
30-
const bootstrapResult: BootstrapResult = await runBootstrap({
31-
skipHealthCheck: false,
32-
debugMode: true,
33-
});
30+
// runBootstrap returns an Effect — run it via Effect.runPromise
31+
const BootstrapResult: BootstrapResult = await Effect.runPromise(
32+
runBootstrap({
33+
skipHealthCheck: false,
34+
debugMode: true,
35+
}),
36+
);
3437

35-
if (bootstrapResult.success) {
36-
console.log("[Electron] ✓ Bootstrap completed successfully");
38+
if (BootstrapResult.success) {
39+
console.log("[Electron] Bootstrap completed successfully");
3740
console.log(
3841
"[Electron] - Total duration:",
39-
bootstrapResult.totalDuration,
42+
BootstrapResult.totalDuration,
4043
"ms",
4144
);
4245

4346
// Log individual stage results
44-
bootstrapResult.stages.forEach(function (stage: BootstrapStage) {
45-
const status = stage.success ? "" : "";
47+
BootstrapResult.stages.forEach((Stage: BootstrapStage) => {
48+
const Status = Stage.success ? "OK" : "FAIL";
4649
console.log(
47-
`[Electron] - ${status} ${stage.stageName}: ${stage.duration}ms`,
50+
`[Electron] - ${Status} ${Stage.stageName}: ${Stage.duration}ms`,
4851
);
4952
});
5053
} else {
51-
console.error("[Electron] ✗ Bootstrap failed:", bootstrapResult.error);
54+
console.error("[Electron] Bootstrap failed:", BootstrapResult.error);
5255
}
53-
} catch (error: unknown) {
54-
console.error("[Electron] ✗ Failed to load/run bootstrap:", error);
56+
} catch (Error: unknown) {
57+
console.error("[Electron] Failed to load/run bootstrap:", Error);
5558
}
5659

5760
console.log("[Electron] ===== Wind bootstrap sequence complete =====");
Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,15 @@
11
---
22
/**
3-
* Electron.astro - Electron Workbench with Complete Polyfills (Approach A3)
3+
* Electron Workbench Layout (Approach A3)
44
*
5-
* Approach A3 Implementation:
6-
* - Uses Electron workbench (vs/code/electron-browser/workbench/workbench.js)
7-
* - Loads all Electron API polyfills to make browser act like Electron
8-
* - Loads Wind preload for VSCode-compatible globals
9-
* - Loads Effect-TS bootstrap for Wind services
5+
* Scripts must load sequentially:
6+
* 1. Wind Preload (Install) — sets window.vscode, process globals
7+
* 2. Polyfills — augments process/fs/ipc (depends on #1)
8+
* 3. Bootstrap — Effect-TS service init (depends on #1)
9+
* 4. Workbench — loads electron-browser/workbench.js (depends on #1-3)
1010
*
11-
* Complexity: High
12-
* Feature Coverage: 95%+
13-
* Trade-offs:
14-
* - Maximum VSCode functionality (if polyfills work correctly)
15-
* - High complexity with 7+ polyfills to maintain
16-
* - May have CSP issues with vscode-file:// protocol
17-
* - High maintenance burden as VSCode evolves
18-
*
19-
* Polyfills Loaded:
20-
* 1. ProcessPolyfill - Node.js process object
21-
* 2. FileProtocolShim - vscode-file:// protocol handling
22-
* 3. FileSystemPolyfill - fs module polyfill
23-
* 4. IPCRendererShim - Electron IPC communication
24-
* 5. ChildProcessPolyfill - child_process module
25-
* 6. NativeModulePolyfill - native module loading
26-
* 7. SharedProcessProxy - Shared process communication
27-
*
28-
* This approach attempts to use the full Electron workbench by providing
29-
* comprehensive polyfills for all Electron APIs.
11+
* All four are chained in a single <script type="module"> so that
12+
* each `await import()` completes before the next begins.
3013
*/
3114
3215
import NLS from "../NLS.astro";
@@ -35,23 +18,18 @@ import NLS from "../NLS.astro";
3518
<Fragment>
3619
<NLS />
3720

38-
<!-- Wind Preload Script - Required for VSCode workbench -->
21+
<!-- Sequential: Preload → Polyfills → Bootstrap → Workbench -->
3922
<script type="module">
23+
// 1. Wind Preload — must complete first (sets window.vscode)
4024
await import("./WindPreload.js");
41-
</script>
4225

43-
<!-- Electron API Polyfills -->
44-
<script type="module">
26+
// 2. Electron API Polyfills — depends on Wind process globals
4527
await import("./Polyfills.js");
46-
</script>
4728

48-
<!-- Wind Effect-TS Bootstrap Script -->
49-
<script type="module">
29+
// 3. Wind Effect-TS Bootstrap — depends on preload
5030
await import("./Bootstrap.js");
51-
</script>
5231

53-
<!-- Electron VSCode Workbench Script -->
54-
<script type="module">
32+
// 4. Electron Workbench — depends on everything above
5533
await import("./Workbench.js");
5634
</script>
5735
</Fragment>

Source/Workbench/Electron/Workbench.ts

Lines changed: 31 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,48 @@
11
/**
2-
* electron-workbench.ts - Electron workbench loading script for Electron
2+
* Electron workbench loading script (Approach A3)
33
*
4-
* This script loads the Electron VSCode workbench and verifies the initial state
5-
* after the workbench is loaded.
4+
* The Electron workbench is an async IIFE that:
5+
* 1. Reads window.vscode (preloadGlobals) for ipcRenderer + process + context
6+
* 2. Calls context.resolveConfiguration() -> INativeWindowConfiguration
7+
* 3. Computes baseUrl from configuration.appRoot
8+
* 4. Imports vs/workbench/workbench.desktop.main.js relative to baseUrl
9+
*
10+
* Wind's Install.ts provides all of these via the preload polyfill.
11+
* The _VSCODE_FILE_ROOT global is set by Base.astro before this script
12+
* runs, redirecting all VS Code asset loads to /Static/Application/.
13+
*
14+
* This path is served from Target/Static/Application/vs/ which is
15+
* populated by CopyVSCodeAssets in astro.config.ts (astro:build:done hook).
16+
* The electron-browser files are only available when Electron=true in the
17+
* build profile (debug-electron), which un-excludes electron-browser paths
18+
* in Output's ESBuild Exclude/Electron.ts and Exclude/Bootstrap.ts.
619
*/
720

8-
interface ElectronPolyfillsWindow extends Window {
9-
__ELECTRON_POLYFILLS_LOADED__?: unknown;
10-
require?: NodeRequire;
11-
}
12-
1321
console.log("[Electron] ===== Loading Electron VSCode workbench =====");
1422
console.log(
15-
"[Electron] Workbench path: vs/code/electron-browser/workbench/workbench.js",
16-
);
17-
console.log(
18-
"[Electron] Note: Electron workbench has more features than browser workbench",
23+
"[Electron] Workbench: vs/code/electron-browser/workbench/workbench.js",
1924
);
2025

2126
try {
22-
// Import the Electron workbench (NOT browser workbench)
23-
// Electron workbench uses Electron-specific APIs
24-
// @ts-ignore - Dynamic import for side effects, .d.ts file is not a module but the .js file exists at runtime
25-
// electron-browser workbench.js is not compiled in Output — only .d.ts exists.
26-
// When the Electron approach is activated, this path will need to be compiled first.
27-
// await import("/vs/code/electron-browser/workbench/workbench.js");
27+
const WorkbenchUrl =
28+
"/Static/Application/vs/code/electron-browser/workbench/workbench.js";
29+
30+
console.log("[Electron] Importing:", WorkbenchUrl);
31+
await import(/* @vite-ignore */ WorkbenchUrl);
2832

29-
console.log("[Electron] ✓ Electron workbench script loaded successfully");
33+
console.log("[Electron] Workbench script loaded successfully");
3034
console.log("[Electron] ===== Workbench load complete =====");
35+
} catch (Error: unknown) {
36+
console.error("[Electron] Failed to load Electron workbench:", Error);
3137

32-
// Log initial state after workbench load
33-
setTimeout(async () => {
34-
console.log("[Electron] ===== Post-workbench load state =====");
35-
console.log(
36-
"[Electron] window.vscode available:",
37-
typeof window.vscode !== "undefined",
38+
if (Error instanceof TypeError && String(Error).includes("MIME")) {
39+
console.error(
40+
"[Electron] MIME error: the file may not exist in Target/Static/Application/vs/",
3841
);
39-
console.log(
40-
"[Electron] Monaco editor available:",
41-
typeof window.monaco !== "undefined",
42+
console.error(
43+
"[Electron] Ensure Electron=true is set and CopyVSCodeAssets copies electron-browser/",
4244
);
43-
44-
// Check for Electron polyfills loaded flag (using bracket notation)
45-
const electronPolyfillsLoaded = (window as ElectronPolyfillsWindow)[
46-
"__ELECTRON_POLYFILLS_LOADED__"
47-
];
48-
console.log(
49-
"[Electron] Electron polyfills loaded:",
50-
typeof electronPolyfillsLoaded !== "undefined",
51-
);
52-
53-
// Check for Electron-specific globals (using bracket notation)
54-
if (typeof window.require !== "undefined") {
55-
console.log(
56-
"[Electron] ✓ Node.js require() available (via polyfill)",
57-
);
58-
try {
59-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
60-
void window.require("electron");
61-
console.log(
62-
"[Electron] ✓ Electron module accessible (polyfill)",
63-
);
64-
} catch {
65-
console.log(
66-
"[Electron] ℹ Electron module polyfill not fully functional",
67-
);
68-
}
69-
}
70-
}, 2000);
71-
} catch (error: unknown) {
72-
console.error("[Electron] ✗ Failed to load Electron workbench:", error);
73-
console.error("[Electron] This may be due to:");
74-
console.error("[Electron] 1. Incomplete or non-functional polyfills");
75-
console.error("[Electron] 2. CSP errors with vscode-file:// protocol");
76-
console.error("[Electron] 3. Missing Electron APIs");
77-
console.error("[Electron] 4. Browser environment limitations");
78-
console.error(
79-
"[Electron] Consider using Mountain.astro (A2) as recommended approach",
80-
);
45+
}
8146
}
8247

8348
console.log("[Electron] ===== Workbench load sequence complete =====");

Source/Workbench/Mountain.astro

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,17 @@ import NLS from "./NLS.astro";
122122
// Import from the Effect module which exports runBootstrap
123123
const { runBootstrap } =
124124
await import("@codeeditorland/wind/Target/Effect/Bootstrap");
125+
const { Effect } = await import("effect");
125126

126127
console.log("[Mountain] ✓ Bootstrap module loaded successfully");
127128

128-
// Run the bootstrap with options
129-
const bootstrapResult = await runBootstrap({
130-
skipHealthCheck: true, // Skip health check for initial test
131-
debugMode: true, // Enable debug logging
132-
});
129+
// runBootstrap returns an Effect — run it via Effect.runPromise
130+
const bootstrapResult = await Effect.runPromise(
131+
runBootstrap({
132+
skipHealthCheck: true,
133+
debugMode: true,
134+
}),
135+
);
133136

134137
if (bootstrapResult.success) {
135138
console.log("[Mountain] ✓ Bootstrap completed successfully");

Source/pages/Electron.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ const Worker = `/Worker.js?BASE_REMOTE=${encodeURIComponent(Astro.url.origin)}`;
118118
"WorkerApplication",
119119
"amdLoader",
120120
"cellRendererEditorText",
121+
"chatDebugTokenizer",
121122
"defaultWorkerFactory",
122123
"diffEditorWidget",
123124
"diffReview",

astro.config.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,97 @@ export default defineConfig({
179179
"[CopyVSCode] Step 4: Stripping CSS imports from Static/Application/vs/",
180180
);
181181
await StripCSSImports(Destination);
182+
183+
// Step 5: Patch Electron workbench.js baseUrl computation.
184+
// The original uses vscode-file://vscode-app/{appRoot}/out/
185+
// which doesn't exist in Tauri. Replace with the embedded
186+
// asset root /Static/Application/ using location.origin.
187+
const ElectronWorkbench = join(
188+
Destination,
189+
"code",
190+
"electron-browser",
191+
"workbench",
192+
"workbench.js",
193+
);
194+
try {
195+
const WB = await readFile(
196+
ElectronWorkbench,
197+
"utf-8",
198+
);
199+
// The original line uses fileUriFromPath which
200+
// produces vscode-file:// URIs that Tauri can't load.
201+
// Replace with direct URL from _VSCODE_FILE_ROOT.
202+
const SearchStr =
203+
"fileUriFromPath(configuration.appRoot";
204+
const Idx = WB.indexOf(SearchStr);
205+
let Patched = WB;
206+
if (Idx !== -1) {
207+
// Find the enclosing: const baseUrl = new URL(`${...}/out/`);
208+
// Replace entire line from "const baseUrl" to the semicolon
209+
const LineStart = WB.lastIndexOf(
210+
"const baseUrl",
211+
Idx,
212+
);
213+
const LineEnd = WB.indexOf(";", Idx) + 1;
214+
if (LineStart !== -1 && LineEnd > 0) {
215+
Patched =
216+
WB.slice(0, LineStart) +
217+
`const baseUrl = new URL(globalThis._VSCODE_FILE_ROOT || "/Static/Application/", globalThis.location?.origin || "https://tauri.localhost")` +
218+
WB.slice(LineEnd);
219+
}
220+
}
221+
if (Patched !== WB) {
222+
await writeFile(
223+
ElectronWorkbench,
224+
Patched,
225+
"utf-8",
226+
);
227+
console.log(
228+
"[CopyVSCode] Step 5: Patched electron workbench baseUrl",
229+
);
230+
} else {
231+
console.log(
232+
"[CopyVSCode] Step 5: Pattern not found in workbench.js (may already be patched)",
233+
);
234+
}
235+
} catch {
236+
// Electron workbench may not exist (Browser/Mountain build)
237+
}
238+
239+
// Step 6: Inject __name shim into extension host iframe.
240+
// The blob worker created by this HTML doesn't have the
241+
// __name global that Dependency/out files expect.
242+
const ExtHostIframe = join(
243+
Destination,
244+
"workbench",
245+
"services",
246+
"extensions",
247+
"worker",
248+
"webWorkerExtensionHostIframe.html",
249+
);
250+
try {
251+
const HTML = await readFile(ExtHostIframe, "utf-8");
252+
const NameShim =
253+
`var __defProp=Object.defineProperty;var __name=(t,v)=>__defProp(t,"name",{value:v,configurable:true});`;
254+
// Inject into the blob content (before the first globalThis._VSCODE line)
255+
const PatchedHTML = HTML.replace(
256+
"`/*extensionHostWorker*/`,",
257+
"`/*extensionHostWorker*/${NameShim}`,",
258+
);
259+
if (PatchedHTML !== HTML) {
260+
await writeFile(
261+
ExtHostIframe,
262+
PatchedHTML,
263+
"utf-8",
264+
);
265+
console.log(
266+
"[CopyVSCode] Step 6: Injected __name shim into ext host iframe",
267+
);
268+
}
269+
} catch {
270+
// May not exist yet
271+
}
272+
182273
console.log("[CopyVSCode] ✓ Assets ready in Target/");
183274
},
184275
},

0 commit comments

Comments
 (0)