fix(js_binary): drop unused mandatory shell toolchain (#2926)

Fix #2347 

### Changes are visible to end-users: no

### Test plan

- Covered by existing test cases
- New test cases added
diff --git a/.github/workflows/ci-workflows.yaml b/.github/workflows/ci-workflows.yaml
index 4a84f47..9833e59 100644
--- a/.github/workflows/ci-workflows.yaml
+++ b/.github/workflows/ci-workflows.yaml
@@ -67,6 +67,7 @@
                     e2e/js_image_oci
                     e2e/js_run_devserver
                     e2e/nextjs
+                    e2e/no_shell_toolchain
                     e2e/npm_link_package
                     e2e/npm_link_package-rerooted
                     e2e/npm_translate_lock
diff --git a/e2e/no_shell_toolchain/.bazelrc b/e2e/no_shell_toolchain/.bazelrc
new file mode 100644
index 0000000..c7a0d3a
--- /dev/null
+++ b/e2e/no_shell_toolchain/.bazelrc
@@ -0,0 +1,8 @@
+import %workspace%/../../tools/preset.bazelrc
+import %workspace%/../e2e.bazelrc
+
+# Regression test for https://github.com/aspect-build/rules_js/issues/2347:
+# build every target for an iOS-like platform that has no shell toolchain (in
+# Bazel 8+ the built-in autodetecting sh toolchain was removed). js_binary must
+# not fail toolchain resolution on the shell toolchain it never uses.
+build --platforms=//:ios_like
diff --git a/e2e/no_shell_toolchain/BUILD.bazel b/e2e/no_shell_toolchain/BUILD.bazel
new file mode 100644
index 0000000..370f034
--- /dev/null
+++ b/e2e/no_shell_toolchain/BUILD.bazel
@@ -0,0 +1,48 @@
+load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_run_binary")
+load("@bazel_skylib//rules:build_test.bzl", "build_test")
+
+# js_binary declared @bazel_tools//tools/sh:toolchain_type as a mandatory
+# toolchain even though the rule never consumes it. Building for a platform with
+# no shell toolchain (e.g. Apple/iOS under Bazel 8+) therefore failed toolchain
+# resolution. The .bazelrc forces every target onto //:ios_like; this must build.
+js_binary(
+    name = "bin",
+    entry_point = "index.js",
+)
+
+# The #2347 scenario: js_binary used as a codegen tool. Building this runs the
+# tool, exercising the target-configured js_binary that failed to resolve the
+# shell toolchain.
+js_run_binary(
+    name = "run_bin",
+    stdout = "out.txt",
+    tool = ":bin",
+)
+
+build_test(
+    name = "build_test",
+    targets = [
+        ":bin",
+        ":run_bin",
+    ],
+)
+
+platform(
+    name = "ios_like",
+    constraint_values = [
+        "@platforms//os:ios",
+        "@platforms//cpu:arm64",
+    ],
+)
+
+# A Node.js runtime toolchain for the iOS platform, so the only toolchain missing
+# on //:ios_like is the shell toolchain -- isolating the regression.
+toolchain(
+    name = "nodejs_ios_arm64_toolchain",
+    target_compatible_with = [
+        "@platforms//os:ios",
+        "@platforms//cpu:arm64",
+    ],
+    toolchain = "@nodejs_linux_amd64//:toolchain",
+    toolchain_type = "@rules_nodejs//nodejs:runtime_toolchain_type",
+)
diff --git a/e2e/no_shell_toolchain/MODULE.bazel b/e2e/no_shell_toolchain/MODULE.bazel
new file mode 100644
index 0000000..b3e7bf8
--- /dev/null
+++ b/e2e/no_shell_toolchain/MODULE.bazel
@@ -0,0 +1,29 @@
+module(name = "no_shell_toolchain")
+
+bazel_dep(name = "aspect_rules_js", version = "0.0.0", dev_dependency = True)
+bazel_dep(name = "bazel_skylib", version = "1.8.1", dev_dependency = True)
+bazel_dep(name = "platforms", version = "0.0.11", dev_dependency = True)
+bazel_dep(name = "rules_nodejs", version = "6.6.0", dev_dependency = True)
+
+local_path_override(
+    module_name = "aspect_rules_js",
+    path = "../..",
+)
+
+# Bring in a Node.js repo so //:nodejs_ios_arm64_toolchain can alias its runtime
+# for the iOS platform. Any platform's Node.js works here: the js_binary is only
+# built (not run), so the runtime is never executed.
+node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node", dev_dependency = True)
+use_repo(node, "nodejs_linux_amd64")
+
+register_toolchains(
+    "//:nodejs_ios_arm64_toolchain",
+    dev_dependency = True,
+)
+
+# Registered so tests resolve @bazel_tools//tools/test:default_test_toolchain_type
+# for the iOS target platform under Bazel 9.
+register_execution_platforms(
+    "//:ios_like",
+    dev_dependency = True,
+)
diff --git a/e2e/no_shell_toolchain/index.js b/e2e/no_shell_toolchain/index.js
new file mode 100644
index 0000000..14c734e
--- /dev/null
+++ b/e2e/no_shell_toolchain/index.js
@@ -0,0 +1 @@
+console.log('hello from js_binary')
diff --git a/js/private/js_binary.bzl b/js/private/js_binary.bzl
index 3e864f8..2e42f49 100644
--- a/js/private/js_binary.bzl
+++ b/js/private/js_binary.bzl
@@ -578,8 +578,6 @@
     create_launcher = _create_launcher,
     implementation = _js_binary_impl,
     toolchains = [
-        # TODO: on Windows this toolchain is never referenced
-        "@bazel_tools//tools/sh:toolchain_type",
         "@rules_nodejs//nodejs:runtime_toolchain_type",
     ] + COPY_FILE_TO_BIN_TOOLCHAINS,
 )