mac-upkeep 2.4.0__tar.gz → 2.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. mac_upkeep-2.4.2/.release-please-manifest.json +3 -0
  2. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/CHANGELOG.md +14 -0
  3. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/CLAUDE.md +1 -1
  4. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/PKG-INFO +4 -4
  5. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/README.md +3 -3
  6. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/pyproject.toml +1 -1
  7. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/src/mac_upkeep/defaults.toml +4 -0
  8. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/src/mac_upkeep/git_sync.py +23 -9
  9. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/tests/test_config.py +1 -1
  10. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/tests/test_git_sync.py +25 -0
  11. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/uv.lock +1 -1
  12. mac_upkeep-2.4.0/.release-please-manifest.json +0 -3
  13. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/.github/workflows/release.yml +0 -0
  14. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/.github/workflows/test.yml +0 -0
  15. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/.gitignore +0 -0
  16. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/CONTRIBUTING.md +0 -0
  17. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/LICENSE +0 -0
  18. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/demo/demo.gif +0 -0
  19. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/demo/record.sh +0 -0
  20. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/docs/reusable-patterns.md +0 -0
  21. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/llms.txt +0 -0
  22. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/release-please-config.json +0 -0
  23. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/src/mac_upkeep/__init__.py +0 -0
  24. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/src/mac_upkeep/cli.py +0 -0
  25. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/src/mac_upkeep/config.py +0 -0
  26. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/src/mac_upkeep/notify.py +0 -0
  27. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/src/mac_upkeep/output.py +0 -0
  28. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/src/mac_upkeep/py.typed +0 -0
  29. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/src/mac_upkeep/tasks.py +0 -0
  30. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/tests/__init__.py +0 -0
  31. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/tests/test_cli.py +0 -0
  32. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/tests/test_notify.py +0 -0
  33. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/tests/test_output.py +0 -0
  34. {mac_upkeep-2.4.0 → mac_upkeep-2.4.2}/tests/test_tasks.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "2.4.2"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.2](https://github.com/calvindotsg/mac-upkeep/compare/v2.4.1...v2.4.2) (2026-06-15)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * git_sync timeout resilience + disable broken pnpm store-prune ([#39](https://github.com/calvindotsg/mac-upkeep/issues/39)) ([c9b2665](https://github.com/calvindotsg/mac-upkeep/commit/c9b2665cb9be85d4f49b39aa967ab001473f5b22))
9
+
10
+ ## [2.4.1](https://github.com/calvindotsg/mac-upkeep/compare/v2.4.0...v2.4.1) (2026-04-22)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * correct Mole link in README to tw93/Mole ([#37](https://github.com/calvindotsg/mac-upkeep/issues/37)) ([6f0b1ff](https://github.com/calvindotsg/mac-upkeep/commit/6f0b1ff5ba107ab9b9aab0242c823e5aaf4e1a08))
16
+
3
17
  ## [2.4.0](https://github.com/calvindotsg/mac-upkeep/compare/v2.3.0...v2.4.0) (2026-04-20)
4
18
 
5
19
 
@@ -103,7 +103,7 @@ A TaskDef with `handler="<name>"` and empty `command` bypasses subprocess buildi
103
103
  - **Do not open terminal windows from launchd** — fragile (focus stealing, macOS 13+ permission escalation). Use headless + notification + click-to-act.
104
104
  - **Testing**: `Config.load()` calls `get_brew_prefix()` which runs `subprocess.run(["brew", "--prefix"])`. Tests that mock `subprocess.run` or `shutil.which` will capture this call too (shared module objects). Mock `mac_upkeep.config.get_brew_prefix` directly in `init` command tests.
105
105
  - **`git_sync` SSH auth under launchd** relies on `~/.ssh/config` `IdentityAgent` (path-based, e.g. 1Password socket). `SSH_AUTH_SOCK` env vars are NOT inherited by LaunchAgents, so env-based agent forwarding won't work here — the `IdentityAgent` directive is the supported path.
106
- - **`git_sync` forces `GIT_TERMINAL_PROMPT=0` and defaults `GIT_ASKPASS=/usr/bin/true`** (user-set `GIT_ASKPASS` is respected) — fail-fast on auth misconfiguration instead of stalling to the 60 s subprocess timeout.
106
+ - **`git_sync` forces `GIT_TERMINAL_PROMPT=0` and defaults `GIT_ASKPASS=/usr/bin/true`** (user-set `GIT_ASKPASS` is respected) — fail-fast on auth misconfiguration instead of stalling to the 60 s subprocess timeout. A genuine stall (network/server hang) still hits the timeout, so `_run_git` catches `subprocess.TimeoutExpired` and returns a synthetic `CompletedProcess(returncode=124, stderr="timed out after {timeout}s")`. That marks only the affected repo failed and lets the loop continue — a hung pull cannot abort the whole run (which would otherwise skip `output.summary()`/`notify()` and later tasks).
107
107
 
108
108
  ## Release Process
109
109
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mac-upkeep
3
- Version: 2.4.0
3
+ Version: 2.4.2
4
4
  Summary: Automated macOS maintenance CLI
5
5
  Project-URL: Homepage, https://github.com/calvindotsg/mac-upkeep
6
6
  Project-URL: Repository, https://github.com/calvindotsg/mac-upkeep
@@ -61,9 +61,9 @@ uvx mac-upkeep run # one-off without installing
61
61
  | `pnpm` | Prune pnpm content-addressable store | Monthly |
62
62
  | `uv` | Prune uv package cache | Monthly |
63
63
  | `fisher` | Update Fish shell plugins | Weekly |
64
- | `mo_clean` | Clean system and user caches ([mole](https://github.com/nicehash/mole)) | Weekly |
65
- | `mo_optimize` | Optimize DNS, Spotlight, fonts, Dock ([mole](https://github.com/nicehash/mole)) | Weekly |
66
- | `mo_purge` | Remove old project artifacts ([mole](https://github.com/nicehash/mole)) | Monthly |
64
+ | `mo_clean` | Clean system and user caches ([Mole](https://github.com/tw93/Mole)) | Weekly |
65
+ | `mo_optimize` | Optimize DNS, Spotlight, fonts, Dock ([Mole](https://github.com/tw93/Mole)) | Weekly |
66
+ | `mo_purge` | Remove old project artifacts ([Mole](https://github.com/tw93/Mole)) | Monthly |
67
67
  | `brew_cleanup` | Remove old versions and cache files | Monthly |
68
68
  | `brew_bundle` | Remove packages not in Brewfile | Weekly |
69
69
  | `git_sync` | Pull configured git repositories | Daily |
@@ -34,9 +34,9 @@ uvx mac-upkeep run # one-off without installing
34
34
  | `pnpm` | Prune pnpm content-addressable store | Monthly |
35
35
  | `uv` | Prune uv package cache | Monthly |
36
36
  | `fisher` | Update Fish shell plugins | Weekly |
37
- | `mo_clean` | Clean system and user caches ([mole](https://github.com/nicehash/mole)) | Weekly |
38
- | `mo_optimize` | Optimize DNS, Spotlight, fonts, Dock ([mole](https://github.com/nicehash/mole)) | Weekly |
39
- | `mo_purge` | Remove old project artifacts ([mole](https://github.com/nicehash/mole)) | Monthly |
37
+ | `mo_clean` | Clean system and user caches ([Mole](https://github.com/tw93/Mole)) | Weekly |
38
+ | `mo_optimize` | Optimize DNS, Spotlight, fonts, Dock ([Mole](https://github.com/tw93/Mole)) | Weekly |
39
+ | `mo_purge` | Remove old project artifacts ([Mole](https://github.com/tw93/Mole)) | Monthly |
40
40
  | `brew_cleanup` | Remove old versions and cache files | Monthly |
41
41
  | `brew_bundle` | Remove packages not in Brewfile | Weekly |
42
42
  | `git_sync` | Pull configured git repositories | Daily |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mac-upkeep"
3
- version = "2.4.0"
3
+ version = "2.4.2"
4
4
  description = "Automated macOS maintenance CLI"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -22,6 +22,10 @@ description = "Prune pnpm content-addressable store"
22
22
  command = "pnpm store prune"
23
23
  detect = "pnpm"
24
24
  frequency = "monthly"
25
+ # Disabled by default: on pnpm 11 `store prune` corrupts the global virtual
26
+ # store, removing in-use packages (pnpm/pnpm#10132). Re-enable per-machine via
27
+ # `[tasks.pnpm]\nenabled = true` once upstream is fixed.
28
+ enabled = false
25
29
 
26
30
  [tasks.uv]
27
31
  description = "Prune uv package cache"
@@ -29,14 +29,23 @@ def _build_env() -> dict[str, str]:
29
29
 
30
30
 
31
31
  def _run_git(path: str, args: list[str], *, timeout: int = 60) -> subprocess.CompletedProcess[str]:
32
- return subprocess.run(
33
- ["git", "-C", path, *args],
34
- capture_output=True,
35
- text=True,
36
- timeout=timeout,
37
- stdin=subprocess.DEVNULL,
38
- env=_build_env(),
39
- )
32
+ cmd = ["git", "-C", path, *args]
33
+ try:
34
+ return subprocess.run(
35
+ cmd,
36
+ capture_output=True,
37
+ text=True,
38
+ timeout=timeout,
39
+ stdin=subprocess.DEVNULL,
40
+ env=_build_env(),
41
+ )
42
+ except subprocess.TimeoutExpired:
43
+ # A hung git call (e.g. network/auth stall on pull) must not abort the
44
+ # whole run. Return a synthetic failure so _sync_repo marks this repo
45
+ # failed and continues. 124 mirrors GNU timeout's convention.
46
+ return subprocess.CompletedProcess(
47
+ cmd, returncode=124, stdout="", stderr=f"timed out after {timeout}s"
48
+ )
40
49
 
41
50
 
42
51
  def _resolve_paths(patterns: list[str], output: Output) -> list[str]:
@@ -62,7 +71,12 @@ def _resolve_paths(patterns: list[str], output: Output) -> list[str]:
62
71
 
63
72
 
64
73
  def _sync_repo(path: str, *, skip_dirty: bool) -> tuple[str, str]:
65
- """Sync one repo. Returns (status, reason) where status is pulled|up-to-date|skipped|failed."""
74
+ """Sync one repo. Returns (status, reason) where status is pulled|up-to-date|skipped|failed.
75
+
76
+ Only `pull` realistically times out (it's the sole network call); a 124 timeout on
77
+ the local pre-pull checks below would fall through to their "skipped" branches, which
78
+ is fine — the run still completes either way thanks to _run_git's TimeoutExpired catch.
79
+ """
66
80
  r = _run_git(path, ["rev-parse", "--is-inside-work-tree"])
67
81
  if r.returncode != 0:
68
82
  return "skipped", "not a git repo"
@@ -394,7 +394,7 @@ def test_env_var_disables_task(monkeypatch):
394
394
  monkeypatch.setenv("MAC_UPKEEP_GCLOUD", "false")
395
395
  config = Config.load(Path("/nonexistent/config.toml"))
396
396
  assert not config.is_enabled("gcloud")
397
- assert config.is_enabled("pnpm")
397
+ assert config.is_enabled("uv") # unrelated task stays enabled
398
398
 
399
399
 
400
400
  def test_env_var_overrides_config(tmp_path):
@@ -277,3 +277,28 @@ def test_failure_surfaces_basename_and_stderr(tmp_path, monkeypatch):
277
277
  assert result.status == "failed"
278
278
  assert "1 failed: biz" == result.reason
279
279
  assert any("Permission denied" in c[0][0] for c in output.task_debug.call_args_list)
280
+
281
+
282
+ def test_pull_timeout_does_not_crash_run(tmp_path, monkeypatch):
283
+ """A hung pull raises TimeoutExpired inside subprocess.run; it must be caught
284
+ and surfaced as a failed repo rather than aborting the whole run."""
285
+ p = _make_repo(tmp_path, "biz")
286
+ config = _config([p])
287
+ output = MagicMock()
288
+ # MagicMock raises any exception instance found in side_effect, so the pull
289
+ # step (last) raises TimeoutExpired exactly where _run_git calls subprocess.run.
290
+ run_mock = MagicMock(
291
+ side_effect=[
292
+ _cp(returncode=0, stdout="true\n"),
293
+ _cp(returncode=0, stdout="origin\n"),
294
+ _cp(returncode=0, stdout="main\n"),
295
+ _cp(returncode=0, stdout="origin/main\n"),
296
+ _cp(returncode=0, stdout=""), # clean worktree
297
+ subprocess.TimeoutExpired(cmd=["git", "pull"], timeout=60), # pull hangs
298
+ ]
299
+ )
300
+ monkeypatch.setattr("mac_upkeep.git_sync.subprocess.run", run_mock)
301
+ result = run_git_sync(config, output, dry_run=False)
302
+ assert result.status == "failed"
303
+ assert "1 failed: biz" == result.reason
304
+ assert any("timed out after 60s" in c[0][0] for c in output.task_debug.call_args_list)
@@ -43,7 +43,7 @@ wheels = [
43
43
 
44
44
  [[package]]
45
45
  name = "mac-upkeep"
46
- version = "2.4.0"
46
+ version = "2.4.2"
47
47
  source = { editable = "." }
48
48
  dependencies = [
49
49
  { name = "typer" },
@@ -1,3 +0,0 @@
1
- {
2
- ".": "2.4.0"
3
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes