From b7df7264201358dc43d2958e1303f93be156e912 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sun, 1 Aug 2021 18:26:43 +0200 Subject: [PATCH] feat: apply realpath for file: URLs in traces (#52) --- jspm-core | 2 +- src/trace/resolver.ts | 20 ++++++++++++++++++-- src/trace/tracemap.ts | 20 ++++++++++---------- test/api/self.test.js | 2 +- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/jspm-core b/jspm-core index 890d8acb..e26ad355 160000 --- a/jspm-core +++ b/jspm-core @@ -1 +1 @@ -Subproject commit 890d8acb31abd5a6e83996c8a6d1d18d497e8fab +Subproject commit e26ad355f6596eba86ad61859dafc12b1978f67f diff --git a/src/trace/resolver.ts b/src/trace/resolver.ts index 60672fe4..e0db94b7 100644 --- a/src/trace/resolver.ts +++ b/src/trace/resolver.ts @@ -9,17 +9,21 @@ import { getProvider, defaultProviders, Provider } from '../providers/index.js'; import { Analysis, createSystemAnalysis, parseTs } from './analysis.js'; import { createEsmAnalysis } from './analysis.js'; import { createCjsAnalysis } from './cjs.js'; -import { getMapMatch, resolveConditional } from '@jspm/import-map'; +import { getMapMatch } from '@jspm/import-map'; + +let realpath, pathToFileURL; export class Resolver { log: Log; pcfgPromises: Record> = Object.create(null); pcfgs: Record = Object.create(null); fetchOpts: any; + preserveSymlinks = false; providers = defaultProviders; - constructor (log: Log, fetchOpts?: any) { + constructor (log: Log, fetchOpts?: any, preserveSymlinks = false) { this.log = log; this.fetchOpts = fetchOpts; + this.preserveSymlinks = preserveSymlinks; } addCustomProvider (name: string, provider: Provider) { @@ -181,6 +185,18 @@ export class Resolver { return pcfg?.exports?.[subpath + '!cjs'] ? true : false; } + async realPath (url: string): Promise { + if (!url.startsWith('file:') || this.preserveSymlinks) + return url; + if (!realpath) { + [{ realpath }, { pathToFileURL }] = await Promise.all([ + import('fs' as any), + import('url' as any) + ]); + } + return pathToFileURL(await new Promise((resolve, reject) => realpath(new URL(url), (err, result) => err ? reject(err) : resolve(result)))).href; + } + async finalizeResolve (url: string, parentIsCjs: boolean, env: string[], pkgUrl?: string): Promise { if (parentIsCjs && url.endsWith('/')) url = url.slice(0, -1); diff --git a/src/trace/tracemap.ts b/src/trace/tracemap.ts index 8f1625cc..c3ad738f 100644 --- a/src/trace/tracemap.ts +++ b/src/trace/tracemap.ts @@ -223,7 +223,7 @@ export default class TraceMap { if (resolvedUrl.protocol !== 'file:' && resolvedUrl.protocol !== 'https:' && resolvedUrl.protocol !== 'http:' && resolvedUrl.protocol !== 'node:' && resolvedUrl.protocol !== 'data:') throw new JspmError(`Found unexpected protocol ${resolvedUrl.protocol}${importedFrom(parentUrl)}`); const resolvedHref = resolvedUrl.href; - const finalized = await this.resolver.finalizeResolve(resolvedHref, parentIsCjs, env); + const finalized = await this.resolver.realPath(await this.resolver.finalizeResolve(resolvedHref, parentIsCjs, env)); if (finalized !== resolvedHref) { this.map.set(resolvedHref.endsWith('/') ? resolvedHref.slice(0, -1) : resolvedHref, finalized); resolvedUrl = new URL(finalized); @@ -244,7 +244,7 @@ export default class TraceMap { for (const [scope] of pkgSubscopes) { const mapMatch = getMapMatch(specifier, this.map.scopes[scope]); if (mapMatch) { - const resolved = new URL(this.map.scopes[scope][mapMatch] + specifier.slice(mapMatch.length), this.map.baseUrl).href; + const resolved = await this.resolver.realPath(new URL(this.map.scopes[scope][mapMatch] + specifier.slice(mapMatch.length), this.map.baseUrl).href); this.log('trace', `${specifier} ${parentUrl.href} -> ${resolved}`); await this.traceUrl(resolved, parentUrl, env); return resolved; @@ -257,7 +257,7 @@ export default class TraceMap { if (userScopeMatch) { const imports = this.map.scopes[userScopeMatch[0]]; const userImportsMatch = getMapMatch(specifier, imports); - const userImportsResolved = userImportsMatch ? new URL(imports[userImportsMatch] + specifier.slice(userImportsMatch.length), this.map.baseUrl).href : null; + const userImportsResolved = userImportsMatch ? await this.resolver.realPath(new URL(imports[userImportsMatch] + specifier.slice(userImportsMatch.length), this.map.baseUrl).href) : null; if (userImportsResolved) { this.log('trace', `${specifier} ${parentUrl.href} -> ${userImportsResolved}`); await this.traceUrl(userImportsResolved, parentUrl, env); @@ -268,7 +268,7 @@ export default class TraceMap { // Own name import const pcfg = await this.resolver.getPackageConfig(parentPkgUrl) || {}; if (pcfg.exports && pcfg.name === pkgName) { - const resolved = await this.resolver.resolveExport(parentPkgUrl, subpath, env, parentIsCjs, specifier, parentUrl); + const resolved = await this.resolver.realPath(await this.resolver.resolveExport(parentPkgUrl, subpath, env, parentIsCjs, specifier, parentUrl)); this.log('trace', `${specifier} ${parentUrl.href} -> ${resolved}`); await this.traceUrl(resolved, parentUrl, env); return resolved; @@ -279,7 +279,7 @@ export default class TraceMap { const match = getMapMatch(pkgName, pcfg.imports); if (!match) throw new JspmError(`No '${pkgName}' import defined in ${parentPkgUrl}${importedFrom(parentUrl)}.`); - const resolved = resolvePackageTarget(pcfg.imports[match], parentPkgUrl, env, subpath === '.' ? '' : subpath.slice(2)); + const resolved = await this.resolver.realPath(resolvePackageTarget(pcfg.imports[match], parentPkgUrl, env, subpath === '.' ? '' : subpath.slice(2))); setResolution(this.installer.installs, pkgName, parentPkgUrl, resolved); this.log('trace', `${specifier} ${parentUrl.href} -> ${resolved}`); await this.traceUrl(resolved, parentUrl, env); @@ -293,7 +293,7 @@ export default class TraceMap { if (subpathBase && !pkgUrl.endsWith('/')) pkgUrl += '/'; const key = subpathBase ? './' + subpathBase + subpath.slice(1) : subpath; - const resolved = await this.resolver.resolveExport(pkgUrl, key, env, parentIsCjs, specifier, parentUrl); + const resolved = await this.resolver.realPath(await this.resolver.resolveExport(pkgUrl, key, env, parentIsCjs, specifier, parentUrl)); this.log('trace', `${specifier} ${parentUrl.href} -> ${resolved}`); await this.traceUrl(resolved, parentUrl, env); return resolved; @@ -301,7 +301,7 @@ export default class TraceMap { // User import overrides const userImportsMatch = getMapMatch(specifier, this.map.imports); - const userImportsResolved = userImportsMatch ? new URL(this.map.imports[userImportsMatch] + specifier.slice(userImportsMatch.length), this.map.baseUrl).href : null; + const userImportsResolved = userImportsMatch ? await this.resolver.realPath(new URL(this.map.imports[userImportsMatch] + specifier.slice(userImportsMatch.length), this.map.baseUrl).href) : null; if (userImportsResolved) { this.log('trace', `${specifier} ${parentUrl.href} -> ${userImportsResolved}`); await this.traceUrl(userImportsResolved, parentUrl, env); @@ -313,7 +313,7 @@ export default class TraceMap { private async traceUrl (resolvedUrl: string, parentUrl: URL, env: string[]): Promise { if (resolvedUrl in this.tracedUrls) return; - + const traceEntry: TraceEntry = this.tracedUrls[resolvedUrl] = { wasCJS: false, deps: Object.create(null), @@ -324,10 +324,10 @@ export default class TraceMap { integrity: '', format: undefined }; - + if (resolvedUrl.endsWith('/')) throw new JspmError(`Trailing "/" installs not yet supported installing ${resolvedUrl} for ${parentUrl.href}`); - + const parentIsCjs = this.tracedUrls[parentUrl.href]?.format === 'commonjs'; const { deps, dynamicDeps, cjsLazyDeps, size, format } = await this.resolver.analyze(resolvedUrl, parentUrl, this.opts.system, parentIsCjs); diff --git a/test/api/self.test.js b/test/api/self.test.js index 9189d6fa..0a5c451e 100644 --- a/test/api/self.test.js +++ b/test/api/self.test.js @@ -7,7 +7,7 @@ const generator = new Generator({ const { staticDeps, dynamicDeps } = await generator.install('@jspm/generator'); -assert.strictEqual(staticDeps.length, 10); +assert.strictEqual(staticDeps.length, 14); assert.strictEqual(dynamicDeps.length, 0); const json = generator.getMap();