1// Flags: --expose-internals
2
3/**
4 * This test ensures defaultResolve returns the found module format in the
5 * return object in the form:
6 * { url: <url_value>, format: <'module'|'commonjs'|undefined> };
7 */
8
9import * as common from '../common/index.mjs';
10import tmpdir from '../common/tmpdir.js';
11import * as fixtures from '../common/fixtures.mjs';
12import path from 'path';
13import fs from 'fs';
14import url from 'url';
15import process from 'process';
16
17if (!common.isMainThread) {
18  common.skip(
19    'test-esm-resolve-type.mjs: process.chdir is not available in Workers'
20  );
21}
22
23import assert from 'assert';
24import internalResolve from 'node:internal/modules/esm/resolve';
25const {
26  defaultResolve: resolve
27} = internalResolve;
28
29const rel = (file) => path.join(tmpdir.path, file);
30const previousCwd = process.cwd();
31const nmDir = rel('node_modules');
32
33try {
34  tmpdir.refresh();
35  process.chdir(tmpdir.path);
36  /**
37   * ensure that resolving by full path does not return the format
38   * with the defaultResolver
39   */
40  [
41    [ '/es-modules/package-type-module/index.js', 'module' ],
42    [ '/es-modules/package-type-commonjs/index.js', 'commonjs' ],
43    [ '/es-modules/package-without-type/index.js', 'commonjs' ],
44    [ '/es-modules/package-without-pjson/index.js', 'commonjs' ],
45  ].forEach(([ testScript, expectedType ]) => {
46    const resolvedPath = path.resolve(fixtures.path(testScript));
47    const resolveResult = resolve(url.pathToFileURL(resolvedPath));
48    assert.strictEqual(resolveResult.format, expectedType);
49  });
50
51  /**
52   * create a test module and try to resolve it by module name.
53   * check the result is as expected
54   *
55   * for test-module-ne: everything .js that is not 'module' is 'commonjs'
56   */
57  for (const [ moduleName, moduleExtenstion, moduleType, expectedResolvedType ] of
58    [ [ 'test-module-mainjs', 'js', 'module', 'module'],
59      [ 'test-module-mainmjs', 'mjs', 'module', 'module'],
60      [ 'test-module-cjs', 'js', 'commonjs', 'commonjs'],
61      [ 'test-module-ne', 'js', undefined, 'commonjs'],
62    ]) {
63    process.chdir(previousCwd);
64    tmpdir.refresh();
65    process.chdir(tmpdir.path);
66    const createDir = (path) => {
67      if (!fs.existsSync(path)) {
68        fs.mkdirSync(path);
69      }
70    };
71
72    const mDir = rel(`node_modules/${moduleName}`);
73    const subDir = rel(`node_modules/${moduleName}/subdir`);
74    const pkg = rel(`node_modules/${moduleName}/package.json`);
75    const script = rel(`node_modules/${moduleName}/subdir/mainfile.${moduleExtenstion}`);
76
77    createDir(nmDir);
78    createDir(mDir);
79    createDir(subDir);
80    const pkgJsonContent = {
81      ...(moduleType !== undefined) && { type: moduleType },
82      main: `subdir/mainfile.${moduleExtenstion}`
83    };
84    fs.writeFileSync(pkg, JSON.stringify(pkgJsonContent));
85    fs.writeFileSync(script,
86                     'export function esm-resolve-tester() {return 42}');
87
88    const resolveResult = resolve(`${moduleName}`);
89    assert.strictEqual(resolveResult.format, expectedResolvedType);
90
91    fs.rmSync(nmDir, { recursive: true, force: true });
92  }
93
94  // Helpers
95  const createDir = (path) => {
96    if (!fs.existsSync(path)) {
97      fs.mkdirSync(path);
98    }
99  };
100
101  {
102    // Create a dummy dual package
103    //
104    /**
105     * this creates the following directory structure:
106     *
107     * ./node_modules:
108     *   |-> my-dual-package
109     *       |-> es
110     *           |-> index.js
111     *           |-> package.json [2]
112     *       |-> lib
113     *           |-> index.js
114     *       |->package.json [1]
115     *
116     * in case the package is imported:
117     *    import * as my-package from 'my-dual-package'
118     * it will cause the resolve method to return:
119     *  {
120     *     url: '<base_path>/node_modules/my-dual-package/es/index.js',
121     *     format: 'module'
122     *  }
123     *
124     *  following testcase ensures that resolve works correctly in this case
125     *  returning the information as specified above. Source for 'url' value
126     *  is [1], source for 'format' value is [2]
127     */
128
129    const moduleName = 'my-dual-package';
130
131    const mDir = rel(`node_modules/${moduleName}`);
132    const esSubDir = rel(`node_modules/${moduleName}/es`);
133    const cjsSubDir = rel(`node_modules/${moduleName}/lib`);
134    const pkg = rel(`node_modules/${moduleName}/package.json`);
135    const esmPkg = rel(`node_modules/${moduleName}/es/package.json`);
136    const esScript = rel(`node_modules/${moduleName}/es/index.js`);
137    const cjsScript = rel(`node_modules/${moduleName}/lib/index.js`);
138
139    createDir(nmDir);
140    createDir(mDir);
141    createDir(esSubDir);
142    createDir(cjsSubDir);
143
144    const mainPkgJsonContent = {
145      type: 'commonjs',
146      exports: {
147        '.': {
148          'require': './lib/index.js',
149          'import': './es/index.js',
150          'default': './lib/index.js'
151        },
152        './package.json': './package.json',
153      }
154    };
155    const esmPkgJsonContent = {
156      type: 'module'
157    };
158
159    fs.writeFileSync(pkg, JSON.stringify(mainPkgJsonContent));
160    fs.writeFileSync(esmPkg, JSON.stringify(esmPkgJsonContent));
161    fs.writeFileSync(
162      esScript,
163      'export function esm-resolve-tester() {return 42}'
164    );
165    fs.writeFileSync(
166      cjsScript,
167      'module.exports = {esm-resolve-tester: () => {return 42}}'
168    );
169
170    // test the resolve
171    const resolveResult = resolve(`${moduleName}`);
172    assert.strictEqual(resolveResult.format, 'module');
173    assert.ok(resolveResult.url.includes('my-dual-package/es/index.js'));
174  }
175
176  // TestParameters are ModuleName, mainRequireScript, mainImportScript,
177  // mainPackageType, subdirPkgJsonType, expectedResolvedFormat, mainSuffix
178  [
179    [ 'mjs-mod-mod', 'index.js', 'index.mjs', 'module', 'module', 'module'],
180    [ 'mjs-com-com', 'idx.js', 'idx.mjs', 'commonjs', 'commonjs', 'module'],
181    [ 'mjs-mod-com', 'index.js', 'imp.mjs', 'module', 'commonjs', 'module'],
182    [ 'cjs-mod-mod', 'index.cjs', 'imp.cjs', 'module', 'module', 'commonjs'],
183    [ 'js-com-com', 'index.js', 'imp.js', 'commonjs', 'commonjs', 'commonjs'],
184    [ 'js-com-mod', 'index.js', 'imp.js', 'commonjs', 'module', 'module'],
185    [ 'qmod', 'index.js', 'imp.js', 'commonjs', 'module', 'module', '?k=v'],
186    [ 'hmod', 'index.js', 'imp.js', 'commonjs', 'module', 'module', '#Key'],
187    [ 'qhmod', 'index.js', 'imp.js', 'commonjs', 'module', 'module', '?k=v#h'],
188    [ 'ts-mod-com', 'index.js', 'imp.ts', 'module', 'commonjs', undefined],
189  ].forEach((testVariant) => {
190    const [
191      moduleName,
192      mainRequireScript,
193      mainImportScript,
194      mainPackageType,
195      subdirPackageType,
196      expectedResolvedFormat,
197      mainSuffix = '' ] = testVariant;
198
199    const mDir = rel(`node_modules/${moduleName}`);
200    const subDir = rel(`node_modules/${moduleName}/subdir`);
201    const pkg = rel(`node_modules/${moduleName}/package.json`);
202    const subdirPkg = rel(`node_modules/${moduleName}/subdir/package.json`);
203    const esScript = rel(`node_modules/${moduleName}/subdir/${mainImportScript}`);
204    const cjsScript = rel(`node_modules/${moduleName}/subdir/${mainRequireScript}`);
205
206    createDir(nmDir);
207    createDir(mDir);
208    createDir(subDir);
209
210    const mainPkgJsonContent = {
211      type: mainPackageType,
212      exports: {
213        '.': {
214          'require': `./subdir/${mainRequireScript}${mainSuffix}`,
215          'import': `./subdir/${mainImportScript}${mainSuffix}`,
216          'default': `./subdir/${mainRequireScript}${mainSuffix}`
217        },
218        './package.json': './package.json',
219      }
220    };
221    const subdirPkgJsonContent = {
222      type: `${subdirPackageType}`
223    };
224
225    fs.writeFileSync(pkg, JSON.stringify(mainPkgJsonContent));
226    fs.writeFileSync(subdirPkg, JSON.stringify(subdirPkgJsonContent));
227    fs.writeFileSync(
228      esScript,
229      'export function esm-resolve-tester() {return 42}'
230    );
231    fs.writeFileSync(
232      cjsScript,
233      'module.exports = {esm-resolve-tester: () => {return 42}}'
234    );
235
236    // test the resolve
237    const resolveResult = resolve(`${moduleName}`);
238    assert.strictEqual(resolveResult.format, expectedResolvedFormat);
239    assert.ok(resolveResult.url.endsWith(`${moduleName}/subdir/${mainImportScript}${mainSuffix}`));
240  });
241
242} finally {
243  process.chdir(previousCwd);
244  fs.rmSync(nmDir, { recursive: true, force: true });
245}
246