Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ jobs:
create_new_release_on_new_metadata_update:
runs-on: ubuntu-latest
# Elevated above the read-only default at the job rather than the file level, so a
# workflow-wide write token is never minted. The update script pushes a commit and
# creates a GitHub release via GITHUB_TOKEN; actions: write lets it dispatch
# publish_nuget.yml, which the release's own push event cannot trigger.
# workflow-wide write token is never minted. The update script pushes a commit to a
# metadata-update/* branch and opens a PR against main via GITHUB_TOKEN; pull-requests:
# write also covers enabling auto-merge on it. Tagging, releasing and dispatching the
# NuGet publish happen later, once that PR merges - see finalize_metadata_release.yml.
permissions:
contents: write
actions: write
pull-requests: write
steps:
# Credentials are persisted on purpose: the update script pushes through this checkout.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/finalize_metadata_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Second half of the metadata-update flow: github-actions-metadata-update.sh (see
# create_new_release_on_new_metadata_update.yml) opens a metadata-update/* PR against main and
# stops. Once that PR's required checks pass and it auto-merges, this workflow tags the merge
# commit, creates the GitHub release, and dispatches the NuGet publish.

name: "finalize_metadata_release"

on:
pull_request:
types: [closed]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: false

permissions:
contents: read

jobs:
finalize_metadata_release:
# user.login is checked, not just the branch name, so a human can't get a release cut by
# opening their own branch named metadata-update/* - only github-actions[bot] (the
# identity github-actions-metadata-update.sh pushes and opens the PR as) can trigger this.
if: >
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
startsWith(github.event.pull_request.head.ref, 'metadata-update/') &&
github.event.pull_request.user.login == 'github-actions[bot]'
runs-on: ubuntu-latest
permissions:
contents: write
actions: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Create release and dispatch publish
run: |
bash lib/finalize-metadata-release.sh \
"${{ github.event.pull_request.head.ref }}" \
"${{ github.event.pull_request.merge_commit_sha }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@ dotnet run -c Release --framework net10.0 -- --filter "*PhoneNumberWorkflowBench
- PRs trigger `build_and_run_unit_tests_linux.yml` (net10.0 only), `run_all_tests_and_upload_code_coverage.yml` (whole solution, every TFM, uploads to Codecov), and `codeql.yml`. Two more are path-filtered: `run_performance_tests.yml` (library or benchmark changes, with `post_performance_test_comment.yml` posting the result) and `build_and_run_demo_tests.yml` (demo changes). `scorecard.yml` runs on `main` and on branch-protection changes.
- **Restore is not locked.** There are no `packages.lock.json` files: every version is exact in `Directory.Packages.props`, so restore already resolves the same graph, and a lock file would only couple the build to the SDK's implicit package versions. `NuGetAudit` covers advisories; `nuget.config` pins the single source.
- `global.json` pins the SDK to 10.0.100 with `latestFeature` roll-forward, and CI verifies the build is reproducible. `EnablePackageValidation` is on for both packable projects, so a change that breaks the public surface — or that makes it inconsistent across TFMs — fails the build rather than shipping.
- Releases are tag-driven: a `vX.Y.Z` tag fires `publish_nuget.yml`, which packs both projects at the tag's version and pushes them, each with its `.snupkg`, to nuget.org via trusted publishing (GitHub OIDC, `NuGet/login`) — there is no API key secret. Metadata-bump tags are created by `create_new_release_on_new_metadata_update.yml`.
- Releases are tag-driven: a `vX.Y.Z` tag fires `publish_nuget.yml`, which packs both projects at the tag's version and pushes them, each with its `.snupkg`, to nuget.org via trusted publishing (GitHub OIDC, `NuGet/login`) — there is no API key secret. Metadata-bump tags are created by `finalize_metadata_release.yml` once the PR opened by `create_new_release_on_new_metadata_update.yml` merges.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for the build settings that will fail CI

## Metadata updates

