Integration
Integrating with Flagship Code™ can be broken down into: configuring assets, configuring environments and linking / generating plugins. While this may seem tedious, it will empower you with idempotent native code generation to effortlessly collaborate on building your application. Your team will no longer have to worry about maintaining native code directories for iOS and Android - just focus on React Native.
To get started, you will want to add the Flagship Code™ core and cli packages.
Configuring Assets
First, add the ios
and android
directories to your .gitignore
. Create a .coderc
directory in the root of your project. This is the Flagship Code™ runtime configuration directory where all configurations will be hosted i.e., CaC. Inside .coderc
, create an env
directory - where all your typed environments will exist. These are the only two mandatory directories.
Additional directories are recommended for separation of concerns. Inside .coderc
create a signing
directory - this will be where your native codesigning exists. For iOS, this would be certs and profiles. For Android, it will be your keystores.
It will be beneficial to break down your iOS codesigning assets into their respective environments e.g., enterprise, adhoc, store, etc. This way only a minimal set of profiles and certs are configured during adding of codesigning keys for CI/CD. Inside .coderc
create an assets
directory - this will be useful to host all plugin assets. To follow along, check out this example.
Your .coderc
directory should now look like:
.coderc
├── assets
├── env
└── signing
Assets will generally be files related to plugins e.g., _.png for app icons, _.png for splash screen, and google-services.json for Firebase, etc. Typically, organizing assets by functionality is the most readable structure. To follow along, check out this example.
./assets
├── app-icon
│ ├── android-adaptive-background.png
│ ├── android-adaptive-foreground.png
│ ├── android-legacy.png
│ └── ios-universal.png
├── extensions
│ └── notifications
│ ├── notifications-Info.plist
│ ├── notifications.h
│ └── notifications.m
├── fonts
│ ├── Font-Bold.ttf
│ ├── Font-ExtraBold.ttf
│ ├── Font-ExtraLight.ttf
│ ├── Font-Light.ttf
│ ├── Font-Medium.ttf
│ ├── Font-Regular.ttf
│ └── Font-SemiBold.ttf
└── splash-screen
├── android
│ └── generated
│ └── logo.png
└── ios
└── generated
└── logo.png
Env will be contain environment files e.g., development, production, adhoc, etc. These are TypeScript files based on the @brandingbrand/code-core
exported type Config<T>
. To follow along, check out this example.
./env
├── env.dev.ts
└── env.prod.ts
The signing folder will host all of your files related to codesigning. Android codesigning relates to your *.keystore. iOS codesigning relates to your certificates, entitlements and provisioning profiles. To follow along, check out this example.
./signing
├── AppleWWDRCA.cer
├── release.keystore
├── enterprise
│ ├── enterprise.cer
│ ├── enterprise.entitlements
│ ├── enterprise.mobileprovision
│ ├── enterprise.p12
│ └── enterprise_notification.mobileprovision
└── store
├── store.cer
├── store.entitlements
├── store.mobileprovision
├── store.p12
└── store_notification.mobileprovision
Configuring Environments
Base
Environment configuration files are the foundation of Flagship Code™ - this is the Configuration as Code (CaC) content.
In the .coderc/env
directory create an environment file e.g., env.prod.ts. Next, Import Config
from @brandingbrand/code-core
in your environment configuration file. This will allow you to generate a basic build configuration for iOS and Android. Plugin types can be imported and extended upon the base config e.g. Config & CodePluginAsset
. With this simplistic approach you get a build configuration with plugins.
import {Config} from '@brandingbrand/code-core';
import {CodePluginAsset} from '@brandingbrand/code-plugin-asset';
const prod: Config & CodePluginAsset = {
ios: {
name: 'code',
bundleId: 'com.code',
displayName: 'Flagship Code™',
.
.
.
},
android: {
name: 'code',
displayName: 'Flagship Code™',
packageName: 'com.code',
.
.
.
},
app: {},
codePluginAsset: {
code: {
assetPath: ['assets/fonts'],
},
},
};
export default prod;
Advanced
The advanced approach will allow you take advantage of build and runtime configuration from a single environment configuration file. We will utiilize TypeScript’s triple-dash directive to solve for a single source of truth.
Move the environment type to a common location inside the src/types
directory. Inside the src/types
directory create an app.d.ts
file. This file is a type declaration file which we will use to define our overall app type.
In this file you will define the build and runtime configuration, where the interface App
is your runtime configuration. This will provide us with type-hints when utilizing runtime configurations.
app.d.ts
declare module '@brandingbrand/code-app' {
type ENV = import('@brandingbrand/code-core').Config<App> &
import('@brandingbrand/code-plugin-asset').CodePluginAsset &
import('@brandingbrand/code-plugin-app-icon').CodePluginAppIcon &
import('@brandingbrand/code-plugin-fastlane').CodePluginFastlane &
import('@brandingbrand/code-plugin-permissions').CodePluginPermissions &
import('@brandingbrand/code-plugin-splash-screen').CodePluginSplashScreen &
import('@brandingbrand/code-plugin-target-extension').CodePluginTargetExtension;
interface App {
foo: string;
bar: number;
}
}
If you are utilizing fsapp
we will overwrite the any
env
type with our defined type via fsapp.d.ts
file - To follow along, check out this example.
fsapp.d.ts
import '@brandingbrand/fsapp';
declare module '@brandingbrand/fsapp' {
let env: import('@brandingbrand/code-core').Config<
import('@brandingbrand/code-app').App
>;
}
In your env file, .coderc/env/env.*.ts
you can now utilize the triple-dash directive to utilize this type outside the src/
directory. To follow along, check out this example.
env.prod.ts
/// <reference path="../../src/types/app.d.ts" />
import {ENV} from '@brandingbrand/code-app';
const prod: ENV = {
ios: {
name: 'code',
bundleId: 'com.code',
displayName: 'Flagship Code™',
.
.
.
},
android: {
name: 'code',
displayName: 'Flagship Code™',
packageName: 'com.code',
.
.
.
},
app: {
foo: 'bar',
bar: 123,
},
codePluginAsset: {
code: {
assetPath: ['assets/fonts'],
},
},
};
export default prod;
If you are using fsapp
to access the env
object - the environment configuration is now typesafe at runtime.
Plugins
Plugins are platform specific scripts that generate or manipulate native code with respect to a specific third-party depedency. Plugins are npm packages that are added as dev dependencies (only required at time of code init
). They may or may not export an interface that will need to be added to your environment as shown in the previous section. Plugins are run by the plugins
executor by auto-discovery.
Linking
Plugins can be remote or local for ultimate flexibility. Link your plugins by adding them to the package.json
code.plugins
array in a priority order. Plugins are run asynchronously from 0th-index to Nth-index (async / await).
package.json
"code": {
"plugins": [
"@brandingbrand/code-plugin-asset",
"@brandingbrand/code-plugin-permissions",
"@brandingbrand/code-plugin-native-navigation",
"@brandingbrand/code-plugin-app-icon",
.
.
.
]
}
Generating
Generating a plugin assumes you have familiarity with creating TypeScript / JavaScript packages. A plugin is a TypeScript package and must abide by the interface of having ios
and android
named exports that accept the Config
type; exported from @brandingbrand/code-core
. As noted by the interface below, these are async functions - all plugins are run via async / await.
index.d.ts
declare const ios: (config: Config) => Promise<void>;
declare const android: (config: Config) => Promise<void>;
The config interface can also be an intersection type of the Config
and the interface of the plugin i.e. CodePluginAppIcon
.
index.d.ts
declare const ios: (config: Config & CodePluginAppIcon) => Promise<void>;
declare const android: (config: Config & CodePluginAppIcon) => Promise<void>;
There are a number of ways to compile / bundle your TypeScript to JavaScript. In this monorepo, microbundle is utilized to compile and bundle TypeScript into JavaScript.
A very simple plugin that can be used as inspiration to generate your own plugin is the local plugin example - the only difference being that if you are distrbuting this package to npm you would not persist the dist
directory. If you are making a local plugin, you should persist the dist
directory. It’s strongly recommended that each plugin have it’s own set of unit tests than can be run via jest.