diff --git a/lib/internal/modules/package_json_reader.js b/lib/internal/modules/package_json_reader.js index 9a9dcebb799c00..d2a29fa4d60200 100644 --- a/lib/internal/modules/package_json_reader.js +++ b/lib/internal/modules/package_json_reader.js @@ -11,12 +11,19 @@ const modulesBinding = internalBinding('modules'); const { resolve, sep } = require('path'); const { kEmptyObject } = require('internal/util'); +/** + * @typedef {import('typings/internalBinding/modules').FullPackageConfig} FullPackageConfig + * @typedef {import('typings/internalBinding/modules').PackageConfig} PackageConfig + * @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 + * @param {boolean} everything + * @returns {everything extends true ? FullPackageConfig : PackageConfig} */ -function deserializePackageJSON(path, contents) { +function deserializePackageJSON(path, contents, everything = false) { if (contents === undefined) { return { __proto__: null, @@ -64,6 +71,7 @@ function deserializePackageJSON(path, contents) { ObjectDefineProperty(this, 'exports', { __proto__: null, value }); return this.exports; }, + ...(everything && contents[6]), }; } @@ -75,7 +83,7 @@ function deserializePackageJSON(path, contents) { * specifier?: URL | string, * isESM?: boolean, * }} options - * @returns {import('typings/internalBinding/modules').PackageConfig} + * @returns {PackageConfig} */ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) { // This function will be called by both CJS and ESM, so we need to make sure @@ -105,16 +113,17 @@ function readPackage(requestPath) { * 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 {boolean} everything Whether to include unrecognised fields. + * @returns {undefined | {data: PackageConfig, path: string}} */ -function getNearestParentPackageJSON(checkPath) { +function getNearestParentPackageJSON(checkPath, everything = false) { const result = modulesBinding.getNearestParentPackageJSON(checkPath); if (result === undefined) { return undefined; } - const data = deserializePackageJSON(checkPath, result); + const data = deserializePackageJSON(checkPath, result, everything); // Path should be the root folder of the matched package.json // For example for ~/path/package.json, it should be ~/path @@ -123,16 +132,26 @@ function getNearestParentPackageJSON(checkPath) { return { data, path }; } +/** + * Find the nearest package.json + * @param {URL['pathname']} origin Where to start searching. + * @returns {URL['pathname']} The fully resolved location of the package.json file. + */ +function findNearestPackageJSON(origin) { + return modulesBinding.findNearestPackageJSON(origin); +} + /** * 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. + * @param {boolean} everything - Whether to include unrecognised fields. + * @returns {PackageConfig} - The package configuration. */ -function getPackageScopeConfig(resolved) { +function getPackageScopeConfig(resolved, everything = false) { const result = modulesBinding.getPackageScopeConfig(`${resolved}`); if (ArrayIsArray(result)) { - return deserializePackageJSON(`${resolved}`, result); + return deserializePackageJSON(`${resolved}`, result, everything); } // This means that the response is a string @@ -160,4 +179,5 @@ module.exports = { getNearestParentPackageJSON, getPackageScopeConfig, getPackageType, + findNearestPackageJSON, }; diff --git a/lib/module.js b/lib/module.js index 48ba9215b081eb..a70b7b19ef9f7b 100644 --- a/lib/module.js +++ b/lib/module.js @@ -10,6 +10,10 @@ const { flushCompileCache, getCompileCacheDir, } = require('internal/modules/helpers'); +const { + getPackageScopeConfig, + findNearestPackageJSON, +} = require('internal/modules/package_json_reader'); Module.findSourceMap = findSourceMap; Module.register = register; @@ -19,4 +23,6 @@ Module.enableCompileCache = enableCompileCache; Module.flushCompileCache = flushCompileCache; Module.getCompileCacheDir = getCompileCacheDir; +Module.getPackageScopeConfig = getPackageScopeConfig; +Module.findNearestPackageJSON = findNearestPackageJSON; module.exports = Module; diff --git a/src/node_modules.cc b/src/node_modules.cc index 157fa8d4442843..4fb01c75a656d7 100644 --- a/src/node_modules.cc +++ b/src/node_modules.cc @@ -313,8 +313,9 @@ const BindingData::PackageConfig* BindingData::TraverseParent( return nullptr; } -void BindingData::GetNearestParentPackageJSON( - const v8::FunctionCallbackInfo& args) { +void BindingData::FindNearestParentPackageJSON( + const v8::FunctionCallbackInfo& args +) { CHECK_GE(args.Length(), 1); CHECK(args[0]->IsString()); @@ -331,8 +332,15 @@ void BindingData::GetNearestParentPackageJSON( path_value_str.push_back(kPathSeparator); } - auto package_json = - TraverseParent(realm, std::filesystem::path(path_value_str)); + args.GetReturnValue().Set(path_value_str); +} + +void BindingData::GetNearestParentPackageJSON( + const v8::FunctionCallbackInfo& args +) { + auto path = FindNearestParentPackageJSON(args); + + auto package_json = TraverseParent(realm, std::filesystem::path(path_value_str)); if (package_json != nullptr) { args.GetReturnValue().Set(package_json->Serialize(realm)); @@ -340,32 +348,16 @@ void BindingData::GetNearestParentPackageJSON( } void BindingData::GetNearestParentPackageJSONType( - const FunctionCallbackInfo& args) { - CHECK_GE(args.Length(), 1); - CHECK(args[0]->IsString()); - - Realm* realm = Realm::GetCurrent(args); - BufferValue path_value(realm->isolate(), args[0]); - // Check if the path has a trailing slash. If so, add it after - // ToNamespacedPath() as it will be deleted by ToNamespacedPath() - bool slashCheck = path_value.ToStringView().ends_with(kPathSeparator); - - ToNamespacedPath(realm->env(), &path_value); - - std::string path_value_str = path_value.ToString(); - if (slashCheck) { - path_value_str.push_back(kPathSeparator); - } - - auto package_json = - TraverseParent(realm, std::filesystem::path(path_value_str)); + const FunctionCallbackInfo& args +) { + auto package_json = GetNearestParentPackageJSON(args); if (package_json == nullptr) { return; } - Local value = - ToV8Value(realm->context(), package_json->type).ToLocalChecked(); + Local value = ToV8Value(realm->context(), package_json->type).ToLocalChecked(); + args.GetReturnValue().Set(value); } @@ -497,6 +489,7 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, SetMethod(isolate, target, "getPackageScopeConfig", GetPackageScopeConfig); SetMethod(isolate, target, "enableCompileCache", EnableCompileCache); SetMethod(isolate, target, "getCompileCacheDir", GetCompileCacheDir); + SetMethod(isolate, target, "findNearestPackageJSON", FindNearestParentPackageJSON); SetMethod(isolate, target, "flushCompileCache", FlushCompileCache); } diff --git a/typings/internalBinding/modules.d.ts b/typings/internalBinding/modules.d.ts index b9aa518abde337..f39913a2189b77 100644 --- a/typings/internalBinding/modules.d.ts +++ b/typings/internalBinding/modules.d.ts @@ -8,6 +8,9 @@ export type PackageConfig = { exports?: string | string[] | Record imports?: string | string[] | Record } +export type FullPackageConfig = PackageConfig & { + [key: string]: unknown, +} export type SerializedPackageConfig = [ PackageConfig['name'], PackageConfig['main'], @@ -23,4 +26,5 @@ export interface ModulesBinding { getNearestParentPackageJSONType(path: string): PackageConfig['type'] getPackageScopeConfig(path: string): SerializedPackageConfig | undefined getPackageJSONScripts(): string | undefined + findNearestPackageJSON(origin: URL['pathname']): URL['pathname'] | undefined }