Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(misc): ensure plugins are not creating workspace context while creating nodes #26253

Merged
merged 9 commits into from
May 31, 2024
1 change: 1 addition & 0 deletions docs/generated/devkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ It only uses language primitives and immutable objects
- [getProjects](../../devkit/documents/getProjects)
- [getWorkspaceLayout](../../devkit/documents/getWorkspaceLayout)
- [glob](../../devkit/documents/glob)
- [globAsync](../../devkit/documents/globAsync)
- [hashArray](../../devkit/documents/hashArray)
- [installPackagesTask](../../devkit/documents/installPackagesTask)
- [isWorkspacesEnabled](../../devkit/documents/isWorkspacesEnabled)
Expand Down
4 changes: 4 additions & 0 deletions docs/generated/devkit/glob.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ Paths should be unix-style with forward slashes.
`string`[]

Normalized paths in the workspace that match the provided glob patterns.

**`Deprecated`**

Use [globAsync](../../devkit/documents/globAsync) instead.
20 changes: 20 additions & 0 deletions docs/generated/devkit/globAsync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Function: globAsync

▸ **globAsync**(`tree`, `patterns`): `Promise`\<`string`[]\>

Performs a tree-aware glob search on the files in a workspace. Able to find newly
created files and hides deleted files before the updates are committed to disk.
Paths should be unix-style with forward slashes.

#### Parameters

| Name | Type | Description |
| :--------- | :------------------------------------ | :---------------------- |
| `tree` | [`Tree`](../../devkit/documents/Tree) | The file system tree |
| `patterns` | `string`[] | A list of glob patterns |

#### Returns

`Promise`\<`string`[]\>

Normalized paths in the workspace that match the provided glob patterns.
1 change: 1 addition & 0 deletions docs/generated/packages/devkit/documents/nx_devkit.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ It only uses language primitives and immutable objects
- [getProjects](../../devkit/documents/getProjects)
- [getWorkspaceLayout](../../devkit/documents/getWorkspaceLayout)
- [glob](../../devkit/documents/glob)
- [globAsync](../../devkit/documents/globAsync)
- [hashArray](../../devkit/documents/hashArray)
- [installPackagesTask](../../devkit/documents/installPackagesTask)
- [isWorkspacesEnabled](../../devkit/documents/isWorkspacesEnabled)
Expand Down
1 change: 1 addition & 0 deletions jest.preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ module.exports = {
coverageReporters: ['html'],
maxWorkers: 1,
testEnvironment: 'node',
setupFiles: ['../../scripts/unit-test-setup.js'],
};
5 changes: 5 additions & 0 deletions packages/cypress/src/executors/cypress/cypress.impl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ describe('Cypress builder', () => {
configuration,
};
};
(devkit as any).logger = {
warn: jest.fn(),
log: jest.fn(),
info: jest.fn(),
};
cypressRun = jest
.spyOn(Cypress, 'run')
.mockReturnValue(Promise.resolve({}));
Expand Down
14 changes: 9 additions & 5 deletions packages/cypress/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ import { getLockFileName } from '@nx/js';

import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { existsSync, readdirSync } from 'fs';
import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context';

import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
import { NX_PLUGIN_OPTIONS } from '../utils/constants';
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
import { hashObject } from 'nx/src/devkit-internals';
import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context';

export interface CypressPluginOptions {
ciTargetName?: string;
Expand Down Expand Up @@ -98,9 +99,12 @@ async function createNodesInternal(
return {};
}

const hash = calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);

