Skip to content

ci: update github workflows#423

Merged
marc-romu merged 21 commits into
release/1.4.2-betafrom
main
Apr 14, 2026
Merged

ci: update github workflows#423
marc-romu merged 21 commits into
release/1.4.2-betafrom
main

Conversation

@marc-romu
Copy link
Copy Markdown
Member

Description

Update workflows for consistency with main branch.

Breaking Changes

None.

Testing Done

None.

Checklist

  • This PR is focused on a single feature or bug fix
  • Version in Solution.props was updated, if necessary, and follows semantic versioning
  • CHANGELOG.md has been updated
  • PR title follows Conventional Commits format
  • PR description follows Pull Request Description Template

github-actions Bot and others added 21 commits March 14, 2026 10:46
chore: add provider hash manifest for version 1.4.2-alpha (dual platform)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
# Pull Request: ci: enhance milestone management and release automation
workflows
 
## Description
 
Enhanced GitHub Actions workflows for comprehensive milestone management
and automated release promotion. This PR introduces a robust system for
managing the complete release lifecycle from alpha through stable
releases.
 
### Key Features Added:
 
**Milestone Management System:**
- Automated milestone creation and lifecycle management across release
stages (alpha → beta → rc → stable)
- Support for multiple major versions with independent milestone
hierarchies
- Intelligent issue migration when milestones close
 
**Release Promotion Workflow:**
- Automatic promotion of versions through release stages after 30 days
without reported issues
- Manual promotion capability with force override option
- Integration with milestone management for seamless version progression
 
**Release Preparation Workflow:**
- Automated release branch creation from dev
- Version updates across Solution.props and project files
- Changelog compilation from milestone issues and PRs
- README badge updates and license header normalization

## Breaking Changes
 
None - this is purely CI/CD infrastructure enhancement.
 
## Testing Done
 
- None. PR to dev to test.
 
## Checklist
 
