Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

module: expose getPackageJSON utility #55229

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
48ab696
module: expose `getPackageJSON` utility
JakobJingleheimer Sep 30, 2024
dcddd1e
fixup: convert `path_value` to string before fn casting to path
JakobJingleheimer Oct 1, 2024
98c72f4
fixup: remove obsolete reference to `findNearestPackageJSON`
JakobJingleheimer Oct 1, 2024
d4ede54
fixup: add some tests cases
JakobJingleheimer Oct 1, 2024
92f2c62
feat: adjust `getNearestParentPackageJSON().path` to pjsonPath
JakobJingleheimer Oct 1, 2024
b83707b
feat: conditionally include non-required fields only when they exist
JakobJingleheimer Oct 1, 2024
e0beefa
fixup: remove unnecessary `JSON.parse()` (it's already parsed)
JakobJingleheimer Oct 1, 2024
6e715ec
test: add more cases
JakobJingleheimer Oct 1, 2024
458dcca
fix: provide missing `pjsonPath` for `GetNearestRawParentPackageJSON`
JakobJingleheimer Oct 1, 2024
903d201
fix: correct internal bindings type decs
JakobJingleheimer Oct 1, 2024
b567e9a
fixup: remove unnecessary string manipulation
JakobJingleheimer Oct 1, 2024
ed4db64
fixup: de-lint
JakobJingleheimer Oct 1, 2024
9a4b1d1
this is awful. why does anyone want this lint rule
JakobJingleheimer Oct 1, 2024
c23c8f1
fixup: correct yaml version needle
JakobJingleheimer Oct 2, 2024
c865d55
fixup: americanize name
JakobJingleheimer Oct 2, 2024
a9bf393
fixup: mangle md
JakobJingleheimer Oct 3, 2024
190e315
fixup: mangle c++ code
JakobJingleheimer Oct 3, 2024
a9e3ded
fixup: specific → nondescript to satisfy linting
JakobJingleheimer Oct 4, 2024
926b2b0
fixup: correct return dec in md
JakobJingleheimer Oct 5, 2024
485b1e3
fixup: remove temp fields & correct type decs
JakobJingleheimer Oct 5, 2024
f824ce9
fixup: wordsmith doc & expand examples
JakobJingleheimer Oct 5, 2024
7deeb7f
fixup: leverage validate* utils
JakobJingleheimer Oct 5, 2024
cf49f71
fixup: `ToString` → `ToStringView `
JakobJingleheimer Oct 5, 2024
358486c
fixup: support file URL strings, update arg name, add test case
JakobJingleheimer Oct 5, 2024
d9bdf23
fixup: damn this limited docs types validator
JakobJingleheimer Oct 5, 2024
bf5265d
fixup: require url → internal/url
JakobJingleheimer Oct 5, 2024
3e1cda8
fixup: pjson.name is optional
JakobJingleheimer Oct 6, 2024
da29815
fixup: remove unnecessary cost for default value object instantiation
JakobJingleheimer Oct 6, 2024
b531497
fixup: throw on relative `statLocation` with remediation instructions
JakobJingleheimer Oct 6, 2024
eef2b60
fixup: add test-case for require() resolving via package.json
JakobJingleheimer Oct 7, 2024
395cfa6
fixup: update consumption of `readPackage` & `read()`
JakobJingleheimer Oct 7, 2024
841848f
Revert "fixup: update consumption of `readPackage` & `read()`"
JakobJingleheimer Oct 8, 2024
945c207
fixus: restore return signature of jpsonReader.read
JakobJingleheimer Oct 8, 2024
4adac4c
fixup: null proto for read return
JakobJingleheimer Oct 9, 2024
0064379
fixup: `!== null` → `!= null`
JakobJingleheimer Oct 9, 2024
eed739c
fixup: spread with `__proto__: null`
JakobJingleheimer Oct 9, 2024
864b98a
fixup: add `exists` to `DeserializedPackageConfig`
JakobJingleheimer Oct 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions doc/api/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,48 @@ added: v22.8.0
* Returns: {string|undefined} Path to the [module compile cache][] directory if it is enabled,
or `undefined` otherwise.
### `module.getPackageJSON(startLocation[, everything])`
<!-- YAML
added: REPLACEME
-->
> Stability: 1.1 - Active Development
* `startLocation` {string} A fully resolved FS path or file URL to start looking
* `everything` {boolean} Whether to return the full contents of the found package.json
Copy link
Contributor

@aduh95 aduh95 Oct 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it is a good idea to introduce yet-another way of loading a JSON file, wouldn't it be simpler to return the path and let the user deal with reading/parsing the file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that (there's another PR I opened and closed that exposed findPackageJSON, which did exactly that). We're already reading the pjson and caching it, so IMO that would force a de-op as well as force the user to manually do what we're already doing. So, why?

