Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1c19278
Add support for BCPT and previous apps
aholstrup1 Mar 26, 2026
56dbc17
update docs
aholstrup1 Mar 26, 2026
c888ef6
Review findings
aholstrup1 Mar 26, 2026
ccd0e60
Review
aholstrup1 Mar 27, 2026
56703bd
Update previousApps
aholstrup1 Mar 27, 2026
1894eb1
Suggestions
aholstrup1 Mar 27, 2026
072dcfd
Suggestions
aholstrup1 Mar 27, 2026
470d6fa
Address review: use app ID for baseline matching and baselinePackageC…
aholstrup1 Apr 9, 2026
d445b8c
Remove redundant dependency JSON write-back in Compile.ps1
aholstrup1 Apr 9, 2026
572a86b
Separate BCPT test app files into own variable for telemetry
aholstrup1 Apr 9, 2026
88f0d55
Fix DownloadRelease project matching for root-level projects
aholstrup1 Apr 9, 2026
28e3175
Fix baselinePackageCachePath to use packageCachePath with all depende…
aholstrup1 Apr 9, 2026
35f76ce
Address code review findings
aholstrup1 Apr 10, 2026
d0e8a53
Update README docs for DownloadPreviousRelease and CompileApps
aholstrup1 Apr 10, 2026
1c9e178
Respect local appsourcecop files
aholstrup1 Apr 10, 2026
14b9f45
Update RELEASENOTES for workspace compilation follow-up
aholstrup1 Apr 10, 2026
9a76a6d
Address round 3 review: version casing, test gaps, whitespace fix
aholstrup1 Apr 12, 2026
dcdc0be
Rename PreviousApps to BaselineApps, add branch info to release messages
aholstrup1 May 4, 2026
2e6add6
Merge branch 'main' into aholstrup/workspace_compilation_part2
aholstrup1 May 4, 2026
ce48982
Address round 4 review findings
aholstrup1 May 4, 2026
cd33e7d
Apply AppSourceCop affix/obsoleteTag config even when skipUpgrade is …
aholstrup1 May 4, 2026
5e0c152
Fix mdformat: add blank line after H3 heading in RELEASENOTES.md
aholstrup1 May 4, 2026
13c7885
fix: add UTF-8 BOM to CompileFromWorkspace.Test.ps1
aholstrup1 May 4, 2026
1e202e1
Merge remote-tracking branch 'origin/main' into pr-2186
aholstrup1 May 4, 2026
190d5d7
Merge branch 'main' into aholstrup/workspace_compilation_part2
mazhelez May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 98 additions & 2 deletions Actions/.Modules/CompileFromWorkspace.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ function Get-ALTool {
.PARAMETER EnableExternalRulesets
Switch to enable external rulesets for code analysis.
.PARAMETER AppType
Type of apps being compiled: 'app' or 'testApp'.
Type of apps being compiled: 'app', 'testApp', or 'bcptApp'.
.PARAMETER PreCompileApp
Scriptblock to execute before compiling each app.
.PARAMETER PostCompileApp
Expand Down Expand Up @@ -257,7 +257,7 @@ function Build-AppsInWorkspace {
[Parameter(Mandatory = $false)]
[switch]$EnableExternalRulesets,
[Parameter(Mandatory = $false)]
[ValidateSet('app', 'testApp')]
[ValidateSet('app', 'testApp', 'bcptApp')]
[string]$AppType,
[Parameter(Mandatory = $false)]
[scriptblock]$PreCompileApp,
Expand Down Expand Up @@ -851,10 +851,106 @@ function New-BuildOutputFile {
return $buildOutputPath
}

<#
.SYNOPSIS
Generates AppSourceCop.json files for app folders with baseline version information.
.DESCRIPTION
For each app folder, creates or updates an AppSourceCop.json file with baseline information
for breaking change detection. If the file already exists, it is merged with AL-Go-managed
fields rather than overwritten.

AL-Go-managed keys (set/overwritten by this function):
- version
- baselinePackageCachePath
- mandatoryAffixes
- obsoleteTagMinAllowedMajorMinor

Any other keys in an existing AppSourceCop.json are preserved.
.PARAMETER AppFolders
Array of app folder paths to generate AppSourceCop.json for.
.PARAMETER BaselineApps
Array of file paths to baseline release .app files.
.PARAMETER BaselinePackageCachePath
Path to the folder containing the baseline .app files and their dependencies (used for baselinePackageCachePath in AppSourceCop.json).
.PARAMETER CompilerFolder
Path to the compiler folder containing the AL tool.
.PARAMETER Settings
Hashtable containing the build settings with AppSourceCop configuration.
#>
function New-AppSourceCopJson {
param(
[Parameter(Mandatory = $true)]
[string[]] $AppFolders,
[Parameter(Mandatory = $false)]
[string[]] $BaselineApps = @(),
[Parameter(Mandatory = $false)]
[string] $BaselinePackageCachePath = '',
[Parameter(Mandatory = $true)]
[string] $CompilerFolder,
[Parameter(Mandatory = $true)]
[hashtable] $Settings
)

# Extract version info from baseline apps using the AL tool, keyed by app ID
$baselineAppVersions = @{}
$alToolPath = Get-ALTool -CompilerFolder $CompilerFolder
foreach ($appFile in $BaselineApps) {
try {
$appInfo = RunAndCheck $alToolPath GetPackageManifest $appFile | ConvertFrom-Json | ConvertTo-HashTable -recurse
$baselineAppVersions[$appInfo.Id] = $appInfo.Version.ToString()
}
catch {
OutputWarning -message "Failed to read manifest from '$appFile': $($_.Exception.Message)"
}
}

# Create/update AppSourceCop.json for each app folder with the previous version as baseline and settings from the project configuration
foreach ($folder in $AppFolders) {
$appSourceCopJsonFile = Join-Path $folder "AppSourceCop.json"

# Start from existing content if present, preserving any user-managed settings
$appSourceCopJson = @{}
if (Test-Path $appSourceCopJsonFile) {
try {
$appSourceCopJson = Get-Content -Path $appSourceCopJsonFile -Raw -Encoding UTF8 | ConvertFrom-Json | ConvertTo-HashTable -recurse
}
catch {
OutputWarning -message "Failed to parse existing AppSourceCop.json in '$folder': $($_.Exception.Message). Creating new file."
$appSourceCopJson = @{}
}
}

# Set/override AL-Go managed fields
if ($Settings.appSourceCopMandatoryAffixes -and $Settings.appSourceCopMandatoryAffixes.Count -gt 0) {
$appSourceCopJson["mandatoryAffixes"] = @() + $Settings.appSourceCopMandatoryAffixes
}

if ($Settings.obsoleteTagMinAllowedMajorMinor) {
$appSourceCopJson["obsoleteTagMinAllowedMajorMinor"] = $Settings.obsoleteTagMinAllowedMajorMinor
}

# Match baseline app version by app ID
$appJsonPath = Join-Path $folder "app.json"
if (Test-Path $appJsonPath) {
$appJson = Get-Content -Path $appJsonPath -Raw -Encoding UTF8 | ConvertFrom-Json | ConvertTo-HashTable -recurse
if ($baselineAppVersions.ContainsKey($appJson.id)) {
$appSourceCopJson["version"] = $baselineAppVersions[$appJson.id]
$appSourceCopJson["baselinePackageCachePath"] = $BaselinePackageCachePath
}
}

if ($appSourceCopJson.Count -gt 0) {
Write-Host "Creating AppSourceCop.json for $folder"
$appSourceCopJson | ConvertTo-Json -Depth 99 | Set-Content -Encoding UTF8 $appSourceCopJsonFile
}
}
}

Export-ModuleMember -Function Build-AppsInWorkspace
Export-ModuleMember -Function New-BuildOutputFile
Export-ModuleMember -Function Get-BuildMetadata
Export-ModuleMember -Function Get-CodeAnalyzers
Export-ModuleMember -Function Get-CustomAnalyzers
Export-ModuleMember -Function Get-AssemblyProbingPaths
Export-ModuleMember -Function Update-AppJsonProperties
Export-ModuleMember -Function New-AppSourceCopJson
48 changes: 35 additions & 13 deletions Actions/CompileApps/Compile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ Param(
[Parameter(HelpMessage = "RunId of the baseline workflow run", Mandatory = $false)]
[string] $baselineWorkflowRunId = '0',
[Parameter(HelpMessage = "SHA of the baseline workflow run", Mandatory = $false)]
[string] $baselineWorkflowSHA = ''
[string] $baselineWorkflowSHA = '',
[Parameter(HelpMessage = "Path to folder containing previous release apps for AppSourceCop baseline", Mandatory = $false)]
[string] $previousAppsPath = ''
)

. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve)
Expand All @@ -31,7 +33,7 @@ $settings = AnalyzeRepo -settings $settings -baseFolder $baseFolder -project $pr
$settings = CheckAppDependencyProbingPaths -settings $settings -token $token -baseFolder $baseFolder -project $project

# Check if there are any app folders or test app folders to compile
if ($settings.appFolders.Count -eq 0 -and $settings.testFolders.Count -eq 0) {
if ($settings.appFolders.Count -eq 0 -and $settings.testFolders.Count -eq 0 -and $settings.bcptTestFolders.Count -eq 0) {
Write-Host "No app folders or test app folders specified for compilation. Skipping compilation step."
return
}
Expand Down Expand Up @@ -122,13 +124,26 @@ try {
Write-Host "Incremental builds based on modified apps is not yet implemented."
}

if ((-not $settings.skipUpgrade) -and $settings.enableAppSourceCop) {
# TODO: Missing implementation of around using latest release as a baseline (skipUpgrade) / Appsourcecop.json baseline implementation (AB#620310)
Write-Host "Checking for required upgrades using AppSourceCop..."
if ($settings.enableAppSourceCop) {
# Collect baseline apps for upgrade testing (only when skipUpgrade is false)
$baselineApps = @()
if ((-not $settings.skipUpgrade) -and $previousAppsPath -and (Test-Path $previousAppsPath)) {
$baselineApps = @(Get-ChildItem -Path $previousAppsPath -Recurse -Filter "*.app" | ForEach-Object { $_.FullName })
if ($baselineApps.Count -gt 0) {
# Copy baseline apps to the package cache so AppSourceCop can resolve them alongside their dependencies
$baselineApps | ForEach-Object {
Copy-Item -Path $_ -Destination $packageCachePath -Force
}
}
}

# Generate AppSourceCop.json with mandatory affixes / obsoleteTag settings (always when AppSourceCop is enabled)
# When baseline apps are available, also include the baseline version + package cache path for breaking change detection
New-AppSourceCopJson -AppFolders $settings.appFolders -BaselineApps $baselineApps -BaselinePackageCachePath $packageCachePath -CompilerFolder $compilerFolder -Settings $settings
}

# Update the app jsons with version number (and other properties) from the app manifest files
Update-AppJsonProperties -Folders ($settings.appFolders + $settings.testFolders) `
Update-AppJsonProperties -Folders ($settings.appFolders + $settings.testFolders + $settings.bcptTestFolders) `
-MajorMinorVersion $versionNumber.MajorMinorVersion -BuildNumber $versionNumber.BuildNumber -RevisionNumber $versionNumber.RevisionNumber `
-BuildBy $buildMetadata.BuildBy -BuildUrl $buildMetadata.BuildUrl

Expand All @@ -155,6 +170,7 @@ try {
# Start compilation
$appFiles = @()
$testAppFiles = @()
$bcptTestAppFiles = @()
try {
if ($settings.appFolders.Count -gt 0) {
# Compile Apps
Expand All @@ -176,17 +192,23 @@ try {
-AppType 'testApp'
}

if ($settings.bcptTestFolders.Count -gt 0) {
if (-not ($settings.enableCodeAnalyzersOnTestApps)) {
$buildParams.Analyzers = @()
}

# Compile BCPT Test Apps
$bcptTestAppFiles = Build-AppsInWorkspace @buildParams `
-Folders $settings.bcptTestFolders `
-OutFolder $testAppOutputFolder `
Comment thread
aholstrup1 marked this conversation as resolved.
-AppType 'bcptApp'
}

} finally {
New-BuildOutputFile -BuildArtifactFolder $buildArtifactFolder -BuildOutputPath (Join-Path $projectFolder "BuildOutput.txt") -DisplayInConsole -FailOn $settings.failOn
}

# OUTPUT - Output the updated list of dependency apps and test apps to JSON files for downstream steps
$dependencyApps += $appFiles
$dependencyTestApps += $testAppFiles
Trace-Information -message "Compilation completed. Compiled $(@($appFiles).Count) apps and $(@($testAppFiles).Count) test apps."

ConvertTo-Json $dependencyApps -Depth 99 -Compress | Out-File -Encoding UTF8 -FilePath $dependencyAppsJson
ConvertTo-Json $dependencyTestApps -Depth 99 -Compress | Out-File -Encoding UTF8 -FilePath $dependencyTestAppsJson
Trace-Information -message "Compilation completed. Compiled $(@($appFiles).Count) apps, $(@($testAppFiles).Count) test apps and $(@($bcptTestAppFiles).Count) BCPT test apps."
} finally {
Pop-Location
}
3 changes: 2 additions & 1 deletion Actions/CompileApps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ Compile AL apps by using workspace compilation from the ALTool
| dependencyTestAppsJson | | Path to a JSON file containing a list of dependency test apps | '' |
| baselineWorkflowRunId | | RunId of the baseline workflow run | '' |
| baselineWorkflowSHA | | SHA of the baseline workflow run | '' |
| previousAppsPath | | Path to folder containing previous release apps for AppSourceCop baseline | '' |

