feat(core): add multi hash fn (#29935)
Adds function to compute multiple glob hashes in native code at the same time, greatly speeding up certain plugin performance.
This commit is contained in:
parent
2ebdd2e5a2
commit
c2e89f87b5
@ -11,8 +11,8 @@
|
|||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
| Name | Type |
|
| Name | Type |
|
||||||
| :------------ | :------------------------------------------------------------------------- |
|
| :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `createNodes` | [`CreateNodesFunction`](../../devkit/documents/CreateNodesFunction)\<`T`\> |
|
| `createNodes` | (`projectConfigurationFile`: `string`, `options`: `T`, `context`: [`CreateNodesContext`](../../devkit/documents/CreateNodesContext), `idx`: `number`) => [`CreateNodesResult`](../../devkit/documents/CreateNodesResult) \| `Promise`\<[`CreateNodesResult`](../../devkit/documents/CreateNodesResult)\> |
|
||||||
| `configFiles` | readonly `string`[] |
|
| `configFiles` | readonly `string`[] |
|
||||||
| `options` | `T` |
|
| `options` | `T` |
|
||||||
| `context` | [`CreateNodesContextV2`](../../devkit/documents/CreateNodesContextV2) |
|
| `context` | [`CreateNodesContextV2`](../../devkit/documents/CreateNodesContextV2) |
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
# Function: isDaemonEnabled
|
# Function: isDaemonEnabled
|
||||||
|
|
||||||
▸ **isDaemonEnabled**(): `boolean`
|
▸ **isDaemonEnabled**(`nxJson?`): `boolean`
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
| Name | Type |
|
||||||
|
| :------- | :----------------------------------------------------------------------------------------- |
|
||||||
|
| `nxJson` | [`NxJsonConfiguration`](../../devkit/documents/NxJsonConfiguration)\<`string`[] \| `"*"`\> |
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,11 @@ import {
|
|||||||
hashArray,
|
hashArray,
|
||||||
} from 'nx/src/devkit-exports';
|
} from 'nx/src/devkit-exports';
|
||||||
|
|
||||||
import { hashObject, hashWithWorkspaceContext } from 'nx/src/devkit-internals';
|
import {
|
||||||
|
hashMultiGlobWithWorkspaceContext,
|
||||||
|
hashObject,
|
||||||
|
hashWithWorkspaceContext,
|
||||||
|
} from 'nx/src/devkit-internals';
|
||||||
|
|
||||||
export async function calculateHashForCreateNodes(
|
export async function calculateHashForCreateNodes(
|
||||||
projectRoot: string,
|
projectRoot: string,
|
||||||
@ -21,3 +25,28 @@ export async function calculateHashForCreateNodes(
|
|||||||
hashObject(options),
|
hashObject(options),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function calculateHashesForCreateNodes(
|
||||||
|
projectRoots: string[],
|
||||||
|
options: object,
|
||||||
|
context: CreateNodesContext | CreateNodesContextV2,
|
||||||
|
additionalGlobs: string[][] = []
|
||||||
|
): Promise<string[]> {
|
||||||
|
if (
|
||||||
|
additionalGlobs.length &&
|
||||||
|
additionalGlobs.length !== projectRoots.length
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'If additionalGlobs is provided, it must be the same length as projectRoots'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return hashMultiGlobWithWorkspaceContext(
|
||||||
|
context.workspaceRoot,
|
||||||
|
projectRoots.map((projectRoot, idx) => [
|
||||||
|
join(projectRoot, '**/*'),
|
||||||
|
...(additionalGlobs.length ? additionalGlobs[idx] : []),
|
||||||
|
])
|
||||||
|
).then((hashes) => {
|
||||||
|
return hashes.map((hash) => hashArray([hash, hashObject(options)]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -11,7 +11,10 @@ import {
|
|||||||
TargetConfiguration,
|
TargetConfiguration,
|
||||||
writeJsonFile,
|
writeJsonFile,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
import {
|
||||||
|
calculateHashesForCreateNodes,
|
||||||
|
calculateHashForCreateNodes,
|
||||||
|
} from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import { basename, dirname, join, normalize, sep } from 'node:path/posix';
|
import { basename, dirname, join, normalize, sep } from 'node:path/posix';
|
||||||
import { hashObject } from 'nx/src/hasher/file-hasher';
|
import { hashObject } from 'nx/src/hasher/file-hasher';
|
||||||
@ -186,10 +189,10 @@ const internalCreateNodesV2 = async (
|
|||||||
configFilePath: string,
|
configFilePath: string,
|
||||||
options: EslintPluginOptions,
|
options: EslintPluginOptions,
|
||||||
context: CreateNodesContextV2,
|
context: CreateNodesContextV2,
|
||||||
eslintConfigFiles: string[],
|
|
||||||
projectRootsByEslintRoots: Map<string, string[]>,
|
projectRootsByEslintRoots: Map<string, string[]>,
|
||||||
lintableFilesPerProjectRoot: Map<string, string[]>,
|
lintableFilesPerProjectRoot: Map<string, string[]>,
|
||||||
projectsCache: Record<string, CreateNodesResult['projects']>
|
projectsCache: Record<string, CreateNodesResult['projects']>,
|
||||||
|
hashByRoot: Map<string, string>
|
||||||
): Promise<CreateNodesResult> => {
|
): Promise<CreateNodesResult> => {
|
||||||
const configDir = dirname(configFilePath);
|
const configDir = dirname(configFilePath);
|
||||||
|
|
||||||
@ -201,19 +204,7 @@ const internalCreateNodesV2 = async (
|
|||||||
const projects: CreateNodesResult['projects'] = {};
|
const projects: CreateNodesResult['projects'] = {};
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
projectRootsByEslintRoots.get(configDir).map(async (projectRoot) => {
|
projectRootsByEslintRoots.get(configDir).map(async (projectRoot) => {
|
||||||
const parentConfigs = eslintConfigFiles.filter((eslintConfig) =>
|
const hash = hashByRoot.get(projectRoot);
|
||||||
isSubDir(projectRoot, dirname(eslintConfig))
|
|
||||||
);
|
|
||||||
const hash = await calculateHashForCreateNodes(
|
|
||||||
projectRoot,
|
|
||||||
options,
|
|
||||||
{
|
|
||||||
configFiles: eslintConfigFiles,
|
|
||||||
nxJsonConfiguration: context.nxJsonConfiguration,
|
|
||||||
workspaceRoot: context.workspaceRoot,
|
|
||||||
},
|
|
||||||
[...parentConfigs, join(projectRoot, '.eslintignore')]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (projectsCache[hash]) {
|
if (projectsCache[hash]) {
|
||||||
// We can reuse the projects in the cache.
|
// We can reuse the projects in the cache.
|
||||||
@ -280,6 +271,20 @@ export const createNodesV2: CreateNodesV2<EslintPluginOptions> = [
|
|||||||
options,
|
options,
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
|
const hashes = await calculateHashesForCreateNodes(
|
||||||
|
projectRoots,
|
||||||
|
options,
|
||||||
|
context,
|
||||||
|
projectRoots.map((root) => {
|
||||||
|
const parentConfigs = eslintConfigFiles.filter((eslintConfig) =>
|
||||||
|
isSubDir(root, dirname(eslintConfig))
|
||||||
|
);
|
||||||
|
return [...parentConfigs, join(root, '.eslintignore')];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const hashByRoot = new Map<string, string>(
|
||||||
|
projectRoots.map((r, i) => [r, hashes[i]])
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
return await createNodesFromFiles(
|
return await createNodesFromFiles(
|
||||||
(configFile, options, context) =>
|
(configFile, options, context) =>
|
||||||
@ -287,10 +292,10 @@ export const createNodesV2: CreateNodesV2<EslintPluginOptions> = [
|
|||||||
configFile,
|
configFile,
|
||||||
options,
|
options,
|
||||||
context,
|
context,
|
||||||
eslintConfigFiles,
|
|
||||||
projectRootsByEslintRoots,
|
projectRootsByEslintRoots,
|
||||||
lintableFilesPerProjectRoot,
|
lintableFilesPerProjectRoot,
|
||||||
targetsCache
|
targetsCache,
|
||||||
|
hashByRoot
|
||||||
),
|
),
|
||||||
eslintConfigFiles,
|
eslintConfigFiles,
|
||||||
options,
|
options,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
CreateNodes,
|
CreateNodes,
|
||||||
CreateNodesContext,
|
CreateNodesContext,
|
||||||
|
CreateNodesContextV2,
|
||||||
createNodesFromFiles,
|
createNodesFromFiles,
|
||||||
CreateNodesV2,
|
CreateNodesV2,
|
||||||
getPackageManagerCommand,
|
getPackageManagerCommand,
|
||||||
@ -13,7 +14,10 @@ import {
|
|||||||
TargetConfiguration,
|
TargetConfiguration,
|
||||||
writeJsonFile,
|
writeJsonFile,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
import {
|
||||||
|
calculateHashesForCreateNodes,
|
||||||
|
calculateHashForCreateNodes,
|
||||||
|
} from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
||||||
import {
|
import {
|
||||||
clearRequireCache,
|
clearRequireCache,
|
||||||
loadConfigFile,
|
loadConfigFile,
|
||||||
@ -72,88 +76,49 @@ export const createNodesV2: CreateNodesV2<JestPluginOptions> = [
|
|||||||
// Cache jest preset(s) to avoid penalties of module load times. Most of jest configs will use the same preset.
|
// Cache jest preset(s) to avoid penalties of module load times. Most of jest configs will use the same preset.
|
||||||
const presetCache: Record<string, unknown> = {};
|
const presetCache: Record<string, unknown> = {};
|
||||||
|
|
||||||
try {
|
|
||||||
return await createNodesFromFiles(
|
|
||||||
(configFile, options, context) =>
|
|
||||||
createNodesInternal(
|
|
||||||
configFile,
|
|
||||||
options,
|
|
||||||
context,
|
|
||||||
targetsCache,
|
|
||||||
presetCache
|
|
||||||
),
|
|
||||||
configFiles,
|
|
||||||
options,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
writeTargetsToCache(cachePath, targetsCache);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated This is replaced with {@link createNodesV2}. Update your plugin to export its own `createNodesV2` function that wraps this one instead.
|
|
||||||
* This function will change to the v2 function in Nx 20.
|
|
||||||
*/
|
|
||||||
export const createNodes: CreateNodes<JestPluginOptions> = [
|
|
||||||
jestConfigGlob,
|
|
||||||
(...args) => {
|
|
||||||
logger.warn(
|
|
||||||
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
|
|
||||||
);
|
|
||||||
|
|
||||||
return createNodesInternal(...args, {}, {});
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
async function createNodesInternal(
|
|
||||||
configFilePath: string,
|
|
||||||
options: JestPluginOptions,
|
|
||||||
context: CreateNodesContext,
|
|
||||||
targetsCache: Record<string, JestTargets>,
|
|
||||||
presetCache: Record<string, unknown>
|
|
||||||
) {
|
|
||||||
const projectRoot = dirname(configFilePath);
|
|
||||||
|
|
||||||
const packageManagerWorkspacesGlob = combineGlobPatterns(
|
const packageManagerWorkspacesGlob = combineGlobPatterns(
|
||||||
getGlobPatternsFromPackageManagerWorkspaces(context.workspaceRoot)
|
getGlobPatternsFromPackageManagerWorkspaces(context.workspaceRoot)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Do not create a project if package.json and project.json isn't there.
|
|
||||||
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
|
|
||||||
if (
|
|
||||||
!siblingFiles.includes('package.json') &&
|
|
||||||
!siblingFiles.includes('project.json')
|
|
||||||
) {
|
|
||||||
return {};
|
|
||||||
} else if (
|
|
||||||
!siblingFiles.includes('project.json') &&
|
|
||||||
siblingFiles.includes('package.json')
|
|
||||||
) {
|
|
||||||
const path = joinPathFragments(projectRoot, 'package.json');
|
|
||||||
|
|
||||||
const isPackageJsonProject = minimatch(path, packageManagerWorkspacesGlob);
|
|
||||||
|
|
||||||
if (!isPackageJsonProject) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const jestConfigContent = readFileSync(
|
|
||||||
resolve(context.workspaceRoot, configFilePath),
|
|
||||||
'utf-8'
|
|
||||||
);
|
|
||||||
if (jestConfigContent.includes('getJestProjectsAsync()')) {
|
|
||||||
// The `getJestProjectsAsync` function uses the project graph, which leads to a
|
|
||||||
// circular dependency. We can skip this since it's no intended to be used for
|
|
||||||
// an Nx project.
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
options = normalizeOptions(options);
|
options = normalizeOptions(options);
|
||||||
|
|
||||||
const hash = await calculateHashForCreateNodes(projectRoot, options, context);
|
const { roots: projectRoots, configFiles: validConfigFiles } =
|
||||||
|
configFiles.reduce(
|
||||||
|
(acc, configFile) => {
|
||||||
|
const potentialRoot = dirname(configFile);
|
||||||
|
if (
|
||||||
|
checkIfConfigFileShouldBeProject(
|
||||||
|
configFile,
|
||||||
|
potentialRoot,
|
||||||
|
packageManagerWorkspacesGlob,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
acc.roots.push(potentialRoot);
|
||||||
|
acc.configFiles.push(configFile);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
roots: [],
|
||||||
|
configFiles: [],
|
||||||
|
} as {
|
||||||
|
roots: string[];
|
||||||
|
configFiles: string[];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const hashes = await calculateHashesForCreateNodes(
|
||||||
|
projectRoots,
|
||||||
|
options,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await createNodesFromFiles(
|
||||||
|
async (configFilePath, options, context, idx) => {
|
||||||
|
const projectRoot = projectRoots[idx];
|
||||||
|
const hash = hashes[idx];
|
||||||
|
|
||||||
targetsCache[hash] ??= await buildJestTargets(
|
targetsCache[hash] ??= await buildJestTargets(
|
||||||
configFilePath,
|
configFilePath,
|
||||||
projectRoot,
|
projectRoot,
|
||||||
@ -173,6 +138,104 @@ async function createNodesInternal(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
validConfigFiles,
|
||||||
|
options,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
writeTargetsToCache(cachePath, targetsCache);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This is replaced with {@link createNodesV2}. Update your plugin to export its own `createNodesV2` function that wraps this one instead.
|
||||||
|
* This function will change to the v2 function in Nx 20.
|
||||||
|
*/
|
||||||
|
export const createNodes: CreateNodes<JestPluginOptions> = [
|
||||||
|
jestConfigGlob,
|
||||||
|
async (configFilePath, options, context) => {
|
||||||
|
logger.warn(
|
||||||
|
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
|
||||||
|
);
|
||||||
|
|
||||||
|
const projectRoot = dirname(configFilePath);
|
||||||
|
|
||||||
|
const packageManagerWorkspacesGlob = combineGlobPatterns(
|
||||||
|
getGlobPatternsFromPackageManagerWorkspaces(context.workspaceRoot)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!checkIfConfigFileShouldBeProject(
|
||||||
|
configFilePath,
|
||||||
|
projectRoot,
|
||||||
|
packageManagerWorkspacesGlob,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
options = normalizeOptions(options);
|
||||||
|
|
||||||
|
const { targets, metadata } = await buildJestTargets(
|
||||||
|
configFilePath,
|
||||||
|
projectRoot,
|
||||||
|
options,
|
||||||
|
context,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
projects: {
|
||||||
|
[projectRoot]: {
|
||||||
|
root: projectRoot,
|
||||||
|
targets,
|
||||||
|
metadata,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function checkIfConfigFileShouldBeProject(
|
||||||
|
configFilePath: string,
|
||||||
|
projectRoot: string,
|
||||||
|
packageManagerWorkspacesGlob: string,
|
||||||
|
context: CreateNodesContext | CreateNodesContextV2
|
||||||
|
): boolean {
|
||||||
|
// Do not create a project if package.json and project.json isn't there.
|
||||||
|
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
|
||||||
|
if (
|
||||||
|
!siblingFiles.includes('package.json') &&
|
||||||
|
!siblingFiles.includes('project.json')
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
} else if (
|
||||||
|
!siblingFiles.includes('project.json') &&
|
||||||
|
siblingFiles.includes('package.json')
|
||||||
|
) {
|
||||||
|
const path = joinPathFragments(projectRoot, 'package.json');
|
||||||
|
|
||||||
|
const isPackageJsonProject = minimatch(path, packageManagerWorkspacesGlob);
|
||||||
|
|
||||||
|
if (!isPackageJsonProject) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const jestConfigContent = readFileSync(
|
||||||
|
resolve(context.workspaceRoot, configFilePath),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
if (jestConfigContent.includes('getJestProjectsAsync()')) {
|
||||||
|
// The `getJestProjectsAsync` function uses the project graph, which leads to a
|
||||||
|
// circular dependency. We can skip this since it's no intended to be used for
|
||||||
|
// an Nx project.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildJestTargets(
|
async function buildJestTargets(
|
||||||
|
|||||||
@ -2,15 +2,16 @@ import { existsSync } from 'fs';
|
|||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
|
|
||||||
import type { ChangelogRenderOptions } from '../../release/changelog-renderer';
|
import type { ChangelogRenderOptions } from '../../release/changelog-renderer';
|
||||||
import { readJsonFile } from '../utils/fileutils';
|
import type { PackageManager } from '../utils/package-manager';
|
||||||
import { PackageManager } from '../utils/package-manager';
|
import type {
|
||||||
import { workspaceRoot } from '../utils/workspace-root';
|
|
||||||
import {
|
|
||||||
InputDefinition,
|
InputDefinition,
|
||||||
TargetConfiguration,
|
TargetConfiguration,
|
||||||
TargetDependencyConfig,
|
TargetDependencyConfig,
|
||||||
} from './workspace-json-project-json';
|
} from './workspace-json-project-json';
|
||||||
|
|
||||||
|
import { readJsonFile } from '../utils/fileutils';
|
||||||
|
import { workspaceRoot } from '../utils/workspace-root';
|
||||||
|
|
||||||
export type ImplicitDependencyEntry<T = '*' | string[]> = {
|
export type ImplicitDependencyEntry<T = '*' | string[]> = {
|
||||||
[key: string]: T | ImplicitJsonSubsetDependency<T>;
|
[key: string]: T | ImplicitJsonSubsetDependency<T>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import { getFullOsSocketPath, killSocketOrPath } from '../socket-utils';
|
|||||||
import {
|
import {
|
||||||
DAEMON_DIR_FOR_CURRENT_WORKSPACE,
|
DAEMON_DIR_FOR_CURRENT_WORKSPACE,
|
||||||
DAEMON_OUTPUT_LOG_FILE,
|
DAEMON_OUTPUT_LOG_FILE,
|
||||||
isDaemonDisabled,
|
|
||||||
removeSocketDir,
|
removeSocketDir,
|
||||||
} from '../tmp-dir';
|
} from '../tmp-dir';
|
||||||
import { FileData, ProjectGraph } from '../../config/project-graph';
|
import { FileData, ProjectGraph } from '../../config/project-graph';
|
||||||
@ -50,7 +49,12 @@ import {
|
|||||||
GET_FILES_IN_DIRECTORY,
|
GET_FILES_IN_DIRECTORY,
|
||||||
HandleGetFilesInDirectoryMessage,
|
HandleGetFilesInDirectoryMessage,
|
||||||
} from '../message-types/get-files-in-directory';
|
} from '../message-types/get-files-in-directory';
|
||||||
import { HASH_GLOB, HandleHashGlobMessage } from '../message-types/hash-glob';
|
import {
|
||||||
|
HASH_GLOB,
|
||||||
|
HASH_MULTI_GLOB,
|
||||||
|
HandleHashGlobMessage,
|
||||||
|
HandleHashMultiGlobMessage,
|
||||||
|
} from '../message-types/hash-glob';
|
||||||
import {
|
import {
|
||||||
GET_ESTIMATED_TASK_TIMINGS,
|
GET_ESTIMATED_TASK_TIMINGS,
|
||||||
GET_FLAKY_TASKS,
|
GET_FLAKY_TASKS,
|
||||||
@ -91,6 +95,7 @@ import {
|
|||||||
POST_TASKS_EXECUTION,
|
POST_TASKS_EXECUTION,
|
||||||
PRE_TASKS_EXECUTION,
|
PRE_TASKS_EXECUTION,
|
||||||
} from '../message-types/run-tasks-execution-hooks';
|
} from '../message-types/run-tasks-execution-hooks';
|
||||||
|
import { isDaemonEnabled } from './enabled';
|
||||||
|
|
||||||
const DAEMON_ENV_SETTINGS = {
|
const DAEMON_ENV_SETTINGS = {
|
||||||
NX_PROJECT_GLOB_CACHE: 'false',
|
NX_PROJECT_GLOB_CACHE: 'false',
|
||||||
@ -136,48 +141,7 @@ export class DaemonClient {
|
|||||||
private _err: FileHandle = null;
|
private _err: FileHandle = null;
|
||||||
|
|
||||||
enabled() {
|
enabled() {
|
||||||
if (this._enabled === undefined) {
|
return isDaemonEnabled(this.nxJson);
|
||||||
const useDaemonProcessOption = this.nxJson?.useDaemonProcess;
|
|
||||||
const env = process.env.NX_DAEMON;
|
|
||||||
|
|
||||||
// env takes precedence
|
|
||||||
// option=true,env=false => no daemon
|
|
||||||
// option=false,env=undefined => no daemon
|
|
||||||
// option=false,env=false => no daemon
|
|
||||||
|
|
||||||
// option=undefined,env=undefined => daemon
|
|
||||||
// option=true,env=true => daemon
|
|
||||||
// option=false,env=true => daemon
|
|
||||||
|
|
||||||
// CI=true,env=undefined => no daemon
|
|
||||||
// CI=true,env=false => no daemon
|
|
||||||
// CI=true,env=true => daemon
|
|
||||||
|
|
||||||
// docker=true,env=undefined => no daemon
|
|
||||||
// docker=true,env=false => no daemon
|
|
||||||
// docker=true,env=true => daemon
|
|
||||||
// WASM => no daemon because file watching does not work
|
|
||||||
if (
|
|
||||||
((isCI() || isDocker()) && env !== 'true') ||
|
|
||||||
isDaemonDisabled() ||
|
|
||||||
nxJsonIsNotPresent() ||
|
|
||||||
(useDaemonProcessOption === undefined && env === 'false') ||
|
|
||||||
(useDaemonProcessOption === true && env === 'false') ||
|
|
||||||
(useDaemonProcessOption === false && env === undefined) ||
|
|
||||||
(useDaemonProcessOption === false && env === 'false')
|
|
||||||
) {
|
|
||||||
this._enabled = false;
|
|
||||||
} else if (IS_WASM) {
|
|
||||||
output.warn({
|
|
||||||
title:
|
|
||||||
'The Nx Daemon is unsupported in WebAssembly environments. Some things may be slower than or not function as expected.',
|
|
||||||
});
|
|
||||||
this._enabled = false;
|
|
||||||
} else {
|
|
||||||
this._enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this._enabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
@ -385,6 +349,14 @@ export class DaemonClient {
|
|||||||
return this.sendToDaemonViaQueue(message);
|
return this.sendToDaemonViaQueue(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hashMultiGlob(globGroups: string[][]): Promise<string[]> {
|
||||||
|
const message: HandleHashMultiGlobMessage = {
|
||||||
|
type: HASH_MULTI_GLOB,
|
||||||
|
globGroups: globGroups,
|
||||||
|
};
|
||||||
|
return this.sendToDaemonViaQueue(message);
|
||||||
|
}
|
||||||
|
|
||||||
getFlakyTasks(hashes: string[]): Promise<string[]> {
|
getFlakyTasks(hashes: string[]): Promise<string[]> {
|
||||||
const message: HandleGetFlakyTasks = {
|
const message: HandleGetFlakyTasks = {
|
||||||
type: GET_FLAKY_TASKS,
|
type: GET_FLAKY_TASKS,
|
||||||
@ -705,27 +677,6 @@ export class DaemonClient {
|
|||||||
|
|
||||||
export const daemonClient = new DaemonClient();
|
export const daemonClient = new DaemonClient();
|
||||||
|
|
||||||
export function isDaemonEnabled() {
|
|
||||||
return daemonClient.enabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDocker() {
|
|
||||||
try {
|
|
||||||
statSync('/.dockerenv');
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
try {
|
|
||||||
return readFileSync('/proc/self/cgroup', 'utf8')?.includes('docker');
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function nxJsonIsNotPresent() {
|
|
||||||
return !hasNxJson(workspaceRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
function daemonProcessException(message: string) {
|
function daemonProcessException(message: string) {
|
||||||
try {
|
try {
|
||||||
let log = readFileSync(DAEMON_OUTPUT_LOG_FILE).toString().split('\n');
|
let log = readFileSync(DAEMON_OUTPUT_LOG_FILE).toString().split('\n');
|
||||||
|
|||||||
79
packages/nx/src/daemon/client/enabled.ts
Normal file
79
packages/nx/src/daemon/client/enabled.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
hasNxJson,
|
||||||
|
readNxJson,
|
||||||
|
type NxJsonConfiguration,
|
||||||
|
} from '../../config/nx-json';
|
||||||
|
|
||||||
|
import { readFileSync, statSync } from 'node:fs';
|
||||||
|
|
||||||
|
import { isCI } from '../../utils/is-ci';
|
||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
import { isDaemonDisabled } from '../tmp-dir';
|
||||||
|
|
||||||
|
let _enabled: boolean | undefined;
|
||||||
|
|
||||||
|
export function isDaemonEnabled(nxJson: NxJsonConfiguration = readNxJson()) {
|
||||||
|
if (_enabled === undefined) {
|
||||||
|
const useDaemonProcessOption = nxJson?.useDaemonProcess;
|
||||||
|
const env = process.env.NX_DAEMON;
|
||||||
|
|
||||||
|
// env takes precedence
|
||||||
|
// option=true,env=false => no daemon
|
||||||
|
// option=false,env=undefined => no daemon
|
||||||
|
// option=false,env=false => no daemon
|
||||||
|
|
||||||
|
// option=undefined,env=undefined => daemon
|
||||||
|
// option=true,env=true => daemon
|
||||||
|
// option=false,env=true => daemon
|
||||||
|
|
||||||
|
// CI=true,env=undefined => no daemon
|
||||||
|
// CI=true,env=false => no daemon
|
||||||
|
// CI=true,env=true => daemon
|
||||||
|
|
||||||
|
// docker=true,env=undefined => no daemon
|
||||||
|
// docker=true,env=false => no daemon
|
||||||
|
// docker=true,env=true => daemon
|
||||||
|
// WASM => no daemon because file watching does not work
|
||||||
|
if (
|
||||||
|
((isCI() || isDocker()) && env !== 'true') ||
|
||||||
|
isDaemonDisabled() ||
|
||||||
|
nxJsonIsNotPresent() ||
|
||||||
|
(useDaemonProcessOption === undefined && env === 'false') ||
|
||||||
|
(useDaemonProcessOption === true && env === 'false') ||
|
||||||
|
(useDaemonProcessOption === false && env === undefined) ||
|
||||||
|
(useDaemonProcessOption === false && env === 'false')
|
||||||
|
) {
|
||||||
|
_enabled = false;
|
||||||
|
} else if (
|
||||||
|
(require('../../native') as typeof import('../../native')).IS_WASM
|
||||||
|
) {
|
||||||
|
(
|
||||||
|
require('../../utils/output') as typeof import('../../utils/output')
|
||||||
|
).output.warn({
|
||||||
|
title:
|
||||||
|
'The Nx Daemon is unsupported in WebAssembly environments. Some things may be slower than or not function as expected.',
|
||||||
|
});
|
||||||
|
_enabled = false;
|
||||||
|
} else {
|
||||||
|
_enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDocker() {
|
||||||
|
try {
|
||||||
|
statSync('/.dockerenv');
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
return readFileSync('/proc/self/cgroup', 'utf8')?.includes('docker');
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nxJsonIsNotPresent() {
|
||||||
|
return !hasNxJson(workspaceRoot);
|
||||||
|
}
|
||||||
@ -16,3 +16,21 @@ export function isHandleHashGlobMessage(
|
|||||||
message['type'] === HASH_GLOB
|
message['type'] === HASH_GLOB
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HASH_MULTI_GLOB = 'HASH_MULTI_GLOB' as const;
|
||||||
|
|
||||||
|
export type HandleHashMultiGlobMessage = {
|
||||||
|
type: typeof HASH_MULTI_GLOB;
|
||||||
|
globGroups: string[][];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isHandleHashMultiGlobMessage(
|
||||||
|
message: unknown
|
||||||
|
): message is HandleHashMultiGlobMessage {
|
||||||
|
return (
|
||||||
|
typeof message === 'object' &&
|
||||||
|
message !== null &&
|
||||||
|
'type' in message &&
|
||||||
|
message['type'] === HASH_MULTI_GLOB
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import { workspaceRoot } from '../../utils/workspace-root';
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
import { hashWithWorkspaceContext } from '../../utils/workspace-context';
|
import {
|
||||||
|
hashMultiGlobWithWorkspaceContext,
|
||||||
|
hashWithWorkspaceContext,
|
||||||
|
} from '../../utils/workspace-context';
|
||||||
import { HandlerResult } from './server';
|
import { HandlerResult } from './server';
|
||||||
|
|
||||||
export async function handleHashGlob(
|
export async function handleHashGlob(
|
||||||
@ -12,3 +15,13 @@ export async function handleHashGlob(
|
|||||||
description: 'handleHashGlob',
|
description: 'handleHashGlob',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function handleHashMultiGlob(
|
||||||
|
globs: string[][]
|
||||||
|
): Promise<HandlerResult> {
|
||||||
|
const files = await hashMultiGlobWithWorkspaceContext(workspaceRoot, globs);
|
||||||
|
return {
|
||||||
|
response: JSON.stringify(files),
|
||||||
|
description: 'handleHashMultiGlob',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -77,8 +77,12 @@ import {
|
|||||||
isHandleGetFilesInDirectoryMessage,
|
isHandleGetFilesInDirectoryMessage,
|
||||||
} from '../message-types/get-files-in-directory';
|
} from '../message-types/get-files-in-directory';
|
||||||
import { handleGetFilesInDirectory } from './handle-get-files-in-directory';
|
import { handleGetFilesInDirectory } from './handle-get-files-in-directory';
|
||||||
import { HASH_GLOB, isHandleHashGlobMessage } from '../message-types/hash-glob';
|
import {
|
||||||
import { handleHashGlob } from './handle-hash-glob';
|
HASH_GLOB,
|
||||||
|
isHandleHashGlobMessage,
|
||||||
|
isHandleHashMultiGlobMessage,
|
||||||
|
} from '../message-types/hash-glob';
|
||||||
|
import { handleHashGlob, handleHashMultiGlob } from './handle-hash-glob';
|
||||||
import {
|
import {
|
||||||
GET_ESTIMATED_TASK_TIMINGS,
|
GET_ESTIMATED_TASK_TIMINGS,
|
||||||
GET_FLAKY_TASKS,
|
GET_FLAKY_TASKS,
|
||||||
@ -264,6 +268,10 @@ async function handleMessage(socket, data: string) {
|
|||||||
await handleResult(socket, HASH_GLOB, () =>
|
await handleResult(socket, HASH_GLOB, () =>
|
||||||
handleHashGlob(payload.globs, payload.exclude)
|
handleHashGlob(payload.globs, payload.exclude)
|
||||||
);
|
);
|
||||||
|
} else if (isHandleHashMultiGlobMessage(payload)) {
|
||||||
|
await handleResult(socket, HASH_GLOB, () =>
|
||||||
|
handleHashMultiGlob(payload.globGroups)
|
||||||
|
);
|
||||||
} else if (isHandleGetFlakyTasksMessage(payload)) {
|
} else if (isHandleGetFlakyTasksMessage(payload)) {
|
||||||
await handleResult(socket, GET_FLAKY_TASKS, () =>
|
await handleResult(socket, GET_FLAKY_TASKS, () =>
|
||||||
handleGetFlakyTasks(payload.hashes)
|
handleGetFlakyTasks(payload.hashes)
|
||||||
|
|||||||
@ -260,4 +260,4 @@ export { cacheDir } from './utils/cache-directory';
|
|||||||
*/
|
*/
|
||||||
export { createProjectFileMapUsingProjectGraph } from './project-graph/file-map-utils';
|
export { createProjectFileMapUsingProjectGraph } from './project-graph/file-map-utils';
|
||||||
|
|
||||||
export { isDaemonEnabled } from './daemon/client/client';
|
export { isDaemonEnabled } from './daemon/client/enabled';
|
||||||
|
|||||||
@ -20,7 +20,10 @@ export { stripIndent } from './utils/logger';
|
|||||||
export { readModulePackageJson } from './utils/package-json';
|
export { readModulePackageJson } from './utils/package-json';
|
||||||
export { splitByColons } from './utils/split-target';
|
export { splitByColons } from './utils/split-target';
|
||||||
export { hashObject } from './hasher/file-hasher';
|
export { hashObject } from './hasher/file-hasher';
|
||||||
export { hashWithWorkspaceContext } from './utils/workspace-context';
|
export {
|
||||||
|
hashWithWorkspaceContext,
|
||||||
|
hashMultiGlobWithWorkspaceContext,
|
||||||
|
} from './utils/workspace-context';
|
||||||
export {
|
export {
|
||||||
createProjectRootMappingsFromProjectConfigurations,
|
createProjectRootMappingsFromProjectConfigurations,
|
||||||
findProjectForPath,
|
findProjectForPath,
|
||||||
|
|||||||
1
packages/nx/src/native/index.d.ts
vendored
1
packages/nx/src/native/index.d.ts
vendored
@ -100,6 +100,7 @@ export declare class WorkspaceContext {
|
|||||||
* as the input globs.
|
* as the input globs.
|
||||||
*/
|
*/
|
||||||
multiGlob(globs: Array<string>, exclude?: Array<string> | undefined | null): Array<Array<string>>
|
multiGlob(globs: Array<string>, exclude?: Array<string> | undefined | null): Array<Array<string>>
|
||||||
|
hashFilesMatchingGlobs(globGroups: Array<Array<string>>): Array<string>
|
||||||
hashFilesMatchingGlob(globs: Array<string>, exclude?: Array<string> | undefined | null): string
|
hashFilesMatchingGlob(globs: Array<string>, exclude?: Array<string> | undefined | null): string
|
||||||
incrementalUpdate(updatedFiles: Array<string>, deletedFiles: Array<string>): Record<string, string>
|
incrementalUpdate(updatedFiles: Array<string>, deletedFiles: Array<string>): Record<string, string>
|
||||||
updateProjectFiles(projectRootMappings: ProjectRootMappings, projectFiles: ExternalObject<ProjectFiles>, globalFiles: ExternalObject<Array<FileData>>, updatedFiles: Record<string, string>, deletedFiles: Array<string>): UpdatedWorkspaceFiles
|
updateProjectFiles(projectRootMappings: ProjectRootMappings, projectFiles: ExternalObject<ProjectFiles>, globalFiles: ExternalObject<Array<FileData>>, updatedFiles: Record<string, string>, deletedFiles: Array<string>): UpdatedWorkspaceFiles
|
||||||
|
|||||||
@ -253,6 +253,29 @@ impl WorkspaceContext {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn hash_files_matching_globs(
|
||||||
|
&self,
|
||||||
|
glob_groups: Vec<Vec<String>>,
|
||||||
|
) -> napi::Result<Vec<String>> {
|
||||||
|
let files = &self.all_file_data();
|
||||||
|
let hashes = glob_groups
|
||||||
|
.into_iter()
|
||||||
|
.map(|globs| {
|
||||||
|
let globbed_files =
|
||||||
|
config_files::glob_files(files, globs, None)?.collect::<Vec<_>>();
|
||||||
|
let mut hasher = xxh3::Xxh3::new();
|
||||||
|
for file in globbed_files {
|
||||||
|
hasher.update(file.file.as_bytes());
|
||||||
|
hasher.update(file.hash.as_bytes());
|
||||||
|
}
|
||||||
|
Ok(hasher.digest().to_string())
|
||||||
|
})
|
||||||
|
.collect::<napi::Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
Ok(hashes)
|
||||||
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn hash_files_matching_glob(
|
pub fn hash_files_matching_glob(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
import { performance } from 'node:perf_hooks';
|
||||||
|
|
||||||
|
performance.mark(`plugin worker ${process.pid} code loading -- start`);
|
||||||
|
|
||||||
import { consumeMessage, isPluginWorkerMessage } from './messaging';
|
import { consumeMessage, isPluginWorkerMessage } from './messaging';
|
||||||
import { createSerializableError } from '../../../utils/serializable-error';
|
import { createSerializableError } from '../../../utils/serializable-error';
|
||||||
import { consumeMessagesFromSocket } from '../../../utils/consume-messages-from-socket';
|
import { consumeMessagesFromSocket } from '../../../utils/consume-messages-from-socket';
|
||||||
@ -10,6 +14,13 @@ if (process.env.NX_PERF_LOGGING === 'true') {
|
|||||||
require('../../../utils/perf-logging');
|
require('../../../utils/perf-logging');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
performance.mark(`plugin worker ${process.pid} code loading -- end`);
|
||||||
|
performance.measure(
|
||||||
|
`plugin worker ${process.pid} code loading`,
|
||||||
|
`plugin worker ${process.pid} code loading -- start`,
|
||||||
|
`plugin worker ${process.pid} code loading -- end`
|
||||||
|
);
|
||||||
|
|
||||||
global.NX_GRAPH_CREATION = true;
|
global.NX_GRAPH_CREATION = true;
|
||||||
global.NX_PLUGIN_WORKER = true;
|
global.NX_PLUGIN_WORKER = true;
|
||||||
let connected = false;
|
let connected = false;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ProjectGraph } from '../../config/project-graph';
|
import type { ProjectGraph } from '../../config/project-graph';
|
||||||
import type { PluginConfiguration } from '../../config/nx-json';
|
import { readNxJson, type PluginConfiguration } from '../../config/nx-json';
|
||||||
import {
|
import {
|
||||||
AggregateCreateNodesError,
|
AggregateCreateNodesError,
|
||||||
isAggregateCreateNodesError,
|
isAggregateCreateNodesError,
|
||||||
@ -17,7 +17,7 @@ import type {
|
|||||||
} from './public-api';
|
} from './public-api';
|
||||||
import { createNodesFromFiles } from './utils';
|
import { createNodesFromFiles } from './utils';
|
||||||
import { isIsolationEnabled } from './isolation/enabled';
|
import { isIsolationEnabled } from './isolation/enabled';
|
||||||
import { isDaemonEnabled } from '../../daemon/client/client';
|
import { isDaemonEnabled } from '../../daemon/client/enabled';
|
||||||
|
|
||||||
export class LoadedNxPlugin {
|
export class LoadedNxPlugin {
|
||||||
index?: number;
|
index?: number;
|
||||||
@ -123,7 +123,10 @@ export class LoadedNxPlugin {
|
|||||||
this.preTasksExecution = async (context: PreTasksExecutionContext) => {
|
this.preTasksExecution = async (context: PreTasksExecutionContext) => {
|
||||||
const updates = {};
|
const updates = {};
|
||||||
let originalEnv = process.env;
|
let originalEnv = process.env;
|
||||||
if (isIsolationEnabled() || isDaemonEnabled()) {
|
if (
|
||||||
|
isIsolationEnabled() ||
|
||||||
|
isDaemonEnabled(context.nxJsonConfiguration)
|
||||||
|
) {
|
||||||
process.env = new Proxy<NodeJS.ProcessEnv>(originalEnv, {
|
process.env = new Proxy<NodeJS.ProcessEnv>(originalEnv, {
|
||||||
set: (target, key: string, value) => {
|
set: (target, key: string, value) => {
|
||||||
target[key] = value;
|
target[key] = value;
|
||||||
|
|||||||
@ -4,12 +4,13 @@ import type {
|
|||||||
} from './public-api';
|
} from './public-api';
|
||||||
import { getPlugins } from './get-plugins';
|
import { getPlugins } from './get-plugins';
|
||||||
import { isOnDaemon } from '../../daemon/is-on-daemon';
|
import { isOnDaemon } from '../../daemon/is-on-daemon';
|
||||||
import { daemonClient, isDaemonEnabled } from '../../daemon/client/client';
|
import { daemonClient } from '../../daemon/client/client';
|
||||||
|
import { isDaemonEnabled } from '../../daemon/client/enabled';
|
||||||
|
|
||||||
export async function runPreTasksExecution(
|
export async function runPreTasksExecution(
|
||||||
pluginContext: PreTasksExecutionContext
|
pluginContext: PreTasksExecutionContext
|
||||||
) {
|
) {
|
||||||
if (isOnDaemon() || !isDaemonEnabled()) {
|
if (isOnDaemon() || !isDaemonEnabled(pluginContext.nxJsonConfiguration)) {
|
||||||
performance.mark(`preTasksExecution:start`);
|
performance.mark(`preTasksExecution:start`);
|
||||||
const plugins = await getPlugins(pluginContext.workspaceRoot);
|
const plugins = await getPlugins(pluginContext.workspaceRoot);
|
||||||
const envs = await Promise.all(
|
const envs = await Promise.all(
|
||||||
@ -30,7 +31,7 @@ export async function runPreTasksExecution(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isDaemonEnabled()) {
|
if (!isDaemonEnabled(pluginContext.nxJsonConfiguration)) {
|
||||||
applyProcessEnvs(envs);
|
applyProcessEnvs(envs);
|
||||||
}
|
}
|
||||||
performance.mark(`preTasksExecution:end`);
|
performance.mark(`preTasksExecution:end`);
|
||||||
@ -57,7 +58,7 @@ function applyProcessEnvs(envs: NodeJS.ProcessEnv[]) {
|
|||||||
export async function runPostTasksExecution(
|
export async function runPostTasksExecution(
|
||||||
context: PostTasksExecutionContext
|
context: PostTasksExecutionContext
|
||||||
) {
|
) {
|
||||||
if (isOnDaemon() || !isDaemonEnabled()) {
|
if (isOnDaemon() || !isDaemonEnabled(context.nxJsonConfiguration)) {
|
||||||
performance.mark(`postTasksExecution:start`);
|
performance.mark(`postTasksExecution:start`);
|
||||||
const plugins = await getPlugins();
|
const plugins = await getPlugins();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|||||||
@ -1,11 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
|
CreateNodesContext,
|
||||||
CreateNodesContextV2,
|
CreateNodesContextV2,
|
||||||
CreateNodesFunction,
|
|
||||||
CreateNodesResult,
|
CreateNodesResult,
|
||||||
} from './public-api';
|
} from './public-api';
|
||||||
import { AggregateCreateNodesError } from '../error-types';
|
import { AggregateCreateNodesError } from '../error-types';
|
||||||
export async function createNodesFromFiles<T = unknown>(
|
export async function createNodesFromFiles<T = unknown>(
|
||||||
createNodes: CreateNodesFunction<T>,
|
createNodes: (
|
||||||
|
projectConfigurationFile: string,
|
||||||
|
options: T | undefined,
|
||||||
|
context: CreateNodesContext,
|
||||||
|
idx: number
|
||||||
|
) => CreateNodesResult | Promise<CreateNodesResult>,
|
||||||
configFiles: readonly string[],
|
configFiles: readonly string[],
|
||||||
options: T,
|
options: T,
|
||||||
context: CreateNodesContextV2
|
context: CreateNodesContextV2
|
||||||
@ -14,12 +19,17 @@ export async function createNodesFromFiles<T = unknown>(
|
|||||||
const errors: Array<[file: string, error: Error]> = [];
|
const errors: Array<[file: string, error: Error]> = [];
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
configFiles.map(async (file) => {
|
configFiles.map(async (file, idx) => {
|
||||||
try {
|
try {
|
||||||
const value = await createNodes(file, options, {
|
const value = await createNodes(
|
||||||
|
file,
|
||||||
|
options,
|
||||||
|
{
|
||||||
...context,
|
...context,
|
||||||
configFiles,
|
configFiles,
|
||||||
});
|
},
|
||||||
|
idx
|
||||||
|
);
|
||||||
if (value) {
|
if (value) {
|
||||||
results.push([file, value] as const);
|
results.push([file, value] as const);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,6 +86,17 @@ export async function hashWithWorkspaceContext(
|
|||||||
return daemonClient.hashGlob(globs, exclude);
|
return daemonClient.hashGlob(globs, exclude);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function hashMultiGlobWithWorkspaceContext(
|
||||||
|
workspaceRoot: string,
|
||||||
|
globGroups: string[][]
|
||||||
|
) {
|
||||||
|
if (isOnDaemon() || !daemonClient.enabled()) {
|
||||||
|
ensureContextAvailable(workspaceRoot);
|
||||||
|
return workspaceContext.hashFilesMatchingGlobs(globGroups);
|
||||||
|
}
|
||||||
|
return daemonClient.hashMultiGlob(globGroups);
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateContextWithChangedFiles(
|
export async function updateContextWithChangedFiles(
|
||||||
workspaceRoot: string,
|
workspaceRoot: string,
|
||||||
createdFiles: string[],
|
createdFiles: string[],
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import type { Compiler } from '@rspack/core';
|
import type { Compiler } from '@rspack/core';
|
||||||
import { daemonClient, isDaemonEnabled } from 'nx/src/daemon/client/client';
|
import { daemonClient } from 'nx/src/daemon/client/client';
|
||||||
|
import { isDaemonEnabled } from 'nx/src/daemon/client/enabled';
|
||||||
import { BatchFunctionRunner } from 'nx/src/command-line/watch/watch';
|
import { BatchFunctionRunner } from 'nx/src/command-line/watch/watch';
|
||||||
import { output } from 'nx/src/utils/output';
|
import { output } from 'nx/src/utils/output';
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {
|
|||||||
CreateDependencies,
|
CreateDependencies,
|
||||||
CreateNodes,
|
CreateNodes,
|
||||||
CreateNodesContext,
|
CreateNodesContext,
|
||||||
|
CreateNodesContextV2,
|
||||||
createNodesFromFiles,
|
createNodesFromFiles,
|
||||||
CreateNodesV2,
|
CreateNodesV2,
|
||||||
detectPackageManager,
|
detectPackageManager,
|
||||||
@ -16,7 +17,10 @@ import {
|
|||||||
import { dirname, isAbsolute, join, relative } from 'path';
|
import { dirname, isAbsolute, join, relative } from 'path';
|
||||||
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
|
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
|
||||||
import { existsSync, readdirSync } from 'fs';
|
import { existsSync, readdirSync } from 'fs';
|
||||||
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
import {
|
||||||
|
calculateHashesForCreateNodes,
|
||||||
|
calculateHashForCreateNodes,
|
||||||
|
} from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
||||||
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||||
import { getLockFileName } from '@nx/js';
|
import { getLockFileName } from '@nx/js';
|
||||||
import { loadViteDynamicImport } from '../utils/executor-utils';
|
import { loadViteDynamicImport } from '../utils/executor-utils';
|
||||||
@ -42,7 +46,10 @@ export interface VitePluginOptions {
|
|||||||
buildDepsTargetName?: string;
|
buildDepsTargetName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ViteTargets = Pick<ProjectConfiguration, 'targets' | 'metadata'>;
|
type ViteTargets = Pick<
|
||||||
|
ProjectConfiguration,
|
||||||
|
'targets' | 'metadata' | 'projectType'
|
||||||
|
>;
|
||||||
|
|
||||||
function readTargetsCache(cachePath: string): Record<string, ViteTargets> {
|
function readTargetsCache(cachePath: string): Record<string, ViteTargets> {
|
||||||
return process.env.NX_CACHE_PROJECT_GRAPH !== 'false' && existsSync(cachePath)
|
return process.env.NX_CACHE_PROJECT_GRAPH !== 'false' && existsSync(cachePath)
|
||||||
@ -67,20 +74,88 @@ export const createNodesV2: CreateNodesV2<VitePluginOptions> = [
|
|||||||
viteVitestConfigGlob,
|
viteVitestConfigGlob,
|
||||||
async (configFilePaths, options, context) => {
|
async (configFilePaths, options, context) => {
|
||||||
const optionsHash = hashObject(options);
|
const optionsHash = hashObject(options);
|
||||||
|
const normalizedOptions = normalizeOptions(options);
|
||||||
const cachePath = join(workspaceDataDirectory, `vite-${optionsHash}.hash`);
|
const cachePath = join(workspaceDataDirectory, `vite-${optionsHash}.hash`);
|
||||||
const targetsCache = readTargetsCache(cachePath);
|
const targetsCache = readTargetsCache(cachePath);
|
||||||
const isUsingTsSolutionSetup = _isUsingTsSolutionSetup();
|
const isUsingTsSolutionSetup = _isUsingTsSolutionSetup();
|
||||||
|
|
||||||
|
const { roots: projectRoots, configFiles: validConfigFiles } =
|
||||||
|
configFilePaths.reduce(
|
||||||
|
(acc, configFile) => {
|
||||||
|
const potentialRoot = dirname(configFile);
|
||||||
|
if (checkIfConfigFileShouldBeProject(potentialRoot, context)) {
|
||||||
|
acc.roots.push(potentialRoot);
|
||||||
|
acc.configFiles.push(configFile);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
roots: [],
|
||||||
|
configFiles: [],
|
||||||
|
} as {
|
||||||
|
roots: string[];
|
||||||
|
configFiles: string[];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const lockfile = getLockFileName(
|
||||||
|
detectPackageManager(context.workspaceRoot)
|
||||||
|
);
|
||||||
|
const hashes = await calculateHashesForCreateNodes(
|
||||||
|
projectRoots,
|
||||||
|
{ ...normalizedOptions, isUsingTsSolutionSetup },
|
||||||
|
context,
|
||||||
|
projectRoots.map((r) => [lockfile])
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await createNodesFromFiles(
|
return await createNodesFromFiles(
|
||||||
(configFile, options, context) =>
|
async (configFile, _, context, idx) => {
|
||||||
createNodesInternal(
|
const projectRoot = dirname(configFile);
|
||||||
|
// Do not create a project if package.json and project.json isn't there.
|
||||||
|
const siblingFiles = readdirSync(
|
||||||
|
join(context.workspaceRoot, projectRoot)
|
||||||
|
);
|
||||||
|
|
||||||
|
const tsConfigFiles =
|
||||||
|
siblingFiles.filter((p) =>
|
||||||
|
minimatch(p, 'tsconfig*{.json,.*.json}')
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
// results from vitest.config.js will be different from results of vite.config.js
|
||||||
|
// but the hash will be the same because it is based on the files under the project root.
|
||||||
|
// Adding the config file path to the hash ensures that the final hash value is different
|
||||||
|
// for different config files.
|
||||||
|
const hash = hashes[idx] + configFile;
|
||||||
|
const { projectType, metadata, targets } = (targetsCache[hash] ??=
|
||||||
|
await buildViteTargets(
|
||||||
configFile,
|
configFile,
|
||||||
options,
|
projectRoot,
|
||||||
context,
|
normalizedOptions,
|
||||||
targetsCache,
|
tsConfigFiles,
|
||||||
isUsingTsSolutionSetup
|
isUsingTsSolutionSetup,
|
||||||
),
|
context
|
||||||
configFilePaths,
|
));
|
||||||
|
|
||||||
|
const project: ProjectConfiguration = {
|
||||||
|
root: projectRoot,
|
||||||
|
targets,
|
||||||
|
metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
// If project is buildable, then the project type.
|
||||||
|
// If it is not buildable, then leave it to other plugins/project.json to set the project type.
|
||||||
|
if (project.targets[normalizedOptions.buildTargetName]) {
|
||||||
|
project.projectType = projectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
projects: {
|
||||||
|
[projectRoot]: project,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
validConfigFiles,
|
||||||
options,
|
options,
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
@ -96,23 +171,6 @@ export const createNodes: CreateNodes<VitePluginOptions> = [
|
|||||||
logger.warn(
|
logger.warn(
|
||||||
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
|
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
|
||||||
);
|
);
|
||||||
return createNodesInternal(
|
|
||||||
configFilePath,
|
|
||||||
options,
|
|
||||||
context,
|
|
||||||
{},
|
|
||||||
_isUsingTsSolutionSetup()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
async function createNodesInternal(
|
|
||||||
configFilePath: string,
|
|
||||||
options: VitePluginOptions,
|
|
||||||
context: CreateNodesContext,
|
|
||||||
targetsCache: Record<string, ViteTargets>,
|
|
||||||
isUsingTsSolutionSetup: boolean
|
|
||||||
) {
|
|
||||||
const projectRoot = dirname(configFilePath);
|
const projectRoot = dirname(configFilePath);
|
||||||
// Do not create a project if package.json and project.json isn't there.
|
// Do not create a project if package.json and project.json isn't there.
|
||||||
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
|
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
|
||||||
@ -124,21 +182,14 @@ async function createNodesInternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tsConfigFiles =
|
const tsConfigFiles =
|
||||||
siblingFiles.filter((p) => minimatch(p, 'tsconfig*{.json,.*.json}')) ?? [];
|
siblingFiles.filter((p) => minimatch(p, 'tsconfig*{.json,.*.json}')) ??
|
||||||
|
[];
|
||||||
|
|
||||||
const normalizedOptions = normalizeOptions(options);
|
const normalizedOptions = normalizeOptions(options);
|
||||||
|
|
||||||
// We do not want to alter how the hash is calculated, so appending the config file path to the hash
|
const isUsingTsSolutionSetup = _isUsingTsSolutionSetup();
|
||||||
// to prevent vite/vitest files overwriting the target cache created by the other
|
|
||||||
const hash =
|
|
||||||
(await calculateHashForCreateNodes(
|
|
||||||
projectRoot,
|
|
||||||
{ ...normalizedOptions, isUsingTsSolutionSetup },
|
|
||||||
context,
|
|
||||||
[getLockFileName(detectPackageManager(context.workspaceRoot))]
|
|
||||||
)) + configFilePath;
|
|
||||||
|
|
||||||
const { isLibrary, ...viteTargets } = await buildViteTargets(
|
const { projectType, metadata, targets } = await buildViteTargets(
|
||||||
configFilePath,
|
configFilePath,
|
||||||
projectRoot,
|
projectRoot,
|
||||||
normalizedOptions,
|
normalizedOptions,
|
||||||
@ -146,9 +197,6 @@ async function createNodesInternal(
|
|||||||
isUsingTsSolutionSetup,
|
isUsingTsSolutionSetup,
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
targetsCache[hash] ??= viteTargets;
|
|
||||||
|
|
||||||
const { targets, metadata } = targetsCache[hash];
|
|
||||||
const project: ProjectConfiguration = {
|
const project: ProjectConfiguration = {
|
||||||
root: projectRoot,
|
root: projectRoot,
|
||||||
targets,
|
targets,
|
||||||
@ -158,7 +206,7 @@ async function createNodesInternal(
|
|||||||
// If project is buildable, then the project type.
|
// If project is buildable, then the project type.
|
||||||
// If it is not buildable, then leave it to other plugins/project.json to set the project type.
|
// If it is not buildable, then leave it to other plugins/project.json to set the project type.
|
||||||
if (project.targets[normalizedOptions.buildTargetName]) {
|
if (project.targets[normalizedOptions.buildTargetName]) {
|
||||||
project.projectType = isLibrary ? 'library' : 'application';
|
project.projectType = projectType;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -166,7 +214,8 @@ async function createNodesInternal(
|
|||||||
[projectRoot]: project,
|
[projectRoot]: project,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
];
|
||||||
|
|
||||||
async function buildViteTargets(
|
async function buildViteTargets(
|
||||||
configFilePath: string,
|
configFilePath: string,
|
||||||
@ -175,7 +224,7 @@ async function buildViteTargets(
|
|||||||
tsConfigFiles: string[],
|
tsConfigFiles: string[],
|
||||||
isUsingTsSolutionSetup: boolean,
|
isUsingTsSolutionSetup: boolean,
|
||||||
context: CreateNodesContext
|
context: CreateNodesContext
|
||||||
): Promise<ViteTargets & { isLibrary: boolean }> {
|
): Promise<ViteTargets> {
|
||||||
const absoluteConfigFilePath = joinPathFragments(
|
const absoluteConfigFilePath = joinPathFragments(
|
||||||
context.workspaceRoot,
|
context.workspaceRoot,
|
||||||
configFilePath
|
configFilePath
|
||||||
@ -304,7 +353,11 @@ async function buildViteTargets(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const metadata = {};
|
const metadata = {};
|
||||||
return { targets, metadata, isLibrary: Boolean(viteBuildConfig.build?.lib) };
|
return {
|
||||||
|
targets,
|
||||||
|
metadata,
|
||||||
|
projectType: viteBuildConfig.build?.lib ? 'library' : 'application',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildTarget(
|
async function buildTarget(
|
||||||
@ -538,3 +591,19 @@ function normalizeOptions(options: VitePluginOptions): VitePluginOptions {
|
|||||||
options.typecheckTargetName ??= 'typecheck';
|
options.typecheckTargetName ??= 'typecheck';
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkIfConfigFileShouldBeProject(
|
||||||
|
projectRoot: string,
|
||||||
|
context: CreateNodesContext | CreateNodesContextV2
|
||||||
|
): boolean {
|
||||||
|
// Do not create a project if package.json and project.json isn't there.
|
||||||
|
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
|
||||||
|
if (
|
||||||
|
!siblingFiles.includes('package.json') &&
|
||||||
|
!siblingFiles.includes('project.json')
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import type { Compiler } from 'webpack';
|
import type { Compiler } from 'webpack';
|
||||||
import { daemonClient, isDaemonEnabled } from 'nx/src/daemon/client/client';
|
import { daemonClient } from 'nx/src/daemon/client/client';
|
||||||
import { BatchFunctionRunner } from 'nx/src/command-line/watch/watch';
|
import { BatchFunctionRunner } from 'nx/src/command-line/watch/watch';
|
||||||
import { output } from 'nx/src/utils/output';
|
import { output } from 'nx/src/utils/output';
|
||||||
|
import { isDaemonEnabled } from 'nx/src/daemon/client/enabled';
|
||||||
|
|
||||||
type PluginOptions = {
|
type PluginOptions = {
|
||||||
skipInitialBuild?: boolean;
|
skipInitialBuild?: boolean;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user