- [x] This PR is focused on a single feature or bug fix
- [ ] Version in Solution.props was updated, if necessary, and follows
semantic versioning
- [ ] CHANGELOG.md has been updated
- [x] PR title follows [Conventional
Commits](https://www.conventionalcommits.org/en/v1.1.0/) format
- [x] PR description follows [Pull Request Description
Template](https://github.com/architects-toolkit/SmartHopper/blob/main/CONTRIBUTING.md#pull-request-description-template)
Copilot AI review requested due to automatic review settings April 14, 2026 20:18
@github-actions github-actions Bot added this to the 1.4.2-alpha milestone Apr 14, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🏷️ This PR has been automatically assigned to milestone 1.4.2-alpha based on the version in Solution.props.

@marc-romu marc-romu modified the milestones: 1.4.2-alpha, 1.4.2-beta Apr 14, 2026
@marc-romu marc-romu merged commit caee104 into release/1.4.2-beta Apr 14, 2026
75 of 77 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the repository’s GitHub Actions workflows to align with main, adding a milestone-driven “stabilization path” release flow and extracting milestone/version automation into reusable composite actions.

Changes:

  • Added stabilization workflows (init/cancel/complete) and expanded existing release workflows to support dev-*/main-* stabilization branches.
  • Introduced reusable versioning/milestone composite actions (label creation, issue checks, milestone creation, milestone item migration) and wired them into workflows.
  • Updated CI/PR validation triggers and documentation to cover the new branching and release automation.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
hashes/1.4.2-alpha.json Adds generated provider artifact hashes/metadata for 1.4.2-alpha.
.github/workflows/stabilization-0-init.yml Initializes stabilization branches (dev-X.Y.Z, main-X.Y.Z) on stable milestone creation.
.github/workflows/stabilization-1-cancel.yml Cancels stabilization path on stable milestone close when sub-milestones remain open (migrates items, deletes branches).
.github/workflows/stabilization-2-complete.yml Completes stabilization path on stable milestone close with no sub-milestones; creates backport PR and deletes branches on merge.
.github/workflows/release-promotion.yml Scheduled/manual promotion logic for alpha→beta→rc→stable with dispatch into release preparation.
.github/workflows/release-6-upload-yak.yml Adds version-label creation before Yak upload.
.github/workflows/release-4-build.yml Adds version-label creation and dispatches Yak upload after stable build to avoid artifact race.
.github/workflows/release-3-pr-to-main-closed.yml Extends release creation to support main-* targets and sets target_commitish accordingly.
.github/workflows/release-2-pr-to-dev-closed.yml Extends dev→main PR creation to support dev-*main-* stabilization flows.
.github/workflows/release-1-milestone.yml Converts release preparation into workflow_dispatch with stabilization branch auto-detection.
.github/workflows/pr-version-validation.yml Runs version validation on PRs targeting main-*/dev-*.
.github/workflows/pr-validation.yml Runs PR validation on PRs targeting main-*/dev-*.
.github/workflows/pr-manifest-validation.yml Runs manifest validation on PRs targeting main-*.
.github/workflows/pr-build-hash-validation.yml Runs build/hash validation on PRs targeting main-*/dev-*.
.github/workflows/milestone-management.yml Reworks milestone management to use new composite actions; adds release-published trigger.
.github/workflows/issue-auto-tag.yml New workflow to auto-label new issues with a version: label based on template content/current version.
.github/workflows/ci-dotnet-tests.yml Expands CI triggers to include main-*/dev-*.
.github/workflows/RELEASE_WORKFLOW.md Documents regular releases + stabilization path + Yak upload behavior.
.github/actions/versioning/update-version/action.yml Updates version parsing to support dotted prerelease suffixes and outputs stage.
.github/actions/versioning/parse-version/action.yml New composite action to parse a version into major/minor/patch/suffix/stage.
.github/actions/versioning/move-milestone-items/action.yml New composite action to migrate open issues/PRs from closed milestones with stabilization-aware routing.
.github/actions/versioning/manage-milestones/action.yml New composite action to create next-stage milestones on release publication.
.github/actions/versioning/get-version/action.yml Updates parsing to support dotted suffixes and outputs stage.
.github/actions/versioning/format-version/action.yml New composite action to format version components back into a version string.
.github/actions/versioning/create-version-label/action.yml New composite action to create/ensure version: X.Y.Z... labels exist.
.github/actions/versioning/check-issues-for-version/action.yml New composite action to gate promotion based on open issues, release age, and recent closures.
.github/actions/versioning/README.md New documentation describing the shared versioning utilities and actions.
.github/MILESTONE_MANAGEMENT_GUIDE.md New guide explaining milestone lifecycle expectations and examples.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to +101
github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
milestone: milestoneNumber,
state: 'open'
}),
github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open'
})
]);

const prsInMilestone = pulls.data.filter(pr =>
pr.milestone && pr.milestone.number === milestoneNumber
);

return {
issues: issues.data.filter(issue => !issue.pull_request),
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

issues.listForRepo here is not paginated and doesn’t set per_page, so only the first page of open items in the milestone is considered. Use github.paginate (and per_page: 100) to avoid leaving issues behind when a milestone has >30 open issues.

Suggested change
github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
milestone: milestoneNumber,
state: 'open'
}),
github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open'
})
]);
const prsInMilestone = pulls.data.filter(pr =>
pr.milestone && pr.milestone.number === milestoneNumber
);
return {
issues: issues.data.filter(issue => !issue.pull_request),
github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
milestone: milestoneNumber,
state: 'open',
per_page: 100
}),
github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100
})
]);
const prsInMilestone = pulls.filter(pr =>
pr.milestone && pr.milestone.number === milestoneNumber
);
return {
issues: issues.filter(issue => !issue.pull_request),

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +96
github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open'
})
]);

