npm package diff

Package: @forge/cli

Versions: 11.0.1-next.14 - 11.1.0-next.24

File: package/out/command-line/controller/install-controller.js

Index: package/out/command-line/controller/install-controller.js
===================================================================
--- package/out/command-line/controller/install-controller.js
+++ package/out/command-line/controller/install-controller.js
@@ -42,9 +42,9 @@
             }
         }
     }
     async installOrUpgrade(upgrade, environment, environmentType, site, product, appId, text, license, overrides) {
-        const isWorkspaceProduct = !!product && (await this.supportedProductsService.isWorkspaceProduct(product));
+        const isWorkspaceProduct = !!product && this.supportedProductsService.isWorkspaceProduct(product);
         return this.ui.displayProgress(async () => {
             if (upgrade) {
                 const isAlreadyUpdated = await this.installationService.upgradeInstallation(site, product, environment, appId);
                 return isAlreadyUpdated;
@@ -58,30 +58,33 @@
                     overrides
                 });
                 return false;
             }
-        }, text.cmd.start(environment, environmentType), (alreadyUpdated) => {
+        }, text.cmd.start(environment, environmentType, (0, cli_shared_1.productDisplayName)(product)), (alreadyUpdated) => {
             if (alreadyUpdated) {
                 return isWorkspaceProduct
                     ? cli_shared_1.Text.upgrade.alreadyUpdated.spinnerWorkspace
                     : cli_shared_1.Text.upgrade.alreadyUpdated.spinnerSite;
             }
             else {
-                return text.cmd.end(false);
+                return text.cmd.end(false, product);
             }
         });
     }
     async promptForProducts(requiredProducts) {
-        this.ui.info(cli_shared_1.Text.installationContext.overviewProduct);
         if (requiredProducts?.length) {
-            return await this.ui.promptForMultiSelectList(cli_shared_1.Text.installationContext.promptOptionalProducts, await this.supportedProductsService.getSupportedProducts(requiredProducts));
+            const supportedProducts = this.supportedProductsService.getSupportedSecondaryProductsForXPA(requiredProducts) ?? [];
+            const choices = supportedProducts.map((product) => ({ names: [product] }));
+            const selected = await this.ui.promptForTable(cli_shared_1.Text.installationContext.promptOptionalProducts, '', [], choices, true);
+            return selected.map((index) => supportedProducts[index]);
         }
+        this.ui.info(cli_shared_1.Text.installationContext.overviewProduct);
         return [
-            await this.ui.promptForList(cli_shared_1.Text.installationContext.promptProduct, await this.supportedProductsService.getSupportedProducts())
+            await this.ui.promptForList(cli_shared_1.Text.installationContext.promptProduct, this.supportedProductsService.getSupportedProducts())
         ];
     }
     async promptForSite(products) {
-        const isWorkspaceBased = products.length > 0 && (await this.supportedProductsService.isWorkspaceProduct(products[0]));
+        const isWorkspaceBased = products.length > 0 && this.supportedProductsService.isWorkspaceProduct(products[0]);
         const overviewText = isWorkspaceBased
             ? cli_shared_1.Text.installationContext.overviewWorkspace
             : cli_shared_1.Text.installationContext.overviewSite;
         this.ui.info(overviewText);
@@ -103,9 +106,9 @@
         });
         const { site, product, environmentKey, environmentType } = await this.installView.promptForUpgrade(installations);
         const productName = (0, cli_shared_1.productDisplayName)(product);
         return {
-            site: await this.supportedProductsService.validateSite(site, productName),
+            site: this.supportedProductsService.validateSite(site, productName),
             product: productName,
             environment: environmentKey,
             environmentType
         };
@@ -152,9 +155,8 @@
         const { id } = await this.appConfigProvider();
         const text = upgrade ? cli_shared_1.Text.upgrade : cli_shared_1.Text.install;
         const validLicense = this.validateLicenseOption(license, environment);
         const overrides = await this.validateEcosystemAppInstallationOverridesInput(licenseModes, usersWithAccess, environment);
