Skip to content

feat: add bazel-deps-query config to dynamically resolve additional paths#13

Open
helenyugithub wants to merge 5 commits intomainfrom
devin/1772522815-bazel-deps-query
Open

feat: add bazel-deps-query config to dynamically resolve additional paths#13
helenyugithub wants to merge 5 commits intomainfrom
devin/1772522815-bazel-deps-query

Conversation

@helenyugithub
Copy link

@helenyugithub helenyugithub commented Mar 3, 2026

Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> 🦕


Summary

Adds a new bazel-deps-query configuration option that allows a package to dynamically resolve its dependency paths by running a bazel query. The resolved paths are merged with any static additional-paths and used to determine which commits should trigger a release for the package.

Example config (recommended)

{
  "packages": {
    "apps/my-app": {
      "bazel-deps-query": true,
      "additional-paths": ["libs/manually-specified-lib"]
    }
  }
}

Example config (custom query)

You can also provide either:

  • a query expression (e.g. "deps(//combined-service)"), or
  • a full command (e.g. "bazel query 'deps(//combined-service)'"), from which the query expression will be extracted.
{
  "packages": {
    "apps/my-app": {
      "bazel-deps-query": "deps(//combined-service)"
    }
  }
}

Changes

  • src/util/bazel-query.ts (new):
    • parseBazelQueryOutput() parses bazel query output into local package paths (filters @... externals, dedupes, optional excludePath).
    • resolveBazelQuery() supports true (auto-construct default) or a string (expression or bazel query ...).
    • runBazelQuery() executes via execFileSync('bazel', ['query', <expr>]) (no shell) with a 2-minute timeout.
  • schemas/config.json: Added bazel-deps-query schema definition as oneOf: [boolean, string].
  • src/manifest.ts:
    • Added bazelDepsQuery?: boolean | string to config types (ReleaserConfig, ReleaserConfigJson, extractReleaserConfig, mergeReleaserConfig).
    • Updated buildPullRequests() to resolve bazel-deps-query per package and merge results with static additionalPaths before CommitSplit.
  • Tests:
    • Unit tests for parseBazelQueryOutput and resolveBazelQuery
    • Manifest tests for config reading + integration tests verifying releases are triggered by bazel-resolved dependency paths and merged with static additionalPaths

Updates since last revision

  • bazel-deps-query can be set to true so the query is auto-constructed from the package path.
  • Switched query execution from execSync (shell) to execFileSync (no shell) to mitigate command injection concerns from config-provided strings.

