diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts index f609d095e3..dc17d5befb 100644 --- a/cli/src/declarations.ts +++ b/cli/src/declarations.ts @@ -474,6 +474,12 @@ export interface CapacitorConfig { includePlugins?: string[]; } +export interface Portal { + name: string; + webDir: string; + appId?: string; +} + export interface PluginsConfig { /** * Plugin configuration by class name. @@ -485,4 +491,14 @@ export interface PluginsConfig { [key: string]: any; } | undefined; + + /** + * Capacitor Portals plugin configuration + * + * @since 3.5.0 + */ + Portals?: { + shell: Portal; + apps: Portal[]; + }; } diff --git a/cli/src/definitions.ts b/cli/src/definitions.ts index ba79b71200..445d5312b8 100644 --- a/cli/src/definitions.ts +++ b/cli/src/definitions.ts @@ -1,4 +1,4 @@ -import type { CapacitorConfig } from './declarations'; +import type { CapacitorConfig, PluginsConfig } from './declarations'; type DeepReadonly = { readonly [P in keyof T]: DeepReadonly }; @@ -119,6 +119,7 @@ export interface Config { readonly web: WebConfig; readonly cli: CLIConfig; readonly app: AppConfig; + readonly plugins?: PluginsConfig; } export interface FrameworkConfig { diff --git a/cli/src/tasks/copy.ts b/cli/src/tasks/copy.ts index 8129cb8b40..2cde358c8b 100644 --- a/cli/src/tasks/copy.ts +++ b/cli/src/tasks/copy.ts @@ -1,5 +1,5 @@ import { copy as fsCopy, pathExists, remove, writeJSON } from '@ionic/utils-fs'; -import { basename, join, relative } from 'path'; +import { basename, join, relative, resolve } from 'path'; import c from '../colors'; import { @@ -15,9 +15,11 @@ import { handleCordovaPluginsJS, writeCordovaAndroidManifest, } from '../cordova'; +import type { Portal } from '../declarations'; import type { Config } from '../definitions'; import { isFatal } from '../errors'; import { logger } from '../log'; +import { getPlugins } from '../plugin'; import { allSerial } from '../util/promise'; import { copyWeb } from '../web/copy'; @@ -70,23 +72,56 @@ export async function copy( 'capacitor:copy:before', ); + const allPlugins = await getPlugins(config, platformName); + let usesCapacitorPortals = false; + if ( + allPlugins.filter( + plugin => plugin.id === '@ionic-enterprise/capacitor-portals', + ).length > 0 + ) { + usesCapacitorPortals = true; + } + if (platformName === config.ios.name) { - await copyWebDir(config, await config.ios.webDirAbs); + if (usesCapacitorPortals) { + await copyFederatedWebDirs(config, await config.ios.webDirAbs); + } else { + await copyWebDir( + config, + await config.ios.webDirAbs, + config.app.webDirAbs, + ); + } await copyCapacitorConfig(config, config.ios.nativeTargetDirAbs); const cordovaPlugins = await getCordovaPlugins(config, platformName); await handleCordovaPluginsJS(cordovaPlugins, config, platformName); } else if (platformName === config.android.name) { - await copyWebDir(config, config.android.webDirAbs); + if (usesCapacitorPortals) { + await copyFederatedWebDirs(config, config.android.webDirAbs); + } else { + await copyWebDir( + config, + config.android.webDirAbs, + config.app.webDirAbs, + ); + } await copyCapacitorConfig(config, config.android.assetsDirAbs); const cordovaPlugins = await getCordovaPlugins(config, platformName); await handleCordovaPluginsJS(cordovaPlugins, config, platformName); await writeCordovaAndroidManifest(cordovaPlugins, config, platformName); } else if (platformName === config.web.name) { - await copyWeb(config); + if (usesCapacitorPortals) { + logger.info( + 'Capacitor Portals Plugin installed, skipping web bundling...', + ); + } else { + await copyWeb(config); + } } else { throw `Platform ${platformName} is not valid.`; } }); + await runPlatformHook( config, platformName, @@ -110,8 +145,11 @@ async function copyCapacitorConfig(config: Config, nativeAbsDir: string) { ); } -async function copyWebDir(config: Config, nativeAbsDir: string) { - const webAbsDir = config.app.webDirAbs; +async function copyWebDir( + config: Config, + nativeAbsDir: string, + webAbsDir: string, +) { const webRelDir = basename(webAbsDir); const nativeRelDir = relative(config.app.rootDir, nativeAbsDir); @@ -138,3 +176,34 @@ async function copyWebDir(config: Config, nativeAbsDir: string) { }, ); } + +async function copyFederatedWebDirs(config: Config, nativeAbsDir: string) { + logger.info('Capacitor Portals Plugin Loaded - Copying Web Assets'); + + if (!config.app.extConfig?.plugins?.Portals) { + throw `Capacitor Portals plugin is present but no valid config is defined.`; + } + + const portalsConfig = config.app.extConfig.plugins.Portals; + if (!isPortal(portalsConfig.shell)) { + throw `Capacitor Portals plugin is present but no valid Shell application is defined in the config.`; + } + + if (!portalsConfig.apps.every(isPortal)) { + throw `Capacitor Portals plugin is present but there is a problem with the apps defined in the config.`; + } + + await Promise.all( + [...portalsConfig.apps, portalsConfig.shell].map(app => { + const appDir = resolve(config.app.rootDir, app.webDir); + return copyWebDir(config, resolve(nativeAbsDir, app.name), appDir); + }), + ); +} + +function isPortal(config: any): config is Portal { + return ( + (config as Portal).webDir !== undefined && + (config as Portal).name !== undefined + ); +}