Skip to content

chore: replace tsc/jest npm scripts with Bazel — hermetic builds, per-spec caching, GCS remote cache, and full CI migration#803

Draft
ashokn1 wants to merge 2 commits intomainfrom
chore/convert-tests-to-bazel
Draft

chore: replace tsc/jest npm scripts with Bazel — hermetic builds, per-spec caching, GCS remote cache, and full CI migration#803
ashokn1 wants to merge 2 commits intomainfrom
chore/convert-tests-to-bazel

Conversation

@ashokn1
Copy link
Copy Markdown

@ashokn1 ashokn1 commented Apr 25, 2026

Background

This PR implements the plan in .claude/plans/fluffy-hopping-bentley.md: migrate snyk-docker-plugin from plain tsc + Jest (via npm scripts) to Bazel, using Bzlmod (MODULE.bazel), aspect_rules_js + aspect_rules_ts + aspect_rules_jest, and a GCS bucket for remote cache shared across dev machines and CI.

Goals: hermetic builds, per-spec-file test result caching, and a single bazel test //test/... that replaces the scattered npm/jest invocations.


What changed

New Bazel workspace files

File Purpose
.bazelversion Pins Bazel 7.4.1
MODULE.bazel + MODULE.bazel.lock Bzlmod deps: aspect_rules_js, aspect_rules_ts, aspect_rules_jest, aspect_bazel_lib
.bazelrc Enables Bzlmod; configures GCS remote cache + test result caching; try-import .bazelrc.local for per-machine overrides
.bazelignore Excludes node_modules, dist, .aspect from Bazel
pnpm-lock.yaml Required by npm_translate_lock in rules_js
BUILD.bazel (root) npm_link_all_packages; copy_to_bin for tsconfig.json and jest.config.js
lib/BUILD.bazel Single ts_project for the entire library with all production deps declared
test/BUILD.bazel All test targets (see below)

Test targets in test/BUILD.bazel

Unit / lib tests (test/lib/, test/unit/) — one jest_test per spec file, untagged, included in bazel test //test/.... Changing one spec reruns only that target.

System tests (test/system/) — one jest_test per spec file, tagged ["manual", "system"]:

  • manual keeps them out of the default bazel test //test/... wildcard (they require Docker)
  • system allows the npm script to enumerate them via bazel query 'attr(tags, "system", //test/...)' — necessary because --test_tag_filters=manual cannot select targets that //test/... already excludes; --build_manual_tests only affects the build graph, not test collection

Windows tests (test/windows/) — one jest_test per spec file, tagged ["windows"], using a separate copy_to_bin'd windows/jest.config.js. The config was updated to compute rootDir from JS_BINARY__RUNFILES (Bazel) or path.resolve(__dirname, '../..') (plain jest), making it work in both contexts.

Fixes discovered during conversion

  • test/system/dockerfile-analysis/Dockerfile* files were not included in any Bazel data glob; fixed by adding "system/**" (excluding .ts and .snap) to _SHARED_DATA
  • Each jest_test target's snapshot glob is scoped to its own __snapshots__/ file to avoid --ci failing on "obsolete snapshots" from other targets
  • Go regression CI job (build_and_test_latest_go_binary) was using npx jest test/unit/go-binaries.spec.ts directly; changed to bazel test //test:unit_go-binaries

package.json script changes

Script Before After
build tsc bazel build //lib:lib
test jest --ci ... bazel test //test/... --test_tag_filters=-manual,-windows
test:unit jest --ci ... --testPathPattern='test/(lib|unit)/' bazel test //test/... --test_tag_filters=-manual,-windows
test:system jest --ci ... --testPathPattern='test/system/' bazel test $(bazel query 'attr(tags, "system", //test/...)')
test-jest-windows jest --ci --config test/windows/jest.config.js bazel test //test/... --test_tag_filters=windows
prepare npm run build bazel build //lib:lib && cp -rL bazel-bin/lib dist

CI (.circleci/config.yml) changes

  • New install_bazelisk command (Linux) and install_bazelisk_windows command (Windows, downloads .exe into ~/bin, adds to $BASH_ENV)
  • New setup_gcs_cache command: writes $GCLOUD_SERVICE_KEY to /tmp/gcs-bazel-key.json and appends --google_credentials to .bazelrc.local
  • install, build, release jobs: add install_bazelisk + setup_gcs_cache; replace npm run build with bazel build //lib:lib
  • test_unit job: add install_bazelisk + setup_gcs_cache; replace npm run test:unit with bazel test //test/... --test_tag_filters=-manual,-windows
  • test_system job: add install_bazelisk + setup_gcs_cache; replace npm run test:system with bazel test $(bazel query ...) --test_output=errors
  • test_jest_windows_with_docker / test_jest_windows_no_docker: add install_bazelisk_windows; replace npm run test-jest-windows with bazel test //test/... --test_tag_filters=windows
  • build_and_test_latest_go_binary: add install_bazelisk; replace npx jest with bazel test //test:unit_go-binaries

