Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0d9e05e
Enhance customALGoFiles feature (#3)
OleWunschmann Jun 3, 2026
73f4ef3
fixed typo
OleWunschmann Jun 3, 2026
bb38962
fixed function description
OleWunschmann Jun 3, 2026
50ad11c
Fixed tests for ps5 and string-prefix check for folder traversal
OleWunschmann Jun 9, 2026
7aa8766
Merge branch 'main' into enhance-customALGoFiles-feature
OleWunschmann Jun 9, 2026
f7496cc
Merge branch 'main' into enhance-customALGoFiles-feature
OleWunschmann Jun 12, 2026
11e2efc
Merge branch 'main' into enhance-customALGoFiles-feature
OleWunschmann Jun 16, 2026
b021013
Merge branch 'main' into enhance-customALGoFiles-feature
OleWunschmann Jun 17, 2026
025747e
Merge branch 'main' into enhance-customALGoFiles-feature
OleWunschmann Jun 22, 2026
dd1771a
Merge remote-tracking branch 'origin/main' into enhance-customALGoFil…
OleWunschmann Jun 30, 2026
2d24972
Merge remote-tracking branch 'origin/main' into enhance-customALGoFil…
OleWunschmann Jul 9, 2026
5976abe
Merge remote-tracking branch 'origin/main' into enhance-customALGoFil…
OleWunschmann Jul 14, 2026
7fa3522
Extended settings schema and documentation for "destinationName"
OleWunschmann Jul 14, 2026
a5c5fb1
Merge branch 'main' into enhance-customALGoFiles-feature
OleWunschmann Jul 20, 2026
181080e
Merge branch 'main' into enhance-customALGoFiles-feature
OleWunschmann Jul 21, 2026
c23a8ae
Merge branch 'main' into enhance-customALGoFiles-feature
OleWunschmann Jul 21, 2026
afce6d7
Potential fix for pull request finding
OleWunschmann Jul 23, 2026
00d1902
Fixed copilot review comments
OleWunschmann Jul 23, 2026
ee510bd
Merge branch 'main' into enhance-customALGoFiles-feature
OleWunschmann Jul 23, 2026
c8ded0d
Fix copilot review comments
OleWunschmann Jul 24, 2026
409371e
Cleanup
OleWunschmann Jul 24, 2026
84a656e
Cleanup
OleWunschmann Jul 24, 2026
8682d62
Fix copilot review comments
OleWunschmann Jul 24, 2026
051d08a
Remove support for fileToRemove
OleWunschmann Jul 29, 2026
1a037b8
Merge remote-tracking branch 'origin/main' into enhance-customALGoFil…
OleWunschmann Jul 29, 2026
d412a75
Fixed copilot review comment
OleWunschmann Jul 29, 2026
6376e16
Fixed issues
OleWunschmann Jul 29, 2026
73e159c
Cleanup
OleWunschmann Jul 29, 2026
cf71143
Fixed boundary escape for destinationFolder and destinationName
OleWunschmann Jul 29, 2026
f94fe4e
Merge branch 'main' into enhance-customALGoFiles-feature
OleWunschmann Jul 31, 2026
61c640d
Fixed boundary escape for destinationName and typos
OleWunschmann Jul 31, 2026
ea2688e
Fixed boundry escape for wildcards and for linux
OleWunschmann Jul 31, 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
6 changes: 6 additions & 0 deletions Actions/.Modules/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@
"type": "object",
"properties": {
"filesToInclude": {
"description": "An array of file specifications to include in the update. Files that match these specifications are copied from the template to the repository. When used in a custom template's settings, inclusions are also propagated from the original template to consumer repos even if the files no longer exist in the custom template.",
"type": "array",
"items": {
"type": "object",
Expand All @@ -759,6 +760,10 @@
"type": "string",
"description": "The destination folder where the files should be updated, relative to the repository root. If not specified, defaults to the same as the source file folder."
},
"destinationName": {
"type": "string",
"description": "The filename to use at the destination. If specified, overrides the source filename, allowing the file to be renamed when copied. Should be used together with a filter that matches a single file."
Comment thread
mazhelez marked this conversation as resolved.
},
"perProject": {
"type": "boolean",
"description": "Indicates whether the file update should be applied per project. In that case, the destinationFolder is considered relative to each project folder."
Expand All @@ -767,6 +772,7 @@
}
},
"filesToExclude": {
"description": "An array of file specifications to exclude from the update. Files that match these specifications are not copied from the template to the repository. When used in a custom template's settings, exclusions are also propagated from the original template to consumer repos even if the files no longer exist in the custom template.",
"type": "array",
"items": {
"type": "object",
Expand Down
125 changes: 97 additions & 28 deletions Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,8 @@ function ResolveFilePaths {
return @()
}

$sourceFolder = Join-Path $sourceFolder '' # Ensure source folder has a trailing slash for correct path resolution

$fullFilePaths = @()
foreach($file in $files) {
if($file.Keys -notcontains 'sourceFolder') {
Expand Down Expand Up @@ -994,28 +996,88 @@ function GetDefaultFilesToExclude {
return @($filesToExclude)
}

<#
.SYNOPSIS
Reads settings using the current custom template repository settings without changing the workspace.
.DESCRIPTION
Temporarily refreshes the custom template repository settings snapshot, reads the merged settings, and restores
the snapshot to its original state. This allows the current template settings to affect the current run while
preserving the workspace state for the normal update comparison.
.PARAMETER baseFolder
The base folder of the repository whose settings are read.
.PARAMETER templateFolder
The folder where the custom template files are located.
#>
function ReadSettingsWithCurrentCustomTemplateRepoSettings {
Param(
[Parameter(Mandatory=$true)]
[string] $baseFolder,
[Parameter(Mandatory=$true)]
[string] $templateFolder
)

$templateFolderRepoSettingsPath = Join-Path $templateFolder $RepoSettingsFile

$baseFolderTemplateSettingsPath = Join-Path $baseFolder $CustomTemplateRepoSettingsFile
$baseFolderTemplateSettingsBackupPath = $null

if (Test-Path -LiteralPath $baseFolderTemplateSettingsPath -PathType Leaf) {
$baseFolderTemplateSettingsBackupPath = Join-Path (GetTemporaryPath) ([Guid]::NewGuid().ToString())
Copy-Item -LiteralPath $baseFolderTemplateSettingsPath -Destination $baseFolderTemplateSettingsBackupPath -Force
}

try {
if (Test-Path -LiteralPath $templateFolderRepoSettingsPath -PathType Leaf) {
Copy-Item -LiteralPath $templateFolderRepoSettingsPath -Destination $baseFolderTemplateSettingsPath -Force
}
return ReadSettings -baseFolder $baseFolder -buildMode '' -project '' -workflowName '' -userName '' -branchName '' -trigger '' | ConvertTo-HashTable -recurse
}
finally {
if ($baseFolderTemplateSettingsBackupPath) {
Copy-Item -LiteralPath $baseFolderTemplateSettingsBackupPath -Destination $baseFolderTemplateSettingsPath -Force
Remove-Item -LiteralPath $baseFolderTemplateSettingsBackupPath -Force
}
elseif (Test-Path -LiteralPath $baseFolderTemplateSettingsPath -PathType Leaf) {
Remove-Item -LiteralPath $baseFolderTemplateSettingsPath -Force
}
}
}

<#
.SYNOPSIS
Get the list of files from the template repository to include and exclude based on the provided settings.
.DESCRIPTION
This function gets the list of files to include and exclude based on the provided settings.
The unusedALGoSystemFiles setting is also applied to exclude files from the include list and add them to the exclude list.
Builds two lists by merging defaults, repository settings, and the original AL-Go template (if given):

1. filesToInclude: Files to copy from the template or original template to the destination.
Built from default files to include and customALGoFiles.filesToInclude in settings, resolved against the template folder and original template folder (if any).
2. filesToExclude: Files to skip from copying; if they already exist in the destination they should be deleted.
Built from default files to exclude and customALGoFiles.filesToExclude in settings, resolved against the template folder and original template folder (if any).

Note: when a custom template is in use, the caller is expected to call
ReadSettingsWithCurrentCustomTemplateRepoSettings before this function, so that the template's
customALGoFiles/unusedALGoSystemFiles are already merged into settings.

The deprecated unusedALGoSystemFiles setting is also applied: matching files are moved from filesToInclude to
filesToExclude with a deprecation warning.
.PARAMETER settings
The settings object containing the customALGoFiles configuration.
.PARAMETER baseFolder
The base folder of the repository. This is the target folder where the files will be updated.
.PARAMETER templateFolder
The folder where the template files are located.
.PARAMETER originalTemplateFolder
The folder where the original template files are located (if any).
If originalTemplateFolder is provided, it means that there is a custom template in use and custom template files should be included.
The folder where the original AL-Go template files are located (if any).
When provided, it signals that a custom template is in use. Both filesToInclude and filesToExclude specs are
resolved against this folder in addition to templateFolder; entries not already covered by originalSourceFullPath
tracking are appended to propagate upstream template additions and deletions to consumer repositories.
.PARAMETER projects
The list of projects in the repository.
The projects are used to resolve per-project files.
.OUTPUTS
An array containing two elements: the list of files to include and the list of files to exclude.
Files are represented as hashtables with the following keys:
- sourceFullPath: The full path to the source file in the template repository.
- sourceFullPath: The full path to the source file.
- originalSourceFullPath: The full path to the original source file in the original template repository (if any).
- type: The type of the file (e.g., workflow, settings).
- destinationFullPath: The full path to the destination file in the target repository.
Expand All @@ -1032,6 +1094,7 @@ function GetFilesToUpdate {
$projects = @()
)

$hasOriginalTemplate = $null -ne $originalTemplateFolder
Write-Host "Getting files to update from template folder '$templateFolder', original template folder '$originalTemplateFolder' and base folder '$baseFolder'"

# Send telemetery about customALGoFiles usage
Expand All @@ -1041,32 +1104,38 @@ function GetFilesToUpdate {
if ($settings.customALGoFiles.filesToExclude.Count -gt 0) {
Trace-Information -Message "Usage: Custom AL-Go Files (Exclude)"
}

$filesToInclude = GetDefaultFilesToInclude -includeCustomTemplateFiles:$($null -ne $originalTemplateFolder)
$filesToInclude += $settings.customALGoFiles.filesToInclude
$filesToInclude = @(ResolveFilePaths -sourceFolder $templateFolder -originalSourceFolder $originalTemplateFolder -destinationFolder $baseFolder -files $filesToInclude -projects $projects)

$filesToExclude = GetDefaultFilesToExclude -settings $settings
$filesToExclude += $settings.customALGoFiles.filesToExclude
$filesToExclude = @(ResolveFilePaths -sourceFolder $templateFolder -originalSourceFolder $originalTemplateFolder -destinationFolder $baseFolder -files $filesToExclude -projects $projects)

# Exclude files from filesToExclude that are not in filesToInclude
$filesToExclude = @($filesToExclude | Where-Object {
$fileToExclude = $_
$include = $filesToInclude | Where-Object { $_.sourceFullPath -eq $fileToExclude.sourceFullPath }
if(-not $include) {
OutputDebug "Excluding file $($fileToExclude.sourceFullPath) from exclude list as it is not in the include list"
}
return $include
# Determine files to include
$filesToIncludeUnresolved = GetDefaultFilesToInclude -includeCustomTemplateFiles:$hasOriginalTemplate
$filesToIncludeUnresolved += $settings.customALGoFiles.filesToInclude
$filesToInclude = @(ResolveFilePaths -sourceFolder $templateFolder -originalSourceFolder $originalTemplateFolder -destinationFolder $baseFolder -files $filesToIncludeUnresolved -projects $projects)
if ($hasOriginalTemplate) {
$filesToInclude += @(ResolveFilePaths -sourceFolder $originalTemplateFolder -destinationFolder $baseFolder -files $filesToIncludeUnresolved -projects $projects)
}
# Deduplicate files to include based on destinationFullPath, keeping the first one (default > settings; template folder > original template folder)
$filesToInclude = @($filesToInclude | Group-Object { $_.destinationFullPath } | Sort-Object -Property Name | ForEach-Object { $_.Group[0] })

# Determine files to exclude
$filesToExcludeUnresolved = GetDefaultFilesToExclude -settings $settings
$filesToExcludeUnresolved += $settings.customALGoFiles.filesToExclude
$filesToExclude = @(ResolveFilePaths -sourceFolder $templateFolder -originalSourceFolder $originalTemplateFolder -destinationFolder $baseFolder -files $filesToExcludeUnresolved -projects $projects)
if ($hasOriginalTemplate) {
$filesToExclude += @(ResolveFilePaths -sourceFolder $originalTemplateFolder -destinationFolder $baseFolder -files $filesToExcludeUnresolved -projects $projects)
}
# filesToExclude is not deduplicated by destinationFullPath here.
# Its destinationFullPath is never part of the actual output; only sourceFullPath is used below to match against filesToInclude.

# Map files from filesToExclude to files that are in filesToInclude (based on source)
# Settings for filesToExclude only define the sources (sourceFolder and filter) but not the destinations (destinationFolder, destinationName and perProject)
$filesToExclude = @($filesToInclude | Where-Object {
$fileToInclude = $_
return $filesToExclude | Where-Object { $_.sourceFullPath -eq $fileToInclude.sourceFullPath }
})
Comment thread
OleWunschmann marked this conversation as resolved.

# Exclude files from filesToInclude that are in filesToExclude
# Exclude files from filesToInclude that are in filesToExclude (based on source)
$filesToInclude = @($filesToInclude | Where-Object {
$fileToInclude = $_
$include = -not ($filesToExclude | Where-Object { $_.sourceFullPath -eq $fileToInclude.sourceFullPath })
if(-not $include) {
OutputDebug "Excluding file $($fileToInclude.sourceFullPath) from include as it is in the exclude list"
}
$file = $_
$include = -not ($filesToExclude | Where-Object { $_.sourceFullPath -eq $file.sourceFullPath })
if (-not $include) { OutputDebug "Excluding source file '$($file.sourceFullPath)' from include list as it is in the exclude list" }
return $include
})

Expand Down
10 changes: 8 additions & 2 deletions Actions/CheckForUpdates/CheckForUpdates.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ if ($token) {
# if $downloadLatest is set to true, CheckForUpdates will download the latest version of the template repository, else it will use the templateSha setting in the .github/AL-Go-Settings file

# Get Repo settings as a hashtable (do NOT read any specific project settings, nor any specific workflow, user or branch settings)
$repoSettings = ReadSettings -buildMode '' -project '' -workflowName '' -userName '' -branchName '' | ConvertTo-HashTable -recurse
$repoSettings = ReadSettings -buildMode '' -project '' -workflowName '' -userName '' -branchName '' -trigger '' | ConvertTo-HashTable -recurse
$templateSha = $repoSettings.templateSha

# If templateUrl has changed, download latest version of the template repository (ignore templateSha)
Expand Down Expand Up @@ -113,6 +113,12 @@ if (-not $isDirectALGo) {

# Get the list of projects in the current repository
$baseFolder = $ENV:GITHUB_WORKSPACE

if ($originalTemplateFolder) {
# Use current custom template settings for this run without changing the workspace before comparison.
$repoSettings = ReadSettingsWithCurrentCustomTemplateRepoSettings -baseFolder $baseFolder -templateFolder $templateFolder
}

$projects = @(GetProjectsFromRepository -baseFolder $baseFolder -projectsFromSettings $repoSettings.projects)

$filesToInclude, $filesToExclude = GetFilesToUpdate -settings $repoSettings -projects $projects -baseFolder $baseFolder -templateFolder $templateFolder -originalTemplateFolder $originalTemplateFolder
Expand Down Expand Up @@ -204,7 +210,7 @@ foreach($fileToInclude in $filesToInclude) {

Push-Location -Path $baseFolder
# Remove files that are in $filesToExclude and exist in the repository
$removeFiles = $filesToExclude | Where-Object { $_ -and (Test-Path -Path $_.destinationFullPath -PathType Leaf) } | ForEach-Object {
$removeFiles = @($filesToExclude) | Where-Object { $_ -and (Test-Path -Path $_.destinationFullPath -PathType Leaf) } | ForEach-Object {
$relativePath = Resolve-Path -Path $_.destinationFullPath -Relative
Write-Host "File marked for removal: $relativePath"
$relativePath
Expand Down
10 changes: 10 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
### Enhanced `customALGoFiles` setting
Comment thread
mazhelez marked this conversation as resolved.
Comment thread
mazhelez marked this conversation as resolved.

The `customALGoFiles` setting of a custom template was only applied on the next Update (from `AL-Go-TemplateRepoSettings.doNotEdit.json`). Now the up-to-date settings of the custom template are used directly during "Update AL-Go System Files". The template's `filesToInclude` and `filesToExclude` settings are merged with the consumer repo's settings before resolution.

- **`filesToInclude`** now also resolves files from the original AL-Go template. Files present in the official template are propagated even when they are absent from your custom template. When a file exists in both, the official template supplies the base content; for workflow files, customizations from the custom template are reapplied.
- **`filesToExclude`** now also resolves files from the original AL-Go template (same dual-resolution as `filesToInclude`). Files resolved by `filesToInclude` whose source matches a `filesToExclude` entry are not copied to consumer repos, and existing copies are removed.
- **`destinationName`** (new property on `filesToInclude`): Allows renaming a file at the destination. When set, the file is written to `<destinationFolder>/<destinationName>` instead of keeping the source filename.

Read more at [Customizing AL-Go for GitHub](Scenarios/CustomizingALGoForGitHub.md#Using-custom-template-files).

### New `doNotPerformUpgrade` setting

AL-Go now supports a new `doNotPerformUpgrade` setting that is passed through to `Run-AlPipeline`. Use it to skip the upgrade phase while still running the rest of the pipeline.
Expand Down
Loading