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

This commit is contained in:
Craigory Coppola 2024-05-31 18:54:56 -04:00 committed by GitHub
parent a308e1dc6b
commit 6f223005b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
65 changed files with 718 additions and 197 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -12,4 +12,5 @@ module.exports = {
coverageReporters: ['html'],
maxWorkers: 1,
testEnvironment: 'node',
setupFiles: ['../../scripts/unit-test-setup.js'],
};

View File

@ -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({}));

View File

@ -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;
@ -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,
@ -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

View File

@ -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);
@ -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);

View File

@ -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,
]),

View File

@ -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';

View File

@ -3,6 +3,7 @@ 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;
@ -10,6 +11,7 @@ describe('@nx/eslint:init', () => {
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
setWorkspaceRoot(tree.root);
options = {
addPlugin: true,
};

View File

@ -51,7 +51,7 @@ export const createNodes: CreateNodes<EslintPluginOptions> = [
}
}
const projectFiles = globWithWorkspaceContext(
const projectFiles = await globWithWorkspaceContext(
context.workspaceRoot,
[
'project.json',
@ -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

View File

@ -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);

View File

@ -72,7 +72,7 @@ export const createNodesV2: CreateNodesV2<GradlePluginOptions> = [
);
const targetsCache = readTargetsCache(cachePath);
populateGradleReport(context.workspaceRoot);
await populateGradleReport(context.workspaceRoot);
const gradleReport = getCurrentGradleReport();
try {
@ -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
@ -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);
},
];

View File

@ -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) {

View File

@ -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,

View File

@ -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(
@ -101,7 +101,7 @@ export const createNodes: CreateNodes<TscPluginOptions> = [
return {};
}
const nodeHash = calculateHashForCreateNodes(
const nodeHash = await calculateHashForCreateNodes(
projectRoot,
pluginOptions,
context,

View File

@ -7,4 +7,5 @@ export default {
globals: {},
displayName: 'nest',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/test-setup.ts'],
};

View File

@ -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: {},
});

View File

@ -11,7 +11,8 @@
"**/*.test.ts",
"**/*_spec.ts",
"**/*_test.ts",
"jest.config.ts"
"jest.config.ts",
"test-setup.ts"
],
"include": ["**/*.ts"]
}

View File

@ -17,6 +17,7 @@
"**/*.spec.jsx",
"**/*.test.jsx",
"**/*.d.ts",
"jest.config.ts"
"jest.config.ts",
"test-setup.ts"
]
}

View File

@ -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,

View File

@ -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,

View File

@ -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);

View File

