A library for generating native app code from JavaScript config through Expo config plugins
Essential utility modules for XML parsing, code generation, warning management, history tracking, and other common operations used throughout the @expo/config-plugins ecosystem.
Utilities for parsing, manipulating, and writing XML files with proper error handling.
namespace XML {
function readXMLAsync(options: {
path: string;
fallback?: string | null;
}): Promise<XMLObject>;
function writeXMLAsync(options: {
path: string;
xml: any;
}): Promise<void>;
}
interface XMLObject {
[key: string]: XMLValue | undefined;
}
type XMLValue = boolean | number | string | null | XMLArray | XMLObject;
interface XMLArray extends Array<XMLValue> {}Usage:
// Read XML file
const manifestXml = await XML.readXMLAsync({
path: "path/to/AndroidManifest.xml"
});
// Write XML file
await XML.writeXMLAsync({
path: "path/to/output.xml",
xml: xmlObject
});Utilities for merging generated content with existing files using tagged sections.
namespace CodeGenerator {
function mergeContents(options: MergeContentsOptions): MergeResults;
function removeContents(src: string, tag: string): string;
function removeGeneratedContents(src: string, tag: string): string;
interface MergeContentsOptions {
src: string;
newSrc: string;
tag: string;
anchor?: RegExp;
offset?: number;
comment?: string;
}
interface MergeResults {
contents: string;
didClear: boolean;
didMerge: boolean;
}
}Usage:
// Merge generated code into existing file
const result = CodeGenerator.mergeContents({
src: existingFileContents,
newSrc: generatedCode,
tag: "expo-config-plugin",
anchor: /\/\* GENERATED_SECTION_START \*\//,
comment: "// Generated by @expo/config-plugins"
});
if (result.didMerge) {
await fs.writeFile(filePath, result.contents);
}Utilities for collecting and reporting warnings during plugin execution.
namespace WarningAggregator {
function addWarningIOS(property: string, message: string): void;
function addWarningAndroid(property: string, message: string): void;
function flushWarningsAsync(): Promise<void>;
}Usage:
// Add platform-specific warnings
WarningAggregator.addWarningIOS(
"bundleIdentifier",
"Bundle identifier conflict detected in Info.plist"
);
WarningAggregator.addWarningAndroid(
"permissions",
"Duplicate permission found in AndroidManifest.xml"
);
// Flush all warnings to console
await WarningAggregator.flushWarningsAsync();Utilities for tracking plugin execution history and preventing duplicate operations.
namespace History {
function getHistoryItem(config: ExpoConfig, name: string): any;
function addHistoryItem(config: ExpoConfig, item: HistoryItem): ExpoConfig;
interface HistoryItem {
name: string;
version?: string;
data?: any;
}
}Usage:
// Check if plugin has already run
const historyItem = History.getHistoryItem(config, "withCustomPlugin");
if (historyItem) {
console.log("Plugin already executed with version:", historyItem.version);
return config;
}
// Add plugin to history
config = History.addHistoryItem(config, {
name: "withCustomPlugin",
version: "1.0.0",
data: { customOption: true }
});Utilities for safe object property access and manipulation.
namespace obj {
function get(object: any, path: string, defaultValue?: any): any;
function set(object: any, path: string, value: any): any;
}Usage:
// Safe property access
const bundleId = obj.get(config, "ios.bundleIdentifier", "com.example.default");
// Set nested property
obj.set(config, "ios.infoPlist.CustomKey", "CustomValue");Type definitions for iOS and Android build properties configuration.
interface PluginConfigTypeiOS {
deploymentTarget?: string;
useFrameworks?: "static" | "dynamic";
ccache?: boolean;
flipper?: boolean;
proguardMinifyEnabled?: boolean;
shrinkResources?: boolean;
packagingOptions?: {
pickFirst?: string[];
exclude?: string[];
merge?: string[];
doNotStrip?: string[];
};
}
interface PluginConfigTypeAndroid {
compileSdkVersion?: number;
targetSdkVersion?: number;
buildToolsVersion?: string;
minSdkVersion?: number;
proguardMinifyEnabled?: boolean;
shrinkResources?: boolean;
packagingOptions?: {
pickFirst?: string[];
exclude?: string[];
merge?: string[];
doNotStrip?: string[];
};
}Enhanced file system utilities with better error handling.
namespace fs {
function fileExists(filePath: string): boolean;
function directoryExists(dirPath: string): boolean;
function ensureDir(dirPath: string): Promise<void>;
}Utilities for resolving and loading plugin modules.
namespace modules {
function resolveModule(moduleId: string, fromDir: string): string;
function requireModule(modulePath: string): any;
}Advanced plugin resolution and loading utilities.
namespace pluginResolver {
function resolveConfigPluginFunction(
projectRoot: string,
pluginReference: string | ConfigPlugin
): ConfigPlugin;
function resolveConfigPluginFunctionWithInfo(
projectRoot: string,
pluginReference: any
): {
plugin: ConfigPlugin;
pluginFile: string;
pluginReference: string;
};
}Custom error classes and error handling utilities.
class PluginError extends Error {
constructor(message: string, cause?: string);
}
function createPluginError(message: string, cause?: string): PluginError;Utilities for parsing and matching brackets in code.
function matchBrackets(input: string, startIndex: number): number | null;
function findMatchingBracket(
input: string,
openBracket: string,
closeBracket: string,
startIndex: number
): number | null;Common code modification utilities for source file manipulation.
function insertContentsAtOffset(
contents: string,
insertion: string,
offset: number
): string;
function removeContentsAtOffset(
contents: string,
removalLength: number,
offset: number
): string;Utilities for handling localization and internationalization.
function getLocales(projectRoot: string): string[];
function createLocaleDir(projectRoot: string, locale: string): Promise<void>;Enhanced file globbing utilities.
function glob(pattern: string, options?: GlobOptions): Promise<string[]>;
function globSync(pattern: string, options?: GlobOptions): string[];
interface GlobOptions {
cwd?: string;
ignore?: string[];
absolute?: boolean;
}Utilities for consistent object key sorting.
function sortObject<T extends Record<string, any>>(obj: T): T;
function sortObjectKeys(obj: any, sortKeys?: string[]): any;import {
XML,
CodeGenerator,
WarningAggregator,
History,
PluginError
} from "@expo/config-plugins";
async function processConfigFile(
projectRoot: string,
configPath: string,
newContent: string
) {
try {
// Check execution history
const historyItem = History.getHistoryItem(config, "processConfigFile");
if (historyItem) {
WarningAggregator.addWarningIOS(
"processConfigFile",
"Configuration file already processed"
);
return config;
}
// Read and parse XML
const existingXml = await XML.readXMLAsync(configPath);
// Merge new content
const mergeResult = CodeGenerator.mergeContents({
src: JSON.stringify(existingXml, null, 2),
newSrc: newContent,
tag: "auto-generated-config",
comment: "// Auto-generated configuration"
});
if (mergeResult.didMerge) {
const updatedXml = JSON.parse(mergeResult.contents);
await XML.writeXMLAsync(configPath, updatedXml);
// Add to history
config = History.addHistoryItem(config, {
name: "processConfigFile",
version: "1.0.0",
data: { configPath, contentLength: newContent.length }
});
}
// Flush any warnings
await WarningAggregator.flushWarningsAsync();
return config;
} catch (error) {
throw new PluginError(
"Failed to process configuration file",
error.message
);
}
}async function updateSourceFile(
filePath: string,
newImports: string[],
newCode: string
) {
const existingContents = await fs.readFile(filePath, "utf8");
// Add imports
let updatedContents = existingContents;
newImports.forEach(importStatement => {
if (!updatedContents.includes(importStatement)) {
updatedContents = `${importStatement}\n${updatedContents}`;
}
});
// Merge generated code
const mergeResult = CodeGenerator.mergeContents({
src: updatedContents,
newSrc: newCode,
tag: "expo-plugin-generated",
anchor: /\/\/ PLUGIN_ANCHOR_POINT/,
offset: 1,
comment: "// Generated by Expo config plugin"
});
if (mergeResult.didMerge || mergeResult.didClear) {
await fs.writeFile(filePath, mergeResult.contents);
console.log(`Updated ${filePath}`);
}
}tessl i tessl/npm-expo--config-plugins@11.0.0docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10