feat(js): add createNodesV2 for typescript plugin (#26788)

<!-- 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 -->

There is no implementation for the `CreateNodesV2` API.

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

There should be an implementation for the `CreateNodesV2` API.

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

Fixes #
This commit is contained in:
Leosvel Pérez Espinosa 2024-07-10 10:21:58 +02:00 committed by GitHub
parent 2b7b5231fb
commit e31b1689c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 121 additions and 76 deletions

View File

@ -1,8 +1,8 @@
import { type CreateNodesContext } from '@nx/devkit'; import { type CreateNodesContext } from '@nx/devkit';
import { TempFs } from '@nx/devkit/internal-testing-utils';
import { minimatch } from 'minimatch'; import { minimatch } from 'minimatch';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { PLUGIN_NAME, TscPluginOptions, createNodes } from './plugin';
import { setupWorkspaceContext } from 'nx/src/utils/workspace-context'; import { setupWorkspaceContext } from 'nx/src/utils/workspace-context';
import { PLUGIN_NAME, createNodesV2, type TscPluginOptions } from './plugin';
describe(`Plugin: ${PLUGIN_NAME}`, () => { describe(`Plugin: ${PLUGIN_NAME}`, () => {
let context: CreateNodesContext; let context: CreateNodesContext;
@ -2170,7 +2170,7 @@ async function applyFilesToTempFsAndContext(
await tempFs.createFiles(fileSys); await tempFs.createFiles(fileSys);
// @ts-expect-error update otherwise readonly property for testing // @ts-expect-error update otherwise readonly property for testing
context.configFiles = Object.keys(fileSys).filter((file) => context.configFiles = Object.keys(fileSys).filter((file) =>
minimatch(file, createNodes[0], { dot: true }) minimatch(file, createNodesV2[0], { dot: true })
); );
setupWorkspaceContext(tempFs.tempDir); setupWorkspaceContext(tempFs.tempDir);
} }
@ -2181,8 +2181,11 @@ async function invokeCreateNodesOnMatchingFiles(
) { ) {
const aggregateProjects: Record<string, any> = {}; const aggregateProjects: Record<string, any> = {};
for (const file of context.configFiles) { for (const file of context.configFiles) {
const nodes = await createNodes[1](file, pluginOptions, context); const results = await createNodesV2[1]([file], pluginOptions, context);
for (const [projectName, project] of Object.entries(nodes.projects ?? {})) { for (const [, nodes] of results) {
for (const [projectName, project] of Object.entries(
nodes.projects ?? {}
)) {
if (aggregateProjects[projectName]) { if (aggregateProjects[projectName]) {
aggregateProjects[projectName].targets = { aggregateProjects[projectName].targets = {
...aggregateProjects[projectName].targets, ...aggregateProjects[projectName].targets,
@ -2193,6 +2196,7 @@ async function invokeCreateNodesOnMatchingFiles(
} }
} }
} }
}
return { return {
projects: aggregateProjects, projects: aggregateProjects,
}; };

View File

@ -1,20 +1,26 @@
import { import {
createNodesFromFiles,
detectPackageManager, detectPackageManager,
joinPathFragments, joinPathFragments,
logger,
normalizePath, normalizePath,
readJsonFile, readJsonFile,
writeJsonFile, writeJsonFile,
type CreateDependencies, type CreateDependencies,
type CreateNodes, type CreateNodes,
type CreateNodesContext, type CreateNodesContext,
type CreateNodesResult,
type CreateNodesV2,
type NxJsonConfiguration, type NxJsonConfiguration,
type ProjectConfiguration,
type TargetConfiguration, type TargetConfiguration,
} from '@nx/devkit'; } from '@nx/devkit';
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 { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs'; import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { minimatch } from 'minimatch';
import { existsSync, readdirSync, statSync } from 'node:fs'; import { existsSync, readdirSync, statSync } from 'node:fs';
import { basename, dirname, join, relative } from 'node:path'; import { basename, dirname, join, relative } from 'node:path';
import { minimatch } from 'minimatch'; import { hashObject } from 'nx/src/hasher/file-hasher';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports // eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { getLockFileName } from 'nx/src/plugins/js/lock-file/lock-file'; import { getLockFileName } from 'nx/src/plugins/js/lock-file/lock-file';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
@ -49,35 +55,68 @@ interface NormalizedPluginOptions {
}; };
} }
const cachePath = join(workspaceDataDirectory, 'tsc.hash'); type TscProjectResult = Pick<ProjectConfiguration, 'targets'>;
const targetsCache = readTargetsCache();
function readTargetsCache(): Record< function readTargetsCache(cachePath: string): Record<string, TscProjectResult> {
string,
Record<string, TargetConfiguration<unknown>>
> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {}; return existsSync(cachePath) ? readJsonFile(cachePath) : {};
} }
function writeTargetsToCache() { function writeTargetsToCache(
const oldCache = readTargetsCache(); cachePath: string,
writeJsonFile(cachePath, { results?: Record<string, TscProjectResult>
...oldCache, ) {
...targetsCache, writeJsonFile(cachePath, results);
});
} }
/**
* @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
*/
export const createDependencies: CreateDependencies = () => { export const createDependencies: CreateDependencies = () => {
writeTargetsToCache();
return []; return [];
}; };
export const PLUGIN_NAME = '@nx/js/typescript'; export const PLUGIN_NAME = '@nx/js/typescript';
const tsConfigGlob = '**/tsconfig*.json';
export const createNodesV2: CreateNodesV2<TscPluginOptions> = [
tsConfigGlob,
async (configFilePaths, options, context) => {
const optionsHash = hashObject(options);
const cachePath = join(workspaceDataDirectory, `tsc-${optionsHash}.hash`);
const targetsCache = readTargetsCache(cachePath);
const normalizedOptions = normalizePluginOptions(options);
try {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context, targetsCache),
configFilePaths,
normalizedOptions,
context
);
} finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];
export const createNodes: CreateNodes<TscPluginOptions> = [ export const createNodes: CreateNodes<TscPluginOptions> = [
'**/tsconfig*.json', tsConfigGlob,
async (configFilePath, options, context) => { async (configFilePath, options, context) => {
const pluginOptions = normalizePluginOptions(options); logger.warn(
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
);
const normalizedOptions = normalizePluginOptions(options);
return createNodesInternal(configFilePath, normalizedOptions, context, {});
},
];
async function createNodesInternal(
configFilePath: string,
options: NormalizedPluginOptions,
context: CreateNodesContext,
targetsCache: Record<string, TscProjectResult>
): Promise<CreateNodesResult> {
const projectRoot = dirname(configFilePath); const projectRoot = dirname(configFilePath);
const fullConfigPath = joinPathFragments( const fullConfigPath = joinPathFragments(
context.workspaceRoot, context.workspaceRoot,
@ -103,7 +142,7 @@ export const createNodes: CreateNodes<TscPluginOptions> = [
const nodeHash = await calculateHashForCreateNodes( const nodeHash = await calculateHashForCreateNodes(
projectRoot, projectRoot,
pluginOptions, options,
context, context,
[getLockFileName(detectPackageManager(context.workspaceRoot))] [getLockFileName(detectPackageManager(context.workspaceRoot))]
); );
@ -113,20 +152,21 @@ export const createNodes: CreateNodes<TscPluginOptions> = [
targetsCache[cacheKey] ??= buildTscTargets( targetsCache[cacheKey] ??= buildTscTargets(
fullConfigPath, fullConfigPath,
projectRoot, projectRoot,
pluginOptions, options,
context context
); );
const { targets } = targetsCache[cacheKey];
return { return {
projects: { projects: {
[projectRoot]: { [projectRoot]: {
projectType: 'library', projectType: 'library',
targets: targetsCache[cacheKey], targets,
}, },
}, },
}; };
}, }
];
function buildTscTargets( function buildTscTargets(
configFilePath: string, configFilePath: string,
@ -220,7 +260,7 @@ function buildTscTargets(
}; };
} }
return targets; return { targets };
} }
function getInputs( function getInputs(

View File

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