From 711d2837e427d008c8e7f560eba24ca68eb62dc1 Mon Sep 17 00:00:00 2001 From: Lexus Drumgold Date: Sat, 2 Nov 2024 21:49:40 -0400 Subject: [PATCH] refactor: async api Signed-off-by: Lexus Drumgold --- build/make.mts | 2 +- package.json | 4 +- .../__tests__/file-system.spec-d.mts | 28 +++-- src/interfaces/file-system.mts | 18 +-- src/internal/fs.browser.mts | 12 +- src/lib/__snapshots__/resolve-module.snap | 2 +- src/lib/__snapshots__/resolver.snap | 2 +- src/lib/__tests__/is-directory.spec.mts | 10 +- src/lib/__tests__/is-file.spec.mts | 10 +- .../__tests__/lookup-package-scope.spec.mts | 8 +- src/lib/__tests__/read-package-json.spec.mts | 12 +- src/lib/__tests__/resolve-module.spec.mts | 8 +- src/lib/__tests__/resolver.spec.mts | 70 ++++++----- src/lib/get-source.mts | 13 +- src/lib/is-directory.mts | 10 +- src/lib/is-file.mts | 10 +- src/lib/lookup-package-scope.mts | 10 +- src/lib/read-package-json.mts | 12 +- src/lib/resolve-module.mts | 12 +- src/lib/resolver.mts | 117 ++++++++++-------- 20 files changed, 207 insertions(+), 163 deletions(-) diff --git a/build/make.mts b/build/make.mts index 7a928d5f..7d5b8195 100644 --- a/build/make.mts +++ b/build/make.mts @@ -183,7 +183,7 @@ async function make( * * @const {PackageJson | null} pkg */ - const pkg: PackageJson | null = readPackageJson( + const pkg: PackageJson | null = await readPackageJson( pathe.pathToFileURL(absWorkingDir + pathe.sep) ) diff --git a/package.json b/package.json index fda63702..5a488f9f 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,8 @@ "mlly": "./src/internal/fs.browser.mts", "default": "./dist/internal/fs.browser.mjs" }, - "node": "fs", - "default": "fs" + "node": "fs/promises", + "default": "fs/promises" }, "#internal/process": { "types": { diff --git a/src/interfaces/__tests__/file-system.spec-d.mts b/src/interfaces/__tests__/file-system.spec-d.mts index 4a92e7a1..28c9cfc2 100644 --- a/src/interfaces/__tests__/file-system.spec-d.mts +++ b/src/interfaces/__tests__/file-system.spec-d.mts @@ -4,11 +4,11 @@ */ import type TestSubject from '#interfaces/file-system' -import type { ModuleId, Stats } from '@flex-development/mlly' +import type { Awaitable, ModuleId, Stats } from '@flex-development/mlly' describe('unit-d:interfaces/FileSystem', () => { - describe('readFileSync', () => { - type Subject = TestSubject['readFileSync'] + describe('readFile', () => { + type Subject = TestSubject['readFile'] it('should match [this: void]', () => { expectTypeOf().thisParameter.toEqualTypeOf() @@ -21,14 +21,16 @@ describe('unit-d:interfaces/FileSystem', () => { }) describe('returns', () => { - it('should return Buffer | string', () => { - expectTypeOf().returns.toEqualTypeOf() + it('should return Awaitable', () => { + expectTypeOf() + .returns + .toEqualTypeOf>() }) }) }) - describe('realpathSync', () => { - type Subject = TestSubject['realpathSync'] + describe('realpath', () => { + type Subject = TestSubject['realpath'] it('should match [this: void]', () => { expectTypeOf().thisParameter.toEqualTypeOf() @@ -41,14 +43,14 @@ describe('unit-d:interfaces/FileSystem', () => { }) describe('returns', () => { - it('should return string', () => { - expectTypeOf().returns.toEqualTypeOf() + it('should return Awaitable', () => { + expectTypeOf().returns.toEqualTypeOf>() }) }) }) - describe('statSync', () => { - type Subject = TestSubject['statSync'] + describe('stat', () => { + type Subject = TestSubject['stat'] it('should match [this: void]', () => { expectTypeOf().thisParameter.toEqualTypeOf() @@ -61,8 +63,8 @@ describe('unit-d:interfaces/FileSystem', () => { }) describe('returns', () => { - it('should return Stats', () => { - expectTypeOf().returns.toEqualTypeOf() + it('should return Awaitable', () => { + expectTypeOf().returns.toEqualTypeOf>() }) }) }) diff --git a/src/interfaces/file-system.mts b/src/interfaces/file-system.mts index 51c84dd1..c224f9a6 100644 --- a/src/interfaces/file-system.mts +++ b/src/interfaces/file-system.mts @@ -3,7 +3,7 @@ * @module mlly/interfaces/FileSystem */ -import type { ModuleId, Stats } from '@flex-development/mlly' +import type { Awaitable, ModuleId, Stats } from '@flex-development/mlly' /** * File system API. @@ -12,6 +12,8 @@ interface FileSystem { /** * Get the contents of `id`. * + * @see {@linkcode Awaitable} + * @see {@linkcode Buffer} * @see {@linkcode ModuleId} * @see https://nodejs.org/api/fs.html#fsreadfilepath-options-callback * @@ -19,14 +21,15 @@ interface FileSystem { * * @param {ModuleId} id * The path or `file:` URL to handle - * @return {Buffer | string} + * @return {Awaitable} * File contents */ - readFileSync(this: void, id: ModuleId): Buffer | string + readFile(this: void, id: ModuleId): Awaitable /** * Get the resolved pathname for `id`. * + * @see {@linkcode Awaitable} * @see {@linkcode ModuleId} * @see https://nodejs.org/api/fs.html#fsrealpathpath-options-callback * @@ -34,14 +37,15 @@ interface FileSystem { * * @param {ModuleId} id * The path or `file:` URL to handle - * @return {string} + * @return {Awaitable} * Resolved pathname */ - realpathSync(this: void, id: ModuleId): string + realpath(this: void, id: ModuleId): Awaitable /** * Get information about a directory or file. * + * @see {@linkcode Awaitable} * @see {@linkcode ModuleId} * @see {@linkcode Stats} * @@ -49,10 +53,10 @@ interface FileSystem { * * @param {ModuleId} id * The path or `file:` URL to handle - * @return {Stats} + * @return {Awaitable} * Info about `id` */ - statSync(this: void, id: ModuleId): Stats + stat(this: void, id: ModuleId): Awaitable } export type { FileSystem as default } diff --git a/src/internal/fs.browser.mts b/src/internal/fs.browser.mts index cd13f789..eed89372 100644 --- a/src/internal/fs.browser.mts +++ b/src/internal/fs.browser.mts @@ -18,8 +18,8 @@ const fs: FileSystem = { * Never; not implemented * @throws {Error} */ - readFileSync(): never { - throw new Error('[readFileSync] not implemented') + readFile(): never { + throw new Error('[readFile] not implemented') }, /** @@ -29,8 +29,8 @@ const fs: FileSystem = { * Never; not implemented * @throws {Error} */ - realpathSync(): never { - throw new Error('[realpathSync] not implemented') + realpath(): never { + throw new Error('[realpath] not implemented') }, /** @@ -40,8 +40,8 @@ const fs: FileSystem = { * Never; not implemented * @throws {Error} */ - statSync(): never { - throw new Error('[statSync] not implemented') + stat(): never { + throw new Error('[stat] not implemented') } } diff --git a/src/lib/__snapshots__/resolve-module.snap b/src/lib/__snapshots__/resolve-module.snap index 57f548e0..4d4887f1 100644 --- a/src/lib/__snapshots__/resolve-module.snap +++ b/src/lib/__snapshots__/resolve-module.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`unit:lib/resolveModule > should return resolved URL ("#internal/fs") 1`] = `"node:fs"`; +exports[`unit:lib/resolveModule > should return resolved URL ("#internal/fs") 1`] = `"node:fs/promises"`; exports[`unit:lib/resolveModule > should return resolved URL ("../../../src") 1`] = `file://\${process.cwd()}/src/index.mts`; diff --git a/src/lib/__snapshots__/resolver.snap b/src/lib/__snapshots__/resolver.snap index ca7b2547..44d61eac 100644 --- a/src/lib/__snapshots__/resolver.snap +++ b/src/lib/__snapshots__/resolver.snap @@ -2,7 +2,7 @@ exports[`unit:lib/resolver > legacyMainResolve > should throw if main entry point is not found 1`] = `Cannot find package '\${process.cwd()}/__fixtures__/node_modules/@flex-development/mlly/' imported from \${process.cwd()}/__fixtures__/parent.mts`; -exports[`unit:lib/resolver > moduleResolve > should return resolved URL (0) 1`] = `"node:fs"`; +exports[`unit:lib/resolver > moduleResolve > should return resolved URL (0) 1`] = `"node:fs/promises"`; exports[`unit:lib/resolver > moduleResolve > should return resolved URL (1) 1`] = `file://\${process.cwd()}/src/internal/fs.browser.mts`; diff --git a/src/lib/__tests__/is-directory.spec.mts b/src/lib/__tests__/is-directory.spec.mts index 80cd83a6..c016a5dd 100644 --- a/src/lib/__tests__/is-directory.spec.mts +++ b/src/lib/__tests__/is-directory.spec.mts @@ -7,21 +7,21 @@ import cwd from '#lib/cwd' import testSubject from '#lib/is-directory' import type { ModuleId } from '@flex-development/mlly' import { pathToFileURL } from '@flex-development/pathe' -import fs from 'node:fs' +import fs from 'node:fs/promises' describe('unit:lib/isDirectory', () => { it.each([ 'directory', new URL('node:fs'), String(new URL('src/index.mts', cwd())) - ])('should return `false` if `id` is not a directory (%#)', id => { - expect(testSubject(id, fs)).to.be.false + ])('should return `false` if `id` is not a directory (%#)', async id => { + expect(await testSubject(id, fs)).to.be.false }) it.each([ pathToFileURL('src'), String(cwd()) - ])('should return `true` if `id` is a directory (%#)', id => { - expect(testSubject(id)).to.be.true + ])('should return `true` if `id` is a directory (%#)', async id => { + expect(await testSubject(id)).to.be.true }) }) diff --git a/src/lib/__tests__/is-file.spec.mts b/src/lib/__tests__/is-file.spec.mts index 0054812f..7431fb86 100644 --- a/src/lib/__tests__/is-file.spec.mts +++ b/src/lib/__tests__/is-file.spec.mts @@ -7,21 +7,21 @@ import cwd from '#lib/cwd' import testSubject from '#lib/is-file' import type { ModuleId } from '@flex-development/mlly' import { pathToFileURL } from '@flex-development/pathe' -import fs from 'node:fs' +import fs from 'node:fs/promises' describe('unit:lib/isFile', () => { it.each([ 'file.mjs', new URL('node:fs'), String(cwd()) - ])('should return `false` if `id` is not a file (%#)', id => { - expect(testSubject(id, fs)).to.be.false + ])('should return `false` if `id` is not a file (%#)', async id => { + expect(await testSubject(id, fs)).to.be.false }) it.each([ pathToFileURL('package.json'), String(new URL('vitest.config.mts', cwd())) - ])('should return `true` if `id` is a file (%#)', id => { - expect(testSubject(id)).to.be.true + ])('should return `true` if `id` is a file (%#)', async id => { + expect(await testSubject(id)).to.be.true }) }) diff --git a/src/lib/__tests__/lookup-package-scope.spec.mts b/src/lib/__tests__/lookup-package-scope.spec.mts index 3a3c17d0..e5dfe777 100644 --- a/src/lib/__tests__/lookup-package-scope.spec.mts +++ b/src/lib/__tests__/lookup-package-scope.spec.mts @@ -7,12 +7,12 @@ import testSubject from '#lib/lookup-package-scope' import { pathToFileURL, sep } from '@flex-development/pathe' describe('unit:lib/lookupPackageScope', () => { - it('should return `null` if package directory is not found', () => { + it('should return `null` if package directory is not found', async () => { // Arrange const url: URL = pathToFileURL('node_modules/@flex-development/404.mjs') // Act + Expect - expect(testSubject(url)).to.be.null + expect(await testSubject(url)).to.be.null }) it.each>([ @@ -24,9 +24,9 @@ describe('unit:lib/lookupPackageScope', () => { 'node_modules/@commitlint/cli/node_modules/@commitlint/config-validator/lib/validate.js' )], [pathToFileURL('node_modules/esbuild/lib/main.js')] - ])('should return URL of package directory (%#)', url => { + ])('should return URL of package directory (%#)', async url => { // Act - const result = testSubject(url) + const result = await testSubject(url) const resultStr = String(result) // Expect diff --git a/src/lib/__tests__/read-package-json.spec.mts b/src/lib/__tests__/read-package-json.spec.mts index 526055e3..abbcb6d1 100644 --- a/src/lib/__tests__/read-package-json.spec.mts +++ b/src/lib/__tests__/read-package-json.spec.mts @@ -14,18 +14,18 @@ import { import pkg from '@flex-development/mlly/package.json' describe('unit:lib/readPackageJson', () => { - it('should return `null` if `package.json` file is not found', () => { - expect(testSubject(import.meta.url)).to.be.null + it('should return `null` if `package.json` file is not found', async () => { + expect(await testSubject(import.meta.url)).to.be.null }) - it('should return package manifest object', () => { - expect(testSubject(cwd())).to.eql(pkg) + it('should return package manifest object', async () => { + expect(await testSubject(cwd())).to.eql(pkg) }) it.each>([ [invalidJsonUrl, String(new URL('package.json', invalidJsonUrl))], [invalidJsonUrl, 'invalid-json', import.meta.url] - ])('should throw if package manifest does not parse as valid JSON (%#)', ( + ])('should throw if package manifest is not valid JSON (%#)', async ( id, specifier, parent @@ -35,7 +35,7 @@ describe('unit:lib/readPackageJson', () => { // Act try { - testSubject(id, specifier, parent) + await testSubject(id, specifier, parent) } catch (e: unknown) { error = e as typeof error } diff --git a/src/lib/__tests__/resolve-module.spec.mts b/src/lib/__tests__/resolve-module.spec.mts index eb3bd3b3..731beb54 100644 --- a/src/lib/__tests__/resolve-module.spec.mts +++ b/src/lib/__tests__/resolve-module.spec.mts @@ -28,9 +28,9 @@ describe('unit:lib/resolveModule', () => { [legacyMain1.name, parent], [legacyMain2.name, parent], [pkg.name + '/package.json', import.meta.url] - ])('should return resolved URL (%j)', (specifier, parent, options) => { + ])('should return resolved URL (%j)', async (specifier, parent, options) => { // Act - const result = testSubject(specifier, parent, options) + const result = await testSubject(specifier, parent, options) // Expect expect(result).to.be.instanceof(URL) @@ -40,7 +40,7 @@ describe('unit:lib/resolveModule', () => { it.each>([ ['#app', import.meta.url], ['../../src', import.meta.url] - ])('should throw if `specifier` cannot be resolved (%#)', ( + ])('should throw if `specifier` cannot be resolved (%#)', async ( specifier, parent, options @@ -50,7 +50,7 @@ describe('unit:lib/resolveModule', () => { // Act try { - testSubject(specifier, parent, options) + await testSubject(specifier, parent, options) } catch (e: unknown) { error = e as typeof error } diff --git a/src/lib/__tests__/resolver.spec.mts b/src/lib/__tests__/resolver.spec.mts index 27bfd7bc..3671fc67 100644 --- a/src/lib/__tests__/resolver.spec.mts +++ b/src/lib/__tests__/resolver.spec.mts @@ -30,13 +30,13 @@ import * as baseline from 'import-meta-resolve' describe('unit:lib/resolver', () => { describe('legacyMainResolve', () => { - it('should throw if main entry point is not found', () => { + it('should throw if main entry point is not found', async () => { // Arrange let error!: NodeError // Act try { - testSubject.legacyMainResolve( + await testSubject.legacyMainResolve( toPackageUrl(pkg.name), pkg as PackageJson, new Set(['unpkg'] as unknown as MainField[]), @@ -85,7 +85,7 @@ describe('unit:lib/resolver', () => { [resolve('src/index.mts'), import.meta.url], [subpathExports.name + '/lib/a', parent], [subpathExports.name + '/lib/a.js', parent] - ])('should return resolved URL (%#)', ( + ])('should return resolved URL (%#)', async ( specifier, parent, conditions, @@ -101,7 +101,7 @@ describe('unit:lib/resolver', () => { ) // Act - const result = testSubject.moduleResolve( + const result = await testSubject.moduleResolve( specifier, parent, conditions, @@ -115,13 +115,16 @@ describe('unit:lib/resolver', () => { expect(result).toMatchSnapshot() }) - it('should throw if `specifier` contains encoded separators', () => { + it('should throw if `specifier` contains encoded separators', async () => { // Arrange let error!: NodeError // Act try { - testSubject.moduleResolve(sep + 'lib%2futils.mjs', import.meta.url) + await testSubject.moduleResolve( + sep + 'lib%2futils.mjs', + import.meta.url + ) } catch (e: unknown) { error = e as typeof error } @@ -132,14 +135,14 @@ describe('unit:lib/resolver', () => { expect(error.message).toMatchSnapshot() }) - it('should throw if `specifier` resolves to a directory', () => { + it('should throw if `specifier` resolves to a directory', async () => { // Arrange const specifier: string = resolve('src') + sep let error!: NodeError // Act try { - testSubject.moduleResolve(specifier, import.meta.url) + await testSubject.moduleResolve(specifier, import.meta.url) } catch (e: unknown) { error = e as typeof error } @@ -151,14 +154,14 @@ describe('unit:lib/resolver', () => { expect(error.message).toMatchSnapshot() }) - it('should throw if `specifier` resolves to missing file', () => { + it('should throw if `specifier` resolves to missing file', async () => { // Arrange const specifier: string = resolve('src/index.ts') let error!: NodeError // Act try { - testSubject.moduleResolve(specifier, import.meta.url) + await testSubject.moduleResolve(specifier, import.meta.url) } catch (e: unknown) { error = e as typeof error } @@ -179,7 +182,7 @@ describe('unit:lib/resolver', () => { 'not-builtin', 'data:text/javascript,export default import.meta.resolve("not-builtin")' ] - ])('should throw if module referrer is invalid (%#)', ( + ])('should throw if module referrer is invalid (%#)', async ( specifier, parent, conditions, @@ -193,7 +196,7 @@ describe('unit:lib/resolver', () => { // Act try { - testSubject.moduleResolve( + await testSubject.moduleResolve( specifier, parent, conditions, @@ -213,13 +216,13 @@ describe('unit:lib/resolver', () => { }) describe('packageExportsResolve', () => { - it('should throw if `exports` object is invalid', () => { + it('should throw if `exports` object is invalid', async () => { // Arrange let error!: NodeError // Act try { - testSubject.packageExportsResolve( + await testSubject.packageExportsResolve( cwd(), dot, { [dot]: './dist/index.mjs', 'ts-node': './src/index.mts' }, @@ -236,14 +239,14 @@ describe('unit:lib/resolver', () => { expect(error.message).toMatchSnapshot() }) - it('should throw if package path is not exported', () => { + it('should throw if package path is not exported', async () => { // Arrange const code: Code = codes.ERR_PACKAGE_PATH_NOT_EXPORTED let error!: NodeError // Act try { - testSubject.packageExportsResolve( + await testSubject.packageExportsResolve( cwd(), './utils', pkg.exports, @@ -262,14 +265,14 @@ describe('unit:lib/resolver', () => { }) describe('packageImportsResolve', () => { - it('should throw if package import is not defined', () => { + it('should throw if package import is not defined', async () => { // Arrange const code: Code = codes.ERR_PACKAGE_IMPORT_NOT_DEFINED let error!: NodeError // Act try { - testSubject.packageImportsResolve('#mocks', import.meta.url) + await testSubject.packageImportsResolve('#mocks', import.meta.url) } catch (e: unknown) { error = e as typeof error } @@ -284,13 +287,13 @@ describe('unit:lib/resolver', () => { chars.hash, chars.hash + sep, pkg.name - ])('should throw if `specifier` is invalid (%#)', specifier => { + ])('should throw if `specifier` is invalid (%#)', async specifier => { // Arrange let error!: NodeError // Act try { - testSubject.packageImportsResolve(specifier, import.meta.url) + await testSubject.packageImportsResolve(specifier, import.meta.url) } catch (e: unknown) { error = e as typeof error } @@ -303,13 +306,13 @@ describe('unit:lib/resolver', () => { }) describe('packageResolve', () => { - it('should throw if `specifier` cannot be resolved', () => { + it('should throw if `specifier` cannot be resolved', async () => { // Arrange let error!: NodeError // Act try { - testSubject.packageResolve('missing-package', parent) + await testSubject.packageResolve('missing-package', parent) } catch (e: unknown) { error = e as typeof error } @@ -323,13 +326,16 @@ describe('unit:lib/resolver', () => { it.each>([ ['@flex-development\\errnode', import.meta.url], [chars.at, import.meta.url] - ])('should throw if `specifier` is invalid (%#)', (specifier, parent) => { + ])('should throw if `specifier` is invalid (%#)', async ( + specifier, + parent + ) => { // Arrange let error!: NodeError // Act try { - testSubject.packageResolve(specifier, parent) + await testSubject.packageResolve(specifier, parent) } catch (e: unknown) { error = e as typeof error } @@ -342,12 +348,12 @@ describe('unit:lib/resolver', () => { }) describe('packageSelfResolve', () => { - it('should return `undefined` if package scope is not found', () => { + it('should return `undefined` if package scope is not found', async () => { // Arrange const parent: URL = pathToFileURL('../loader.mjs') // Act - const result = testSubject.packageSelfResolve(pkg.name, dot, parent) + const result = await testSubject.packageSelfResolve(pkg.name, dot, parent) // Expect expect(result).to.be.undefined @@ -355,13 +361,13 @@ describe('unit:lib/resolver', () => { }) describe('packageTargetResolve', () => { - it('should throw if `patternMatch` contains invalid segments', () => { + it('should throw if `patternMatch` contains invalid segments', async () => { // Arrange let error!: NodeError // Act try { - testSubject.packageTargetResolve( + await testSubject.packageTargetResolve( toPackageUrl('invalid-pattern-match'), './__fixtures__/*', '#fixtures/*', @@ -380,13 +386,13 @@ describe('unit:lib/resolver', () => { expect(error).to.have.property('code', codes.ERR_INVALID_MODULE_SPECIFIER) }) - it('should throw if package config is invalid', () => { + it('should throw if package config is invalid', async () => { // Arrange let error!: NodeError // Act try { - testSubject.packageTargetResolve( + await testSubject.packageTargetResolve( toPackageUrl('invalid-package-config'), [{ 13: null }], dot, @@ -477,7 +483,7 @@ describe('unit:lib/resolver', () => { null, import.meta.url ] - ])('should throw if package target is invalid (%#)', ( + ])('should throw if package target is invalid (%#)', async ( packageUrl, target, subpath, @@ -493,7 +499,7 @@ describe('unit:lib/resolver', () => { // Act try { - testSubject.packageTargetResolve( + await testSubject.packageTargetResolve( packageUrl, target, subpath, diff --git a/src/lib/get-source.mts b/src/lib/get-source.mts index ec049f81..6c51faa3 100644 --- a/src/lib/get-source.mts +++ b/src/lib/get-source.mts @@ -147,14 +147,19 @@ function data(this: GetSourceContext, url: URL): Buffer { } /** + * @async + * * @this {GetSourceContext} * * @param {URL} url * Module URL - * @return {Buffer | string | null} + * @return {Promise} * Source code or `null` if module is not found */ -function file(this: GetSourceContext, url: URL): Buffer | string | null { +async function file( + this: GetSourceContext, + url: URL +): Promise { ok(url.protocol === 'file:', 'expected `file:` URL') /** @@ -164,11 +169,13 @@ function file(this: GetSourceContext, url: URL): Buffer | string | null { */ let code: Buffer | string | null = null - if (isFile(url, this.fs)) code = this.fs.readFileSync(url) + if (await isFile(url, this.fs)) code = await this.fs.readFile(url) return code } /** + * @async + * * @this {GetSourceContext} * * @param {URL} url diff --git a/src/lib/is-directory.mts b/src/lib/is-directory.mts index 9245b12b..576936f7 100644 --- a/src/lib/is-directory.mts +++ b/src/lib/is-directory.mts @@ -12,20 +12,22 @@ import type { FileSystem, ModuleId } from '@flex-development/mlly' * @see {@linkcode FileSystem} * @see {@linkcode ModuleId} * + * @async + * * @param {ModuleId} id * Module id to check * @param {FileSystem | null | undefined} fs * File system API - * @return {boolean} + * @return {Promise} * `true` if directory exists at `id`, `false` otherwise */ -function isDirectory( +async function isDirectory( id: ModuleId, fs?: FileSystem | null | undefined -): boolean { +): Promise { try { if (typeof id === 'string' && id.startsWith('file:')) id = new URL(id) - return (fs ?? dfs).statSync(id).isDirectory() + return (await (fs ?? dfs).stat(id)).isDirectory() } catch { return false } diff --git a/src/lib/is-file.mts b/src/lib/is-file.mts index f0bde375..516a6da4 100644 --- a/src/lib/is-file.mts +++ b/src/lib/is-file.mts @@ -12,20 +12,22 @@ import type { FileSystem, ModuleId } from '@flex-development/mlly' * @see {@linkcode FileSystem} * @see {@linkcode ModuleId} * + * @async + * * @param {ModuleId} id * Module id to check * @param {FileSystem | null | undefined} fs * File system API - * @return {boolean} + * @return {Promise} * `true` if file exists at `id`, `false` otherwise */ -function isFile( +async function isFile( id: ModuleId, fs?: FileSystem | null | undefined -): boolean { +): Promise { try { if (typeof id === 'string' && id.startsWith('file:')) id = new URL(id) - return (fs ?? dfs).statSync(id).isFile() + return (await (fs ?? dfs).stat(id)).isFile() } catch { return false } diff --git a/src/lib/lookup-package-scope.mts b/src/lib/lookup-package-scope.mts index e9cf6fdf..bcc0bc8f 100644 --- a/src/lib/lookup-package-scope.mts +++ b/src/lib/lookup-package-scope.mts @@ -16,20 +16,22 @@ import pathe from '@flex-development/pathe' * @see {@linkcode FileSystem} * @see {@linkcode ModuleId} * + * @async + * * @param {ModuleId} url * URL of module to get package scope for * @param {ModuleId | null | undefined} [end] * URL of directory to end search, defaults to {@linkcode root} * @param {FileSystem | null | undefined} [fs] * File system API - * @return {URL | null} + * @return {Promise} * URL of nearest directory containing `package.json` file or `null` */ -function lookupPackageScope( +async function lookupPackageScope( url: ModuleId, end?: ModuleId | null | undefined, fs?: FileSystem | null | undefined -): URL | null { +): Promise { /** * Scope URL. * @@ -47,7 +49,7 @@ function lookupPackageScope( if (/node_modules[/\\]$/.test(scopeUrl.pathname)) break // return scopeUrl if `package.json` file exists in directory - if (isFile(new URL('package.json', scopeUrl), fs)) return scopeUrl + if (await isFile(new URL('package.json', scopeUrl), fs)) return scopeUrl } return null diff --git a/src/lib/read-package-json.mts b/src/lib/read-package-json.mts index b8f8421c..b650c14e 100644 --- a/src/lib/read-package-json.mts +++ b/src/lib/read-package-json.mts @@ -23,6 +23,8 @@ import type { PackageJson } from '@flex-development/pkg-types' * @see {@linkcode ModuleId} * @see {@linkcode PackageJson} * + * @async + * * @param {ModuleId} id * URL of package directory, `package.json` file, or module in the same * directory as a `package.json` file @@ -33,17 +35,17 @@ import type { PackageJson } from '@flex-development/pkg-types' * URL of parent module * @param {FileSystem | null | undefined} [fs] * File system API - * @return {PackageJson | null} + * @return {Promise} * Parsed file contents or `null` * @throws {ErrInvalidPackageConfig} * If `package.json` file does not parse as valid JSON */ -function readPackageJson( +async function readPackageJson( id: ModuleId, specifier?: string | null | undefined, parent?: ModuleId | null | undefined, fs?: FileSystem | null | undefined -): PackageJson | null { +): Promise { /** * URL of `package.json` file. * @@ -51,13 +53,13 @@ function readPackageJson( */ const url: URL = new URL('package.json', id) - if (isFile(url, fs)) { + if (await isFile(url, fs)) { /** * Stringified package config. * * @const {string} data */ - const data: string = String((fs ?? dfs).readFileSync(url)) + const data: string = String(await (fs ?? dfs).readFile(url)) try { return JSON.parse(data) as PackageJson diff --git a/src/lib/resolve-module.mts b/src/lib/resolve-module.mts index c55dd41d..2fef2ee5 100644 --- a/src/lib/resolve-module.mts +++ b/src/lib/resolve-module.mts @@ -40,23 +40,25 @@ export default resolveModule * @see {@linkcode NodeError} * @see {@linkcode ResolveModuleOptions} * + * @async + * * @param {string} specifier * The module specifier to resolve * @param {ModuleId} parent * URL of parent module * @param {ResolveModuleOptions | null | undefined} [options] * Resolution options - * @return {URL} + * @return {Promise} * Resolved URL * @throws {NodeError} */ -function resolveModule( +async function resolveModule( specifier: string, parent: ModuleId, options?: ResolveModuleOptions | null | undefined -): URL { +): Promise { try { - return changeExt(moduleResolve( + return changeExt(await moduleResolve( resolveAlias(specifier, { ...options, parent }) ?? specifier, parent, options?.conditions, @@ -125,7 +127,7 @@ function resolveModule( // try module resolution attempts. for (const attempt of tries) { try { - return changeExt(moduleResolve( + return changeExt(await moduleResolve( attempt, parent, options?.conditions, diff --git a/src/lib/resolver.mts b/src/lib/resolver.mts index 0d26d9e0..47f1b775 100644 --- a/src/lib/resolver.mts +++ b/src/lib/resolver.mts @@ -92,6 +92,8 @@ export { * @see {@linkcode ModuleId} * @see {@linkcode PackageJson} * + * @async + * * @param {ModuleId} packageUrl * URL of package directory, `package.json` file, or module in the same * directory as a `package.json` file @@ -103,17 +105,17 @@ export { * URL of parent module * @param {FileSystem | null | undefined} [fs] * File system API - * @return {URL} + * @return {Promise} * Resolved URL * @throws {ErrModuleNotFound} */ -function legacyMainResolve( +async function legacyMainResolve( packageUrl: ModuleId, manifest?: PackageJson | null | undefined, mainFields?: MainField[] | Set | null | undefined, parent?: ModuleId | null | undefined, fs?: FileSystem | null | undefined -): URL { +): Promise { if (manifest) { for (const field of mainFields ?? defaultMainFields) { /** @@ -153,7 +155,7 @@ function legacyMainResolve( for (const input of tries) { resolved = new URL(input, packageUrl) - if (isFile(resolved, fs)) return resolved + if (await isFile(resolved, fs)) return resolved } } } @@ -177,6 +179,8 @@ function legacyMainResolve( * @see {@linkcode ModuleId} * @see https://github.com/nodejs/node/blob/v22.9.0/doc/api/esm.md#resolution-algorithm * + * @async + * * @param {string} specifier * Module specifier to resolve * @param {ModuleId} parent @@ -189,20 +193,20 @@ function legacyMainResolve( * Keep symlinks instead of resolving them * @param {FileSystem | null | undefined} [fs] * File system API - * @return {URL} + * @return {Promise} * Resolved URL * @throws {ErrInvalidModuleSpecifier} * @throws {ErrModuleNotFound} * @throws {ErrUnsupportedDirImport} */ -function moduleResolve( +async function moduleResolve( specifier: string, parent: ModuleId, conditions?: Condition[] | Set | null | undefined, mainFields?: MainField[] | Set | null | undefined, preserveSymlinks?: boolean | null | undefined, fs?: FileSystem | null | undefined -): URL { +): Promise { /** * URL protocol of parent module. * @@ -236,7 +240,7 @@ function moduleResolve( throw error } } else if (protocol === 'file:' && isImportsSubpath(specifier)) { - resolved = packageImportsResolve( + resolved = await packageImportsResolve( specifier, parent, conditions, @@ -261,7 +265,7 @@ function moduleResolve( throw error } - resolved = packageResolve( + resolved = await packageResolve( specifier, parent, conditions, @@ -284,7 +288,7 @@ function moduleResolve( ) } - if (isDirectory(resolved, fs)) { + if (await isDirectory(resolved, fs)) { /** * Node error. * @@ -302,7 +306,7 @@ function moduleResolve( throw error } - if (!isFile(resolved, fs)) { + if (!(await isFile(resolved, fs))) { throw new ERR_MODULE_NOT_FOUND( pathname, pathe.fileURLToPath(parent), @@ -312,7 +316,7 @@ function moduleResolve( if (!preserveSymlinks) { fs ??= dfs - resolved = new URL(pathe.pathToFileURL(fs.realpathSync(resolved))) + resolved = new URL(pathe.pathToFileURL(await fs.realpath(resolved))) resolved.hash = hash resolved.search = search } @@ -335,6 +339,8 @@ function moduleResolve( * @see {@linkcode ModuleId} * @see https://github.com/nodejs/node/blob/v22.9.0/doc/api/esm.md#resolution-algorithm * + * @async + * * @param {ModuleId} packageUrl * URL of package directory, `package.json` file, or module in the same * directory as a `package.json` file @@ -348,19 +354,19 @@ function moduleResolve( * URL of parent module * @param {FileSystem | null | undefined} [fs] * File system API - * @return {URL} + * @return {Promise} * Resolved URL * @throws {ErrInvalidPackageConfig} * @throws {ErrPackagePathNotExported} */ -function packageExportsResolve( +async function packageExportsResolve( packageUrl: ModuleId, subpath: string, exports: Exports | undefined, conditions?: Condition[] | Set | null | undefined, parent?: ModuleId | null | undefined, fs?: FileSystem | null | undefined -): URL { +): Promise { if (exports) { /** * Boolean indicating all {@linkcode exports} object keys must start with a @@ -420,7 +426,7 @@ function packageExportsResolve( } if (mainExport !== undefined) { - resolved = packageTargetResolve( + resolved = await packageTargetResolve( packageUrl, mainExport, subpath, @@ -436,7 +442,7 @@ function packageExportsResolve( ok(!Array.isArray(exports), 'expected `exports` to not be an array') ok(subpath.startsWith('./'), 'expected `subpath` to start with "./"') - resolved = packageImportsExportsResolve( + resolved = await packageImportsExportsResolve( subpath, exports, packageUrl, @@ -470,6 +476,8 @@ function packageExportsResolve( * @see {@linkcode ModuleId} * @see https://github.com/nodejs/node/blob/v22.9.0/doc/api/esm.md#resolution-algorithm * + * @async + * * @param {string} matchKey * Package subpath from module specifier or dot character (`'.'`) * @param {ExportsObject | Imports | null | undefined} matchObject @@ -486,10 +494,10 @@ function packageExportsResolve( * URL of parent module * @param {FileSystem | null | undefined} [fs] * File system API - * @return {URL | null | undefined} + * @return {Promise} * Resolved URL */ -function packageImportsExportsResolve( +async function packageImportsExportsResolve( matchKey: string, matchObject: ExportsObject | Imports | null | undefined, packageUrl: ModuleId, @@ -498,7 +506,7 @@ function packageImportsExportsResolve( mainFields?: MainField[] | Set | null | undefined, parent?: ModuleId | null | undefined, fs?: FileSystem | null | undefined -): URL | null | undefined { +): Promise { if (typeof matchObject === 'object' && matchObject) { /** * List containing expansion key and subpath pattern match. @@ -546,18 +554,18 @@ function packageImportsExportsResolve( * List of legacy main fields * @param {FileSystem | null | undefined} [fs] * File system API - * @return {URL} + * @return {Promise} * Resolved URL * @throws {ErrInvalidModuleSpecifier} * @throws {ErrPackageImportNotDefined} */ -function packageImportsResolve( +async function packageImportsResolve( specifier: string, parent: ModuleId, conditions?: Condition[] | Set | null | undefined, mainFields?: MainField[] | Set | null | undefined, fs?: FileSystem | null | undefined -): URL { +): Promise { if ( !specifier.startsWith(chars.hash) || specifier === chars.hash || @@ -575,7 +583,7 @@ function packageImportsResolve( * * @const {URL | null} packageUrl */ - const packageUrl: URL | null = lookupPackageScope(parent, null, fs) + const packageUrl: URL | null = await lookupPackageScope(parent, null, fs) if (packageUrl) { /** @@ -583,7 +591,7 @@ function packageImportsResolve( * * @const {PackageJson | null} pjson */ - const pjson: PackageJson | null = readPackageJson( + const pjson: PackageJson | null = await readPackageJson( packageUrl, specifier, parent, @@ -596,16 +604,17 @@ function packageImportsResolve( * * @const {URL | null | undefined} resolved */ - const resolved: URL | null | undefined = packageImportsExportsResolve( - specifier, - pjson.imports, - packageUrl, - true, - conditions, - mainFields, - parent, - fs - ) + const resolved: URL | null | undefined = + await packageImportsExportsResolve( + specifier, + pjson.imports, + packageUrl, + true, + conditions, + mainFields, + parent, + fs + ) if (resolved) return resolved } @@ -639,6 +648,8 @@ function packageImportsResolve( * @see {@linkcode ModuleId} * @see https://github.com/nodejs/node/blob/v22.9.0/doc/api/esm.md#resolution-algorithm * + * @async + * * @param {string} specifier * The package specifier to resolve * @param {ModuleId} parent @@ -649,18 +660,18 @@ function packageImportsResolve( * List of legacy main fields * @param {FileSystem | null | undefined} [fs] * File system API - * @return {URL} + * @return {Promise} * Resolved URL * @throws {ErrInvalidModuleSpecifier} * @throws {ErrModuleNotFound} */ -function packageResolve( +async function packageResolve( specifier: string, parent: ModuleId, conditions?: Condition[] | Set | null | undefined, mainFields?: MainField[] | Set | null | undefined, fs?: FileSystem | null | undefined -): URL { +): Promise { if (isBuiltin(specifier)) { return new URL('node:' + specifier.replace(/^node:/, '')) } @@ -727,7 +738,7 @@ function packageResolve( * * @const {URL | undefined} selfUrl */ - const selfUrl: URL | undefined = packageSelfResolve( + const selfUrl: URL | undefined = await packageSelfResolve( packageName, packageSubpath, parent, @@ -756,14 +767,14 @@ function packageResolve( parentUrl = new URL(pathe.dirname(parentUrl.href) + pathe.sep) // continue if the folder at packageUrl does not exist - if (!isDirectory(packageUrl, fs)) continue + if (!(await isDirectory(packageUrl, fs))) continue /** * Package manifest. * * @const {PackageJson | null} pjson */ - const pjson: PackageJson | null = readPackageJson( + const pjson: PackageJson | null = await readPackageJson( packageUrl, null, parent, @@ -801,6 +812,8 @@ function packageResolve( * @see {@linkcode ModuleId} * @see https://github.com/nodejs/node/blob/v22.9.0/doc/api/esm.md#resolution-algorithm * + * @async + * * @param {string} name * Package name * @param {string} subpath @@ -811,22 +824,22 @@ function packageResolve( * List of export conditions * @param {FileSystem | null | undefined} [fs] * File system API - * @return {URL | undefined} + * @return {Promise} * Resolved URL */ -function packageSelfResolve( +async function packageSelfResolve( name: string, subpath: string, parent: ModuleId, conditions?: Condition[] | Set | null | undefined, fs?: FileSystem | null | undefined -): URL | undefined { +): Promise { /** * URL of package directory. * * @const {URL | null} packageUrl */ - const packageUrl: URL | null = lookupPackageScope(parent, null, fs) + const packageUrl: URL | null = await lookupPackageScope(parent, null, fs) if (packageUrl) { /** @@ -834,7 +847,7 @@ function packageSelfResolve( * * @const {PackageJson | null} pjson */ - const pjson: PackageJson | null = readPackageJson( + const pjson: PackageJson | null = await readPackageJson( packageUrl, null, parent, @@ -869,6 +882,8 @@ function packageSelfResolve( * @see {@linkcode Target} * @see https://github.com/nodejs/node/blob/v22.9.0/doc/api/esm.md#resolution-algorithm * + * @async + * * @param {ModuleId} packageUrl * URL of directory containing `package.json` file * @param {unknown} target @@ -887,12 +902,12 @@ function packageSelfResolve( * URL of parent module * @param {FileSystem | null | undefined} [fs] * File system API - * @return {URL | null | undefined} + * @return {Promise} * Resolved URL * @throws {ErrInvalidPackageConfig} * @throws {ErrInvalidPackageTarget} */ -function packageTargetResolve( +async function packageTargetResolve( packageUrl: ModuleId, target: unknown, subpath: string, @@ -902,7 +917,7 @@ function packageTargetResolve( mainFields?: MainField[] | Set | null | undefined, parent?: ModuleId | null | undefined, fs?: FileSystem | null | undefined -): URL | null | undefined { +): Promise { if (typeof target === 'string') { if (!target.startsWith(pathe.dot + pathe.sep)) { if ( @@ -997,7 +1012,7 @@ function packageTargetResolve( let resolved: URL | null | undefined try { - resolved = packageTargetResolve( + resolved = await packageTargetResolve( packageUrl, targetValue, subpath, @@ -1045,7 +1060,7 @@ function packageTargetResolve( * * @const {URL | null | undefined} resolved */ - const resolved: URL | null | undefined = packageTargetResolve( + const resolved: URL | null | undefined = await packageTargetResolve( packageUrl, (target as ConditionalTargets)[key], subpath,