You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/04 05:04:35 UTC

(maka) branch fix/windows-local-remote-access updated: fix(desktop): launch Windows local setup safely

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch fix/windows-local-remote-access
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/fix/windows-local-remote-access by this push:
     new 6c3cc86be2 fix(desktop): launch Windows local setup safely
6c3cc86be2 is described below

commit 6c3cc86be2012a47a4f8565bfdb7d0e750b808fd
Author: M4n5ter <m4...@gmail.com>
AuthorDate: Fri Sep 4 13:04:26 2026 +0800

    fix(desktop): launch Windows local setup safely
---
 .../src/main/runtime-host-local-operator.ts        | 123 ++++++++++++++-------
 .../src/renderer/locales/settings-projects-copy.ts |   3 +
 .../settings/runtime-host-profiles-section.tsx     |  12 +-
 3 files changed, 98 insertions(+), 40 deletions(-)

diff --git a/apps/desktop/src/main/runtime-host-local-operator.ts b/apps/desktop/src/main/runtime-host-local-operator.ts
index 3bda1aef6b..dd6175d9c8 100644
--- a/apps/desktop/src/main/runtime-host-local-operator.ts
+++ b/apps/desktop/src/main/runtime-host-local-operator.ts
@@ -20,7 +20,7 @@
 import { spawn, type ChildProcess } from 'node:child_process';
 import { mkdtemp, rm, rmdir, stat } from 'node:fs/promises';
 import { tmpdir } from 'node:os';
-import { dirname, join } from 'node:path';
+import { delimiter, dirname, isAbsolute, join } from 'node:path';
 import { redactSecrets } from '@maka/core/redaction';
 import {
   DEFAULT_PROCESS_TERMINATION_GRACE_MS,
@@ -238,10 +238,14 @@ export function createDesktopRuntimeHostLocalOperator(input: {
       if (closed) throw new Error('Local Runtime Host operator is closed');
       setup.signal?.throwIfAborted();
       const setupPackage = await resolveLocalSetupPackage(setup.setupPackage);
-      const command = runtimeHostLocalSetupCommand({
-        ...setup,
-        packageSpecifier: setupPackage.specifier,
-      });
+      const environment = input.environment ?? process.env;
+      const command = await resolveLocalNpmCommand(
+        runtimeHostLocalSetupCommand({
+          ...setup,
+          packageSpecifier: setupPackage.specifier,
+        }),
+        environment,
+      );
       const workingDirectory = await mkdtemp(join(tmpdir(), 'maka-runtime-host-local-setup-'));
       try {
         if (closed) throw new Error('Local Runtime Host operator is closed');
@@ -251,7 +255,7 @@ export function createDesktopRuntimeHostLocalOperator(input: {
           command,
           cwd: workingDirectory,
           environment: {
-            ...(input.environment ?? process.env),
+            ...environment,
             ...(setupPackage.integrity
               ? {
                   [RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV]:
@@ -387,45 +391,49 @@ export function createDesktopRuntimeHostLocalOperator(input: {
         active,
       }).then((frame) => requireServiceFrame(frame, command.action));
     },
-    runUpdate(command, onProgress) {
+    async runUpdate(command, onProgress) {
       if (closed) throw new Error('Local Runtime Host operator is closed');
       if (!command.target.deploymentId) {
         return Promise.reject(new Error('Runtime Host update requires a deployment generation'));
       }
       const setupPackage = resolveLocalSetupPackage(command.setupPackage);
       const targetVersion = runtimeHostSetupPackageVersion(command.setupPackage);
+      const environment = {
+        ...(input.environment ?? process.env),
+        ...(setupPackage.integrity
+          ? { [RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV]: setupPackage.integrity }
+          : {}),
+        [RUNTIME_HOST_OPERATOR_PROJECT_DIRECTORY_CONFIGURATION_REQUEST_ENV]: '1',
+      };
       return runServiceFrameProcess({
-        command: {
-          executable: 'npm',
-          args: [
-            'exec',
-            '--yes',
-            '--package',
-            setupPackage.specifier,
-            '--',
-            'maka',
-            'runtime-host',
-            'service',
-            'update',
-            '--framed',
-            ...(targetVersion ? ['--target', targetVersion] : []),
-            '--managed-root-id',
-            command.target.rootId,
-            ...(command.expectedHost
-              ? ['--expected-host-json', JSON.stringify(command.expectedHost)]
-              : []),
-            ...managedTargetArgs(command.target),
-            ...(command.allowManualUpdate && targetVersion ? ['--allow-manual-update'] : []),
-            ...(command.allowInterruptActiveTasks ? ['--allow-interrupt-active-tasks'] : []),
-          ],
-        },
-        environment: {
-          ...(input.environment ?? process.env),
-          ...(setupPackage.integrity
-            ? { [RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV]: setupPackage.integrity }
-            : {}),
-          [RUNTIME_HOST_OPERATOR_PROJECT_DIRECTORY_CONFIGURATION_REQUEST_ENV]: '1',
-        },
+        command: await resolveLocalNpmCommand(
+          {
+            executable: 'npm',
+            args: [
+              'exec',
+              '--yes',
+              '--package',
+              setupPackage.specifier,
+              '--',
+              'maka',
+              'runtime-host',
+              'service',
+              'update',
+              '--framed',
+              ...(targetVersion ? ['--target', targetVersion] : []),
+              '--managed-root-id',
+              command.target.rootId,
+              ...(command.expectedHost
+                ? ['--expected-host-json', JSON.stringify(command.expectedHost)]
+                : []),
+              ...managedTargetArgs(command.target),
+              ...(command.allowManualUpdate && targetVersion ? ['--allow-manual-update'] : []),
+              ...(command.allowInterruptActiveTasks ? ['--allow-interrupt-active-tasks'] : []),
+            ],
+          },
+          environment,
+        ),
+        environment,
         spawnProcess: input.spawnProcess ?? spawn,
         timeoutMs: input.setupTimeoutMs ?? SETUP_TIMEOUT_MS,
         terminate,
@@ -662,6 +670,45 @@ function resolveLocalSetupPackage(
   return { specifier: setupPackage.path, integrity: setupPackage.integrity };
 }
 
+async function resolveLocalNpmCommand(
+  command: DesktopRuntimeHostLocalSetupCommand,
+  environment: NodeJS.ProcessEnv,
+): Promise<DesktopRuntimeHostLocalSetupCommand> {
+  if (process.platform !== 'win32' || command.executable !== 'npm') return command;
+
+  const configuredNode = environment.npm_node_execpath;
+  const configuredCli = environment.npm_execpath;
+  const candidates: Array<readonly [string, string]> = [];
+  if (configuredNode && configuredCli) candidates.push([configuredNode, configuredCli]);
+
+  const path = Object.entries(environment).find(([key]) => key.toUpperCase() === 'PATH')?.[1];
+  for (const entry of path?.split(delimiter) ?? []) {
+    const directory = entry.replace(/^"|"$/gu, '').trim();
+    if (!directory) continue;
+    candidates.push([
+      join(directory, 'node.exe'),
+      join(directory, 'node_modules', 'npm', 'bin', 'npm-cli.js'),
+    ]);
+  }
+
+  for (const [nodePath, cliPath] of candidates) {
+    if (!isAbsolute(nodePath) || !isAbsolute(cliPath)) continue;
+    if ((await regularFile(nodePath)) && (await regularFile(cliPath))) {
+      return { executable: nodePath, args: [cliPath, ...command.args] };
+    }
+  }
+  throw new Error('Local Runtime Host setup requires a complete Node.js and npm installation');
+}
+
+async function regularFile(path: string): Promise<boolean> {
+  try {
+    return (await stat(path)).isFile();
+  } catch (error) {
+    if (isNodeError(error, 'ENOENT')) return false;
+    throw error;
+  }
+}
+
 function managedTargetArgs(target: DesktopRuntimeHostLocalServiceTarget): string[] {
   return [
     '--expected-service-id',
diff --git a/apps/desktop/src/renderer/locales/settings-projects-copy.ts b/apps/desktop/src/renderer/locales/settings-projects-copy.ts
index 3666a18ddd..fa2aee3c2c 100644
--- a/apps/desktop/src/renderer/locales/settings-projects-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-projects-copy.ts
@@ -38,6 +38,7 @@ export type SettingsProjectsCopy = {
     configureManuallyDescription: string;
     thisComputerRemoteAccess: string;
     thisComputerRemoteAccessHelp: string;
+    remoteAccessEnabling: string;
     remoteAccessOn: string;
     remoteAccessOff: string;
     enableRemoteAccess: string;
@@ -329,6 +330,7 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = {
       configureManuallyDescription: '为已有 Host 填写 TLS、SSH 或 Direct peer 参数',
       thisComputerRemoteAccess: '远程访问',
       thisComputerRemoteAccessHelp: '通过实验性端到端直连访问此 Host;可自动发现公共协调节点来辅助打洞',
+      remoteAccessEnabling: '正在准备并开启远程访问;首次可能需要一点时间。',
       remoteAccessOn: '已开启',
       remoteAccessOff: '未开启',
       enableRemoteAccess: '开启',
@@ -645,6 +647,7 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = {
       configureManuallyDescription: 'Enter TLS, SSH, or Direct peer details for an existing Host',
       thisComputerRemoteAccess: 'Remote access',
       thisComputerRemoteAccessHelp: 'Reach this Host through experimental end-to-end direct connections, with automatic public coordination discovery',
+      remoteAccessEnabling: 'Preparing and enabling remote access. The first setup may take a moment.',
       remoteAccessOn: 'On',
       remoteAccessOff: 'Off',
       enableRemoteAccess: 'Enable',
diff --git a/apps/desktop/src/renderer/settings/runtime-host-profiles-section.tsx b/apps/desktop/src/renderer/settings/runtime-host-profiles-section.tsx
index 3e39df880f..6d60724cda 100644
--- a/apps/desktop/src/renderer/settings/runtime-host-profiles-section.tsx
+++ b/apps/desktop/src/renderer/settings/runtime-host-profiles-section.tsx
@@ -112,6 +112,7 @@ export function RuntimeHostProfilesSection(props: {
   const [showJoinSharedSession, setShowJoinSharedSession] = useState(false);
   const [managedTarget, setManagedTarget] = useState<RuntimeHostManagementTarget>();
   const [localAccess, setLocalAccess] = useState<DesktopLocalRuntimeHostRemoteAccessSnapshot>();
+  const [localAccessEnabling, setLocalAccessEnabling] = useState(false);
   const [connectionCodeDialog, setConnectionCodeDialog] = useState<
     | { readonly mode: 'import' }
     | {
@@ -263,6 +264,7 @@ export function RuntimeHostProfilesSection(props: {
 
   async function enableLocalRemoteAccess(allowInterruptActiveTasks = false): Promise<void> {
     setSwitching(true);
+    setLocalAccessEnabling(true);
     try {
       const result = await window.maka.localRuntimeHostRemoteAccess.enable({
         allowInterruptActiveTasks,
@@ -298,7 +300,10 @@ export function RuntimeHostProfilesSection(props: {
         );
       }
     } finally {
-      if (mountedRef.current) setSwitching(false);
+      if (mountedRef.current) {
+        setSwitching(false);
+        setLocalAccessEnabling(false);
+      }
     }
   }
 
@@ -399,7 +404,9 @@ export function RuntimeHostProfilesSection(props: {
         <SettingsRow
           label={copy.thisComputerRemoteAccess}
           description={
-            localAccess?.state === 'unsupported'
+            localAccessEnabling
+              ? copy.remoteAccessEnabling
+              : localAccess?.state === 'unsupported'
               ? localAccess.message
               : localAccess?.state === 'unavailable'
                 ? localAccess.message
@@ -450,6 +457,7 @@ export function RuntimeHostProfilesSection(props: {
                     variant="secondary"
                     size="sm"
                     label={copy.enableRemoteAccess}
+                    isLoading={localAccessEnabling}
                     isDisabled={
                       switching ||
                       !localAccess ||


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org