├── MaskInput.ps1 ├── LICENSE ├── GenerateIndirectPackage.ps1 ├── GenerateNuGetPackages.ps1 ├── .github └── workflows │ ├── generateNuGet.yml │ └── generateRuntimeNuGet.yml ├── DetermineArtifacts.ps1 ├── GenerateRuntimeNuGetPackages.ps1 ├── HelperFunctions.ps1 └── README.md /MaskInput.ps1: -------------------------------------------------------------------------------- 1 | if ($env:GITHUB_EVENT_NAME -eq 'workflow_dispatch') { 2 | $eventPath = Get-Content -Encoding UTF8 -Path $env:GITHUB_EVENT_PATH -Raw | ConvertFrom-Json 3 | if ($null -ne $eventPath.inputs) { 4 | $eventPath.inputs.psObject.Properties | Where-Object { @('Apps','Dependencies','NuGetToken','LicenseFileUrl') -contains $_.Name } | ForEach-Object { 5 | $property = $_.Name 6 | $value = $eventPath.inputs."$property" 7 | Write-Host "::add-mask::$value" 8 | } 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Business Central Apps on github 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /GenerateIndirectPackage.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "Generate Indirect NuGet Package" 2 | 3 | . (Join-Path $PSScriptRoot "HelperFunctions.ps1") 4 | 5 | $appsFolder = Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString()) 6 | $apps = @(Copy-AppFilesToFolder -appFiles @("$env:apps".Split(',')) -folder $appsFolder) 7 | 8 | $nuGetServerUrl, $githubRepository = GetNuGetServerUrlAndRepository -nuGetServerUrl $env:nuGetServerUrl 9 | $nuGetToken = $env:nuGetToken 10 | 11 | foreach($appFile in $apps) { 12 | $appJson = Get-AppJsonFromAppFile -appFile $appFile 13 | 14 | # Test whether a NuGet package exists for this app? 15 | $bcContainerHelperConfig.TrustedNuGetFeeds = @( 16 | [PSCustomObject]@{ "url" = $nuGetServerUrl; "token" = $nuGetToken; "Patterns" = @("*.runtime.$($appJson.id)") } 17 | ) 18 | $package = Get-BcNuGetPackage -packageName "runtime.$($appJson.id)" -version $appJson.version -select Exact 19 | if (-not $package) { 20 | # If just one of the apps doesn't exist as a nuGet package, we need to create a new indirect nuGet package and build all runtime versions of the nuGet 21 | $package = New-BcNuGetPackage -appfile $appFile -githubRepository $githubRepository -isIndirectPackage -packageId "{publisher}.{name}.runtime.{id}" -runtimeDependencyId '{publisher}.{name}.runtime-{version}' 22 | Push-BcNuGetPackage -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken -bcNuGetPackage $package 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /GenerateNuGetPackages.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "Generate Runtime NuGet Packages" 2 | 3 | . (Join-Path $PSScriptRoot "HelperFunctions.ps1") 4 | 5 | $appsFolder = Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString()) 6 | $apps = @(Copy-AppFilesToFolder -appFiles @("$env:apps".Split(',')) -folder $appsFolder) 7 | 8 | $nuGetServerUrl, $githubRepository = GetNuGetServerUrlAndRepository -nuGetServerUrl $env:nuGetServerUrl 9 | $nuGetToken = $env:nuGetToken 10 | $symbolsOnly = ($env:symbolsOnly -eq 'true') 11 | $packageIdTemplate = $env:packageIdTemplate 12 | 13 | foreach($appFile in $apps) { 14 | $appJson = Get-AppJsonFromAppFile -appFile $appFile 15 | 16 | # Test whether a NuGet package exists for this app? 17 | $bcContainerHelperConfig.TrustedNuGetFeeds = @( 18 | [PSCustomObject]@{ "url" = $nuGetServerUrl; "token" = $nuGetToken; "Patterns" = @("*.$($appJson.id)") } 19 | ) 20 | $package = Get-BcNuGetPackage -packageName $appJson.id -version $appJson.version -select Exact 21 | if (-not $package) { 22 | # If the app doesn't exist as a nuGet package, create it 23 | $useAppFile = GetAppFile -appFile $appFile -symbolsOnly:$symbolsOnly 24 | $package = New-BcNuGetPackage -appfile $useAppFile -githubRepository $githubRepository -packageId $packageIdTemplate -dependencyIdTemplate $packageIdTemplate 25 | Push-BcNuGetPackage -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken -bcNuGetPackage $package 26 | if ($useAppFile -ne $appFile) { 27 | Remove-Item -Path $useAppFile -Force 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/generateNuGet.yml: -------------------------------------------------------------------------------- 1 | name: Generate NuGet Packages 2 | 3 | # Controls when the workflow will run 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | nuGetServerUrl: 8 | description: NuGet server URL (leave empty to use NUGETSERVERURL variable) 9 | required: false 10 | default: '' 11 | nuGetToken: 12 | description: NuGet auth token (leave empty to use NUGETTOKEN secret) 13 | required: false 14 | default: '' 15 | apps: 16 | description: Comma separated list of apps (leave empty to use APPS secret) 17 | required: false 18 | default: '' 19 | symbolsOnly: 20 | description: Generate Symbols Only NuGet packages 21 | type: boolean 22 | required: false 23 | packageIdTemplate: 24 | description: Package ID (leave empty to use the default package ID) 25 | required: false 26 | default: '{publisher}.{name}.{id}' 27 | run-name: 28 | description: Name of the run (leave empty to use the default name) 29 | required: false 30 | default: '' 31 | 32 | run-name: ${{ github.event.inputs.run-name != '' && github.event.inputs.run-name || github.workflow }} 33 | 34 | concurrency: 35 | group: ${{ github.event.inputs.run-name != '' && github.event.inputs.run-name || github.workflow }} 36 | cancel-in-progress: false 37 | 38 | jobs: 39 | GenerateNuGetPackages: 40 | name: Generate NuGet Packages 41 | runs-on: [ ubuntu-latest ] 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v3 45 | 46 | - name: Mask input 47 | shell: pwsh 48 | env: 49 | secrets: ${{ toJson(secrets) }} 50 | run: | 51 | . (Join-Path $env:GITHUB_WORKSPACE "MaskInput.ps1") 52 | 53 | - name: Generate NuGet Packages 54 | shell: pwsh 55 | env: 56 | nuGetToken: ${{ github.event.inputs.nuGetToken != '' && github.event.inputs.nuGetToken || secrets.NUGETTOKEN }} 57 | nuGetServerUrl: ${{ github.event.inputs.nuGetServerUrl != '' && github.event.inputs.nuGetServerUrl || vars.NUGETSERVERURL }} 58 | apps: ${{ github.event.inputs.apps != '' && github.event.inputs.apps || secrets.APPS }} 59 | symbolsOnly: ${{ (github.event.inputs.symbolsOnly == 'true') && 'true' || vars.SYMBOLSONLY }} 60 | packageIdTemplate: ${{ github.event.inputs.packageIdTemplate != '' && github.event.inputs.packageIdTemplate || vars.PACKAGEIDTEMPLATE }} 61 | run: | 62 | . (Join-Path $env:GITHUB_WORKSPACE "GenerateNuGetPackages.ps1") 63 | -------------------------------------------------------------------------------- /DetermineArtifacts.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "Determine Artifacts" 2 | 3 | . (Join-Path $PSScriptRoot "HelperFunctions.ps1") 4 | 5 | # Get array of apps 6 | $appsFolder = Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString()) 7 | $apps = @(Copy-AppFilesToFolder -appFiles @("$env:apps".Split(',')) -folder $appsFolder) 8 | 9 | # Get workflow input 10 | $nuGetServerUrl, $githubRepository = GetNuGetServerUrlAndRepository -nuGetServerUrl $env:nuGetServerUrl 11 | $nuGetToken = $env:nuGetToken 12 | $country = $env:country 13 | if ($country -eq '') { $country = 'w1' } 14 | $artifactType = $env:artifactType 15 | if ($artifactType -eq '') { $artifactType = 'sandbox' } 16 | $artifactVersion = "$env:artifactVersion".Trim() 17 | 18 | # Determine runtime dependency package ids for all apps and whether any of the apps doesn't exist as a nuGet package 19 | $runtimeDependencyPackageIds, $newPackage = GetRuntimeDependencyPackageIds -apps $apps -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken 20 | 21 | # If artifact Version is empty or it is a starting version (like 20.0-) then determine which artifact versions are needed 22 | if ($artifactVersion -eq '' -or $artifactVersion.EndsWith('-')) { 23 | 24 | # Find the highest application dependency for the apps in order to determine which BC Application version to use for runtime packages 25 | $highestApplicationDependency = GetHighestApplicationDependency -apps $apps -lowestVersion ($artifactVersion.Split('-')[0]) 26 | 27 | Write-Host "Highest application dependency: $highestApplicationDependency" 28 | 29 | # Determine which artifacts are needed for any of the apps 30 | $allArtifactVersions = @(GetArtifactVersionsSince -type $artifactType -country $country -version "$highestApplicationDependency") 31 | 32 | if ($newPackage) { 33 | # If a new package is to be created, all artifacts are needed 34 | $artifactVersions = $allArtifactVersions 35 | } 36 | else { 37 | # all indirect packages exists - determine which runtime package versions doesn't exist for the app 38 | $artifactVersions = @(GetArtifactVersionsNeeded -apps $apps -allArtifactVersions $allArtifactVersions -runtimeDependencyPackageIds $runtimeDependencyPackageIds -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken) 39 | } 40 | } 41 | else { 42 | $artifactVersions = @($artifactVersion.Split(',') | ForEach-Object { 43 | $version = NormalizeVersionStr -versionStr $_ 44 | $artifactUrl = Get-BCArtifactUrl -type $artifactType -country $country -version $version -select Closest 45 | if (-not $artifactUrl) { 46 | throw "Cannot find artifact for $_" 47 | } 48 | [System.Version]($artifactUrl.Split('/')[4]) 49 | }) 50 | } 51 | 52 | $artifactVersions = @($artifactVersions | ForEach-Object { @{ "artifactVersion" = "$_"; "incompatibleArtifactVersion" = "$($_.Major).$($_.Minor+1)" } }) 53 | 54 | Write-Host "Artifact versions:" 55 | $artifactVersions | ForEach-Object { Write-Host "- $(ConvertTo-Json -InputObject $_ -Compress)" } 56 | Add-Content -Path $ENV:GITHUB_OUTPUT -Value "ArtifactVersions=$(ConvertTo-Json -InputObject @($artifactVersions) -Compress)" -Encoding UTF8 57 | Add-Content -Path $ENV:GITHUB_OUTPUT -Value "ArtifactVersionCount=$($artifactVersions.Count)" -Encoding UTF8 58 | -------------------------------------------------------------------------------- /GenerateRuntimeNuGetPackages.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "Generate Runtime NuGet Packages" 2 | 3 | . (Join-Path $PSScriptRoot "HelperFunctions.ps1") 4 | 5 | $containerName = 'bcserver' 6 | 7 | # Get apps and depenedencies 8 | $appsFolder = Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString()) 9 | $apps = @(Copy-AppFilesToFolder -appFiles @("$env:apps".Split(',')) -folder $appsFolder) 10 | 11 | $dependenciesFolder = Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString()) 12 | $dependencies = @(Copy-AppFilesToFolder -appFiles @("$env:dependencies".Split(',')) -folder $dependenciesFolder) 13 | 14 | # Get parameters from workflow (and dependent job) 15 | $nuGetServerUrl, $githubRepository = GetNuGetServerUrlAndRepository -nuGetServerUrl $env:nuGetServerUrl 16 | $nuGetToken = $env:nuGetToken 17 | $country = $env:country 18 | if ($country -eq '') { $country = 'w1' } 19 | $additionalCountries = @("$env:additionalCountries".Split(',') | Where-Object { $_ -and $_ -ne $country }) 20 | $artifactType = $env:artifactType 21 | if ($artifactType -eq '') { $artifactType = 'sandbox' } 22 | # Artifact version is from the matrix 23 | $artifactVersion = $env:artifactVersion 24 | $incompatibleArtifactVersion = $env:incompatibleArtifactVersion 25 | 26 | # Determine runtime dependency package ids for all apps and whether any of the apps doesn't exist as a nuGet package 27 | $runtimeDependencyPackageIds, $newPackage = GetRuntimeDependencyPackageIds -apps $apps -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken 28 | 29 | $licenseFileUrl = $env:licenseFileUrl 30 | if ([System.Version]$artifactVersion -ge [System.Version]'22.0.0.0') { 31 | $licenseFileUrl = '' 32 | } 33 | 34 | # Create Runtime packages for main country and additional countries 35 | $runtimeAppFiles, $countrySpecificRuntimeAppFiles = GenerateRuntimeAppFiles -containerName $containerName -type $artifactType -country $country -additionalCountries $additionalCountries -artifactVersion $artifactVersion -apps $apps -dependencies $dependencies -licenseFileUrl $licenseFileUrl 36 | 37 | # For every app create and push nuGet package (unless the exact version already exists) 38 | foreach($appFile in $apps) { 39 | $appName = [System.IO.Path]::GetFileName($appFile) 40 | $runtimeDependencyPackageId = $runtimeDependencyPackageIds."$appName" 41 | $bcContainerHelperConfig.TrustedNuGetFeeds = @( 42 | [PSCustomObject]@{ "url" = $nuGetServerUrl; "token" = $nuGetToken; "Patterns" = @($runtimeDependencyPackageId) } 43 | ) 44 | $package = Get-BcNuGetPackage -packageName $runtimeDependencyPackageId -version $artifactVersion -select Exact 45 | if (-not $package) { 46 | $runtimePackage = New-BcNuGetPackage -appfile $runtimeAppFiles."$appName" -countrySpecificAppFiles $countrySpecificRuntimeAppFiles."$appName" -packageId $runtimeDependencyPackageId -packageVersion $artifactVersion -applicationDependency "[$artifactVersion,$incompatibleArtifactVersion)" -githubRepository $githubRepository 47 | $cnt = 0 48 | while ($true) { 49 | try { 50 | $cnt++ 51 | Push-BcNuGetPackage -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken -bcNuGetPackage $runtimePackage 52 | break 53 | } 54 | catch { 55 | if ($cnt -eq 5 -or $_.Exception.Message -notlike '*409*') { throw $_ } 56 | Write-Host "Error pushing package: $($_.Exception.Message). Retry in 10 seconds" 57 | Start-Sleep -Seconds 10 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/generateRuntimeNuGet.yml: -------------------------------------------------------------------------------- 1 | name: Generate Runtime NuGet Packages 2 | 3 | # Controls when the workflow will run 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | nuGetServerUrl: 8 | description: NuGet server URL (leave empty to use RUNTIMENUGETSERVERURL variable) 9 | required: false 10 | default: '' 11 | nuGetToken: 12 | description: NuGet auth token (leave empty to use RUNTIMENUGETTOKEN or NUGETTOKEN secret) 13 | required: false 14 | default: '' 15 | apps: 16 | description: Comma separated list of apps (leave empty to use APPS secret) 17 | required: false 18 | default: '' 19 | dependencies: 20 | description: Comma separated list of dependencies (leave empty to use DEPENDENCIES secret) 21 | required: false 22 | default: '' 23 | country: 24 | description: Country for the main runtime version (leave empty to use COUNTRY variable, default is w1) 25 | required: false 26 | default: '' 27 | additionalCountries: 28 | description: Comma separated list of additional countries (leave empty to use ADDITIONALCOUNTRIES variable, default is none) 29 | required: false 30 | default: '' 31 | artifactVersion: 32 | description: Business Central artifacts version range (leave empty to use ARTIFACTVERSION variable, default is to auto-calculate needed artifacts) 33 | required: false 34 | default: '' 35 | artifactType: 36 | description: Type of Business Central artifacts to use, onprem or sandbox (leave empty to use ARTIFACTTYPE variable, default is sandbox) 37 | required: false 38 | default: '' 39 | licenseFileUrl: 40 | description: License File URL to use for versions before 22.0 (leave empty to use LICENSEFILEURL secret) 41 | required: false 42 | default: '' 43 | run-name: 44 | description: Name of the run (leave empty to use the default name) 45 | required: false 46 | default: '' 47 | 48 | run-name: ${{ github.event.inputs.run-name != '' && github.event.inputs.run-name || github.workflow }} 49 | 50 | concurrency: 51 | group: ${{ github.event.inputs.run-name != '' && github.event.inputs.run-name || github.workflow }} 52 | cancel-in-progress: false 53 | 54 | jobs: 55 | DetermineArtifacts: 56 | name: Determine Business Central Artifacts 57 | runs-on: [ ubuntu-latest ] 58 | outputs: 59 | artifactVersions: ${{ steps.determineArtifacts.outputs.ArtifactVersions }} 60 | artifactVersionCount: ${{ steps.determineArtifacts.outputs.ArtifactVersionCount }} 61 | steps: 62 | - name: Checkout 63 | uses: actions/checkout@v3 64 | 65 | - name: Mask input 66 | shell: pwsh 67 | env: 68 | secrets: ${{ toJson(secrets) }} 69 | run: | 70 | . (Join-Path $env:GITHUB_WORKSPACE "MaskInput.ps1") 71 | 72 | - name: Determine Artifacts 73 | id: determineArtifacts 74 | shell: pwsh 75 | env: 76 | nuGetToken: ${{ github.event.inputs.nuGetToken != '' && github.event.inputs.nuGetToken || (secrets.RUNTIMENUGETTOKEN != '' && secrets.RUNTIMENUGETTOKEN || secrets.NUGETTOKEN) }} 77 | nuGetServerUrl: ${{ github.event.inputs.nuGetServerUrl != '' && github.event.inputs.nuGetServerUrl || vars.RUNTIMENUGETSERVERURL }} 78 | apps: ${{ github.event.inputs.apps != '' && github.event.inputs.apps || secrets.APPS }} 79 | country: ${{ github.event.inputs.country != '' && github.event.inputs.country || vars.COUNTRY }} 80 | artifactVersion: ${{ github.event.inputs.artifactVersion != '' && github.event.inputs.artifactVersion || vars.ARTIFACTVERSION }} 81 | artifactType: ${{ github.event.inputs.artifactType != '' && github.event.inputs.artifactType || vars.ARTIFACTTYPE }} 82 | run: | 83 | . (Join-Path $env:GITHUB_WORKSPACE "DetermineArtifacts.ps1") 84 | 85 | GenerateRuntimeNuGetPackages: 86 | needs: [ DetermineArtifacts ] 87 | if: needs.DetermineArtifacts.outputs.artifactVersionCount > 0 88 | runs-on: [ windows-latest ] 89 | strategy: 90 | matrix: 91 | include: ${{ fromJson(needs.DetermineArtifacts.outputs.artifactVersions) }} 92 | fail-fast: false 93 | max-parallel: 12 94 | name: Runtime ${{ matrix.artifactVersion }} 95 | steps: 96 | - name: Checkout 97 | uses: actions/checkout@v3 98 | 99 | - name: Mask input 100 | shell: pwsh 101 | env: 102 | secrets: ${{ toJson(secrets) }} 103 | run: | 104 | . (Join-Path $env:GITHUB_WORKSPACE "MaskInput.ps1") 105 | 106 | - name: Generate Runtime NuGet Packages 107 | shell: pwsh 108 | env: 109 | nuGetToken: ${{ github.event.inputs.nuGetToken != '' && github.event.inputs.nuGetToken || (secrets.RUNTIMENUGETTOKEN != '' && secrets.RUNTIMENUGETTOKEN || secrets.NUGETTOKEN) }} 110 | nuGetServerUrl: ${{ github.event.inputs.nuGetServerUrl != '' && github.event.inputs.nuGetServerUrl || (vars.RUNTIMENUGETSERVERURL != '' && vars.RUNTIMENUGETSERVERURL || vars.NUGETSERVERURL) }} 111 | apps: ${{ github.event.inputs.apps != '' && github.event.inputs.apps || secrets.APPS }} 112 | dependencies: ${{ github.event.inputs.dependencies != '' && github.event.inputs.dependencies || secrets.DEPENDENCIES }} 113 | country: ${{ github.event.inputs.country != '' && github.event.inputs.country || vars.COUNTRY }} 114 | additionalCountries: ${{ github.event.inputs.additionalCountries != '' && github.event.inputs.additionalCountries || vars.ADDITIONALCOUNTRIES }} 115 | artifactType: ${{ github.event.inputs.artifactType != '' && github.event.inputs.artifactType || vars.ARTIFACTTYPE }} 116 | licenseFileUrl: ${{ github.event.inputs.licenseFileUrl != '' && github.event.inputs.licenseFileUrl || secrets.LICENSEFILEURL }} 117 | artifactVersion: ${{ matrix.artifactVersion }} 118 | incompatibleArtifactVersion: ${{ matrix.incompatibleArtifactVersion }} 119 | run: | 120 | . (Join-Path $env:GITHUB_WORKSPACE "GenerateRuntimeNuGetPackages.ps1") 121 | 122 | GenerateIndirectNuGetPackage: 123 | name: Generate Indirect NuGet Package 124 | needs: [ DetermineArtifacts, GenerateRuntimeNuGetPackages ] 125 | if: needs.DetermineArtifacts.outputs.artifactVersionCount > 0 126 | runs-on: [ ubuntu-latest ] 127 | steps: 128 | - name: Checkout 129 | uses: actions/checkout@v3 130 | 131 | - name: Mask input 132 | shell: pwsh 133 | env: 134 | secrets: ${{ toJson(secrets) }} 135 | run: | 136 | . (Join-Path $env:GITHUB_WORKSPACE "MaskInput.ps1") 137 | 138 | - name: Generate Indirect NuGet Package 139 | shell: pwsh 140 | env: 141 | nuGetToken: ${{ github.event.inputs.nuGetToken != '' && github.event.inputs.nuGetToken || (secrets.RUNTIMENUGETTOKEN != '' && secrets.RUNTIMENUGETTOKEN || secrets.NUGETTOKEN) }} 142 | nuGetServerUrl: ${{ github.event.inputs.nuGetServerUrl != '' && github.event.inputs.nuGetServerUrl || (vars.RUNTIMENUGETSERVERURL != '' && vars.RUNTIMENUGETSERVERURL || vars.NUGETSERVERURL) }} 143 | apps: ${{ github.event.inputs.apps != '' && github.event.inputs.apps || secrets.APPS }} 144 | run: | 145 | . (Join-Path $env:GITHUB_WORKSPACE "GenerateIndirectPackage.ps1") 146 | -------------------------------------------------------------------------------- /HelperFunctions.ps1: -------------------------------------------------------------------------------- 1 | $bcContainerHelperVersion = 'https://bccontainerhelper.blob.core.windows.net/public/preview.zip' 2 | 3 | $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) 4 | Write-Host "Downloading BcContainerHelper developer version from $bcContainerHelperVersion" 5 | $webclient = New-Object System.Net.WebClient 6 | $webclient.DownloadFile($bcContainerHelperVersion, "$tempName.zip") 7 | Expand-Archive -Path "$tempName.zip" -DestinationPath "$tempName" 8 | Remove-Item "$tempName.zip" 9 | $bcContainerHelperPath = (Get-Item -Path (Join-Path $tempName "*\BcContainerHelper.ps1")).FullName 10 | . $bcContainerHelperPath 11 | 12 | $bcContainerHelperConfig.DoNotUseCdnForArtifacts = $true 13 | 14 | $ErrorActionPreference = "stop" 15 | 16 | function GetRuntimeDependencyPackageId { 17 | Param( 18 | [string] $package 19 | ) 20 | $nuspecFile = Join-Path $package 'manifest.nuspec' 21 | $nuspec = [xml](Get-Content -Path $nuspecFile -Encoding UTF8) 22 | $packageId = $nuspec.package.metadata.id 23 | if ($packageId -match "^([^.]+)\.([^.]+)\..*$($appJson.id)`$") { 24 | $publisherAndName = "$($Matches[1]).$($Matches[2])" 25 | Write-Host "Publisher is $($Matches[1]) and name is $($Matches[2])" 26 | } 27 | else { 28 | throw "Cannot determine publisher and name from the $packageId" 29 | } 30 | $runtimeDependencyPackageId = $nuspec.package.metadata.dependencies.dependency | Where-Object { $_.id -like "$($publisherAndName).runtime-*" } | Select-Object -ExpandProperty id 31 | if (-not $runtimeDependencyPackageId) { 32 | throw "Cannot determine dependency package id" 33 | } 34 | return $runtimeDependencyPackageId 35 | } 36 | 37 | function GetRuntimeDependencyPackageIds { 38 | Param( 39 | [string[]] $apps, 40 | [string] $nuGetServerUrl, 41 | [string] $nuGetToken 42 | ) 43 | $runtimeDependencyPackageIds = @{} 44 | $newPackage = $false 45 | foreach($appFile in $apps) { 46 | $appName = [System.IO.Path]::GetFileName($appFile) 47 | $appJson = Get-AppJsonFromAppFile -appFile $appFile 48 | # Test whether a NuGet package exists for this app? 49 | $bcContainerHelperConfig.TrustedNuGetFeeds = @( 50 | [PSCustomObject]@{ "url" = $nuGetServerUrl; "token" = $nuGetToken; "Patterns" = @("*.runtime.$($appJson.id)") } 51 | ) 52 | $package = Get-BcNuGetPackage -packageName "runtime.$($appJson.id)" -version $appJson.version -select Exact 53 | if (-not $package) { 54 | # If just one of the apps doesn't exist as a nuGet package, we need to create a new indirect nuGet package and build all runtime versions of the nuGet 55 | $package = Join-Path ([System.IO.Path]::GetTempPath()) ([GUID]::NewGuid().ToString()) 56 | New-BcNuGetPackage -appfile $appFile -isIndirectPackage -packageId "{publisher}.{name}.runtime.{id}" -runtimeDependencyId '{publisher}.{name}.runtime-{version}' -destinationFolder $package | Out-Null 57 | $newPackage = $true 58 | } 59 | $runTimeDependencyPackageId = GetRuntimeDependencyPackageId -package $package 60 | if ($newPackage) { 61 | Remove-Item -Path $package -Recurse -Force 62 | } 63 | $runtimeDependencyPackageIds += @{ $appName = $runTimeDependencyPackageId } 64 | } 65 | return $runtimeDependencyPackageIds, $newPackage 66 | } 67 | 68 | function GetNuGetServerUrlAndRepository { 69 | Param( 70 | [string] $nuGetServerUrl 71 | ) 72 | if ($nugetServerUrl -match '^https:\/\/github\.com\/([^\/]+)\/([^\/]+)$') { 73 | $githubRepository = $nuGetServerUrl 74 | $nuGetServerUrl = "https://nuget.pkg.github.com/$($Matches[1])/index.json" 75 | } 76 | else { 77 | $githubRepository = '' 78 | } 79 | return $nuGetServerUrl, $githubRepository 80 | } 81 | 82 | function NormalizeVersionStr { 83 | Param( 84 | [string] $versionStr 85 | ) 86 | $version = [System.version]$versionStr 87 | if ($version.Build -eq -1) { $version = [System.Version]::new($version.Major, $version.Minor, 0, 0) } 88 | if ($version.Revision -eq -1) { $version = [System.Version]::new($version.Major, $version.Minor, $version.Build, 0) } 89 | return "$version" 90 | } 91 | 92 | # Find the highest application dependency for the apps in order to determine which BC Application version to use for runtime packages 93 | function GetHighestApplicationDependency { 94 | Param( 95 | [string[]] $apps, 96 | [string] $lowestVersion 97 | ) 98 | if (-not $lowestVersion) { $lowestVersion = '1.0' } 99 | $highestApplicationDependency = NormalizeVersionStr($lowestVersion) 100 | foreach($appFile in $apps) { 101 | $appJson = Get-AppJsonFromAppFile -appFile $appFile 102 | # Determine Application Dependency for this app 103 | if ($appJson.PSObject.Properties.Name -eq "Application") { 104 | $applicationDependency = $appJson.application 105 | } 106 | else { 107 | $baseAppDependency = $appJson.dependencies | Where-Object { $_.Name -eq "Base Application" -and $_.Publisher -eq "Microsoft" } 108 | if ($baseAppDependency) { 109 | $applicationDependency = $baseAppDependency.Version 110 | } 111 | else { 112 | throw "Cannot determine application dependency for $appFile" 113 | } 114 | } 115 | # Determine highest application dependency for all apps 116 | if ([System.Version]$applicationDependency -gt [System.Version]$highestApplicationDependency) { 117 | $highestApplicationDependency = $applicationDependency 118 | } 119 | } 120 | return $highestApplicationDependency 121 | } 122 | 123 | function GetArtifactVersionsSince { 124 | Param( 125 | [string] $type, 126 | [string] $country, 127 | [string] $version, 128 | [switch] $includeLatest 129 | ) 130 | $artifactVersions = @() 131 | $applicationVersion = [System.Version]$version 132 | while ($true) { 133 | $artifacturl = Get-BCArtifactUrl -type $type -country $country -version "$applicationVersion" -select Closest 134 | if ($artifacturl) { 135 | $artifactVersions += @([System.Version]($artifacturl.split('/')[4])) 136 | if ($includeLatest) { 137 | $latestArtifacturl = Get-BCArtifactUrl -type $type -country $country -version "$($applicationVersion.Major).$($applicationVersion.Minor).$([Int32]::MaxValue).$([Int32]::MaxValue)" -select Closest 138 | if ($latestArtifacturl -ne $artifacturl) { 139 | $artifactVersions += @([System.Version]($latestArtifacturl.split('/')[4])) 140 | } 141 | } 142 | $applicationVersion = [System.Version]"$($applicationVersion.Major).$($applicationVersion.Minor+1).0.0" 143 | } 144 | elseif ($applicationVersion.Minor -eq 0) { 145 | break 146 | } 147 | else { 148 | $applicationVersion = [System.Version]"$($applicationVersion.Major+1).0.0.0" 149 | } 150 | } 151 | return $artifactVersions 152 | } 153 | 154 | function GetArtifactVersionsNeeded { 155 | Param( 156 | [string[]] $apps, 157 | [System.Version[]] $allArtifactVersions, 158 | [hashtable] $runtimeDependencyPackageIds, 159 | [string] $nuGetServerUrl, 160 | [string] $nuGetToken 161 | ) 162 | 163 | # Look for latest artifacts first 164 | [Array]::Reverse($allArtifactVersions) 165 | # Search for runtime nuGet packages for all apps 166 | $artifactsNeeded = @() 167 | foreach($appFile in $apps) { 168 | $appName = [System.IO.Path]::GetFileName($appFile) 169 | foreach($artifactVersion in $allArtifactVersions) { 170 | $runtimeDependencyPackageId = $runtimeDependencyPackageIds."$appName" 171 | $bcContainerHelperConfig.TrustedNuGetFeeds = @( 172 | [PSCustomObject]@{ "url" = $nuGetServerUrl; "token" = $nuGetToken; "Patterns" = @($runtimeDependencyPackageId) } 173 | ) 174 | $package = Get-BcNuGetPackage -packageName $runtimeDependencyPackageId -version "$artifactVersion" -select Exact 175 | if ($package) { 176 | break 177 | } 178 | else { 179 | $artifactsNeeded += @($artifactVersion) 180 | } 181 | } 182 | } 183 | return ($artifactsNeeded | Select-Object -Unique) 184 | } 185 | 186 | function GenerateRuntimeAppFiles { 187 | Param( 188 | [string] $containerName, 189 | [string] $type, 190 | [string] $country, 191 | [string[]] $additionalCountries, 192 | [string] $artifactVersion, 193 | [string[]] $apps, 194 | [string[]] $dependencies, 195 | [string] $licenseFileUrl 196 | ) 197 | $artifacturl = Get-BCArtifactUrl -type $type -country $country -version $artifactVersion -select Closest 198 | $global:runtimeAppFiles = @{} 199 | $global:countrySpecificRuntimeAppFiles = @{} 200 | Convert-BcAppsToRuntimePackages -containerName $containerName -artifactUrl $artifacturl -imageName '' -apps $apps -publishApps $dependencies -licenseFile $licenseFileUrl -skipVerification -afterEachRuntimeCreation { Param($ht) 201 | if (-not $ht.runtimeFile) { throw "Could not generate runtime package" } 202 | $appName = [System.IO.Path]::GetFileName($ht.appFile) 203 | $global:runtimeAppFiles += @{ $appName = $ht.runtimeFile } 204 | $global:countrySpecificRuntimeAppFiles += @{ $appName = @{} } 205 | } | Out-Null 206 | foreach($ct in $additionalCountries) { 207 | $artifacturl = Get-BCArtifactUrl -type $type -country $ct -version $artifactVersion -select Closest 208 | Convert-BcAppsToRuntimePackages -containerName $containerName -artifactUrl $artifacturl -imageName '' -apps $apps -publishApps $dependencies -licenseFile $licenseFileUrl -skipVerification -afterEachRuntimeCreation { Param($ht) 209 | if (-not $ht.runtimeFile) { throw "Could not generate runtime package" } 210 | $appName = [System.IO.Path]::GetFileName($ht.appFile) 211 | $global:countrySpecificRuntimeAppFiles."$appName" += @{ $ct = $ht.runtimeFile } 212 | } | Out-Null 213 | } 214 | return $global:runtimeAppFiles, $global:countrySpecificRuntimeAppFiles 215 | } 216 | 217 | function GetAppFile { 218 | Param( 219 | [string] $appFile, 220 | [switch] $symbolsOnly 221 | ) 222 | Write-Host "'$appFile'" 223 | Write-Host $appFile.GetType() 224 | if ($symbolsOnly) { 225 | $symbolsFolder = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) 226 | New-Item -Path $symbolsFolder -ItemType Directory | Out-Null 227 | $symbolsFile = Join-Path $symbolsFolder "$([System.IO.Path]::GetFileNameWithoutExtension(($appFile)))_symbols.app" 228 | Write-Host "Creating symbols file $symbolsFile" 229 | Create-SymbolsFileFromAppFile -appFile $appFile -symbolsFile $symbolsFile | Out-Null 230 | if (-not (Test-Path $symbolsFile)) { 231 | throw "Could not create symbols file from $appFile" 232 | } 233 | return $symbolsFile 234 | } 235 | else { 236 | return $appFile 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proof-Of-Concept: Generate BcNuGet packages 2 | 3 | ## What is this? 4 | 5 | This repository contains code and workflows to generate NuGet packages with Business Central apps. 6 | 7 | BcNuGet packages comes in two flavors: 8 | 9 | 1. Including the full .app file 10 | 2. Including runtime packages, compiled for supported versions of Business Central 11 | 12 | You shouldn't place both these types of BcNuGet packages on the same NuGet Server. If people have access to the full package, they shouldn't need the runtime packages. 13 | 14 | ## Package Format 15 | 16 | After healthy discussions on various media, the following format was the one agreed upon. 17 | 18 | ### Naming 19 | 20 | Naming of the packages almost follows NuGet standards. We use `publisher.name.appid` - this allows people to use registered prefixes on nuget.org and increases visibility in the UI. For runtime package BcNuGet packages we use `publisher.name.runtime.appid` for two reasons: human distinction and the ability to host both packages in the same GitHub organization with separate security models. 21 | 22 | ### Content 23 | 24 | One BcNuGet package is one Business Central app, 25 | 26 | ### Dependencies 27 | 28 | Full dependency list is included in the BcNuGet packages - using `publisher.name.appid` and `version` 29 | 30 | ### Search/Dependency resolution 31 | 32 | We only use the `appid` and `version` to resolve dependencies. This is what Business Central does and this allows partners to do publisher name or name changes seamlessly. 33 | 34 | ### Runtime packages 35 | 36 | Runtime packages are a bit special because we need to provide a binary version of the .app for every minor version of Business Central the .app supports. Including all these binary files in the BcNuGet package would be possible, but it would require us to modify the BcNuGet package whenever a new version of Business Central has shipped. 37 | 38 | Therefore, the runtime version of a BcNuGet package does NOT contain the actual .app. Instead, it is an indirect (empty) package, containing an extra dependency to another BcNuGet package named `publisher.name.runtime-version` and the version number of this package is the Microsoft Application the containing runtime package was built for. Also, the Microsoft.Application dependency in the package containing the runtime package file has a Microsoft.Application dependency on f.ex. `[23.2,23.3)` which allows us to find the right package for any Business Central version by looking at dependencies. 39 | 40 | ### Example of a BcNuGet package 41 | 42 | This package contains version 5.1.23.0 of my BingMaps.PTE app. Note the Publisher and App names have been normalized as NuGet recommends. We do however keep dashes due to the appid part. 43 | 44 | ``` 45 | 46 | 47 |     48 |         FreddyKristiansen.BingMapsPTE.165d73c1-39a4-4fb6-85a5-925edc1684fb 49 |         5.1.23.0 50 |         BingMaps.PTE 51 |         BingMaps Integration App with geocode functionality and map control 52 |         Freddy Kristiansen 53 |         54 |             55 |             56 |         57 |     58 |     59 |         60 |     61 | 62 | ``` 63 | 64 | ### Example of a BcNuGet indirect runtime package 65 | 66 | This package contains version 5.1.23.0 of my BingMaps.PTE app as a runtime package. Only differences to the above full app package is `.runtime` in the package id, a dependency to a BcNuGet package containing the actual binary and no actual files in this package. In this, this package is called the indirect package and and package containing the actual runtime binary is called the BcNuGet compiled runtime package. 67 | 68 | ``` 69 | 70 | 71 |     72 |         FreddyKristiansen.BingMapsPTE.runtime.165d73c1-39a4-4fb6-85a5-925edc1684fb 73 |         5.1.23.0 74 |         BingMaps.PTE 75 |         BingMaps Integration App with geocode functionality and map control 76 |         Freddy Kristiansen 77 |         78 |             79 |             80 |             81 |         82 |     83 | 84 | ``` 85 | 86 | ### Example of a BcNuGet compiled runtime package 87 | 88 | This package contains version 5.1.23.0 of my BingMaps.PTE app compiled with Business Central version 23.2.14098.14562 (first BC version in 23.2 minor) and is compatible with all 23.2 versions (but not 23.3). When 23.3 ships, we add another version to the BcNuGet compiled runtime package. 89 | 90 | ``` 91 | 92 | 93 |     94 |         FreddyKristiansen.BingMapsPTE.runtime-5-1-23-0 95 |         23.2.14098.14562 96 |         BingMaps.PTE 97 |         BingMaps Integration App with geocode functionality and map control 98 |         Freddy Kristiansen 99 |         100 |             101 |             102 |         103 |     104 |     105 |         106 |     107 | 108 | ``` 109 | 110 | > [!NOTE] 111 | > Business Central runtime packages are only really guaranteed to work if they are compiled for the same minor version AND the same localization they are built for. Theoretically, there can be a difference between a US and a DK runtime package. 112 | > 113 | > Therefore, a compiled runtime package can contain multiple country versions of the same runtime package in subfolders with the name of the localization. After downloading a compiled runtime package, if a folder exists with the name of the needed localization - this is the package used - else the package in the root is used. The root does not have to be w1 - it is just always the default. 114 | > 115 | > When (sometime in the future) Business Central doesn't have localizations anymore - this problem does away and we have a clean model. 116 | 117 | ## Prerequisites 118 | 119 | You need to have a direct download URL to the apps you want to create BcNuGet packages for. For testing purposes, you can use this URL: `https://github.com/microsoft/bcsamples-bingmaps.pte/releases/download/6.0.0/bcsamples-bingmaps.pte-main-Apps-5.1.23.0.zip`, which points to a release in the public BingMaps.PTE repository. The URL can contain a SAS token if private. 120 | 121 | You need a GitHub account and the [GitHub CLI](https://cli.github.com/) installed. 122 | 123 | You need a NuGet Server where you can place your BcNuGet packages. Thìs tool supports 3 options: 124 | 1. nuget.org (public only) 125 | 2. GitHub (private only) 126 | 3. Azure DevOps (public or private) 127 | 128 | > [!NOTE] 129 | > nuget.org is public. ALL packages published to nuget.org are available for everybody to download. **Publishing BcNuGet packages to nuget.org might expose your Intellectual Property (IP).** 130 | 131 | For the selected option, you will need a server url and an authentication token (API Key). The following describes how to obtain these: 132 | 133 | ### Using nuget.org 134 | 135 | The NuGet server url for nuget.org is always `https://api.nuget.org/v3/index.json`. In order to obtain an API Key, you first need a nuget.org account. 136 | 137 | [Register for a free account on nuget.org](https://learn.microsoft.com/en-us/nuget/nuget-org/individual-accounts#add-a-new-individual-account) if you don't have one already. 138 | 139 | Go to [https://www.nuget.org/account/apikeys](https://www.nuget.org/account/apikeys) and create an API Key with permissions to push new packages and package versions. 140 | 141 | > [!WARNING] 142 | > Do NOT share this API Key with other people, this token should ONLY be used for generating packages. 143 | 144 | > [!NOTE] 145 | > nuget.org is public. 146 | > People doesn't need an invitation or an authentication token in order to read packages from nuget.org. 147 | 148 | ### Using GitHub 149 | 150 | In order to use GitHub you need a GitHub account, which you probably have since you are reading this. If not, sign up by visiting [https://github.com/signup](https://github.com/signup). 151 | 152 | On GitHub, the NuGet Server is scoped to the organization and access to packages is controlled by access to the owning repository. Since you cannot have both types of NuGet packages on the same server, you should create a new organization for NuGet Packages and create an empty repository for each set of packages you want to provide to other people. Go to [https://github.com/account/organizations/new?plan=free](https://github.com/account/organizations/new?plan=free) to create a free GitHub organization. I used FreddyKristiansen-RuntimePackages for the server which contains my runtime packages. 153 | 154 | The NuGet server url for my GitHub organization is `https://nuget.pkg.github.com/FreddyKristiansen-RuntimePackages/index.json` - replace the organization name to access yours. GitHub doesn't support public NuGet packages, access is permitted to authenticated users only, who has access to the repository owning the package. I have created a repository called [https://github.com/FreddyKristiansen-RuntimePackages/BingMapsPTE](https://github.com/FreddyKristiansen-RuntimePackages/BingMapsPTE) and used this URL as the NuGet server url when running the Generate NuGet Packages tool. 155 | 156 | In order to push new packages and package versions, you need to create a Personal Access Token (classic) with write:packages permissions. 157 | 158 | > [!WARNING] 159 | > Do NOT share this token with other people, this token should ONLY be used for generating packages. 160 | 161 | > [!NOTE] 162 | > GitHub packages are private by default (even if the owning repository is public) 163 | > In order for people to get access to your NuGet packages, you either have to change visibility of the package to public or you need to invite them to your repository (read permissions is sufficient). They will then get an invitation to join the repository and after use their own Personal Access Token with read:packages permissions for accessing packages in your organization. Even if the packages are public, people still needs to authenticate in order to download packages. 164 | 165 | ### Using Azure DevOps 166 | 167 | In order to use Azure DevOps you need an Azure DevOps account. You can create an account by visiting [https://azure.microsoft.com/en-us/products/devops](https://azure.microsoft.com/en-us/products/devops). 168 | 169 | On Azure DevOps you can create multiple NuGet feeds under your organization and/or under a repository. You can create an empty repository for each set of packages you want to provide to other people. If you create a public repository (like [https://dev.azure.com/freddydk/apps](https://dev.azure.com/freddydk/apps)) then your artifact feeds will also be public. 170 | 171 | In order to push new packages and package versions, you need to create a Personal Access Token (classic) with Packaging Read&Write permissions. Visit [https://dev.azure.com/freddydk/_usersSettings/tokens](https://dev.azure.com/freddydk/_usersSettings/tokens) to create a personal access token. 172 | 173 | > [!WARNING] 174 | > Do NOT share this token with other people, this token should ONLY be used for generating packages. 175 | 176 | > [!NOTE] 177 | > Artifacts under Azure DevOps follows the permissions of the owning repository. If the repository is public, then users will not need an access token to query them. 178 | > If the owning repository is private you need to give people permissions and they will have to create their own Personal Access Token to get access. 179 | 180 | ## Parameters 181 | 182 | The Generate NuGet Packages workflow has a subset of the parameters from the Generate Runtime NuGet Packages workflow (nuGetServerUrl, nuGetToken, apps and run-name) and the parameters have the same meaning. 183 | 184 | For the parameters, where the column masked is set yes, these values will not be visible in the workflow output. 185 | 186 | | Name | Masked | Description | Default | 187 | | :-- | :-- | :-- | :-- | 188 | | `nuGetServerUrl` | | The url to your nuGet feed (based on type). Note that for GitHub, this should be the repository carrying the security model for the package. | | 189 | | `nuGetToken` | yes | Auth Token with permissions to create packages and versions on the nuGet server. | | 190 | | `apps` | yes | A comma-separated list of urls where the tool can download .zip files or .app files to publish as BcNuGet packages. All apps in this list will be published as BcNuGet packages. | | 191 | | `dependencies` | yes | A comma-separated list of urls where the tool can download .zip files or .app files, which are needed dependencies when creating runtime packages. These apps will only be used during BcNuGet package generation. No BcNuGet package will be created from these. | | 192 | | `country` | | Localization to use for the root runtime package. See [this](#example-of-a-bcnuget-compiled-runtime-package). | w1 | 193 | | `additionalCountries` | | A comma-separated list of localizations for which a special compiled runtime version will be created and added in a subfolder. | | 194 | | `artifactVersion` | | Which Business Central artifact versions to build runtime packages for. You can specify a comma-separated list of version numbers to use or a minimum-version followed by a - to indicate that you want all available Business Central versions after this version. | all supported by app | 195 | | `artifactType` | | onprem or sandbox | sandbox | 196 | | `licenseFileUrl` | yes | When generating runtime packages for apps in non-public number ranges versions prior to 22.0, we need a license file for Business Central. This should be a direct download url to that license file and it will ONLY be used for versions prior to 22.0 | | 197 | | `run-name` | | The name of the workflow run in the GitHub UI. | name of workflow | 198 | 199 | These parameters can be specified directly in GitHub UI when invoking `Run workflow` - or they can be specified on the command-line when using `gh workflow run`. 200 | 201 | ## Running the tool 202 | 203 | In order to run this tool, you need to create a fork in your own organization or in your personal GitHub account and under actions, enable workflows in the fork. 204 | 205 | Running the **Generate NuGet Packages** workflow will generate BcNuGet packages with full apps. 206 | 207 | Running the **Generate Runtime NuGet Packages** workflow will generate BcNuGet packages with runtime packages of your apps. 208 | 209 | Mandatory fields are **nuGetServerUrl**, **nuGetToken** and **apps**. Parameters can be specified in the UI or created as secrets and variables, but they will most likely be provided as parameters when invoking the workflow from code using `gh workflow run`. 210 | 211 | ### Example 1 212 | 213 | How I created full packages in [https://github.com/FreddyKristiansen-Apps/BingMapsPTE](https://github.com/FreddyKristiansen-Apps/BingMapsPTE) 214 | 215 | ```powershell 216 | $apps = 'https://github.com/microsoft/bcsamples-bingmaps.pte/releases/download/6.0.0/bcsamples-bingmaps.pte-main-Apps-5.1.23.0.zip' 217 | $nuGetServerUrl = 'https://github.com/FreddyKristiansen-Apps/BingMapsPTE' 218 | $nuGetToken = '' 219 | gh workflow run --repo freddydk/GenerateBcNuGet "Generate NuGet Packages" -f apps=$apps -f nuGetServerUrl=$nuGetServerUrl -f nuGetToken=$nuGetToken 220 | ``` 221 | 222 | ### Example 2 223 | 224 | How I created runtime packages in [https://github.com/FreddyKristiansen-RuntimePackages/BingMapsPTE](https://github.com/FreddyKristiansen-RuntimePackages/BingMapsPTE) 225 | 226 | ```powershell 227 | $apps = 'https://github.com/microsoft/bcsamples-bingmaps.pte/releases/download/6.0.0/bcsamples-bingmaps.pte-main-Apps-5.1.23.0.zip' 228 | $nuGetServerUrl = 'https://github.com/FreddyKristiansen-RuntimePackages/BingMapsPTE' 229 | $nuGetToken = '' 230 | gh workflow run --repo freddydk/GenerateBcNuGet "Generate Runtime NuGet Packages" -f apps=$apps -f nuGetServerUrl=$nuGetServerUrl -f nuGetToken=$nuGetToken -f country=w1 231 | ``` 232 | 233 | > [!NOTE] 234 | > Re-running the same line again, will generate runtime packages for new versions of Business Central. 235 | 236 | ### Example 3 237 | 238 | How I created full packages in [https://dev.azure.com/freddydk/apps/_artifacts/feed/Apps](https://dev.azure.com/freddydk/apps/_artifacts/feed/Apps) 239 | 240 | ```powershell 241 | $apps = 'https://github.com/microsoft/bcsamples-bingmaps.pte/releases/download/6.0.0/bcsamples-bingmaps.pte-main-Apps-5.1.23.0.zip' 242 | $nuGetServerUrl = 'https://pkgs.dev.azure.com/freddydk/apps/_packaging/Apps/nuget/v3/index.json' 243 | $nuGetToken = '' 244 | gh workflow run --repo freddydk/GenerateBcNuGet "Generate NuGet Packages" -f apps=$apps -f nuGetServerUrl=$nuGetServerUrl -f nuGetToken=$nuGetToken 245 | ``` 246 | 247 | ### Example 4 248 | 249 | How I created runtime packages in [https://dev.azure.com/freddydk/apps/_artifacts/feed/RuntimePackages](https://dev.azure.com/freddydk/apps/_artifacts/feed/RuntimePackages) 250 | 251 | ```powershell 252 | $apps = 'https://github.com/microsoft/bcsamples-bingmaps.pte/releases/download/6.0.0/bcsamples-bingmaps.pte-main-Apps-5.1.23.0.zip' 253 | $nuGetServerUrl = 'https://pkgs.dev.azure.com/freddydk/apps/_packaging/RuntimePackages/nuget/v3/index.json' 254 | $nuGetToken = '' 255 | gh workflow run --repo freddydk/GenerateBcNuGet "Generate Runtime NuGet Packages" -f apps=$apps -f nuGetServerUrl=$nuGetServerUrl -f nuGetToken=$nuGetToken -f country=w1 256 | ``` 257 | 258 | > [!NOTE] 259 | > Re-running the same line again, will generate runtime packages for new versions of Business Central. 260 | 261 | ### Example 5 262 | 263 | In order to publish runtime packages on nuget.org, you can use this 264 | 265 | ```powershell 266 | $apps = '' 267 | $nuGetServerUrl = 'https://api.nuget.org/v3/index.json' 268 | $nuGetToken = '' 269 | gh workflow run --repo /GenerateBcNuGet "Generate Runtime NuGet Packages" -f apps=$apps -f nuGetServerUrl=$nuGetServerUrl -f nuGetToken=$nuGetToken -f country=w1 270 | ``` 271 | 272 | ## Secrets 273 | 274 | If you are using a company name or like in the name of your apps, and you don't want to share this with the public (since this repository is public), you can create secrets in your fork of the repository, like: 275 | 276 | ``` 277 | MASK1 = Microsoft 278 | MASK2 = Freddy 279 | ``` 280 | 281 | With these secrets in place, the workflow log will replace all occurrences of Microsoft or Freddy with ***. 282 | --------------------------------------------------------------------------------