@jspm/generator
Version:
Package Import Map Generation Tool
194 lines (192 loc) • 8.87 kB
JavaScript
import { JspmError } from '../common/err.js';
import { baseUrl, isRelative } from '../common/url.js';
// @ts-ignore
import sver from 'sver';
const { SemverRange } = sver;
// @ts-ignore
import convertRange from 'sver/convert-range.js';
import { builtinSchemes } from '../providers/index.js';
const supportedProtocols = [
'https',
'http',
'data',
'file',
'pkg'
];
export async function parseUrlOrBuiltinTarget(resolver, targetStr, parentUrl) {
const registryIndex = targetStr.indexOf(':');
if (isRelative(targetStr) || registryIndex !== -1 && supportedProtocols.includes(targetStr.slice(0, registryIndex)) || builtinSchemes.has(targetStr.slice(0, registryIndex))) {
let target;
let alias;
let subpath = '.';
const maybeBuiltin = builtinSchemes.has(targetStr.slice(0, registryIndex)) && resolver.resolveBuiltin(targetStr);
if (maybeBuiltin) {
if (typeof maybeBuiltin === 'string') {
throw new Error(`Builtin "${targetStr}" was resolved to package specifier ${maybeBuiltin}, but JSPM does not currently support installing specifiers for builtins.`);
} else {
({ alias, subpath = '.', target } = maybeBuiltin);
}
} else {
var _this;
const subpathIndex = targetStr.indexOf('|');
if (subpathIndex !== -1) {
subpath = `./${targetStr.slice(subpathIndex + 1)}`;
targetStr = targetStr.slice(0, subpathIndex);
}
target = {
pkgTarget: new URL(targetStr + (targetStr.endsWith('/') ? '' : '/'), parentUrl || baseUrl),
installSubpath: null
};
const pkgUrl = await resolver.getPackageBase(target.pkgTarget.href);
alias = ((_this = pkgUrl ? await resolver.getPackageConfig(pkgUrl) : null) === null || _this === void 0 ? void 0 : _this.name) || target.pkgTarget.pathname.split('/').slice(0, -1).pop();
}
if (!alias) throw new JspmError(`Unable to determine an alias for target package ${targetStr}`);
return {
alias,
target,
subpath
};
}
}
// ad-hoc determination of local path v remote package for eg "jspm deno react" v "jspm deno react@2" v "jspm deno ./react.ts" v "jspm deno react.ts"
const supportedRegistries = [
'npm',
'github',
'deno',
'nest',
'denoland'
];
export function isPackageTarget(targetStr) {
if (isRelative(targetStr)) return false;
const registryIndex = targetStr.indexOf(':');
if (registryIndex !== -1 && supportedRegistries.includes(targetStr.slice(0, registryIndex))) return true;
const pkg = parsePkg(targetStr);
if (!pkg) return false;
if (pkg.pkgName.indexOf('@') !== -1) return true;
if (targetStr.endsWith('.ts') || targetStr.endsWith('.js') || targetStr.endsWith('.mjs')) return false;
return true;
}
export async function parseTarget(resolver, targetStr, parentPkgUrl, defaultRegistry) {
const urlTarget = await parseUrlOrBuiltinTarget(resolver, targetStr, parentPkgUrl);
if (urlTarget) return urlTarget;
// TODO: package aliases support as per https://github.com/npm/rfcs/blob/latest/implemented/0001-package-aliases.md
const registryIndex = targetStr.indexOf(':');
const versionOrScopeIndex = targetStr.indexOf('@');
if (targetStr.indexOf(':') !== -1 && versionOrScopeIndex !== -1 && versionOrScopeIndex < registryIndex) throw new Error(`Package aliases not yet supported. PRs welcome.`);
const pkg = parsePkg(registryIndex === -1 ? targetStr : targetStr.slice(registryIndex + 1));
if (!pkg) throw new JspmError(`Invalid package name ${targetStr}`);
let registry = null;
if (registryIndex !== -1) registry = targetStr.slice(0, registryIndex);
let alias = pkg.pkgName;
const versionIndex = pkg.pkgName.indexOf('@', 1);
if (versionIndex !== -1) alias = pkg.pkgName.slice(0, versionIndex);
else alias = pkg.pkgName;
// If no version is specified, we fallback to the constraints in the parent
// package config if they exist:
const pcfg = await resolver.getPackageConfig(parentPkgUrl.href);
if (versionIndex === -1 && pcfg) {
var _pcfg_dependencies, _pcfg_peerDependencies, _pcfg_optionalDependencies, _pcfg_devDependencies;
const dep = ((_pcfg_dependencies = pcfg.dependencies) === null || _pcfg_dependencies === void 0 ? void 0 : _pcfg_dependencies[alias]) || ((_pcfg_peerDependencies = pcfg.peerDependencies) === null || _pcfg_peerDependencies === void 0 ? void 0 : _pcfg_peerDependencies[alias]) || ((_pcfg_optionalDependencies = pcfg.optionalDependencies) === null || _pcfg_optionalDependencies === void 0 ? void 0 : _pcfg_optionalDependencies[alias]) || ((_pcfg_devDependencies = pcfg.devDependencies) === null || _pcfg_devDependencies === void 0 ? void 0 : _pcfg_devDependencies[alias]);
if (dep) {
return {
target: newPackageTarget(dep, parentPkgUrl, registry || defaultRegistry, pkg.pkgName),
alias,
subpath: pkg.subpath
};
}
}
// Otherwise we construct a package target from what we were given:
return {
target: newPackageTarget(pkg.pkgName, parentPkgUrl, registry || defaultRegistry),
alias,
subpath: pkg.subpath
};
}
export function newPackageTarget(target, parentPkgUrl, defaultRegistry, pkgName) {
if (target === '.') {
// useful shorthand
target = './';
}
let registry, name, ranges;
const registryIndex = target.indexOf(':');
if (target.startsWith('./') || target.startsWith('../') || target.startsWith('/') || registryIndex === 1) return {
pkgTarget: new URL(target, parentPkgUrl),
installSubpath: null
};
registry = registryIndex < 1 ? defaultRegistry : target.slice(0, registryIndex);
if (registry === 'file') return {
pkgTarget: new URL(target.slice(registry.length + 1), parentPkgUrl),
installSubpath: null
};
if (registry === 'https' || registry === 'http') return {
pkgTarget: new URL(target),
installSubpath: null
};
const versionIndex = target.lastIndexOf('@');
let unstable = false;
if (versionIndex > registryIndex + 1) {
name = target.slice(registryIndex + 1, versionIndex);
const version = target.slice(versionIndex + 1);
ranges = pkgName || SemverRange.isValid(version) ? [
new SemverRange(version)
] : version.split('||').map((v)=>convertRange(v));
if (version === '') unstable = true;
} else if (registryIndex === -1 && pkgName) {
name = pkgName;
ranges = SemverRange.isValid(target) ? [
new SemverRange(target)
] : target.split('||').map((v)=>convertRange(v));
} else {
name = target.slice(registryIndex + 1);
ranges = [
new SemverRange('*')
];
}
if (registryIndex === -1 && name.indexOf('/') !== -1 && name[0] !== '@') registry = 'github';
const targetNameLen = name.split('/').length;
if (targetNameLen > 2 || targetNameLen === 1 && name[0] === '@') throw new JspmError(`Invalid package target ${target}`);
return {
pkgTarget: {
registry,
name,
ranges,
unstable
},
installSubpath: null
};
}
export function pkgToStr(pkg) {
return `${pkg.registry ? pkg.registry + ':' : ''}${pkg.name}${pkg.version ? '@' + pkg.version : ''}`;
}
/**
* Throws unless the given specifier is a valid npm-style package specifier.
*
* @param {string} specifier Specifier to validate.
*/ export function validatePkgName(specifier) {
const parsed = parsePkg(specifier);
if (!parsed || parsed.subpath !== '.') throw new Error(`"${specifier}" is not a valid npm-style package name. Subpaths must be provided separately to the installation package name.`);
}
/**
* Parses an npm-style module specifier, such as '@jspm/generator/index.js',
* and splits it into the package name ('@jspm/generator') and module subpath
* ('./index.js'). Returns undefined if the given specifier is invalid.
*
* @param {string} specifier Specifier to parse.
* @returns {{ pkgName: string, subpath: '.' | `./${string}` } | undefined}
*/ export function parsePkg(specifier) {
let sepIndex = specifier.indexOf('/');
if (specifier[0] === '@') {
if (sepIndex === -1) return;
sepIndex = specifier.indexOf('/', sepIndex + 1);
}
// TODO: Node.js validations like percent encodng checks
if (sepIndex === -1) return {
pkgName: specifier,
subpath: '.'
};
return {
pkgName: specifier.slice(0, sepIndex),
subpath: `.${specifier.slice(sepIndex)}`
};
}
//# sourceMappingURL=package.js.map