Skip to content

chore: add support for separate module versioning to CI #426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7f130f2
Add support for separate module versioning
matifali Apr 10, 2025
b0069e2
Refactor: Combine version scripts into one versatile tool
matifali Apr 10, 2025
0611ef2
Update CI workflow to use new modules-version.sh tool
matifali Apr 10, 2025
2269e58
Improve CI version check for different workflows
matifali Apr 10, 2025
c6cffc8
Simplify CI version checking
matifali Apr 10, 2025
9bc5419
Remove check-version.sh as it's been replaced by modules-version.sh
matifali Apr 10, 2025
9b89ef4
Improve CI workflow for version checks
matifali Apr 10, 2025
038ef33
Refactor CI workflow for version checks
matifali Apr 10, 2025
0b7a602
Update CONTRIBUTING.md to clarify release process steps
matifali Apr 10, 2025
7d481e8
`fmt`
matifali Apr 10, 2025
d5ecb98
refactor: simplify modules-version script with dry-run and bump-only …
matifali Apr 14, 2025
7c95af8
chore: implement tag-first workflow with automated PRs
matifali Apr 14, 2025
324a383
feat: implement GitHub Action for automatic README updates when tags …
matifali Apr 14, 2025
96657d7
feat: add auto-approve and auto-merge for PR workflow
matifali Apr 14, 2025
1a1dd69
refactor: reuse modules-version.sh in GitHub Action
matifali Apr 14, 2025
4dbe948
feat: add --version parameter for setting exact versions
matifali Apr 14, 2025
bd1703b
refactor: remove environment variable support
matifali Apr 14, 2025
6f2fe83
docs: update release process documentation
matifali Apr 14, 2025
2998721
feat: split scripts into release.sh and update-version.sh
matifali Apr 14, 2025
d63b332
refactor: simplify scripts and workflows
matifali Apr 14, 2025
a6984fb
refactor: further simplify scripts to bare essentials
matifali Apr 14, 2025
0c4f954
feat: use GitHub CLI for PR management
matifali Apr 14, 2025
fb3ae6f
refactor: use cdrci as PR author and auto-approve action
matifali Apr 14, 2025
6d925ca
refactor: remove unnecessary comments and streamline code
matifali Apr 14, 2025
bdd8dba
docs: add helpful comments to scripts and workflow
matifali Apr 14, 2025
3f0615b
fmt
matifali Apr 14, 2025
8200f2d
Remove release script from package.json
matifali Apr 14, 2025
70e5da7
Remove trailing comma in package.json
matifali Apr 14, 2025
e4e5c73
Remove commented-out version check steps in CI
matifali Apr 14, 2025
57fc91f
Simplify and improve GitHub workflow and release scripts
matifali Apr 15, 2025
68f5396
Rename update-version.sh to update_version.sh and improve docs
matifali Apr 15, 2025
8aa9154
Review: refactor scripts
mafredri Apr 17, 2025
974f3f6
Delete .github/workflows/update-readme-version.yaml
matifali Apr 22, 2025
2086134
Update CONTRIBUTING.md
matifali Apr 22, 2025
6b1b617
Merge branch 'main' into separate-versioning
matifali Apr 22, 2025
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
18 changes: 7 additions & 11 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,12 @@ jobs:
uses: crate-ci/[email protected]
- name: Lint
run: bun lint
- name: Check version

