'use strict'; const common = require('../common'); const fixtures = require('../common/fixtures'); const tmpdir = require('../common/tmpdir'); const assert = require('node:assert'); const fs = require('node:fs'); const { findPackageJSON } = require('node:module'); const path = require('node:path'); const { describe, it } = require('node:test'); describe('findPackageJSON', () => { // Throws when no arguments are provided it('should throw when no arguments are provided', () => { assert.throws( () => findPackageJSON(), { code: 'ERR_MISSING_ARGS' } ); }); it('should throw when parentLocation is invalid', () => { for (const invalid of [null, {}, [], Symbol(), () => {}, true, false, 1, 0]) { assert.throws( () => findPackageJSON('', invalid), { code: 'ERR_INVALID_ARG_TYPE' }, ); } }); it('should accept a file URL (string), like from `import.meta.resolve()`', () => { const importMetaUrl = `${fixtures.fileURL('whatever.ext')}`; const specifierBase = './packages/root-types-field'; assert.strictEqual( findPackageJSON(`${specifierBase}/index.js`, importMetaUrl), path.toNamespacedPath(fixtures.path(specifierBase, 'package.json')), ); }); it('should accept a file URL instance', () => { const importMetaUrl = fixtures.fileURL('whatever.ext'); const specifierBase = './packages/root-types-field'; assert.strictEqual( findPackageJSON( new URL(`${specifierBase}/index.js`, importMetaUrl), importMetaUrl, ), path.toNamespacedPath(fixtures.path(specifierBase, 'package.json')), ); }); it('should be able to crawl up (CJS)', () => { const pathToMod = fixtures.path('packages/nested/sub-pkg-cjs/index.js'); const parentPkg = require(pathToMod); const pathToParent = path.toNamespacedPath(fixtures.path('packages/nested/package.json')); assert.strictEqual(parentPkg, pathToParent); }); it('should be able to crawl up (ESM)', () => { const pathToMod = fixtures.path('packages/nested/sub-pkg-esm/index.js'); const parentPkg = require(pathToMod).default; // This test is a CJS file const pathToParent = path.toNamespacedPath(fixtures.path('packages/nested/package.json')); assert.strictEqual(parentPkg, pathToParent); }); it('can require via package.json', () => { const pathToMod = fixtures.path('packages/cjs-main-no-index/other.js'); // `require()` falls back to package.json values like "main" to resolve when there is no index const answer = require(pathToMod); assert.strictEqual(answer, 43); }); it('should be able to resolve both root and closest package.json', () => { tmpdir.refresh(); fs.writeFileSync(tmpdir.resolve('entry.mjs'), ` import { findPackageJSON } from 'node:module'; import fs from 'node:fs'; const readPJSON = (locus) => JSON.parse(fs.readFileSync(locus, 'utf8')); const { secretNumberPkgRoot } = readPJSON(findPackageJSON('pkg', import.meta.url)); const { secretNumberSubfolder } = readPJSON(findPackageJSON(import.meta.resolve('pkg'))); const { secretNumberSubfolder2 } = readPJSON(findPackageJSON(import.meta.resolve('pkg2'))); const { secretNumberPkg2 } = readPJSON(findPackageJSON('pkg2', import.meta.url)); console.log(secretNumberPkgRoot, secretNumberSubfolder, secretNumberSubfolder2, secretNumberPkg2); `); const secretNumberPkgRoot = Math.ceil(Math.random() * 999); const secretNumberSubfolder = Math.ceil(Math.random() * 999); const secretNumberSubfolder2 = Math.ceil(Math.random() * 999); const secretNumberPkg2 = Math.ceil(Math.random() * 999); fs.mkdirSync(tmpdir.resolve('node_modules/pkg/subfolder'), { recursive: true }); fs.writeFileSync( tmpdir.resolve('node_modules/pkg/subfolder/index.js'), '', ); fs.writeFileSync( tmpdir.resolve('node_modules/pkg/subfolder/package.json'), JSON.stringify({ type: 'module', secretNumberSubfolder, }), ); fs.writeFileSync( tmpdir.resolve('node_modules/pkg/package.json'), JSON.stringify({ name: 'pkg', exports: './subfolder/index.js', secretNumberPkgRoot, }), ); fs.mkdirSync(tmpdir.resolve('node_modules/pkg/subfolder2')); fs.writeFileSync( tmpdir.resolve('node_modules/pkg/subfolder2/package.json'), JSON.stringify({ type: 'module', secretNumberSubfolder2, }), ); fs.writeFileSync( tmpdir.resolve('node_modules/pkg/subfolder2/index.js'), '', ); fs.mkdirSync(tmpdir.resolve('node_modules/pkg2')); fs.writeFileSync( tmpdir.resolve('node_modules/pkg2/package.json'), JSON.stringify({ name: 'pkg', main: tmpdir.resolve('node_modules/pkg/subfolder2/index.js'), secretNumberPkg2, }), ); common.spawnPromisified(process.execPath, [tmpdir.resolve('entry.mjs')]).then(common.mustCall((result) => { console.error(result.stderr); console.log(result.stdout); assert.deepStrictEqual(result, { stdout: `${secretNumberPkgRoot} ${secretNumberSubfolder} ${secretNumberSubfolder2} ${secretNumberPkg2}\n`, stderr: '', code: 0, signal: null, }); })); }); it('should work within a loader', async () => { const specifierBase = './packages/root-types-field'; const target = fixtures.fileURL(specifierBase, 'index.js'); const foundPjsonPath = path.toNamespacedPath(fixtures.path(specifierBase, 'package.json')); const { code, stderr, stdout } = await common.spawnPromisified(process.execPath, [ '--no-warnings', '--loader', [ 'data:text/javascript,', 'import fs from "node:fs";', 'import module from "node:module";', encodeURIComponent(`fs.writeSync(1, module.findPackageJSON(${JSON.stringify(target)}));`), 'export const resolve = async (s, c, n) => n(s);', ].join(''), '--eval', 'import "node:os";', // Can be anything that triggers the resolve hook chain ]); assert.strictEqual(stderr, ''); assert.ok(stdout.includes(foundPjsonPath), stdout); assert.strictEqual(code, 0); }); it('should work with an async resolve hook registered', async () => { const specifierBase = './packages/root-types-field'; const target = fixtures.fileURL(specifierBase, 'index.js'); const foundPjsonPath = path.toNamespacedPath(fixtures.path(specifierBase, 'package.json')); const { code, stderr, stdout } = await common.spawnPromisified(process.execPath, [ '--no-warnings', '--loader', 'data:text/javascript,export const resolve = async (s, c, n) => n(s);', '--print', `require("node:module").findPackageJSON(${JSON.stringify(target)})`, ]); assert.strictEqual(stderr, ''); assert.ok(stdout.includes(foundPjsonPath), stdout); assert.strictEqual(code, 0); }); it('should work when unrecognised keys are specified within the export', async () => { const specifierBase = './packages/unrecognised-export-keys'; const target = fixtures.fileURL(specifierBase, 'index.js'); const foundPjsonPath = path.toNamespacedPath(fixtures.path(specifierBase, 'package.json')); const { code, stderr, stdout } = await common.spawnPromisified(process.execPath, [ '--print', `require("node:module").findPackageJSON(${JSON.stringify(target)})`, ]); assert.strictEqual(stderr, ''); assert.ok(stdout.includes(foundPjsonPath), stdout); assert.strictEqual(code, 0); }); });