The [`create_new_release_on_new_metadata_update`](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/create_new_release_on_new_metadata_update.yml) workflow runs daily and drives [`lib/github-actions-metadata-update.sh`](lib/github-actions-metadata-update.sh). When the latest `google/libphonenumber` release is newer than the published NuGet package, it copies the upstream `resources/`, regenerates `resources/locale/country_names.txt`, builds and tests, then commits, pushes and creates a matching GitHub release.
The [`create_new_release_on_new_metadata_update`](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/create_new_release_on_new_metadata_update.yml) workflow runs daily and drives [`lib/github-actions-metadata-update.sh`](lib/github-actions-metadata-update.sh). When the latest `google/libphonenumber` release is newer than the published NuGet package, it copies the upstream `resources/`, regenerates `resources/locale/country_names.txt`, then commits, pushes to a `metadata-update/*` branch and opens a PR against `main` with auto-merge enabled. Once that PR's required checks pass and it merges, [`finalize_metadata_release`](https://github.com/twcclegg/libphonenumber-csharp/actions/workflows/finalize_metadata_release.yml) tags the merge commit, creates a matching GitHub release, and dispatches the NuGet publish.

Before doing any of that it inspects the upstream diff and stops if it contains `.java` or `.proto` files, because changes to the Java sources may need porting by hand and an unattended metadata bump would silently skip them.

Expand Down
69 changes: 69 additions & 0 deletions lib/finalize-metadata-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#! /bin/bash
# Creates the GitHub release and dispatches the NuGet publish for a metadata-update PR that
# has just been merged - the second half of github-actions-metadata-update.sh, which only
# opens that PR and stops. Split across two scripts because they run from two different
# triggers (schedule/dispatch vs. pull_request closed) with nothing else in common: this
# half needs no upstream checkout, build, or test, just the tag and the merge commit.
set -euo pipefail

usage() {
cat <<'EOF'
Usage: finalize-metadata-release.sh <upstream-tag-or-branch> <release-commit> [github-token]

<upstream-tag-or-branch> is either a bare tag (v9.0.38) or a metadata-update branch name
(metadata-update/v9.0.38, as created by github-actions-metadata-update.sh) - the
metadata-update/ prefix is stripped if present, so the caller can pass the merged PR's head
ref directly.

The GitHub token may be supplied as the third positional argument or via the GITHUB_TOKEN
environment variable.

Environment variables:
GITHUB_TOKEN GitHub token used for the api calls.
GITHUB_REPOSITORY owner/name of the repository to release in. Set automatically by
GitHub Actions.
NUGET_PACKAGE_ID Package linked from the release notes (default libphonenumber-csharp).
NUGET_EXTENSIONS_PACKAGE_ID
Companion package linked from the release notes
(default <NUGET_PACKAGE_ID>.extensions).
UPSTREAM_REPOSITORY Repository the metadata came from (default google/libphonenumber).
PUBLISH_WORKFLOW Workflow dispatched to publish the release to nuget.org
(default publish_nuget.yml).
EOF
}

if [ $# -lt 2 ] || [ $# -gt 3 ]; then
usage >&2
exit 2
fi

TAG_OR_BRANCH="$1"
RELEASE_COMMIT="$2"
GITHUB_TOKEN="${3:-${GITHUB_TOKEN:-}}"

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=./github-release-helpers.sh
source "${SCRIPT_DIR}/github-release-helpers.sh"

if [ -z "${GITHUB_TOKEN}" ]; then
usage >&2
fail 2 "GitHub token required"
fi

UPSTREAM_GITHUB_RELEASE_TAG="${TAG_OR_BRANCH#metadata-update/}"

if [[ ! "${UPSTREAM_GITHUB_RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
fail 1 "unexpected upstream release tag: ${UPSTREAM_GITHUB_RELEASE_TAG}"
fi

if [[ ! "${RELEASE_COMMIT}" =~ ^[0-9a-f]{40}$ ]]; then
fail 1 "unexpected release commit: ${RELEASE_COMMIT}"
fi

: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY required}"

createRelease "${GITHUB_REPOSITORY}" "${UPSTREAM_GITHUB_RELEASE_TAG}" "${RELEASE_COMMIT}"
log "created release ${UPSTREAM_GITHUB_RELEASE_TAG} at ${RELEASE_COMMIT}"

dispatchPublish "${GITHUB_REPOSITORY}" "${UPSTREAM_GITHUB_RELEASE_TAG}"
log "dispatched ${PUBLISH_WORKFLOW} for ${UPSTREAM_GITHUB_RELEASE_TAG}"
Loading