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}`);
}
}