-        const { installations } = (await this.installationService.listAppInstallations()) ?? [];
         const environmentPermissions = await this.installationService.getAppEnvironmentPermissions(id, environment);
         const requiredProducts = environmentPermissions?.requiredProducts;
         if (upgrade && (!site || !products?.length)) {
             const upgradeResult = await this.promptForUpgrade(site, products?.[0], environment);
@@ -164,35 +166,19 @@
         }
         if (!requiredProducts?.length) {
             products = products?.length ? products : await this.promptForProducts();
             site = site ? site : await this.promptForSite(products);
+            const bannerText = (await this.supportedProductsService.isWorkspaceProduct(products[0]))
+                ? text.bannerWorkspace
+                : text.bannerSite;
+            this.ui.info(bannerText);
         }
         else {
-            site = site ? site : await this.promptForSite([]);
-            if (!products?.length) {
-                const hasRequiredInstallations = this.checkRequiredInstallationExists(installations, site.host, requiredProducts);
-                if (hasRequiredInstallations) {
-                    this.ui.info(cli_shared_1.Text.install.alreadyInstalledInRequiredProduct(requiredProducts[0]));
-                    products = products?.length ? products : await this.promptForProducts(requiredProducts);
-                }
-                else {
-                    this.ui.info(cli_shared_1.Text.install.installingToRequiredProduct(requiredProducts[0]));
-                    products = requiredProducts;
-                }
-            }
-            else {
-                if (requiredProducts.includes(products[0])) {
-                    this.ui.info(cli_shared_1.Text.install.installingToRequiredProduct(products[0]));
-                }
-                else {
-                    this.ui.info(cli_shared_1.Text.install.installingToOptionalProduct);
-                }
-            }
+            const result = await this.getXPAProductsAndSite(requiredProducts.map(cli_shared_1.productDisplayName), products, site);
+            site = result.site;
+            products = result.products;
+            this.ui.info(cli_shared_1.Text.install.installConfirmation(products.join(', '), site.host));
         }
