Skip to content

[PM-13621] ci: Add cron job to regularly update SPM dependencies in project files - #1753

Closed
KatherineInCode wants to merge 27 commits into
mainfrom
katherine/renovate
Closed

KatherineInCode wants to merge 27 commits into
mainfrom
katherine/renovate

Conversation

@KatherineInCode

@KatherineInCode KatherineInCode commented Jul 11, 2025 •

Copy link
Copy Markdown
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-13621

📔 Objective

This creates a new cron job in the pattern of our "update public suffix list" job that checks the SPM packages defined in our project files for updates, and if they exist creates a PR for it. The update-dependencies.sh script can also be run offline if desired.

This should hopefully let us keep better tabs on those updates, and keep our dependencies up to date.

This has also been an opportunity for me to experiment with Claude Code, in terms of writing the bash script.

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@github-actions

github-actions Bot commented Jul 11, 2025 •

Copy link
Copy Markdown
Contributor

Logo
Checkmarx One – Scan Summary & Details – a7bb110c-5a57-4d21-966e-eac33062d088

Great job, no security vulnerabilities found in this Pull Request

@KatherineInCode
KatherineInCode marked this pull request as ready for review July 11, 2025 17:45
@KatherineInCode
KatherineInCode requested review from a team and matt-livefront as code owners July 11, 2025 17:45
@codecov

codecov Bot commented Jul 11, 2025 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 41.18%. Comparing base (9d93d87) to head (0c9748e).
⚠️ Report is 66 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main    #1753       +/-   ##
===========================================
- Coverage   87.20%   41.18%   -46.03%     
===========================================
  Files        1895      592     -1303     
  Lines      167767    32503   -135264     
===========================================
- Hits       146304    13385   -132919     
+ Misses      21463    19118     -2345     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vvolkgang vvolkgang left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidating what we discussed and some review notes:

  • xcodegen project files will be the source of truth and we should untrack Package.resolved, we'll use xcodegen project files for caching purposes
  • We need to pin dependencies to revision: - for human review purposes confirm if we can continue having the exactVersion: setting in project-*.yml files, otherwise we can add a comment next to the revision hash with the release tag name, example.
  • Replace GitHub API curl calls with GH CLI - for examples search for GH_TOKEN in our repo.
  • Consider using python (with classes) instead of bash for the script, they've been easier to maintain and improve. I can help you set it up with uv.
  • Listing updated packages in the PR description - I can help with that, we can try mimicking some of the Renovate PRs structure. 🤔

@vvolkgang
vvolkgang marked this pull request as draft July 16, 2025 15:34
@github-actions github-actions Bot added the t:ci Change Type - Updates to automated workflows label May 1, 2026
- Refactor update-dependencies.py with GitHubClient, ProjectFileUpdater,
  and DependencyUpdateRunner classes
- Write spm-update-summary.md with a package update table; use it as PR body
- Fix broken $PSL_FILE references in workflow; track project YAML files instead
- Add pip install step for pyyaml and packaging
- Untrack Package.resolved and uncomment it in .gitignore
- Remove update-dependencies.sh (superseded by Python script)
@github-actions github-actions Bot added the t:deps Change Type - Dependencies label May 1, 2026
@KatherineInCode KatherineInCode changed the title [PM-13621] Add cron job to regularly update SPM dependencies in project files [PM-13621] ci: Add cron job to regularly update SPM dependencies in project files May 1, 2026
@KatherineInCode KatherineInCode added ai-review-vnext Request a Claude code review using the vNext workflow and removed t:deps Change Type - Dependencies labels May 1, 2026
@github-actions

github-actions Bot commented May 1, 2026 •

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: REQUEST CHANGES

Reviewed the new weekly cron workflow that runs Scripts/update-dependencies.py to bump SPM dependencies in the xcodegen project YAML files, plus the corresponding .gitignore change that removes Package.resolved from version control. The script is well-structured (separated GitHubClient / ProjectFileUpdater / DependencyUpdateRunner, per-method docstrings, comment-preserving YAML round-trip via ruamel.yaml) and the recent commits already addressed earlier review feedback about downgrade safety and release-pinned detection. The main concern is that the workflow step that runs the script does not pass a token to the environment, so the gh api calls inside it will fail.

Code Review Details
  • ⚠️ : "Run update script" step is missing GH_TOKEN/GITHUB_TOKEN, so the script's gh api calls will fail (and exit non-zero) every cron run
    • .github/workflows/cron-update-spm-dependencies.yml:25-26

Comment on lines +340 to +376
def _update_revision(
self,
name: str,
info: object,
url: str,
current: str,
branch: str,
) -> Optional[PackageUpdate]:
"""Check for and apply a newer commit revision.

The inline comment on the ``revision`` key (if any) is preserved as-is,
since the script has no way to determine the human-readable version
identifier for an arbitrary commit SHA.

Args:
name: Package name (for logging).
info: Package CommentedMap; ``revision`` is mutated on update.
url: GitHub repository URL.
current: Current revision SHA.
branch: Branch to query for the latest commit.

Returns:
A PackageUpdate if a newer commit was found and applied, else None.
"""
latest = self.client.get_latest_commit(url, branch)
if latest and latest != current:
print(f" Updating {name} revision: {current[:8]}… → {latest[:8]}…")
info["revision"] = latest
return PackageUpdate(
package_name=name,
source_file=self.path,
old_value=current,
new_value=latest,
update_kind="revision",
)
print(f" {name} revision is up to date ({current[:8]}…)")
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ QUESTION: After an exactVersion package is converted to revision/branch, subsequent cron runs follow branch HEAD rather than the next stable tag — was this intentional?

