Skip to content

Commit

Permalink
fix(misc): ensure plugins are not creating workspace context while cr…
Browse files Browse the repository at this point in the history
…eating nodes (#26253)
  • Loading branch information
AgentEnder authored May 31, 2024
1 parent a308e1d commit 6f22300
Show file tree
Hide file tree
Showing 65 changed files with 718 additions and 197 deletions.
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
6 changes: 3 additions & 3 deletions packages/devkit/src/utils/calculate-hash-for-create-nodes.ts
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

0 comments on commit 6f22300

Please sign in to comment.