targetsCache[hash] ??= await buildCypressTargets(
configFilePath,
Expand Down Expand Up @@ -237,7 +241,7 @@ async function buildCypressTargets(
: Array.isArray(cypressConfig.e2e.excludeSpecPattern)
? cypressConfig.e2e.excludeSpecPattern.map((p) => join(projectRoot, p))
: [join(projectRoot, cypressConfig.e2e.excludeSpecPattern)];
const specFiles = globWithWorkspaceContext(
const specFiles = await globWithWorkspaceContext(
context.workspaceRoot,
specPatterns,
excludeSpecPatterns
Expand Down
11 changes: 7 additions & 4 deletions packages/detox/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const createDependencies: CreateDependencies = () => {

export const createNodes: CreateNodes<DetoxPluginOptions> = [
'**/{detox.config,.detoxrc}.{json,js}',
(configFilePath, options, context) => {
async (configFilePath, options, context) => {
options = normalizeOptions(options);
const projectRoot = dirname(configFilePath);

Expand All @@ -52,9 +52,12 @@ export const createNodes: CreateNodes<DetoxPluginOptions> = [
return {};
}

const hash = calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);

targetsCache[hash] ??= buildDetoxTargets(projectRoot, options, context);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { CreateNodesContext, hashArray } from 'nx/src/devkit-exports';

import { hashObject, hashWithWorkspaceContext } from 'nx/src/devkit-internals';

export function calculateHashForCreateNodes(
export async function calculateHashForCreateNodes(
projectRoot: string,
options: object,
context: CreateNodesContext,
additionalGlobs: string[] = []
): string {
): Promise<string> {
return hashArray([
hashWithWorkspaceContext(context.workspaceRoot, [
await hashWithWorkspaceContext(context.workspaceRoot, [
join(projectRoot, '**/*'),
...additionalGlobs,
]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports';
import { WORKSPACE_PLUGIN_DIR } from '../../constants';
import update from './rename-workspace-rules';

import 'nx/src/internal-testing-utils/mock-project-graph';

const rule1Name = 'test-rule';
const rule2Name = 'my-rule';

Expand Down
2 changes: 2 additions & 0 deletions packages/eslint/src/generators/init/init.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import 'nx/src/internal-testing-utils/mock-project-graph';
import { NxJsonConfiguration, readJson, Tree, updateJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { LinterInitOptions, lintInitGenerator } from './init';
import { setWorkspaceRoot } from 'nx/src/utils/workspace-root';

describe('@nx/eslint:init', () => {
let tree: Tree;
let options: LinterInitOptions;

beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
setWorkspaceRoot(tree.root);
options = {
addPlugin: true,
};
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const createNodes: CreateNodes<EslintPluginOptions> = [
}
}

const projectFiles = globWithWorkspaceContext(
const projectFiles = await globWithWorkspaceContext(
context.workspaceRoot,
[
'project.json',
Expand All @@ -77,7 +77,7 @@ export const createNodes: CreateNodes<EslintPluginOptions> = [
const nestedProjectRootPatterns = excludePatterns.slice(index + 1);

// Ignore project roots where the project does not contain any lintable files
const lintableFiles = globWithWorkspaceContext(
const lintableFiles = await globWithWorkspaceContext(
context.workspaceRoot,
[join(childProjectRoot, `**/*.{${options.extensions.join(',')}}`)],
// exclude nested eslint roots and nested project roots
Expand Down
9 changes: 6 additions & 3 deletions packages/expo/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,12 @@ export const createNodes: CreateNodes<ExpoPluginOptions> = [
return {};
}

const hash = calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);

targetsCache[hash] ??= buildExpoTargets(projectRoot, options, context);

Expand Down
12 changes: 6 additions & 6 deletions packages/gradle/src/plugin/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const createNodesV2: CreateNodesV2<GradlePluginOptions> = [
);
const targetsCache = readTargetsCache(cachePath);

populateGradleReport(context.workspaceRoot);
await populateGradleReport(context.workspaceRoot);
const gradleReport = getCurrentGradleReport();

try {
Expand All @@ -93,14 +93,14 @@ export const makeCreateNodes =
gradleReport: GradleReport,
targetsCache: GradleTargets
): CreateNodesFunction =>
(
async (
gradleFilePath,
options: GradlePluginOptions | undefined,
context: CreateNodesContext
) => {
const projectRoot = dirname(gradleFilePath);

const hash = calculateHashForCreateNodes(
const hash = await calculateHashForCreateNodes(
projectRoot,
options ?? {},
context
Expand Down Expand Up @@ -128,14 +128,14 @@ export const makeCreateNodes =
*/
export const createNodes: CreateNodes<GradlePluginOptions> = [
gradleConfigGlob,
(configFile, options, context) => {
async (configFile, options, context) => {
logger.warn(
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
);
populateGradleReport(context.workspaceRoot);
await populateGradleReport(context.workspaceRoot);
const gradleReport = getCurrentGradleReport();
const internalCreateNodes = makeCreateNodes(gradleReport, {});
return internalCreateNodes(configFile, options, context);
return await internalCreateNodes(configFile, options, context);
},
];

Expand Down
6 changes: 4 additions & 2 deletions packages/gradle/src/utils/get-gradle-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ export function getCurrentGradleReport() {
return gradleReportCache;
}

export function populateGradleReport(workspaceRoot: string): void {
const gradleConfigHash = hashWithWorkspaceContext(workspaceRoot, [
export async function populateGradleReport(
workspaceRoot: string
): Promise<void> {
const gradleConfigHash = await hashWithWorkspaceContext(workspaceRoot, [
gradleConfigGlob,
]);
if (gradleReportCache && gradleConfigHash === gradleCurrentConfigHash) {
Expand Down
2 changes: 1 addition & 1 deletion packages/jest/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ async function createNodesInternal(

options = normalizeOptions(options);

const hash = calculateHashForCreateNodes(projectRoot, options, context);
const hash = await calculateHashForCreateNodes(projectRoot, options, context);
targetsCache[hash] ??= await buildJestTargets(
configFilePath,
projectRoot,
Expand Down
4 changes: 2 additions & 2 deletions packages/js/src/plugins/typescript/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const PLUGIN_NAME = '@nx/js/typescript';

export const createNodes: CreateNodes<TscPluginOptions> = [
'**/tsconfig*.json',
(configFilePath, options, context) => {
async (configFilePath, options, context) => {
const pluginOptions = normalizePluginOptions(options);
const projectRoot = dirname(configFilePath);
const fullConfigPath = joinPathFragments(
Expand All @@ -101,7 +101,7 @@ export const createNodes: CreateNodes<TscPluginOptions> = [
return {};
}

const nodeHash = calculateHashForCreateNodes(
const nodeHash = await calculateHashForCreateNodes(
projectRoot,
pluginOptions,
context,
Expand Down
1 change: 1 addition & 0 deletions packages/nest/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export default {
globals: {},
displayName: 'nest',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/test-setup.ts'],
};
12 changes: 12 additions & 0 deletions packages/nest/test-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// If a test uses a util from devkit, but that util
// lives in the Nx package and creates the project graph,
// we need to mock the resolved value inside the Nx package
jest
.spyOn(
require('nx/src/project-graph/project-graph'),
'createProjectGraphAsync'
)
.mockResolvedValue({
nodes: {},
dependencies: {},
});
3 changes: 2 additions & 1 deletion packages/nest/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"**/*.test.ts",
"**/*_spec.ts",
"**/*_test.ts",
"jest.config.ts"
"jest.config.ts",
"test-setup.ts"
],
"include": ["**/*.ts"]
}
3 changes: 2 additions & 1 deletion packages/nest/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"**/*.spec.jsx",
"**/*.test.jsx",
"**/*.d.ts",
"jest.config.ts"
"jest.config.ts",
"test-setup.ts"
]
}
9 changes: 6 additions & 3 deletions packages/next/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,12 @@ export const createNodes: CreateNodes<NextPluginOptions> = [
}
options = normalizeOptions(options);

const hash = calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);

targetsCache[hash] ??= await buildNextTargets(
configFilePath,
Expand Down
9 changes: 6 additions & 3 deletions packages/nuxt/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ export const createNodes: CreateNodes<NuxtPluginOptions> = [

options = normalizeOptions(options);

const hash = calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);
targetsCache[hash] ??= await buildNuxtTargets(
configFilePath,
projectRoot,
Expand Down
8 changes: 8 additions & 0 deletions packages/nx/src/adapter/ngcli-adapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import {
import { createTreeWithEmptyWorkspace } from '../generators/testing-utils/create-tree-with-empty-workspace';
import { addProjectConfiguration } from '../generators/utils/project-configuration';

jest.mock('../project-graph/project-graph', () => ({
...jest.requireActual('../project-graph/project-graph'),
createProjectGraphAsync: () => ({
nodes: {},
externalNodes: {},
}),
}));

describe('ngcli-adapter', () => {
it('arrayBufferToString should support large buffers', () => {
const largeString = 'a'.repeat(1000000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ async function addBundler(options: NormalizedOptions) {
options.isStandalone,
options.appIsJs
);
renameJsToJsx(options.reactAppName, options.isStandalone);
await renameJsToJsx(options.reactAppName, options.isStandalone);
} else {
output.log({ title: '🧑‍🔧 Setting up craco + Webpack' });
const { addCracoCommandsToPackageScripts } = await import(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { globWithWorkspaceContext } from '../../../../utils/workspace-context';
import { fileExists } from '../../../../utils/fileutils';

// Vite cannot process JSX like <div> or <Header> unless the file is named .jsx or .tsx
export function renameJsToJsx(appName: string, isStandalone: boolean) {
const files = globWithWorkspaceContext(process.cwd(), [
export async function renameJsToJsx(appName: string, isStandalone: boolean) {
const files = await globWithWorkspaceContext(process.cwd(), [
isStandalone ? 'src/**/*.js' : `apps/${appName}/src/**/*.js`,
]);

Expand Down
Loading