2025/2/8第一次更新

This commit is contained in:
爱吃咸鱼小猫咪
2025-02-08 18:50:38 +08:00
commit d7af560866
26519 changed files with 5046029 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
var test = require('tape');
var keys = require('object-keys');
var semver = require('semver');
var resolve = require('../');
var brokenNode = semver.satisfies(process.version, '11.11 - 11.13');
test('core modules', function (t) {
t.test('isCore()', function (st) {
st.ok(resolve.isCore('fs'));
st.ok(resolve.isCore('net'));
st.ok(resolve.isCore('http'));
st.ok(!resolve.isCore('seq'));
st.ok(!resolve.isCore('../'));
st.ok(!resolve.isCore('toString'));
st.end();
});
t.test('core list', function (st) {
var cores = keys(resolve.core);
st.plan(cores.length);
for (var i = 0; i < cores.length; ++i) {
var mod = cores[i];
// note: this must be require, not require.resolve, due to https://github.com/nodejs/node/issues/43274
var requireFunc = function () { require(mod); }; // eslint-disable-line no-loop-func
t.comment(mod + ': ' + resolve.core[mod]);
if (resolve.core[mod]) {
st.doesNotThrow(requireFunc, mod + ' supported; requiring does not throw');
} else if (brokenNode) {
st.ok(true, 'this version of node is broken: attempting to require things that fail to resolve breaks "home_paths" tests');
} else {
st.throws(requireFunc, mod + ' not supported; requiring throws');
}
}
st.end();
});
t.test('core via repl module', { skip: !resolve.core.repl }, function (st) {
var libs = require('repl')._builtinLibs; // eslint-disable-line no-underscore-dangle
if (!libs) {
st.skip('module.builtinModules does not exist');
return st.end();
}
for (var i = 0; i < libs.length; ++i) {
var mod = libs[i];
st.ok(resolve.core[mod], mod + ' is a core module');
st.doesNotThrow(
function () { require(mod); }, // eslint-disable-line no-loop-func
'requiring ' + mod + ' does not throw'
);
}
st.end();
});
t.test('core via builtinModules list', { skip: !resolve.core.module }, function (st) {
var libs = require('module').builtinModules;
if (!libs) {
st.skip('module.builtinModules does not exist');
return st.end();
}
var blacklist = [
'_debug_agent',
'v8/tools/tickprocessor-driver',
'v8/tools/SourceMap',
'v8/tools/tickprocessor',
'v8/tools/profile'
];
for (var i = 0; i < libs.length; ++i) {
var mod = libs[i];
if (blacklist.indexOf(mod) === -1) {
st.ok(resolve.core[mod], mod + ' is a core module');
st.doesNotThrow(
function () { require(mod); }, // eslint-disable-line no-loop-func
'requiring ' + mod + ' does not throw'
);
}
}
st.end();
});
t.end();
});
+29
View File
@@ -0,0 +1,29 @@
var path = require('path');
var test = require('tape');
var resolve = require('../');
test('dotdot', function (t) {
t.plan(4);
var dir = path.join(__dirname, '/dotdot/abc');
resolve('..', { basedir: dir }, function (err, res, pkg) {
t.ifError(err);
t.equal(res, path.join(__dirname, 'dotdot/index.js'));
});
resolve('.', { basedir: dir }, function (err, res, pkg) {
t.ifError(err);
t.equal(res, path.join(dir, 'index.js'));
});
});
test('dotdot sync', function (t) {
t.plan(2);
var dir = path.join(__dirname, '/dotdot/abc');
var a = resolve.sync('..', { basedir: dir });
t.equal(a, path.join(__dirname, 'dotdot/index.js'));
var b = resolve.sync('.', { basedir: dir });
t.equal(b, path.join(dir, 'index.js'));
});
+2
View File
@@ -0,0 +1,2 @@
var x = require('..');
console.log(x);
+1
View File
@@ -0,0 +1 @@
module.exports = 'whatever';
+29
View File
@@ -0,0 +1,29 @@
var test = require('tape');
var path = require('path');
var resolve = require('../');
test('faulty basedir must produce error in windows', { skip: process.platform !== 'win32' }, function (t) {
t.plan(1);
var resolverDir = 'C:\\a\\b\\c\\d';
resolve('tape/lib/test.js', { basedir: resolverDir }, function (err, res, pkg) {
t.equal(!!err, true);
});
});
test('non-existent basedir should not throw when preserveSymlinks is false', function (t) {
t.plan(2);
var opts = {
basedir: path.join(path.sep, 'unreal', 'path', 'that', 'does', 'not', 'exist'),
preserveSymlinks: false
};
var module = './dotdot/abc';
resolve(module, opts, function (err, res) {
t.equal(err.code, 'MODULE_NOT_FOUND');
t.equal(res, undefined);
});
});
+34
View File
@@ -0,0 +1,34 @@
var path = require('path');
var test = require('tape');
var resolve = require('../');
test('filter', function (t) {
t.plan(4);
var dir = path.join(__dirname, 'resolver');
var packageFilterArgs;
resolve('./baz', {
basedir: dir,
packageFilter: function (pkg, pkgfile) {
pkg.main = 'doom'; // eslint-disable-line no-param-reassign
packageFilterArgs = [pkg, pkgfile];
return pkg;
}
}, function (err, res, pkg) {
if (err) t.fail(err);
t.equal(res, path.join(dir, 'baz/doom.js'), 'changing the package "main" works');
var packageData = packageFilterArgs[0];
t.equal(pkg, packageData, 'first packageFilter argument is "pkg"');
t.equal(packageData.main, 'doom', 'package "main" was altered');
var packageFile = packageFilterArgs[1];
t.equal(
packageFile,
path.join(dir, 'baz/package.json'),
'second packageFilter argument is "pkgfile"'
);
t.end();
});
});
+33
View File
@@ -0,0 +1,33 @@
var path = require('path');
var test = require('tape');
var resolve = require('../');
test('filter', function (t) {
var dir = path.join(__dirname, 'resolver');
var packageFilterArgs;
var res = resolve.sync('./baz', {
basedir: dir,
// NOTE: in v2.x, this will be `pkg, pkgfile, dir`, but must remain "broken" here in v1.x for compatibility
packageFilter: function (pkg, /*pkgfile,*/ dir) { // eslint-disable-line spaced-comment
pkg.main = 'doom'; // eslint-disable-line no-param-reassign
packageFilterArgs = 'is 1.x' ? [pkg, dir] : [pkg, pkgfile, dir]; // eslint-disable-line no-constant-condition, no-undef
return pkg;
}
});
t.equal(res, path.join(dir, 'baz/doom.js'), 'changing the package "main" works');
var packageData = packageFilterArgs[0];
t.equal(packageData.main, 'doom', 'package "main" was altered');
if (!'is 1.x') { // eslint-disable-line no-constant-condition
var packageFile = packageFilterArgs[1];
t.equal(packageFile, path.join(dir, 'baz', 'package.json'), 'package.json path is correct');
}
var packageDir = packageFilterArgs['is 1.x' ? 1 : 2]; // eslint-disable-line no-constant-condition
// eslint-disable-next-line no-constant-condition
t.equal(packageDir, path.join(dir, 'baz'), ('is 1.x' ? 'second' : 'third') + ' packageFilter argument is "dir"');
t.end();
});
+127
View File
@@ -0,0 +1,127 @@
'use strict';
var fs = require('fs');
var homedir = require('../lib/homedir');
var path = require('path');
var test = require('tape');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var mv = require('mv');
var copyDir = require('copy-dir');
var tmp = require('tmp');
var HOME = homedir();
var hnm = path.join(HOME, '.node_modules');
var hnl = path.join(HOME, '.node_libraries');
var resolve = require('../async');
function makeDir(t, dir, cb) {
mkdirp(dir, function (err) {
if (err) {
cb(err);
} else {
t.teardown(function cleanup() {
rimraf.sync(dir);
});
cb();
}
});
}
function makeTempDir(t, dir, cb) {
if (fs.existsSync(dir)) {
var tmpResult = tmp.dirSync();
t.teardown(tmpResult.removeCallback);
var backup = path.join(tmpResult.name, path.basename(dir));
mv(dir, backup, function (err) {
if (err) {
cb(err);
} else {
t.teardown(function () {
mv(backup, dir, cb);
});
makeDir(t, dir, cb);
}
});
} else {
makeDir(t, dir, cb);
}
}
test('homedir module paths', function (t) {
t.plan(7);
makeTempDir(t, hnm, function (err) {
t.error(err, 'no error with HNM temp dir');
if (err) {
return t.end();
}
var bazHNMDir = path.join(hnm, 'baz');
var dotMainDir = path.join(hnm, 'dot_main');
copyDir.sync(path.join(__dirname, 'resolver/baz'), bazHNMDir);
copyDir.sync(path.join(__dirname, 'resolver/dot_main'), dotMainDir);
var bazPkg = { name: 'baz', main: 'quux.js' };
var dotMainPkg = { main: 'index' };
var bazHNMmain = path.join(bazHNMDir, 'quux.js');
t.equal(require.resolve('baz'), bazHNMmain, 'sanity check: require.resolve finds HNM `baz`');
var dotMainMain = path.join(dotMainDir, 'index.js');
t.equal(require.resolve('dot_main'), dotMainMain, 'sanity check: require.resolve finds `dot_main`');
makeTempDir(t, hnl, function (err) {
t.error(err, 'no error with HNL temp dir');
if (err) {
return t.end();
}
var bazHNLDir = path.join(hnl, 'baz');
copyDir.sync(path.join(__dirname, 'resolver/baz'), bazHNLDir);
var dotSlashMainDir = path.join(hnl, 'dot_slash_main');
var dotSlashMainMain = path.join(dotSlashMainDir, 'index.js');
var dotSlashMainPkg = { main: 'index' };
copyDir.sync(path.join(__dirname, 'resolver/dot_slash_main'), dotSlashMainDir);
t.equal(require.resolve('baz'), bazHNMmain, 'sanity check: require.resolve finds HNM `baz`');
t.equal(require.resolve('dot_slash_main'), dotSlashMainMain, 'sanity check: require.resolve finds HNL `dot_slash_main`');
t.test('with temp dirs', function (st) {
st.plan(3);
st.test('just in `$HOME/.node_modules`', function (s2t) {
s2t.plan(3);
resolve('dot_main', function (err, res, pkg) {
s2t.error(err, 'no error resolving `dot_main`');
s2t.equal(res, dotMainMain, '`dot_main` resolves in `$HOME/.node_modules`');
s2t.deepEqual(pkg, dotMainPkg);
});
});
st.test('just in `$HOME/.node_libraries`', function (s2t) {
s2t.plan(3);
resolve('dot_slash_main', function (err, res, pkg) {
s2t.error(err, 'no error resolving `dot_slash_main`');
s2t.equal(res, dotSlashMainMain, '`dot_slash_main` resolves in `$HOME/.node_libraries`');
s2t.deepEqual(pkg, dotSlashMainPkg);
});
});
st.test('in `$HOME/.node_libraries` and `$HOME/.node_modules`', function (s2t) {
s2t.plan(3);
resolve('baz', function (err, res, pkg) {
s2t.error(err, 'no error resolving `baz`');
s2t.equal(res, bazHNMmain, '`baz` resolves in `$HOME/.node_modules` when in both');
s2t.deepEqual(pkg, bazPkg);
});
});
});
});
});
});
+114
View File
@@ -0,0 +1,114 @@
'use strict';
var fs = require('fs');
var homedir = require('../lib/homedir');
var path = require('path');
var test = require('tape');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var mv = require('mv');
var copyDir = require('copy-dir');
var tmp = require('tmp');
var HOME = homedir();
var hnm = path.join(HOME, '.node_modules');
var hnl = path.join(HOME, '.node_libraries');
var resolve = require('../sync');
function makeDir(t, dir, cb) {
mkdirp(dir, function (err) {
if (err) {
cb(err);
} else {
t.teardown(function cleanup() {
rimraf.sync(dir);
});
cb();
}
});
}
function makeTempDir(t, dir, cb) {
if (fs.existsSync(dir)) {
var tmpResult = tmp.dirSync();
t.teardown(tmpResult.removeCallback);
var backup = path.join(tmpResult.name, path.basename(dir));
mv(dir, backup, function (err) {
if (err) {
cb(err);
} else {
t.teardown(function () {
mv(backup, dir, cb);
});
makeDir(t, dir, cb);
}
});
} else {
makeDir(t, dir, cb);
}
}
test('homedir module paths', function (t) {
t.plan(7);
makeTempDir(t, hnm, function (err) {
t.error(err, 'no error with HNM temp dir');
if (err) {
return t.end();
}
var bazHNMDir = path.join(hnm, 'baz');
var dotMainDir = path.join(hnm, 'dot_main');
copyDir.sync(path.join(__dirname, 'resolver/baz'), bazHNMDir);
copyDir.sync(path.join(__dirname, 'resolver/dot_main'), dotMainDir);
var bazHNMmain = path.join(bazHNMDir, 'quux.js');
t.equal(require.resolve('baz'), bazHNMmain, 'sanity check: require.resolve finds HNM `baz`');
var dotMainMain = path.join(dotMainDir, 'index.js');
t.equal(require.resolve('dot_main'), dotMainMain, 'sanity check: require.resolve finds `dot_main`');
makeTempDir(t, hnl, function (err) {
t.error(err, 'no error with HNL temp dir');
if (err) {
return t.end();
}
var bazHNLDir = path.join(hnl, 'baz');
copyDir.sync(path.join(__dirname, 'resolver/baz'), bazHNLDir);
var dotSlashMainDir = path.join(hnl, 'dot_slash_main');
var dotSlashMainMain = path.join(dotSlashMainDir, 'index.js');
copyDir.sync(path.join(__dirname, 'resolver/dot_slash_main'), dotSlashMainDir);
t.equal(require.resolve('baz'), bazHNMmain, 'sanity check: require.resolve finds HNM `baz`');
t.equal(require.resolve('dot_slash_main'), dotSlashMainMain, 'sanity check: require.resolve finds HNL `dot_slash_main`');
t.test('with temp dirs', function (st) {
st.plan(3);
st.test('just in `$HOME/.node_modules`', function (s2t) {
s2t.plan(1);
var res = resolve('dot_main');
s2t.equal(res, dotMainMain, '`dot_main` resolves in `$HOME/.node_modules`');
});
st.test('just in `$HOME/.node_libraries`', function (s2t) {
s2t.plan(1);
var res = resolve('dot_slash_main');
s2t.equal(res, dotSlashMainMain, '`dot_slash_main` resolves in `$HOME/.node_libraries`');
});
st.test('in `$HOME/.node_libraries` and `$HOME/.node_modules`', function (s2t) {
s2t.plan(1);
var res = resolve('baz');
s2t.equal(res, bazHNMmain, '`baz` resolves in `$HOME/.node_modules` when in both');
});
});
});
});
});
+1
View File
@@ -0,0 +1 @@
packages/tests/fixtures/**
+14
View File
@@ -0,0 +1,14 @@
{
"root": true,
"extends": "@ljharb/eslint-config/node/10",
"overrides": [
{
"files": "since.js",
"rules": {
"no-console": 0,
},
},
],
}
+12
View File
@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: [ljharb]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: npm/list-exports
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
@@ -0,0 +1,78 @@
name: 'Tests: export conditions'
on: [pull_request, push]
jobs:
conditions:
name: 'exports conditions'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version:
- '17' # ↥ pattern-trailers-no-dir-slash
- '16'
- '16.9' # ⤓ with pattern trailers
- '16.8' # ↥ no pattern trailers
- '16.0' # ↥ patterns
- '15'
- '14.19' # ⤓ with pattern trailers
- '14.18' # ↥ no pattern trailers
- '14.13' # ⤓ with patterns
- '14.12' # ↥ no patterns
- '14.0' # ⤓ with conditions
- '13' # ↥ no conditions
- '13.14' # ⤓ first conditions with broken dir slash
- '13.13' # ↥ last conditions without broken dir slash
- '13.7' # ⤓ with conditions
- '13.6' # ↥ no conditions
- '13.5'
- '13.4'
- '13.3'
- '13.2' # ⤓ experimental
- '13.1' # broken
- '13.0' # broken
- '12'
- '12.20' # ⤓ with patterns
- '12.19' # ↥ no patterns
- '12.18'
- '12.17' # ⤓ with conditions
- '12.16' # ↥ no conditions
- '8'
- '6'
- '4'
- 'iojs'
- '0.12'
- '0.10'
- '0.8'
steps:
- uses: actions/checkout@v4
- uses: ljharb/actions/node/install@main
name: 'nvm install lts/* && npm install'
with:
node-version: 'lts/*'
skip-ls-check: true
- uses: ljharb/actions/node/install@main
name: 'nvm install ${{ matrix.node-version }}'
with:
node-version: ${{ matrix.node-version }}
skip-install: true
skip-ls-check: true
- run: npm run tests:conditions
exports-conditions:
name: 'exports conditions'
needs: [conditions]
runs-on: ubuntu-latest
steps:
- run: 'echo tests completed'
+25
View File
@@ -0,0 +1,25 @@
name: 'Tests: fixtures'
on: [pull_request, push]
jobs:
fixtures:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ljharb/actions/node/install@main
name: 'nvm install ${{ matrix.node-version }} && npm install'
with:
node-version: 'lts/*'
skip-ls-check: true
- name: 'list fixtures'
run: npm run fixtures:ls
working-directory: packages/tests
- name: 'verify all fixtures have `"bundleDependencies": true'
run: npm run fixtures:bundleCheck
working-directory: packages/tests
- name: 'install all fixtures in tests `package.json`'
run: npm run fixtures:install
working-directory: packages/tests
- run: git diff --quiet --exit-code
@@ -0,0 +1,42 @@
name: 'Tests: ls-engines'
on: [pull_request, push]
jobs:
engines-matrix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ljharb/actions/node/install@main
name: 'nvm install lts/* && npm install'
with:
node-version: 'lts/*'
- id: set-matrix
run: |
echo "matrix=$(npm query --no-workspaces .workspace | npx json -a location | { cat; echo; } | sort | node -pe 'JSON.stringify(fs.readFileSync(0, "utf-8").split("\n").slice(0,-1).map(x => "./"+x))')" >> $GITHUB_OUTPUT
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
engines-job:
name: 'ls-engines: package'
needs: [engines-matrix]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
package: ${{ fromJson(needs.engines-matrix.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- uses: ljharb/actions/node/install@main
name: 'nvm install lts/* && npm install'
with:
node-version: 'lts/*'
- run: npx ls-engines
working-directory: ${{ matrix.package }}
engines:
name: 'ls-engines'
needs: [engines-job]
runs-on: ubuntu-latest
steps:
- run: 'echo tests completed'
@@ -0,0 +1,29 @@
name: 'Tests: pretest/posttest'
on: [pull_request, push]
jobs:
pretest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ljharb/actions/node/install@main
name: 'nvm install ${{ matrix.node-version }} && npm install'
with:
node-version: 'lts/*'
skip-ls-check: true
- run: npm run pretest
posttest:
if: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ljharb/actions/node/install@main
name: 'nvm install ${{ matrix.node-version }} && npm install'
with:
node-version: 'lts/*'
skip-ls-check: true
- run: npm run posttest
+63
View File
@@ -0,0 +1,63 @@
name: 'Tests: node.js'
on: [pull_request, push]
jobs:
matrix:
runs-on: ubuntu-latest
outputs:
latest: ${{ steps.set-matrix.outputs.requireds }}
minors: ${{ steps.set-matrix.outputs.optionals }}
steps:
- uses: ljharb/actions/node/matrix@main
id: set-matrix
with:
preset: '^18.17.0 || >=20.5.0'
type: 'minors'
latest:
needs: [matrix]
name: 'latest minors'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.matrix.outputs.latest) }}
steps:
- uses: actions/checkout@v4
- uses: ljharb/actions/node/install@main
name: 'nvm install ${{ matrix.node-version }} && npm install'
with:
node-version: ${{ matrix.node-version }}
skip-ls-check: true
- run: npm run tests-only
- uses: codecov/codecov-action@v2
minors:
needs: [matrix, latest]
name: 'non-latest minors'
continue-on-error: true
if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.matrix.outputs.minors) }}
steps:
- uses: actions/checkout@v4
- uses: ljharb/actions/node/install@main
name: 'nvm install ${{ matrix.node-version }} && npm install'
with:
node-version: ${{ matrix.node-version }}
skip-ls-check: true
- run: npm run tests-only
- uses: codecov/codecov-action@v2
node:
name: 'node 18+'
needs: [latest, minors]
runs-on: ubuntu-latest
steps:
- run: 'echo tests completed'
+15
View File
@@ -0,0 +1,15 @@
name: Automatic Rebase
on: [pull_request_target]
jobs:
_:
name: "Automatic Rebase"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ljharb/rebase@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,12 @@
name: Require “Allow Edits”
on: [pull_request_target]
jobs:
_:
name: "Require “Allow Edits”"
runs-on: ubuntu-latest
steps:
- uses: ljharb/require-allow-edits@main
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Jordan Harband
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.
+2
View File
@@ -0,0 +1,2 @@
# list-exports
Given a package name and a version number, or a path to a package.json, what specifiers does it expose?
+52
View File
@@ -0,0 +1,52 @@
{
"name": "list-exports-monorepo",
"version": "0.0.0",
"private": true,
"description": "Given a package name and a version number, or a path to a package.json, what specifiers does it expose?",
"scripts": {
"lint": "eslint . '**/bin/*'",
"pretest": "npm run lint",
"tests-only": "tape packages/tests",
"tests:conditions": "tape packages/tests/conditions",
"test": "npm run tests-only && npm run tests:conditions",
"posttest": "aud --production",
"since": "node since"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ljharb/list-exports.git"
},
"keywords": [
"exports",
"cjs",
"esm",
"module",
"commonjs",
"es",
"export",
"entrypoint",
"resolve"
],
"author": "Jordan Harband <ljharb@gmail.com>",
"funding": {
"url": "https://github.com/sponsors/ljharb"
},
"devDependencies": {
"@ljharb/eslint-config": "^21.1.0",
"aud": "^2.0.4",
"eslint": "=8.8.0",
"evalmd": "^0.0.19",
"glob-gitignore": "^1.0.14",
"has-dynamic-import": "^2.1.0",
"ls-engines": "^0.9.1",
"node-exports-info": "^1.3.0",
"semver": "^7.6.0",
"tape": "^5.7.5"
},
"engines": {
"node": "^18.17.0 || >=20.5.0"
},
"workspaces": [
"packages/*"
]
}
+21
View File
@@ -0,0 +1,21 @@
{
"root": true,
"extends": "@ljharb/eslint-config/node/10",
"rules": {
"complexity": 0,
"eqeqeq": [2, "allow-null"],
"func-style": 1,
"id-length": 0,
"max-lines": 0,
"max-lines-per-function": 0,
"max-params": 1,
"max-statements": 1,
"multiline-comment-style": 0,
"new-cap": [2, {
"capIsNewExceptions": ["GetIntrinsic"],
}],
"sort-keys": 0,
},
}
+123
View File
@@ -0,0 +1,123 @@
# list-exports <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
[![github actions][actions-image]][actions-url]
[![coverage][codecov-image]][codecov-url]
[![License][license-image]][license-url]
[![Downloads][downloads-image]][downloads-url]
[![npm badge][npm-badge-png]][package-url]
Given a path to a package.json, what specifiers does it expose?
The package export defaults an `async function`. It takes a path to a `package.json` as the only required argument.
It fulfills with an object with the following structure:
- `name` the package name
- `version`: the package version
- `engines`: the package's `engines` requirements
- `problems`: a Set of strings describing problems or validation issues encountered during exports traversal. <sub>Note that these errors *do not* necessarily interfere with the listed entry points being accessible at runtime.</sub>
- `exports`: an object with the following structure:
- `binaries`: a Map of executable program names, to the relative file path that name will execute.
- `latest`: a string describing the latest “[category][]” in the given node version range. This [category][] will be present in the following list.
- `...categories`: each [category][] that the node version range overlaps will have an object with this structure:
- `import`: a Map of import specifier, to relative file path
- `require`: a Map of require specifier, to relative file path
- `files`: a Set of relative file paths that are included in `import` and/or `require`
- `tree`: a Map. Its keys are filenames (no leading `./`), whose values are a Set of `import` or `require` specifiers that point to it; or, directory names, whose values are a Map of the same recursive structure as `tree` itself.
- `pre-exports`: this [category][] will always be present, whether it's in the above list or not, with the above structure.
In addition to the required `package.json` path, it also takes a second argument, an options object. This object supports the following properties:
- `node`: either `true`, which reads the `engines.node` field in `package.json`, or a valid semver range of node versions to target. Defaults to the current node version.
## Example
<details>
<summary>`const expected`:</summary>
```js
const expected = {
name: 'list-exports',
version: '1.1.0',
engines: { node: '^18.17.0 || >=20.5.0' },
problems: new Set(),
exports: {
binaries: {},
latest: 'pattern-trailers-no-dir-slash',
'pattern-trailers-no-dir-slash': {
import: new Map([
['.', './index.js'],
]),
require: new Map([
['.', './index.js'],
['./package.json', './package.json'],
]),
files: new Set([
'./index.js',
'./package.json',
]),
tree: new Map([
['index.js', new Set(['.'])],
['package.json', new Set(['./package.json'])],
]),
},
'pre-exports': {
import: new Map(),
require: new Map([
['.', './index.js'],
['./', './index.js'],
['./index', './index.js'],
['./index.js', './index.js'],
['./package', './package.json'],
['./package.json', './package.json'],
]),
files: new Set([
'./index.js',
'./package.json',
]),
tree: new Map([
['index.js', new Set([
'.',
'./',
'./index.js',
'./index',
])],
['package.json', new Set([
'./package.json',
'./package',
])],
]),
},
},
};
```
</details>
```js
const assert = require('assert');
const path = require('path');
const listExports = require('list-exports');
listExports('./package.json', { node: true }).then((data) => {
assert.deepEqual(data, expected);
}).catch((e) => {
console.error(e);
process.exit(1);
});
```
[package-url]: https://npmjs.org/package/list-exports
[npm-version-svg]: https://versionbadg.es/ljharb/list-exports.svg
[deps-svg]: https://david-dm.org/ljharb/list-exports.svg
[deps-url]: https://david-dm.org/ljharb/list-exports
[dev-deps-svg]: https://david-dm.org/ljharb/list-exports/dev-status.svg
[dev-deps-url]: https://david-dm.org/ljharb/list-exports#info=devDependencies
[npm-badge-png]: https://nodei.co/npm/list-exports.png?downloads=true&stars=true
[license-image]: https://img.shields.io/npm/l/list-exports.svg
[license-url]: LICENSE
[downloads-image]: https://img.shields.io/npm/dm/list-exports.svg
[downloads-url]: https://npm-stat.com/charts.html?package=list-exports
[codecov-image]: https://codecov.io/gh/ljharb/list-exports/branch/main/graphs/badge.svg
[codecov-url]: https://app.codecov.io/gh/ljharb/list-exports/
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/list-exports
[actions-url]: https://github.com/ljharb/list-exports/actions
[category]: https://github.com/inspect-js/node-exports-info#categories
+930
View File
@@ -0,0 +1,930 @@
'use strict';
/* eslint no-negated-condition: 1 */
const {
lstatSync,
existsSync,
realpathSync,
} = require('fs');
const {
basename,
dirname,
extname,
join: pathJoin,
normalize: pathNormalize,
relative: pathRelative,
sep: pathSep,
} = require('path');
const readPackageJSON = require('read-package-json');
const entries = require('object.entries');
const fromEntries = require('object.fromentries');
const flatMap = require('array.prototype.flatmap');
const flat = require('array.prototype.flat');
const filter = require('array.prototype.filter');
const some = require('array.prototype.some');
const resolve = require('resolve');
const packlist = require('npm-packlist');
const getPackageType = require('get-package-type').sync;
const inspect = require('object-inspect');
const Arborist = require('@npmcli/arborist');
const forEach = require('for-each');
const {
validRange,
intersects,
} = require('semver');
const includes = require('array-includes');
const map = require('array.prototype.map');
const reduce = require('array.prototype.reduce');
const startsWith = require('string.prototype.startswith');
const endsWith = require('string.prototype.endswith');
const GetIntrinsic = require('get-intrinsic');
const callBind = require('call-bind');
const callBound = require('call-bind/callBound');
const keys = require('object-keys');
const sortPaths = require('sort-paths');
const arrayFrom = require('array.from');
const hasOwn = require('hasown');
const validateExportsObject = require('validate-exports-object');
const getCategoriesForRange = require('node-exports-info/getCategoriesForRange');
const getConditionsForCategory = require('node-exports-info/getConditionsForCategory');
const $concat = callBound('Array.prototype.concat');
const $sort = callBound('Array.prototype.sort');
const $localeCompare = callBound('String.prototype.localeCompare');
const $replace = callBound('String.prototype.replace');
const $split = callBound('String.prototype.split');
const $all = callBind(GetIntrinsic('%Promise.all%'), Promise);
const $resolve = callBind(GetIntrinsic('%Promise.resolve%'), Promise);
function isDirectory(file) {
try {
return lstatSync(file).isDirectory();
} catch (e) {
return false;
}
}
function resolveFrom(file, basedir, extensions) {
try {
return resolve.sync(file, { basedir, extensions });
} catch (e) {
return null;
}
}
function stringSort(a, b) {
return $localeCompare(a, b);
}
function sortFiles(tree) {
return fromEntries(flatMap(entries(tree), ([k, v]) => {
if (k === 'hasDirSlash') {
return [];
}
if (k === 'files') {
return [[k, new Set(sortPaths(filter(arrayFrom(v), Boolean), '/'))]];
}
if (k === 'require' || k === 'import') {
return [[k, new Map(sortPaths(filter(arrayFrom(v), ([, vv]) => vv), ([a]) => a, '/'))]];
}
return [[k, v]];
}));
}
function getExtensions(packageType = 'commonjs') {
if (packageType !== 'commonjs' && packageType !== 'module') {
throw new TypeError(`unknown package type found: ${inspect(packageType)}`);
}
const base = filter(
keys(require.extensions),
(x) => startsWith(x, '.') && (packageType !== 'module' || x !== '.js') && x !== '.mjs',
);
const legacy = packageType === 'module' ? $concat(base, '.js') : base;
const esm = $concat(['.mjs'], packageType === 'module' ? '.js' : []);
const all = $concat([], esm, '.cjs', base);
return {
all,
base,
esm,
legacy,
};
}
function isCJS(filename, usingExports = false) {
const packageType = getPackageType(filename);
if (packageType !== 'commonjs' && packageType !== 'module') {
throw new TypeError(`unknown package type found: ${inspect(packageType)}`);
}
const { base, legacy } = getExtensions(packageType);
return includes(usingExports ? base : legacy, extname(filename));
}
function isESM(filename) {
const packageType = getPackageType(filename);
if (packageType !== 'commonjs' && packageType !== 'module') {
throw new TypeError(`unknown package type found: ${inspect(packageType)}`);
}
const { esm } = getExtensions(packageType);
return includes(esm, extname(filename));
}
async function readPackage(packageJSON) {
return new Promise((resolveP, rejectP) => {
readPackageJSON(packageJSON, (err, data) => {
if (err) {
rejectP(err);
} else {
resolveP(data);
}
});
});
}
async function serial(items, cb) {
return reduce(
items,
async (prev, item) => {
await prev;
return cb(item);
},
$resolve(),
);
}
async function asyncReduce(items, task, initial = void undefined) {
return reduce(
items,
async (prev, value) => task(await prev, value),
initial,
);
}
async function asyncForEach(items, task) {
return asyncReduce(
items,
async (prev, item) => task(item),
);
}
async function getMain(rootDir, dir, extensions, problems) {
let hasExplicitMain = false;
let main;
const fullDir = pathJoin(rootDir, dir);
const pkgJSON = pathJoin(fullDir, 'package.json');
const hasPkgJSON = existsSync(pkgJSON);
if (hasPkgJSON) {
try {
const pkg = await readPackage(pkgJSON);
hasExplicitMain = 'main' in pkg;
if (hasExplicitMain) {
if (typeof pkg.main !== 'string') {
return null;
}
main = $replace(pathNormalize(pkg.main), /^(?:\.\/)?/, './');
}
} catch (e) {
problems.add(`\`${dir}\` has a \`package.json\`, but it is invalid!`);
}
}
if (hasExplicitMain) {
const fullMain = resolveFrom(main, fullDir, extensions);
const fullMainExists = existsSync(fullMain);
if (fullMainExists) {
return `./${pathRelative(rootDir, fullMain)}`;
}
}
const indexMain = resolveFrom('./index.js', fullDir, extensions);
if (existsSync(indexMain)) {
if (hasExplicitMain) {
problems.add(`\`${dir}\` has a \`package.json\`, but its \`main\` does not exist, although \`index.js\` does.`);
}
return `./${pathRelative(rootDir, indexMain)}`;
} else if (hasExplicitMain) {
problems.add(`\`${dir}\` has a \`package.json\`, but both its \`main\` and \`index.js\` do not exist!`);
}
if (dir === '.') {
problems.add(`\`${dir}\` has a \`package.json\`, but lacks both a \`main\` and an \`index.js\`!`);
}
return null;
}
function safeSet(mapInstance, key, newVal) {
if (!mapInstance.has(key)) {
mapInstance.set(key, newVal);
}
}
async function forEachSubfile(realFile, {
dir,
options,
rootDir,
mains,
tree,
packageExports,
}, fakeFile = realFile) {
const ext = extname(realFile);
const extensionless = basename(fakeFile, ext);
const realFullFile = pathJoin(rootDir, realFile);
const canRequire = !options.useType || (
ext !== '.mjs'
&& (
getPackageType(realFullFile) !== 'module' // not type module
|| ext !== '.js' // not .js
)
);
const dirMain = mains && mains.get(dir);
const canImport = options.useType && ext !== '.json';
if (canImport) {
if (!options.skipMainDot && mains && mains.get('.') === realFile && !hasOwn(packageExports || {}, '.')) {
safeSet(tree.import, '.', realFile);
}
if (!hasOwn(packageExports || {}, fakeFile)) {
safeSet(tree.import, fakeFile, realFile);
}
}
if (canRequire) {
if (dirMain === realFile) {
safeSet(tree.require, dir, realFile);
safeSet(tree.require, `${dir}/`, realFile);
}
if (mains && mains.get('.') === realFile) {
if (!options.skipMainDot && !hasOwn(packageExports || {}, '.')) {
safeSet(tree.require, '.', realFile);
}
if (!options.skipDirSlash) {
safeSet(tree.require, './', realFile);
}
}
if (!hasOwn(packageExports || {}, fakeFile)) {
safeSet(tree.require, fakeFile, realFile);
}
if (ext !== '.cjs' && ext !== '.mjs') {
const extlessFile = `${dir}/${extensionless}`;
if (!hasOwn(packageExports || {}, extlessFile)) {
safeSet(tree.require, extlessFile, realFile);
}
}
}
if (canRequire || canImport) {
tree.files.add(realFile);
}
}
function newTree() {
return {
import: new Map(),
require: new Map(),
files: new Set(),
tree: new Map(),
hasDirSlash: null, // will be deleted
};
}
async function traverseDir(
dir,
rootDir,
filteredFiles,
mains,
packageExports,
options = {},
tree = newTree(),
) {
const subFiles = new Set();
const subDirs = new Set();
forEach(
filter(
arrayFrom(filteredFiles, (file) => `./${file}`),
(file) => startsWith(file, `${dir}/`),
),
(file) => {
const subFile = $replace(file, `${dir}/`, '');
const subFileParts = $split(subFile, pathSep);
// ignore published files inside a node_modules dir
if (!includes(subFileParts, 'node_modules')) {
if (includes(subFile, pathSep)) {
subDirs.add(subFileParts[0]);
} else {
subFiles.add(file);
}
}
},
);
const dirMain = mains.get(dir);
if (dirMain) {
const fullDirMain = pathJoin(rootDir, dir, dirMain);
const canRequire = isCJS(fullDirMain, options.useType);
const canImport = options.useType && isESM(fullDirMain);
if (canImport) {
tree.import.set(dir, dirMain);
}
if (canRequire) {
safeSet(tree.require, dir, dirMain);
const dirSlash = `${dir}/`;
safeSet(tree.require, dirSlash, dirMain);
}
}
await serial(arrayFrom(subFiles), (file) => forEachSubfile(file, {
dir,
options,
rootDir,
mains,
tree,
packageExports,
}));
// build up the tree structure, from all included files
tree.files.forEach((file) => {
const parts = $split(file, '/');
reduce(parts, (acc, part, i) => {
if (part === '.') {
return acc.tree;
}
const isLastPart = i + 1 === parts.length;
safeSet(acc, part, isLastPart ? new Set() : new Map());
return acc.get(part);
}, tree);
});
function addToTree(file, specifier) {
const parts = $split(file, '/');
reduce(parts, (acc, part, i) => {
if (part === '.') {
return acc.tree;
}
const isLastPart = i + 1 === parts.length;
if (!acc.has(part)) {
safeSet(acc, part, isLastPart ? new Set() : new Map());
}
const item = acc.get(part);
if (isLastPart) {
item.add(specifier);
}
return item;
}, tree);
}
tree.require.forEach(addToTree);
tree.import.forEach(addToTree);
await $all(arrayFrom(subDirs, (subDir) => traverseDir(
`./${pathJoin(dir, subDir)}`,
rootDir,
filteredFiles,
mains,
packageExports,
options,
tree,
)));
return sortFiles(tree);
}
function addMainString(string, packageDir, tree) {
const main = `./${pathNormalize(string)}`;
const fullMain = pathJoin(packageDir, main);
if (existsSync(fullMain)) {
const resolved = `./${pathRelative(packageDir, fullMain)}`;
if (isESM(main)) {
if (!tree.import.has(main)) {
tree.import.set('.', resolved);
tree.files.add(main);
}
} else if (isCJS(main, true)) {
if (!tree.import.has(main)) {
tree.import.set('.', resolved);
tree.files.add(main);
}
if (!tree.require.has(main)) {
tree.require.set('.', resolved);
tree.files.add(main);
}
}
}
}
function addFullPath(
packageDir,
category,
tree,
lhs,
rhs,
conditionChain,
problems,
filteredFiles,
) {
if (startsWith(rhs, './')) {
const fullPath = pathJoin(packageDir, rhs);
if (filteredFiles.has($replace(rhs, /^\.\//, '')) && existsSync(fullPath)) {
const ext = extname(fullPath);
const canRequire = ext !== '.mjs'
&& (
!isESM(fullPath) // not type module
|| ext !== '.js' // not .js
)
&& !includes(conditionChain, 'import');
const canImport = category !== 'broken'
&& ext !== '.json'
&& !includes(conditionChain, 'require');
if (canImport) {
safeSet(tree.import, lhs, rhs);
}
if (canRequire) {
safeSet(tree.require, lhs, rhs);
}
if (tree.import.get(lhs) === rhs || tree.require.get(lhs) === rhs) {
tree.files.add(rhs);
}
return true;
}
problems.add(`${lhs}”: ${rhs} does not appear to exist!`);
} else {
problems.add(`\`exports[${lhs}]\`: ${rhs} must start with \`./\``);
}
return false;
}
function hasDirSlash(category) {
return category !== 'broken-dir-slash-conditions' && category !== 'patterns' && category !== 'pattern-trailers-no-dir-slash';
}
function traverseExportsSubtree({
tree,
subtree,
problems,
packageDir,
packageExports,
mains,
dir,
lhs,
rhs,
category,
}) {
subtree.forEach((value, key) => {
if (value instanceof Set) {
// it's a file
const relativeFilePath = `./${pathJoin(dir, key)}`;
const replacedFilePath = $replace(relativeFilePath, lhs, rhs);
forEachSubfile(relativeFilePath, {
dir,
options: {
useType: true,
skipMainDot: true,
skipDirSlash: !hasDirSlash(category),
},
rootDir: packageDir,
mains,
tree,
packageExports,
}, replacedFilePath);
} else if (value instanceof Map) {
// it's a dir
traverseExportsSubtree({
tree,
subtree: value,
problems,
packageDir,
packageExports,
mains,
dir: `./${pathJoin(dir, key)}`,
lhs,
rhs,
category,
});
} else {
throw new TypeError('tree has a non-collection value!');
}
});
}
function traverseExportsSubdir({
packageDir,
packageExports,
lhs,
rhs,
problems,
tree,
legacy,
mains,
category,
}) {
const fullRHS = pathJoin(packageDir, rhs);
// traverse into rhs, mapping paths to lhs
if (!existsSync(fullRHS)) {
problems.add(`\`${lhs}\`: \`${rhs}\` does not appear to exist!`);
} else if (!isDirectory(fullRHS)) {
problems.add(`\`${lhs}\`: \`${rhs}\` is not a directory!`);
} else {
const subtree = rhs === './' ? legacy.tree : legacy.tree.get(rhs);
if (subtree) {
traverseExportsSubtree({
tree,
subtree,
problems,
packageDir,
packageExports,
mains,
dir: '.',
lhs,
rhs,
category,
});
}
}
}
async function forEachExportEntry([lhs, maybeRHS], conditionChain, {
packageDir,
packageExports,
problems,
category,
conditions,
tree,
legacy,
filteredFiles,
mains,
}) {
return asyncReduce($concat([], maybeRHS), async (prev, rhs) => {
if (await prev) {
return true;
}
if (typeof rhs === 'string') {
rhs = decodeURI(rhs); // eslint-disable-line no-param-reassign
if (endsWith(lhs, '/') && endsWith(rhs, '/')) {
if (category === 'pattern-trailers-no-dir-slash') {
return false;
}
tree.hasDirSlash = true; // eslint-disable-line no-param-reassign
traverseExportsSubdir({
packageDir,
packageExports,
lhs,
rhs,
problems,
tree,
legacy,
mains,
category,
});
return true;
}
return addFullPath(
packageDir,
category,
tree,
lhs,
rhs,
conditionChain,
problems,
filteredFiles,
);
}
const rhsResults = validateExportsObject(rhs);
rhsResults.problems.forEach((problem) => {
problems.add(problem);
});
if (rhsResults.status === 'files') {
problems.add('`./package.json`: inside a conditions object, a files object (keys starting with `.`) is invalid');
return false;
}
if (category !== 'broken') {
const validConditionEntries = filter(entries(rhs), ([x]) => conditions.has(x));
if (validConditionEntries.length === 0) {
safeSet(tree.import, lhs, false);
safeSet(tree.require, lhs, false);
return false;
}
return asyncReduce(validConditionEntries, (matchedSomething, [condition, conditionRHS]) => {
if (typeof conditionRHS === 'string') {
if (endsWith(lhs, '/') && endsWith(conditionRHS, '/')) {
return traverseExportsSubdir({
packageDir,
packageExports,
lhs,
rhs: conditionRHS,
problems,
tree,
legacy,
mains,
});
}
return addFullPath(
packageDir,
category,
tree,
lhs,
conditionRHS,
conditionChain.concat(condition),
problems,
filteredFiles,
) || matchedSomething;
}
return forEachExportEntry([lhs, conditionRHS], $concat(conditionChain, condition), {
packageDir,
packageExports,
problems,
category,
conditions,
tree,
legacy,
filteredFiles,
mains,
}) || matchedSomething;
}, false);
}
return false;
}, false);
}
async function traverseExports(category, packageDir, pkgData, filteredFiles, legacy, mains, problems) {
const tree = newTree();
function addToTree(file, specifier) {
if (file !== false) {
const parts = $split(file, '/');
reduce(parts, (acc, part, i) => {
if (part === '.') {
return acc.tree;
}
let item = acc.get(part);
if (i + 1 === parts.length) {
if (!item) {
item = new Set();
}
item.add(specifier);
} else if (!item) {
item = new Map();
}
acc.set(part, item);
return item;
}, tree);
}
}
const conditions = new Set(getConditionsForCategory(category));
if (typeof pkgData.exports === 'string') {
addMainString(pkgData.exports, packageDir, tree);
} else {
// handle array fallback for main
const exportValues = flat($concat([], pkgData.exports), Infinity);
await asyncReduce(exportValues, async (prev, value) => { // TODO: fixtures for nested arrays in "broken"
if (await prev) {
return true;
}
if (typeof value === 'string') {
addMainString(value, packageDir, tree);
return true;
}
if (value && typeof value === 'object') {
const topLevelResults = validateExportsObject(value);
topLevelResults.problems.forEach((problem) => {
problems.add(problem);
});
if (topLevelResults.status === 'empty') {
return false;
}
if (topLevelResults.status === 'conditions') {
if (category === 'broken') {
safeSet(tree.import, '.', false);
safeSet(tree.require, '.', false);
return false;
}
return forEachExportEntry(['.', value], [], {
packageDir,
packageExports: pkgData.exports,
problems,
category,
conditions,
tree,
legacy,
filteredFiles,
mains,
});
}
if (topLevelResults.status !== 'files') {
console.error({ topLevelResults });
throw new TypeError(`unknown top-level exports object type found: ${topLevelResults.status}`);
}
return asyncForEach(
entries(value),
([lhs, rhs]) => {
const matched = forEachExportEntry([lhs, rhs], [], {
packageDir,
packageExports: pkgData.exports,
problems,
category,
conditions,
tree,
legacy,
filteredFiles,
mains,
});
if (!matched) {
safeSet(tree.import, lhs, false);
safeSet(tree.require, lhs, false);
}
},
);
}
return false;
}, false);
}
tree.require.forEach(addToTree);
tree.import.forEach(addToTree);
return sortFiles(tree);
}
async function traverseMains(rootDir, filteredFiles, extensions, problems) {
// first pass: get every dir and its alleged main
const dirs = new Map(await $all(arrayFrom(
new Set(arrayFrom(filteredFiles, (file) => dirname(`./${file}`))),
async (dir) => [dir, await getMain(rootDir, dir, extensions, problems)],
)));
// second pass: any alleged main that points to a dir, remap it to an actual main
return new Map(filter(
arrayFrom(dirs, ([dir, maybeMain]) => {
const found = maybeMain && dirs.get($replace(maybeMain, /\/?$/, ''));
return [dir, found && endsWith(found, '/') ? `./${pathJoin(found, 'index.js')}` : found || maybeMain];
}),
([, x]) => x,
));
}
async function getExports(packageDir, pkgData, nodeRange, problems) {
const {
type: rootType = 'commonjs',
} = pkgData;
const { all: rootAllExtensions, base: rootBaseExtensions } = getExtensions(rootType);
const arborist = new Arborist({ path: packageDir });
const arbTree = await arborist.loadActual();
const packedFiles = await packlist(arbTree, { path: packageDir });
/* eslint function-paren-newline: 0 */
const filteredFiles = new Set(
$sort(
filter(
flatMap(
packedFiles,
(x) => {
const resolved = resolveFrom(dirname(x), packageDir, rootAllExtensions);
return [
x,
resolved && pathRelative(packageDir, resolved),
];
},
),
(x) => x && some(rootAllExtensions, (ext) => endsWith(x, ext)),
),
),
);
const mains = await traverseMains(packageDir, filteredFiles, rootBaseExtensions, problems);
const legacyP = traverseDir('.', packageDir, filteredFiles, mains, {});
const categories = getCategoriesForRange(nodeRange);
const latest = categories[0];
const binaryEntries = typeof pkgData.bin === 'string'
? [[pkgData.name, pkgData.bin]]
: entries(pkgData.bin || {});
const binaries = fromEntries(flatMap(binaryEntries, ([n, p]) => {
const resolved = resolveFrom($replace(p, /^(?:\.\/)?/, './'), packageDir, $concat('', rootAllExtensions));
if (resolved) {
const relativeBin = `./${pathRelative(packageDir, resolved)}`;
return [[n, relativeBin]];
}
return [];
}));
if (categories.length === 1 && latest === 'pre-exports') {
return {
binaries,
latest,
[latest]: await legacyP,
};
}
if (!('exports' in pkgData)) {
const [
postExports,
preExports,
] = await $all([
traverseDir('.', packageDir, filteredFiles, mains, pkgData.exports, { useType: true }),
legacyP,
]);
return {
binaries,
latest: 'conditions',
conditions: postExports,
'pre-exports': preExports,
};
}
// traverse "exports", respect "type" field, etc
const legacy = await legacyP;
const categoryExports = await $all(map(categories, async (category) => [
category,
category === 'pre-exports'
? legacy
: await traverseExports(category, packageDir, pkgData, filteredFiles, legacy, mains, problems),
]));
return {
binaries,
latest,
...fromEntries(categoryExports),
'pre-exports': legacy,
};
}
module.exports = async function listExports(packageJSON, options = {}) {
const packageJSONpath = realpathSync(packageJSON);
const packageDir = dirname(packageJSONpath);
const pkgData = await readPackage(packageJSON);
const {
name,
version,
private: isPrivate,
engines = { node: '*' },
} = pkgData;
let node = process.version;
if (options.node === true) {
({ node } = engines);
if (!validRange(node)) {
throw new RangeError('when the provided node version is `true`, this packages `engines.node` declaration must be a valid semver range');
}
} else if ('node' in options) {
if (!validRange(options.node)) {
throw new RangeError('`node` option must be `true`, or a valid semver range');
}
({ node } = options);
}
const problems = new Set();
if (!intersects(engines.node || '*', node)) {
problems.add('node' in options
? `the provided node version (${node}) does not match the packages \`engines.node\` declaration (${engines.node || '*'})`
: `the current node version (${node}) does not match the packages \`engines.node\` declaration (${engines.node || '*'})`);
}
if (isPrivate) {
return {
name,
version,
private: !!isPrivate,
problems: new Set($sort(arrayFrom(problems), stringSort)),
};
}
const exports = await getExports(packageDir, pkgData, node, problems);
return {
name,
version,
engines: {
node: '*',
...engines,
},
problems: new Set($sort(arrayFrom(problems), stringSort)),
exports,
};
};
// Map/Set has/add/get/set
@@ -0,0 +1,84 @@
{
"name": "list-exports",
"version": "1.1.0",
"description": "Given a package name and a version number, or a path to a package.json, what specifiers does it expose?",
"main": "index.js",
"exports": {
".": "./index.js",
"./package.json": "./package.json"
},
"scripts": {
"prepublish": "not-in-publish || (safe-publish-latest && cp ../../LICENSE ./)",
"lint": "eslint .",
"postlint": "evalmd README.md",
"pretest": "npm run lint",
"test": ">&2 echo tests are ran in the monorepo only",
"posttest": "aud --production"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ljharb/list-exports.git",
"directory": "./packages/list-exports"
},
"keywords": [
"exports",
"cjs",
"esm",
"module",
"commonjs",
"es",
"export",
"entrypoint",
"resolve"
],
"author": "Jordan Harband <ljharb@gmail.com>",
"funding": {
"url": "https://github.com/sponsors/ljharb"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/ljharb/list-exports/issues"
},
"homepage": "https://github.com/ljharb/list-exports#readme",
"dependencies": {
"@npmcli/arborist": "^7.3.1",
"array-includes": "^3.1.7",
"array.from": "^1.1.5",
"array.prototype.filter": "^1.0.3",
"array.prototype.flat": "^1.3.2",
"array.prototype.flatmap": "^1.3.2",
"array.prototype.map": "^1.0.6",
"array.prototype.reduce": "^1.0.6",
"array.prototype.some": "^1.1.5",
"call-bind": "^1.0.7",
"for-each": "^0.3.3",
"get-intrinsic": "^1.2.4",
"get-package-type": "^0.1.0",
"hasown": "^2.0.1",
"node-exports-info": "^1.3.0",
"npm-packlist": "^8.0.2",
"object-inspect": "^1.13.1",
"object-keys": "^1.1.1",
"object.entries": "^1.1.7",
"object.fromentries": "^2.0.7",
"read-package-json": "^7.0.0",
"resolve": "^2.0.0-next.5",
"semver": "^7.6.0",
"sort-paths": "^1.1.1",
"string.prototype.endswith": "^1.0.1",
"string.prototype.startswith": "^1.0.0",
"validate-exports-object": "^1.0.1"
},
"devDependencies": {
"@ljharb/eslint-config": "^21.1.0",
"aud": "^2.0.4",
"eslint": "=8.8.0",
"evalmd": "^0.0.19",
"in-publish": "^2.0.1",
"ls-engines": "^0.9.1",
"safe-publish-latest": "^2.0.0"
},
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"root": true,
"extends": "@ljharb/eslint-config/node/10",
"rules": {
"func-style": 1,
"function-call-argument-newline": ["off", "consistent"],
"max-lines-per-function": 0,
"sort-keys": 0,
},
}
+51
View File
@@ -0,0 +1,51 @@
# ls-exports <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
[![github actions][actions-image]][actions-url]
[![coverage][codecov-image]][codecov-url]
[![License][license-image]][license-url]
[![Downloads][downloads-image]][downloads-url]
[![npm badge][npm-badge-png]][package-url]
Given a package name and a version number, or a path to a package.json, what specifiers does it expose?
The package export defaults an `async function`. It fulfills with an object with the following keys:
- `name` the package name
- `version`: the package version
- `engines`: the package's `engines` requirements
- `binaries`: the files that are made available as executable programs
- `errors`: any validation errors encountered during parsing. <sub>Note that these errors *do not* necessarily interfere with the listed entry points being accessible at runtime.</sub>
For ESM-supporting node versions (at the time of this writing, `^12.17 || >= 13.2`):
- `require`: valid specifiers to pass into `require`
- `import`: valid specifiers to pass into `import()`, or to use in a static `import` statement
- `files`: all files on the filesystem that are directly exposed by the above entry points
- `tree`: a hierarchical object structure where each directory is represented as a key containing an object, and each file is represented as a key containing a list of the entry points that expose that file
For node versions prior to ESM support (at the time of this writing, `< 12.17 || ~13.0 || ~13.1`):
- `require (pre-exports)`: valid specifiers to pass into `require`
- `files (pre-exports)`: all files on the filesystem that are directly exposed by the above entry points
- `tree (pre-exports)`: a hierarchical object structure where each directory is represented as a key containing an object, and each file is represented as a key containing a list of the entry points that expose that file
## Example
```sh
ls-exports package resolve@1
```
[package-url]: https://npmjs.org/package/ls-exports
[npm-version-svg]: https://versionbadg.es/ljharb/list-exports.svg
[deps-svg]: https://david-dm.org/ljharb/list-exports.svg
[deps-url]: https://david-dm.org/ljharb/list-exports
[dev-deps-svg]: https://david-dm.org/ljharb/list-exports/dev-status.svg
[dev-deps-url]: https://david-dm.org/ljharb/list-exports#info=devDependencies
[npm-badge-png]: https://nodei.co/npm/ls-exports.png?downloads=true&stars=true
[license-image]: https://img.shields.io/npm/l/ls-exports.svg
[license-url]: LICENSE
[downloads-image]: https://img.shields.io/npm/dm/ls-exports.svg
[downloads-url]: https://npm-stat.com/charts.html?package=ls-exports
[codecov-image]: https://codecov.io/gh/ljharb/list-exports/branch/main/graphs/badge.svg
[codecov-url]: https://app.codecov.io/gh/ljharb/list-exports/
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/list-exports
[actions-url]: https://github.com/ljharb/list-exports/actions
[category]: https://github.com/inspect-js/node-exports-info#categories
@@ -0,0 +1,71 @@
#!/usr/bin/env node
'use strict';
const path = require('path');
const npa = require('npm-package-arg');
const colorize = require('json-colorizer');
const yargs = require('yargs');
const fromEntries = require('object.fromentries');
const arrayFrom = require('array.from');
const listExports = require('list-exports');
const exportsTable = require('../exportsTable');
const getPackageJSONPath = require('../getPackageJSONPath');
const argv = yargs
.option('json', {
describe: 'output the results as JSON',
default: false,
type: 'boolean',
})
.command(
'package <specifier>', 'list the exports for the given package (that matches the optionally given version)',
(y) => y.positional('specifier', {
coerce(arg) {
npa(arg);
return String(arg);
},
conflicts: 'path',
describe: 'package specifier with optional version, ex `foo`, `@scope/foo`, `foo@^1`, etc',
type: 'string',
}),
)
.command(
'path <path>', 'list the exports for the relative path to a directory containing a `package.json`',
(y) => y.positional('path', {
coerce(arg) {
return path.join(path.resolve(arg), 'package.json');
},
describe: `a relative path to a directory containing a \`package.json\`
Usage: $0 --path=./path/to/directory
`,
required: true,
requiresArg: true,
}),
)
.demandCommand(1, 'must specify a command: “path” or “package”')
.strict()
.help()
.parse();
const packageDirP = argv.path ? Promise.resolve(argv.path) : getPackageJSONPath(argv.specifier);
function serializer(key, value) {
if (value instanceof Set) {
return arrayFrom(value);
}
if (value instanceof Map) {
return fromEntries(arrayFrom(value));
}
return value;
}
const promise = packageDirP.then((packageDir) => (argv.json
? listExports(packageDir).then((x) => console.log(colorize(JSON.stringify(x, serializer))))
: exportsTable(packageDir, (x) => console.log(x))));
promise.catch((err) => {
console.error(err);
process.exitCode = 1;
});
@@ -0,0 +1,99 @@
'use strict';
const colors = require('colors/safe');
const fromEntries = require('object.fromentries');
const values = require('object.values');
const stripANSI = require('strip-ansi');
const walk = require('tree-walk');
const listExports = require('list-exports');
const table = require('./table');
function sumTreeLeaves(root) {
walk.postorder(root, (value, key, parent) => {
/* eslint no-param-reassign: 1 */
if (Array.isArray(parent) || parent === root) {
return;
}
if (Array.isArray(value)) {
parent[key] = value.length;
} else if (parent) {
if (typeof value !== 'number') { // TODO: remove this, once this function no longer mutates
const sum = values(value).reduce((a, b) => a + b, 0);
if (typeof sum === 'number') {
delete parent[key];
parent[`${key}/`] = sum;
}
}
}
});
}
module.exports = async function exportsTable(packageDir, log) {
const x = await listExports(packageDir);
if (x.private) {
log(`${colors.blue(x.name)} @ ${x.version}`);
log(colors.bold.red('package is private'));
return;
}
const summaryRows = [
[
`${colors.blue(x.name)} @ ${x.version}`,
colors.green('node with ESM (>= 13.1)'),
colors.green('node pre-ESM (> 13.1)'),
].map((r) => colors.bold(r)),
[
colors.red('Binaries'),
x.binaries.length || '',
x.binaries.length || '',
],
[
colors.red('CJS + ESM Export Specifiers'),
x.require.length,
x['require (pre-exports)'].length,
],
[
colors.red('ESM-only Export Specifiers'),
x.import.length,
'',
],
[
colors.red('Exposed Files'),
x.files.length,
x['files (pre-exports)'].length,
],
];
const widths = summaryRows.reduce(
(maxes, cols) => cols
.map((col) => stripANSI(String(col)).length)
.map((len, i) => Math.max(maxes[i] || 0, len)),
[],
);
const columns = fromEntries(widths.map((width, i) => [
i,
{ width, alignment: i === 0 ? 'left' : 'right' },
]));
const tableOptions = { columns };
log(table(summaryRows, tableOptions));
sumTreeLeaves(x.tree);
sumTreeLeaves(x['tree (pre-exports)']);
log(colors.bold(`Top-level ${colors.reset.magenta('files')}/${colors.bold.cyan('directories')} that contribute specifiers:`));
const treeRows = Object.keys({ ...x.tree[x.name], ...x['tree (pre-exports)'][x.name] })
.sort((a, b) => (a.endsWith('/') ? b.endsWith('/') ? a.localeCompare(b) : -1 : 1))
.map((file) => [
file.endsWith('/') ? colors.bold.cyan(file) : colors.magenta(file),
x.tree[x.name][file],
x['tree (pre-exports)'][x.name][file],
]);
log(table(treeRows, tableOptions));
if (x.errors.length > 0) {
log(colors.bold(colors.red('!! Errors:')));
log(table([x.errors.map((e) => e.replace(process.cwd(), '$PWD'))]));
}
log(colors.dim('run the same command with `--json` for full details'));
};
@@ -0,0 +1,21 @@
'use strict';
const { promisify } = require('util');
const path = require('path');
const npa = require('npm-package-arg');
const pacote = require('pacote');
const { dir } = require('tmp');
const tmpDir = promisify(dir);
module.exports = async function getPackageJSONPath(specifier) {
const { name } = npa(specifier);
const cwd = await tmpDir();
const packageDir = path.join(cwd, 'node_modules', name);
await pacote.extract(specifier, packageDir);
return path.join(packageDir, 'package.json');
};
@@ -0,0 +1,68 @@
{
"name": "ls-exports",
"version": "1.1.1",
"description": "CLI for `list-exports`: Given a package name and a version number, or a path to a package.json, what specifiers does it expose?",
"bin": "./bin/ls-exports",
"main": false,
"exports": {
"./package.json": "./package.json"
},
"scripts": {
"prepublish": "not-in-publish || (safe-publish-latest && cp ../../LICENSE ./)",
"lint": "eslint . 'bin/**'",
"pretest": "npm run lint",
"test": ">&2 echo tests are ran in the monorepo only",
"posttest": "aud --production"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ljharb/list-exports.git",
"directory": "./packages/ls-exports"
},
"keywords": [
"exports",
"cjs",
"esm",
"module",
"commonjs",
"es",
"export",
"entrypoint",
"resolve"
],
"author": "Jordan Harband <ljharb@gmail.com>",
"funding": {
"url": "https://github.com/sponsors/ljharb"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/ljharb/list-exports/issues"
},
"homepage": "https://github.com/ljharb/list-exports#readme",
"dependencies": {
"array.from": "^1.1.5",
"colors": "=1.4.0",
"json-colorizer": "^2.2.2",
"list-exports": "^1.1.0",
"npm-package-arg": "^11.0.1",
"object.fromentries": "^2.0.7",
"object.values": "^1.1.7",
"pacote": "^17.0.6",
"strip-ansi": "^6.0.1",
"table": "^6.8.1",
"tmp": "^0.2.1",
"tree-walk": "^0.4.0",
"yargs": "^17.7.2"
},
"devDependencies": {
"@ljharb/eslint-config": "^21.1.0",
"aud": "^2.0.4",
"eslint": "=8.8.0",
"in-publish": "^2.0.1",
"ls-engines": "^0.9.1",
"safe-publish-latest": "^2.0.0"
},
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
}
+13
View File
@@ -0,0 +1,13 @@
'use strict';
const {
table: makeTable,
getBorderCharacters,
} = require('table');
module.exports = function table(data, options = {}) {
return makeTable(data, {
border: getBorderCharacters('norc'),
...options,
});
};
+1
View File
@@ -0,0 +1 @@
fixtures/*/project
+32
View File
@@ -0,0 +1,32 @@
{
"root": true,
"extends": ["@ljharb/eslint-config/node/10", "@ljharb/eslint-config/tests"],
"rules": {
"complexity": 0,
"func-style": 0,
"id-length": 0,
"max-lines-per-function": 0,
"max-len": 0,
"multiline-comment-style": 0,
"no-negated-condition": 0,
},
"overrides": [
{
"files": [
"conditions.js",
"conditions-expected.js",
],
"extends": ["@ljharb", "@ljharb/eslint-config/tests"],
"rules": {
"complexity": 0,
"func-style": 0,
"id-length": 0,
"max-lines-per-function": 0,
"no-negated-condition": 0,
}
}
],
}
@@ -0,0 +1,79 @@
'use strict';
var fs = require('fs');
var path = require('path');
var semver = require('semver');
var entries = require('object.entries');
var fromEntries = require('object.fromentries');
var hasBrokenExports = semver.satisfies(process.version, '~13.0 || ~13.1', { includePrerelease: true });
var hasPackageExports = require('has-package-exports');
var hasConditions = require('has-package-exports/conditional');
var conditionsPkg = JSON.parse(String(fs.readFileSync(path.join(__dirname, './fixtures/ex-conditions/project/package.json'))));
var empty = {};
function makeResult(slug) {
return {
resolved: path.basename(path.join(__dirname, 'fixtures/ex-conditions/project/' + slug + '.js')),
result: slug
};
}
module.exports = function getExpectedConditions(resolve) {
var expected = {
'.': makeResult(hasBrokenExports ? 'fallback' : hasPackageExports ? 'default' : 'main'),
'package.json': {
resolved: path.basename(resolve('./fixtures/ex-conditions/project/package.json')),
result: empty
},
dnri: makeResult(hasPackageExports ? hasBrokenExports ? 'fallback' : 'default' : 'dnri'),
dnir: makeResult(hasPackageExports ? hasBrokenExports ? 'fallback' : 'default' : 'dnir'),
drni: makeResult(hasPackageExports ? hasBrokenExports ? 'fallback' : 'default' : 'drni'),
drin: makeResult(hasPackageExports ? hasBrokenExports ? 'fallback' : 'default' : 'drin'),
dinr: makeResult(hasPackageExports ? hasBrokenExports ? 'fallback' : 'default' : 'dinr'),
dirn: makeResult(hasPackageExports ? hasBrokenExports ? 'fallback' : 'default' : 'dirn'),
ndri: makeResult(hasPackageExports ? hasConditions ? 'node' : hasBrokenExports ? 'fallback' : 'default' : 'ndri'),
ndir: makeResult(hasPackageExports ? hasConditions ? 'node' : hasBrokenExports ? 'fallback' : 'default' : 'ndir'),
nrdi: makeResult(hasPackageExports ? hasConditions ? 'node' : hasBrokenExports ? 'fallback' : 'default' : 'nrdi'),
nrid: makeResult(hasPackageExports ? hasConditions ? 'node' : hasBrokenExports ? 'fallback' : 'default' : 'nrid'),
nidr: makeResult(hasPackageExports ? hasConditions ? 'node' : hasBrokenExports ? 'fallback' : 'default' : 'nidr'),
nird: makeResult(hasPackageExports ? hasConditions ? 'node' : hasBrokenExports ? 'fallback' : 'default' : 'nird'),
rdni: makeResult(hasPackageExports ? hasConditions ? 'require' : hasBrokenExports ? 'fallback' : 'default' : 'rdni'),
rdin: makeResult(hasPackageExports ? hasConditions ? 'require' : hasBrokenExports ? 'fallback' : 'default' : 'rdin'),
rndi: makeResult(hasPackageExports ? hasConditions ? 'require' : hasBrokenExports ? 'fallback' : 'default' : 'rndi'),
rnid: makeResult(hasPackageExports ? hasConditions ? 'require' : hasBrokenExports ? 'fallback' : 'default' : 'rnid'),
ridn: makeResult(hasPackageExports ? hasConditions ? 'require' : hasBrokenExports ? 'fallback' : 'default' : 'ridn'),
rind: makeResult(hasPackageExports ? hasConditions ? 'require' : hasBrokenExports ? 'fallback' : 'default' : 'rind'),
idnr: makeResult(hasPackageExports ? hasBrokenExports ? 'fallback' : 'default' : 'idnr'),
idrn: makeResult(hasPackageExports ? hasBrokenExports ? 'fallback' : 'default' : 'idrn'),
indr: makeResult(hasPackageExports ? hasConditions ? 'node' : hasBrokenExports ? 'fallback' : 'default' : 'indr'),
inrd: makeResult(hasPackageExports ? hasConditions ? 'node' : hasBrokenExports ? 'fallback' : 'default' : 'inrd'),
irdn: makeResult(hasPackageExports ? hasConditions ? 'require' : hasBrokenExports ? 'fallback' : 'default' : 'irdn'),
irnd: makeResult(hasPackageExports ? hasConditions ? 'require' : hasBrokenExports ? 'fallback' : 'default' : 'irnd')
};
var actual = fromEntries(entries(expected).map(function (entry) {
var result;
var resolved;
try {
var exportPath = '@fixtures/ex-conditions' + (entry[0] === '.' ? '' : '/' + entry[0]);
// eslint-disable-next-line global-require
result = entry[0] === 'package.json' ? empty : require(exportPath);
resolved = path.basename(resolve(exportPath));
} catch (e) {
result = e;
}
return [entry[0], {
resolved: resolved,
result: result
}];
}));
return {
actual: actual,
'package': conditionsPkg,
expected: expected
};
};
+63
View File
@@ -0,0 +1,63 @@
'use strict';
var test = require('tape');
var semver = require('semver');
var whyNotEqual = require('is-equal/why');
var forEach = require('for-each');
var resolve = require('resolve/sync');
var hasBrokenExports = semver.satisfies(process.version, '~13.0 || ~13.1', { includePrerelease: true });
var hasPackageExports = require('has-package-exports');
var hasConditions = require('has-package-exports/conditional');
// var hasPatterns = require('has-package-exports/pattern');
var getExpected = require('./conditions-expected');
var re = process.env.GREP && new RegExp(process.env.GREP);
test('condition ordering', { skip: re && !re.test('condition ordering') }, function (t) {
if (hasBrokenExports) {
t.ok(
semver.satisfies(process.version, '~13.0 || ~13.1', { includePrerelease: true }),
'node ~13.0 || ~13.1: the "exports" field should not work, but does, incorrectly, and only supports a string'
);
} else if (!hasPackageExports) {
t.ok(
semver.satisfies(process.version, '<12.17', { includePrerelease: true }),
'node < 12.17: no support for the "exports" field'
);
} else if (!hasConditions) {
t.ok(
semver.satisfies(process.version, '13.2 - 13.6', { includePrerelease: true }),
'node 13.2 - 13.6: supports the "exports" fields object form, but no conditions beyond "default"'
);
} else {
t.ok(
semver.satisfies(process.version, '^12.17 || ^13.7 || >= 14', { includePrerelease: true }),
'node ^12.17 || ^13.7 || >= 14: supports the "exports" field'
);
}
forEach([
['require.resolve', getExpected(require.resolve)],
['resolve', getExpected(function (x) { return resolve(x); })]
], function (entry) {
var desc = entry[0];
var results = entry[1];
t.test(desc, { todo: desc === 'resolve' || (desc === 'require.resolve' && semver.satisfies(process.version, '< 6')) }, function (st) {
st.deepEqual(
Object.keys(results.expected).map(function (e) { return e === '.' ? e : './' + e; }),
Object.keys(results['package'].exports),
'test expects proper exports'
);
st.equal(whyNotEqual(results.expected, results.actual), '', 'expected exported values match actual exported values');
st.deepEqual(results.expected, results.actual);
st.end();
});
});
t.end();
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More