Copy link
Member

@joyeecheung joyeecheung Oct 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think returning the parsed data means every time we consider parsing another field in the package.json, it needs to be surfaced into this API, even though that field may not even be useful for tools, but we'll end up paying the serialization/deserialization cost even though internally only selected places need selected fields. IMO even just returning the string would be better than this. Users need this for the lookup algorithm, they tend to have other fields to parse anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I recommend a different naming and API surface?

module.findPackageJson(path, { includeContents: true })

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, @joyeecheung I'm not sure I follow in your message. I think you've misunderstood the behaviour here: internally, we do not use the full version—we do use getNearestParentPackageJSON but do not set everything to true. When everything is not true, only the select fields we use internally are parsed. That has not changed in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I recommend a different naming and API surface?

module.findPackageJson(path, { includeContents: true })

@anonrig if you recall, when we were working on this a few days ago, that did not make sense due to how the C++ worked, which is what caused me to abandon the findPackageJSON + getPackageJSON route in the first place.

Regarding "find" + includeContents, this seems counter-intuitive to me: I expect something called "find" to only locate, not retrieve. I could perhaps see the inverse

module.getPackageJSON(path, { includeContents: false })

I do not like either option though because the shape of the return is changed significantly in a foot-gun way:

getPackageJSON(path, false) // '…/package.json'

getPackageJSON(path, true)  // { data: {…}, path: '…/package.json' }

