partnercore-proxy 0.1.5 → 0.4.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.
@@ -0,0 +1,740 @@
1
+ "use strict";
2
+ /**
3
+ * Business Central Container Manager
4
+ *
5
+ * Provides tools for interacting with BC Docker containers
6
+ * using BcContainerHelper PowerShell module.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.BCContainerManager = void 0;
43
+ const child_process_1 = require("child_process");
44
+ const util_1 = require("util");
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const logger_js_1 = require("../utils/logger.js");
48
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
49
+ /**
50
+ * BC Container Manager
51
+ */
52
+ class BCContainerManager {
53
+ logger = (0, logger_js_1.getLogger)();
54
+ workspaceRoot;
55
+ constructor(workspaceRoot) {
56
+ this.workspaceRoot = workspaceRoot;
57
+ }
58
+ /**
59
+ * List all BC containers
60
+ */
61
+ async listContainers() {
62
+ try {
63
+ const { stdout } = await execAsync('docker ps -a --filter "ancestor=mcr.microsoft.com/businesscentral" --format "{{json .}}"');
64
+ const containers = [];
65
+ const lines = stdout.trim().split('\n').filter(l => l);
66
+ for (const line of lines) {
67
+ try {
68
+ const data = JSON.parse(line);
69
+ containers.push({
70
+ id: data.ID,
71
+ name: data.Names,
72
+ image: data.Image,
73
+ status: data.Status,
74
+ ports: data.Ports ? data.Ports.split(',').map((p) => p.trim()) : [],
75
+ created: data.CreatedAt,
76
+ running: data.State === 'running',
77
+ });
78
+ }
79
+ catch {
80
+ // Skip malformed lines
81
+ }
82
+ }
83
+ // Also try BcContainerHelper format
84
+ try {
85
+ const { stdout: psStdout } = await execAsync('powershell -Command "Get-BcContainers | ConvertTo-Json"');
86
+ const bcContainers = JSON.parse(psStdout);
87
+ if (Array.isArray(bcContainers)) {
88
+ for (const bc of bcContainers) {
89
+ if (!containers.find(c => c.name === bc)) {
90
+ containers.push({
91
+ id: bc,
92
+ name: bc,
93
+ image: 'unknown',
94
+ status: 'unknown',
95
+ ports: [],
96
+ created: 'unknown',
97
+ running: true,
98
+ });
99
+ }
100
+ }
101
+ }
102
+ }
103
+ catch {
104
+ // BcContainerHelper not available, use docker only
105
+ }
106
+ return containers;
107
+ }
108
+ catch (error) {
109
+ this.logger.error('Failed to list containers:', error);
110
+ return [];
111
+ }
112
+ }
113
+ /**
114
+ * Get a specific container by name
115
+ */
116
+ async getContainer(containerName) {
117
+ const containers = await this.listContainers();
118
+ return containers.find(c => c.name === containerName || c.id.startsWith(containerName)) || null;
119
+ }
120
+ /**
121
+ * Compile AL project using BcContainerHelper
122
+ */
123
+ async compile(containerName, options) {
124
+ const startTime = Date.now();
125
+ const appFolder = options?.appProjectFolder || this.workspaceRoot;
126
+ const outputFolder = options?.outputFolder || path.join(appFolder, '.output');
127
+ // Ensure output folder exists
128
+ if (!fs.existsSync(outputFolder)) {
129
+ fs.mkdirSync(outputFolder, { recursive: true });
130
+ }
131
+ // Read app.json to get app info
132
+ const appJsonPath = path.join(appFolder, 'app.json');
133
+ if (!fs.existsSync(appJsonPath)) {
134
+ return {
135
+ success: false,
136
+ errors: ['app.json not found in project folder'],
137
+ warnings: [],
138
+ duration: Date.now() - startTime,
139
+ };
140
+ }
141
+ const appJson = JSON.parse(fs.readFileSync(appJsonPath, 'utf-8'));
142
+ const expectedAppFile = path.join(outputFolder, `${appJson.publisher}_${appJson.name}_${appJson.version}.app`);
143
+ try {
144
+ // Use BcContainerHelper to compile
145
+ const escapedAppFolder = appFolder.replace(/\\/g, '\\\\');
146
+ const escapedOutputFolder = outputFolder.replace(/\\/g, '\\\\');
147
+ const psCommand = [
148
+ '$ErrorActionPreference = "Stop"',
149
+ '$result = Compile-AppInBcContainer \\',
150
+ ` -containerName '${containerName}' \\`,
151
+ ` -appProjectFolder '${escapedAppFolder}' \\`,
152
+ ` -appOutputFolder '${escapedOutputFolder}' \\`,
153
+ ' -EnableCodeCop \\',
154
+ ' -EnableAppSourceCop \\',
155
+ ' -EnableUICop \\',
156
+ ' -EnablePerTenantExtensionCop \\',
157
+ ' 2>&1',
158
+ '$result | ConvertTo-Json -Depth 10',
159
+ ].join('\n');
160
+ const { stdout, stderr } = await execAsync(`powershell -Command "${psCommand.replace(/"/g, '\\"')}"`, { maxBuffer: 10 * 1024 * 1024 });
161
+ const errors = [];
162
+ const warnings = [];
163
+ // Parse output for errors and warnings
164
+ const outputLines = (stdout + stderr).split('\n');
165
+ for (const line of outputLines) {
166
+ if (line.includes('error ') || line.includes('Error:')) {
167
+ errors.push(line.trim());
168
+ }
169
+ else if (line.includes('warning ') || line.includes('Warning:')) {
170
+ warnings.push(line.trim());
171
+ }
172
+ }
173
+ // Check if app file was created
174
+ const appFileExists = fs.existsSync(expectedAppFile);
175
+ return {
176
+ success: errors.length === 0 && appFileExists,
177
+ appFile: appFileExists ? expectedAppFile : undefined,
178
+ errors,
179
+ warnings,
180
+ duration: Date.now() - startTime,
181
+ };
182
+ }
183
+ catch (error) {
184
+ const errorMessage = error instanceof Error ? error.message : String(error);
185
+ return {
186
+ success: false,
187
+ errors: [errorMessage],
188
+ warnings: [],
189
+ duration: Date.now() - startTime,
190
+ };
191
+ }
192
+ }
193
+ /**
194
+ * Publish app to BC container
195
+ */
196
+ async publish(containerName, options) {
197
+ const appFolder = this.workspaceRoot;
198
+ const outputFolder = path.join(appFolder, '.output');
199
+ const syncMode = options?.syncMode || 'Development';
200
+ // Find the app file
201
+ let appFile = options?.appFile;
202
+ if (!appFile) {
203
+ if (fs.existsSync(outputFolder)) {
204
+ const files = fs.readdirSync(outputFolder).filter(f => f.endsWith('.app'));
205
+ if (files.length > 0) {
206
+ // Get the most recent app file
207
+ const appFiles = files
208
+ .map(f => ({ name: f, time: fs.statSync(path.join(outputFolder, f)).mtime.getTime() }))
209
+ .sort((a, b) => b.time - a.time);
210
+ appFile = path.join(outputFolder, appFiles[0].name);
211
+ }
212
+ }
213
+ }
214
+ if (!appFile || !fs.existsSync(appFile)) {
215
+ return {
216
+ success: false,
217
+ message: 'App file not found. Run compile first.',
218
+ };
219
+ }
220
+ try {
221
+ const escapedAppFile = appFile.replace(/\\/g, '\\\\');
222
+ const psCommandParts = [
223
+ '$ErrorActionPreference = "Stop"',
224
+ 'Publish-BcContainerApp \\',
225
+ ` -containerName '${containerName}' \\`,
226
+ ` -appFile '${escapedAppFile}' \\`,
227
+ ` -syncMode ${syncMode} \\`,
228
+ options?.skipVerification ? ' -skipVerification \\' : '',
229
+ options?.install ? ' -install \\' : '',
230
+ ' -useDevEndpoint',
231
+ "Write-Output 'SUCCESS'",
232
+ ].filter(Boolean);
233
+ const psCommand = psCommandParts.join('\n');
234
+ const { stdout, stderr } = await execAsync(`powershell -Command "${psCommand.replace(/"/g, '\\"')}"`, { maxBuffer: 10 * 1024 * 1024 });
235
+ const success = stdout.includes('SUCCESS');
236
+ return {
237
+ success,
238
+ message: success ? `Published ${path.basename(appFile)}` : stderr || 'Publish failed',
239
+ syncMode,
240
+ };
241
+ }
242
+ catch (error) {
243
+ const errorMessage = error instanceof Error ? error.message : String(error);
244
+ return {
245
+ success: false,
246
+ message: errorMessage,
247
+ };
248
+ }
249
+ }
250
+ /**
251
+ * Run tests in BC container
252
+ */
253
+ async runTests(containerName, options) {
254
+ const startTime = Date.now();
255
+ try {
256
+ // Build test parameters
257
+ let testParams = '';
258
+ if (options?.testCodeunit) {
259
+ testParams += ` -testCodeunit ${options.testCodeunit}`;
260
+ }
261
+ if (options?.testFunction) {
262
+ testParams += ` -testFunction '${options.testFunction}'`;
263
+ }
264
+ if (options?.extensionId) {
265
+ testParams += ` -extensionId '${options.extensionId}'`;
266
+ }
267
+ const psCommandParts = [
268
+ '$ErrorActionPreference = "Stop"',
269
+ '$results = Run-TestsInBcContainer \\',
270
+ ` -containerName '${containerName}' \\`,
271
+ testParams ? ` ${testParams.trim()} \\` : '',
272
+ ` -detailed:${options?.detailed ? '$true' : '$false'} \\`,
273
+ ' -returnTrueIfAllPassed',
274
+ '$results | ConvertTo-Json -Depth 10',
275
+ ].filter(Boolean);
276
+ const psCommand = psCommandParts.join('\n');
277
+ const { stdout } = await execAsync(`powershell -Command "${psCommand.replace(/"/g, '\\"')}"`, { maxBuffer: 10 * 1024 * 1024, timeout: 600000 });
278
+ // Parse results
279
+ let testsPassed = 0;
280
+ let testsFailed = 0;
281
+ let testsSkipped = 0;
282
+ const results = [];
283
+ try {
284
+ const parsed = JSON.parse(stdout);
285
+ if (typeof parsed === 'boolean') {
286
+ // Simple result
287
+ return {
288
+ success: parsed,
289
+ testsRun: 1,
290
+ testsPassed: parsed ? 1 : 0,
291
+ testsFailed: parsed ? 0 : 1,
292
+ testsSkipped: 0,
293
+ duration: Date.now() - startTime,
294
+ results: [],
295
+ };
296
+ }
297
+ // Detailed results
298
+ if (Array.isArray(parsed)) {
299
+ for (const test of parsed) {
300
+ const result = {
301
+ name: test.name || test.testFunction || 'Unknown',
302
+ codeunitId: test.codeunitId || 0,
303
+ codeunitName: test.codeunitName || 'Unknown',
304
+ result: test.result === '0' || test.result === 'Passed' ? 'Passed' : test.result === '1' || test.result === 'Failed' ? 'Failed' : 'Skipped',
305
+ message: test.message,
306
+ duration: test.duration || 0,
307
+ };
308
+ results.push(result);
309
+ if (result.result === 'Passed')
310
+ testsPassed++;
311
+ else if (result.result === 'Failed')
312
+ testsFailed++;
313
+ else
314
+ testsSkipped++;
315
+ }
316
+ }
317
+ }
318
+ catch {
319
+ // Parse error, try to extract from output
320
+ if (stdout.includes('True')) {
321
+ testsPassed = 1;
322
+ }
323
+ else {
324
+ testsFailed = 1;
325
+ }
326
+ }
327
+ return {
328
+ success: testsFailed === 0,
329
+ testsRun: testsPassed + testsFailed + testsSkipped,
330
+ testsPassed,
331
+ testsFailed,
332
+ testsSkipped,
333
+ duration: Date.now() - startTime,
334
+ results,
335
+ };
336
+ }
337
+ catch (error) {
338
+ const errorMessage = error instanceof Error ? error.message : String(error);
339
+ return {
340
+ success: false,
341
+ testsRun: 0,
342
+ testsPassed: 0,
343
+ testsFailed: 1,
344
+ testsSkipped: 0,
345
+ duration: Date.now() - startTime,
346
+ results: [{
347
+ name: 'Test Execution',
348
+ codeunitId: 0,
349
+ codeunitName: 'N/A',
350
+ result: 'Failed',
351
+ message: errorMessage,
352
+ duration: 0,
353
+ }],
354
+ };
355
+ }
356
+ }
357
+ /**
358
+ * Get container logs
359
+ */
360
+ async getLogs(containerName, options) {
361
+ try {
362
+ let dockerCmd = `docker logs ${containerName}`;
363
+ if (options?.tail) {
364
+ dockerCmd += ` --tail ${options.tail}`;
365
+ }
366
+ if (options?.since) {
367
+ dockerCmd += ` --since ${options.since}`;
368
+ }
369
+ const { stdout, stderr } = await execAsync(dockerCmd, { maxBuffer: 10 * 1024 * 1024 });
370
+ return stdout + stderr;
371
+ }
372
+ catch (error) {
373
+ const errorMessage = error instanceof Error ? error.message : String(error);
374
+ return `Error getting logs: ${errorMessage}`;
375
+ }
376
+ }
377
+ /**
378
+ * Start a container
379
+ */
380
+ async startContainer(containerName) {
381
+ try {
382
+ await execAsync(`docker start ${containerName}`);
383
+ return { success: true, message: `Container ${containerName} started` };
384
+ }
385
+ catch (error) {
386
+ const errorMessage = error instanceof Error ? error.message : String(error);
387
+ return { success: false, message: errorMessage };
388
+ }
389
+ }
390
+ /**
391
+ * Stop a container
392
+ */
393
+ async stopContainer(containerName) {
394
+ try {
395
+ await execAsync(`docker stop ${containerName}`);
396
+ return { success: true, message: `Container ${containerName} stopped` };
397
+ }
398
+ catch (error) {
399
+ const errorMessage = error instanceof Error ? error.message : String(error);
400
+ return { success: false, message: errorMessage };
401
+ }
402
+ }
403
+ /**
404
+ * Restart a container
405
+ */
406
+ async restartContainer(containerName) {
407
+ try {
408
+ await execAsync(`docker restart ${containerName}`);
409
+ return { success: true, message: `Container ${containerName} restarted` };
410
+ }
411
+ catch (error) {
412
+ const errorMessage = error instanceof Error ? error.message : String(error);
413
+ return { success: false, message: errorMessage };
414
+ }
415
+ }
416
+ /**
417
+ * Create a new BC container
418
+ */
419
+ async createContainer(containerName, options) {
420
+ const startTime = Date.now();
421
+ try {
422
+ // Build the New-BcContainer command
423
+ const params = [
424
+ '$ErrorActionPreference = "Stop"',
425
+ ];
426
+ // Determine artifact URL
427
+ let artifactUrl = options.artifactUrl;
428
+ if (!artifactUrl) {
429
+ const version = options.version || '';
430
+ const country = options.country || 'us';
431
+ const type = options.type || 'Sandbox';
432
+ params.push(`$artifactUrl = Get-BCArtifactUrl -type ${type} -country ${country}${version ? ` -version '${version}'` : ''} -select Latest`);
433
+ artifactUrl = '$artifactUrl';
434
+ }
435
+ else {
436
+ params.push(`$artifactUrl = '${artifactUrl}'`);
437
+ artifactUrl = '$artifactUrl';
438
+ }
439
+ // Build New-BcContainer parameters
440
+ const containerParams = [
441
+ `New-BcContainer`,
442
+ `-containerName '${containerName}'`,
443
+ `-artifactUrl ${artifactUrl}`,
444
+ `-accept_eula${options.accept_eula !== false ? '' : ':$false'}`,
445
+ ];
446
+ // Auth
447
+ if (options.auth) {
448
+ containerParams.push(`-auth ${options.auth}`);
449
+ }
450
+ // Credential
451
+ if (options.credential) {
452
+ params.push(`$credential = New-Object System.Management.Automation.PSCredential('${options.credential.username}', (ConvertTo-SecureString '${options.credential.password}' -AsPlainText -Force))`);
453
+ containerParams.push('-credential $credential');
454
+ }
455
+ // License file
456
+ if (options.licenseFile) {
457
+ containerParams.push(`-licenseFile '${options.licenseFile.replace(/\\/g, '\\\\')}'`);
458
+ }
459
+ // Test toolkit options
460
+ if (options.includeTestToolkit) {
461
+ containerParams.push('-includeTestToolkit');
462
+ }
463
+ if (options.includeTestLibrariesOnly) {
464
+ containerParams.push('-includeTestLibrariesOnly');
465
+ }
466
+ if (options.includeTestFrameworkOnly) {
467
+ containerParams.push('-includeTestFrameworkOnly');
468
+ }
469
+ // Other options
470
+ if (options.accept_outdated) {
471
+ containerParams.push('-accept_outdated');
472
+ }
473
+ if (options.enableTaskScheduler) {
474
+ containerParams.push('-enableTaskScheduler');
475
+ }
476
+ if (options.assignPremiumPlan) {
477
+ containerParams.push('-assignPremiumPlan');
478
+ }
479
+ if (options.multitenant) {
480
+ containerParams.push('-multitenant');
481
+ }
482
+ if (options.memoryLimit) {
483
+ containerParams.push(`-memoryLimit '${options.memoryLimit}'`);
484
+ }
485
+ if (options.isolation) {
486
+ containerParams.push(`-isolation ${options.isolation}`);
487
+ }
488
+ if (options.updateHosts) {
489
+ containerParams.push('-updateHosts');
490
+ }
491
+ params.push(containerParams.join(' `\n '));
492
+ params.push("Write-Output 'CONTAINER_CREATED_SUCCESS'");
493
+ params.push(`Write-Output "http://${containerName}/BC/"`);
494
+ const psCommand = params.join('\n');
495
+ this.logger.info(`Creating container ${containerName}...`);
496
+ const { stdout, stderr } = await execAsync(`powershell -Command "${psCommand.replace(/"/g, '\\"')}"`, {
497
+ maxBuffer: 50 * 1024 * 1024,
498
+ timeout: 1800000 // 30 minutes for container creation
499
+ });
500
+ const success = stdout.includes('CONTAINER_CREATED_SUCCESS');
501
+ const duration = Math.round((Date.now() - startTime) / 1000);
502
+ if (success) {
503
+ // Extract web client URL
504
+ const urlMatch = stdout.match(/http:\/\/[^\s]+/);
505
+ return {
506
+ success: true,
507
+ message: `Container ${containerName} created successfully in ${duration}s`,
508
+ containerName,
509
+ webClientUrl: urlMatch ? urlMatch[0] : undefined,
510
+ };
511
+ }
512
+ else {
513
+ return {
514
+ success: false,
515
+ message: stderr || stdout || 'Container creation failed',
516
+ };
517
+ }
518
+ }
519
+ catch (error) {
520
+ const errorMessage = error instanceof Error ? error.message : String(error);
521
+ return { success: false, message: errorMessage };
522
+ }
523
+ }
524
+ /**
525
+ * Remove a container
526
+ */
527
+ async removeContainer(containerName, force) {
528
+ try {
529
+ // Try BcContainerHelper first
530
+ const psCommand = `Remove-BcContainer -containerName '${containerName}'`;
531
+ await execAsync(`powershell -Command "${psCommand}"`, { timeout: 300000 });
532
+ return { success: true, message: `Container ${containerName} removed` };
533
+ }
534
+ catch {
535
+ // Fall back to docker
536
+ try {
537
+ await execAsync(`docker rm ${force ? '-f' : ''} ${containerName}`);
538
+ return { success: true, message: `Container ${containerName} removed` };
539
+ }
540
+ catch (error) {
541
+ const errorMessage = error instanceof Error ? error.message : String(error);
542
+ return { success: false, message: errorMessage };
543
+ }
544
+ }
545
+ }
546
+ /**
547
+ * Get container info including web client URL
548
+ */
549
+ async getContainerUrl(containerName) {
550
+ try {
551
+ const psCommand = `
552
+ $config = Get-BcContainerServerConfiguration -containerName '${containerName}'
553
+ @{
554
+ webClientUrl = "http://${containerName}/$($config.ServerInstance)/WebClient"
555
+ soapUrl = "http://${containerName}:7047/$($config.ServerInstance)/WS"
556
+ oDataUrl = "http://${containerName}:7048/$($config.ServerInstance)/OData"
557
+ } | ConvertTo-Json
558
+ `;
559
+ const { stdout } = await execAsync(`powershell -Command "${psCommand.replace(/"/g, '\\"')}"`);
560
+ return JSON.parse(stdout);
561
+ }
562
+ catch {
563
+ return {};
564
+ }
565
+ }
566
+ /**
567
+ * Get installed extensions/apps from container
568
+ */
569
+ async getExtensions(containerName) {
570
+ try {
571
+ const psCommand = `
572
+ $apps = Get-BcContainerAppInfo -containerName '${containerName}' -tenantSpecificProperties
573
+ $apps | ForEach-Object {
574
+ @{
575
+ name = $_.Name
576
+ publisher = $_.Publisher
577
+ version = $_.Version
578
+ appId = $_.AppId
579
+ scope = $_.Scope
580
+ isPublished = $_.IsPublished
581
+ isInstalled = $_.IsInstalled
582
+ }
583
+ } | ConvertTo-Json -Depth 5
584
+ `;
585
+ const { stdout } = await execAsync(`powershell -Command "${psCommand.replace(/"/g, '\\"')}"`, { maxBuffer: 10 * 1024 * 1024 });
586
+ let extensions = [];
587
+ try {
588
+ const parsed = JSON.parse(stdout);
589
+ extensions = Array.isArray(parsed) ? parsed : [parsed];
590
+ }
591
+ catch {
592
+ // Empty result
593
+ }
594
+ return {
595
+ success: true,
596
+ extensions,
597
+ };
598
+ }
599
+ catch (error) {
600
+ const errorMessage = error instanceof Error ? error.message : String(error);
601
+ return { success: false, extensions: [], message: errorMessage };
602
+ }
603
+ }
604
+ /**
605
+ * Uninstall an app from container
606
+ */
607
+ async uninstallApp(containerName, options) {
608
+ try {
609
+ const params = [
610
+ '$ErrorActionPreference = "Stop"',
611
+ ];
612
+ // Add credential if provided
613
+ if (options.credential) {
614
+ params.push(`$credential = New-Object System.Management.Automation.PSCredential('${options.credential.username}', (ConvertTo-SecureString '${options.credential.password}' -AsPlainText -Force))`);
615
+ }
616
+ const uninstallParams = [
617
+ 'Uninstall-BcContainerApp',
618
+ `-containerName '${containerName}'`,
619
+ `-name '${options.name}'`,
620
+ ];
621
+ if (options.publisher) {
622
+ uninstallParams.push(`-publisher '${options.publisher}'`);
623
+ }
624
+ if (options.version) {
625
+ uninstallParams.push(`-version '${options.version}'`);
626
+ }
627
+ if (options.force) {
628
+ uninstallParams.push('-Force');
629
+ }
630
+ if (options.credential) {
631
+ uninstallParams.push('-credential $credential');
632
+ }
633
+ params.push(uninstallParams.join(' '));
634
+ params.push("Write-Output 'UNINSTALL_SUCCESS'");
635
+ const psCommand = params.join('\n');
636
+ const { stdout, stderr } = await execAsync(`powershell -Command "${psCommand.replace(/"/g, '\\"')}"`, { maxBuffer: 10 * 1024 * 1024 });
637
+ const success = stdout.includes('UNINSTALL_SUCCESS');
638
+ return {
639
+ success,
640
+ message: success
641
+ ? `Successfully uninstalled ${options.name}`
642
+ : stderr || stdout || 'Uninstall failed',
643
+ };
644
+ }
645
+ catch (error) {
646
+ const errorMessage = error instanceof Error ? error.message : String(error);
647
+ return { success: false, message: errorMessage };
648
+ }
649
+ }
650
+ /**
651
+ * Compile and return only warnings (quick check)
652
+ */
653
+ async compileWarningsOnly(containerName, options) {
654
+ const appFolder = options?.appProjectFolder || this.workspaceRoot;
655
+ const outputFolder = path.join(appFolder, '.output');
656
+ if (!fs.existsSync(outputFolder)) {
657
+ fs.mkdirSync(outputFolder, { recursive: true });
658
+ }
659
+ try {
660
+ const escapedAppFolder = appFolder.replace(/\\/g, '\\\\');
661
+ const escapedOutputFolder = outputFolder.replace(/\\/g, '\\\\');
662
+ const psCommand = [
663
+ '$ErrorActionPreference = "Continue"',
664
+ '$warnings = @()',
665
+ 'Compile-AppInBcContainer \\',
666
+ ` -containerName '${containerName}' \\`,
667
+ ` -appProjectFolder '${escapedAppFolder}' \\`,
668
+ ` -appOutputFolder '${escapedOutputFolder}' \\`,
669
+ ' -EnableCodeCop \\',
670
+ ' -EnableAppSourceCop \\',
671
+ ' -EnableUICop \\',
672
+ ' -EnablePerTenantExtensionCop \\',
673
+ ' 2>&1 | ForEach-Object {',
674
+ ' if ($_ -match "warning") { $warnings += $_.ToString() }',
675
+ ' }',
676
+ '$warnings | ConvertTo-Json',
677
+ ].join('\n');
678
+ const { stdout, stderr } = await execAsync(`powershell -Command "${psCommand.replace(/"/g, '\\"')}"`, { maxBuffer: 10 * 1024 * 1024 });
679
+ const warnings = [];
680
+ // Parse warnings from output
681
+ const outputLines = (stdout + stderr).split('\n');
682
+ for (const line of outputLines) {
683
+ if (line.toLowerCase().includes('warning')) {
684
+ warnings.push(line.trim());
685
+ }
686
+ }
687
+ return {
688
+ success: true,
689
+ warnings,
690
+ warningCount: warnings.length,
691
+ message: warnings.length === 0
692
+ ? 'No warnings found'
693
+ : `Found ${warnings.length} warning(s)`,
694
+ };
695
+ }
696
+ catch (error) {
697
+ const errorMessage = error instanceof Error ? error.message : String(error);
698
+ return {
699
+ success: false,
700
+ warnings: [],
701
+ warningCount: 0,
702
+ message: errorMessage
703
+ };
704
+ }
705
+ }
706
+ /**
707
+ * Download symbols from container
708
+ */
709
+ async downloadSymbols(containerName, targetFolder) {
710
+ const folder = targetFolder || path.join(this.workspaceRoot, '.alpackages');
711
+ try {
712
+ if (!fs.existsSync(folder)) {
713
+ fs.mkdirSync(folder, { recursive: true });
714
+ }
715
+ const psCommand = `
716
+ $ErrorActionPreference = 'Stop'
717
+ Get-BcContainerAppInfo -containerName '${containerName}' -tenantSpecificProperties -sort DependenciesFirst | ForEach-Object {
718
+ if ($_.IsPublished) {
719
+ $appFile = Join-Path '${folder.replace(/\\/g, '\\\\')}' "$($_.Publisher)_$($_.Name)_$($_.Version).app"
720
+ Get-BcContainerApp -containerName '${containerName}' -appName $_.Name -appVersion $_.Version -appPublisher $_.Publisher > $appFile
721
+ Write-Output $appFile
722
+ }
723
+ }
724
+ `;
725
+ const { stdout } = await execAsync(`powershell -Command "${psCommand.replace(/"/g, '\\"')}"`, { maxBuffer: 50 * 1024 * 1024, timeout: 300000 });
726
+ const files = stdout.trim().split('\n').filter(f => f && f.endsWith('.app'));
727
+ return {
728
+ success: true,
729
+ message: `Downloaded ${files.length} symbol files`,
730
+ files,
731
+ };
732
+ }
733
+ catch (error) {
734
+ const errorMessage = error instanceof Error ? error.message : String(error);
735
+ return { success: false, message: errorMessage };
736
+ }
737
+ }
738
+ }
739
+ exports.BCContainerManager = BCContainerManager;
740
+ //# sourceMappingURL=bc-container.js.map