├── 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 |
--------------------------------------------------------------------------------