# Only run version checks on PRs
- name: Verify module versions
if: github.event_name == 'pull_request'
shell: bash
run: |
# check for version changes
./update-version.sh
# Check if any changes were made in README.md files
if [[ -n "$(git status --porcelain -- '**/README.md')" ]]; then
echo "Version mismatch detected. Please run ./update-version.sh and commit the updated README.md files."
git diff -- '**/README.md'
exit 1
else
echo "No version mismatch detected. All versions are up to date."
fi
echo "📌 Checking module versions for changed modules..."
./modules-version.sh
echo "ℹ️ Remember to update versions with ./modules-version.sh if needed."
20 changes: 9 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,18 @@ module "example" {
> [!WARNING]
> When creating a new release, make sure that your new version number is fully accurate. If a version number is incorrect or does not exist, we may end up serving incorrect/old data for our various tools and providers.

Much of our release process is automated. To cut a new release:
The release process is automated and follows these steps:

1. Navigate to [GitHub's Releases page](https://github.com/coder/modules/releases)
2. Click "Draft a new release"
3. Click the "Choose a tag" button and type a new release number in the format `v<major>.<minor>.<patch>` (e.g., `v1.18.0`). Then click "Create new tag".
4. Click the "Generate release notes" button, and clean up the resulting README. Be sure to remove any notes that would not be relevant to end-users (e.g., bumping dependencies).
5. Once everything looks good, click the "Publish release" button.
1. Create a PR with your changes
2. Update the version in the module's README.md file with the next version.
3. The CI will automatically check that the version in README.md is updated
4. Once the PR is approved and merged to main:
- The changes will be available on the main branch
- You can then push a new tag for the module from main
- The tag should follow the format: `release/module-name/v1.0.0`

Once the release has been cut, a script will run to check whether there are any modules that will require that the new release number be published to Terraform. If there are any, a new pull request will automatically be generated. Be sure to approve this PR and merge it into the `main` branch.

Following that, our automated processes will handle publishing new data for [`registry.coder.com`](https://github.com/coder/registry.coder.com/):

1. Publishing new versions to Coder's [Terraform Registry](https://registry.terraform.io/providers/coder/coder/latest)
2. Publishing new data to the [Coder Registry](https://registry.coder.com)
Following that, our automated processes will handle publishing new data to [`registry.coder.com`](https://registry.coder.com):

> [!NOTE]
> Some data in `registry.coder.com` is fetched on demand from the Module repo's main branch. This data should be updated almost immediately after a new release, but other changes will take some time to propagate.
278 changes: 278 additions & 0 deletions modules-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
#!/usr/bin/env bash

# modules-version: A versatile tool for managing module versions in the Coder modules repository
#
# This script handles module versioning with support for module-specific tags in the format:
# release/module-name/v1.0.0
#
# Features:
# - Check that README.md versions match module tags (CI-friendly)
# - Update module versions automatically or to specified versions
# - Support for module-specific tags and versioning
# - Create tags for new module versions
#
# Usage:
# ./modules-version.sh # Check all modules with changes
# ./modules-version.sh module-name # Check or update a specific module
# ./modules-version.sh --check # Check-only mode (no changes)
# ./modules-version.sh --set-version=1.2.3 module-name # Set specific version
# ./modules-version.sh --bump=patch module-name # Bump patch version (default)
# ./modules-version.sh --bump=minor module-name # Bump minor version
# ./modules-version.sh --bump=major module-name # Bump major version
# ./modules-version.sh --tag module-name # Create git tag after updating

set -euo pipefail

# Default values
CHECK_ONLY=false
MODULE_NAME=""
VERSION_ACTION=""
VERSION_TYPE="patch"
NEW_VERSION=""
CREATE_TAG=false
SHOW_HELP=false

# Function to show usage
show_help() {
cat << EOF
modules-version: A versatile tool for managing module versions

Usage:
./modules-version.sh [options] [module-name]

Options:
--check Check mode - verify versions without making changes
--set-version=X.Y.Z Set version to specific number
--bump=patch|minor|major Bump version (patch is default)
--tag Create git tag after updating version
--help Show this help message

Examples:
./modules-version.sh # Check all modules with changes
./modules-version.sh --check # Check-only mode (CI-friendly)
./modules-version.sh module-name # Check or update a specific module
./modules-version.sh --set-version=1.2.3 module-name # Set specific version
./modules-version.sh --bump=minor --tag module-name # Bump minor version and create tag
EOF
exit 0
}

# Parse arguments
for arg in "$@"; do
if [[ "$arg" == "--check" ]]; then
CHECK_ONLY=true
elif [[ "$arg" == "--tag" ]]; then
CREATE_TAG=true
elif [[ "$arg" == "--help" ]]; then
SHOW_HELP=true
elif [[ "$arg" == --set-version=* ]]; then
VERSION_ACTION="set"
NEW_VERSION="${arg#*=}"
elif [[ "$arg" == --bump=* ]]; then
VERSION_ACTION="bump"
VERSION_TYPE="${arg#*=}"
if [[ "$VERSION_TYPE" != "patch" && "$VERSION_TYPE" != "minor" && "$VERSION_TYPE" != "major" ]]; then
echo "Error: Version bump type must be 'patch', 'minor', or 'major'"
exit 1
fi
elif [[ "$arg" != --* ]]; then
MODULE_NAME="$arg"
fi
done

# Show help if requested
if [[ "$SHOW_HELP" == "true" ]]; then
show_help
fi

# Report mode
if [[ "$CHECK_ONLY" == "true" ]]; then
echo "Running in check-only mode (no changes will be made)"
fi

if [[ -n "$MODULE_NAME" ]]; then
echo "Working with module: $MODULE_NAME"
fi

if [[ "$VERSION_ACTION" == "set" ]]; then
echo "Will set version to: $NEW_VERSION"
elif [[ "$VERSION_ACTION" == "bump" ]]; then
echo "Will bump $VERSION_TYPE version"
fi

# Function to extract version from README.md
extract_readme_version() {
local file="$1"
grep -o 'version *= *"[0-9]\+\.[0-9]\+\.[0-9]\+"' "$file" | head -1 | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' || echo "0.0.0"
}

# Function to bump version according to semantic versioning
bump_version() {
local version="$1"
local type="$2"

IFS='.' read -r major minor patch <<< "$version"

if [[ "$type" == "major" ]]; then
echo "$((major + 1)).0.0"
elif [[ "$type" == "minor" ]]; then
echo "$major.$((minor + 1)).0"
else # Default to patch
echo "$major.$minor.$((patch + 1))"
fi
}

# Function to update version in README.md
update_version_in_readme() {
local file="$1"
local new_version="$2"
local tmpfile

tmpfile=$(mktemp /tmp/tempfile.XXXXXX)
awk -v tag="$new_version" '
BEGIN { in_code_block = 0; in_nested_block = 0 }
{
# Detect the start and end of Markdown code blocks.
if ($0 ~ /^```/) {
in_code_block = !in_code_block
# Reset nested block tracking when exiting a code block.
if (!in_code_block) {
in_nested_block = 0
}
}

# Handle nested blocks within a code block.
if (in_code_block) {
# Detect the start of a nested block (skipping "module" blocks).
if ($0 ~ /{/ && !($1 == "module" || $1 ~ /^[a-zA-Z0-9_]+$/)) {
in_nested_block++
}

# Detect the end of a nested block.
if ($0 ~ /}/ && in_nested_block > 0) {
in_nested_block--
}

# Update "version" only if not in a nested block.
if (!in_nested_block && $1 == "version" && $2 == "=") {
sub(/"[^"]*"/, "\"" tag "\"")
}
}

print
}
' "$file" > "$tmpfile" && mv "$tmpfile" "$file"
}

# List directories with changes that are not README.md or test files
if [[ -n "$MODULE_NAME" ]]; then
# If a specific module is provided, only check that directory
if [[ ! -d "$MODULE_NAME" ]]; then
echo "Error: Module directory '$MODULE_NAME' not found."
exit 1
fi
mapfile -t changed_dirs < <(echo "$MODULE_NAME")
else
# Get the latest tag for the repository
latest_repo_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")

# Find directories with changes since the latest tag
mapfile -t changed_dirs < <(git diff --name-only "$latest_repo_tag" -- ':!**/README.md' ':!**/*.test.ts' | xargs dirname | grep -v '^\.' | sort -u)

echo "Directories with changes: ${changed_dirs[*]}"
fi

EXIT_CODE=0

# Iterate over directories and process versions
for dir in "${changed_dirs[@]}"; do
if [[ -f "$dir/README.md" ]]; then
# Get the module name from the directory
module_name=$(basename "$dir")

# Get version from README.md
readme_version=$(extract_readme_version "$dir/README.md")

# Check for module-specific tag with format: release/module-name/v1.0.0
latest_module_tag=$(git tag -l "release/$module_name/v*" | sort -V | tail -n 1)
if [[ -n "$latest_module_tag" ]]; then
latest_tag_version=$(echo "$latest_module_tag" | sed 's|release/'"$module_name"'/v||')
echo "Module $module_name: Latest tag=$latest_tag_version, README version=$readme_version"
else
echo "Module $module_name: No module-specific tags found, README version=$readme_version"
fi

# Update version if requested and not in check-only mode
if [[ "$CHECK_ONLY" == "false" && ("$VERSION_ACTION" == "set" || "$VERSION_ACTION" == "bump") ]]; then
# Determine the new version
if [[ "$VERSION_ACTION" == "set" ]]; then
target_version="$NEW_VERSION"
else # bump
# Start with the latest tag version if available, otherwise use README version
base_version=""
if [[ -n "$latest_module_tag" ]]; then
base_version="$latest_tag_version"
else
base_version="$readme_version"
fi
target_version=$(bump_version "$base_version" "$VERSION_TYPE")
fi

# Update README if needed
if [[ "$readme_version" == "$target_version" ]]; then
echo "Version in $dir/README.md is already set to $target_version"
else
echo "Updating version in $dir/README.md from $readme_version to $target_version"
update_version_in_readme "$dir/README.md" "$target_version"

# Create tag if requested
if [[ "$CREATE_TAG" == "true" ]]; then
tag_name="release/$module_name/v$target_version"
echo "Creating tag: $tag_name"
git tag "$tag_name"
else
echo "To tag this release, use: git tag release/$module_name/v$target_version"
fi
fi
continue
fi

# Only do version checking if we're not updating
if [[ "$VERSION_ACTION" != "set" && "$VERSION_ACTION" != "bump" ]]; then
# Get all tags for the module
module_tags=$(git tag -l "release/$module_name/v*" | sort -V)

# Skip modules that don't have module-specific tags
if [[ -z "$module_tags" ]]; then
echo "Skipping version check for $dir: No module-specific tags found"
continue
fi

# Check if README version matches any of the module's tags
version_found=false
for tag in $module_tags; do
tag_version=$(echo "$tag" | sed 's|release/'"$module_name"'/v||')
if [[ "$readme_version" == "$tag_version" ]]; then
version_found=true
echo "✅ Version in $dir/README.md ($readme_version) matches tag $tag"
break
fi
done

if [[ "$version_found" == "false" ]]; then
echo "❌ ERROR: Version in $dir/README.md ($readme_version) does not match any existing tag"
echo "Available tags:"
echo "$module_tags"
EXIT_CODE=1
fi
fi
fi
done

if [[ $EXIT_CODE -eq 0 ]]; then
echo "✅ All checked modules have valid versions"
else
echo "❌ Some modules have version mismatches - see errors above"
fi

exit $EXIT_CODE
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"fmt": "bun x prettier -w **/*.sh .sample/run.sh new.sh **/*.ts **/*.md *.md && terraform fmt **/*.tf .sample/main.tf",
"fmt:ci": "bun x prettier --check **/*.sh .sample/run.sh new.sh **/*.ts **/*.md *.md && terraform fmt -check **/*.tf .sample/main.tf",
"lint": "bun run lint.ts && ./terraform_validate.sh",
"update-version": "./update-version.sh"
"version": "./modules-version.sh",
"version:check": "./modules-version.sh --check"
},
"devDependencies": {
"bun-types": "^1.1.23",
Expand Down
Loading
Loading