diff --git a/concore_cli/cli.py b/concore_cli/cli.py index dc8fca3..f0a4559 100644 --- a/concore_cli/cli.py +++ b/concore_cli/cli.py @@ -21,7 +21,10 @@ @click.group() @click.version_option(version=__version__, prog_name="concore") def cli(): - pass + # Legacy code pages (e.g. cp1252 when output is redirected on Windows) can't + # encode symbols like the check mark; replace them instead of crashing. + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(errors="replace") @cli.command() diff --git a/tests/test_cli.py b/tests/test_cli.py index b95c60d..682212a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -882,6 +882,22 @@ def test_build_command_shared_source_specialization_merges_edge_params(self): self.assertIn("PORT_NAME_B_C", content) self.assertIn("PORT_B_C", content) + def test_cli_redirected_output_with_legacy_encoding(self): + env = dict(os.environ, PYTHONIOENCODING="cp1252") + repo_root = str(Path(__file__).resolve().parent.parent) + env["PYTHONPATH"] = repo_root + os.pathsep + env.get("PYTHONPATH", "") + result = subprocess.run( + [sys.executable, "-c", "from concore_cli.cli import cli; cli()"] + + ["init", "demo"], + cwd=self.temp_dir, + env=env, + stdin=subprocess.DEVNULL, + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr.decode(errors="replace")) + self.assertTrue((Path(self.temp_dir) / "demo" / "workflow.graphml").exists()) + def test_build_command_existing_output(self): with self.runner.isolated_filesystem(temp_dir=self.temp_dir): result = self.runner.invoke(cli, ["init", "test-project"])