@jspm/generator
Version:
Package Import Map Generation Tool
193 lines (191 loc) • 8.85 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"
];
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