Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/api/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,23 @@ file, you can use the following command::

$ semantic-release generate-config -f toml --pyproject >> pyproject.toml

On Windows PowerShell, the redirection operators (`>`/`>>`) default to UTF-16LE,
which can introduce NUL characters. Prefer one of the following to keep UTF-8:

::

# PowerShell (recommended)
semantic-release generate-config -f toml --pyproject | Out-File -Encoding utf8 pyproject.toml
# or
semantic-release generate-config -f toml --pyproject | Set-Content -Encoding utf8 pyproject.toml

You can also set a session default:

::

$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
$PSDefaultParameterValues['Set-Content:Encoding'] = 'utf8'

If your project doesn't already leverage TOML files for configuration, it might better
suit your project to use JSON instead::

Expand Down
25 changes: 25 additions & 0 deletions docs/misc/troubleshooting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,28 @@ For example::

.. note::
The provided GitHub action sets the verbosity level to INFO by default.

.. _troubleshooting-windows-redirection:

Windows redirection creates NUL characters
-----------------------------------------

When running ``semantic-release generate-config`` in Windows PowerShell, the ``>``/``>>``
operators default to UTF-16LE and can insert NUL characters, making ``pyproject.toml``
or other config files unreadable. Use one of the following instead to force UTF-8:

::

semantic-release generate-config -f toml --pyproject | Out-File -Encoding utf8 pyproject.toml
# or
semantic-release generate-config -f toml --pyproject | Set-Content -Encoding utf8 pyproject.toml

To make this the default for the session:

::

$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
$PSDefaultParameterValues['Set-Content:Encoding'] = 'utf8'

If redirection still produces NULs, ensure your PowerShell profile or CI runner does not
override these defaults, or set ``PYTHONIOENCODING=utf-8`` in the environment.
20 changes: 17 additions & 3 deletions src/semantic_release/cli/commands/generate_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import json
import sys

import click
import tomlkit
Expand Down Expand Up @@ -49,7 +50,20 @@ def generate_config(fmt: str = "toml", is_pyproject_toml: bool = False) -> None:
config_dct = {"tool": config_dct}

if fmt == "toml":
click.echo(tomlkit.dumps(config_dct))

output = tomlkit.dumps(config_dct)
elif fmt == "json":
click.echo(json.dumps(config_dct, indent=4))
output = json.dumps(config_dct, indent=4)
else:
return

# Write output directly to stdout buffer as UTF-8 bytes
# This ensures consistent UTF-8 output on all platforms, especially Windows where
# shell redirection (>, >>) defaults to the system encoding (e.g., UTF-16LE or cp1252)
# By writing to sys.stdout.buffer, we bypass the encoding layer and guarantee UTF-8.
try:
sys.stdout.buffer.write(output.encode("utf-8"))
sys.stdout.buffer.write(b"\n")
sys.stdout.buffer.flush()
except (AttributeError, TypeError):
# Fallback for environments without buffer (shouldn't happen in standard Python)
click.echo(output)
60 changes: 60 additions & 0 deletions tests/e2e/cmd_config/test_generate_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations

import json
import subprocess
import sys
from typing import TYPE_CHECKING

import pytest
Expand Down Expand Up @@ -157,3 +159,61 @@ def test_generate_config_pyproject_toml(
# Evaluate: Check that the version command in noop mode ran successfully
# which means PSR loaded the configuration successfully
assert_successful_exit_code(result, cli_cmd)


@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific encoding check")
@pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__)
def test_generate_config_emits_utf8_bytes_windows(
tmp_path: Path, example_project_dir: ExProjectDir
) -> None:
"""Ensure stdout bytes are UTF-8 and contain no NULs on Windows."""
output_file = tmp_path / "config.toml"
cmd = [
sys.executable,
"-m",
"semantic_release",
"generate-config",
"-f",
"toml",
"--pyproject",
]

with output_file.open("wb") as stdout_fp:
subprocess.run(
cmd,
stdout=stdout_fp,
stderr=subprocess.PIPE,
check=True,
cwd=example_project_dir,
)

data = output_file.read_bytes()
assert b"\x00" not in data
# Ensure the emitted bytes decode as UTF-8
decoded = data.decode("utf-8")
assert "tool.semantic_release" in decoded


@pytest.mark.usefixtures(repo_w_no_tags_conventional_commits.__name__)
def test_generate_config_stdout_decodes_utf8(
tmp_path: Path, example_project_dir: ExProjectDir
) -> None:
"""Ensure stdout captured as bytes decodes as UTF-8 without NULs."""
cmd = [
sys.executable,
"-m",
"semantic_release",
"generate-config",
"-f",
"toml",
"--pyproject",
]

result = subprocess.run(
cmd, capture_output=True, check=True, cwd=example_project_dir
)

stdout_bytes = result.stdout
assert b"\x00" not in stdout_bytes
decoded = stdout_bytes.decode("utf-8")
assert "tool.semantic_release" in decoded
Loading