Details

_process_package only enters _convert_version while exactVersion is still present. Once converted, every later run takes the _update_revision path, which calls get_latest_commit(url, branch) and pins to whatever commit is currently at the head of the default branch.

Two follow-on effects:

  1. Packages migrate from "stable releases only" to "main branch HEAD" tracking after the first conversion. For example, Firebase at exactVersion: 11.14.0 would convert to that tag's SHA, but the next cron run would jump to whatever commit landed on firebase-ios-sdk's default branch — potentially ahead of any tagged release. The cron will never re-pin to a newer stable tag (e.g. 11.15.0) once exactVersion is gone.
  2. The inline tag comment added by info.yaml_add_eol_comment(target_tag, "revision") is preserved as-is by _update_revision (per the docstring), so a revision: <sha> # 11.14.0 line can drift to point at a commit that has nothing to do with 11.14.0, and reviewers of generated PRs lose the human-readable version signal.

If the intent is to track stable releases only, _update_revision could re-query get_latest_release + get_tag_commit_sha for converted packages and refresh the inline comment when the tag changes. If branch-HEAD tracking is the goal for these packages, it's worth calling out in the PR description so future maintainers (and reviewers of the generated PRs) understand what the comment represents.

@github-actions github-actions Bot added the t:deps Change Type - Dependencies label May 1, 2026
Comment on lines +327 to +346
is_version_bump = _is_older(current, latest_tag)
target_tag = latest_tag

sha = self.client.get_tag_commit_sha(url, target_tag)
if sha is None:
print(f" {name}: Could not resolve SHA for {target_tag}, skipping...")
return None

default_branch = self.client.get_default_branch(url)

if is_version_bump:
print(f" Converting and updating {name}: {current} → {target_tag} ({sha[:8]}…)")
update_kind = "version"
else:
print(f" Converting {name} {current} to revision format ({sha[:8]}…)")
update_kind = "conversion"

del info["exactVersion"]
info["revision"] = sha
info.yaml_add_eol_comment(target_tag, "revision")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ QUESTION: The docstring says "otherwise the current version is re-pinned at its commit SHA," but target_tag = latest_tag is unconditional — was this intentional?

Details

The docstring (lines 306–308) describes a two-branch behavior:

If the latest release is newer than current, the new tag is used; otherwise the current version is re-pinned at its commit SHA.

But in the code, target_tag is always set to latest_tag regardless of is_version_bump. The SHA fetched on line 330, the comment written on line 346, and the new_value returned on line 353 all reflect latest_tag, not current.

Concrete scenario where this diverges from the docstring: a developer manually pins exactVersion: 12.0.0-beta1 (e.g., to consume a fix that hasn't shipped in a stable release yet). On the next cron run:

  • latest_tag = "11.14.0" (the latest stable)
  • _is_older("12.0.0-beta1", "11.14.0") → False (12.0.0-beta1 is newer per PEP 440)
  • is_version_bump = False, the log says "Converting 12.0.0-beta1 to revision format…"
  • But target_tag = "11.14.0", so the package is silently downgraded to 11.14.0's SHA.

The current exactVersion packages (Firebase 11.14.0, SwiftProtobuf 1.31.0, SwiftUIIntrospect 1.3.0, SnapshotTesting 1.18.4, ViewInspector 0.10.3) are all probably behind their latest stable, so this won't trigger on the first run — but the divergence from the docstring is worth resolving either by branching target_tag based on is_version_bump, or by updating the docstring to reflect that the script always converts to latest_tag.

Comment on lines +25 to +26
- name: Run update script
run: ./Scripts/update-dependencies.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ IMPORTANT: The script makes gh api calls but this step does not set GH_TOKEN/GITHUB_TOKEN, so authentication will fail.

Details and fix

./Scripts/update-dependencies.py shells out to gh api for releases, tags, default branch, and commits — roughly a dozen calls per run. The gh CLI on GitHub-hosted runners is not pre-authenticated; it requires GH_TOKEN (or GITHUB_TOKEN) in the step's environment, which other workflows in this repo set explicitly (e.g. sdlc-sdk-update.yml lines 96, 126, 153). Unauthenticated gh api calls will either fail with gh: To authenticate, please run... or, for public-only endpoints, be capped at the 60-request/hour anonymous rate limit.

Because _call_api now raises RuntimeError on failure (and main() exits with status 1), the workflow step will fail loudly every Monday and the downstream "Check for changes" / commit / PR steps (gated on has_changes == 'true') will never run.

Suggested change
- name: Run update script
run: ./Scripts/update-dependencies.py
- name: Run update script
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./Scripts/update-dependencies.py

@KatherineInCode

Copy link
Copy Markdown
Contributor Author

Closing in favor of #2679

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review-vnext Request a Claude code review using the vNext workflow t:ci Change Type - Updates to automated workflows t:deps Change Type - Dependencies

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants