Skip to content
Open
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
33 changes: 21 additions & 12 deletions cli/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Debug from 'debug';
import { pathExists, readFile, readJSON, writeFile, writeJSON } from 'fs-extra';
import { existsSync, pathExists, readFile, readJSON, writeFile, writeJSON } from 'fs-extra';
import { dirname, extname, join, relative, resolve } from 'path';

import c from './colors';
Expand All @@ -22,7 +22,7 @@ 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';
import { getCommandOutput, isInstalled } from './util/subprocess';

const debug = Debug('capacitor:config');

Expand Down Expand Up @@ -273,7 +273,8 @@ async function loadIOSConfig(rootDir: string, extConfig: ExternalConfig): Promis
const nativeXcodeProjDir = `${nativeProjectDir}/App.xcodeproj`;
const nativeXcodeProjDirAbs = resolve(platformDirAbs, nativeXcodeProjDir);
const nativeXcodeWorkspaceDirAbs = lazy(() => determineXcodeWorkspaceDirAbs(nativeProjectDirAbs));
const podPath = lazy(() => determineGemfileOrCocoapodPath(rootDir, platformDirAbs, nativeProjectDirAbs));
const podPath = lazy(() => determineCocoapodPath());
const packageManager = lazy(() => determinePackageManager(rootDir, platformDirAbs, nativeProjectDirAbs));
const webDirAbs = lazy(() => determineIOSWebDirAbs(nativeProjectDirAbs, nativeTargetDirAbs, nativeXcodeProjDirAbs));
const cordovaPluginsDir = 'capacitor-cordova-ios-plugins';
const buildOptions = {
Expand Down Expand Up @@ -301,6 +302,7 @@ async function loadIOSConfig(rootDir: string, extConfig: ExternalConfig): Promis
webDir: lazy(async () => relative(platformDirAbs, await webDirAbs)),
webDirAbs,
podPath,
packageManager,
buildOptions,
};
}
Expand Down Expand Up @@ -415,13 +417,20 @@ async function determineAndroidStudioPath(os: OS): Promise<string> {
return '';
}

