Skip to content

Enhance customALGoFiles feature - #2273

Open
OleWunschmann wants to merge 32 commits into
microsoft:mainfrom
OleWunschmann:enhance-customALGoFiles-feature
Open

Enhance customALGoFiles feature#2273
OleWunschmann wants to merge 32 commits into
microsoft:mainfrom
OleWunschmann:enhance-customALGoFiles-feature

Conversation

@OleWunschmann

@OleWunschmann OleWunschmann commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

❔What, Why & How

This pull request significantly enhances how custom template file settings are handled during the "Update AL-Go System Files" process. The main improvements are that the latest custom template settings are now used immediately (rather than only after a subsequent update), both filesToInclude and filesToExclude are resolved from both the custom and original templates, and a new destinationName property enables file renaming on copy. Additionally, several path resolution and safety improvements have been made to prevent files from being copied outside intended directories.

Improvements to custom template file handling:

  • The current customALGoFiles settings from the custom template are now merged and used directly during updates, ensuring that the latest template configuration is always applied without waiting for a new update cycle. [1] [2]
  • Both filesToInclude and filesToExclude are now resolved against both the custom template and the original AL-Go template, ensuring that files added or removed upstream are properly propagated to consumer repositories. [1] [2]
  • A new destinationName property is added to filesToInclude, allowing files to be renamed when copied to the destination. This is reflected in both the schema and implementation. [1] [2]

Path resolution and safety:

  • All source and destination folders are now canonicalized to absolute paths, and checks are added to ensure no file is copied outside the intended destination folder (with warnings if such a case is detected). [1] [2] [3]

Documentation and schema updates:

  • The schema and documentation for filesToInclude and filesToExclude have been updated to clarify their new behavior and the new destinationName property. [1] [2] [3]

These changes make custom template management more robust, predictable, and safer for consumer repositories.

Related to discussion: #2227

✅ Checklist

  • Add tests (E2E, unit tests)
  • Update RELEASENOTES.md
  • Update documentation (e.g. for new settings or scenarios)
  • Add telemetry

Copilot AI review requested due to automatic review settings June 3, 2026 15:24
@OleWunschmann
OleWunschmann requested a review from a team as a code owner June 3, 2026 15:24

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

Note

Copilot was unable to run its full agentic suite in this review.

This PR enhances the customALGoFiles behavior during “Update AL-Go System Files”, adding support for unconditional removals (filesToRemove) and improving custom-template behavior by merging template settings directly and resolving files from the original AL-Go template where applicable.

Changes:

  • Add customALGoFiles.filesToRemove support end-to-end (schema, defaults, resolution logic, docs, and release notes).
  • Update CheckForUpdates to read template repo settings via ReadSettings and merge template settings during file resolution.
  • Expand automated coverage (unit + e2e) for include/exclude/remove resolution and custom-template propagation.

Reviewed changes

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

Show a summary per file
File Description
e2eTests/scenarios/CustomTemplate/runtest.ps1 Extends e2e scenario to validate custom-template file include/exclude/remove propagation and workflow presence.
Tests/CheckForUpdates.Action.Test.ps1 Adds unit tests for destination-folder resolution and expanded GetFilesToUpdate behaviors (including filesToRemove).
Actions/CheckForUpdates/CheckForUpdates.ps1 Updates settings reading (incl. trigger) and wires template settings + filesToRemove into update/removal flow.
Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 Implements ResolveFilePathsInDestinationFolder and extends GetFilesToUpdate to produce include/exclude/remove lists.
Actions/.Modules/settings.schema.json Extends settings schema with customALGoFiles.filesToRemove and clarifying descriptions.
Actions/.Modules/ReadSettings.psm1 Adds default filesToRemove array under customALGoFiles.
Scenarios/settings.md Documents customALGoFiles.filesToRemove in settings reference.
Scenarios/CustomizingALGoForGitHub.md Adds conceptual docs + examples for original-template resolution and filesToRemove.
RELEASENOTES.md Documents enhanced customALGoFiles behavior and new filesToRemove.

Comment thread Tests/CheckForUpdates.Action.Test.ps1 Outdated
Comment thread Tests/CheckForUpdates.Action.Test.ps1 Outdated
Comment thread e2eTests/scenarios/CustomTemplate/runtest.ps1
Comment thread e2eTests/scenarios/CustomTemplate/runtest.ps1
Comment thread Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 Outdated
Comment thread Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1
Comment thread e2eTests/scenarios/CustomTemplate/runtest.ps1 Outdated

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 3 comments.

