diff --git a/cli/package.json b/cli/package.json index 38b044f35..0f6478073 100644 --- a/cli/package.json +++ b/cli/package.json @@ -48,6 +48,7 @@ "@ionic/utils-fs": "^3.1.6", "@ionic/utils-subprocess": "2.1.11", "@ionic/utils-terminal": "^2.3.3", + "@npmcli/map-workspaces": "^4.0.1", "commander": "^9.3.0", "debug": "^4.3.4", "env-paths": "^2.2.0", @@ -65,6 +66,7 @@ "devDependencies": { "@types/debug": "^4.1.7", "@types/jest": "^29.5.0", + "@types/npmcli__map-workspaces": "^3.0.4", "@types/plist": "^3.0.2", "@types/prompts": "^2.0.14", "@types/semver": "^7.3.10", diff --git a/cli/src/config.ts b/cli/src/config.ts index c88590185..22ddd25ae 100644 --- a/cli/src/config.ts +++ b/cli/src/config.ts @@ -10,7 +10,7 @@ import { fatal, isFatal } from './errors'; import { logger } from './log'; import { tryFn } from './util/fn'; import { formatJSObject } from './util/js'; -import { findNXMonorepoRoot, isNXMonorepo } from './util/monorepotools'; +import { findMonorepoRoot, findNXMonorepoRoot, isMonorepo, isNXMonorepo } from './util/monorepotools'; import { requireTS, resolveNode } from './util/node'; import { lazy } from './util/promise'; import { getCommandOutput } from './util/subprocess'; @@ -40,6 +40,26 @@ export async function loadConfig(): Promise { return {}; })(); + const workspacesSetup = await (async (): Promise => { + if (isMonorepo(appRootDir) && conf.extConfig.workspaces === 'npm') { + const { fileType, path: rootOfMonorepo } = findMonorepoRoot(appRootDir); + if (fileType === 'yaml') { + return undefined; + } + const pkgJSONOfMonorepoRoot: { workspaces: string[] } | null = await tryFn( + readJSON, + resolve(rootOfMonorepo, 'package.json'), + ); + const workspaces = pkgJSONOfMonorepoRoot?.workspaces ?? []; + return { + type: conf.extConfig.workspaces, + workspaceDirs: workspaces, + workspaceRoot: rootOfMonorepo, + }; + } + return undefined; + })(); + const appId = conf.extConfig.appId ?? ''; const appName = conf.extConfig.appName ?? ''; const webDir = conf.extConfig.webDir ?? 'www'; @@ -61,6 +81,7 @@ export async function loadConfig(): Promise { version: '1.0.0', ...depsForNx, }, + workspaces: workspacesSetup, ...conf, }, }; diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts index 4bd9b54cd..ce2101135 100644 --- a/cli/src/declarations.ts +++ b/cli/src/declarations.ts @@ -582,6 +582,17 @@ export interface CapacitorConfig { * @since 3.0.0 */ includePlugins?: string[]; + + /** + * Name of the workspace tool you are using. + * + * Setting this option tells `npx cap sync` to check you workspace packages for capacitor plugins. + * + * Currently only `npm` workspaces are supported. + * + * @since X.X.X + * */ + readonly workspaces?: 'npm'; } export interface PluginsConfig { diff --git a/cli/src/definitions.ts b/cli/src/definitions.ts index a81278120..6589ba411 100644 --- a/cli/src/definitions.ts +++ b/cli/src/definitions.ts @@ -14,6 +14,7 @@ export const enum OS { export interface PackageJson { readonly name: string; readonly version: string; + readonly workspaces?: string[]; readonly dependencies?: { readonly [key: string]: string | undefined }; readonly devDependencies?: { readonly [key: string]: string | undefined }; } @@ -62,6 +63,9 @@ export interface AppConfig { readonly extConfigName: string; readonly extConfigFilePath: string; readonly extConfig: ExternalConfig; + readonly workspaces: + | { type: 'npm'; workspaceDirs: string[]; workspaceRoot: string } + | undefined; } export interface AndroidConfig extends PlatformConfig { diff --git a/cli/src/plugin.ts b/cli/src/plugin.ts index b5b98c996..ec16dca6b 100644 --- a/cli/src/plugin.ts +++ b/cli/src/plugin.ts @@ -1,4 +1,5 @@ import { readJSON } from '@ionic/utils-fs'; +import mapWorkspaces from '@npmcli/map-workspaces'; import { dirname, join } from 'path'; import c from './colors'; @@ -52,8 +53,29 @@ export function getIncludedPluginPackages(config: Config, platform: string): rea return extConfig.ios?.includePlugins ?? extConfig.includePlugins; } } - export async function getPlugins(config: Config, platform: string): Promise { + if (config.app.workspaces?.type === 'npm') { + const { workspaceDirs, workspaceRoot } = config.app.workspaces; + const workspacePackages = await mapWorkspaces({ + cwd: workspaceRoot, + pkg: { workspaces: workspaceDirs }, + }); + const resolvedWorkspacePackages = await Promise.all( + Array.from(workspacePackages.entries()).map(async ([name, path]) => { + return parsePluginMeta(join(path, 'package.json'), name); + }), + ); + const possiblePackageJsonPlugins = getIncludedPluginPackages(config, platform) ?? getDependencies(config); + + const resolvedPackageJsonPlugins = await Promise.all( + possiblePackageJsonPlugins.map(async (p) => resolvePlugin(config, p)), + ); + + const plugins = [...resolvedPackageJsonPlugins, ...resolvedWorkspacePackages].filter( + (p): p is Plugin => p !== null, + ); + return plugins; + } const possiblePlugins = getIncludedPluginPackages(config, platform) ?? getDependencies(config); const resolvedPlugins = await Promise.all(possiblePlugins.map(async (p) => resolvePlugin(config, p))); @@ -67,6 +89,15 @@ export async function resolvePlugin(config: Config, name: string): Promise => { + try { const rootPath = dirname(packagePath); const meta = await readJSON(packagePath); if (!meta) { @@ -92,11 +123,11 @@ export async function resolvePlugin(config: Config, name: string): Promise