UNPKG

@process-engine/ci_tools

Version:
137 lines (107 loc) 4.72 kB
import * as yargsParser from 'yargs-parser'; import { readFileSync } from 'fs'; import * as chalk from 'chalk'; import { getGitBranch, mapReleaseChannelNameToBranch } from '../git/git'; import { getNpmTag } from '../npm/tag'; import { getPackageVersion } from '../versions/package_version'; import { printMultiLineString } from '../cli/printMultiLineString'; import { setupNpm } from './internal/setup-git-and-npm-connections'; import { sh } from '../cli/shell'; import { isRedundantRunTriggeredBySystemUserPush } from '../versions/retry_run'; import { parseVersion } from '../versions/parse_version'; const COMMAND_NAME = 'publish-npm-package'; const BADGE = `[${COMMAND_NAME}]\t`; const DEFAULT_MODE = 'node'; const DOC = ` Publishes the current package to npm. Does not complain if re-run (providing idempotency for CI). `; // DOC: see above export async function run(...args): Promise<boolean> { const argv = yargsParser(args, { default: { mode: DEFAULT_MODE } }); const isDryRun = argv.dry === true; const createTagFromBranchName = argv.createTagFromBranchName === true; const mode = argv.mode; const packageName = getPackageName(); const packageVersion = await getPackageVersion(mode); const npmPublishShellCommand = getNpmPublishShellCommand(createTagFromBranchName, isDryRun, packageVersion); setupNpm(); const npmPublishShellCommandOutput = annotatedSh(npmPublishShellCommand); const lines = npmPublishShellCommandOutput.trim().split('\n'); const expectedMessage = `+ ${packageName}@${packageVersion}`; const publishCommandSuccessful = lines[lines.length - 1] === expectedMessage; if (publishCommandSuccessful) { await ensureVersionIsAvailable(packageName, packageVersion, isDryRun); } else { const isAlreadyPublished = npmPublishShellCommandOutput.match(/You cannot publish over the previously published versions/gi) != null; if (isAlreadyPublished) { console.log(chalk.yellow(`${BADGE}This package version was already published: '${packageVersion}'.`)); } if (await isRedundantRunTriggeredBySystemUserPush(mode)) { console.error(chalk.yellowBright(`${BADGE}Nothing to do here!`)); process.exit(0); } } return publishCommandSuccessful; } export function getShortDoc(): string { return DOC.trim().split('\n')[0]; } export function printHelp(): void { console.log(`Usage: ci_tools ${COMMAND_NAME} [--create-tag-from-branch-name] [--dry]`); console.log(''); console.log(DOC.trim()); } function getNpmPublishShellCommand(useBranchForTag: boolean, isDryRun: boolean, packageVersion: string): string { const dryRun = isDryRun ? '--dry-run ' : ''; const gitBranch = getGitBranch(); const parsedVersion = parseVersion(packageVersion); const branchName = gitBranch || mapReleaseChannelNameToBranch(parsedVersion.releaseChannelName); const npmTag = getNpmTag(branchName); const tag = useBranchForTag && npmTag ? `--tag ${npmTag} ` : ''; return `npm publish ${dryRun}${tag}`.trim(); } function annotatedSh(cmd: string): string { console.log(`${BADGE}|>>> ${cmd}`); const output = sh(cmd); printMultiLineString(output, `${BADGE}| `); return output; } function getPackageName(): string { const content = readFileSync('package.json').toString(); const json = JSON.parse(content); return json.name; } async function ensureVersionIsAvailable(packageName: string, packageVersion: string, isDryRun = false): Promise<void> { if (isDryRun) { console.log(chalk.green(`${BADGE}Successfully published version '${packageVersion}'.`)); return; } const viewCommand = `npm view ${packageName} versions --json`; let packageVersionFound = false; // TODO: It is certainly debatable on what the best settings would be here. // For now, a window of 30 minutes is granted, before the publishing is regarded as failure. const maxNumberOfRetries = 3600; const timeoutBetweenRetriesInMs = 500; let currentTry = 0; while (packageVersionFound === false && currentTry < maxNumberOfRetries) { const versions = sh(viewCommand); packageVersionFound = versions.includes(packageVersion); if (packageVersionFound) { break; } else { console.log( chalk.yellow(`${BADGE}Version '${packageVersion}' not found. Retrying in ${timeoutBetweenRetriesInMs}ms...`) ); currentTry++; await new Promise((resolve): any => setTimeout(resolve, timeoutBetweenRetriesInMs)); } } if (packageVersionFound) { console.log(chalk.green(`${BADGE}Successfully published version '${packageVersion}'.`)); } else { console.error(chalk.red(`${BADGE}Version '${packageVersion}' is not reported by 'npm view'.`)); process.exit(1); } }