Skip to content
This repository was archived by the owner on Sep 26, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions os/env/path-extender-windows/path-extender-windows.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ HKEY_CURRENT_USER\\Environment
expect(report).toStrictEqual([
{
variable: 'PNPM_HOME',
action: 'skipped',
action: 'updated',
oldValue: pnpmHomeDirNormalized,
newValue: pnpmHomeDirNormalized,
},
Expand All @@ -337,9 +337,53 @@ HKEY_CURRENT_USER\\Environment
},
])
expect(execa).toHaveBeenNthCalledWith(3, 'reg', ['query', regKey], { windowsHide: false })
expect(execa).toHaveBeenNthCalledWith(4, 'reg', ['add', regKey, '/v', 'Path', '/t', 'REG_EXPAND_SZ', '/d', `%PNPM_HOME%;${currentPathInRegistry}`, '/f'], { windowsHide: false })
expect(execa).toHaveBeenNthCalledWith(5, 'setx', ['REFRESH_ENV_VARS', '1'], { windowsHide: false })
expect(execa).toHaveBeenNthCalledWith(6, 'reg', ['delete' ,regKey, '/v', 'REFRESH_ENV_VARS', '/f'], { windowsHide: false })
expect(execa).toHaveBeenNthCalledWith(4, 'reg', ['add', regKey, '/v', 'PNPM_HOME', '/t', 'REG_SZ', '/d', pnpmHomeDirNormalized, '/f'], { windowsHide: false })
expect(execa).toHaveBeenNthCalledWith(5, 'reg', ['add', regKey, '/v', 'Path', '/t', 'REG_EXPAND_SZ', '/d', `%PNPM_HOME%;${currentPathInRegistry}`, '/f'], { windowsHide: false })
expect(execa).toHaveBeenNthCalledWith(6, 'setx', ['REFRESH_ENV_VARS', '1'], { windowsHide: false })
expect(execa).toHaveBeenNthCalledWith(7, 'reg', ['delete' ,regKey, '/v', 'REFRESH_ENV_VARS', '/f'], { windowsHide: false })
})

test('PNPM_HOME with the correct registry type is left unchanged', async () => {
const pnpmHomeDir = tempDir(false)
const pnpmHomeDirNormalized = path.normalize(pnpmHomeDir)
const currentPathInRegistry = '%PNPM_HOME%;C:\Windows'
execa['mockResolvedValueOnce']({
failed: false,
stdout: 'Active code page: 437',
}).mockResolvedValueOnce({
failed: false,
stdout: '',
}).mockResolvedValueOnce({
failed: false,
stdout: '\nHKEY_CURRENT_USER\\Environment\n PNPM_HOME REG_SZ ' + pnpmHomeDirNormalized + '\n Path REG_EXPAND_SZ ' + currentPathInRegistry + '\n',
}).mockResolvedValueOnce({
failed: false,
stdout: '',
}).mockResolvedValueOnce({
failed: false,
stdout: '',
}).mockResolvedValueOnce({
failed: false,
stdout: '',
})

const report = await addDirToWindowsEnvPath(pnpmHomeDir, { proxyVarName: 'PNPM_HOME' })

expect(report).toStrictEqual([
{
variable: 'PNPM_HOME',
action: 'skipped',
oldValue: pnpmHomeDirNormalized,
newValue: pnpmHomeDirNormalized,
},
{
variable: 'Path',
action: 'skipped',
oldValue: currentPathInRegistry,
newValue: currentPathInRegistry,
},
])
expect(execa).not.toHaveBeenCalledWith('reg', expect.arrayContaining(['add']), { windowsHide: false })
})

test('setup throws an error if PNPM_HOME is already set to a different directory', async () => {
Expand Down
22 changes: 16 additions & 6 deletions os/env/path-extender-windows/path-extender-windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,19 @@ async function updateEnvVariable (
overwrite: boolean
}
): Promise<EnvVariableChange> {
const currentValue = await getEnvValueFromRegistry(registryOutput, name)
const current = await getEnvValueFromRegistryWithType(registryOutput, name)
const currentValue = current?.data
if (currentValue && !opts.overwrite) {
if (currentValue !== value) {
throw new BadEnvVariableError({ envName: name, currentValue, wantedValue: value })
}
return { variable: name, action: 'skipped', oldValue: currentValue, newValue: value }
} else {
await setEnvVarInRegistry(name, value, { expandableString: opts.expandableString })
return { variable: name, action: 'updated', oldValue: currentValue as string, newValue: value }
const wantedType = opts.expandableString ? 'REG_EXPAND_SZ' : 'REG_SZ'
if (current.type === wantedType) {
return { variable: name, action: 'skipped', oldValue: currentValue, newValue: value }
}
}
await setEnvVarInRegistry(name, value, { expandableString: opts.expandableString })
return { variable: name, action: 'updated', oldValue: currentValue as string, newValue: value }
}

async function addToPath (registryOutput: string, addedDir: string, position: AddingPosition = 'start'): Promise<EnvVariableChange> {
Expand Down Expand Up @@ -136,9 +139,16 @@ async function getRegistryOutput (): Promise<string> {
}

async function getEnvValueFromRegistry (registryOutput: string, envVarName: string): Promise<string | undefined> {
return (await getEnvValueFromRegistryWithType(registryOutput, envVarName))?.data
}

async function getEnvValueFromRegistryWithType (
registryOutput: string,
envVarName: string
): Promise<IEnvironmentValueMatch['groups'] | undefined> {
const regexp = new RegExp(`^ {4}(?<name>${envVarName}) {4}(?<type>\\w+) {4}(?<data>.*)$`, 'gim')
const match = Array.from(matchAll(registryOutput, regexp))[0] as IEnvironmentValueMatch
return match?.groups.data
return match?.groups
}

async function setEnvVarInRegistry (
Expand Down
Loading