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 #22967

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/generated/devkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,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 @@ -123,6 +123,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
15 changes: 9 additions & 6 deletions packages/cypress/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ 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 { globWithWorkspaceContext } from 'nx/src/utils/workspace-context';

export interface CypressPluginOptions {
ciTargetName?: string;
Expand Down Expand Up @@ -65,9 +66,12 @@ export const createNodes: CreateNodes<CypressPluginOptions> = [
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 @@ -205,8 +209,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(
context.workspaceRoot,
const specFiles = await globWithWorkspaceContext(
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
18 changes: 18 additions & 0 deletions packages/eslint/src/plugins/plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
import 'nx/src/internal-testing-utils/mock-fs';

let fsRoot: string = '';

jest.mock(
'nx/src/utils/globs',
(): Partial<typeof import('nx/src/utils/globs')> => {
const glob: typeof import('fast-glob') = require('fast-glob');
return {
...jest.requireActual('nx/src/utils/globs'),
globAsync(patterns: string[]) {
// This glob will operate on memfs thanks to 'nx/src/internal-testing-utils/mock-fs'
return glob(patterns, { cwd: fsRoot });
},
};
}
);

import { CreateNodesContext } from '@nx/devkit';
import { minimatch } from 'minimatch';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
Expand Down
8 changes: 3 additions & 5 deletions packages/eslint/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { existsSync } from 'node:fs';
import { dirname, join, normalize, sep } from 'node:path';
import { combineGlobPatterns } from 'nx/src/utils/globs';
import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context';
import { globAsync } from 'nx/src/utils/globs';
import {
ESLINT_CONFIG_FILENAMES,
baseEsLintConfigFile,
Expand Down Expand Up @@ -51,8 +51,7 @@ export const createNodes: CreateNodes<EslintPluginOptions> = [
}
}

const projectFiles = globWithWorkspaceContext(
context.workspaceRoot,
const projectFiles = await globAsync(
[
'project.json',
'package.json',
Expand All @@ -77,8 +76,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(
context.workspaceRoot,
const lintableFiles = await globAsync(
[join(childProjectRoot, `**/*.{${options.extensions.join(',')}}`)],
// exclude nested eslint roots and nested project roots
[...nestedEslintRootPatterns, ...nestedProjectRootPatterns]
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
4 changes: 2 additions & 2 deletions packages/gradle/src/plugin/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ export function writeTargetsToCache() {

export const createNodes: CreateNodes<GradlePluginOptions> = [
'**/build.{gradle.kts,gradle}',
(
async (
gradleFilePath,
options: GradlePluginOptions | undefined,
context: CreateNodesContext
) => {
const projectRoot = dirname(gradleFilePath);

const hash = calculateHashForCreateNodes(
const hash = await calculateHashForCreateNodes(
projectRoot,
options ?? {},
context
Expand Down
6 changes: 5 additions & 1 deletion packages/jest/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ export const createNodes: CreateNodes<JestPluginOptions> = [

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
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
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
2 changes: 1 addition & 1 deletion packages/nx/src/command-line/init/init-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ async function detectPlugins(): Promise<{
updatePackageScripts: boolean;
}> {
let files = ['package.json'].concat(
globWithWorkspaceContext(process.cwd(), ['**/*/package.json'])
await globWithWorkspaceContext(process.cwd(), ['**/*/package.json'])
);

const detectedPlugins = new Set<string>();
Expand Down
2 changes: 1 addition & 1 deletion packages/nx/src/config/to-project-name.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('Workspaces', () => {
readNxJson(fs.tempDir).plugins,
fs.tempDir
);
const res = retrieveProjectConfigurations(
const res = await retrieveProjectConfigurations(
plugins,
fs.tempDir,
readNxJson(fs.tempDir)
Expand Down
66 changes: 66 additions & 0 deletions packages/nx/src/daemon/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,28 @@ import {
ProjectGraphError,
} from '../../project-graph/error-types';
import { loadRootEnvFiles } from '../../utils/dotenv';
import { HandleGlobMessage } from '../message-types/glob';
import {
GET_NX_WORKSPACE_FILES,
HandleNxWorkspaceFilesMessage,
} from '../message-types/get-nx-workspace-files';
import {
GET_CONTEXT_FILE_DATA,
HandleContextFileDataMessage,
} from '../message-types/get-context-file-data';
import {
GET_FILES_IN_DIRECTORY,
HandleGetFilesInDirectoryMessage,
} from '../message-types/get-files-in-directory';
import { HASH_GLOB, HandleHashGlobMessage } from '../message-types/hash-glob';
import { NxWorkspaceFiles } from '../../native';

const DAEMON_ENV_SETTINGS = {
NX_PROJECT_GLOB_CACHE: 'false',
NX_CACHE_PROJECTS_CONFIG: 'false',

// Used to identify that the code is running in the daemon process.
NX_ON_DAEMON_PROCESS: 'true',
AgentEnder marked this conversation as resolved.
Show resolved Hide resolved
};

export type UnregisterCallback = () => void;
Expand Down Expand Up @@ -256,6 +274,54 @@ export class DaemonClient {
});
}

glob(globs: string[], exclude?: string[]): Promise<string[]> {
const message: HandleGlobMessage = {
type: 'GLOB',
globs,
exclude,
};
return this.sendToDaemonViaQueue(message);
}

getWorkspaceContextFileData(
globs: string[],
exclude?: string[]
): Promise<FileData[]> {
const message: HandleContextFileDataMessage = {
type: GET_CONTEXT_FILE_DATA,
globs,
exclude,
};
return this.sendToDaemonViaQueue(message);
}

getWorkspaceFiles(
projectRootMap: Record<string, string>
): Promise<NxWorkspaceFiles> {
const message: HandleNxWorkspaceFilesMessage = {
type: GET_NX_WORKSPACE_FILES,
projectRootMap,
};
return this.sendToDaemonViaQueue(message);
}

getFilesInDirectory(dir: string): Promise<string[]> {
const message: HandleGetFilesInDirectoryMessage = {
type: GET_FILES_IN_DIRECTORY,
dir,
};
return this.sendToDaemonViaQueue(message);
}

hashGlob(globs: string[], exclude?: string[]): Promise<string> {
const message: HandleHashGlobMessage = {
type: HASH_GLOB,
globs,
exclude,
};
return this.sendToDaemonViaQueue(message);
}

async isServerAvailable(): Promise<boolean> {
return new Promise((resolve) => {
try {
Expand Down
Loading