Skip to content

Commit 52ab485

Browse files
1 parent cd2347f commit 52ab485

1 file changed

Lines changed: 203 additions & 7 deletions

File tree

astro.config.ts

Lines changed: 203 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,83 @@ export default defineConfig({
103103
Error,
104104
);
105105
}
106+
// Step 1b: Copy root-level files VS Code loads directly
107+
// (nls.messages.js, nls.messages.json, bootstrap-*.js, etc.)
108+
// into `Static/Application/`. These sit alongside `vs/` at
109+
// the Output root, and the workbench HTML references them at
110+
// URLs like `/Static/Application/nls.messages.js` — without
111+
// this step the webview 404s to the SPA fallback and the
112+
// browser reports `Unexpected token '<'` when trying to
113+
// parse the HTML as a JS module.
114+
const StaticApplicationDir = join(
115+
TargetDir,
116+
"Static",
117+
"Application",
118+
);
119+
120+
const OutputRoot = resolve(
121+
process.cwd(),
122+
"../Output/Target/Microsoft/VSCode",
123+
);
124+
125+
const DependencyOutBuild = resolve(
126+
process.cwd(),
127+
"../../Dependency/Microsoft/Dependency/Editor/out-build",
128+
);
129+
130+
const DependencyOut = resolve(
131+
process.cwd(),
132+
"../../Dependency/Microsoft/Dependency/Editor/out",
133+
);
134+
135+
// Whitelist only the root files the webview actually loads.
136+
// tsconfig/*, *.map, typings/, date/ etc. stay out of the
137+
// shipped bundle even if the upstream ships them.
138+
const RootFilesToCopy = [
139+
"nls.keys.json",
140+
"nls.messages.js",
141+
"nls.messages.json",
142+
"nls.metadata.json",
143+
"bootstrap-esm.js",
144+
"bootstrap-import.js",
145+
"bootstrap-meta.js",
146+
];
147+
148+
console.log(
149+
`[CopyVSCode] Step 1b: Copying ${RootFilesToCopy.length} root-level VSCode files → Static/Application/`,
150+
);
151+
152+
for (const File of RootFilesToCopy) {
153+
// Primary: Output. Fallbacks: Dependency/out-build,
154+
// then Dependency/out. The .env.Land tier set
155+
// controls which trees have been populated at any
156+
// given time; try each until one resolves.
157+
const Candidates = [
158+
join(OutputRoot, File),
159+
join(DependencyOutBuild, File),
160+
join(DependencyOut, File),
161+
];
162+
163+
let Copied = false;
164+
for (const Source of Candidates) {
165+
try {
166+
await copyFile(
167+
Source,
168+
join(StaticApplicationDir, File),
169+
);
170+
Copied = true;
171+
break;
172+
} catch {
173+
// Try next candidate
174+
}
175+
}
176+
177+
if (!Copied) {
178+
console.warn(
179+
`[CopyVSCode] Step 1b: ${File} not found in Output or Dependency — skipping`,
180+
);
181+
}
182+
}
106183
// Step 2: Supplement with tsc-compiled files from Dependency
107184
// (fills gaps like workbench.web.main.js missing from Output)
108185
const DependencySource = resolve(
@@ -325,6 +402,68 @@ export default defineConfig({
325402
console.log(
326403
"[CopyVSCode] Step 7: Replaced ElectronIPCMainProcessService with TauriMainProcessService (ESM wrapper)",
327404
);
405+
406+
// Step 7b: Replace sharedProcessService.js too.
407+
// The Extensions sidebar queries
408+
// `sharedProcessService.getChannel('extensions')`
409+
// (see `extensionManagementServerService.ts:35`
410+
// in VS Code's electron-browser source — it's the
411+
// channel ExtensionManagementChannelClient wraps).
412+
// Land has no Electron shared process, so the
413+
// shipped SharedProcessService tries
414+
// `acquirePort(...)` → hangs forever → the
415+
// @builtin sidebar stays empty despite 94
416+
// extensions being scanned. Substituting
417+
// `TauriMainProcessService` here routes every
418+
// `getChannel(name)` through the same
419+
// `ChannelRouteMap` that already backs the main
420+
// process — `extensions` maps to Mountain's
421+
// `extensions:*` handler family (getAll /
422+
// getInstalled / scanSystemExtensions / …).
423+
const SharedProcessDir = join(
424+
Destination,
425+
"workbench",
426+
"services",
427+
"sharedProcess",
428+
"electron-browser",
429+
);
430+
try {
431+
// The original service exports
432+
// `{ SharedProcessService }` with a ctor
433+
// `(windowId, logService)` and a
434+
// `notifyRestored()` method. Our replacement
435+
// extends TauriMainProcessService (which
436+
// provides `getChannel`) and stubs the
437+
// restore-barrier API as a no-op — Land
438+
// doesn't gate channel access on window
439+
// restoration.
440+
await writeFile(
441+
join(SharedProcessDir, "sharedProcessService.js"),
442+
[
443+
`import { TauriMainProcessService } from '../../../../platform/ipc/electron-browser/TauriMainProcessService.js';`,
444+
``,
445+
`class SharedProcessService extends TauriMainProcessService {`,
446+
` constructor(windowId, _logService) { super(windowId); }`,
447+
` notifyRestored() { /* Land has no shared process; channels go direct to Mountain */ }`,
448+
` async getConnection() { return this; /* self-satisfy the IPC Client shape */ }`,
449+
`}`,
450+
``,
451+
`export { SharedProcessService };`,
452+
`export default SharedProcessService;`,
453+
``,
454+
].join("\n"),
455+
"utf-8",
456+
);
457+
458+
console.log(
459+
"[CopyVSCode] Step 7b: Replaced SharedProcessService with TauriMainProcessService-backed shim (unblocks @builtin Extensions sidebar)",
460+
);
461+
} catch (Error) {
462+
console.warn(
463+
"[CopyVSCode] Step 7b: SharedProcessService replacement failed:",
464+
Error,
465+
);
466+
}
328467
} catch (Error) {
329468
console.warn(
330469
"[CopyVSCode] Step 7: TauriMainProcessService replacement failed:",
@@ -363,7 +502,27 @@ export default defineConfig({
363502
"const configuration2 = await resolveWindowConfiguration()",
364503
`performance.mark("land:wb:resolveConfig");const configuration2 = await resolveWindowConfiguration()`,
365504
);
366-
// 8b-fix: Ensure profile URIs exist for reviveProfile().
505+
// 8b-fix: Ensure profile URIs exist for reviveProfile(),
506+
// and backfill every init-data field VS Code assumes is
507+
// non-null. Each missing field produced a concrete
508+
// PostHog error in the 2026-04-21 report (counts below
509+
// from a single boot session):
510+
//
511+
// colorScheme → nativeHostColorSchemeService
512+
// destructures {highContrast,dark}
513+
// from the scheme literal (14×)
514+
// detectedProfiles → terminalPlatformConfiguration
515+
// .map over the profile list (25×)
516+
// externalTerminal → .windows / .osx / .linux pane (5×)
517+
// backupPath → base/common/path rejects undefined (33×)
518+
// perfMarks → timerService .marks.filter (14×)
519+
// watcher / utilityProcess → destructure .reason (14×)
520+
// colorScheme initial → same service, initial read (14×)
521+
//
522+
// Providing neutral defaults here means the workbench
523+
// boots cleanly even when Mountain hasn't yet populated
524+
// a field; later, when Mountain DOES send a real value
525+
// via Tauri IPC, the assignment overwrites our default.
367526
Content = Content.replace(
368527
"setupNLS(configuration2)",
369528
[
@@ -378,6 +537,26 @@ export default defineConfig({
378537
`_fix(configuration2.profiles.profile);`,
379538
`if(Array.isArray(configuration2.profiles.all))configuration2.profiles.all.forEach(_fix);`,
380539
`}`,
540+
// Backfill colorScheme so nativeHostColorSchemeService's
541+
// `initial.highContrast` read (nativeHostColorSchemeService.ts:46)
542+
// and the subsequent promise-resolved `update({highContrast,dark})`
543+
// never destructure from undefined.
544+
`if(!configuration2.colorScheme)configuration2.colorScheme={dark:false,highContrast:false};`,
545+
// Backfill detectedProfiles so terminalPlatformConfiguration's
546+
// .map call doesn't explode on cold boots where the terminal
547+
// profile detection hasn't completed yet.
548+
`if(!Array.isArray(configuration2.detectedProfiles))configuration2.detectedProfiles=[];`,
549+
// Backfill external-terminal per-OS config so
550+
// externalTerminal.electron-browser doesn't destructure
551+
// .windows / .osx / .linux from undefined.
552+
`if(!configuration2.externalTerminal)configuration2.externalTerminal={windows:{},osx:{},linux:{}};`,
553+
// Backfill perfMarks so timerService's marks.filter()
554+
// doesn't fire on a missing array.
555+
`if(!Array.isArray(configuration2.perfMarks))configuration2.perfMarks=[];`,
556+
// Backfill an empty backupPath so base/common/path
557+
// validators that assume a string get one. Empty
558+
// string is also what the browser workbench carries.
559+
`if(typeof configuration2.backupPath!=="string")configuration2.backupPath="";`,
381560
`performance.mark("land:wb:setupNLS");setupNLS(configuration2)`,
382561
].join(""),
383562
);
@@ -389,10 +568,30 @@ export default defineConfig({
389568
"return { result: result2, configuration: configuration2 }",
390569
`performance.mark("land:wb:importDone");return { result: result2, configuration: configuration2 }`,
391570
);
392-
// 8c: Wrap result.main(configuration) with try/catch + await
571+
// 8c: Wrap result.main(configuration) with try/catch + await,
572+
// bookended by Atom H1c diagnostic:log invokes that route to
573+
// Mountain.dev.log. Confirms whether each post-nav page reload
574+
// enters result.main, what workspace the new page reads, and
575+
// how many extensions survive to the workbench registry after
576+
// boot completes.
393577
Content = Content.replace(
394578
"result.main(configuration)",
395-
`performance.mark("land:wb:main");try{await result.main(configuration);performance.mark("land:wb:mainDone")}catch(_e){performance.mark("land:wb:mainError")}`,
579+
[
580+
`performance.mark("land:wb:main");`,
581+
`try{`,
582+
`const _L=(tag,msg,...extras)=>{try{const I=globalThis.__TAURI__?.core?.invoke??globalThis.__TAURI__?.invoke;if(typeof I==="function")I("MountainIPCInvoke",{method:"diagnostic:log",params:[tag,msg,...extras]});}catch{}};`,
583+
`_L("wb:boot","pre-main href="+location.href+" search="+location.search,{folderUri:configuration?.folderUri??null,workspace:configuration?.workspace??null,windowId:configuration?.windowId??null});`,
584+
`await result.main(configuration);`,
585+
`performance.mark("land:wb:mainDone");`,
586+
`_L("wb:boot","post-main completed","reloadCount="+(performance.getEntriesByType?performance.getEntriesByType("navigation")?.length??-1:-1));`,
587+
// Probe the workbench's DI container for the ExtensionService
588+
// one event loop later (give microtasks time to settle) and
589+
// report how many extensions registered + active.
590+
`setTimeout(()=>{try{const W=globalThis;const MP=W.__monacoPostBoot??null;const reg=(W.monaco&&W.monaco.extensions)||null;_L("wb:boot","ext-registry-probe",{monacoExtensionsApi:!!reg,postBootHook:!!MP,pluginApiKeys:reg?Object.keys(reg):null});}catch(e){_L("wb:boot","ext-registry-probe-error",String(e));}},1500);`,
591+
`}catch(_e){performance.mark("land:wb:mainError");`,
592+
`try{const I=globalThis.__TAURI__?.core?.invoke??globalThis.__TAURI__?.invoke;if(typeof I==="function")I("MountainIPCInvoke",{method:"diagnostic:log",params:["wb:boot","main-threw",String(_e?.message||_e),String(_e?.stack||"").slice(0,400)]});}catch{}`,
593+
`}`,
594+
].join(""),
396595
);
397596

398597
await writeFile(WorkbenchJS, Content, "utf-8");
@@ -556,10 +755,7 @@ export default defineConfig({
556755
"node_modules",
557756
Pkg,
558757
);
559-
const FallbackSource = resolve(
560-
VSCodeNodeModules,
561-
Pkg,
562-
);
758+
const FallbackSource = resolve(VSCodeNodeModules, Pkg);
563759
const Destination = join(
564760
TargetDir,
565761
"Static/Application/node_modules",

0 commit comments

Comments
 (0)