diff --git a/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx b/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx index d72d212ddfb0..eda2684114ae 100755 --- a/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx +++ b/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx @@ -3,11 +3,11 @@ # This file should be checked into version control along with the pnpm-lock.yaml file. .npmrc=-1406867100 modules/testing/builder/package.json=973445093 -package.json=1411918173 +package.json=-462103860 packages/angular/build/package.json=1920607808 packages/angular/cli/package.json=-1917515334 packages/angular/pwa/package.json=1108903917 -packages/angular/ssr/package.json=-2027233365 +packages/angular/ssr/package.json=1556449772 packages/angular_devkit/architect/package.json=-363443363 packages/angular_devkit/architect_cli/package.json=1551210941 packages/angular_devkit/build_angular/package.json=595549079 @@ -17,7 +17,7 @@ packages/angular_devkit/schematics/package.json=-1133510866 packages/angular_devkit/schematics_cli/package.json=-2026655035 packages/ngtools/webpack/package.json=884391309 packages/schematics/angular/package.json=251715148 -pnpm-lock.yaml=2112966384 +pnpm-lock.yaml=-758853739 pnpm-workspace.yaml=-1264044456 tests/package.json=700948366 -yarn.lock=1484073494 +yarn.lock=1188157 diff --git a/.bazelrc b/.bazelrc index 6944b4807a3d..da0450325967 100644 --- a/.bazelrc +++ b/.bazelrc @@ -33,10 +33,6 @@ test:no-sharding --flaky_test_attempts=1 --test_sharding_strategy=disabled # See https://github.com/bazelbuild/bazel/issues/4603 build --symlink_prefix=dist/ -# Disable watchfs as it causes tests to be flaky on Windows -# https://github.com/angular/angular/issues/29541 -build --nowatchfs - # Turn off legacy external runfiles build --nolegacy_external_runfiles @@ -133,9 +129,9 @@ build:remote --jobs=150 # Setup the toolchain and platform for the remote build execution. The platform # is provided by the shared dev-infra package and targets k8 remote containers. -build:remote --extra_execution_platforms=@npm//@angular/build-tooling/bazel/remote-execution:platform_with_network -build:remote --host_platform=@npm//@angular/build-tooling/bazel/remote-execution:platform_with_network -build:remote --platforms=@npm//@angular/build-tooling/bazel/remote-execution:platform_with_network +build:remote --extra_execution_platforms=@devinfra//bazel/remote-execution:platform_with_network +build:remote --host_platform=@devinfra//bazel/remote-execution:platform_with_network +build:remote --platforms=@devinfra//bazel/remote-execution:platform_with_network # Set remote caching settings build:remote --remote_accept_cached=true @@ -162,10 +158,6 @@ build:remote-cache --google_default_credentials # Fixes use of npm paths with spaces such as some within the puppeteer module build --experimental_inprocess_symlink_creation -# Enable runfiles even on Windows. -# Architect resolves output files from data files, and this isn't possible without runfile support. -build --enable_runfiles - #################################################### # rules_js specific flags #################################################### diff --git a/.github/shared-actions/windows-bazel-test/action.yml b/.github/shared-actions/windows-bazel-test/action.yml new file mode 100644 index 000000000000..a0bc927297c4 --- /dev/null +++ b/.github/shared-actions/windows-bazel-test/action.yml @@ -0,0 +1,79 @@ +name: 'Native Windows Bazel e2e test' +description: 'Runs an Angular CLI e2e Bazel test on native Windows (dispatched from inside WSL)' +author: 'Angular' + +inputs: + test_target_name: + description: E2E test target name + required: true + test_args: + description: | + Text representing the command line arguments that + should be passed to the e2e test runner. + required: false + default: '' + +runs: + using: composite + steps: + - name: Setup Bazel RBE + uses: angular/dev-infra/github-actions/bazel/configure-remote@2667d139a421977a40c3ea7ec768609fb19a8b9d + with: + allow_windows_rbe: true + + - name: Initialize WSL + id: init_wsl + uses: angular/dev-infra/github-actions/setup-wsl@9a3e28a515bf51cd2ecfd5f4d5b17613845e6f44 + with: + wsl_firewall_interface: 'vEthernet (WSL (Hyper-V firewall))' + + - name: Install node modules in WSL (re-using from previous install/cache restore) + run: | + cd ${{steps.init_wsl.outputs.repo_path}} + yarn install --immutable + shell: wsl-bash {0} + + - name: Build test binary for Windows (inside WSL) + shell: wsl-bash {0} + run: | + cd ${{steps.init_wsl.outputs.repo_path}} + yarn bazel \ + build --config=e2e //tests/legacy-cli:${{inputs.test_target_name}} --platforms=tools:windows_x64 + env: + # See: https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows + WSLENV: 'GOOGLE_APPLICATION_CREDENTIALS/p' + + - name: Copying binary artifact to host + shell: wsl-bash {0} + run: | + cd ${{steps.init_wsl.outputs.repo_path}} + tar -cf /tmp/test.tar.gz dist/bin/tests/legacy-cli/${{inputs.test_target_name}}_ + mkdir /mnt/c/test + mv /tmp/test.tar.gz /mnt/c/test + (cd /mnt/c/test && tar -xf /mnt/c/test/test.tar.gz) + + - name: Convert symlinks for Windows host + shell: wsl-bash {0} + run: | + cd ${{steps.init_wsl.outputs.repo_path}} + + runfiles_dir="/mnt/c/test/dist/bin/tests/legacy-cli/${{inputs.test_target_name}}_/${{inputs.test_target_name}}.bat.runfiles" + + # Make WSL symlinks compatible on Windows native file system. + node scripts/windows-testing/convert-symlinks.mjs $runfiles_dir "${{steps.init_wsl.outputs.cmd_path}}" + + # Needed for resolution because Aspect/Bazel looks for repositories at `/external`. + # TODO(devversion): consult with Aspect on why this is needed. + (cd $runfiles_dir/angular_cli && ${{steps.init_wsl.outputs.cmd_path}} /C "mklink /D external ..") + + - name: Run tests + # Note: This is Git Bash. + shell: bash + env: + BAZEL_BINDIR: '.' + working-directory: "C:\\test" + run: | + node "${{github.workspace}}\\scripts\\windows-testing\\parallel-executor.mjs" \ + $PWD/dist/bin/tests/legacy-cli/${{inputs.test_target_name}}_/${{inputs.test_target_name}}.bat.runfiles \ + ${{inputs.test_target_name}} \ + "${{inputs.test_args}}" \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2b9c345bb9e..91fad7bcdd51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,20 +74,12 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest] node: [20, 22] subset: [npm, esbuild] shard: [0, 1, 2, 3, 4, 5] - exclude: - # Skip Node.js v20 tests on Windows - - os: windows-latest - node: 20 runs-on: ${{ matrix.os }} steps: - # Workaround for: https://github.com/bazel-contrib/bazel-lib/issues/968. - # TODO(devversion): Remove when Aspect lib issue is fixed. - - run: choco install gzip - if: ${{matrix.os == 'windows-latest'}} - name: Initialize environment uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@836bdd0543d15904c469f5a0ce869d30a8029971 - name: Install node modules @@ -97,7 +89,27 @@ jobs: - name: Setup Bazel RBE uses: angular/dev-infra/github-actions/bazel/configure-remote@836bdd0543d15904c469f5a0ce869d30a8029971 - name: Run CLI E2E tests - run: yarn bazel test --define=E2E_SHARD_TOTAL=6 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }} + run: yarn bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }} + + e2e_windows: + strategy: + fail-fast: false + matrix: + os: [windows-2025] + node: [22] + subset: [npm, esbuild] + shard: [0, 1, 2, 3, 4, 5] + runs-on: ${{ matrix.os }} + steps: + - name: Initialize environment + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0000d926624b2fd918e93f1c6b5e2defba9af91f + - name: Run CLI E2E tests + uses: ./.github/shared-actions/windows-bazel-test + with: + test_target_name: e2e.${{ matrix.subset }}_node${{ matrix.node }} + env: + E2E_SHARD_TOTAL: 6 + E2E_SHARD_INDEX: ${{ matrix.shard }} e2e-package-managers: needs: test @@ -119,7 +131,7 @@ jobs: - name: Setup Bazel RBE uses: angular/dev-infra/github-actions/bazel/configure-remote@836bdd0543d15904c469f5a0ce869d30a8029971 - name: Run CLI E2E tests - run: yarn bazel test --define=E2E_SHARD_TOTAL=3 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }} + run: yarn bazel test --test_env=E2E_SHARD_TOTAL=3 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }} e2e-snapshots: needs: test @@ -141,7 +153,7 @@ jobs: - name: Setup Bazel RBE uses: angular/dev-infra/github-actions/bazel/configure-remote@836bdd0543d15904c469f5a0ce869d30a8029971 - name: Run CLI E2E tests - run: yarn bazel test --define=E2E_SHARD_TOTAL=6 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.snapshots.${{ matrix.subset }}_node${{ matrix.node }} + run: yarn bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.snapshots.${{ matrix.subset }}_node${{ matrix.node }} browsers: needs: build diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b967cff7b7c1..436c32b47b37 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -123,25 +123,19 @@ jobs: - name: Setup Bazel RBE uses: angular/dev-infra/github-actions/bazel/configure-remote@836bdd0543d15904c469f5a0ce869d30a8029971 - name: Run CLI E2E tests - run: yarn bazel test --define=E2E_SHARD_TOTAL=6 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }} + run: yarn bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }} e2e-windows-subset: needs: build - runs-on: windows-latest + runs-on: windows-2025 steps: - # Workaround for: https://github.com/bazel-contrib/bazel-lib/issues/968. - # TODO(devversion): Remove when Aspect lib issue is fixed. - - run: choco install gzip - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@836bdd0543d15904c469f5a0ce869d30a8029971 - - name: Install node modules - run: yarn install --immutable - - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@836bdd0543d15904c469f5a0ce869d30a8029971 - - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@836bdd0543d15904c469f5a0ce869d30a8029971 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0000d926624b2fd918e93f1c6b5e2defba9af91f - name: Run CLI E2E tests - run: yarn bazel test --config=e2e //tests/legacy-cli:e2e_node22 --test_filter="tests/basic/{build,rebuild}.ts" --test_arg="--esbuild" + uses: ./.github/shared-actions/windows-bazel-test + with: + test_target_name: e2e_node22 + test_args: --esbuild --glob "tests/basic/{build,rebuild}.ts" e2e-package-managers: needs: build @@ -163,7 +157,7 @@ jobs: - name: Setup Bazel RBE uses: angular/dev-infra/github-actions/bazel/configure-remote@836bdd0543d15904c469f5a0ce869d30a8029971 - name: Run CLI E2E tests - run: yarn bazel test --define=E2E_SHARD_TOTAL=3 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }} + run: yarn bazel test --test_env=E2E_SHARD_TOTAL=3 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.${{ matrix.subset }}_node${{ matrix.node }} e2e-snapshots: needs: [analyze, build] @@ -186,4 +180,4 @@ jobs: - name: Setup Bazel RBE uses: angular/dev-infra/github-actions/bazel/configure-remote@836bdd0543d15904c469f5a0ce869d30a8029971 - name: Run CLI E2E tests - run: yarn bazel test --define=E2E_SHARD_TOTAL=6 --define=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.snapshots.${{ matrix.subset }}_node${{ matrix.node }} + run: yarn bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests/legacy-cli:e2e.snapshots.${{ matrix.subset }}_node${{ matrix.node }} diff --git a/WORKSPACE b/WORKSPACE index 1f58027380e1..5637bd00cb06 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,12 +2,6 @@ workspace(name = "angular_cli") DEFAULT_NODE_VERSION = "20.11.1" -# Workaround for: https://github.com/bazel-contrib/bazel-lib/issues/968. -# Override toolchain for tar on windows. -register_toolchains( - "//tools:windows_tar_system_toolchain", -) - load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file") http_archive( @@ -145,17 +139,6 @@ aspect_bazel_lib_dependencies() aspect_bazel_lib_register_toolchains() -register_toolchains( - "@npm//@angular/build-tooling/bazel/git-toolchain:git_linux_toolchain", - "@npm//@angular/build-tooling/bazel/git-toolchain:git_macos_x86_toolchain", - "@npm//@angular/build-tooling/bazel/git-toolchain:git_macos_arm64_toolchain", - "@npm//@angular/build-tooling/bazel/git-toolchain:git_windows_toolchain", -) - -load("@npm//@angular/build-tooling/bazel/browsers:browser_repositories.bzl", "browser_repositories") - -browser_repositories() - load("@build_bazel_rules_nodejs//toolchains/esbuild:esbuild_repositories.bzl", "esbuild_repositories") esbuild_repositories( @@ -205,6 +188,10 @@ npm_translate_lock( # for `rules_nodejs` dependencies :) }, pnpm_lock = "//:pnpm-lock.yaml", + public_hoist_packages = { + # TODO: Remove when https://github.com/verdaccio/verdaccio/commit/bf0e09a509e8e0a74167b0307d129202bc3f40d2 is available. + "@verdaccio/config": [""], + }, update_pnpm_lock = True, verify_node_modules_ignored = "//:.bazelignore", yarn_lock = "//:yarn.lock", @@ -216,8 +203,6 @@ npm_repositories() http_archive( name = "aspect_rules_ts", - patch_args = ["-p1"], - patches = ["//tools:rules_ts_windows.patch"], sha256 = "4263532b2fb4d16f309d80e3597191a1cb2fb69c19e95d91711bd6b97874705e", strip_prefix = "rules_ts-3.5.0", url = "https://github.com/aspect-build/rules_ts/releases/download/v3.5.0/rules_ts-v3.5.0.tar.gz", @@ -253,7 +238,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") git_repository( name = "devinfra", - commit = "0ad6a370f70638e785d6ef1f90dc6ede34684a47", + commit = "bf0dd632ed129ee8770b09a6e11c6497162b3edb", remote = "https://github.com/angular/dev-infra.git", ) @@ -264,3 +249,14 @@ setup_dependencies_1() load("@devinfra//bazel:setup_dependencies_2.bzl", "setup_dependencies_2") setup_dependencies_2() + +load("@devinfra//bazel/browsers:browser_repositories.bzl", "browser_repositories") + +browser_repositories() + +register_toolchains( + "@devinfra//bazel/git-toolchain:git_linux_toolchain", + "@devinfra//bazel/git-toolchain:git_macos_x86_toolchain", + "@devinfra//bazel/git-toolchain:git_macos_arm64_toolchain", + "@devinfra//bazel/git-toolchain:git_windows_toolchain", +) diff --git a/goldens/BUILD.bazel b/goldens/BUILD.bazel index 3b3283026537..6dbbdd28f25b 100644 --- a/goldens/BUILD.bazel +++ b/goldens/BUILD.bazel @@ -1,6 +1,8 @@ +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") + package(default_visibility = ["//visibility:public"]) -filegroup( +copy_to_bin( name = "public-api", srcs = glob([ "public-api/**/*.md", diff --git a/package.json b/package.json index 5c2aaf6582cc..20a0210b8057 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "devDependencies": { "@ampproject/remapping": "2.3.0", "@angular/animations": "19.2.0", - "@angular/bazel": "https://github.com/angular/bazel-builds.git#8faa06d66416ce78073ab59539ff03f5253b8d52", - "@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#a6a996a69cfc03b3fbe538f11dd24b7bc4b30592", + "@angular/bazel": "https://github.com/angular/bazel-builds.git#58e1a344eed2dfea489cd290a4b4a963f7e3ac65", + "@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#d4727212a9d0f7eb63ae3116d73c769d9bd0bdc1", "@angular/cdk": "19.2.1", "@angular/common": "19.2.0", "@angular/compiler": "19.2.0", @@ -72,7 +72,6 @@ "@babel/runtime": "7.26.9", "@bazel/bazelisk": "1.25.0", "@bazel/buildifier": "8.0.3", - "@bazel/runfiles": "^6.0.0", "@discoveryjs/json-ext": "0.6.3", "@eslint/compat": "1.2.7", "@eslint/eslintrc": "3.3.0", @@ -82,6 +81,7 @@ "@listr2/prompt-adapter-inquirer": "2.0.18", "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-commonjs": "^28.0.0", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^13.0.5", "@stylistic/eslint-plugin": "^4.0.0", "@types/babel__core": "7.20.5", diff --git a/packages/angular/build/BUILD.bazel b/packages/angular/build/BUILD.bazel index 77677355c546..2be247eb62e7 100644 --- a/packages/angular/build/BUILD.bazel +++ b/packages/angular/build/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") load("@npm2//:defs.bzl", "npm_link_all_packages") load("//tools:defaults2.bzl", "copy_to_bin", "jasmine_test", "npm_package", "ts_project") load("//tools:ts_json_schema.bzl", "ts_json_schema") @@ -309,6 +309,6 @@ api_golden_test_npm_package( ":npm_package", "//goldens:public-api", ], - golden_dir = "angular_cli/goldens/public-api/angular/build", - npm_package = "angular_cli/packages/angular/build/npm_package", + golden_dir = "goldens/public-api/angular/build", + npm_package = "packages/angular/build/npm_package", ) diff --git a/packages/angular/ssr/BUILD.bazel b/packages/angular/ssr/BUILD.bazel index f16b21afea70..fdd727079ae7 100644 --- a/packages/angular/ssr/BUILD.bazel +++ b/packages/angular/ssr/BUILD.bazel @@ -1,5 +1,5 @@ load("@aspect_rules_js//npm:defs.bzl", "npm_package") -load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") load("@npm2//:defs.bzl", "npm_link_all_packages") load("@rules_pkg//:pkg.bzl", "pkg_tar") load("//tools:defaults2.bzl", "ng_package", "ts_project") @@ -90,6 +90,6 @@ api_golden_test_npm_package( ":npm_package", "//goldens:public-api", ], - golden_dir = "angular_cli/goldens/public-api/angular/ssr", - npm_package = "angular_cli/packages/angular/ssr/npm_package", + golden_dir = "goldens/public-api/angular/ssr", + npm_package = "packages/angular/ssr/npm_package", ) diff --git a/packages/angular/ssr/package.json b/packages/angular/ssr/package.json index a961859ad100..baf26214afd1 100644 --- a/packages/angular/ssr/package.json +++ b/packages/angular/ssr/package.json @@ -35,7 +35,6 @@ "@angular/platform-browser": "19.2.0", "@angular/platform-server": "19.2.0", "@angular/router": "19.2.0", - "@bazel/runfiles": "^6.0.0", "@schematics/angular": "workspace:*" }, "sideEffects": false, diff --git a/packages/angular/ssr/test/npm_package/BUILD.bazel b/packages/angular/ssr/test/npm_package/BUILD.bazel index 6f5964521dcf..0fcfd9a87ba6 100644 --- a/packages/angular/ssr/test/npm_package/BUILD.bazel +++ b/packages/angular/ssr/test/npm_package/BUILD.bazel @@ -7,7 +7,6 @@ ts_project( testonly = True, srcs = glob(["**/*.ts"]), deps = [ - "//:node_modules/@bazel/runfiles", "//:node_modules/@types/node", ], ) diff --git a/packages/angular/ssr/test/npm_package/package_spec.ts b/packages/angular/ssr/test/npm_package/package_spec.ts index 39580cce2013..2bd37aeabf5b 100644 --- a/packages/angular/ssr/test/npm_package/package_spec.ts +++ b/packages/angular/ssr/test/npm_package/package_spec.ts @@ -6,17 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import { runfiles } from '@bazel/runfiles'; import { existsSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; -import { dirname, join } from 'node:path'; +import { join, resolve } from 'node:path'; /** * Resolve paths for the Beasties license file and the golden reference file. */ -const ANGULAR_SSR_PACKAGE_PATH = dirname( - runfiles.resolve('angular_cli/packages/angular/ssr/npm_package/package.json'), -); +const ANGULAR_SSR_PACKAGE_PATH = resolve('../../npm_package'); /** * Path to the actual license file for the Beasties library. diff --git a/packages/angular_devkit/architect/BUILD.bazel b/packages/angular_devkit/architect/BUILD.bazel index 276e649b337f..98a15714bff7 100644 --- a/packages/angular_devkit/architect/BUILD.bazel +++ b/packages/angular_devkit/architect/BUILD.bazel @@ -3,7 +3,7 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.dev/license -load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") load("@npm2//:defs.bzl", "npm_link_all_packages") load("//tools:defaults2.bzl", "jasmine_test", "npm_package", "ts_project") load("//tools:ts_json_schema.bzl", "ts_json_schema") @@ -122,7 +122,7 @@ api_golden_test_npm_package( ":npm_package", "//goldens:public-api", ], - golden_dir = "angular_cli/goldens/public-api/angular_devkit/architect", - npm_package = "angular_cli/packages/angular_devkit/architect/npm_package", + golden_dir = "goldens/public-api/angular_devkit/architect", + npm_package = "packages/angular_devkit/architect/npm_package", ) # @external_end diff --git a/packages/angular_devkit/build_angular/BUILD.bazel b/packages/angular_devkit/build_angular/BUILD.bazel index c86f2a1fe929..80245e6f842e 100644 --- a/packages/angular_devkit/build_angular/BUILD.bazel +++ b/packages/angular_devkit/build_angular/BUILD.bazel @@ -3,7 +3,7 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.dev/license -load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") load("@npm2//:defs.bzl", "npm_link_all_packages") load("//tools:defaults2.bzl", "copy_to_bin", "jasmine_test", "npm_package", "ts_project") load("//tools:ts_json_schema.bzl", "ts_json_schema") @@ -275,8 +275,8 @@ api_golden_test_npm_package( ":npm_package", "//goldens:public-api", ], - golden_dir = "angular_cli/goldens/public-api/angular_devkit/build_angular", - npm_package = "angular_cli/packages/angular_devkit/build_angular/npm_package", + golden_dir = "goldens/public-api/angular_devkit/build_angular", + npm_package = "packages/angular_devkit/build_angular/npm_package", ) # Large build_angular specs diff --git a/packages/angular_devkit/build_webpack/BUILD.bazel b/packages/angular_devkit/build_webpack/BUILD.bazel index 5e198c7ba883..a02d2feba31b 100644 --- a/packages/angular_devkit/build_webpack/BUILD.bazel +++ b/packages/angular_devkit/build_webpack/BUILD.bazel @@ -3,7 +3,7 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.dev/license -load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") load("@npm2//:defs.bzl", "npm_link_all_packages") load("//tools:defaults2.bzl", "jasmine_test", "npm_package", "ts_project") load("//tools:ts_json_schema.bzl", "ts_json_schema") @@ -120,6 +120,6 @@ api_golden_test_npm_package( ":npm_package", "//goldens:public-api", ], - golden_dir = "angular_cli/goldens/public-api/angular_devkit/build_webpack", - npm_package = "angular_cli/packages/angular_devkit/build_webpack/npm_package", + golden_dir = "goldens/public-api/angular_devkit/build_webpack", + npm_package = "packages/angular_devkit/build_webpack/npm_package", ) diff --git a/packages/angular_devkit/core/BUILD.bazel b/packages/angular_devkit/core/BUILD.bazel index c71a9c84e0b4..528dabcffe82 100644 --- a/packages/angular_devkit/core/BUILD.bazel +++ b/packages/angular_devkit/core/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") load("//tools:defaults2.bzl", "jasmine_test", "npm_package", "ts_project") # Copyright Google Inc. All Rights Reserved. @@ -90,8 +90,10 @@ api_golden_test_npm_package( ":npm_package", "//goldens:public-api", ], - golden_dir = "angular_cli/goldens/public-api/angular_devkit/core", - npm_package = "angular_cli/packages/angular_devkit/core/npm_package", - types = ["@npm//@types/node"], + golden_dir = "goldens/public-api/angular_devkit/core", + npm_package = "packages/angular_devkit/core/npm_package", + types = { + "//:node_modules/@types/node": "node", + }, ) # @external_end diff --git a/packages/angular_devkit/schematics/BUILD.bazel b/packages/angular_devkit/schematics/BUILD.bazel index e2c1e4bbbff8..491b1cbfd9ed 100644 --- a/packages/angular_devkit/schematics/BUILD.bazel +++ b/packages/angular_devkit/schematics/BUILD.bazel @@ -1,4 +1,4 @@ -load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") load("@npm2//:defs.bzl", "npm_link_all_packages") load("//tools:defaults2.bzl", "jasmine_test", "npm_package", "ts_project") @@ -85,7 +85,9 @@ api_golden_test_npm_package( ":npm_package", "//goldens:public-api", ], - golden_dir = "angular_cli/goldens/public-api/angular_devkit/schematics", - npm_package = "angular_cli/packages/angular_devkit/schematics/npm_package", - types = ["@npm//@types/node"], + golden_dir = "goldens/public-api/angular_devkit/schematics", + npm_package = "packages/angular_devkit/schematics/npm_package", + types = { + "//:node_modules/@types/node": "node", + }, ) diff --git a/packages/ngtools/webpack/BUILD.bazel b/packages/ngtools/webpack/BUILD.bazel index 36d3096f6f03..7e4d5ce7a53e 100644 --- a/packages/ngtools/webpack/BUILD.bazel +++ b/packages/ngtools/webpack/BUILD.bazel @@ -3,7 +3,7 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.dev/license -load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") +load("@devinfra//bazel/api-golden:index_rjs.bzl", "api_golden_test_npm_package") load("@npm2//:defs.bzl", "npm_link_all_packages") load("//tools:defaults2.bzl", "jasmine_test", "npm_package", "ts_project") @@ -88,6 +88,6 @@ api_golden_test_npm_package( ":npm_package", "//goldens:public-api", ], - golden_dir = "angular_cli/goldens/public-api/ngtools/webpack", - npm_package = "angular_cli/packages/ngtools/webpack/npm_package", + golden_dir = "goldens/public-api/ngtools/webpack", + npm_package = "packages/ngtools/webpack/npm_package", ) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de87bd9577a5..b7e065fe9910 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,11 +23,11 @@ importers: specifier: 19.2.0 version: 19.2.0(@angular/core@19.2.0) '@angular/bazel': - specifier: https://github.com/angular/bazel-builds.git#8faa06d66416ce78073ab59539ff03f5253b8d52 - version: github.com/angular/bazel-builds/8faa06d66416ce78073ab59539ff03f5253b8d52(@angular/compiler-cli@19.2.0)(@rollup/plugin-commonjs@28.0.2)(@rollup/plugin-node-resolve@13.3.0)(@types/node@20.17.19)(rollup-plugin-sourcemaps@0.6.3)(rollup@4.34.9)(terser@5.39.0)(typescript@5.8.2) + specifier: https://github.com/angular/bazel-builds.git#58e1a344eed2dfea489cd290a4b4a963f7e3ac65 + version: github.com/angular/bazel-builds/58e1a344eed2dfea489cd290a4b4a963f7e3ac65(@angular/compiler-cli@19.2.0)(@rollup/plugin-commonjs@28.0.2)(@rollup/plugin-node-resolve@13.3.0)(@types/node@20.17.19)(rollup-plugin-sourcemaps@0.6.3)(rollup@4.34.9)(terser@5.39.0)(typescript@5.8.2) '@angular/build-tooling': - specifier: https://github.com/angular/dev-infra-private-build-tooling-builds.git#a6a996a69cfc03b3fbe538f11dd24b7bc4b30592 - version: github.com/angular/dev-infra-private-build-tooling-builds/a6a996a69cfc03b3fbe538f11dd24b7bc4b30592(debug@4.4.0)(karma-chrome-launcher@3.2.0)(karma-jasmine@5.1.0)(karma@6.4.4)(rxjs@7.8.2)(terser@5.39.0)(zone.js@0.15.0) + specifier: https://github.com/angular/dev-infra-private-build-tooling-builds.git#d4727212a9d0f7eb63ae3116d73c769d9bd0bdc1 + version: github.com/angular/dev-infra-private-build-tooling-builds/d4727212a9d0f7eb63ae3116d73c769d9bd0bdc1(debug@4.4.0)(karma-chrome-launcher@3.2.0)(karma-jasmine@5.1.0)(karma@6.4.4)(rxjs@7.8.2)(terser@5.39.0)(zone.js@0.15.0) '@angular/cdk': specifier: 19.2.1 version: 19.2.1(@angular/common@19.2.0)(@angular/core@19.2.0)(rxjs@7.8.2) @@ -106,9 +106,6 @@ importers: '@bazel/buildifier': specifier: 8.0.3 version: 8.0.3 - '@bazel/runfiles': - specifier: ^6.0.0 - version: 6.3.1 '@discoveryjs/json-ext': specifier: 0.6.3 version: 0.6.3 @@ -136,6 +133,9 @@ importers: '@rollup/plugin-commonjs': specifier: ^28.0.0 version: 28.0.2(rollup@4.34.9) + '@rollup/plugin-json': + specifier: ^6.1.0 + version: 6.1.0(rollup@4.34.9) '@rollup/plugin-node-resolve': specifier: ^13.0.5 version: 13.3.0(rollup@4.34.9) @@ -735,9 +735,6 @@ importers: '@angular/router': specifier: 19.2.0 version: 19.2.0(@angular/common@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0)(rxjs@7.8.2) - '@bazel/runfiles': - specifier: ^6.0.0 - version: 6.3.1 '@schematics/angular': specifier: workspace:* version: link:../../schematics/angular @@ -2387,7 +2384,6 @@ packages: /@bazel/typescript@5.8.1(typescript@5.8.2): resolution: {integrity: sha512-NAJ8WQHZL1WE1YmRoCrq/1hhG15Mvy/viWh6TkvFnBeEhNUiQUsA5GYyhU1ztnBIYW03nATO3vwhAEfO7Q0U5g==} - deprecated: No longer maintained, https://github.com/aspect-build/rules_ts is the recommended replacement hasBin: true peerDependencies: typescript: 5.8.2 @@ -11578,7 +11574,7 @@ packages: /puppeteer@18.2.1: resolution: {integrity: sha512-7+UhmYa7wxPh2oMRwA++k8UGVDxh3YdWFB52r9C3tM81T6BU7cuusUSxImz0GEYSOYUKk/YzIhkQ6+vc0gHbxQ==} engines: {node: '>=14.1.0'} - deprecated: < 22.8.2 is no longer supported + deprecated: < 19.4.0 is no longer supported requiresBuild: true dependencies: https-proxy-agent: 5.0.1(supports-color@10.0.0) @@ -14233,15 +14229,15 @@ packages: resolution: {integrity: sha512-9oxn0IIjbCZkJ67L+LkhYWRyAy7axphb3VgE2MBDlOqnmHMPWGYMxJxBYFueFq/JGY2GMwS0rU+UCLunEmy5UA==} dev: true - github.com/angular/bazel-builds/8faa06d66416ce78073ab59539ff03f5253b8d52(@angular/compiler-cli@19.2.0)(@rollup/plugin-commonjs@28.0.2)(@rollup/plugin-node-resolve@13.3.0)(@types/node@20.17.19)(rollup-plugin-sourcemaps@0.6.3)(rollup@4.34.9)(terser@5.39.0)(typescript@5.8.2): - resolution: {tarball: https://codeload.github.com/angular/bazel-builds/tar.gz/8faa06d66416ce78073ab59539ff03f5253b8d52} - id: github.com/angular/bazel-builds/8faa06d66416ce78073ab59539ff03f5253b8d52 + github.com/angular/bazel-builds/58e1a344eed2dfea489cd290a4b4a963f7e3ac65(@angular/compiler-cli@19.2.0)(@rollup/plugin-commonjs@28.0.2)(@rollup/plugin-node-resolve@13.3.0)(@types/node@20.17.19)(rollup-plugin-sourcemaps@0.6.3)(rollup@4.34.9)(terser@5.39.0)(typescript@5.8.2): + resolution: {tarball: https://codeload.github.com/angular/bazel-builds/tar.gz/58e1a344eed2dfea489cd290a4b4a963f7e3ac65} + id: github.com/angular/bazel-builds/58e1a344eed2dfea489cd290a4b4a963f7e3ac65 name: '@angular/bazel' - version: 20.0.0-next.0 + version: 19.3.0-next.0 engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} hasBin: true peerDependencies: - '@angular/compiler-cli': 20.0.0-next.0+sha-51b8ff2 + '@angular/compiler-cli': 19.3.0-next.0+sha-8657a0e '@bazel/concatjs': ^5.3.0 '@bazel/worker': ^5.3.0 '@rollup/plugin-commonjs': ^28.0.0 @@ -14268,11 +14264,11 @@ packages: - '@types/node' dev: true - github.com/angular/dev-infra-private-build-tooling-builds/a6a996a69cfc03b3fbe538f11dd24b7bc4b30592(debug@4.4.0)(karma-chrome-launcher@3.2.0)(karma-jasmine@5.1.0)(karma@6.4.4)(rxjs@7.8.2)(terser@5.39.0)(zone.js@0.15.0): - resolution: {tarball: https://codeload.github.com/angular/dev-infra-private-build-tooling-builds/tar.gz/a6a996a69cfc03b3fbe538f11dd24b7bc4b30592} - id: github.com/angular/dev-infra-private-build-tooling-builds/a6a996a69cfc03b3fbe538f11dd24b7bc4b30592 + github.com/angular/dev-infra-private-build-tooling-builds/d4727212a9d0f7eb63ae3116d73c769d9bd0bdc1(debug@4.4.0)(karma-chrome-launcher@3.2.0)(karma-jasmine@5.1.0)(karma@6.4.4)(rxjs@7.8.2)(terser@5.39.0)(zone.js@0.15.0): + resolution: {tarball: https://codeload.github.com/angular/dev-infra-private-build-tooling-builds/tar.gz/d4727212a9d0f7eb63ae3116d73c769d9bd0bdc1} + id: github.com/angular/dev-infra-private-build-tooling-builds/d4727212a9d0f7eb63ae3116d73c769d9bd0bdc1 name: '@angular/build-tooling' - version: 0.0.0-b015169b635123c1ab9084f604e36b6342eac171 + version: 0.0.0-74aabba6d202918280dafe92f87f9c154476fa86 dependencies: '@angular/benchpress': 0.3.0(rxjs@7.8.2)(zone.js@0.15.0) '@angular/build': link:packages/angular/build diff --git a/scripts/windows-testing/convert-symlinks.mjs b/scripts/windows-testing/convert-symlinks.mjs new file mode 100644 index 000000000000..56d8b1ad85bc --- /dev/null +++ b/scripts/windows-testing/convert-symlinks.mjs @@ -0,0 +1,158 @@ +/** + * @fileoverview Script that takes a directory and converts all its Unix symlinks + * to relative Windows-compatible symlinks. This is necessary because when building + * tests via Bazel inside WSL; the output cannot simply be used outside WSL to perform + * native Windows testing. This is a known limitation/bug of the WSL <> Windows interop. + * + * Symlinks are commonly used by Bazel inside the `.runfiles` directory, which is relevant + * for executing tests outside Bazel on the host machine. In addition, `rules_js` heavily + * relies on symlinks for node modules. + * + * Some more details in: + * - https://blog.trailofbits.com/2024/02/12/why-windows-cant-follow-wsl-symlinks/. + * - https://pnpm.io/symlinked-node-modules-structure. + */ + +import path from 'node:path'; +import fs from 'node:fs/promises'; +import childProcess from 'node:child_process'; + +const [rootDir, cmdPath] = process.argv.slice(2); + +// GitHub actions can set this environment variable when pressing the "re-run" button. +const debug = process.env.ACTIONS_STEP_DEBUG === 'true'; +const skipDirectories = [ + // Modules that we don't need and would unnecessarily slow-down this. + '_windows_amd64/bin/nodejs/node_modules', +]; + +const workspaceRootPaths = [/.*\.runfiles\/angular_cli\//, /^.*-fastbuild\/bin\//]; + +// Copying can be parallelized and doesn't cause any WSL flakiness (no exe is invoked). +const parallelCopyTasks = []; + +async function transformDir(p) { + // We perform all command executions in parallel here to speed up. + // Note that we can't parallelize for the full recursive directory, + // as WSL and its interop would otherwise end up with some flaky errors. + // See: https://github.com/microsoft/WSL/issues/8677. + const tasks = []; + // We explore directories after all files were checked at this level. + const directoriesToVisit = []; + + for (const file of await fs.readdir(p, { withFileTypes: true })) { + const subPath = path.join(p, file.name); + + if (skipDirectories.some((d) => subPath.endsWith(d))) { + continue; + } + + if (file.isSymbolicLink()) { + // Allow for parallel processing of directory entries. + tasks.push( + (async () => { + let target = ''; + try { + target = await fs.realpath(subPath); + } catch (e) { + if (debug) { + console.error('Skipping', subPath); + } + return; + } + + await fs.rm(subPath); + + const subPathId = relativizeForSimilarWorkspacePaths(subPath); + const targetPathId = relativizeForSimilarWorkspacePaths(target); + const isSelfLink = subPathId === targetPathId; + + // This is an actual file that needs to be copied. Copy contents. + // - the target path is equivalent to the link. This is a self-link from `.runfiles` to `bin/`. + // - the target path is outside any of our workspace roots. + if (isSelfLink || targetPathId.startsWith('..')) { + parallelCopyTasks.push(exec(`cp -Rf ${target} ${subPath}`)); + return; + } + + const relativeSubPath = relativizeToRoot(subPath); + const targetAtDestination = path.relative(path.dirname(subPathId), targetPathId); + const targetAtDestinationWindowsPath = targetAtDestination.replace(/\//g, '\\'); + + const wslSubPath = relativeSubPath.replace(/\//g, '\\'); + + if (debug) { + console.log({ + targetAtDestination, + subPath, + relativeSubPath, + target, + targetPathId, + subPathId, + }); + } + + if ((await fs.stat(target)).isDirectory()) { + // This is a symlink to a directory, create a dir junction. + // Re-create this symlink on the Windows FS using the Windows mklink command. + await exec( + `${cmdPath} /c mklink /d "${wslSubPath}" "${targetAtDestinationWindowsPath}"`, + ); + } else { + // This is a symlink to a file, create a file junction. + // Re-create this symlink on the Windows FS using the Windows mklink command. + await exec(`${cmdPath} /c mklink "${wslSubPath}" "${targetAtDestinationWindowsPath}"`); + } + })(), + ); + } else if (file.isDirectory()) { + directoriesToVisit.push(subPath); + } + } + + // Wait for all commands/tasks to complete, executed in parallel. + await Promise.all(tasks); + + // Descend into other directories, sequentially to avoid WSL interop errors. + for (const d of directoriesToVisit) { + await transformDir(d); + } +} + +function exec(cmd) { + return new Promise((resolve, reject) => { + childProcess.exec(cmd, { cwd: rootDir }, (error) => { + if (error !== null) { + reject(error); + } else { + resolve(); + } + }); + }); +} + +function relativizeForSimilarWorkspacePaths(p) { + const workspaceRootMatch = workspaceRootPaths.find((r) => r.test(p)); + if (workspaceRootMatch !== undefined) { + return p.replace(workspaceRootMatch, ''); + } + + return path.relative(rootDir, p); +} + +function relativizeToRoot(p) { + const res = path.relative(rootDir, p); + if (!res.startsWith('..')) { + return res; + } + + throw new Error('Could not relativize to root: ' + p); +} + +try { + await transformDir(rootDir); + await Promise.all(parallelCopyTasks); +} catch (err) { + console.error('Could not convert symlinks:', err); + process.exitCode = 1; +} diff --git a/scripts/windows-testing/parallel-executor.mjs b/scripts/windows-testing/parallel-executor.mjs new file mode 100644 index 000000000000..0020354692a7 --- /dev/null +++ b/scripts/windows-testing/parallel-executor.mjs @@ -0,0 +1,178 @@ +import * as child_process from 'node:child_process'; +import path from 'node:path'; +import { stripVTControlCharacters } from 'node:util'; + +const initialStatusRegex = /Running (\d+) tests/; + +async function main() { + const [runfilesDir, targetName, testArgs] = process.argv.slice(2); + const maxShards = 4; + + const testEntrypoint = path.resolve(runfilesDir, '../', targetName); + const testWorkingDir = path.resolve(runfilesDir, 'angular_cli'); + const tasks = []; + const progress = {}; + + for (let i = 0; i < maxShards; i++) { + tasks.push( + spawnTest( + 'bash', + [testEntrypoint, ...testArgs.split(' ').filter((arg) => arg !== '')], + { + cwd: testWorkingDir, + env: { + // Try to construct a pretty hermetic environment, as within Bazel. + PATH: process.env.PATH, + TEST_TOTAL_SHARDS: maxShards, + TEST_SHARD_INDEX: i, + E2E_SHARD_TOTAL: process.env.E2E_SHARD_TOTAL, + E2E_SHARD_INDEX: process.env.E2E_SHARD_INDEX, + FORCE_COLOR: '3', + // Needed by `rules_js` + BAZEL_BINDIR: '.', + }, + }, + (s) => (progress[i] = s), + ), + ); + } + + const printUpdate = () => { + console.error(`----`); + for (const [taskId, status] of Object.entries(progress)) { + const durationInMin = (Date.now() - status.startTime) / 1000 / 60; + console.error( + `Shard #${taskId}: stage ${status.state} | ` + + `${status.current}/${status.max} tests completed (${durationInMin.toFixed(2)}min)`, + ); + } + }; + + const progressInterval = setInterval(printUpdate, 4000); + + try { + const outputs = await Promise.all(tasks); + printUpdate(); + + for (const [idx, text] of outputs.entries()) { + console.log(`---------- ${idx} -----------`); + console.log(text); + } + + console.error(''); + console.error('Done! Passing'); + } catch (e) { + if (e instanceof TestSpawnError) { + console.error(e.output); + console.error(e.message); + } else if (e instanceof Error) { + console.error(e.message, e.stack); + } else { + console.error(e); + } + + console.error('Tests failed!'); + process.exitCode = 1; + } finally { + clearInterval(progressInterval); + } +} + +function spawnTest(cmd, args, options, reportStatus, startTime = Date.now(), testAttempts = 2) { + testAttempts -= 1; + + const testProgressRegex = /Running test[^\(]+\((\d+) of/g; + + return new Promise((resolve, reject) => { + let output = ''; + let state = 'setup'; + let current = 0; + let max = 0; + + const proc = child_process.spawn(cmd, args, { ...options, stdio: 'pipe' }); + const syncStatus = () => reportStatus({ current, max, state, startTime }); + const restartTest = () => { + console.error(output); + console.error(`Test restarted due to failure.`); + resolve(spawnTest(cmd, args, options, reportStatus, startTime, testAttempts)); + }; + const onOutputChange = () => { + // Extract initial status (i.e. how many tests there are in this shard) + if (initialStatusRegex.test(output) && state === 'setup') { + max = Number(output.match(initialStatusRegex)[1]); + } + if (/Running initializer/.test(output) && state === 'setup') { + state = 'initializing'; + } + if (/Running test/.test(output) && state === 'initializing') { + state = 'testing'; + } + if (state === 'testing') { + const oldLastIndex = testProgressRegex.lastIndex; + const newMatch = testProgressRegex.exec(stripVTControlCharacters(output))?.[1]; + // Do not advance the Regex, or more precisely, reset to index `0`. + if (newMatch === undefined) { + testProgressRegex.lastIndex = oldLastIndex; + } else { + current = Number(newMatch); + } + } + syncStatus(); + }; + proc.stdout.on('data', (data) => { + output += data; + onOutputChange(); + }); + proc.stderr.on('data', (data) => { + output += data; + onOutputChange(); + }); + proc.on('error', (err) => { + syncStatus(); + + // If this test failed and there are test attempts remaining, re-run. + if (testAttempts > 0) { + restartTest(); + return; + } + + reject(new TestSpawnError(err.message, output)); + }); + proc.on('close', (code, signal) => { + syncStatus(); + + if (code === 0 && signal === null) { + resolve(output); + } else { + if (testAttempts > 0) { + restartTest(); + return; + } + + reject( + new TestSpawnError(`Command failed with code: ${code} and signal ${signal}`, output), + ); + } + }); + + // Report initial status, without knowing anything. + syncStatus(); + }); +} + +class TestSpawnError extends Error { + /** @type {string} */ + output; + + constructor(message, output) { + super(message); + this.output = output; + } +} + +try { + main(); +} catch (e) { + console.error(e); + process.exitCode = 1; +} diff --git a/tests/legacy-cli/BUILD.bazel b/tests/legacy-cli/BUILD.bazel index 0dd104c5b43b..f382333beb5b 100644 --- a/tests/legacy-cli/BUILD.bazel +++ b/tests/legacy-cli/BUILD.bazel @@ -1,16 +1,16 @@ +load("@aspect_bazel_lib//lib:directory_path.bzl", "directory_path") +load("@npm2//:rollup/package_json.bzl", rollup = "bin") load("//tools:interop.bzl", "ts_project") load(":e2e.bzl", "e2e_suites") +package(default_visibility = ["//visibility:public"]) + ts_project( name = "runner", testonly = True, srcs = [ "e2e_runner.ts", ], - data = [ - "verdaccio.yaml", - "verdaccio_auth.yaml", - ], deps = [ "//:node_modules/@types/node", "//:node_modules/ansi-colors", @@ -21,17 +21,54 @@ ts_project( ], ) +rollup.rollup( + name = "runner_bundled", + testonly = True, + srcs = [ + "rollup.config.mjs", + ":runner_rjs", + "//:node_modules/@rollup/plugin-alias", + "//:node_modules/@rollup/plugin-commonjs", + "//:node_modules/@rollup/plugin-json", + "//:node_modules/@rollup/plugin-node-resolve", + "//:node_modules/fast-glob", + "//tests/legacy-cli/e2e/initialize:initialize_rjs", + "//tests/legacy-cli/e2e/ng-snapshot", + "//tests/legacy-cli/e2e/setup:setup_rjs", + "//tests/legacy-cli/e2e/tests:tests_rjs", + ], + args = [ + "--format=cjs", + "--config=./rollup.config.mjs", + ], + chdir = package_name(), + out_dirs = ["runner_bundled_out"], + progress_message = "Bundling e2e test runner", +) + +directory_path( + name = "runner_entrypoint", + testonly = True, + directory = ":runner_bundled", + path = "./e2e_runner.js", +) + e2e_suites( name = "e2e", data = [ - ":runner", + ":runner_bundled", + "verdaccio.yaml", + "verdaccio_auth.yaml", - # Tests + setup - # Loaded dynamically at runtime, not compiletime deps + # Dynamically loaded. "//tests/legacy-cli/e2e/assets", - "//tests/legacy-cli/e2e/setup", - "//tests/legacy-cli/e2e/initialize", - "//tests/legacy-cli/e2e/tests", + "//:node_modules/verdaccio", + "//:node_modules/verdaccio-auth-memory", + + # Extra runtime deps due to bundling issues. + # TODO: Clean this up. + "//:node_modules/@verdaccio/config", + "//:node_modules/express", ], - runner = ":e2e_runner.ts", + runner = ":runner_entrypoint", ) diff --git a/tests/legacy-cli/e2e.bzl b/tests/legacy-cli/e2e.bzl index 74402064f961..1bed11a77596 100644 --- a/tests/legacy-cli/e2e.bzl +++ b/tests/legacy-cli/e2e.bzl @@ -1,4 +1,4 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") +load("@aspect_rules_js//js:defs.bzl", "js_test") load("//tools:toolchain_info.bzl", "TOOLCHAINS_NAMES", "TOOLCHAINS_VERSIONS") # bazel query --output=label "kind('pkg_tar', //packages/...)" @@ -87,7 +87,7 @@ def e2e_suites(name, runner, data): # Saucelabs tests are only run on the default toolchain _e2e_suite(name, runner, "saucelabs", data) -def _e2e_tests(name, runner, **kwargs): +def _e2e_tests(name, runner, toolchain, **kwargs): # Always specify all the npm packages args = kwargs.pop("templated_args", []) + [ "--package $(rootpath %s)" % p @@ -100,15 +100,12 @@ def _e2e_tests(name, runner, **kwargs): # Tags that must always be applied tags = kwargs.pop("tags", []) + TEST_TAGS - # Passthru E2E variables in case it is customized by CI etc - configuration_env_vars = kwargs.pop("configuration_env_vars", []) + ["E2E_TEMP", "E2E_SHARD_INDEX", "E2E_SHARD_TOTAL"] - env = kwargs.pop("env", {}) toolchains = kwargs.pop("toolchains", []) # The git toolchain + env env.update({"GIT_BIN": "$(GIT_BIN_PATH)"}) - toolchains = toolchains + ["@npm//@angular/build-tooling/bazel/git-toolchain:current_git_toolchain"] + toolchains = toolchains + ["@devinfra//bazel/git-toolchain:current_git_toolchain"] # Chromium browser toolchain env.update({ @@ -116,18 +113,24 @@ def _e2e_tests(name, runner, **kwargs): "CHROME_PATH": "$(CHROMIUM)", "CHROMEDRIVER_BIN": "$(CHROMEDRIVER)", }) - toolchains = toolchains + ["@npm//@angular/build-tooling/bazel/browsers/chromium:toolchain_alias"] - data = data + ["@npm//@angular/build-tooling/bazel/browsers/chromium"] + toolchains = toolchains + ["@devinfra//bazel/browsers/chromium:toolchain_alias"] + data = data + ["@devinfra//bazel/browsers/chromium"] - nodejs_test( + js_test( name = name, - templated_args = args, + fixed_args = args, data = data, entry_point = runner, env = env, - configuration_env_vars = configuration_env_vars, tags = tags, toolchains = toolchains, + node_toolchain = toolchain, + include_npm = select({ + # For Windows testing mode, we use the real global NPM as otherwise this + # will be a lot of files that need to be brought from WSL to the host FS. + "@platforms//os:windows": False, + "//conditions:default": True, + }), **kwargs ) diff --git a/tests/legacy-cli/e2e/setup/010-local-publish.ts b/tests/legacy-cli/e2e/setup/010-local-publish.ts index 4809b5a8c12d..b58a5b871daf 100644 --- a/tests/legacy-cli/e2e/setup/010-local-publish.ts +++ b/tests/legacy-cli/e2e/setup/010-local-publish.ts @@ -2,7 +2,7 @@ import { writeFile } from 'node:fs/promises'; import { join } from 'node:path/posix'; import { getGlobalVariable } from '../utils/env'; import { PkgInfo } from '../utils/packages'; -import { globalNpm, extractNpmEnv } from '../utils/process'; +import { globalNpm, extractNpmEnv, extractCIAndInfraEnv } from '../utils/process'; import { isPrereleaseCli } from '../utils/project'; export default async function () { @@ -21,6 +21,7 @@ export default async function () { packageTars.map(({ path: p }) => globalNpm(['publish', '--tag', isPrereleaseCli() ? 'next' : 'latest', p], { ...extractNpmEnv(), + ...extractCIAndInfraEnv(), 'NPM_CONFIG_USERCONFIG': npmrc, }), ), diff --git a/tests/legacy-cli/e2e/tests/BUILD.bazel b/tests/legacy-cli/e2e/tests/BUILD.bazel index 61fc5a6c2120..23c3de288a28 100644 --- a/tests/legacy-cli/e2e/tests/BUILD.bazel +++ b/tests/legacy-cli/e2e/tests/BUILD.bazel @@ -6,9 +6,6 @@ ts_project( name = "tests", testonly = True, srcs = glob(["**/*.ts"]), - data = [ - "//tests/legacy-cli/e2e/ng-snapshot", - ], deps = [ "//:node_modules/@types/express", "//:node_modules/@types/node", diff --git a/tests/legacy-cli/e2e/tests/build/auto-csp.ts b/tests/legacy-cli/e2e/tests/build/auto-csp.ts index 6b975dd5a3e8..1839b160d549 100644 --- a/tests/legacy-cli/e2e/tests/build/auto-csp.ts +++ b/tests/legacy-cli/e2e/tests/build/auto-csp.ts @@ -122,6 +122,7 @@ export default async function () { const port = await findFreePort(); await execAndWaitForOutputToMatch('node', ['serve.js'], /Node Express server listening on/, { + ...process.env, 'PORT': String(port), }); diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-csp-nonce.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-csp-nonce.ts index e7bd9d9b1ecb..96be34e524da 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-csp-nonce.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-csp-nonce.ts @@ -149,6 +149,7 @@ export default async function () { ['run', runCommand], /Node Express server listening on/, { + ...process.env, 'PORT': String(port), }, ); diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-ngmodule.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-ngmodule.ts index 6cb4d9e15ed2..dda29bdced62 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-ngmodule.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-ngmodule.ts @@ -155,6 +155,7 @@ export default async function () { ['run', runCommand], /Node Express server listening on/, { + ...process.env, 'PORT': String(port), }, ); diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-standalone.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-standalone.ts index 632c90522a3e..b697ac513ab4 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-standalone.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/express-engine-standalone.ts @@ -118,6 +118,7 @@ export default async function () { ['run', runCommand], /Node Express server listening on/, { + ...process.env, 'PORT': String(port), }, ); diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n-base-href.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n-base-href.ts index c9bcc6ee5a09..c4c2065f8b64 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n-base-href.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n-base-href.ts @@ -109,6 +109,7 @@ async function spawnServer(): Promise { ['run', 'serve:ssr:test-project'], /Node Express server listening on/, { + ...process.env, 'PORT': String(port), }, ); diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n-sub-path.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n-sub-path.ts index 6e473880b32c..9b7f75f04a87 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n-sub-path.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n-sub-path.ts @@ -144,6 +144,7 @@ async function spawnServer(): Promise { ['run', 'serve:ssr:test-project'], /Node Express server listening on/, { + ...process.env, 'PORT': String(port), }, ); diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n.ts index 1c327922d5d2..0f10a959a9de 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server-i18n.ts @@ -120,6 +120,7 @@ async function spawnServer(): Promise { ['run', 'serve:ssr:test-project'], /Node Express server listening on/, { + ...process.env, 'PORT': String(port), }, ); diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server.ts index 822b9ea9bb7e..891b646bfc38 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server.ts @@ -204,6 +204,7 @@ async function spawnServer(): Promise { ['run', 'serve:ssr:test-project'], /Node Express server listening on/, { + ...process.env, 'PORT': String(port), }, ); diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-preload-links.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-preload-links.ts index 77670e5eb64d..92c154db3891 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-preload-links.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-preload-links.ts @@ -215,6 +215,7 @@ async function spawnServer(): Promise { ['run', 'serve:ssr:test-project'], /Node Express server listening on/, { + ...process.env, 'PORT': String(port), }, ); diff --git a/tests/legacy-cli/e2e/tests/build/styles/tailwind-v3.ts b/tests/legacy-cli/e2e/tests/build/styles/tailwind-v3.ts index aa47808d8203..efda7dbcef66 100644 --- a/tests/legacy-cli/e2e/tests/build/styles/tailwind-v3.ts +++ b/tests/legacy-cli/e2e/tests/build/styles/tailwind-v3.ts @@ -1,4 +1,4 @@ -import { deleteFile, expectFileToMatch, writeFile } from '../../../utils/fs'; +import { deleteFile, expectFileToMatch, rimraf, writeFile } from '../../../utils/fs'; import { installPackage, uninstallPackage } from '../../../utils/packages'; import { ng, silentExec } from '../../../utils/process'; import { expectToFail } from '../../../utils/utils'; @@ -8,6 +8,10 @@ export default async function () { // and its configuration file. Otherwise cached builds without tailwind will cause test failures. await ng('cache', 'off'); + // In case a previous test installed tailwindcss, clear it. + // (we don't clear node module directories between tests) + await rimraf('node_modules/tailwindcss'); + // Create configuration file await silentExec('npx', 'tailwindcss@3', 'init'); diff --git a/tests/legacy-cli/e2e/tests/commands/analytics/analytics-info.ts b/tests/legacy-cli/e2e/tests/commands/analytics/analytics-info.ts index 68d15db2358e..d92dfd2ffde6 100644 --- a/tests/legacy-cli/e2e/tests/commands/analytics/analytics-info.ts +++ b/tests/legacy-cli/e2e/tests/commands/analytics/analytics-info.ts @@ -5,16 +5,19 @@ export default async function () { // Should be disabled by default. await configureTest(undefined /** analytics */); await execAndWaitForOutputToMatch('ng', ['analytics', 'info'], /Effective status: disabled/, { + ...process.env, NG_FORCE_TTY: '0', // Disable prompts }); await configureTest('1dba0835-38a3-4957-bf34-9974e2df0df3' /** analytics */); await execAndWaitForOutputToMatch('ng', ['analytics', 'info'], /Effective status: enabled/, { + ...process.env, NG_FORCE_TTY: '0', // Disable prompts }); await configureTest(false /** analytics */); await execAndWaitForOutputToMatch('ng', ['analytics', 'info'], /Effective status: disabled/, { + ...process.env, NG_FORCE_TTY: '0', // Disable prompts }); } diff --git a/tests/legacy-cli/e2e/tests/misc/invalid-schematic-dependencies.ts b/tests/legacy-cli/e2e/tests/misc/invalid-schematic-dependencies.ts index 1579125f0d0e..88300951965e 100644 --- a/tests/legacy-cli/e2e/tests/misc/invalid-schematic-dependencies.ts +++ b/tests/legacy-cli/e2e/tests/misc/invalid-schematic-dependencies.ts @@ -1,6 +1,12 @@ import { join } from 'node:path'; import { expectFileToMatch } from '../../utils/fs'; -import { execWithEnv, extractNpmEnv, ng, silentNpm } from '../../utils/process'; +import { + execWithEnv, + extractCIAndInfraEnv, + extractNpmEnv, + ng, + silentNpm, +} from '../../utils/process'; import { getActivePackageManager, installPackage, uninstallPackage } from '../../utils/packages'; import { isPrereleaseCli } from '../../utils/project'; import { appendFile, writeFile } from 'node:fs/promises'; @@ -48,6 +54,7 @@ async function publishOutdated(npmSpecifier: string): Promise { await execWithEnv('npm', ['publish', stdoutPack.trim(), '--tag=outdated'], { ...extractNpmEnv(), + ...extractCIAndInfraEnv(), 'NPM_CONFIG_USERCONFIG': npmrc, }); } diff --git a/tests/legacy-cli/e2e/tests/vite/reuse-dep-optimization-cache.ts b/tests/legacy-cli/e2e/tests/vite/reuse-dep-optimization-cache.ts index ddcbe5e60d20..56ecdfee8cd0 100644 --- a/tests/legacy-cli/e2e/tests/vite/reuse-dep-optimization-cache.ts +++ b/tests/legacy-cli/e2e/tests/vite/reuse-dep-optimization-cache.ts @@ -12,18 +12,19 @@ export default async function () { await ng('cache', 'on'); const port = await findFreePort(); - await execAndWaitForOutputToMatch( + const serveReady = execAndWaitForOutputToMatch( 'ng', ['serve', '--port', `${port}`], /Application bundle generation complete/, // Use CI:0 to force caching - { DEBUG: 'vite:deps', CI: '0', NO_COLOR: 'true' }, + { ...process.env, DEBUG: 'vite:deps', CI: '0', NO_COLOR: 'true' }, ); - const [, response] = await Promise.all([ - waitForAnyProcessOutputToMatch(/dependencies optimized/, 10_000), - fetch(`http://localhost:${port}/main.js`), - ]); + // Note: Don't await `serveReady` before, as otherwise we might not see + // the dependencies optimized output. There is some debouncing for `ng serve` + // going on that could cause this. + await Promise.all([serveReady, waitForAnyProcessOutputToMatch(/dependencies optimized/, 10_000)]); + const response = await fetch(`http://localhost:${port}/main.js`); assert(response.ok, `Expected 'response.ok' to be 'true'.`); @@ -35,6 +36,6 @@ export default async function () { ['serve', '--port=0'], /Hash is consistent\. Skipping/, // Use CI:0 to force caching - { DEBUG: 'vite:deps', CI: '0', NO_COLOR: 'true' }, + { ...process.env, DEBUG: 'vite:deps', CI: '0', NO_COLOR: 'true' }, ); } diff --git a/tests/legacy-cli/e2e/tests/vite/ssr-new-dep-optimization.ts b/tests/legacy-cli/e2e/tests/vite/ssr-new-dep-optimization.ts index d2dfb8b554b9..be814b01bf89 100644 --- a/tests/legacy-cli/e2e/tests/vite/ssr-new-dep-optimization.ts +++ b/tests/legacy-cli/e2e/tests/vite/ssr-new-dep-optimization.ts @@ -31,7 +31,7 @@ export default async function () { 'ng', ['serve', '--port', port.toString()], /Application bundle generation complete/, - { CI: '0', NO_COLOR: 'true' }, + { ...process.env, CI: '0', NO_COLOR: 'true' }, ); await validateResponse('/', /Hello,/); diff --git a/tests/legacy-cli/e2e/tests/vite/ssr-no-server-entry-sub-path.ts b/tests/legacy-cli/e2e/tests/vite/ssr-no-server-entry-sub-path.ts index a55f48d0b39f..a4d4ac2cfc61 100644 --- a/tests/legacy-cli/e2e/tests/vite/ssr-no-server-entry-sub-path.ts +++ b/tests/legacy-cli/e2e/tests/vite/ssr-no-server-entry-sub-path.ts @@ -39,6 +39,7 @@ export default async function () { const port = await findFreePort(); await execAndWaitForOutputToMatch('ng', ['serve', '--port', `${port}`], /complete/, { + ...process.env, NO_COLOR: 'true', }); diff --git a/tests/legacy-cli/e2e/utils/BUILD.bazel b/tests/legacy-cli/e2e/utils/BUILD.bazel index 4d690d4bace2..1ae6a25a3144 100644 --- a/tests/legacy-cli/e2e/utils/BUILD.bazel +++ b/tests/legacy-cli/e2e/utils/BUILD.bazel @@ -9,6 +9,8 @@ ts_project( data = [ "//tests/legacy-cli/e2e/ng-snapshot", ], + # TODO(devversion): Remove + enable_runtime_rnjs_interop = False, deps = [ "//:node_modules/@types/jasmine", "//:node_modules/@types/node", diff --git a/tests/legacy-cli/e2e/utils/assets.ts b/tests/legacy-cli/e2e/utils/assets.ts index 51b09a7416a9..d421086c1b9e 100644 --- a/tests/legacy-cli/e2e/utils/assets.ts +++ b/tests/legacy-cli/e2e/utils/assets.ts @@ -8,7 +8,7 @@ import { installWorkspacePackages, setRegistry } from './packages'; import { useBuiltPackagesVersions } from './project'; export function assetDir(assetName: string) { - return join(__dirname, '../assets', assetName); + return join(__dirname, '../e2e/assets', assetName); } export function copyProjectAsset(assetName: string, to?: string) { diff --git a/tests/legacy-cli/e2e/utils/process.ts b/tests/legacy-cli/e2e/utils/process.ts index db279cf6cc8b..3cd6c77bc187 100644 --- a/tests/legacy-cli/e2e/utils/process.ts +++ b/tests/legacy-cli/e2e/utils/process.ts @@ -188,16 +188,16 @@ export function extractNpmEnv() { }, {}); } -function extractCIEnv(): NodeJS.ProcessEnv { +export function extractCIAndInfraEnv(): NodeJS.ProcessEnv { return Object.keys(process.env) .filter( (v) => v.startsWith('SAUCE_') || v === 'CI' || - v === 'CIRCLECI' || v === 'CHROME_BIN' || v === 'CHROME_PATH' || - v === 'CHROMEDRIVER_BIN', + v === 'CHROMEDRIVER_BIN' || + v.startsWith('JS_BINARY__'), ) .reduce((vars, n) => { vars[n] = process.env[n]; @@ -214,14 +214,16 @@ function extractNgEnv() { }, {}); } -export function waitForAnyProcessOutputToMatch( +export async function waitForAnyProcessOutputToMatch( match: RegExp, timeout = 30000, ): Promise { + let timeoutId: ReturnType | null = null; + // Race between _all_ processes, and the timeout. First one to resolve/reject wins. const timeoutPromise: Promise = new Promise((_resolve, reject) => { // Wait for 30 seconds and timeout. - setTimeout(() => { + timeoutId = setTimeout(() => { reject(new Error(`Waiting for ${match} timed out (timeout: ${timeout}msec)...`)); }, timeout); }); @@ -248,7 +250,11 @@ export function waitForAnyProcessOutputToMatch( }), ); - return Promise.race(matchPromises.concat([timeoutPromise])); + const matchingProcess = await Promise.race(matchPromises.concat([timeoutPromise])); + if (timeoutId !== null) { + clearTimeout(timeoutId); + } + return matchingProcess; } export async function killAllProcesses(signal = 'SIGTERM'): Promise { @@ -393,7 +399,7 @@ export function globalNpm(args: string[], env?: NodeJS.ProcessEnv) { ); } - return _exec({ silent: true, env }, process.execPath, [require.resolve('npm'), ...args]); + return _exec({ silent: true, env }, 'npm', args); } export function node(...args: string[]) { @@ -435,7 +441,7 @@ export async function launchTestProcess(entry: string, ...args: any[]): Promise< BAZEL_TARGET: process.env.BAZEL_TARGET, ...extractNpmEnv(), - ...extractCIEnv(), + ...extractCIAndInfraEnv(), ...extractNgEnv(), ...getGlobalVariablesEnv(), }; @@ -447,7 +453,12 @@ export async function launchTestProcess(entry: string, ...args: any[]): Promise< .filter((p) => p.startsWith(tempRoot) || p.startsWith(TEMP) || !p.includes('angular-cli')) .join(delimiter); - const testProcessArgs = [resolve(__dirname, 'test_process'), entry, ...args]; + const testProcessArgs = [ + // Note: `__dirname` is the bundle directory here. + resolve(__dirname, 'e2e/utils/test_process.js'), + entry, + ...args, + ]; return new Promise((resolve, reject) => { spawn(process.execPath, testProcessArgs, { diff --git a/tests/legacy-cli/e2e/utils/registry.ts b/tests/legacy-cli/e2e/utils/registry.ts index 8aad8a57eda2..1bd3084d4f48 100644 --- a/tests/legacy-cli/e2e/utils/registry.ts +++ b/tests/legacy-cli/e2e/utils/registry.ts @@ -14,7 +14,7 @@ export async function createNpmRegistry( const registryPath = await mktempd('angular-cli-e2e-registry-'); let configContent = await readFile( - join(__dirname, '../../', withAuthentication ? 'verdaccio_auth.yaml' : 'verdaccio.yaml'), + join(__dirname, '../', withAuthentication ? 'verdaccio_auth.yaml' : 'verdaccio.yaml'), ); configContent = configContent.replace(/\$\{HTTP_PORT\}/g, String(port)); configContent = configContent.replace(/\$\{HTTPS_PORT\}/g, String(httpsPort)); diff --git a/tests/legacy-cli/e2e/utils/test_process.ts b/tests/legacy-cli/e2e/utils/test_process.ts index dace5cb35b3b..af6bd61af365 100644 --- a/tests/legacy-cli/e2e/utils/test_process.ts +++ b/tests/legacy-cli/e2e/utils/test_process.ts @@ -1,3 +1,5 @@ +import { killAllProcesses } from './process'; + const testScript: string = process.argv[2]; const testModule = require(testScript); const testFunction: () => Promise | void = @@ -16,6 +18,6 @@ const testFunction: () => Promise | void = console.error('Test Process error', e); process.exitCode = -1; } finally { - process.exit(); + killAllProcesses().finally(() => process.exit()); } })(); diff --git a/tests/legacy-cli/e2e_runner.ts b/tests/legacy-cli/e2e_runner.ts index 0904e1ed67d9..01f5a6683c27 100644 --- a/tests/legacy-cli/e2e_runner.ts +++ b/tests/legacy-cli/e2e_runner.ts @@ -12,6 +12,7 @@ import { findFreePort } from './e2e/utils/network'; import { extractFile } from './e2e/utils/tar'; import { realpathSync } from 'node:fs'; import { PkgInfo } from './e2e/utils/packages'; +import { rm } from 'node:fs/promises'; Error.stackTraceLimit = Infinity; @@ -173,12 +174,16 @@ const tests = allTests.filter((name) => { }); }); +console.log(`Running with shard configuration:`); +console.log(`Total shards: ${nbShards}, current shard: ${shardId}`); + // Remove tests that are not part of this shard. const testsToRun = tests.filter((name, i) => shardId === null || i % nbShards == shardId); if (testsToRun.length === 0) { if (shardId !== null && tests.length <= shardId) { - console.log(`No tests to run on shard ${shardId}, exiting.`); + console.log(`No tests to run on shard ${shardId}, exiting`); + console.log(`Without sharding, there were ${tests.length} tests found.`); process.exit(0); } else { console.log(`No tests would be ran, aborting.`); @@ -328,7 +333,17 @@ async function runTest(absoluteName: string): Promise { process.chdir(join(getGlobalVariable('projects-root'), 'test-project')); await launchTestProcess(absoluteName); + await cleanTestProject(); +} + +async function cleanTestProject() { await gitClean(); + + const testProject = join(getGlobalVariable('projects-root'), 'test-project'); + + // Note: Dist directory is not cleared between tests, as `git clean` + // doesn't delete it. + await rm(join(testProject, 'dist/'), { recursive: true, force: true }); } function printHeader( diff --git a/tests/legacy-cli/rollup.config.mjs b/tests/legacy-cli/rollup.config.mjs new file mode 100644 index 000000000000..0fc2768c5057 --- /dev/null +++ b/tests/legacy-cli/rollup.config.mjs @@ -0,0 +1,45 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import glob from 'fast-glob'; + +const testFiles = [ + 'e2e_runner.js', + 'e2e/utils/test_process.js', + ...glob.sync('e2e/(initialize|setup|tests)/**/*.js'), +]; + +// Generate chunks to keep the original folder structure. +// Needed as we dynamically load these files. +const chunks = {}; +for (const file of testFiles) { + chunks[file.slice(0, -'.js'.length)] = file; +} + +export default { + input: chunks, + external: [], + plugins: [ + nodeResolve({ + preferBuiltins: true, + browser: false, + }), + json(), + commonjs({ + // Test runner uses dynamic requires, and those are fine. + // Rollup should not try to process them. + ignoreDynamicRequires: true, + }), + ], + output: { + dir: './runner_bundled_out', + exports: 'auto', + }, +}; diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel index 88cc63790940..ab42b524c5d5 100644 --- a/tools/BUILD.bazel +++ b/tools/BUILD.bazel @@ -1,9 +1,16 @@ -load("@aspect_bazel_lib//lib/private:tar_toolchain.bzl", "tar_toolchain") load("@bazel_skylib//rules:copy_file.bzl", "copy_file") load("//tools:defaults2.bzl", "js_binary") package(default_visibility = ["//visibility:public"]) +platform( + name = "windows_x64", + constraint_values = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +) + exports_files([ "package_json_release_filter.jq", ]) @@ -25,18 +32,6 @@ js_binary( entry_point = "quicktype_runner.js", ) -tar_toolchain( - name = "system_tar_exec", - binary = "tar_system.bat", -) - -toolchain( - name = "windows_tar_system_toolchain", - exec_compatible_with = ["@platforms//os:windows"], - toolchain = ":system_tar_exec", - toolchain_type = "@aspect_bazel_lib//lib:tar_toolchain_type", -) - # TODO(devversion): Improve this by potentially sharing this common block. copy_file( name = "copy_worker_js", diff --git a/tools/interop.bzl b/tools/interop.bzl index bc3a98fec08e..e4f76822e66f 100644 --- a/tools/interop.bzl +++ b/tools/interop.bzl @@ -35,6 +35,7 @@ ts_deps_interop = rule( attrs = { "deps": attr.label_list(providers = [DeclarationInfo], mandatory = True), }, + toolchains = ["@devinfra//bazel/git-toolchain:toolchain_type"], ) def _ts_project_module_impl(ctx): @@ -106,6 +107,7 @@ def ts_project( testonly = False, visibility = None, ignore_strict_deps = False, + enable_runtime_rnjs_interop = True, **kwargs): interop_deps = [] @@ -115,11 +117,12 @@ def ts_project( # dependencies so that we can forward and capture the module mappings for runtime # execution, with regards to first-party dependency linking. rjs_modules_to_rnjs = [] - for d in deps: - if d.startswith("//:node_modules/"): - rjs_modules_to_rnjs.append(d.replace("//:node_modules/", "@npm//")) - if d.endswith("_rjs"): - rjs_modules_to_rnjs.append(d.replace("_rjs", "")) + if enable_runtime_rnjs_interop: + for d in deps: + if d.startswith("//:node_modules/"): + rjs_modules_to_rnjs.append(d.replace("//:node_modules/", "@npm//")) + if d.endswith("_rjs"): + rjs_modules_to_rnjs.append(d.replace("_rjs", "")) if tsconfig == None: tsconfig = "//:test-tsconfig" if testonly else "//:build-tsconfig" diff --git a/tools/rules_ts_windows.patch b/tools/rules_ts_windows.patch deleted file mode 100644 index 7a7cd342b0a5..000000000000 --- a/tools/rules_ts_windows.patch +++ /dev/null @@ -1,30 +0,0 @@ -diff --git a/ts/private/ts_project.bzl b/ts/private/ts_project.bzl -index 367bba0..a112f8f 100644 ---- a/ts/private/ts_project.bzl -+++ b/ts/private/ts_project.bzl -@@ -93,25 +93,6 @@ def _ts_project_impl(ctx): - elif ctx.attr.supports_workers == 0: - supports_workers = False - -- host_is_windows = platform_utils.host_platform_is_windows() -- if host_is_windows and supports_workers: -- supports_workers = False -- -- # buildifier: disable=print -- print("""\ --WARNING: disabling ts_project workers which are not currently supported on Windows hosts. --See https://github.com/aspect-build/rules_ts/issues/228 for more details. --""") -- -- if ctx.attr.is_typescript_5_or_greater and supports_workers: -- supports_workers = False -- -- # buildifier: disable=print -- print("""\ --WARNING: disabling ts_project workers which are not currently supported with TS >= 5.0.0. --See https://github.com/aspect-build/rules_ts/issues/361 for more details. --""") -- - if supports_workers: - execution_requirements["supports-workers"] = "1" - execution_requirements["worker-key-mnemonic"] = "TsProject" diff --git a/tools/tar_system.bat b/tools/tar_system.bat deleted file mode 100755 index c80a06339804..000000000000 --- a/tools/tar_system.bat +++ /dev/null @@ -1,7 +0,0 @@ -@ECHO OFF - -if exist "C:\Program Files\Git\bin\bash.exe" ( - set "BASH=C:\Program Files\Git\bin\bash.exe" -) - -"%BASH%" -c "tar %*" diff --git a/yarn.lock b/yarn.lock index f27e98e79a70..876ae9d3ce70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,15 +55,15 @@ __metadata: languageName: node linkType: hard -"@angular/bazel@https://github.com/angular/bazel-builds.git#8faa06d66416ce78073ab59539ff03f5253b8d52": - version: 20.0.0-next.0+sha-51b8ff2 - resolution: "@angular/bazel@https://github.com/angular/bazel-builds.git#commit=8faa06d66416ce78073ab59539ff03f5253b8d52" +"@angular/bazel@https://github.com/angular/bazel-builds.git#58e1a344eed2dfea489cd290a4b4a963f7e3ac65": + version: 19.3.0-next.0+sha-8657a0e + resolution: "@angular/bazel@https://github.com/angular/bazel-builds.git#commit=58e1a344eed2dfea489cd290a4b4a963f7e3ac65" dependencies: "@microsoft/api-extractor": "npm:^7.24.2" magic-string: "npm:^0.30.0" tslib: "npm:^2.3.0" peerDependencies: - "@angular/compiler-cli": 20.0.0-next.0+sha-51b8ff2 + "@angular/compiler-cli": 19.3.0-next.0+sha-8657a0e "@bazel/concatjs": ^5.3.0 "@bazel/worker": ^5.3.0 "@rollup/plugin-commonjs": ^28.0.0 @@ -80,7 +80,7 @@ __metadata: packager: ./src/ng_package/packager.mjs types_bundler: ./src/types_bundle/index.mjs xi18n: ./src/ngc-wrapped/extract_i18n.mjs - checksum: 10c0/c582964a0172c4a1f62964750b0f70766bbcac01bf1be85ca5b4dac623ef1fdaa92c38ce49ca08e0b0e1b97d8240faa42f06c6aa2d85810b0a3657e15b4b543e + checksum: 10c0/aa554ae9966a4a2a4057214c8811fd5e9fa8b27dd20af05043b181a6daadc6ae66c33a0c2acb23d7c355d1d8f368f9e7ee76f0155fb091aaedd3f15494e5828e languageName: node linkType: hard @@ -94,9 +94,9 @@ __metadata: languageName: node linkType: hard -"@angular/build-tooling@https://github.com/angular/dev-infra-private-build-tooling-builds.git#a6a996a69cfc03b3fbe538f11dd24b7bc4b30592": - version: 0.0.0-b015169b635123c1ab9084f604e36b6342eac171 - resolution: "@angular/build-tooling@https://github.com/angular/dev-infra-private-build-tooling-builds.git#commit=a6a996a69cfc03b3fbe538f11dd24b7bc4b30592" +"@angular/build-tooling@https://github.com/angular/dev-infra-private-build-tooling-builds.git#d4727212a9d0f7eb63ae3116d73c769d9bd0bdc1": + version: 0.0.0-74aabba6d202918280dafe92f87f9c154476fa86 + resolution: "@angular/build-tooling@https://github.com/angular/dev-infra-private-build-tooling-builds.git#commit=d4727212a9d0f7eb63ae3116d73c769d9bd0bdc1" dependencies: "@angular/benchpress": "npm:0.3.0" "@angular/build": "npm:19.2.0-next.1" @@ -133,7 +133,7 @@ __metadata: dependenciesMeta: re2: built: false - checksum: 10c0/e700e9ff2f8c55ad2d4983489271c0cef1d8b27d92a8a9d51ebcdad636f9f7b9f306d3960b6b8fd650e8376e3200ec9745bc38708c6cc7a23454afeda0f50aa6 + checksum: 10c0/9c7dc4060ae176d30fdd8763cd3942a6e9c53a49e43dc75e3a46f09a556588699091f2a5e813732c14ea43e01bca9b79fa3dceb595e8a0f4fcb8962df58209ad languageName: node linkType: hard @@ -299,8 +299,8 @@ __metadata: dependencies: "@ampproject/remapping": "npm:2.3.0" "@angular/animations": "npm:19.2.0" - "@angular/bazel": "https://github.com/angular/bazel-builds.git#8faa06d66416ce78073ab59539ff03f5253b8d52" - "@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#a6a996a69cfc03b3fbe538f11dd24b7bc4b30592" + "@angular/bazel": "https://github.com/angular/bazel-builds.git#58e1a344eed2dfea489cd290a4b4a963f7e3ac65" + "@angular/build-tooling": "https://github.com/angular/dev-infra-private-build-tooling-builds.git#d4727212a9d0f7eb63ae3116d73c769d9bd0bdc1" "@angular/cdk": "npm:19.2.1" "@angular/common": "npm:19.2.0" "@angular/compiler": "npm:19.2.0" @@ -327,7 +327,6 @@ __metadata: "@babel/runtime": "npm:7.26.9" "@bazel/bazelisk": "npm:1.25.0" "@bazel/buildifier": "npm:8.0.3" - "@bazel/runfiles": "npm:^6.0.0" "@discoveryjs/json-ext": "npm:0.6.3" "@eslint/compat": "npm:1.2.7" "@eslint/eslintrc": "npm:3.3.0" @@ -337,6 +336,7 @@ __metadata: "@listr2/prompt-adapter-inquirer": "npm:2.0.18" "@rollup/plugin-alias": "npm:^5.1.1" "@rollup/plugin-commonjs": "npm:^28.0.0" + "@rollup/plugin-json": "npm:^6.1.0" "@rollup/plugin-node-resolve": "npm:^13.0.5" "@stylistic/eslint-plugin": "npm:^4.0.0" "@types/babel__core": "npm:7.20.5" @@ -1859,7 +1859,7 @@ __metadata: languageName: node linkType: hard -"@bazel/runfiles@npm:^6.0.0, @bazel/runfiles@npm:^6.3.1": +"@bazel/runfiles@npm:^6.3.1": version: 6.3.1 resolution: "@bazel/runfiles@npm:6.3.1" checksum: 10c0/7b542dcff9e917cc521520db137bd4f4a478796693700e2ec2c27f4beede800c9f4987e20c6b965d81000638f63549160780aea51eca2f0d0275be76fdc5e49f