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
65 changes: 64 additions & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contributing

Thank you for your interest in contributing to the Response Verification package for the Internet Computer.
Thank you for your interest in contributing to ICP CLI Recipes for the Internet Computer.
By participating in this project, you agree to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md).

As a member of the community, you are invited and encouraged to contribute by submitting issues, offering suggestions for improvements, adding review comments to existing pull requests, or creating new pull requests to fix issues.
Expand Down Expand Up @@ -81,3 +81,66 @@ If you want to submit a pull request to fix an issue or add a feature, here's a
## Development

Refer to the documentation in the [Recipe Authoring Guide](../docs/recipe-authoring.md) for more information on how to develop recipes.

## Releases

Releases are managed automatically through GitHub Actions. Each recipe is versioned independently.

### For Maintainers

To create a new release for a recipe:

1. **Create and push a version tag** for the specific recipe:
```bash
# For a Rust recipe release
git tag rust-v4.0.0
git push origin rust-v4.0.0
```

2. **The workflow automatically**:
- Creates a GitHub release for `rust-v4.0.0` with changelog and release artifacts
- Updates the `rust-latest` tag to point to the same commit as `rust-v4.0.0`
- Creates/updates the `rust-latest` release with:
- The same changelog as `rust-v4.0.0`
- The same release artifacts (but named with "latest")
- A fresh timestamp showing when it was updated

### Release Naming Convention

Tags follow the pattern `<recipe-name>-v<version>`:
- `rust-v4.0.0` - Rust recipe version 4.0.0
- `motoko-v5.0.0` - Motoko recipe version 5.0.0
- `asset-canister-v3.0.0` - Asset canister recipe version 3.0.0
- `prebuilt-v2.0.0` - Pre-built recipe version 2.0.0

### Release Notes

Release notes are automatically generated and include:
- All commits affecting the specific recipe directory since the previous version
- A link to the full changelog comparing the two versions
- Release artifacts (tar.gz, zip files, checksums)

### What is the `-latest` Release?

Each recipe has two types of releases:

1. **Versioned releases** (e.g., `rust-v4.0.0`):
- Permanent, immutable releases
- Pin to specific versions for stability
- Example: `rust-v4.0.0`, `rust-v3.0.0`, etc.

2. **Latest release** (e.g., `rust-latest`):
- Always points to the most recent version
- Updated automatically when a new version is released
- Same content as the newest versioned release, but with:
- Updated timestamp
- "latest" naming for artifacts
- Use this to always get the newest recipe version

### Viewing Releases

