@process-engine/ci_tools
Version:
CI tools for process-engine.io
137 lines (107 loc) • 4.72 kB
text/typescript
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);
}
}