jdalrymple/semantic-release-npmx

View on GitHub
src/npm-utils.js

Summary

Maintainability
A
35 mins
Test Coverage
import execa from 'execa';
import normalizeUrl from 'normalize-url';
import path from 'path';
import rc from 'rc';
import getAuthToken from 'registry-auth-token';
import getRegistryUrl from 'registry-auth-token/registry-url';
import { outputFile, readFile } from 'fs-extra';
import nerfDart from 'nerf-dart';
import { getError } from './error';

const NPMX_DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/';

// Set the environment variable `LEGACY_TOKEN` when user use the legacy auth, so it can be resolved by npm CLI
export function getLegacyToken({ NPM_EMAIL, NPM_PASSWORD, NPM_USERNAME } = {}) {
  if (NPM_USERNAME && NPM_PASSWORD && NPM_EMAIL) {
    return {
      LEGACY_TOKEN: Buffer.from(`${NPM_USERNAME}:${NPM_PASSWORD}`, 'utf8').toString('base64'),
    };
  }

  return {};
}

export async function setAuth(
  npmrc,
  registry,
  { cwd = '.', env: { NPM_TOKEN, NPM_EMAIL, NPM_CONFIG_USERCONFIG, LEGACY_TOKEN } = {}, logger },
) {
  logger.log('Verify authentication for registry %s', registry);

  const { configs, ...rcConfig } = rc(
    'npm',
    { registry: NPMX_DEFAULT_NPM_REGISTRY },
    { config: NPM_CONFIG_USERCONFIG || path.resolve(cwd, '.npmrc') },
  );
  let currentConfig = '';

  if (configs.length > 0) {
    logger.log('Reading npm config from %s', configs.join(', '));

    currentConfig = await Promise.all(configs.map(c => readFile(c)));
    currentConfig = `${currentConfig.join('\n')}\n`;
  }

  if (getAuthToken(registry, { npmrc: rcConfig, recursive: true })) {
    await outputFile(npmrc, currentConfig.trim());
  } else if (LEGACY_TOKEN && NPM_EMAIL) {
    await outputFile(npmrc, `${currentConfig}_auth = \${LEGACY_TOKEN}\nemail = \${NPM_EMAIL}`);

    logger.log(`Wrote NPM_USERNAME, NPM_PASSWORD and NPM_EMAIL to ${npmrc}`);
  } else if (NPM_TOKEN) {
    await outputFile(npmrc, `${currentConfig}${nerfDart(registry)}:_authToken = \${NPM_TOKEN}`);

    logger.log(`Wrote NPM_TOKEN to ${npmrc}`);
  } else {
    throw getError('ENONPMTOKEN', { registry });
  }
}

export function getReleasesInfo(
  { name },
  { env: { DEFAULT_NPM_REGISTRY = NPMX_DEFAULT_NPM_REGISTRY } = {}, nextRelease: { version } },
  distTag,
  registry,
) {
  return {
    name: `npm package (@${distTag} dist-tag)`,
    url:
      normalizeUrl(registry) === normalizeUrl(DEFAULT_NPM_REGISTRY)
        ? `https://www.npmjs.com/package/${name}/v/${version}`
        : undefined,
    channel: distTag,
  };
}

export function getRegistry(
  { publishConfig: { registry } = {}, name },
  { cwd = '.', env = {} } = {},
) {
  return (
    registry ||
    env.NPM_CONFIG_REGISTRY ||
    getRegistryUrl(
      name.split('/')[0],
      rc('npm', { registry: NPMX_DEFAULT_NPM_REGISTRY }, { config: path.resolve(cwd, '.npmrc') }),
    )
  );
}

export async function verifyNpmAuth(npmrc, context, pkg) {
  const {
    cwd,
    stdout,
    stderr,
    env = { DEFAULT_NPM_REGISTRY: NPMX_DEFAULT_NPM_REGISTRY, ...context.env },
  } = context;
  const registry = getRegistry(pkg, context);

  await setAuth(npmrc, registry, context);

  if (
    env.DEFAULT_NPM_REGISTRY &&
    normalizeUrl(registry) === normalizeUrl(env.DEFAULT_NPM_REGISTRY)
  ) {
    try {
      const whoamiResult = execa('npm', ['whoami', '--userconfig', npmrc, '--registry', registry], {
        cwd,
        env,
      });

      whoamiResult.stdout.pipe(stdout, { end: false });
      whoamiResult.stderr.pipe(stderr, { end: false });

      await whoamiResult;
    } catch (e) {
      throw getError('EINVALIDNPMTOKEN', { registry });
    }
  }
}

export function summarizeReleasesInfo(releasesInfo) {
  const validReleases = releasesInfo.filter(i => i !== false);
  const urls = validReleases.filter(i => i.url !== undefined).map(i => i.url);

  if (validReleases.length === 0) return false;

  return {
    name: validReleases[0].name,
    urls,
    channel: validReleases[0].channel,
  };
}