-        const bannerText = (await this.supportedProductsService.isWorkspaceProduct(products[0]))
-            ? text.bannerWorkspace
-            : text.bannerSite;
-        this.ui.info(bannerText);
         if ((0, cli_shared_1.isSecureSite)(site)) {
             await this.securityPrompt(site);
         }
         if (!environmentPermissions?.hasDeployments) {
@@ -215,34 +201,84 @@
         const addedScopes = await this.extractAddedScopes(environmentPermissions);
         const scopesConfirmationResult = await this.installView.promptForPermissionsConfirmation(environmentPermissions, addedScopes, [...manifestScopes], manifestEgressAddresses, environment, confirmScopes, !!nonInteractive, text);
         if (!scopesConfirmationResult)
             return;
+        const successfulProducts = [];
+        const failedProducts = [];
         for (const product of products) {
-            const isAlreadyUpdated = await this.installOrUpgrade(upgrade, environment, environmentType, site, product, id, text, validLicense, overrides);
-            if (isAlreadyUpdated) {
-                this.ui.info(cli_shared_1.Text.upgrade.alreadyUpdated.banner(environment, product, site.host));
+            try {
+                const isAlreadyUpdated = await this.installOrUpgrade(upgrade, environment, environmentType, site, product, id, text, validLicense, overrides);
+                if (isAlreadyUpdated) {
+                    this.ui.info(cli_shared_1.Text.upgrade.alreadyUpdated.banner(environment, (0, cli_shared_1.productDisplayName)(product), site.host));
+                }
+                else {
+                    this.ui.clearSpinner();
+                    this.ui.emptyLine();
+                    successfulProducts.push(product);
+                }
             }
-            else {
-                this.ui.emptyLine();
-                this.ui.info(text.success.banner(environment, environmentType, product, site.host));
-                const uniqueProductsFromScopes = this.getUniqueInstallationProductsFromScopes(environmentScopes);
-                if (!uniqueProductsFromScopes || uniqueProductsFromScopes.length <= 1)
-                    return;
-                const { installations } = await this.installationService.listNonTechnicalAppInstallations({
-                    site,
-                    environment
-                });
-                const productsToUpgrade = installations
-                    .filter((installation) => !installation.version.isLatest)
-                    .map((installation) => installation.product);
-                const installedProducts = installations.map((installation) => installation.product);
-                const productsToInstall = uniqueProductsFromScopes.filter((product) => !installedProducts.includes(product));
-                if (!productsToInstall.length && productsToUpgrade.length === 0)
-                    return;
-                this.ui.warn(cli_shared_1.Text.install.multiProductScopesDetected(productsToInstall, productsToUpgrade, site.host, environment));
+            catch (error) {
+                this.ui.clearSpinner();
+                this.ui.error(error);
+                failedProducts.push(product);
             }
+            await this.checkForMultiProductScopes(environmentScopes, site, environment);
         }
+        if (successfulProducts.length) {
+            this.ui.info(text.success.banner(environment, environmentType, (0, cli_shared_1.productDisplayName)(successfulProducts.join(', ')), site.host));
+        }
+        else {
+            this.ui.info(cli_shared_1.Text.install.failedAll(site, environment));
+        }
+        if (failedProducts.length) {
+            throw new cli_shared_1.PartialInstallationError(cli_shared_1.Text.error.partialInstallation(failedProducts));
+        }
     }
+    getXPAProductsAndSite = async (requiredProducts, products, site) => {
+        const { installations } = (await this.installationService.listAppInstallations()) ?? [];
+        site = site ? site : await this.promptForSite([]);
+        if (!products?.length) {
+            const hasRequiredInstallations = this.checkRequiredInstallationExists(installations, site.host, requiredProducts);
+            if (hasRequiredInstallations) {
+                this.ui.info(cli_shared_1.Text.install.alreadyInstalledInRequiredProduct(requiredProducts[0]));
+                products = products?.length ? products : await this.promptForProducts(requiredProducts);
+            }
+            else {
+                this.ui.info(cli_shared_1.Text.install.installingToRequiredProduct(requiredProducts[0]));
+                products = requiredProducts;
+            }
+        }
+        else {
+            const allowedProducts = this.supportedProductsService.getSupportedSecondaryProductsForXPA([]);
+            if (products.some((product) => !allowedProducts.includes(product))) {
+                throw new cli_shared_1.ValidationError(cli_shared_1.Text.error.invalidProduct);
+            }
+            if (requiredProducts.includes(products[0])) {
+                this.ui.info(cli_shared_1.Text.install.installingToRequiredProduct(products[0]));
+            }
+            else {
+                this.ui.info(cli_shared_1.Text.install.installingToOptionalProduct(products[0]));
+            }
+        }
+        return { site, products };
+    };
+    checkForMultiProductScopes = async (environmentScopes, site, environment) => {
+        const uniqueProductsFromScopes = this.getUniqueInstallationProductsFromScopes(environmentScopes);
+        if (!uniqueProductsFromScopes || uniqueProductsFromScopes.length <= 1)
+            return;
+        const { installations: nonTechnicalInstallations } = await this.installationService.listNonTechnicalAppInstallations({
+            site,
+            environment
+        });
+        const productsToUpgrade = nonTechnicalInstallations
+            .filter((installation) => !installation.version.isLatest)
+            .map((installation) => installation.product);
+        const installedProducts = nonTechnicalInstallations.map((installation) => installation.product);
+        const productsToInstall = uniqueProductsFromScopes.filter((product) => !installedProducts.includes(product));
+        if (productsToInstall.length === 0 && productsToUpgrade.length === 0)
+            return;
+        this.ui.warn(cli_shared_1.Text.install.multiProductScopesDetected(productsToInstall, productsToUpgrade, site.host, environment));
+    };
     async extractAddedScopes({ addedScopes }) {
         const scopesWithInteractiveConsent = (0, manifest_1.getScopesWithInteractiveConsent)();
         return addedScopes.map((scope) => ({
             name: scope,