Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
4f14364
use private containerhelper
freddydk Dec 25, 2025
001c298
check for apps/TestApps
freddydk Dec 25, 2025
bbcbdcd
add concept of Solo projects
freddydk Dec 25, 2025
ad0fb9b
change solo mechanism
freddydk Dec 25, 2025
d27ff30
dump msg
freddydk Dec 25, 2025
b164aea
array
freddydk Dec 25, 2025
45127f4
push to ghcr
freddydk Dec 26, 2025
969a48e
pull
freddydk Dec 26, 2025
55ad671
pull
freddydk Dec 26, 2025
a0755d6
dump fullname
freddydk Dec 26, 2025
80b189a
use full
freddydk Dec 26, 2025
d261c93
lower
freddydk Dec 26, 2025
c5ea8f3
revert
freddydk Dec 27, 2025
371ab33
use bhg containerhelper
freddydk Dec 29, 2025
ec14162
swallow errors
freddydk Dec 29, 2025
0b6e66f
Only grab the latest version
freddydk Dec 29, 2025
515ee0e
dumps
freddydk Dec 29, 2025
ad29d15
select Obj
freddydk Dec 29, 2025
b402ad0
Grab latest version
freddydk Dec 29, 2025
7e94d82
revert
freddydk Jan 5, 2026
fdbb93f
better output
freddydk Jan 6, 2026
9567259
spell
freddydk Jan 6, 2026
aeaf93f
only one project that builds the app is needed
freddydk Jan 6, 2026
0673701
check previously build projects
freddydk Jan 6, 2026
541b465
just check previously build projects
freddydk Jan 6, 2026
a698be7
just check
freddydk Jan 6, 2026
2de55ca
try
freddydk Jan 6, 2026
27c6a1b
recalc
freddydk Jan 6, 2026
e823b15
check built
freddydk Jan 6, 2026
b674381
check other
freddydk Jan 6, 2026
6b3ab8d
dumps
freddydk Jan 6, 2026
8c99adc
check alreadybuilt
freddydk Jan 6, 2026
c43f03a
correct dump
freddydk Jan 6, 2026
b58b310
use projectswithoutdependants
freddydk Jan 6, 2026
acee7d5
remove comment
freddydk Jan 6, 2026
7a11bbe
try old build mechanism
freddydk Jan 6, 2026
feabb73
sort projects
freddydk Jan 6, 2026
0b0b476
Merge branch 'main' into bhg
freddydk Jan 7, 2026
167d3d3
Get branch for artifacts
freddydk Jan 11, 2026
48773ba
Merge branch 'bhg' of https://github.com/freddydk/AL-Go into bhg
freddydk Jan 11, 2026
09dd39d
dump branch
freddydk Jan 11, 2026
92d8916
comments
freddydk Jan 11, 2026
13e36b8
sort pr builds
freddydk Jan 11, 2026
88031ae
use trim
freddydk Jan 15, 2026
8cd6ca2
Merge branch 'main' into bhg
freddydk Jan 16, 2026
9d153ae
remove dump and fix comments
freddydk Jan 18, 2026
6993edb
release notes
freddydk Jan 18, 2026
716979e
Update Actions/AL-Go-Helper.ps1
freddydk Jan 19, 2026
7c4ca5d
review
freddydk Jan 19, 2026
a1f74a5
Merge branch 'bhg' of https://github.com/freddydk/AL-Go into bhg
freddydk Jan 19, 2026
6098e23
use preview
freddydk Jan 19, 2026
ea880dd
Merge branch 'main' into bhg
freddydk Jan 22, 2026
640923e
Implement postponeProjectInBuildOrder setting
freddydk Jan 22, 2026
e660cda
Merge branch 'bhg' of https://github.com/freddydk/AL-Go into bhg
freddydk Jan 22, 2026
9a19dfd
add test for postpone
freddydk Jan 22, 2026
e44504d
add docs
freddydk Jan 22, 2026
5dab34d
code review
freddydk Jan 26, 2026
a10dc26
Update Actions/AL-Go-Helper.ps1
freddydk Jan 28, 2026
483f423
Update Actions/AL-Go-Helper.ps1
freddydk Jan 28, 2026
b43f174
Update Actions/AL-Go-Helper.ps1
freddydk Jan 28, 2026
9a879ab
Update Actions/AL-Go-Helper.ps1
freddydk Jan 28, 2026
d1fd33e
Update Actions/AL-Go-Helper.ps1
freddydk Jan 28, 2026
ba19388
Update Actions/AL-Go-Helper.ps1
freddydk Jan 28, 2026
718efd6
Merge branch 'main' into bhg
freddydk Jan 28, 2026
f120454
review
freddydk Jan 28, 2026
1114d44
code review
freddydk Jan 29, 2026
9ad6ccf
fix downloadrelease
freddydk Jan 29, 2026
38e149d
Update Actions/AL-Go-Helper.ps1
freddydk Jan 29, 2026
aabb050
Update Actions/AL-Go-Helper.ps1
freddydk Jan 29, 2026
c960505
spell
freddydk Jan 29, 2026
d96cc88
Merge branch 'bhg' of https://github.com/freddydk/AL-Go into bhg
freddydk Jan 29, 2026
9de168d
Merge branch 'main' into bhg
freddydk Jan 29, 2026
20bfab6
Merge branch 'main' into bhg
aholstrup1 Feb 4, 2026
e725a12
Merge branch 'main' into bhg
aholstrup1 Feb 5, 2026
1e30f08
Merge branch 'main' into bhg
aholstrup1 Feb 10, 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
3 changes: 2 additions & 1 deletion Actions/.Modules/ReadSettings.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,10 @@ function GetDefaultSettings
"reportSuppressedDiagnostics" = $false
"workflowDefaultInputs" = @()
"customALGoFiles" = [ordered]@{
"filesToInclude" = @()
"filesToInclude" = @()
"filesToExclude" = @()
}
"postponeProjectInBuildOrder" = $false
}
}

