Skip to content

Add opt-in unpublishOldVersions setting to unpublish stale PTE app versions after deploy - #2340

Open
Louage Job (louagej) wants to merge 10 commits into
microsoft:mainfrom
louagej:feature/unpublish-old-versions
Open

Add opt-in unpublishOldVersions setting to unpublish stale PTE app versions after deploy#2340
Louage Job (louagej) wants to merge 10 commits into
microsoft:mainfrom
louagej:feature/unpublish-old-versions

Conversation

@louagej

Copy link
Copy Markdown

Scenario

When AL-Go deploys a new version of a Per Tenant Extension (PTE) to an environment, the previous version is upgraded and uninstalled — but it stays published. Over time these old, uninstalled versions pile up in Extension Management, making the list noisy and harder to reason about.

This PR adds an opt-in setting that lets AL-Go automatically clean up those stale versions right after a successful deployment.

What's new

A new boolean property unpublishOldVersions on the DeployTo<environment> setting (default false):

{
  "DeployToSandbox": {
    "EnvironmentName": "MySandbox",
    "unpublishOldVersions": true
  }
}

When enabled, after publishing the apps AL-Go enumerates the published extensions in the environment and unpublishes any version that is:

  1. not installed, AND
  2. older than a version of the same app (matched by app id) that is installed.

It uses the Automation API v2.0 action:

POST .../companies({companyId})/extensions({packageId})/Microsoft.NAV.unpublish

Background

This was originally requested in discussion #1557.

