feat(expo): use createNodesV2 (#28005)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Emily Xiong 2024-11-04 10:56:42 -08:00 committed by GitHub
parent 735300c63e
commit 277c58fbcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 295 additions and 144 deletions

View File

@ -1,5 +1,5 @@
export { export {
createNodes, createNodes,
createDependencies, createNodesV2,
DetoxPluginOptions, DetoxPluginOptions,
} from './src/plugins/plugin'; } from './src/plugins/plugin';

View File

@ -2,16 +2,15 @@ import {
createProjectGraphAsync, createProjectGraphAsync,
formatFiles, formatFiles,
readNxJson, readNxJson,
readProjectConfiguration,
type Tree, type Tree,
updateNxJson, updateNxJson,
} from '@nx/devkit'; } from '@nx/devkit';
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
import { import {
migrateProjectExecutorsToPluginV1, migrateProjectExecutorsToPlugin,
NoTargetsToMigrateError, NoTargetsToMigrateError,
} from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { createNodes } from '../../plugins/plugin'; import { createNodesV2 } from '../../plugins/plugin';
import { processBuildOptions } from './lib/process-build-options'; import { processBuildOptions } from './lib/process-build-options';
import { postTargetTransformer } from './lib/post-target-transformer'; import { postTargetTransformer } from './lib/post-target-transformer';
import { processTestOptions } from './lib/process-test-options'; import { processTestOptions } from './lib/process-test-options';
@ -24,11 +23,11 @@ interface Schema {
export async function convertToInferred(tree: Tree, options: Schema) { export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync(); const projectGraph = await createProjectGraphAsync();
const migrationLogs = new AggregatedLog(); const migrationLogs = new AggregatedLog();
const migratedProjects = await migrateProjectExecutorsToPluginV1( const migratedProjects = await migrateProjectExecutorsToPlugin(
tree, tree,
projectGraph, projectGraph,
'@nx/detox/plugin', '@nx/detox/plugin',
createNodes, createNodesV2,
{ {
buildTargetName: 'build', buildTargetName: 'build',
startTargetName: 'start', startTargetName: 'start',

View File

@ -1,7 +1,9 @@
import { import {
CreateDependencies,
CreateNodes, CreateNodes,
CreateNodesContext, CreateNodesContext,
createNodesFromFiles,
CreateNodesResult,
CreateNodesV2,
detectPackageManager, detectPackageManager,
NxJsonConfiguration, NxJsonConfiguration,
readJsonFile, readJsonFile,
@ -11,9 +13,10 @@ import {
import { dirname, join } from 'path'; import { dirname, join } from 'path';
import { getLockFileName } from '@nx/js'; import { getLockFileName } from '@nx/js';
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs'; import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { existsSync, readdirSync } from 'fs'; import { existsSync } from 'fs';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes'; import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { hashObject } from 'nx/src/devkit-internals';
export interface DetoxPluginOptions { export interface DetoxPluginOptions {
buildTargetName?: string; buildTargetName?: string;
@ -21,56 +24,97 @@ export interface DetoxPluginOptions {
testTargetName?: string; testTargetName?: string;
} }
const cachePath = join(workspaceDataDirectory, 'detox.hash'); function readTargetsCache(
const targetsCache = readTargetsCache(); cachePath: string
): Record<string, Record<string, TargetConfiguration<DetoxPluginOptions>>> {
function readTargetsCache(): Record<
string,
Record<string, TargetConfiguration<DetoxPluginOptions>>
> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {}; return existsSync(cachePath) ? readJsonFile(cachePath) : {};
} }
function writeTargetsToCache() { function writeTargetsToCache(
writeJsonFile(cachePath, targetsCache); cachePath: string,
targetsCache: Record<
string,
Record<string, TargetConfiguration<DetoxPluginOptions>>
>
) {
const oldCache = readTargetsCache(cachePath);
writeJsonFile(cachePath, {
...oldCache,
targetsCache,
});
} }
export const createDependencies: CreateDependencies = () => { export const createNodesV2: CreateNodesV2<DetoxPluginOptions> = [
writeTargetsToCache(); '**/{detox.config,.detoxrc}.{json,js}',
return []; async (configFiles, options, context) => {
}; const optionsHash = hashObject(options);
const cachePath = join(workspaceDataDirectory, `expo-${optionsHash}.hash`);
const targetsCache = readTargetsCache(cachePath);
try {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context, targetsCache),
configFiles,
options,
context
);
} finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];
export const createNodes: CreateNodes<DetoxPluginOptions> = [ export const createNodes: CreateNodes<DetoxPluginOptions> = [
'**/{detox.config,.detoxrc}.{json,js}', '**/{detox.config,.detoxrc}.{json,js}',
async (configFilePath, options, context) => { async (configFilePath, options, context) => {
options = normalizeOptions(options); const optionsHash = hashObject(options);
const projectRoot = dirname(configFilePath); const cachePath = join(workspaceDataDirectory, `detox-${optionsHash}.hash`);
// Do not create a project if project.json isn't there. const targetsCache = readTargetsCache(cachePath);
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); const result = await createNodesInternal(
if (!siblingFiles.includes('project.json')) { configFilePath,
return {};
}
const hash = await calculateHashForCreateNodes(
projectRoot,
options, options,
context, context,
[getLockFileName(detectPackageManager(context.workspaceRoot))] targetsCache
); );
targetsCache[hash] ??= buildDetoxTargets(projectRoot, options, context); writeTargetsToCache(cachePath, targetsCache);
return { return result;
projects: {
[projectRoot]: {
targets: targetsCache[hash],
},
},
};
}, },
]; ];
async function createNodesInternal(
configFile: string,
options: DetoxPluginOptions,
context: CreateNodesContext,
targetsCache: Record<
string,
Record<string, TargetConfiguration<DetoxPluginOptions>>
>
): Promise<CreateNodesResult> {
options = normalizeOptions(options);
const projectRoot = dirname(configFile);
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);
targetsCache[hash] ??= buildDetoxTargets(projectRoot, options, context);
return {
projects: {
[projectRoot]: {
targets: targetsCache[hash],
},
},
};
}
function buildDetoxTargets( function buildDetoxTargets(
projectRoot: string, projectRoot: string,
options: DetoxPluginOptions, options: DetoxPluginOptions,

View File

@ -1,5 +1,5 @@
export { export {
createNodes, createNodes,
createDependencies, createNodesV2,
ExpoPluginOptions, ExpoPluginOptions,
} from './plugins/plugin'; } from './plugins/plugin';

View File

@ -1,12 +1,14 @@
import { import {
CreateDependencies,
CreateNodes, CreateNodes,
CreateNodesContext, CreateNodesContext,
createNodesFromFiles,
CreateNodesResult,
CreateNodesV2,
detectPackageManager, detectPackageManager,
logger,
NxJsonConfiguration, NxJsonConfiguration,
readJsonFile, readJsonFile,
TargetConfiguration, TargetConfiguration,
workspaceRoot,
writeJsonFile, writeJsonFile,
} from '@nx/devkit'; } from '@nx/devkit';
import { dirname, join } from 'path'; import { dirname, join } from 'path';
@ -15,6 +17,8 @@ import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { existsSync, readdirSync } from 'fs'; import { existsSync, readdirSync } from 'fs';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes'; import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { combineGlobPatterns } from 'nx/src/utils/globs';
import { hashObject } from 'nx/src/devkit-internals';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils'; import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
export interface ExpoPluginOptions { export interface ExpoPluginOptions {
@ -29,68 +33,114 @@ export interface ExpoPluginOptions {
submitTargetName?: string; submitTargetName?: string;
} }
const cachePath = join(workspaceDataDirectory, 'expo.hash'); function readTargetsCache(
const targetsCache = readTargetsCache(); cachePath: string
): Record<string, Record<string, TargetConfiguration<ExpoPluginOptions>>> {
function readTargetsCache(): Record<
string,
Record<string, TargetConfiguration<ExpoPluginOptions>>
> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {}; return existsSync(cachePath) ? readJsonFile(cachePath) : {};
} }
function writeTargetsToCache() { function writeTargetsToCache(
const oldCache = readTargetsCache(); cachePath: string,
targetsCache: Record<
string,
Record<string, TargetConfiguration<ExpoPluginOptions>>
>
) {
const oldCache = readTargetsCache(cachePath);
writeJsonFile(cachePath, { writeJsonFile(cachePath, {
...oldCache, ...oldCache,
targetsCache, targetsCache,
}); });
} }
export const createDependencies: CreateDependencies = () => { export const createNodesV2: CreateNodesV2<ExpoPluginOptions> = [
writeTargetsToCache(); '**/app.{json,config.js,config.ts}',
return []; async (configFiles, options, context) => {
}; const optionsHash = hashObject(options);
const cachePath = join(workspaceDataDirectory, `expo-${optionsHash}.hash`);
const targetsCache = readTargetsCache(cachePath);
try {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context, targetsCache),
configFiles,
options,
context
);
} finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];
export const createNodes: CreateNodes<ExpoPluginOptions> = [ export const createNodes: CreateNodes<ExpoPluginOptions> = [
'**/app.{json,config.js,config.ts}', '**/app.{json,config.js,config.ts}',
async (configFilePath, options, context) => { async (configFilePath, options, context) => {
options = normalizeOptions(options); logger.warn(
const projectRoot = dirname(configFilePath); '`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
// Do not create a project if package.json or project.json or metro.config.js isn't there.
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') ||
!siblingFiles.includes('metro.config.js')
) {
return {};
}
const appConfig = await getAppConfig(configFilePath, context);
// if appConfig.expo is not defined
if (!appConfig.expo) {
return {};
}
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
); );
targetsCache[hash] ??= buildExpoTargets(projectRoot, options, context); const optionsHash = hashObject(options);
const cachePath = join(workspaceDataDirectory, `expo-${optionsHash}.hash`);
const targetsCache = readTargetsCache(cachePath);
return { return createNodesInternal(configFilePath, options, context, targetsCache);
projects: {
[projectRoot]: {
targets: targetsCache[hash],
},
},
};
}, },
]; ];
async function createNodesInternal(
configFile: string,
options: ExpoPluginOptions,
context: CreateNodesContext,
targetsCache: Record<
string,
Record<string, TargetConfiguration<ExpoPluginOptions>>
>
): Promise<CreateNodesResult> {
options = normalizeOptions(options);
const projectRoot = dirname(configFile);
// Do not create a project if package.json or project.json or metro.config.js isn't there.
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') ||
!siblingFiles.includes('metro.config.js')
) {
return {};
}
// Check if it's an Expo project
const packageJson = readJsonFile(
join(context.workspaceRoot, projectRoot, 'package.json')
);
const appConfig = await getAppConfig(configFile, context);
if (
!appConfig.expo &&
!packageJson.dependencies?.['expo'] &&
!packageJson.devDependencies?.['expo']
) {
return {};
}
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);
targetsCache[hash] ??= buildExpoTargets(projectRoot, options, context);
return {
projects: {
[projectRoot]: {
targets: targetsCache[hash],
},
},
};
}
function buildExpoTargets( function buildExpoTargets(
projectRoot: string, projectRoot: string,
options: ExpoPluginOptions, options: ExpoPluginOptions,

View File

@ -6,10 +6,10 @@ import {
} from '@nx/devkit'; } from '@nx/devkit';
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
import { import {
migrateProjectExecutorsToPluginV1, migrateProjectExecutorsToPlugin,
NoTargetsToMigrateError, NoTargetsToMigrateError,
} from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { createNodes } from '../../../plugins/plugin'; import { createNodesV2 } from '../../../plugins/plugin';
import { processBuildOptions } from './lib/process-build-options'; import { processBuildOptions } from './lib/process-build-options';
import { postTargetTransformer } from './lib/post-target-transformer'; import { postTargetTransformer } from './lib/post-target-transformer';
import { processExportOptions } from './lib/process-export-options'; import { processExportOptions } from './lib/process-export-options';
@ -29,11 +29,11 @@ export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync(); const projectGraph = await createProjectGraphAsync();
const migrationLogs = new AggregatedLog(); const migrationLogs = new AggregatedLog();
const projects = getProjects(tree); const projects = getProjects(tree);
const migratedProjects = await migrateProjectExecutorsToPluginV1( const migratedProjects = await migrateProjectExecutorsToPlugin(
tree, tree,
projectGraph, projectGraph,
'@nx/expo/plugin', '@nx/expo/plugin',
createNodes, createNodesV2,
{ {
buildTargetName: 'build', buildTargetName: 'build',
exportTargetName: 'export', exportTargetName: 'export',

View File

@ -1,5 +1,5 @@
export { export {
createNodes, createNodes,
createDependencies, createNodesV2,
ReactNativePluginOptions, ReactNativePluginOptions,
} from './plugins/plugin'; } from './plugins/plugin';

View File

@ -1,7 +1,9 @@
import { import {
CreateDependencies,
CreateNodes, CreateNodes,
CreateNodesContext, CreateNodesContext,
createNodesFromFiles,
CreateNodesResult,
CreateNodesV2,
detectPackageManager, detectPackageManager,
joinPathFragments, joinPathFragments,
NxJsonConfiguration, NxJsonConfiguration,
@ -16,6 +18,7 @@ import { existsSync, readdirSync } from 'fs';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes'; import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils'; import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
import { hashObject } from 'nx/src/devkit-internals';
export interface ReactNativePluginOptions { export interface ReactNativePluginOptions {
startTargetName?: string; startTargetName?: string;
@ -29,70 +32,125 @@ export interface ReactNativePluginOptions {
upgradeTargetName?: string; upgradeTargetName?: string;
} }
const cachePath = join(workspaceDataDirectory, 'react-native.hash'); function readTargetsCache(
const targetsCache = readTargetsCache(); cachePath: string
function readTargetsCache(): Record< ): Record<
string, string,
Record<string, TargetConfiguration<ReactNativePluginOptions>> Record<string, TargetConfiguration<ReactNativePluginOptions>>
> { > {
return existsSync(cachePath) ? readJsonFile(cachePath) : {}; return existsSync(cachePath) ? readJsonFile(cachePath) : {};
} }
function writeTargetsToCache() { function writeTargetsToCache(
const oldCache = readTargetsCache(); cachePath: string,
targetsCache: Record<
string,
Record<string, TargetConfiguration<ReactNativePluginOptions>>
>
) {
const oldCache = readTargetsCache(cachePath);
writeJsonFile(cachePath, { writeJsonFile(cachePath, {
...oldCache, ...oldCache,
...targetsCache, targetsCache,
}); });
} }
export const createDependencies: CreateDependencies = () => { export const createNodesV2: CreateNodesV2<ReactNativePluginOptions> = [
writeTargetsToCache(); '**/app.{json,config.js,config.ts}',
return []; async (configFiles, options, context) => {
}; const optionsHash = hashObject(options);
const cachePath = join(workspaceDataDirectory, `expo-${optionsHash}.hash`);
const targetsCache = readTargetsCache(cachePath);
export const createNodes: CreateNodes<ReactNativePluginOptions> = [ try {
'**/app.{json,config.js}', return await createNodesFromFiles(
async (configFilePath, options, context) => { (configFile, options, context) =>
options = normalizeOptions(options); createNodesInternal(configFile, options, context, targetsCache),
const projectRoot = dirname(configFilePath); configFiles,
options,
// Do not create a project if package.json or project.json or metro.config.js isn't there. context
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot)); );
if ( } finally {
!siblingFiles.includes('package.json') || writeTargetsToCache(cachePath, targetsCache);
!siblingFiles.includes('metro.config.js')
) {
return {};
} }
const appConfig = await getAppConfig(configFilePath, context);
if (appConfig.expo) {
return {};
}
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);
targetsCache[hash] ??= buildReactNativeTargets(
projectRoot,
options,
context
);
return {
projects: {
[projectRoot]: {
targets: targetsCache[hash],
},
},
};
}, },
]; ];
export const createNodes: CreateNodes<ReactNativePluginOptions> = [
'**/app.{json,config.js,config.ts}',
async (configFilePath, options, context) => {
const optionsHash = hashObject(options);
const cachePath = join(
workspaceDataDirectory,
`react-native-${optionsHash}.hash`
);
const targetsCache = readTargetsCache(cachePath);
const result = await createNodesInternal(
configFilePath,
options,
context,
targetsCache
);
writeTargetsToCache(cachePath, targetsCache);
return result;
},
];
async function createNodesInternal(
configFile: string,
options: ReactNativePluginOptions,
context: CreateNodesContext,
targetsCache: Record<
string,
Record<string, TargetConfiguration<ReactNativePluginOptions>>
>
): Promise<CreateNodesResult> {
options = normalizeOptions(options);
const projectRoot = dirname(configFile);
// Do not create a project if package.json or project.json or metro.config.js isn't there.
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') ||
!siblingFiles.includes('metro.config.js')
) {
return {};
}
// Check if it's an Expo project
const packageJson = readJsonFile(
join(context.workspaceRoot, projectRoot, 'package.json')
);
const appConfig = await getAppConfig(configFile, context);
if (
appConfig.expo ||
packageJson.dependencies?.['expo'] ||
packageJson.devDependencies?.['expo']
) {
return {};
}
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);
targetsCache[hash] ??= buildReactNativeTargets(projectRoot, options, context);
return {
projects: {
[projectRoot]: {
targets: targetsCache[hash],
},
},
};
}
function buildReactNativeTargets( function buildReactNativeTargets(
projectRoot: string, projectRoot: string,
options: ReactNativePluginOptions, options: ReactNativePluginOptions,

View File

@ -1,10 +1,10 @@
import { createProjectGraphAsync, formatFiles, type Tree } from '@nx/devkit'; import { createProjectGraphAsync, formatFiles, type Tree } from '@nx/devkit';
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
import { import {
migrateProjectExecutorsToPluginV1, migrateProjectExecutorsToPlugin,
NoTargetsToMigrateError, NoTargetsToMigrateError,
} from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { createNodes } from '../../../plugins/plugin'; import { createNodesV2 } from '../../../plugins/plugin';
import { postTargetTransformer } from './lib/post-target-transformer'; import { postTargetTransformer } from './lib/post-target-transformer';
import { processStartOptions } from './lib/process-start-options'; import { processStartOptions } from './lib/process-start-options';
import { createProcessOptions } from './lib/create-process-options'; import { createProcessOptions } from './lib/create-process-options';
@ -17,11 +17,11 @@ interface Schema {
export async function convertToInferred(tree: Tree, options: Schema) { export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync(); const projectGraph = await createProjectGraphAsync();
const migrationLogs = new AggregatedLog(); const migrationLogs = new AggregatedLog();
const migratedProjects = await migrateProjectExecutorsToPluginV1( const migratedProjects = await migrateProjectExecutorsToPlugin(
tree, tree,
projectGraph, projectGraph,
'@nx/react-native/plugin', '@nx/react-native/plugin',
createNodes, createNodesV2,
{ {
buildAndroidTargetName: 'build-android', buildAndroidTargetName: 'build-android',
buildIosTargetName: 'build-ios', buildIosTargetName: 'build-ios',