module: add --experimental-strip-private-modules

Signed-off-by: Marco Ippolito <marcoippolito54@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/63869
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Pietro Marchini <pietro.marchini94@gmail.com>
This commit is contained in:
Marco Ippolito authored and GitHub committed 2026-10-19 13:03:51 +00:00
1 parent 135b70e670
commit 2052d0dcdf
36 files changed
+470 -3

No files matched your search

+21
View File
@@ -1647,6 +1647,25 @@ added:
Enable the experimental [`node:stream/iter`][] module.
### `--experimental-strip-private-modules`
<!-- YAML
added: REPLACEME
-->
> Stability: 1 - Experimental
By default, Node.js refuses to strip types from TypeScript files inside
folders under a `node_modules` path. This flag enables type stripping for
such files when they belong to a package whose `package.json` contains
`"private": true`. Files belonging to non-private packages still throw
[`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`][].
The limitation to private packages should never be lifted,
as publishing packages with TypeScript sources
is highly discouraged.
Stripping types inside `node_modules` is intended only
for local development.
### `--experimental-test-coverage`
<!-- YAML
@@ -4287,6 +4306,7 @@ one is included in the list below.
* `--experimental-shadow-realm`
* `--experimental-specifier-resolution`
* `--experimental-stream-iter`
* `--experimental-strip-private-modules`
* `--experimental-test-isolation`
* `--experimental-top-level-await`
* `--experimental-vm-modules`
@@ -4941,6 +4961,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`CRYPTO_secure_malloc_init`]: https://www.openssl.org/docs/man3.0/man3/CRYPTO_secure_malloc_init.html
[`ERR_INVALID_TYPESCRIPT_SYNTAX`]: errors.md#err_invalid_typescript_syntax
[`ERR_RX_MEMORY_NOT_SUPPORTED`]: errors.md#err_rx_memory_not_supported
[`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`]: errors.md#err_unsupported_node_modules_type_stripping
[`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`]: errors.md#err_unsupported_typescript_syntax
[`ERR_WEBASSEMBLY_NOT_SUPPORTED`]: errors.md#err_webassembly_not_supported
[`NODE_OPTIONS`]: #node_optionsoptions
+32
View File
@@ -1153,6 +1153,9 @@ The following fields in `package.json` files are used in Node.js:
limits which submodules can be loaded from within the package.
* [`"imports"`][] - Package imports, for use by modules within the package
itself.
* [`"private"`][] - When `true`, marks the package as not for publication. Used
by [`--experimental-strip-private-modules`][] to allow type stripping for the
package's TypeScript files under `node_modules`.
### `"name"`
@@ -1348,6 +1351,33 @@ Package imports permit mapping to external packages.
This field defines [subpath imports][] for the current package.
### `"private"`
<!-- YAML
added: REPLACEME
-->
* Type: {boolean}
```json
{
"private": true
}
```
The `"private"` field marks a package as private, signaling to package managers
such as _npm_ that it must not be published to a public registry.
By default, Node.js refuses to strip types from TypeScript files inside
`node_modules`. When [`--experimental-strip-private-modules`][] is enabled,
Node.js allows type stripping for:
* files that are not inside a `node_modules` directory.
* files inside `node_modules`, but only when the package that contains the
file sets `"private": true`. Only the `package.json` at each package boundary
is considered: the package's root directory, which is the directory directly
under `node_modules` (or under `node_modules/<@scope>` for scoped packages).
Nested `package.json` files are ignored.
[CommonJS]: modules.md
[Conditional exports]: #conditional-exports
[ES module]: esm.md
@@ -1362,10 +1392,12 @@ This field defines [subpath imports][] for the current package.
[`"imports"`]: #imports
[`"main"`]: #main
[`"name"`]: #name
[`"private"`]: #private
[`"type"`]: #type
[`--conditions` / `-C` flag]: #resolving-user-conditions
[`--experimental-addon-modules`]: cli.md#--experimental-addon-modules
[`--experimental-package-map`]: cli.md#--experimental-package-mappath
[`--experimental-strip-private-modules`]: cli.md#--experimental-strip-private-modules
[`--no-addons` flag]: cli.md#--no-addons
[`ERR_PACKAGE_MAP_EXTERNAL_FILE`]: errors.md#err_package_map_external_file
[`ERR_PACKAGE_PATH_NOT_EXPORTED`]: errors.md#err_package_path_not_exported
+10
View File
@@ -222,6 +222,15 @@ To discourage package authors from publishing packages written in TypeScript,
Node.js refuses to handle TypeScript files inside folders under a `node_modules`
path.
The [`--experimental-strip-private-modules`][] flag relaxes this restriction
for packages whose `package.json` contains `"private": true`, such as
workspace packages in a monorepo, which are not published to a registry.
The limitation to private packages should never be lifted,
as publishing packages with TypeScript sources
is highly discouraged.
Stripping types inside `node_modules` is intended only
for local development.
### Paths aliases
[`tsconfig` "paths"][] won't be transformed and therefore produce an error. The closest
@@ -231,6 +240,7 @@ with `#`.
[CommonJS]: modules.md
[ES Modules]: esm.md
[Full TypeScript support]: #full-typescript-support
[`--experimental-strip-private-modules`]: cli.md#--experimental-strip-private-modules
[`--no-strip-types`]: cli.md#--no-strip-types
[`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`]: errors.md#err_unsupported_typescript_syntax
[`tsconfig` "paths"]: https://www.typescriptlang.org/tsconfig/#paths
+4
View File
@@ -367,6 +367,10 @@
"type": "boolean",
"description": "experimental iterable streams API (node:stream/iter)"
},
"experimental-strip-private-modules": {
"type": "boolean",
"description": "Type-stripping for TypeScript files under node_modules belonging to packages marked \"private\": true"
},
"experimental-vm-modules": {
"type": "boolean",
"description": "experimental ES Module support in vm module"
+14
View File
@@ -909,6 +909,18 @@ Enable experimental support for storage inspection
.It Fl -experimental-stream-iter
Enable the experimental \fBnode:stream/iter\fR module.
.
.It Fl -experimental-strip-private-modules
By default, Node.js refuses to strip types from TypeScript files inside
folders under a \fBnode_modules\fR path. This flag enables type stripping for
such files when they belong to a package whose \fBpackage.json\fR contains
\fB"private": true\fR. Files belonging to non-private packages still throw
\fBERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING\fR.
The limitation to private packages should never be lifted,
as publishing packages with TypeScript sources
is highly discouraged.
Stripping types inside \fBnode_modules\fR is intended only
for local development.
.
.It Fl -experimental-test-coverage
When used in conjunction with the \fBnode:test\fR module, a code coverage report is
generated as part of the test runner output. If no tests are run, a coverage
@@ -2273,6 +2285,8 @@ one is included in the list below.
.It
\fB--experimental-stream-iter\fR
.It
\fB--experimental-strip-private-modules\fR
.It
\fB--experimental-test-isolation\fR
.It
\fB--experimental-top-level-await\fR
+2
View File
@@ -85,6 +85,8 @@ const nativeLoaderMethods = {
modulesBinding.readPackageJSON(jsonPath, isESM, base, specifier),
getNearestParentPackageJSON: (checkPath) =>
modulesBinding.getNearestParentPackageJSON(checkPath),
getRootPackageJSON: (checkPath) =>
modulesBinding.getRootPackageJSON(checkPath),
getNearestParentPackageJSONType: (checkPath) =>
modulesBinding.getNearestParentPackageJSONType(checkPath),
getPackageScopeConfig: (resolved) =>
@@ -64,6 +64,7 @@ function deserializePackageJSON(path, contents) {
3: plainImports,
4: plainExports,
5: optionalFilePath,
6: isPrivate,
} = contents;
const pjsonPath = optionalFilePath ?? path;
@@ -73,6 +74,7 @@ function deserializePackageJSON(path, contents) {
...(name != null && { name }),
...(main != null && { main }),
...(type != null && { type }),
...(isPrivate != null && { private: isPrivate }),
};
if (plainExports !== null) {
@@ -217,6 +219,40 @@ function getNearestParentPackageJSON(checkPath) {
return deserializedPackageJSONCache.get(parentPackageJSONPath);
}
const moduleToRootPackageJSONCache = new SafeMap();
/**
* This is stricter than {@link getNearestParentPackageJSON}: nested
* `package.json` files inside a package are ignored, and a non-private package
* anywhere in the `node_modules` chain (for example a publishable package that
* bundles a private dependency) disqualifies the path. A directory under
* `node_modules` with no `package.json` (such as pnpm's `.pnpm` store) is not
* a package and is skipped.
*
* Returns 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 | DeserializedPackageConfig}
*/
function getRootPackageJSON(checkPath) {
const packageJSONPath = moduleToRootPackageJSONCache.get(checkPath);
if (packageJSONPath !== undefined) {
return deserializedPackageJSONCache.get(packageJSONPath);
}
const result = loaderMethods.getRootPackageJSON(checkPath);
const packageConfig = deserializePackageJSON(checkPath, result);
moduleToRootPackageJSONCache.set(checkPath, packageConfig.path);
const maybeCachedPackageConfig = deserializedPackageJSONCache.get(packageConfig.path);
if (maybeCachedPackageConfig !== undefined) {
return maybeCachedPackageConfig;
}
deserializedPackageJSONCache.set(packageConfig.path, packageConfig);
return packageConfig;
}
/**
* Returns the package configuration for the given resolved URL.
* @param {URL | string} resolved - The resolved URL.
@@ -412,6 +448,7 @@ function purgePackageJSONCacheForPrefix(mountPoint) {
module.exports = {
read,
getNearestParentPackageJSON,
getRootPackageJSON,
getPackageScopeConfig,
getPackageType,
getPackageJSONURL,
+34 -1
View File
@@ -18,6 +18,7 @@ const {
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING,
ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX,
} = require('internal/errors').codes;
const { getOptionValue } = require('internal/options');
const assert = require('internal/assert');
const {
getCompileCacheEntry,
@@ -141,6 +142,35 @@ function stripTypeScriptTypesForCoverage(code) {
}
/**
* Whether type-stripping is allowed for files under node_modules belonging
* to packages whose package.json contains `"private": true`.
* @returns {boolean}
*/
const isStripPrivateModulesEnabled = getLazy(
() => getOptionValue('--experimental-strip-private-modules'),
);
/**
* Checks whether a file under node_modules is owned by a private package, in
* which case type stripping is allowed under `--experimental-strip-private-modules`.
*
* Every package enclosing the file inside `node_modules` must be marked
* `"private": true`. npm refuses to publish a private package, so requiring
* the package root (not a nested package.json, which is published verbatim)
* and rejecting any non-private enclosing package (e.g. a publishable package
* bundling a private dependency) prevents published packages from shipping
* strippable TypeScript. See {@link getRootPackageJSON}.
* @param {string} filename The filename (path or file URL) of the source code.
* @returns {boolean} Whether the file belongs to a package with `"private": true`.
*/
function isInsidePrivatePackage(filename) {
const { getRootPackageJSON } = require('internal/modules/package_json_reader');
const { urlToFilename } = require('internal/modules/helpers');
const packageJSON = getRootPackageJSON(urlToFilename(filename));
return packageJSON?.data.private === true;
}
/**
* Performs type-stripping to TypeScript source code internally.
* It is used by internal loaders.
@@ -152,7 +182,10 @@ function stripTypeScriptTypesForCoverage(code) {
function stripTypeScriptModuleTypes(source, filename, sourceURL) {
assert(typeof source === 'string');
if (isUnderNodeModules(filename)) {
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
if (!isStripPrivateModulesEnabled() || !isInsidePrivatePackage(filename)) {
throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename);
}
emitExperimentalWarning('Type stripping in node_modules');
}
// Get a compile cache entry into the native compile cache store,
// keyed by the filename. If the cache can already be loaded on disk,
+95 -2
View File
@@ -21,6 +21,7 @@ namespace node {
namespace modules {
using v8::Array;
using v8::Boolean;
using v8::Context;
using v8::External;
using v8::FunctionCallbackInfo;
@@ -80,15 +81,18 @@ Local<Array> BindingData::PackageConfig::Serialize(Realm* realm) const {
isolate, input.data(), NewStringType::kNormal, input.size())
.ToLocalChecked();
};
Local<Value> values[6] = {
Local<Value> values[7] = {
name.has_value() ? ToString(*name) : Undefined(isolate),
main.has_value() ? ToString(*main) : Undefined(isolate),
ToString(type),
imports.has_value() ? ToString(*imports) : Undefined(isolate),
exports.has_value() ? ToString(*exports) : Undefined(isolate),
ToString(file_path),
is_private.has_value()
? Boolean::New(isolate, *is_private).As<Primitive>()
: Undefined(isolate),
};
return Array::New(isolate, values, 6);
return Array::New(isolate, values, 7);
}
bool BindingData::ParsePackageJSON(Realm* realm,
@@ -207,6 +211,11 @@ bool BindingData::ParsePackageJSON(Realm* realm,
if (field_value == "commonjs" || field_value == "module") {
package_config->type = field_value;
}
} else if (key == "private") {
bool is_private;
if (!value.get_bool().get(is_private)) {
package_config->is_private = is_private;
}
} else if (key == "scripts") {
if (value.type().get(field_type)) {
return throw_invalid_package_config();
@@ -405,6 +414,72 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
return nullptr;
}
const BindingData::PackageConfig* BindingData::FindNodeModulesPackage(
Realm* realm, const std::filesystem::path& check_path) {
std::filesystem::path current_path = check_path;
std::filesystem::path child;
std::filesystem::path grandchild;
const PackageConfig* innermost = nullptr;
auto env = realm->env();
const bool is_permissions_enabled = env->permission()->enabled();
while (true) {
auto parent_path = current_path.parent_path();
if (parent_path == current_path) {
// Reached the filesystem root.
break;
}
if (current_path.filename() == "node_modules") {
// `child` is the directory directly under node_modules. For scoped
// packages the package root is one level further down.
const std::string child_name = child.filename().generic_string();
const std::filesystem::path& package_root =
(!child_name.empty() && child_name[0] == '@') ? grandchild : child;
if (package_root.empty()) {
// The path points at a node_modules or bare scope directory rather
// than into a package.
return nullptr;
}
auto package_json_path = package_root / "package.json";
// GetPackageJSON()'s ReadFileSync() reads the file directly without
// consulting the permission model, so check read access here as
// TraverseParent() does. Without permission to confirm the package is
// private, fail closed and deny stripping.
if (is_permissions_enabled) [[unlikely]] {
if (!env->permission()->is_granted(
env,
permission::PermissionScope::kFileSystemRead,
ConvertGenericPathToUTF8(package_json_path))) {
return nullptr;
}
}
auto package_json =
GetPackageJSON(realm, ConvertPathToUTF8(package_json_path), nullptr);
if (package_json != nullptr) {
if (!package_json->is_private.value_or(false)) {
// A publishable (non-private) package encloses the file.
return nullptr;
}
if (innermost == nullptr) {
innermost = package_json;
}
}
// A directory under node_modules with no package.json is not a package
// (e.g. pnpm's `.pnpm` store); skip it and keep walking upwards.
}
grandchild = child;
child = current_path;
current_path = parent_path;
}
return innermost;
}
const std::filesystem::path BindingData::NormalizePath(
Realm* realm, BufferValue* path_value) {
// Check if the path has a trailing slash. If so, add it after
@@ -439,6 +514,22 @@ void BindingData::GetNearestParentPackageJSON(
}
}
void BindingData::GetRootPackageJSON(const FunctionCallbackInfo<Value>& args) {
CHECK_GE(args.Length(), 1);
CHECK(args[0]->IsString());
Realm* realm = Realm::GetCurrent(args);
BufferValue path_value(realm->isolate(), args[0]);
auto path = NormalizePath(realm, &path_value);
auto package_json = FindNodeModulesPackage(realm, path);
if (package_json != nullptr) {
args.GetReturnValue().Set(package_json->Serialize(realm));
}
}
void BindingData::GetNearestParentPackageJSONType(
const FunctionCallbackInfo<Value>& args) {
CHECK_GE(args.Length(), 1);
@@ -696,6 +787,7 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
target,
"getNearestParentPackageJSON",
GetNearestParentPackageJSON);
SetMethod(isolate, target, "getRootPackageJSON", GetRootPackageJSON);
SetMethod(
isolate, target, "getPackageScopeConfig", GetPackageScopeConfig<false>);
SetMethod(isolate, target, "getPackageType", GetPackageScopeConfig<true>);
@@ -769,6 +861,7 @@ void BindingData::RegisterExternalReferences(
registry->Register(ParsePackageJSONBinding);
registry->Register(GetNearestParentPackageJSONType);
registry->Register(GetNearestParentPackageJSON);
registry->Register(GetRootPackageJSON);
registry->Register(GetPackageScopeConfig<false>);
registry->Register(GetPackageScopeConfig<true>);
registry->Register(EnableCompileCache);
+8
View File
@@ -33,6 +33,7 @@ class BindingData : public SnapshotableObject {
std::optional<std::string> exports;
std::optional<std::string> imports;
std::optional<std::string> scripts;
std::optional<bool> is_private;
std::string raw_json;
v8::Local<v8::Array> Serialize(Realm* realm) const;
@@ -62,6 +63,8 @@ class BindingData : public SnapshotableObject {
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetNearestParentPackageJSON(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetRootPackageJSON(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetNearestParentPackageJSONType(
const v8::FunctionCallbackInfo<v8::Value>& args);
template <bool return_only_type>
@@ -103,6 +106,11 @@ class BindingData : public SnapshotableObject {
ErrorContext* error_context);
static const PackageConfig* TraverseParent(
Realm* realm, const std::filesystem::path& check_path);
// Returns the package.json of the package that owns check_path inside the
// innermost node_modules directory, or null when check_path is not inside
// node_modules or the package root has no package.json.
static const PackageConfig* FindNodeModulesPackage(
Realm* realm, const std::filesystem::path& check_path);
};
} // namespace modules
+5
View File
@@ -1479,6 +1479,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
kAllowedInEnvvar,
HAVE_AMARO);
AddAlias("--experimental-strip-types", "--strip-types");
AddOption("--experimental-strip-private-modules",
"Type-stripping for TypeScript files under node_modules "
"belonging to packages marked \"private\": true",
BOOL_FIELD(experimental_strip_private_modules),
kAllowedInEnvvar);
AddOption("--interactive",
"always enter the REPL even if stdin does not appear "
"to be a terminal",
+2
View File
@@ -298,6 +298,8 @@ class EnvironmentOptions : public Options {
DEFINE_BOOL_FIELD(tls_max_v1_2) = false;
DEFINE_BOOL_FIELD(tls_max_v1_3) = false;
DEFINE_BOOL_FIELD(strip_types) = HAVE_AMARO;
DEFINE_BOOL_FIELD(experimental_strip_private_modules) =
EXPERIMENTALS_DEFAULT_VALUE;
DEFINE_BOOL_FIELD(report_exclude_env) = false;
DEFINE_BOOL_FIELD(report_exclude_network) = false;
#if HAVE_INSPECTOR
+130
View File
@@ -61,6 +61,136 @@ test('execute a TypeScript file with node_modules', async () => {
assert.strictEqual(result.code, 0);
});
test('execute a TypeScript file from a private package in node_modules', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-private-modules',
'--no-warnings',
fixtures.path('typescript/ts/test-typescript-private-node-modules.ts'),
]);
assert.strictEqual(result.stderr, '');
assert.match(result.stdout, /Hello, TypeScript!/);
assert.strictEqual(result.code, 0);
});
test('execute a TypeScript file from a private package with a non-private nested package.json',
async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-private-modules',
'--no-warnings',
fixtures.path('typescript/ts/test-typescript-nested-nonprivate-node-modules.ts'),
]);
assert.strictEqual(result.stderr, '');
assert.match(result.stdout, /Hello, TypeScript!/);
assert.strictEqual(result.code, 0);
});
test('emit an experimental warning when stripping types in a private node_modules package',
async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-private-modules',
fixtures.path('typescript/ts/test-typescript-private-node-modules.ts'),
]);
assert.match(result.stderr, /Type stripping in node_modules is an experimental feature/);
assert.match(result.stdout, /Hello, TypeScript!/);
assert.strictEqual(result.code, 0);
});
test('require a TypeScript file from a private package in node_modules', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-private-modules',
'--no-warnings',
fixtures.path('typescript/ts/test-typescript-private-node-modules-require.ts'),
]);
assert.strictEqual(result.stderr, '');
assert.match(result.stdout, /Hello, TypeScript!/);
assert.strictEqual(result.code, 0);
});
test('expect failure for a private package in node_modules without --experimental-strip-private-modules',
async () => {
const result = await spawnPromisified(process.execPath, [
'--no-warnings',
fixtures.path('typescript/ts/test-typescript-private-node-modules.ts'),
]);
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
assert.strictEqual(result.stdout, '');
assert.strictEqual(result.code, 1);
});
// A nested package.json must not be able to enable type stripping: npm only
// refuses to publish packages whose root package.json has "private": true, so
// a published package can freely ship a nested package.json with
// "private": true. Only the package.json at the package root is consulted.
test('expect failure for a nested package.json faking "private": true in node_modules',
async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-private-modules',
'--no-warnings',
fixtures.path('typescript/ts/test-typescript-fake-private-node-modules.ts'),
]);
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
assert.strictEqual(result.stdout, '');
assert.strictEqual(result.code, 1);
});
test('expect failure for a private package bundled inside a non-private package',
async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-private-modules',
'--no-warnings',
fixtures.path('typescript/ts/test-typescript-bundled-private-node-modules.ts'),
]);
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
assert.strictEqual(result.stdout, '');
assert.strictEqual(result.code, 1);
});
test('expect failure for a relative import into a non-private node_modules package',
async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-private-modules',
'--no-warnings',
fixtures.path('typescript/ts/test-typescript-relative-node-modules.ts'),
]);
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
assert.strictEqual(result.stdout, '');
assert.strictEqual(result.code, 1);
});
test('expect failure for a ".." path resolving to a non-private node_modules package',
async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-private-modules',
'--no-warnings',
fixtures.path('typescript/ts/test-typescript-dotdot-node-modules.ts'),
]);
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
assert.strictEqual(result.stdout, '');
assert.strictEqual(result.code, 1);
});
test('expect failure for a non-private package in node_modules with --experimental-strip-private-modules',
async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-private-modules',
'--no-warnings',
fixtures.path('typescript/ts/test-import-ts-node-modules.ts'),
]);
assert.match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/);
assert.strictEqual(result.stdout, '');
assert.strictEqual(result.code, 1);
});
test('expect error when executing a TypeScript file with imports with no extensions', async () => {
const result = await spawnPromisified(process.execPath, [
fixtures.path('typescript/ts/test-import-no-extension.ts'),
+1
View File
@@ -0,0 +1 @@
export { baz } from 'bundled-private';
@@ -0,0 +1 @@
export const baz: string = 'Hello, TypeScript!';
@@ -0,0 +1,7 @@
{
"name": "bundled-private",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "bundled-private.ts"
}
+6
View File
@@ -0,0 +1,6 @@
{
"name": "bundler-pkg",
"version": "1.0.0",
"type": "module",
"exports": "./index.js"
}
@@ -0,0 +1 @@
export const baz: string = 'Hello, TypeScript!';
@@ -0,0 +1,4 @@
{
"private": true,
"type": "module"
}
@@ -0,0 +1,5 @@
{
"name": "fake-private-pkg",
"version": "1.0.0",
"main": "lib/fake-private-pkg.ts"
}
@@ -0,0 +1 @@
export const baz: string = 'Hello, TypeScript!';
@@ -0,0 +1,3 @@
{
"type": "module"
}
@@ -0,0 +1,7 @@
{
"name": "nested-nonprivate-pkg",
"version": "1.0.0",
"private": true,
"type": "module",
"exports": "./lib/index.ts"
}
@@ -0,0 +1,6 @@
{
"name": "private-pkg-cjs",
"version": "1.0.0",
"private": true,
"main": "private-pkg-cjs.ts"
}
@@ -0,0 +1,3 @@
const baz: string = 'Hello, TypeScript!';
module.exports = { baz };
+1
View File
@@ -0,0 +1 @@
export const greeting: string = 'Hello, TypeScript!';
+7
View File
@@ -0,0 +1,7 @@
{
"name": "private-pkg",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "private-pkg.ts"
}
+3
View File
@@ -0,0 +1,3 @@
import { greeting } from './greeting.ts';
export const baz: string = greeting;
@@ -0,0 +1,3 @@
import { baz } from 'bundler-pkg';
console.log(baz);
@@ -0,0 +1 @@
require('./node_modules/private-pkg-cjs/../bar/bar.ts');
@@ -0,0 +1,3 @@
import { baz } from 'fake-private-pkg';
console.log(baz);
@@ -0,0 +1,3 @@
import { baz } from 'nested-nonprivate-pkg';
console.log(baz);
@@ -0,0 +1,3 @@
const { baz } = require('private-pkg-cjs');
console.log(baz);
@@ -0,0 +1,3 @@
import { baz } from 'private-pkg';
console.log(baz);
@@ -0,0 +1 @@
require('./node_modules/bar/bar.ts');
+3
View File
@@ -5,6 +5,7 @@ export type PackageConfig = {
type: PackageType
exports?: string | string[] | Record<string, unknown>
imports?: string | string[] | Record<string, unknown>
private?: boolean
}
export type DeserializedPackageConfig = {
data: PackageConfig,
@@ -18,12 +19,14 @@ export type SerializedPackageConfig = [
string | undefined, // exports
string | undefined, // imports
DeserializedPackageConfig['path'], // pjson file path
PackageConfig['private'], // private
]
export interface ModulesBinding {
readPackageJSON(path: string): SerializedPackageConfig | undefined;
getNearestParentPackageJSONType(path: string): PackageConfig['type']
getNearestParentPackageJSON(path: string): SerializedPackageConfig | undefined
getRootPackageJSON(path: string): SerializedPackageConfig | undefined
getPackageScopeConfig(path: string): SerializedPackageConfig | undefined
getPackageType(path: string): PackageConfig['type'] | undefined
enableCompileCache(path?: string): { status: number, message?: string, directory?: string }