Skip to content

feat: log output to stderr on deploy --json --verbose#8090

Merged
eduardoboucas merged 4 commits intomainfrom
feat/cli-deploy-verbose
Mar 25, 2026
Merged

feat: log output to stderr on deploy --json --verbose#8090
eduardoboucas merged 4 commits intomainfrom
feat/cli-deploy-verbose

Conversation

@eduardoboucas
Copy link
Member

In #7656 we introduced --verbose which pipes the build output to stderr when --json is used and the build fails. This PR extends that to do the same even if the build succeeds.

@eduardoboucas eduardoboucas requested a review from a team as a code owner March 25, 2026 16:11
@github-actions
Copy link

github-actions bot commented Mar 25, 2026

📊 Benchmark results

Comparing with d77ff79

  • Dependency count: 1,059 (no change)
  • Package size: 354 MB (no change)
  • Number of ts-expect-error directives: 356 (no change)

serhalp
serhalp previously approved these changes Mar 25, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • When using --json with --verbose during deploy, build logs are forwarded to stderr so stdout remains valid JSON for tooling.
  • Tests
    • Added an integration test that runs the CLI with --json and --verbose to verify JSON stdout and preserved build output on stderr.

Walkthrough

The deploy command's build handler now, when both --verbose and --json are enabled, writes the captured build logs.stdout and logs.stderr to process.stderr after runBuild(resolvedOptions) returns and before existing non-zero exit handling. An integration test was added that runs the CLI via execa with --json --verbose, asserting stdout remains JSON-parseable and stderr contains the build command's output (including preserved stdout/stderr lines).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: logging output to stderr when deploy is called with --json and --verbose flags.
Description check ✅ Passed The description is directly related to the changeset, explaining the context and rationale for extending --verbose behavior to successful builds.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-deploy-verbose

Comment @coderabbitai help to get the list of available commands and usage tips.

aitchiss
aitchiss previously approved these changes Mar 25, 2026
@eduardoboucas eduardoboucas enabled auto-merge (squash) March 25, 2026 16:21
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/commands/deploy/deploy.ts (1)

720-722: Remove explanatory comments to comply with coding guidelines.

As per coding guidelines, "Never write comments on what the code does; make the code clean and self-explanatory instead" for TypeScript files. The function name and condition (options.verbose && options.json) make the intent clear.

✏️ Proposed fix to remove comments
   const { configMutations, exitCode, newConfig, logs } = await runBuild(resolvedOptions)

-  // When --verbose is used with --json, pipe the build's stdout and stderr
-  // to the process stderr so that callers can see the full build output
-  // while still getting clean JSON on stdout.
   if (options.verbose && options.json && logs) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/deploy/deploy.ts` around lines 720 - 722, Remove the explanatory
three-line comment block that precedes the conditional handling for combined
verbose+json output; locate the check using options.verbose && options.json (in
the deploy command code) and delete the comment lines so only self-explanatory
code remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/deploy/deploy.ts`:
- Around line 723-730: The conditional writes to stderr should use optional
chaining for the logs properties to match the rest of the function: replace
checks of logs.stdout.length and logs.stderr.length with logs?.stdout?.length
and logs?.stderr?.length (within the block where options.verbose && options.json
&& logs is true) so that accessing potentially undefined stdout/stderr is safe
and consistent with existing uses of logs?.stdout and logs?.stderr.

---

Nitpick comments:
In `@src/commands/deploy/deploy.ts`:
- Around line 720-722: Remove the explanatory three-line comment block that
precedes the conditional handling for combined verbose+json output; locate the
check using options.verbose && options.json (in the deploy command code) and
delete the comment lines so only self-explanatory code remains.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3421c9eb-175d-42db-a3a0-ec2716ba7086

📥 Commits

Reviewing files that changed from the base of the PR and between f35bd4b and 883a645.

📒 Files selected for processing (2)
  • src/commands/deploy/deploy.ts
  • tests/integration/commands/deploy/deploy.test.ts

