From 6f8f0b5101b76272fa896d3f39543214083d7272 Mon Sep 17 00:00:00 2001 From: Maria Zhelezova <43066499+mazhelez@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:19:51 +0200 Subject: [PATCH 1/5] Add GDL development App Views to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b3b16aa36f..6a49a2ef00 100644 --- a/.gitignore +++ b/.gitignore @@ -328,6 +328,9 @@ TestResults.xml **/.pbi/localSettings.json **/.pbi/cache.abf +# App Views in GDL development +src/Views/** + # Exceptions !src/System Application/Test/Extension Management/testArtifacts/*.app !src/Apps/IN/INTaxBase/app/Translations/India Tax Base.en-US.xlf From 904c59c60f31db2a1c17592b025231faf1b2ca1b Mon Sep 17 00:00:00 2001 From: Maria Zhelezova <43066499+mazhelez@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:28:06 +0200 Subject: [PATCH 2/5] Refactor Get-GitCurrentRemoteBranch to improve error handling for upstream branch retrieval --- build/scripts/Miapp/MicroAppGitHelper.psm1 | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/build/scripts/Miapp/MicroAppGitHelper.psm1 b/build/scripts/Miapp/MicroAppGitHelper.psm1 index 1a17829af4..87d2c656ef 100644 --- a/build/scripts/Miapp/MicroAppGitHelper.psm1 +++ b/build/scripts/Miapp/MicroAppGitHelper.psm1 @@ -414,14 +414,15 @@ function Get-GitCurrentRemoteBranch { [OutputType([string])] param() - try { - (git rev-parse --symbolic-full-name '@{u}' 2>&1).ToString() - } catch { - $allowedErrors = @('fatal: no upstream configured for branch *') - $ex = $_.Exception - if(-not ($allowedErrors | ? { $ex.Message -ilike $_})) { - Throw $_ - } + [string] $output = (git rev-parse --symbolic-full-name '@{u}' 2>&1) + + if ($LASTEXITCODE -eq 0) { + return $output + } + + $allowedErrors = @('fatal: no upstream configured for branch *') + if(-not ($allowedErrors | ? { $output -ilike $_})) { + Throw $output } } From 6762e088dce47d9f79b48982b9385bfb584be02c Mon Sep 17 00:00:00 2001 From: Maria Zhelezova <43066499+mazhelez@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:43:59 +0200 Subject: [PATCH 3/5] Update LOCAL_DEV_ENV.md to enhance GDL development section and fix formatting issues --- LOCAL_DEV_ENV.md | 58 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/LOCAL_DEV_ENV.md b/LOCAL_DEV_ENV.md index ec7f6c875c..c2581a6ecd 100644 --- a/LOCAL_DEV_ENV.md +++ b/LOCAL_DEV_ENV.md @@ -20,7 +20,7 @@ In order to create it, simply run `.\build\scripts\DevEnv\NewDevEnv.ps1` with th Running the above will * Create a new container (if one doesn't already exist) -* Set up launch.jsons and settings.jsons in your VSCode +* Set up launch.jsons and settings.jsons in your VSCode ### Example - Set up a container, VSCode and publish a new system app @@ -29,7 +29,7 @@ Running the above will ``` Running the above will * Create a new container (if one doesn't already exist) -* Set up launch.jsons and settings.jsons in your VSCode +* Set up launch.jsons and settings.jsons in your VSCode * Compile and publish a new system app using your local codebase ### Example - Set up a container, VSCode and publish a new system app and tests @@ -38,5 +38,55 @@ Running the above will ``` Running the above will * Create a new container (if one doesn't already exist) -* Set up launch.jsons and settings.jsons in your VSCode -* Compile and publish all AL apps that match `.\src\System Application\` \ No newline at end of file +* Set up launch.jsons and settings.jsons in your VSCode +* Compile and publish all AL apps that match `.\src\System Application\` + +## GDL development (layers and views) + +Anything that ships in multiple localizations (the **Base Application** and the application layers) lives under `src/Layers`, organized by country/region. Each country's app is composed by overlapping multiple **layers** in order: a `W1` ("worldwide") base, optional regional layers, and the country layer. For example, the `US` app is composed of `W1` + `NA` + `US`, where each layer either introduces new objects or replaces objects from a base layer. + +Because the source is split across layers, you don't edit the layer folders directly. Instead, you compose the layers into a single, unified **view** that can be opened in VSCode as a regular AL project. A view is materialized under `src/Views/` (this folder is git-ignored). The view uses symbolic links/junctions back into `src/Layers`, so the file you see in the view is the same file as in the layer it originates from. + +> **Prerequisite:** Creating a view requires permission to create symbolic links on Windows. Either enable [Developer Mode](https://learn.microsoft.com/windows/apps/get-started/enable-your-device-for-development) or run your shell as Administrator. + +All commands are provided by the `GDLDevelopment` PowerShell module. Import it once per session: + +```powershell +Import-Module .\build\scripts\GDLDevelopment\GDLDevelopment.psm1 +``` + +### Create a view + +```powershell +New-GDLView -CountryCode US -ContainerName 'BCApps-Dev' +``` + +This composes the layers for the given country/region into `src/Views/US` and configures the VSCode `launch.json`/`settings.json` for each project in the view, pointing them at the given dev container. The settings are read from the container, so create a container first (see above) and pass its name with `-ContainerName`. If you only want the composed view without touching VSCode settings, add `-skipSetupDevelopmentSettings` (in which case `-ContainerName` is not needed). + +Open the resulting `src/Views/US` folder in VSCode and develop as you would in any AL project. + +### Synchronize your changes back to the layers + +Files you edit in the view update the underlying layer file directly (they are linked). New files you add in the view are real files that must be copied into the correct layer. Run: + +```powershell +Sync-GDLView -CountryCode US +``` + +This copies any new files from the view into the layer, recreates the view (creating the appropriate links), and leaves the view in a clean, synchronized state. To also propagate files you **moved or deleted** in the view back to the layers, use: + +```powershell +Sync-GDLView -CountryCode US -SyncMovesAndDeletes +``` + +When a moved/deleted file exists in more than one layer, you'll be prompted to choose how the change should be applied. + +### Remove a view + +When you're done, remove the view to clean up the links: + +```powershell +Remove-GDLView -CountryCode US +``` + +`Remove-GDLView` first verifies the view has no unsynchronized changes. To discard any unsynchronized files and remove the view anyway, add `-Force`. To remove every view at once, use `Remove-AllGDLViews` (optionally with `-Force`). \ No newline at end of file From 982c91b33ff1f89d7346702bce49f45916508c34 Mon Sep 17 00:00:00 2001 From: Maria Zhelezova <43066499+mazhelez@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:46:13 +0200 Subject: [PATCH 4/5] Fix GDL view setup to use current DevEnv API SetupDevelopmentSettings referenced the removed internal helper Get-NavServerInstanceNameForPublishing, an obsolete INETROOT projects.json path, and called Configure-ALProject with parameters that no longer exist, so New-GDLView failed with 'Configure-ALProject is not recognized'. Import ALDev.psm1, resolve launch/project settings via Get-LaunchSettings/Get-ProjectSettings, read build/projects.json from the repo root, and add -ContainerName/-Authentication to New-GDLView. --- .../GDLDevelopment/GDLDevelopment.psm1 | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/build/scripts/GDLDevelopment/GDLDevelopment.psm1 b/build/scripts/GDLDevelopment/GDLDevelopment.psm1 index 66b062308e..00a6d332f3 100644 --- a/build/scripts/GDLDevelopment/GDLDevelopment.psm1 +++ b/build/scripts/GDLDevelopment/GDLDevelopment.psm1 @@ -1,5 +1,6 @@ Import-Module "$PSScriptRoot\GDLDevelopmentHelpers.psm1" -DisableNameChecking Import-Module "$PSScriptRoot\..\Logger.psm1" -DisableNameChecking +Import-Module "$PSScriptRoot\..\DevEnv\ALDev.psm1" -DisableNameChecking <# .SYNOPSIS @@ -632,6 +633,8 @@ function New-GDLView( [Parameter(Mandatory = $true)] [ValidateScript( { $_ -in (GetAllGDLCountryCodes) })] [string]$CountryCode, + [string] $ContainerName, + [string] $Authentication, [switch] $skipSetupDevelopmentSettings ) { @@ -641,13 +644,15 @@ function New-GDLView( if (!$skipSetupDevelopmentSettings) { - SetupDevelopmentSettings $gdlViewConfiguration + SetupDevelopmentSettings -GdlViewConfiguration $gdlViewConfiguration -ContainerName $ContainerName -Authentication $Authentication } } function SetupDevelopmentSettings ( - [GDLViewConfiguration] $GdlViewConfiguration + [GDLViewConfiguration] $GdlViewConfiguration, + [string] $ContainerName, + [string] $Authentication ) { # The recursion is needed for the Tests. We should find a better way of setting up the settings for these. @@ -655,26 +660,22 @@ function SetupDevelopmentSettings Where-Object { Test-Path (Join-Path $_.FullName app.json) } | ForEach-Object { return $_.FullName } - # Reading the server settings is expensive so we do it once and use the value for all projects - $defaultLaunchSettings = @{ - "serverInstance" = (Get-NavServerInstanceNameForPublishing -CountryCode $GdlViewConfiguration.CountryCode) - } + # Resolving the launch and project settings is expensive (it inspects the container), so we do it once and reuse it for all projects. + $defaultLaunchSettings = Get-LaunchSettings -ContainerName $ContainerName -Authentication $Authentication + $defaultProjectSettings = Get-ProjectSettings -ContainerName $ContainerName # We load the settings for all projects once. These are used to get the configuration of each project, e.g. analyzers and rulesets used. The same definition is used in ALAppBuild. - $projectsSettings = (Get-Content -Path "$ENV:INETROOT\Eng\Core\Build\projects.json" -Raw | ConvertFrom-Json).projects + $projectsSettings = (Get-Content -Path (Join-Path (Get-BaseFolder) "build\projects.json") -Raw | ConvertFrom-Json).projects $vscodeSettingFolders | ForEach-Object { - $ConfigureALProjectParams = @{ - ProjectFolder = $_ - CountryCode = $GdlViewConfiguration.CountryCode - ResetConfiguration = $true - LaunchSettings = $defaultLaunchSettings - } - $projectSettings = GetProjectSettings $projectsSettings $(Join-Path $_ app.json) - if ($projectSettings.Count -ne 0) { - $ConfigureALProjectParams."ProjectSettings" = $projectSettings + # Start from the shared project settings and overlay any project-specific configuration (analyzers, rulesets). + $projectSettings = $defaultProjectSettings.Clone() + $projectSpecificSettings = GetProjectSettings $projectsSettings $(Join-Path $_ app.json) + foreach ($key in $projectSpecificSettings.Keys) { + $projectSettings[$key] = $projectSpecificSettings[$key] } - Configure-ALProject @ConfigureALProjectParams + + Configure-ALProject -ProjectFolder $_ -LaunchSettings $defaultLaunchSettings -ProjectSettings $projectSettings } } From b5bd76d0024bedda5611237e58b1b26744dc1bfe Mon Sep 17 00:00:00 2001 From: Maria Zhelezova <43066499+mazhelez@users.noreply.github.com> Date: Wed, 1 Jul 2026 00:50:43 +0200 Subject: [PATCH 5/5] Document Miapp usage; revert GDL dev-settings fix for now Add a Miapp section to LOCAL_DEV_ENV.md explaining what it does and how to invoke it. Revert the GDLDevelopment.psm1 dev-settings changes; automatic VSCode settings setup stays broken and will be fixed later, so the GDL doc now uses -skipSetupDevelopmentSettings. --- LOCAL_DEV_ENV.md | 39 +++++++++++++++++-- .../GDLDevelopment/GDLDevelopment.psm1 | 35 ++++++++--------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/LOCAL_DEV_ENV.md b/LOCAL_DEV_ENV.md index c2581a6ecd..cb724a60b9 100644 --- a/LOCAL_DEV_ENV.md +++ b/LOCAL_DEV_ENV.md @@ -58,12 +58,12 @@ Import-Module .\build\scripts\GDLDevelopment\GDLDevelopment.psm1 ### Create a view ```powershell -New-GDLView -CountryCode US -ContainerName 'BCApps-Dev' +New-GDLView -CountryCode US -skipSetupDevelopmentSettings ``` -This composes the layers for the given country/region into `src/Views/US` and configures the VSCode `launch.json`/`settings.json` for each project in the view, pointing them at the given dev container. The settings are read from the container, so create a container first (see above) and pass its name with `-ContainerName`. If you only want the composed view without touching VSCode settings, add `-skipSetupDevelopmentSettings` (in which case `-ContainerName` is not needed). +This composes the layers for the given country/region into `src/Views/US`. Open the resulting `src/Views/US` folder in VSCode and develop as you would in any AL project. -Open the resulting `src/Views/US` folder in VSCode and develop as you would in any AL project. +> **Note:** Automatic configuration of the VSCode `launch.json`/`settings.json` (i.e. running `New-GDLView` without `-skipSetupDevelopmentSettings`) is currently broken and will be fixed later. For now, pass `-skipSetupDevelopmentSettings` and configure the projects manually if needed. ### Synchronize your changes back to the layers @@ -89,4 +89,35 @@ When you're done, remove the view to clean up the links: Remove-GDLView -CountryCode US ``` -`Remove-GDLView` first verifies the view has no unsynchronized changes. To discard any unsynchronized files and remove the view anyway, add `-Force`. To remove every view at once, use `Remove-AllGDLViews` (optionally with `-Force`). \ No newline at end of file +`Remove-GDLView` first verifies the view has no unsynchronized changes. To discard any unsynchronized files and remove the view anyway, add `-Force`. To remove every view at once, use `Remove-AllGDLViews` (optionally with `-Force`). + +## Miapp (propagating changes across layers) + +When you change a file in the `W1` (worldwide) base layer, the same change often needs to be applied to the country-specific layers that build on top of it. **Miapp** (Micro Application Integration) is a PowerShell tool that automates this propagation: it finds the files you changed in `W1` and merges them into each dependent country layer (`AT`, `AU`, `BE`, `DE`, `US`, ...), resolving conflicts automatically or with a merge tool. + +Import the module and run `Invoke-Miapp` from the repository root: + +```powershell +Import-Module .\build\scripts\Miapp\MicroApp.psm1 +Invoke-Miapp +``` + +`Invoke-Miapp` validates the repository state, discovers the files that need integrating, merges them into every dependent layer, and stages the result. Commonly used options: + +```powershell +# Only propagate to a single country layer +Invoke-Miapp -Country DE + +# Only propagate files matching a regex +Invoke-Miapp -FileNameFilter '\.al$' + +# Resolve conflicts automatically, preferring the W1 (source) version +Invoke-Miapp -AutoResolve theirs + +# Prompt before propagating each file +Invoke-Miapp -Interactive +``` + +> **Tip:** Run Miapp after committing your `W1` changes — it compares against the base branch (`origin/HEAD`, typically `main`) to determine what to propagate. + +For the full list of parameters, the integration workflow, configuration, and troubleshooting, see the [Miapp README](build/scripts/Miapp/README.md). \ No newline at end of file diff --git a/build/scripts/GDLDevelopment/GDLDevelopment.psm1 b/build/scripts/GDLDevelopment/GDLDevelopment.psm1 index 00a6d332f3..66b062308e 100644 --- a/build/scripts/GDLDevelopment/GDLDevelopment.psm1 +++ b/build/scripts/GDLDevelopment/GDLDevelopment.psm1 @@ -1,6 +1,5 @@ Import-Module "$PSScriptRoot\GDLDevelopmentHelpers.psm1" -DisableNameChecking Import-Module "$PSScriptRoot\..\Logger.psm1" -DisableNameChecking -Import-Module "$PSScriptRoot\..\DevEnv\ALDev.psm1" -DisableNameChecking <# .SYNOPSIS @@ -633,8 +632,6 @@ function New-GDLView( [Parameter(Mandatory = $true)] [ValidateScript( { $_ -in (GetAllGDLCountryCodes) })] [string]$CountryCode, - [string] $ContainerName, - [string] $Authentication, [switch] $skipSetupDevelopmentSettings ) { @@ -644,15 +641,13 @@ function New-GDLView( if (!$skipSetupDevelopmentSettings) { - SetupDevelopmentSettings -GdlViewConfiguration $gdlViewConfiguration -ContainerName $ContainerName -Authentication $Authentication + SetupDevelopmentSettings $gdlViewConfiguration } } function SetupDevelopmentSettings ( - [GDLViewConfiguration] $GdlViewConfiguration, - [string] $ContainerName, - [string] $Authentication + [GDLViewConfiguration] $GdlViewConfiguration ) { # The recursion is needed for the Tests. We should find a better way of setting up the settings for these. @@ -660,22 +655,26 @@ function SetupDevelopmentSettings Where-Object { Test-Path (Join-Path $_.FullName app.json) } | ForEach-Object { return $_.FullName } - # Resolving the launch and project settings is expensive (it inspects the container), so we do it once and reuse it for all projects. - $defaultLaunchSettings = Get-LaunchSettings -ContainerName $ContainerName -Authentication $Authentication - $defaultProjectSettings = Get-ProjectSettings -ContainerName $ContainerName + # Reading the server settings is expensive so we do it once and use the value for all projects + $defaultLaunchSettings = @{ + "serverInstance" = (Get-NavServerInstanceNameForPublishing -CountryCode $GdlViewConfiguration.CountryCode) + } # We load the settings for all projects once. These are used to get the configuration of each project, e.g. analyzers and rulesets used. The same definition is used in ALAppBuild. - $projectsSettings = (Get-Content -Path (Join-Path (Get-BaseFolder) "build\projects.json") -Raw | ConvertFrom-Json).projects + $projectsSettings = (Get-Content -Path "$ENV:INETROOT\Eng\Core\Build\projects.json" -Raw | ConvertFrom-Json).projects $vscodeSettingFolders | ForEach-Object { - # Start from the shared project settings and overlay any project-specific configuration (analyzers, rulesets). - $projectSettings = $defaultProjectSettings.Clone() - $projectSpecificSettings = GetProjectSettings $projectsSettings $(Join-Path $_ app.json) - foreach ($key in $projectSpecificSettings.Keys) { - $projectSettings[$key] = $projectSpecificSettings[$key] + $ConfigureALProjectParams = @{ + ProjectFolder = $_ + CountryCode = $GdlViewConfiguration.CountryCode + ResetConfiguration = $true + LaunchSettings = $defaultLaunchSettings } - - Configure-ALProject -ProjectFolder $_ -LaunchSettings $defaultLaunchSettings -ProjectSettings $projectSettings + $projectSettings = GetProjectSettings $projectsSettings $(Join-Path $_ app.json) + if ($projectSettings.Count -ne 0) { + $ConfigureALProjectParams."ProjectSettings" = $projectSettings + } + Configure-ALProject @ConfigureALProjectParams } }