Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: better support monorepos #6811

Merged
merged 8 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions cli/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -61,11 +62,15 @@ export async function checkWebDir(config: Config): Promise<string | null> {

export async function checkPackage(): Promise<string | null> {
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;
}
Expand Down Expand Up @@ -164,7 +169,12 @@ export async function runPlatformHook(
hook: string,
): Promise<void> {
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) {
Expand Down
21 changes: 21 additions & 0 deletions cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -38,6 +39,25 @@ export async function loadConfig(): Promise<Config> {
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';
Expand All @@ -57,6 +77,7 @@ export async function loadConfig(): Promise<Config> {
package: (await tryFn(readJSON, resolve(appRootDir, 'package.json'))) ?? {
name: appName,
version: '1.0.0',
...depsForNx,
},
...conf,
},
Expand Down
15 changes: 15 additions & 0 deletions cli/src/ios/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
114 changes: 114 additions & 0 deletions cli/src/util/monorepotools.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
13 changes: 12 additions & 1 deletion ios-template/App/Podfile
Original file line number Diff line number Diff line change
@@ -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!
Expand Down