Comment on lines +723 to +730
if (options.verbose && options.json && logs) {
if (logs.stdout.length) {
process.stderr.write(logs.stdout.join(''))
}
if (logs.stderr.length) {
process.stderr.write(logs.stderr.join(''))
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

What is the Logs type structure in @netlify/build package?

💡 Result:

The @netlify/build package does not expose a publicly documented "Logs" type structure in its API. The package is the core Netlify Build engine that orchestrates build commands, plugins, and function bundling during deploys. From source code analysis in the netlify/build GitHub monorepo (packages/build), logging is handled internally through modules like src/log/ (inferred from PRs mentioning log verbosity, system logs, and error logging). Build plugins receive logging capabilities via the utils object in event handlers, where console.log outputs appear in build logs, and utils.status.show displays deploy summary info. No explicit "Logs" interface or type is exported for plugin authors in the TypeScript definitions. npm package docs confirm no README details on types, and official Netlify docs focus on plugin utils for logging rather than a dedicated Logs structure. Recent PRs (e.g., #5643, #5679, #6156) discuss internal log handling like output flushing, system logs for plugin failures, and verbosity reduction, but no public Logs type. If referring to internal types, error types were typed in PR #5374 (chore/build-error-types), but no Logs-specific structure found in public API.

Citations:


🏁 Script executed:

# First, let's check if the file exists and its size
wc -l src/commands/deploy/deploy.ts

Repository: netlify/cli

Length of output: 89


🏁 Script executed:

# Read the specific section mentioned in the review (lines 715-745 for context)
sed -n '715,745p' src/commands/deploy/deploy.ts | cat -n

Repository: netlify/cli

Length of output: 1149


🏁 Script executed:

# Look for the Logs type import and definition
rg "import.*Logs|type Logs|interface Logs" src/commands/deploy/deploy.ts -A 3 -B 3

Repository: netlify/cli

Length of output: 37


🏁 Script executed:

# Check what `@netlify/build` exports for Logs type
rg "export.*Logs" --type ts --type tsx

Repository: netlify/cli

Length of output: 82


🏁 Script executed:

# Search for runBuild function and Logs type definition
rg "runBuild|type Logs|interface Logs" src/commands/deploy/deploy.ts -B 2 -A 2

Repository: netlify/cli

Length of output: 1188


🏁 Script executed:

# Look for imports in the file
head -50 src/commands/deploy/deploy.ts | grep -E "^import|^from"

Repository: netlify/cli

Length of output: 1745


🏁 Script executed:

# Search for Logs type across the codebase
fd -e ts -e tsx . src | xargs rg "type Logs|interface Logs" -A 5 | head -50

Repository: netlify/cli

Length of output: 471


🏁 Script executed:

# Now let's check the Logs type definition in src/lib/build.ts
sed -n '1,30p' src/lib/build.ts | cat -n

Repository: netlify/cli

Length of output: 1313


🏁 Script executed:

# Check the actual Logs type from `@netlify/build` by examining how it's used elsewhere
rg "logs\." src/lib/build.ts -B 2 -A 2

Repository: netlify/cli

Length of output: 37


🏁 Script executed:

# Look at the runBuild return type
rg "function runBuild|export.*runBuild" src/lib/build.ts -A 10

Repository: netlify/cli

Length of output: 430


🏁 Script executed:

# Let's get more context on the Logs type usage and understand the pattern better
rg "logs\?" src/commands/deploy/deploy.ts -B 1 -A 1

Repository: netlify/cli

Length of output: 229


🏁 Script executed:

# Check if there's type definition or any hints about stdout/stderr optionality
rg "stdout|stderr" src/lib/build.ts -B 2 -A 2 | head -40

Repository: netlify/cli

Length of output: 237


🏁 Script executed:

# Get more precise line numbers for the exact code snippet
grep -n "When --verbose is used with --json" src/commands/deploy/deploy.ts

Repository: netlify/cli

Length of output: 134


🏁 Script executed:

# Get the exact lines around the code in question
sed -n '715,750p' src/commands/deploy/deploy.ts | cat -n

Repository: netlify/cli

Length of output: 1370


Add optional chaining to logs.stdout and logs.stderr for consistency and safety.

The code at lines 736 and 740 uses optional chaining (logs?.stdout.length, logs?.stderr.length), but the new code at lines 724 and 727 does not. Even though logs is checked for truthiness, the pattern used elsewhere in the same function suggests these properties may be undefined in some cases. Add optional chaining for consistency.

🛡️ Proposed fix
   if (options.verbose && options.json && logs) {
-    if (logs.stdout.length) {
+    if (logs.stdout?.length) {
       process.stderr.write(logs.stdout.join('\n'))
     }
-    if (logs.stderr.length) {
+    if (logs.stderr?.length) {
       process.stderr.write(logs.stderr.join('\n'))
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (options.verbose && options.json && logs) {
if (logs.stdout.length) {
process.stderr.write(logs.stdout.join(''))
}
if (logs.stderr.length) {
process.stderr.write(logs.stderr.join(''))
}
}
if (options.verbose && options.json && logs) {
if (logs.stdout?.length) {
process.stderr.write(logs.stdout.join(''))
}
if (logs.stderr?.length) {
process.stderr.write(logs.stderr.join(''))
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/deploy/deploy.ts` around lines 723 - 730, The conditional writes
to stderr should use optional chaining for the logs properties to match the rest
of the function: replace checks of logs.stdout.length and logs.stderr.length
with logs?.stdout?.length and logs?.stderr?.length (within the block where
options.verbose && options.json && logs is true) so that accessing potentially
undefined stdout/stderr is safe and consistent with existing uses of
logs?.stdout and logs?.stderr.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/commands/deploy/deploy.ts (1)

724-729: ⚠️ Potential issue | 🟡 Minor

Add trailing newlines to prevent concatenated output.

When both logs.stdout and logs.stderr have content, the output will be concatenated without a newline separator (e.g., stdout_line\nstderr_line instead of stdout_line\n\nstderr_line\n). Adding trailing newlines ensures proper visual separation and conventional stream behavior.

🐛 Proposed fix
   if (options.verbose && options.json && logs) {
-    if (logs.stdout.length) {
-      process.stderr.write(logs.stdout.join('\n'))
+    if (logs.stdout?.length) {
+      process.stderr.write(logs.stdout.join('\n') + '\n')
     }
-    if (logs.stderr.length) {
-      process.stderr.write(logs.stderr.join('\n'))
+    if (logs.stderr?.length) {
+      process.stderr.write(logs.stderr.join('\n') + '\n')
     }
   }

Note: The diff also includes optional chaining (logs.stdout?.length, logs.stderr?.length) to align with the existing pattern at lines 736 and 740 for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/deploy/deploy.ts` around lines 724 - 729, The current writes
using process.stderr.write(logs.stdout.join('\n')) and
process.stderr.write(logs.stderr.join('\n')) can concatenate outputs; update the
block that checks logs.stdout and logs.stderr to use optional chaining
(logs.stdout?.length, logs.stderr?.length) and append a trailing newline to each
write (e.g., process.stderr.write(logs.stdout.join('\n') + '\n') and
process.stderr.write(logs.stderr.join('\n') + '\n')) so stdout and stderr output
are visually separated and end with a newline while preserving the existing
stream usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/deploy/deploy.ts`:
- Around line 720-722: Remove the explanatory comment that describes what the
code does around the logic that pipes build stdout/stderr to process.stderr when
both the --verbose and --json flags are set; instead rely on the
self-documenting flag and function names (e.g., the verbose/json flag handling
and the code that writes to process.stderr) so delete the three-line comment
block immediately above the piping logic in deploy.ts without changing the
surrounding implementation.

---

Duplicate comments:
In `@src/commands/deploy/deploy.ts`:
- Around line 724-729: The current writes using
process.stderr.write(logs.stdout.join('\n')) and
process.stderr.write(logs.stderr.join('\n')) can concatenate outputs; update the
block that checks logs.stdout and logs.stderr to use optional chaining
(logs.stdout?.length, logs.stderr?.length) and append a trailing newline to each
write (e.g., process.stderr.write(logs.stdout.join('\n') + '\n') and
process.stderr.write(logs.stderr.join('\n') + '\n')) so stdout and stderr output
are visually separated and end with a newline while preserving the existing
stream usage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d61d2a56-4e59-4f10-a581-ae0652aee892

📥 Commits

Reviewing files that changed from the base of the PR and between 883a645 and 6587942.

📒 Files selected for processing (2)
  • src/commands/deploy/deploy.ts
  • tests/integration/commands/deploy/deploy.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/integration/commands/deploy/deploy.test.ts

Comment on lines +720 to +722
// When --verbose is used with --json, pipe the build's stdout and stderr
// to the process stderr so that callers can see the full build output
// while still getting clean JSON on stdout.
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove explanatory comment per coding guidelines.

The comment describes what the code does rather than why. The function and flag names are self-documenting. As per coding guidelines: "Never write comments on what the code does; make the code clean and self-explanatory instead."

♻️ Proposed fix
   const { configMutations, exitCode, newConfig, logs } = await runBuild(resolvedOptions)

-  // When --verbose is used with --json, pipe the build's stdout and stderr
-  // to the process stderr so that callers can see the full build output
-  // while still getting clean JSON on stdout.
   if (options.verbose && options.json && logs) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/deploy/deploy.ts` around lines 720 - 722, Remove the explanatory
comment that describes what the code does around the logic that pipes build
stdout/stderr to process.stderr when both the --verbose and --json flags are
set; instead rely on the self-documenting flag and function names (e.g., the
verbose/json flag handling and the code that writes to process.stderr) so delete
the three-line comment block immediately above the piping logic in deploy.ts
without changing the surrounding implementation.

@eduardoboucas eduardoboucas merged commit 13e973c into main Mar 25, 2026
71 checks passed
@eduardoboucas eduardoboucas deleted the feat/cli-deploy-verbose branch March 25, 2026 17:00
eduardoboucas pushed a commit that referenced this pull request Mar 25, 2026
🤖 I have created a release *beep* *boop*
---


## [24.6.0](v24.5.1...v24.6.0)
(2026-03-25)


### Features

* log output to stderr on `deploy --json --verbose`
([#8090](#8090))
([13e973c](13e973c))


### Bug Fixes

* AX for env setting
([#8091](#8091))
([d77ff79](d77ff79))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: token-generator-app[bot] <82042599+token-generator-app[bot]@users.noreply.github.com>
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.

3 participants