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
37 changes: 37 additions & 0 deletions .github/RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Releasing

## One-time signing setup

Create a service account in the Unity organization that should sign the package.
Give it the **Package Manager Package Signer** organization role, then add these
repository secrets:

- `UPM_SERVICE_ACCOUNT_KEY_ID`
- `UPM_SERVICE_ACCOUNT_KEY_SECRET`
- `UPM_ORG_ID`

See [Unity's UPM CLI setup](https://docs.unity3d.com/6000.6/Documentation/Manual/upm-cli-install.html#prereqs).
The existing `UNITY_EMAIL` and `UNITY_PASSWORD` secrets remain the credentials
for the Editor license used by the example build and `.unitypackage` export.

Before the next version tag is created, merge these settings into the package's
[OpenUPM metadata](https://github.com/openupm/openupm/blob/master/data/packages/com.mackysoft.serializereference-extensions.yml):

```yaml
trackingMode: githubRelease
githubReleaseAssetName: com.mackysoft.serializereference-extensions-
```

This order prevents OpenUPM from publishing an unsigned archive packed from Git.
Existing published versions remain unchanged. See [OpenUPM's signing workflow](https://openupm.com/docs/signing-upm-packages).

## Release a version

1. Run **Release** from a branch with the new SemVer tag. It updates `package.json`,
builds the example, exports the `.unitypackage`, and signs the UPM `.tgz` from
the same commit. Signing must succeed before the tag and draft Release are created.
2. Review the draft's release notes and attached packages, then publish it.
3. The **Release / publish-openupm** job requests publication through GitHub OIDC
and checks that OpenUPM reports the version as signed. No OpenUPM token is needed.

Keep the signed `.tgz` attached to the public Release so OpenUPM can retrieve it.
44 changes: 44 additions & 0 deletions .github/scripts/sign-upm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail

package_dir="${1:?Expected package directory}"
destination="${2:?Expected output directory}"
: "${VERSION:?Expected release version}"
: "${UPM_ORG_ID:?Expected signing organization ID}"

package_name="$(jq -er '.name' "$package_dir/package.json")"
package_version="$(jq -er '.version' "$package_dir/package.json")"
if [[ "$package_version" != "$VERSION" ]]; then
echo "::error::UPM package version does not match the release version."
exit 1
fi

upm pack "$package_dir" --organization-id "$UPM_ORG_ID" --destination "$destination"

shopt -s nullglob
archives=("$destination"/*.tgz "$destination"/*.tar.gz)
if [[ "${#archives[@]}" != 1 ]]; then
echo "::error::Expected exactly one signed UPM archive."
exit 1
fi
archive="${archives[0]}"

if ! tar -xOzf "$archive" package/package.json | jq --exit-status \
--arg name "$package_name" --arg version "$VERSION" \
'.name == $name and .version == $version' > /dev/null; then
echo "::error::Signed UPM archive has an unexpected package name or version."
exit 1
fi

signature_size="$(tar -xOzf "$archive" package/.attestation.p7m | wc -c)"
if [[ "$signature_size" -eq 0 ]]; then
echo "::error::UPM archive has no signing attestation."
exit 1
fi

# Rename only; repacking would change the archive produced by Unity's signer.
release_archive="$destination/$package_name-$VERSION.tgz"
if [[ "$archive" != "$release_archive" ]]; then
mv -- "$archive" "$release_archive"
fi
printf 'Signed UPM package: %s\n' "$release_archive"
106 changes: 102 additions & 4 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ on:
description: Package version and Git tag (for example 1.7.1).
required: true
type: string
release:
types: [published]

concurrency:
group: release-${{ github.ref }}
Expand All @@ -17,13 +19,40 @@ permissions:

jobs:
prepare:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-24.04
env:
TARGET_FILE: ./Assets/MackySoft/MackySoft.SerializeReferenceExtensions/package.json
outputs:
sha: ${{ steps.metadata.outputs.sha }}
version: ${{ steps.metadata.outputs.version }}
steps:
- name: Check signing credentials
env:
UPM_SERVICE_ACCOUNT_KEY_ID: ${{ secrets.UPM_SERVICE_ACCOUNT_KEY_ID }}
UPM_SERVICE_ACCOUNT_KEY_SECRET: ${{ secrets.UPM_SERVICE_ACCOUNT_KEY_SECRET }}
UPM_ORG_ID: ${{ secrets.UPM_ORG_ID }}
run: |
for setting in UPM_SERVICE_ACCOUNT_KEY_ID UPM_SERVICE_ACCOUNT_KEY_SECRET UPM_ORG_ID; do
if [[ -z "${!setting}" ]]; then
echo "::error::Repository secret $setting is required for signed OpenUPM releases."
exit 1
fi
done

# The Git tracker must stop packing unsigned source before a new tag exists.
- name: Require OpenUPM release asset tracking
run: |
curl --fail --silent --show-error --location \
https://raw.githubusercontent.com/openupm/openupm/master/data/packages/com.mackysoft.serializereference-extensions.yml \
--output "$RUNNER_TEMP/openupm-package.yml"
if ! yq --exit-status \
'.trackingMode == "githubRelease" and .githubReleaseAssetName == "com.mackysoft.serializereference-extensions-"' \
"$RUNNER_TEMP/openupm-package.yml" > /dev/null; then
echo "::error::Merge the OpenUPM metadata change to signed GitHub Release assets before releasing."
exit 1
fi

- name: Create release app token
id: release_app
uses: actions/create-github-app-token@v3
Expand Down Expand Up @@ -55,6 +84,42 @@ jobs:
RELEASE_REF: ${{ github.ref }}
run: bash .github/scripts/release.sh prepare

sign-upm:
needs: prepare
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.sha }}

- name: Install Unity UPM CLI
run: |
curl --fail --silent --show-error --location \
https://cdn.packages.unity.com/upm-cli/install.sh \
--output "$RUNNER_TEMP/install-upm.sh"
bash "$RUNNER_TEMP/install-upm.sh" v9.35.0
echo "$HOME/.upm/bin" >> "$GITHUB_PATH"
"$HOME/.upm/bin/upm" --version

- name: Pack and sign UPM package
env:
VERSION: ${{ needs.prepare.outputs.version }}
UPM_SERVICE_ACCOUNT_KEY_ID: ${{ secrets.UPM_SERVICE_ACCOUNT_KEY_ID }}
UPM_SERVICE_ACCOUNT_KEY_SECRET: ${{ secrets.UPM_SERVICE_ACCOUNT_KEY_SECRET }}
UPM_ORG_ID: ${{ secrets.UPM_ORG_ID }}
run: |
bash .github/scripts/sign-upm.sh \
Assets/MackySoft/MackySoft.SerializeReferenceExtensions \
"$RUNNER_TEMP/signed-upm"

- name: Upload signed UPM package
uses: actions/upload-artifact@v7
with:
name: Signed-UPM
path: ${{ runner.temp }}/signed-upm/*.tgz
if-no-files-found: error

build:
needs: prepare
runs-on: ubuntu-22.04
Expand Down Expand Up @@ -131,7 +196,7 @@ jobs:
path: Logs/

create-release:
needs: [prepare, build]
needs: [prepare, build, sign-upm]
runs-on: ubuntu-24.04
steps:
# Each job owns a short-lived token; prepare's token is revoked before build.
Expand Down Expand Up @@ -161,8 +226,14 @@ jobs:
with:
name: SerializeReference-Extensions.unitypackage
path: SerializeReference-Extensions.unitypackage

# Create release and upload unitypackage.

- uses: actions/download-artifact@v8
with:
name: Signed-UPM
path: Signed-UPM

# Keep the release draft until its notes are ready. Both packages must be
# attached before publication makes the signed tarball visible to OpenUPM.
- uses: softprops/action-gh-release@v3
with:
token: ${{ steps.release_app.outputs.token }}
Expand All @@ -171,4 +242,31 @@ jobs:
target_commitish: ${{ needs.prepare.outputs.sha }}
draft: true
prerelease: false
files: SerializeReference-Extensions.unitypackage/SerializeReference-Extensions.unitypackage
fail_on_unmatched_files: true
files: |
SerializeReference-Extensions.unitypackage/SerializeReference-Extensions.unitypackage
Signed-UPM/*.tgz

publish-openupm:
if: github.event_name == 'release'
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
id-token: write
steps:
- name: Publish signed release to OpenUPM
id: openupm
uses: openupm/openupm-action@v1
with:
package: com.mackysoft.serializereference-extensions
tag: ${{ github.event.release.tag_name }}

- name: Require a signed OpenUPM version
env:
SIGNED: ${{ steps.openupm.outputs.signed }}
run: |
if [[ "$SIGNED" != true ]]; then
echo "::error::OpenUPM did not publish the signed package."
exit 1
fi