| """ | |
| js_binary examples | |
| Simple examples of running JS programs in node with js_binary, js_run_binary, and js_test. | |
| """ | |
| load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_run_binary", "js_test") | |
| load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") | |
| load("@bazel_lib//lib:diff_test.bzl", "diff_test") | |
| load("@bazel_lib//lib:directory_path.bzl", "directory_path") | |
| load("@bazel_lib//lib:expand_template.bzl", "expand_template") | |
| load("@bazel_lib//lib:paths.bzl", "BASH_RLOCATION_FUNCTION") | |
| load("@bazel_skylib//rules:write_file.bzl", "write_file") | |
| load("@npm//:defs.bzl", "npm_link_all_packages") | |
| load("@npm//npm_deps:@aspect-test/a/package_json.bzl", aspect_test_a_bin = "bin") | |
| load("@rules_shell//shell:sh_binary.bzl", "sh_binary") | |
| load(":custom_rule.bzl", "custom_rule") | |
| # Link all direct dependencies in /npm_deps/package.json to | |
| # bazel-bin/npm_deps/node_modules | |
| npm_link_all_packages() | |
| # A simple program that runs the Acorn JS parser to produce an AST | |
| js_binary( | |
| name = "bin_mjs", | |
| # Reference the location where the acorn npm module was linked in the root Bazel package | |
| data = ["//:node_modules/acorn"], | |
| 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", | |
| ) | |
| #################################################### | |
| # Use case 1 | |
| # js_binary can be used with genrule | |
| # because everything it needs to run is in the runfiles | |
| [ | |
| [ | |
| genrule( | |
| name = "run1_%s" % t, | |
| srcs = [], | |
| outs = ["out1-%s" % t], | |
| # 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_%s) {}/out1-%s".format(package_name()) % (t, t), | |
| tools = [":bin_%s" % t], | |
| ), | |
| diff_test( | |
| name = "test_js_binary_under_genrule_%s" % t, | |
| file1 = "//:expected_one_ast.json", | |
| file2 = "out1-%s" % t, | |
| ), | |
| ] | |
| for t in [ | |
| "mjs", | |
| "cjs", | |
| ] | |
| ] | |
| #################################################### | |
| # Use case 2 | |
| # Using js_run_binary has some nice syntax sugar vs. | |
| # a genrule() or the run_binary rule from bazel-skylib. | |
| [ | |
| [ | |
| js_run_binary( | |
| name = "run2_%s" % t, | |
| srcs = [], | |
| outs = ["out2_%s" % t], | |
| args = ["out2_%s" % t], | |
| chdir = package_name(), | |
| # Request that the rules_js launcher prints extra information | |
| log_level = "debug", | |
| silent_on_success = False, | |
| tool = ":bin_%s" % t, | |
| ), | |
| diff_test( | |
| name = "test_js_binary_under_js_run_binary_%s" % t, | |
| file1 = "//:expected_one_ast.json", | |
| file2 = "out2_%s" % t, | |
| ), | |
| ] | |
| for t in [ | |
| "mjs", | |
| "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. | |
| [ | |
| [ | |
| js_run_binary( | |
| name = "run2_local_%s" % t, | |
| srcs = [], | |
| outs = ["out2_local_%s" % t], | |
| args = ["out2_local_%s" % t], | |
| chdir = package_name(), | |
| execution_requirements = { | |
| "local": "1", | |
| }, | |
| # Request that the rules_js launcher prints extra information | |
| log_level = "debug", | |
| silent_on_success = False, | |
| tool = ":bin_%s" % t, | |
| ), | |
| diff_test( | |
| name = "test_js_binary_under_js_run_binary_local_%s" % t, | |
| file1 = "//:expected_one_ast.json", | |
| file2 = "out2_local_%s" % t, | |
| ), | |
| ] | |
| for t in [ | |
| "mjs", | |
| "cjs", | |
| ] | |
| ] | |
| # Also test with copy_data_to_bin disabled | |
| copy_to_bin( | |
| name = "require_acorn_mjs", | |
| srcs = ["require_acorn.mjs"], | |
| ) | |
| js_binary( | |
| name = "bin_no_copy_data_to_bin", | |
| 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_mjs", | |
| ) | |
| js_run_binary( | |
| name = "run2_no_copy_data_to_bin", | |
| srcs = [], | |
| outs = ["out2_no_copy_data_to_bin"], | |
| allow_execroot_entry_point_with_no_copy_data_to_bin = True, | |
| args = ["out2_no_copy_data_to_bin"], | |
| chdir = package_name(), | |
| # Request that the rules_js launcher prints extra information | |
| log_level = "debug", | |
| silent_on_success = False, | |
| tool = ":bin_no_copy_data_to_bin", | |
| ) | |
| diff_test( | |
| name = "test_js_binary_under_js_run_binary_no_copy_data_to_bin", | |
| file1 = "//:expected_one_ast.json", | |
| file2 = "out2_no_copy_data_to_bin", | |
| ) | |
| ################################ | |
| # Use case 3 | |
| # js_test is just a js_binary | |
| # Bazel will check the exit code: a zero means the test passes, anything else means it fails. | |
| js_test( | |
| name = "test_mjs", | |
| data = ["//:node_modules/@types/node"], | |
| entry_point = "test.mjs", | |
| ) | |
| js_test( | |
| name = "test_cjs", | |
| data = ["//:node_modules/@types/node"], | |
| entry_point = "test.js", | |
| ) | |
| ############################### | |
| # Use case 4 | |
| # A first-party library which we want to run as a program. | |
| # This relies on @mycorp/pkg-a and @mycorp/pkg-b which are packages within this monorepo. | |
| write_file( | |
| name = "write4_cjs", | |
| out = "case4.cjs", | |
| content = [ | |
| """require('fs').writeFileSync( | |
| process.argv[2], | |
| require(process.argv[3]).toAst("1") | |
| )""", | |
| ], | |
| ) | |
| write_file( | |
| name = "write4_mjs", | |
| out = "case4.mjs", | |
| content = [ | |
| "import { writeFileSync } from 'fs'", | |
| """writeFileSync( | |
| process.argv[2], | |
| (await import(process.argv[3])).toAst("1") | |
| )""", | |
| ], | |
| ) | |
| [ | |
| [ | |
| js_binary( | |
| name = "bin4_%s" % t, | |
| data = [ | |
| ":node_modules/@mycorp/pkg-a", | |
| "//:node_modules/@mycorp/pkg-b", | |
| ], | |
| entry_point = "case4.%s" % t, | |
| ), | |
| js_run_binary( | |
| name = "run4-a_%s" % t, | |
| args = [ | |
| "out4-%s-dist/out4-a" % t, | |
| "@mycorp/pkg-a", | |
| ], | |
| chdir = package_name(), | |
| # This specifically tests that a `select` can be used when setting `env`. | |
| env = select({ | |
| "//conditions:default": { | |
| "NODE_ENV": "production", | |
| }, | |
| }), | |
| out_dirs = ["out4-%s-dist" % t], | |
| silent_on_success = False, | |
| tool = ":bin4_%s" % t, | |
| ), | |
| directory_path( | |
| name = "out4-a-%s" % t, | |
| directory = ":run4-a_%s" % t, | |
| path = "out4-a", | |
| ), | |
| diff_test( | |
| name = "test4-%s-a" % t, | |
| file1 = "//:expected_one_ast.json", | |
| file2 = ":out4-a-%s" % t, | |
| ), | |
| js_run_binary( | |
| name = "run4-b_%s" % t, | |
| outs = ["out4-b_%s" % t], | |
| args = [ | |
| "out4-b_%s" % t, | |
| "@mycorp/pkg-b", | |
| ], | |
| chdir = package_name(), | |
| # This specifically tests that a `select` and `|` operator can be used when setting `env`. | |
| env = {} | select({ | |
| "//conditions:default": { | |
| "NODE_ENV": "production", | |
| }, | |
| }), | |
| silent_on_success = False, | |
| tool = ":bin4_%s" % t, | |
| ), | |
| diff_test( | |
| name = "test4-b_%s" % t, | |
| file1 = "//:expected_one_ast.json", | |
| file2 = ":out4-b_%s" % t, | |
| ), | |
| ] | |
| for t in [ | |
| "mjs", | |
| "cjs", | |
| ] | |
| ] | |
| ####################################### | |
| # Use case 5 | |
| # js_run_binary that reads a data file at runtime. | |
| write_file( | |
| name = "write5_mjs", | |
| out = "case5.mjs", | |
| content = [ | |
| "import { writeFileSync } from 'node:fs';", | |
| "import { join } from 'node:path';", | |
| "import { pathToFileURL } from 'url';", | |
| "// We need to use pathToFileURL so that on Windows the path begins with \"file://\".", | |
| "const jsonData = await import(pathToFileURL(join(process.cwd(), 'data.json')).href, { with: { type: 'json' } });", | |
| "writeFileSync(process.argv[2], JSON.stringify(jsonData.default))", | |
| ], | |
| ) | |
| write_file( | |
| name = "write5_cjs", | |
| out = "case5.cjs", | |
| content = ["""\ | |
| require('fs').writeFileSync( | |
| process.argv[2], | |
| JSON.stringify(require(require('path').join(process.cwd(), "data.json"))) | |
| )"""], | |
| ) | |
| write_file( | |
| name = "expected5", | |
| out = "expected5.txt", | |
| content = ["{\"answer\":42}"], | |
| ) | |
| [ | |
| [ | |
| js_binary( | |
| name = "bin5_%s" % t, | |
| entry_point = "case5.%s" % t, | |
| ), | |
| js_run_binary( | |
| name = "run5_%s" % t, | |
| srcs = ["data.json"], | |
| outs = ["out5_%s" % t], | |
| args = ["out5_%s" % t], | |
| chdir = package_name(), | |
| silent_on_success = False, | |
| tool = ":bin5_%s" % t, | |
| ), | |
| diff_test( | |
| name = "test5_%s" % t, | |
| file1 = "expected5", | |
| file2 = "out5_%s" % t, | |
| ), | |
| ] | |
| for t in [ | |
| "mjs", | |
| "cjs", | |
| ] | |
| ] | |
| ####################################### | |
| # Use case 6 | |
| # A program that relies on environment variables and node_options | |
| write_file( | |
| name = "write6", | |
| out = "case6.js", | |
| content = ["require('fs').writeFileSync(process.argv[2], process.env.NODE_ENV + ' ' + process.title)"], | |
| ) | |
| write_file( | |
| name = "expected6", | |
| out = "expected6.txt", | |
| content = ["production myapp"], | |
| ) | |
| js_binary( | |
| name = "bin6", | |
| entry_point = "case6.js", | |
| env = { | |
| "NODE_ENV": "production", | |
| }, | |
| node_options = [ | |
| "--title=myapp", | |
| "--throw-deprecation", | |
| ], | |
| ) | |
| js_run_binary( | |
| name = "run6", | |
| outs = ["out6"], | |
| args = ["../../../$@"], | |
| silent_on_success = False, | |
| tool = ":bin6", | |
| ) | |
| diff_test( | |
| name = "test6", | |
| file1 = "expected6", | |
| file2 = "out6", | |
| ) | |
| js_binary( | |
| name = "bin6_alt", | |
| entry_point = "case6.js", | |
| env = { | |
| "NODE_ENV": "production", | |
| "NODE_OPTIONS": "--title=myapp --throw-deprecation", | |
| }, | |
| ) | |
| js_run_binary( | |
| name = "run6_alt", | |
| outs = ["out6_alt"], | |
| args = ["../../../$@"], | |
| silent_on_success = False, | |
| tool = ":bin6_alt", | |
| ) | |
| diff_test( | |
| name = "test6_alt", | |
| file1 = "expected6", | |
| file2 = "out6_alt", | |
| ) | |
| ####################################### | |
| # Use case 7 | |
| # capture stdout, stderr & exit code | |
| write_file( | |
| name = "write7", | |
| out = "case7.js", | |
| content = ["""\ | |
| process.stdout.write("to stdout\\n") | |
| process.stderr.write("to stderr\\n") | |
| process.exit(42) | |
| """], | |
| ) | |
| js_binary( | |
| name = "bin7", | |
| entry_point = "case7.js", | |
| ) | |
| js_run_binary( | |
| name = "run7", | |
| outs = [], | |
| chdir = package_name(), | |
| exit_code_out = "actual_exitcode", | |
| silent_on_success = False, | |
| stderr = "actual_stderr", | |
| stdout = "actual_stdout", | |
| tool = ":bin7", | |
| ) | |
| write_file( | |
| name = "expected_stdout", | |
| out = "expected_stdout.txt", | |
| content = ["to stdout\n"], | |
| ) | |
| write_file( | |
| name = "expected_stderr", | |
| out = "expected_stderr.txt", | |
| content = ["to stderr\n"], | |
| ) | |
| write_file( | |
| name = "expected_exitcode", | |
| out = "expected_exitcode.txt", | |
| content = ["42"], | |
| ) | |
| diff_test( | |
| name = "test_stdout", | |
| file1 = "expected_stdout", | |
| file2 = "actual_stdout", | |
| ) | |
| diff_test( | |
| name = "test_stderr", | |
| file1 = "expected_stderr", | |
| file2 = "actual_stderr", | |
| ) | |
| diff_test( | |
| name = "test_exitcode", | |
| file1 = "expected_exitcode", | |
| file2 = "actual_exitcode", | |
| ) | |
| js_test( | |
| name = "case7_test", | |
| args = ["dummy"], | |
| entry_point = "case7.js", | |
| expected_exit_code = 42, | |
| log_level = "debug", | |
| ) | |
| # bazel run //:case7_binary | |
| js_binary( | |
| name = "case7_binary", | |
| args = ["dummy"], | |
| entry_point = "case7.js", | |
| # Prevent bazel failing when a build step exits non-zero | |
| expected_exit_code = 42, | |
| log_level = "debug", | |
| ) | |
| #################################################### | |
| # Use case 8 | |
| # Show that a js_binary can use a DirectoryPathInfo entry point | |
| directory_path( | |
| name = "acorn_entry_point", | |
| directory = "//:node_modules/acorn/dir", | |
| path = "bin/acorn", | |
| ) | |
| js_binary( | |
| name = "acorn_bin", | |
| args = ["--help"], | |
| entry_point = ":acorn_entry_point", | |
| ) | |
| js_run_binary( | |
| name = "run8", | |
| args = ["--help"], | |
| silent_on_success = False, | |
| stdout = "out8", | |
| tool = ":acorn_bin", | |
| ) | |
| write_file( | |
| name = "expected8", | |
| out = "expected8.txt", | |
| content = [ | |
| "usage: acorn [--ecma3|--ecma5|--ecma6|--ecma7|--ecma8|--ecma9|...|--ecma2015|--ecma2016|--ecma2017|--ecma2018|...]", | |
| " [--tokenize] [--locations] [---allow-hash-bang] [--allow-await-outside-function] [--compact] [--silent] [--module] [--help] [--] [infile]", | |
| "", | |
| ], | |
| newline = "unix", | |
| ) | |
| diff_test( | |
| name = "test8", | |
| file1 = ":expected8", | |
| file2 = ":out8", | |
| ) | |
| #################################################### | |
| # Use case 9 | |
| # Show that we can run a generated bin from a package where the npm package | |
| # is not linked. In this case @aspect-test/a is linked to the //npm_deps:__pkg__ | |
| aspect_test_a_bin.bin_a_test( | |
| name = "aspect_bin_a_test", | |
| ) | |
| #################################################### | |
| # Use case 10 | |
| # Show launching js_binary() indirectly from a sh_binary() | |
| write_file( | |
| name = "write10_js", | |
| out = "case10.js", | |
| content = ["require('fs').writeFileSync(process.argv[2], 'case10')"], | |
| ) | |
| js_binary( | |
| name = "bin10-js_binary", | |
| entry_point = "case10.js", | |
| ) | |
| expand_template( | |
| name = "write10_launch_sh", | |
| out = "launch_case10.sh", | |
| data = [":bin10-js_binary"], | |
| is_executable = True, | |
| substitutions = { | |
| "{rlocationpath}": "$(rlocationpath :bin10-js_binary)", | |
| }, | |
| template = [ | |
| BASH_RLOCATION_FUNCTION, | |
| "$(rlocation {rlocationpath}) \"$@\"", | |
| ], | |
| ) | |
| # NB: https://github.com/aspect-build/rules_js/issues/285 is only reproducable | |
| # with .bazel run //js_binary:bin10.. The genrule below that runs | |
| # the same binary does not reproduce as the environment is setup differently. | |
| sh_binary( | |
| name = "bin10", | |
| srcs = [":launch_case10.sh"], | |
| args = ["out10"], | |
| data = [ | |
| ":bin10-js_binary", | |
| "@bazel_tools//tools/bash/runfiles", | |
| ], | |
| ) | |
| genrule( | |
| name = "test10", | |
| outs = ["out10"], | |
| # 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) $(execpath :bin10) {}/out10".format(package_name()), | |
| tools = [":bin10"], | |
| ) | |
| #################################################### | |
| # Use case 11 | |
| # js_binary can be used with a simple custom_role | |
| # because everything it needs to run is in the runfiles | |
| custom_rule( | |
| name = "run11", | |
| tool = ":bin_mjs", | |
| ) | |
| diff_test( | |
| name = "test_js_binary_under_custom_rule", | |
| file1 = "//:expected_one_ast.json", | |
| file2 = ":run11", | |
| ) | |
| # Also test with local (no sandbox) exection 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. | |
| custom_rule( | |
| name = "run11_local", | |
| execution_requirements = { | |
| "local": "1", | |
| }, | |
| tool = ":bin_mjs", | |
| ) | |
| diff_test( | |
| name = "test_js_binary_under_custom_rule_local", | |
| file1 = "//:expected_one_ast.json", | |
| file2 = ":run11_local", | |
| ) | |
| ################################ | |
| # Use case 12 | |
| # js_test that requires npm from the Node.js toolchain available on the PATH | |
| js_test( | |
| name = "npm_version_test", | |
| entry_point = "npm_version_test.js", | |
| include_npm = True, | |
| ) | |
| #################################################### | |
| # Use case 13 | |
| # npm_package() with root_paths[], linked and consumed | |
| # https://github.com/aspect-build/rules_js/issues/471 | |
| write_file( | |
| name = "write13_cjs", | |
| out = "case13.cjs", | |
| content = [ | |
| """require('fs').writeFileSync( | |
| process.argv[2], | |
| JSON.stringify(require(process.argv[3])) | |
| )""", | |
| ], | |
| ) | |
| write_file( | |
| name = "write13_mjs", | |
| out = "case13.mjs", | |
| content = [ | |
| "import { writeFileSync } from 'fs';", | |
| """ | |
| writeFileSync( | |
| process.argv[2], | |
| JSON.stringify((await import(process.argv[3])).default) | |
| )""", | |
| ], | |
| ) | |
| write_file( | |
| name = "expected13-1-out", | |
| out = "expected13-1-out.json", | |
| content = [ | |
| """{"name":"c1"}""", | |
| ], | |
| visibility = ["//visibility:private"], | |
| ) | |
| write_file( | |
| name = "expected13-2-out", | |
| out = "expected13-2-out.json", | |
| content = [ | |
| """{"name":"c2"}""", | |
| ], | |
| visibility = ["//visibility:private"], | |
| ) | |
| [ | |
| [ | |
| js_binary( | |
| name = "bin13_%s" % t, | |
| data = [ | |
| "//:node_modules/@mycorp/pkg-c1", | |
| "//:node_modules/@mycorp/pkg-c2", | |
| ], | |
| entry_point = "case13.%s" % t, | |
| ), | |
| js_run_binary( | |
| name = "run13-1_%s" % t, | |
| args = [ | |
| "out13-dist-1_%s/out13-1" % t, | |
| "@mycorp/pkg-c1", | |
| ], | |
| chdir = package_name(), | |
| out_dirs = ["out13-dist-1_%s" % t], | |
| silent_on_success = False, | |
| tool = ":bin13_%s" % t, | |
| ), | |
| directory_path( | |
| name = "out13-1_%s" % t, | |
| directory = ":run13-1_%s" % t, | |
| path = "out13-1", | |
| ), | |
| diff_test( | |
| name = "test13-1_%s" % t, | |
| file1 = ":expected13-1-out.json", | |
| file2 = ":out13-1_%s" % t, | |
| ), | |
| js_run_binary( | |
| name = "run13-2_%s" % t, | |
| args = [ | |
| "out13-dist-2_%s/out13-2" % t, | |
| "@mycorp/pkg-c2", | |
| ], | |
| chdir = package_name(), | |
| out_dirs = ["out13-dist-2_%s" % t], | |
| silent_on_success = False, | |
| tool = ":bin13_%s" % t, | |
| ), | |
| directory_path( | |
| name = "out13-2_%s" % t, | |
| directory = ":run13-2_%s" % t, | |
| path = "out13-2", | |
| ), | |
| diff_test( | |
| name = "test13-2_%s" % t, | |
| file1 = ":expected13-2-out.json", | |
| file2 = ":out13-2_%s" % t, | |
| ), | |
| ] | |
| for t in [ | |
| "mjs", | |
| "cjs", | |
| ] | |
| ] |