Skip to content
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
9 changes: 1 addition & 8 deletions .bitmap
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,7 @@
"mainFile": "index.ts",
"rootDir": "config/nerf-dart"
},
"policy": {
"name": "policy",
"scope": "pnpm.builder",
"version": "3.0.2",
"mainFile": "index.ts",
"rootDir": "builder/policy"
},
"proxy-agent": {
"proxy-agent": {
"name": "proxy-agent",
"scope": "pnpm.network",
"version": "2.0.3",
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ jobs:
fail-fast: false
matrix:
node:
- '16.14'
- '18'
- '20'
- '21'
- '22'
- '24'
platform:
- ubuntu-latest
- windows-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
public
node_modules
.claude
1 change: 0 additions & 1 deletion builder/policy/index.ts

This file was deleted.

4 changes: 0 additions & 4 deletions builder/policy/onlyBuild.json

This file was deleted.

10 changes: 0 additions & 10 deletions builder/policy/policy.docs.mdx

This file was deleted.

54 changes: 0 additions & 54 deletions builder/policy/policy.spec.ts

This file was deleted.

23 changes: 0 additions & 23 deletions builder/policy/policy.ts

This file was deleted.

84 changes: 84 additions & 0 deletions os/env/path-extender-posix/path-extender-posix.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,36 @@ case ":$PATH:" in
*) export PATH="${pnpmHomeDir}:$PATH" ;;
esac
# pnpm end
`)
})
it('should append to shell script with proxyVarSubDir', async () => {
fs.writeFileSync(configFile, '', 'utf8')
const report = await addDirToPosixEnvPath(pnpmHomeDir, {
proxyVarName: 'PNPM_HOME',
proxyVarSubDir: 'bin',
configSectionName: 'pnpm',
})
expect(report).toStrictEqual({
configFile: {
path: configFile,
changeType: 'appended',
},
oldSettings: '',
newSettings: `export PNPM_HOME="${pnpmHomeDir}"
case ":$PATH:" in
*":$PNPM_HOME/bin:"*) ;;
*) export PATH="$PNPM_HOME/bin:$PATH" ;;
esac`,
})
const configContent = fs.readFileSync(configFile, 'utf8')
expect(configContent).toEqual(`
# pnpm
export PNPM_HOME="${pnpmHomeDir}"
case ":$PATH:" in
*":$PNPM_HOME/bin:"*) ;;
*) export PATH="$PNPM_HOME/bin:$PATH" ;;
esac
# pnpm end
`)
})
it('should put the new directory to the end of the PATH', async () => {
Expand Down Expand Up @@ -978,6 +1008,35 @@ if not string match -q -- $PNPM_HOME $PATH
set -gx PATH "$PNPM_HOME" $PATH
end
# pnpm end
`)
})
it('should append to shell script with proxyVarSubDir', async () => {
fs.mkdirSync('.config/fish', { recursive: true })
fs.writeFileSync(configFile, '', 'utf8')
const report = await addDirToPosixEnvPath(pnpmHomeDir, {
proxyVarName: 'PNPM_HOME',
proxyVarSubDir: 'bin',
configSectionName: 'pnpm',
})
expect(report).toStrictEqual({
configFile: {
path: configFile,
changeType: 'appended',
},
oldSettings: ``,
newSettings: `set -gx PNPM_HOME "${pnpmHomeDir}"
if not string match -q -- "$PNPM_HOME/bin" $PATH
set -gx PATH "$PNPM_HOME/bin" $PATH
end`,
})
const configContent = fs.readFileSync(configFile, 'utf8')
expect(configContent).toEqual(`
# pnpm
set -gx PNPM_HOME "${pnpmHomeDir}"
if not string match -q -- "$PNPM_HOME/bin" $PATH
set -gx PATH "$PNPM_HOME/bin" $PATH
end
# pnpm end
`)
})
it('should append to empty shell script without using a proxy varialbe', async () => {
Expand Down Expand Up @@ -1178,6 +1237,31 @@ $env.PATH = ($env.PATH | split row (char esep) | prepend $env.PNPM_HOME )`,
$env.PNPM_HOME = "${pnpmHomeDir}"
$env.PATH = ($env.PATH | split row (char esep) | prepend $env.PNPM_HOME )
# pnpm end
`)
})
it('should append to shell script with proxyVarSubDir', async () => {
fs.mkdirSync('.config/nushell', { recursive: true })
fs.writeFileSync(configFile, '', 'utf8')
const report = await addDirToPosixEnvPath(pnpmHomeDir, {
proxyVarName: 'PNPM_HOME',
proxyVarSubDir: 'bin',
configSectionName: 'pnpm',
})
expect(report).toStrictEqual({
configFile: {
path: configFile,
changeType: 'appended',
},
oldSettings: ``,
newSettings: `$env.PNPM_HOME = "${pnpmHomeDir}"
$env.PATH = ($env.PATH | split row (char esep) | prepend ($env.PNPM_HOME | path join "bin") )`,
})
const configContent = fs.readFileSync(configFile, 'utf8')
expect(configContent).toEqual(`
# pnpm
$env.PNPM_HOME = "${pnpmHomeDir}"
$env.PATH = ($env.PATH | split row (char esep) | prepend ($env.PNPM_HOME | path join "bin") )
# pnpm end
`)
})
it('should append to empty shell script without using a proxy varialbe', async () => {
Expand Down
17 changes: 12 additions & 5 deletions os/env/path-extender-posix/path-extender-posix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type AddingPosition = 'start' | 'end'

export interface AddDirToPosixEnvPathOpts {
proxyVarName?: string
proxyVarSubDir?: string
overwrite?: boolean
position?: AddingPosition
configSectionName: string
Expand Down Expand Up @@ -93,10 +94,11 @@ async function setupShell (
let newSettings!: string
const _createPathValue = createPathValue.bind(null, opts.position ?? 'start')
if (opts.proxyVarName) {
const pathRef = opts.proxyVarSubDir ? `$${opts.proxyVarName}/${opts.proxyVarSubDir}` : `$${opts.proxyVarName}`
newSettings = `export ${opts.proxyVarName}="${dir}"
case ":$PATH:" in
*":$${opts.proxyVarName}:"*) ;;
*) export PATH="${_createPathValue(`$${opts.proxyVarName}`)}" ;;
*":${pathRef}:"*) ;;
*) export PATH="${_createPathValue(pathRef)}" ;;
esac`
} else {
newSettings = `case ":$PATH:" in
Expand Down Expand Up @@ -141,9 +143,11 @@ async function setupFishShell (dir: string, opts: AddDirToPosixEnvPathOpts): Pro
let newSettings!: string
const _createPathValue = createFishPathValue.bind(null, opts.position ?? 'start')
if (opts.proxyVarName) {
const pathRef = opts.proxyVarSubDir ? `$${opts.proxyVarName}/${opts.proxyVarSubDir}` : `$${opts.proxyVarName}`
const matchPattern = opts.proxyVarSubDir ? `"${pathRef}"` : pathRef
newSettings = `set -gx ${opts.proxyVarName} "${dir}"
if not string match -q -- $${opts.proxyVarName} $PATH
set -gx PATH ${_createPathValue(`$${opts.proxyVarName}`)}
if not string match -q -- ${matchPattern} $PATH
set -gx PATH ${_createPathValue(pathRef)}
end`
} else {
newSettings = `if not string match -q -- "${dir}" $PATH
Expand All @@ -167,8 +171,11 @@ async function setupNuShell (dir: string, opts: AddDirToPosixEnvPathOpts): Promi
let newSettings!: string
const addingCommand = (opts.position ?? "start") === "start" ? "prepend" : "append"
if (opts.proxyVarName) {
const pathRef = opts.proxyVarSubDir
? `($env.${opts.proxyVarName} | path join "${opts.proxyVarSubDir}")`
: `$env.${opts.proxyVarName}`
newSettings = `$env.${opts.proxyVarName} = "${dir}"
$env.PATH = ($env.PATH | split row (char esep) | ${addingCommand} $env.${opts.proxyVarName} )`
$env.PATH = ($env.PATH | split row (char esep) | ${addingCommand} ${pathRef} )`
} else {
newSettings = `$env.PATH = ($env.PATH | split row (char esep) | ${addingCommand} ${dir} )`
}
Expand Down
50 changes: 50 additions & 0 deletions os/env/path-extender-windows/path-extender-windows.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,56 @@ HKEY_CURRENT_USER\\Environment
expect(execa).toHaveBeenNthCalledWith(7, 'reg', ['delete' ,regKey, '/v', 'REFRESH_ENV_VARS', '/f'], { windowsHide: false })
})

test('successful first time installation with proxyVarSubDir', async () => {
const currentPathInRegistry = '%USERPROFILE%\\AppData\\Local\\Microsoft\\WindowsApps;%USERPROFILE%\\.config\\etc;'

execa['mockResolvedValueOnce']({
failed: false,
stdout: '活动代码页: 936',
}).mockResolvedValueOnce({
failed: false,
stdout: '',
}).mockResolvedValueOnce({
failed: false,
stdout: `
HKEY_CURRENT_USER\\Environment
Path REG_EXPAND_SZ ${currentPathInRegistry}
`,
}).mockResolvedValueOnce({
failed: false,
stdout: 'PNPM_HOME ENV VAR SET',
}).mockResolvedValueOnce({
failed: false,
stdout: 'setx PNPM_HOME',
}).mockResolvedValueOnce({
failed: false,
stdout: 'setx PNPM_HOME',
}).mockResolvedValue({
failed: true,
stderr: 'UNEXPECTED',
})

const pnpmHomeDir = tempDir(false)
const pnpmHomeDirNormalized = path.normalize(pnpmHomeDir)
const report = await addDirToWindowsEnvPath(pnpmHomeDir, { proxyVarName: 'PNPM_HOME', proxyVarSubDir: 'bin' })

expect(report).toStrictEqual([
{
action: 'updated',
variable: 'PNPM_HOME',
oldValue: undefined,
newValue: pnpmHomeDirNormalized,
},
{
action: 'updated',
variable: 'Path',
oldValue: currentPathInRegistry,
newValue: `%PNPM_HOME%\\bin;${currentPathInRegistry}`,
},
])
expect(execa).toHaveBeenNthCalledWith(5, 'reg', ['add', regKey, '/v', 'Path', '/t', 'REG_EXPAND_SZ', '/d', `%PNPM_HOME%\\bin;${currentPathInRegistry}`, '/f'], { windowsHide: false })
})

test('successful first time installation when no additional env variable is used', async () => {
const currentPathInRegistry = '%USERPROFILE%\\AppData\\Local\\Microsoft\\WindowsApps;%USERPROFILE%\\.config\\etc;'

Expand Down
6 changes: 5 additions & 1 deletion os/env/path-extender-windows/path-extender-windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type AddingPosition = 'start' | 'end'

export interface AddDirToWindowsEnvPathOpts {
proxyVarName?: string
proxyVarSubDir?: string
overwriteProxyVar?: boolean
position?: AddingPosition
}
Expand Down Expand Up @@ -69,7 +70,10 @@ async function _addDirToWindowsEnvPath (dir: string, opts: AddDirToWindowsEnvPat
expandableString: false,
overwrite: opts.overwriteProxyVar ?? false
}))
changes.push(await addToPath(registryOutput, `%${opts.proxyVarName}%`, opts.position))
const pathEntry = opts.proxyVarSubDir
? `%${opts.proxyVarName}%${path.sep}${opts.proxyVarSubDir}`
: `%${opts.proxyVarName}%`
changes.push(await addToPath(registryOutput, pathEntry, opts.position))
Comment on lines +73 to +76
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New Windows behavior (proxyVarSubDir) is introduced here but isn't covered by path-extender-windows.spec.ts. Please add a unit test asserting the Path entry becomes something like %PNPM_HOME%\\bin when proxyVarSubDir: 'bin' is provided, including both prepend/append positions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows path extender tests require registry access and can only run on Windows. Skipping for now.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added Windows test for proxyVarSubDir.

} else {
changes.push(await addToPath(registryOutput, addedDir, opts.position))
}
Expand Down
12 changes: 12 additions & 0 deletions os/env/path-extender/path-extender.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PnpmError } from '@pnpm/error'
import {
addDirToPosixEnvPath,
AddDirToPosixEnvPathOpts,
Expand All @@ -14,10 +15,21 @@ export type PathExtenderReport = Pick<PathExtenderPosixReport, 'oldSettings' | '
export type AddDirToEnvPathOpts = AddDirToPosixEnvPathOpts

export async function addDirToEnvPath(dir: string, opts: AddDirToEnvPathOpts): Promise<PathExtenderReport> {
if (opts.proxyVarSubDir) {
if (
opts.proxyVarSubDir.startsWith('/') ||
opts.proxyVarSubDir.startsWith('\\') ||
opts.proxyVarSubDir.includes('..') ||
/[;%"'`$<>&|\n\r]/.test(opts.proxyVarSubDir)
) {
throw new PnpmError('INVALID_SUBDIR', `Invalid proxyVarSubDir: "${opts.proxyVarSubDir}"`)
}
}
if (process.platform === 'win32') {
return renderWindowsReport(await addDirToWindowsEnvPath(dir, {
position: opts.position,
proxyVarName: opts.proxyVarName,
proxyVarSubDir: opts.proxyVarSubDir,
overwriteProxyVar: opts.overwrite,
})
)
Expand Down
Loading
Loading