Skip to content

Commit 7886acf

Browse files
newbe36524Copilot
andcommitted
fix(runtime): align bundled service launch paths
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 505e20c commit 7886acf

5 files changed

Lines changed: 136 additions & 44 deletions

File tree

src/main/code-server-manager.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import {
66
} from './code-server-runtime.js';
77
import type DependencyManagementService from './dependency-management-service.js';
88
import type { ConfigManager } from './config.js';
9+
import { getNodeExecutableRelativePath } from './embedded-node-runtime-config.js';
910
import { buildPm2MajorHomePaths } from './portable-toolchain-paths.js';
10-
import { ensurePm2HomeAlias } from './pm2-home-alias.js';
11+
import { ensureNoSpacePathAlias, ensurePm2HomeAlias } from './pm2-home-alias.js';
1112
import {
1213
injectCodeServerRuntimeEnv,
1314
injectManagedCliPathEnv,
@@ -297,7 +298,7 @@ export class CodeServerManager {
297298
}
298299

299300
const snapshot = await this.getRuntimeSnapshot();
300-
if (!snapshot.wrapperPath && !(process.platform === 'win32' && snapshot.entryScriptPath)) {
301+
if (!snapshot.wrapperPath && !snapshot.entryScriptPath) {
301302
throw new Error(snapshot.message ?? 'Vendored code-server runtime is not ready.');
302303
}
303304

@@ -310,8 +311,7 @@ export class CodeServerManager {
310311
const runtimeEnv = injectCodeServerRuntimeEnv(pm2.env, this.pathManager, {
311312
platform: process.platform,
312313
});
313-
const nodeExecutablePath = pm2.env.HAGICODE_NODE_EXECUTABLE_PATH ?? pm2.env.HAGICODE_DOTNET_EXE;
314-
const launchSpec = this.resolveLaunchSpec(snapshot, nodeExecutablePath);
314+
const launchSpec = await this.resolveLaunchSpec(snapshot);
315315
await this.renderEcosystem(paths, launchSpec, runtimeEnv.env);
316316

317317
if (action === 'start') {
@@ -569,10 +569,9 @@ export class CodeServerManager {
569569
}
570570
}
571571

572-
private resolveLaunchSpec(
572+
private async resolveLaunchSpec(
573573
runtime: VendoredRuntimeStatusSnapshot,
574-
nodeExecutablePath: string | undefined,
575-
): CodeServerLaunchSpec {
574+
): Promise<CodeServerLaunchSpec> {
576575
const args = [
577576
'--bind-addr',
578577
`127.0.0.1:${this.getConfig().port}`,
@@ -585,24 +584,46 @@ export class CodeServerManager {
585584
'--disable-telemetry',
586585
];
587586

588-
if (process.platform === 'win32' && runtime.entryScriptPath && nodeExecutablePath) {
587+
const runtimeRoot = await ensureNoSpacePathAlias(
588+
this.pathManager.getCodeServerRuntimeRoot(),
589+
'code-server-runtime',
590+
);
591+
const portableToolchainRoot = await ensureNoSpacePathAlias(
592+
this.pathManager.getPortableToolchainRoot(),
593+
'desktop-toolchain',
594+
);
595+
596+
if (runtime.entryScriptPath) {
597+
const aliasedEntryScriptPath = path.join(
598+
runtimeRoot,
599+
path.relative(this.pathManager.getCodeServerRuntimeRoot(), runtime.entryScriptPath),
600+
);
601+
const nodeExecutablePath = path.join(
602+
portableToolchainRoot,
603+
getNodeExecutableRelativePath(process.platform),
604+
);
589605
return {
590606
script: nodeExecutablePath,
591-
args: [runtime.entryScriptPath, ...args],
607+
args: [aliasedEntryScriptPath, ...args],
592608
interpreterNone: true,
593-
cwd: this.pathManager.getCodeServerRuntimeRoot(),
609+
cwd: runtimeRoot,
594610
};
595611
}
596612

597613
if (!runtime.wrapperPath) {
598614
throw new Error(runtime.message ?? 'Vendored code-server runtime is not ready.');
599615
}
600616

617+
const aliasedWrapperPath = path.join(
618+
runtimeRoot,
619+
path.relative(this.pathManager.getCodeServerRuntimeRoot(), runtime.wrapperPath),
620+
);
621+
601622
return {
602-
script: runtime.wrapperPath,
623+
script: aliasedWrapperPath,
603624
args,
604625
interpreterNone: true,
605-
cwd: this.pathManager.getCodeServerRuntimeRoot(),
626+
cwd: runtimeRoot,
606627
};
607628
}
608629

src/main/hagiscript-runtime-context.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
22
import os from 'node:os';
33
import path from 'node:path';
44
import type { DependencyManagementService } from './dependency-management-service.js';
5+
import { ensureNoSpacePathAlias } from './pm2-home-alias.js';
56
import type { PathManager } from './path-manager.js';
67
import type { ActiveRuntimeDescriptor } from '../types/distribution-mode.js';
78

@@ -77,6 +78,15 @@ export class HagiscriptRuntimeContextResolver {
7778
const runtimeHome = path.resolve(this.pathManager.getRuntimeProgramHome());
7879
const runtimeDataRoot = path.resolve(this.pathManager.getRuntimeDataHome());
7980
const runtimeRoot = path.resolve(input.activeRuntime.rootPath);
81+
const aliasedRuntimeHome = await ensureNoSpacePathAlias(runtimeHome, 'desktop-runtime-home');
82+
const aliasedRuntimeRoot = await ensureNoSpacePathAlias(runtimeRoot, 'desktop-active-runtime-root');
83+
const dotnetRuntimeRoot = path.resolve(this.pathManager.getEmbeddedRuntimeRoot(this.pathManager.getCurrentPlatform()));
84+
const aliasedDotnetRuntimeRoot = await ensureNoSpacePathAlias(dotnetRuntimeRoot, 'desktop-dotnet-runtime-root');
85+
const serviceWorkingDirectory = path.resolve(input.serviceWorkingDirectory);
86+
const aliasedServiceWorkingDirectory = await ensureNoSpacePathAlias(
87+
serviceWorkingDirectory,
88+
'desktop-service-working-directory',
89+
);
8090
const runtimeLogsDirectory = path.join(runtimeDataRoot, 'logs');
8191
const runtimeStateFilePath = path.join(runtimeDataRoot, 'state.json');
8292
const serviceDataHome = path.join(runtimeDataRoot, 'components', SERVER_RUNTIME_DATA_DIR);
@@ -90,14 +100,17 @@ export class HagiscriptRuntimeContextResolver {
90100
manifestPath,
91101
`${JSON.stringify(
92102
this.buildManifestOverride({
93-
runtimeRoot,
94-
runtimeHome,
103+
runtimeRoot: aliasedRuntimeRoot,
104+
runtimeHome: aliasedRuntimeHome,
95105
runtimeDataRoot,
96106
npmPrefix: path.resolve(hagiscriptContext.environment.npmGlobalPrefix),
97107
hagiscriptPackageRoot,
98-
dotnetRuntimeRoot: path.resolve(this.pathManager.getEmbeddedRuntimeRoot(this.pathManager.getCurrentPlatform())),
99-
servicePayloadPath: path.resolve(input.servicePayloadPath),
100-
serviceWorkingDirectory: path.resolve(input.serviceWorkingDirectory),
108+
dotnetRuntimeRoot: aliasedDotnetRuntimeRoot,
109+
servicePayloadPath: path.join(
110+
aliasedServiceWorkingDirectory,
111+
path.basename(path.resolve(input.servicePayloadPath)),
112+
),
113+
serviceWorkingDirectory: aliasedServiceWorkingDirectory,
101114
serviceEnv: input.serviceEnv ?? {},
102115
}),
103116
null,
@@ -111,8 +124,8 @@ export class HagiscriptRuntimeContextResolver {
111124
hagiscriptExecutablePath,
112125
hagiscriptPackageRoot,
113126
commandEnv: hagiscriptContext.commandEnv,
114-
runtimeRoot,
115-
runtimeHome,
127+
runtimeRoot: aliasedRuntimeRoot,
128+
runtimeHome: aliasedRuntimeHome,
116129
runtimeDataRoot,
117130
runtimeLogsDirectory,
118131
runtimeStateFilePath,
@@ -123,8 +136,11 @@ export class HagiscriptRuntimeContextResolver {
123136
manifestPath,
124137
manifestDirectory,
125138
appName: 'hagicode-server',
126-
servicePayloadPath: path.resolve(input.servicePayloadPath),
127-
serviceWorkingDirectory: path.resolve(input.serviceWorkingDirectory),
139+
servicePayloadPath: path.join(
140+
aliasedServiceWorkingDirectory,
141+
path.basename(path.resolve(input.servicePayloadPath)),
142+
),
143+
serviceWorkingDirectory: aliasedServiceWorkingDirectory,
128144
cleanup: async () => {
129145
await fs.rm(manifestDirectory, { recursive: true, force: true });
130146
},

src/main/non-interactive-runtime-lifecycle.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { CodeServerManager } from './code-server-manager.js';
2121
import OmniRouteManager from './omniroute-manager.js';
2222
import { CODE_SERVER_PROCESS_NAME } from '../types/code-server-management.js';
2323
import { OMNIROUTE_PROCESS_NAME } from '../types/omniroute-management.js';
24+
import type { ActiveRuntimeDescriptor } from '../types/distribution-mode.js';
2425

2526
const DEFAULT_VERIFICATION_TIMEOUT_MS = 30_000;
2627
const DEFAULT_POLL_INTERVAL_MS = 1_000;
@@ -204,6 +205,17 @@ function createEmptyBackendReport(): BackendLifecycleReport {
204205
};
205206
}
206207

208+
function buildEmbeddedRuntimeDescriptor(pathManager: PathManager): ActiveRuntimeDescriptor {
209+
return {
210+
kind: 'portable-fixed',
211+
rootPath: pathManager.getEmbeddedRuntimeRoot(),
212+
versionId: `embedded-${app.getVersion()}-${pathManager.getCurrentPlatform()}`,
213+
versionLabel: app.getVersion(),
214+
displayName: 'embedded-runtime',
215+
isReadOnly: true,
216+
};
217+
}
218+
207219
async function verifyCodeServerLifecycle(input: {
208220
manager: CodeServerManager;
209221
pm2Command: string | null;
@@ -373,7 +385,9 @@ async function verifyBackendLifecycle(input: {
373385
const packageSourceConfigManager = new PackageSourceConfigManager(input.configManager.getStore() as unknown as Store);
374386
const versionManager = new VersionManager(dependencyManager, packageSourceConfigManager);
375387
const distributionModeState = await versionManager.initializeDistributionMode();
376-
const activeRuntime = distributionModeState.activeRuntime ?? await versionManager.getActiveRuntimeDescriptor();
388+
const activeRuntime = distributionModeState.activeRuntime
389+
?? await versionManager.getActiveRuntimeDescriptor()
390+
?? buildEmbeddedRuntimeDescriptor(input.pathManager);
377391

378392
report.activeRuntimeRoot = activeRuntime?.rootPath ?? null;
379393
if (!activeRuntime) {

src/main/omniroute-manager.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import { app, shell } from 'electron';
66
import log from 'electron-log';
77
import { ConfigManager } from './config.js';
88
import type DependencyManagementService from './dependency-management-service.js';
9+
import { getNodeExecutableRelativePath } from './embedded-node-runtime-config.js';
910
import { buildOmniRouteDependencyRemediation } from './omniroute-remediation.js';
1011
import { inspectVendoredOmniRouteRuntime } from './omniroute-runtime.js';
1112
import { Pm2DotnetManager, resolvePm2LaunchPlan } from './pm2-dotnet-manager.js';
12-
import { ensurePm2HomeAlias } from './pm2-home-alias.js';
13+
import { ensureNoSpacePathAlias, ensurePm2HomeAlias } from './pm2-home-alias.js';
1314
import { injectManagedCliPathEnv, resolvePathEnvKey } from './portable-toolchain-env.js';
1415
import { buildPm2MajorHomePaths } from './portable-toolchain-paths.js';
1516
import { resolveCommandLaunch } from './toolchain-launch.js';
@@ -482,13 +483,12 @@ export class OmniRouteManager {
482483
throw new Error(remediation.message);
483484
}
484485
const pm2ExecutablePath = pm2Context.executablePath;
485-
const nodeExecutablePath = pm2Context.environment.node.executablePath;
486-
if (!pm2ExecutablePath || !nodeExecutablePath || !runtime.entryScriptPath) {
486+
if (!pm2ExecutablePath || !runtime.entryScriptPath) {
487487
throw new Error('Desktop-managed OmniRoute dependencies are unavailable.');
488488
}
489489

490490
await this.renderEnvironment(paths);
491-
const launchSpec = this.resolveVendoredRuntimeLaunchSpec(paths, nodeExecutablePath, runtime.entryScriptPath);
491+
const launchSpec = await this.resolveVendoredRuntimeLaunchSpec(runtime);
492492
await this.renderEcosystemConfig(paths, launchSpec, managedPm2Env);
493493

494494
if (action === 'start') {
@@ -658,16 +658,27 @@ export class OmniRouteManager {
658658
}
659659
}
660660

661-
private resolveVendoredRuntimeLaunchSpec(
662-
paths: OmniRouteManagedPaths,
663-
nodeExecutablePath: string,
664-
entryScriptPath: string,
665-
): ManagedCliLaunchSpec {
661+
private async resolveVendoredRuntimeLaunchSpec(
662+
runtime: VendoredRuntimeStatusSnapshot,
663+
): Promise<ManagedCliLaunchSpec> {
664+
const runtimeRoot = await ensureNoSpacePathAlias(
665+
this.pathManager.getOmniRouteRuntimeRoot(),
666+
'omniroute-runtime',
667+
);
668+
const bundledNodeExecutablePath = path.join(
669+
runtimeRoot,
670+
'toolchain',
671+
getNodeExecutableRelativePath(process.platform),
672+
);
673+
const entryScriptPath = path.join(
674+
runtimeRoot,
675+
path.relative(this.pathManager.getOmniRouteRuntimeRoot(), runtime.entryScriptPath ?? ''),
676+
);
666677
return {
667-
script: stripWrappingQuotes(nodeExecutablePath),
678+
script: stripWrappingQuotes(bundledNodeExecutablePath),
668679
args: [entryScriptPath, 'serve', '--no-open'],
669680
interpreterNone: true,
670-
cwd: paths.root,
681+
cwd: runtimeRoot,
671682
};
672683
}
673684

src/main/pm2-home-alias.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,14 @@ function sanitizeSegment(value: string): string {
66
return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'pm2';
77
}
88

9-
export async function ensurePm2HomeAlias(targetPath: string, label: string): Promise<string> {
10-
await fs.mkdir(targetPath, { recursive: true });
11-
if (!targetPath.includes(' ')) {
12-
return targetPath;
13-
}
14-
15-
const aliasRoot = process.platform === 'win32'
16-
? path.join(path.parse(targetPath).root, 'hagicode-desktop-pm2-home')
17-
: path.join('/tmp', 'hagicode-desktop-pm2-home');
9+
async function ensureSymlinkAlias(input: {
10+
targetPath: string;
11+
label: string;
12+
aliasRoot: string;
13+
type: 'dir' | 'file';
14+
}): Promise<string> {
15+
const { targetPath, label, aliasRoot, type } = input;
1816
await fs.mkdir(aliasRoot, { recursive: true });
19-
2017
const digest = createHash('sha256').update(targetPath).digest('hex').slice(0, 12);
2118
const aliasPath = path.join(aliasRoot, `${sanitizeSegment(label)}-${digest}`);
2219

@@ -33,6 +30,39 @@ export async function ensurePm2HomeAlias(targetPath: string, label: string): Pro
3330
}
3431

3532
await fs.rm(aliasPath, { recursive: true, force: true });
36-
await fs.symlink(targetPath, aliasPath, process.platform === 'win32' ? 'junction' : 'dir');
33+
await fs.symlink(targetPath, aliasPath, process.platform === 'win32' ? 'junction' : type);
3734
return aliasPath;
3835
}
36+
37+
export async function ensureNoSpacePathAlias(targetPath: string, label: string): Promise<string> {
38+
if (process.platform === 'win32') {
39+
return targetPath;
40+
}
41+
if (!targetPath.includes(' ')) {
42+
return targetPath;
43+
}
44+
45+
const stats = await fs.lstat(targetPath);
46+
return ensureSymlinkAlias({
47+
targetPath,
48+
label,
49+
aliasRoot: path.join('/tmp', 'hagicode-desktop-path-alias'),
50+
type: stats.isDirectory() ? 'dir' : 'file',
51+
});
52+
}
53+
54+
export async function ensurePm2HomeAlias(targetPath: string, label: string): Promise<string> {
55+
await fs.mkdir(targetPath, { recursive: true });
56+
if (!targetPath.includes(' ')) {
57+
return targetPath;
58+
}
59+
60+
return ensureSymlinkAlias({
61+
targetPath,
62+
label,
63+
aliasRoot: process.platform === 'win32'
64+
? path.join(path.parse(targetPath).root, 'hagicode-desktop-pm2-home')
65+
: path.join('/tmp', 'hagicode-desktop-pm2-home'),
66+
type: 'dir',
67+
});
68+
}

0 commit comments

Comments
 (0)