expo-modules-autolinking
Version:
Scripts that autolink Expo modules.
213 lines (199 loc) • 7.49 kB
text/typescript
import commander from 'commander';
import path from 'path';
import {
findModulesAsync,
generateModulesProviderAsync,
generatePackageListAsync,
getProjectPackageJsonPathAsync,
mergeLinkingOptionsAsync,
resolveExtraBuildDependenciesAsync,
resolveModulesAsync,
verifySearchResults,
} from './autolinking';
import { type RNConfigCommandOptions, createReactNativeConfigAsync } from './reactNativeConfig';
import type {
GenerateModulesProviderOptions,
GenerateOptions,
ResolveOptions,
SearchOptions,
SearchResults,
} from './types';
/**
* Registers a command that only searches for available expo modules.
*/
function registerSearchCommand<OptionsType extends SearchOptions>(
commandName: string,
fn: (search: SearchResults, options: OptionsType) => any
) {
return commander
.command(`${commandName} [paths...]`)
.option<string[] | null>(
'-i, --ignore-paths <ignorePaths...>',
'Paths to ignore when looking up for modules.',
(value, previous) => (previous ?? []).concat(value)
)
.option<string[] | null>(
'-e, --exclude <exclude...>',
'Package names to exclude when looking up for modules.',
(value, previous) => (previous ?? []).concat(value)
)
.option(
'-p, --platform [platform]',
'The platform that the resulting modules must support. Available options: "apple", "android"',
'apple'
)
.option('--silent', 'Silence resolution warnings')
.addOption(
new commander.Option(
'--project-root <projectRoot>',
'The path to the root of the project'
).default(process.cwd(), 'process.cwd()')
)
.option(
'--only-project-deps',
'For a monorepo, include only modules that are the project dependencies.',
true
)
.option('--no-only-project-deps', 'Opposite of --only-project-deps', false)
.action(async (searchPaths, providedOptions) => {
const options = await mergeLinkingOptionsAsync<OptionsType>(
searchPaths.length > 0
? {
...providedOptions,
searchPaths,
}
: providedOptions
);
const searchResults = await findModulesAsync(options);
return await fn(searchResults, options);
});
}
/**
* Registers a command that searches for modules and then resolves them for specific platform.
*/
function registerResolveCommand<OptionsType extends ResolveOptions>(
commandName: string,
fn: (search: SearchResults, options: OptionsType) => any
) {
return registerSearchCommand<OptionsType>(commandName, fn);
}
/**
* Registry the `react-native-config` command.
*/
function registerReactNativeConfigCommand() {
return commander
.command('react-native-config [paths...]')
.option(
'-p, --platform [platform]',
'The platform that the resulting modules must support. Available options: "android", "ios"',
'ios'
)
.addOption(
new commander.Option(
'--project-root <projectRoot>',
'The path to the root of the project'
).default(process.cwd(), 'process.cwd()')
)
.option<boolean>('-j, --json', 'Output results in the plain JSON format.', () => true, false)
.action(async (searchPaths, providedOptions) => {
if (!['android', 'ios'].includes(providedOptions.platform)) {
throw new Error(`Unsupported platform: ${providedOptions.platform}`);
}
const projectRoot = path.dirname(
await getProjectPackageJsonPathAsync(providedOptions.projectRoot)
);
const linkingOptions = await mergeLinkingOptionsAsync<SearchOptions>(
searchPaths.length > 0
? {
...providedOptions,
projectRoot,
searchPaths,
}
: {
...providedOptions,
projectRoot,
}
);
const options: RNConfigCommandOptions = {
platform: linkingOptions.platform,
projectRoot,
searchPaths: linkingOptions.searchPaths,
};
const results = await createReactNativeConfigAsync(options);
if (providedOptions.json) {
console.log(JSON.stringify(results));
} else {
console.log(require('util').inspect(results, false, null, true));
}
});
}
module.exports = async function (args: string[]) {
// Searches for available expo modules.
registerSearchCommand<SearchOptions & { json?: boolean }>('search', async (results, options) => {
if (options.json) {
console.log(JSON.stringify(results));
} else {
console.log(require('util').inspect(results, false, null, true));
}
}).option<boolean>('-j, --json', 'Output results in the plain JSON format.', () => true, false);
// Checks whether there are no resolving issues in the current setup.
registerSearchCommand('verify', (results, options) => {
const numberOfDuplicates = verifySearchResults(results, options);
if (!numberOfDuplicates) {
console.log('✅ Everything is fine!');
}
});
// Searches for available expo modules and resolves the results for given platform.
registerResolveCommand('resolve', async (results, options) => {
const modules = await resolveModulesAsync(results, options);
const extraDependencies = await resolveExtraBuildDependenciesAsync(options);
if (options.json) {
console.log(JSON.stringify({ extraDependencies, modules }));
} else {
console.log(require('util').inspect({ extraDependencies, modules }, false, null, true));
}
}).option<boolean>('-j, --json', 'Output results in the plain JSON format.', () => true, false);
// Generates a source file listing all packages to link.
// It's deprecated for apple platforms, use `generate-modules-provider` instead.
registerResolveCommand<GenerateOptions>('generate-package-list', async (results, options) => {
const modules = options.empty ? [] : await resolveModulesAsync(results, options);
generatePackageListAsync(modules, options);
})
.option(
'-t, --target <path>',
'Path to the target file, where the package list should be written to.'
)
.option(
'-n, --namespace <namespace>',
'Java package name under which the package list should be placed.'
)
.option(
'--empty',
'Whether to only generate an empty list. Might be used when the user opts-out of autolinking.',
false
);
// Generates a source file listing all packages to link in the runtime.
registerResolveCommand<GenerateModulesProviderOptions>(
'generate-modules-provider',
async (results, options) => {
const packages = options.packages ?? [];
const modules = await resolveModulesAsync(results, options);
const filteredModules = modules.filter((module) => packages.includes(module.packageName));
generateModulesProviderAsync(filteredModules, options);
}
)
.option(
'-t, --target <path>',
'Path to the target file, where the package list should be written to.'
)
.option('--entitlement <path>', 'Path to the Apple code signing entitlements file.')
.option(
'-p, --packages <packages...>',
'Names of the packages to include in the generated modules provider.'
);
registerReactNativeConfigCommand();
await commander
.version(require('expo-modules-autolinking/package.json').version)
.description('CLI command that searches for Expo modules to autolink them.')
.parseAsync(args, { from: 'user' });
};