Other file changes

  • jest.config.js: added rootDir computation from JS_BINARY__RUNFILES so Jest finds test files from the workspace root when running under Bazel
  • tsconfig.json: commented out "outDir": "./dist" — Bazel's ts_project controls output location; outDir in tsconfig would conflict
  • .gitignore: added bazel-*, .aspect, .bazelrc.local

GCS bucket setup (prerequisite, done outside this PR)

  1. Create gs://snyk-docker-plugin-bazel-cache
  2. Service account with Storage Object Admin on the bucket
  3. JSON key stored as CircleCI env var GCLOUD_SERVICE_KEY
  4. Local dev: gcloud auth application-default login (picked up by --google_default_credentials=true)

Test plan

  • npm run test:unit — 47 unit/lib targets, all pass
  • npm run test:system — 50 system targets run; passes requiring Docker pass in CI (setup_remote_docker), fixture-only tests pass locally
  • npm run test-jest-windows — 5 windows targets, run on Windows CI executor
  • npm run buildbazel build //lib:lib compiles library

🤖 Generated with Claude Code

…ndows tests)

- Add Bazel workspace files: MODULE.bazel, .bazelrc, .bazelversion, .bazelignore
- Add BUILD.bazel targets for lib/ (ts_project) and all test suites
- Unit/lib tests: one jest_test target per spec file, cached independently
- System tests: tagged ["manual","system"], run via bazel query to bypass
  Bazel's wildcard exclusion of manual targets
- Windows tests: tagged ["windows"], own jest config with Bazel runfiles
  rootDir handling; CI jobs install Bazelisk (Windows) and use bazel test
- Fix system/dockerfile-analysis Dockerfile fixtures missing from test data
- Fix go regression CI job: npx jest -> bazel test //test:unit_go-binaries
- Update package.json scripts and circleci config to use Bazel throughout
- Add pnpm-lock.yaml required by rules_js npm_translate_lock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ashokn1 ashokn1 requested review from a team as code owners April 25, 2026 22:45
@ashokn1 ashokn1 requested a review from mtstanley-snyk April 25, 2026 22:45
@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Invalid Placeholder 🟠 [major]

The --remote_cache URL contains the placeholder <YOUR_BUCKET>. Bazel will attempt to connect to this literal URL unless overridden in .bazelrc.local. This will cause 'invalid URL' or DNS resolution errors for any contributor who does not manually create a local config file immediately after cloning.

build --remote_cache=https://storage.googleapis.com/<YOUR_BUCKET>
Broken Package Structure 🟠 [major]

The prepare script copies bazel-bin/lib into dist. Because the ts_project in lib/BUILD.bazel is also named lib, the compiled files will likely be nested inside an extra lib/ directory or the copy command will include the directory itself. This will result in a published package where main: dist/index.js (defined at line 11) is missing because the file is actually at dist/lib/index.js.

"prepare": "bazel build //lib:lib && cp -rL bazel-bin/lib dist"
📚 Repository Context Analyzed

This review considered 17 relevant code sections from 7 files (average relevance: 0.65)

@ashokn1 ashokn1 changed the title chore: migrate build and all test scripts from npm/jest to Bazel chore: replace tsc/jest npm scripts with Bazel — hermetic builds, per-spec caching, GCS remote cache, and full CI migration Apr 26, 2026
@ashokn1 ashokn1 marked this pull request as draft April 26, 2026 01:59
- .bazelrc: remove <YOUR_BUCKET> placeholder; remote_cache URL now injected
  by setup_gcs_cache via $BAZEL_REMOTE_CACHE_BUCKET env var
- .circleci/config.yml: pin Bazelisk to v1.20.0 (not "latest"); secure GCS
  key with mktemp+chmod 600; add $BAZEL_REMOTE_CACHE_BUCKET to
  setup_gcs_cache; split bazel query and bazel test into separate steps in
  test_system; remove install_bazelisk/setup_gcs_cache from install job
  (not persisted to workspace); add setup_gcs_cache to Windows and Go jobs
- MODULE.bazel: sync ts_version from 5.4.5 to 5.4.2 to match package.json
- package.json: rm -rf dist before cp in prepare to prevent stale nesting
- test/BUILD.bazel: document that Windows snapshots must not be updated on Linux

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ashokn1 ashokn1 removed the request for review from mtstanley-snyk April 26, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant