diff --git a/cli/src/common.ts b/cli/src/common.ts index 2dfdd222e7..2d184c8430 100644 --- a/cli/src/common.ts +++ b/cli/src/common.ts @@ -6,6 +6,7 @@ import c from './colors'; import type { Config, PackageJson } from './definitions'; import { fatal } from './errors'; import { output, logger } from './log'; +import { findNXMonorepoRoot, isNXMonorepo } from './util/monorepotools'; import { resolveNode } from './util/node'; import { runCommand } from './util/subprocess'; @@ -61,11 +62,15 @@ export async function checkWebDir(config: Config): Promise { export async function checkPackage(): Promise { if (!(await pathExists('package.json'))) { - return ( - `The Capacitor CLI needs to run at the root of an npm package.\n` + - `Make sure you have a package.json file in the directory where you run the Capacitor CLI.\n` + - `More info: ${c.strong('https://docs.npmjs.com/cli/init')}` - ); + if (await pathExists('project.json')) { + return null; + } else { + return ( + `The Capacitor CLI needs to run at the root of an npm package or in a valid NX monorepo.\n` + + `Make sure you have a package.json or project.json file in the directory where you run the Capacitor CLI.\n` + + `More info: ${c.strong('https://docs.npmjs.com/cli/init')}` + ); + } } return null; } @@ -164,7 +169,12 @@ export async function runPlatformHook( hook: string, ): Promise { const { spawn } = await import('child_process'); - const pkg = await readJSON(join(platformDir, 'package.json')); + let pkg; + if (isNXMonorepo(platformDir)) { + pkg = await readJSON(join(findNXMonorepoRoot(platformDir), 'package.json')); + } else { + pkg = await readJSON(join(platformDir, 'package.json')); + } const cmd = pkg.scripts?.[hook]; if (!cmd) { diff --git a/cli/src/config.ts b/cli/src/config.ts index ecf4feffcd..f617ce32d7 100644 --- a/cli/src/config.ts +++ b/cli/src/config.ts @@ -23,6 +23,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 { requireTS, resolveNode } from './util/node'; import { lazy } from './util/promise'; import { getCommandOutput } from './util/subprocess'; @@ -38,6 +39,25 @@ export async function loadConfig(): Promise { const cliRootDir = dirname(__dirname); const conf = await loadExtConfig(appRootDir); + const depsForNx = await (async (): Promise< + { devDependencies: any; dependencies: any } | object + > => { + if (isNXMonorepo(appRootDir)) { + const rootOfNXMonorepo = findNXMonorepoRoot(appRootDir); + const pkgJSONOfMonorepoRoot: any = await tryFn( + readJSON, + resolve(rootOfNXMonorepo, 'package.json'), + ); + const devDependencies = pkgJSONOfMonorepoRoot?.devDependencies ?? {}; + const dependencies = pkgJSONOfMonorepoRoot?.dependencies ?? {}; + return { + devDependencies, + dependencies, + }; + } + return {}; + })(); + const appId = conf.extConfig.appId ?? ''; const appName = conf.extConfig.appName ?? ''; const webDir = conf.extConfig.webDir ?? 'www'; @@ -57,6 +77,7 @@ export async function loadConfig(): Promise { package: (await tryFn(readJSON, resolve(appRootDir, 'package.json'))) ?? { name: appName, version: '1.0.0', + ...depsForNx, }, ...conf, }, diff --git a/cli/src/ios/update.ts b/cli/src/ios/update.ts index 606179ced0..d2c0d2b7c0 100644 --- a/cli/src/ios/update.ts +++ b/cli/src/ios/update.ts @@ -101,6 +101,21 @@ async function updatePodfile( /(def capacitor_pods)[\s\S]+?(\nend)/, `$1${dependenciesContent}$2`, ); + podfileContent = podfileContent.replace( + `require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'`, + `def assertDeploymentTarget(installer) + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + # ensure IPHONEOS_DEPLOYMENT_TARGET is at least 13.0 + deployment_target = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f + should_upgrade = deployment_target < 13.0 && deployment_target != 0.0 + if should_upgrade + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + end + end + end +end`, + ); await writeFile(podfilePath, podfileContent, { encoding: 'utf-8' }); const podPath = await config.ios.podPath; diff --git a/cli/src/util/monorepotools.ts b/cli/src/util/monorepotools.ts new file mode 100644 index 0000000000..726bf1ecad --- /dev/null +++ b/cli/src/util/monorepotools.ts @@ -0,0 +1,114 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { join, dirname, relative } from 'node:path'; + +/** + * Finds the monorepo root from the given path. + * @param currentPath - The current path to start searching from. + * @returns The path to the monorepo root. + * @throws An error if the monorepo root is not found. + */ +export function findMonorepoRoot(currentPath: string): string { + const packageJsonPath = join(currentPath, 'package.json'); + const pnpmWorkspacePath = join(currentPath, 'pnpm-workspace.yaml'); + if ( + existsSync(pnpmWorkspacePath) || + (existsSync(packageJsonPath) && + JSON.parse(readFileSync(packageJsonPath, 'utf-8')).workspaces) + ) { + return currentPath; + } + const parentPath = dirname(currentPath); + if (parentPath === currentPath) { + throw new Error('Monorepo root not found'); + } + return findMonorepoRoot(parentPath); +} + +/** + * Finds the NX monorepo root from the given path. + * @param currentPath - The current path to start searching from. + * @returns The path to the monorepo root. + * @throws An error if the monorepo root is not found. + */ +export function findNXMonorepoRoot(currentPath: string): string { + const nxJsonPath = join(currentPath, 'nx.json'); + if (existsSync(nxJsonPath)) { + return currentPath; + } + const parentPath = dirname(currentPath); + if (parentPath === currentPath) { + throw new Error('Monorepo root not found'); + } + return findNXMonorepoRoot(parentPath); +} + +/** + * Finds the path to a package within the node_modules folder, + * searching up the directory hierarchy until the last possible directory is reached. + * @param packageName - The name of the package to find. + * @param currentPath - The current path to start searching from. + * @param lastPossibleDirectory - The last possible directory to search for the package. + * @returns The path to the package, or null if not found. + */ +export function findPackagePath( + packageName: string, + currentPath: string, + lastPossibleDirectory: string, +): string | null { + const nodeModulesPath = join(currentPath, 'node_modules', packageName); + if (existsSync(nodeModulesPath)) { + return nodeModulesPath; + } + if (currentPath === lastPossibleDirectory) { + return null; + } + const parentPath = dirname(currentPath); + return findPackagePath(packageName, parentPath, lastPossibleDirectory); +} + +/** + * Finds the relative path to a package from the current directory, + * using the monorepo root as the last possible directory. + * @param packageName - The name of the package to find. + * @param currentPath - The current path to start searching from. + * @returns The relative path to the package, or null if not found. + */ +export function findPackageRelativePathInMonorepo( + packageName: string, + currentPath: string, +): string | null { + const monorepoRoot = findMonorepoRoot(currentPath); + const packagePath = findPackagePath(packageName, currentPath, monorepoRoot); + if (packagePath) { + return relative(currentPath, packagePath); + } + return null; +} + +/** + * Detects if the current directory is part of a monorepo (npm, yarn, pnpm). + * @param currentPath - The current path to start searching from. + * @returns True if the current directory is part of a monorepo, false otherwise. + */ +export function isMonorepo(currentPath: string): boolean { + try { + findMonorepoRoot(currentPath); + return true; + } catch (error) { + return false; + } +} + +/** + * Detects if the current directory is part of a nx integrated monorepo. + * @param currentPath - The current path to start searching from. + * @returns True if the current directory is part of a monorepo, false otherwise. + */ +export function isNXMonorepo(currentPath: string): boolean { + try { + findNXMonorepoRoot(currentPath); + return true; + } catch (error) { + return false; + } +} diff --git a/ios-template/App/Podfile b/ios-template/App/Podfile index affb695cb6..73bd027ca6 100644 --- a/ios-template/App/Podfile +++ b/ios-template/App/Podfile @@ -1,4 +1,15 @@ -require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' +def assertDeploymentTarget(installer) + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + # ensure IPHONEOS_DEPLOYMENT_TARGET is at least 13.0 + deployment_target = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f + should_upgrade = deployment_target < 13.0 && deployment_target != 0.0 + if should_upgrade + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + end + end + end +end platform :ios, '13.0' use_frameworks!