Trapeze is part of the Ionic Ecosystem →
Skip to main content

Project API

To write custom scripts and code that manage iOS and Android targets in your native projects, install the @trapezedev/project package:

npm install @trapezedev/project

TypeScript Note

Make sure the dom lib is enabled in your tsconfig.json in the lib entry under compilerOptions.

Requirements

For Android: java must be on PATH or 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. Note: JAVA_HOME should be set to the root of the JDK installation, not the /bin folder.

API Usage

To initialize the project, set the config and initialize a new MobileProject instance:

import { MobileProject, MobileProjectConfig } from '@trapezedev/project';

const config: MobileProjectConfig = {
ios: {
path: 'ios',
},
android: {
path: 'android',
},
};

const project = new MobileProject('project/root', config);
await project.load();

Committing Changes

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

iOS Supports multiple targets and build names (i.e. Debug or Release).

Targets

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();

Project Settings

Bundle ID

// 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');

Version and Build Number

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');

Display Name

The display name can be managed:

project.ios?.getDisplayName(targetName, buildName);
project.ios?.setDisplayName(targetName, buildName, 'Really Awesome App');

Info.plist

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

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

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 Configurations

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');

JSON files

Use JsonFile to make modifications against project JSON files:

// Get a JSON file from the iOS project, or create a new one:
const jsonFile = ctx.project.ios?.getProjectFile<JsonFile>(
filename,
(filename: string) => new JsonFile(filename, ctx.project.vfs, ctx.project),
);

// Load it
await jsonFile.load();

// Set values
jsonFile.set({
field: 'value'
});
// Merge values
jsonFile.merge({
field: {
field2: 'value'
}
});

Android

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 Settings

Package Name

project.android?.setPackageName('com.ionicframework.awesome');
project.android?.getPackageName();

Version Name and Code

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();
await project.android?.setVersionNameSuffix('beta');
await project.android?.getVersionNameSuffix();

Android Manifest

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

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');

copyToResources supports a URL as the last source option which will fetch the remote file instead.

Gradle

Gradle modifications are the most complicated and powerful of the capabilities in this library. Remember, java must be on PATH or 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 insert a variable assignment (thing = value) set the insertType to 'variable':
buildGradleFile.insertProperties(
{
buildscript: {},
},
insertType: 'variable',
[{ firebaseMessagingVersion: '20.0.6' }]
);

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 }
]
}]

JSON files

Use JsonFile to make modifications against project JSON files:

// Get a JSON file from the iOS project, or create a new one:
const jsonFile = ctx.project.android?.getProjectFile<JsonFile>(
filename,
(filename: string) => new JsonFile(filename, ctx.project.vfs, ctx.project),
);

// Load it
await jsonFile.load();

// Set values
jsonFile.set({
field: 'value'
});
// Merge values
jsonFile.merge({
field: {
field2: 'value'
}
});