diff --git a/.gitattributes b/.gitattributes
index c14502a2a..6bf8e63e5 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -6,5 +6,5 @@ js/private/coverage/coverage.js linguist-generated=true
js/private/devserver/js_run_devserver.mjs linguist-generated=true
js/private/watch/aspect_watch_protocol.mjs linguist-generated=true
js/private/watch/aspect_watch_protocol.d.mts linguist-generated=true
-js/private/node-patches/fs.cjs linguist-generated=true
+js/private/node-patches/fs*.cjs linguist-generated=true
js/private/js_image_layer.mjs linguist-generated=true
diff --git a/.prettierignore b/.prettierignore
index 59e391e51..84c376e1e 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -6,6 +6,7 @@ examples/**/*-docs.md
js/private/coverage/coverage.js
js/private/devserver/js_run_devserver.mjs
js/private/node-patches/fs.cjs
+js/private/node-patches/fs_stat.cjs
js/private/watch/aspect_watch_protocol.mjs
js/private/watch/aspect_watch_protocol.d.mts
min/
diff --git a/MODULE.bazel b/MODULE.bazel
index 0752e91fd..e4037a003 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -14,7 +14,7 @@ bazel_dep(name = "bazel_features", version = "1.9.0")
bazel_dep(name = "bazel_lib", version = "3.0.0")
bazel_dep(name = "bazel_skylib", version = "1.5.0")
bazel_dep(name = "platforms", version = "0.0.5")
-bazel_dep(name = "rules_nodejs", version = "6.3.0")
+bazel_dep(name = "rules_nodejs", version = "6.4.0")
bazel_dep(name = "yq.bzl", version = "0.3.2")
tel = use_extension("@aspect_tools_telemetry//:extension.bzl", "telemetry")
diff --git a/js/private/js_binary.bzl b/js/private/js_binary.bzl
index a3469bd8f..6274363c0 100644
--- a/js/private/js_binary.bzl
+++ b/js/private/js_binary.bzl
@@ -205,6 +205,13 @@ _ATTRS = {
which can lead to non-hermetic behavior.""",
default = True,
),
+ "patch_node_esm_loader": attr.bool(
+ doc = """Apply the internal lstat patch to prevent the program from following symlinks out of
+ the execroot, runfiles and the sandbox even when using the ESM loader.
+
+ This flag only has an effect when `patch_node_fs` is True.""",
+ default = False,
+ ),
"include_sources": attr.bool(
doc = """When True, `sources` from `JsInfo` providers in `data` targets are included in the runfiles of the target.""",
default = True,
@@ -320,7 +327,10 @@ _ATTRS = {
"_windows_constraint": attr.label(default = "@platforms//os:windows"),
"_node_patches_files": attr.label_list(
allow_files = True,
- default = [Label("@aspect_rules_js//js/private/node-patches:fs.cjs")],
+ default = [
+ Label("@aspect_rules_js//js/private/node-patches:fs.cjs"),
+ Label("@aspect_rules_js//js/private/node-patches:fs_stat.cjs"),
+ ],
),
"_node_patches": attr.label(
allow_single_file = True,
@@ -566,11 +576,18 @@ def _create_launcher(ctx, log_prefix_rule_set, log_prefix_rule, fixed_args = [],
)
def _js_binary_impl(ctx):
+ # Only apply lstat patch if it's requested
+ JS_BINARY__PATCH_NODE_ESM_LOADER = "1" if ctx.attr.patch_node_esm_loader else "0"
+ fixed_env = {
+ "JS_BINARY__PATCH_NODE_ESM_LOADER": JS_BINARY__PATCH_NODE_ESM_LOADER,
+ }
+
launcher = _create_launcher(
ctx,
log_prefix_rule_set = "aspect_rules_js",
log_prefix_rule = "js_test" if ctx.attr.testonly else "js_binary",
fixed_args = ctx.attr.fixed_args,
+ fixed_env = fixed_env,
)
runfiles = launcher.runfiles
diff --git a/js/private/node-patches/BUILD.bazel b/js/private/node-patches/BUILD.bazel
index d6af777ee..ae23a883f 100644
--- a/js/private/node-patches/BUILD.bazel
+++ b/js/private/node-patches/BUILD.bazel
@@ -4,10 +4,12 @@ write_source_files(
name = "checked_in_compile",
files = {
"fs.cjs": "//js/private/node-patches/src:fs-generated.cjs",
+ "fs_stat.cjs": "//js/private/node-patches/src:fs_stat.cjs",
},
)
exports_files([
"fs.cjs",
+ "fs_stat.cjs",
"register.cjs",
])
diff --git a/js/private/node-patches/fs.cjs b/js/private/node-patches/fs.cjs
index f01045cf8..6e280e435 100644
--- a/js/private/node-patches/fs.cjs
+++ b/js/private/node-patches/fs.cjs
@@ -39,6 +39,7 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
Object.defineProperty(exports, "__esModule", { value: true });
exports.patcher = patcher;
exports.isSubPath = isSubPath;
+exports.resolvePathLike = resolvePathLike;
exports.escapeFunction = escapeFunction;
const path = require("path");
const util = require("util");
@@ -65,7 +66,7 @@ const PATCHED_FS_METHODS = [
* Function that patches the `fs` module to not escape the given roots.
* @returns a function to undo the patches.
*/
-function patcher(roots) {
+function patcher(roots, useInternalLstatPatch = false) {
if (fs._unpatched) {
throw new Error('FS is already patched.');
}
@@ -100,82 +101,93 @@ function patcher(roots) {
.native;
const { canEscape, isEscape } = escapeFunction(roots);
// =========================================================================
+ // fsInternal.lstat (to patch ESM resolve's `realpathSync`!)
+ // =========================================================================
+ let unpatchEsm;
+ if (useInternalLstatPatch) {
+ const lstatEsmPatcher = new (require('./fs_stat.cjs').FsInternalStatPatcher)({ canEscape, isEscape }, guardedReadLink, guardedReadLinkSync, unguardedRealPath, unguardedRealPathSync);
+ lstatEsmPatcher.patch();
+ unpatchEsm = lstatEsmPatcher.revert.bind(lstatEsmPatcher);
+ }
+ // =========================================================================
// fs.lstat
// =========================================================================
- fs.lstat = function lstat(...args) {
- // preserve error when calling function without required callback
- if (typeof args[args.length - 1] !== 'function') {
- return origLstat(...args);
- }
- const cb = once(args[args.length - 1]);
- // override the callback
- args[args.length - 1] = function lstatCb(err, stats) {
- if (err)
- return cb(err);
- if (!stats.isSymbolicLink()) {
+ if (!useInternalLstatPatch) {
+ fs.lstat = function lstat(...args) {
+ // preserve error when calling function without required callback
+ if (typeof args[args.length - 1] !== 'function') {
+ return origLstat(...args);
+ }
+ const cb = once(args[args.length - 1]);
+ // override the callback
+ args[args.length - 1] = function lstatCb(err, stats) {
+ if (err)
+ return cb(err);
+ if (!stats.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return cb(null, stats);
+ }
+ args[0] = resolvePathLike(args[0]);
+ if (!canEscape(args[0])) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return cb(null, stats);
+ }
+ return guardedReadLink(args[0], guardedReadLinkCb);
+ function guardedReadLinkCb(str) {
+ if (str != args[0]) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return cb(null, stats);
+ }
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPath here since that function calls lstat internally
+ // which can result in an infinite loop
+ return unguardedRealPath(args[0], unguardedRealPathCb);
+ function unguardedRealPathCb(err, str) {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return cb(null, stats);
+ }
+ return cb(err);
+ }
+ return origLstat(str, cb);
+ }
+ }
+ };
+ origLstat(...args);
+ };
+ fs.lstatSync = function lstatSync(...args) {
+ const stats = origLstatSync(...args);
+ if (!(stats === null || stats === void 0 ? void 0 : stats.isSymbolicLink())) {
// the file is not a symbolic link so there is nothing more to do
- return cb(null, stats);
+ return stats;
}
args[0] = resolvePathLike(args[0]);
if (!canEscape(args[0])) {
// the file can not escaped the sandbox so there is nothing more to do
- return cb(null, stats);
+ return stats;
}
- return guardedReadLink(args[0], guardedReadLinkCb);
- function guardedReadLinkCb(str) {
- if (str != args[0]) {
- // there are one or more hops within the guards so there is nothing more to do
- return cb(null, stats);
- }
+ const guardedReadLink = guardedReadLinkSync(args[0]);
+ if (guardedReadLink != args[0]) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return stats;
+ }
+ try {
+ args[0] = unguardedRealPathSync(args[0]);
// there are no hops so lets report the stats of the real file;
- // we can't use origRealPath here since that function calls lstat internally
+ // we can't use origRealPathSync here since that function calls lstat internally
// which can result in an infinite loop
- return unguardedRealPath(args[0], unguardedRealPathCb);
- function unguardedRealPathCb(err, str) {
- if (err) {
- if (err.code === 'ENOENT') {
- // broken link so there is nothing more to do
- return cb(null, stats);
- }
- return cb(err);
- }
- return origLstat(str, cb);
+ return origLstatSync(...args);
+ }
+ catch (err) {
+ if (err.code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return stats;
}
+ throw err;
}
};
- origLstat(...args);
- };
- fs.lstatSync = function lstatSync(...args) {
- const stats = origLstatSync(...args);
- if (!(stats === null || stats === void 0 ? void 0 : stats.isSymbolicLink())) {
- // the file is not a symbolic link so there is nothing more to do
- return stats;
- }
- args[0] = resolvePathLike(args[0]);
- if (!canEscape(args[0])) {
- // the file can not escaped the sandbox so there is nothing more to do
- return stats;
- }
- const guardedReadLink = guardedReadLinkSync(args[0]);
- if (guardedReadLink != args[0]) {
- // there are one or more hops within the guards so there is nothing more to do
- return stats;
- }
- try {
- args[0] = unguardedRealPathSync(args[0]);
- // there are no hops so lets report the stats of the real file;
- // we can't use origRealPathSync here since that function calls lstat internally
- // which can result in an infinite loop
- return origLstatSync(...args);
- }
- catch (err) {
- if (err.code === 'ENOENT') {
- // broken link so there is nothing more to do
- return stats;
- }
- throw err;
- }
- };
+ }
// =========================================================================
// fs.realpath
// =========================================================================
@@ -256,10 +268,14 @@ function patcher(roots) {
// The escape from the root is not mappable back into the root; throw EINVAL
return cb(enoent('readlink', args[0]));
}
- else {
+ else if (!useInternalLstatPatch) {
// The escape from the root is not mappable back into the root; throw EINVAL
return cb(einval('readlink', args[0]));
}
+ else {
+ // TODO: why does this only apply when useInternalLstatPatch is true?
+ return cb(null, str);
+ }
}
const r = path.resolve(path.dirname(resolved), path.relative(path.dirname(str), next));
if (r != resolved &&
@@ -388,7 +404,9 @@ function patcher(roots) {
let unpatchPromises;
if (promisePropertyDescriptor) {
const promises = {};
- promises.lstat = util.promisify(fs.lstat);
+ if (!useInternalLstatPatch) {
+ promises.lstat = util.promisify(fs.lstat);
+ }
// NOTE: node core uses the newer realpath function fs.promises.native instead of fs.realPath
promises.realpath = util.promisify(fs.realpath.native);
promises.readlink = util.promisify(fs.readlink);
@@ -558,36 +576,32 @@ function patcher(roots) {
readHopLink(maybe, readNextHop);
});
}
+ const symlinkNoThrow = Object.freeze({
+ throwIfNoEntry: false,
+ });
const hopLinkCache = Object.create(null);
function readHopLinkSync(p) {
if (hopLinkCache[p]) {
return hopLinkCache[p];
}
let link;
- try {
- if (origLstatSync(p).isSymbolicLink()) {
- link = origReadlinkSync(p);
- if (link) {
- if (!path.isAbsolute(link)) {
- link = path.resolve(path.dirname(p), link);
- }
- }
- else {
- link = HOP_NON_LINK;
+ const pStats = origLstatSync(p, symlinkNoThrow);
+ if (!pStats) {
+ link = HOP_NOT_FOUND;
+ }
+ else if (pStats.isSymbolicLink()) {
+ link = origReadlinkSync(p);
+ if (link) {
+ if (!path.isAbsolute(link)) {
+ link = path.resolve(path.dirname(p), link);
}
}
else {
link = HOP_NON_LINK;
}
}
- catch (err) {
- if (err.code === 'ENOENT') {
- // file does not exist
- link = HOP_NOT_FOUND;
- }
- else {
- link = HOP_NON_LINK;
- }
+ else {
+ link = HOP_NON_LINK;
}
hopLinkCache[p] = link;
return link;
@@ -761,6 +775,9 @@ function patcher(roots) {
if (unpatchPromises) {
unpatchPromises();
}
+ if (unpatchEsm) {
+ unpatchEsm();
+ }
// Re-sync the esm modules to revert to the unpatched module.
esmModule.syncBuiltinESMExports();
};
diff --git a/js/private/node-patches/fs_stat.cjs b/js/private/node-patches/fs_stat.cjs
new file mode 100644
index 000000000..279129368
--- /dev/null
+++ b/js/private/node-patches/fs_stat.cjs
@@ -0,0 +1,135 @@
+"use strict";
+// Patches Node's internal FS bindings, right before they would call into C++.
+// See full context in: https://github.com/aspect-build/rules_js/issues/362.
+// This is to ensure ESM imports don't escape accidentally via `realpathSync`.
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.FsInternalStatPatcher = void 0;
+///
+const binding_1 = require("internal/test/binding");
+const utils_1 = require("internal/fs/utils");
+const fs_cjs_1 = require("./fs.cjs");
+const internalFs = (0, binding_1.internalBinding)('fs');
+const internalFsLStat = internalFs.lstat;
+class FsInternalStatPatcher {
+ constructor(escapeFns, guardedReadLink, guardedReadLinkSync, unguardedRealPath, unguardedRealPathSync) {
+ this.escapeFns = escapeFns;
+ this.guardedReadLink = guardedReadLink;
+ this.guardedReadLinkSync = guardedReadLinkSync;
+ this.unguardedRealPath = unguardedRealPath;
+ this.unguardedRealPathSync = unguardedRealPathSync;
+ }
+ revert() {
+ internalFs.lstat = internalFsLStat;
+ }
+ patch() {
+ const statPatcher = this;
+ internalFs.lstat = function (path, bigint, reqCallback, throwIfNoEntry) {
+ const currentStack = new Error().stack;
+ const needsGuarding = currentStack &&
+ currentStack.includes('finalizeResolution (node:internal/modules/esm/resolve') &&
+ !currentStack.includes('eeguardStats');
+ if (!needsGuarding) {
+ return internalFsLStat.call(internalFs, path, bigint, reqCallback, throwIfNoEntry);
+ }
+ if (reqCallback === internalFs.kUsePromises) {
+ return internalFsLStat.call(internalFs, path, bigint, reqCallback, throwIfNoEntry).then((stats) => {
+ return new Promise((resolve, reject) => {
+ statPatcher.eeguardStats(path, bigint, stats, throwIfNoEntry, (err, guardedStats) => {
+ err ? reject(err) : resolve(guardedStats);
+ });
+ });
+ });
+ }
+ else if (reqCallback === undefined) {
+ const stats = internalFsLStat.call(internalFs, path, bigint, undefined, throwIfNoEntry);
+ if (!stats) {
+ return stats;
+ }
+ return statPatcher.eeguardStatsSync(path, bigint, throwIfNoEntry, stats);
+ }
+ else {
+ // Just re-use the promise path from above.
+ ;
+ internalFs.lstat(path, bigint, internalFs.kUsePromises, throwIfNoEntry)
+ .then((stats) => reqCallback.oncomplete(null, stats))
+ .catch((err) => reqCallback.oncomplete(err));
+ }
+ };
+ }
+ eeguardStats(path, bigint, stats, throwIfNotFound, cb) {
+ if (!stats) {
+ if (throwIfNotFound) {
+ return cb(new Error('ENOENT'));
+ }
+ return cb(null, stats);
+ }
+ const statsObj = (0, utils_1.getStatsFromBinding)(stats);
+ if (!statsObj.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return cb(null, stats);
+ }
+ path = (0, fs_cjs_1.resolvePathLike)(path);
+ if (!this.escapeFns.canEscape(path)) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return cb(null, stats);
+ }
+ return this.guardedReadLink(path, (str) => {
+ if (str != path) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return cb(null, stats);
+ }
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPath here since that function calls lstat internally
+ // which can result in an infinite loop.
+ return this.unguardedRealPath(path, (err, str) => {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return cb(null, stats);
+ }
+ return cb(err);
+ }
+ // Forward request to original callback.
+ const req2 = new internalFs.FSReqCallback(bigint);
+ req2.oncomplete = (err, realStats) => cb(err, realStats);
+ return internalFsLStat.call(internalFs, str, bigint, req2, throwIfNotFound);
+ });
+ });
+ }
+ eeguardStatsSync(path, bigint, throwIfNoEntry, stats) {
+ // No stats available
+ if (!stats) {
+ return stats;
+ }
+ const statsObj = (0, utils_1.getStatsFromBinding)(stats);
+ if (!statsObj.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return stats;
+ }
+ path = (0, fs_cjs_1.resolvePathLike)(path);
+ if (!this.escapeFns.canEscape(path)) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return stats;
+ }
+ const guardedReadLink = this.guardedReadLinkSync(path);
+ if (guardedReadLink != path) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return stats;
+ }
+ try {
+ path = this.unguardedRealPathSync(path);
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPathSync here since that function calls lstat internally
+ // which can result in an infinite loop
+ return internalFsLStat.call(internalFs, path, bigint, undefined, throwIfNoEntry);
+ }
+ catch (err) {
+ if (err.code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return stats;
+ }
+ throw err;
+ }
+ }
+}
+exports.FsInternalStatPatcher = FsInternalStatPatcher;
diff --git a/js/private/node-patches/register.cjs b/js/private/node-patches/register.cjs
index 85d67ddf9..5fd10591b 100644
--- a/js/private/node-patches/register.cjs
+++ b/js/private/node-patches/register.cjs
@@ -5,6 +5,7 @@ const {
JS_BINARY__LOG_PREFIX,
JS_BINARY__NODE_WRAPPER,
JS_BINARY__PATCH_NODE_FS,
+ JS_BINARY__PATCH_NODE_ESM_LOADER,
} = process.env
// Keep a count of how many times these patches are applied; this should reflect the depth
@@ -41,5 +42,8 @@ if (
`DEBUG: ${JS_BINARY__LOG_PREFIX}: node fs patches will be applied with roots: ${roots}`
)
}
- patchfs(roots)
+ const useLstatPatch =
+ JS_BINARY__PATCH_NODE_ESM_LOADER &&
+ JS_BINARY__PATCH_NODE_ESM_LOADER != '0'
+ patchfs(roots, useLstatPatch)
}
diff --git a/js/private/node-patches/src/BUILD.bazel b/js/private/node-patches/src/BUILD.bazel
index 5ed1769c5..ea19c014f 100644
--- a/js/private/node-patches/src/BUILD.bazel
+++ b/js/private/node-patches/src/BUILD.bazel
@@ -4,18 +4,24 @@ typescript_bin.tsc(
name = "compile",
srcs = [
"fs.cts",
+ "fs_stat.cts",
+ "fs_stat_types.d.cts",
"tsconfig.json",
"//:node_modules/@types/node",
],
outs = [
"fs.cjs",
+ "fs_stat.cjs",
],
args = [
"-p",
"tsconfig.json",
],
chdir = package_name(),
- visibility = ["//js/private/test/node-patches:__pkg__"],
+ visibility = [
+ "//js/private/node-patches:__pkg__",
+ "//js/private/test/node-patches:__pkg__",
+ ],
)
genrule(
diff --git a/js/private/node-patches/src/fs.cts b/js/private/node-patches/src/fs.cts
index 5362eb5a9..45496229b 100644
--- a/js/private/node-patches/src/fs.cts
+++ b/js/private/node-patches/src/fs.cts
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import type { PathLike, Stats, BigIntStats } from 'fs'
+import type { PathLike, Stats, StatSyncOptions, BigIntStats } from 'fs'
import type * as FsType from 'fs'
import type * as UrlType from 'url'
import * as path from 'path'
@@ -55,7 +55,10 @@ const PATCHED_FS_METHODS: ReadonlyArray = [
* Function that patches the `fs` module to not escape the given roots.
* @returns a function to undo the patches.
*/
-export function patcher(roots: string[]): () => void {
+export function patcher(
+ roots: string[],
+ useInternalLstatPatch: boolean = false
+): () => void {
if (fs._unpatched) {
throw new Error('FS is already patched.')
}
@@ -107,101 +110,125 @@ export function patcher(roots: string[]): () => void {
const { canEscape, isEscape } = escapeFunction(roots)
// =========================================================================
- // fs.lstat
+ // fsInternal.lstat (to patch ESM resolve's `realpathSync`!)
// =========================================================================
+ let unpatchEsm: Function | undefined
+ if (useInternalLstatPatch) {
+ const lstatEsmPatcher =
+ new (require('./fs_stat.cjs').FsInternalStatPatcher)(
+ { canEscape, isEscape },
+ guardedReadLink,
+ guardedReadLinkSync,
+ unguardedRealPath,
+ unguardedRealPathSync
+ )
- fs.lstat = function lstat(...args: Parameters) {
- // preserve error when calling function without required callback
- if (typeof args[args.length - 1] !== 'function') {
- return origLstat(...args)
- }
+ lstatEsmPatcher.patch()
- const cb = once(args[args.length - 1] as Function)
+ unpatchEsm = lstatEsmPatcher.revert.bind(lstatEsmPatcher)
+ }
- // override the callback
- args[args.length - 1] = function lstatCb(
- err: Error | null,
- stats: Stats | BigIntStats
- ) {
- if (err) return cb(err)
+ // =========================================================================
+ // fs.lstat
+ // =========================================================================
- if (!stats.isSymbolicLink()) {
- // the file is not a symbolic link so there is nothing more to do
- return cb(null, stats)
+ if (!useInternalLstatPatch) {
+ fs.lstat = function lstat(...args: Parameters) {
+ // preserve error when calling function without required callback
+ if (typeof args[args.length - 1] !== 'function') {
+ return origLstat(...args)
}
- args[0] = resolvePathLike(args[0])
+ const cb = once(args[args.length - 1] as Function)
- if (!canEscape(args[0])) {
- // the file can not escaped the sandbox so there is nothing more to do
- return cb(null, stats)
- }
+ // override the callback
+ args[args.length - 1] = function lstatCb(
+ err: Error | null,
+ stats: Stats | BigIntStats
+ ) {
+ if (err) return cb(err)
+
+ if (!stats.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return cb(null, stats)
+ }
- return guardedReadLink(args[0], guardedReadLinkCb)
+ args[0] = resolvePathLike(args[0])
- function guardedReadLinkCb(str: string) {
- if (str != args[0]) {
- // there are one or more hops within the guards so there is nothing more to do
+ if (!canEscape(args[0])) {
+ // the file can not escaped the sandbox so there is nothing more to do
return cb(null, stats)
}
- // there are no hops so lets report the stats of the real file;
- // we can't use origRealPath here since that function calls lstat internally
- // which can result in an infinite loop
- return unguardedRealPath(args[0], unguardedRealPathCb)
+ return guardedReadLink(args[0], guardedReadLinkCb)
- function unguardedRealPathCb(err: Error | null, str?: string) {
- if (err) {
- if ((err as any).code === 'ENOENT') {
- // broken link so there is nothing more to do
- return cb(null, stats)
+ function guardedReadLinkCb(str: string) {
+ if (str != args[0]) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return cb(null, stats)
+ }
+
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPath here since that function calls lstat internally
+ // which can result in an infinite loop
+ return unguardedRealPath(args[0], unguardedRealPathCb)
+
+ function unguardedRealPathCb(
+ err: Error | null,
+ str?: string
+ ) {
+ if (err) {
+ if ((err as any).code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return cb(null, stats)
+ }
+ return cb(err)
}
- return cb(err)
+ return origLstat(str!, cb)
}
- return origLstat(str!, cb)
}
}
+
+ origLstat(...args)
}
- origLstat(...args)
- }
+ fs.lstatSync = function lstatSync(
+ ...args: Parameters
+ ) {
+ const stats = origLstatSync(...args)
- fs.lstatSync = function lstatSync(
- ...args: Parameters
- ) {
- const stats = origLstatSync(...args)
+ if (!stats?.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return stats
+ }
- if (!stats?.isSymbolicLink()) {
- // the file is not a symbolic link so there is nothing more to do
- return stats
- }
+ args[0] = resolvePathLike(args[0])
- args[0] = resolvePathLike(args[0])
+ if (!canEscape(args[0])) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return stats
+ }
- if (!canEscape(args[0])) {
- // the file can not escaped the sandbox so there is nothing more to do
- return stats
- }
+ const guardedReadLink: string = guardedReadLinkSync(args[0])
+ if (guardedReadLink != args[0]) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return stats
+ }
- const guardedReadLink: string = guardedReadLinkSync(args[0])
- if (guardedReadLink != args[0]) {
- // there are one or more hops within the guards so there is nothing more to do
- return stats
- }
+ try {
+ args[0] = unguardedRealPathSync(args[0])
- try {
- args[0] = unguardedRealPathSync(args[0])
-
- // there are no hops so lets report the stats of the real file;
- // we can't use origRealPathSync here since that function calls lstat internally
- // which can result in an infinite loop
- return origLstatSync(...args)
- } catch (err: any) {
- if (err.code === 'ENOENT') {
- // broken link so there is nothing more to do
- return stats
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPathSync here since that function calls lstat internally
+ // which can result in an infinite loop
+ return origLstatSync(...args)
+ } catch (err: any) {
+ if (err.code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return stats
+ }
+ throw err
}
- throw err
}
}
@@ -309,9 +336,12 @@ export function patcher(roots: string[]): () => void {
if (next == undefined) {
// The escape from the root is not mappable back into the root; throw EINVAL
return cb(enoent('readlink', args[0]))
- } else {
+ } else if (!useInternalLstatPatch) {
// The escape from the root is not mappable back into the root; throw EINVAL
return cb(einval('readlink', args[0]))
+ } else {
+ // TODO: why does this only apply when useInternalLstatPatch is true?
+ return cb(null, str)
}
}
const r = path.resolve(
@@ -478,7 +508,9 @@ export function patcher(roots: string[]): () => void {
if (promisePropertyDescriptor) {
const promises: typeof fs.promises = {}
- promises.lstat = util.promisify(fs.lstat)
+ if (!useInternalLstatPatch) {
+ promises.lstat = util.promisify(fs.lstat)
+ }
// NOTE: node core uses the newer realpath function fs.promises.native instead of fs.realPath
promises.realpath = util.promisify(fs.realpath.native)
promises.readlink = util.promisify(fs.readlink)
@@ -677,6 +709,10 @@ export function patcher(roots: string[]): () => void {
})
}
+ const symlinkNoThrow: StatSyncOptions = Object.freeze({
+ throwIfNoEntry: false,
+ })
+
const hopLinkCache = Object.create(null) as { [f: string]: HopResults }
function readHopLinkSync(p: string): HopResults {
if (hopLinkCache[p]) {
@@ -685,26 +721,20 @@ export function patcher(roots: string[]): () => void {
let link: HopResults
- try {
- if (origLstatSync(p).isSymbolicLink()) {
- link = origReadlinkSync(p) as string
- if (link) {
- if (!path.isAbsolute(link)) {
- link = path.resolve(path.dirname(p), link)
- }
- } else {
- link = HOP_NON_LINK
+ const pStats = origLstatSync(p, symlinkNoThrow)
+ if (!pStats) {
+ link = HOP_NOT_FOUND
+ } else if (pStats.isSymbolicLink()) {
+ link = origReadlinkSync(p) as string
+ if (link) {
+ if (!path.isAbsolute(link)) {
+ link = path.resolve(path.dirname(p), link)
}
} else {
link = HOP_NON_LINK
}
- } catch (err: any) {
- if (err.code === 'ENOENT') {
- // file does not exist
- link = HOP_NOT_FOUND
- } else {
- link = HOP_NON_LINK
- }
+ } else {
+ link = HOP_NON_LINK
}
hopLinkCache[p] = link
@@ -921,6 +951,10 @@ export function patcher(roots: string[]): () => void {
unpatchPromises()
}
+ if (unpatchEsm) {
+ unpatchEsm()
+ }
+
// Re-sync the esm modules to revert to the unpatched module.
esmModule.syncBuiltinESMExports()
}
@@ -945,7 +979,7 @@ function stringifyPathLike(p: PathLike): string {
}
}
-function resolvePathLike(p: PathLike): string {
+export function resolvePathLike(p: PathLike): string {
return path.resolve(stringifyPathLike(p))
}
diff --git a/js/private/node-patches/src/fs_stat.cts b/js/private/node-patches/src/fs_stat.cts
new file mode 100644
index 000000000..d955a890a
--- /dev/null
+++ b/js/private/node-patches/src/fs_stat.cts
@@ -0,0 +1,219 @@
+// Patches Node's internal FS bindings, right before they would call into C++.
+// See full context in: https://github.com/aspect-build/rules_js/issues/362.
+// This is to ensure ESM imports don't escape accidentally via `realpathSync`.
+
+///
+
+import { internalBinding, FsInternalModule } from 'internal/test/binding'
+import { getStatsFromBinding } from 'internal/fs/utils'
+import { resolvePathLike, type escapeFunction } from './fs.cjs'
+
+const internalFs = internalBinding('fs')
+const internalFsLStat = internalFs.lstat
+
+export class FsInternalStatPatcher {
+ constructor(
+ private escapeFns: ReturnType,
+ private guardedReadLink: (
+ start: string,
+ cb: (str: string) => void
+ ) => void,
+ private guardedReadLinkSync: (start: string) => string,
+ private unguardedRealPath: (
+ start: string,
+ cb: (err: Error | null, str?: string) => void
+ ) => void,
+ private unguardedRealPathSync: (start: string) => string
+ ) {}
+
+ revert() {
+ internalFs.lstat = internalFsLStat
+ }
+
+ patch() {
+ const statPatcher = this
+
+ internalFs.lstat = function (
+ path,
+ bigint,
+ reqCallback,
+ throwIfNoEntry
+ ) {
+ const currentStack = new Error().stack
+ const needsGuarding =
+ currentStack &&
+ currentStack.includes(
+ 'finalizeResolution (node:internal/modules/esm/resolve'
+ ) &&
+ !currentStack.includes('eeguardStats')
+
+ if (!needsGuarding) {
+ return internalFsLStat.call(
+ internalFs,
+ path,
+ bigint,
+ reqCallback,
+ throwIfNoEntry
+ )
+ }
+
+ if (reqCallback === internalFs.kUsePromises) {
+ return (
+ internalFsLStat.call(
+ internalFs,
+ path,
+ bigint,
+ reqCallback,
+ throwIfNoEntry
+ ) as Promise
+ ).then((stats) => {
+ return new Promise((resolve, reject) => {
+ statPatcher.eeguardStats(
+ path,
+ bigint,
+ stats,
+ throwIfNoEntry,
+ (err, guardedStats) => {
+ err ? reject(err) : resolve(guardedStats as any)
+ }
+ )
+ })
+ })
+ } else if (reqCallback === undefined) {
+ const stats = internalFsLStat.call(
+ internalFs,
+ path,
+ bigint,
+ undefined,
+ throwIfNoEntry
+ ) as FsInternalModule.InternalStats
+ if (!stats) {
+ return stats
+ }
+ return statPatcher.eeguardStatsSync(
+ path,
+ bigint,
+ throwIfNoEntry,
+ stats
+ )
+ } else {
+ // Just re-use the promise path from above.
+ ;(
+ internalFs.lstat(
+ path,
+ bigint,
+ internalFs.kUsePromises,
+ throwIfNoEntry
+ ) as Promise
+ )
+ .then((stats) => reqCallback.oncomplete(null, stats))
+ .catch((err) => reqCallback.oncomplete(err))
+ }
+ }
+ }
+
+ eeguardStats(
+ path: string,
+ bigint: boolean,
+ stats: FsInternalModule.InternalStats | undefined,
+ throwIfNotFound: boolean,
+ cb: (err: unknown, stats?: FsInternalModule.InternalStats) => void
+ ) {
+ if (!stats) {
+ if (throwIfNotFound) {
+ return cb(new Error('ENOENT'))
+ }
+ return cb(null, stats)
+ }
+ const statsObj = getStatsFromBinding(stats)
+ if (!statsObj.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return cb(null, stats)
+ }
+
+ path = resolvePathLike(path)
+ if (!this.escapeFns.canEscape(path)) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return cb(null, stats)
+ }
+
+ return this.guardedReadLink(path, (str) => {
+ if (str != path) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return cb(null, stats)
+ }
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPath here since that function calls lstat internally
+ // which can result in an infinite loop.
+ return this.unguardedRealPath(path, (err, str) => {
+ if (err) {
+ if ((err as Partial<{ code: string }>).code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return cb(null, stats)
+ }
+ return cb(err)
+ }
+
+ // Forward request to original callback.
+ const req2 = new internalFs.FSReqCallback(bigint)
+ req2.oncomplete = (err, realStats) => cb(err, realStats)
+ return internalFsLStat.call(
+ internalFs,
+ str!,
+ bigint,
+ req2,
+ throwIfNotFound
+ )
+ })
+ })
+ }
+
+ eeguardStatsSync(
+ path: string,
+ bigint: boolean,
+ throwIfNoEntry: boolean,
+ stats: FsInternalModule.InternalStats
+ ): FsInternalModule.InternalStats {
+ // No stats available
+ if (!stats) {
+ return stats
+ }
+
+ const statsObj = getStatsFromBinding(stats)
+ if (!statsObj.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return stats
+ }
+
+ path = resolvePathLike(path)
+ if (!this.escapeFns.canEscape(path)) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return stats
+ }
+
+ const guardedReadLink = this.guardedReadLinkSync(path)
+ if (guardedReadLink != path) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return stats
+ }
+ try {
+ path = this.unguardedRealPathSync(path)
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPathSync here since that function calls lstat internally
+ // which can result in an infinite loop
+ return internalFsLStat.call(
+ internalFs,
+ path,
+ bigint,
+ undefined,
+ throwIfNoEntry
+ ) as FsInternalModule.InternalStats
+ } catch (err) {
+ if ((err as Partial<{ code: string }>).code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return stats
+ }
+ throw err
+ }
+ }
+}
diff --git a/js/private/node-patches/src/fs_stat_types.d.cts b/js/private/node-patches/src/fs_stat_types.d.cts
new file mode 100644
index 000000000..24199ab26
--- /dev/null
+++ b/js/private/node-patches/src/fs_stat_types.d.cts
@@ -0,0 +1,33 @@
+// Types of internal modules exposes via `--expose-internals`.
+// See: https://github.com/nodejs/node/blob/f58613a64c8e02b42391952a6e55a330a7607fa7/typings/internalBinding/fs.d.ts#L17.
+
+declare module 'internal/test/binding' {
+ namespace FsInternalModule {
+ // A random unique symbol to brand the internal stats type.
+ type InternalStats = { readonly __internalStatsBrandedType: unique symbol };
+
+ const kUsePromises: unique symbol;
+
+ class FSReqCallback {
+ constructor(bigint: boolean);
+ oncomplete: (err: unknown, stats?: InternalStats) => void;
+ }
+
+ // https://github.com/nodejs/node/blob/f58613a64c8e02b42391952a6e55a330a7607fa7/typings/internalBinding/fs.d.ts#L129-L137
+ function lstat(
+ path: string,
+ bigint: boolean,
+ cb: typeof kUsePromises | FSReqCallback | undefined,
+ throwIfNotFound: boolean,
+ ): InternalStats | Promise | void;
+ }
+
+ function internalBinding(module: 'fs'): typeof FsInternalModule;
+}
+
+declare module 'internal/fs/utils' {
+ import type { Stats } from 'node:fs';
+ import type { FsInternalModule } from 'internal/test/binding';
+
+ function getStatsFromBinding(stat: FsInternalModule.InternalStats): Stats;
+}
diff --git a/js/private/node_wrapper.sh b/js/private/node_wrapper.sh
index fae2c2a56..fcfec059f 100755
--- a/js/private/node_wrapper.sh
+++ b/js/private/node_wrapper.sh
@@ -2,4 +2,13 @@
set -o pipefail -o errexit -o nounset
-exec "$JS_BINARY__NODE_BINARY" --require "$JS_BINARY__NODE_PATCHES" "$@"
+if [[ "${JS_BINARY__PATCH_NODE_ESM_LOADER:-}" == "1" ]]; then
+ # --expose-internals is needed for FS esm patches.
+ # TODO: --disable-warning for stop warnings from that
+
+ exec "$JS_BINARY__NODE_BINARY" \
+ --expose-internals \
+ --require "$JS_BINARY__NODE_PATCHES" "$@"
+else
+ exec "$JS_BINARY__NODE_BINARY" --require "$JS_BINARY__NODE_PATCHES" "$@"
+fi
diff --git a/js/private/test/image/checksum_test.expected b/js/private/test/image/checksum_test.expected
index 7029c0493..bfd0c2508 100644
--- a/js/private/test/image/checksum_test.expected
+++ b/js/private/test/image/checksum_test.expected
@@ -1,4 +1,4 @@
-3fd4581c0ae8774a7d6d4b174802d91d0fcfc1b99d7b438abeac409636114a11 js/private/test/image/cksum_node
+6d3413395e3f7299a5983eb643a0e4ce606f6bcb13ca64b59b25982bd9adc0b7 js/private/test/image/cksum_node
052600f3a82ab6a4cc12cab7384971c960f9c589fdbfcf21bca563c36ff7d16e js/private/test/image/cksum_package_store_3p
971f291232f3ab63aff37fb66c96fbf0eddc05ea9564b9673d0d2c9bfe958994 js/private/test/image/cksum_package_store_1p
febf95a6d554c9bda3f0515bfd5ef273ac67d31c231d8162beaef8c4b7bc72f3 js/private/test/image/cksum_node_modules
diff --git a/js/private/test/image/custom_layers_nomatch_test_node.listing b/js/private/test/image/custom_layers_nomatch_test_node.listing
index 7ebe60575..226688a17 100644
--- a/js/private/test/image/custom_layers_nomatch_test_node.listing
+++ b/js/private/test/image/custom_layers_nomatch_test_node.listing
@@ -8,8 +8,9 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 0 0 34430 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
--r-xr-xr-x 0 0 0 1460 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
+-r-xr-xr-x 0 0 0 35651 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
+-r-xr-xr-x 0 0 0 6104 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs_stat.cjs
+-r-xr-xr-x 0 0 0 1631 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/nodejs/
diff --git a/js/private/test/image/custom_owner_test_app.listing b/js/private/test/image/custom_owner_test_app.listing
index 7d944026f..b24a87812 100644
--- a/js/private/test/image/custom_owner_test_app.listing
+++ b/js/private/test/image/custom_owner_test_app.listing
@@ -2,7 +2,7 @@ drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/
--r-xr-xr-x 0 100 0 xxxxx Jan 1 1970 ./js/private/test/image/bin
+-r-xr-xr-x 0 100 0 24025 Jan 1 1970 ./js/private/test/image/bin
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/
@@ -16,7 +16,7 @@ drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runf
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/
--r-xr-xr-x 0 100 0 xxxxx Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
+-r-xr-xr-x 0 100 0 24025 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/
--r-xr-xr-x 0 100 0 133 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
+-r-xr-xr-x 0 100 0 437 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
-r-xr-xr-x 0 100 0 20 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/main.js
diff --git a/js/private/test/image/custom_owner_test_node.listing b/js/private/test/image/custom_owner_test_node.listing
index c3c23c0a9..ddc6c453d 100644
--- a/js/private/test/image/custom_owner_test_node.listing
+++ b/js/private/test/image/custom_owner_test_node.listing
@@ -7,8 +7,9 @@ drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runf
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 100 0 34430 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
--r-xr-xr-x 0 100 0 1460 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
+-r-xr-xr-x 0 100 0 35651 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
+-r-xr-xr-x 0 100 0 6104 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs_stat.cjs
+-r-xr-xr-x 0 100 0 1631 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/nodejs/
diff --git a/js/private/test/image/default_test_app.listing b/js/private/test/image/default_test_app.listing
index 983df884a..f30e3488d 100644
--- a/js/private/test/image/default_test_app.listing
+++ b/js/private/test/image/default_test_app.listing
@@ -2,7 +2,7 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/
--r-xr-xr-x 0 0 0 xxxxx Jan 1 1970 ./js/private/test/image/bin
+-r-xr-xr-x 0 0 0 24025 Jan 1 1970 ./js/private/test/image/bin
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/
@@ -16,7 +16,7 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runf
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/
--r-xr-xr-x 0 0 0 xxxxx Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
+-r-xr-xr-x 0 0 0 24025 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/
--r-xr-xr-x 0 0 0 133 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
+-r-xr-xr-x 0 0 0 437 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
-r-xr-xr-x 0 0 0 20 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/main.js
diff --git a/js/private/test/image/default_test_node.listing b/js/private/test/image/default_test_node.listing
index 5f975bbfa..2e00f8baf 100644
--- a/js/private/test/image/default_test_node.listing
+++ b/js/private/test/image/default_test_node.listing
@@ -7,8 +7,9 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runf
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 0 0 34430 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
--r-xr-xr-x 0 0 0 1460 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
+-r-xr-xr-x 0 0 0 35651 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
+-r-xr-xr-x 0 0 0 6104 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs_stat.cjs
+-r-xr-xr-x 0 0 0 1631 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/nodejs/
diff --git a/js/private/test/image/non_ascii/custom_layer_groups_test_app.listing b/js/private/test/image/non_ascii/custom_layer_groups_test_app.listing
index 46846288d..41c33c0fe 100644
--- a/js/private/test/image/non_ascii/custom_layer_groups_test_app.listing
+++ b/js/private/test/image/non_ascii/custom_layer_groups_test_app.listing
@@ -4,7 +4,7 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/
--r-xr-xr-x 0 0 0 24076 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2
+-r-xr-xr-x 0 0 0 24120 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/
@@ -14,8 +14,8 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/
-r-xr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/ㅑㅕㅣㅇ.ㄴㅅ
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_/
--r-xr-xr-x 0 0 0 24076 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_/bin2
+-r-xr-xr-x 0 0 0 24120 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_/bin2
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_node_bin/
--r-xr-xr-x 0 0 0 133 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_node_bin/node
+-r-xr-xr-x 0 0 0 437 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_node_bin/node
-r-xr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/empty empty.ㄴㅅ
-r-xr-xr-x 0 0 0 20 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/main.js
diff --git a/js/private/test/image/non_ascii/custom_layer_groups_test_just_the_fs_patch.listing b/js/private/test/image/non_ascii/custom_layer_groups_test_just_the_fs_patch.listing
index 96cffcc60..eb6734f07 100644
--- a/js/private/test/image/non_ascii/custom_layer_groups_test_just_the_fs_patch.listing
+++ b/js/private/test/image/non_ascii/custom_layer_groups_test_just_the_fs_patch.listing
@@ -9,4 +9,4 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 0 0 34430 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/fs.cjs
+-r-xr-xr-x 0 0 0 35651 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/fs.cjs
diff --git a/js/private/test/image/non_ascii/custom_layer_groups_test_node.listing b/js/private/test/image/non_ascii/custom_layer_groups_test_node.listing
index 480acc973..71ca5a884 100644
--- a/js/private/test/image/non_ascii/custom_layer_groups_test_node.listing
+++ b/js/private/test/image/non_ascii/custom_layer_groups_test_node.listing
@@ -9,7 +9,8 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 0 0 1460 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/register.cjs
+-r-xr-xr-x 0 0 0 6104 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/fs_stat.cjs
+-r-xr-xr-x 0 0 0 1631 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/register.cjs
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/rules_nodejs~~node~nodejs_linux_amd64/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/nodejs/
diff --git a/js/private/test/image/regex_edge_cases_test_app.listing b/js/private/test/image/regex_edge_cases_test_app.listing
index 48ba14254..0d4efcf2b 100644
--- a/js/private/test/image/regex_edge_cases_test_app.listing
+++ b/js/private/test/image/regex_edge_cases_test_app.listing
@@ -3,7 +3,7 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/
--r-xr-xr-x 0 0 0 xxxxx Jan 1 1970 ./app/js/private/test/image/bin
+-r-xr-xr-x 0 0 0 24025 Jan 1 1970 ./app/js/private/test/image/bin
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/examples/
@@ -17,7 +17,7 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/
--r-xr-xr-x 0 0 0 xxxxx Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
+-r-xr-xr-x 0 0 0 24025 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/
--r-xr-xr-x 0 0 0 133 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
+-r-xr-xr-x 0 0 0 437 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
-r-xr-xr-x 0 0 0 20 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/main.js
diff --git a/js/private/test/image/regex_edge_cases_test_node.listing b/js/private/test/image/regex_edge_cases_test_node.listing
index 7ebe60575..226688a17 100644
--- a/js/private/test/image/regex_edge_cases_test_node.listing
+++ b/js/private/test/image/regex_edge_cases_test_node.listing
@@ -8,8 +8,9 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 0 0 34430 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
--r-xr-xr-x 0 0 0 1460 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
+-r-xr-xr-x 0 0 0 35651 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
+-r-xr-xr-x 0 0 0 6104 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs_stat.cjs
+-r-xr-xr-x 0 0 0 1631 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/nodejs/
diff --git a/js/private/test/node-patches/BUILD.bazel b/js/private/test/node-patches/BUILD.bazel
index 6f44f9458..5b57583e0 100644
--- a/js/private/test/node-patches/BUILD.bazel
+++ b/js/private/test/node-patches/BUILD.bazel
@@ -69,52 +69,70 @@ babel_bin.babel(
# Basic tests
[
[
- # The primary tests, both .js and .mjs
[
- js_test(
- name = "{}_{}_test".format(
- t.replace(".mjs", "").replace(".js", ""),
- toolchain_name,
- ),
- data = [
- "//:node_modules/inline-fixtures",
- "//js/private/node-patches/src:compile",
- ],
- entry_point = "copy_entry_{}".format(t),
- node_toolchain = toolchain,
- patch_node_fs = False,
- # Without node patches on for these tests, the program is going to escape the sandbox if it
- # is on since the fs patches are not on for the tests as they are the code under test
- tags = ["no-sandbox"],
- )
- for t in TESTS + MJS_TESTS
- ],
+ # The primary tests, both .js and .mjs
+ [
+ js_test(
+ name = "{}_{}{}_test".format(
+ t.replace(".mjs", "").replace(".js", ""),
+ toolchain_name,
+ "_esm" if useEsmPatch else "",
+ ),
+ data = [
+ "//:node_modules/inline-fixtures",
+ "//js/private/node-patches/src:compile",
+ ],
+ entry_point = "copy_entry_{}".format(t),
+ env = {
+ "NODE_PATCHES_TEST_ESM_LOADER": "1" if useEsmPatch else "",
+ },
+ node_options = ["--expose-internals"] if useEsmPatch else [],
+ node_toolchain = toolchain,
+ patch_node_esm_loader = False,
+ patch_node_fs = False,
+ # Without node patches on for these tests, the program is going to escape the sandbox if it
+ # is on since the fs patches are not on for the tests as they are the code under test
+ tags = ["no-sandbox"],
+ )
+ for t in TESTS + MJS_TESTS
+ ],
- # The .cjs tests converted from .mjs
- [
- js_test(
- name = "{}_{}_cjs_test".format(
- t.replace(".cjs", ""),
- toolchain_name,
- ),
- data = [
- "//:node_modules/inline-fixtures",
- "//js/private/node-patches/src:compile",
- ],
- entry_point = t,
- node_toolchain = toolchain,
- patch_node_fs = False,
- # Without node patches on for these tests, the program is going to escape the sandbox if it
- # is on since the fs patches are not on for the tests as they are the code under test
- tags = ["no-sandbox"],
- )
- for t in CJS_TESTS
- ],
+ # The .cjs tests converted from .mjs
+ [
+ js_test(
+ name = "{}_{}{}_cjs_test".format(
+ t.replace(".cjs", ""),
+ toolchain_name,
+ "_esm" if useEsmPatch else "",
+ ),
+ data = [
+ "//:node_modules/inline-fixtures",
+ "//js/private/node-patches/src:compile",
+ ],
+ entry_point = "copy_entry_{}".format(t) if (t in TESTS) else t,
+ env = {
+ "NODE_PATCHES_TEST_ESM_LOADER": "1" if useEsmPatch else "",
+ },
+ node_options = ["--expose-internals"] if useEsmPatch else [],
+ node_toolchain = toolchain,
+ patch_node_esm_loader = False,
+ patch_node_fs = False,
+ # Without node patches on for these tests, the program is going to escape the sandbox if it
+ # is on since the fs patches are not on for the tests as they are the code under test
+ tags = ["no-sandbox"],
+ )
+ for t in CJS_TESTS
+ ],
+ ]
+ for toolchain_name, toolchain in zip(
+ TOOLCHAINS_NAMES,
+ TOOLCHAINS_VERSIONS,
+ )
+ ]
+ for useEsmPatch in [
+ False,
+ True,
]
- for toolchain_name, toolchain in zip(
- TOOLCHAINS_NAMES,
- TOOLCHAINS_VERSIONS,
- )
]
# Node process spawning tests
diff --git a/js/private/test/node-patches/lstat.mjs b/js/private/test/node-patches/lstat.mjs
index 41d583a0c..e322c385a 100644
--- a/js/private/test/node-patches/lstat.mjs
+++ b/js/private/test/node-patches/lstat.mjs
@@ -22,6 +22,8 @@ import * as util from 'node:util'
import { patcher } from '../../node-patches/src/fs.cjs'
+const useEsmPatch = process.env.NODE_PATCHES_TEST_ESM_LOADER === '1'
+
// We don't want to bring jest into this repo so we just fake the describe and it functions here
async function describe(_, fn) {
await fn()
@@ -45,7 +47,10 @@ describe('testing lstat', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir)])
+ const revertPatches = patcher(
+ [path.join(fixturesDir)],
+ useEsmPatch
+ )
const linkPath = path.join(fixturesDir, 'a', 'link')
assert.ok(
@@ -77,10 +82,10 @@ describe('testing lstat', async () => {
async (fixturesDir) => {
fixturesDir = fs.realpathSync(fixturesDir)
- const revertPatches = patcher([
- path.join(fixturesDir),
- path.join(fixturesDir, 'a', 'g'),
- ])
+ const revertPatches = patcher(
+ [path.join(fixturesDir), path.join(fixturesDir, 'a', 'g')],
+ useEsmPatch
+ )
assert.equal(
undefined,
@@ -108,10 +113,10 @@ describe('testing lstat', async () => {
path.join(fixturesDir, 'a', 'g', 'link')
)
- const revertPatches = patcher([
- path.join(fixturesDir),
- path.join(fixturesDir, 'a', 'g'),
- ])
+ const revertPatches = patcher(
+ [path.join(fixturesDir), path.join(fixturesDir, 'a', 'g')],
+ useEsmPatch
+ )
console.error('Starting')
console.error(fs.readlink.toString())
@@ -151,7 +156,10 @@ describe('testing lstat', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir, 'a')])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'a')],
+ useEsmPatch
+ )
const linkPath = path.join(fixturesDir, 'a', 'link')
@@ -222,7 +230,10 @@ describe('testing lstat', async () => {
path.join(fixturesDir, 'b', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir, 'a')])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'a')],
+ useEsmPatch
+ )
const linkPath = path.join(fixturesDir, 'b', 'link')
diff --git a/js/private/test/node-patches/opendir.mjs b/js/private/test/node-patches/opendir.mjs
index 6c9b63ad9..d958099cf 100644
--- a/js/private/test/node-patches/opendir.mjs
+++ b/js/private/test/node-patches/opendir.mjs
@@ -22,6 +22,8 @@ import * as util from 'node:util'
import { patcher } from '../../node-patches/src/fs.cjs'
+const useEsmPatch = process.env.NODE_PATCHES_TEST_ESM_LOADER === '1'
+
// We don't want to bring jest into this repo so we just fake the describe and it functions here
async function describe(_, fn) {
await fn()
@@ -45,7 +47,7 @@ describe('testing opendir', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([fixturesDir])
+ const revertPatches = patcher([fixturesDir], useEsmPatch)
let dir
dir = await util.promisify(fs.opendir)(
@@ -114,7 +116,10 @@ describe('testing opendir', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir, 'a')])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'a')],
+ useEsmPatch
+ )
let dir
dir = await util.promisify(fs.opendir)(
@@ -178,7 +183,10 @@ describe('testing opendir', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir)])
+ const revertPatches = patcher(
+ [path.join(fixturesDir)],
+ useEsmPatch
+ )
const dir = await util.promisify(fs.opendir)(
path.join(fixturesDir, 'a')
@@ -214,7 +222,10 @@ describe('testing opendir', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir, 'a')])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'a')],
+ useEsmPatch
+ )
const dir = await util.promisify(fs.opendir)(
path.join(fixturesDir, 'a')
@@ -271,9 +282,10 @@ describe('testing opendir', async () => {
path.join(fixturesDir, 'sandbox', 'link')
)
- const revertPatches = patcher([
- path.join(fixturesDir, 'sandbox'),
- ])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'sandbox')],
+ useEsmPatch
+ )
let dir
dir = await util.promisify(fs.opendir)(
diff --git a/js/private/test/node-patches/readdir.mjs b/js/private/test/node-patches/readdir.mjs
index 835afa2ac..c62efb1ad 100644
--- a/js/private/test/node-patches/readdir.mjs
+++ b/js/private/test/node-patches/readdir.mjs
@@ -22,6 +22,8 @@ import * as util from 'node:util'
import { patcher } from '../../node-patches/src/fs.cjs'
+const useEsmPatch = process.env.NODE_PATCHES_TEST_ESM_LOADER === '1'
+
// We don't want to bring jest into this repo so we just fake the describe and it functions here
async function describe(_, fn) {
await fn()
@@ -45,7 +47,7 @@ describe('testing readdir', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([fixturesDir])
+ const revertPatches = patcher([fixturesDir], useEsmPatch)
let dirents = fs.readdirSync(path.join(fixturesDir, 'a'), {
withFileTypes: true,
@@ -104,7 +106,10 @@ describe('testing readdir', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir, 'a')])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'a')],
+ useEsmPatch
+ )
console.error('FOO')
console.error(fs.readdirSync)
@@ -180,9 +185,10 @@ describe('testing readdir', async () => {
path.join(fixturesDir, 'sandbox', 'link')
)
- const revertPatches = patcher([
- path.join(fixturesDir, 'sandbox'),
- ])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'sandbox')],
+ useEsmPatch
+ )
let dirents = fs.readdirSync(
path.join(fixturesDir, 'sandbox'),
diff --git a/js/private/test/node-patches/readlink.mjs b/js/private/test/node-patches/readlink.mjs
index cfa90f167..5ca75cef1 100644
--- a/js/private/test/node-patches/readlink.mjs
+++ b/js/private/test/node-patches/readlink.mjs
@@ -22,6 +22,8 @@ import * as util from 'node:util'
import { patcher } from '../../node-patches/src/fs.cjs'
+const useEsmPatch = process.env.NODE_PATCHES_TEST_ESM_LOADER === '1'
+
// We don't want to bring jest into this repo so we just fake the describe and it functions here
async function describe(_, fn) {
await fn()
@@ -45,7 +47,10 @@ describe('testing readlink', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir)])
+ const revertPatches = patcher(
+ [path.join(fixturesDir)],
+ useEsmPatch
+ )
const linkPath = path.join(fixturesDir, 'a', 'link')
@@ -100,7 +105,10 @@ describe('testing readlink', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir, 'a')])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'a')],
+ useEsmPatch
+ )
const linkPath = path.join(
fs.realpathSync(fixturesDir),
@@ -160,9 +168,10 @@ describe('testing readlink', async () => {
path.join(fixturesDir, 'sandbox', 'link')
)
- const revertPatches = patcher([
- path.join(fixturesDir, 'sandbox'),
- ])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'sandbox')],
+ useEsmPatch
+ )
const linkPath = path.join(fixturesDir, 'sandbox', 'link')
const filePath = path.join(fixturesDir, 'sandbox', 'file')
@@ -224,9 +233,10 @@ describe('testing readlink', async () => {
path.join(fixturesDir, 'sandbox', 'link')
)
- const revertPatches = patcher([
- path.join(fixturesDir, 'sandbox'),
- ])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'sandbox')],
+ useEsmPatch
+ )
const linkPath = path.join(fixturesDir, 'sandbox', 'link')
const filePath = path.join(fixturesDir, 'sandbox', 'file')
diff --git a/js/private/test/node-patches/realpath.mjs b/js/private/test/node-patches/realpath.mjs
index de37b435b..8ddb0b5f4 100644
--- a/js/private/test/node-patches/realpath.mjs
+++ b/js/private/test/node-patches/realpath.mjs
@@ -22,6 +22,8 @@ import * as util from 'node:util'
import { patcher } from '../../node-patches/src/fs.cjs'
+const useEsmPatch = process.env.NODE_PATCHES_TEST_ESM_LOADER === '1'
+
// We don't want to bring jest into this repo so we just fake the describe and it functions here
async function describe(_, fn) {
await fn()
@@ -32,7 +34,7 @@ async function it(_, fn) {
describe('testing realpath', async () => {
await it('can handle empty, dot, undefined & null values', async () => {
- const revertPatches = patcher([process.cwd()])
+ const revertPatches = patcher([process.cwd()], useEsmPatch)
// ---------------------------------------------------------------------
// empty string
@@ -166,7 +168,10 @@ describe('testing realpath', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir)])
+ const revertPatches = patcher(
+ [path.join(fixturesDir)],
+ useEsmPatch
+ )
const linkPath = path.join(
fs.realpathSync(fixturesDir),
'a',
@@ -217,7 +222,10 @@ describe('testing realpath', async () => {
// on mac, inside of bazel, the fixtures dir returned here is not realpath-ed.
fixturesDir = fs.realpathSync(fixturesDir)
- const revertPatches = patcher([path.join(fixturesDir)])
+ const revertPatches = patcher(
+ [path.join(fixturesDir)],
+ useEsmPatch
+ )
const filePath = path.join(
fs.realpathSync(fixturesDir),
'a',
@@ -310,7 +318,10 @@ describe('testing realpath', async () => {
path.join(fixturesDir, 'a', 'link')
)
- const revertPatches = patcher([path.join(fixturesDir, 'a')])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'a')],
+ useEsmPatch
+ )
const linkPath = path.join(
fs.realpathSync(fixturesDir),
'a',
@@ -492,9 +503,10 @@ describe('testing realpath', async () => {
path.join(fixturesDir, 'sandbox', 'link')
)
- const revertPatches = patcher([
- path.join(fixturesDir, 'sandbox'),
- ])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'sandbox')],
+ useEsmPatch
+ )
const linkPath = path.join(fixturesDir, 'sandbox', 'link')
assert.deepStrictEqual(
@@ -571,9 +583,10 @@ describe('testing realpath', async () => {
path.join(fixturesDir, 'sandbox', 'link')
)
- const revertPatches = patcher([
- path.join(fixturesDir, 'sandbox'),
- ])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'sandbox')],
+ useEsmPatch
+ )
const linkPath = path.join(fixturesDir, 'sandbox', 'link')
assert.throws(() => {
@@ -663,9 +676,10 @@ describe('testing realpath', async () => {
path.join(fixturesDir, 'sandbox', 'node_modules', 'pkg')
)
- const revertPatches = patcher([
- path.join(fixturesDir, 'sandbox'),
- ])
+ const revertPatches = patcher(
+ [path.join(fixturesDir, 'sandbox')],
+ useEsmPatch
+ )
const linkPath = path.join(
fixturesDir,
'sandbox',
diff --git a/js/private/test/node-patches/spawn.js b/js/private/test/node-patches/spawn.js
index 24a54d519..e343b9479 100644
--- a/js/private/test/node-patches/spawn.js
+++ b/js/private/test/node-patches/spawn.js
@@ -16,7 +16,12 @@ async function it(_, fn) {
describe('child_process node path', async () => {
function assertNodePath({ stdout, stderr, code, error }) {
// Process errors
- if (stderr?.toString().trim()) {
+ if (
+ stderr
+ ?.toString()
+ .replace(/.*internal\/test\/binding.*\.\n.*/, '')
+ .trim()
+ ) {
throw new Error(`Received stderr: ${stderr.toString()}`)
} else if (code) {
throw new Error(`Exit code: ${code}`)
@@ -128,7 +133,12 @@ describe('child_process node path', async () => {
describe('child_process patch depth', async () => {
function assertPatchDepth({ stdout, stderr, code, error }) {
// Process errors
- if (stderr?.toString().trim()) {
+ if (
+ stderr
+ ?.toString()
+ .replace(/.*internal\/test\/binding.*\.\n.*/, '')
+ .trim()
+ ) {
throw new Error(`Received stderr: ${stderr.toString()}`)
} else if (code) {
throw new Error(`Exit code: ${code}`)
diff --git a/js/private/test/snapshots/launcher.sh b/js/private/test/snapshots/launcher.sh
index 106b436e3..a8398c6b8 100644
--- a/js/private/test/snapshots/launcher.sh
+++ b/js/private/test/snapshots/launcher.sh
@@ -12,6 +12,7 @@
set -o pipefail -o errexit -o nounset
+export JS_BINARY__PATCH_NODE_ESM_LOADER="0"
export JS_BINARY__BINDIR="bazel-out/k8-fastbuild/bin"
export JS_BINARY__COMPILATION_MODE="fastbuild"
export JS_BINARY__TARGET_CPU="k8"