@velcro/resolver
Version:
Resolve references to absolute urls using the node module resolution algorithm using an generic host interface
817 lines (807 loc) • 37.2 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var common = require('@velcro/common');
var package_json = require('@velcro/node-libs/package.json');
const SPEC_RX = /^((@[^/]+\/[^/@]+|[^./@][^/@]*)(?:@([^/]+))?)(.*)?$/;
function parseBareModuleSpec(bareModuleSpec) {
const matches = bareModuleSpec.match(SPEC_RX);
if (matches) {
const [, nameSpec, name, spec, path = ''] = matches;
return {
nameSpec,
name,
spec,
path,
};
}
return null;
}
const NODE_CORE_SHIMS = Object.assign(Object.create(null), {
string_decoder: parseBareModuleSpec('string_decoder@1.2.0'),
punycode: parseBareModuleSpec('punycode@2.1.1'),
});
for (const name of [
'assert',
'buffer',
'constants',
'crypto',
'events',
'fs',
'http',
'https',
'net',
'os',
'path',
'process',
'querystring',
'stream',
'tls',
'url',
'util',
'vm',
'zlib',
]) {
NODE_CORE_SHIMS[name] = parseBareModuleSpec(`@velcro/node-libs@${package_json.version}/lib/${name}.js`);
}
(function (ResolverStrategy) {
let EntryKind;
(function (EntryKind) {
EntryKind["File"] = "file";
EntryKind["Directory"] = "directory";
})(EntryKind = ResolverStrategy.EntryKind || (ResolverStrategy.EntryKind = {}));
})(exports.ResolverStrategy || (exports.ResolverStrategy = {}));
class AbstractResolverStrategy {
getCanonicalUrl(_ctx, uri) {
return {
uri,
};
}
getSettings(ctx, _uri) {
return {
settings: ctx.settings,
};
}
/**
* Create a new ResolverStrategy having one or more methods overridden.
*
* You might use this if you want to override specific behaviour of another strategy without
* wanting to re-implement the whole strategy.
*
* If you need to invoke an overridden method, the overridden strategy will be available
* on `this.parent`.
*
* @param overrides A map of ResolverStrategy methods that you would like to override
*/
withOverrides(overrides) {
const strategy = { ...overrides, parent: this };
return Object.setPrototypeOf(Object.assign(Object.create(null), strategy), this);
}
}
class AbstractResolverStrategyWithRoot extends AbstractResolverStrategy {
constructor(rootUri) {
super();
this.rootUri = rootUri;
}
}
// type UncachedReturnType<T> = { [K in keyof T] : K extends typeof CACHE ? never : T[K] };
// type UncachedReturn<
// T extends (...any: any[]) => any,
// TReturn = ReturnType<T>
// > = TReturn extends Thenable<infer U>
// ? Thenable<UncachedReturnType<U>>
// : UncachedReturnType<TReturn>;
const CACHE = Symbol('Context.cache');
class Visits {
constructor(uri, parent) {
this.uri = uri;
this.visits = [];
this.parent = parent;
}
child(uri) {
return new Visits(uri, this);
}
push(visit) {
if (!this.visits.find((cmp) => cmp.type == visit.type && common.Uri.equals(cmp.uri, visit.uri))) {
this.visits.push(visit);
if (this.parent) {
this.parent.push(visit);
}
}
}
toArray() {
return this.parent ? this.parent.toArray() : this.visits.slice();
}
}
class ResolverContext {
constructor(options) {
this.mapResultWithVisits = (result) => Object.assign(result, { visited: this.visits.toArray() });
this.cache = options.cache;
this.cacheInvalidations = options.cacheInvalidations;
this.debugMode = options.debug;
this.decoder = options.decoder;
this.path = options.path;
this.resolver = options.resolver;
this.settings = options.settings;
this.strategy = options.strategy;
this.tokenSource = new common.CancellationTokenSource(options.token);
this.visits = options.visits;
}
static create(resolver, strategy, settings, token, options = {}) {
return new ResolverContext({
cache: new Map(),
cacheInvalidations: new common.MapSet(),
debug: !!options.debug,
decoder: new common.Decoder(),
path: [],
resolver,
settings,
strategy,
token,
visits: new Visits(common.Uri.parse('velcro:/root')),
});
}
get token() {
return this.tokenSource.token;
}
get visited() {
return this.visits.toArray();
}
dispose() {
this.tokenSource.dispose(true);
}
forOperation(operationName, uri, options = {}) {
const encodedOperation = encodePathNode(operationName, uri);
if (this.path.includes(encodedOperation)) {
const formattedPath = this.path
.map((segment) => {
const { operationName, uri } = decodePathNode(segment);
return `${operationName}(${uri.toString()})`;
})
.join(' -> ');
throw this._wrapError(new Error(`Detected a recursive call to the operation '${operationName}' for '${uri.toString()}' at path '${formattedPath}'`));
}
return new ResolverContext({
cache: this.cache,
cacheInvalidations: this.cacheInvalidations,
debug: this.debugMode,
decoder: this.decoder,
path: options.resetPath ? [] : this.path.concat(encodedOperation),
resolver: this.resolver,
settings: this.settings,
strategy: this.strategy,
token: this.tokenSource.token,
visits: options.resetVisits ? new Visits(uri) : this.visits.child(uri),
});
}
getCanonicalUrl(uri) {
const method = this.strategy.getCanonicalUrl;
const receiver = this.strategy;
const operationName = 'Strategy.getCanonicalUrl';
const href = uri.toString();
return this.runInChildContext(operationName, uri, (ctx) => ctx.runWithCache(operationName, href, method, receiver, ctx, uri));
}
getResolveRoot(uri) {
const method = this.strategy.getResolveRoot;
const receiver = this.strategy;
const operationName = 'Strategy.getResolveRoot';
const href = uri.toString();
return this.runInChildContext(operationName, uri, (ctx) => ctx.runWithCache(operationName, href, method, receiver, ctx, uri));
}
getSettings(uri) {
const method = this.strategy.getSettings;
const receiver = this.strategy;
const operationName = 'Strategy.getSettings';
const href = uri.toString();
return this.runInChildContext(operationName, uri, (ctx) => ctx.runWithCache(operationName, href, method, receiver, ctx, uri));
}
getUrlForBareModule(name, spec, path) {
const method = this.strategy.getUrlForBareModule;
if (!method) {
return Promise.reject(new Error(`Unable to resolve bare module spec '${name}@${spec}${path}' because no strategy was found that supports resolving bare modules`));
}
const receiver = this.strategy;
const operationName = 'Strategy.getUrlForBareModule';
const href = `${name}@${spec}${path}`;
return this.runInChildContext(operationName, href, (ctx) => ctx.runWithCache(operationName, href, method, receiver, ctx, name, spec, path));
}
invalidate(uri) {
const href = uri.toString();
const invalidations = this.cacheInvalidations.get(href);
let invalidated = false;
if (invalidations) {
for (const { cacheKey, operationCache } of invalidations) {
invalidated = operationCache.delete(cacheKey) || invalidated;
}
}
this.cacheInvalidations.deleteAll(href);
return invalidated;
}
listEntries(uri) {
const method = this.strategy.listEntries;
const receiver = this.strategy;
const operationName = 'Strategy.listEntries';
const href = uri.toString();
return this.runInChildContext(operationName, uri, (ctx) => ctx.runWithCache(operationName, href, method, receiver, ctx, uri));
}
readFileContent(uri) {
const method = this.strategy.readFileContent;
const receiver = this.strategy;
const operationName = 'Strategy.readFileContent';
const href = uri.toString();
this.recordVisit(uri, ResolverContext.VisitKind.File);
return this.runInChildContext(operationName, uri, (ctx) => ctx.runWithCache(operationName, href, method, receiver, ctx, uri));
}
readParentPackageJson(uri) {
console.debug('readParentPackageJson(%s)', uri.toString());
return this.runWithCache('readParentPackageJson', uri.toString(), readParentPackageJson, null, this, uri);
}
recordVisit(uri, type = ResolverContext.VisitKind.File) {
this.visits.push({ type, uri });
}
resolve(spec, fromUri) {
const method = resolveDependency;
const receiver = null;
const operationName = 'resolve';
const href = `${fromUri}|${spec}`;
return this.runInChildContext(operationName, href, (ctx) => ctx.runWithCache(operationName, href, method, receiver, ctx, fromUri, spec));
}
resolveUri(uri) {
const method = resolve;
const receiver = null;
const operationName = 'resolveUri';
const href = uri.toString();
return this.runInChildContext(operationName, uri, (ctx) => ctx.runWithCache(operationName, href, method, receiver, ctx, uri));
}
runInChildContext(operationName, uri, contextFn) {
return this.runInContext(operationName, uri, { resetPath: false, resetVisits: false }, contextFn);
}
runInIsolatedContext(operationName, uri, contextFn) {
return this.runInContext(operationName, uri, { resetPath: true, resetVisits: true }, contextFn);
}
runInContext(operationName, uri, options, contextFn) {
const ctx = this.forOperation(operationName, uri, options);
ctx.debug('%s(%s)', operationName, uri.toString());
return contextFn(ctx);
}
createStoreResultFn(operationCache, cacheKey) {
return (result) => {
const mappedResult = this.mapResultWithVisits(result);
const visited = mappedResult.visited;
if (mappedResult[CACHE]) {
const cacheEntries = mappedResult[CACHE];
delete mappedResult[CACHE];
for (const [cacheKey, value] of cacheEntries) {
operationCache.set(cacheKey, value);
for (const visit of visited) {
this.cacheInvalidations.add(visit.uri.toString(), { cacheKey, operationCache });
}
}
}
// Override the pending value with the resolved value
operationCache.set(cacheKey, mappedResult);
for (const visit of visited) {
this.cacheInvalidations.add(visit.uri.toString(), { cacheKey, operationCache });
}
return mappedResult;
};
}
runWithCache(cacheSegment, cacheKey, fn, target, ...args) {
let operationCache = this.cache.get(cacheSegment);
if (!operationCache) {
operationCache = new Map();
this.cache.set(cacheSegment, operationCache);
}
const cached = operationCache.get(cacheKey);
if (cached) {
this.debug('%s(%s) [HIT]', cacheSegment, cacheKey);
// We either have a cached result or a cached promise for a result. Either way, the value
// is suitable as a return.
return cached;
}
const cacheResult = this.createStoreResultFn(operationCache, cacheKey);
this.debug('%s(%s) [MISS]', cacheSegment, cacheKey);
// Nothing is cached
const ret = fn.apply(target, args);
if (common.isThenable(ret)) {
const promiseRet = ret;
// Produce a promise that will only be settled once the cache has been updated accordingly.
const wrappedRet = promiseRet.then(cacheResult, (err) => {
// Delete the entry from the cache in case it was a transient failure
operationCache.delete(cacheKey);
return Promise.reject(err);
});
// Set the pending value in the cache for now
operationCache.set(cacheKey, wrappedRet);
return wrappedRet;
}
return cacheResult(ret);
}
_wrapError(err) {
return Object.assign(err, {
path: this.path.map(decodePathNode),
});
}
debug(...args) {
if (this.debugMode) {
if (typeof args[0] === 'string') {
args[0] = ' '.repeat(this.path.length) + args[0];
}
console.warn(...args);
}
}
}
function encodePathNode(operationName, uri) {
return `${operationName}:${uri.toString()}`;
}
function decodePathNode(node) {
const parts = node.split(':', 2);
if (parts.length !== 2) {
console.log('WTF', { node, parts });
throw new Error(`Invariant violation: Unexpected path node: '${node}'`);
}
return {
operationName: parts[0],
uri: parts[1].includes(':') ? common.Uri.parse(parts[1]) : parts[1],
};
}
async function resolve(ctx, uri) {
const bothResolved = common.all([ctx.getCanonicalUrl(uri), ctx.getResolveRoot(uri), ctx.getSettings(uri)], ctx.token);
const [canonicalizationResult, resolveRootResult, settingsResult] = common.isThenable(bothResolved)
? await common.checkCancellation(bothResolved, ctx.token)
: bothResolved;
const rootUri = resolveRootResult.uri;
const rootUriWithoutTrailingSlash = common.Uri.ensureTrailingSlash(rootUri, '');
if (!common.Uri.isPrefixOf(rootUriWithoutTrailingSlash, canonicalizationResult.uri)) {
throw new Error(`Unable to resolve a module whose path ${canonicalizationResult.uri.toString(true)} is above the host's root ${rootUri.toString()}`);
}
const resolveReturn = common.Uri.equals(rootUriWithoutTrailingSlash, canonicalizationResult.uri) ||
common.Uri.equals(rootUri, canonicalizationResult.uri)
? ctx.runInChildContext('resolveAsDirectory', canonicalizationResult.uri, (ctx) => resolveAsDirectory(ctx, common.Uri.ensureTrailingSlash(canonicalizationResult.uri), resolveRootResult.uri, settingsResult.settings))
: ctx.runInChildContext('resolveAsFile', canonicalizationResult.uri, (ctx) => resolveAsFile(ctx, canonicalizationResult.uri, resolveRootResult.uri, settingsResult.settings, null));
const readParentPackageJsonReturn = readParentPackageJsonInternal(ctx, canonicalizationResult.uri, rootUri, { uriIsCanonicalized: true });
const resolveAndPackageJson = common.all([resolveReturn, readParentPackageJsonReturn], ctx.token);
const [resolveResult, readParentPackageJsonResult] = common.isThenable(resolveAndPackageJson)
? await resolveAndPackageJson
: resolveAndPackageJson;
let parentPackageJson = undefined;
if (readParentPackageJsonResult.found &&
typeof readParentPackageJsonResult.packageJson.name === 'string' &&
typeof readParentPackageJsonResult.packageJson.version === 'string') {
parentPackageJson = {
packageJson: readParentPackageJsonResult.packageJson,
uri: readParentPackageJsonResult.uri,
};
}
else if (readParentPackageJsonResult.found) {
// If a parent packageJson WAS found but was missing name or version, let's continue traversing up
let nextUri = common.Uri.joinPath(readParentPackageJsonResult.uri, '..');
while (common.Uri.isPrefixOf(rootUri, nextUri, true)) {
const readParentPackageJsonReturn = readParentPackageJsonInternal(ctx, nextUri, rootUri, {
uriIsCanonicalized: false,
});
const readParentPackageJsonResult = common.isThenable(readParentPackageJsonReturn)
? await common.checkCancellation(readParentPackageJsonReturn, ctx.token)
: readParentPackageJsonReturn;
if (!readParentPackageJsonResult.found) {
break;
}
if (readParentPackageJsonResult.packageJson.name &&
readParentPackageJsonResult.packageJson.version) {
parentPackageJson = {
packageJson: readParentPackageJsonResult.packageJson,
uri: readParentPackageJsonResult.uri,
};
break;
}
nextUri = common.Uri.joinPath(readParentPackageJsonResult.uri, '..');
}
}
return {
...resolveResult,
parentPackageJson,
};
}
async function resolveDependency(ctx, fromUri, spec) {
const parsedSpec = parseBareModuleSpec(spec);
if (parsedSpec) {
return ctx.runInChildContext('resolveBareModule', fromUri, (ctx) => resolveBareModule(ctx, fromUri, parsedSpec));
}
const relativeUri = common.Uri.joinPath(common.Uri.from({
...fromUri,
path: common.dirname(fromUri.path),
}), spec);
return ctx.runInChildContext('resolveUri', relativeUri, (ctx) => resolve(ctx, relativeUri));
}
async function resolveBareModule(ctx, uri, parsedSpec) {
let locatorName = parsedSpec.name;
let locatorSpec = parsedSpec.spec;
let locatorPath = parsedSpec.path;
if (!locatorSpec) {
const resolveRootReturn = ctx.getResolveRoot(uri);
const resolveRootResult = common.isThenable(resolveRootReturn)
? await common.checkCancellation(resolveRootReturn, ctx.token)
: resolveRootReturn;
let nextUri = uri;
const maxIterations = 10;
const consultedUris = [];
while (common.Uri.isPrefixOf(resolveRootResult.uri, nextUri, true)) {
if (consultedUris.length >= maxIterations) {
throw new Error(`Consulted a maximum of ${maxIterations} locations while trying to resolve '${bareModuleToSpec(parsedSpec)}' from '${uri.toString()}', via ${ctx.path.join(' -> ')}: ${consultedUris
.map((uri) => uri.toString())
.join(', ')}`);
}
const currentUri = nextUri;
consultedUris.push(currentUri);
const parentPackageJsonReturn = readParentPackageJsonInternal(ctx, currentUri, resolveRootResult.uri, { uriIsCanonicalized: false });
const parentPackageJsonResult = common.isThenable(parentPackageJsonReturn)
? await common.checkCancellation(parentPackageJsonReturn, ctx.token)
: parentPackageJsonReturn;
if (!parentPackageJsonResult.found) {
throw new common.DependencyNotFoundError(parsedSpec.nameSpec, uri);
}
ctx.recordVisit(parentPackageJsonResult.uri, ResolverContext.VisitKind.File);
if (parentPackageJsonResult.packageJson.name === parsedSpec.name) {
// We found a parent directory that *IS* the module we're looking for
const directoryUri = common.Uri.ensureTrailingSlash(common.Uri.joinPath(parentPackageJsonResult.uri, '../'));
return ctx.runInChildContext('resolveAsDirectory', directoryUri, (ctx) => resolveAsDirectory(ctx, directoryUri, resolveRootResult.uri, ctx.settings));
}
const dependencies = {
...(parentPackageJsonResult.packageJson.devDependencies || {}),
...(parentPackageJsonResult.packageJson.peerDependencies || {}),
...(parentPackageJsonResult.packageJson.dependencies || {}),
};
locatorSpec = dependencies[parsedSpec.name];
if (locatorSpec) {
break;
}
nextUri = common.Uri.joinPath(parentPackageJsonResult.uri, '..');
if (common.Uri.equals(nextUri, currentUri) || common.Uri.equals(nextUri, resolveRootResult.uri)) {
break;
}
}
}
if (!locatorSpec) {
const builtIn = NODE_CORE_SHIMS[parsedSpec.name];
if (builtIn) {
locatorName = builtIn.name;
locatorSpec = builtIn.spec;
locatorPath = builtIn.path;
}
}
// If no locator spec was found, it means we were unable
if (!locatorSpec) {
throw new common.DependencyNotFoundError(parsedSpec.nameSpec, uri);
}
const bareModuleUriReturn = ctx.getUrlForBareModule(locatorName, locatorSpec, locatorPath);
const bareModuleUriResult = common.isThenable(bareModuleUriReturn)
? await common.checkCancellation(bareModuleUriReturn, ctx.token)
: bareModuleUriReturn;
if (!bareModuleUriResult.found) {
throw new common.DependencyNotFoundError(parsedSpec.nameSpec, uri);
}
if (!bareModuleUriResult.uri) {
// TODO: Inject empty module
throw new common.EntryExcludedError(parsedSpec.nameSpec);
}
const resolveReturn = ctx.resolveUri(bareModuleUriResult.uri);
const resolveResult = common.isThenable(resolveReturn)
? await common.checkCancellation(resolveReturn, ctx.token)
: resolveReturn;
return resolveResult;
}
(function (ResolverContext) {
let VisitKind;
(function (VisitKind) {
VisitKind["Directory"] = "Directory";
VisitKind["File"] = "File";
})(VisitKind = ResolverContext.VisitKind || (ResolverContext.VisitKind = {}));
})(ResolverContext || (ResolverContext = {}));
async function resolveAsDirectory(ctx, uri, rootUri, settings) {
ctx.recordVisit(uri, ResolverContext.VisitKind.Directory);
const listEntriesReturn = ctx.listEntries(uri);
const listEntriesResult = common.isThenable(listEntriesReturn)
? await common.checkCancellation(listEntriesReturn, ctx.token)
: listEntriesReturn;
let mainPathname = 'index';
// Step 1: Look for a package.json with an main field
const packageJsonUri = common.Uri.joinPath(uri, './package.json');
ctx.recordVisit(packageJsonUri, ResolverContext.VisitKind.File);
const packageJsonEntry = listEntriesResult.entries.find((entry) => entry.type === exports.ResolverStrategy.EntryKind.File && common.Uri.equals(packageJsonUri, entry.uri));
let packageJson = null;
if (packageJsonEntry) {
const packageJsonContentReturn = ctx.readFileContent(packageJsonUri);
const packageJsonContentResult = common.isThenable(packageJsonContentReturn)
? await common.checkCancellation(packageJsonContentReturn, ctx.token)
: packageJsonContentReturn;
packageJson = common.parseBufferAsPackageJson(ctx.decoder, packageJsonContentResult.content, uri.toString());
for (const packageMain of settings.packageMain) {
const pathname = packageJson[packageMain];
if (typeof pathname === 'string') {
mainPathname = pathname;
break;
}
}
}
const fileUri = common.Uri.joinPath(uri, mainPathname);
return ctx.runInChildContext('resolveAsFile', uri, (ctx) => resolveAsFile(ctx, fileUri, rootUri, settings, packageJson));
}
async function resolveAsFile(ctx, uri, rootUri, settings, packageJson, ignoreBrowserOverrides = false) {
if (uri.path === '' || uri.path === '/') {
throw new TypeError(`Unable to resolve the root as a file: ${uri.toString()}`);
}
ctx.recordVisit(uri, ResolverContext.VisitKind.File);
const browserOverrides = new Map();
if (packageJson === null) {
// The parent package.json is only interesting if we are going to look at the `browser`
// field and then consider browser mapping overrides in there.
const parentPackageJsonResult = settings.packageMain.includes('browser') && !ignoreBrowserOverrides
? await common.checkCancellation(ctx.runInChildContext('readParentPackageJsonInternal', uri, (ctx) => readParentPackageJsonInternal(ctx, uri, rootUri, { uriIsCanonicalized: true })), ctx.token)
: undefined;
if (parentPackageJsonResult && parentPackageJsonResult.found) {
ctx.recordVisit(parentPackageJsonResult.uri, ResolverContext.VisitKind.File);
packageJson = parentPackageJsonResult.packageJson;
if (parentPackageJsonResult.packageJson.browser &&
typeof parentPackageJsonResult.packageJson.browser === 'object') {
const browserMap = parentPackageJsonResult.packageJson.browser;
const packageJsonDir = common.Uri.joinPath(parentPackageJsonResult.uri, '..');
for (const entry in browserMap) {
const impliedUri = common.Uri.joinPath(packageJsonDir, entry);
const targetSpec = browserMap[entry];
const target = targetSpec === false ? false : common.Uri.joinPath(packageJsonDir, targetSpec);
if (common.Uri.equals(impliedUri, uri)) {
if (target === false) {
return {
found: false,
uri: null,
};
}
// console.warn('REMAPPED %s to %s', url, target);
// We found an exact match so let's make sure we resolve the re-mapped file but
// also that we don't go through the browser overrides rodeo again.
return ctx.runInChildContext('resolveAsFile', target, (ctx) => resolveAsFile(ctx, target, rootUri, settings, packageJson, true));
}
browserOverrides.set(impliedUri.toString(), target);
}
}
}
}
const containingDirUri = common.Uri.ensureTrailingSlash(common.Uri.joinPath(uri, '..'));
const filename = common.basename(uri.path);
const entriesReturn = ctx.listEntries(containingDirUri);
const entriesResult = common.isThenable(entriesReturn)
? await common.checkCancellation(entriesReturn, ctx.token)
: entriesReturn;
const entryDirectoryMap = new Map();
const entryFileMap = new Map();
for (const entry of entriesResult.entries) {
if (common.Uri.equals(entry.uri, uri) && entry.type == exports.ResolverStrategy.EntryKind.File) {
// Found an exact match
return {
found: true,
rootUri,
uri,
};
}
if (entry.type === exports.ResolverStrategy.EntryKind.Directory) {
const childFilename = common.Uri.getFirstPathSegmentAfterPrefix(entry.uri, containingDirUri);
entryDirectoryMap.set(childFilename, entry);
}
else if (entry.type === exports.ResolverStrategy.EntryKind.File) {
const childFilename = common.basename(entry.uri.path);
entryFileMap.set(childFilename, entry);
}
}
// Look for browser overrides
for (const ext of settings.extensions) {
const hrefWithExtensionUri = uri.with({ path: `${uri.path}${ext}` });
const hrefWithExtension = hrefWithExtensionUri.toString();
const mapping = browserOverrides.get(hrefWithExtension);
ctx.recordVisit(hrefWithExtensionUri, ResolverContext.VisitKind.File);
if (mapping === false) {
// console.warn('REMAPPED %s to undefined', url);
return {
found: true,
rootUri,
uri: null,
};
}
else if (mapping) {
// console.warn('REMAPPED %s to %s', url, mapping);
return ctx.runInChildContext('resolveAsFile', mapping, (ctx) => resolveAsFile(ctx, mapping, rootUri, settings, packageJson, true));
}
const match = entryFileMap.get(`${filename}${ext}`);
if (match) {
if (match.type !== exports.ResolverStrategy.EntryKind.File) {
continue;
}
return {
found: true,
rootUri,
uri: match.uri,
};
}
}
// First, attempt to find a matching file or directory
const match = entryDirectoryMap.get(filename);
if (match) {
if (match.type !== exports.ResolverStrategy.EntryKind.Directory) {
throw new Error(`Invariant violation ${match.type} is unexpected`);
}
return ctx.runInChildContext('resolveAsDirectory', match.uri, (ctx) => resolveAsDirectory(ctx, common.Uri.ensureTrailingSlash(match.uri), rootUri, settings));
}
throw new common.EntryNotFoundError(uri);
}
async function readParentPackageJson(ctx, uri) {
const canonicalizationReturn = ctx.getCanonicalUrl(uri);
const resolveRootReturn = ctx.getResolveRoot(uri);
const bothResolved = common.all([canonicalizationReturn, resolveRootReturn], ctx.token);
const [canonicalizationResult, resolveRootResult] = common.isThenable(bothResolved)
? await common.checkCancellation(bothResolved, ctx.token)
: bothResolved;
const readReturn = ctx.runInChildContext('readParentPackageJsonInternal', canonicalizationResult.uri, (ctx) => readParentPackageJsonInternal(ctx, canonicalizationResult.uri, resolveRootResult.uri, {
uriIsCanonicalized: true,
}));
const readResult = common.isThenable(readReturn) ? await readReturn : readReturn;
if (readResult.found && readResult.visitedDirs) {
const visitedDirs = readResult.visitedDirs;
delete readResult.visitedDirs;
readResult[CACHE] = visitedDirs.map((uri) => [uri.toString(), { ...readResult, uri }]);
}
return readResult;
}
async function readParentPackageJsonInternal(ctx, uri, rootUri, options) {
if (!options.uriIsCanonicalized) {
const canonicalizationReturn = ctx.getCanonicalUrl(uri);
const canonicalizationResult = common.isThenable(canonicalizationReturn)
? await common.checkCancellation(canonicalizationReturn, ctx.token)
: canonicalizationReturn;
uri = canonicalizationResult.uri;
}
const hostRootHref = common.Uri.ensureTrailingSlash(rootUri);
const containingDirUrl = common.Uri.ensureTrailingSlash(common.Uri.joinPath(uri, '..'));
const visitedDirs = [];
const readPackageJsonOrRecurse = async (ctx, dir) => {
if (!common.Uri.isPrefixOf(hostRootHref, dir)) {
// Terminal condition for recursion
return {
found: false,
packageJson: null,
uri: null,
};
}
ctx.recordVisit(dir, ResolverContext.VisitKind.Directory);
const entriesReturn = ctx.listEntries(dir);
const entriesResult = common.isThenable(entriesReturn)
? await common.checkCancellation(entriesReturn, ctx.token)
: entriesReturn;
const packageJsonUri = common.Uri.joinPath(dir, 'package.json');
const packageJsonEntry = entriesResult.entries.find((entry) => entry.type === exports.ResolverStrategy.EntryKind.File && common.Uri.equals(entry.uri, packageJsonUri));
ctx.recordVisit(packageJsonUri, ResolverContext.VisitKind.File);
if (packageJsonEntry) {
// Found! Let's try to parse
try {
const parentPackageJsonContentReturn = ctx.readFileContent(packageJsonUri);
const parentPackageJsonContentResult = common.isThenable(parentPackageJsonContentReturn)
? await common.checkCancellation(parentPackageJsonContentReturn, ctx.token)
: parentPackageJsonContentReturn;
const packageJson = common.parseBufferAsPackageJson(ctx.decoder, parentPackageJsonContentResult.content, packageJsonUri.toString());
return { found: true, packageJson, uri: packageJsonUri, visitedDirs };
}
catch (err) {
if (err instanceof common.CanceledError || (err && err.name === 'CanceledError')) {
throw err;
}
// TODO: Maybe issue some warning?
}
}
// Not found here, let's try one up
const parentDir = common.Uri.ensureTrailingSlash(common.Uri.joinPath(dir, '..'));
// Skip infinite recursion
if (common.Uri.equals(dir, parentDir) || common.Uri.isPrefixOf(dir, parentDir)) {
return {
found: false,
packageJson: null,
uri: null,
};
}
visitedDirs.push(dir);
return ctx.runInChildContext('readPackageJsonOrRecurse', parentDir, (ctx) => readPackageJsonOrRecurse(ctx, parentDir));
};
if (common.Uri.equals(uri, containingDirUrl) || common.Uri.isPrefixOf(uri, containingDirUrl)) {
return {
found: false,
packageJson: null,
uri: null,
};
}
return ctx.runInChildContext('readPackageJsonOrRecurse', containingDirUrl, (ctx) => readPackageJsonOrRecurse(ctx, containingDirUrl));
}
function bareModuleToSpec(bareModule) {
return `${bareModule.nameSpec}${bareModule.path}`;
}
class Resolver {
constructor(strategy, settings) {
this.disposed = false;
this.tokenSource = new common.CancellationTokenSource();
this.settings = settings;
this.strategy = strategy;
this.rootCtx = ResolverContext.create(this, this.strategy, this.settings, this.tokenSource.token, { debug: settings.debug });
}
decode(buf) {
if (typeof buf === 'string') {
return buf;
}
return this.rootCtx.decoder.decode(buf);
}
dispose() {
this.disposed = true;
return this.rootCtx.dispose();
}
getCanonicalUrl(uri) {
if (this.disposed) {
throw new Error('Resolver has been disposed');
}
return this.rootCtx.runInIsolatedContext('Resolver.getCanonicalUrl', uri, (ctx) => ctx.getCanonicalUrl(typeof uri === 'string' ? common.Uri.parse(uri) : uri));
}
getResolveRoot(uri) {
if (this.disposed) {
throw new Error('Resolver has been disposed');
}
return this.rootCtx.runInIsolatedContext('Resolver.getResolveRoot', uri, (ctx) => ctx.getResolveRoot(typeof uri === 'string' ? common.Uri.parse(uri) : uri));
}
getSettings(uri) {
if (this.disposed) {
throw new Error('Resolver has been disposed');
}
return this.rootCtx.runInIsolatedContext('Resolver.getSettings', uri, (ctx) => ctx.getSettings(typeof uri === 'string' ? common.Uri.parse(uri) : uri));
}
getUrlForBareModule(name, spec, path) {
if (this.disposed) {
throw new Error('Resolver has been disposed');
}
return this.rootCtx.runInIsolatedContext('Resolver.getUrlForBareModule', `${name}|${spec}|${path}`, (ctx) => ctx.getUrlForBareModule(name, spec, path));
}
invalidate(uri) {
if (this.disposed) {
throw new Error('Resolver has been disposed');
}
return this.rootCtx.runInIsolatedContext('Resolver.invalidate', uri, (ctx) => ctx.invalidate(typeof uri === 'string' ? common.Uri.parse(uri) : uri));
}
listEntries(uri) {
if (this.disposed) {
throw new Error('Resolver has been disposed');
}
return this.rootCtx.runInIsolatedContext('Resolver.listEntries', uri, (ctx) => ctx.listEntries(typeof uri === 'string' ? common.Uri.parse(uri) : uri));
}
readFileContent(uri) {
if (this.disposed) {
throw new Error('Resolver has been disposed');
}
return this.rootCtx.runInIsolatedContext('Resolver.readFileContent', uri, (ctx) => ctx.readFileContent(typeof uri === 'string' ? common.Uri.parse(uri) : uri));
}
readParentPackageJson(uri) {
if (this.disposed) {
throw new Error('Resolver has been disposed');
}
return this.rootCtx.runInIsolatedContext('Resolver.readParentPackageJson', uri, (ctx) => ctx.readParentPackageJson(typeof uri === 'string' ? common.Uri.parse(uri) : uri));
}
resolve(spec, fromUri) {
if (this.disposed) {
throw new Error('Resolver has been disposed');
}
if (common.Uri.isUri(spec)) {
return this.rootCtx.runInIsolatedContext('Resolver.resolveUri', spec, (ctx) => ctx.resolveUri(spec));
}
if (!fromUri) {
throw new Error('When calling Resolver.resolve with a string spec, a second "fromUri" argument is required');
}
return this.rootCtx.runInIsolatedContext('Resolver.resolve', `${fromUri ? fromUri.toString() : ''}|${spec}`, (ctx) => ctx.resolve(spec, fromUri));
}
}
const version = '0.56.2';
exports.AbstractResolverStrategy = AbstractResolverStrategy;
exports.AbstractResolverStrategyWithRoot = AbstractResolverStrategyWithRoot;
exports.Resolver = Resolver;
exports.ResolverContext = ResolverContext;
exports.version = version;
//# sourceMappingURL=index.js.map