UNPKG

@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
'use strict'; 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