Skip to content

Commit

Permalink
fix(npm): Fix NPM_TOKEN usage again
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
BYK committed Dec 2, 2020
1 parent e798120 commit 1b3a27c
Showing 1 changed file with 37 additions and 15 deletions.
52 changes: 37 additions & 15 deletions src/targets/npm.ts
Expand Up @@ -12,6 +12,8 @@ import {
BaseArtifactProvider,
RemoteArtifact,
} from '../artifact_providers/base';
import { withTempFile } from 'src/utils/files';
import { writeFileSync } from 'fs';

const logger = loggerRaw.withScope('[npm]');

Expand All @@ -24,6 +26,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$/;

Expand All @@ -43,6 +47,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 */
Expand Down Expand Up @@ -125,8 +131,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)) {
Expand Down Expand Up @@ -180,22 +192,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,
};
}

// 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,
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);

// Disable output buffering because NPM/Yarn can ask us for one-time passwords
result = spawnProcess(bin, args, spawnOptions, {
showStdout: true,
});
});

return result;
}

/**
Expand Down

0 comments on commit 1b3a27c

Please sign in to comment.