Human Review Checklist

  • Default query correctness: when bazel-deps-query: true, we run deps(//<packagePath>). Confirm //<packagePath> is a valid label pattern for your Bazel repo layout (package path vs. target naming conventions).
  • Runtime environment: ensure bazel is available wherever release-please runs (local/CI/container) and that the 2-minute timeout is acceptable for your dependency graphs.
  • Failure behavior: if bazel query fails (bazel missing, invalid label, query error), release-please will throw and abort the run. Confirm fail-hard is desired.
  • Output parsing assumptions: confirm parseBazelQueryOutput matches the query output format you expect (target shapes, root targets, external deps filtering).

Link to Devin session: https://app.devin.ai/sessions/ccaef19250184b538e9c3f320b16a148
Requested by: @helenyugithub

…aths

Co-Authored-By: helen <helen@helenyu.tech>
@devin-ai-integration
Copy link

Original prompt from helen
https://github.com/nominal-io/release-please/pull/5 in this pr, we added additional-paths config to release-please. i would like to add an option to calculate additional-paths dynamically by running a bazel query (like bazel query 'deps(//combined-service)') to get all dependencies 

@devin-ai-integration
Copy link

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@greptile-apps
Copy link

greptile-apps bot commented Mar 3, 2026

Greptile Summary

Adds a new bazel-deps-query configuration option that dynamically resolves package dependency paths by running bazel query, merging the results with any static additional-paths before commit splitting. This enables monorepo packages to automatically detect which dependency changes should trigger a release, without manually maintaining path lists.

  • New src/util/bazel-query.ts utility with parseBazelQueryOutput, resolveBazelQuery, and runBazelQuery functions; uses execFileSync (no shell) to mitigate command injection
  • Config supports true (auto-constructs deps(//<packagePath>)) or a custom query expression string
  • Schema updated with oneOf [boolean, string] and propagation enabled from top-level config
  • buildPullRequests() in manifest.ts resolves bazel queries per package and merges/deduplicates with static paths before CommitSplit
  • Good test coverage: unit tests for parsing/resolving, integration tests for config reading and end-to-end release triggering

Confidence Score: 4/5

  • This PR is safe to merge with minor style considerations; no correctness or security issues found.
  • The implementation is well-structured with proper use of execFileSync (no shell) addressing earlier command injection concerns, good test coverage for both unit and integration scenarios, and clean integration into the existing manifest pipeline. Score reduced from 5 due to: synchronous execFileSync blocking the event loop (scalability concern for large monorepos), and duplicate info-level logging between runBazelQuery and buildPullRequests.
  • src/util/bazel-query.ts — synchronous execFileSync may block the event loop for extended periods in monorepos with many packages.

Important Files Changed

Filename Overview
src/util/bazel-query.ts New utility module with well-structured functions for parsing bazel query output, resolving config values, and executing queries. Uses execFileSync (no shell) for safety. Minor concern: synchronous blocking could slow CI for large monorepos.
src/manifest.ts Integrates bazel-deps-query into the build pipeline — resolves queries per package and merges with static additional paths before CommitSplit. Has minor duplicate logging with runBazelQuery.
schemas/config.json Adds bazel-deps-query schema definition as oneOf [boolean, string] and enables propagation from top-level config to packages.
test/util/bazel-query.ts Good unit test coverage for parseBazelQueryOutput and resolveBazelQuery. Missing test for resolveBazelQuery(false, ...) and edge cases like whitespace-only strings.
test/manifest.ts Adds integration tests verifying config reading, bazel-resolved dependency path triggering releases, and merging with static additional paths. Proper use of sinon stubs for the bazel module.
test/fixtures/manifest/config/bazel-deps-query.json Simple fixture config for testing bazel-deps-query: true with a single package.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[buildPullRequests] --> B{For each package}
    B --> C{bazelDepsQuery set?}
    C -- Yes --> D[resolveBazelQuery]
    C -- No --> G[Use static additionalPaths only]
    D -- "true" --> E["buildBazelQueryExpression\ndeps(//packagePath)"]
    D -- "string" --> F[Parse expression from config]
    E --> H[runBazelQuery\nexecFileSync bazel query expr]
    F --> H
    H --> I[parseBazelQueryOutput\nFilter externals, dedupe, exclude self]
    I --> J[Merge bazel paths + static additionalPaths]
    G --> J
    J --> K[Deduplicate merged paths]
    K --> L[CommitSplit with resolved paths]
    L --> M[Continue release flow]
Loading

Last reviewed commit: 54b3a81

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

logger?.info(`Running bazel deps query: ${query}`);

try {
const output = execSync(query, {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arbitrary command execution via execSync

The query parameter is passed directly to execSync, which spawns a shell and executes it as-is. Since this value comes from release-please-config.json, anyone with write access to the config file can execute arbitrary commands on the CI runner — not just bazel queries.

For example, a config value like "rm -rf / && bazel query 'deps(//app)'" would be executed. Consider either:

  1. Validating that the command starts with bazel query (e.g., /^bazel\s+query\s/), or
  2. Splitting the command and using execFileSync('bazel', ['query', ...args]) instead, which avoids shell interpretation entirely.

This was acknowledged in the PR description's review checklist, but adding a basic prefix validation would be a low-cost safeguard.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/util/bazel-query.ts
Line: 96

Comment:
**Arbitrary command execution via `execSync`**

The `query` parameter is passed directly to `execSync`, which spawns a shell and executes it as-is. Since this value comes from `release-please-config.json`, anyone with write access to the config file can execute arbitrary commands on the CI runner — not just bazel queries.

For example, a config value like `"rm -rf / && bazel query 'deps(//app)'"` would be executed. Consider either:
1. Validating that the command starts with `bazel query` (e.g., `/^bazel\s+query\s/`), or
2. Splitting the command and using `execFileSync('bazel', ['query', ...args])` instead, which avoids shell interpretation entirely.

This was acknowledged in the PR description's review checklist, but adding a basic prefix validation would be a low-cost safeguard.

How can I resolve this? If you propose a fix, please make it concise.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 8cdd609:

  • Switched to execFileSync('bazel', ['query', ...]) (no shell) to mitigate command-injection risk.
  • bazel-deps-query now supports true (auto-constructs default expression from package path) or a string (either a query expression like deps(//...) or a full bazel query '...' command, from which we extract the expression before execution).

}
},
"bazel-deps-query": {
"description": "A bazel query command to run to dynamically resolve additional dependency paths. The output is parsed to extract local package paths which are merged with additional-paths. Example: \"bazel query 'deps(//my-service)'\"",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devin you should be able to construct the query based on the package name

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! I'll update it so the query is auto-constructed from the package path. I'll make bazel-deps-query accept either:

  • true (boolean) → auto-constructs bazel query 'deps(//package-path)'
  • a string → uses it as a custom query (for advanced use cases)

So the typical config becomes just:

{
  "packages": {
    "apps/my-app": {
      "bazel-deps-query": true
    }
  }
}

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

Co-Authored-By: helen <helen@helenyu.tech>
Comment on lines +119 to +125
// If the expression is quoted, strip surrounding quotes.
if (
(expr.startsWith('"') && expr.endsWith('"')) ||
(expr.startsWith("'") && expr.endsWith("'"))
) {
expr = expr.slice(1, -1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quote stripping can corrupt multi-part expressions

The quote-stripping logic checks if the expression starts and ends with the same quote character, then strips both. This will silently corrupt valid multi-argument bazel query inputs.

For example, bazel query 'deps(//a)' union 'deps(//b)' after prefix removal yields 'deps(//a)' union 'deps(//b)'. This starts with ' and ends with ', so the code strips them, producing deps(//a)' union 'deps(//b) — a broken expression.

A safer approach would be to only strip quotes when the inner content contains no unescaped quotes of the same type:

Suggested change
// If the expression is quoted, strip surrounding quotes.
if (
(expr.startsWith('"') && expr.endsWith('"')) ||
(expr.startsWith("'") && expr.endsWith("'"))
) {
expr = expr.slice(1, -1);
}
if (
(expr.startsWith('"') && expr.endsWith('"') && !expr.slice(1, -1).includes('"')) ||
(expr.startsWith("'") && expr.endsWith("'") && !expr.slice(1, -1).includes("'"))
) {
expr = expr.slice(1, -1);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/util/bazel-query.ts
Line: 119-125

Comment:
**Quote stripping can corrupt multi-part expressions**

The quote-stripping logic checks if the expression starts and ends with the same quote character, then strips both. This will silently corrupt valid multi-argument bazel query inputs.

For example, `bazel query 'deps(//a)' union 'deps(//b)'` after prefix removal yields `'deps(//a)' union 'deps(//b)'`. This starts with `'` and ends with `'`, so the code strips them, producing `deps(//a)' union 'deps(//b)` — a broken expression.

A safer approach would be to only strip quotes when the inner content contains no unescaped quotes of the same type:

```suggestion
    if (
      (expr.startsWith('"') && expr.endsWith('"') && !expr.slice(1, -1).includes('"')) ||
      (expr.startsWith("'") && expr.endsWith("'") && !expr.slice(1, -1).includes("'"))
    ) {
      expr = expr.slice(1, -1);
    }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +681 to +683
if (config.bazelDepsQuery) {
const queryExpression = resolveBazelQuery(config.bazelDepsQuery, path);
bazelPaths = runBazelQuery(queryExpression, path, this.logger);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty query expression passed to runBazelQuery

When config.bazelDepsQuery is a non-empty string (truthy), resolveBazelQuery can still return an empty string — for example, if the config value is "bazel query " (just the prefix with trailing whitespace). In that case, runBazelQuery("") would be called, executing bazel query "" which will fail with a confusing error.

Consider guarding against an empty resolved expression:

Suggested change
if (config.bazelDepsQuery) {
const queryExpression = resolveBazelQuery(config.bazelDepsQuery, path);
bazelPaths = runBazelQuery(queryExpression, path, this.logger);
if (config.bazelDepsQuery) {
const queryExpression = resolveBazelQuery(config.bazelDepsQuery, path);
if (queryExpression) {
bazelPaths = runBazelQuery(queryExpression, path, this.logger);
}
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/manifest.ts
Line: 681-683

Comment:
**Empty query expression passed to `runBazelQuery`**

When `config.bazelDepsQuery` is a non-empty string (truthy), `resolveBazelQuery` can still return an empty string — for example, if the config value is `"bazel query "` (just the prefix with trailing whitespace). In that case, `runBazelQuery("")` would be called, executing `bazel query ""` which will fail with a confusing error.

Consider guarding against an empty resolved expression:

```suggestion
      if (config.bazelDepsQuery) {
        const queryExpression = resolveBazelQuery(config.bazelDepsQuery, path);
        if (queryExpression) {
          bazelPaths = runBazelQuery(queryExpression, path, this.logger);
        }
      }
```

How can I resolve this? If you propose a fix, please make it concise.

let bazelPaths: string[] = [];
if (config.bazelDepsQuery) {
const queryExpression = resolveBazelQuery(config.bazelDepsQuery, path);
bazelPaths = runBazelQuery(queryExpression, path, this.logger);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devin can you debug log the bazel paths

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devin can you debug log the bazel paths

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added debug logging for the resolved paths in 555a901:

  • Manifest.buildPullRequests() now logs bazel-deps-query resolved paths for <path>: [...] via this.logger.debug(...).
  • runBazelQuery() also logs start/results via logger.debug(...).

So you should see the resolved paths when debug logging is enabled.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devin actually switch all those logs to info level

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 54b3a81: switched the bazel-deps-query logs in both Manifest.buildPullRequests() and runBazelQuery() to info level.

devin-ai-integration bot and others added 3 commits March 3, 2026 18:16
Co-Authored-By: helen <helen@helenyu.tech>
Co-Authored-By: helen <helen@helenyu.tech>
Co-Authored-By: helen <helen@helenyu.tech>
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

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