Comment thread Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 Outdated
Comment thread Scenarios/CustomizingALGoForGitHub.md Outdated
Comment thread Actions/.Modules/settings.schema.json Outdated
@OleWunschmann

Copy link
Copy Markdown
Contributor Author

@mazhelez The failing PS5 tests should be fixed now.

Comment thread RELEASENOTES.md
Comment thread RELEASENOTES.md
@OleWunschmann
OleWunschmann requested a review from spetersenms July 9, 2026 17:11
Comment thread Actions/.Modules/settings.schema.json Outdated
Copilot AI review requested due to automatic review settings July 21, 2026 09:45

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 3 comments.

Comments suppressed due to low confidence (1)

Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1:1205

  • Removal destinations are not constrained to $baseFolder. A filesToRemove entry with a matching template file and destinationFolder = '..' produces a destinationFullPath outside the repository; CheckForUpdates.ps1 later converts it to a relative path and passes it to Remove-Item. Normalize every removal destination and reject paths that are not descendants of the repository root before returning this list.
        $filesToRemove += @(ResolveFilePaths -sourceFolder $originalTemplateFolder -destinationFolder $baseFolder -files $filesToRemoveUnresolved -projects $projects)
    }
    $filesToRemove += @(ResolveFilePaths -sourceFolder $templateFolder -originalSourceFolder $originalTemplateFolder -destinationFolder $baseFolder -files $filesToRemoveUnresolved -projects $projects)
    $filesToRemove += @(ResolveFilePathsInDestinationFolder -destinationFolder $baseFolder -files $filesToRemoveUnresolved -projects $projects)

Comment on lines +1168 to +1171
if ($null -ne $templateSettings) {
$filesToIncludeUnresolved += $templateSettings.customALGoFiles.filesToInclude
}
$filesToIncludeUnresolved += $settings.customALGoFiles.filesToInclude

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.

What Copilot says he was what I was worried about - not sure whether it is a problem though

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread Actions/CheckForUpdates/CheckForUpdates.ps1 Outdated
Comment thread Scenarios/CustomizingALGoForGitHub.md Outdated
@mazhelez mazhelez self-assigned this Jul 21, 2026

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 no new comments.

Comments suppressed due to low confidence (4)

Scenarios/CustomizingALGoForGitHub.md:250

  • This description is inaccurate for settings files. CheckForUpdates.ps1:164-167 passes them through GetModifiedSettingsContent, which preserves existing destination settings and primarily refreshes template metadata such as $schema; it does not use the original template file content as-is. Separate settings files from the “all other files” behavior so consumers do not expect their repository settings to be replaced.
- **Settings files** and **all other files** (e.g. PowerShell scripts, `.copy.md`, `.agent.md`): the original template's file content is used as-is; changes made to that same file in your custom template are not applied in this case.

Actions/.Modules/settings.schema.json:746

  • The PR description promises a new customALGoFiles.filesToRemove array for unconditional removal, but the updated schema still defines only filesToInclude and filesToExclude, and no runtime implementation or documentation for filesToRemove remains in this revision. Either restore that advertised feature across schema/code/docs/tests or update the PR description to reflect the actual scope.
                "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.",

e2eTests/scenarios/CustomTemplate/runtest.ps1:261

  • This comment names the wrong list: Troubleshooting.yaml is restored because it is matched by the default filesToInclude workflow pattern and resolved from the original template, not because it is in filesToExclude.
# Check that missing workflow file is present (in default filesToExclude)

e2eTests/scenarios/CustomTemplate/runtest.ps1:445

  • Use the possessive “repo's,” not “repos's.”
# Check that optional custom file is present (in repos's filesToInclude)

Copilot AI review requested due to automatic review settings July 29, 2026 14:05

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 no new comments.

Comments suppressed due to low confidence (2)

e2eTests/scenarios/CustomTemplate/runtest.ps1:359

  • This second $legacyCustomFileName assertion also references an undefined variable under strict mode. Since this scenario no longer creates a legacy file, remove the stale assertion (or restore a complete setup if that behavior still needs coverage).
(Join-Path (Get-Location) $legacyCustomFileName) | Should -Exist

e2eTests/scenarios/CustomTemplate/runtest.ps1:257

  • $legacyCustomFileName is never defined anywhere in this scenario (or the repository), and strict mode is enabled at line 44. The test will terminate here with an undefined-variable error before reaching the new update validations. Remove this stale assertion or restore the missing legacy-file setup.

This issue also appears on line 359 of the same file.

(Join-Path (Get-Location) $legacyCustomFileName) | Should -Not -Exist

Copilot AI review requested due to automatic review settings July 29, 2026 14:13

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 no new comments.