Each recipe has its own release history:
- [Rust releases](https://github.com/dfinity/icp-cli-recipes/releases?q=rust&expanded=true)
- [Motoko releases](https://github.com/dfinity/icp-cli-recipes/releases?q=motoko&expanded=true)
- [Pre-built releases](https://github.com/dfinity/icp-cli-recipes/releases?q=prebuilt&expanded=true)
- [Asset Canister releases](https://github.com/dfinity/icp-cli-recipes/releases?q=asset-canister&expanded=true)
111 changes: 103 additions & 8 deletions .github/workflows/release-recipe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0 # Fetch all history and tags

- name: Extract recipe and version from tag
id: tag_info
Expand All @@ -30,14 +32,14 @@ jobs:
VERSION=$(echo "$TAG" | sed 's/^.*-//')

# Handle latest tags specially
if [[ "$VERSION" == "latest" ]]; then
echo "recipe=$RECIPE" >> $GITHUB_OUTPUT
echo "version=latest" >> $GITHUB_OUTPUT
echo "is_latest=true" >> $GITHUB_OUTPUT
if [ "$VERSION" = "latest" ]; then
echo "recipe=$RECIPE" >> "$GITHUB_OUTPUT"
echo "version=latest" >> "$GITHUB_OUTPUT"
echo "is_latest=true" >> "$GITHUB_OUTPUT"
else
echo "recipe=$RECIPE" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "is_latest=false" >> $GITHUB_OUTPUT
echo "recipe=$RECIPE" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "is_latest=false" >> "$GITHUB_OUTPUT"
fi

- name: Package Recipe
Expand All @@ -56,12 +58,105 @@ jobs:
cd release
sha256sum * > checksums.txt

- name: Find previous recipe version
id: prev_version
run: |
RECIPE="${{ steps.tag_info.outputs.recipe }}"
CURRENT_TAG="${GITHUB_REF#refs/tags/}"
IS_LATEST="${{ steps.tag_info.outputs.is_latest }}"

if [ "$IS_LATEST" = "true" ]; then
# For -latest tags: find the versioned tag pointing to the same commit
CURRENT_COMMIT=$(git rev-parse HEAD)
CURRENT_VERSION_TAG=$(git tag --sort=-version:refname -l "${RECIPE}-v*" | while read -r tag; do
if [ "$(git rev-parse "$tag")" = "$CURRENT_COMMIT" ]; then
echo "$tag"
break
fi
done)

if [ -z "$CURRENT_VERSION_TAG" ]; then
echo "::error::No versioned tag found for this commit. A -latest tag must have a corresponding versioned tag (e.g., ${RECIPE}-v1.0.0) on the same commit."
exit 1
else
echo "Found versioned tag for this commit: ${CURRENT_VERSION_TAG}"
# Find the previous version before the current versioned tag
PREV_TAG=$(git tag --sort=-version:refname -l "${RECIPE}-v*" | grep -v "${CURRENT_VERSION_TAG}" | head -n 1)
echo "previous_tag=${PREV_TAG}" >> "$GITHUB_OUTPUT"
echo "compare_tag=${CURRENT_VERSION_TAG}" >> "$GITHUB_OUTPUT"
fi
else
# For versioned tags: find the previous version tag
PREV_TAG=$(git tag --sort=-version:refname -l "${RECIPE}-v*" | grep -v "${CURRENT_TAG}" | head -n 1)

if [ -z "$PREV_TAG" ]; then
echo "No previous version found for ${RECIPE}"
echo "previous_tag=" >> "$GITHUB_OUTPUT"
else
echo "Found previous tag: ${PREV_TAG}"
echo "previous_tag=${PREV_TAG}" >> "$GITHUB_OUTPUT"
fi
echo "compare_tag=${CURRENT_TAG}" >> "$GITHUB_OUTPUT"
fi

- name: Generate Release Notes Body
id: release_notes
run: |
RECIPE="${{ steps.tag_info.outputs.recipe }}"
PREV_TAG="${{ steps.prev_version.outputs.previous_tag }}"
COMPARE_TAG="${{ steps.prev_version.outputs.compare_tag }}"

if [ -n "$PREV_TAG" ] && [ -n "$COMPARE_TAG" ]; then
echo "## What's Changed" > release_notes.md
echo "" >> release_notes.md

# Get commits that affect this recipe's directory
COMMITS=$(git log ${PREV_TAG}..${COMPARE_TAG} --pretty=format:"* %s (%h)" --no-merges -- recipes/${RECIPE}/)

if [ -n "$COMMITS" ]; then
echo "Changes since ${PREV_TAG}:" >> release_notes.md
echo "" >> release_notes.md
echo "$COMMITS" >> release_notes.md
echo "" >> release_notes.md
else
echo "No changes to the ${RECIPE} recipe files." >> release_notes.md
echo "" >> release_notes.md
fi

echo "" >> release_notes.md
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...${COMPARE_TAG}" >> release_notes.md
else
echo "## What's Changed" > release_notes.md
echo "" >> release_notes.md
echo "Initial release of the ${RECIPE} recipe." >> release_notes.md
fi

- name: Create Release
uses: softprops/action-gh-release@aec2ec56f94eb8180ceec724245f64ef008b89f5 # v2.4.0
with:
tag_name: ${{ steps.tag_info.outputs.recipe }}-${{ steps.tag_info.outputs.version }}
name: ${{ steps.tag_info.outputs.recipe }} ${{ steps.tag_info.outputs.version }}
files: release/*
generate_release_notes: true
body_path: release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Update latest tag
Copy link
Copy Markdown
Collaborator

@raymondk raymondk Jan 20, 2026

Choose a reason for hiding this comment

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

This is automatically pushing a $RECIPE-latest tag
When the tag is pushed doesn't that throw off the pipeline for latest because it is going to compare to whatever the highest version tag is (which will likely be the same) and not generate the proper release notes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I am not entirely sure what your concern is, so let me address both possibilities:

  1. if you're worried about the -latest release having empty/wrong release notes:

    • you're right - the original code would compare rust-v5.0.0..rust-latest which are the same commit, resulting in empty release notes.
    • I fixed this now by finding the versioned tag on the same commit (rust-v5.0.0), then comparing against its predecessor (rust-v4.0.0..rust-v5.0.0). now both releases should get the same correct release notes.
  2. if you're worried about -latest tags interfering with versioned releases:

    • this should already be safe - the previous tag search uses the pattern ${RECIPE}-v*, which only matches versioned tags like rust-v5.0.0 and never matches rust-latest.

these are the scenarios that should be covered now:

Scenario PREV_TAG COMPARE_TAG Result
1. Push rust-v4.0.0 rust-v3.0.0 rust-v4.0.0 Changelog ✓
2. rust-latest triggered (same commit as rust-v4.0.0) rust-v3.0.0 rust-v4.0.0 Changelog ✓
3. Push newrecipe-v1.0.0 (first ever version) (empty) newrecipe-v1.0.0 Initial release ✓
4. newrecipe-latest triggered (same commit as v1.0.0) (empty) newrecipe-v1.0.0 Initial release ✓
5. Push newrecipe-latest without versioned tag - - Fails ✓

let me know if you had a different concern in mind.

if: steps.tag_info.outputs.is_latest == 'false'
run: |
RECIPE="${{ steps.tag_info.outputs.recipe }}"
CURRENT_TAG="${GITHUB_REF#refs/tags/}"

echo "Updating ${RECIPE}-latest to point to ${CURRENT_TAG}"

# Delete the old latest tag locally and remotely
git tag -d ${RECIPE}-latest 2>/dev/null || true
git push origin :refs/tags/${RECIPE}-latest 2>/dev/null || true

# Create new latest tag pointing to current commit
git tag ${RECIPE}-latest

# Push the new latest tag
git push origin ${RECIPE}-latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}