Compare commits

..

2 Commits

Author SHA1 Message Date
DevCats 17705fac52 Merge branch 'main' into cat/version-label-workflow 2025-06-12 21:01:31 -05:00
DevelopmentCats 29743bf0da feat: add GitHub Actions workflow for automatic version bumping
This commit introduces a new workflow that triggers on pull request labeling to automatically detect version bumps (patch, minor, major) for Coder modules. It processes modified modules, updates their version references in README files, and commits the changes. Additionally, it comments on the pull request with a summary of the version bumps and any modules without existing git tags.
2025-06-05 01:08:00 +00:00
4 changed files with 191 additions and 178 deletions
+179 -72
View File
@@ -3,8 +3,6 @@ name: Version Bump
on:
pull_request:
types: [labeled]
paths:
- "registry/**/modules/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -15,9 +13,8 @@ jobs:
if: github.event.label.name == 'version:patch' || github.event.label.name == 'version:minor' || github.event.label.name == 'version:major'
runs-on: ubuntu-latest
permissions:
contents: read
contents: write
pull-requests: write
issues: write
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -25,85 +22,195 @@ jobs:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Extract bump type from label
id: bump-type
- name: Setup Git
run: |
case "${{ github.event.label.name }}" in
"version:patch")
echo "type=patch" >> $GITHUB_OUTPUT
;;
"version:minor")
echo "type=minor" >> $GITHUB_OUTPUT
;;
"version:major")
echo "type=major" >> $GITHUB_OUTPUT
;;
*)
echo "Invalid version label: ${{ github.event.label.name }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Detect version bump type
id: version-type
run: |
if [[ "${{ github.event.label.name }}" == "version:patch" ]]; then
echo "bump_type=patch" >> $GITHUB_OUTPUT
elif [[ "${{ github.event.label.name }}" == "version:minor" ]]; then
echo "bump_type=minor" >> $GITHUB_OUTPUT
elif [[ "${{ github.event.label.name }}" == "version:major" ]]; then
echo "bump_type=major" >> $GITHUB_OUTPUT
else
echo "Invalid version label: ${{ github.event.label.name }}"
exit 1
fi
- name: Detect modified modules
id: detect-modules
run: |
CHANGED_FILES=$(git diff --name-only origin/main...HEAD)
MODULES=$(echo "$CHANGED_FILES" | grep -E '^registry/[^/]+/modules/[^/]+/' | cut -d'/' -f1-4 | sort -u)
if [ -z "$MODULES" ]; then
echo "No modules detected in changes"
exit 1
fi
echo "Found modules:"
echo "$MODULES"
echo "modules<<EOF" >> $GITHUB_OUTPUT
echo "$MODULES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Process version bumps
id: process-bumps
run: |
BUMP_TYPE="${{ steps.version-type.outputs.bump_type }}"
BUMPED_MODULES=""
UPDATED_READMES=""
UNTAGGED_MODULES=""
while IFS= read -r module_path; do
if [ -z "$module_path" ]; then continue; fi
NAMESPACE=$(echo "$module_path" | cut -d'/' -f2)
MODULE_NAME=$(echo "$module_path" | cut -d'/' -f4)
echo "Processing: $NAMESPACE/$MODULE_NAME"
LATEST_TAG=$(git tag -l "release/${NAMESPACE}/${MODULE_NAME}/v*" | sort -V | tail -1)
README_PATH="$module_path/README.md"
if [ -z "$LATEST_TAG" ]; then
# No tag found, check if README has version references
if [ -f "$README_PATH" ] && grep -q 'version\s*=\s*"' "$README_PATH"; then
# Extract version from README
README_VERSION=$(grep 'version\s*=\s*"' "$README_PATH" | head -1 | sed 's/.*version\s*=\s*"\([^"]*\)".*/\1/')
echo "No git tag found, but README shows version: $README_VERSION"
# Validate extracted version format
if ! [[ "$README_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "❌ Invalid README version format: '$README_VERSION'. Expected X.Y.Z format."
echo "Starting from v1.0.0 instead"
CURRENT_VERSION="1.0.0"
else
CURRENT_VERSION="$README_VERSION"
UNTAGGED_MODULES="$UNTAGGED_MODULES\n- $NAMESPACE/$MODULE_NAME (README: v$README_VERSION)"
fi
else
echo "No existing tags or version references found for $NAMESPACE/$MODULE_NAME, starting from v1.0.0"
CURRENT_VERSION="1.0.0"
fi
else
CURRENT_VERSION=$(echo "$LATEST_TAG" | sed 's/.*\/v//')
echo "Found git tag: $LATEST_TAG (v$CURRENT_VERSION)"
fi
echo "Current version: $CURRENT_VERSION"
# Validate version format (semantic versioning: X.Y.Z)
if ! [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "❌ Invalid version format: '$CURRENT_VERSION'. Expected X.Y.Z format."
exit 1
;;
esac
- name: Check version bump requirements
id: version-check
fi
IFS='.' read -r major minor patch <<< "$CURRENT_VERSION"
# Validate that components are numeric
if ! [[ "$major" =~ ^[0-9]+$ ]] || ! [[ "$minor" =~ ^[0-9]+$ ]] || ! [[ "$patch" =~ ^[0-9]+$ ]]; then
echo "❌ Version components must be numeric: major='$major' minor='$minor' patch='$patch'"
exit 1
fi
case "$BUMP_TYPE" in
"patch")
NEW_VERSION="$major.$minor.$((patch + 1))"
;;
"minor")
NEW_VERSION="$major.$((minor + 1)).0"
;;
"major")
NEW_VERSION="$((major + 1)).0.0"
;;
esac
echo "New version: $NEW_VERSION"
if [ -f "$README_PATH" ]; then
# Check if README contains version references for this specific module
MODULE_SOURCE="registry.coder.com/${NAMESPACE}/${MODULE_NAME}/coder"
if grep -q "source.*${MODULE_SOURCE}" "$README_PATH"; then
echo "Updating version references for $NAMESPACE/$MODULE_NAME in $README_PATH"
# Use awk to only update versions that follow the specific module source
awk -v module_source="$MODULE_SOURCE" -v new_version="$NEW_VERSION" '
/source.*=.*/ {
if ($0 ~ module_source) {
in_target_module = 1
} else {
in_target_module = 0
}
}
/version.*=.*"/ {
if (in_target_module) {
gsub(/version[[:space:]]*=[[:space:]]*"[^"]*"/, "version = \"" new_version "\"")
in_target_module = 0
}
}
{ print }
' "$README_PATH" > "${README_PATH}.tmp" && mv "${README_PATH}.tmp" "$README_PATH"
UPDATED_READMES="$UPDATED_READMES\n- $NAMESPACE/$MODULE_NAME"
elif grep -q 'version\s*=\s*"' "$README_PATH"; then
echo "⚠️ Found version references but no module source match for $NAMESPACE/$MODULE_NAME"
fi
fi
BUMPED_MODULES="$BUMPED_MODULES\n- $NAMESPACE/$MODULE_NAME: v$CURRENT_VERSION → v$NEW_VERSION"
done <<< "${{ steps.detect-modules.outputs.modules }}"
echo "bumped_modules<<EOF" >> $GITHUB_OUTPUT
echo -e "$BUMPED_MODULES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "updated_readmes<<EOF" >> $GITHUB_OUTPUT
echo -e "$UPDATED_READMES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "untagged_modules<<EOF" >> $GITHUB_OUTPUT
echo -e "$UNTAGGED_MODULES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Commit changes
run: |
output_file=$(mktemp)
if ./.github/scripts/version-bump.sh "${{ steps.bump-type.outputs.type }}" origin/main > "$output_file" 2>&1; then
echo "Script completed successfully"
# Check if any README files were modified
if git diff --quiet 'registry/*/modules/*/README.md'; then
echo "No README changes to commit"
else
echo "Script failed"
cat "$output_file"
exit 1
echo "Committing README changes..."
git diff --name-only 'registry/*/modules/*/README.md'
git add registry/*/modules/*/README.md
git commit -m "chore: bump module versions (${{ steps.version-type.outputs.bump_type }})"
git push
fi
{
echo "output<<EOF"
cat "$output_file"
echo "EOF"
} >> $GITHUB_OUTPUT
cat "$output_file"
if git diff --quiet; then
echo "versions_up_to_date=true" >> $GITHUB_OUTPUT
echo "✅ All module versions are already up to date"
else
echo "versions_up_to_date=false" >> $GITHUB_OUTPUT
echo "❌ Module versions need to be updated"
echo "Files that would be changed:"
git diff --name-only
echo ""
echo "Diff preview:"
git diff
git checkout .
git clean -fd
exit 1
fi
- name: Comment on PR - Failure
if: failure() && steps.version-check.outputs.versions_up_to_date == 'false'
- name: Comment on PR
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `${{ steps.version-check.outputs.output }}`;
const bumpType = `${{ steps.bump-type.outputs.type }}`;
let comment = `## ❌ Version Bump Validation Failed\n\n`;
const bumpedModules = `${{ steps.process-bumps.outputs.bumped_modules }}`;
const updatedReadmes = `${{ steps.process-bumps.outputs.updated_readmes }}`;
const untaggedModules = `${{ steps.process-bumps.outputs.untagged_modules }}`;
const bumpType = `${{ steps.version-type.outputs.bump_type }}`;
let comment = `## 🚀 Version Bump Summary\n\n`;
comment += `**Bump Type:** \`${bumpType}\`\n\n`;
comment += `Module versions need to be updated but haven't been bumped yet.\n\n`;
comment += `**Required Actions:**\n`;
comment += `1. Run the version bump script locally: \`./.github/scripts/version-bump.sh ${bumpType}\`\n`;
comment += `2. Commit the changes: \`git add . && git commit -m "chore: bump module versions (${bumpType})"\`\n`;
comment += `3. Push the changes: \`git push\`\n\n`;
comment += `### Script Output:\n\`\`\`\n${output}\n\`\`\`\n\n`;
comment += `> Please update the module versions and push the changes to continue.`;
comment += `**Modules Updated:**\n${bumpedModules}\n\n`;
if (updatedReadmes.trim()) {
comment += `**READMEs Updated:**\n${updatedReadmes}\n\n`;
}
if (untaggedModules.trim()) {
comment += `⚠️ **Modules Without Git Tags:**\n${untaggedModules}\n\n`;
comment += `> These modules were versioned based on README content. Consider creating proper release tags after merging.\n\n`;
}
comment += `> Versions have been automatically updated in module READMEs where applicable.`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
});
+8 -97
View File
@@ -3,8 +3,8 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.7.0"
source = "coder/coder"
version = ">= 2.5"
}
}
}
@@ -54,18 +54,6 @@ variable "claude_code_version" {
default = "latest"
}
variable "install_agentapi" {
type = bool
description = "Whether to install AgentAPI."
default = true
}
variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.2.2"
}
variable "experiment_use_screen" {
type = bool
description = "Whether to use screen for running Claude Code in the background."
@@ -99,15 +87,6 @@ variable "experiment_post_install_script" {
locals {
encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : ""
encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : ""
agentapi_start_command = <<-EOT
#!/bin/bash
set -e
# use low width to fit in the tasks UI sidebar. height is adjusted to ~match the default 80k (80x1000) characters
# visible in the terminal screen.
agentapi server --term-width 67 --term-height 1190 -- bash -c "claude --dangerously-skip-permissions \"$(cat ~/.claude-code-prompt)\""
EOT
agentapi_start_command_base64 = base64encode(local.agentapi_start_command)
}
# Install and Initialize Claude Code
@@ -152,35 +131,9 @@ resource "coder_script" "claude_code" {
npm install -g @anthropic-ai/claude-code@${var.claude_code_version}
fi
# Install AgentAPI if enabled
if [ "${var.install_agentapi}" = "true" ]; then
echo "Installing AgentAPI..."
arch=$(uname -m)
if [ "$arch" = "x86_64" ]; then
binary_name="agentapi-linux-amd64"
elif [ "$arch" = "aarch64" ]; then
binary_name="agentapi-linux-arm64"
else
echo "Error: Unsupported architecture: $arch"
exit 1
fi
wget "https://github.com/coder/agentapi/releases/download/${var.agentapi_version}/$binary_name"
chmod +x "$binary_name"
sudo mv "$binary_name" /usr/local/bin/agentapi
fi
if ! command_exists agentapi; then
echo "Error: AgentAPI is not installed. Please enable install_agentapi or install it manually."
exit 1
fi
echo -n "$CODER_MCP_CLAUDE_TASK_PROMPT" > ~/.claude-code-prompt
echo -n "${local.agentapi_start_command_base64}" | base64 -d > ~/.agentapi-start-command
chmod +x ~/.agentapi-start-command
if [ "${var.experiment_report_tasks}" = "true" ]; then
echo "Configuring Claude Code to report tasks via Coder MCP..."
coder exp mcp configure claude-code ${var.folder} --ai-agentapi-url http://localhost:3284
coder exp mcp configure claude-code ${var.folder}
fi
# Run post-install script if provided
@@ -213,24 +166,8 @@ resource "coder_script" "claude_code" {
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
# use low width to fit in the tasks UI sidebar. height is adjusted to ~match the default 80k (80x1000) characters
# visible in the terminal screen.
tmux new-session -d -s claude-code-agentapi -c ${var.folder} '~/.agentapi-start-command; exec bash'
echo "Waiting for agentapi server to start on port 3284..."
for i in $(seq 1 15); do
if lsof -i :3284 | grep -q 'LISTEN'; then
echo "agentapi server started on port 3284."
break
fi
echo "Waiting... ($i/15)"
sleep 1
done
if ! lsof -i :3284 | grep -q 'LISTEN'; then
echo "Error: agentapi server did not start on port 3284 after 15 seconds."
exit 1
fi
tmux new-session -d -s claude-code -c ${var.folder} "agentapi attach"
# Create a new tmux session in detached mode
tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\""
fi
@@ -266,7 +203,7 @@ resource "coder_script" "claude_code" {
screen -U -dmS claude-code bash -c '
cd ${var.folder}
~/.agentapi-start-command
claude --dangerously-skip-permissions "$CODER_MCP_CLAUDE_TASK_PROMPT" | tee -a "$HOME/.claude-code.log"
exec bash
'
else
@@ -280,20 +217,6 @@ resource "coder_script" "claude_code" {
run_on_start = true
}
resource "coder_app" "claude_code_web" {
slug = "claude-code-web"
display_name = "Claude Code Web"
agent_id = var.agent_id
url = "http://localhost:3284/"
icon = var.icon
subdomain = true
healthcheck {
url = "http://localhost:3284/status"
interval = 5
threshold = 3
}
}
resource "coder_app" "claude_code" {
slug = "claude-code"
display_name = "Claude Code"
@@ -306,18 +229,12 @@ resource "coder_app" "claude_code" {
export LC_ALL=en_US.UTF-8
if [ "${var.experiment_use_tmux}" = "true" ]; then
if ! tmux has-session -t claude-code-agentapi 2>/dev/null; then
echo "Starting a new Claude Code agentapi tmux session." | tee -a "$HOME/.claude-code.log"
tmux new-session -d -s claude-code-agentapi -c ${var.folder} '~/.agentapi-start-command; exec bash'
fi
if tmux has-session -t claude-code 2>/dev/null; then
echo "Attaching to existing Claude Code tmux session." | tee -a "$HOME/.claude-code.log"
tmux attach-session -t claude-code
else
echo "Starting a new Claude Code tmux session." | tee -a "$HOME/.claude-code.log"
tmux new-session -s claude-code -c ${var.folder} "agentapi attach; exec bash"
tmux new-session -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions | tee -a \"$HOME/.claude-code.log\"; exec bash"
fi
elif [ "${var.experiment_use_screen}" = "true" ]; then
if screen -list | grep -q "claude-code"; then
@@ -325,7 +242,7 @@ resource "coder_app" "claude_code" {
screen -xRR claude-code
else
echo "Starting a new Claude Code screen session." | tee -a "$HOME/.claude-code.log"
screen -S claude-code bash -c 'agentapi attach; exec bash'
screen -S claude-code bash -c 'claude --dangerously-skip-permissions | tee -a "$HOME/.claude-code.log"; exec bash'
fi
else
cd ${var.folder}
@@ -336,9 +253,3 @@ resource "coder_app" "claude_code" {
order = var.order
group = var.group
}
resource "coder_ai_task" "claude_code" {
sidebar_app {
id = coder_app.claude_code.id
}
}
+4 -4
View File
@@ -16,7 +16,7 @@ Enable Remote Desktop + a web based client on Windows workspaces, powered by [de
module "windows_rdp" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/windows-rdp/coder"
version = "1.2.1"
version = "1.2.0"
agent_id = resource.coder_agent.main.id
resource_id = resource.aws_instance.dev.id
}
@@ -34,7 +34,7 @@ module "windows_rdp" {
module "windows_rdp" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/windows-rdp/coder"
version = "1.2.1"
version = "1.2.0"
agent_id = resource.coder_agent.main.id
resource_id = resource.aws_instance.dev.id
}
@@ -46,7 +46,7 @@ module "windows_rdp" {
module "windows_rdp" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/windows-rdp/coder"
version = "1.2.1"
version = "1.2.0"
agent_id = resource.coder_agent.main.id
resource_id = resource.google_compute_instance.dev[0].id
}
@@ -58,7 +58,7 @@ module "windows_rdp" {
module "windows_rdp" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/windows-rdp/coder"
version = "1.2.1"
version = "1.2.0"
agent_id = resource.coder_agent.main.id
resource_id = resource.aws_instance.dev.id
devolutions_gateway_version = "2025.1.6" # Specify a specific version
@@ -16,11 +16,6 @@ function Configure-RDP {
New-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name "SecurityLayer" -Value 1 -PropertyType DWORD -Force
# Enable RDP through Windows Firewall
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
# Disable UDP. It doesn't work via `coder port-forward` and is broken due to MTU issues in Coder Connect.
# Requires a restart to take effect. c.f. https://github.com/coder/internal/issues/608#issuecomment-2965923672
New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services' -Name "SelectTransport" -Value 1 -PropertyType DWORD -Force
Restart-Service -Name "TermService" -Force
}
function Install-DevolutionsGateway {