mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-11 10:40:53 -04:00
feat: allow using token helpers in pnpm publish (#7443)
Close #7316 --------- Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
5
.changeset/gentle-jars-check.md
Normal file
5
.changeset/gentle-jars-check.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/run-npm": minor
|
||||
---
|
||||
|
||||
Allow to set custom env.
|
||||
5
.changeset/hot-lies-invite.md
Normal file
5
.changeset/hot-lies-invite.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/network.auth-header": minor
|
||||
---
|
||||
|
||||
Export the loadToken function.
|
||||
7
.changeset/shiny-coins-walk.md
Normal file
7
.changeset/shiny-coins-walk.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-publishing": minor
|
||||
"@pnpm/run-npm": minor
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Allow using token helpers in `pnpm publish` [#7316](https://github.com/pnpm/pnpm/issues/7316).
|
||||
@@ -5,6 +5,7 @@ import PATH from 'path-name'
|
||||
|
||||
export interface RunNPMOptions {
|
||||
cwd?: string
|
||||
env?: Record<string, string>
|
||||
}
|
||||
|
||||
export function runNpm (npmPath: string | undefined, args: string[], options?: RunNPMOptions) {
|
||||
@@ -13,6 +14,7 @@ export function runNpm (npmPath: string | undefined, args: string[], options?: R
|
||||
cwd: options?.cwd ?? process.cwd(),
|
||||
stdio: 'inherit',
|
||||
userAgent: undefined,
|
||||
env: options?.env ?? {},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,12 +25,17 @@ export function runScriptSync (
|
||||
cwd: string
|
||||
stdio: childProcess.StdioOptions
|
||||
userAgent?: string
|
||||
env: Record<string, string>
|
||||
}
|
||||
) {
|
||||
opts = Object.assign({}, opts)
|
||||
const result = spawn.sync(command, args, Object.assign({}, opts, {
|
||||
env: createEnv(opts),
|
||||
}))
|
||||
const env = {
|
||||
...createEnv(opts),
|
||||
...opts.env,
|
||||
}
|
||||
const result = spawn.sync(command, args, {
|
||||
...opts,
|
||||
env,
|
||||
})
|
||||
if (result.error) throw result.error
|
||||
return result
|
||||
}
|
||||
@@ -39,7 +46,7 @@ function createEnv (
|
||||
userAgent?: string
|
||||
}
|
||||
) {
|
||||
const env = Object.create(process.env)
|
||||
const env = { ...process.env }
|
||||
|
||||
env[PATH] = [
|
||||
path.join(opts.cwd, 'node_modules', '.bin'),
|
||||
|
||||
@@ -57,7 +57,7 @@ function splitKey (key: string) {
|
||||
return [key.slice(0, index), key.slice(index + 1)]
|
||||
}
|
||||
|
||||
function loadToken (helperPath: string, settingName: string) {
|
||||
export function loadToken (helperPath: string, settingName: string) {
|
||||
if (!path.isAbsolute(helperPath) || !fs.existsSync(helperPath)) {
|
||||
throw new PnpmError('BAD_TOKEN_HELPER_PATH', `${settingName} must be an absolute path, without arguments`)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import nerfDart from 'nerf-dart'
|
||||
import { getAuthHeadersFromConfig } from './getAuthHeadersFromConfig'
|
||||
import { getAuthHeadersFromConfig, loadToken } from './getAuthHeadersFromConfig'
|
||||
import { removePort } from './helpers/removePort'
|
||||
|
||||
export {
|
||||
loadToken,
|
||||
}
|
||||
|
||||
export function createGetAuthHeaderByURI (
|
||||
opts: {
|
||||
allSettings: Record<string, string>
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -4856,6 +4856,9 @@ importers:
|
||||
'@pnpm/filter-workspace-packages':
|
||||
specifier: workspace:*
|
||||
version: link:../../workspace/filter-workspace-packages
|
||||
'@pnpm/network.auth-header':
|
||||
specifier: workspace:*
|
||||
version: link:../../network/auth-header
|
||||
'@pnpm/plugin-commands-publishing':
|
||||
specifier: workspace:*
|
||||
version: 'link:'
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"devDependencies": {
|
||||
"@pnpm/filter-workspace-packages": "workspace:*",
|
||||
"@pnpm/plugin-commands-publishing": "workspace:*",
|
||||
"@pnpm/network.auth-header": "workspace:*",
|
||||
"@pnpm/prepare": "workspace:*",
|
||||
"@pnpm/registry-mock": "3.17.1",
|
||||
"@pnpm/test-ipc-server": "workspace:*",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { runLifecycleHook, type RunLifecycleHookOptions } from '@pnpm/lifecycle'
|
||||
import { runNpm } from '@pnpm/run-npm'
|
||||
import { type ProjectManifest } from '@pnpm/types'
|
||||
import { getCurrentBranch, isGitRepo, isRemoteHistoryClean, isWorkingTreeClean } from '@pnpm/git-utils'
|
||||
import { loadToken } from '@pnpm/network.auth-header'
|
||||
import { prompt } from 'enquirer'
|
||||
import rimraf from '@zkochan/rimraf'
|
||||
import pick from 'ramda/src/pick'
|
||||
@@ -235,6 +236,7 @@ Do you want to continue?`,
|
||||
await copyNpmrc({ dir, workspaceDir: opts.workspaceDir, packDestination })
|
||||
const { status } = runNpm(opts.npmPath, ['publish', '--ignore-scripts', path.basename(tarballName), ...args], {
|
||||
cwd: packDestination,
|
||||
env: getEnvWithTokens(opts),
|
||||
})
|
||||
await rimraf(packDestination)
|
||||
|
||||
@@ -250,6 +252,31 @@ Do you want to continue?`,
|
||||
return { manifest }
|
||||
}
|
||||
|
||||
/**
|
||||
* The npm CLI doesn't support token helpers, so we transform the token helper settings
|
||||
* to regular auth token settings that the npm CLI can understand.
|
||||
*/
|
||||
function getEnvWithTokens (opts: Pick<PublishRecursiveOpts, 'rawConfig' | 'argv'>) {
|
||||
const tokenHelpers = Object.entries(opts.rawConfig).filter(([key]) => key.endsWith(':tokenHelper'))
|
||||
const tokenHelpersFromArgs = opts.argv.original
|
||||
.filter(arg => arg.includes(':tokenHelper='))
|
||||
.map(arg => arg.split('=', 2) as [string, string])
|
||||
|
||||
const env: Record<string, string> = {}
|
||||
for (const [key, helperPath] of tokenHelpers.concat(tokenHelpersFromArgs)) {
|
||||
const authHeader = loadToken(helperPath, key)
|
||||
const authType = authHeader.startsWith('Bearer')
|
||||
? '_authToken'
|
||||
: '_auth'
|
||||
|
||||
const registry = key.replace(/:tokenHelper$/, '')
|
||||
env[`NPM_CONFIG_${registry}:${authType}`] = authType === '_authToken'
|
||||
? authHeader.slice('Bearer '.length)
|
||||
: authHeader.replace(/Basic /i, '')
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
async function copyNpmrc (
|
||||
{ dir, workspaceDir, packDestination }: {
|
||||
dir: string
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync, promises as fs } from 'fs'
|
||||
import { chmodSync, existsSync, promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import execa from 'execa'
|
||||
import { isCI } from 'ci-info'
|
||||
@@ -734,3 +734,60 @@ test('publish: provenance', async () => {
|
||||
dir: process.cwd(),
|
||||
}, [])
|
||||
})
|
||||
|
||||
test('publish: use basic token helper for authentication', async () => {
|
||||
prepare({
|
||||
name: 'test-publish-helper-token-basic.json',
|
||||
version: '0.0.2',
|
||||
})
|
||||
|
||||
const os = process.platform
|
||||
const file = os === 'win32'
|
||||
? 'tokenHelperBasic.bat'
|
||||
: 'tokenHelperBasic.js'
|
||||
|
||||
const tokenHelper = path.join(__dirname, 'utils', file)
|
||||
|
||||
chmodSync(tokenHelper, 0o755)
|
||||
|
||||
await publish.handler({
|
||||
...DEFAULT_OPTS,
|
||||
argv: {
|
||||
original: [
|
||||
'publish',
|
||||
CREDENTIALS[0],
|
||||
`--//localhost:${REGISTRY_MOCK_PORT}/:tokenHelper=${tokenHelper}`,
|
||||
],
|
||||
},
|
||||
dir: process.cwd(),
|
||||
gitChecks: false,
|
||||
}, [])
|
||||
})
|
||||
|
||||
test('publish: use bearer token helper for authentication', async () => {
|
||||
prepare({
|
||||
name: 'test-publish-helper-token-bearer.json',
|
||||
version: '0.0.2',
|
||||
})
|
||||
|
||||
const os = process.platform
|
||||
const file = os === 'win32'
|
||||
? 'tokenHelperBearer.bat'
|
||||
: 'tokenHelperBearer.js'
|
||||
const tokenHelper = path.join(__dirname, 'utils', file)
|
||||
|
||||
chmodSync(tokenHelper, 0o755)
|
||||
|
||||
await publish.handler({
|
||||
...DEFAULT_OPTS,
|
||||
argv: {
|
||||
original: [
|
||||
'publish',
|
||||
CREDENTIALS[0],
|
||||
`--//localhost:${REGISTRY_MOCK_PORT}/:tokenHelper=${tokenHelper}`,
|
||||
],
|
||||
},
|
||||
dir: process.cwd(),
|
||||
gitChecks: false,
|
||||
}, [])
|
||||
})
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set "PASSWORD=password"
|
||||
|
||||
for /f "delims=" %%i in ('powershell -Command "[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes('!PASSWORD!'))"') do set ENCODED=%%i
|
||||
|
||||
echo Basic %ENCODED%
|
||||
2
releasing/plugin-commands-publishing/test/utils/tokenHelperBasic.js
Executable file
2
releasing/plugin-commands-publishing/test/utils/tokenHelperBasic.js
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
console.log("Basic " + Buffer.from("password").toString("base64"));
|
||||
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set "PASSWORD=password"
|
||||
|
||||
for /f "delims=" %%i in ('powershell -Command "[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes('!PASSWORD!'))"') do set ENCODED=%%i
|
||||
|
||||
echo Bearer %ENCODED%
|
||||
2
releasing/plugin-commands-publishing/test/utils/tokenHelperBearer.js
Executable file
2
releasing/plugin-commands-publishing/test/utils/tokenHelperBearer.js
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
console.log("Bearer " + Buffer.from("password").toString("base64"));
|
||||
@@ -36,6 +36,9 @@
|
||||
{
|
||||
"path": "../../fs/packlist"
|
||||
},
|
||||
{
|
||||
"path": "../../network/auth-header"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/error"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user