UNPKG

@yeoman/namespace

Version:

Namespace parsing for yeoman's generator/environment stack

279 lines 9.08 kB
const NAME_REGEX = /^[a-z\d-~][a-z\d-._~]*$/; const SEMVER_REGEX = /^[a-z\d-. *+<=>^~]*$/; const METHOD_REGEX = /^[\dA-Za-z]\w*$/; const TOKEN_SCOPE_SEPARATOR = '/'; const TOKEN_GENERATOR = ':'; const TOKEN_SEMVER = '@'; const TOKEN_INSTANCE_ID = '#'; const TOKEN_METHOD = '+'; const TOKEN_OPTIONAL = '?'; const UNESCOPED_STOP_TOKENS = [TOKEN_GENERATOR, TOKEN_SEMVER, TOKEN_INSTANCE_ID, TOKEN_METHOD, TOKEN_OPTIONAL]; const GENERATOR_STOP_TOKENS = [TOKEN_SEMVER, TOKEN_INSTANCE_ID, TOKEN_METHOD, TOKEN_OPTIONAL]; const INSTANCE_ID_STOP_TOKENS = [TOKEN_METHOD, TOKEN_OPTIONAL]; const flags = { optional: '?' }; export const ENTRYPOINT_GENERATOR_NAME = 'app'; function nextTokenIndex(value, start, tokens) { let end = value.length; for (const token of tokens) { const index = value.indexOf(token, start); if (index !== -1 && index < end) { end = index; } } return end; } function isName(value) { return NAME_REGEX.test(value); } function isGenerator(value) { return value.length > 0 && value.split(':').every(value => isName(value)); } function isSemver(value) { return SEMVER_REGEX.test(value); } function isMethodList(value) { if (value === '') { return true; } const methods = value.split('+'); for (let index = 0; index < methods.length; index += 1) { const method = methods[index]; // Keep compatibility with trailing '+' (e.g. 'build+') from previous parser behavior. if (method === '' && index === methods.length - 1) { return true; } if (!METHOD_REGEX.test(method)) { return false; } } return true; } function parseNamespace(complete) { if (typeof complete !== 'string') { throw new TypeError('Must be a string'); } let cursor = 0; const parsed = { complete, unscoped: '' }; if (complete.startsWith('@')) { const scopeEnd = complete.indexOf(TOKEN_SCOPE_SEPARATOR); if (scopeEnd === -1) { return; } const scope = complete.slice(0, scopeEnd); if (!isName(scope.slice(1))) { return; } parsed.scope = scope; cursor = scopeEnd + 1; } const unscopedEnd = nextTokenIndex(complete, cursor, UNESCOPED_STOP_TOKENS); const unscoped = complete.slice(cursor, unscopedEnd); if (!isName(unscoped)) { return; } parsed.unscoped = unscoped; cursor = unscopedEnd; if (complete.charAt(cursor) === TOKEN_GENERATOR) { cursor += 1; const generatorEnd = nextTokenIndex(complete, cursor, GENERATOR_STOP_TOKENS); const generator = complete.slice(cursor, generatorEnd); if (!isGenerator(generator)) { return; } parsed.generator = generator; cursor = generatorEnd; } if (complete.charAt(cursor) === TOKEN_SEMVER) { cursor += 1; const hashAt = complete.indexOf(TOKEN_INSTANCE_ID, cursor); const flagAt = complete.indexOf(TOKEN_OPTIONAL, cursor); let tokenEnd = complete.length; if (hashAt !== -1 && hashAt < tokenEnd) { tokenEnd = hashAt; } if (flagAt !== -1 && flagAt < tokenEnd) { tokenEnd = flagAt; } let semverEnd = tokenEnd; let nextCursor = tokenEnd; const closingAt = complete.indexOf(TOKEN_SEMVER, cursor); if (closingAt !== -1 && closingAt <= tokenEnd) { semverEnd = closingAt; nextCursor = closingAt + 1; } const semver = complete.slice(cursor, semverEnd); if (!isSemver(semver)) { return; } parsed.semver = semver; cursor = nextCursor; } if (complete.charAt(cursor) === TOKEN_INSTANCE_ID) { cursor += 1; const instanceIdEnd = nextTokenIndex(complete, cursor, INSTANCE_ID_STOP_TOKENS); const instanceId = complete.slice(cursor, instanceIdEnd); if (instanceId !== '*' && !isName(instanceId)) { return; } parsed.instanceId = instanceId; cursor = instanceIdEnd; } if (complete.charAt(cursor) === TOKEN_METHOD) { cursor += 1; const methodsEnd = complete.endsWith(TOKEN_OPTIONAL) ? complete.length - 1 : complete.length; const method = complete.slice(cursor, methodsEnd); if (!isMethodList(method)) { return; } parsed.method = method; cursor = methodsEnd; } if (complete.charAt(cursor) === TOKEN_OPTIONAL) { parsed.flags = TOKEN_OPTIONAL; cursor += 1; } if (cursor !== complete.length) { return; } return new YeomanNamespace(parsed); } export class YeomanNamespace { _original; scope; unscoped; generator; instanceId; semver; methods; flags; command; constructor(parsed) { this._original = parsed.complete; this.scope = parsed.scope; this.unscoped = parsed.unscoped; this.generator = parsed.generator; this.instanceId = parsed.instanceId; this.semver = parsed.semver; this.methods = parsed.method?.split('+'); this.flags = parsed.flags; // Populate flags if (this.flags) { for (const [name, value] of Object.entries(flags)) { if (this.flags === value) { this[name] = true; } else { delete this[name]; } } } } get _scopeAddition() { return this.scope ? `${this.scope}/` : ''; } get generatorName() { return this.generator ? `:${this.generator}` : ''; } get instanceName() { return this.instanceId ? `#${this.instanceId}` : ''; } get complete() { let methods = ''; if (this.methods && this.methods.length > 0) { methods = `+${this.methods.join('+')}`; } const postSemver = `${this.instanceName}${methods}${this.flags ?? ''}`; return `${this.namespace}${this._semverAddition(postSemver)}`; } get packageNamespace() { return `${this._scopeAddition}${this.unscoped}`; } get namespace() { return `${this.packageNamespace}${this.generatorName}`; } set namespace(namespace) { const parsed = parseNamespace(namespace); if (!parsed) { throw new Error(`Error parsing namespace ${namespace}`); } this._update(parsed); } get unscopedNamespace() { return `${this.unscoped}${this.generatorName}`; } get id() { return `${this.namespace}${this.instanceName}`; } get generatorHint() { return `${this._scopeAddition}generator-${this.unscoped}`; } get versionedHint() { return this.semver ? `${this.generatorHint}@"${this.semver}"` : this.generatorHint; } with(newValues) { return new YeomanNamespace({ ...this, ...newValues, }); } toString() { return this.complete; } _semverAddition(post) { if (!this.semver) { return post ?? ''; } if (post) { return `@${this.semver}@${post}`; } return `@${this.semver}`; } _update(parsed) { this.scope = parsed.scope ?? this.scope; this.unscoped = parsed.unscoped ?? this.unscoped; this.generator = parsed.generator ?? this.generator; this.instanceId = parsed.instanceId ?? this.instanceId; this.command = parsed.command ?? this.command; this.flags = parsed.flags ?? this.flags; } } /** * Convert a namespace to a namespace object */ export function toNamespace(namespace) { return isNamespaceObject(namespace) ? namespace : parseNamespace(namespace); } /** * Convert a package name to a namespace object * * @throws if not a valid generator package name (starts with 'generator-') */ export function namespaceFromPackageName(packageName) { const namespace = parseNamespace(packageName); if (!namespace?.unscoped?.startsWith('generator-')) { throw new Error(`${packageName} is not a valid generator package name`); } namespace.unscoped = namespace.unscoped.replace(/^generator-/, ''); return namespace; } export function requireNamespace(namespace, options = {}) { const { allowPackageOnlyNamespace = true } = options; const parsed = toNamespace(namespace); if (!parsed) { throw new Error(`Error parsing namespace ${namespace.toString()}`); } if (allowPackageOnlyNamespace || hasGenerator(parsed)) { return parsed; } throw new Error(`Namespace ${namespace.toString()} does not contain a generator`); } /** * Test if the object is an YeomanNamespace instance. */ export function isNamespaceObject(namespace) { return namespace?.constructor?.name === 'YeomanNamespace'; } export function hasGenerator(value) { return value.generator !== undefined; } //# sourceMappingURL=index.js.map