pax_global_header00006660000000000000000000000064147233603230014515gustar00rootroot0000000000000052 comment=6613167c0b4fc1511fc52171f4f52cb030c928c3 lukeed-resolve.exports-75bac9f/000077500000000000000000000000001472336032300166505ustar00rootroot00000000000000lukeed-resolve.exports-75bac9f/.editorconfig000066400000000000000000000003231472336032300213230ustar00rootroot00000000000000# http://editorconfig.org root = true [*] indent_size = 2 indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.{json,yml,md}] indent_style = space lukeed-resolve.exports-75bac9f/.github/000077500000000000000000000000001472336032300202105ustar00rootroot00000000000000lukeed-resolve.exports-75bac9f/.github/FUNDING.yml000066400000000000000000000000171472336032300220230ustar00rootroot00000000000000github: lukeed lukeed-resolve.exports-75bac9f/.github/workflows/000077500000000000000000000000001472336032300222455ustar00rootroot00000000000000lukeed-resolve.exports-75bac9f/.github/workflows/ci.yml000066400000000000000000000022071472336032300233640ustar00rootroot00000000000000name: CI on: push: paths-ignore: - 'bench/**' - '*.md' branches: - '**' tags-ignore: - '**' pull_request: paths-ignore: - 'bench/**' - '*.md' branches: - master jobs: test: name: Node.js v${{ matrix.nodejs }} runs-on: ubuntu-latest strategy: matrix: # Node 10.x not supported by tsm & bundt nodejs: [12, 14, 16, 18] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: ${{ matrix.nodejs }} - name: Install run: npm install - name: Type Check run: npm run types - name: Test if: matrix.nodejs < 18 run: npm test - name: Test w/ Coverage if: matrix.nodejs >= 18 run: | npm install -g nyc nyc --include=src npm test - name: Compile if: matrix.nodejs >= 18 run: npm run build - name: Report if: matrix.nodejs >= 18 run: | nyc report --reporter=text-lcov > coverage.lcov bash <(curl -s https://codecov.io/bash) env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} lukeed-resolve.exports-75bac9f/.gitignore000066400000000000000000000000721472336032300206370ustar00rootroot00000000000000node_modules .DS_Store *-lock.* *.lock *.log /dist /lite lukeed-resolve.exports-75bac9f/index.d.ts000066400000000000000000000054001472336032300205500ustar00rootroot00000000000000export type Options = { /** * When true, adds the "browser" conditions. * Otherwise the "node" condition is enabled. * @default false */ browser?: boolean; /** * Any custom conditions to match. * @note Array order does not matter. Priority is determined by the key-order of conditions defined within a package's imports/exports mapping. * @default [] */ conditions?: readonly string[]; /** * When true, adds the "require" condition. * Otherwise the "import" condition is enabled. * @default false */ require?: boolean; /** * Prevents "require", "import", "browser", and/or "node" conditions from being added automatically. * When enabled, only `options.conditions` are added alongside the "default" condition. * @important Enabling this deviates from Node.js default behavior. * @default false */ unsafe?: boolean; } export function resolve(pkg: T, entry?: string, options?: Options): Imports.Output | Exports.Output | void; export function imports(pkg: T, entry?: string, options?: Options): Imports.Output | void; export function exports(pkg: T, target: string, options?: Options): Exports.Output | void; export function legacy(pkg: T, options: { browser: true, fields?: readonly string[] }): Browser | void; export function legacy(pkg: T, options: { browser: string, fields?: readonly string[] }): string | false | void; export function legacy(pkg: T, options: { browser: false, fields?: readonly string[] }): string | void; export function legacy(pkg: T, options?: { browser?: boolean | string; fields?: readonly string[]; }): Browser | string; // --- /** * A resolve condition * @example "node", "default", "production" */ export type Condition = string; /** An internal file path */ export type Path = `./${string}`; export type Imports = { [entry: Imports.Entry]: Imports.Value; } export namespace Imports { export type Entry = `#${string}`; type External = string; /** strings are dependency names OR internal paths */ export type Value = External | Path | null | { [c: Condition]: Value; } | Value[]; export type Output = Array; } export type Exports = Path | { [path: Exports.Entry]: Exports.Value; [cond: Condition]: Exports.Value; } export namespace Exports { /** Allows "." and "./{name}" */ export type Entry = `.${string}`; /** strings must be internal paths */ export type Value = Path | null | { [c: Condition]: Value; } | Value[]; export type Output = Path[]; } export type Package = { name: string; version?: string; module?: string; main?: string; imports?: Imports; exports?: Exports; browser?: Browser; [key: string]: any; } export type Browser = string[] | string | { [file: Path | string]: string | false; } lukeed-resolve.exports-75bac9f/license000066400000000000000000000021321472336032300202130ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) Luke Edwards (lukeed.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lukeed-resolve.exports-75bac9f/package.json000066400000000000000000000020531472336032300211360ustar00rootroot00000000000000{ "version": "2.0.3", "name": "resolve.exports", "repository": "lukeed/resolve.exports", "description": "A tiny (952b), correct, general-purpose, and configurable \"exports\" and \"imports\" resolver without file-system reliance", "module": "dist/index.mjs", "main": "dist/index.js", "types": "index.d.ts", "license": "MIT", "author": { "name": "Luke Edwards", "email": "luke.edwards05@gmail.com", "url": "https://lukeed.com" }, "engines": { "node": ">=10" }, "scripts": { "build": "bundt -m", "types": "tsc --noEmit", "test": "uvu -r tsm test" }, "files": [ "*.d.ts", "dist" ], "exports": { ".": { "types": "./index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.js" }, "./package.json": "./package.json" }, "keywords": [ "esm", "exports", "esmodules", "fields", "modules", "resolution", "resolve" ], "devDependencies": { "bundt": "next", "tsm": "2.3.0", "typescript": "4.9.4", "uvu": "0.5.4" } } lukeed-resolve.exports-75bac9f/readme.md000066400000000000000000000357031472336032300204370ustar00rootroot00000000000000# resolve.exports [![CI](https://github.com/lukeed/resolve.exports/workflows/CI/badge.svg)](https://github.com/lukeed/resolve.exports/actions) [![licenses](https://licenses.dev/b/npm/resolve.exports)](https://licenses.dev/npm/resolve.exports) [![codecov](https://codecov.io/gh/lukeed/resolve.exports/branch/master/graph/badge.svg?token=4P7d4Omw2h)](https://codecov.io/gh/lukeed/resolve.exports) > A tiny (952b), correct, general-purpose, and configurable `"exports"` and `"imports"` resolver without file-system reliance ***Why?*** Hopefully, this module may serve as a reference point (and/or be used directly) so that the varying tools and bundlers within the ecosystem can share a common approach with one another **as well as** with the native Node.js implementation. With the push for ESM, we must be _very_ careful and avoid fragmentation. If we, as a community, begin propagating different _dialects_ of the resolution algorithm, then we're headed for deep trouble. It will make supporting (and using) `"exports"` nearly impossible, which may force its abandonment and along with it, its benefits. Let's have nice things. ## Install ```sh $ npm install resolve.exports ``` ## Usage > Please see [`/test/`](/test) for examples. ```js import * as resolve from 'resolve.exports'; // package.json contents const pkg = { "name": "foobar", "module": "dist/module.mjs", "main": "dist/require.js", "imports": { "#hash": { "import": { "browser": "./hash/web.mjs", "node": "./hash/node.mjs", }, "default": "./hash/detect.js" } }, "exports": { ".": { "import": "./dist/module.mjs", "require": "./dist/require.js" }, "./lite": { "worker": { "browser": "./lite/worker.browser.js", "node": "./lite/worker.node.js" }, "import": "./lite/module.mjs", "require": "./lite/require.js" } } }; // --- // Exports // --- // entry: "foobar" === "." === default // conditions: ["default", "import", "node"] resolve.exports(pkg); resolve.exports(pkg, '.'); resolve.exports(pkg, 'foobar'); //=> ["./dist/module.mjs"] // entry: "foobar/lite" === "./lite" // conditions: ["default", "import", "node"] resolve.exports(pkg, 'foobar/lite'); resolve.exports(pkg, './lite'); //=> ["./lite/module.mjs"] // Enable `require` condition // conditions: ["default", "require", "node"] resolve.exports(pkg, 'foobar', { require: true }); //=> ["./dist/require.js"] resolve.exports(pkg, './lite', { require: true }); //=> ["./lite/require.js"] // Throws "Missing specifier in package" Error resolve.exports(pkg, 'foobar/hello'); resolve.exports(pkg, './hello/world'); // Add custom condition(s) // conditions: ["default", "worker", "import", "node"] resolve.exports(pkg, 'foobar/lite', { conditions: ['worker'] }); //=> ["./lite/worker.node.js"] // Toggle "browser" condition // conditions: ["default", "worker", "import", "browser"] resolve.exports(pkg, 'foobar/lite', { conditions: ['worker'], browser: true }); //=> ["./lite/worker.browser.js"] // Disable non-"default" condition activate // NOTE: breaks from Node.js default behavior // conditions: ["default", "custom"] resolve.exports(pkg, 'foobar/lite', { conditions: ['custom'], unsafe: true, }); //=> Error: No known conditions for "./lite" specifier in "foobar" package // --- // Imports // --- // conditions: ["default", "import", "node"] resolve.imports(pkg, '#hash'); resolve.imports(pkg, 'foobar/#hash'); //=> ["./hash/node.mjs"] // conditions: ["default", "import", "browser"] resolve.imports(pkg, '#hash', { browser: true }); resolve.imports(pkg, 'foobar/#hash'); //=> ["./hash/web.mjs"] // conditions: ["default"] resolve.imports(pkg, '#hash', { unsafe: true }); resolve.imports(pkg, 'foobar/#hash'); //=> ["./hash/detect.mjs"] resolve.imports(pkg, '#hello/world'); resolve.imports(pkg, 'foobar/#hello/world'); //=> Error: Missing "#hello/world" specifier in "foobar" package // --- // Legacy // --- // prefer "module" > "main" (default) resolve.legacy(pkg); //=> "dist/module.mjs" // customize fields order resolve.legacy(pkg, { fields: ['main', 'module'] }); //=> "dist/require.js" ``` ## API The [`resolve()`](#resolvepkg-entry-options), [`exports()`](#exportspkg-entry-options), and [`imports()`](#importspkg-target-options) functions share similar API signatures: ```ts export function resolve(pkg: Package, entry?: string, options?: Options): string[] | undefined; export function exports(pkg: Package, entry?: string, options?: Options): string[] | undefined; export function imports(pkg: Package, target: string, options?: Options): string[] | undefined; // ^ not optional! ``` All three: * accept a `package.json` file's contents as a JSON object * accept a target/entry identifier * may accept an [Options](#options) object * return `string[]`, `string`, or `undefined` The only difference is that `imports()` must accept a target identifier as there can be no inferred default. See below for further API descriptions. > **Note:** There is also a [Legacy Resolver API](#legacy-resolver) --- ### resolve(pkg, entry?, options?) Returns: `string[]` or `undefined` A convenience helper which automatically reroutes to [`exports()`](#exportspkg-entry-options) or [`imports()`](#importspkg-target-options) depending on the `entry` value. When unspecified, `entry` defaults to the `"."` identifier, which means that `exports()` will be invoked. ```js import * as r from 'resolve.exports'; let pkg = { name: 'foobar', // ... }; r.resolve(pkg); //~> r.exports(pkg, '.'); r.resolve(pkg, 'foobar'); //~> r.exports(pkg, '.'); r.resolve(pkg, 'foobar/subpath'); //~> r.exports(pkg, './subpath'); r.resolve(pkg, '#hash/md5'); //~> r.imports(pkg, '#hash/md5'); r.resolve(pkg, 'foobar/#hash/md5'); //~> r.imports(pkg, '#hash/md5'); ``` ### exports(pkg, entry?, options?) Returns: `string[]` or `undefined` Traverse the `"exports"` within the contents of a `package.json` file.
If the contents _does not_ contain an `"exports"` map, then `undefined` will be returned. Successful resolutions will always result in a `string` or `string[]` value. This will be the value of the resolved mapping itself – which means that the output is a relative file path. This function may throw an Error if: * the requested `entry` cannot be resolved (aka, not defined in the `"exports"` map) * an `entry` _is_ defined but no known conditions were matched (see [`options.conditions`](#optionsconditions)) #### pkg Type: `object`
Required: `true` The `package.json` contents. #### entry Type: `string`
Required: `false`
Default: `.` (aka, root) The desired target entry, or the original `import` path. When `entry` _is not_ a relative path (aka, does not start with `'.'`), then `entry` is given the `'./'` prefix. When `entry` begins with the package name (determined via the `pkg.name` value), then `entry` is truncated and made relative. When `entry` is already relative, it is accepted as is. ***Examples*** Assume we have a module named "foobar" and whose `pkg` contains `"name": "foobar"`. | `entry` value | treated as | reason | |-|-|-| | `null` / `undefined` | `'.'` | default | | `'.'` | `'.'` | value was relative | | `'foobar'` | `'.'` | value was `pkg.name` | | `'foobar/lite'` | `'./lite'` | value had `pkg.name` prefix | | `'./lite'` | `'./lite'` | value was relative | | `'lite'` | `'./lite'` | value was not relative & did not have `pkg.name` prefix | ### imports(pkg, target, options?) Returns: `string[]` or `undefined` Traverse the `"imports"` within the contents of a `package.json` file.
If the contents _does not_ contain an `"imports"` map, then `undefined` will be returned. Successful resolutions will always result in a `string` or `string[]` value. This will be the value of the resolved mapping itself – which means that the output is a relative file path. This function may throw an Error if: * the requested `target` cannot be resolved (aka, not defined in the `"imports"` map) * an `target` _is_ defined but no known conditions were matched (see [`options.conditions`](#optionsconditions)) #### pkg Type: `object`
Required: `true` The `package.json` contents. #### target Type: `string`
Required: `true` The target import identifier; for example, `#hash` or `#hash/md5`. Import specifiers _must_ begin with the `#` character, as required by the resolution specification. However, if `target` begins with the package name (determined by the `pkg.name` value), then `resolve.exports` will trim it from the `target` identifier. For example, `"foobar/#hash/md5"` will be treated as `"#hash/md5"` for the `"foobar"` package. ## Options The [`resolve()`](#resolvepkg-entry-options), [`imports()`](#importspkg-target-options), and [`exports()`](#exportspkg-entry-options) functions share these options. All properties are optional and you are not required to pass an `options` argument. Collectively, the `options` are used to assemble a list of [conditions](https://nodejs.org/docs/latest-v18.x/api/packages.html#conditional-exports) that should be activated while resolving your target(s). > **Note:** Although the Node.js documentation primarily showcases conditions alongside `"exports"` usage, they also apply to `"imports"` maps too. _([example](https://nodejs.org/docs/latest-v18.x/api/packages.html#subpath-imports))_ #### options.require Type: `boolean`
Default: `false` When truthy, the `"require"` field is added to the list of allowed/known conditions.
Otherwise the `"import"` field is added instead. #### options.browser Type: `boolean`
Default: `false` When truthy, the `"browser"` field is added to the list of allowed/known conditions.
Otherwise the `"node"` field is added instead. #### options.conditions Type: `string[]`
Default: `[]` A list of additional/custom conditions that should be accepted when seen. > **Important:** The order specified within `options.conditions` does not matter.
The matching order/priority is **always** determined by the `"exports"` map's key order. For example, you may choose to accept a `"production"` condition in certain environments. Given the following `pkg` content: ```js const pkg = { // package.json ... "exports": { "worker": "./$worker.js", "require": "./$require.js", "production": "./$production.js", "import": "./$import.mjs", } }; resolve.exports(pkg, '.'); // Conditions: ["default", "import", "node"] //=> ["./$import.mjs"] resolve.exports(pkg, '.', { conditions: ['production'] }); // Conditions: ["default", "production", "import", "node"] //=> ["./$production.js"] resolve.exports(pkg, '.', { conditions: ['production'], require: true, }); // Conditions: ["default", "production", "require", "node"] //=> ["./$require.js"] resolve.exports(pkg, '.', { conditions: ['production', 'worker'], require: true, }); // Conditions: ["default", "production", "worker", "require", "node"] //=> ["./$worker.js"] resolve.exports(pkg, '.', { conditions: ['production', 'worker'] }); // Conditions: ["default", "production", "worker", "import", "node"] //=> ["./$worker.js"] ``` #### options.unsafe Type: `boolean`
Default: `false` > **Important:** You probably do not want this option!
It will break out of Node's default resolution conditions. When enabled, this option will ignore **all other options** except [`options.conditions`](#optionsconditions). This is because, when enabled, `options.unsafe` **does not** assume or provide any default conditions except the `"default"` condition. ```js resolve.exports(pkg, '.'); //=> Conditions: ["default", "import", "node"] resolve.exports(pkg, '.', { unsafe: true }); //=> Conditions: ["default"] resolve.exports(pkg, '.', { unsafe: true, require: true, browser: true }); //=> Conditions: ["default"] ``` In other words, this means that trying to use `options.require` or `options.browser` alongside `options.unsafe` will have no effect. In order to enable these conditions, you must provide them manually into the `options.conditions` list: ```js resolve.exports(pkg, '.', { unsafe: true, conditions: ["require"] }); //=> Conditions: ["default", "require"] resolve.exports(pkg, '.', { unsafe: true, conditions: ["browser", "require", "custom123"] }); //=> Conditions: ["default", "browser", "require", "custom123"] ``` ## Legacy Resolver Also included is a "legacy" method for resolving non-`"exports"` package fields. This may be used as a fallback method when for when no `"exports"` mapping is defined. In other words, it's completely optional (and tree-shakeable). ### legacy(pkg, options?) Returns: `string` or `undefined` You may customize the field priority via [`options.fields`](#optionsfields). When a field is found, its value is returned _as written_.
When no fields were found, `undefined` is returned. If you wish to mimic Node.js behavior, you can assume this means `'index.js'` – but this module does not make that assumption for you. #### options.browser Type: `boolean` or `string`
Default: `false` When truthy, ensures that the `'browser'` field is part of the acceptable `fields` list. > **Important:** If your custom [`options.fields`](#optionsfields) value includes `'browser'`, then _your_ order is respected.
Otherwise, when truthy, `options.browser` will move `'browser'` to the front of the list, making it the top priority. When `true` and `"browser"` is an object, then `legacy()` will return the the entire `"browser"` object. You may also pass a string value, which will be treated as an import/file path. When this is the case and `"browser"` is an object, then `legacy()` may return: * `false` – if the package author decided a file should be ignored; or * your `options.browser` string value – but made relative, if not already > See the [`"browser" field specification](https://github.com/defunctzombie/package-browser-field-spec) for more information. #### options.fields Type: `string[]`
Default: `['module', 'main']` A list of fields to accept. The order of the array determines the priority/importance of each field, with the most important fields at the beginning of the list. By default, the `legacy()` method will accept any `"module"` and/or "main" fields if they are defined. However, if both fields are defined, then "module" will be returned. ```js import { legacy } from 'resolve.exports'; // package.json const pkg = { "name": "...", "worker": "worker.js", "module": "module.mjs", "browser": "browser.js", "main": "main.js", }; legacy(pkg); // fields = [module, main] //=> "module.mjs" legacy(pkg, { browser: true }); // fields = [browser, module, main] //=> "browser.mjs" legacy(pkg, { fields: ['missing', 'worker', 'module', 'main'] }); // fields = [missing, worker, module, main] //=> "worker.js" legacy(pkg, { fields: ['missing', 'worker', 'module', 'main'], browser: true, }); // fields = [browser, missing, worker, module, main] //=> "browser.js" legacy(pkg, { fields: ['module', 'browser', 'main'], browser: true, }); // fields = [module, browser, main] //=> "module.mjs" ``` ## License MIT © [Luke Edwards](https://lukeed.com) lukeed-resolve.exports-75bac9f/src/000077500000000000000000000000001472336032300174375ustar00rootroot00000000000000lukeed-resolve.exports-75bac9f/src/index.ts000066400000000000000000000017371472336032300211260ustar00rootroot00000000000000import { toEntry, walk } from './utils'; import type * as t from 'resolve.exports'; export { legacy } from './legacy'; export function exports(pkg: t.Package, input?: string, options?: t.Options): string[] | void { let map = pkg.exports, k: string; if (map) { if (typeof map === 'string') { map = { '.': map }; } else for (k in map) { // convert {conditions} to "."={condtions} if (k[0] !== '.') map = { '.': map }; break; } return walk(pkg.name, map, input||'.', options); } } export function imports(pkg: t.Package, input: string, options?: t.Options): string[] | void { if (pkg.imports) return walk(pkg.name, pkg.imports, input, options); } export function resolve(pkg: t.Package, input?: string, options?: t.Options): string[] | void { // let entry = input && input !== '.' // ? toEntry(pkg.name, input) // : '.'; input = toEntry(pkg.name, input || '.'); return input[0] === '#' ? imports(pkg, input, options) : exports(pkg, input, options); } lukeed-resolve.exports-75bac9f/src/legacy.ts000066400000000000000000000022631472336032300212560ustar00rootroot00000000000000import * as $ from './utils'; import type * as t from 'resolve.exports'; type LegacyOptions = { fields?: string[]; browser?: string | boolean; } type BrowserObject = { [file: string]: string | undefined; } export function legacy(pkg: t.Package, options: LegacyOptions = {}): t.Path | t.Browser | void { let i=0, value: string | t.Browser | undefined, browser = options.browser, fields = options.fields || ['module', 'main'], isSTRING = typeof browser == 'string'; if (browser && !fields.includes('browser')) { fields.unshift('browser'); // "module-a" -> "module-a" // "./path/file.js" -> "./path/file.js" // "foobar/path/file.js" -> "./path/file.js" if (isSTRING) browser = $.toEntry(pkg.name, browser as string, true); } for (; i < fields.length; i++) { if (value = pkg[fields[i]]) { if (typeof value == 'string') { // } else if (typeof value == 'object' && fields[i] == 'browser') { if (isSTRING) { value = (value as BrowserObject)[browser as string]; if (value == null) return browser as string; } } else { continue; } return typeof value == 'string' ? ('./' + value.replace(/^\.?\//, '')) as t.Path : value; } } } lukeed-resolve.exports-75bac9f/src/utils.ts000066400000000000000000000070431472336032300211530ustar00rootroot00000000000000import type * as t from 'resolve.exports'; export type Entry = t.Exports.Entry | t.Imports.Entry; export type Value = t.Exports.Value | t.Imports.Value; export type Mapping = Record; export function throws(name: string, entry: Entry, condition?: number): never { throw new Error( condition ? `No known conditions for "${entry}" specifier in "${name}" package` : `Missing "${entry}" specifier in "${name}" package` ); } export function conditions(options: t.Options): Set { let out = new Set([ 'default', ...options.conditions || [] ]); options.unsafe || out.add(options.require ? 'require' : 'import'); options.unsafe || out.add(options.browser ? 'browser' : 'node'); return out; } export function walk(name: string, mapping: Mapping, input: string, options?: t.Options): string[] { let entry = toEntry(name, input); let c = conditions(options || {}); let m: Value|void = mapping[entry]; let v: string[]|void, replace: string|void; if (m === void 0) { // loop for longest key match let match: RegExpExecArray|null; let longest: Entry|undefined; let tmp: string|number; let key: Entry; for (key in mapping) { if (longest && key.length < longest.length) { // do not allow "./" to match if already matched "./foo*" key } else if (key[key.length - 1] === '/' && entry.startsWith(key)) { replace = entry.substring(key.length); longest = key; } else if (key.length > 1) { tmp = key.indexOf('*', 1); if (!!~tmp) { match = RegExp( '^' + key.substring(0, tmp) + '(.*)' + key.substring(1+tmp) + '$' ).exec(entry); if (match && match[1]) { replace = match[1]; longest = key; } } } } m = mapping[longest!]; } if (!m) { // missing export throws(name, entry); } v = loop(m, c); // unknown condition(s) if (!v) throws(name, entry, 1); if (replace) injects(v, replace); return v; } /** @note: mutates! */ export function injects(items: string[], value: string): void { let i=0, len=items.length, tmp: string; let rgx1=/[*]/g, rgx2 = /[/]$/; for (; i < len; i++) { items[i] = rgx1.test(tmp = items[i]) ? tmp.replace(rgx1, value) : rgx2.test(tmp) ? (tmp+value) : tmp; } } /** * @param name package name * @param ident entry identifier * @param externals allow non-path (external) result * @see https://esbench.com/bench/59fa3e6799634800a0349382 */ export function toEntry(name: string, ident: string, externals?: false): Entry; export function toEntry(name: string, ident: string, externals: true): Entry | string; export function toEntry(name: string, ident: string, externals?: boolean): Entry | string { if (name === ident || ident === '.') return '.'; let root = name+'/', len = root.length; let bool = ident.slice(0, len) === root; let output = bool ? ident.slice(len) : ident; if (output[0] === '#') return output as t.Imports.Entry; return (bool || !externals) ? (output.slice(0,2) === './' ? output : './' + output) as t.Path : output as string | t.Exports.Entry; } export function loop(m: Value, keys: Set, result?: Set): string[] | void { if (m) { if (typeof m === 'string') { if (result) result.add(m); return [m]; } let idx: number | string, arr: Set; if (Array.isArray(m)) { arr = result || new Set; for (idx=0; idx < m.length; idx++) { loop(m[idx], keys, arr); } // return if initialized set if (!result && arr.size) { return [...arr]; } } else for (idx in m) { if (keys.has(idx)) { return loop(m[idx], keys, result); } } } } lukeed-resolve.exports-75bac9f/test/000077500000000000000000000000001472336032300176275ustar00rootroot00000000000000lukeed-resolve.exports-75bac9f/test/index.ts000066400000000000000000001226541472336032300213200ustar00rootroot00000000000000import * as uvu from 'uvu'; import * as assert from 'uvu/assert'; import * as lib from '../src/index'; import type * as t from 'resolve.exports'; type Package = t.Package; type Entry = t.Exports.Entry | t.Imports.Entry; type Options = t.Options; function pass(pkg: Package, expects: string|string[], entry?: string, options?: Options) { let out = lib.resolve(pkg, entry, options); if (typeof expects === 'string') { assert.ok(Array.isArray(out)); assert.is(out[0], expects); assert.is(out.length, 1); } else { // Array | null | undefined assert.equal(out, expects); } } function fail(pkg: Package, target: Entry, entry?: string, options?: Options) { try { lib.resolve(pkg, entry, options); assert.unreachable(); } catch (err) { assert.instance(err, Error); assert.is((err as Error).message, `Missing "${target}" specifier in "${pkg.name}" package`); } } function describe( name: string, cb: (it: uvu.Test) => void ) { let t = uvu.suite(name); cb(t); t.run(); } // --- describe('$.resolve', it => { it('should be a function', () => { assert.type(lib.resolve, 'function'); }); it('should return nothing if no maps', () => { let output = lib.resolve({ "name": "foobar" }); assert.is(output, undefined); }); it('should default to `$.exports` handler', () => { let pkg: Package = { "name": "foobar", "exports": "./hello.mjs" }; let output = lib.resolve(pkg); assert.equal(output, ['./hello.mjs']); output = lib.resolve(pkg, '.'); assert.equal(output, ['./hello.mjs']); try { lib.resolve(pkg, './other'); assert.unreachable(); } catch (err) { assert.instance(err, Error); assert.is((err as Error).message, `Missing "./other" specifier in "foobar" package`); } }); it('should run `$.imports` if given #ident', () => { let pkg: Package = { "name": "foobar", "imports": { "#foo": "./foo.mjs" } }; let output = lib.resolve(pkg, '#foo'); assert.equal(output, ['./foo.mjs']); output = lib.resolve(pkg, 'foobar/#foo'); assert.equal(output, ['./foo.mjs']); try { lib.resolve(pkg, '#bar'); assert.unreachable(); } catch (err) { assert.instance(err, Error); assert.is((err as Error).message, `Missing "#bar" specifier in "foobar" package`); } }); it('should run `$.export` if given "external" identifier', () => { let pkg: Package = { "name": "foobar", "exports": { ".": "./foo.mjs" } }; try { lib.resolve(pkg, 'external'); assert.unreachable(); } catch (err) { assert.instance(err, Error); // IMPORTANT: treats "external" as "./external" assert.is((err as Error).message, `Missing "./external" specifier in "foobar" package`); } }); it('should run `$.export` if given "external/subpath" identifier', () => { let pkg: Package = { "name": "foobar", "exports": { ".": "./foo.mjs" } }; try { lib.resolve(pkg, 'external/subpath'); assert.unreachable(); } catch (err) { assert.instance(err, Error); // IMPORTANT: treats "external/subpath" as "./external/subpath" assert.is((err as Error).message, `Missing "./external/subpath" specifier in "foobar" package`); } }); }); describe('$.imports', it => { it('should be a function', () => { assert.type(lib.imports, 'function'); }); it('should return nothing if no "imports" map', () => { let pkg: Package = { "name": "foobar" }; let output = lib.imports(pkg, '#any'); assert.is(output, undefined); }); it('imports["#foo"] = string', () => { let pkg: Package = { "name": "foobar", "imports": { "#foo": "./$import", "#bar": "module-a", } }; pass(pkg, './$import', '#foo'); pass(pkg, './$import', 'foobar/#foo'); pass(pkg, 'module-a', '#bar'); pass(pkg, 'module-a', 'foobar/#bar'); fail(pkg, '#other', 'foobar/#other'); }); it('imports["#foo"] = object', () => { let pkg: Package = { "name": "foobar", "imports": { "#foo": { "import": "./$import", "require": "./$require", } } }; pass(pkg, './$import', '#foo'); pass(pkg, './$import', 'foobar/#foo'); fail(pkg, '#other', 'foobar/#other'); }); it('nested conditions :: subpath', () => { let pkg: Package = { "name": "foobar", "imports": { "#lite": { "node": { "import": "./$node.import", "require": "./$node.require" }, "browser": { "import": "./$browser.import", "require": "./$browser.require" }, } } }; pass(pkg, './$node.import', 'foobar/#lite'); pass(pkg, './$node.require', 'foobar/#lite', { require: true }); pass(pkg, './$browser.import', 'foobar/#lite', { browser: true }); pass(pkg, './$browser.require', 'foobar/#lite', { browser: true, require: true }); }); it('nested conditions :: subpath :: inverse', () => { let pkg: Package = { "name": "foobar", "imports": { "#lite": { "import": { "browser": "./$browser.import", "node": "./$node.import", }, "require": { "browser": "./$browser.require", "node": "./$node.require", } } } }; pass(pkg, './$node.import', 'foobar/#lite'); pass(pkg, './$node.require', 'foobar/#lite', { require: true }); pass(pkg, './$browser.import', 'foobar/#lite', { browser: true }); pass(pkg, './$browser.require', 'foobar/#lite', { browser: true, require: true }); }); // https://nodejs.org/api/packages.html#packages_subpath_folder_mappings it('imports["#key/*"]', () => { let pkg: Package = { "name": "foobar", "imports": { "#key/*": "./cheese/*.mjs" } }; pass(pkg, './cheese/hello.mjs', 'foobar/#key/hello'); pass(pkg, './cheese/hello/world.mjs', '#key/hello/world'); // evaluate as defined, not wrong pass(pkg, './cheese/hello.js.mjs', '#key/hello.js'); pass(pkg, './cheese/hello.js.mjs', 'foobar/#key/hello.js'); pass(pkg, './cheese/hello/world.js.mjs', '#key/hello/world.js'); }); it('imports["#key/dir*"]', () => { let pkg: Package = { "name": "foobar", "imports": { "#key/dir*": "./cheese/*.mjs" } }; pass(pkg, './cheese/test.mjs', '#key/dirtest'); pass(pkg, './cheese/test.mjs', 'foobar/#key/dirtest'); pass(pkg, './cheese/test/wheel.mjs', '#key/dirtest/wheel'); pass(pkg, './cheese/test/wheel.mjs', 'foobar/#key/dirtest/wheel'); }); // https://github.com/lukeed/resolve.exports/issues/9 it('imports["#key/dir*"] :: repeat "*" value', () => { let pkg: Package = { "name": "foobar", "imports": { "#key/dir*": "./*sub/dir*/file.js" } }; pass(pkg, './testsub/dirtest/file.js', '#key/dirtest'); pass(pkg, './testsub/dirtest/file.js', 'foobar/#key/dirtest'); pass(pkg, './test/innersub/dirtest/inner/file.js', '#key/dirtest/inner'); pass(pkg, './test/innersub/dirtest/inner/file.js', 'foobar/#key/dirtest/inner'); }); /** * @deprecated Documentation-only deprecation in Node 14.13 * @deprecated Runtime deprecation in Node 16.0 * @removed Removed in Node 18.0 * @see https://nodejs.org/docs/latest-v16.x/api/packages.html#subpath-folder-mappings */ it('imports["#features/"]', () => { let pkg: Package = { "name": "foobar", "imports": { "#features/": "./features/" } }; pass(pkg, './features/', '#features/'); pass(pkg, './features/', 'foobar/#features/'); pass(pkg, './features/hello.js', 'foobar/#features/hello.js'); fail(pkg, '#features', '#features'); fail(pkg, '#features', 'foobar/#features'); }); it('imports["#features/"] :: conditions', () => { let pkg: Package = { "name": "foobar", "imports": { "#features/": { "browser": { "import": "./browser.import/", "require": "./browser.require/", }, "import": "./import/", "require": "./require/", }, } }; // import pass(pkg, './import/', '#features/'); pass(pkg, './import/', 'foobar/#features/'); pass(pkg, './import/hello.js', '#features/hello.js'); pass(pkg, './import/hello.js', 'foobar/#features/hello.js'); // require pass(pkg, './require/', '#features/', { require: true }); pass(pkg, './require/', 'foobar/#features/', { require: true }); pass(pkg, './require/hello.js', '#features/hello.js', { require: true }); pass(pkg, './require/hello.js', 'foobar/#features/hello.js', { require: true }); // require + browser pass(pkg, './browser.require/', '#features/', { browser: true, require: true }); pass(pkg, './browser.require/', 'foobar/#features/', { browser: true, require: true }); pass(pkg, './browser.require/hello.js', '#features/hello.js', { browser: true, require: true }); pass(pkg, './browser.require/hello.js', 'foobar/#features/hello.js', { browser: true, require: true }); }); // https://nodejs.org/api/packages.html#packages_subpath_folder_mappings it('imports["#features/*"]', () => { let pkg: Package = { "name": "foobar", "imports": { "#features/*": "./features/*.js", } }; fail(pkg, '#features', '#features'); fail(pkg, '#features', 'foobar/#features'); fail(pkg, '#features/', '#features/'); fail(pkg, '#features/', 'foobar/#features/'); pass(pkg, './features/a.js', 'foobar/#features/a'); pass(pkg, './features/ab.js', 'foobar/#features/ab'); pass(pkg, './features/abc.js', 'foobar/#features/abc'); pass(pkg, './features/hello.js', 'foobar/#features/hello'); pass(pkg, './features/foo/bar.js', 'foobar/#features/foo/bar'); // Valid: Pattern trailers allow any exact substrings to be matched pass(pkg, './features/hello.js.js', 'foobar/#features/hello.js'); pass(pkg, './features/foo/bar.js.js', 'foobar/#features/foo/bar.js'); }); // https://nodejs.org/api/packages.html#packages_subpath_folder_mappings it('imports["#fooba*"] :: with "#foo*" key', () => { let pkg: Package = { "name": "foobar", "imports": { "#fooba*": "./features/*.js", "#foo*": "./" } }; pass(pkg, './features/r.js', '#foobar'); pass(pkg, './features/r.js', 'foobar/#foobar'); pass(pkg, './features/r/hello.js', 'foobar/#foobar/hello'); pass(pkg, './features/r/foo/bar.js', 'foobar/#foobar/foo/bar'); // Valid: Pattern trailers allow any exact substrings to be matched pass(pkg, './features/r/hello.js.js', 'foobar/#foobar/hello.js'); pass(pkg, './features/r/foo/bar.js.js', 'foobar/#foobar/foo/bar.js'); }); // https://github.com/lukeed/resolve.exports/issues/7 it('imports["#fooba*"] :: with "#foo*" key first', () => { let pkg: Package = { "name": "foobar", "imports": { "#foo*": "./", "#fooba*": "./features/*.js" } }; pass(pkg, './features/r.js', '#foobar'); pass(pkg, './features/r.js', 'foobar/#foobar'); pass(pkg, './features/r/hello.js', 'foobar/#foobar/hello'); pass(pkg, './features/r/foo/bar.js', 'foobar/#foobar/foo/bar'); // Valid: Pattern trailers allow any exact substrings to be matched pass(pkg, './features/r/hello.js.js', 'foobar/#foobar/hello.js'); pass(pkg, './features/r/foo/bar.js.js', 'foobar/#foobar/foo/bar.js'); }); // https://github.com/lukeed/resolve.exports/issues/27 it('imports["#*"] :: with "#foo*" key', () => { let pkg: Package = { "name": "foobar", "imports": { "#*": "./root/*.js", "#foo*": "./foo/*.js" } }; // "#foo*" pass(pkg, './foo/bar.js', '#foobar'); pass(pkg, './foo/bar.js', 'foobar/#foobar'); pass(pkg, './foo/bar/hello.js', 'foobar/#foobar/hello'); pass(pkg, './foo/bar/foo/bar.js', 'foobar/#foobar/foo/bar'); // Valid: Pattern trailers allow any exact substrings to be matched pass(pkg, './foo/bar/hello.js.js', 'foobar/#foobar/hello.js'); pass(pkg, './foo/bar/foo/bar.js.js', 'foobar/#foobar/foo/bar.js'); // "#*" pass(pkg, './root/other.js', '#other'); pass(pkg, './root/other.js', 'foobar/#other'); pass(pkg, './root/other/hello.js', 'foobar/#other/hello'); pass(pkg, './root/other/foo/bar.js', 'foobar/#other/foo/bar'); // Valid: Pattern trailers allow any exact substrings to be matched pass(pkg, './root/other/hello.js.js', 'foobar/#other/hello.js'); pass(pkg, './root/other/foo/bar.js.js', 'foobar/#other/foo/bar.js'); }); // https://github.com/lukeed/resolve.exports/issues/27 it('imports["#*"] :: with "#foo*" key first', () => { let pkg: Package = { "name": "foobar", "imports": { "#foo*": "./foo/*.js", "#*": "./root/*.js" } }; // "#foo*" pass(pkg, './foo/bar.js', '#foobar'); pass(pkg, './foo/bar.js', 'foobar/#foobar'); pass(pkg, './foo/bar/hello.js', 'foobar/#foobar/hello'); pass(pkg, './foo/bar/foo/bar.js', 'foobar/#foobar/foo/bar'); // Valid: Pattern trailers allow any exact substrings to be matched pass(pkg, './foo/bar/hello.js.js', 'foobar/#foobar/hello.js'); pass(pkg, './foo/bar/foo/bar.js.js', 'foobar/#foobar/foo/bar.js'); // "#*" pass(pkg, './root/other.js', '#other'); pass(pkg, './root/other.js', 'foobar/#other'); pass(pkg, './root/other/hello.js', 'foobar/#other/hello'); pass(pkg, './root/other/foo/bar.js', 'foobar/#other/foo/bar'); // Valid: Pattern trailers allow any exact substrings to be matched pass(pkg, './root/other/hello.js.js', 'foobar/#other/hello.js'); pass(pkg, './root/other/foo/bar.js.js', 'foobar/#other/foo/bar.js'); }); it('imports["#*"] :: with "#a" static key', () => { let pkg: Package = { "name": "foobar", "imports": { "#*": "./root/*.js", "#a": "./a.js", } }; pass(pkg, './root/other.js', '#other'); pass(pkg, './root/other.js', 'foobar/#other'); pass(pkg, './a.js', '#a'); pass(pkg, './a.js', 'foobar/#a'); }); it('imports["#*"] :: with "#a" static key first', () => { let pkg: Package = { "name": "foobar", "imports": { "#a": "./a.js", "#*": "./root/*.js", } }; pass(pkg, './root/other.js', '#other'); pass(pkg, './root/other.js', 'foobar/#other'); pass(pkg, './a.js', '#a'); pass(pkg, './a.js', 'foobar/#a'); }); // https://github.com/lukeed/resolve.exports/issues/16 it('imports["#features/*"] :: with `null` internals', () => { let pkg: Package = { "name": "foobar", "imports": { "#features/*": "./src/features/*.js", "#features/internal/*": null } }; pass(pkg, './src/features/hello.js', '#features/hello'); pass(pkg, './src/features/hello.js', 'foobar/#features/hello'); pass(pkg, './src/features/foo/bar.js', '#features/foo/bar'); pass(pkg, './src/features/foo/bar.js', 'foobar/#features/foo/bar'); // TODO? Native throws `ERR_PACKAGE_PATH_NOT_EXPORTED` // Currently throwing `Missing "%s" specifier in "$s" package` fail(pkg, '#features/internal/hello', '#features/internal/hello'); fail(pkg, '#features/internal/foo/bar', '#features/internal/foo/bar'); }); // https://github.com/lukeed/resolve.exports/issues/16 it('imports["#features/*"] :: with `null` internals first', () => { let pkg: Package = { "name": "foobar", "imports": { "#features/internal/*": null, "#features/*": "./src/features/*.js", } }; pass(pkg, './src/features/hello.js', '#features/hello'); pass(pkg, './src/features/hello.js', 'foobar/#features/hello'); pass(pkg, './src/features/foo/bar.js', '#features/foo/bar'); pass(pkg, './src/features/foo/bar.js', 'foobar/#features/foo/bar'); // TODO? Native throws `ERR_PACKAGE_PATH_NOT_EXPORTED` // Currently throwing `Missing "%s" specifier in "$s" package` fail(pkg, '#features/internal/hello', '#features/internal/hello'); fail(pkg, '#features/internal/foo/bar', '#features/internal/foo/bar'); }); // https://nodejs.org/docs/latest-v18.x/api/packages.html#package-entry-points it('imports["#features/*"] :: with "#features/*.js" key', () => { let pkg: Package = { "name": "foobar", "imports": { "#features/*": "./features/*.js", "#features/*.js": "./features/*.js", } }; fail(pkg, '#features', '#features'); fail(pkg, '#features', 'foobar/#features'); fail(pkg, '#features/', '#features/'); fail(pkg, '#features/', 'foobar/#features/'); pass(pkg, './features/a.js', 'foobar/#features/a'); pass(pkg, './features/ab.js', 'foobar/#features/ab'); pass(pkg, './features/abc.js', 'foobar/#features/abc'); pass(pkg, './features/hello.js', 'foobar/#features/hello'); pass(pkg, './features/hello.js', 'foobar/#features/hello.js'); pass(pkg, './features/foo/bar.js', 'foobar/#features/foo/bar'); pass(pkg, './features/foo/bar.js', 'foobar/#features/foo/bar.js'); }); it('imports["#features/*"] :: conditions', () => { let pkg: Package = { "name": "foobar", "imports": { "#features/*": { "browser": { "import": "./browser.import/*.mjs", "require": "./browser.require/*.js", }, "import": "./import/*.mjs", "require": "./require/*.js", }, } }; // import fail(pkg, '#features/', '#features/'); // no file fail(pkg, '#features/', 'foobar/#features/'); // no file pass(pkg, './import/hello.mjs', '#features/hello'); pass(pkg, './import/hello.mjs', 'foobar/#features/hello'); // require fail(pkg, '#features/', '#features/', { require: true }); // no file fail(pkg, '#features/', 'foobar/#features/', { require: true }); // no file pass(pkg, './require/hello.js', '#features/hello', { require: true }); pass(pkg, './require/hello.js', 'foobar/#features/hello', { require: true }); // require + browser fail(pkg, '#features/', '#features/', { browser: true, require: true }); // no file fail(pkg, '#features/', 'foobar/#features/', { browser: true, require: true }); // no file pass(pkg, './browser.require/hello.js', '#features/hello', { browser: true, require: true }); pass(pkg, './browser.require/hello.js', 'foobar/#features/hello', { browser: true, require: true }); }); it('should handle mixed path/conditions', () => { let pkg: Package = { "name": "foobar", "imports": { "#foo": [ { "require": "./$foo.require" }, "./$foo.string" ] } }; // TODO? if len==1 then single? pass(pkg, ['./$foo.string'], '#foo'); pass(pkg, ['./$foo.string'], 'foobar/#foo'); pass(pkg, ['./$foo.require', './$foo.string'], '#foo', { require: true }); pass(pkg, ['./$foo.require', './$foo.string'], 'foobar/#foo', { require: true }); }); // https://github.com/lukeed/resolve.exports/issues/34 it('imports["#features/*"] :: avoid lazy matching', () => { let pkg: Package = { name: 'test', imports: { '#features/*.css': './src/*.css', '#features/*.ts': './src/*.ts', } }; pass(pkg, './src/asdf/css.ts', '#features/asdf/css.ts'); }); }); describe('$.exports', it => { it('should be a function', () => { assert.type(lib.exports, 'function'); }); it('should return nothing if no "exports" map', () => { let pkg: Package = { "name": "foobar" }; let output = lib.exports(pkg, '#any'); assert.is(output, undefined); }); it('should default to "." target input', () => { let pkg: Package = { "name": "foobar", "exports": { ".": "./hello.mjs" } }; let output = lib.exports(pkg); assert.equal(output, ['./hello.mjs']); output = lib.exports(pkg, '.'); assert.equal(output, ['./hello.mjs']); output = lib.exports(pkg, 'foobar'); assert.equal(output, ['./hello.mjs']); }); it('exports=string', () => { let pkg: Package = { "name": "foobar", "exports": "./$string", }; pass(pkg, './$string'); pass(pkg, './$string', '.'); pass(pkg, './$string', 'foobar'); fail(pkg, './other', 'other'); fail(pkg, './other', 'foobar/other'); fail(pkg, './hello', './hello'); }); it('exports = { self }', () => { let pkg: Package = { "name": "foobar", "exports": { "import": "./$import", "require": "./$require", } }; pass(pkg, './$import'); pass(pkg, './$import', '.'); pass(pkg, './$import', 'foobar'); fail(pkg, './other', 'other'); fail(pkg, './other', 'foobar/other'); fail(pkg, './hello', './hello'); }); it('exports["."] = string', () => { let pkg: Package = { "name": "foobar", "exports": { ".": "./$self", } }; pass(pkg, './$self'); pass(pkg, './$self', '.'); pass(pkg, './$self', 'foobar'); fail(pkg, './other', 'other'); fail(pkg, './other', 'foobar/other'); fail(pkg, './hello', './hello'); }); it('exports["."] = object', () => { let pkg: Package = { "name": "foobar", "exports": { ".": { "import": "./$import", "require": "./$require", } } }; pass(pkg, './$import'); pass(pkg, './$import', '.'); pass(pkg, './$import', 'foobar'); fail(pkg, './other', 'other'); fail(pkg, './other', 'foobar/other'); fail(pkg, './hello', './hello'); }); it('exports["./foo"] = string', () => { let pkg: Package = { "name": "foobar", "exports": { "./foo": "./$import", } }; pass(pkg, './$import', './foo'); pass(pkg, './$import', 'foobar/foo'); fail(pkg, '.'); fail(pkg, '.', 'foobar'); fail(pkg, './other', 'foobar/other'); }); it('exports["./foo"] = object', () => { let pkg: Package = { "name": "foobar", "exports": { "./foo": { "import": "./$import", "require": "./$require", } } }; pass(pkg, './$import', './foo'); pass(pkg, './$import', 'foobar/foo'); fail(pkg, '.'); fail(pkg, '.', 'foobar'); fail(pkg, './other', 'foobar/other'); }); // https://nodejs.org/api/packages.html#packages_nested_conditions it('nested conditions', () => { let pkg: Package = { "name": "foobar", "exports": { "node": { "import": "./$node.import", "require": "./$node.require" }, "default": "./$default", } }; pass(pkg, './$node.import'); pass(pkg, './$node.import', 'foobar'); // browser => no "node" key pass(pkg, './$default', '.', { browser: true }); pass(pkg, './$default', 'foobar', { browser: true }); fail(pkg, './hello', './hello'); fail(pkg, './other', 'foobar/other'); fail(pkg, './other', 'other'); }); it('nested conditions :: subpath', () => { let pkg: Package = { "name": "foobar", "exports": { "./lite": { "node": { "import": "./$node.import", "require": "./$node.require" }, "browser": { "import": "./$browser.import", "require": "./$browser.require" }, } } }; pass(pkg, './$node.import', 'foobar/lite'); pass(pkg, './$node.require', 'foobar/lite', { require: true }); pass(pkg, './$browser.import', 'foobar/lite', { browser: true }); pass(pkg, './$browser.require', 'foobar/lite', { browser: true, require: true }); }); it('nested conditions :: subpath :: inverse', () => { let pkg: Package = { "name": "foobar", "exports": { "./lite": { "import": { "browser": "./$browser.import", "node": "./$node.import", }, "require": { "browser": "./$browser.require", "node": "./$node.require", } } } }; pass(pkg, './$node.import', 'foobar/lite'); pass(pkg, './$node.require', 'foobar/lite', { require: true }); pass(pkg, './$browser.import', 'foobar/lite', { browser: true }); pass(pkg, './$browser.require', 'foobar/lite', { browser: true, require: true }); }); // https://nodejs.org/api/packages.html#packages_subpath_folder_mappings it('exports["./"]', () => { let pkg: Package = { "name": "foobar", "exports": { ".": { "require": "./$require", "import": "./$import" }, "./package.json": "./package.json", "./": "./" } }; pass(pkg, './$import'); pass(pkg, './$import', 'foobar'); pass(pkg, './$require', 'foobar', { require: true }); pass(pkg, './package.json', 'package.json'); pass(pkg, './package.json', 'foobar/package.json'); pass(pkg, './package.json', './package.json'); // "loose" / everything exposed pass(pkg, './hello.js', 'hello.js'); pass(pkg, './hello.js', 'foobar/hello.js'); pass(pkg, './hello/world.js', './hello/world.js'); }); it('exports["./"] :: w/o "." key', () => { let pkg: Package = { "name": "foobar", "exports": { "./package.json": "./package.json", "./": "./" } }; fail(pkg, '.', "."); fail(pkg, '.', "foobar"); pass(pkg, './package.json', 'package.json'); pass(pkg, './package.json', 'foobar/package.json'); pass(pkg, './package.json', './package.json'); // "loose" / everything exposed pass(pkg, './hello.js', 'hello.js'); pass(pkg, './hello.js', 'foobar/hello.js'); pass(pkg, './hello/world.js', './hello/world.js'); }); // https://nodejs.org/api/packages.html#packages_subpath_folder_mappings it('exports["./*"]', () => { let pkg: Package = { "name": "foobar", "exports": { "./*": "./cheese/*.mjs" } }; fail(pkg, '.', "."); fail(pkg, '.', "foobar"); pass(pkg, './cheese/hello.mjs', 'hello'); pass(pkg, './cheese/hello.mjs', 'foobar/hello'); pass(pkg, './cheese/hello/world.mjs', './hello/world'); // evaluate as defined, not wrong pass(pkg, './cheese/hello.js.mjs', 'hello.js'); pass(pkg, './cheese/hello.js.mjs', 'foobar/hello.js'); pass(pkg, './cheese/hello/world.js.mjs', './hello/world.js'); }); it('exports["./dir*"]', () => { let pkg: Package = { "name": "foobar", "exports": { "./dir*": "./cheese/*.mjs" } }; fail(pkg, '.', "."); fail(pkg, '.', "foobar"); pass(pkg, './cheese/test.mjs', 'dirtest'); pass(pkg, './cheese/test.mjs', 'foobar/dirtest'); pass(pkg, './cheese/test/wheel.mjs', 'dirtest/wheel'); pass(pkg, './cheese/test/wheel.mjs', 'foobar/dirtest/wheel'); }); // https://github.com/lukeed/resolve.exports/issues/9 it('exports["./dir*"] :: repeat "*" value', () => { let pkg: Package = { "name": "foobar", "exports": { "./dir*": "./*sub/dir*/file.js" } }; fail(pkg, '.', "."); fail(pkg, '.', "foobar"); pass(pkg, './testsub/dirtest/file.js', 'dirtest'); pass(pkg, './testsub/dirtest/file.js', 'foobar/dirtest'); pass(pkg, './test/innersub/dirtest/inner/file.js', 'dirtest/inner'); pass(pkg, './test/innersub/dirtest/inner/file.js', 'foobar/dirtest/inner'); }); it('exports["./dir/*"] :: "*" value', () => { let pkg: Package = { "name": "foobar", "exports": { ".": "./dir/index.js", "./dir": "./dir/index.js", "./dir/*": "./dir/index.js" } }; pass(pkg, './dir/index.js', 'foobar'); pass(pkg, './dir/index.js', 'foobar/dir'); pass(pkg, './dir/index.js', 'foobar/dir/profile'); }); it('exports["./dir*"] :: share "name" start', () => { let pkg: Package = { "name": "director", "exports": { "./dir*": "./*sub/dir*/file.js" } }; fail(pkg, '.', "."); fail(pkg, '.', "director"); pass(pkg, './testsub/dirtest/file.js', 'dirtest'); pass(pkg, './testsub/dirtest/file.js', 'director/dirtest'); pass(pkg, './test/innersub/dirtest/inner/file.js', 'dirtest/inner'); pass(pkg, './test/innersub/dirtest/inner/file.js', 'director/dirtest/inner'); }); /** * @deprecated Documentation-only deprecation in Node 14.13 * @deprecated Runtime deprecation in Node 16.0 * @removed Removed in Node 18.0 * @see https://nodejs.org/docs/latest-v16.x/api/packages.html#subpath-folder-mappings */ it('exports["./features/"]', () => { let pkg: Package = { "name": "foobar", "exports": { "./features/": "./features/" } }; pass(pkg, './features/', 'features/'); pass(pkg, './features/', 'foobar/features/'); pass(pkg, './features/hello.js', 'foobar/features/hello.js'); fail(pkg, './features', 'features'); fail(pkg, './features', 'foobar/features'); fail(pkg, './package.json', 'package.json'); fail(pkg, './package.json', 'foobar/package.json'); fail(pkg, './package.json', './package.json'); }); // https://nodejs.org/api/packages.html#packages_subpath_folder_mappings it('exports["./features/"] :: with "./" key', () => { let pkg: Package = { "name": "foobar", "exports": { "./features/": "./features/", "./package.json": "./package.json", "./": "./" } }; pass(pkg, './features', 'features'); // via "./" pass(pkg, './features', 'foobar/features'); // via "./" pass(pkg, './features/', 'features/'); // via "./features/" pass(pkg, './features/', 'foobar/features/'); // via "./features/" pass(pkg, './features/hello.js', 'foobar/features/hello.js'); pass(pkg, './package.json', 'package.json'); pass(pkg, './package.json', 'foobar/package.json'); pass(pkg, './package.json', './package.json'); // Does NOT hit "./" (match Node) fail(pkg, '.', '.'); fail(pkg, '.', 'foobar'); }); it('exports["./features/"] :: conditions', () => { let pkg: Package = { "name": "foobar", "exports": { "./features/": { "browser": { "import": "./browser.import/", "require": "./browser.require/", }, "import": "./import/", "require": "./require/", }, } }; // import pass(pkg, './import/', 'features/'); pass(pkg, './import/', 'foobar/features/'); pass(pkg, './import/hello.js', './features/hello.js'); pass(pkg, './import/hello.js', 'foobar/features/hello.js'); // require pass(pkg, './require/', 'features/', { require: true }); pass(pkg, './require/', 'foobar/features/', { require: true }); pass(pkg, './require/hello.js', './features/hello.js', { require: true }); pass(pkg, './require/hello.js', 'foobar/features/hello.js', { require: true }); // require + browser pass(pkg, './browser.require/', 'features/', { browser: true, require: true }); pass(pkg, './browser.require/', 'foobar/features/', { browser: true, require: true }); pass(pkg, './browser.require/hello.js', './features/hello.js', { browser: true, require: true }); pass(pkg, './browser.require/hello.js', 'foobar/features/hello.js', { browser: true, require: true }); }); // https://nodejs.org/api/packages.html#packages_subpath_folder_mappings it('exports["./features/*"]', () => { let pkg: Package = { "name": "foobar", "exports": { "./features/*": "./features/*.js", } }; fail(pkg, './features', 'features'); fail(pkg, './features', 'foobar/features'); fail(pkg, './features/', 'features/'); fail(pkg, './features/', 'foobar/features/'); pass(pkg, './features/a.js', 'foobar/features/a'); pass(pkg, './features/ab.js', 'foobar/features/ab'); pass(pkg, './features/abc.js', 'foobar/features/abc'); pass(pkg, './features/hello.js', 'foobar/features/hello'); pass(pkg, './features/foo/bar.js', 'foobar/features/foo/bar'); // Valid: Pattern trailers allow any exact substrings to be matched pass(pkg, './features/hello.js.js', 'foobar/features/hello.js'); pass(pkg, './features/foo/bar.js.js', 'foobar/features/foo/bar.js'); fail(pkg, './package.json', 'package.json'); fail(pkg, './package.json', 'foobar/package.json'); fail(pkg, './package.json', './package.json'); }); // https://nodejs.org/api/packages.html#packages_subpath_folder_mappings it('exports["./features/*"] :: with "./" key', () => { let pkg: Package = { "name": "foobar", "exports": { "./features/*": "./features/*.js", "./": "./" } }; pass(pkg, './features', 'features'); // via "./" pass(pkg, './features', 'foobar/features'); // via "./" pass(pkg, './features/', 'features/'); // via "./" pass(pkg, './features/', 'foobar/features/'); // via "./" pass(pkg, './features/hello.js', 'foobar/features/hello'); pass(pkg, './features/foo/bar.js', 'foobar/features/foo/bar'); // Valid: Pattern trailers allow any exact substrings to be matched pass(pkg, './features/hello.js.js', 'foobar/features/hello.js'); pass(pkg, './features/foo/bar.js.js', 'foobar/features/foo/bar.js'); pass(pkg, './package.json', 'package.json'); pass(pkg, './package.json', 'foobar/package.json'); pass(pkg, './package.json', './package.json'); // Does NOT hit "./" (match Node) fail(pkg, '.', '.'); fail(pkg, '.', 'foobar'); }); // https://github.com/lukeed/resolve.exports/issues/7 it('exports["./features/*"] :: with "./" key first', () => { let pkg: Package = { "name": "foobar", "exports": { "./": "./", "./features/*": "./features/*.js" } }; pass(pkg, './features', 'features'); // via "./" pass(pkg, './features', 'foobar/features'); // via "./" pass(pkg, './features/', 'features/'); // via "./" pass(pkg, './features/', 'foobar/features/'); // via "./" pass(pkg, './features/hello.js', 'foobar/features/hello'); pass(pkg, './features/foo/bar.js', 'foobar/features/foo/bar'); // Valid: Pattern trailers allow any exact substrings to be matched pass(pkg, './features/hello.js.js', 'foobar/features/hello.js'); pass(pkg, './features/foo/bar.js.js', 'foobar/features/foo/bar.js'); pass(pkg, './package.json', 'package.json'); pass(pkg, './package.json', 'foobar/package.json'); pass(pkg, './package.json', './package.json'); // Does NOT hit "./" (match Node) fail(pkg, '.', '.'); fail(pkg, '.', 'foobar'); }); // https://github.com/lukeed/resolve.exports/issues/16 it('exports["./features/*"] :: with `null` internals', () => { let pkg: Package = { "name": "foobar", "exports": { "./features/*": "./src/features/*.js", "./features/internal/*": null } }; pass(pkg, './src/features/hello.js', 'features/hello'); pass(pkg, './src/features/hello.js', 'foobar/features/hello'); pass(pkg, './src/features/foo/bar.js', 'features/foo/bar'); pass(pkg, './src/features/foo/bar.js', 'foobar/features/foo/bar'); // TODO? Native throws `ERR_PACKAGE_PATH_NOT_EXPORTED` // Currently throwing `Missing "%s" specifier in "$s" package` fail(pkg, './features/internal/hello', 'features/internal/hello'); fail(pkg, './features/internal/foo/bar', 'features/internal/foo/bar'); }); // https://github.com/lukeed/resolve.exports/issues/16 it('exports["./features/*"] :: with `null` internals first', () => { let pkg: Package = { "name": "foobar", "exports": { "./features/internal/*": null, "./features/*": "./src/features/*.js", } }; pass(pkg, './src/features/hello.js', 'features/hello'); pass(pkg, './src/features/hello.js', 'foobar/features/hello'); pass(pkg, './src/features/foo/bar.js', 'features/foo/bar'); pass(pkg, './src/features/foo/bar.js', 'foobar/features/foo/bar'); // TODO? Native throws `ERR_PACKAGE_PATH_NOT_EXPORTED` // Currently throwing `Missing "%s" specifier in "$s" package` fail(pkg, './features/internal/hello', 'features/internal/hello'); fail(pkg, './features/internal/foo/bar', 'features/internal/foo/bar'); }); // https://nodejs.org/docs/latest-v18.x/api/packages.html#package-entry-points it('exports["./features/*"] :: with "./features/*.js" key', () => { let pkg: Package = { "name": "foobar", "exports": { "./features/*": "./features/*.js", "./features/*.js": "./features/*.js", } }; fail(pkg, './features', 'features'); fail(pkg, './features', 'foobar/features'); fail(pkg, './features/', 'features/'); fail(pkg, './features/', 'foobar/features/'); pass(pkg, './features/a.js', 'foobar/features/a'); pass(pkg, './features/ab.js', 'foobar/features/ab'); pass(pkg, './features/abc.js', 'foobar/features/abc'); pass(pkg, './features/hello.js', 'foobar/features/hello'); pass(pkg, './features/hello.js', 'foobar/features/hello.js'); pass(pkg, './features/foo/bar.js', 'foobar/features/foo/bar'); pass(pkg, './features/foo/bar.js', 'foobar/features/foo/bar.js'); fail(pkg, './package.json', 'package.json'); fail(pkg, './package.json', 'foobar/package.json'); fail(pkg, './package.json', './package.json'); }); it('exports["./features/*"] :: conditions', () => { let pkg: Package = { "name": "foobar", "exports": { "./features/*": { "browser": { "import": "./browser.import/*.mjs", "require": "./browser.require/*.js", }, "import": "./import/*.mjs", "require": "./require/*.js", }, } }; // import fail(pkg, './features/', 'features/'); // no file fail(pkg, './features/', 'foobar/features/'); // no file pass(pkg, './import/hello.mjs', './features/hello'); pass(pkg, './import/hello.mjs', 'foobar/features/hello'); // require fail(pkg, './features/', 'features/', { require: true }); // no file fail(pkg, './features/', 'foobar/features/', { require: true }); // no file pass(pkg, './require/hello.js', './features/hello', { require: true }); pass(pkg, './require/hello.js', 'foobar/features/hello', { require: true }); // require + browser fail(pkg, './features/', 'features/', { browser: true, require: true }); // no file fail(pkg, './features/', 'foobar/features/', { browser: true, require: true }); // no file pass(pkg, './browser.require/hello.js', './features/hello', { browser: true, require: true }); pass(pkg, './browser.require/hello.js', 'foobar/features/hello', { browser: true, require: true }); }); it('should handle mixed path/conditions', () => { let pkg: Package = { "name": "foobar", "exports": { ".": [ { "import": "./$root.import", }, "./$root.string" ], "./foo": [ { "require": "./$foo.require" }, "./$foo.string" ] } } pass(pkg, ['./$root.import', './$root.string']); pass(pkg, ['./$root.import', './$root.string'], 'foobar'); // TODO? if len==1 then single? pass(pkg, ['./$foo.string'], 'foo'); pass(pkg, ['./$foo.string'], 'foobar/foo'); pass(pkg, ['./$foo.string'], './foo'); pass(pkg, ['./$foo.require', './$foo.string'], 'foo', { require: true }); pass(pkg, ['./$foo.require', './$foo.string'], 'foobar/foo', { require: true }); pass(pkg, ['./$foo.require', './$foo.string'], './foo', { require: true }); }); it('should handle file with leading dot', () => { let pkg: Package = { "version": "2.41.0", "name": "aws-cdk-lib", "exports": { ".": "./index.js", "./package.json": "./package.json", "./.jsii": "./.jsii", "./.warnings.jsii.js": "./.warnings.jsii.js", "./alexa-ask": "./alexa-ask/index.js" } }; pass(pkg, "./.warnings.jsii.js", ".warnings.jsii.js"); }); }); describe('options.requires', it => { let pkg: Package = { "name": "r", "exports": { "require": "./$require", "import": "./$import", } }; it('should ignore "require" keys by default', () => { pass(pkg, './$import'); }); it('should use "require" key when defined first', () => { pass(pkg, './$require', '.', { require: true }); }); it('should ignore "import" key when enabled', () => { let pkg: Package = { "name": "r", "exports": { "import": "./$import", "require": "./$require", } }; pass(pkg, './$require', '.', { require: true }); pass(pkg, './$import', '.'); }); it('should match "default" if "require" is after', () => { let pkg: Package = { "name": "r", "exports": { "default": "./$default", "require": "./$require", } }; pass(pkg, './$default', '.', { require: true }); }); }); describe('options.browser', it => { let pkg: Package = { "name": "b", "exports": { "browser": "./$browser", "node": "./$node", } }; it('should ignore "browser" keys by default', () => { pass(pkg, './$node'); }); it('should use "browser" key when defined first', () => { pass(pkg, './$browser', '.', { browser: true }); }); it('should ignore "node" key when enabled', () => { let pkg: Package = { "name": "b", "exports": { "node": "./$node", "import": "./$import", "browser": "./$browser", } }; // import defined before browser pass(pkg, './$import', '.', { browser: true }); }); }); describe('options.conditions', it => { const pkg: Package = { "name": "c", "exports": { "production": "./$prod", "development": "./$dev", "default": "./$default", } }; it('should ignore unknown conditions by default', () => { pass(pkg, './$default'); }); it('should recognize custom field(s) when specified', () => { pass(pkg, './$dev', '.', { conditions: ['development'] }); pass(pkg, './$prod', '.', { conditions: ['development', 'production'] }); }); it('should throw an error if no known conditions', () => { let ctx: Package = { "name": "hello", "exports": { // @ts-ignore ...pkg.exports }, }; // @ts-ignore delete ctx.exports.default; try { lib.resolve(ctx); assert.unreachable(); } catch (err) { assert.instance(err, Error); assert.is((err as Error).message, `No known conditions for "." specifier in "hello" package`); } }); }); describe('options.unsafe', it => { let pkg: Package = { "name": "unsafe", "exports": { ".": { "production": "./$prod", "development": "./$dev", "default": "./$default", }, "./spec/type": { "import": "./$import", "require": "./$require", "default": "./$default" }, "./spec/env": { "worker": { "default": "./$worker" }, "browser": "./$browser", "node": "./$node", "default": "./$default" } } }; it('should ignore unknown conditions by default', () => { pass(pkg, './$default', '.', { unsafe: true, }); }); it('should ignore "import" and "require" conditions by default', () => { pass(pkg, './$default', './spec/type', { unsafe: true, }); pass(pkg, './$default', './spec/type', { unsafe: true, require: true, }); }); it('should ignore "node" and "browser" conditions by default', () => { pass(pkg, './$default', './spec/type', { unsafe: true, }); pass(pkg, './$default', './spec/type', { unsafe: true, browser: true, }); }); it('should respect/accept any custom condition(s) when specified', () => { // root, dev only pass(pkg, './$dev', '.', { unsafe: true, conditions: ['development'] }); // root, defined order pass(pkg, './$prod', '.', { unsafe: true, conditions: ['development', 'production'] }); // import vs require, defined order pass(pkg, './$require', './spec/type', { unsafe: true, conditions: ['require'] }); // import vs require, defined order pass(pkg, './$import', './spec/type', { unsafe: true, conditions: ['import', 'require'] }); // import vs require, defined order pass(pkg, './$node', './spec/env', { unsafe: true, conditions: ['node'] }); // import vs require, defined order pass(pkg, './$browser', './spec/env', { unsafe: true, conditions: ['browser', 'node'] }); // import vs require, defined order pass(pkg, './$worker', './spec/env', { unsafe: true, conditions: ['browser', 'node', 'worker'] }); }); }); lukeed-resolve.exports-75bac9f/test/legacy.ts000066400000000000000000000106371472336032300214520ustar00rootroot00000000000000import * as uvu from 'uvu'; import * as assert from 'uvu/assert'; import { legacy } from '../src/legacy'; import type { Package } from 'resolve.exports'; function describe( name: string, cb: (it: uvu.Test) => void ) { let t = uvu.suite(name); cb(t); t.run(); } describe('lib.legacy', it => { it('should be a function', () => { assert.type(legacy, 'function'); }); it('should prefer "module" > "main" entry', () => { let pkg: Package = { "name": "foobar", "module": "build/module.js", "main": "build/main.js", }; let output = legacy(pkg); assert.is(output, './build/module.js'); }); it('should read "main" field', () => { let pkg: Package = { "name": "foobar", "main": "build/main.js", }; let output = legacy(pkg); assert.is(output, './build/main.js'); }); it('should return nothing when no fields', () => { let pkg: Package = { "name": "foobar" }; let output = legacy(pkg); assert.is(output, undefined); }); it('should ignore boolean-type field values', () => { let pkg = { "module": true, "main": "main.js" }; let output = legacy(pkg as any); assert.is(output, './main.js'); }); }); describe('options.fields', it => { let pkg: Package = { "name": "foobar", "module": "build/module.js", "browser": "build/browser.js", "custom": "build/custom.js", "main": "build/main.js", }; it('should customize field search order', () => { let output = legacy(pkg); assert.is(output, './build/module.js', 'default: module'); output = legacy(pkg, { fields: ['main'] }); assert.is(output, './build/main.js', 'custom: main only'); output = legacy(pkg, { fields: ['custom', 'main', 'module'] }); assert.is(output, './build/custom.js', 'custom: custom > main > module'); }); it('should return first *resolved* field', () => { let output = legacy(pkg, { fields: ['howdy', 'partner', 'hello', 'world', 'main'] }); assert.is(output, './build/main.js'); }); }); describe('options.browser', it => { let pkg: Package = { "name": "foobar", "module": "build/module.js", "browser": "build/browser.js", "unpkg": "build/unpkg.js", "main": "build/main.js", }; it('should prioritize "browser" field when defined', () => { let output = legacy(pkg); assert.is(output, './build/module.js'); output = legacy(pkg, { browser: true }); assert.is(output, './build/browser.js'); }); it('should respect existing "browser" order in custom fields', () => { let output = legacy(pkg, { fields: ['main', 'browser'], browser: true, }); assert.is(output, './build/main.js'); }); // https://github.com/defunctzombie/package-browser-field-spec it('should resolve object format', () => { let pkg: Package = { "name": "foobar", "browser": { "module-a": "./shims/module-a.js", "./server/only.js": "./shims/client-only.js" } }; assert.is( legacy(pkg, { browser: 'module-a' }), './shims/module-a.js' ); assert.is( legacy(pkg, { browser: './server/only.js' }), './shims/client-only.js' ); assert.is( legacy(pkg, { browser: 'foobar/server/only.js' }), './shims/client-only.js' ); }); it('should allow object format to "ignore" modules/files :: string', () => { let pkg: Package = { "name": "foobar", "browser": { "module-a": false, "./foo.js": false, } }; assert.is( legacy(pkg, { browser: 'module-a' }), false ); assert.is( legacy(pkg, { browser: './foo.js' }), false ); assert.is( legacy(pkg, { browser: 'foobar/foo.js' }), false ); }); it('should return the `browser` string (entry) if no custom mapping :: string', () => { let pkg: Package = { "name": "foobar", "browser": { // } }; assert.is( legacy(pkg, { browser: './hello.js' }), './hello.js' ); assert.is( legacy(pkg, { browser: 'foobar/hello.js' }), './hello.js' ); }); it('should return the full "browser" object :: true', () => { let pkg: Package = { "name": "foobar", "browser": { "./other.js": "./world.js" } }; let output = legacy(pkg, { browser: true }); assert.equal(output, pkg.browser); }); it('still ensures string output is made relative', () => { let pkg: Package = { "name": "foobar", "browser": { "./foo.js": "bar.js", } } as any; assert.is( legacy(pkg, { browser: './foo.js' }), './bar.js' ); assert.is( legacy(pkg, { browser: 'foobar/foo.js' }), './bar.js' ); }); }); lukeed-resolve.exports-75bac9f/test/utils.ts000066400000000000000000000200251472336032300213360ustar00rootroot00000000000000import * as uvu from 'uvu'; import * as assert from 'uvu/assert'; import * as $ from '../src/utils'; import type * as t from 'resolve.exports'; function describe( name: string, cb: (it: uvu.Test) => void ) { let t = uvu.suite(name); cb(t); t.run(); } describe('$.conditions', it => { const EMPTY = {}; function run(o?: t.Options): string[] { return [...$.conditions(o||{})]; } it('should be a function', () => { assert.type($.conditions, 'function'); }); it('should return `Set` instance', () => { let output = $.conditions(EMPTY); assert.instance(output, Set); }); it('default conditions', () => { assert.equal( [ ...$.conditions(EMPTY) ], ['default', 'import', 'node'] ); }); it('default conditions :: unsafe', () => { assert.equal( run({ unsafe: true }), ['default'] ); }); it('option.browser', () => { assert.equal( run({ browser: true }), ['default', 'import', 'browser'] ); }); // unsafe ignores all but conditions it('option.browser :: unsafe', () => { let output = run({ browser: true, unsafe: true, }); assert.equal(output, ['default']); }); it('option.require', () => { assert.equal( run({ require: true }), ['default', 'require', 'node'] ); }); // unsafe ignores all but conditions it('option.require :: unsafe', () => { let output = run({ require: true, unsafe: true, }); assert.equal(output, ['default']); }); it('option.conditions', () => { let output = run({ conditions: ['foo', 'bar'] }); assert.equal(output, ['default', 'foo', 'bar', 'import', 'node']); }); it('option.conditions :: order', () => { let output = run({ conditions: ['node', 'import', 'foobar'] }); assert.equal(output, ['default', 'node', 'import', 'foobar']); }); it('option.conditions :: unsafe', () => { let output = run({ unsafe: true, conditions: ['foo', 'bar'] }); assert.equal(output, ['default', 'foo', 'bar']); }); it('option.conditions :: browser', () => { let output = run({ browser: true, conditions: ['foo', 'bar'] }); assert.equal(output, ['default', 'foo', 'bar', 'import', 'browser']); }); it('option.conditions :: browser :: order', () => { let output = run({ browser: true, conditions: ['browser', 'foobar'] }); assert.equal(output, ['default', 'browser', 'foobar', 'import']); }); it('option.conditions :: require', () => { let output = run({ require: true, conditions: ['foo', 'bar'] }); assert.equal(output, ['default', 'foo', 'bar', 'require', 'node']); }); it('option.conditions :: require :: order', () => { let output = run({ require: true, conditions: ['require', 'foobar'] }); assert.equal(output, ['default', 'require', 'foobar', 'node']); }); }); describe('$.toEntry', it => { const PKG = 'PACKAGE'; const EXTERNAL = 'EXTERNAL'; function run(input: string, expect: string, externals?: boolean) { // overloading not working -,- let output = externals ? $.toEntry(PKG, input, true) : $.toEntry(PKG, input); let msg = `"${input}" -> "${expect}"` + (externals ? ' (externals)' : ''); assert.is(output, expect, msg); } it('should be a function', () => { assert.type($.toEntry, 'function'); }); it('PKG', () => { run(PKG, '.'); run(PKG, '.', true); }); it('.', () => { run('.', '.'); run('.', '.', true); }); it('./', () => { run('./', './'); run('./', './', true); }); it('#inner', () => { run('#inner', '#inner'); run('#inner', '#inner', true); }); it('./foo', () => { run('./foo', './foo'); run('./foo', './foo', true); }); it('foo', () => { run('foo', './foo'); // forces path by default run('foo', 'foo', true); }); it('.ini', () => { run('.ini', './.ini'); // forces path by default run('.ini', '.ini', true); }); // handle "import 'lib/lib';" case it('./PKG', () => { let input = './' + PKG; run(input, input); run(input, input, true); }); it('PKG/subpath', () => { let input = PKG + '/other'; run(input, './other'); run(input, './other', true); }); it('PKG/#inner', () => { let input = PKG + '/#inner'; run(input, '#inner'); run(input, '#inner', true); }); it('PKG/.ini', () => { let input = PKG + '/.ini'; run(input, './.ini'); run(input, './.ini', true); }); it('EXTERNAL', () => { run(EXTERNAL, './'+EXTERNAL); // forces path by default run(EXTERNAL, EXTERNAL, true); }); }); describe('$.injects', it => { function run(value: string, input: T, expect: T) { let output = $.injects(input, value); assert.is(output, undefined); assert.equal(input, expect); } it('should be a function', () => { assert.type($.injects, 'function'); }); it('should replace "*" character', () => { run('bar', ['./foo*.jpg'], ['./foobar.jpg']); }); it('should replace multiple "*" characters w/ same value', () => { run('bar', ['./*/foo-*.jpg'], ['./bar/foo-bar.jpg']); }); // for the "./features/" => "./src/features/" scenario it('should append `value` if missing "*" character', () => { run('app.js', ['./src/features/'], ['./src/features/app.js']); }); it('should handle mixed array input', () => { run('xyz', ['./foo/', './esm/*.mjs', './build/*/index-*.js'], ['./foo/xyz', './esm/xyz.mjs', './build/xyz/index-xyz.js'], ); }); }); describe('$.loop', it => { const FILE = './file.js'; const DEFAULT = './foobar.js'; type Expect = string | string[] | null | undefined; function run(expect: Expect, map: t.Exports.Value, conditions?: string[]) { let output = $.loop(map, new Set([ 'default', ...conditions||[] ])); if (typeof expect == 'string') { assert.ok(Array.isArray(output)); assert.is(output[0], expect); assert.is(output.length, 1); } else { // Array, null, undefined assert.equal(output, expect); } } it('should be a function', () => { assert.type($.loop, 'function'); }); it('string', () => { run('./foo.mjs', './foo.mjs'); // @ts-expect-error run('.', '.'); }); it('empties', () => { // @ts-expect-error run(undefined, ''); run(undefined, null); run(undefined, []); run(undefined, {}); }); it('{ default }', () => { run(FILE, { default: FILE, }); run(FILE, { other: './unknown.js', default: FILE, }); run(undefined, { other: './unknown.js', }); run(FILE, { foo: './foo.js', default: { bar: './bar.js', default: { baz: './baz.js', default: FILE, } } }); }); it('{ custom }', () => { let conditions = ['custom']; run(DEFAULT, { default: DEFAULT, custom: FILE, }, conditions); run(FILE, { custom: FILE, default: DEFAULT, }, conditions); run(undefined, { foo: './foo.js', bar: './bar.js', }, conditions); run(FILE, { foo: './foo.js', custom: { default: { custom: FILE, default: DEFAULT, } }, default: { custom: './bar.js' } }, conditions); }); it('[ string ]', () => { run( [DEFAULT, FILE], [DEFAULT, FILE] ); run(undefined, [ null, ]); run( [DEFAULT, FILE], [null, DEFAULT, FILE] ); run( [DEFAULT, FILE], [DEFAULT, null, FILE] ); }); it('[{ default }]', () => { run([DEFAULT, FILE], [ { default: DEFAULT, }, FILE ]); run([FILE, DEFAULT], [ FILE, null, { default: DEFAULT, }, ]); run([DEFAULT, FILE], [ { default: { default: { default: DEFAULT, } } }, null, FILE ]); run([DEFAULT, FILE, './foo.js'], [ { default: { default: DEFAULT, } }, null, { default: { default: DEFAULT, } }, FILE, { default: './foo.js' } ]); }); it('{ [mixed] }', () => { run([DEFAULT, FILE], { default: [DEFAULT, FILE] }); run([DEFAULT, FILE], { default: [null, DEFAULT, FILE] }); run([DEFAULT, FILE], { default: [null, { default: DEFAULT }, FILE] }); run([FILE, DEFAULT], { default: { custom: [{ default: [FILE, FILE, null, DEFAULT] }, null, DEFAULT, FILE] } }, ['custom']); run([DEFAULT, FILE], { default: { custom: [{ custom: [DEFAULT, null], default: [FILE, FILE, null, DEFAULT] }, null, DEFAULT, FILE] } }, ['custom']); }); }); lukeed-resolve.exports-75bac9f/tsconfig.json000066400000000000000000000007371472336032300213660ustar00rootroot00000000000000{ "compilerOptions": { "strict": true, "target": "esnext", "strictBindCallApply": true, "forceConsistentCasingInFileNames": true, "moduleResolution": "nodenext", "strictFunctionTypes": true, "strictNullChecks": true, "noImplicitThis": true, "noImplicitAny": true, "alwaysStrict": true, "module": "esnext", "noEmit": true, "paths": { "resolve.exports": ["./index.d.ts"] } }, "include": [ "src", "test" ] }