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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oleksandr (Ciellos)
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# bcd — BC Docker MCP Server & CLI
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server and CLI for managing Business Central Docker containers. Lets AI assistants in Cursor, VS Code, or Claude Desktop directly manage your BC dev environments.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- **Node.js 18+**
|
|
8
|
+
- **Windows PowerShell 5.1** (BC management cmdlets require it)
|
|
9
|
+
- **Docker Desktop** in Windows containers mode
|
|
10
|
+
- **BCDocker PowerShell module** (the `PartnerScript/` sibling folder)
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd bcd
|
|
16
|
+
npm install
|
|
17
|
+
npm run build
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage: MCP Server
|
|
21
|
+
|
|
22
|
+
### Cursor
|
|
23
|
+
|
|
24
|
+
Add to your Cursor MCP settings (`.cursor/mcp.json` or workspace settings):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"bcd": {
|
|
30
|
+
"command": "node",
|
|
31
|
+
"args": ["C:/myspace/work/Mine/DockerUtils/bcd/dist/server.js"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Claude Desktop
|
|
38
|
+
|
|
39
|
+
Add to `claude_desktop_config.json`:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"bcd": {
|
|
45
|
+
"command": "node",
|
|
46
|
+
"args": ["C:/myspace/work/Mine/DockerUtils/bcd/dist/server.js"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### VS Code (Copilot)
|
|
53
|
+
|
|
54
|
+
Add to `.vscode/mcp.json`:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"servers": {
|
|
59
|
+
"bcd": {
|
|
60
|
+
"type": "stdio",
|
|
61
|
+
"command": "node",
|
|
62
|
+
"args": ["C:/myspace/work/Mine/DockerUtils/bcd/dist/server.js"]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
After configuring, your AI assistant can:
|
|
69
|
+
|
|
70
|
+
- "List my BC containers"
|
|
71
|
+
- "Create a new BC sandbox container with version 26.0"
|
|
72
|
+
- "Show me the apps in bcsandbox"
|
|
73
|
+
- "Run tests in my container"
|
|
74
|
+
- "Compile and publish my AL project to bcsandbox"
|
|
75
|
+
- "Stop the bcsandbox container"
|
|
76
|
+
|
|
77
|
+
## Usage: CLI
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# List containers
|
|
81
|
+
node dist/cli.js list
|
|
82
|
+
|
|
83
|
+
# Container details
|
|
84
|
+
node dist/cli.js info bcsandbox
|
|
85
|
+
|
|
86
|
+
# Create container
|
|
87
|
+
node dist/cli.js create --name bcsandbox --version 26.0 --country w1 --bypass-cdn
|
|
88
|
+
|
|
89
|
+
# Start / Stop / Restart
|
|
90
|
+
node dist/cli.js start bcsandbox
|
|
91
|
+
node dist/cli.js stop bcsandbox
|
|
92
|
+
node dist/cli.js restart bcsandbox
|
|
93
|
+
|
|
94
|
+
# Remove container
|
|
95
|
+
node dist/cli.js remove bcsandbox
|
|
96
|
+
|
|
97
|
+
# Open web client
|
|
98
|
+
node dist/cli.js open bcsandbox
|
|
99
|
+
|
|
100
|
+
# List apps
|
|
101
|
+
node dist/cli.js apps bcsandbox
|
|
102
|
+
node dist/cli.js apps bcsandbox --publisher Microsoft
|
|
103
|
+
|
|
104
|
+
# Install .app file
|
|
105
|
+
node dist/cli.js install bcsandbox "C:\apps\MyApp.app"
|
|
106
|
+
|
|
107
|
+
# Compile and publish AL project
|
|
108
|
+
node dist/cli.js publish bcsandbox "C:\Projects\MyApp"
|
|
109
|
+
|
|
110
|
+
# Run tests
|
|
111
|
+
node dist/cli.js test bcsandbox
|
|
112
|
+
node dist/cli.js test bcsandbox --codeunit 50100
|
|
113
|
+
node dist/cli.js test bcsandbox --app "C:\Projects\MyApp.Test"
|
|
114
|
+
|
|
115
|
+
# Import test toolkit
|
|
116
|
+
node dist/cli.js toolkit bcsandbox
|
|
117
|
+
node dist/cli.js toolkit bcsandbox --full
|
|
118
|
+
|
|
119
|
+
# Import license
|
|
120
|
+
node dist/cli.js license bcsandbox "C:\license.bclicense"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Global install (optional)
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npm link
|
|
127
|
+
# Now use 'bcd' directly:
|
|
128
|
+
bcd list
|
|
129
|
+
bcd create --name mybc --version 26.0
|
|
130
|
+
bcd apps mybc
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## MCP Tools Reference
|
|
134
|
+
|
|
135
|
+
| Tool | Description |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `list-containers` | List all BC containers with status |
|
|
138
|
+
| `container-info` | Get version, status, and endpoints |
|
|
139
|
+
| `create-container` | Create a new BC container (5-30 min) |
|
|
140
|
+
| `remove-container` | Remove a container |
|
|
141
|
+
| `start-container` | Start a stopped container |
|
|
142
|
+
| `stop-container` | Stop a running container |
|
|
143
|
+
| `restart-container` | Restart a container |
|
|
144
|
+
| `list-apps` | List apps in a container |
|
|
145
|
+
| `install-app` | Install a .app file |
|
|
146
|
+
| `publish-project` | Compile and publish an AL project |
|
|
147
|
+
| `import-test-toolkit` | Import test toolkit |
|
|
148
|
+
| `import-license` | Import a license file |
|
|
149
|
+
| `run-tests` | Run AL tests |
|
|
150
|
+
|
|
151
|
+
## Architecture
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
bcd/
|
|
155
|
+
├── src/
|
|
156
|
+
│ ├── server.ts # MCP server — exposes tools over stdio
|
|
157
|
+
│ ├── cli.ts # CLI — same operations via command line
|
|
158
|
+
│ └── executor.ts # PowerShell 5.1 execution layer
|
|
159
|
+
├── dist/ # Compiled JS (after npm run build)
|
|
160
|
+
├── package.json
|
|
161
|
+
├── tsconfig.json
|
|
162
|
+
└── README.md
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The MCP server and CLI both call through `executor.ts`, which spawns `powershell.exe` (5.1, not pwsh 7.x) and imports the `BCDocker.psm1` module from the sibling `PartnerScript/` folder. This ensures all the BcContainerHelper cmdlets work correctly, including on BC 28+.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { runPowerShell, runRawPowerShell } from "./executor.js";
|
|
4
|
+
const program = new Command();
|
|
5
|
+
program
|
|
6
|
+
.name("bcd")
|
|
7
|
+
.description("CLI for Business Central Docker container management")
|
|
8
|
+
.version("1.0.0");
|
|
9
|
+
// ── Containers ───────────────────────────────────────────
|
|
10
|
+
program
|
|
11
|
+
.command("list")
|
|
12
|
+
.alias("ls")
|
|
13
|
+
.description("List all BC containers with status")
|
|
14
|
+
.action(async () => {
|
|
15
|
+
const { stdout } = await runRawPowerShell(`
|
|
16
|
+
Import-Module BcContainerHelper -DisableNameChecking -ErrorAction Stop
|
|
17
|
+
$containers = Get-BcContainers
|
|
18
|
+
if ($containers.Count -eq 0) { Write-Output "No BC containers found." }
|
|
19
|
+
else {
|
|
20
|
+
foreach ($name in $containers) {
|
|
21
|
+
$status = docker inspect $name --format '{{.State.Status}}' 2>$null
|
|
22
|
+
Write-Output "$($name.PadRight(25)) $status"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`);
|
|
26
|
+
console.log(stdout);
|
|
27
|
+
});
|
|
28
|
+
program
|
|
29
|
+
.command("info <container>")
|
|
30
|
+
.description("Show container details: version, status, endpoints")
|
|
31
|
+
.action(async (container) => {
|
|
32
|
+
const { stdout } = await runRawPowerShell(`
|
|
33
|
+
Import-Module BcContainerHelper -DisableNameChecking -ErrorAction Stop
|
|
34
|
+
$v = Get-BcContainerNavVersion -containerNameOrId '${container}'
|
|
35
|
+
$u = Get-BcContainerUrl -containerName '${container}' -useHttps:$false
|
|
36
|
+
$s = docker inspect '${container}' --format '{{.State.Status}}' 2>$null
|
|
37
|
+
Write-Output "Container : ${container}"
|
|
38
|
+
Write-Output "Status : $s"
|
|
39
|
+
Write-Output "BC Version : $v"
|
|
40
|
+
Write-Output "Web Client : $u"
|
|
41
|
+
Write-Output "OData/API : http://${container}:7048/BC/api"
|
|
42
|
+
Write-Output "Dev Service: http://${container}:7049/BC"
|
|
43
|
+
`);
|
|
44
|
+
console.log(stdout);
|
|
45
|
+
});
|
|
46
|
+
program
|
|
47
|
+
.command("create")
|
|
48
|
+
.description("Create a new BC container")
|
|
49
|
+
.option("-n, --name <name>", "Container name", "bcsandbox")
|
|
50
|
+
.option("-v, --version <version>", "BC version (sandbox, onprem, 26.0)", "sandbox")
|
|
51
|
+
.option("-c, --country <code>", "Country code (us, w1, gb, nl)", "us")
|
|
52
|
+
.option("-u, --user <username>", "Admin username", "admin")
|
|
53
|
+
.option("-p, --password <password>", "Admin password", "P@ssw0rd!")
|
|
54
|
+
.option("-m, --memory <limit>", "Memory limit", "8G")
|
|
55
|
+
.option("-i, --isolation <mode>", "Isolation: hyperv or process", "hyperv")
|
|
56
|
+
.option("-t, --toolkit <mode>", "Test toolkit: none, libraries, full", "libraries")
|
|
57
|
+
.option("--bypass-cdn", "Skip Azure CDN, use blob storage")
|
|
58
|
+
.option("-l, --license <file>", "Path or URL to .bclicense / .flf license file")
|
|
59
|
+
.action(async (opts) => {
|
|
60
|
+
const includeToolkit = opts.toolkit !== "none" ? "$true" : "$false";
|
|
61
|
+
const libOnly = opts.toolkit === "libraries" ? "$true" : "$false";
|
|
62
|
+
const cdn = opts.bypassCdn ? "-BypassCDN" : "";
|
|
63
|
+
const lic = opts.license ? `-LicenseFile '${opts.license}'` : "";
|
|
64
|
+
console.log(`Creating container '${opts.name}'...`);
|
|
65
|
+
console.log(` Version: ${opts.version}, Country: ${opts.country}, Toolkit: ${opts.toolkit}`);
|
|
66
|
+
console.log(` This may take 5-30 minutes.\n`);
|
|
67
|
+
const { stdout, stderr } = await runPowerShell(`
|
|
68
|
+
New-BCDContainer \`
|
|
69
|
+
-ContainerName '${opts.name}' \`
|
|
70
|
+
-Version '${opts.version}' \`
|
|
71
|
+
-Country '${opts.country}' \`
|
|
72
|
+
-UserName '${opts.user}' \`
|
|
73
|
+
-Password '${opts.password}' \`
|
|
74
|
+
-MemoryLimit '${opts.memory}' \`
|
|
75
|
+
-Isolation '${opts.isolation}' \`
|
|
76
|
+
-IncludeTestToolkit ${includeToolkit} \`
|
|
77
|
+
-TestLibrariesOnly ${libOnly} \`
|
|
78
|
+
${cdn} ${lic}
|
|
79
|
+
`, 1_800_000);
|
|
80
|
+
if (stdout)
|
|
81
|
+
console.log(stdout);
|
|
82
|
+
if (stderr)
|
|
83
|
+
console.error(stderr);
|
|
84
|
+
});
|
|
85
|
+
program
|
|
86
|
+
.command("remove <container>")
|
|
87
|
+
.alias("rm")
|
|
88
|
+
.description("Remove a BC container")
|
|
89
|
+
.action(async (container) => {
|
|
90
|
+
const { stdout } = await runPowerShell(`Remove-BCDContainer -ContainerName '${container}'`);
|
|
91
|
+
console.log(stdout || `Container '${container}' removed.`);
|
|
92
|
+
});
|
|
93
|
+
program
|
|
94
|
+
.command("start <container>")
|
|
95
|
+
.description("Start a stopped container")
|
|
96
|
+
.action(async (container) => {
|
|
97
|
+
const { stdout } = await runPowerShell(`Start-BCDContainer -ContainerName '${container}'`);
|
|
98
|
+
console.log(stdout || `Container '${container}' started.`);
|
|
99
|
+
});
|
|
100
|
+
program
|
|
101
|
+
.command("stop <container>")
|
|
102
|
+
.description("Stop a running container")
|
|
103
|
+
.action(async (container) => {
|
|
104
|
+
const { stdout } = await runPowerShell(`Stop-BCDContainer -ContainerName '${container}'`);
|
|
105
|
+
console.log(stdout || `Container '${container}' stopped.`);
|
|
106
|
+
});
|
|
107
|
+
program
|
|
108
|
+
.command("restart <container>")
|
|
109
|
+
.description("Restart a container")
|
|
110
|
+
.action(async (container) => {
|
|
111
|
+
const { stdout } = await runPowerShell(`Restart-BCDContainer -ContainerName '${container}'`);
|
|
112
|
+
console.log(stdout || `Container '${container}' restarted.`);
|
|
113
|
+
});
|
|
114
|
+
program
|
|
115
|
+
.command("open <container>")
|
|
116
|
+
.description("Open the BC Web Client in your browser")
|
|
117
|
+
.action(async (container) => {
|
|
118
|
+
const { stdout } = await runPowerShell(`Open-BCDWebClient -ContainerName '${container}'`);
|
|
119
|
+
console.log(stdout || "Opening web client...");
|
|
120
|
+
});
|
|
121
|
+
// ── Apps ─────────────────────────────────────────────────
|
|
122
|
+
program
|
|
123
|
+
.command("apps <container>")
|
|
124
|
+
.description("List apps in a container")
|
|
125
|
+
.option("--publisher <name>", "Filter by publisher")
|
|
126
|
+
.action(async (container, opts) => {
|
|
127
|
+
const filter = opts.publisher
|
|
128
|
+
? `| Where-Object { $_.Publisher -eq '${opts.publisher}' }`
|
|
129
|
+
: "";
|
|
130
|
+
const { stdout } = await runRawPowerShell(`
|
|
131
|
+
Import-Module BcContainerHelper -DisableNameChecking -ErrorAction Stop
|
|
132
|
+
Get-BcContainerAppInfo -containerName '${container}' -tenant default -tenantSpecificProperties ${filter} |
|
|
133
|
+
Select-Object Name, Publisher, Version, IsInstalled, Scope |
|
|
134
|
+
Sort-Object Publisher, Name |
|
|
135
|
+
Format-Table -AutoSize | Out-String -Width 200
|
|
136
|
+
`);
|
|
137
|
+
console.log(stdout || "No apps found.");
|
|
138
|
+
});
|
|
139
|
+
program
|
|
140
|
+
.command("install <container> <appFile>")
|
|
141
|
+
.description("Install a .app file into a container")
|
|
142
|
+
.option("-u, --user <username>", "Admin username", "admin")
|
|
143
|
+
.option("-p, --password <password>", "Admin password", "P@ssw0rd!")
|
|
144
|
+
.action(async (container, appFile, opts) => {
|
|
145
|
+
const { stdout } = await runPowerShell(`
|
|
146
|
+
$cred = Get-BCCredential -UserName '${opts.user}' -Password '${opts.password}'
|
|
147
|
+
Install-BCDApp -ContainerName '${container}' -AppFile '${appFile}' -Credential $cred
|
|
148
|
+
`);
|
|
149
|
+
console.log(stdout || "App installed.");
|
|
150
|
+
});
|
|
151
|
+
program
|
|
152
|
+
.command("uninstall <container> <appName> <publisher>")
|
|
153
|
+
.description("Uninstall an app by name and publisher")
|
|
154
|
+
.action(async (container, appName, publisher) => {
|
|
155
|
+
const { stdout } = await runRawPowerShell(`
|
|
156
|
+
Import-Module BcContainerHelper -DisableNameChecking -ErrorAction Stop
|
|
157
|
+
$sorted = Get-BcContainerAppInfo -containerName '${container}' -tenant default -tenantSpecificProperties -sort DependenciesLast
|
|
158
|
+
$target = $sorted | Where-Object { $_.Name -eq '${appName}' -and $_.Publisher -eq '${publisher}' -and $_.IsInstalled }
|
|
159
|
+
if (-not $target) { Write-Output "App '${appName}' by '${publisher}' not found or not installed."; exit }
|
|
160
|
+
foreach ($app in $target) {
|
|
161
|
+
UnInstall-BcContainerApp -name $app.Name -containerName '${container}' -publisher $app.Publisher -version $app.Version -force
|
|
162
|
+
Write-Output "Uninstalled: $($app.Name) v$($app.Version)"
|
|
163
|
+
}
|
|
164
|
+
`);
|
|
165
|
+
console.log(stdout);
|
|
166
|
+
});
|
|
167
|
+
program
|
|
168
|
+
.command("publish <container> <projectFolder>")
|
|
169
|
+
.description("Compile and publish an AL project into a container")
|
|
170
|
+
.option("-u, --user <username>", "Admin username", "admin")
|
|
171
|
+
.option("-p, --password <password>", "Admin password", "P@ssw0rd!")
|
|
172
|
+
.action(async (container, folder, opts) => {
|
|
173
|
+
const { stdout } = await runPowerShell(`
|
|
174
|
+
$cred = Get-BCCredential -UserName '${opts.user}' -Password '${opts.password}'
|
|
175
|
+
Publish-BCDProject -ContainerName '${container}' -ProjectFolder '${folder}' -Credential $cred
|
|
176
|
+
`, 300_000);
|
|
177
|
+
console.log(stdout || "Project compiled and published.");
|
|
178
|
+
});
|
|
179
|
+
// ── Tests & License ──────────────────────────────────────
|
|
180
|
+
program
|
|
181
|
+
.command("test [container]")
|
|
182
|
+
.description("Run AL tests in a container")
|
|
183
|
+
.option("-c, --codeunit <id>", "Test codeunit ID")
|
|
184
|
+
.option("-f, --function <name>", "Test function name")
|
|
185
|
+
.option("-a, --app <folder>", "AL test project folder to compile/publish/run")
|
|
186
|
+
.option("-u, --user <username>", "Admin username", "admin")
|
|
187
|
+
.option("-p, --password <password>", "Admin password", "P@ssw0rd!")
|
|
188
|
+
.action(async (container = "bcsandbox", opts) => {
|
|
189
|
+
const params = [`-ContainerName '${container}'`];
|
|
190
|
+
params.push(`-Credential (Get-BCCredential -UserName '${opts.user}' -Password '${opts.password}')`);
|
|
191
|
+
if (opts.codeunit)
|
|
192
|
+
params.push(`-TestCodeunitId ${opts.codeunit}`);
|
|
193
|
+
if (opts.function)
|
|
194
|
+
params.push(`-TestFunctionName '${opts.function}'`);
|
|
195
|
+
if (opts.app)
|
|
196
|
+
params.push(`-AppProjectFolder '${opts.app}'`);
|
|
197
|
+
const { stdout } = await runPowerShell(`Invoke-BCDTests ${params.join(" ")}`, 600_000);
|
|
198
|
+
console.log(stdout || "Test run complete.");
|
|
199
|
+
});
|
|
200
|
+
program
|
|
201
|
+
.command("toolkit <container>")
|
|
202
|
+
.description("Import BC Test Toolkit into a container")
|
|
203
|
+
.option("--full", "Import full test framework (default: libraries only)")
|
|
204
|
+
.option("-u, --user <username>", "Admin username", "admin")
|
|
205
|
+
.option("-p, --password <password>", "Admin password", "P@ssw0rd!")
|
|
206
|
+
.action(async (container, opts) => {
|
|
207
|
+
const libOnly = opts.full ? "$false" : "$true";
|
|
208
|
+
const { stdout } = await runPowerShell(`
|
|
209
|
+
$cred = Get-BCCredential -UserName '${opts.user}' -Password '${opts.password}'
|
|
210
|
+
Import-BCDTestToolkit -ContainerName '${container}' -Credential $cred -LibrariesOnly ${libOnly}
|
|
211
|
+
`, 300_000);
|
|
212
|
+
console.log(stdout || "Test toolkit imported.");
|
|
213
|
+
});
|
|
214
|
+
program
|
|
215
|
+
.command("license <container> <file>")
|
|
216
|
+
.description("Import a license file into a container")
|
|
217
|
+
.action(async (container, file) => {
|
|
218
|
+
const { stdout } = await runPowerShell(`Import-BCDLicense -ContainerName '${container}' -LicenseFile '${file}'`);
|
|
219
|
+
console.log(stdout || "License imported.");
|
|
220
|
+
});
|
|
221
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
222
|
+
console.error("Error:", err.message);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ExecResult {
|
|
2
|
+
stdout: string;
|
|
3
|
+
stderr: string;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function runPowerShell(script: string, timeoutMs?: number): Promise<ExecResult>;
|
|
7
|
+
export declare function runRawPowerShell(script: string, timeoutMs?: number): Promise<ExecResult>;
|
package/dist/executor.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
function resolveModulePath() {
|
|
7
|
+
// 1. Environment variable override
|
|
8
|
+
if (process.env.BCD_MODULE_PATH)
|
|
9
|
+
return process.env.BCD_MODULE_PATH;
|
|
10
|
+
// 2. Bundled inside npm package (dist/../ps/BCDocker.psm1)
|
|
11
|
+
const bundled = resolve(__dirname, "../ps/BCDocker.psm1");
|
|
12
|
+
if (existsSync(bundled))
|
|
13
|
+
return bundled;
|
|
14
|
+
// 3. Development layout (dist/../../PartnerScript/BCDocker.psm1)
|
|
15
|
+
const dev = resolve(__dirname, "../../PartnerScript/BCDocker.psm1");
|
|
16
|
+
if (existsSync(dev))
|
|
17
|
+
return dev;
|
|
18
|
+
throw new Error("BCDocker.psm1 not found. Set BCD_MODULE_PATH or ensure the ps/ folder exists.");
|
|
19
|
+
}
|
|
20
|
+
const MODULE_PATH = resolveModulePath();
|
|
21
|
+
// BC management cmdlets require Windows PowerShell 5.1, not pwsh 7.x
|
|
22
|
+
const PS_EXE = "powershell.exe";
|
|
23
|
+
export function runPowerShell(script, timeoutMs = 600_000) {
|
|
24
|
+
const wrappedScript = `
|
|
25
|
+
$ErrorActionPreference = 'Stop'
|
|
26
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
27
|
+
Import-Module '${MODULE_PATH.replace(/\\/g, "\\\\")}' -DisableNameChecking -Force
|
|
28
|
+
${script}
|
|
29
|
+
`;
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const child = execFile(PS_EXE, [
|
|
32
|
+
"-NoProfile",
|
|
33
|
+
"-NonInteractive",
|
|
34
|
+
"-ExecutionPolicy",
|
|
35
|
+
"Bypass",
|
|
36
|
+
"-Command",
|
|
37
|
+
wrappedScript,
|
|
38
|
+
], { timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
39
|
+
if (error && !stdout) {
|
|
40
|
+
reject(new Error(stderr || error.message));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
resolve({
|
|
44
|
+
stdout: stdout.trim(),
|
|
45
|
+
stderr: stderr.trim(),
|
|
46
|
+
exitCode: error ? 1 : 0,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function runRawPowerShell(script, timeoutMs = 600_000) {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
execFile(PS_EXE, [
|
|
54
|
+
"-NoProfile",
|
|
55
|
+
"-NonInteractive",
|
|
56
|
+
"-ExecutionPolicy",
|
|
57
|
+
"Bypass",
|
|
58
|
+
"-Command",
|
|
59
|
+
script,
|
|
60
|
+
], { timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
61
|
+
if (error && !stdout) {
|
|
62
|
+
reject(new Error(stderr || error.message));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
resolve({
|
|
66
|
+
stdout: stdout.trim(),
|
|
67
|
+
stderr: stderr.trim(),
|
|
68
|
+
exitCode: error ? 1 : 0,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|