11cb0ef41Sopenharmony_ciimport { mustCall } from '../common/index.mjs';
21cb0ef41Sopenharmony_ciimport { ok, deepStrictEqual, strictEqual } from 'assert';
31cb0ef41Sopenharmony_ciimport { sep } from 'path';
41cb0ef41Sopenharmony_ci
51cb0ef41Sopenharmony_ciimport { requireFixture, importFixture } from '../fixtures/pkgexports.mjs';
61cb0ef41Sopenharmony_ciimport fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
71cb0ef41Sopenharmony_ci
81cb0ef41Sopenharmony_ci[requireFixture, importFixture].forEach((loadFixture) => {
91cb0ef41Sopenharmony_ci  const isRequire = loadFixture === requireFixture;
101cb0ef41Sopenharmony_ci
111cb0ef41Sopenharmony_ci  const validSpecifiers = new Map([
121cb0ef41Sopenharmony_ci    // A simple mapping of a path.
131cb0ef41Sopenharmony_ci    ['pkgexports/valid-cjs', { default: 'asdf' }],
141cb0ef41Sopenharmony_ci    // A mapping pointing to a file that needs special encoding (%20) in URLs.
151cb0ef41Sopenharmony_ci    ['pkgexports/space', { default: 'encoded path' }],
161cb0ef41Sopenharmony_ci    // Verifying that normal packages still work with exports turned on.
171cb0ef41Sopenharmony_ci    isRequire ? ['baz/index', { default: 'eye catcher' }] : [null],
181cb0ef41Sopenharmony_ci    // Fallbacks
191cb0ef41Sopenharmony_ci    ['pkgexports/fallbackdir/asdf.js', { default: 'asdf' }],
201cb0ef41Sopenharmony_ci    ['pkgexports/fallbackfile', { default: 'asdf' }],
211cb0ef41Sopenharmony_ci    // Conditional split for require
221cb0ef41Sopenharmony_ci    ['pkgexports/condition', isRequire ? { default: 'encoded path' } :
231cb0ef41Sopenharmony_ci      { default: 'asdf' }],
241cb0ef41Sopenharmony_ci    // String exports sugar
251cb0ef41Sopenharmony_ci    ['pkgexports-sugar', { default: 'main' }],
261cb0ef41Sopenharmony_ci    // Conditional object exports sugar
271cb0ef41Sopenharmony_ci    ['pkgexports-sugar2', isRequire ? { default: 'not-exported' } :
281cb0ef41Sopenharmony_ci      { default: 'main' }],
291cb0ef41Sopenharmony_ci    // Resolve self
301cb0ef41Sopenharmony_ci    ['pkgexports/resolve-self', isRequire ?
311cb0ef41Sopenharmony_ci      { default: 'self-cjs' } : { default: 'self-mjs' }],
321cb0ef41Sopenharmony_ci    // Resolve self sugar
331cb0ef41Sopenharmony_ci    ['pkgexports-sugar', { default: 'main' }],
341cb0ef41Sopenharmony_ci    // Path patterns
351cb0ef41Sopenharmony_ci    ['pkgexports/subpath/sub-dir1', { default: 'main' }],
361cb0ef41Sopenharmony_ci    ['pkgexports/subpath/sub-dir1.js', { default: 'main' }],
371cb0ef41Sopenharmony_ci    ['pkgexports/features/dir1', { default: 'main' }],
381cb0ef41Sopenharmony_ci    ['pkgexports/dir1/dir1/trailer', { default: 'main' }],
391cb0ef41Sopenharmony_ci    ['pkgexports/dir2/dir2/trailer', { default: 'index' }],
401cb0ef41Sopenharmony_ci    ['pkgexports/a/dir1/dir1', { default: 'main' }],
411cb0ef41Sopenharmony_ci    ['pkgexports/a/b/dir1/dir1', { default: 'main' }],
421cb0ef41Sopenharmony_ci
431cb0ef41Sopenharmony_ci    // Deprecated:
441cb0ef41Sopenharmony_ci    // Double slashes:
451cb0ef41Sopenharmony_ci    ['pkgexports/a//dir1/dir1', { default: 'main' }],
461cb0ef41Sopenharmony_ci    // double slash target
471cb0ef41Sopenharmony_ci    ['pkgexports/doubleslash', { default: 'asdf' }],
481cb0ef41Sopenharmony_ci    // Null target with several slashes
491cb0ef41Sopenharmony_ci    ['pkgexports/sub//internal/test.js', { default: 'internal only' }],
501cb0ef41Sopenharmony_ci    ['pkgexports/sub//internal//test.js', { default: 'internal only' }],
511cb0ef41Sopenharmony_ci    ['pkgexports/sub/////internal/////test.js', { default: 'internal only' }],
521cb0ef41Sopenharmony_ci    // trailing slash
531cb0ef41Sopenharmony_ci    ['pkgexports/trailing-pattern-slash/',
541cb0ef41Sopenharmony_ci     { default: 'trailing-pattern-slash' }],
551cb0ef41Sopenharmony_ci  ]);
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_ci  if (!isRequire) {
581cb0ef41Sopenharmony_ci    // No exports or main field
591cb0ef41Sopenharmony_ci    validSpecifiers.set('no_exports', { default: 'index' });
601cb0ef41Sopenharmony_ci    // Main field without extension
611cb0ef41Sopenharmony_ci    validSpecifiers.set('default_index', { default: 'main' });
621cb0ef41Sopenharmony_ci  }
631cb0ef41Sopenharmony_ci
641cb0ef41Sopenharmony_ci  for (const [validSpecifier, expected] of validSpecifiers) {
651cb0ef41Sopenharmony_ci    if (validSpecifier === null) continue;
661cb0ef41Sopenharmony_ci
671cb0ef41Sopenharmony_ci    loadFixture(validSpecifier)
681cb0ef41Sopenharmony_ci      .then(mustCall((actual) => {
691cb0ef41Sopenharmony_ci        deepStrictEqual({ ...actual }, expected);
701cb0ef41Sopenharmony_ci      }));
711cb0ef41Sopenharmony_ci  }
721cb0ef41Sopenharmony_ci
731cb0ef41Sopenharmony_ci  const undefinedExports = new Map([
741cb0ef41Sopenharmony_ci    // There's no such export - so there's nothing to do.
751cb0ef41Sopenharmony_ci    ['pkgexports/missing', './missing'],
761cb0ef41Sopenharmony_ci    // The file exists but isn't exported. The exports is a number which counts
771cb0ef41Sopenharmony_ci    // as a non-null value without any properties, just like `{}`.
781cb0ef41Sopenharmony_ci    ['pkgexports-number/hidden.js', './hidden.js'],
791cb0ef41Sopenharmony_ci    // Sugar cases still encapsulate
801cb0ef41Sopenharmony_ci    ['pkgexports-sugar/not-exported.js', './not-exported.js'],
811cb0ef41Sopenharmony_ci    ['pkgexports-sugar2/not-exported.js', './not-exported.js'],
821cb0ef41Sopenharmony_ci    // Conditional exports with no match are "not exported" errors
831cb0ef41Sopenharmony_ci    ['pkgexports/invalid1', './invalid1'],
841cb0ef41Sopenharmony_ci    ['pkgexports/invalid4', './invalid4'],
851cb0ef41Sopenharmony_ci    // Null mapping
861cb0ef41Sopenharmony_ci    ['pkgexports/sub/internal/test.js', './sub/internal/test.js'],
871cb0ef41Sopenharmony_ci    ['pkgexports/sub/internal//test.js', './sub/internal//test.js'],
881cb0ef41Sopenharmony_ci    ['pkgexports/null', './null'],
891cb0ef41Sopenharmony_ci    ['pkgexports//null', './/null'],
901cb0ef41Sopenharmony_ci    ['pkgexports/////null', './////null'],
911cb0ef41Sopenharmony_ci    ['pkgexports/null/subpath', './null/subpath'],
921cb0ef41Sopenharmony_ci    // Empty fallback
931cb0ef41Sopenharmony_ci    ['pkgexports/nofallback1', './nofallback1'],
941cb0ef41Sopenharmony_ci    // Non pattern matches
951cb0ef41Sopenharmony_ci    ['pkgexports/trailer', './trailer'],
961cb0ef41Sopenharmony_ci  ]);
971cb0ef41Sopenharmony_ci
981cb0ef41Sopenharmony_ci  const invalidExports = new Map([
991cb0ef41Sopenharmony_ci    // This path steps back inside the package but goes through an exports
1001cb0ef41Sopenharmony_ci    // target that escapes the package, so we still catch that as invalid
1011cb0ef41Sopenharmony_ci    ['pkgexports/belowdir/pkgexports/asdf.js', './belowdir/'],
1021cb0ef41Sopenharmony_ci    // This target file steps below the package
1031cb0ef41Sopenharmony_ci    ['pkgexports/belowfile', './belowfile'],
1041cb0ef41Sopenharmony_ci    // Invalid targets
1051cb0ef41Sopenharmony_ci    ['pkgexports/invalid2', './invalid2'],
1061cb0ef41Sopenharmony_ci    ['pkgexports/invalid3', './invalid3'],
1071cb0ef41Sopenharmony_ci    ['pkgexports/invalid5', 'invalid5'],
1081cb0ef41Sopenharmony_ci    // Missing / invalid fallbacks
1091cb0ef41Sopenharmony_ci    ['pkgexports/nofallback2', './nofallback2'],
1101cb0ef41Sopenharmony_ci    // Reaching into nested node_modules
1111cb0ef41Sopenharmony_ci    ['pkgexports/nodemodules', './nodemodules'],
1121cb0ef41Sopenharmony_ci    // Self resolve invalid
1131cb0ef41Sopenharmony_ci    ['pkgexports/resolve-self-invalid', './invalid2'],
1141cb0ef41Sopenharmony_ci  ]);
1151cb0ef41Sopenharmony_ci
1161cb0ef41Sopenharmony_ci  const invalidSpecifiers = new Map([
1171cb0ef41Sopenharmony_ci    // Even though 'pkgexports/sub/asdf.js' works, alternate "path-like"
1181cb0ef41Sopenharmony_ci    // variants do not to prevent confusion and accidental loopholes.
1191cb0ef41Sopenharmony_ci    ['pkgexports/sub/./../asdf.js', './sub/./../asdf.js'],
1201cb0ef41Sopenharmony_ci    // Cannot reach into node_modules, even percent encoded
1211cb0ef41Sopenharmony_ci    ['pkgexports/sub/no%64e_modules', './sub/no%64e_modules'],
1221cb0ef41Sopenharmony_ci    // Cannot backtrack below exposed path, even with percent encoded "."
1231cb0ef41Sopenharmony_ci    ['pkgexports/sub/%2e./asdf', './asdf'],
1241cb0ef41Sopenharmony_ci  ]);
1251cb0ef41Sopenharmony_ci
1261cb0ef41Sopenharmony_ci  for (const [specifier, subpath] of undefinedExports) {
1271cb0ef41Sopenharmony_ci    loadFixture(specifier).catch(mustCall((err) => {
1281cb0ef41Sopenharmony_ci      strictEqual(err.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED');
1291cb0ef41Sopenharmony_ci      assertStartsWith(err.message, 'Package subpath ');
1301cb0ef41Sopenharmony_ci      assertIncludes(err.message, subpath);
1311cb0ef41Sopenharmony_ci    }));
1321cb0ef41Sopenharmony_ci  }
1331cb0ef41Sopenharmony_ci
1341cb0ef41Sopenharmony_ci  for (const [specifier, subpath] of invalidExports) {
1351cb0ef41Sopenharmony_ci    loadFixture(specifier).catch(mustCall((err) => {
1361cb0ef41Sopenharmony_ci      strictEqual(err.code, 'ERR_INVALID_PACKAGE_TARGET');
1371cb0ef41Sopenharmony_ci      assertStartsWith(err.message, 'Invalid "exports"');
1381cb0ef41Sopenharmony_ci      assertIncludes(err.message, subpath);
1391cb0ef41Sopenharmony_ci      if (!subpath.startsWith('./')) {
1401cb0ef41Sopenharmony_ci        assertIncludes(err.message, 'targets must start with');
1411cb0ef41Sopenharmony_ci      }
1421cb0ef41Sopenharmony_ci    }));
1431cb0ef41Sopenharmony_ci  }
1441cb0ef41Sopenharmony_ci
1451cb0ef41Sopenharmony_ci  for (const [specifier, subpath] of invalidSpecifiers) {
1461cb0ef41Sopenharmony_ci    loadFixture(specifier).catch(mustCall((err) => {
1471cb0ef41Sopenharmony_ci      strictEqual(err.code, 'ERR_INVALID_MODULE_SPECIFIER');
1481cb0ef41Sopenharmony_ci      assertStartsWith(err.message, 'Invalid module ');
1491cb0ef41Sopenharmony_ci      assertIncludes(err.message, 'is not a valid match in pattern');
1501cb0ef41Sopenharmony_ci      assertIncludes(err.message, subpath);
1511cb0ef41Sopenharmony_ci    }));
1521cb0ef41Sopenharmony_ci  }
1531cb0ef41Sopenharmony_ci
1541cb0ef41Sopenharmony_ci  // Conditional export, even with no match, should still be used instead
1551cb0ef41Sopenharmony_ci  // of falling back to main
1561cb0ef41Sopenharmony_ci  if (isRequire) {
1571cb0ef41Sopenharmony_ci    loadFixture('pkgexports-main').catch(mustCall((err) => {
1581cb0ef41Sopenharmony_ci      strictEqual(err.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED');
1591cb0ef41Sopenharmony_ci      assertStartsWith(err.message, 'No "exports" main ');
1601cb0ef41Sopenharmony_ci    }));
1611cb0ef41Sopenharmony_ci  }
1621cb0ef41Sopenharmony_ci
1631cb0ef41Sopenharmony_ci  const notFoundExports = new Map([
1641cb0ef41Sopenharmony_ci    // Non-existing file
1651cb0ef41Sopenharmony_ci    ['pkgexports/sub/not-a-file.js', `pkgexports${sep}not-a-file.js`],
1661cb0ef41Sopenharmony_ci    // No extension lookups
1671cb0ef41Sopenharmony_ci    ['pkgexports/no-ext', `pkgexports${sep}asdf`],
1681cb0ef41Sopenharmony_ci    // Pattern specificity
1691cb0ef41Sopenharmony_ci    ['pkgexports/dir2/trailer', `subpath${sep}dir2.js`],
1701cb0ef41Sopenharmony_ci    // Pattern double $$ escaping!
1711cb0ef41Sopenharmony_ci    ['pkgexports/a/$$', `subpath${sep}$$.js`],
1721cb0ef41Sopenharmony_ci  ]);
1731cb0ef41Sopenharmony_ci
1741cb0ef41Sopenharmony_ci  if (!isRequire) {
1751cb0ef41Sopenharmony_ci    const onDirectoryImport = (err) => {
1761cb0ef41Sopenharmony_ci      strictEqual(err.code, 'ERR_UNSUPPORTED_DIR_IMPORT');
1771cb0ef41Sopenharmony_ci      assertStartsWith(err.message, 'Directory import');
1781cb0ef41Sopenharmony_ci    };
1791cb0ef41Sopenharmony_ci    loadFixture('pkgexports/subpath/dir1').catch(mustCall(onDirectoryImport));
1801cb0ef41Sopenharmony_ci    loadFixture('pkgexports/subpath/dir2').catch(mustCall(onDirectoryImport));
1811cb0ef41Sopenharmony_ci  }
1821cb0ef41Sopenharmony_ci
1831cb0ef41Sopenharmony_ci  for (const [specifier, request] of notFoundExports) {
1841cb0ef41Sopenharmony_ci    loadFixture(specifier).catch(mustCall((err) => {
1851cb0ef41Sopenharmony_ci      strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND');
1861cb0ef41Sopenharmony_ci      assertIncludes(err.message, request);
1871cb0ef41Sopenharmony_ci      assertStartsWith(err.message, 'Cannot find module');
1881cb0ef41Sopenharmony_ci    }));
1891cb0ef41Sopenharmony_ci  }
1901cb0ef41Sopenharmony_ci
1911cb0ef41Sopenharmony_ci  // The use of %2F and %5C escapes in paths fails loading
1921cb0ef41Sopenharmony_ci  loadFixture('pkgexports/sub/..%2F..%2Fbar.js').catch(mustCall((err) => {
1931cb0ef41Sopenharmony_ci    strictEqual(err.code, 'ERR_INVALID_MODULE_SPECIFIER');
1941cb0ef41Sopenharmony_ci  }));
1951cb0ef41Sopenharmony_ci  loadFixture('pkgexports/sub/..%5C..%5Cbar.js').catch(mustCall((err) => {
1961cb0ef41Sopenharmony_ci    strictEqual(err.code, 'ERR_INVALID_MODULE_SPECIFIER');
1971cb0ef41Sopenharmony_ci  }));
1981cb0ef41Sopenharmony_ci
1991cb0ef41Sopenharmony_ci  // Package export with numeric index properties must throw a validation error
2001cb0ef41Sopenharmony_ci  loadFixture('pkgexports-numeric').catch(mustCall((err) => {
2011cb0ef41Sopenharmony_ci    strictEqual(err.code, 'ERR_INVALID_PACKAGE_CONFIG');
2021cb0ef41Sopenharmony_ci  }));
2031cb0ef41Sopenharmony_ci
2041cb0ef41Sopenharmony_ci  // Sugar conditional exports main mixed failure case
2051cb0ef41Sopenharmony_ci  loadFixture('pkgexports-sugar-fail').catch(mustCall((err) => {
2061cb0ef41Sopenharmony_ci    strictEqual(err.code, 'ERR_INVALID_PACKAGE_CONFIG');
2071cb0ef41Sopenharmony_ci    assertStartsWith(err.message, 'Invalid package');
2081cb0ef41Sopenharmony_ci    assertIncludes(err.message, '"exports" cannot contain some keys starting ' +
2091cb0ef41Sopenharmony_ci    'with \'.\' and some not. The exports object must either be an object of ' +
2101cb0ef41Sopenharmony_ci    'package subpath keys or an object of main entry condition name keys ' +
2111cb0ef41Sopenharmony_ci    'only.');
2121cb0ef41Sopenharmony_ci  }));
2131cb0ef41Sopenharmony_ci});
2141cb0ef41Sopenharmony_ci
2151cb0ef41Sopenharmony_ciconst { requireFromInside, importFromInside } = fromInside;
2161cb0ef41Sopenharmony_ci[importFromInside, requireFromInside].forEach((loadFromInside) => {
2171cb0ef41Sopenharmony_ci  const validSpecifiers = new Map([
2181cb0ef41Sopenharmony_ci    // A file not visible from outside of the package
2191cb0ef41Sopenharmony_ci    ['../not-exported.js', { default: 'not-exported' }],
2201cb0ef41Sopenharmony_ci    // Part of the public interface
2211cb0ef41Sopenharmony_ci    ['pkgexports/valid-cjs', { default: 'asdf' }],
2221cb0ef41Sopenharmony_ci  ]);
2231cb0ef41Sopenharmony_ci  for (const [validSpecifier, expected] of validSpecifiers) {
2241cb0ef41Sopenharmony_ci    if (validSpecifier === null) continue;
2251cb0ef41Sopenharmony_ci
2261cb0ef41Sopenharmony_ci    loadFromInside(validSpecifier)
2271cb0ef41Sopenharmony_ci      .then(mustCall((actual) => {
2281cb0ef41Sopenharmony_ci        deepStrictEqual({ ...actual }, expected);
2291cb0ef41Sopenharmony_ci      }));
2301cb0ef41Sopenharmony_ci  }
2311cb0ef41Sopenharmony_ci});
2321cb0ef41Sopenharmony_ci
2331cb0ef41Sopenharmony_cifunction assertStartsWith(actual, expected) {
2341cb0ef41Sopenharmony_ci  const start = actual.toString().substr(0, expected.length);
2351cb0ef41Sopenharmony_ci  strictEqual(start, expected);
2361cb0ef41Sopenharmony_ci}
2371cb0ef41Sopenharmony_ci
2381cb0ef41Sopenharmony_cifunction assertIncludes(actual, expected) {
2391cb0ef41Sopenharmony_ci  ok(actual.toString().indexOf(expected) !== -1,
2401cb0ef41Sopenharmony_ci     `${JSON.stringify(actual)} includes ${JSON.stringify(expected)}`);
2411cb0ef41Sopenharmony_ci}
242