Comments suppressed due to low confidence (5)

RELEASENOTES.md:3

  • The PR description still advertises a new filesToRemove setting and unconditional removal behavior, but the current schema, release notes, and runtime expose only filesToInclude and filesToExclude. Please update the PR description so it does not promise functionality that was removed from this revision.
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.

Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1:830

  • Appending a separator does not make the later -like "$sourceFolder*" check literal: PowerShell still interprets wildcard metacharacters in the resolved path. A workspace path containing [/] can reject legitimate files, and * can broaden this boundary check. Use canonical full paths with a literal StartsWith comparison (and the platform-appropriate case comparison) instead of a wildcard pattern.
    $sourceFolder = Join-Path $sourceFolder '' # Ensure source folder has a trailing slash for correct path resolution

e2eTests/scenarios/CustomTemplate/runtest.ps1:441

  • Correct the possessive typo from “repos's” to “repo's.”
# Check that optional custom file is present (in repos's filesToInclude)

Actions/.Modules/settings.schema.json:765

  • destinationName is described as a filename but accepts path separators and ... ResolveFilePaths passes it directly to Join-Path, so a value such as ../outside.txt produces a destination outside destinationFolder and can escape the repository when the update is written. Reject non-leaf names/path traversal at runtime and constrain the schema accordingly.
                            "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."

Scenarios/CustomizingALGoForGitHub.md:242

  • The new destinationName setting is documented here and in the schema, but the canonical settings reference at Scenarios/settings.md:251 was not updated with its type, constraints, and behavior. Add it there so the setting documentation remains complete and aligned.
- `destinationName`: 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. _Example_: `customScript.ps1`.

Copilot AI review requested due to automatic review settings July 29, 2026 14:46

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.

Comments suppressed due to low confidence (4)

Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1:830

  • Appending a separator does not make the later wildcard operations path-safe: Get-ChildItem -Path and -like "$sourceFolder*" still interpret characters such as [ in a valid repository path as wildcard syntax, causing contained files to be skipped or misresolved. Use -LiteralPath for enumeration and a canonical GetFullPath/StartsWith boundary comparison instead.
    $sourceFolder = Join-Path $sourceFolder '' # Ensure source folder has a trailing slash for correct path resolution

RELEASENOTES.md:7

  • destinationName was already supported by ResolveFilePaths and its existing tests; this PR documents it and adds it to the schema rather than introducing a new runtime property. Reword the release note to avoid announcing an existing capability as new.
- **`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.

e2eTests/scenarios/CustomTemplate/runtest.ps1:441

  • Use the possessive “repo's,” not “repos's.”
# Check that optional custom file is present (in repos's filesToInclude)

Actions/.Modules/settings.schema.json:775

  • The PR description still advertises filesToRemove and ResolveFilePathsInDestinationFolder as major additions, but the current schema and implementation only support filesToInclude and filesToExclude. Update the PR description to reflect the removed functionality, or restore the promised implementation before merging.
                "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.",

Comment thread Actions/.Modules/settings.schema.json
Copilot AI review requested due to automatic review settings July 29, 2026 18:19

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 no new comments.

Comments suppressed due to low confidence (3)

Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1:1135

  • Group-Object groups strings case-insensitively by default. On the Ubuntu/PowerShell 7 test target, destinations such as Foo.ps1 and foo.ps1 are distinct filesystem paths, but this deduplication silently drops one—particularly when one comes from the custom template and the other from the original template. Group case-sensitively on non-Windows platforms while retaining case-insensitive grouping on Windows.
    $filesToInclude = @($filesToInclude | Group-Object { $_.destinationFullPath } | Sort-Object -Property Name | ForEach-Object { $_.Group[0] })

Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1:834

  • These roots are later interpolated directly into -like patterns for all three containment checks. -like treats characters such as [ as wildcard syntax and is case-insensitive even on Linux, so a valid self-hosted runner path containing wildcard characters can be rejected, while a traversal into a case-variant sibling (for example /tmp/Repo to /tmp/repo) can pass on a case-sensitive filesystem. The trailing separator only fixes ordinary prefix collisions; use a literal, OS-aware canonical-path containment helper for the source and both destination checks.
    $sourceFolder = [System.IO.Path]::GetFullPath($sourceFolder) # Canonicalize the source folder to an absolute path
    $sourceFolder = Join-Path $sourceFolder '' # Ensure source folder has a trailing slash for correct path resolution

    $destinationFolder = [System.IO.Path]::GetFullPath($destinationFolder) # Canonicalize the destination folder to an absolute path
    $destinationFolder = Join-Path $destinationFolder '' # Ensure destination folder has a trailing slash for correct path resolution

e2eTests/scenarios/CustomTemplate/runtest.ps1:441

  • Use the possessive “repo's” here.
# Check that optional custom file is present (in repos's filesToInclude)

@OleWunschmann

Copy link
Copy Markdown
Contributor Author

@mazhelez
I think all copilot comments are resolved on my side.

Copilot AI review requested due to automatic review settings July 31, 2026 16:48

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.

🟡 Not ready to approve

destinationName can still escape its configured destination folder, and a minor spelling correction remains.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (2)

e2eTests/scenarios/CustomTemplate/runtest.ps1:441

  • Correct the possessive typo repos's to repo's.
# Check that optional custom file is present (in repos's filesToInclude)

Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1:927

  • The new containment check only protects the repository root, so a value such as destinationName = "../renamed.txt" still escapes the configured destinationFolder while remaining inside the repository and passes this check. That contradicts the documented filename-only contract and can overwrite an unrelated destination; validate destinationName as a leaf name before either the per-project or regular path is constructed.
                    $fullProjectFilePath.destinationFullPath = Join-Path $fullProjectFilePath.destinationFullPath $destinationName
  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings July 31, 2026 18:58

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.

🟡 Not ready to approve

The path-containment checks remain bypassable on case-sensitive platforms, and the central settings reference is incomplete.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (1)

Scenarios/CustomizingALGoForGitHub.md:242

  • The new destinationName setting is missing from the central Scenarios/settings.md settings reference; that file's customALGoFiles row still only names the two arrays and does not describe this property's type/default or that it is honored by Update AL-Go System Files. Add the new property to that reference so the setting documentation remains complete and aligned with the schema.
- `destinationName`: 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. _Example_: `customScript.ps1`.
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +830 to +834
$sourceFolder = [System.IO.Path]::GetFullPath($sourceFolder) # Canonicalize the source folder to an absolute path
$sourceFolder = Join-Path $sourceFolder '' # Ensure source folder has a trailing slash for correct path resolution

$destinationFolder = [System.IO.Path]::GetFullPath($destinationFolder) # Canonicalize the destination folder to an absolute path
$destinationFolder = Join-Path $destinationFolder '' # Ensure destination folder has a trailing slash for correct path resolution

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed

Copilot AI review requested due to automatic review settings July 31, 2026 21:31

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.

🟡 Not ready to approve

Destination normalization rejects valid relative paths, and case-insensitive deduplication drops valid Linux files.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (3)

Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1:956

  • The destination file is canonicalized but $fileDestinationFolder is not. Consequently, valid relative folder paths such as . or scripts/../shared are treated as traversal and skipped because the normalized file path cannot start with the unnormalized folder string. Canonicalize the folder first and check that it remains under $destinationFolder; then retain the existing filename-boundary check.
                $fileDestinationFolder = Join-Path $destinationFolder $file.destinationFolder
                $fileDestinationFolder = Join-Path $fileDestinationFolder '' # Ensure file destination folder has a trailing slash for correct path resolution

                $fullFilePath.destinationFullPath = Join-Path $fileDestinationFolder $destinationName
                $fullFilePath.destinationFullPath = [System.IO.Path]::GetFullPath($fullFilePath.destinationFullPath) # Canonicalize the destination full path to an absolute path

Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1:928

  • $fileDestinationFolder is compared before it is canonicalized, while destinationFullPath is canonicalized. A valid relative path containing . or an internal .. (for example, destinationFolder: './scripts') therefore fails StartsWith and is skipped. Canonicalize the project and file destination folders first, then separately verify the project stays under the base folder and the file folder stays under the project folder before checking destinationName.

This issue also appears on line 952 of the same file.

                    $fileDestinationFolder = Join-Path $destinationFolder $project
                    $fileDestinationFolder = Join-Path $fileDestinationFolder $file.destinationFolder
                    $fileDestinationFolder = Join-Path $fileDestinationFolder '' # Ensure file destination folder has a trailing slash for correct path resolution

Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1:1141

  • Group-Object groups strings case-insensitively by default. On Linux, the new dual-template resolution can produce distinct destinations such as Foo.ps1 from the custom template and foo.ps1 from the original template, but this line drops one even though both paths are valid and distinct. Deduplicate with an OS-appropriate StringComparer, matching the path comparison used by ResolveFilePaths.
    $filesToInclude = @($filesToInclude | Group-Object { $_.destinationFullPath } | Sort-Object -Property Name | ForEach-Object { $_.Group[0] })
  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

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.

5 participants