## OUTPUT

None but the action will edit the dependencyAppsJson (installAppsJson) and dependencyTestAppsJson (installTestAppsJson) to include the compiled apps. That way the apps will be installed in the RunPipeline step.
None
7 changes: 6 additions & 1 deletion Actions/CompileApps/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ inputs:
description: SHA of the baseline workflow run
required: false
default: ''
previousAppsPath:
description: Path to folder containing previous release apps for AppSourceCop baseline
required: false
default: ''
runs:
using: composite
steps:
Expand All @@ -51,9 +55,10 @@ runs:
_dependencyTestAppsJson: ${{ inputs.dependencyTestAppsJson }}
_baselineWorkflowRunId: ${{ inputs.baselineWorkflowRunId }}
_baselineWorkflowSHA: ${{ inputs.baselineWorkflowSHA }}
_previousAppsPath: ${{ inputs.previousAppsPath }}
run: |
${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "Compile Apps" -Action {
${{ github.action_path }}/Compile.ps1 -token $ENV:_token -artifact $ENV:_artifact -project $ENV:_project -buildMode $ENV:_buildMode -dependencyAppsJson $ENV:_dependencyAppsJson -dependencyTestAppsJson $ENV:_dependencyTestAppsJson -baselineWorkflowRunId $ENV:_baselineWorkflowRunId -baselineWorkflowSHA $ENV:_baselineWorkflowSHA
${{ github.action_path }}/Compile.ps1 -token $ENV:_token -artifact $ENV:_artifact -project $ENV:_project -buildMode $ENV:_buildMode -dependencyAppsJson $ENV:_dependencyAppsJson -dependencyTestAppsJson $ENV:_dependencyTestAppsJson -baselineWorkflowRunId $ENV:_baselineWorkflowRunId -baselineWorkflowSHA $ENV:_baselineWorkflowSHA -previousAppsPath $ENV:_previousAppsPath
}
branding:
icon: terminal
Expand Down
40 changes: 40 additions & 0 deletions Actions/DownloadPreviousRelease/DownloadPreviousRelease.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Param(
[Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)]
[string] $token,
[Parameter(HelpMessage = "Project folder", Mandatory = $false)]
[string] $project = ""
)

. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve)

$baseFolder = (Get-BasePath)
$projectFolder = Join-Path $baseFolder $project
$previousAppsPath = Join-Path $projectFolder ".previousRelease"
New-Item $previousAppsPath -ItemType Directory -Force | Out-Null
Comment thread
aholstrup1 marked this conversation as resolved.

OutputGroupStart -Message "Locating previous release"
try {
$branchForRelease = if ($ENV:GITHUB_BASE_REF) { $ENV:GITHUB_BASE_REF } else { $ENV:GITHUB_REF_NAME }
$latestRelease = GetLatestRelease -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -ref $branchForRelease
if ($latestRelease) {
Write-Host "Using $($latestRelease.name) (tag $($latestRelease.tag_name)) as previous release for branch '$branchForRelease'"
# Use the project name for asset matching; for root projects (".") use wildcard to match repo-named assets
$releaseProject = if ($project -eq '.' -or $project -eq '') { '*' } else { $project }
DownloadRelease -token $token -projects $releaseProject -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $latestRelease -path $previousAppsPath -mask "Apps" -unpack
$previousApps = @(Get-ChildItem -Path $previousAppsPath -Recurse -Filter "*.app" | ForEach-Object { $_.FullName })
Write-Host "Downloaded $($previousApps.Count) previous release app(s)"
}
else {
OutputWarning -message "No previous release found for branch '$branchForRelease'"
}
Comment thread
aholstrup1 marked this conversation as resolved.
}
catch {
OutputError -message "Error trying to locate previous release. Error was $($_.Exception.Message)"
throw
}
finally {
OutputGroupEnd
}

