From f910480f590a63a1b80f8a2209b0084abd0c3c76 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 2 Dec 2020 16:34:34 +0300 Subject: [PATCH] fix(npm): Fix NPM_TOKEN usage again This is a retake on #130. Although npm/cli#8 claims to have support for `npm_config_//registry.npmjs.org/:_authToken=` usage, my tests and the reports on the internet says this still doesn't work, even with the latest npm (7.0.15 at the time). The only way to pass the token is to have the `authToken` line in an `.npmrc` file. The quick&dirty way would have been to create one in the project directory but that may collide with a potentially pre-existing project `.npmrc`. Trying to merge these seems more trouble than it is worth: https://github.com/actions/setup-node/blob/59e61b89511ed136a0b17773f07c349fa5c01e8b/src/authutil.ts (even worse as you'd need to revert these changes after the fact) The "better" solution I found is: 1. Create a temporary file as your npmrc 2. Put the token/registry line there 3. Tell npm to use that file as the user config 4. Use the `npm_config_userconfig` for the above to support yarn too This may still fail for yarn, see yarnpkg/yarn#4568. --- src/targets/npm.ts | 49 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/targets/npm.ts b/src/targets/npm.ts index 28bed524..1021c647 100644 --- a/src/targets/npm.ts +++ b/src/targets/npm.ts @@ -12,6 +12,9 @@ import { BaseArtifactProvider, RemoteArtifact, } from '../artifact_providers/base'; +import { withTempFile } from 'src/utils/files'; +import { fstat, writeFileSync } from 'fs'; +import { fileURLToPath } from 'url'; const logger = loggerRaw.withScope('[npm]'); @@ -24,6 +27,8 @@ export const YARN_BIN = process.env.YARN_BIN || 'yarn'; const NPM_MIN_MAJOR = 5; const NPM_MIN_MINOR = 6; +const NPM_TOKEN_ENV_VAR = 'NPM_TOKEN'; + /** A regular expression used to find the package tarball */ const DEFAULT_PACKAGE_REGEX = /^.*\d\.\d.*\.tgz$/; @@ -43,6 +48,8 @@ export interface NpmTargetOptions extends TargetConfig { useOtp?: boolean; /** Do we use Yarn instead of NPM? */ useYarn: boolean; + /** Value of NPM_TOKEN so we can pass it to npm executable */ + token: string; } /** Options for running the NPM publish command */ @@ -125,8 +132,14 @@ export class NpmTarget extends BaseTarget { * Extracts NPM target options from the raw configuration */ protected getNpmConfig(): NpmTargetOptions { + const token = process.env.NPM_TOKEN; + if (!token) { + throw new Error('NPM target: NPM_TOKEN not found in the environment'); + } + const npmConfig: NpmTargetOptions = { useYarn: !!process.env.USE_YARN || !hasExecutable(NPM_BIN), + token, }; if (this.config.access) { if (Object.values(NpmPackageAccess).includes(this.config.access)) { @@ -180,22 +193,32 @@ export class NpmTarget extends BaseTarget { args.push('--tag=next'); } - // Pass OTP if configured - const spawnOptions: SpawnOptions = {}; - if (options.otp) { - spawnOptions.env = { - ...process.env, - NPM_CONFIG_OTP: options.otp, - }; - } + let result; + await withTempFile(filePath => { + // Pass OTP if configured + const spawnOptions: SpawnOptions = {}; + spawnOptions.env = { ...process.env }; + if (options.otp) { + spawnOptions.env.NPM_CONFIG_OTP = options.otp; + } + spawnOptions.env[NPM_TOKEN_ENV_VAR] = this.npmConfig.token; + // WARNING: This may fail for Yarn: https://github.com/yarnpkg/yarn/issues/4568 + spawnOptions.env.npm_config_userconfig = filePath; + writeFileSync( + filePath, + `//registry.npmjs.org/:_authToken=\${${NPM_TOKEN_ENV_VAR}}` + ); - // The path has to be pushed always as the last arg - args.push(path); + // The path has to be pushed always as the last arg + args.push(path); - // Disable output buffering because NPM/Yarn can ask us for one-time passwords - return spawnProcess(bin, args, spawnOptions, { - showStdout: true, + // Disable output buffering because NPM/Yarn can ask us for one-time passwords + result = spawnProcess(bin, args, spawnOptions, { + showStdout: true, + }); }); + + return result; } /**