213 lines
8.2 KiB
JavaScript
213 lines
8.2 KiB
JavaScript
|
"use strict";
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||
|
// See LICENSE in the project root for license information.
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.PackageName = exports.PackageNameParser = void 0;
|
||
|
/**
|
||
|
* A configurable parser for validating and manipulating NPM package names such as `my-package` or `@scope/my-package`.
|
||
|
*
|
||
|
* @remarks
|
||
|
* If you do not need to customize the parser configuration, it is recommended to use {@link PackageName}
|
||
|
* which exposes these operations as a simple static class.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
class PackageNameParser {
|
||
|
constructor(options = {}) {
|
||
|
this._options = Object.assign({}, options);
|
||
|
}
|
||
|
/**
|
||
|
* This attempts to parse a package name that may include a scope component.
|
||
|
* The packageName must not be an empty string.
|
||
|
* @remarks
|
||
|
* This function will not throw an exception.
|
||
|
*
|
||
|
* @returns an {@link IParsedPackageNameOrError} structure whose `error` property will be
|
||
|
* nonempty if the string could not be parsed.
|
||
|
*/
|
||
|
tryParse(packageName) {
|
||
|
const result = {
|
||
|
scope: '',
|
||
|
unscopedName: '',
|
||
|
error: ''
|
||
|
};
|
||
|
let input = packageName;
|
||
|
if (input === null || input === undefined) {
|
||
|
result.error = 'The package name must not be null or undefined';
|
||
|
return result;
|
||
|
}
|
||
|
// Rule from npmjs.com:
|
||
|
// "The name must be less than or equal to 214 characters. This includes the scope for scoped packages."
|
||
|
if (packageName.length > 214) {
|
||
|
// Don't attempt to parse a ridiculously long input
|
||
|
result.error = 'The package name cannot be longer than 214 characters';
|
||
|
return result;
|
||
|
}
|
||
|
if (input[0] === '@') {
|
||
|
const indexOfScopeSlash = input.indexOf('/');
|
||
|
if (indexOfScopeSlash <= 0) {
|
||
|
result.scope = input;
|
||
|
result.error = `Error parsing "${packageName}": The scope must be followed by a slash`;
|
||
|
return result;
|
||
|
}
|
||
|
// Extract the scope substring
|
||
|
result.scope = input.substr(0, indexOfScopeSlash);
|
||
|
input = input.substr(indexOfScopeSlash + 1);
|
||
|
}
|
||
|
result.unscopedName = input;
|
||
|
if (result.scope === '@') {
|
||
|
result.error = `Error parsing "${packageName}": The scope name cannot be empty`;
|
||
|
return result;
|
||
|
}
|
||
|
if (result.unscopedName === '') {
|
||
|
result.error = 'The package name must not be empty';
|
||
|
return result;
|
||
|
}
|
||
|
// Rule from npmjs.com:
|
||
|
// "The name can't start with a dot or an underscore."
|
||
|
if (result.unscopedName[0] === '.' || result.unscopedName[0] === '_') {
|
||
|
result.error = `The package name "${packageName}" starts with an invalid character`;
|
||
|
return result;
|
||
|
}
|
||
|
// Convert "@scope/unscoped-name" --> "scopeunscoped-name"
|
||
|
const nameWithoutScopeSymbols = (result.scope ? result.scope.slice(1, -1) : '') + result.unscopedName;
|
||
|
if (!this._options.allowUpperCase) {
|
||
|
// "New packages must not have uppercase letters in the name."
|
||
|
// This can't be enforced because "old" packages are still actively maintained.
|
||
|
// Example: https://www.npmjs.com/package/Base64
|
||
|
// However it's pretty reasonable to require the scope to be lower case
|
||
|
if (result.scope !== result.scope.toLowerCase()) {
|
||
|
result.error = `The package scope "${result.scope}" must not contain upper case characters`;
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
// "The name ends up being part of a URL, an argument on the command line, and a folder name.
|
||
|
// Therefore, the name can't contain any non-URL-safe characters"
|
||
|
const match = nameWithoutScopeSymbols.match(PackageNameParser._invalidNameCharactersRegExp);
|
||
|
if (match) {
|
||
|
result.error = `The package name "${packageName}" contains an invalid character: "${match[0]}"`;
|
||
|
return result;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
/**
|
||
|
* Same as {@link PackageName.tryParse}, except this throws an exception if the input
|
||
|
* cannot be parsed.
|
||
|
* @remarks
|
||
|
* The packageName must not be an empty string.
|
||
|
*/
|
||
|
parse(packageName) {
|
||
|
const result = this.tryParse(packageName);
|
||
|
if (result.error) {
|
||
|
throw new Error(result.error);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
/**
|
||
|
* {@inheritDoc IParsedPackageName.scope}
|
||
|
*/
|
||
|
getScope(packageName) {
|
||
|
return this.parse(packageName).scope;
|
||
|
}
|
||
|
/**
|
||
|
* {@inheritDoc IParsedPackageName.unscopedName}
|
||
|
*/
|
||
|
getUnscopedName(packageName) {
|
||
|
return this.parse(packageName).unscopedName;
|
||
|
}
|
||
|
/**
|
||
|
* Returns true if the specified package name is valid, or false otherwise.
|
||
|
* @remarks
|
||
|
* This function will not throw an exception.
|
||
|
*/
|
||
|
isValidName(packageName) {
|
||
|
const result = this.tryParse(packageName);
|
||
|
return !result.error;
|
||
|
}
|
||
|
/**
|
||
|
* Throws an exception if the specified name is not a valid package name.
|
||
|
* The packageName must not be an empty string.
|
||
|
*/
|
||
|
validate(packageName) {
|
||
|
this.parse(packageName);
|
||
|
}
|
||
|
/**
|
||
|
* Combines an optional package scope with an unscoped root name.
|
||
|
* @param scope - Must be either an empty string, or a scope name such as "\@example"
|
||
|
* @param unscopedName - Must be a nonempty package name that does not contain a scope
|
||
|
* @returns A full package name such as "\@example/some-library".
|
||
|
*/
|
||
|
combineParts(scope, unscopedName) {
|
||
|
if (scope !== '') {
|
||
|
if (scope[0] !== '@') {
|
||
|
throw new Error('The scope must start with an "@" character');
|
||
|
}
|
||
|
}
|
||
|
if (scope.indexOf('/') >= 0) {
|
||
|
throw new Error('The scope must not contain a "/" character');
|
||
|
}
|
||
|
if (unscopedName[0] === '@') {
|
||
|
throw new Error('The unscopedName cannot start with an "@" character');
|
||
|
}
|
||
|
if (unscopedName.indexOf('/') >= 0) {
|
||
|
throw new Error('The unscopedName must not contain a "/" character');
|
||
|
}
|
||
|
let result;
|
||
|
if (scope === '') {
|
||
|
result = unscopedName;
|
||
|
}
|
||
|
else {
|
||
|
result = scope + '/' + unscopedName;
|
||
|
}
|
||
|
// Make sure the result is a valid package name
|
||
|
this.validate(result);
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
// encodeURIComponent() escapes all characters except: A-Z a-z 0-9 - _ . ! ~ * ' ( )
|
||
|
// However, these are disallowed because they are shell characters: ! ~ * ' ( )
|
||
|
PackageNameParser._invalidNameCharactersRegExp = /[^A-Za-z0-9\-_\.]/;
|
||
|
exports.PackageNameParser = PackageNameParser;
|
||
|
/**
|
||
|
* Provides basic operations for validating and manipulating NPM package names such as `my-package`
|
||
|
* or `@scope/my-package`.
|
||
|
*
|
||
|
* @remarks
|
||
|
* This is the default implementation of {@link PackageNameParser}, exposed as a convenient static class.
|
||
|
* If you need to configure the parsing rules, use `PackageNameParser` instead.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
class PackageName {
|
||
|
/** {@inheritDoc PackageNameParser.tryParse} */
|
||
|
static tryParse(packageName) {
|
||
|
return PackageName._parser.tryParse(packageName);
|
||
|
}
|
||
|
/** {@inheritDoc PackageNameParser.parse} */
|
||
|
static parse(packageName) {
|
||
|
return this._parser.parse(packageName);
|
||
|
}
|
||
|
/** {@inheritDoc PackageNameParser.getScope} */
|
||
|
static getScope(packageName) {
|
||
|
return this._parser.getScope(packageName);
|
||
|
}
|
||
|
/** {@inheritDoc PackageNameParser.getUnscopedName} */
|
||
|
static getUnscopedName(packageName) {
|
||
|
return this._parser.getUnscopedName(packageName);
|
||
|
}
|
||
|
/** {@inheritDoc PackageNameParser.isValidName} */
|
||
|
static isValidName(packageName) {
|
||
|
return this._parser.isValidName(packageName);
|
||
|
}
|
||
|
/** {@inheritDoc PackageNameParser.validate} */
|
||
|
static validate(packageName) {
|
||
|
return this._parser.validate(packageName);
|
||
|
}
|
||
|
/** {@inheritDoc PackageNameParser.combineParts} */
|
||
|
static combineParts(scope, unscopedName) {
|
||
|
return this._parser.combineParts(scope, unscopedName);
|
||
|
}
|
||
|
}
|
||
|
PackageName._parser = new PackageNameParser();
|
||
|
exports.PackageName = PackageName;
|
||
|
//# sourceMappingURL=PackageName.js.map
|