This project has two utilities for automatically configuring native Capacitor projects in a predictable and safe way.
The first is the Project API which provides a fully-typed JavaScript API for writing custom project configuration scripts and modifying underlying iOS and Android configuration, build, and project files.
The second is a tool for configuration-based modifications which is useful for plugins and other scripts that need to apply certain settings to a file in a way that fits the configuration approach.
To write custom scripts and code that manage iOS and Android targets in your Capacitor project, install the @capacitor/project
package:
npm install @capacitor/project
Make sure the dom
lib is enabled in your tsconfig.json
in the lib
entry under compilerOptions
.
For iOS: the tool currently expects your iOS project to be in an App
folder inside of the Capacitor platform project folder. For example: ios/App
.
For Android: JAVA_HOME
must be set to use Gradle configuration. This is because the Gradle modification functionality uses a Java utility under the hood for accuracy, as Gradle is a Groovy DSL and Groovy is a JVM language. If you have Android Studio installed, you can use the JDK bundled with it.
To initialize the project, set the config and initialize a new CapacitorProject
instance:
import { CapacitorProject } from '@capacitor/project';
import { CapacitorConfig } from '@capacitor/cli';
// This takes a CapacitorConfig, such as the one in capacitor.config.ts, but only needs a few properties
// to know where the ios and android projects are
const config: CapacitorConfig = {
ios: {
path: 'ios',
},
android: {
path: 'android',
},
};
const project = new CapacitorProject(config);
await project.load();
The API works by updating files in a virtual filesystem, and no changes are actually committed to the filesystem until project.commit()
is called. When your changes are ready to be saved, run
project.commit();
To get a preview of changes that will be committed, the VFS
object can be accessed on the project:
const changedFiles = project.vfs.all();
changedFiles.forEach(f => {
console.log(f.getFilename(), f.getData());
});
Once the project is loaded, iOS and Android operations can be performed on the project, as shown below:
iOS Supports multiple targets and build names (i.e. Debug
or Release
).
For apps that use multiple targets, such as an App Clip or Watch app, operations on the project can be isolated to specific targets and also build names (Debug
or Release
). However, most methods allow you to pass null
as the targetName
which will then default to using the main App target in the app, which is useful for apps that only have one main App target.
Additionally, buildName
can be set to null
in all methods or, if it's the last argument, left out entirely, however the behavior here is less well defined. Sometimes this uses the first build (such as Debug
or Release
), and sometimes it uses both. Documentation on which scenarios occur when is forthcoming.
To get the Targets in the app, use:
// Get all targets in the project
project.ios?.getTargets();
// Targets have properties like id, name, productName, productType, and a list of buildConfigurations
// Get the main app target in the project
const appTarget = project.ios?.getAppTarget();
// Get the bundle id for the given target, pass the build name as an optional second parameter
project.ios?.getBundleId(appTarget.name);
project.ios?.setBundleId(targetName, buildName, 'io.ionic.betterBundleId');
project.ios?.setBundleId(targetName, null, 'io.ionic.betterBundleId');
The version and build number can be managed, including incrementing the build number which is useful for automated builds:
// Get the numeric build number (aka CURRENT_PROJECT_VERSION) for the given target and build name
project.ios?.getBuild(targetName, buildName);
// Get the version name (aka the MARKETING_VERSION)
project.ios?.getVersion(targetName, buildName);
// Set the numeric build number
await project.ios?.setBuild(targetName, buildName, 42);
// Increment the build number
await project.ios?.incrementBuild(targetName, buildName);
// Set the marketing version
await project.ios?.setVersion(targetName, buildName, '1.2.3');
The display name can be managed:
project.ios?.getDisplayName(targetName, buildName);
project.ios?.setDisplayName(targetName, buildName, 'Really Awesome App');
Modifications to the Info.plist
for the given target and build can be made by passing in an object corresponding to entries in the plist.
Note: this method will use the registered INFOPLIST_FILE
for the given target and build so make sure that is set correctly if you've renamed the Info.plist
file.
await project.ios?.updateInfoPlist(targetName, buildName, {
NSFaceIDUsageDescription: 'The better to see you with',
});
// Get the relative path to the Info.plist for the target and build
await project.ios?.getInfoPlist(targetName, buildName);
// Get the full path to the Info.plist
await project.ios?.getInfoPlistFilename(targetName, buildName);
Frameworks, Libraries, and Embedded Content can be managed:
project.ios?.addFramework(targetName, 'ImageIO.framework');
// Complex framework setups can pass options. Boolean fields supported:
// embed, link, customFramework
project.ios?.addFramework(targetName, 'Custom.framework', {
embed: true,
});
project.ios?.getFrameworks(targetName);
Entitlements can be managed:
// The key for the key/value entries should be the low-level entitlement key,
// which can be found on the Apple docs: https://developer.apple.com/documentation/bundleresources/entitlements?language=objc
await project.ios?.addEntitlements(targetName, buildName, {
'keychain-access-groups': ['group1', 'group2'],
});
await project.ios?.getEntitlements(targetName);
Build settings for targets and specific builds can be managed:
// Configurations will be an array of object with fields name and buildSettings which is an object
// containing all build varibles for the build in the target, such as compiler options like
// ENABLE_BITCODE
project.ios?.getBuildConfigurations(target);
// Individual build properties can be read or written:
project.ios?.setBuildProperty(targetName, buildName, 'FAKE_PROPERTY', 'YES');
project.ios?.getBuildProperty(targetName, buildName, 'FAKE_PROPERTY');
Android functionality currently supported includes making modifications to AndroidManifest.xml
(attributes and new elements), updating package name, updating version name/code, adding resources files, and making Gradle modifications.
project.android?.setPackageName('com.ionicframework.awesome');
project.android?.getPackageName();
await project.ios?.setVersion('App', 'Debug', '1.4.5');
await project.ios?.incrementBuild('App');
project.ios?.getBuild('App', 'Debug');
project.ios?.getBuild('App', 'Release');
await project.android?.setVersionName('1.0.2');
await project.android?.getVersionName();
await project.android?.setVersionCode(11);
await project.android?.getVersionCode();
await project.android?.incrementVersionCode();
Attributes can be added/modified on target elements, and new XML fragments can be injected. Note: if an XPath selector returns multiple elements, the operation will be applied to each.
// Set attributes on a target element:
project.android?.getAndroidManifest().setAttrs('manifest/application', {
'android:name': 'com.ionicframework.test.CoolApplication',
});
// Inject fragment at target:
project.android?.getAndroidManifest().injectFragment(
'manifest',
`
<queries>
<package />
<intent>
</intent>
</queries>
`,
);
There is also a method for querying the manifest using an XPath selector:
project.android?.getAndroidManifest().find('manifest/application');
Resource files can be created or existing files copied to:
// Add a resource to the given resource directory with the given name and contents
await project.android?.addResource('raw', 'test.json', `{}`);
// Existing files can also be copied, passing the source of the file as the last argument
await project.android?.copyToResources('drawable', 'icon.png', source);
// To load an existing resource:
const data = await project.android?.getResource('raw', 'test.json');
Gradle modifications are the most complicated and powerful of the capabilities in this library. Remember, JAVA_HOME
must be set before using these methods.
Note: this feature only supports Groovy-based Gradle files.
First, get a reference to the GradleFile
from the project. There are two possible options currently supported when referenced from the project: build.gradle
or app/build.gradle
. To modify other Gradle files use GradleFile directly.
const buildGradleFile = project.android?.getGradleFile('build.gradle');
const appBuildGradleFile = project.android?.getGradleFile('app/build.gradle');
Gradle fragment strings can be injected at specific locations in the Gradle file, or new properties can be added using an object syntax.
To add properties:
// The first argument is the target element. This uses a nested syntax where the terminal method/property name should have an empty object. The second element is an array of new gradle properties to insert:
buildGradleFile.insertProperties(
{
buildscript: {},
},
[{ classpath: 'com.my.custom.gradle.plugin' }],
);
To add raw Gradle strings:
appBuildGradleFile.insertFragment({
allprojects: {
repositories: {}
}
}, [{
maven: [
{ url: 'https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1' },
{ name: 'Duo-SDK-Feed }
]
}]
To configure projects using configuration and the configuration tool, install the @capacitor/configure
package. This package uses the @capacitor/project
API under the hood:
npm install @capacitor/configure
Add to your npm scripts:
"scripts": {
"cap-config": "cap-config"
}
npm run cap-config run config.yaml
Configuration files are written in YAML. New to YAML? Read Learn YAML in five minutes.
See an Example Yaml Configuration for a real-world example using many of the supported features.
Platform | Operation | Supported |
---|---|---|
ios | Bundle ID and Product Name | ✅ |
ios | Version and Build Number | ✅ |
ios | Increment Build Number | ✅ |
ios | Build Settings | ✅ |
ios | Plist Modifications | ✅ |
ios | Add Frameworks | ✅ |
ios | Set Entitlements | ✅ |
ios | Add Source/Header files | WIP |
android | Package Name | ✅ |
android | Version Name and Code | ✅ |
android | Version Code | ✅ |
android | Increment Version Code | ✅ |
android | Gradle Config | ✅ |
android | Resource Files | ✅ |
android | Manifest File Modification | ✅ |
android | Add Source/Header files | WIP |
Thank you to Cordova for the lower-level cordova-node-xcode project used to parse and manage the pbxproj
file in Xcode projects.