UNPKG

@salesforce/plugin-release-management

Version:
166 lines 6.21 kB
/* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import os from 'node:os'; import { Ux } from '@salesforce/sf-plugins-core'; import shelljs from 'shelljs'; import { Logger, SfError } from '@salesforce/core'; import { AsyncOptionalCreatable, Env, sleep } from '@salesforce/kit'; import chalk from 'chalk'; import { isString } from '@salesforce/ts-types'; import { Package } from './package.js'; import { Registry } from './registry.js'; import { api as packAndSignApi } from './codeSigning/packAndSign.js'; class Repository extends AsyncOptionalCreatable { options; ux; env; registry; stepCounter = 1; constructor(options) { super(options); this.options = options; this.ux = options?.ux ?? new Ux(); this.env = new Env(); this.registry = new Registry(); } install(silent = false) { this.execCommand(`yarn install ${this.registry.getRegistryParameter()}`, silent); } build(silent = false) { this.execCommand('yarn build', silent); } run(script, location, silent = false) { if (location) { this.execCommand(`(cd ${location} && yarn run ${script})`, silent); } else { this.execCommand(`(yarn run ${script})`, silent); } } test() { this.execCommand('yarn test'); } printStage(msg) { this.ux.log(chalk.green.bold(`${os.EOL}${this.stepCounter}) ${msg}`)); this.stepCounter += 1; } async writeNpmToken() { const home = this.env.getString('HOME') ?? os.homedir(); await this.registry.setNpmAuth(home); await this.registry.setNpmRegistry(home); } execCommand(cmd, silent) { if (!silent) this.ux.log(`${chalk.dim(cmd)}${os.EOL}`); const result = shelljs.exec(cmd, { silent }); if (result.code !== 0) { throw new SfError(result.stderr, 'FailedCommandExecution'); } else { return result; } } async poll(checkFn) { const isNonTTY = this.env.getBoolean('CI') || this.env.getBoolean('CIRCLECI'); let found = false; let attempts = 0; const maxAttempts = 300; const start = isNonTTY ? (msg) => this.ux.log(msg) : (msg) => this.ux.spinner.start(msg); const update = isNonTTY ? (msg) => this.ux.log(msg) : (msg) => (this.ux.spinner.status = msg); const stop = isNonTTY ? (msg) => this.ux.log(msg) : (msg) => this.ux.spinner.stop(msg); start('Polling for new version(s) to become available on npm'); while (attempts < maxAttempts && !found) { attempts += 1; update(`attempt: ${attempts} of ${maxAttempts}`); found = checkFn(); // eslint-disable-next-line no-await-in-loop await sleep(1000); } stop(attempts >= maxAttempts ? 'failed' : 'done'); return found; } } export class PackageRepo extends Repository { // all props are set in init(), so ! is safe name; nextVersion; package; // Both loggers are used because some logs we only want to show in the debug output // but other logs we always want to go to stdout logger; constructor(options) { super(options); } async sign() { packAndSignApi.setUx(this.ux); return packAndSignApi.packSignVerifyModifyPackageJSON(this.package.location); } // eslint-disable-next-line class-methods-use-this async revertChanges() { return packAndSignApi.revertPackageJsonIfExists(); } getPkgInfo() { return { name: this.name, nextVersion: this.nextVersion, registryParam: this.registry.getRegistryParameter(), }; } async publish(opts = {}) { const { dryrun, signatures, access, tag } = opts; if (!dryrun) await this.writeNpmToken(); let cmd = 'npm publish'; if (signatures?.[0]?.fileTarPath) cmd += ` ${signatures[0]?.fileTarPath}`; if (tag) cmd += ` --tag ${tag}`; if (dryrun) cmd += ' --dry-run'; cmd += ` ${this.registry.getRegistryParameter()}`; cmd += ` --access ${access ?? 'public'}`; this.execCommand(cmd); } async waitForAvailability() { return this.poll(() => this.package.nextVersionIsAvailable(this.nextVersion)); } getSuccessMessage() { return chalk.green.bold(`Successfully released ${this.name}@${this.nextVersion}`); } async init() { this.logger = await Logger.child(this.constructor.name); this.package = await Package.create({ location: undefined }); this.nextVersion = this.determineNextVersion(); this.name = this.package.npmPackage.name; } determineNextVersion() { if (this.package.nextVersionIsHardcoded()) { this.logger.debug(`${this.package.packageJson.name}@${this.package.packageJson.version} does not exist in the registry. Assuming that it's the version we want published`); return this.package.packageJson.version; } else { this.logger.debug('Using commit-and-tag-version to determine next version'); let command = 'npx commit-and-tag-version --dry-run --skip.tag --skip.commit --skip.changelog'; // It can be an empty string if they want if (isString(this.options?.useprerelease)) { command += ` --prerelease ${this.options?.useprerelease}`; } const result = this.execCommand(command, true); const nextVersionRegex = /(?<=to\s)([0-9]{1,}\.|.){2,}/gi; const nextVersion = result.match(nextVersionRegex)?.[0]; if (!nextVersion) { throw new SfError(`Could not determine next version from ${result} using regex`); } return nextVersion; } } } //# sourceMappingURL=repository.js.map