async function determineGemfileOrCocoapodPath(
async function determineCocoapodPath(): Promise<string> {
if (process.env.CAPACITOR_COCOAPODS_PATH) {
return process.env.CAPACITOR_COCOAPODS_PATH;
}
return 'pod';
}

async function determinePackageManager(
rootDir: string,
platformDir: any,
nativeProjectDirAbs: string,
): Promise<string> {
if (process.env.CAPACITOR_COCOAPODS_PATH) {
return process.env.CAPACITOR_COCOAPODS_PATH;
): Promise<'Cocoapods' | 'bundler' | 'SPM'> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This return type is used several times, maybe it makes sense to create a determined type for this.

if (existsSync(resolve(nativeProjectDirAbs, 'CapApp-SPM'))) {
return 'SPM';
}

let gemfilePath = '';
Expand Down Expand Up @@ -450,17 +459,17 @@ async function determineGemfileOrCocoapodPath(
try {
const gemfileText = (await readFile(gemfilePath)).toString();
if (!gemfileText) {
return 'pod';
return 'Cocoapods';
}
const cocoapodsInGemfile = new RegExp(/gem\s+['"]cocoapods/).test(gemfileText);

if (cocoapodsInGemfile) {
return 'bundle exec pod';
if (cocoapodsInGemfile && (await isInstalled('bundle'))) {
return 'bundler';
} else {
return 'pod';
return 'Cocoapods';
}
} catch {
return 'pod';
return 'Cocoapods';
}
}

Expand Down
2 changes: 2 additions & 0 deletions cli/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { CapacitorConfig, PluginsConfig } from './declarations';
type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> };

export type ExternalConfig = DeepReadonly<CapacitorConfig>;
export type Writable<T> = T extends object ? { -readonly [K in keyof T]: Writable<T[K]> } : T;

export const enum OS {
Unknown = 'unknown',
Expand Down Expand Up @@ -116,6 +117,7 @@ export interface IOSConfig extends PlatformConfig {
readonly cordovaPluginsDirAbs: string;
readonly minVersion: string;
readonly podPath: Promise<string>;
readonly packageManager: Promise<'Cocoapods' | 'bundler' | 'SPM'>;
readonly scheme: string;
readonly webDir: Promise<string>;
readonly webDirAbs: Promise<string>;
Expand Down
15 changes: 12 additions & 3 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@ import { resolve } from 'path';

import c from './colors';
import { loadConfig } from './config';
import type { Config } from './definitions';
import type { Config, Writable } from './definitions';
import { fatal, isFatal } from './errors';
import { receive } from './ipc';
import { logger, output } from './log';
import { telemetryAction } from './telemetry';
import { wrapAction } from './util/cli';
import { emoji as _e } from './util/emoji';

type Writable<T> = T extends object ? { -readonly [K in keyof T]: Writable<T[K]> } : T;

process.on('unhandledRejection', (error) => {
console.error(c.failure('[fatal]'), error);
});
Expand All @@ -29,6 +27,16 @@ export async function run(): Promise<void> {
}
}

async function getPackageManager(config: Config, packageManager: any): Promise<string> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return type could be narrowed down instead of Promise.

if (packageManager === 'cocoapods') {
if ((await config.ios.packageManager) === 'bundler') {
return 'bundler';
}
return 'Cocoapods';
}
return 'SPM';
}

export function runProgram(config: Config): void {
program.version(config.cli.package.version);

Expand Down Expand Up @@ -313,6 +321,7 @@ export function runProgram(config: Config): void {
const { addCommand } = await import('./tasks/add');

const configWritable: Writable<Config> = config as Writable<Config>;
configWritable.ios.packageManager = getPackageManager(config, packagemanager?.toLowerCase());
if (packagemanager?.toLowerCase() === 'CocoaPods'.toLowerCase()) {
configWritable.cli.assets.ios.platformTemplateArchive = 'ios-pods-template.tar.gz';
configWritable.cli.assets.ios.platformTemplateArchiveAbs = resolve(
Expand Down
5 changes: 1 addition & 4 deletions cli/src/ios/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@ import { runTask } from '../common';
import { XcodeExportMethod, type Config } from '../definitions';
import { logSuccess } from '../log';
import { type BuildCommandOptions } from '../tasks/build';
import { checkPackageManager } from '../util/spm';
import { runCommand } from '../util/subprocess';

export async function buildiOS(config: Config, buildOptions: BuildCommandOptions): Promise<void> {
const theScheme = buildOptions.scheme ?? 'App';

const packageManager = await checkPackageManager(config);

let typeOfBuild: string;
let projectName: string;

if (packageManager == 'Cocoapods') {
if ((await config.ios.packageManager) !== 'SPM') {
typeOfBuild = '-workspace';
projectName = basename(await config.ios.nativeXcodeWorkspaceDirAbs);
} else {
Expand Down
11 changes: 11 additions & 0 deletions cli/src/ios/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { join, resolve } from 'path';

import c from '../colors';
import { checkCapacitorPlatform } from '../common';
import type { CheckFunction } from '../common';
import { getIncompatibleCordovaPlugins } from '../cordova';
import { OS } from '../definitions';
import type { Config } from '../definitions';
Expand All @@ -25,6 +26,16 @@ function execBundler() {
}
}

export async function getCommonChecks(config: Config): Promise<CheckFunction[]> {
const checks: CheckFunction[] = [];
if ((await config.ios.packageManager) === 'bundler') {
checks.push(() => checkBundler(config));
} else if ((await config.ios.packageManager) === 'Cocoapods') {
checks.push(() => checkCocoaPods(config));
}
return checks;
}

export async function checkBundler(config: Config): Promise<string | null> {
if (config.cli.os === OS.Mac) {
let bundlerResult = execBundler();
Expand Down
4 changes: 2 additions & 2 deletions cli/src/ios/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fatal } from '../errors';
import { logSuccess } from '../log';
import { isInstalled } from '../util/subprocess';

import { checkBundler, checkCocoaPods } from './common';
import { getCommonChecks } from './common';

export async function doctorIOS(config: Config): Promise<void> {
// DOCTOR ideas for iOS:
Expand All @@ -19,7 +19,7 @@ export async function doctorIOS(config: Config): Promise<void> {
// check online datebase of common errors
// check if www folder is empty (index.html does not exist)
try {
await check([() => checkBundler(config) || checkCocoaPods(config), () => checkWebDir(config), checkXcode]);
await check([() => checkWebDir(config), checkXcode, ...(await getCommonChecks(config))]);
logSuccess('iOS looking great! 👌');
} catch (e: any) {
fatal(e.stack ?? e);
Expand Down
3 changes: 1 addition & 2 deletions cli/src/ios/open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import open from 'open';

import { wait } from '../common';
import type { Config } from '../definitions';
import { checkPackageManager } from '../util/spm';

export async function openIOS(config: Config): Promise<void> {
if ((await checkPackageManager(config)) == 'SPM') {
if ((await config.ios.packageManager) == 'SPM') {
await open(config.ios.nativeXcodeProjDirAbs, { wait: false });
} else {
await open(await config.ios.nativeXcodeWorkspaceDirAbs, { wait: false });
Expand Down
5 changes: 2 additions & 3 deletions cli/src/ios/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { promptForPlatformTarget, runTask } from '../common';
import type { Config } from '../definitions';
import type { RunCommandOptions } from '../tasks/run';
import { runNativeRun, getPlatformTargets } from '../util/native-run';
import { checkPackageManager } from '../util/spm';
import { runCommand } from '../util/subprocess';

const debug = Debug('capacitor:ios:run');
Expand All @@ -33,12 +32,12 @@ export async function runIOS(

const derivedDataPath = resolve(config.ios.platformDirAbs, 'DerivedData', target.id);

const packageManager = await checkPackageManager(config);
const packageManager = await config.ios.packageManager;

let typeOfBuild: string;
let projectName: string;

if (packageManager == 'Cocoapods') {
if (packageManager !== 'SPM') {
typeOfBuild = '-workspace';
projectName = basename(await config.ios.nativeXcodeWorkspaceDirAbs);
} else {
Expand Down
25 changes: 10 additions & 15 deletions cli/src/ios/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { copy as copyTask } from '../tasks/copy';
import { convertToUnixPath } from '../util/fs';
import { generateIOSPackageJSON } from '../util/iosplugin';
import { resolveNode } from '../util/node';
import { checkPackageManager, generatePackageFile, checkPluginsForPackageSwift } from '../util/spm';
import { generatePackageFile, checkPluginsForPackageSwift } from '../util/spm';
import { runCommand, isInstalled } from '../util/subprocess';
import { extractTemplate } from '../util/template';

Expand Down Expand Up @@ -51,7 +51,7 @@ async function updatePluginFiles(config: Config, plugins: Plugin[], deployment:
}
await handleCordovaPluginsJS(cordovaPlugins, config, platform);
await checkPluginDependencies(plugins, platform, config.app.extConfig.cordova?.failOnUninstalledPlugins);
if ((await checkPackageManager(config)) === 'SPM') {
if ((await config.ios.packageManager) === 'SPM') {
await generateCordovaPackageFiles(cordovaPlugins, config);

const validSPMPackages = await checkPluginsForPackageSwift(config, plugins);
Expand Down Expand Up @@ -131,20 +131,15 @@ async function updatePodfile(config: Config, plugins: Plugin[], deployment: bool
await writeFile(podfilePath, podfileContent, { encoding: 'utf-8' });

const podPath = await config.ios.podPath;
const useBundler = podPath.startsWith('bundle') && (await isInstalled('bundle'));
const podCommandExists = await isInstalled('pod');
if (useBundler || podCommandExists) {
if (useBundler) {
await runCommand('bundle', ['exec', 'pod', 'install', ...(deployment ? ['--deployment'] : [])], {
cwd: config.ios.nativeProjectDirAbs,
});
} else {
await runCommand(podPath, ['install', ...(deployment ? ['--deployment'] : [])], {
cwd: config.ios.nativeProjectDirAbs,
});
}
const useBundler = (await config.ios.packageManager) === 'bundler';
if (useBundler) {
await runCommand('bundle', ['exec', 'pod', 'install', ...(deployment ? ['--deployment'] : [])], {
cwd: config.ios.nativeProjectDirAbs,
});
} else {
logger.warn('Skipping pod install because CocoaPods is not installed');
await runCommand(podPath, ['install', ...(deployment ? ['--deployment'] : [])], {
cwd: config.ios.nativeProjectDirAbs,
});
}

const isXcodebuildAvailable = await isInstalled('xcodebuild');
Expand Down
8 changes: 4 additions & 4 deletions cli/src/tasks/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import type { CheckFunction } from '../common';
import type { Config } from '../definitions';
import { fatal, isFatal } from '../errors';
import { addIOS } from '../ios/add';
import { editProjectSettingsIOS, checkBundler, checkCocoaPods, checkIOSPackage } from '../ios/common';
import { editProjectSettingsIOS, checkIOSPackage, getCommonChecks } from '../ios/common';
import { logger, logSuccess, output } from '../log';

import { sync } from './sync';
Expand Down Expand Up @@ -75,7 +75,7 @@ export async function addCommand(config: Config, selectedPlatformName: string):
}

try {
await check([() => checkPackage(), () => checkAppConfig(config), ...addChecks(config, platformName)]);
await check([() => checkPackage(), () => checkAppConfig(config), ...(await getAddChecks(config, platformName))]);
await doAdd(config, platformName);
await editPlatforms(config, platformName);

Expand Down Expand Up @@ -110,9 +110,9 @@ function printNextSteps(platformName: string) {
);
}

function addChecks(config: Config, platformName: string): CheckFunction[] {
async function getAddChecks(config: Config, platformName: string): Promise<CheckFunction[]> {
if (platformName === config.ios.name) {
return [() => checkIOSPackage(config), () => checkBundler(config) || checkCocoaPods(config)];
return [() => checkIOSPackage(config), ...(await getCommonChecks(config))];
} else if (platformName === config.android.name) {
return [() => checkAndroidPackage(config)];
} else if (platformName === config.web.name) {
Expand Down
14 changes: 8 additions & 6 deletions cli/src/tasks/migrate-spm.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Config } from '../definitions';
import { check } from '../common';
import type { Config, Writable } from '../definitions';
import { fatal } from '../errors';
import { getCommonChecks } from '../ios/common';
import { logger } from '../log';
import {
checkPackageManager,
extractSPMPackageDirectory,
removeCocoapodsFiles,
runCocoapodsDeintegrate,
Expand All @@ -12,16 +13,17 @@ import {
import { update } from './update';

export async function migrateToSPM(config: Config): Promise<void> {
if ((await checkPackageManager(config)) == 'SPM') {
if ((await config.ios.packageManager) == 'SPM') {
fatal('Capacitor project is already using SPM, exiting.');
}

await check(await getCommonChecks(config));
await extractSPMPackageDirectory(config);
await runCocoapodsDeintegrate(config);
await removeCocoapodsFiles(config);
await addInfoPlistDebugIfNeeded(config);
await update(config, 'ios', true);

const configWritable: Writable<Config> = config as Writable<Config>;
configWritable.ios.packageManager = Promise.resolve('SPM');
await update(configWritable as Config, 'ios', false);
logger.info(
'To complete migration follow the manual steps at https://capacitorjs.com/docs/ios/spm#using-our-migration-tool',
);
Expand Down
3 changes: 1 addition & 2 deletions cli/src/tasks/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { fatal } from '../errors';
import { getMajoriOSVersion } from '../ios/common';
import { logger, logPrompt, logSuccess } from '../log';
import { deleteFolderRecursive } from '../util/fs';
import { checkPackageManager } from '../util/spm';
import { runCommand } from '../util/subprocess';
import { extractTemplate } from '../util/template';

Expand Down Expand Up @@ -161,7 +160,7 @@ export async function migrateCommand(config: Config, noprompt: boolean, packagem
);
});

if ((await checkPackageManager(config)) === 'Cocoapods') {
if ((await config.ios.packageManager) !== 'SPM') {
// Update Podfile
await runTask(`Migrating Podfile to ${iOSVersion}.0.`, () => {
return updateFile(
Expand Down
4 changes: 2 additions & 2 deletions cli/src/tasks/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { logger } from '../log';
import { allSerial } from '../util/promise';

import { copy, copyCommand } from './copy';
import { update, updateChecks, updateCommand } from './update';
import { addUpdateChecks, update, updateCommand } from './update';

/**
* Sync is a copy and an update in one.
Expand All @@ -27,7 +27,7 @@ export async function syncCommand(
const then = +new Date();
const platforms = await selectPlatforms(config, selectedPlatformName);
try {
await check([() => checkPackage(), () => checkWebDir(config), ...updateChecks(config, platforms)]);
await check([() => checkPackage(), () => checkWebDir(config), ...(await addUpdateChecks(config, platforms))]);
await allSerial(platforms.map((platformName) => () => sync(config, platformName, deployment, inline)));
const now = +new Date();
const diff = (now - then) / 1000;
Expand Down
Loading