bcdocker 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +224 -0
- package/dist/executor.d.ts +7 -0
- package/dist/executor.js +72 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +203 -0
- package/package.json +59 -0
- package/ps/Apps.ps1 +360 -0
- package/ps/BCDocker.psm1 +30 -0
- package/ps/Container.ps1 +397 -0
- package/ps/Helpers.ps1 +505 -0
- package/ps/Tests.ps1 +144 -0
package/ps/Helpers.ps1
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
function Write-BCBanner {
|
|
2
|
+
param([string]$Title, [string]$Color = "Cyan")
|
|
3
|
+
|
|
4
|
+
$line = "=" * 50
|
|
5
|
+
Write-Host ""
|
|
6
|
+
Write-Host " $line" -ForegroundColor $Color
|
|
7
|
+
Write-Host " $Title" -ForegroundColor $Color
|
|
8
|
+
Write-Host " $line" -ForegroundColor $Color
|
|
9
|
+
Write-Host ""
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function Write-BCStep {
|
|
13
|
+
param([string]$Step, [string]$Message)
|
|
14
|
+
Write-Host " [$Step] $Message" -ForegroundColor Yellow
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function Write-BCInfo {
|
|
18
|
+
param([string]$Message)
|
|
19
|
+
Write-Host " $Message" -ForegroundColor Gray
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function Write-BCSuccess {
|
|
23
|
+
param([string]$Message)
|
|
24
|
+
Write-Host " $Message" -ForegroundColor Green
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function Write-BCError {
|
|
28
|
+
param([string]$Message)
|
|
29
|
+
Write-Host " $Message" -ForegroundColor Red
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function Write-BCProperty {
|
|
33
|
+
param([string]$Label, [string]$Value, [string]$ValueColor = "White")
|
|
34
|
+
Write-Host " $($Label.PadRight(18)): " -ForegroundColor Gray -NoNewline
|
|
35
|
+
Write-Host $Value -ForegroundColor $ValueColor
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function Assert-DockerReady {
|
|
39
|
+
try {
|
|
40
|
+
$null = docker version --format '{{.Server.Version}}' 2>$null
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
Write-BCError "Docker is not running or not installed."
|
|
44
|
+
Write-BCError "Install Docker Desktop and switch to Windows containers."
|
|
45
|
+
return $false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
$osType = docker info --format '{{.OSType}}' 2>$null
|
|
49
|
+
if ($osType -ne "windows") {
|
|
50
|
+
Write-BCError "Docker is in Linux containers mode."
|
|
51
|
+
Write-BCInfo "Right-click Docker Desktop tray icon -> 'Switch to Windows containers...'"
|
|
52
|
+
return $false
|
|
53
|
+
}
|
|
54
|
+
return $true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function Assert-BcContainerHelper {
|
|
58
|
+
$module = Get-Module -ListAvailable -Name BcContainerHelper |
|
|
59
|
+
Sort-Object Version -Descending |
|
|
60
|
+
Select-Object -First 1
|
|
61
|
+
|
|
62
|
+
if (-not $module) {
|
|
63
|
+
Write-BCStep "INIT" "Installing BcContainerHelper..."
|
|
64
|
+
Install-Module BcContainerHelper -Force -AllowClobber -Scope CurrentUser
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
Import-Module BcContainerHelper -DisableNameChecking -ErrorAction Stop
|
|
68
|
+
return $true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Select-BCContainer {
|
|
72
|
+
[CmdletBinding()]
|
|
73
|
+
param(
|
|
74
|
+
[string]$Title = "Select a container",
|
|
75
|
+
[switch]$AllowMultiple
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (-not (Assert-BcContainerHelper)) { return $null }
|
|
79
|
+
|
|
80
|
+
$containers = Get-BcContainers
|
|
81
|
+
if ($containers.Count -eq 0) {
|
|
82
|
+
Write-BCError "No BC containers found."
|
|
83
|
+
return $null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if ($AllowMultiple) {
|
|
87
|
+
return $containers | Out-GridView -PassThru -Title $Title
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
$selected = $containers | Out-GridView -PassThru -Title $Title
|
|
91
|
+
if ($selected -is [array]) {
|
|
92
|
+
Write-BCError "Please select only one container."
|
|
93
|
+
return $null
|
|
94
|
+
}
|
|
95
|
+
return $selected
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function Get-BCCredential {
|
|
100
|
+
param(
|
|
101
|
+
[string]$UserName = "admin",
|
|
102
|
+
[string]$Password = "P@ssw0rd!"
|
|
103
|
+
)
|
|
104
|
+
$secure = ConvertTo-SecureString $Password -AsPlainText -Force
|
|
105
|
+
return [PSCredential]::new($UserName, $secure)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function Select-AppFiles {
|
|
109
|
+
param([string]$Title = "Select .app file(s)")
|
|
110
|
+
|
|
111
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
112
|
+
$dialog = New-Object System.Windows.Forms.OpenFileDialog
|
|
113
|
+
$dialog.Filter = "AL App files (*.app)|*.app|All files (*.*)|*.*"
|
|
114
|
+
$dialog.Multiselect = $true
|
|
115
|
+
$dialog.Title = $Title
|
|
116
|
+
|
|
117
|
+
if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
118
|
+
return $dialog.FileNames
|
|
119
|
+
}
|
|
120
|
+
return $null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function Add-DarkScrollStyle {
|
|
124
|
+
param([System.Windows.Window]$Window)
|
|
125
|
+
|
|
126
|
+
$rdXaml = @"
|
|
127
|
+
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
128
|
+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
|
129
|
+
<Style TargetType="{x:Type ScrollBar}">
|
|
130
|
+
<Setter Property="Background" Value="Transparent"/>
|
|
131
|
+
<Setter Property="Width" Value="10"/>
|
|
132
|
+
<Setter Property="MinWidth" Value="10"/>
|
|
133
|
+
<Setter Property="Template">
|
|
134
|
+
<Setter.Value>
|
|
135
|
+
<ControlTemplate TargetType="{x:Type ScrollBar}">
|
|
136
|
+
<Grid Background="Transparent" Width="10">
|
|
137
|
+
<Track x:Name="PART_Track" IsDirectionReversed="True">
|
|
138
|
+
<Track.DecreaseRepeatButton>
|
|
139
|
+
<RepeatButton Command="ScrollBar.PageUpCommand" Focusable="False">
|
|
140
|
+
<RepeatButton.Template>
|
|
141
|
+
<ControlTemplate><Border Background="Transparent"/></ControlTemplate>
|
|
142
|
+
</RepeatButton.Template>
|
|
143
|
+
</RepeatButton>
|
|
144
|
+
</Track.DecreaseRepeatButton>
|
|
145
|
+
<Track.Thumb>
|
|
146
|
+
<Thumb x:Name="thumb">
|
|
147
|
+
<Thumb.Template>
|
|
148
|
+
<ControlTemplate TargetType="{x:Type Thumb}">
|
|
149
|
+
<Border x:Name="tb" Background="#404040" CornerRadius="5" Margin="2,0">
|
|
150
|
+
<Border.Triggers>
|
|
151
|
+
<EventTrigger RoutedEvent="MouseEnter">
|
|
152
|
+
<BeginStoryboard>
|
|
153
|
+
<Storyboard>
|
|
154
|
+
<ColorAnimation Storyboard.TargetName="tb"
|
|
155
|
+
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
|
|
156
|
+
To="#686868" Duration="0:0:0.15"/>
|
|
157
|
+
</Storyboard>
|
|
158
|
+
</BeginStoryboard>
|
|
159
|
+
</EventTrigger>
|
|
160
|
+
<EventTrigger RoutedEvent="MouseLeave">
|
|
161
|
+
<BeginStoryboard>
|
|
162
|
+
<Storyboard>
|
|
163
|
+
<ColorAnimation Storyboard.TargetName="tb"
|
|
164
|
+
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
|
|
165
|
+
To="#404040" Duration="0:0:0.3"/>
|
|
166
|
+
</Storyboard>
|
|
167
|
+
</BeginStoryboard>
|
|
168
|
+
</EventTrigger>
|
|
169
|
+
</Border.Triggers>
|
|
170
|
+
</Border>
|
|
171
|
+
</ControlTemplate>
|
|
172
|
+
</Thumb.Template>
|
|
173
|
+
</Thumb>
|
|
174
|
+
</Track.Thumb>
|
|
175
|
+
<Track.IncreaseRepeatButton>
|
|
176
|
+
<RepeatButton Command="ScrollBar.PageDownCommand" Focusable="False">
|
|
177
|
+
<RepeatButton.Template>
|
|
178
|
+
<ControlTemplate><Border Background="Transparent"/></ControlTemplate>
|
|
179
|
+
</RepeatButton.Template>
|
|
180
|
+
</RepeatButton>
|
|
181
|
+
</Track.IncreaseRepeatButton>
|
|
182
|
+
</Track>
|
|
183
|
+
</Grid>
|
|
184
|
+
</ControlTemplate>
|
|
185
|
+
</Setter.Value>
|
|
186
|
+
</Setter>
|
|
187
|
+
</Style>
|
|
188
|
+
</ResourceDictionary>
|
|
189
|
+
"@
|
|
190
|
+
$rd = [System.Windows.Markup.XamlReader]::Load(
|
|
191
|
+
[System.Xml.XmlReader]::Create([System.IO.StringReader]::new($rdXaml))
|
|
192
|
+
)
|
|
193
|
+
$Window.Resources.MergedDictionaries.Add($rd)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function Select-Folder {
|
|
197
|
+
param([string]$Description = "Select folder")
|
|
198
|
+
|
|
199
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
200
|
+
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
|
|
201
|
+
$dialog.Description = $Description
|
|
202
|
+
|
|
203
|
+
if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
204
|
+
return $dialog.SelectedPath
|
|
205
|
+
}
|
|
206
|
+
return $null
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function Show-BCDContainerForm {
|
|
210
|
+
Add-Type -AssemblyName PresentationFramework
|
|
211
|
+
|
|
212
|
+
$bc = [System.Windows.Media.BrushConverter]::new()
|
|
213
|
+
$bgWindow = $bc.ConvertFrom("#252526")
|
|
214
|
+
$bgField = $bc.ConvertFrom("#3c3c3c")
|
|
215
|
+
$fgText = $bc.ConvertFrom("#cccccc")
|
|
216
|
+
$fgValue = $bc.ConvertFrom("#e0e0e0")
|
|
217
|
+
$fgDim = $bc.ConvertFrom("#5a5a5a")
|
|
218
|
+
$fgHint = $bc.ConvertFrom("#666666")
|
|
219
|
+
$brField = $bc.ConvertFrom("#4a4a4a")
|
|
220
|
+
$brFocus = $bc.ConvertFrom("#0078d4")
|
|
221
|
+
|
|
222
|
+
$window = New-Object System.Windows.Window
|
|
223
|
+
$window.Title = "BCDocker"
|
|
224
|
+
$window.Width = 580
|
|
225
|
+
$window.Height = 680
|
|
226
|
+
$window.WindowStartupLocation = "CenterScreen"
|
|
227
|
+
$window.ResizeMode = "NoResize"
|
|
228
|
+
$window.Background = $bgWindow
|
|
229
|
+
$window.FontFamily = New-Object System.Windows.Media.FontFamily("Segoe UI")
|
|
230
|
+
$window.FontSize = 13
|
|
231
|
+
|
|
232
|
+
$root = New-Object System.Windows.Controls.DockPanel
|
|
233
|
+
$root.Margin = [System.Windows.Thickness]::new(32, 24, 32, 24)
|
|
234
|
+
|
|
235
|
+
# Title
|
|
236
|
+
$titlePanel = New-Object System.Windows.Controls.StackPanel
|
|
237
|
+
[System.Windows.Controls.DockPanel]::SetDock($titlePanel, "Top")
|
|
238
|
+
$titlePanel.Margin = [System.Windows.Thickness]::new(0, 0, 0, 8)
|
|
239
|
+
$t1 = New-Object System.Windows.Controls.TextBlock; $t1.Text = "Create Container"; $t1.FontSize = 20; $t1.FontWeight = "SemiBold"; $t1.Foreground = $fgValue
|
|
240
|
+
$t2 = New-Object System.Windows.Controls.TextBlock; $t2.Text = "Configure a new Business Central Docker sandbox"; $t2.FontSize = 11.5; $t2.Foreground = $fgDim; $t2.Margin = [System.Windows.Thickness]::new(0,3,0,0)
|
|
241
|
+
$titlePanel.Children.Add($t1) | Out-Null
|
|
242
|
+
$titlePanel.Children.Add($t2) | Out-Null
|
|
243
|
+
$root.Children.Add($titlePanel) | Out-Null
|
|
244
|
+
|
|
245
|
+
# Buttons
|
|
246
|
+
$btnPanel = New-Object System.Windows.Controls.StackPanel
|
|
247
|
+
$btnPanel.Orientation = "Horizontal"
|
|
248
|
+
$btnPanel.HorizontalAlignment = "Right"
|
|
249
|
+
$btnPanel.Margin = [System.Windows.Thickness]::new(0, 16, 0, 0)
|
|
250
|
+
[System.Windows.Controls.DockPanel]::SetDock($btnPanel, "Bottom")
|
|
251
|
+
$btnCancel = New-Object System.Windows.Controls.Button; $btnCancel.Content = "Cancel"; $btnCancel.Padding = [System.Windows.Thickness]::new(22,7,22,7)
|
|
252
|
+
$btnCancel.Background = $bc.ConvertFrom("#333333"); $btnCancel.Foreground = $fgText; $btnCancel.BorderBrush = $bc.ConvertFrom("#505050"); $btnCancel.Margin = [System.Windows.Thickness]::new(0,0,10,0)
|
|
253
|
+
$btnCreate = New-Object System.Windows.Controls.Button; $btnCreate.Content = "Create Container"; $btnCreate.Padding = [System.Windows.Thickness]::new(22,7,22,7)
|
|
254
|
+
$btnCreate.Background = $brFocus; $btnCreate.Foreground = [System.Windows.Media.Brushes]::White; $btnCreate.BorderThickness = [System.Windows.Thickness]::new(0); $btnCreate.FontWeight = "SemiBold"; $btnCreate.IsDefault = $true
|
|
255
|
+
$btnPanel.Children.Add($btnCancel) | Out-Null; $btnPanel.Children.Add($btnCreate) | Out-Null
|
|
256
|
+
$root.Children.Add($btnPanel) | Out-Null
|
|
257
|
+
|
|
258
|
+
# Form body
|
|
259
|
+
$scroll = New-Object System.Windows.Controls.ScrollViewer; $scroll.VerticalScrollBarVisibility = "Auto"
|
|
260
|
+
$formBody = New-Object System.Windows.Controls.StackPanel
|
|
261
|
+
|
|
262
|
+
$addSection = {
|
|
263
|
+
param($text, $topMargin)
|
|
264
|
+
$tb = New-Object System.Windows.Controls.TextBlock; $tb.Text = $text; $tb.FontSize = 10.5; $tb.FontWeight = "SemiBold"; $tb.Foreground = $fgDim
|
|
265
|
+
$tb.Margin = [System.Windows.Thickness]::new(0, $topMargin, 0, 8)
|
|
266
|
+
$formBody.Children.Add($tb) | Out-Null
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
$fields = @{}
|
|
270
|
+
$addField = {
|
|
271
|
+
param($label, $key, $default, $hint)
|
|
272
|
+
$row = New-Object System.Windows.Controls.DockPanel; $row.Margin = [System.Windows.Thickness]::new(0,0,0,2)
|
|
273
|
+
$lbl = New-Object System.Windows.Controls.TextBlock; $lbl.Text = $label; $lbl.Width = 120; $lbl.Foreground = $fgText; $lbl.VerticalAlignment = "Center"
|
|
274
|
+
$tb = New-Object System.Windows.Controls.TextBox; $tb.Text = $default; $tb.Background = $bgField; $tb.Foreground = $fgValue
|
|
275
|
+
$tb.BorderBrush = $brField; $tb.Padding = [System.Windows.Thickness]::new(8,6,8,6); $tb.CaretBrush = $fgValue
|
|
276
|
+
$tb.Add_GotFocus({ $this.BorderBrush = [System.Windows.Media.BrushConverter]::new().ConvertFrom("#0078d4") })
|
|
277
|
+
$tb.Add_LostFocus({ $this.BorderBrush = [System.Windows.Media.BrushConverter]::new().ConvertFrom("#4a4a4a") })
|
|
278
|
+
[System.Windows.Controls.DockPanel]::SetDock($lbl, "Left")
|
|
279
|
+
$row.Children.Add($lbl) | Out-Null; $row.Children.Add($tb) | Out-Null
|
|
280
|
+
$formBody.Children.Add($row) | Out-Null
|
|
281
|
+
if ($hint) {
|
|
282
|
+
$h = New-Object System.Windows.Controls.TextBlock; $h.Text = $hint; $h.FontSize = 11; $h.Foreground = $fgHint
|
|
283
|
+
$h.Margin = [System.Windows.Thickness]::new(120, 0, 0, 6)
|
|
284
|
+
$formBody.Children.Add($h) | Out-Null
|
|
285
|
+
} else {
|
|
286
|
+
$sp = New-Object System.Windows.Controls.Border; $sp.Height = 4
|
|
287
|
+
$formBody.Children.Add($sp) | Out-Null
|
|
288
|
+
}
|
|
289
|
+
$fields[$key] = $tb
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# --- Build form ---
|
|
293
|
+
& $addSection "CONTAINER" 0
|
|
294
|
+
& $addField "Name" "ContainerName" "bcsandbox" $null
|
|
295
|
+
& $addField "Version" "Version" "sandbox" "sandbox, onprem, or specific version (e.g. 26.0)"
|
|
296
|
+
& $addField "Country" "Country" "us" "us, w1, gb, nl, de, dk, fr, it, es, ca, au"
|
|
297
|
+
|
|
298
|
+
& $addSection "CREDENTIALS" 12
|
|
299
|
+
& $addField "Username" "UserName" "admin" $null
|
|
300
|
+
& $addField "Password" "Password" "P@ssw0rd!" $null
|
|
301
|
+
|
|
302
|
+
& $addSection "CONFIGURATION" 12
|
|
303
|
+
& $addField "Memory" "MemoryLimit" "8G" "4G, 8G, 12G, or 16G"
|
|
304
|
+
& $addField "Isolation" "Isolation" "hyperv" "hyperv (Win10/11) or process (Server)"
|
|
305
|
+
& $addField "Test Toolkit" "TestToolkit" "Libraries" "None, Libraries (faster), or Full (all MS tests)"
|
|
306
|
+
|
|
307
|
+
& $addSection "OPTIONS" 12
|
|
308
|
+
|
|
309
|
+
$chkCDN = New-Object System.Windows.Controls.CheckBox; $chkCDN.Foreground = $fgText
|
|
310
|
+
$chkCDN.Content = " Bypass CDN (blob storage - fixes Win11 25H2 failures)"; $chkCDN.Margin = [System.Windows.Thickness]::new(0,0,0,10)
|
|
311
|
+
$formBody.Children.Add($chkCDN) | Out-Null
|
|
312
|
+
|
|
313
|
+
$licRow = New-Object System.Windows.Controls.DockPanel; $licRow.Margin = [System.Windows.Thickness]::new(0,0,0,6)
|
|
314
|
+
$licLbl = New-Object System.Windows.Controls.TextBlock; $licLbl.Text = "License File"; $licLbl.Width = 120; $licLbl.Foreground = $fgText; $licLbl.VerticalAlignment = "Center"
|
|
315
|
+
$licBrowse = New-Object System.Windows.Controls.Button; $licBrowse.Content = "Browse"; $licBrowse.Padding = [System.Windows.Thickness]::new(10,4,10,4)
|
|
316
|
+
$licBrowse.Background = $bc.ConvertFrom("#333333"); $licBrowse.Foreground = $fgText; $licBrowse.BorderBrush = $bc.ConvertFrom("#505050"); $licBrowse.Width = 70
|
|
317
|
+
$licTb = New-Object System.Windows.Controls.TextBox; $licTb.Background = $bgField; $licTb.Foreground = $fgValue; $licTb.BorderBrush = $brField
|
|
318
|
+
$licTb.Padding = [System.Windows.Thickness]::new(8,6,8,6); $licTb.CaretBrush = $fgValue; $licTb.Margin = [System.Windows.Thickness]::new(0,0,6,0)
|
|
319
|
+
[System.Windows.Controls.DockPanel]::SetDock($licLbl, "Left")
|
|
320
|
+
[System.Windows.Controls.DockPanel]::SetDock($licBrowse, "Right")
|
|
321
|
+
$licRow.Children.Add($licLbl) | Out-Null; $licRow.Children.Add($licBrowse) | Out-Null; $licRow.Children.Add($licTb) | Out-Null
|
|
322
|
+
$formBody.Children.Add($licRow) | Out-Null
|
|
323
|
+
$licHint = New-Object System.Windows.Controls.TextBlock; $licHint.Text = "Optional - path or URL to .bclicense / .flf"; $licHint.FontSize = 11; $licHint.Foreground = $fgHint
|
|
324
|
+
$licHint.Margin = [System.Windows.Thickness]::new(120, 0, 0, 0)
|
|
325
|
+
$formBody.Children.Add($licHint) | Out-Null
|
|
326
|
+
|
|
327
|
+
$scroll.Content = $formBody
|
|
328
|
+
$root.Children.Add($scroll) | Out-Null
|
|
329
|
+
$window.Content = $root
|
|
330
|
+
Add-DarkScrollStyle -Window $window
|
|
331
|
+
|
|
332
|
+
$script:formResult = $null
|
|
333
|
+
|
|
334
|
+
$licBrowse.Add_Click({
|
|
335
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
336
|
+
$dlg = New-Object System.Windows.Forms.OpenFileDialog
|
|
337
|
+
$dlg.Filter = "BC License (*.bclicense;*.flf)|*.bclicense;*.flf|All files (*.*)|*.*"
|
|
338
|
+
if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $licTb.Text = $dlg.FileName }
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
$btnCreate.Add_Click({
|
|
342
|
+
if ([string]::IsNullOrWhiteSpace($fields["ContainerName"].Text)) {
|
|
343
|
+
[System.Windows.MessageBox]::Show("Container name is required.", "Validation", "OK", "Warning"); return
|
|
344
|
+
}
|
|
345
|
+
$script:formResult = @{
|
|
346
|
+
ContainerName = $fields["ContainerName"].Text.Trim()
|
|
347
|
+
Version = $fields["Version"].Text.Trim()
|
|
348
|
+
Country = $fields["Country"].Text.Trim()
|
|
349
|
+
UserName = $fields["UserName"].Text.Trim()
|
|
350
|
+
Password = $fields["Password"].Text
|
|
351
|
+
MemoryLimit = $fields["MemoryLimit"].Text.Trim()
|
|
352
|
+
Isolation = $fields["Isolation"].Text.Trim()
|
|
353
|
+
TestToolkit = $fields["TestToolkit"].Text.Trim()
|
|
354
|
+
BypassCDN = $chkCDN.IsChecked
|
|
355
|
+
LicenseFile = $licTb.Text.Trim()
|
|
356
|
+
}
|
|
357
|
+
$window.Close()
|
|
358
|
+
})
|
|
359
|
+
$btnCancel.Add_Click({ $window.Close() })
|
|
360
|
+
|
|
361
|
+
$null = $window.ShowDialog()
|
|
362
|
+
return $script:formResult
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function Show-BCDMainMenu {
|
|
366
|
+
Add-Type -AssemblyName PresentationFramework
|
|
367
|
+
|
|
368
|
+
$menuItems = @(
|
|
369
|
+
@{ Tag = "create"; Icon = [char]0xE710; Label = "Create new container" }
|
|
370
|
+
@{ Tag = "list"; Icon = [char]0xE8FD; Label = "List containers" }
|
|
371
|
+
@{ Tag = "info"; Icon = [char]0xE946; Label = "Container info" }
|
|
372
|
+
@{ Tag = "start"; Icon = [char]0xE768; Label = "Start container" }
|
|
373
|
+
@{ Tag = "stop"; Icon = [char]0xE71A; Label = "Stop container" }
|
|
374
|
+
@{ Tag = "restart"; Icon = [char]0xE72C; Label = "Restart container" }
|
|
375
|
+
@{ Tag = "remove"; Icon = [char]0xE74D; Label = "Remove container" }
|
|
376
|
+
@{ Tag = "webclient"; Icon = [char]0xE774; Label = "Open Web Client" }
|
|
377
|
+
@{ Tag = "_sep1" }
|
|
378
|
+
@{ Tag = "apps"; Icon = [char]0xE8F1; Label = "List apps" }
|
|
379
|
+
@{ Tag = "install"; Icon = [char]0xE8E5; Label = "Install .app file" }
|
|
380
|
+
@{ Tag = "uninstall"; Icon = [char]0xE738; Label = "Uninstall app" }
|
|
381
|
+
@{ Tag = "publish"; Icon = [char]0xE74E; Label = "Compile and publish" }
|
|
382
|
+
@{ Tag = "_sep2" }
|
|
383
|
+
@{ Tag = "toolkit"; Icon = [char]0xE9D5; Label = "Import Test Toolkit" }
|
|
384
|
+
@{ Tag = "license"; Icon = [char]0xEB95; Label = "Import license" }
|
|
385
|
+
@{ Tag = "tests"; Icon = [char]0xE9D9; Label = "Run tests" }
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
$xaml = @"
|
|
389
|
+
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
390
|
+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
391
|
+
Title="BCDocker" Width="400" Height="580"
|
|
392
|
+
WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
|
|
393
|
+
Background="#252526" FontFamily="Segoe UI">
|
|
394
|
+
<DockPanel Margin="24,22,24,22">
|
|
395
|
+
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,16">
|
|
396
|
+
<TextBlock Text="BCDocker" FontSize="20" FontWeight="SemiBold" Foreground="#cccccc"/>
|
|
397
|
+
<TextBlock Text="Business Central Container Management" FontSize="11.5" Foreground="#555555" Margin="0,3,0,0"/>
|
|
398
|
+
</StackPanel>
|
|
399
|
+
<ScrollViewer VerticalScrollBarVisibility="Auto" Focusable="False">
|
|
400
|
+
<StackPanel x:Name="spMenu"/>
|
|
401
|
+
</ScrollViewer>
|
|
402
|
+
</DockPanel>
|
|
403
|
+
</Window>
|
|
404
|
+
"@
|
|
405
|
+
|
|
406
|
+
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($xaml))
|
|
407
|
+
$window = [System.Windows.Markup.XamlReader]::Load($reader)
|
|
408
|
+
$spMenu = $window.FindName("spMenu")
|
|
409
|
+
Add-DarkScrollStyle -Window $window
|
|
410
|
+
|
|
411
|
+
$bc = [System.Windows.Media.BrushConverter]::new()
|
|
412
|
+
|
|
413
|
+
# Custom button template that removes all default Windows chrome
|
|
414
|
+
$templateXaml = @"
|
|
415
|
+
<ControlTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
416
|
+
TargetType="Button">
|
|
417
|
+
<Border Background="{TemplateBinding Background}"
|
|
418
|
+
BorderBrush="{TemplateBinding BorderBrush}"
|
|
419
|
+
BorderThickness="{TemplateBinding BorderThickness}"
|
|
420
|
+
Padding="{TemplateBinding Padding}"
|
|
421
|
+
SnapsToDevicePixels="True">
|
|
422
|
+
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
|
423
|
+
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
|
|
424
|
+
</Border>
|
|
425
|
+
</ControlTemplate>
|
|
426
|
+
"@
|
|
427
|
+
$templateReader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($templateXaml))
|
|
428
|
+
$btnTemplate = [System.Windows.Markup.XamlReader]::Load($templateReader)
|
|
429
|
+
|
|
430
|
+
$script:menuChoice = $null
|
|
431
|
+
|
|
432
|
+
$sections = @("CONTAINERS", "APPS", "TESTS AND LICENSE")
|
|
433
|
+
$sectionIdx = 0
|
|
434
|
+
|
|
435
|
+
foreach ($item in $menuItems) {
|
|
436
|
+
if ($item.Tag.StartsWith("_sep")) {
|
|
437
|
+
$sectionIdx++
|
|
438
|
+
continue
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (($sectionIdx -eq 0 -and $item.Tag -eq "create") -or
|
|
442
|
+
($sectionIdx -eq 1 -and $item.Tag -eq "apps") -or
|
|
443
|
+
($sectionIdx -eq 2 -and $item.Tag -eq "toolkit")) {
|
|
444
|
+
|
|
445
|
+
$header = New-Object System.Windows.Controls.TextBlock
|
|
446
|
+
$header.Text = $sections[$sectionIdx]
|
|
447
|
+
$header.FontSize = 10.5
|
|
448
|
+
$header.FontWeight = "SemiBold"
|
|
449
|
+
$header.Foreground = $bc.ConvertFrom("#5a5a5a")
|
|
450
|
+
$header.Margin = [System.Windows.Thickness]::new(2, $(if ($sectionIdx -eq 0) { 0 } else { 16 }), 0, 6)
|
|
451
|
+
$spMenu.Children.Add($header) | Out-Null
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
$btn = New-Object System.Windows.Controls.Button
|
|
455
|
+
$btn.Tag = $item.Tag
|
|
456
|
+
$btn.Template = $btnTemplate
|
|
457
|
+
$btn.Background = $bc.ConvertFrom("#2d2d2d")
|
|
458
|
+
$btn.BorderBrush = $bc.ConvertFrom("#2d2d2d")
|
|
459
|
+
$btn.BorderThickness = [System.Windows.Thickness]::new(1)
|
|
460
|
+
$btn.HorizontalContentAlignment = "Left"
|
|
461
|
+
$btn.Padding = [System.Windows.Thickness]::new(10, 8, 10, 8)
|
|
462
|
+
$btn.Margin = [System.Windows.Thickness]::new(0, 1, 0, 1)
|
|
463
|
+
$btn.Cursor = [System.Windows.Input.Cursors]::Hand
|
|
464
|
+
$btn.Focusable = $false
|
|
465
|
+
|
|
466
|
+
$sp = New-Object System.Windows.Controls.StackPanel
|
|
467
|
+
$sp.Orientation = "Horizontal"
|
|
468
|
+
|
|
469
|
+
$iconBlock = New-Object System.Windows.Controls.TextBlock
|
|
470
|
+
$iconBlock.Text = [string]$item.Icon
|
|
471
|
+
$iconBlock.FontFamily = New-Object System.Windows.Media.FontFamily("Segoe MDL2 Assets")
|
|
472
|
+
$iconBlock.FontSize = 14
|
|
473
|
+
$iconBlock.Foreground = $bc.ConvertFrom("#858585")
|
|
474
|
+
$iconBlock.VerticalAlignment = "Center"
|
|
475
|
+
$iconBlock.Width = 24
|
|
476
|
+
|
|
477
|
+
$labelBlock = New-Object System.Windows.Controls.TextBlock
|
|
478
|
+
$labelBlock.Text = $item.Label
|
|
479
|
+
$labelBlock.FontSize = 13
|
|
480
|
+
$labelBlock.Foreground = $bc.ConvertFrom("#cccccc")
|
|
481
|
+
$labelBlock.VerticalAlignment = "Center"
|
|
482
|
+
|
|
483
|
+
$sp.Children.Add($iconBlock) | Out-Null
|
|
484
|
+
$sp.Children.Add($labelBlock) | Out-Null
|
|
485
|
+
$btn.Content = $sp
|
|
486
|
+
|
|
487
|
+
$btn.Add_MouseEnter({
|
|
488
|
+
$this.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom("#37373d")
|
|
489
|
+
$this.BorderBrush = [System.Windows.Media.BrushConverter]::new().ConvertFrom("#37373d")
|
|
490
|
+
})
|
|
491
|
+
$btn.Add_MouseLeave({
|
|
492
|
+
$this.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom("#2d2d2d")
|
|
493
|
+
$this.BorderBrush = [System.Windows.Media.BrushConverter]::new().ConvertFrom("#2d2d2d")
|
|
494
|
+
})
|
|
495
|
+
$btn.Add_Click({
|
|
496
|
+
$script:menuChoice = $this.Tag
|
|
497
|
+
$window.Close()
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
$spMenu.Children.Add($btn) | Out-Null
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
$null = $window.ShowDialog()
|
|
504
|
+
return $script:menuChoice
|
|
505
|
+
}
|
package/ps/Tests.ps1
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
function Invoke-BCDTests {
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
Runs AL tests inside a BC Docker container.
|
|
5
|
+
|
|
6
|
+
.PARAMETER ContainerName
|
|
7
|
+
Target container. Default: "bcsandbox"
|
|
8
|
+
|
|
9
|
+
.PARAMETER TestCodeunitId
|
|
10
|
+
Run a single test codeunit by ID. Omit to run all.
|
|
11
|
+
|
|
12
|
+
.PARAMETER TestFunctionName
|
|
13
|
+
Run a single test function. Requires -TestCodeunitId.
|
|
14
|
+
|
|
15
|
+
.PARAMETER AppProjectFolder
|
|
16
|
+
Path to an AL test app project. Compiles, publishes, and runs tests.
|
|
17
|
+
|
|
18
|
+
.PARAMETER Credential
|
|
19
|
+
Container credentials.
|
|
20
|
+
|
|
21
|
+
.PARAMETER TestResultsFile
|
|
22
|
+
Path for XUnit results XML. Default: ".\TestResults.xml"
|
|
23
|
+
|
|
24
|
+
.PARAMETER ExtensionId
|
|
25
|
+
Run tests from a specific extension only.
|
|
26
|
+
|
|
27
|
+
.PARAMETER DisabledTests
|
|
28
|
+
Array of hashtables to skip: @(@{ codeunitId = 134000; method = "TestToSkip" })
|
|
29
|
+
#>
|
|
30
|
+
[CmdletBinding()]
|
|
31
|
+
param(
|
|
32
|
+
[string]$ContainerName = "bcsandbox",
|
|
33
|
+
[int]$TestCodeunitId = 0,
|
|
34
|
+
[string]$TestFunctionName = "",
|
|
35
|
+
[string]$AppProjectFolder = "",
|
|
36
|
+
[PSCredential]$Credential,
|
|
37
|
+
[string]$TestResultsFile = ".\TestResults.xml",
|
|
38
|
+
[string]$ExtensionId = "",
|
|
39
|
+
[hashtable[]]$DisabledTests = @()
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if (-not (Assert-BcContainerHelper)) { return }
|
|
43
|
+
|
|
44
|
+
if (-not $Credential) {
|
|
45
|
+
$Credential = Get-BCCredential
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Write-BCBanner "BC Test Runner"
|
|
49
|
+
Write-BCProperty "Container" $ContainerName
|
|
50
|
+
Write-BCProperty "Results" $TestResultsFile
|
|
51
|
+
|
|
52
|
+
if ($AppProjectFolder -and (Test-Path $AppProjectFolder)) {
|
|
53
|
+
Write-BCStep "1/3" "Compiling test app from: $AppProjectFolder"
|
|
54
|
+
$appFile = Compile-AppInBcContainer `
|
|
55
|
+
-containerName $ContainerName `
|
|
56
|
+
-appProjectFolder $AppProjectFolder `
|
|
57
|
+
-credential $Credential `
|
|
58
|
+
-UpdateSymbols
|
|
59
|
+
|
|
60
|
+
Write-BCSuccess "Compiled: $appFile"
|
|
61
|
+
|
|
62
|
+
Write-BCStep "2/3" "Publishing..."
|
|
63
|
+
Publish-BcContainerApp `
|
|
64
|
+
-containerName $ContainerName `
|
|
65
|
+
-appFile $appFile `
|
|
66
|
+
-credential $Credential `
|
|
67
|
+
-install -sync `
|
|
68
|
+
-syncMode ForceSync `
|
|
69
|
+
-skipVerification `
|
|
70
|
+
-useDevEndpoint
|
|
71
|
+
|
|
72
|
+
Write-BCSuccess "Test app installed."
|
|
73
|
+
|
|
74
|
+
if (-not $ExtensionId) {
|
|
75
|
+
$appJson = Get-Content (Join-Path $AppProjectFolder "app.json") | ConvertFrom-Json
|
|
76
|
+
$ExtensionId = $appJson.id
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
Write-BCStep "1/3" "No test app folder — running installed tests."
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Write-BCStep "3/3" "Running tests..."
|
|
84
|
+
$runParams = @{
|
|
85
|
+
containerName = $ContainerName
|
|
86
|
+
credential = $Credential
|
|
87
|
+
detailed = $true
|
|
88
|
+
returnTrueIfAllPassed = $true
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ($TestCodeunitId -gt 0) { $runParams.testCodeunit = $TestCodeunitId }
|
|
92
|
+
if ($TestFunctionName) { $runParams.testFunction = $TestFunctionName }
|
|
93
|
+
if ($ExtensionId) { $runParams.extensionId = $ExtensionId }
|
|
94
|
+
if ($DisabledTests.Count -gt 0) { $runParams.disabledTests = $DisabledTests }
|
|
95
|
+
if ($TestResultsFile) { $runParams.XUnitResultFileName = $TestResultsFile }
|
|
96
|
+
|
|
97
|
+
$startTime = Get-Date
|
|
98
|
+
$allPassed = Run-TestsInBcContainer @runParams
|
|
99
|
+
$elapsed = (Get-Date) - $startTime
|
|
100
|
+
$elapsedStr = "{0:mm\:ss}" -f $elapsed
|
|
101
|
+
|
|
102
|
+
if ($allPassed) {
|
|
103
|
+
Write-BCBanner "ALL TESTS PASSED ($elapsedStr)" "Green"
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
Write-BCBanner "SOME TESTS FAILED ($elapsedStr)" "Red"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if ($TestResultsFile -and (Test-Path $TestResultsFile)) {
|
|
110
|
+
[xml]$results = Get-Content $TestResultsFile
|
|
111
|
+
$total = $passed = $failed = $skipped = 0
|
|
112
|
+
|
|
113
|
+
foreach ($assembly in $results.assemblies.assembly) {
|
|
114
|
+
$total += [int]$assembly.total
|
|
115
|
+
$passed += [int]$assembly.passed
|
|
116
|
+
$failed += [int]$assembly.failed
|
|
117
|
+
$skipped += [int]$assembly.skipped
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Write-BCProperty "Total" $total
|
|
121
|
+
Write-BCProperty "Passed" $passed "Green"
|
|
122
|
+
Write-BCProperty "Failed" $failed $(if ($failed -gt 0) { "Red" } else { "White" })
|
|
123
|
+
Write-BCProperty "Skipped" $skipped "Yellow"
|
|
124
|
+
|
|
125
|
+
if ($failed -gt 0) {
|
|
126
|
+
Write-Host ""
|
|
127
|
+
Write-BCError "Failed tests:"
|
|
128
|
+
foreach ($assembly in $results.assemblies.assembly) {
|
|
129
|
+
foreach ($collection in $assembly.collection) {
|
|
130
|
+
foreach ($test in $collection.test) {
|
|
131
|
+
if ($test.result -eq "Fail") {
|
|
132
|
+
Write-Host " - $($test.name)" -ForegroundColor Red
|
|
133
|
+
if ($test.failure.message) {
|
|
134
|
+
Write-Host " $($test.failure.message.'#text')" -ForegroundColor DarkRed
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return $allPassed
|
|
144
|
+
}
|