At the time it was dismissed because the platform lacked the capability (ref: microsoft/navcontainerhelper#2808):

"Currently, there is no functionality in the API to unpublish an app that we can use... if anybody has the time, it is possible to create this functionality themselves."

That blocker is now solved — the Automation API v2.0 exposes Microsoft.NAV.unpublish, which this PR builds on.

Design decisions

  • Opt-in — new DeployTo<env> boolean unpublishOldVersions, default false, so there is zero behaviour change for existing users.
  • PTE scope only — the cleanup runs only on the automation-API deployment path. The Dev endpoint already replaces the package via -replacePackageId.
  • Conservative — only versions that are not installed and older than a currently installed version of the same app id are unpublished. Installed versions and newer versions are always kept.
  • Non-fatal — the whole operation is wrapped in try/catch; any failure is reported as a warning and never fails the deployment.
  • Reuses BcContainerHelper's own auth pattern (Renew-BcAuthContext + bearer AccessToken, apiBaseUrl-based automation URL) rather than rolling a custom token flow.

Note: BcContainerHelper's Publish-PerTenantExtensionApps has a -unpublishPreviousVersions switch (BC 25.4+), but it only unpublishes the single directly-upgraded previous version. UnpublishOldAppVersions is broader — it cleans up all stale published-but-uninstalled versions of the deployed apps.

Changes

File Change
Actions/Deploy/Deploy.psm1 New function UnpublishOldAppVersions
Actions/Deploy/Deploy.ps1 Call it after Publish-PerTenantExtensionApps, gated by the setting (PTE branch)
Scenarios/settings.md Document the new unpublishOldVersions property in the DeployTo<environment> table
RELEASENOTES.md Add an entry
Tests/UnpublishOldAppVersions.Test.ps1 Pester unit tests

Tests

New Pester tests (Tests/UnpublishOldAppVersions.Test.ps1) cover:

  • Unpublishing an old, uninstalled version when a newer version is installed
  • Using the correct Automation API v2.0 endpoint + bearer token
  • Skipping when only one version is published
  • Skipping when the deployed version is not installed
  • Never touching installed or newer versions
  • Only unpublishing versions of the matching app id
  • Non-fatal behaviour when the unpublish call or environment query fails
  • Warning when no company is found
  • No-op when no app files are provided

All new tests pass, and the existing Deploy.Module tests continue to pass.

Checklist

  • Links discussion Question: Unpublish old version automatically? #1557 and quotes the old blocker
  • Links navcontainerhelper#2808, notes Microsoft.NAV.unpublish now exists
  • Opt-in + PTE-only (no behaviour change by default)
  • Unit tests included
  • Settings documented in Scenarios/settings.md
  • RELEASENOTES.md updated

After a new PTE version is installed, previous (uninstalled) versions stay published and clutter Extension Management. Add an opt-in DeployTo<env> setting 'unpublishOldVersions' that unpublishes old, uninstalled app versions via the Automation API v2.0 Microsoft.NAV.unpublish action (PTE scope only, non-fatal). Includes Pester unit tests, settings docs and release notes.
@louagej
Louage Job (louagej) requested a review from a team as a code owner August 5, 2026 09:27
Copilot AI balanced review requested due to automatic review settings August 5, 2026 09:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds opt-in cleanup of stale, uninstalled PTE versions after deployment.

Changes:

  • Adds Automation API cleanup logic and deployment gating.
  • Documents the setting and release behavior.
  • Adds Pester coverage for cleanup scenarios.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Actions/Deploy/Deploy.psm1 Implements stale-version cleanup.
Actions/Deploy/Deploy.ps1 Invokes cleanup after PTE deployment.
Tests/UnpublishOldAppVersions.Test.ps1 Tests cleanup behavior and failures.
Scenarios/settings.md Documents the new setting.
RELEASENOTES.md Announces the feature.

Comment thread Actions/Deploy/Deploy.psm1
Comment thread Scenarios/settings.md
| <a id="environments"></a>environments | Array of logical environment names. You can specify environments in GitHub environments or in the repo settings file. If you specify environments in the settings file, you can create your AUTHCONTEXT secret using **\<environmentname>\_AUTHCONTEXT**. You can specify additional information about environments in a setting called **DeployTo\<environmentname>** |
| <a id="deliverto"></a>DeliverTo\<deliveryTarget> | Structure with additional properties for the deliveryTarget specified. Some properties are deliveryTarget specific. The structure can contain the following properties:<br />**Branches** = an array of branch patterns, which are allowed to deliver to this deliveryTarget. (Default main)<br />**CreateContainerIfNotExist** = *[Only for DeliverToStorage]* Create Blob Storage Container if it doesn't already exist. (Default false)<br /> |
| <a id="deployto"></a>DeployTo\<environmentname> | Structure with additional properties for the environment specified. `<environmentName>` refers to the GitHub environment name. The structure can contain the following properties:<br />**EnvironmentType** = specifies the type of environment. The environment type can be used to invoke a custom deployment. (Default SaaS)<br />**EnvironmentName** = specifies the "real" name of the environment if it differs from the GitHub environment.<br />**Branches** = an array of branch patterns, which are allowed to deploy to this environment. These branches can also be defined under the environment in GitHub settings and both settings are honored. If neither setting is defined, the default is the **main** branch only.<br />**Projects** = In multi-project repositories, this property can be a comma separated list of project patterns to deploy to this environment. (Default \*)<br />**DependencyInstallMode** = Determines how dependencies are deployed if `GenerateDependencyArtifact` is true. Default value is `install` to install dependencies if not already installed. Other values are `ignore` for ignoring dependencies and `upgrade` or `forceUpgrade` for upgrading dependencies.<br />**includeTestAppsInSandboxEnvironment** = deploys test apps and their dependencies if the environment type is sandbox (Default is `false`)<br />**excludeAppIds** = array of app ids to exclude from deployment. (Default is `[]`)<br />**Scope** = Determines the mechanism for deployment to the environment (Dev or PTE). If not specified, AL-Go for GitHub will always use the Dev Scope for AppSource Apps, but also for PTEs when deploying to sandbox environments when impersonation (refreshtoken) is used for authentication.<br />**SyncMode** = ForceSync if deployment to this environment should happen with ForceSync, else Add. If deploying to the development endpoint you can also specify Development or Clean. (Default Add)<br />**BuildMode** = specifies which buildMode to use for the deployment. Default is to use the Default buildMode<br />**ContinuousDeployment** = true if this environment should be used for continuous deployment, else false. (Default: AL-Go will continuously deploy to sandbox environments or environments, which doesn't end in (PROD) or (FAT)<br />**runs-on** = specifies which runner to use when deploying to this environment. (Default is settings.runs-on)<br />**shell** = specifies which shell to use when deploying to this environment, pwsh or powershell. (Default is settings.shell)<br />**companyId** = Company Id from Business Central (for PowerPlatform connection)<br />**ppEnvironmentUrl** = Url of the PowerPlatform environment to deploy to<br /> |
| <a id="deployto"></a>DeployTo\<environmentname> | Structure with additional properties for the environment specified. `<environmentName>` refers to the GitHub environment name. The structure can contain the following properties:<br />**EnvironmentType** = specifies the type of environment. The environment type can be used to invoke a custom deployment. (Default SaaS)<br />**EnvironmentName** = specifies the "real" name of the environment if it differs from the GitHub environment.<br />**Branches** = an array of branch patterns, which are allowed to deploy to this environment. These branches can also be defined under the environment in GitHub settings and both settings are honored. If neither setting is defined, the default is the **main** branch only.<br />**Projects** = In multi-project repositories, this property can be a comma separated list of project patterns to deploy to this environment. (Default \*)<br />**DependencyInstallMode** = Determines how dependencies are deployed if `GenerateDependencyArtifact` is true. Default value is `install` to install dependencies if not already installed. Other values are `ignore` for ignoring dependencies and `upgrade` or `forceUpgrade` for upgrading dependencies.<br />**includeTestAppsInSandboxEnvironment** = deploys test apps and their dependencies if the environment type is sandbox (Default is `false`)<br />**excludeAppIds** = array of app ids to exclude from deployment. (Default is `[]`)<br />**Scope** = Determines the mechanism for deployment to the environment (Dev or PTE). If not specified, AL-Go for GitHub will always use the Dev Scope for AppSource Apps, but also for PTEs when deploying to sandbox environments when impersonation (refreshtoken) is used for authentication.<br />**SyncMode** = ForceSync if deployment to this environment should happen with ForceSync, else Add. If deploying to the development endpoint you can also specify Development or Clean. (Default Add)<br />**unpublishOldVersions** = When set to `true`, AL-Go will unpublish old, uninstalled versions of the deployed apps from the environment after a successful deployment, to keep Extension Management clean. Only applies to PTE deployments (Scope PTE / automation API) and is non-fatal (failures are reported as warnings). (Default false)<br />**BuildMode** = specifies which buildMode to use for the deployment. Default is to use the Default buildMode<br />**ContinuousDeployment** = true if this environment should be used for continuous deployment, else false. (Default: AL-Go will continuously deploy to sandbox environments or environments, which doesn't end in (PROD) or (FAT)<br />**runs-on** = specifies which runner to use when deploying to this environment. (Default is settings.runs-on)<br />**shell** = specifies which shell to use when deploying to this environment, pwsh or powershell. (Default is settings.shell)<br />**companyId** = Company Id from Business Central (for PowerPlatform connection)<br />**ppEnvironmentUrl** = Url of the PowerPlatform environment to deploy to<br /> |
Comment thread Actions/Deploy/Deploy.ps1 Outdated
Write-Host "Publishing apps using automation API"
Publish-PerTenantExtensionApps @parameters

if ($deploymentSettings.unpublishOldVersions) {
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 09:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

Scenarios/settings.md:78

  • The new setting is documented here but is absent from Actions/.Modules/settings.schema.json. Add schema metadata for dynamic DeployTo<environment> objects with unpublishOldVersions typed as boolean and defaulting to false; otherwise editors and settings validation cannot enforce the advertised type/default, which is especially important for an option that deletes published packages.
| <a id="deployto"></a>DeployTo\<environmentname> | Structure with additional properties for the environment specified. `<environmentName>` refers to the GitHub environment name. The structure can contain the following properties:<br />**EnvironmentType** = specifies the type of environment. The environment type can be used to invoke a custom deployment. (Default SaaS)<br />**EnvironmentName** = specifies the "real" name of the environment if it differs from the GitHub environment.<br />**Branches** = an array of branch patterns, which are allowed to deploy to this environment. These branches can also be defined under the environment in GitHub settings and both settings are honored. If neither setting is defined, the default is the **main** branch only.<br />**Projects** = In multi-project repositories, this property can be a comma separated list of project patterns to deploy to this environment. (Default \*)<br />**DependencyInstallMode** = Determines how dependencies are deployed if `GenerateDependencyArtifact` is true. Default value is `install` to install dependencies if not already installed. Other values are `ignore` for ignoring dependencies and `upgrade` or `forceUpgrade` for upgrading dependencies.<br />**includeTestAppsInSandboxEnvironment** = deploys test apps and their dependencies if the environment type is sandbox (Default is `false`)<br />**excludeAppIds** = array of app ids to exclude from deployment. (Default is `[]`)<br />**Scope** = Determines the mechanism for deployment to the environment (Dev or PTE). If not specified, AL-Go for GitHub will always use the Dev Scope for AppSource Apps, but also for PTEs when deploying to sandbox environments when impersonation (refreshtoken) is used for authentication.<br />**SyncMode** = ForceSync if deployment to this environment should happen with ForceSync, else Add. If deploying to the development endpoint you can also specify Development or Clean. (Default Add)<br />**unpublishOldVersions** = When set to `true`, AL-Go will unpublish old, uninstalled versions of the deployed apps from the environment after a successful deployment, to keep Extension Management clean. Only applies to PTE deployments (Scope PTE / automation API) and is non-fatal (failures are reported as warnings). (Default false)<br />**BuildMode** = specifies which buildMode to use for the deployment. Default is to use the Default buildMode<br />**ContinuousDeployment** = true if this environment should be used for continuous deployment, else false. (Default: AL-Go will continuously deploy to sandbox environments or environments, which doesn't end in (PROD) or (FAT)<br />**runs-on** = specifies which runner to use when deploying to this environment. (Default is settings.runs-on)<br />**shell** = specifies which shell to use when deploying to this environment, pwsh or powershell. (Default is settings.shell)<br />**companyId** = Company Id from Business Central (for PowerPlatform connection)<br />**ppEnvironmentUrl** = Url of the PowerPlatform environment to deploy to<br /> |

Tests/UnpublishOldAppVersions.Test.ps1:37

  • The mocked /extensions response omits the Application record. UnpublishOldAppVersions reads that record to enforce the BC 25.4 minimum, so these fixtures produce version 0.0.0.0 (or throw under strict mode) and return before any unpublish POST. Consequently, the positive test and the expected Failed to unpublish warning test fail, while several negative cases pass without exercising their intended selection logic. Include an installed Application extension at version 25.4+ in the default fixture and in each override meant to reach that logic.
            $script:mockExtensions = @(
                @{ id = $script:appId; displayName = "App 1"; packageId = "pkg-app1-v1"; isInstalled = $false; versionMajor = 1; versionMinor = 0; versionBuild = 0; versionRevision = 0 },
                @{ id = $script:appId; displayName = "App 1"; packageId = "pkg-app1-v2"; isInstalled = $true;  versionMajor = 2; versionMinor = 0; versionBuild = 0; versionRevision = 0 }
            )

Comment thread Actions/Deploy/Deploy.ps1 Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 5, 2026 10:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (4)

Scenarios/settings.md:78

  • This new user-facing setting is documented but has not been added to Actions/.Modules/settings.schema.json. Add a boolean schema definition with the same description and default so configuration tooling can validate and discover it, as required for AL-Go settings.
| <a id="deployto"></a>DeployTo\<environmentname> | Structure with additional properties for the environment specified. `<environmentName>` refers to the GitHub environment name. The structure can contain the following properties:<br />**EnvironmentType** = specifies the type of environment. The environment type can be used to invoke a custom deployment. (Default SaaS)<br />**EnvironmentName** = specifies the "real" name of the environment if it differs from the GitHub environment.<br />**Branches** = an array of branch patterns, which are allowed to deploy to this environment. These branches can also be defined under the environment in GitHub settings and both settings are honored. If neither setting is defined, the default is the **main** branch only.<br />**Projects** = In multi-project repositories, this property can be a comma separated list of project patterns to deploy to this environment. (Default \*)<br />**DependencyInstallMode** = Determines how dependencies are deployed if `GenerateDependencyArtifact` is true. Default value is `install` to install dependencies if not already installed. Other values are `ignore` for ignoring dependencies and `upgrade` or `forceUpgrade` for upgrading dependencies.<br />**includeTestAppsInSandboxEnvironment** = deploys test apps and their dependencies if the environment type is sandbox (Default is `false`)<br />**excludeAppIds** = array of app ids to exclude from deployment. (Default is `[]`)<br />**Scope** = Determines the mechanism for deployment to the environment (Dev or PTE). If not specified, AL-Go for GitHub will always use the Dev Scope for AppSource Apps, but also for PTEs when deploying to sandbox environments when impersonation (refreshtoken) is used for authentication.<br />**SyncMode** = ForceSync if deployment to this environment should happen with ForceSync, else Add. If deploying to the development endpoint you can also specify Development or Clean. (Default Add)<br />**unpublishOldVersions** = When set to `true`, AL-Go will unpublish old, uninstalled versions of the deployed apps from the environment after a successful deployment, to keep Extension Management clean. Only applies to PTE deployments (Scope PTE / automation API) and is non-fatal (failures are reported as warnings). (Default false)<br />**BuildMode** = specifies which buildMode to use for the deployment. Default is to use the Default buildMode<br />**ContinuousDeployment** = true if this environment should be used for continuous deployment, else false. (Default: AL-Go will continuously deploy to sandbox environments or environments, which doesn't end in (PROD) or (FAT)<br />**runs-on** = specifies which runner to use when deploying to this environment. (Default is settings.runs-on)<br />**shell** = specifies which shell to use when deploying to this environment, pwsh or powershell. (Default is settings.shell)<br />**companyId** = Company Id from Business Central (for PowerPlatform connection)<br />**ppEnvironmentUrl** = Url of the PowerPlatform environment to deploy to<br /> |

Tests/UnpublishOldAppVersions.Test.ps1:45

  • The shared API fixture never returns the Application extension, but the function now requires its version before entering the cleanup loop. As a result, the positive unpublish test and the per-call failure test return through the outer warning path without issuing a POST, while several negative tests pass without exercising their intended conditions. Add an installed Application record at version 25.4+ to every mocked extensions response (the shared mock is the simplest place).
                    return @{ value = $script:mockExtensions }

Actions/Deploy/Deploy.ps1:219

  • The advertised opt-in behavior is not covered at this call site: the new tests invoke UnpublishOldAppVersions directly, so they cannot catch a regression where a missing/false setting triggers cleanup, a true setting does not, or the Dev path invokes it. Add action-level Pester cases for missing/false/true values and both deployment paths.
if (($deploymentSettings.unpublishOldVersions -is [bool]) -and $deploymentSettings.unpublishOldVersions) {
                    Write-Host "Unpublishing old app versions"
                    UnpublishOldAppVersions -bcAuthContext $bcAuthContext -environment $deploymentSettings.EnvironmentName -appFiles $apps

Actions/Deploy/Deploy.psm1:297

  • The Business Central 25.4 compatibility gate is a new behavior with no corresponding boundary test. Add Pester cases for a pre-25.4 Application record (warning and no POST) and a 25.4 record (cleanup proceeds), so the minimum-version contract cannot regress.
        $application = $extensions | Where-Object { $_.displayName -eq 'Application' } | Select-Object -First 1
        $applicationVersion = [version]::new($application.versionMajor, $application.versionMinor, $application.versionBuild, $application.versionRevision)
        if ($applicationVersion -lt [version]'25.4.0.0') {
            OutputWarning -message "Unpublishing old app versions requires Business Central 25.4 or later; environment $environment is running $applicationVersion."
            return

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

Scenarios/settings.md:78

  • This introduces a new user-facing AL-Go setting but does not add corresponding metadata to Actions/.Modules/settings.schema.json. Please define the boolean setting there as well, with an aligned description and default, so schema-based validation and editor tooling recognize the documented setting.
| <a id="deployto"></a>DeployTo\<environmentname> | Structure with additional properties for the environment specified. `<environmentName>` refers to the GitHub environment name. The structure can contain the following properties:<br />**EnvironmentType** = specifies the type of environment. The environment type can be used to invoke a custom deployment. (Default SaaS)<br />**EnvironmentName** = specifies the "real" name of the environment if it differs from the GitHub environment.<br />**Branches** = an array of branch patterns, which are allowed to deploy to this environment. These branches can also be defined under the environment in GitHub settings and both settings are honored. If neither setting is defined, the default is the **main** branch only.<br />**Projects** = In multi-project repositories, this property can be a comma separated list of project patterns to deploy to this environment. (Default \*)<br />**DependencyInstallMode** = Determines how dependencies are deployed if `GenerateDependencyArtifact` is true. Default value is `install` to install dependencies if not already installed. Other values are `ignore` for ignoring dependencies and `upgrade` or `forceUpgrade` for upgrading dependencies.<br />**includeTestAppsInSandboxEnvironment** = deploys test apps and their dependencies if the environment type is sandbox (Default is `false`)<br />**excludeAppIds** = array of app ids to exclude from deployment. (Default is `[]`)<br />**Scope** = Determines the mechanism for deployment to the environment (Dev or PTE). If not specified, AL-Go for GitHub will always use the Dev Scope for AppSource Apps, but also for PTEs when deploying to sandbox environments when impersonation (refreshtoken) is used for authentication.<br />**SyncMode** = ForceSync if deployment to this environment should happen with ForceSync, else Add. If deploying to the development endpoint you can also specify Development or Clean. (Default Add)<br />**unpublishOldVersions** = When set to `true`, AL-Go will unpublish old, uninstalled versions of the deployed apps from the environment after a successful deployment, to keep Extension Management clean. Only applies to PTE deployments (Scope PTE / automation API) and is non-fatal (failures are reported as warnings). (Default false)<br />**BuildMode** = specifies which buildMode to use for the deployment. Default is to use the Default buildMode<br />**ContinuousDeployment** = true if this environment should be used for continuous deployment, else false. (Default: AL-Go will continuously deploy to sandbox environments or environments, which doesn't end in (PROD) or (FAT)<br />**runs-on** = specifies which runner to use when deploying to this environment. (Default is settings.runs-on)<br />**shell** = specifies which shell to use when deploying to this environment, pwsh or powershell. (Default is settings.shell)<br />**companyId** = Company Id from Business Central (for PowerPlatform connection)<br />**ppEnvironmentUrl** = Url of the PowerPlatform environment to deploy to<br /> |

Comment on lines +31 to +35
# Default published extensions in the environment:
# - app 1 v1.0.0.0 (uninstalled, old) -> eligible for unpublish
# - app 1 v2.0.0.0 (installed, deployed) -> keep
$script:mockExtensions = @(
@{ id = $script:appId; displayName = "App 1"; packageId = "pkg-app1-v1"; isInstalled = $false; versionMajor = 1; versionMinor = 0; versionBuild = 0; versionRevision = 0 },
Comment thread Actions/Deploy/Deploy.ps1 Outdated
Write-Host "Publishing apps using automation API"
Publish-PerTenantExtensionApps @parameters

if (($deploymentSettings.unpublishOldVersions -is [bool]) -and $deploymentSettings.unpublishOldVersions) {
- Add unpublishOldVersions=false to both default deployment-setting maps in DetermineDeploymentEnvironments.ps1 so StrictMode 2.0 does not throw for repos that do not set the new opt-in setting.
- Add a DeployTo patternProperties schema entry for unpublishOldVersions (boolean, default false) so tooling can validate/expose the setting.
…xture

- Use index access for unpublishOldVersions so a missing key returns $null under StrictMode 2.0 instead of throwing (also fixes the mis-indented call site).
- Inject an installed 'Application' 25.4+ record into every /extensions mock response so the version gate is satisfied and tests exercise the real cleanup path.
- Add a test covering the BC 25.4 gate (older environment warns and skips).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Actions/Deploy/Deploy.ps1:217

  • This condition is not strictly opt-in for malformed settings. The merge above only warns on a type mismatch and still assigns the supplied value, so a JSON string such as "false" is truthy in PowerShell and will enable this destructive cleanup (especially on PowerShell 5, where schema validation is skipped). Require an actual Boolean true before unpublishing.
                if ($deploymentSettings['unpublishOldVersions']) {

"ppEnvironmentUrl" = ''
"includeTestAppsInSandboxEnvironment" = $false
"excludeAppIds" = @()
"unpublishOldVersions" = $false
- Require an actual Boolean true for unpublishOldVersions so a JSON string like 'false' (truthy in PowerShell) does not enable cleanup.
- Add unpublishOldVersions=false to the expected DeploymentEnvironmentsJson hashes in DetermineDeploymentEnvironments.Test.ps1 so exact MatchHashtable assertions stay green.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

Suppressed comments (1)

Actions/Deploy/Deploy.psm1:293

  • The extensions response includes published-but-uninstalled entries, and its ordering does not identify the environment's active version. Selecting the first Application record can therefore use a stale version and incorrectly skip cleanup on a supported environment (or attempt it on an unsupported one). Derive the version from the installed Application entry instead.
        $application = $extensions | Where-Object { $_.displayName -eq 'Application' } | Select-Object -First 1

The extensions response can contain multiple Application records (including uninstalled ones), so selecting the first could use a stale version. Filter on isInstalled and skip with a warning if no installed Application is found. Adds a test proving an uninstalled Application record is ignored.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

Suppressed comments (1)

Actions/Deploy/Deploy.psm1:320

  • This exact-version guard skips cleanup when the environment already has a newer installed version than the deployment artifact. Publish-PerTenantExtensionApps treats an installed version greater than or equal to the artifact as successfully “already installed,” so an environment with installed v3, deployed v2, and stale uninstalled v1 reaches this code but leaves v1 published. Use the currently installed version of this app ID as the cleanup threshold; the existing no-installed-version case will still be skipped.
            # Only unpublish old versions if the just-deployed version is actually installed
            $newInstalled = $matching | Where-Object {
                ([version]::new($_.versionMajor, $_.versionMinor, $_.versionBuild, $_.versionRevision) -eq $deployed.Version) -and $_.isInstalled
            }
            if (-not $newInstalled) {

Previously cleanup required the deployed artifact version to be installed. If the environment already has a newer installed version (which Publish-PerTenantExtensionApps treats as 'already installed' success), stale uninstalled versions were left published. Now the currently installed (highest) version of the app id is used as the threshold; the no-installed-version case is still skipped. Adds a test for installed v3 / deployed v2 / stale v1.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

Suppressed comments (2)

Actions/Deploy/Deploy.ps1:219

  • The destructive opt-in wiring is not covered by the new tests: they exercise the helper directly, while the existing deployment tests do not verify this branch. Please add action-level Pester coverage showing that true invokes cleanup only after a PTE publish, and that the default/false value and Dev scope never invoke it; these are the key safety guarantees of this setting.
                if (($deploymentSettings['unpublishOldVersions'] -is [bool]) -and $deploymentSettings['unpublishOldVersions']) {
                    Write-Host "Unpublishing old app versions"
                    UnpublishOldAppVersions -bcAuthContext $bcAuthContext -environment $deploymentSettings.EnvironmentName -appFiles $apps

Actions/Deploy/Deploy.psm1:260

  • This parameter help says the artifact version determines eligibility, but the implementation uses only the artifact app ID and compares published versions against the currently installed environment version. Please describe that threshold accurately so callers do not assume the deployed artifact version is a safety bound.
    .PARAMETER appFiles
        The list of deployed app files. The app id and version are read from these files to determine which published
        versions are considered "old" and eligible for unpublishing.

- Add Deploy action tests proving unpublishOldVersions=true invokes cleanup only after a PTE publish, default/unset never invokes it, and Dev scope never invokes it.
- Correct the appFiles parameter help to state only the app id is used and versions are compared against the installed environment version, not the deployed artifact version.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

DownloadAndImportBcContainerHelper -baseFolder $([System.IO.Path]::GetTempPath())

function InvokeDeploy {
Param([bool] $sandbox = $true, [hashtable] $deploymentSettings)
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.

3 participants