# Output variable with path to previous apps for use in subsequent steps
Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "PreviousAppsPath=$previousAppsPath"
Comment thread
aholstrup1 marked this conversation as resolved.
28 changes: 28 additions & 0 deletions Actions/DownloadPreviousRelease/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Download Previous Release
Comment thread
aholstrup1 marked this conversation as resolved.

Downloads the latest release apps for the current branch, for use as a baseline in AppSourceCop validation and upgrade testing.
The release is determined based on the target branch (for pull requests) or the current branch (for pushes).

## INPUT

### ENV variables

| Name | Description |
| :-- | :-- |
| Settings | env.Settings must be set by a prior call to the ReadSettings Action |

### Parameters

| Name | Required | Description | Default value |
| :-- | :-: | :-- | :-- |
| shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell |
| token | | The GitHub token running the action | github.token |
| project | | The AL-Go project for which to download previous release apps | '.' |

## OUTPUT

### OUTPUT variables

| Name | Description |
| :-- | :-- |
| PreviousAppsPath | Path to the folder containing the downloaded previous release apps. |
35 changes: 35 additions & 0 deletions Actions/DownloadPreviousRelease/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Download Previous Release
author: Microsoft Corporation
inputs:
shell:
description: Shell in which you want to run the action (powershell or pwsh)
required: false
default: powershell
token:
description: The GitHub token running the action
required: false
default: ${{ github.token }}
project:
description: Project folder
required: false
default: '.'
outputs:
PreviousAppsPath:
description: Path to the folder containing the downloaded previous release apps.
value: ${{ steps.DownloadPreviousRelease.outputs.PreviousAppsPath }}
runs:
using: composite
steps:
- name: run
shell: ${{ inputs.shell }}
id: DownloadPreviousRelease
env:
_token: ${{ inputs.token }}
_project: ${{ inputs.project }}
run: |
${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "DownloadPreviousRelease" -Action {
${{ github.action_path }}/DownloadPreviousRelease.ps1 -token $ENV:_token -project $ENV:_project
}
branding:
icon: terminal
color: blue
Loading
Loading