Handle merging or copying of .vscode/settings.json files. Note: when merge produces changes, rewritten output is normalized JSON and existing JSONC comments/trailing commas are not preserved.
(sub_item, dest_file, rel_path, verbose=False, tracker=None)
| 145 | |
| 146 | |
| 147 | def handle_vscode_settings(sub_item, dest_file, rel_path, verbose=False, tracker=None) -> None: |
| 148 | """Handle merging or copying of .vscode/settings.json files. |
| 149 | |
| 150 | Note: when merge produces changes, rewritten output is normalized JSON and |
| 151 | existing JSONC comments/trailing commas are not preserved. |
| 152 | """ |
| 153 | def log(message, color="green"): |
| 154 | if verbose and not tracker: |
| 155 | console.print(f"[{color}]{message}[/] {rel_path}") |
| 156 | |
| 157 | def atomic_write_json(target_file: Path, payload: dict[str, Any]) -> None: |
| 158 | """Atomically write JSON while preserving existing mode bits when possible.""" |
| 159 | temp_path: Path | None = None |
| 160 | try: |
| 161 | with tempfile.NamedTemporaryFile( |
| 162 | mode='w', |
| 163 | encoding='utf-8', |
| 164 | dir=target_file.parent, |
| 165 | prefix=f"{target_file.name}.", |
| 166 | suffix=".tmp", |
| 167 | delete=False, |
| 168 | ) as f: |
| 169 | temp_path = Path(f.name) |
| 170 | json.dump(payload, f, indent=4) |
| 171 | f.write('\n') |
| 172 | |
| 173 | if target_file.exists(): |
| 174 | try: |
| 175 | existing_stat = target_file.stat() |
| 176 | os.chmod(temp_path, stat.S_IMODE(existing_stat.st_mode)) |
| 177 | if hasattr(os, "chown"): |
| 178 | try: |
| 179 | os.chown(temp_path, existing_stat.st_uid, existing_stat.st_gid) |
| 180 | except PermissionError: |
| 181 | # Best-effort owner/group preservation without requiring elevated privileges. |
| 182 | pass |
| 183 | except OSError: |
| 184 | # Best-effort metadata preservation; data safety is prioritized. |
| 185 | pass |
| 186 | |
| 187 | os.replace(temp_path, target_file) |
| 188 | except Exception: |
| 189 | if temp_path and temp_path.exists(): |
| 190 | temp_path.unlink() |
| 191 | raise |
| 192 | |
| 193 | try: |
| 194 | with open(sub_item, 'r', encoding='utf-8') as f: |
| 195 | # json5 natively supports comments and trailing commas (JSONC) |
| 196 | new_settings = json5.load(f) |
| 197 | |
| 198 | if dest_file.exists(): |
| 199 | merged = merge_json_files(dest_file, new_settings, verbose=verbose and not tracker) |
| 200 | if merged is not None: |
| 201 | atomic_write_json(dest_file, merged) |
| 202 | log("Merged:", "green") |
| 203 | log("Note: comments/trailing commas are normalized when rewritten", "yellow") |
| 204 | else: |