(I simplified the 2nd arg for brevity, not necessarily to say we shouldn't use an object)

I suppose it could always return an object for consistency, but that seems like a clumsy compromise

getPackageJSON(path, false) // { path: '…/package.json' }

getPackageJSON(path, true)  // { data: {…}, path: '…/package.json' }

If we were to do this, I think exposing 2 different utils (findPackageJSON + getPackageJSON) would be better.

* Returns: {Object | undefined}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Returns: {Object | undefined}
* Returns: {Object|undefined}

Ditto to the rest

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the way the docs are formatted. There is no space between the |.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it actually matter or is it a style/consistency issue?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the linter will throw an error, but it's documented in the Style Guide (https://github.com/nodejs/node/blob/main/doc/README.md#function-arguments-and-returns)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah let's keep them consistent, i.e. without spaces

* data: {Object}
* name: {string | undefined}
* type: {string | undefined}
Comment on lines +231 to +233
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto with the rest

Suggested change
* data: {Object}
* name: {string | undefined}
* type: {string | undefined}
* `data`: {Object}
* `name`: {string|undefined}
* `type`: {string|undefined}

* exports: string | string\[] | Record\<string, unknown> | undefined
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a valid type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno if the validator considers it one, but it absolutely valid 🙂

Copy link
Contributor

@aduh95 aduh95 Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we are not using TS nor JSDoc syntax, so it doesn't really make sense to claim it's "valid syntax" if the validator disagrees

Suggested change
* exports: string | string\[] | Record\<string, unknown> | undefined
* `exports`: {string|string\[]|Object|undefined}

* imports: string | string\[] | Record\<string, unknown> | undefined
* …
* path: {string}
Retreives the contents and location of the package.json closest to the supplied `startLocation`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Retreives the contents and location of the package.json closest to the supplied `startLocation`;
Retreives the contents and location of the `package.json` closest to the supplied `startLocation`;

this behaves identically to node's own lookup and consumption of package.json for a given module.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this behaves identically to node's own lookup and consumption of package.json for a given module.
this behaves identically to Node.js own lookup and consumption of `package.json` for a given module.

```mjs
import { getPackageJSON } from 'node:module';

const somePackage = getPackageJSON(import.meta.resolve('some-package'), true);

somePackage?.path; // '/…/node_modules/some-package/package.json'
somePackage?.data.name; // 'some-package-real-name'
somePackage?.data.types; // './index.d.ts'

const thisParentPackage = getPackageJSON(import.meta.resolve('..'));

thisParentPackage?.path; // '../../package.json'
thisParentPackage?.data.type; // 'module'

const thisSubPackage = getPackageJSON(import.meta.url);

thisSubPackage?.path; // './package.json'
thisSubPackage?.data.type; // 'commonjs'
```
### `module.isBuiltin(moduleName)`
<!-- YAML
Expand Down
22 changes: 11 additions & 11 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -604,11 +604,11 @@ function trySelf(parentPath, request) {
try {
const { packageExportsResolve } = require('internal/modules/esm/resolve');
return finalizeEsmResolution(packageExportsResolve(
pathToFileURL(pkg.path + '/package.json'), expansion, pkg.data,
pathToFileURL(pkg.path), expansion, pkg.data,
pathToFileURL(parentPath), getCjsConditions()), parentPath, pkg.path);
} catch (e) {
if (e.code === 'ERR_MODULE_NOT_FOUND') {
throw createEsmNotFoundErr(request, pkg.path + '/package.json');
throw createEsmNotFoundErr(request, pkg.path);
}
throw e;
}
Expand Down Expand Up @@ -1214,14 +1214,15 @@ Module._resolveFilename = function(request, parent, isMain, options) {

if (request[0] === '#' && (parent?.filename || parent?.id === '<repl>')) {
const parentPath = parent?.filename ?? process.cwd() + path.sep;
const pkg = packageJsonReader.getNearestParentPackageJSON(parentPath) || { __proto__: null };
if (pkg.data?.imports != null) {
const pkg = packageJsonReader.getNearestParentPackageJSON(parentPath);
if (pkg?.data.imports != null) {
try {
const { packageImportsResolve } = require('internal/modules/esm/resolve');
return finalizeEsmResolution(
packageImportsResolve(request, pathToFileURL(parentPath),
getCjsConditions()), parentPath,
pkg.path);
packageImportsResolve(request, pathToFileURL(parentPath), getCjsConditions()),
parentPath,
pkg.path,
);
} catch (e) {
if (e.code === 'ERR_MODULE_NOT_FOUND') {
throw createEsmNotFoundErr(request);
Expand Down Expand Up @@ -1281,8 +1282,7 @@ function finalizeEsmResolution(resolved, parentPath, pkgPath) {
if (actual) {
return actual;
}
const err = createEsmNotFoundErr(filename,
path.resolve(pkgPath, 'package.json'));
const err = createEsmNotFoundErr(filename, pkgPath);
throw err;
}

Expand Down Expand Up @@ -1614,7 +1614,7 @@ function loadTS(module, filename) {

const parent = module[kModuleParent];
const parentPath = parent?.filename;
const packageJsonPath = path.resolve(pkg.path, 'package.json');
const packageJsonPath = pkg.path;
const usesEsm = containsModuleSyntax(content, filename);
const err = new ERR_REQUIRE_ESM(filename, usesEsm, parentPath,
packageJsonPath);
Expand Down Expand Up @@ -1673,7 +1673,7 @@ Module._extensions['.js'] = function(module, filename) {
// This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
const parent = module[kModuleParent];
const parentPath = parent?.filename;
const packageJsonPath = path.resolve(pkg.path, 'package.json');
const packageJsonPath = pkg.path;
const usesEsm = containsModuleSyntax(content, filename);
const err = new ERR_REQUIRE_ESM(filename, usesEsm, parentPath,
packageJsonPath);
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ const legacyMainResolveExtensionsIndexes = {
* 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
* 5. NOT_FOUND
* @param {URL} packageJSONUrl
* @param {import('typings/internalBinding/modules').PackageConfig} packageConfig
* @param {import('typings/internalBinding/modules').RecognizedPackageConfig} packageConfig
* @param {string | URL | undefined} base
* @returns {URL}
*/
Expand Down
167 changes: 117 additions & 50 deletions lib/internal/modules/package_json_reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,49 @@

const {
ArrayIsArray,
ArrayPrototypeJoin,
JSONParse,
ObjectDefineProperty,
StringPrototypeLastIndexOf,
StringPrototypeSlice,
StringPrototypeEndsWith,
} = primordials;
const {
codes: {
ERR_INVALID_ARG_VALUE,
},
} = require('internal/errors');
const modulesBinding = internalBinding('modules');
const { resolve, sep } = require('path');
const path = require('path');
const { kEmptyObject } = require('internal/util');
const { fileURLToPath, URL } = require('internal/url');
const {
validateBoolean,
validateString,
} = require('internal/validators');

/**
* @typedef {import('typings/internalBinding/modules').DeserializedPackageConfig} DeserializedPackageConfig
* @typedef {import('typings/internalBinding/modules').FullPackageConfig} FullPackageConfig
* @typedef {import('typings/internalBinding/modules').RecognizedPackageConfig} RecognizedPackageConfig
* @typedef {import('typings/internalBinding/modules').SerializedPackageConfig} SerializedPackageConfig
*/

/**
* @param {string} path
* @param {import('typings/internalBinding/modules').SerializedPackageConfig} contents
* @returns {import('typings/internalBinding/modules').PackageConfig}
* @param {SerializedPackageConfig} contents
* @returns {DeserializedPackageConfig}
*/
function deserializePackageJSON(path, contents) {
if (contents === undefined) {
return {
__proto__: null,
JakobJingleheimer marked this conversation as resolved.
Show resolved Hide resolved
data: {
__proto__: null,
type: 'none', // Ignore unknown types for forwards compatibility
},
exists: false,
pjsonPath: path,
type: 'none', // Ignore unknown types for forwards compatibility
path,
};
}

let pjsonPath = path;
const {
0: name,
1: main,
Expand All @@ -37,36 +55,40 @@ function deserializePackageJSON(path, contents) {
} = contents;

// This is required to be used in getPackageScopeConfig.
if (optionalFilePath) {
pjsonPath = optionalFilePath;
}

// The imports and exports fields can be either undefined or a string.
// - If it's a string, it's either plain string or a stringified JSON string.
// - If it's a stringified JSON string, it starts with either '[' or '{'.
const requiresJSONParse = (value) => (value !== undefined && (value[0] === '[' || value[0] === '{'));
const pjsonPath = optionalFilePath ?? path;

return {
__proto__: null,
JakobJingleheimer marked this conversation as resolved.
Show resolved Hide resolved
exists: true,
pjsonPath,
name,
main,
type,
// This getters are used to lazily parse the imports and exports fields.
get imports() {
const value = requiresJSONParse(plainImports) ? JSONParse(plainImports) : plainImports;
ObjectDefineProperty(this, 'imports', { __proto__: null, value });
return this.imports;
},
get exports() {
const value = requiresJSONParse(plainExports) ? JSONParse(plainExports) : plainExports;
ObjectDefineProperty(this, 'exports', { __proto__: null, value });
return this.exports;
data: {
__proto__: null,
...(name != null && { __proto__: null, name }),
...(main != null && { __proto__: null, main }),
...(type != null && { __proto__: null, type }),
Comment on lines +63 to +65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null prototype is not useful here, spread only copies own properties

Suggested change
...(name != null && { __proto__: null, name }),
...(main != null && { __proto__: null, main }),
...(type != null && { __proto__: null, type }),
...(name != null && { name }),
...(main != null && { main }),
...(type != null && { type }),

...(plainImports != null && {
// This getters are used to lazily parse the imports and exports fields.
get imports() {
const value = requiresJSONParse(plainImports) ? JSONParse(plainImports) : plainImports;
ObjectDefineProperty(this, 'imports', { __proto__: null, value });
return this.imports;
},
}),
...(plainExports != null && {
get exports() {
const value = requiresJSONParse(plainExports) ? JSONParse(plainExports) : plainExports;
ObjectDefineProperty(this, 'exports', { __proto__: null, value });
return this.exports;
},
}),
},
exists: true,
path: pjsonPath,
};
}

// The imports and exports fields can be either undefined or a string.
// - If it's a string, it's either plain string or a stringified JSON string.
// - If it's a stringified JSON string, it starts with either '[' or '{'.
const requiresJSONParse = (value) => (value !== undefined && (value[0] === '[' || value[0] === '{'));

/**
* Reads a package.json file and returns the parsed contents.
* @param {string} jsonPath
Expand All @@ -75,7 +97,7 @@ function deserializePackageJSON(path, contents) {
* specifier?: URL | string,
* isESM?: boolean,
* }} options
* @returns {import('typings/internalBinding/modules').PackageConfig}
* @returns {RecognizedPackageConfig}
*/
function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
// This function will be called by both CJS and ESM, so we need to make sure
Expand All @@ -87,52 +109,97 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
specifier == null ? undefined : `${specifier}`,
);

return deserializePackageJSON(jsonPath, parsed);
const result = deserializePackageJSON(jsonPath, parsed);

return {
__proto__: null,
...result.data,
JakobJingleheimer marked this conversation as resolved.
Show resolved Hide resolved
exists: result.exists,
pjsonPath: result.path,
};
}

/**
* @deprecated Expected to be removed in favor of `read` in the future.
* Behaves the same was as `read`, but appends package.json to the path.
* @param {string} requestPath
* @return {PackageConfig}
* @return {RecognizedPackageConfig}
*/
function readPackage(requestPath) {
// TODO(@anonrig): Remove this function.
return read(resolve(requestPath, 'package.json'));
return read(path.resolve(requestPath, 'package.json'));
}

/**
* Get the nearest parent package.json file from a given path.
* Return the package.json data and the path to the package.json file, or undefined.
* @param {string} checkPath The path to start searching from.
* @returns {undefined | {data: import('typings/internalBinding/modules').PackageConfig, path: string}}
* @param {URL['href'] | URL['pathname']} startLocation The path to start searching from.
* @param {boolean} everything Whether to include the full contents of the package.json.
* @returns {undefined | DeserializedPackageConfig<everything>}
*/
function getNearestParentPackageJSON(checkPath) {
const result = modulesBinding.getNearestParentPackageJSON(checkPath);
function getNearestParentPackageJSON(startLocation, everything = false) {
let startPath = URL.canParse(startLocation) ? fileURLToPath(startLocation) : startLocation;

validateString(startPath, 'startPath');
validateBoolean(everything, 'everything');

if (!path.isAbsolute(startPath)) {
throw new ERR_INVALID_ARG_VALUE(
'startLocation',
startLocation,
ArrayPrototypeJoin([
'must be a fully resolved location. To use a relative location, first wrap with',
'`import.meta.resolve(startLocation)` in ESM',
'or',
'`path.resolve(__dirname, startLocation) in CJS',
], ' '),
);
}

if (result === undefined) {
return undefined;
if (!StringPrototypeEndsWith(startPath, path.sep)) { startPath += path.sep; }

if (everything) {
const result = modulesBinding.getNearestRawParentPackageJSON(startPath);
JakobJingleheimer marked this conversation as resolved.
Show resolved Hide resolved

return {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this also need to be null prototyped

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aduh95 please advise 🙏

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, it's user-facing

data: {
__proto__: null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having null-prototype object returned to the object feels weird

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I did it for consistency between everything (not everything is used internally and needs the null prototype).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That feels like another reason not to expose everything and let the user parse the JSON themselves. We should be freezing it to avoid folks mutating it – but really, why do we bother? The API would be much simpler if it only returns a path

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is because we've already done the work, and handled the caching. Doing it this way also provides better performance. Users having to do exactly the same thing is the whole reason we as engineers create utilities, and there is basically no chance any user would not subsequently parse it.

Freezing seems like a good idea.

...JSONParse(result?.[0]),
},
exists: true,
path: result?.[1],
};
}

const data = deserializePackageJSON(checkPath, result);
const result = modulesBinding.getNearestParentPackageJSON(startPath);

// Path should be the root folder of the matched package.json
// For example for ~/path/package.json, it should be ~/path
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Why does this statement doesn't hold anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const path = StringPrototypeSlice(data.pjsonPath, 0, StringPrototypeLastIndexOf(data.pjsonPath, sep));
if (result === undefined) {
return undefined;
}

return { data, path };
return deserializePackageJSON(startPath, result);
}

/**
* Returns the package configuration for the given resolved URL.
* @param {URL | string} resolved - The resolved URL.
* @returns {import('typings/internalBinding/modules').PackageConfig} - The package configuration.
* @returns {RecognizedPackageConfig & {
* exists: boolean,
* pjsonPath: DeserializedPackageConfig['path'],
* }} - The package configuration.
*/
function getPackageScopeConfig(resolved) {
const result = modulesBinding.getPackageScopeConfig(`${resolved}`);

if (ArrayIsArray(result)) {
return deserializePackageJSON(`${resolved}`, result);
const { data, exists, path } = deserializePackageJSON(`${resolved}`, result);

return {
__proto__: null,
...data,
exists,
pjsonPath: path,
};
}

// This means that the response is a string
Expand Down
4 changes: 4 additions & 0 deletions lib/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const {
flushCompileCache,
getCompileCacheDir,
} = require('internal/modules/helpers');
const {
getNearestParentPackageJSON,
} = require('internal/modules/package_json_reader');

Module.findSourceMap = findSourceMap;
Module.register = register;
Expand All @@ -19,4 +22,5 @@ Module.enableCompileCache = enableCompileCache;
Module.flushCompileCache = flushCompileCache;

Module.getCompileCacheDir = getCompileCacheDir;
Module.getPackageJSON = getNearestParentPackageJSON;
module.exports = Module;
Loading
Loading