From 0a5bccaadd332b8df2b191d19e09bf125b4b8e88 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Mon, 22 Dec 2025 17:30:34 -0800 Subject: [PATCH 1/2] test: add additional esm sandbox assertions --- .../npm_translate_lock_LTE4Nzc1MDcwNjU= | 4 +- examples/js_binary/BUILD.bazel | 87 ++++++++++++++----- .../{require_acorn.js => require_acorn.cjs} | 0 examples/js_binary/require_acorn.mjs | 10 +++ examples/js_binary/test.js | 2 - examples/js_binary/test.mjs | 2 + examples/macro/mocha.bzl | 2 + examples/macro/test.js | 10 +++ examples/npm_deps/BUILD.bazel | 18 ++-- .../npm_package/packages/pkg_c/BUILD.bazel | 4 +- .../packages/pkg_c/data1/package.json | 4 + .../packages/pkg_c/data2/package.json | 4 + .../npm_package/packages/pkg_c/src/index.js | 1 - .../npm_package/packages/pkg_c/src/index.mjs | 27 ++++++ .../npm_package/packages/pkg_d/BUILD.bazel | 3 +- examples/npm_package/packages/pkg_d/index.js | 19 ---- examples/npm_package/packages/pkg_d/index.mjs | 28 ++++++ .../npm_package/packages/pkg_d/package.json | 6 ++ .../npm_package/packages/pkg_e/BUILD.bazel | 3 +- examples/npm_package/packages/pkg_e/index.js | 6 -- examples/npm_package/packages/pkg_e/index.mjs | 25 ++++++ .../npm_package/packages/pkg_e/package.json | 6 ++ .../test/image/custom_owner_test_app.listing | 4 +- .../test/image/default_test_app.listing | 4 +- .../image/regex_edge_cases_test_app.listing | 4 +- 25 files changed, 217 insertions(+), 66 deletions(-) rename examples/js_binary/{require_acorn.js => require_acorn.cjs} (100%) create mode 100644 examples/js_binary/require_acorn.mjs delete mode 100644 examples/js_binary/test.js create mode 100644 examples/js_binary/test.mjs delete mode 100644 examples/npm_package/packages/pkg_c/src/index.js create mode 100644 examples/npm_package/packages/pkg_c/src/index.mjs delete mode 100644 examples/npm_package/packages/pkg_d/index.js create mode 100644 examples/npm_package/packages/pkg_d/index.mjs delete mode 100644 examples/npm_package/packages/pkg_e/index.js create mode 100644 examples/npm_package/packages/pkg_e/index.mjs diff --git a/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= b/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= index c8fe9d1397..e39f88122f 100755 --- a/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= +++ b/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= @@ -16,8 +16,8 @@ examples/npm_deps/patches/meaning-of-life@1.0.0-pnpm.patch=-442666336 examples/npm_package/libs/lib_a/package.json=-1377103079 examples/npm_package/packages/pkg_a/package.json=1006424040 examples/npm_package/packages/pkg_b/package.json=1041247977 -examples/npm_package/packages/pkg_d/package.json=1110895851 -examples/npm_package/packages/pkg_e/package.json=-2145239245 +examples/npm_package/packages/pkg_d/package.json=-1434007576 +examples/npm_package/packages/pkg_e/package.json=-336138332 examples/runfiles/package.json=-1545884645 examples/stack_traces/package.json=2011229626 examples/webpack_cli/package.json=1911342006 diff --git a/examples/js_binary/BUILD.bazel b/examples/js_binary/BUILD.bazel index a20a605c8b..1d52347aa1 100644 --- a/examples/js_binary/BUILD.bazel +++ b/examples/js_binary/BUILD.bazel @@ -25,7 +25,14 @@ js_binary( name = "bin", # Reference the location where the acorn npm module was linked in the root Bazel package data = ["//:node_modules/acorn"], - entry_point = "require_acorn.js", + entry_point = "require_acorn.mjs", +) + +js_binary( + name = "bin_cjs", + # Reference the location where the acorn npm module was linked in the root Bazel package + data = ["//:node_modules/acorn"], + entry_point = "require_acorn.cjs", ) #################################################### @@ -48,6 +55,22 @@ diff_test( file2 = "out1", ) +genrule( + name = "run1_cjs", + srcs = [], + outs = ["out1_cjs"], + # All js_binary rules need a BAZEL_BINDIR environment variable set so they can + # run from that directory as the working directory. + cmd = "BAZEL_BINDIR=$(BINDIR) $(location :bin_cjs) {}/out1_cjs".format(package_name()), + tools = [":bin_cjs"], +) + +diff_test( + name = "test_js_binary_under_genrule_cjs", + file1 = "//examples:expected_one_ast.json", + file2 = "out1_cjs", +) + #################################################### # Use case 2 # Using js_run_binary has some nice syntax sugar vs. @@ -73,6 +96,26 @@ diff_test( file2 = "out2", ) +js_run_binary( + name = "run2_cjs", + srcs = [], + outs = ["out2_cjs"], + args = ["out2_cjs"], + chdir = package_name(), + # Request that the rules_js launcher prints extra information + log_level = "debug", + tool = ":bin_cjs", + # Uncomment the setting below to see debug output even on a + # successful run of the build action. + # silent_on_success = False, +) + +diff_test( + name = "test_js_binary_under_js_run_binary_cjs", + file1 = "//examples:expected_one_ast.json", + file2 = "out2_cjs", +) + # Also test with local (no sandbox) execution by setting execution_requirements "local" to "1". # Bazel sets different environment variables in this case such as RUNFILES_MANIFEST_FILE. # This case tests for regression of the fix in https://github.com/aspect-build/rules_js/pull/323. @@ -101,8 +144,8 @@ diff_test( # Also test with copy_data_to_bin disabled copy_to_bin( - name = "require_acorn_js", - srcs = ["require_acorn.js"], + name = "require_acorn_mjs", + srcs = ["require_acorn.mjs"], ) js_binary( @@ -110,7 +153,7 @@ js_binary( copy_data_to_bin = False, # Reference the location where the acorn npm module was linked in the root Bazel package data = ["//:node_modules/acorn"], - entry_point = ":require_acorn_js", + entry_point = ":require_acorn_mjs", ) js_run_binary( @@ -142,7 +185,7 @@ diff_test( js_test( name = "test_test", data = ["//:node_modules/@types/node"], - entry_point = "test.js", + entry_point = "test.mjs", ) ############################### @@ -152,11 +195,12 @@ js_test( write_file( name = "write4", - out = "case4.js", + out = "case4.mjs", content = [ - """require('fs').writeFileSync( + "import { writeFileSync } from 'fs'", + """writeFileSync( process.argv[2], - require(process.argv[3]).toAst("1") + (await import(process.argv[3])).toAst("1") )""", ], ) @@ -167,7 +211,7 @@ js_binary( ":node_modules/@mycorp/pkg-a", "//:node_modules/@mycorp/pkg-b", ], - entry_point = "case4.js", + entry_point = "case4.mjs", ) js_run_binary( @@ -228,12 +272,13 @@ diff_test( write_file( name = "write5", - out = "case5.js", - content = ["""\ -require('fs').writeFileSync( - process.argv[2], - JSON.stringify(require(require('path').join(process.cwd(), "data.json"))) -)"""], + out = "case5.mjs", + content = [ + "import { writeFileSync } from 'fs';", + "import { join } from 'path';", + "const jsonData = await import(join(process.cwd(), 'data.json'), { with: { type: 'json' } });", + "writeFileSync(process.argv[2], JSON.stringify(jsonData.default))", + ], ) write_file( @@ -244,7 +289,7 @@ write_file( js_binary( name = "bin5", - entry_point = "case5.js", + entry_point = "case5.mjs", ) js_run_binary( @@ -557,11 +602,13 @@ js_test( write_file( name = "write13", - out = "case13.js", + out = "case13.mjs", content = [ - """require('fs').writeFileSync( + "import { writeFileSync } from 'fs';", + """ + writeFileSync( process.argv[2], - JSON.stringify(require(process.argv[3])) + JSON.stringify((await import(process.argv[3])).default) )""", ], ) @@ -572,7 +619,7 @@ js_binary( "//:node_modules/@mycorp/pkg-c1", "//:node_modules/@mycorp/pkg-c2", ], - entry_point = "case13.js", + entry_point = "case13.mjs", ) write_file( diff --git a/examples/js_binary/require_acorn.js b/examples/js_binary/require_acorn.cjs similarity index 100% rename from examples/js_binary/require_acorn.js rename to examples/js_binary/require_acorn.cjs diff --git a/examples/js_binary/require_acorn.mjs b/examples/js_binary/require_acorn.mjs new file mode 100644 index 0000000000..c08a5e12d4 --- /dev/null +++ b/examples/js_binary/require_acorn.mjs @@ -0,0 +1,10 @@ +/** + * @fileoverview minimal test program that requires a third-party package from npm + */ +import { writeFileSync } from 'node:fs' +import { parse } from 'acorn' + +writeFileSync( + process.argv[2], + JSON.stringify(parse('1', { ecmaVersion: 2020 })) + '\n' +) diff --git a/examples/js_binary/test.js b/examples/js_binary/test.js deleted file mode 100644 index 43d055e484..0000000000 --- a/examples/js_binary/test.js +++ /dev/null @@ -1,2 +0,0 @@ -console.log('\n\n\nThis is only a test.\n\n\n') -console.log(require.resolve('@types/node/package.json')) diff --git a/examples/js_binary/test.mjs b/examples/js_binary/test.mjs new file mode 100644 index 0000000000..8e700623e3 --- /dev/null +++ b/examples/js_binary/test.mjs @@ -0,0 +1,2 @@ +console.log('\n\n\nThis is only a test.\n\n\n') +console.log(import.meta.resolve('@types/node/package.json')) diff --git a/examples/macro/mocha.bzl b/examples/macro/mocha.bzl index 367260a78a..275097538d 100644 --- a/examples/macro/mocha.bzl +++ b/examples/macro/mocha.bzl @@ -22,5 +22,7 @@ def mocha_test(name, srcs, args = [], data = [], env = {}, **kwargs): # to the location Bazel expects. "MOCHA_FILE": "$$XML_OUTPUT_FILE", }), + preserve_symlinks_main = False, + copy_data_to_bin = False, **kwargs ) diff --git a/examples/macro/test.js b/examples/macro/test.js index ec3591f222..621246bc3f 100644 --- a/examples/macro/test.js +++ b/examples/macro/test.js @@ -1,4 +1,5 @@ const assert = require('assert') +const { dirname } = require('node:path') describe('mocha', () => { it('integrates with Bazel', () => { @@ -8,4 +9,13 @@ describe('mocha', () => { it('is in bazel-out', () => { assert.match(__dirname, /bazel-out/) }) + + it('is sandboxed', () => { + assert.match(__dirname, /examples\/macro\/test_\/test\.runfiles/) + assert.match( + __filename, + /-sandbox\/\d+\/execroot\/_main\/bazel-out\/[^/]+\/bin\/examples\/macro\/test_\/test.runfiles\/_main\/examples\/macro\/test\.js/ + ) + assert.equal(__dirname, dirname(__filename)) + }) }) diff --git a/examples/npm_deps/BUILD.bazel b/examples/npm_deps/BUILD.bazel index 9c2dcb2a8c..07fd30d9d0 100644 --- a/examples/npm_deps/BUILD.bazel +++ b/examples/npm_deps/BUILD.bazel @@ -265,8 +265,11 @@ js_test( write_file( name = "write9", - out = "case9.js", - content = ["require('@mycorp/pkg-d')"], + out = "case9.mjs", + content = [ + "import { sandboxAssert } from '@mycorp/pkg-d'", + "sandboxAssert()", + ], ) js_test( @@ -274,7 +277,7 @@ js_test( data = [ ":node_modules/@mycorp/pkg-d", ], - entry_point = "case9.js", + entry_point = "case9.mjs", ) ####################################### @@ -282,8 +285,11 @@ js_test( write_file( name = "write10", - out = "case10.js", - content = ["require('@mycorp/pkg-e')"], + out = "case10.mjs", + content = [ + "import { sandboxAssert } from '@mycorp/pkg-e'", + "sandboxAssert()", + ], ) js_test( @@ -291,5 +297,5 @@ js_test( data = [ ":node_modules/@mycorp/pkg-e", ], - entry_point = "case10.js", + entry_point = "case10.mjs", ) diff --git a/examples/npm_package/packages/pkg_c/BUILD.bazel b/examples/npm_package/packages/pkg_c/BUILD.bazel index 2e246c3a10..cbb0a71ca0 100644 --- a/examples/npm_package/packages/pkg_c/BUILD.bazel +++ b/examples/npm_package/packages/pkg_c/BUILD.bazel @@ -9,7 +9,7 @@ npm_package( srcs = [ "data1/package.json", "data1/pkg-c.json", - "src/index.js", + "src/index.mjs", ], package = "@mycorp/pkg-c1", root_paths = [ @@ -25,7 +25,7 @@ npm_package( srcs = [ "data2/package.json", "data2/pkg-c.json", - "src/index.js", + "src/index.mjs", ], package = "@mycorp/pkg-c2", root_paths = [ diff --git a/examples/npm_package/packages/pkg_c/data1/package.json b/examples/npm_package/packages/pkg_c/data1/package.json index 2d75d20ba5..34759e98cf 100644 --- a/examples/npm_package/packages/pkg_c/data1/package.json +++ b/examples/npm_package/packages/pkg_c/data1/package.json @@ -1,5 +1,9 @@ { "name": "@mycorp/pkg-c1", + "type": "module", + "exports": { + ".": "./index.mjs" + }, "private": true, "dependencies": {} } diff --git a/examples/npm_package/packages/pkg_c/data2/package.json b/examples/npm_package/packages/pkg_c/data2/package.json index c42a189569..7f267863be 100644 --- a/examples/npm_package/packages/pkg_c/data2/package.json +++ b/examples/npm_package/packages/pkg_c/data2/package.json @@ -1,5 +1,9 @@ { "name": "@mycorp/pkg-c2", + "type": "module", + "exports": { + ".": "./index.mjs" + }, "private": true, "dependencies": {} } diff --git a/examples/npm_package/packages/pkg_c/src/index.js b/examples/npm_package/packages/pkg_c/src/index.js deleted file mode 100644 index 32e977a33a..0000000000 --- a/examples/npm_package/packages/pkg_c/src/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./pkg-c.json') diff --git a/examples/npm_package/packages/pkg_c/src/index.mjs b/examples/npm_package/packages/pkg_c/src/index.mjs new file mode 100644 index 0000000000..4ad2b436d8 --- /dev/null +++ b/examples/npm_package/packages/pkg_c/src/index.mjs @@ -0,0 +1,27 @@ +import { fileURLToPath } from 'node:url' +import pkgC from './pkg-c.json' assert { type: 'json' } + +export default pkgC + +export function sandboxAssert() { + const __filename = fileURLToPath(import.meta.url) + + if ( + !/-sandbox\/\d+\/execroot\/_main\/bazel-out\/.*\/bin\/node_modules\/\.aspect_rules_js\/[^\/]+\/node_modules\/@mycorp\/pkg-c\d\/index\.mjs$/.test( + __filename + ) + ) { + throw new Error(`Not in sandbox: ${__filename}`) + } + + // TODO: https://github.com/aspect-build/rules_js/issues/362 + // if ( + // !/\/bazel-out\/[^/]+\/bin\/.*\.runfiles\/.*\/index.mjs$/.test( + // __filename + // ) + // ) { + // throw new Error(`Not in runfiles: ${__filename}`) + // } +} + +sandboxAssert() diff --git a/examples/npm_package/packages/pkg_d/BUILD.bazel b/examples/npm_package/packages/pkg_d/BUILD.bazel index 366b368fcc..d6e68c6897 100644 --- a/examples/npm_package/packages/pkg_d/BUILD.bazel +++ b/examples/npm_package/packages/pkg_d/BUILD.bazel @@ -6,9 +6,10 @@ npm_link_all_packages(name = "node_modules") js_library( name = "pkg", srcs = [ - "index.js", + "index.mjs", "package.json", ], + copy_data_to_bin = False, visibility = ["//visibility:public"], # because we're linking this js_library, we must explictly add our npm dependendies to `deps` so # they are picked up my the linker. npm dependendies in `data` are not propogated through the diff --git a/examples/npm_package/packages/pkg_d/index.js b/examples/npm_package/packages/pkg_d/index.js deleted file mode 100644 index fef5665088..0000000000 --- a/examples/npm_package/packages/pkg_d/index.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @fileoverview minimal test program that requires a third-party package from npm - */ -const acorn = require('acorn') -const { v4: uuid } = require('uuid') - -function toAst(program) { - return JSON.stringify(acorn.parse(program, { ecmaVersion: 2020 })) + '\n' -} - -function getAcornVersion() { - return acorn.version -} - -module.exports = { - toAst, - getAcornVersion, - uuid, -} diff --git a/examples/npm_package/packages/pkg_d/index.mjs b/examples/npm_package/packages/pkg_d/index.mjs new file mode 100644 index 0000000000..f1d7db8f21 --- /dev/null +++ b/examples/npm_package/packages/pkg_d/index.mjs @@ -0,0 +1,28 @@ +/** + * @fileoverview minimal test program that requires a third-party package from npm + */ +import * as acorn from 'acorn' +import { fileURLToPath } from 'node:url' +export { v4 as uuid } from 'uuid' + +export function toAst(program) { + return JSON.stringify(acorn.parse(program, { ecmaVersion: 2020 })) + '\n' +} + +export function getAcornVersion() { + return acorn.version +} + +export function sandboxAssert() { + const __filename = fileURLToPath(import.meta.url) + + if ( + !/-sandbox\/\d+\/execroot\/_main\/bazel-out\/[^/]+\/bin\/.*\.runfiles\/.*\/index.mjs$/.test( + __filename + ) + ) { + throw new Error(`Not in sandbox runfiles: ${__filename}`) + } +} + +sandboxAssert() diff --git a/examples/npm_package/packages/pkg_d/package.json b/examples/npm_package/packages/pkg_d/package.json index f6bd811259..7a76c78dfa 100644 --- a/examples/npm_package/packages/pkg_d/package.json +++ b/examples/npm_package/packages/pkg_d/package.json @@ -1,5 +1,11 @@ { "name": "@mycorp/pkg-d", + "type": "module", + "exports": { + ".": { + "import": "./index.mjs" + } + }, "private": true, "dependencies": { "uuid": "8.3.2" diff --git a/examples/npm_package/packages/pkg_e/BUILD.bazel b/examples/npm_package/packages/pkg_e/BUILD.bazel index 366b368fcc..d6e68c6897 100644 --- a/examples/npm_package/packages/pkg_e/BUILD.bazel +++ b/examples/npm_package/packages/pkg_e/BUILD.bazel @@ -6,9 +6,10 @@ npm_link_all_packages(name = "node_modules") js_library( name = "pkg", srcs = [ - "index.js", + "index.mjs", "package.json", ], + copy_data_to_bin = False, visibility = ["//visibility:public"], # because we're linking this js_library, we must explictly add our npm dependendies to `deps` so # they are picked up my the linker. npm dependendies in `data` are not propogated through the diff --git a/examples/npm_package/packages/pkg_e/index.js b/examples/npm_package/packages/pkg_e/index.js deleted file mode 100644 index 83db516b86..0000000000 --- a/examples/npm_package/packages/pkg_e/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @fileoverview minimal test program that requires a another workspace project - * that defines its package using js_library. - */ - -module.exports = require('@mycorp/pkg-d') diff --git a/examples/npm_package/packages/pkg_e/index.mjs b/examples/npm_package/packages/pkg_e/index.mjs new file mode 100644 index 0000000000..b677d04fa3 --- /dev/null +++ b/examples/npm_package/packages/pkg_e/index.mjs @@ -0,0 +1,25 @@ +/** + * @fileoverview minimal test program that requires a another workspace project + * that defines its package using js_library. + */ + +import { fileURLToPath } from 'node:url' + +import { sandboxAssert as dSandboxAssert } from '@mycorp/pkg-d' +export { getAcornVersion, toAst, uuid } from '@mycorp/pkg-d' + +export function sandboxAssert() { + const __filename = fileURLToPath(import.meta.url) + + if ( + !/-sandbox\/\d+\/execroot\/_main\/bazel-out\/[^/]+\/bin\/.*\.runfiles\/.*\/index.mjs$/.test( + __filename + ) + ) { + throw new Error(`Not in sandbox runfiles: ${__filename}`) + } + + dSandboxAssert() +} + +sandboxAssert() diff --git a/examples/npm_package/packages/pkg_e/package.json b/examples/npm_package/packages/pkg_e/package.json index 20ba5f1b7b..7784c7a75b 100644 --- a/examples/npm_package/packages/pkg_e/package.json +++ b/examples/npm_package/packages/pkg_e/package.json @@ -1,6 +1,12 @@ { "name": "@mycorp/pkg-e", + "type": "module", "private": true, + "exports": { + ".": { + "import": "./index.mjs" + } + }, "dependencies": { "@mycorp/pkg-d": "workspace:*" } diff --git a/js/private/test/image/custom_owner_test_app.listing b/js/private/test/image/custom_owner_test_app.listing index 7d944026fe..2b3bd16c30 100644 --- a/js/private/test/image/custom_owner_test_app.listing +++ b/js/private/test/image/custom_owner_test_app.listing @@ -9,8 +9,8 @@ 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/examples/npm_package/ drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/ drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/ --r-xr-xr-x 0 100 0 387 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/index.js --r-xr-xr-x 0 100 0 164 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/package.json +-r-xr-xr-x 0 100 0 707 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/index.mjs +-r-xr-xr-x 0 100 0 271 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/package.json 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/test/ diff --git a/js/private/test/image/default_test_app.listing b/js/private/test/image/default_test_app.listing index 983df884a8..675f8cb5f3 100644 --- a/js/private/test/image/default_test_app.listing +++ b/js/private/test/image/default_test_app.listing @@ -9,8 +9,8 @@ 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/examples/npm_package/ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/ --r-xr-xr-x 0 0 0 387 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/index.js --r-xr-xr-x 0 0 0 164 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/package.json +-r-xr-xr-x 0 0 0 707 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/index.mjs +-r-xr-xr-x 0 0 0 271 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/package.json 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/test/ 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 48ba14254d..42442059b2 100644 --- a/js/private/test/image/regex_edge_cases_test_app.listing +++ b/js/private/test/image/regex_edge_cases_test_app.listing @@ -10,8 +10,8 @@ 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/examples/npm_package/ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/ --r-xr-xr-x 0 0 0 387 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/index.js --r-xr-xr-x 0 0 0 164 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/package.json +-r-xr-xr-x 0 0 0 707 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/index.mjs +-r-xr-xr-x 0 0 0 271 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/examples/npm_package/packages/pkg_d/package.json 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/test/ From d64d517e5910c2fbc5d57cba9835641e33f5f8a6 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 12 Jun 2025 17:30:29 +0000 Subject: [PATCH 2/2] feat: add experimental js_binary(patch_node_esm_loader) --- .gitattributes | 2 +- .prettierignore | 1 + docs/js_binary.md | 8 +- docs/js_run_binary.md | 5 +- js/private/js_binary.bzl | 14 +- js/private/js_run_binary.bzl | 8 + js/private/node-patches/BUILD.bazel | 2 + js/private/node-patches/fs.cjs | 27 ++- js/private/node-patches/fs_stat.cjs | 146 +++++++++++++ js/private/node-patches/register.cjs | 6 +- js/private/node-patches/src/BUILD.bazel | 8 +- js/private/node-patches/src/fs.cts | 42 +++- js/private/node-patches/src/fs_stat.cts | 205 ++++++++++++++++++ .../node-patches/src/fs_stat_types.d.cts | 32 +++ js/private/node_wrapper.sh | 6 +- js/private/test/image/asserts.bzl | 2 +- js/private/test/image/checksum_test.expected | 2 +- .../custom_layers_nomatch_test_node.listing | 5 +- .../test/image/custom_owner_test_app.listing | 2 +- .../test/image/custom_owner_test_node.listing | 5 +- .../test/image/default_test_app.listing | 2 +- .../test/image/default_test_node.listing | 5 +- .../custom_layer_groups_test_app.listing | 6 +- ...ayer_groups_test_just_the_fs_patch.listing | 2 +- .../custom_layer_groups_test_node.listing | 3 +- .../image/regex_edge_cases_test_app.listing | 2 +- .../image/regex_edge_cases_test_node.listing | 5 +- js/private/test/node-patches/BUILD.bazel | 131 +++++++---- js/private/test/node-patches/data.json | 4 + js/private/test/node-patches/esm.mjs | 29 +++ js/private/test/node-patches/lib.mjs | 13 ++ js/private/test/node-patches/lstat.mjs | 33 ++- js/private/test/node-patches/opendir.mjs | 26 ++- js/private/test/node-patches/readdir.mjs | 16 +- js/private/test/node-patches/readlink.mjs | 26 ++- js/private/test/node-patches/realpath.mjs | 40 ++-- js/private/test/node-patches/spawn.js | 14 +- tools/update-snapshots.sh | 1 + 38 files changed, 766 insertions(+), 120 deletions(-) create mode 100644 js/private/node-patches/fs_stat.cjs create mode 100644 js/private/node-patches/src/fs_stat.cts create mode 100644 js/private/node-patches/src/fs_stat_types.d.cts create mode 100644 js/private/test/node-patches/data.json create mode 100644 js/private/test/node-patches/esm.mjs create mode 100644 js/private/test/node-patches/lib.mjs diff --git a/.gitattributes b/.gitattributes index c14502a2ac..6bf8e63e5b 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 59e391e515..84c376e1ea 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/docs/js_binary.md b/docs/js_binary.md index c9f5001934..49538586c4 100644 --- a/docs/js_binary.md +++ b/docs/js_binary.md @@ -24,8 +24,8 @@ js_binary( js_binary(name, data, chdir, copy_data_to_bin, enable_runfiles, entry_point, env, expand_args, expand_env, expected_exit_code, fixed_args, include_npm, include_npm_sources, include_sources, include_transitive_sources, include_transitive_types, include_types, - log_level, no_copy_to_bin, node_options, node_toolchain, patch_node_fs, - preserve_symlinks_main) + log_level, no_copy_to_bin, node_options, node_toolchain, patch_node_esm_loader, + patch_node_fs, preserve_symlinks_main) Execute a program in the Node.js runtime. @@ -91,6 +91,7 @@ The following environment variables are made available to the Node.js runtime ba | no_copy_to_bin | List of files to not copy to the Bazel output tree when `copy_data_to_bin` is True.

This is useful for exceptional cases where a `copy_to_bin` is not possible or not suitable for an input file such as a file in an external repository. In most cases, this option is not needed. See `copy_data_to_bin` docstring for more info. | List of labels | optional | `[]` | | node_options | Options to pass to the node invocation on the command line.

https://nodejs.org/api/cli.html

These options are passed directly to the node invocation on the command line. Options passed here will take precendence over options passed via the NODE_OPTIONS environment variable. Options passed here are not added to the NODE_OPTIONS environment variable so will not be automatically picked up by child processes that inherit that enviroment variable. | List of strings | optional | `[]` | | node_toolchain | The Node.js toolchain to use for this target.

See https://bazel-contrib.github.io/rules_nodejs/Toolchains.html

Typically this is left unset so that Bazel automatically selects the right Node.js toolchain for the target platform. See https://bazel.build/extending/toolchains#toolchain-resolution for more information. | Label | optional | `None` | +| patch_node_esm_loader | 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. | Boolean | optional | `False` | | patch_node_fs | Patch the to Node.js `fs` API (https://nodejs.org/api/fs.html) for this node program to prevent the program from following symlinks out of the execroot, runfiles and the sandbox.

When enabled, `js_binary` patches the Node.js sync and async `fs` API functions `lstat`, `readlink`, `realpath`, `readdir` and `opendir` so that the node program being run cannot resolve symlinks out of the execroot and the runfiles tree. When in the sandbox, these patches prevent the program being run from resolving symlinks out of the sandbox.

When disabled, node programs can leave the execroot, runfiles and sandbox by following symlinks which can lead to non-hermetic behavior. | Boolean | optional | `True` | | preserve_symlinks_main | When True, the --preserve-symlinks-main flag is passed to node.

This prevents node from following an ESM entry script out of runfiles and the sandbox. This can happen for `.mjs` ESM entry points where the fs node patches, which guard the runfiles and sandbox, are not applied. See https://github.com/aspect-build/rules_js/issues/362 for more information. Once #362 is resolved, the default for this attribute can be set to False.

This flag was added in Node.js v10.2.0 (released 2018-05-23). If your node toolchain is configured to use a Node.js version older than this you'll need to set this attribute to False.

See https://nodejs.org/api/cli.html#--preserve-symlinks-main for more information. | Boolean | optional | `True` | @@ -103,7 +104,7 @@ The following environment variables are made available to the Node.js runtime ba js_test(name, data, chdir, copy_data_to_bin, enable_runfiles, entry_point, env, env_inherit, expand_args, expand_env, expected_exit_code, fixed_args, include_npm, include_npm_sources, include_sources, include_transitive_sources, include_transitive_types, include_types, - log_level, no_copy_to_bin, node_options, node_toolchain, patch_node_fs, + log_level, no_copy_to_bin, node_options, node_toolchain, patch_node_esm_loader, patch_node_fs, preserve_symlinks_main) @@ -156,6 +157,7 @@ the contract between Bazel and a test runner. | no_copy_to_bin | List of files to not copy to the Bazel output tree when `copy_data_to_bin` is True.

This is useful for exceptional cases where a `copy_to_bin` is not possible or not suitable for an input file such as a file in an external repository. In most cases, this option is not needed. See `copy_data_to_bin` docstring for more info. | List of labels | optional | `[]` | | node_options | Options to pass to the node invocation on the command line.

https://nodejs.org/api/cli.html

These options are passed directly to the node invocation on the command line. Options passed here will take precendence over options passed via the NODE_OPTIONS environment variable. Options passed here are not added to the NODE_OPTIONS environment variable so will not be automatically picked up by child processes that inherit that enviroment variable. | List of strings | optional | `[]` | | node_toolchain | The Node.js toolchain to use for this target.

See https://bazel-contrib.github.io/rules_nodejs/Toolchains.html

Typically this is left unset so that Bazel automatically selects the right Node.js toolchain for the target platform. See https://bazel.build/extending/toolchains#toolchain-resolution for more information. | Label | optional | `None` | +| patch_node_esm_loader | 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. | Boolean | optional | `False` | | patch_node_fs | Patch the to Node.js `fs` API (https://nodejs.org/api/fs.html) for this node program to prevent the program from following symlinks out of the execroot, runfiles and the sandbox.

When enabled, `js_binary` patches the Node.js sync and async `fs` API functions `lstat`, `readlink`, `realpath`, `readdir` and `opendir` so that the node program being run cannot resolve symlinks out of the execroot and the runfiles tree. When in the sandbox, these patches prevent the program being run from resolving symlinks out of the sandbox.

When disabled, node programs can leave the execroot, runfiles and sandbox by following symlinks which can lead to non-hermetic behavior. | Boolean | optional | `True` | | preserve_symlinks_main | When True, the --preserve-symlinks-main flag is passed to node.

This prevents node from following an ESM entry script out of runfiles and the sandbox. This can happen for `.mjs` ESM entry points where the fs node patches, which guard the runfiles and sandbox, are not applied. See https://github.com/aspect-build/rules_js/issues/362 for more information. Once #362 is resolved, the default for this attribute can be set to False.

This flag was added in Node.js v10.2.0 (released 2018-05-23). If your node toolchain is configured to use a Node.js version older than this you'll need to set this attribute to False.

See https://nodejs.org/api/cli.html#--preserve-symlinks-main for more information. | Boolean | optional | `True` | diff --git a/docs/js_run_binary.md b/docs/js_run_binary.md index b2211697cf..c276225b12 100644 --- a/docs/js_run_binary.md +++ b/docs/js_run_binary.md @@ -20,8 +20,8 @@ js_run_binary(name, silent_on_success, use_execroot_entry_point, copy_srcs_to_bin, include_sources, include_types, include_transitive_sources, include_transitive_types, include_npm_sources, log_level, mnemonic, progress_message, execution_requirements, - stamp, patch_node_fs, allow_execroot_entry_point_with_no_copy_data_to_bin, - use_default_shell_env, kwargs) + stamp, patch_node_fs, patch_node_esm_loader, + allow_execroot_entry_point_with_no_copy_data_to_bin, use_default_shell_env, kwargs) Wrapper around @aspect_bazel_lib `run_binary` that adds convenience attributes for using a `js_binary` tool. @@ -73,6 +73,7 @@ The following environment variables are made available to the Node.js runtime ba | execution_requirements | Information for scheduling the action.

For example,

execution_requirements = {
    "no-cache": "1",
},


See https://docs.bazel.build/versions/main/be/common-definitions.html#common.tags for useful keys. | `None` | | stamp | Whether to include build status files as inputs to the tool. Possible values:

- `stamp = 0 (default)`: Never include build status files as inputs to the tool. This gives good build result caching. Most tools don't use the status files, so including them in `--stamp` builds makes those builds have many needless cache misses. (Note: this default is different from most rules with an integer-typed `stamp` attribute.) - `stamp = 1`: Always include build status files as inputs to the tool, even in [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. This setting should be avoided, since it is non-deterministic. It potentially causes remote cache misses for the target and any downstream actions that depend on the result. - `stamp = -1`: Inclusion of build status files as inputs is controlled by the [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag. Stamped targets are not rebuilt unless their dependencies change.

Default value is `0` since the majority of js_run_binary targets in a build graph typically do not use build status files and including them for all js_run_binary actions whenever `--stamp` is set would result in invalidating the entire graph and would prevent cache hits. Stamping is typically done in terminal targets when building release artifacts and stamp should typically be set explicitly in these targets to `-1` so it is enabled when the `--stamp` flag is set.

When stamping is enabled, an additional two environment variables will be set for the action: - `BAZEL_STABLE_STATUS_FILE` - `BAZEL_VOLATILE_STATUS_FILE`

These files can be read and parsed by the action, for example to pass some values to a bundler. | `0` | | patch_node_fs | Patch the to Node.js `fs` API (https://nodejs.org/api/fs.html) for this node program to prevent the program from following symlinks out of the execroot, runfiles and the sandbox.

When enabled, `js_binary` patches the Node.js sync and async `fs` API functions `lstat`, `readlink`, `realpath`, `readdir` and `opendir` so that the node program being run cannot resolve symlinks out of the execroot and the runfiles tree. When in the sandbox, these patches prevent the program being run from resolving symlinks out of the sandbox.

When disabled, node programs can leave the execroot, runfiles and sandbox by following symlinks which can lead to non-hermetic behavior. | `True` | +| patch_node_esm_loader | additionally patch the Node.js ESM loader | `False` | | allow_execroot_entry_point_with_no_copy_data_to_bin | Turn off validation that the `js_binary` tool has `copy_data_to_bin` set to True when `use_execroot_entry_point` is set to True.

See `use_execroot_entry_point` doc for more info. | `False` | | use_default_shell_env | If set, passed to the underlying run_binary.

May introduce non-determinism when True; use with care! See e.g. https://github.com/bazelbuild/bazel/issues/4912

Requires a minimum of aspect_bazel_lib v1.40.3 or v2.4.2.

Refer to https://bazel.build/rules/lib/builtins/actions#run for more details. | `None` | | kwargs | Additional arguments | none | diff --git a/js/private/js_binary.bzl b/js/private/js_binary.bzl index a3469bd8fe..25c131290d 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, @@ -391,6 +401,8 @@ def _bash_launcher(ctx, nodeinfo, entry_point_path, log_prefix_rule_set, log_pre if ctx.attr.patch_node_fs: # Set patch node fs API env if not already set to allow js_run_binary to override envs.append(_ENV_SET_IFF_NOT_SET.format(var = "JS_BINARY__PATCH_NODE_FS", value = "1")) + if ctx.attr.patch_node_esm_loader: + envs.append(_ENV_SET_IFF_NOT_SET.format(var = "JS_BINARY__PATCH_NODE_ESM_LOADER", value = "1")) if ctx.attr.expected_exit_code: envs.append(_ENV_SET.format( diff --git a/js/private/js_run_binary.bzl b/js/private/js_run_binary.bzl index dc98bd864c..37c7cfcb79 100644 --- a/js/private/js_run_binary.bzl +++ b/js/private/js_run_binary.bzl @@ -43,6 +43,7 @@ def js_run_binary( execution_requirements = None, stamp = 0, patch_node_fs = True, + patch_node_esm_loader = False, allow_execroot_entry_point_with_no_copy_data_to_bin = False, use_default_shell_env = None, **kwargs): @@ -224,6 +225,8 @@ def js_run_binary( When disabled, node programs can leave the execroot, runfiles and sandbox by following symlinks which can lead to non-hermetic behavior. + patch_node_esm_loader: additionally patch the Node.js ESM loader + allow_execroot_entry_point_with_no_copy_data_to_bin: Turn off validation that the `js_binary` tool has `copy_data_to_bin` set to True when `use_execroot_entry_point` is set to True. @@ -337,6 +340,11 @@ WARNING: js_library 'include_declarations' is deprecated. Use 'include_types' in # Disable node patches if requested if patch_node_fs: fixed_env["JS_BINARY__PATCH_NODE_FS"] = "1" + + if patch_node_esm_loader: + fixed_env["JS_BINARY__PATCH_NODE_ESM_LOADER"] = "1" + else: + fixed_env["JS_BINARY__PATCH_NODE_ESM_LOADER"] = "0" else: # Set explicitly to "0" so disable overrides any enable in the js_binary fixed_env["JS_BINARY__PATCH_NODE_FS"] = "0" diff --git a/js/private/node-patches/BUILD.bazel b/js/private/node-patches/BUILD.bazel index d6af777ee7..ae23a883f5 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 b0dc0d30d0..018a573d68 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"); @@ -63,9 +64,21 @@ const PATCHED_FS_METHODS = [ ]; /** * Function that patches the `fs` module to not escape the given roots. + * + * NOTE: internally Node may call back to `fs.*` methods for example + * realpath: https://github.com/nodejs/node/blob/v24.12.0/lib/fs.js#L2927 + * https://github.com/nodejs/node/blob/v24.12.0/lib/fs.js#L2951-L2957 + * + * writeFile: https://github.com/nodejs/node/blob/v24.12.0/lib/fs.js#L2372 + * + * ... many other places. + * + * However in other scenarios such as ESM module resolution it uses internal invocations + * that can not be patched via `fs.*` methods. + * * @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,6 +113,15 @@ 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) { @@ -757,6 +779,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 0000000000..b482d3ccd0 --- /dev/null +++ b/js/private/node-patches/fs_stat.cjs @@ -0,0 +1,146 @@ +"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; + // Patch the internalFs.lstat call + // from realpathSync: https://github.com/nodejs/node/blob/v25.2.1/lib/fs.js#L2752 + // invoked from finalizeResolution: https://github.com/nodejs/node/blob/v25.2.1/lib/internal/modules/esm/resolve.js#L279 + // while avoiding recursive calls. + const needsGuarding = currentStack && + currentStack.includes('finalizeResolution (node:internal/modules/esm/resolve') && + !currentStack.includes('eeguardStats'); + if (!needsGuarding) { + return internalFsLStat.apply(internalFs, arguments); + } + if (reqCallback === internalFs.kUsePromises) { + return internalFsLStat + .call(internalFs, path, bigint, reqCallback) + .then((stats) => { + return new Promise((resolve, reject) => { + statPatcher.eeguardStats((0, fs_cjs_1.resolvePathLike)(path), bigint, stats, !!throwIfNoEntry, (err, guardedStats) => { + err + ? reject(err) + : resolve(guardedStats); + }); + }); + }); + } + else if (reqCallback === undefined) { + const stats = internalFsLStat.apply(internalFs, arguments); + if (!stats) { + return stats; + } + return statPatcher.eeguardStatsSync((0, fs_cjs_1.resolvePathLike)(path), bigint, !!throwIfNoEntry, stats); + } + else { + // Just re-use the promise path from above. + internalFs + .lstat(path, bigint, internalFs.kUsePromises) + .then((stats) => reqCallback.oncomplete(null, stats)) + .catch((err) => reqCallback.oncomplete(err)); + return undefined; + } + }; + } + 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 // TODO: why type mismatch here? + ); + }); + }); + } + 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 + // TODO: typing + 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 85d67ddf90..5fd10591bb 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 5ed1769c52..ea19c014f2 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 f4c9a5ec53..d5030bc1bf 100644 --- a/js/private/node-patches/src/fs.cts +++ b/js/private/node-patches/src/fs.cts @@ -53,9 +53,24 @@ const PATCHED_FS_METHODS: ReadonlyArray = [ /** * Function that patches the `fs` module to not escape the given roots. + * + * NOTE: internally Node may call back to `fs.*` methods for example + * realpath: https://github.com/nodejs/node/blob/v24.12.0/lib/fs.js#L2927 + * https://github.com/nodejs/node/blob/v24.12.0/lib/fs.js#L2951-L2957 + * + * writeFile: https://github.com/nodejs/node/blob/v24.12.0/lib/fs.js#L2372 + * + * ... many other places. + * + * However in other scenarios such as ESM module resolution it uses internal invocations + * that can not be patched via `fs.*` methods. + * * @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.') } @@ -106,6 +121,25 @@ export function patcher(roots: string[]): () => void { const { canEscape, isEscape } = escapeFunction(roots) + // ========================================================================= + // 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 + ) + + lstatEsmPatcher.patch() + + unpatchEsm = lstatEsmPatcher.revert.bind(lstatEsmPatcher) + } + // ========================================================================= // fs.lstat // ========================================================================= @@ -919,6 +953,10 @@ export function patcher(roots: string[]): () => void { unpatchPromises() } + if (unpatchEsm) { + unpatchEsm() + } + // Re-sync the esm modules to revert to the unpatched module. esmModule.syncBuiltinESMExports() } @@ -943,7 +981,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 0000000000..7cfea5978f --- /dev/null +++ b/js/private/node-patches/src/fs_stat.cts @@ -0,0 +1,205 @@ +// 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 readonly escapeFns: ReturnType, + private readonly guardedReadLink: ( + start: string, + cb: (str: string) => void + ) => void, + private readonly guardedReadLinkSync: (start: string) => string, + private readonly unguardedRealPath: ( + start: string, + cb: (err: Error | null, str?: string) => void + ) => void, + private readonly unguardedRealPathSync: (start: string) => string + ) {} + + revert() { + internalFs.lstat = internalFsLStat + } + + patch() { + const statPatcher = this + + internalFs.lstat = function ( + path, + bigint, + reqCallback, + throwIfNoEntry?: boolean + ) { + const currentStack = new Error().stack + // Patch the internalFs.lstat call + // from realpathSync: https://github.com/nodejs/node/blob/v25.2.1/lib/fs.js#L2752 + // invoked from finalizeResolution: https://github.com/nodejs/node/blob/v25.2.1/lib/internal/modules/esm/resolve.js#L279 + // while avoiding recursive calls. + const needsGuarding = + currentStack && + currentStack.includes( + 'finalizeResolution (node:internal/modules/esm/resolve' + ) && + !currentStack.includes('eeguardStats') + + if (!needsGuarding) { + return internalFsLStat.apply(internalFs, arguments as any) + } + + if (reqCallback === internalFs.kUsePromises) { + return internalFsLStat + .call(internalFs, path, bigint, reqCallback) + .then((stats) => { + return new Promise((resolve, reject) => { + statPatcher.eeguardStats( + resolvePathLike(path), + bigint, + stats, + !!throwIfNoEntry, + (err, guardedStats) => { + err + ? reject(err) + : resolve(guardedStats as any) + } + ) + }) + }) + } else if (reqCallback === undefined) { + const stats = internalFsLStat.apply( + internalFs, + arguments as any + ) as any as FsInternalModule.InternalStats + if (!stats) { + return stats + } + return statPatcher.eeguardStatsSync( + resolvePathLike(path), + bigint, + !!throwIfNoEntry, + stats + ) + } else { + // Just re-use the promise path from above. + internalFs + .lstat(path, bigint, internalFs.kUsePromises) + .then((stats) => reqCallback.oncomplete(null, stats)) + .catch((err) => reqCallback.oncomplete(err)) + return undefined as any + } + } + } + + 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 as any // TODO: why type mismatch here? + ) + }) + }) + } + + 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 + // TODO: typing + return (internalFsLStat as any).call( + internalFs, + path, + bigint, + undefined, + throwIfNoEntry + ) + } 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 0000000000..0b5191696d --- /dev/null +++ b/js/private/node-patches/src/fs_stat_types.d.cts @@ -0,0 +1,32 @@ +// 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 { + type StringOrBuffer = string | Buffer; + + // 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: StringOrBuffer, useBigint: boolean, req: FSReqCallback): void; + function lstat(path: StringOrBuffer, useBigint: boolean, req: undefined, throwIfNoEntry: boolean): InternalStats; + function lstat(path: StringOrBuffer, useBigint: boolean, req: typeof kUsePromises): Promise; + } + + 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 fae2c2a56f..064e40b650 100755 --- a/js/private/node_wrapper.sh +++ b/js/private/node_wrapper.sh @@ -2,4 +2,8 @@ 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 + 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/asserts.bzl b/js/private/test/image/asserts.bzl index d3845526da..0949d787ab 100644 --- a/js/private/test/image/asserts.bzl +++ b/js/private/test/image/asserts.bzl @@ -6,7 +6,7 @@ load("//js:defs.bzl", "js_image_layer") # buildifier: disable=function-docstring def assert_tar_listing(name, actual, expected): # Either of these two file sizes may be observed on a file like /js/private/test/image/bin - sanitize_cmd = "sed -E 's/239[0-9]{2}/xxxxx/g'" + sanitize_cmd = "sed -E 's/ 2[3-4][0-9]{3} / xxxxx /g'" actual_listing = "_{}_listing".format(name) native.genrule( name = actual_listing, diff --git a/js/private/test/image/checksum_test.expected b/js/private/test/image/checksum_test.expected index f48c0fd829..bb04567d5a 100644 --- a/js/private/test/image/checksum_test.expected +++ b/js/private/test/image/checksum_test.expected @@ -1,4 +1,4 @@ -b1b723afb1aac593e55cc5050909103650a13306d1f0366c5405dcc6cf73461f js/private/test/image/cksum_node.tar +55bb484fcd403d4d06c1e229ee9f7c7d08fbc045d6245afb077908da39760864 js/private/test/image/cksum_node.tar 052600f3a82ab6a4cc12cab7384971c960f9c589fdbfcf21bca563c36ff7d16e js/private/test/image/cksum_package_store_3p.tar 971f291232f3ab63aff37fb66c96fbf0eddc05ea9564b9673d0d2c9bfe958994 js/private/test/image/cksum_package_store_1p.tar febf95a6d554c9bda3f0515bfd5ef273ac67d31c231d8162beaef8c4b7bc72f3 js/private/test/image/cksum_node_modules.tar 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 9f2f3ac7fc..94f124a600 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 34317 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 35525 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs +-r-xr-xr-x 0 0 0 6606 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 2b3bd16c30..9f13c4af74 100644 --- a/js/private/test/image/custom_owner_test_app.listing +++ b/js/private/test/image/custom_owner_test_app.listing @@ -18,5 +18,5 @@ 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/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 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 303 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 6008ba8111..004c0a9606 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 34317 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 35525 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs +-r-xr-xr-x 0 100 0 6606 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 675f8cb5f3..b762099998 100644 --- a/js/private/test/image/default_test_app.listing +++ b/js/private/test/image/default_test_app.listing @@ -18,5 +18,5 @@ 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/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 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 303 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 60ecaf4bb6..9cb2f587f0 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 34317 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 35525 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs +-r-xr-xr-x 0 0 0 6606 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 46846288d6..68e43a5c22 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 xxxxx 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 xxxxx 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 303 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 f0a770d1a2..fb36f25f0f 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 34317 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 35525 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 480acc973e..ecbe702046 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 6606 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 42442059b2..78d5153933 100644 --- a/js/private/test/image/regex_edge_cases_test_app.listing +++ b/js/private/test/image/regex_edge_cases_test_app.listing @@ -19,5 +19,5 @@ 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/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 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 303 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 9f2f3ac7fc..94f124a600 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 34317 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 35525 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs +-r-xr-xr-x 0 0 0 6606 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 646526b963..4241b272be 100644 --- a/js/private/test/node-patches/BUILD.bazel +++ b/js/private/test/node-patches/BUILD.bazel @@ -77,52 +77,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, + "_esmloader" if useEsmLoader 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 useEsmLoader else "", + }, + node_options = ["--expose-internals"] if useEsmLoader 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, + "_esmloader" if useEsmLoader 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 useEsmLoader else "", + }, + node_options = ["--expose-internals"] if useEsmLoader 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 useEsmLoader in [ + False, + True, ] - for toolchain_name, toolchain in zip( - TOOLCHAINS_NAMES, - TOOLCHAINS_VERSIONS, - ) ] # Node process spawning tests @@ -150,3 +168,30 @@ babel_bin.babel( TOOLCHAINS_VERSIONS, ) ] + +# TODO: this doesn't fail with patch_node_esm_loader=False, need failing test!! +[ + js_test( + name = "esm_test_%s" % toolchain_name, + data = [":lib"], + entry_point = "esm.mjs", + node_toolchain = toolchain, + patch_node_esm_loader = True, + patch_node_fs = True, + preserve_symlinks_main = False, # TODO: test both? + ) + for toolchain_name, toolchain in zip( + TOOLCHAINS_NAMES, + TOOLCHAINS_VERSIONS, + ) + if toolchain_name != "node16" # ESM json loading not supported on Node 16 +] + +# A basic library with esm imports, esm json data etc +js_library( + name = "lib", + srcs = [ + "data.json", + "lib.mjs", + ], +) diff --git a/js/private/test/node-patches/data.json b/js/private/test/node-patches/data.json new file mode 100644 index 0000000000..aeb9c294a9 --- /dev/null +++ b/js/private/test/node-patches/data.json @@ -0,0 +1,4 @@ +{ + "name": "json-data", + "answer": 42 +} diff --git a/js/private/test/node-patches/esm.mjs b/js/private/test/node-patches/esm.mjs new file mode 100644 index 0000000000..8ef1740e16 --- /dev/null +++ b/js/private/test/node-patches/esm.mjs @@ -0,0 +1,29 @@ +import { fileURLToPath } from 'url' +import { dirname } from 'node:path' +import { data2, data3, my__dirname, my__filename, my__pwd } from './lib.mjs' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +console.log('process.cwd():', my__pwd) +console.log('data2:', data2) +console.log('data3:', data3) + +if (my__dirname !== __dirname) { + throw new Error('__dirname does not match expected value') +} + +if (my__pwd !== process.cwd()) { + throw new Error('process.cwd() does not match expected value') +} + +if (dirname(my__filename) !== dirname(__filename)) { + throw new Error('__filename does not match expected value') +} + +if ( + dirname(__filename) != + dirname(fileURLToPath(import.meta.resolve('./lib2.mjs'))) +) { + throw new Error('import.meta.resolve does not match expected value') +} diff --git a/js/private/test/node-patches/lib.mjs b/js/private/test/node-patches/lib.mjs new file mode 100644 index 0000000000..4ec96c7ea0 --- /dev/null +++ b/js/private/test/node-patches/lib.mjs @@ -0,0 +1,13 @@ +import { fileURLToPath } from 'url' +import { dirname } from 'path' +import data from './data.json' with { type: 'json' } + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +export const my__dirname = __dirname +export const my__filename = __filename +export const my__pwd = process.cwd() + +export const data2 = data +export const data3 = { ...data, addedKey: 'addedValue' } diff --git a/js/private/test/node-patches/lstat.mjs b/js/private/test/node-patches/lstat.mjs index 41d583a0ce..e322c385a9 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 6c9b63ad9c..d958099cf6 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 835afa2ac7..c62efb1ad3 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 cfa90f167d..5ca75cef1e 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 de37b435b3..8ddb0b5f45 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 24a54d519c..e343b94799 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/tools/update-snapshots.sh b/tools/update-snapshots.sh index 8e6f71f61d..d711aba2cd 100755 --- a/tools/update-snapshots.sh +++ b/tools/update-snapshots.sh @@ -107,6 +107,7 @@ run_target "$REPO_ROOT" "//js/private/test/image:default_test_update_all" "js/pr run_target "$REPO_ROOT" "//js/private/test/image:custom_owner_test_update_all" "js/private/test/image custom_owner" "--enable_bzlmod=true" run_target "$REPO_ROOT" "//js/private/test/image:regex_edge_cases_test_update_all" "js/private/test/image regex_edge_cases" "--enable_bzlmod=true" run_target "$REPO_ROOT" "//js/private/test/image:custom_layers_nomatch_test_update_all" "js/private/test/image custom_layers_nomatch" "--enable_bzlmod=true" +run_target "$REPO_ROOT" "//js/private/test/image/non_ascii:assert_custom_layer_groups_test_app" "js/private/test/image default" "--enable_bzlmod=true" ############################################################################## # GENERATED SOURCES