frontend/.pnpm-store/v3/files/26/de922ab8ef0be6547580ac2384d21c405f5d21eb1c381fc6b51c382221b17b6f4933a3443e90ae88da91ced6b139510570b0ed8095d8858dff05c4f4589215

273 lines
8.5 KiB
Plaintext

import path from 'node:path';
import semver from 'semver';
import urlJoin from 'url-join';
import Plugin from '../Plugin.js';
import { hasAccess, rejectAfter, parseVersion, readJSON, e } from '../../util.js';
import prompts from './prompts.js';
const docs = 'https://git.io/release-it-npm';
const options = { write: false };
const MANIFEST_PATH = './package.json';
const DEFAULT_TAG = 'latest';
const DEFAULT_TAG_PRERELEASE = 'next';
const NPM_BASE_URL = 'https://www.npmjs.com';
const fixArgs = args => (args ? (typeof args === 'string' ? args.split(' ') : args) : []);
class npm extends Plugin {
static isEnabled(options) {
return hasAccess(MANIFEST_PATH) && options !== false;
}
constructor(...args) {
super(...args);
this.registerPrompts(prompts);
}
async init() {
const { name, version: latestVersion, private: isPrivate, publishConfig } = readJSON(path.resolve(MANIFEST_PATH));
this.setContext({ name, latestVersion, private: isPrivate, publishConfig });
this.config.setContext({ npm: { name } });
const { publish, skipChecks } = this.options;
const timeout = Number(this.options.timeout) * 1000;
if (publish === false || isPrivate) return;
if (skipChecks) return;
const validations = Promise.all([this.isRegistryUp(), this.isAuthenticated(), this.getLatestRegistryVersion()]);
await Promise.race([validations, rejectAfter(timeout, e(`Timed out after ${timeout}ms.`, docs))]);
const [isRegistryUp, isAuthenticated, latestVersionInRegistry] = await validations;
if (!isRegistryUp) {
throw e(`Unable to reach npm registry (timed out after ${timeout}ms).`, docs);
}
if (!isAuthenticated) {
throw e('Not authenticated with npm. Please `npm login` and try again.', docs);
}
if (!(await this.isCollaborator())) {
const { username } = this.getContext();
throw e(`User ${username} is not a collaborator for ${name}.`, docs);
}
if (!latestVersionInRegistry) {
this.log.warn('No version found in npm registry. Assuming new package.');
} else {
if (!semver.eq(latestVersion, latestVersionInRegistry)) {
this.log.warn(
`Latest version in registry (${latestVersionInRegistry}) does not match package.json (${latestVersion}).`
);
}
}
}
getName() {
return this.getContext('name');
}
getLatestVersion() {
return this.options.ignoreVersion ? null : this.getContext('latestVersion');
}
async bump(version) {
const tag = this.options.tag || (await this.resolveTag(version));
this.setContext({ version, tag });
if (!this.config.isIncrement) return false;
const { versionArgs, allowSameVersion } = this.options;
const args = [version, '--no-git-tag-version', allowSameVersion && '--allow-same-version', ...fixArgs(versionArgs)];
const task = () => this.exec(`npm version ${args.filter(Boolean).join(' ')}`);
return this.spinner.show({ task, label: 'npm version' });
}
release() {
if (this.options.publish === false) return false;
if (this.getContext('private')) return false;
const publish = () => this.publish({ otpCallback });
const otpCallback =
this.config.isCI && !this.config.isPromptOnlyVersion ? null : task => this.step({ prompt: 'otp', task });
return this.step({ task: publish, label: 'npm publish', prompt: 'publish' });
}
isRegistryUp() {
const registry = this.getRegistry();
const registryArg = registry ? ` --registry ${registry}` : '';
return this.exec(`npm ping${registryArg}`, { options }).then(
() => true,
err => {
if (/code E40[04]|404.*(ping not found|No content for path)/.test(err)) {
this.log.warn('Ignoring response from unsupported `npm ping` command.');
return true;
}
return false;
}
);
}
isAuthenticated() {
const registry = this.getRegistry();
const registryArg = registry ? ` --registry ${registry}` : '';
return this.exec(`npm whoami${registryArg}`, { options }).then(
output => {
const username = output ? output.trim() : null;
this.setContext({ username });
return true;
},
err => {
this.debug(err);
if (/code E40[04]/.test(err)) {
this.log.warn('Ignoring response from unsupported `npm whoami` command.');
return true;
}
return false;
}
);
}
async isCollaborator() {
const registry = this.getRegistry();
const registryArg = registry ? ` --registry ${registry}` : '';
const name = this.getName();
const { username } = this.getContext();
if (username === undefined) return true;
if (username === null) return false;
try {
let npmVersion = await this.exec('npm --version', { options });
let accessCommand;
if (semver.gt(npmVersion, '9.0.0')) {
accessCommand = 'npm access list collaborators --json';
} else {
accessCommand = 'npm access ls-collaborators';
}
const output = await this.exec(`${accessCommand} ${name}${registryArg}`, { options });
try {
const collaborators = JSON.parse(output);
const permissions = collaborators[username];
return permissions && permissions.includes('write');
} catch (err) {
this.debug(err);
return false;
}
} catch (err) {
this.debug(err);
if (/code E400/.test(err)) {
this.log.warn('Ignoring response from unsupported `npm access` command.');
} else {
this.log.warn(`Unable to verify if user ${username} is a collaborator for ${name}.`);
}
return true;
}
}
async getLatestRegistryVersion() {
const registry = this.getRegistry();
const registryArg = registry ? ` --registry ${registry}` : '';
const name = this.getName();
const latestVersion = this.getLatestVersion();
const tag = await this.resolveTag(latestVersion);
return this.exec(`npm show ${name}@${tag} version${registryArg}`, { options }).catch(() => null);
}
getRegistryPreReleaseTags() {
return this.exec(`npm view ${this.getName()} dist-tags --json`, { options }).then(
output => {
try {
const tags = JSON.parse(output);
return Object.keys(tags).filter(tag => tag !== DEFAULT_TAG);
} catch (err) {
this.debug(err);
return [];
}
},
() => []
);
}
getPackageUrl() {
const baseUrl = this.getRegistry() || NPM_BASE_URL;
return urlJoin(baseUrl, 'package', this.getName());
}
getRegistry() {
const { publishConfig } = this.getContext();
const registries = publishConfig
? publishConfig.registry
? [publishConfig.registry]
: Object.keys(publishConfig)
.filter(key => key.endsWith('registry'))
.map(key => publishConfig[key])
: [];
return registries[0];
}
async guessPreReleaseTag() {
const [tag] = await this.getRegistryPreReleaseTags();
if (tag) {
return tag;
} else {
this.log.warn(`Unable to get pre-release tag(s) from npm registry. Using "${DEFAULT_TAG_PRERELEASE}".`);
return DEFAULT_TAG_PRERELEASE;
}
}
async resolveTag(version) {
const { tag } = this.options;
const { isPreRelease, preReleaseId } = parseVersion(version);
if (!isPreRelease) {
return DEFAULT_TAG;
} else {
return tag || preReleaseId || (await this.guessPreReleaseTag());
}
}
async publish({ otp = this.options.otp, otpCallback } = {}) {
const { publishPath = '.', publishArgs } = this.options;
const { private: isPrivate, tag = DEFAULT_TAG } = this.getContext();
const otpArg = otp ? `--otp ${otp}` : '';
const dryRunArg = this.config.isDryRun ? '--dry-run' : '';
if (isPrivate) {
this.log.warn('Skip publish: package is private.');
return false;
}
const args = [publishPath, `--tag ${tag}`, otpArg, dryRunArg, ...fixArgs(publishArgs)].filter(Boolean);
return this.exec(`npm publish ${args.join(' ')}`, { options })
.then(() => {
this.setContext({ isReleased: true });
})
.catch(err => {
this.debug(err);
if (/one-time pass/.test(err)) {
if (otp != null) {
this.log.warn('The provided OTP is incorrect or has expired.');
}
if (otpCallback) {
return otpCallback(otp => this.publish({ otp, otpCallback }));
}
}
throw err;
});
}
afterRelease() {
const { isReleased } = this.getContext();
if (isReleased) {
this.log.log(`🔗 ${this.getPackageUrl()}`);
}
}
}
export default npm;