Expand Down
4 changes: 4 additions & 0 deletions Actions/.Modules/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,10 @@
}
}
},
"postponeProjectInBuildOrder": {
"type": "boolean",
"description": "Indicates whether the project can be postponed in the build order to optimize build times. See https://aka.ms/ALGoSettings#postponeProjectInBuildOrder"
},
"workflowDefaultInputs": {
"type": "array",
"items": {
Expand Down
69 changes: 66 additions & 3 deletions Actions/AL-Go-Helper.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,38 @@ function CheckAndCreateProjectFolder {
}
}

function TestIfProjectHasDependents {
Param(
[string] $project,
[string[]] $projects,
[hashtable] $appDependencies,
[array] $projectsOrder
)

$hasRemainingDependents = $false
foreach($otherProject in $projects) {
if ($otherProject -ne $project) {
# Grab dependencies from other project, which haven't been included in the build order yet
$otherDependencies = $appDependencies."$otherProject".dependencies | Where-Object {
$dependency = $_
$alreadyBuilt = ($projectsOrder | ForEach-Object { $_.Projects | Where-Object { $appDependencies."$_".apps -contains $dependency } })
return -not $alreadyBuilt
}
Write-Host "Other project $otherProject has dependencies that are not in the build order yet: $($otherDependencies -join ", ")"
foreach($dependency in $otherDependencies) {
if ($appDependencies."$project".apps -contains $dependency) {
Write-Host "Project $project is still a dependency for project $otherProject"
$hasRemainingDependents = $true
}
}
}
}
if (!$hasRemainingDependents) {
Write-Host "Project $project has no dependents, can be built later"
}
return $hasRemainingDependents
}

Function AnalyzeProjectDependencies {
Param(
[string] $baseFolder,
Expand All @@ -1737,10 +1769,14 @@ Function AnalyzeProjectDependencies {
# Loop through all projects
# Get all apps in the project
# Get all dependencies for the apps
$projectsThatCanBePostponed = @()
foreach($project in $projects) {
Write-Host "- Analyzing project: $project"
Write-Host -NoNewline "Analyzing project: $project, "

$projectSettings = ReadSettings -project $project -baseFolder $baseFolder
if ($projectSettings.postponeProjectInBuildOrder) {
$projectsThatCanBePostponed += $project
}
ResolveProjectFolders -baseFolder $baseFolder -project $project -projectSettings ([ref] $projectSettings)

# App folders are relative to the AL-Go project folder. Convert them to relative to the base folder
Expand All @@ -1754,7 +1790,7 @@ Function AnalyzeProjectDependencies {
Pop-Location
}

OutputMessageAndArray -Message "Folders containing apps" -arrayOfStrings $folders
OutputMessageAndArray -Message "folders containing apps" -arrayOfStrings $folders

$unknownDependencies = @()
$apps = @()
Expand Down Expand Up @@ -1785,6 +1821,11 @@ Function AnalyzeProjectDependencies {
# }
$no = 1
$projectsOrder = @()
# Collect projects without dependents, which can be built later
# This is done to avoid building projects at an earlier stage than needed and increase the time until next job subsequently
# For every time we have determined a set of projects that can be build in parallel, we check whether any of these projects has no dependents
# If so, we remove these projects from the build order and add them at the end of the build order (by adding them to projectsWithoutDependents)
$projectsWithoutDependents = @()
Write-Host "Analyzing dependencies"
while ($projects.Count -gt 0) {
$thisJob = @()
Expand All @@ -1799,6 +1840,11 @@ Function AnalyzeProjectDependencies {
# Loop through all dependencies and locate the projects, containing the apps for which the current project has a dependency
$foundDependencies = @()
foreach($dependency in $dependencies) {
# Check whether dependency is already resolved by a previous build project
$depProject = @($projectsOrder | ForEach-Object { $_.Projects | Where-Object { $_ -ne $project -and $appDependencies."$_".apps -contains $dependency } })
if ($depProject.Count -gt 0) {
continue
}
# Find the project that contains the app for which the current project has a dependency
$depProjects = @($projects | Where-Object { $_ -ne $project -and $appDependencies."$_".apps -contains $dependency })
# Add this project and all projects on which that project has a dependency to the list of dependencies for the current project
Expand Down Expand Up @@ -1851,11 +1897,28 @@ Function AnalyzeProjectDependencies {
if ($thisJob.Count -eq 0) {
throw "Circular project reference encountered, cannot determine build order"
}

# Check whether any of the projects in $thisJob can be built later (has postponeProjectInBuildOrder set to true and no remaining dependents)
$projectsWithoutDependents += @($thisJob | Where-Object { $projectsThatCanBePostponed -contains $_ } | Where-Object {
return -not (TestIfProjectHasDependents -project $_ -projects $projects -appDependencies $appDependencies -projectsOrder $projectsOrder)
})

# Remove projects in this job from the list of projects to be built (including the projects without dependents)
$projects = @($projects | Where-Object { $thisJob -notcontains $_ })

# Do not build jobs without dependents until the last job, remove them from this job
$thisJob = @($thisJob | Where-Object { $projectsWithoutDependents -notcontains $_ })

if ($projects.Count -eq 0) {
# Last job, add jobs without dependents
Write-Host "Adding projects without dependents to last build job"
$thisJob += $projectsWithoutDependents
}

Write-Host "#$no - build projects: $($thisJob -join ", ")"

$projectsOrder += @{'projects' = $thisJob; 'projectsCount' = $thisJob.Count }

$projects = @($projects | Where-Object { $thisJob -notcontains $_ })
$no++
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function DownloadDependenciesFromCurrentBuild {
Write-Host "Dependency projects: $($dependencyProjects -join ', ')"

# For each dependency project, calculate the corresponding probing path
$dependeciesProbingPaths = @()
$dependenciesProbingPaths = @()
foreach($dependencyProject in $dependencyProjects) {
Write-Host "Reading settings for project '$dependencyProject'"
$dependencyProjectSettings = ReadSettings -baseFolder $baseFolder -project $dependencyProject
Expand All @@ -70,7 +70,7 @@ function DownloadDependenciesFromCurrentBuild {
$baseBranch = $ENV:GITHUB_REF_NAME
}

$dependeciesProbingPaths += @(@{
$dependenciesProbingPaths += @(@{
"release_status" = "thisBuild"
"version" = "latest"
"buildMode" = $dependencyBuildMode
Expand All @@ -85,7 +85,7 @@ function DownloadDependenciesFromCurrentBuild {

# For each probing path, download the dependencies
$downloadedDependencies = @()
foreach($probingPath in $dependeciesProbingPaths) {
foreach($probingPath in $dependenciesProbingPaths) {
$buildMode = $probingPath.buildMode
$project = $probingPath.projects
$branch = $probingPath.branch
Expand Down
63 changes: 56 additions & 7 deletions Actions/Github-Helper.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,16 @@ function GetDependencies {
$projects = $dependency.projects
$buildMode = $dependency.buildMode

if ($mask -eq 'TestApps') {
$altMask = 'Apps'
}
else {
$altMask = 'TestApps'
}
# change the mask to include the build mode
if($buildMode -ne "Default") {
$mask = "$buildMode$mask"
$altMask = "$buildMode$altMask"
}

Write-Host "Locating $mask artifacts for projects: $projects"
Expand All @@ -169,8 +176,15 @@ function GetDependencies {
}
}
elseif ($mask -like '*Apps') {
Write-Host "$project not built, downloading from artifacts"
$missingProjects += @($project)
# Check whether Apps/TestApps exists before determining that project isn't built
$altDownloadName = Join-Path $saveToPath "$project-$branchName-$altMask-*"
if (!(Test-Path $altDownloadName -PathType Container)) {
Write-Host "$project not built, downloading from artifacts"
$missingProjects += @($project)
}
else {
Write-Host "$project built, but $mask not found"
}
}
}
if ($missingProjects -and $dependency.baselineWorkflowID) {
Expand Down Expand Up @@ -796,10 +810,14 @@ function DownloadRelease {
# GitHub replaces series of special characters with a single dot when uploading release assets
$project = [Uri]::EscapeDataString($project.Replace('\','_').Replace('/','_').Replace(' ','.')).Replace('%2A','*').Replace('%3F','?').Replace('%','')
Write-Host "project '$project'"
$assetPattern1 = "$project-*-$mask-*.zip"
$assetPattern2 = "$project-$mask-*.zip"
# Pattern 1: project-branch-mask-version.zip (branch used for release creation cannot contain -)
# Pattern 2: project-mask-version.zip (no branch)
$escapedProject = [regex]::Escape($project)
$escapedMask = [regex]::Escape($mask)
$assetPattern1 = "^$escapedProject-[^-]+-$escapedMask-.+\.zip$"
$assetPattern2 = "^$escapedProject-$escapedMask-.+\.zip$"
Write-Host "AssetPatterns: '$assetPattern1' | '$assetPattern2'"
$assets = @($release.assets | Where-Object { $_.name -like $assetPattern1 -or $_.name -like $assetPattern2 })
$assets = @($release.assets | Where-Object { $_.name -match $assetPattern1 -or $_.name -match $assetPattern2 })
foreach($asset in $assets) {
$uri = "$api_url/repos/$repository/releases/assets/$($asset.id)"
Write-Host $uri
Expand Down Expand Up @@ -1069,6 +1087,11 @@ function GetArtifactsFromWorkflowRun {
# Get sanitized project names (the way they appear in the artifact names)
$projectArr = @(@($projects.Split(',')) | ForEach-Object { $_.Replace('\','_').Replace('/','_') })

# Get branch used in workflowRun
$workflowRunInfo = (InvokeWebRequest -Headers $headers -Uri "$api_url/repos/$repository/actions/runs/$workflowRun").Content | ConvertFrom-Json
$branch = $workflowRunInfo.head_branch.Replace('\', '_').Replace('/', '_')
Write-Host "Branch for workflow run $workflowRun is $branch"

# Get the artifacts from the the workflow run
while($true) {
$artifactsURI = "$api_url/repos/$repository/actions/runs/$workflowRun/artifacts?per_page=$per_page&page=$page"
Expand All @@ -1080,14 +1103,40 @@ function GetArtifactsFromWorkflowRun {
}

foreach($project in $projectArr) {
$artifactPattern = "$project-*-$mask-*" # e.g. "MyProject-*-Apps-*", format is: "project-branch-mask-version"
# e.g. "MyProject-main-Apps-*", format is: "project-branch-mask-version"
# Mask might include buildMode like TranslatedTestApps
$artifactPattern = "$project-$branch-$mask-*"
$matchingArtifacts = @($artifacts.artifacts | Where-Object { $_.name -like $artifactPattern })

if ($matchingArtifacts.Count -eq 0) {
continue
}

$matchingArtifacts = @($matchingArtifacts) #enforce array
# If there are reruns of the build we found, we might see artifacts like:
# Test DBC-BHG-SAF-T-main-TestApps-1.0.48.0
# Test DBC-BHG-SAF-T-main-TestApps-1.0.48.1
# We want to keep only the latest version of each artifact (based on the last segment of the version)
$matchingArtifacts = @($matchingArtifacts | ForEach-Object {
# Sort on version number object
if ($_.name -match '^(.*)-(\d+\.\d+\.\d+\.\d+)$') {
[PSCustomObject]@{
Name = $Matches[1]
Version = [version]$Matches[2]
Obj = $_
}
}
else {
# artifacts from PR builds doesn't match the versioning pattern but are sortable
[PSCustomObject]@{
Name = $_.name
Version = $_.name
Obj = $_
}
}
} | Group-Object Name | ForEach-Object {
$_.Group | Sort-Object Version -Descending | Select-Object -First 1
} |
Select-Object -ExpandProperty Obj)

foreach($artifact in $matchingArtifacts) {
Write-Host "Found artifact $($artifact.name) (ID: $($artifact.id)) for mask $mask and project $project"
Expand Down
15 changes: 10 additions & 5 deletions Actions/PipelineCleanup/PipelineCleanup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
[string] $project = "."
)

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

if ($project -eq ".") { $project = "" }
if ($project -eq ".") { $project = "" }

$containerName = GetContainerName($project)
Remove-Bccontainer $containerName
$containerName = GetContainerName($project)
Remove-Bccontainer $containerName
}
catch {
Write-Host "Pipeline Cleanup failed: $($_.Exception.Message)"
}
7 changes: 7 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

### Issues

- Issue 2084 Multiple artifacts failure if you re-run failed jobs after flaky tests
- Issue 2085 Projects that doesn't contain both Apps and TestApps are wrongly seen as not built.
- Issue 2086 Postpone jobs, which doesn't have any dependents to the end of the build order.
- Issue 2095 DeliverToAppSource.ProductId needs to be specified (Library app)
- Issue 2082 Sign action no longer fails when repository is empty or no artifacts are generated
- Issue 2078 Workflows run since January 14th '26 have space before CI/CD removed
Expand All @@ -24,6 +27,10 @@ Previously, when running the "Publish To Environment" workflow with an environme

Now, the workflow will fail with a clear error message if the specified environment doesn't exist. If you intentionally want to deploy to a new environment that hasn't been configured yet, you can check the **Create environment if it does not exist** checkbox when running the workflow.

### New Settings

- `postponeProjectInBuildOrder` is a new project setting, which will (if set to true) cause the project to be postponed until the last build job when possible. If set on test projects, then all tests can be deferred until all builds have succeeded.

### Set default values for workflow inputs

The `workflowDefaultInputs` setting now also applies to `workflow_call` inputs when an input with the same name exists for `workflow_dispatch`.
Expand Down
1 change: 1 addition & 0 deletions Scenarios/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ When running a workflow or a local script, the settings are applied by reading s
| <a id="appDependencyProbingPaths"></a>appDependencyProbingPaths | Array of dependency specifications, from which apps will be downloaded when the CI/CD workflow is starting. Every dependency specification consists of the following properties:<br />**repo** = repository<br />**version** = version (default latest)<br />**release_status** = latestBuild/release/prerelease/draft (default release)<br />**projects** = projects (default * = all)<br />**branch** = branch (default main)<br />**AuthTokenSecret** = Name of secret containing auth token (default none)<br /> | [ ] |
| <a id="preprocessorSymbols"></a>preprocessorSymbols | List of preprocessor symbols to use when building the apps. This setting can be specified in [workflow specific settings files](https://aka.ms/algosettings#where-are-the-settings-located) or in [conditional settings](https://aka.ms/algosettings#conditional-settings). | [ ] |
| <a id="bcptThresholds"></a>bcptThresholds | Structure with properties for the thresholds when running performance tests using the Business Central Performance Toolkit.<br />**DurationWarning** = a warning is shown if the duration of a bcpt test degrades more than this percentage (default 10)<br />**DurationError** - an error is shown if the duration of a bcpt test degrades more than this percentage (default 25)<br />**NumberOfSqlStmtsWarning** - a warning is shown if the number of SQL statements from a bcpt test increases more than this percentage (default 5)<br />**NumberOfSqlStmtsError** - an error is shown if the number of SQL statements from a bcpt test increases more than this percentage (default 10)<br />*Note that errors and warnings on the build in GitHub are only issued when a threshold is exceeded on the codeunit level, when an individual operation threshold is exceeded, it is only shown in the test results viewer.* |
| <a id="postponeProjectInBuildOrder"></a>postponeProjectInBuildOrder | When this setting is enabled (true), the project will be postponed to the end of the build sequence whenever possible - specifically, when no other projects depend on it. This allows, for example, test projects to run only after all build projects have completed successfully.<br/>This setting only has effect when useProjectDependencies is also enabled, as dependency information is required to determine whether postponement is allowed. | false |

## AppSource specific basic project settings

Expand Down
Loading