Invoke ``specify init`` in-process and capture exit code/output. Runs with the working directory set to ``context.project_root`` so that ``--here`` and relative project paths target the right place.
(
argv: list[str], context: StepContext
)
| 243 | |
| 244 | @staticmethod |
| 245 | def _run_init( |
| 246 | argv: list[str], context: StepContext |
| 247 | ) -> tuple[int, str, str]: |
| 248 | """Invoke ``specify init`` in-process and capture exit code/output. |
| 249 | |
| 250 | Runs with the working directory set to ``context.project_root`` so |
| 251 | that ``--here`` and relative project paths target the right place. |
| 252 | """ |
| 253 | from typer.testing import CliRunner |
| 254 | |
| 255 | from specify_cli import app |
| 256 | |
| 257 | runner = CliRunner() |
| 258 | |
| 259 | prev_cwd = os.getcwd() |
| 260 | if context.project_root: |
| 261 | try: |
| 262 | os.chdir(context.project_root) |
| 263 | except OSError as exc: |
| 264 | return (1, "", f"Cannot enter project root: {exc}") |
| 265 | try: |
| 266 | result = runner.invoke(app, argv, catch_exceptions=True) |
| 267 | finally: |
| 268 | try: |
| 269 | os.chdir(prev_cwd) |
| 270 | except OSError: |
| 271 | # Best-effort cleanup: avoid masking the init command result |
| 272 | # if restoring the previous working directory fails. |
| 273 | pass |
| 274 | |
| 275 | stdout = result.output or "" |
| 276 | # click >= 8.2 captures stderr separately; older versions mix it into |
| 277 | # stdout and raise when ``result.stderr`` is accessed. |
| 278 | try: |
| 279 | stderr = result.stderr or "" |
| 280 | except (ValueError, AttributeError): |
| 281 | # Older Click: stderr is mixed into stdout. On failure, treat |
| 282 | # stdout as stderr so workflows can consistently read |
| 283 | # steps.<id>.output.stderr for error details. |
| 284 | stderr = stdout if result.exit_code != 0 else "" |
| 285 | |
| 286 | if result.exit_code != 0 and result.exception is not None: |
| 287 | detail = f"{type(result.exception).__name__}: {result.exception}" |
| 288 | stderr = f"{stderr}\n{detail}".strip() if stderr else detail |
| 289 | |
| 290 | return (result.exit_code, stdout, stderr) |
| 291 | |
| 292 | def validate(self, config: dict[str, Any]) -> list[str]: |
| 293 | errors = super().validate(config) |