@ -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(

View File

@ -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`,
]);

View File

@ -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>();

View File

@ -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)

View File

@ -4,7 +4,7 @@ import { readFileSync, statSync } from 'fs';
import { FileHandle, open } from 'fs/promises';
import { ensureDirSync, ensureFileSync } from 'fs-extra';
import { connect } from 'net';
import { join } from 'path';
import { extname, join } from 'path';
import { performance } from 'perf_hooks';
import { output } from '../../utils/output';
import { getFullOsSocketPath, killSocketOrPath } from '../socket-utils';
@ -30,6 +30,21 @@ 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',
@ -256,6 +271,49 @@ export class DaemonClient {
});
}
glob(globs: string[], exclude?: string[]): Promise<string[]> {
const message: HandleGlobMessage = {
type: 'GLOB',
globs,
exclude,
};
return this.sendToDaemonViaQueue(message);
}
getWorkspaceContextFileData(): Promise<FileData[]> {
const message: HandleContextFileDataMessage = {
type: GET_CONTEXT_FILE_DATA,
};
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 {
@ -414,14 +472,17 @@ export class DaemonClient {
const backgroundProcess = spawn(
process.execPath,
[join(__dirname, '../server/start.js')],
[join(__dirname, `../server/start.js`)],
{
cwd: workspaceRoot,
stdio: ['ignore', this._out.fd, this._err.fd],
detached: true,
windowsHide: true,
shell: false,
env: { ...process.env, ...DAEMON_ENV_SETTINGS },
env: {
...process.env,
...DAEMON_ENV_SETTINGS,
},
}
);
backgroundProcess.unref();

View File

@ -0,0 +1,3 @@
export function isOnDaemon() {
return !!global.NX_DAEMON;
}

View File

@ -0,0 +1,16 @@
export const GET_CONTEXT_FILE_DATA = 'GET_CONTEXT_FILE_DATA' as const;
export type HandleContextFileDataMessage = {
type: typeof GET_CONTEXT_FILE_DATA;
};
export function isHandleContextFileDataMessage(
message: unknown
): message is HandleContextFileDataMessage {
return (
typeof message === 'object' &&
message !== null &&
'type' in message &&
message['type'] === GET_CONTEXT_FILE_DATA
);
}

View File

@ -0,0 +1,17 @@
export const GET_FILES_IN_DIRECTORY = 'GET_FILES_IN_DIRECTORY' as const;
export type HandleGetFilesInDirectoryMessage = {
type: typeof GET_FILES_IN_DIRECTORY;
dir: string;
};
export function isHandleGetFilesInDirectoryMessage(
message: unknown
): message is HandleGetFilesInDirectoryMessage {
return (
typeof message === 'object' &&
message !== null &&
'type' in message &&
message['type'] === GET_FILES_IN_DIRECTORY
);
}

View File

@ -0,0 +1,17 @@
export const GET_NX_WORKSPACE_FILES = 'GET_NX_WORKSPACE_FILES' as const;
export type HandleNxWorkspaceFilesMessage = {
type: typeof GET_NX_WORKSPACE_FILES;
projectRootMap: Record<string, string>;
};
export function isHandleNxWorkspaceFilesMessage(
message: unknown
): message is HandleNxWorkspaceFilesMessage {
return (
typeof message === 'object' &&
message !== null &&
'type' in message &&
message['type'] === GET_NX_WORKSPACE_FILES
);
}

View File

@ -0,0 +1,18 @@
export const GLOB = 'GLOB' as const;
export type HandleGlobMessage = {
type: typeof GLOB;
globs: string[];
exclude?: string[];
};
export function isHandleGlobMessage(
message: unknown
): message is HandleGlobMessage {
return (
typeof message === 'object' &&
message !== null &&
'type' in message &&
message['type'] === GLOB
);
}

View File

@ -0,0 +1,18 @@
export const HASH_GLOB = 'HASH_GLOB' as const;
export type HandleHashGlobMessage = {
type: typeof HASH_GLOB;
globs: string[];
exclude?: string[];
};
export function isHandleHashGlobMessage(
message: unknown
): message is HandleHashGlobMessage {
return (
typeof message === 'object' &&
message !== null &&
'type' in message &&
message['type'] === HASH_GLOB
);
}

View File

@ -0,0 +1,18 @@
export const GLOB = 'GLOB' as const;
export type HandleUpdateContextMessage = {
type: typeof GLOB;
updatedFiles: string[];
deletedFiles: string[];
};
export function isHandleUpdateContextMessage(
message: unknown
): message is HandleUpdateContextMessage {
return (
typeof message === 'object' &&
message !== null &&
'type' in message &&
message['type'] === GLOB
);
}

View File

@ -0,0 +1,11 @@
import { getAllFileDataInContext } from '../../utils/workspace-context';
import { workspaceRoot } from '../../utils/workspace-root';
import { HandlerResult } from './server';
export async function handleContextFileData(): Promise<HandlerResult> {
const files = await getAllFileDataInContext(workspaceRoot);
return {
response: JSON.stringify(files),
description: 'handleContextFileData',
};
}

View File

@ -0,0 +1,13 @@
import { getFilesInDirectoryUsingContext } from '../../utils/workspace-context';
import { workspaceRoot } from '../../utils/workspace-root';
import { HandlerResult } from './server';
export async function handleGetFilesInDirectory(
dir: string
): Promise<HandlerResult> {
const files = await getFilesInDirectoryUsingContext(workspaceRoot, dir);
return {
response: JSON.stringify(files),
description: 'handleNxWorkspaceFiles',
};
}

View File

@ -0,0 +1,14 @@
import { workspaceRoot } from '../../utils/workspace-root';
import { globWithWorkspaceContext } from '../../utils/workspace-context';
import { HandlerResult } from './server';
export async function handleGlob(
globs: string[],
exclude?: string[]
): Promise<HandlerResult> {
const files = await globWithWorkspaceContext(workspaceRoot, globs, exclude);
return {
response: JSON.stringify(files),
description: 'handleGlob',
};
}

View File

@ -0,0 +1,14 @@
import { workspaceRoot } from '../../utils/workspace-root';
import { hashWithWorkspaceContext } from '../../utils/workspace-context';
import { HandlerResult } from './server';
export async function handleHashGlob(
globs: string[],
exclude?: string[]
): Promise<HandlerResult> {
const files = await hashWithWorkspaceContext(workspaceRoot, globs, exclude);
return {
response: JSON.stringify(files),
description: 'handleHashGlob',
};
}

View File

@ -0,0 +1,16 @@
import { getNxWorkspaceFilesFromContext } from '../../utils/workspace-context';
import { workspaceRoot } from '../../utils/workspace-root';
import { HandlerResult } from './server';
export async function handleNxWorkspaceFiles(
projectRootMap: Record<string, string>
): Promise<HandlerResult> {
const files = await getNxWorkspaceFilesFromContext(
workspaceRoot,
projectRootMap
);
return {
response: JSON.stringify(files),
description: 'handleNxWorkspaceFiles',
};
}

View File

@ -1,10 +0,0 @@
import { getAllFileDataInContext } from '../../utils/workspace-context';
import { workspaceRoot } from '../../utils/workspace-root';
export async function handleRequestFileData() {
const response = JSON.stringify(getAllFileDataInContext(workspaceRoot));
return {
response,
description: 'handleRequestFileData',
};
}

View File

@ -26,7 +26,6 @@ import {
handleRecordOutputsHash,
} from './handle-outputs-tracking';
import { handleProcessInBackground } from './handle-process-in-background';
import { handleRequestFileData } from './handle-request-file-data';
import { handleRequestProjectGraph } from './handle-request-project-graph';
import { handleRequestShutdown } from './handle-request-shutdown';
import { serverLogger } from './logger';
@ -52,11 +51,32 @@ import {
watchOutputFiles,
watchWorkspace,
} from './watcher';
import { handleGlob } from './handle-glob';
import { GLOB, isHandleGlobMessage } from '../message-types/glob';
import {
GET_NX_WORKSPACE_FILES,
isHandleNxWorkspaceFilesMessage,
} from '../message-types/get-nx-workspace-files';
import { handleNxWorkspaceFiles } from './handle-nx-workspace-files';
import {
GET_CONTEXT_FILE_DATA,
isHandleContextFileDataMessage,
} from '../message-types/get-context-file-data';
import { handleContextFileData } from './handle-context-file-data';
import {
GET_FILES_IN_DIRECTORY,
isHandleGetFilesInDirectoryMessage,
} from '../message-types/get-files-in-directory';
import { handleGetFilesInDirectory } from './handle-get-files-in-directory';
import { HASH_GLOB, isHandleHashGlobMessage } from '../message-types/hash-glob';
import { handleHashGlob } from './handle-hash-glob';
let performanceObserver: PerformanceObserver | undefined;
let workspaceWatcherError: Error | undefined;
let outputsWatcherError: Error | undefined;
global.NX_DAEMON = true;
export type HandlerResult = {
description: string;
error?: any;
@ -111,11 +131,12 @@ async function handleMessage(socket, data: string) {
);
}
if (daemonIsOutdated()) {
const outdated = daemonIsOutdated();
if (outdated) {
await respondWithErrorAndExit(
socket,
`Lock files changed`,
new Error('LOCK-FILES-CHANGED')
`Daemon outdated`,
new Error(outdated)
);
}
@ -143,10 +164,6 @@ async function handleMessage(socket, data: string) {
);
} else if (payload.type === 'HASH_TASKS') {
await handleResult(socket, 'HASH_TASKS', () => handleHashTasks(payload));
} else if (payload.type === 'REQUEST_FILE_DATA') {
await handleResult(socket, 'REQUEST_FILE_DATA', () =>
handleRequestFileData()
);
} else if (payload.type === 'PROCESS_IN_BACKGROUND') {
await handleResult(socket, 'PROCESS_IN_BACKGROUND', () =>
handleProcessInBackground(payload)
@ -165,6 +182,26 @@ async function handleMessage(socket, data: string) {
);
} else if (payload.type === 'REGISTER_FILE_WATCHER') {
registeredFileWatcherSockets.push({ socket, config: payload.config });
} else if (isHandleGlobMessage(payload)) {
await handleResult(socket, GLOB, () =>
handleGlob(payload.globs, payload.exclude)
);
} else if (isHandleNxWorkspaceFilesMessage(payload)) {
await handleResult(socket, GET_NX_WORKSPACE_FILES, () =>
handleNxWorkspaceFiles(payload.projectRootMap)
);
} else if (isHandleGetFilesInDirectoryMessage(payload)) {
await handleResult(socket, GET_FILES_IN_DIRECTORY, () =>
handleGetFilesInDirectory(payload.dir)
);
} else if (isHandleContextFileDataMessage(payload)) {
await handleResult(socket, GET_CONTEXT_FILE_DATA, () =>
handleContextFileData()
);
} else if (isHandleHashGlobMessage(payload)) {
await handleResult(socket, HASH_GLOB, () =>
handleHashGlob(payload.globs, payload.exclude)
);
} else {
await respondWithErrorAndExit(
socket,
@ -233,8 +270,13 @@ function registerProcessTerminationListeners() {
let existingLockHash: string | undefined;
function daemonIsOutdated(): boolean {
return nxVersionChanged() || lockFileHashChanged();
function daemonIsOutdated(): string | null {
if (nxVersionChanged()) {
return 'NX_VERSION_CHANGED';
} else if (lockFileHashChanged()) {
return 'LOCK_FILES_CHANGED';
}
return null;
}
function nxVersionChanged(): boolean {
@ -291,10 +333,11 @@ const handleWorkspaceChanges: FileWatcherCallback = async (
try {
resetInactivityTimeout(handleInactivityTimeout);
if (daemonIsOutdated()) {
const outdatedReason = daemonIsOutdated();
if (outdatedReason) {
await handleServerProcessTermination({
server,
reason: 'Lock file changed',
reason: outdatedReason,
});
return;
}

View File

@ -132,7 +132,7 @@ export {
/**
* @category Generators
*/
export { glob } from './generators/utils/glob';
export { glob, globAsync } from './generators/utils/glob';
/**
* @category Generators

View File

@ -1,7 +1,28 @@
import { minimatch } from 'minimatch';
import { Tree } from '../tree';
import { combineGlobPatterns } from '../../utils/globs';
import { globWithWorkspaceContext } from '../../utils/workspace-context';
import {
globWithWorkspaceContext,
globWithWorkspaceContextSync,
} from '../../utils/workspace-context';
/**
* 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.
*
* @param tree The file system tree
* @param patterns A list of glob patterns
* @returns Normalized paths in the workspace that match the provided glob patterns.
* @deprecated Use {@link globAsync} instead.
*/
export function glob(tree: Tree, patterns: string[]): string[] {
return combineGlobResultsWithTree(
tree,
patterns,
globWithWorkspaceContextSync(tree.root, patterns)
);
}
/**
* Performs a tree-aware glob search on the files in a workspace. Able to find newly
@ -12,8 +33,23 @@ import { globWithWorkspaceContext } from '../../utils/workspace-context';
* @param patterns A list of glob patterns
* @returns Normalized paths in the workspace that match the provided glob patterns.
*/
export function glob(tree: Tree, patterns: string[]): string[] {
const matches = new Set(globWithWorkspaceContext(tree.root, patterns));
export async function globAsync(
tree: Tree,
patterns: string[]
): Promise<string[]> {
return combineGlobResultsWithTree(
tree,
patterns,
await globWithWorkspaceContext(tree.root, patterns)
);
}
function combineGlobResultsWithTree(
tree: Tree,
patterns: string[],
results: string[]
) {
const matches = new Set(results);
const combinedGlob = combineGlobPatterns(patterns);
const matcher = minimatch.makeRe(combinedGlob);

View File

@ -4,12 +4,8 @@ import { basename, join, relative } from 'path';
import {
buildProjectConfigurationFromPackageJson,
getGlobPatternsFromPackageManagerWorkspaces,
createNodes as packageJsonWorkspacesCreateNodes,
} from '../../plugins/package-json-workspaces';
import {
buildProjectFromProjectJson,
ProjectJsonProjectsPlugin,
} from '../../plugins/project-json/build-nodes/project-json';
import { buildProjectFromProjectJson } from '../../plugins/project-json/build-nodes/project-json';
import { renamePropertyWithStableKeys } from '../../adapter/angular-json';
import {
ProjectConfiguration,
@ -19,8 +15,7 @@ import {
mergeProjectConfigurationIntoRootMap,
readProjectConfigurationsFromRootMap,
} from '../../project-graph/utils/project-configuration-utils';
import { configurationGlobs } from '../../project-graph/utils/retrieve-workspace-files';
import { globWithWorkspaceContext } from '../../utils/workspace-context';
import { globWithWorkspaceContextSync } from '../../utils/workspace-context';
import { output } from '../../utils/output';
import { PackageJson } from '../../utils/package-json';
import { joinPathFragments, normalizePath } from '../../utils/path';
@ -28,7 +23,6 @@ import { readJson, writeJson } from './json';
import { readNxJson } from './nx-json';
import type { Tree } from '../tree';
import { NxPlugin } from '../../project-graph/plugins';
export { readNxJson, updateNxJson } from './nx-json';
@ -200,7 +194,7 @@ function readAndCombineAllProjectConfigurations(tree: Tree): {
readJson(tree, p, { expectComments: true })
),
];
const globbedFiles = globWithWorkspaceContext(tree.root, patterns);
const globbedFiles = globWithWorkspaceContextSync(tree.root, patterns);
const createdFiles = findCreatedProjectFiles(tree, patterns);
const deletedFiles = findDeletedProjectFiles(tree, patterns);
const projectFiles = [...globbedFiles, ...createdFiles].filter(

View File

@ -89,7 +89,7 @@ describe('explicit package json dependencies', () => {
const fileMap = createFileMap(
projectsConfigurations as any,
getAllFileDataInContext(tempFs.tempDir)
await getAllFileDataInContext(tempFs.tempDir)
).fileMap;
const builder = new ProjectGraphBuilder(undefined, fileMap.projectFileMap);

View File

@ -39,12 +39,7 @@ export async function createFileMapUsingProjectGraph(
): Promise<WorkspaceFileMap> {
const configs = readProjectsConfigurationFromProjectGraph(graph);
let files: FileData[];
if (daemonClient.enabled()) {
files = await daemonClient.getAllFileData();
} else {
files = getAllFileDataInContext(workspaceRoot);
}
let files: FileData[] = await getAllFileDataInContext(workspaceRoot);
return createFileMap(configs, files);
}

View File

@ -1,6 +1,6 @@
import { execSync } from 'child_process';
import { existsSync, readFileSync } from 'fs';
import { extname, join, relative, sep } from 'path';
import { basename, extname, join, relative, sep } from 'path';
import { readNxJson } from '../config/configuration';
import { FileData } from '../config/project-graph';
import {
@ -17,16 +17,18 @@ import {
} from './project-graph';
import { toOldFormat } from '../adapter/angular-json';
import { getIgnoreObject } from '../utils/ignore';
import { retrieveProjectConfigurationPaths } from './utils/retrieve-workspace-files';
import {
mergeProjectConfigurationIntoRootMap,
readProjectConfigurationsFromRootMap,
} from './utils/project-configuration-utils';
import { NxJsonConfiguration } from '../config/nx-json';
import { getDefaultPluginsSync } from '../utils/nx-plugin.deprecated';
import { minimatch } from 'minimatch';
import { CreateNodesResult } from '../devkit-exports';
import { PackageJsonProjectsNextToProjectJsonPlugin } from '../plugins/project-json/build-nodes/package-json-next-to-project-json';
import {
buildProjectConfigurationFromPackageJson,
getGlobPatternsFromPackageManagerWorkspaces,
} from '../plugins/package-json-workspaces';
import { globWithWorkspaceContextSync } from '../utils/workspace-context';
import { buildProjectFromProjectJson } from '../plugins/project-json/build-nodes/project-json';
import { PackageJson } from '../utils/package-json';
import { NxJsonConfiguration } from '../devkit-exports';
export interface Change {
type: string;
@ -151,7 +153,7 @@ export function readWorkspaceConfig(opts: {
} catch {
configuration = {
version: 2,
projects: getProjectsSyncNoInference(root, nxJson).projects,
projects: getProjectsSync(root, nxJson),
};
}
if (opts.format === 'angularCli') {
@ -179,50 +181,59 @@ export { FileData };
/**
* TODO(v20): Remove this function.
*/
function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) {
const allConfigFiles = retrieveProjectConfigurationPaths(
root,
getDefaultPluginsSync(root)
);
const plugins = [
PackageJsonProjectsNextToProjectJsonPlugin,
...getDefaultPluginsSync(root),
function getProjectsSync(
root: string,
nxJson: NxJsonConfiguration
): {
[name: string]: ProjectConfiguration;
} {
/**
* We can't update projects that come from plugins anyways, so we are going
* to ignore them for now. Plugins should add their own add/create/update methods
* if they would like to use devkit to update inferred projects.
*/
const patterns = [
'**/project.json',
'project.json',
...getGlobPatternsFromPackageManagerWorkspaces(root, readJsonFile),
];
const projectFiles = globWithWorkspaceContextSync(root, patterns);
const projectRootMap: Record<string, ProjectConfiguration> = {};
// We iterate over plugins first - this ensures that plugins specified first take precedence.
for (const plugin of plugins) {
const [pattern, createNodes] = plugin.createNodes ?? [];
if (!pattern) {
continue;
}
const matchingConfigFiles = allConfigFiles.filter((file) =>
minimatch(file, pattern, { dot: true })
const rootMap: Record<string, ProjectConfiguration> = {};
for (const projectFile of projectFiles) {
if (basename(projectFile) === 'project.json') {
const json = readJsonFile(projectFile);
const config = buildProjectFromProjectJson(json, projectFile);
mergeProjectConfigurationIntoRootMap(
rootMap,
config,
undefined,
undefined,
true
);
for (const file of matchingConfigFiles) {
if (minimatch(file, pattern, { dot: true })) {
let r = createNodes(
file,
{},
} else if (basename(projectFile) === 'package.json') {
const packageJson = readJsonFile<PackageJson>(projectFile);
const config = buildProjectConfigurationFromPackageJson(
packageJson,
projectFile,
nxJson
);
if (!rootMap[config.root]) {
mergeProjectConfigurationIntoRootMap(
rootMap,
// Inferred targets, tags, etc don't show up when running generators
// This is to help avoid running into issues when trying to update the workspace
{
nxJsonConfiguration: nxJson,
workspaceRoot: root,
configFiles: matchingConfigFiles,
}
) as CreateNodesResult;
for (const node in r.projects) {
const project = {
root: node,
...r.projects[node],
};
mergeProjectConfigurationIntoRootMap(projectRootMap, project);
}
name: config.name,
root: config.root,
},
undefined,
undefined,
true
);
}
}
}
return {
projects: readProjectConfigurationsFromRootMap(projectRootMap),
};
return readProjectConfigurationsFromRootMap(rootMap);
}

View File

@ -28,17 +28,23 @@ export function loadRemoteNxPlugin(
// but its typescript.
const isWorkerTypescript = path.extname(__filename) === '.ts';
const workerPath = path.join(__dirname, 'plugin-worker');
const worker = fork(workerPath, [], {
stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
env: {
const env: Record<string, string> = {
...process.env,
...(isWorkerTypescript
? {
// Ensures that the worker uses the same tsconfig as the main process
TS_NODE_PROJECT: path.join(__dirname, '../../../tsconfig.lib.json'),
TS_NODE_PROJECT: path.join(
__dirname,
'../../../../tsconfig.lib.json'
),
}
: {}),
},
};
const worker = fork(workerPath, [], {
stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
env,
execArgv: [
...process.execArgv,
// If the worker is typescript, we need to register ts-node

View File

@ -25,7 +25,7 @@ describe('retrieveProjectConfigurationPaths', () => {
})
);
const configPaths = retrieveProjectConfigurationPaths(fs.tempDir, [
const configPaths = await retrieveProjectConfigurationPaths(fs.tempDir, [
{
createNodes: [
'{project.json,**/project.json}',

View File

@ -38,7 +38,7 @@ export async function retrieveWorkspaceFiles(
performance.mark('get-workspace-files:start');
const { projectFileMap, globalFiles, externalReferences } =
getNxWorkspaceFilesFromContext(workspaceRoot, projectRootMap);
await getNxWorkspaceFilesFromContext(workspaceRoot, projectRootMap);
performance.mark('get-workspace-files:end');
performance.measure(
'get-workspace-files',
@ -60,13 +60,16 @@ export async function retrieveWorkspaceFiles(
* Walk through the workspace and return `ProjectConfigurations`. Only use this if the projectFileMap is not needed.
*/
export function retrieveProjectConfigurations(
export async function retrieveProjectConfigurations(
plugins: LoadedNxPlugin[],
workspaceRoot: string,
nxJson: NxJsonConfiguration
): Promise<ConfigurationResult> {
const globPatterns = configurationGlobs(plugins);
const workspaceFiles = globWithWorkspaceContext(workspaceRoot, globPatterns);
const workspaceFiles = await globWithWorkspaceContext(
workspaceRoot,
globPatterns
);
return createProjectConfigurations(
workspaceRoot,
@ -98,7 +101,11 @@ export async function retrieveProjectConfigurationsWithAngularProjects(
workspaceRoot
);
const res = retrieveProjectConfigurations(plugins, workspaceRoot, nxJson);
const res = await retrieveProjectConfigurations(
plugins,
workspaceRoot,
nxJson
);
cleanup();
return res;
}
@ -106,7 +113,7 @@ export async function retrieveProjectConfigurationsWithAngularProjects(
export function retrieveProjectConfigurationPaths(
root: string,
plugins: Array<{ createNodes?: readonly [string, ...unknown[]] } & unknown>
): string[] {
): Promise<string[]> {
const projectGlobPatterns = configurationGlobs(plugins);
return globWithWorkspaceContext(root, projectGlobPatterns);
}
@ -122,7 +129,10 @@ export async function retrieveProjectConfigurationsWithoutPluginInference(
): Promise<Record<string, ProjectConfiguration>> {
const nxJson = readNxJson(root);
const [plugins, cleanup] = await loadNxPlugins([]); // only load default plugins
const projectGlobPatterns = retrieveProjectConfigurationPaths(root, plugins);
const projectGlobPatterns = await retrieveProjectConfigurationPaths(
root,
plugins
);
const cacheKey = root + ',' + projectGlobPatterns.join(',');
if (projectsWithoutPluginCache.has(cacheKey)) {
@ -130,7 +140,7 @@ export async function retrieveProjectConfigurationsWithoutPluginInference(
}
const projectFiles =
globWithWorkspaceContext(root, projectGlobPatterns) ?? [];
(await globWithWorkspaceContext(root, projectGlobPatterns)) ?? [];
const { projects } = await createProjectConfigurations(
root,
nxJson,

View File

@ -1,12 +1,7 @@
import { FileData } from '../config/project-graph';
import { daemonClient } from '../daemon/client/client';
import { getAllFileDataInContext } from './workspace-context';
import { workspaceRoot } from './workspace-root';
export function allFileData(): Promise<FileData[]> {
if (daemonClient.enabled()) {
return daemonClient.getAllFileData();
} else {
return Promise.resolve(getAllFileDataInContext(workspaceRoot));
}
return getAllFileDataInContext(workspaceRoot);
}

View File

@ -1,6 +1,8 @@
import type { NxWorkspaceFilesExternals, WorkspaceContext } from '../native';
import { performance } from 'perf_hooks';
import { cacheDirectoryForWorkspace } from './cache-directory';
import { isOnDaemon } from '../daemon/is-on-daemon';
import { daemonClient } from '../daemon/client/client';
let workspaceContext: WorkspaceContext | undefined;
@ -20,15 +22,25 @@ export function setupWorkspaceContext(workspaceRoot: string) {
);
}
export function getNxWorkspaceFilesFromContext(
export async function getNxWorkspaceFilesFromContext(
workspaceRoot: string,
projectRootMap: Record<string, string>
) {
if (isOnDaemon() || !daemonClient.enabled()) {
ensureContextAvailable(workspaceRoot);
return workspaceContext.getWorkspaceFiles(projectRootMap);
}
return daemonClient.getWorkspaceFiles(projectRootMap);
}
export function globWithWorkspaceContext(
/**
* Sync method to get files matching globs from workspace context.
* NOTE: This method will create the workspace context if it doesn't exist.
* It should only be used within Nx internal in code paths that **must** be sync.
* If used in an isolated plugin thread this will cause the workspace context
* to be recreated which is slow.
*/
export function globWithWorkspaceContextSync(
workspaceRoot: string,
globs: string[],
exclude?: string[]
@ -37,14 +49,30 @@ export function globWithWorkspaceContext(
return workspaceContext.glob(globs, exclude);
}
export function hashWithWorkspaceContext(
export async function globWithWorkspaceContext(
workspaceRoot: string,
globs: string[],
exclude?: string[]
) {
if (isOnDaemon() || !daemonClient.enabled()) {
ensureContextAvailable(workspaceRoot);
return workspaceContext.glob(globs, exclude);
} else {
return daemonClient.glob(globs, exclude);
}
}
export async function hashWithWorkspaceContext(
workspaceRoot: string,
globs: string[],
exclude?: string[]
) {
if (isOnDaemon() || !daemonClient.enabled()) {
ensureContextAvailable(workspaceRoot);
return workspaceContext.hashFilesMatchingGlob(globs, exclude);
}
return daemonClient.hashGlob(globs, exclude);
}
export function updateFilesInContext(
updatedFiles: string[],
@ -53,18 +81,24 @@ export function updateFilesInContext(
return workspaceContext?.incrementalUpdate(updatedFiles, deletedFiles);
}
export function getAllFileDataInContext(workspaceRoot: string) {
export async function getAllFileDataInContext(workspaceRoot: string) {
if (isOnDaemon() || !daemonClient.enabled()) {
ensureContextAvailable(workspaceRoot);
return workspaceContext.allFileData();
}
return daemonClient.getWorkspaceContextFileData();
}
export function getFilesInDirectoryUsingContext(
export async function getFilesInDirectoryUsingContext(
workspaceRoot: string,
dir: string
) {
if (isOnDaemon() || !daemonClient.enabled()) {
ensureContextAvailable(workspaceRoot);
return workspaceContext.getFilesInDirectory(dir);
}
return daemonClient.getFilesInDirectory(dir);
}
export function updateProjectFiles(
projectRootMappings: Record<string, string>,

View File

@ -108,9 +108,12 @@ async function createNodesInternal(
const normalizedOptions = 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 buildPlaywrightTargets(
configFilePath,
@ -199,7 +202,7 @@ async function buildPlaywrightTargets(
playwrightConfig.testMatch ??= '**/*.@(spec|test).?(c|m)[jt]s?(x)';
const dependsOn: TargetConfiguration['dependsOn'] = [];
forEachTestFile(
await forEachTestFile(
(testFile) => {
const relativeSpecFilePath = normalizePath(
relative(projectRoot, testFile)
@ -246,7 +249,7 @@ async function buildPlaywrightTargets(
return { targets, metadata };
}
function forEachTestFile(
async function forEachTestFile(
cb: (path: string) => void,
opts: {
context: CreateNodesContext;
@ -254,7 +257,7 @@ function forEachTestFile(
config: PlaywrightTestConfig;
}
) {
const files = getFilesInDirectoryUsingContext(
const files = await getFilesInDirectoryUsingContext(
opts.context.workspaceRoot,
opts.path
);

View File

@ -70,9 +70,12 @@ export const createNodes: CreateNodes<ReactNativePluginOptions> = [
return {};
}
const hash = calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);
targetsCache[hash] ??= buildReactNativeTargets(
projectRoot,

View File

@ -66,9 +66,12 @@ export const createNodes: CreateNodes<RemixPluginOptions> = [
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 buildRemixTargets(
configFilePath,
projectRoot,

View File

@ -6,10 +6,39 @@ import {
describe('createWatchPaths', () => {
it('should list root paths of dependencies relative to project root', async () => {
const testDir = joinPathFragments(workspaceRoot, 'e2e/remix');
// This test is written based on the Nx repo's project graph.
jest
.spyOn(require('@nx/devkit'), 'createProjectGraphAsync')
.mockResolvedValue({
nodes: {
parent: {
type: 'app',
name: 'parent',
data: { root: 'apps/parent' },
},
lib: {
type: 'lib',
name: 'lib',
data: { root: 'packages/lib' },
},
example: {
type: 'app',
name: 'example',
data: { root: 'examples/example' },
},
},
dependencies: {
parent: [
{ type: 'static', source: 'parent', target: 'lib' },
{ type: 'static', source: 'parent', target: 'example' },
],
example: [{ type: 'static', source: 'example', target: 'lib' }],
},
});
const testDir = joinPathFragments(workspaceRoot, 'apps/parent');
const paths = await createWatchPaths(testDir);
expect(paths).toEqual(['../../packages', '../../graph', '../../e2e/utils']);
expect(paths).toEqual(['../../packages', '../../examples']);
});
});

View File

@ -63,9 +63,12 @@ export const createNodes: CreateNodes<RollupPluginOptions> = [
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 buildRollupTarget(
configFilePath,

View File

@ -72,9 +72,12 @@ export const createNodes: CreateNodes<StorybookPluginOptions> = [
}
options = normalizeOptions(options);
const hash = calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);
const projectName = buildProjectName(projectRoot, context.workspaceRoot);

View File

@ -44,9 +44,10 @@ describe('@nx/vite/plugin', () => {
production: ['!{projectRoot}/**/*.spec.ts'],
},
},
workspaceRoot: '',
workspaceRoot: tempFs.tempDir,
};
tempFs.createFileSync('index.html', '');
tempFs.createFileSync('package.json', '');
});
afterEach(() => {

View File

@ -6,7 +6,6 @@ import {
joinPathFragments,
readJsonFile,
TargetConfiguration,
workspaceRoot,
writeJsonFile,
} from '@nx/devkit';
import { dirname, isAbsolute, join, relative } from 'path';
@ -118,7 +117,8 @@ async function buildViteTargets(
const { buildOutputs, testOutputs, hasTest, isBuildable } = getOutputs(
viteConfig,
projectRoot
projectRoot,
context.workspaceRoot
);
const namedInputs = getNamedInputs(projectRoot, context);
@ -244,7 +244,8 @@ function serveStaticTarget(options: VitePluginOptions) {
function getOutputs(
viteConfig: Record<string, any> | undefined,
projectRoot: string
projectRoot: string,
workspaceRoot: string
): {
buildOutputs: string[];
testOutputs: string[];
@ -256,6 +257,7 @@ function getOutputs(
const buildOutputPath = normalizeOutputPath(
build?.outDir,
projectRoot,
workspaceRoot,
'dist'
);
@ -267,6 +269,7 @@ function getOutputs(
const reportsDirectoryPath = normalizeOutputPath(
test?.coverage?.reportsDirectory,
projectRoot,
workspaceRoot,
'coverage'
);
@ -281,6 +284,7 @@ function getOutputs(
function normalizeOutputPath(
outputPath: string | undefined,
projectRoot: string,
workspaceRoot: string,
path: 'coverage' | 'dist'
): string | undefined {
if (!outputPath) {

View File

@ -69,9 +69,12 @@ export const createNodes: CreateNodes<WebpackPluginOptions> = [
return {};
}
const hash = calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);
const hash = await calculateHashForCreateNodes(
projectRoot,
options,
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);
const targets = targetsCache[hash]
? targetsCache[hash]
: await createWebpackTargets(

View File

@ -0,0 +1,34 @@
module.exports = () => {
/**
* When the daemon is enabled during unit tests,
* and the daemon is already running, the daemon-client.ts
* code will be used, but it will hit the already running
* daemon which is from the installed version of Nx.
*
* In the vast majority of cases, this is fine. However,
* if a new message type has been added to the daemon in
* the source code, and isn't yet in the installed version,
* any test that hits that codepath will fail. This is because
* the installed version of the daemon doesn't know how to
* handle the new message type.
*
* To prevent this, we disable the daemon during unit tests.
*/
process.env.NX_DAEMON = 'false';
/**
* When `createProjectGraphAsync` is called during tests,
* if its not mocked, it will return the Nx repo's project
* graph. We don't want any unit tests to depend on the structure
* of the Nx repo, so we mock it to return an empty project graph.
*/
jest.doMock('@nx/devkit', () => ({
...jest.requireActual('@nx/devkit'),
createProjectGraphAsync: jest.fn().mockImplementation(async () => {
return {
nodes: {},
dependencies: {},
};
}),
}));
};

View File

@ -1,5 +1,13 @@
const nxPreset = require('@nx/jest/preset').default;
/* eslint-disable */
export default {
...nxPreset,
testTimeout: 35000,
testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
coverageReporters: ['html'],
maxWorkers: 1,
testEnvironment: 'node',
displayName: 'typedoc-theme',
globals: {},
@ -14,5 +22,5 @@ export default {
resolver: '../scripts/patched-jest-resolver.js',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../coverage/typedoc-theme',
preset: '../jest.preset.js',
setupFiles: [],
};