const prsInMilestone = pulls.data.filter(pr =>
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

pulls.list is also not paginated and doesn’t set per_page, so PRs beyond the first page won’t be inspected/migrated. Use github.paginate (and/or per_page: 100) to ensure all open PRs are checked for milestone assignment.

Suggested change
github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open'
})
]);
const prsInMilestone = pulls.data.filter(pr =>
github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100
})
]);
const prsInMilestone = pulls.filter(pr =>

Copilot uses AI. Check for mistakes.
Comment on lines +150 to +154
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
startsWith(github.event.pull_request.head.ref, 'main-') &&
github.event.pull_request.base.ref == 'main'
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The cleanup job will delete branches for any merged PR into main whose head starts with main-. That can accidentally delete non-stabilization branches like main-feature if someone uses that naming. Tighten the condition (and/or extraction) to only match stabilization branches with a semantic version (e.g., ^main-\d+\.\d+\.\d+$) before deleting dev-* / main-* refs.

Copilot uses AI. Check for mistakes.
echo "version=$version" >> $env:GITHUB_OUTPUT

- name: Create version label if doesn't exist
if: inputs.upload_to_yak == 'true'
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This step compares a typed boolean input (inputs.upload_to_yak) to the string 'true'. For workflow_dispatch boolean inputs, inputs.upload_to_yak is a boolean, so this condition can evaluate to false and the label step never runs (including when dispatching with string inputs). Prefer if: inputs.upload_to_yak (or compare to true).

Suggested change
if: inputs.upload_to_yak == 'true'
if: inputs.upload_to_yak

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +10
# 2. Keeps only the latest beta, rc, and stable milestones active (closes older ones)
# 3. Moves open issues from closed milestones to the next alpha version
# 4. Allows unlimited alpha milestones
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The description claims older beta/rc/stable milestones are closed, but this workflow only invokes manage-milestones (which only creates milestones) and never closes any. Either implement the closing logic (and scope rules) or update the description to match the actual behavior to avoid operator confusion.

Suggested change
# 2. Keeps only the latest beta, rc, and stable milestones active (closes older ones)
# 3. Moves open issues from closed milestones to the next alpha version
# 4. Allows unlimited alpha milestones
# 2. Moves open issues and PRs from closed milestones to the next alpha version
# 3. Allows unlimited alpha milestones

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +116
# Search for all open issues with labels matching the base version (any stage)
# This will match: version: X.Y.Z, version: X.Y.Z-alpha, version: X.Y.Z-beta, etc.
$openIssues = @()
$page = 1
$perPage = 100

do {
# Search for issues with labels starting with "version: X.Y.Z"
$query = "state:open label:""version: $baseVersion"" repo:$repo"
$uri = "https://api.github.com/search/issues?q=$([Uri]::EscapeDataString($query))&per_page=$perPage&page=$page"

try {
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method GET
$openIssues += $response.items

if ($response.items.Count -lt $perPage) {
break
}
$page++
} catch {
Write-Error "Failed to fetch open issues: $($_.Exception.Message)"
exit 1
}
} while ($response.items.Count -eq $perPage)

Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

CHECK 1 says it searches “base + all stages”, but the query only searches for the exact label version: X.Y.Z (no suffix). GitHub search doesn’t do prefix matching on labels, so issues labeled version: X.Y.Z-alpha / version: X.Y.Z-beta / dated suffixes won’t be included and promotion can proceed with staged issues still open. Consider enumerating all repo labels that start with version: ${baseVersion} and aggregating open issues for each matching label (or running separate searches per discovered label).

Suggested change
# Search for all open issues with labels matching the base version (any stage)
# This will match: version: X.Y.Z, version: X.Y.Z-alpha, version: X.Y.Z-beta, etc.
$openIssues = @()
$page = 1
$perPage = 100
do {
# Search for issues with labels starting with "version: X.Y.Z"
$query = "state:open label:""version: $baseVersion"" repo:$repo"
$uri = "https://api.github.com/search/issues?q=$([Uri]::EscapeDataString($query))&per_page=$perPage&page=$page"
try {
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method GET
$openIssues += $response.items
if ($response.items.Count -lt $perPage) {
break
}
$page++
} catch {
Write-Error "Failed to fetch open issues: $($_.Exception.Message)"
exit 1
}
} while ($response.items.Count -eq $perPage)
# Discover all repository labels for this base version, including staged variants.
# GitHub issue search only matches exact label names, so we must enumerate labels first.
$openIssues = @()
$perPage = 100
$matchingLabels = @()
$labelPage = 1
do {
$labelsUri = "https://api.github.com/repos/$repoOwner/$repoName/labels?per_page=$perPage&page=$labelPage"
try {
$labelsResponse = Invoke-RestMethod -Uri $labelsUri -Headers $headers -Method GET
} catch {
Write-Error "Failed to fetch repository labels: $($_.Exception.Message)"
exit 1
}
foreach ($label in $labelsResponse) {
if ($label.name -eq "version: $baseVersion" -or $label.name.StartsWith("version: $baseVersion-")) {
$matchingLabels += $label.name
}
}
if ($labelsResponse.Count -lt $perPage) {
break
}
$labelPage++
} while ($labelsResponse.Count -eq $perPage)
if ($matchingLabels.Count -eq 0) {
$matchingLabels = @("version: $baseVersion")
} else {
$matchingLabels = $matchingLabels | Sort-Object -Unique
}
Write-Host " Matching labels: $($matchingLabels -join ', ')"
$issuesByNumber = @{}
foreach ($matchingLabel in $matchingLabels) {
$page = 1
do {
$query = "state:open label:""$matchingLabel"" repo:$repo"
$uri = "https://api.github.com/search/issues?q=$([Uri]::EscapeDataString($query))&per_page=$perPage&page=$page"
try {
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method GET
foreach ($item in $response.items) {
$issuesByNumber[$item.number.ToString()] = $item
}
if ($response.items.Count -lt $perPage) {
break
}
$page++
} catch {
Write-Error "Failed to fetch open issues for label '$matchingLabel': $($_.Exception.Message)"
exit 1
}
} while ($response.items.Count -eq $perPage)
}
$openIssues = @($issuesByNumber.Values)

Copilot uses AI. Check for mistakes.
Write-Warning "Failed to fetch release: $($_.Exception.Message)"
echo "release_date=unknown" >> $env:GITHUB_OUTPUT
echo "release_age_days=-1" >> $env:GITHUB_OUTPUT
echo "release_old_enough=unknown" >> $env:GITHUB_OUTPUT
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

If the release lookup fails (e.g., tag not found / API transient error), the workflow currently sets release_old_enough=unknown but still allows $canPromote to remain true. That contradicts the stated requirement that the release must be ≥ N days old and can cause unintended promotions. Recommend treating an unknown release age as blocking (set $canPromote = $false and add a blocking reason), or explicitly document/rename the check if “unknown” is meant to pass.

Suggested change
echo "release_old_enough=unknown" >> $env:GITHUB_OUTPUT
echo "release_old_enough=unknown" >> $env:GITHUB_OUTPUT
$canPromote = $false
$blockingReasons += "release_age_unknown"

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +64
const milestones = await github.rest.issues.listMilestones({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open'
});

return milestones.data.find(m => m.title === title);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

findMilestone calls issues.listMilestones without pagination (default page size is limited), so it can fail to find the intended milestone when there are many open milestones. Use github.paginate and/or set per_page: 100 to ensure all open milestones are considered.

Suggested change
const milestones = await github.rest.issues.listMilestones({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open'
});
return milestones.data.find(m => m.title === title);
const milestones = await github.paginate(
github.rest.issues.listMilestones,
{
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100
}
);
return milestones.find(m => m.title === title);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants