@misterhuydo/sentinel 1.4.68 → 1.4.69

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.
@@ -51,6 +51,27 @@ def _git_env(repo: RepoConfig) -> dict:
51
51
  return env
52
52
 
53
53
 
54
+ def maven_compile_check(local_path: str, timeout: int = 300) -> tuple[bool, str]:
55
+ """
56
+ Run `mvn compile -DskipTests -q --batch-mode` to verify the pom.xml is valid
57
+ and all dependencies resolve before committing.
58
+ Returns (success, output). Raises MissingToolError if mvn is not installed.
59
+ """
60
+ import shutil
61
+ mvn = shutil.which("mvn")
62
+ if not mvn:
63
+ raise MissingToolError("mvn")
64
+ r = subprocess.run(
65
+ [mvn, "compile", "-DskipTests", "-q", "--batch-mode"],
66
+ cwd=local_path,
67
+ capture_output=True,
68
+ text=True,
69
+ timeout=timeout,
70
+ )
71
+ output = (r.stdout + r.stderr).strip()
72
+ return r.returncode == 0, output
73
+
74
+
54
75
  def _check_protected_paths(patch_path: Path) -> bool:
55
76
  text = patch_path.read_text(encoding="utf-8", errors="replace")
56
77
  for line in text.splitlines():
@@ -162,19 +183,24 @@ def commit_file_change(
162
183
  repo: RepoConfig,
163
184
  files: list[str],
164
185
  commit_msg: str,
186
+ skip_pull: bool = False,
165
187
  ) -> tuple[str, str]:
166
188
  """
167
- Pull, stage the given files, and commit.
189
+ Stage the given files and commit, optionally pulling first.
168
190
  Returns (status, commit_hash) — status: "committed" | "failed".
169
191
  Does NOT run tests (dependency bumps don't need them).
192
+
193
+ skip_pull=True when the caller already pulled before modifying files
194
+ (git pull --rebase fails on a dirty working tree).
170
195
  """
171
196
  env = _git_env(repo)
172
197
  local_path = repo.local_path
173
198
 
174
- r = _git(["pull", "--rebase", "origin", repo.branch], cwd=local_path, env=env)
175
- if r.returncode != 0:
176
- logger.error("git pull failed for %s:\n%s", repo.repo_name, r.stderr)
177
- return "failed", ""
199
+ if not skip_pull:
200
+ r = _git(["pull", "--rebase", "origin", repo.branch], cwd=local_path, env=env)
201
+ if r.returncode != 0:
202
+ logger.error("git pull failed for %s:\n%s", repo.repo_name, r.stderr)
203
+ return "failed", ""
178
204
 
179
205
  _git(["add"] + files, cwd=local_path, env=env)
180
206
  r = _git(["commit", "-m", commit_msg], cwd=local_path, env=env)
@@ -194,11 +220,12 @@ def push_dep_update(
194
220
  artifact_id: str,
195
221
  new_version: str,
196
222
  commit_hash: str,
197
- ) -> tuple[str, str]:
223
+ ) -> tuple[str, str, bool]:
198
224
  """
199
225
  Push a dependency update commit. AUTO_PUBLISH=true → main branch.
200
226
  AUTO_PUBLISH=false → sentinel/dep-<artifact>-<version> branch + open PR.
201
- Returns (branch, pr_url).
227
+ Returns (branch, pr_url, push_ok).
228
+ push_ok=False means the commit is local only (no write access) — chain release continues.
202
229
  """
203
230
  env = _git_env(repo)
204
231
  local_path = repo.local_path
@@ -206,8 +233,12 @@ def push_dep_update(
206
233
  if repo.auto_publish:
207
234
  r = _git(["push", "origin", repo.branch], cwd=local_path, env=env)
208
235
  if r.returncode != 0:
209
- logger.error("git push failed for %s:\n%s", repo.repo_name, r.stderr)
210
- return repo.branch, ""
236
+ logger.warning(
237
+ "git push failed for %s (commit is local only): %s",
238
+ repo.repo_name, r.stderr.strip().splitlines()[0] if r.stderr else "",
239
+ )
240
+ return repo.branch, "", False
241
+ return repo.branch, "", True
211
242
  else:
212
243
  safe_artifact = re.sub(r"[^a-zA-Z0-9_-]", "-", artifact_id)
213
244
  safe_version = re.sub(r"[^a-zA-Z0-9._-]", "-", new_version)
@@ -215,12 +246,15 @@ def push_dep_update(
215
246
  _git(["checkout", "-B", branch], cwd=local_path, env=env)
216
247
  r = _git(["push", "-u", "origin", branch], cwd=local_path, env=env)
217
248
  if r.returncode != 0:
218
- logger.error("git push branch failed for %s:\n%s", repo.repo_name, r.stderr)
249
+ logger.warning(
250
+ "git push branch failed for %s (commit is local only): %s",
251
+ repo.repo_name, r.stderr.strip().splitlines()[0] if r.stderr else "",
252
+ )
219
253
  _git(["checkout", repo.branch], cwd=local_path, env=env)
220
- return branch, ""
254
+ return branch, "", False
221
255
  _git(["checkout", repo.branch], cwd=local_path, env=env)
222
256
  pr_url = open_dep_pr(repo, cfg, branch, artifact_id, new_version, commit_hash)
223
- return branch, pr_url
257
+ return branch, pr_url, True
224
258
 
225
259
 
226
260
  def open_dep_pr(
@@ -355,6 +355,40 @@ def notify_cascade_result(
355
355
  slack_alert(cfg.slack_bot_token, cfg.slack_channel, text)
356
356
 
357
357
 
358
+ def notify_cascade_build_failed(
359
+ cfg,
360
+ repo_name: str,
361
+ artifact_id: str,
362
+ new_version: str,
363
+ build_output: str,
364
+ user_id: str = "",
365
+ ) -> None:
366
+ """Alert admin when a Maven compile check fails after a dep bump — pom.xml was reverted."""
367
+ # Maven errors accumulate at the bottom — show the last 600 chars
368
+ snippet = build_output.strip()[-600:]
369
+ text = (
370
+ f":x: *Cascade build check failed — `{repo_name}`*\n"
371
+ f"Bumping `{artifact_id}` \u2192 `{new_version}` caused a compile error.\n"
372
+ f"`pom.xml` has been reverted. Manual intervention required.\n"
373
+ f"```{snippet}```"
374
+ )
375
+ if user_id:
376
+ slack_alert(cfg.slack_bot_token, cfg.slack_channel, f"<@{user_id}> {text}")
377
+ else:
378
+ slack_alert(cfg.slack_bot_token, cfg.slack_channel, f"<!channel> {text}")
379
+ try:
380
+ from .reporter import send_failure_notification
381
+ send_failure_notification(cfg, {
382
+ "source": repo_name,
383
+ "message": f"Cascade dep bump {artifact_id}={new_version} failed Maven compile",
384
+ "repo_name": repo_name,
385
+ "reason": f"Maven compile error after dep bump: {build_output[:300]}",
386
+ "body": build_output,
387
+ })
388
+ except Exception as exc:
389
+ logger.warning("notify_cascade_build_failed: email notification failed: %s", exc)
390
+
391
+
358
392
  def alert_if_rate_limited(
359
393
  bot_token: str,
360
394
  channel: str,