diff --git a/.github/ai-context/powershell-rules.md b/.github/ai-context/powershell-rules.md index 916a6c8..ab96bd3 100644 --- a/.github/ai-context/powershell-rules.md +++ b/.github/ai-context/powershell-rules.md @@ -88,4 +88,5 @@ DataCenter track requires `CI_CONFLUENCE_TYPE=DataCenter`, `CI_CONFLUENCE_URL`, - Include changelog updates for user-visible behavior changes. - `.github/workflows/ci.yml` path filters can skip instruction-only changes. - `.github/workflows/integration_tests.yml` is the full-suite Cloud/Data Center integration workflow (nightly + manual). +- `.github/ai-context/releasing.md` links to the canonical AtlassianPS Standards release blueprint and keeps ConfluencePS-specific release details. - Run local validation before opening/updating PRs when changing instruction files. diff --git a/.github/ai-context/releasing.md b/.github/ai-context/releasing.md new file mode 100644 index 0000000..61ef941 --- /dev/null +++ b/.github/ai-context/releasing.md @@ -0,0 +1,35 @@ +# Releasing ConfluencePS + +ConfluencePS follows the canonical [AtlassianPS release blueprint](https://github.com/AtlassianPS/AtlassianPS.Standards/blob/master/docs/ReleaseBlueprint.md). +Keep cross-repository release strategy in the blueprint and keep this runbook limited to ConfluencePS-specific details. + +## Files to Update + +| File | What to Change | +|------|----------------| +| `CHANGELOG.md` | Add a release entry matching the tag: `## vX.Y.Z - YYYY-MM-DD` | +| `ConfluencePS/ConfluencePS.psd1` | Update `ModuleVersion` to `X.Y.Z` | + +## Local Preflight + +```powershell +Invoke-Build -Task Build, Test +Invoke-Build -Task Build, SetVersion -VersionToPublish vX.Y.Z +``` + +The release metadata preflight must find a matching changelog section before a tag is pushed. + +## Release Flow + +1. Start from an up-to-date `master` branch. +2. Update `CHANGELOG.md` and `ConfluencePS/ConfluencePS.psd1`. +3. Run the local preflight commands above. +4. Commit the release changes. +5. Create and push an annotated `vX.Y.Z` tag. + +`release.yml` validates the annotated tag, downloads the `Release` artifact from CI for the tagged commit, builds release notes from `CHANGELOG.md`, publishes to PSGallery, creates the GitHub Release from the same release-notes file, and notifies the homepage repository for stable releases. + +## Version Format + +Use `vX.Y.Z` tags and `## vX.Y.Z - YYYY-MM-DD` changelog headings for future releases. +Pre-release tags may use suffixes such as `vX.Y.Z-beta`. diff --git a/.github/changelog.configuration.json b/.github/changelog.configuration.json deleted file mode 100644 index 443406d..0000000 --- a/.github/changelog.configuration.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "emojisPrefix": true, - "emojis": { - "changed": "⚙️", - "added": "🚀", - "fixed": "🔧", - "removed": "❌" - } -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6127575..2996205 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,52 +7,74 @@ concurrency: on: pull_request: branches: [master] - paths-ignore: - &paths-ignore # Documentation (NOT docs/** - that's PlatyPS source!) - - "CHANGELOG.md" - - "LICENSE" - # AI assistant instruction files - - "AGENTS.md" - - "CLAUDE.md" - - "GEMINI.md" - - ".cursor/**" - - ".github/copilot-instructions.md" - - ".github/ai-context/**" - - ".github/instructions/**" - # GitHub/editor meta files - - ".github/*.md" - - ".github/ISSUE_TEMPLATE/**" - - ".github/PULL_REQUEST_TEMPLATE/**" - - ".vscode/**" - - ".editorconfig" - - ".spelling" - # Untracked regenerated docs (kept in case they ever land) - - "docs-regenerated/**" push: branches: [master] - paths-ignore: *paths-ignore workflow_dispatch: +permissions: + contents: read + pull-requests: read + jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + outputs: + run_ci: ${{ github.event_name == 'workflow_dispatch' || steps.filter.outputs.code == 'true' }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - id: filter + name: Decide whether full CI is required + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4 + with: + base: ${{ github.ref }} + predicate-quantifier: every + filters: | + code: + - '**' + - '!.editorconfig' + - '!.github/copilot-instructions.md' + - '!.spelling' + - '!AGENTS.md' + - '!CHANGELOG.md' + - '!CLAUDE.md' + - '!GEMINI.md' + - '!LICENSE' + - '!.cursor/**' + - '!.github/*.md' + - '!.github/ai-context/**' + - '!.github/instructions/**' + - '!.github/ISSUE_TEMPLATE/**' + - '!.github/PULL_REQUEST_TEMPLATE/**' + - '!.vscode/**' + - '!docs-regenerated/**' + lint: name: Lint + needs: changes + if: needs.changes.outputs.run_ci == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@9a9367e22847bd24f86208ed2d98d207b0e2a3b3 # v0.1.6 + - uses: rhysd/actionlint@914e7df21a07ef503a81201c76d2b11c789d3fca # v1.7.12 + - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@6fe5d05db84cdd10c9e4284e235a8f359c9537ad # v0.1.11 - run: Invoke-Build -Task Lint shell: pwsh build: name: Build Module - needs: lint + needs: [changes, lint] + if: needs.changes.outputs.run_ci == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@9a9367e22847bd24f86208ed2d98d207b0e2a3b3 # v0.1.6 + - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@6fe5d05db84cdd10c9e4284e235a8f359c9537ad # v0.1.11 - - run: Invoke-Build -Task Clean, Build + - run: Invoke-Build -Task Clean, TestPublish shell: pwsh - uses: actions/upload-artifact@v7 @@ -62,16 +84,11 @@ jobs: test_windows_ps5: name: Test (Windows PS5) - needs: build + needs: [changes, build] + if: needs.changes.outputs.run_ci == 'true' runs-on: windows-latest steps: - uses: actions/checkout@v6 - - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@9a9367e22847bd24f86208ed2d98d207b0e2a3b3 # v0.1.6 - with: - ps-version: "5" - # Setup is run below in the powershell (PS 5.1) shell instead of pwsh, - # so the Windows PowerShell module path is populated. - skip-setup: "true" - uses: actions/download-artifact@v8 with: @@ -95,7 +112,8 @@ jobs: test_pwsh: name: Test (${{ matrix.name }}) - needs: build + needs: [changes, build] + if: needs.changes.outputs.run_ci == 'true' runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -106,14 +124,14 @@ jobs: - { os: macos-latest, name: "macOS" } steps: - uses: actions/checkout@v6 - - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@9a9367e22847bd24f86208ed2d98d207b0e2a3b3 # v0.1.6 + - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@6fe5d05db84cdd10c9e4284e235a8f359c9537ad # v0.1.11 - uses: actions/download-artifact@v8 with: name: Release path: ./Release/ - - run: Invoke-Build -Task Test -ExcludeTag "Integration", "Documentation" + - run: Invoke-Build -Task Test shell: pwsh - uses: actions/upload-artifact@v7 @@ -124,7 +142,7 @@ jobs: smoke_tests: name: Smoke Tests - needs: lint + needs: [changes, lint] runs-on: ubuntu-latest # Skip on fork PRs and Dependabot PRs: the shared cloud PAT secret is not exposed # in those contexts and the smoke run would fail. The ci-required @@ -132,9 +150,10 @@ jobs: # a green CI Result. First-party PRs and pushes to master gate on the # smoke result, which in turn gates release.yml's artifact download # (workflow_conclusion: success). - if: github.event_name != 'pull_request' || - (github.event.pull_request.head.repo.full_name == github.repository && - github.actor != 'dependabot[bot]') + if: needs.changes.outputs.run_ci == 'true' && + (github.event_name != 'pull_request' || + (github.event.pull_request.head.repo.full_name == github.repository && + github.actor != 'dependabot[bot]')) env: CI_CONFLUENCE_TYPE: Cloud CONFLUENCE_CLOUD_URL: ${{ vars.CONFLUENCE_CLOUD_URL }} @@ -142,7 +161,7 @@ jobs: ATLASSIAN_CLOUD_PAT: ${{ secrets.ATLASSIAN_CLOUD_PAT }} steps: - uses: actions/checkout@v6 - - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@9a9367e22847bd24f86208ed2d98d207b0e2a3b3 # v0.1.6 + - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@6fe5d05db84cdd10c9e4284e235a8f359c9537ad # v0.1.11 - name: Run smoke integration tests run: | @@ -159,7 +178,7 @@ jobs: # Sentinel job that always runs and aggregates the result of the pipeline. # Branch protection should require ONLY this check (`CI / CI Result`), # not the individual jobs. That way a docs-only PR (where lint/build/test - # are skipped via paths-ignore) still produces a green required check + # are skipped by the Detect Changes job) still produces a green required check # and can be merged. Smoke tests are part of the gate: they run on # first-party PRs and on every push to master, and skip (without failing # the gate) on fork/Dependabot PRs where secrets are not exposed. @@ -168,7 +187,7 @@ jobs: ci-required: name: CI Result if: always() - needs: [lint, build, test_windows_ps5, test_pwsh, smoke_tests] + needs: [changes, lint, build, test_windows_ps5, test_pwsh, smoke_tests] runs-on: ubuntu-latest steps: - name: Aggregate job results diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 5555808..39a2452 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -35,7 +35,7 @@ jobs: ATLASSIAN_CLOUD_PAT: ${{ secrets.ATLASSIAN_CLOUD_PAT }} steps: - uses: actions/checkout@v6 - - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@9a9367e22847bd24f86208ed2d98d207b0e2a3b3 # v0.1.6 + - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@6fe5d05db84cdd10c9e4284e235a8f359c9537ad # v0.1.11 - name: Run Cloud-tagged integration tests run: | @@ -68,7 +68,7 @@ jobs: run: docker compose up -d shell: bash - - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@9a9367e22847bd24f86208ed2d98d207b0e2a3b3 # v0.1.6 + - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@6fe5d05db84cdd10c9e4284e235a8f359c9537ad # v0.1.11 - name: Wait for Confluence to become reachable run: ./Tools/Wait-ConfluenceServer.ps1 -TimeoutSeconds 1500 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6006176..a645a8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,19 +24,11 @@ jobs: fetch-depth: 0 ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }} - - name: Resolve release ref + - name: Validate release tag id: release_ref - shell: bash - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - release_tag="${{ inputs.release_tag }}" - else - release_tag="${{ github.ref_name }}" - fi - - release_sha="$(git rev-list -n 1 "$release_tag")" - echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT" - echo "release_sha=$release_sha" >> "$GITHUB_OUTPUT" + uses: AtlassianPS/AtlassianPS.Standards/.github/actions/resolve-release-tag@6fe5d05db84cdd10c9e4284e235a8f359c9537ad # v0.1.11 + with: + tag: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }} - name: Download artifact from tagged commit CI run uses: dawidd6/action-download-artifact@v21 @@ -48,15 +40,24 @@ jobs: path: ./Release/ if_no_artifact_found: fail - - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@9a9367e22847bd24f86208ed2d98d207b0e2a3b3 # v0.1.6 + - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/setup-powershell@6fe5d05db84cdd10c9e4284e235a8f359c9537ad # v0.1.11 - - run: Invoke-Build -Task Publish -VersionToPublish ${{ steps.release_ref.outputs.release_tag }} - -PSGalleryAPIKey ${{ secrets.PSGALLERY_API_KEY }} + - uses: AtlassianPS/AtlassianPS.Standards/.github/actions/build-release-notes@6fe5d05db84cdd10c9e4284e235a8f359c9537ad # v0.1.11 + id: release_notes + with: + release-version: ${{ steps.release_ref.outputs.release_tag }} + + - name: Publish module + run: | + Invoke-Build -Task Publish ` + -VersionToPublish ${{ steps.release_ref.outputs.release_tag }} ` + -PSGalleryAPIKey ${{ secrets.PSGALLERY_API_KEY }} shell: pwsh - name: Create Release and Upload Asset uses: softprops/action-gh-release@v3 with: + body_path: ${{ steps.release_notes.outputs.release_notes_path }} tag_name: ${{ steps.release_ref.outputs.release_tag }} name: ${{ steps.release_ref.outputs.release_tag }} files: ./Release/ConfluencePS.zip diff --git a/AGENTS.md b/AGENTS.md index b419ab6..40e6452 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -115,6 +115,7 @@ Before merging: ensure the Windows PowerShell 5.1 CI test job is green. - `.github/workflows/ci.yml` is the required PR/push quality gate. - `.github/workflows/integration_tests.yml` runs full integration suites on a nightly schedule (Cloud + Dockerized Data Center) and manual dispatch. - `.github/workflows/release.yml` publishes tagged releases. +- `.github/ai-context/releasing.md` keeps ConfluencePS-specific release steps and links to the canonical AtlassianPS Standards release blueprint. - For instruction-only changes, run local validation before opening/updating a PR. ## Instruction Maintenance diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b8a6e2..db68935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed `-Label` parsing for the Get-ConfluencePage cmdlet (#193 [@claudiospizzi]) -## [2.5] 2019-03-27 +## v2.5.2 - 2026-05-30 + +### Changed + +- Aligned release workflow, release-note generation, and publish-time manifest metadata with the AtlassianPS Standards release blueprint. + +## v2.5.0 - 2019-03-27 ### Added diff --git a/ConfluencePS.build.ps1 b/ConfluencePS.build.ps1 index 85c664a..66b6266 100644 --- a/ConfluencePS.build.ps1 +++ b/ConfluencePS.build.ps1 @@ -19,6 +19,24 @@ param( Import-Module "$PSScriptRoot/Tools/BuildTools.psm1" -Force -ErrorAction Stop +function Import-ConfluencePSStandard { + [CmdletBinding()] + param() + + $buildRequirements = Import-PowerShellDataFile -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Tools/build.requirements.psd1') + $standardsRequirement = $buildRequirements | + Where-Object { $_.ModuleName -eq 'AtlassianPS.Standards' } | + Select-Object -First 1 + + if (-not $standardsRequirement) { + throw 'AtlassianPS.Standards is missing from Tools/build.requirements.psd1.' + } + + Import-Module AtlassianPS.Standards -RequiredVersion $standardsRequirement.RequiredVersion -Force -ErrorAction Stop +} + +Import-ConfluencePSStandard + Remove-Item -Path env:\BH* -ErrorAction SilentlyContinue $ProjectName = 'ConfluencePS' @@ -421,29 +439,16 @@ Task UpdateManifest { } Task SetVersion { - [System.Management.Automation.SemanticVersion]$versionToPublish = $VersionToPublish - - $published = Find-Module -Name $env:BHProjectName -ErrorAction SilentlyContinue - if ($published) { - [System.Management.Automation.SemanticVersion]$latestPublished = $published.Version - Write-Build Gray "Latest published version: $latestPublished" - Assert-True { $versionToPublish -gt $latestPublished } "Version must be greater than latest published version: $latestPublished" - } - else { - Write-Build Gray "No published version found in PSGallery; skipping version guard" - } - - $versionString = "{0}.{1}.{2}" -f $versionToPublish.Major, $versionToPublish.Minor, $versionToPublish.Patch - Metadata\Update-Metadata -Path $builtManifestPath -PropertyName "ModuleVersion" -Value $versionString - - if ($versionToPublish.PreReleaseLabel) { - Write-Build Gray "Setting Prerelease label: $($versionToPublish.PreReleaseLabel)" - Metadata\Update-Metadata -Path $builtManifestPath -PropertyName "Prerelease" -Value $versionToPublish.PreReleaseLabel - } - else { - Write-Build Gray "Removing Prerelease label (stable release)" - Metadata\Update-Metadata -Path $builtManifestPath -PropertyName "Prerelease" -Value '' - } + $releaseNotes = Get-AtlassianPSReleaseNotesFromChangelog ` + -ChangelogPath (Join-Path -Path $env:BHProjectPath -ChildPath 'CHANGELOG.md') ` + -ReleaseVersion $VersionToPublish + + $versionString = Set-AtlassianPSModuleManifestVersion ` + -BuiltManifestPath $builtManifestPath ` + -ModuleName $env:BHProjectName ` + -VersionToPublish $VersionToPublish ` + -ReleaseNotes $releaseNotes + Write-Build Gray "Resolved release version: $versionString" } Task Test { @@ -602,27 +607,27 @@ Task StopConfluenceDocker { Invoke-BuildExec { docker compose -f $composeFile down -v } } -Task Publish SetVersion, SignCode, Package, { +Task Publish SetVersion, Package, { Assert-True (-not [String]::IsNullOrEmpty($PSGalleryAPIKey)) "No key for the PSGallery" - Publish-Module -Path "$env:BHBuildOutput/$env:BHProjectName" -NuGetApiKey $PSGalleryAPIKey -}, UpdateHomepage - -Task UpdateHomepage { - # TODO: -} -Task SignCode { - # TODO: waiting for certificates + Publish-Module -Path (Join-Path $env:BHBuildOutput $env:BHProjectName) -NuGetApiKey $PSGalleryAPIKey } Task Package { - $source = "$env:BHBuildOutput\$env:BHProjectName" - $destination = "$env:BHBuildOutput\$env:BHProjectName.zip" + $script:PackagePath = New-AtlassianPSModulePackage -BuildOutputPath $env:BHBuildOutput -ModuleName $env:BHProjectName +} - Assert-True { Test-Path $source } "Missing files to package" +Task TestPublish Build, Package, { + $testPackageParameters = @{ + BuildOutputPath = $env:BHBuildOutput + ModuleName = $env:BHProjectName + } + if ($script:PackagePath) { + $testPackageParameters.PackagePath = $script:PackagePath + } - Remove-Item $destination -ErrorAction SilentlyContinue - $null = Compress-Archive -Path $source -DestinationPath $destination + $package = Test-AtlassianPSModulePackage @testPackageParameters + Write-Build Green "Publish dry-run passed: $($package.PackagePath)" } Task . Clean, Build, Test diff --git a/ConfluencePS/ConfluencePS.psd1 b/ConfluencePS/ConfluencePS.psd1 index fbd8791..72fabee 100644 --- a/ConfluencePS/ConfluencePS.psd1 +++ b/ConfluencePS/ConfluencePS.psd1 @@ -1,4 +1,4 @@ -# +# # Module manifest for module 'ConfluencePS' # # Generated by: Brian Bunke @@ -12,7 +12,7 @@ RootModule = 'ConfluencePS.psm1' # Version number of this module. - ModuleVersion = '2.5' + ModuleVersion = '2.5.2' # ID used to uniquely identify this module GUID = '20d32089-48ef-464d-ba73-6ada240e26b3' @@ -104,7 +104,7 @@ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = 'https://github.com/AtlassianPS/ConfluencePS/blob/master/CHANGELOG.md' + # ReleaseNotes = '' } # End of PSData hashtable diff --git a/Tests/Build.Tests.ps1 b/Tests/Build.Tests.ps1 index ee092f4..41e6de1 100644 --- a/Tests/Build.Tests.ps1 +++ b/Tests/Build.Tests.ps1 @@ -30,7 +30,7 @@ Describe "Validation of build environment" -Tag Unit { $script:changelogVersion = $null foreach ($line in (Get-Content $changelogFile)) { - if ($line -match "(?:##|\)\s*\[?(?\d+(?:\.\d+){1,2})\]?(\-(?(?:alpha|beta|rc)\d*))?") { + if ($line -match "(?:##|\)\s*\[?v?(?\d+\.\d+(?:\.\d+)?)\]?(\-(?(?:alpha|beta|rc)\d*))?") { $changelogVersion = $matches.Version break } diff --git a/Tests/Tools/StandardsVersionConsistency.Unit.Tests.ps1 b/Tests/Tools/StandardsVersionConsistency.Unit.Tests.ps1 index b7350f0..0bec3f6 100644 --- a/Tests/Tools/StandardsVersionConsistency.Unit.Tests.ps1 +++ b/Tests/Tools/StandardsVersionConsistency.Unit.Tests.ps1 @@ -1,7 +1,9 @@ -#requires -modules @{ ModuleName = "Pester"; ModuleVersion = "4.10" } +#requires -modules @{ ModuleName = "Pester"; ModuleVersion = "5.7"; MaximumVersion = "5.999" } Describe 'AtlassianPS.Standards version consistency' -Tag Unit { BeforeAll { + $script:ExpectedStandardsSha = '6fe5d05db84cdd10c9e4284e235a8f359c9537ad' + function Get-RepositoryRoot { if ( $env:BHProjectPath -and @@ -24,50 +26,36 @@ Describe 'AtlassianPS.Standards version consistency' -Tag Unit { throw "Could not resolve repository root from '$PSScriptRoot'." } - } - - It 'keeps workflow setup action pins aligned with build.requirements' { - $projectRoot = Get-RepositoryRoot - $buildRequirementsPath = Join-Path -Path $projectRoot -ChildPath 'Tools/build.requirements.psd1' - $buildRequirements = Import-PowerShellDataFile -Path $buildRequirementsPath - $standardsRequirement = $buildRequirements | - Where-Object { $_.ModuleName -eq 'AtlassianPS.Standards' } | - Select-Object -First 1 - - if (-not $standardsRequirement -or -not $standardsRequirement.RequiredVersion) { - $tokens = $null - $parseErrors = $null - $ast = [System.Management.Automation.Language.Parser]::ParseFile($buildRequirementsPath, [ref]$tokens, [ref]$parseErrors) - if ($parseErrors -and $parseErrors.Count -gt 0) { - throw "Unable to parse build requirements file '$buildRequirementsPath': $($parseErrors[0].Message)" - } - - $statement = $ast.EndBlock.Statements[0] - if ( - $statement -isnot [System.Management.Automation.Language.PipelineAst] -or - $statement.PipelineElements[0] -isnot [System.Management.Automation.Language.CommandExpressionAst] - ) { - throw "Build requirements file '$buildRequirementsPath' does not contain a supported data expression." - } + function Get-StandardsVersion { + param([Parameter(Mandatory)][String]$ProjectRoot) - $buildRequirements = @($statement.PipelineElements[0].Expression.SafeGetValue()) + $buildRequirementsPath = Join-Path -Path $ProjectRoot -ChildPath 'Tools/build.requirements.psd1' + $buildRequirements = Import-PowerShellDataFile -Path $buildRequirementsPath $standardsRequirement = $buildRequirements | Where-Object { $_.ModuleName -eq 'AtlassianPS.Standards' } | Select-Object -First 1 + + if (-not $standardsRequirement -or -not $standardsRequirement.RequiredVersion) { + throw "Could not resolve AtlassianPS.Standards required version from '$buildRequirementsPath'." + } + + return [string] $standardsRequirement.RequiredVersion } + } - $standardsVersion = [string] $standardsRequirement.RequiredVersion - $standardsVersion | Should -Not -BeNullOrEmpty + It 'keeps workflow Standards action pins aligned with build.requirements' { + $projectRoot = Get-RepositoryRoot + $standardsVersion = Get-StandardsVersion -ProjectRoot $projectRoot $workflowPaths = Get-ChildItem -Path (Join-Path -Path $projectRoot -ChildPath '.github/workflows') -File -Filter '*.yml' | Select-Object -ExpandProperty FullName - $workflowActionMatches = foreach ($workflowPath in $workflowPaths) { + $standardsActionReferences = foreach ($workflowPath in $workflowPaths) { $workflowContent = Get-Content -LiteralPath $workflowPath -Raw [regex]::Matches( $workflowContent, - "AtlassianPS/AtlassianPS\.Standards/\.github/actions/setup-powershell@(?[^\s#]+)(?:\s+#\s+v(?[0-9]+\.[0-9]+\.[0-9]+))?" + 'AtlassianPS/AtlassianPS\.Standards/\.github/actions/[^@\s]+@(?[^\s#]+)(?:\s+#\s+v(?[0-9]+\.[0-9]+\.[0-9]+))?' ) | ForEach-Object { [PSCustomObject]@{ WorkflowPath = $workflowPath @@ -77,19 +65,60 @@ Describe 'AtlassianPS.Standards version consistency' -Tag Unit { } } - @($workflowActionMatches).Count | Should -BeGreaterThan 0 + @($standardsActionReferences).Count | Should -BeGreaterThan 0 + @($standardsActionReferences | Where-Object { $_.Ref -notmatch '^[0-9a-f]{40}$' }).Count | Should -Be 0 + @($standardsActionReferences | Where-Object { [string]::IsNullOrWhiteSpace($_.Version) }).Count | Should -Be 0 + ($standardsActionReferences | Select-Object -ExpandProperty Version -Unique) | Should -Be @($standardsVersion) + ($standardsActionReferences | Select-Object -ExpandProperty Ref -Unique) | Should -Be @($script:ExpectedStandardsSha) + } - @($workflowActionMatches | Where-Object { $_.Ref -notmatch '^[0-9a-f]{40}$' }).Count | Should -Be 0 - @($workflowActionMatches | Where-Object { [string]::IsNullOrWhiteSpace($_.Version) }).Count | Should -Be 0 + It 'uses the shared Standards release tag resolver action' { + $projectRoot = Get-RepositoryRoot + $releaseWorkflowContent = Get-Content -LiteralPath (Join-Path -Path $projectRoot -ChildPath '.github/workflows/release.yml') -Raw - @($workflowActionMatches | Select-Object -ExpandProperty Ref -Unique).Count | Should -Be 1 + $releaseWorkflowContent | Should -Match "AtlassianPS/AtlassianPS\.Standards/\.github/actions/resolve-release-tag@$script:ExpectedStandardsSha" + $releaseWorkflowContent | Should -Not -Match 'git\s+rev-list|Resolve-ReleaseTag|release_sha="\$\(git' + } + + It 'builds changelog release notes before publishing and reuses them for GitHub releases' { + $projectRoot = Get-RepositoryRoot + $releaseWorkflowContent = Get-Content -LiteralPath (Join-Path -Path $projectRoot -ChildPath '.github/workflows/release.yml') -Raw + + $releaseWorkflowContent | Should -Match "AtlassianPS/AtlassianPS\.Standards/\.github/actions/build-release-notes@$script:ExpectedStandardsSha" + $releaseWorkflowContent | Should -Match 'body_path:\s+\$\{\{\s*steps\.release_notes\.outputs\.release_notes_path\s*\}\}' + $releaseWorkflowContent | Should -Match 'build-release-notes[\s\S]+Publish module' + $releaseWorkflowContent | Should -Not -Match 'changelog-to-release|changelog\.configuration\.json|steps\.changelog\.outputs\.body|Set-Content|Out-File|release-notes\.md' + Test-Path -LiteralPath (Join-Path -Path $projectRoot -ChildPath '.github/changelog.configuration.json') | Should -BeFalse + } + + It 'keeps published manifest release notes sourced from the changelog' { + $projectRoot = Get-RepositoryRoot + $buildScriptContent = Get-Content -LiteralPath (Join-Path -Path $projectRoot -ChildPath 'ConfluencePS.build.ps1') -Raw - $matchedVersions = @( - $workflowActionMatches | - Select-Object -ExpandProperty Version -Unique - ) - $matchedVersions.Count | Should -Be 1 - $matchedVersions[0] | Should -Be $standardsVersion + $buildScriptContent | Should -Match 'Get-AtlassianPSReleaseNotesFromChangelog[\s\S]+CHANGELOG\.md' + $buildScriptContent | Should -Match 'Set-AtlassianPSModuleManifestVersion[\s\S]+-ReleaseNotes\s+\$releaseNotes' + $buildScriptContent | Should -Not -Match 'function\s+Get-.*ReleaseNotesFromChangelog|Metadata\\Update-Metadata[\s\S]+PropertyName\s+"ReleaseNotes"|Get-Content[\s\S]+CHANGELOG\.md[\s\S]+Set-Content' + } + + It 'uses Standards package validation for CI publish dry-runs' { + $projectRoot = Get-RepositoryRoot + $buildScriptContent = Get-Content -LiteralPath (Join-Path -Path $projectRoot -ChildPath 'ConfluencePS.build.ps1') -Raw + $ciWorkflowContent = Get-Content -LiteralPath (Join-Path -Path $projectRoot -ChildPath '.github/workflows/ci.yml') -Raw + + $buildScriptContent | Should -Match 'Task\s+TestPublish\s+Build,\s+Package' + $buildScriptContent | Should -Match 'New-AtlassianPSModulePackage' + $buildScriptContent | Should -Match 'Test-AtlassianPSModulePackage' + $buildScriptContent | Should -Not -Match 'Task\s+SignCode|Task\s+UpdateHomepage|Compress-Archive' + $ciWorkflowContent | Should -Match 'Invoke-Build -Task Clean, TestPublish' + $ciWorkflowContent | Should -Match 'rhysd/actionlint@[0-9a-f]{40}\s+#\s+v[0-9]+\.[0-9]+\.[0-9]+' + $ciWorkflowContent | Should -Match 'dorny/paths-filter@[0-9a-f]{40}\s+#\s+v[0-9]+' + } + + It 'uses the future release changelog format required by the blueprint' { + $projectRoot = Get-RepositoryRoot + $changelogContent = Get-Content -LiteralPath (Join-Path -Path $projectRoot -ChildPath 'CHANGELOG.md') -Raw + + $changelogContent | Should -Match '(?m)^## v[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)? - \d{4}-\d{2}-\d{2}$' } It 'reads AtlassianPS.Standards version from build.requirements in tool scripts' { @@ -97,6 +126,7 @@ Describe 'AtlassianPS.Standards version consistency' -Tag Unit { $setupScriptContent = Get-Content -LiteralPath (Join-Path -Path $projectRoot -ChildPath 'Tools/setup.ps1') -Raw $updateScriptContent = Get-Content -LiteralPath (Join-Path -Path $projectRoot -ChildPath 'Tools/update.dependencies.ps1') -Raw + $buildScriptContent = Get-Content -LiteralPath (Join-Path -Path $projectRoot -ChildPath 'ConfluencePS.build.ps1') -Raw $setupScriptContent | Should -Match '\$buildRequirements\s*=\s*Import-PowerShellDataFile' $setupScriptContent | Should -Not -Match '\$standardsVersion\s*=\s*''' @@ -107,5 +137,9 @@ Describe 'AtlassianPS.Standards version consistency' -Tag Unit { $updateScriptContent | Should -Match '-RequiredVersion\s+\$standardsVersion' $updateScriptContent | Should -Match '\$PSCmdlet\.ShouldProcess\(' $updateScriptContent | Should -Match 'AtlassianPS\.Standards\\Update-AtlassianPSDependencyReference' + + $buildScriptContent | Should -Match '\$buildRequirements\s*=\s*Import-PowerShellDataFile' + $buildScriptContent | Should -Match '-RequiredVersion\s+\$standardsRequirement\.RequiredVersion' + $buildScriptContent | Should -Not -Match "AtlassianPS\.Standards.*RequiredVersion\s+'[0-9]+\.[0-9]+\.[0-9]+'" } } diff --git a/Tools/build.requirements.psd1 b/Tools/build.requirements.psd1 index be79714..d17f6c5 100644 --- a/Tools/build.requirements.psd1 +++ b/Tools/build.requirements.psd1 @@ -1,5 +1,5 @@ @( - @{ ModuleName = "AtlassianPS.Standards"; RequiredVersion = "0.1.6" } + @{ ModuleName = "AtlassianPS.Standards"; RequiredVersion = "0.1.11" } @{ ModuleName = "InvokeBuild"; RequiredVersion = "5.14.23" } @{ ModuleName = "Metadata"; RequiredVersion = "1.5.7" } @{ ModuleName = "Pester"; RequiredVersion = "5.7.1" }