From d7e22ffb9763aa65d90ae340a57d0b42a5e6c2c5 Mon Sep 17 00:00:00 2001 From: yaojin3616 Date: Mon, 17 Aug 2026 16:25:57 +0800 Subject: [PATCH 1/3] fix(market-installer): ensure bundled Node and pnpm runtimes are used for plugin installation --- package-lock.json | 1 + package.json | 1 + .../dsh-desktop-market-installer/index.js | 33 ++++++++++++++----- test/market-installer.test.js | 21 ++++++++++++ 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index c868aeb7..b15dbaf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "dsh-desktop-market-installer": "file:packages/dsh-desktop-market-installer", "electron-updater": "^6.8.9", "node": "24.9.0", + "pnpm": "10.34.5", "qrcode": "^1.5.4" }, "devDependencies": { diff --git a/package.json b/package.json index 66b1deba..d7d81a3c 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "dsh-desktop-market-installer": "file:packages/dsh-desktop-market-installer", "electron-updater": "^6.8.9", "node": "24.9.0", + "pnpm": "10.34.5", "qrcode": "^1.5.4" }, "devDependencies": { diff --git a/packages/dsh-desktop-market-installer/index.js b/packages/dsh-desktop-market-installer/index.js index 8de903f4..73a22745 100644 --- a/packages/dsh-desktop-market-installer/index.js +++ b/packages/dsh-desktop-market-installer/index.js @@ -120,21 +120,38 @@ export async function ensurePnpmShim(home = dshHome()) { const executable = process.execPath if (process.platform === 'win32') { - const path = join(directory, 'pnpm.cmd') - await writeFile(path, `@\"${executable}\" \"${pnpmEntry}\" %*\r\n`, 'utf8') + const pnpmPath = join(directory, 'pnpm.cmd') + await writeFile(pnpmPath, `@\"${executable}\" \"${pnpmEntry}\" %*\r\n`, 'utf8') + const nodePath = join(directory, 'node.cmd') + await writeFile(nodePath, `@\"${executable}\" %*\r\n`, 'utf8') } else { - const path = join(directory, 'pnpm') + const pnpmPath = join(directory, 'pnpm') await writeFile( - path, + pnpmPath, `#!/bin/sh\nexec ${shellQuote(executable)} ${shellQuote(pnpmEntry)} \"$@\"\n`, { encoding: 'utf8', mode: 0o755 } ) - await chmod(path, 0o755) + await chmod(pnpmPath, 0o755) + const nodePath = join(directory, 'node') + await writeFile( + nodePath, + `#!/bin/sh\nexec ${shellQuote(executable)} \"$@\"\n`, + { encoding: 'utf8', mode: 0o755 } + ) + await chmod(nodePath, 0o755) } - const current = process.env.PATH ?? '' - if (!current.split(delimiter).includes(directory)) { - process.env.PATH = current ? `${directory}${delimiter}${current}` : directory + const nodeDir = dirname(executable) + const pathKey = process.platform === 'win32' ? 'Path' : 'PATH' + const current = process.env[pathKey] ?? process.env.PATH ?? process.env.Path ?? '' + const parts = current.split(delimiter).filter(Boolean) + const additions = [directory, nodeDir].filter((dir) => !parts.includes(dir)) + if (additions.length > 0) { + const updated = [...additions, current].filter(Boolean).join(delimiter) + process.env.PATH = updated + if (process.platform === 'win32') { + process.env.Path = updated + } } return directory } diff --git a/test/market-installer.test.js b/test/market-installer.test.js index 2a4a7f8b..64226c30 100644 --- a/test/market-installer.test.js +++ b/test/market-installer.test.js @@ -10,6 +10,7 @@ import { UNINSTALL_PATH, buildInstallArguments, buildUninstallArguments, + ensurePnpmShim, isTrustedRequest, readMarketInstallation, resolvePnpmEntry @@ -45,6 +46,26 @@ describe('desktop plugin market installer', () => { expect(resolvePnpmEntry()).toMatch(/node_modules[/\\]pnpm[/\\]bin[/\\]pnpm\.(c|m)js$/u) }) + it('generates packaged node and pnpm shims in desktop-bin', async () => { + const home = await mkdtemp(join(tmpdir(), 'dsh-market-shim-')) + const binDir = await ensurePnpmShim(home) + expect(binDir).toBe(join(home, '.desktop-bin')) + + if (process.platform === 'win32') { + const pnpmCmd = await readFile(join(binDir, 'pnpm.cmd'), 'utf8') + const nodeCmd = await readFile(join(binDir, 'node.cmd'), 'utf8') + expect(pnpmCmd).toContain(process.execPath) + expect(pnpmCmd).toContain('pnpm') + expect(nodeCmd).toContain(process.execPath) + } else { + const pnpmScript = await readFile(join(binDir, 'pnpm'), 'utf8') + const nodeScript = await readFile(join(binDir, 'node'), 'utf8') + expect(pnpmScript).toContain(process.execPath) + expect(pnpmScript).toContain('pnpm') + expect(nodeScript).toContain(process.execPath) + } + }) + it('reports both the requested dependency and installed package version', async () => { const home = await mkdtemp(join(tmpdir(), 'dsh-market-status-')) const profile = join(home, 'profiles', 'web') From 349d3727d0dbf26ca79f1ca8b23a5399af578efa Mon Sep 17 00:00:00 2001 From: yaojin3616 Date: Mon, 17 Aug 2026 16:57:04 +0800 Subject: [PATCH 2/3] fix(market-installer): fix Windows CMD syntax for generated node and pnpm shims --- packages/dsh-desktop-market-installer/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dsh-desktop-market-installer/index.js b/packages/dsh-desktop-market-installer/index.js index 73a22745..2bada597 100644 --- a/packages/dsh-desktop-market-installer/index.js +++ b/packages/dsh-desktop-market-installer/index.js @@ -121,9 +121,9 @@ export async function ensurePnpmShim(home = dshHome()) { if (process.platform === 'win32') { const pnpmPath = join(directory, 'pnpm.cmd') - await writeFile(pnpmPath, `@\"${executable}\" \"${pnpmEntry}\" %*\r\n`, 'utf8') + await writeFile(pnpmPath, `@echo off\r\n\"${executable}\" \"${pnpmEntry}\" %*\r\n`, 'utf8') const nodePath = join(directory, 'node.cmd') - await writeFile(nodePath, `@\"${executable}\" %*\r\n`, 'utf8') + await writeFile(nodePath, `@echo off\r\n\"${executable}\" %*\r\n`, 'utf8') } else { const pnpmPath = join(directory, 'pnpm') await writeFile( From 9b9e087901ddf284e11c14f9ac274e602ff8edfc Mon Sep 17 00:00:00 2001 From: yaojin3616 Date: Mon, 17 Aug 2026 17:24:27 +0800 Subject: [PATCH 3/3] fix(market-installer): support Windows UTF-8 non-ASCII profile paths with chcp 65001 --- .../dsh-desktop-market-installer/index.js | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/dsh-desktop-market-installer/index.js b/packages/dsh-desktop-market-installer/index.js index 2bada597..9e12e2f3 100644 --- a/packages/dsh-desktop-market-installer/index.js +++ b/packages/dsh-desktop-market-installer/index.js @@ -121,9 +121,17 @@ export async function ensurePnpmShim(home = dshHome()) { if (process.platform === 'win32') { const pnpmPath = join(directory, 'pnpm.cmd') - await writeFile(pnpmPath, `@echo off\r\n\"${executable}\" \"${pnpmEntry}\" %*\r\n`, 'utf8') + await writeFile( + pnpmPath, + `@chcp 65001 >nul\r\n@echo off\r\n\"${executable}\" \"${pnpmEntry}\" %*\r\n`, + 'utf8' + ) const nodePath = join(directory, 'node.cmd') - await writeFile(nodePath, `@echo off\r\n\"${executable}\" %*\r\n`, 'utf8') + await writeFile( + nodePath, + `@chcp 65001 >nul\r\n@echo off\r\n\"${executable}\" %*\r\n`, + 'utf8' + ) } else { const pnpmPath = join(directory, 'pnpm') await writeFile( @@ -275,9 +283,17 @@ export function apply(ctx) { if (error?.code !== 'ENOENT') throw error } + const pathKey = process.platform === 'win32' ? 'Path' : 'PATH' + const envPath = process.env[pathKey] ?? process.env.PATH ?? process.env.Path ?? '' const child = spawn(process.execPath, buildInstallArguments(), { cwd: directory, - env: { ...process.env, CI: 'true', NO_COLOR: '1' }, + env: { + ...process.env, + PATH: envPath, + Path: envPath, + CI: 'true', + NO_COLOR: '1' + }, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, detached: process.platform !== 'win32' @@ -351,9 +367,17 @@ export function apply(ctx) { if (error?.code !== 'ENOENT') throw error } + const pathKey = process.platform === 'win32' ? 'Path' : 'PATH' + const envPath = process.env[pathKey] ?? process.env.PATH ?? process.env.Path ?? '' const child = spawn(process.execPath, buildUninstallArguments(), { cwd: directory, - env: { ...process.env, CI: 'true', NO_COLOR: '1' }, + env: { + ...process.env, + PATH: envPath, + Path: envPath, + CI: 'true', + NO_COLOR: '1' + }, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, detached: process.platform !== 'win32'