2019-10-11 23:57:13 +02:00
|
|
|
// Flags: --experimental-modules
|
2019-01-16 02:11:10 +01:00
|
|
|
import { mustCall } from '../common/index.mjs';
|
2019-07-24 06:25:24 +02:00
|
|
|
import { ok, deepStrictEqual, strictEqual } from 'assert';
|
|
|
|
|
|
|
|
import { requireFixture, importFixture } from '../fixtures/pkgexports.mjs';
|
2019-08-20 05:59:25 +02:00
|
|
|
import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
|
2019-07-24 06:25:24 +02:00
|
|
|
|
|
|
|
[requireFixture, importFixture].forEach((loadFixture) => {
|
|
|
|
const isRequire = loadFixture === requireFixture;
|
|
|
|
|
|
|
|
const validSpecifiers = new Map([
|
|
|
|
// A simple mapping of a path.
|
|
|
|
['pkgexports/valid-cjs', { default: 'asdf' }],
|
|
|
|
// A directory mapping, pointing to the package root.
|
|
|
|
['pkgexports/sub/asdf.js', { default: 'asdf' }],
|
|
|
|
// A mapping pointing to a file that needs special encoding (%20) in URLs.
|
|
|
|
['pkgexports/space', { default: 'encoded path' }],
|
|
|
|
// Verifying that normal packages still work with exports turned on.
|
|
|
|
isRequire ? ['baz/index', { default: 'eye catcher' }] : [null],
|
2019-08-02 07:30:32 +02:00
|
|
|
// Fallbacks
|
|
|
|
['pkgexports/fallbackdir/asdf.js', { default: 'asdf' }],
|
|
|
|
['pkgexports/fallbackfile', { default: 'asdf' }],
|
2019-10-14 01:27:39 +02:00
|
|
|
// Conditional split for require
|
|
|
|
['pkgexports/condition', isRequire ? { default: 'encoded path' } :
|
|
|
|
{ default: 'asdf' }],
|
|
|
|
// String exports sugar
|
|
|
|
['pkgexports-sugar', { default: 'main' }],
|
|
|
|
// Conditional object exports sugar
|
|
|
|
['pkgexports-sugar2', isRequire ? { default: 'not-exported' } :
|
2019-12-05 03:14:48 +01:00
|
|
|
{ default: 'main' }],
|
2019-12-17 07:58:19 +01:00
|
|
|
// Resolve self
|
|
|
|
['pkgexports/resolve-self', isRequire ?
|
|
|
|
{ default: 'self-cjs' } : { default: 'self-mjs' }],
|
|
|
|
// Resolve self sugar
|
2020-02-03 12:31:12 +01:00
|
|
|
['pkgexports-sugar', { default: 'main' }],
|
2019-07-24 06:25:24 +02:00
|
|
|
]);
|
2019-10-14 01:27:39 +02:00
|
|
|
|
2020-03-19 00:49:49 +01:00
|
|
|
if (isRequire) {
|
|
|
|
validSpecifiers.set('pkgexports/subpath/file', { default: 'file' });
|
|
|
|
validSpecifiers.set('pkgexports/subpath/dir1', { default: 'main' });
|
|
|
|
validSpecifiers.set('pkgexports/subpath/dir1/', { default: 'main' });
|
|
|
|
validSpecifiers.set('pkgexports/subpath/dir2', { default: 'index' });
|
|
|
|
validSpecifiers.set('pkgexports/subpath/dir2/', { default: 'index' });
|
|
|
|
}
|
|
|
|
|
2019-07-24 06:25:24 +02:00
|
|
|
for (const [validSpecifier, expected] of validSpecifiers) {
|
|
|
|
if (validSpecifier === null) continue;
|
|
|
|
|
|
|
|
loadFixture(validSpecifier)
|
|
|
|
.then(mustCall((actual) => {
|
|
|
|
deepStrictEqual({ ...actual }, expected);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2019-08-02 07:30:32 +02:00
|
|
|
const undefinedExports = new Map([
|
|
|
|
// There's no such export - so there's nothing to do.
|
|
|
|
['pkgexports/missing', './missing'],
|
|
|
|
// The file exists but isn't exported. The exports is a number which counts
|
|
|
|
// as a non-null value without any properties, just like `{}`.
|
|
|
|
['pkgexports-number/hidden.js', './hidden.js'],
|
2019-10-14 01:27:39 +02:00
|
|
|
// Sugar cases still encapsulate
|
|
|
|
['pkgexports-sugar/not-exported.js', './not-exported.js'],
|
2019-12-05 03:14:48 +01:00
|
|
|
['pkgexports-sugar2/not-exported.js', './not-exported.js'],
|
2020-02-03 12:31:12 +01:00
|
|
|
// Conditional exports with no match are "not exported" errors
|
|
|
|
['pkgexports/invalid1', './invalid1'],
|
|
|
|
['pkgexports/invalid4', './invalid4'],
|
2020-04-14 07:36:15 +02:00
|
|
|
// Null mapping
|
|
|
|
['pkgexports/null', './null'],
|
|
|
|
['pkgexports/null/subpath', './null/subpath'],
|
|
|
|
// Empty fallback
|
|
|
|
['pkgexports/nofallback1', './nofallback1'],
|
2019-08-02 07:30:32 +02:00
|
|
|
]);
|
2019-07-24 06:25:24 +02:00
|
|
|
|
2019-08-02 07:30:32 +02:00
|
|
|
const invalidExports = new Map([
|
2020-02-03 12:31:12 +01:00
|
|
|
// Directory mappings require a trailing / to work
|
|
|
|
['pkgexports/missingtrailer/x', './missingtrailer/'],
|
2019-08-02 07:30:32 +02:00
|
|
|
// This path steps back inside the package but goes through an exports
|
|
|
|
// target that escapes the package, so we still catch that as invalid
|
2020-02-03 12:31:12 +01:00
|
|
|
['pkgexports/belowdir/pkgexports/asdf.js', './belowdir/'],
|
2019-08-02 07:30:32 +02:00
|
|
|
// This target file steps below the package
|
|
|
|
['pkgexports/belowfile', './belowfile'],
|
2020-04-14 07:36:15 +02:00
|
|
|
// Invalid targets
|
2019-08-02 07:30:32 +02:00
|
|
|
['pkgexports/invalid2', './invalid2'],
|
|
|
|
['pkgexports/invalid3', './invalid3'],
|
2020-04-17 23:13:16 +02:00
|
|
|
['pkgexports/invalid5', 'invalid5'],
|
2019-08-02 07:30:32 +02:00
|
|
|
// Missing / invalid fallbacks
|
|
|
|
['pkgexports/nofallback2', './nofallback2'],
|
|
|
|
// Reaching into nested node_modules
|
|
|
|
['pkgexports/nodemodules', './nodemodules'],
|
2020-02-03 12:31:12 +01:00
|
|
|
// Self resolve invalid
|
|
|
|
['pkgexports/resolve-self-invalid', './invalid2'],
|
|
|
|
]);
|
|
|
|
|
|
|
|
const invalidSpecifiers = new Map([
|
|
|
|
// Even though 'pkgexports/sub/asdf.js' works, alternate "path-like"
|
|
|
|
// variants do not to prevent confusion and accidental loopholes.
|
|
|
|
['pkgexports/sub/./../asdf.js', './sub/./../asdf.js'],
|
2019-08-02 07:30:32 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
for (const [specifier, subpath] of undefinedExports) {
|
|
|
|
loadFixture(specifier).catch(mustCall((err) => {
|
2020-02-03 12:31:12 +01:00
|
|
|
strictEqual(err.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED');
|
|
|
|
assertStartsWith(err.message, 'Package subpath ');
|
|
|
|
assertIncludes(err.message, subpath);
|
2019-08-02 07:30:32 +02:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const [specifier, subpath] of invalidExports) {
|
|
|
|
loadFixture(specifier).catch(mustCall((err) => {
|
2020-02-03 12:31:12 +01:00
|
|
|
strictEqual(err.code, 'ERR_INVALID_PACKAGE_TARGET');
|
|
|
|
assertStartsWith(err.message, 'Invalid "exports"');
|
|
|
|
assertIncludes(err.message, subpath);
|
2020-04-17 23:13:16 +02:00
|
|
|
if (!subpath.startsWith('./')) {
|
|
|
|
assertIncludes(err.message, 'targets must start with');
|
|
|
|
}
|
2020-02-03 12:31:12 +01:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const [specifier, subpath] of invalidSpecifiers) {
|
|
|
|
loadFixture(specifier).catch(mustCall((err) => {
|
|
|
|
strictEqual(err.code, 'ERR_INVALID_MODULE_SPECIFIER');
|
|
|
|
assertStartsWith(err.message, 'Package subpath ');
|
|
|
|
assertIncludes(err.message, subpath);
|
2019-08-02 07:30:32 +02:00
|
|
|
}));
|
|
|
|
}
|
2019-07-24 06:25:24 +02:00
|
|
|
|
2019-12-05 03:14:48 +01:00
|
|
|
// Conditional export, even with no match, should still be used instead
|
|
|
|
// of falling back to main
|
|
|
|
if (isRequire) {
|
|
|
|
loadFixture('pkgexports-main').catch(mustCall((err) => {
|
2020-02-03 12:31:12 +01:00
|
|
|
strictEqual(err.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED');
|
|
|
|
assertStartsWith(err.message, 'No "exports" main ');
|
2019-12-05 03:14:48 +01:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2020-03-19 00:49:49 +01:00
|
|
|
const notFoundExports = new Map([
|
|
|
|
// Non-existing file
|
|
|
|
['pkgexports/sub/not-a-file.js', 'pkgexports/sub/not-a-file.js'],
|
|
|
|
// No extension lookups
|
|
|
|
['pkgexports/no-ext', 'pkgexports/no-ext'],
|
|
|
|
]);
|
|
|
|
|
|
|
|
if (!isRequire) {
|
|
|
|
notFoundExports.set('pkgexports/subpath/file', 'pkgexports/subpath/file');
|
|
|
|
notFoundExports.set('pkgexports/subpath/dir1', 'pkgexports/subpath/dir1');
|
|
|
|
notFoundExports.set('pkgexports/subpath/dir2', 'pkgexports/subpath/dir2');
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const [specifier, request] of notFoundExports) {
|
|
|
|
loadFixture(specifier).catch(mustCall((err) => {
|
|
|
|
strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND');
|
|
|
|
// ESM returns a full file path
|
|
|
|
assertStartsWith(err.message, isRequire ?
|
|
|
|
`Cannot find module '${request}'` :
|
|
|
|
'Cannot find module');
|
|
|
|
}));
|
|
|
|
}
|
2019-07-24 06:25:24 +02:00
|
|
|
|
2019-10-14 01:27:39 +02:00
|
|
|
// The use of %2F escapes in paths fails loading
|
2019-08-02 07:30:32 +02:00
|
|
|
loadFixture('pkgexports/sub/..%2F..%2Fbar.js').catch(mustCall((err) => {
|
2020-03-07 06:12:08 +01:00
|
|
|
strictEqual(err.code, 'ERR_INVALID_FILE_URL_PATH');
|
2019-07-24 06:25:24 +02:00
|
|
|
}));
|
2019-10-14 01:27:39 +02:00
|
|
|
|
2019-12-17 20:00:21 +01:00
|
|
|
// Package export with numeric index properties must throw a validation error
|
|
|
|
loadFixture('pkgexports-numeric').catch(mustCall((err) => {
|
|
|
|
strictEqual(err.code, 'ERR_INVALID_PACKAGE_CONFIG');
|
|
|
|
}));
|
|
|
|
|
2019-10-14 01:27:39 +02:00
|
|
|
// Sugar conditional exports main mixed failure case
|
|
|
|
loadFixture('pkgexports-sugar-fail').catch(mustCall((err) => {
|
|
|
|
strictEqual(err.code, 'ERR_INVALID_PACKAGE_CONFIG');
|
2020-02-03 12:31:12 +01:00
|
|
|
assertStartsWith(err.message, 'Invalid package');
|
2019-10-14 01:27:39 +02:00
|
|
|
assertIncludes(err.message, '"exports" cannot contain some keys starting ' +
|
|
|
|
'with \'.\' and some not. The exports object must either be an object of ' +
|
|
|
|
'package subpath keys or an object of main entry condition name keys ' +
|
|
|
|
'only.');
|
|
|
|
}));
|
2019-07-24 06:25:24 +02:00
|
|
|
});
|
|
|
|
|
2019-08-20 05:59:25 +02:00
|
|
|
const { requireFromInside, importFromInside } = fromInside;
|
|
|
|
[importFromInside, requireFromInside].forEach((loadFromInside) => {
|
|
|
|
const validSpecifiers = new Map([
|
|
|
|
// A file not visible from outside of the package
|
|
|
|
['../not-exported.js', { default: 'not-exported' }],
|
|
|
|
// Part of the public interface
|
2019-12-17 07:58:19 +01:00
|
|
|
['pkgexports/valid-cjs', { default: 'asdf' }],
|
2019-08-20 05:59:25 +02:00
|
|
|
]);
|
|
|
|
for (const [validSpecifier, expected] of validSpecifiers) {
|
|
|
|
if (validSpecifier === null) continue;
|
|
|
|
|
|
|
|
loadFromInside(validSpecifier)
|
|
|
|
.then(mustCall((actual) => {
|
|
|
|
deepStrictEqual({ ...actual }, expected);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-07-24 06:25:24 +02:00
|
|
|
function assertStartsWith(actual, expected) {
|
|
|
|
const start = actual.toString().substr(0, expected.length);
|
|
|
|
strictEqual(start, expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
function assertIncludes(actual, expected) {
|
2019-10-14 01:27:39 +02:00
|
|
|
ok(actual.toString().indexOf(expected) !== -1,
|
2019-07-24 06:25:24 +02:00
|
|
|
`${JSON.stringify(actual)} includes ${JSON.stringify(expected)}`);
|
|
|
|
}
|