11from __future__ import annotations
22
33import functools
4- import json
54import os
65import sys
76from contextvars import ContextVar
87from pathlib import Path
98from typing import Any , Callable
109
1110from ._runtime import create_runtime_directory , prune_log_files , runtime_layout
11+ from ._private_files import write_private_json
1212from .config import load_config , read_user_config
1313from .context import Context , reset_current_context , set_current_context
1414from .exit_codes import ExitCode
@@ -170,7 +170,10 @@ def wrapper(**kwargs: Any):
170170 if context .manifest_path is not None :
171171 context .log .debug ("manifest_path=%s" , context .manifest_path )
172172 result = func (context , ** kwargs )
173- exit_code = int (result or ExitCode .SUCCESS )
173+ try :
174+ exit_code = _normalize_command_result (result )
175+ except TypeError as exc :
176+ raise click .ClickException (str (exc )) from exc
174177 return result
175178 except Exception :
176179 exit_code = ExitCode .FAILURE
@@ -238,6 +241,7 @@ def _create_context(self, standard: dict[str, Any], sensitive_options: set[str],
238241 log_file = _default_log_file (layout , inherited_path )
239242 create_runtime_directory (log_file .parent , cache_root )
240243 if inherited_path is None and not dry_run and self .log_to_file :
244+ create_runtime_directory (layout .owner_root , cache_root )
241245 create_runtime_directory (layout .run_root , cache_root )
242246 try :
243247 run_metadata = {
@@ -252,33 +256,24 @@ def _create_context(self, standard: dict[str, Any], sensitive_options: set[str],
252256 "workspace_root" : str (user_config .workspace .root ) if user_config .workspace .root else None ,
253257 }
254258 run_metadata_path = layout .run_root / "run.json"
255- run_metadata_path .write_text (
256- json .dumps (run_metadata , sort_keys = True ) + "\n " ,
257- encoding = "utf-8" ,
258- )
259- run_metadata_path .chmod (0o600 )
259+ write_private_json (run_metadata_path , run_metadata )
260260 except OSError :
261261 pass
262262 if runtime_owner == "project" and selected_project_root is not None and not dry_run and self .log_to_file :
263263 try :
264264 create_runtime_directory (layout .owner_root , cache_root )
265265 identity_path = layout .owner_root / "identity.json"
266266 if not identity_path .exists ():
267- identity_path .write_text (
268- json .dumps (
269- {
270- "schema_version" : 1 ,
271- "project" : selected_project_name ,
272- "project_root" : str (selected_project_root ),
273- "manifest" : str (manifest_path ) if manifest_path is not None else None ,
274- "checkout_id" : layout .owner_root .name ,
275- },
276- sort_keys = True ,
277- )
278- + "\n " ,
279- encoding = "utf-8" ,
267+ write_private_json (
268+ identity_path ,
269+ {
270+ "schema_version" : 1 ,
271+ "project" : selected_project_name ,
272+ "project_root" : str (selected_project_root ),
273+ "manifest" : str (manifest_path ) if manifest_path is not None else None ,
274+ "checkout_id" : layout .owner_root .name ,
275+ },
280276 )
281- identity_path .chmod (0o600 )
282277 except OSError :
283278 pass
284279 logger = configure_logger (self .name , log_file , debug , quiet = quiet )
@@ -341,7 +336,22 @@ def run_app(app: App, argv: list[str] | None = None) -> int:
341336 except click .ClickException as exc :
342337 exc .show ()
343338 return int (exc .exit_code )
344- return int (result or 0 )
339+ try :
340+ return _normalize_command_result (result )
341+ except TypeError as exc :
342+ print (f"ERROR: { exc } " , file = sys .stderr )
343+ return ExitCode .FAILURE
344+
345+
346+ def _normalize_command_result (result : Any ) -> int :
347+ if result is None :
348+ return ExitCode .SUCCESS
349+ if isinstance (result , int ):
350+ return result
351+ raise TypeError (
352+ "Commands must return None or an int exit code; "
353+ f"got { type (result ).__name__ } ."
354+ )
345355
346356
347357def _effective_invocation_argv (
0 commit comments