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..9e12e2f3 100644 --- a/packages/dsh-desktop-market-installer/index.js +++ b/packages/dsh-desktop-market-installer/index.js @@ -120,21 +120,46 @@ 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, + `@chcp 65001 >nul\r\n@echo off\r\n\"${executable}\" \"${pnpmEntry}\" %*\r\n`, + 'utf8' + ) + const nodePath = join(directory, 'node.cmd') + await writeFile( + nodePath, + `@chcp 65001 >nul\r\n@echo off\r\n\"${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 } @@ -258,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' @@ -334,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' 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')