expo-modules-autolinking
Version:
Scripts that autolink Expo modules.
179 lines (156 loc) • 5.81 kB
JavaScript
/**
* Replace XCFramework for Debug/Release Configuration
*
* This script extracts the correct flavor tarball to switch between debug and release
* xcframeworks. It's invoked from a CocoaPods script_phase before each compile
* to ensure the correct XCFramework variant is linked.
*
* Directory structure:
* <xcframeworks_dir>/
* artifacts/
* <Product>-debug.tar.gz (tarball, source of truth)
* <Product>-release.tar.gz (tarball, source of truth)
* .last_build_configuration
* <Product>.xcframework/ (real dir, extracted from tarball)
* <Dependency>.xcframework/ (real dir, if any, extracted from same tarball)
*
* Usage:
* node replace-xcframework.js -c <CONFIG> -m <MODULE_NAME> -x <XCFRAMEWORKS_DIR>
*
* Arguments:
* -c, --config Build configuration: "debug" or "release"
* -m, --module Module/product name (used for tarball lookup and logging)
* -x, --xcframeworks Path to the pod directory (Pods/<PodName>/)
*
* The script:
* 1. Finds the tarball: <xcframeworksDir>/artifacts/<module>-<config>.tar.gz
* 2. Checks artifacts/.last_build_configuration — skips if unchanged
* 3. Removes all *.xcframework directories in xcframeworksDir
* 4. Extracts the tarball: tar -xzf ... -C <xcframeworksDir>
* 5. Writes the new config to artifacts/.last_build_configuration
*
* Based on React Native's replace-rncore-version.js pattern.
*/
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const LOG_PREFIX = '[Expo XCFramework]';
// Parse command line arguments
function parseArgs() {
const args = process.argv.slice(2);
const result = {
config: null,
module: null,
xcframeworksDir: null,
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '-c':
case '--config':
result.config = args[++i];
break;
case '-m':
case '--module':
result.module = args[++i];
break;
case '-x':
case '--xcframeworks':
result.xcframeworksDir = args[++i];
break;
}
}
return result;
}
function main() {
const args = parseArgs();
// Validate arguments
if (!args.config || !args.module || !args.xcframeworksDir) {
console.error(
'Usage: replace-xcframework.js -c <CONFIG> -m <MODULE_NAME> -x <XCFRAMEWORKS_DIR>'
);
console.error(' -c, --config Build configuration: "debug" or "release"');
console.error(' -m, --module Module/product name');
console.error(' -x, --xcframeworks Path to the xcframeworks directory');
process.exit(1);
}
// Normalize config to lowercase
const configLower = args.config.toLowerCase();
if (configLower !== 'debug' && configLower !== 'release') {
console.error(
`${LOG_PREFIX} Invalid configuration: ${args.config}. Must be "debug" or "release".`
);
process.exit(1);
}
const xcframeworksDir = args.xcframeworksDir;
const moduleName = args.module;
// Validate xcframeworksDir exists
if (!fs.existsSync(xcframeworksDir) || !fs.statSync(xcframeworksDir).isDirectory()) {
console.error(`${LOG_PREFIX} ${moduleName}: Directory not found: ${xcframeworksDir}`);
process.exit(1);
}
// Ensure artifacts directory exists
const artifactsDir = path.join(xcframeworksDir, 'artifacts');
fs.mkdirSync(artifactsDir, { recursive: true });
// Find the tarball for the requested configuration (stored in artifacts/)
const tarballPath = path.join(artifactsDir, `${moduleName}-${configLower}.tar.gz`);
const lastConfigFile = path.join(artifactsDir, '.last_build_configuration');
// Check if tarball exists
if (!fs.existsSync(tarballPath)) {
console.error(
`${LOG_PREFIX} ${moduleName}: Tarball not found at ${tarballPath}, skipping.`
);
return;
}
// Read last build configuration
let lastConfig = null;
if (fs.existsSync(lastConfigFile)) {
try {
lastConfig = fs.readFileSync(lastConfigFile, 'utf8').trim();
} catch (e) {
// Ignore read errors — will proceed with extraction
}
}
// Check if configuration has changed
if (lastConfig === configLower) {
console.log(`${LOG_PREFIX} ${moduleName}: Already extracted ${configLower}, skipping.`);
return;
}
// Remove all existing *.xcframework directories
const entries = fs.readdirSync(xcframeworksDir);
for (const entry of entries) {
if (!entry.endsWith('.xcframework')) continue;
const entryPath = path.join(xcframeworksDir, entry);
try {
const stat = fs.lstatSync(entryPath);
if (stat.isDirectory() || stat.isSymbolicLink()) {
fs.rmSync(entryPath, { recursive: true, force: true });
}
} catch (e) {
console.error(`${LOG_PREFIX} ${moduleName}: Warning: failed to remove ${entry}: ${e.message}`);
}
}
// Extract the tarball using spawnSync to avoid shell interpretation of paths
const result = spawnSync('tar', ['-xzf', tarballPath, '-C', xcframeworksDir], {
stdio: 'pipe',
});
if (result.status !== 0) {
const stderr = result.stderr ? result.stderr.toString().trim() : 'unknown error';
console.error(`${LOG_PREFIX} ${moduleName}: Failed to extract tarball: ${stderr}`);
process.exit(1);
}
// Write last build configuration
try {
fs.writeFileSync(lastConfigFile, configLower);
} catch (e) {
console.error(`${LOG_PREFIX} ${moduleName}: Warning: failed to write config file: ${e.message}`);
}
if (lastConfig && lastConfig !== configLower) {
console.log(
`${LOG_PREFIX} ${moduleName}: Switched from ${lastConfig} to ${configLower} (extracted tarball).`
);
} else {
console.log(`${LOG_PREFIX} ${moduleName}: Extracted ${configLower} tarball.`);
}
}
main();