From 8a0707df86217f8ec1aa94b529ac9f2eeb136e15 Mon Sep 17 00:00:00 2001 From: Jason Jean Date: Wed, 15 Nov 2023 16:04:52 -0500 Subject: [PATCH] feat(testing): distribute cypress tests for ci (#20188) --- .../generated/packages/nx/executors/noop.json | 2 +- packages/cypress/plugin.ts | 2 +- .../add-nx-cypress-plugin.spec.ts | 21 +++- packages/cypress/src/plugins/plugin.spec.ts | 83 ++++++++++--- packages/cypress/src/plugins/plugin.ts | 112 ++++++++++++++--- .../utils/calculate-hash-for-create-nodes.ts | 17 +++ packages/nx/src/devkit-internals.ts | 1 + packages/nx/src/executors/noop/schema.json | 2 +- packages/nx/src/native/hasher.rs | 8 +- packages/nx/src/native/index.d.ts | 3 +- .../nx/src/native/workspace/config_files.rs | 44 ++++--- packages/nx/src/native/workspace/context.rs | 115 ++++++++++-------- .../src/native/workspace/workspace_files.rs | 25 ++-- .../utils/retrieve-workspace-files.ts | 4 +- packages/nx/src/utils/workspace-context.ts | 14 ++- 15 files changed, 321 insertions(+), 132 deletions(-) create mode 100644 packages/devkit/src/utils/calculate-hash-for-create-nodes.ts diff --git a/docs/generated/packages/nx/executors/noop.json b/docs/generated/packages/nx/executors/noop.json index a4eb444a3f231..767dddbcf9146 100644 --- a/docs/generated/packages/nx/executors/noop.json +++ b/docs/generated/packages/nx/executors/noop.json @@ -9,7 +9,7 @@ "cli": "nx", "outputCapture": "pipe", "properties": {}, - "additionalProperties": false, + "additionalProperties": true, "presets": [] }, "description": "An executor that does nothing", diff --git a/packages/cypress/plugin.ts b/packages/cypress/plugin.ts index f8ca64185f7ae..c2bb22ffb070e 100644 --- a/packages/cypress/plugin.ts +++ b/packages/cypress/plugin.ts @@ -1 +1 @@ -export { createNodes } from './src/plugins/plugin'; +export { createNodes, createDependencies } from './src/plugins/plugin'; diff --git a/packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.spec.ts b/packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.spec.ts index 68982ea551520..7d8d15967103c 100644 --- a/packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.spec.ts +++ b/packages/cypress/src/migrations/update-17-2-0/add-nx-cypress-plugin.spec.ts @@ -52,7 +52,9 @@ describe('add-nx-cypress-plugin migration', () => { }, ciDevServerTarget: 'my-app:serve-static', }, - e2e: {}, + e2e: { + specPattern: '**/*.cy.ts', + }, }) ); updateProjectConfiguration(tree, 'e2e', { @@ -77,7 +79,13 @@ describe('add-nx-cypress-plugin migration', () => { await update(tree); - expect(readProjectConfiguration(tree, 'e2e').targets.e2e).toBeUndefined(); + expect(readProjectConfiguration(tree, 'e2e').targets.e2e).toEqual({ + configurations: { + ci: { + devServerTarget: 'my-app:serve-static', + }, + }, + }); }); it('should not the e2e target when it uses a different executor', async () => { @@ -119,7 +127,9 @@ describe('add-nx-cypress-plugin migration', () => { }, ciDevServerTarget: 'my-app:serve-static', }, - e2e: {}, + e2e: { + specPattern: '**/*.cy.ts', + }, }) ); updateProjectConfiguration(tree, 'e2e', { @@ -149,6 +159,11 @@ describe('add-nx-cypress-plugin migration', () => { options: { watch: false, }, + configurations: { + ci: { + devServerTarget: 'my-app:serve-static', + }, + }, }); }); }); diff --git a/packages/cypress/src/plugins/plugin.spec.ts b/packages/cypress/src/plugins/plugin.spec.ts index ccdd3f6fe31fd..9e161e17de8bf 100644 --- a/packages/cypress/src/plugins/plugin.spec.ts +++ b/packages/cypress/src/plugins/plugin.spec.ts @@ -2,12 +2,21 @@ import { CreateNodesContext } from '@nx/devkit'; import { defineConfig } from 'cypress'; import { createNodes } from './plugin'; +import { TempFs } from 'nx/src/internal-testing-utils/temp-fs'; +import { join } from 'path'; describe('@nx/cypress/plugin', () => { let createNodesFunction = createNodes[1]; let context: CreateNodesContext; + let tempFs: TempFs; beforeEach(async () => { + tempFs = new TempFs('cypress-plugin'); + + await tempFs.createFiles({ + 'package.json': '{}', + 'test.cy.ts': '', + }); context = { nxJsonConfiguration: { namedInputs: { @@ -15,12 +24,13 @@ describe('@nx/cypress/plugin', () => { production: ['!{projectRoot}/**/*.spec.ts'], }, }, - workspaceRoot: '', + workspaceRoot: tempFs.tempDir, }; }); afterEach(() => { jest.resetModules(); + tempFs.cleanup(); }); it('should add a target for e2e', () => { @@ -45,7 +55,6 @@ describe('@nx/cypress/plugin', () => { "projects": { ".": { "projectType": "application", - "root": ".", "targets": { "e2e": { "cache": true, @@ -98,7 +107,6 @@ describe('@nx/cypress/plugin', () => { "projects": { ".": { "projectType": "application", - "root": ".", "targets": { "component-test": { "cache": true, @@ -125,10 +133,11 @@ describe('@nx/cypress/plugin', () => { `); }); - it('should use nxMetadata to create additional configurations', () => { + it('should use ciDevServerTarget to create additional configurations', () => { mockCypressConfig( defineConfig({ e2e: { + specPattern: '**/*.cy.ts', env: { devServerTargets: { default: 'my-app:serve', @@ -152,14 +161,10 @@ describe('@nx/cypress/plugin', () => { "projects": { ".": { "projectType": "application", - "root": ".", "targets": { "e2e": { "cache": true, "configurations": { - "ci": { - "devServerTarget": "my-app:serve-static", - }, "production": { "devServerTarget": "my-app:serve:production", }, @@ -179,22 +184,60 @@ describe('@nx/cypress/plugin', () => { "{options.screenshotsFolder}", ], }, + "e2e-ci": { + "cache": true, + "dependsOn": [ + { + "params": "forward", + "projects": "self", + "target": "e2e-ci--test.cy.ts", + }, + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + ], + "outputs": [ + "{options.videosFolder}", + "{options.screenshotsFolder}", + ], + }, + "e2e-ci--test.cy.ts": { + "cache": true, + "configurations": undefined, + "executor": "@nx/cypress:cypress", + "inputs": [ + "default", + "^production", + ], + "options": { + "cypressConfig": "cypress.config.js", + "devServerTarget": "my-app:serve-static", + "spec": "test.cy.ts", + "testingType": "e2e", + }, + "outputs": [ + "{options.videosFolder}", + "{options.screenshotsFolder}", + ], + }, }, }, }, } `); }); -}); -function mockCypressConfig(cypressConfig: Cypress.ConfigOptions) { - jest.mock( - 'cypress.config.js', - () => ({ - default: cypressConfig, - }), - { - virtual: true, - } - ); -} + function mockCypressConfig(cypressConfig: Cypress.ConfigOptions) { + jest.mock( + join(tempFs.tempDir, 'cypress.config.js'), + () => ({ + default: cypressConfig, + }), + { + virtual: true, + } + ); + } +}); diff --git a/packages/cypress/src/plugins/plugin.ts b/packages/cypress/src/plugins/plugin.ts index d6319b38a1891..97cf61d401c46 100644 --- a/packages/cypress/src/plugins/plugin.ts +++ b/packages/cypress/src/plugins/plugin.ts @@ -1,9 +1,12 @@ import { + CreateDependencies, CreateNodes, CreateNodesContext, + readJsonFile, TargetConfiguration, + writeJsonFile, } from '@nx/devkit'; -import { basename, dirname, extname, join } from 'path'; +import { dirname, extname, join } from 'path'; import { registerTsProject } from '@nx/js/src/internal'; import { getRootTsConfigPath } from '@nx/js'; @@ -11,13 +14,46 @@ import { getRootTsConfigPath } from '@nx/js'; import { CypressExecutorOptions } from '../executors/cypress/cypress.impl'; import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils'; import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs'; -import { readdirSync } from 'fs'; +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'; export interface CypressPluginOptions { + ciTargetName?: string; targetName?: string; componentTestingTargetName?: string; } +const cachePath = join(projectGraphCacheDirectory, 'cypress.hash'); +const targetsCache = existsSync(cachePath) ? readTargetsCache() : {}; + +const calculatedTargets: Record< + string, + Record +> = {}; + +function readTargetsCache(): Record< + string, + Record> +> { + return readJsonFile(cachePath); +} + +function writeTargetsToCache( + targets: Record< + string, + Record> + > +) { + writeJsonFile(cachePath, targets); +} + +export const createDependencies: CreateDependencies = () => { + writeTargetsToCache(calculatedTargets); + return []; +}; + export const createNodes: CreateNodes = [ '**/cypress.config.{js,ts,mjs,mts,cjs,cts}', (configFilePath, options, context) => { @@ -33,19 +69,19 @@ export const createNodes: CreateNodes = [ return {}; } - const projectName = basename(projectRoot); + const hash = calculateHashForCreateNodes(projectRoot, options, context); + + const targets = targetsCache[hash] + ? targetsCache[hash] + : buildCypressTargets(configFilePath, projectRoot, options, context); + + calculatedTargets[hash] = targets; return { projects: { - [projectName]: { - root: projectRoot, + [projectRoot]: { projectType: 'application', - targets: buildCypressTargets( - configFilePath, - projectRoot, - options, - context - ), + targets, }, }, }; @@ -99,7 +135,6 @@ function getOutputs( return outputs; } - function buildCypressTargets( configFilePath: string, projectRoot: string, @@ -183,10 +218,56 @@ function buildCypressTargets( const ciDevServerTarget: string = cypressEnv?.ciDevServerTarget; if (ciDevServerTarget) { - targets[options.targetName].configurations ??= {}; + const specPatterns = Array.isArray(cypressConfig.e2e.specPattern) + ? cypressConfig.e2e.specPattern.map((p) => join(projectRoot, p)) + : [join(projectRoot, cypressConfig.e2e.specPattern)]; + + const excludeSpecPatterns: string[] = !cypressConfig.e2e + .excludeSpecPattern + ? cypressConfig.e2e.excludeSpecPattern + : Array.isArray(cypressConfig.e2e.excludeSpecPattern) + ? cypressConfig.e2e.excludeSpecPattern.map((p) => join(projectRoot, p)) + : [join(projectRoot, cypressConfig.e2e.excludeSpecPattern)]; + const specFiles = globWithWorkspaceContext( + context.workspaceRoot, + specPatterns, + excludeSpecPatterns + ); - targets[options.targetName].configurations['ci'] = { - devServerTarget: ciDevServerTarget, + const dependsOn: TargetConfiguration['dependsOn'] = []; + const outputs = getOutputs(projectRoot, cypressConfig, 'e2e'); + const inputs = + 'production' in namedInputs + ? ['default', '^production'] + : ['default', '^default']; + for (const file of specFiles) { + const targetName = options.ciTargetName + '--' + file; + targets[targetName] = { + ...targets[options.targetName], + outputs, + inputs, + cache: true, + configurations: undefined, + options: { + ...targets[options.targetName].options, + devServerTarget: ciDevServerTarget, + spec: file, + }, + }; + dependsOn.push({ + target: targetName, + projects: 'self', + params: 'forward', + }); + } + targets[options.ciTargetName] ??= {}; + + targets[options.ciTargetName] = { + executor: 'nx:noop', + cache: true, + inputs, + outputs, + dependsOn, }; } } @@ -260,5 +341,6 @@ function normalizeOptions(options: CypressPluginOptions): CypressPluginOptions { options ??= {}; options.targetName ??= 'e2e'; options.componentTestingTargetName ??= 'component-test'; + options.ciTargetName ??= 'e2e-ci'; return options; } diff --git a/packages/devkit/src/utils/calculate-hash-for-create-nodes.ts b/packages/devkit/src/utils/calculate-hash-for-create-nodes.ts new file mode 100644 index 0000000000000..0fde7048b8d76 --- /dev/null +++ b/packages/devkit/src/utils/calculate-hash-for-create-nodes.ts @@ -0,0 +1,17 @@ +import type { CreateNodesContext } from 'nx/src/devkit-exports'; +import { requireNx } from '../../nx'; +import { join } from 'path'; +const { hashWithWorkspaceContext, hashArray, hashObject } = requireNx(); + +export function calculateHashForCreateNodes( + projectRoot: string, + options: object, + context: CreateNodesContext +): string { + return hashArray([ + hashWithWorkspaceContext(context.workspaceRoot, [ + join(projectRoot, '**/*'), + ]), + hashObject(options), + ]); +} diff --git a/packages/nx/src/devkit-internals.ts b/packages/nx/src/devkit-internals.ts index b8e3cca1f8a23..1fec764f3a746 100644 --- a/packages/nx/src/devkit-internals.ts +++ b/packages/nx/src/devkit-internals.ts @@ -15,6 +15,7 @@ export { stripIndent } from './utils/logger'; export { readModulePackageJson } from './utils/package-json'; export { splitByColons } from './utils/split-target'; export { hashObject } from './hasher/file-hasher'; +export { hashWithWorkspaceContext } from './utils/workspace-context'; export { createProjectRootMappingsFromProjectConfigurations, findProjectForPath, diff --git a/packages/nx/src/executors/noop/schema.json b/packages/nx/src/executors/noop/schema.json index 76abff6c79c09..b2951652bef90 100644 --- a/packages/nx/src/executors/noop/schema.json +++ b/packages/nx/src/executors/noop/schema.json @@ -6,5 +6,5 @@ "cli": "nx", "outputCapture": "pipe", "properties": {}, - "additionalProperties": false + "additionalProperties": true } diff --git a/packages/nx/src/native/hasher.rs b/packages/nx/src/native/hasher.rs index 1a37165b35320..38c0e17c03118 100644 --- a/packages/nx/src/native/hasher.rs +++ b/packages/nx/src/native/hasher.rs @@ -3,11 +3,15 @@ use crate::native::walker::nx_walker; use std::collections::HashMap; use xxhash_rust::xxh3; +pub fn hash(content: &[u8]) -> String { + xxh3::xxh3_64(content).to_string() +} + #[napi] pub fn hash_array(input: Vec) -> String { let joined = input.join(","); let content = joined.as_bytes(); - xxh3::xxh3_64(content).to_string() + hash(content) } #[napi] @@ -16,7 +20,7 @@ pub fn hash_file(file: String) -> Option { return None; }; - Some(xxh3::xxh3_64(&content).to_string()) + Some(hash(&content)) } #[napi] diff --git a/packages/nx/src/native/index.d.ts b/packages/nx/src/native/index.d.ts index a13b8762cf5de..5cd69a4cb34ee 100644 --- a/packages/nx/src/native/index.d.ts +++ b/packages/nx/src/native/index.d.ts @@ -135,7 +135,8 @@ export class WorkspaceContext { workspaceRoot: string constructor(workspaceRoot: string) getWorkspaceFiles(globs: Array, parseConfigurations: (arg0: Array) => Promise>): Promise - glob(globs: Array): Array + glob(globs: Array, exclude?: Array | undefined | null): Array + hashFilesMatchingGlob(globs: Array, exclude?: Array | undefined | null): string getProjectConfigurations(globs: Array, parseConfigurations: (arg0: Array) => Promise>): Promise> incrementalUpdate(updatedFiles: Array, deletedFiles: Array): Record allFileData(): Array diff --git a/packages/nx/src/native/workspace/config_files.rs b/packages/nx/src/native/workspace/config_files.rs index 1b85116af7762..cf2e69b5d6d2a 100644 --- a/packages/nx/src/native/workspace/config_files.rs +++ b/packages/nx/src/native/workspace/config_files.rs @@ -1,40 +1,48 @@ use crate::native::glob::build_glob_set; use crate::native::utils::path::Normalize; -use std::collections::HashMap; use napi::bindgen_prelude::Promise; +use std::collections::HashMap; -use crate::native::workspace::errors::{InternalWorkspaceErrors, WorkspaceErrors}; use rayon::prelude::*; use std::path::PathBuf; /// Get workspace config files based on provided globs pub(super) fn glob_files( + files: &[(PathBuf, String)], globs: Vec, - files: Option<&[(PathBuf, String)]>, -) -> napi::Result, WorkspaceErrors> { - let Some(files) = files else { - return Ok(Default::default()); - }; - - let globs = - build_glob_set(&globs).map_err(|err| InternalWorkspaceErrors::Generic(err.to_string()))?; - Ok(files - .par_iter() - .map(|file| file.0.to_normalized_string()) - .filter(|path| globs.is_match(path)) - .collect()) + exclude: Option>, +) -> napi::Result> { + let globs = build_glob_set(&globs)?; + + let exclude_glob_set = exclude + .map(|exclude| build_glob_set(&exclude)) + .transpose()?; + + Ok(files.par_iter().filter(move |file| { + let path = file.0.to_normalized_string(); + let is_match = globs.is_match(&path); + + if !is_match { + return is_match; + } + + exclude_glob_set + .as_ref() + .map(|exclude_glob_set| exclude_glob_set.is_match(&path)) + .unwrap_or(is_match) + })) } /// Get workspace config files based on provided globs pub(super) fn get_project_configurations( globs: Vec, - files: Option<&[(PathBuf, String)]>, + files: &[(PathBuf, String)], parse_configurations: ConfigurationParser, ) -> napi::Result>> where ConfigurationParser: Fn(Vec) -> napi::Result>>, { - let config_paths = glob_files(globs, files).map_err(anyhow::Error::from)?; + let files = glob_files(files, globs, None).map_err(anyhow::Error::from)?; - parse_configurations(config_paths) + parse_configurations(files.map(|file| file.0.to_normalized_string()).collect()) } diff --git a/packages/nx/src/native/workspace/context.rs b/packages/nx/src/native/workspace/context.rs index fa65ffab92c0e..7db21fac69175 100644 --- a/packages/nx/src/native/workspace/context.rs +++ b/packages/nx/src/native/workspace/context.rs @@ -1,21 +1,19 @@ use crate::native::logger::enable_logger; use std::collections::HashMap; +use crate::native::hasher::hash; use crate::native::types::FileData; use crate::native::utils::path::Normalize; use napi::bindgen_prelude::*; -use parking_lot::lock_api::MutexGuard; -use parking_lot::{Condvar, Mutex, RawMutex}; +use parking_lot::{Condvar, Mutex}; use rayon::prelude::*; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::thread; use tracing::{trace, warn}; -use xxhash_rust::xxh3; use crate::native::walker::nx_walker; -use crate::native::workspace::errors::WorkspaceErrors; use crate::native::workspace::{config_files, workspace_files}; #[napi] @@ -27,7 +25,6 @@ pub struct WorkspaceContext { type Files = Vec<(PathBuf, String)>; struct FilesWorker(Option, Condvar)>>); - impl FilesWorker { fn gather_files(workspace_root: &Path) -> Self { if !workspace_root.exists() { @@ -49,7 +46,7 @@ impl FilesWorker { let files = nx_walker(workspace_root, |rec| { let mut file_hashes: Vec<(PathBuf, String)> = vec![]; for (path, content) in rec { - file_hashes.push((path, xxh3::xxh3_64(&content).to_string())); + file_hashes.push((path, hash(&content))); } file_hashes }); @@ -65,26 +62,25 @@ impl FilesWorker { FilesWorker(Some(files_lock)) } - pub fn get_files(&self) -> Option> { - let Some(files_sync) = &self.0 else { - trace!("there were no files because the workspace root did not exist"); - return None; - }; - - let (files_lock, cvar) = &files_sync.deref(); - trace!("locking files"); - let mut files = files_lock.lock(); - let files_len = files.len(); - if files_len == 0 { - trace!("waiting for files"); - cvar.wait(&mut files); + pub fn get_files(&self) -> Vec<(PathBuf, String)> { + if let Some(files_sync) = &self.0 { + let (files_lock, cvar) = files_sync.deref(); + trace!("locking files"); + let mut files = files_lock.lock(); + let files_len = files.len(); + if files_len == 0 { + trace!("waiting for files"); + cvar.wait(&mut files); + } + + let cloned_files = files.clone(); + drop(files); + + trace!("files are available"); + cloned_files + } else { + vec![] } - - let cloned_files = files.clone(); - drop(files); - - trace!("files are available"); - Some(cloned_files) } pub fn update_files( @@ -114,7 +110,7 @@ impl FilesWorker { trace!("could not read file: ?full_path"); return None; }; - Some((path.to_string(), xxh3::xxh3_64(&content).to_string())) + Some((path.to_string(), hash(&content))) }) .collect(); @@ -158,18 +154,39 @@ impl WorkspaceContext { where ConfigurationParser: Fn(Vec) -> napi::Result>>, { - workspace_files::get_files( - env, - globs, - parse_configurations, - self.files_worker.get_files().as_deref(), - ) - .map_err(anyhow::Error::from) + let files = self.files_worker.get_files(); + workspace_files::get_files(env, globs, parse_configurations, &files) + .map_err(anyhow::Error::from) + } + + #[napi] + pub fn glob( + &self, + globs: Vec, + exclude: Option>, + ) -> napi::Result> { + let files = self.files_worker.get_files(); + + let globbed_files = config_files::glob_files(&files, globs, exclude)?; + Ok(globbed_files + .map(|file| file.0.to_normalized_string()) + .collect()) } #[napi] - pub fn glob(&self, globs: Vec) -> napi::Result, WorkspaceErrors> { - config_files::glob_files(globs, self.files_worker.get_files().as_deref()) + pub fn hash_files_matching_glob( + &self, + globs: Vec, + exclude: Option>, + ) -> napi::Result { + let files = self.files_worker.get_files(); + let globbed_files = config_files::glob_files(&files, globs, exclude)?; + Ok(hash( + &globbed_files + .map(|file| file.1.as_bytes()) + .collect::>() + .concat(), + )) } #[napi(ts_return_type = "Promise>")] @@ -182,11 +199,11 @@ impl WorkspaceContext { where ConfigurationParser: Fn(Vec) -> napi::Result>>, { - let promise = config_files::get_project_configurations( - globs, - self.files_worker.get_files().as_deref(), - parse_configurations, - )?; + let files = self.files_worker.get_files(); + + let promise = + config_files::get_project_configurations(globs, &files, parse_configurations)?; + env.spawn_future(async move { let result = promise.await?; Ok(result) @@ -205,16 +222,14 @@ impl WorkspaceContext { #[napi] pub fn all_file_data(&self) -> Vec { - self.files_worker - .get_files() - .map_or_else(Vec::new, |files| { - files - .iter() - .map(|(path, content)| FileData { - file: path.to_normalized_string(), - hash: content.clone(), - }) - .collect() + let files = self.files_worker.get_files(); + + files + .iter() + .map(|(path, content)| FileData { + file: path.to_normalized_string(), + hash: content.clone(), }) + .collect() } } diff --git a/packages/nx/src/native/workspace/workspace_files.rs b/packages/nx/src/native/workspace/workspace_files.rs index 5df4d31cec903..670db2b28af18 100644 --- a/packages/nx/src/native/workspace/workspace_files.rs +++ b/packages/nx/src/native/workspace/workspace_files.rs @@ -9,7 +9,6 @@ use tracing::trace; use crate::native::types::FileData; use crate::native::utils::path::Normalize; use crate::native::workspace::config_files; -use crate::native::workspace::errors::{InternalWorkspaceErrors, WorkspaceErrors}; use crate::native::workspace::types::FileLocation; #[napi(object)] @@ -23,26 +22,20 @@ pub(super) fn get_files( env: Env, globs: Vec, parse_configurations: ConfigurationParser, - file_data: Option<&[(PathBuf, String)]>, + file_data: &[(PathBuf, String)], ) -> napi::Result> where ConfigurationParser: Fn(Vec) -> napi::Result>>, { - let Some(file_data) = file_data else { - return Ok(Default::default()); - }; - trace!("{globs:?}"); let file_data = file_data.to_vec(); let promise = - config_files::get_project_configurations(globs, Some(&file_data), parse_configurations)?; + config_files::get_project_configurations(globs, &file_data, parse_configurations)?; let result = env.spawn_future(async move { let parsed_graph_nodes = promise.await?; - let root_map = transform_root_map( - parsed_graph_nodes - ); + let root_map = transform_root_map(parsed_graph_nodes); trace!(?root_map); @@ -82,12 +75,14 @@ where for (file_location, file_data) in file_locations { match file_location { FileLocation::Global => global_files.push(file_data), - FileLocation::Project(project_name) => match project_file_map.get_mut(&project_name) { - None => { - project_file_map.insert(project_name.clone(), vec![file_data]); + FileLocation::Project(project_name) => { + match project_file_map.get_mut(&project_name) { + None => { + project_file_map.insert(project_name.clone(), vec![file_data]); + } + Some(project_files) => project_files.push(file_data), } - Some(project_files) => project_files.push(file_data), - }, + } } } diff --git a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts index 91698963b536d..2494166376884 100644 --- a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts @@ -274,8 +274,6 @@ export async function createProjectConfigurations( workspaceRoot ); - let projectConfigurations = projects; - performance.mark('build-project-configs:end'); performance.measure( 'build-project-configs', @@ -284,7 +282,7 @@ export async function createProjectConfigurations( ); return { - projects: projectConfigurations, + projects, externalNodes, rootMap, }; diff --git a/packages/nx/src/utils/workspace-context.ts b/packages/nx/src/utils/workspace-context.ts index 73bf6bbbaf131..73a77b40dc6a7 100644 --- a/packages/nx/src/utils/workspace-context.ts +++ b/packages/nx/src/utils/workspace-context.ts @@ -27,10 +27,20 @@ export function getNxWorkspaceFilesFromContext( export function globWithWorkspaceContext( workspaceRoot: string, - globs: string[] + globs: string[], + exclude?: string[] +) { + ensureContextAvailable(workspaceRoot); + return workspaceContext.glob(globs, exclude); +} + +export function hashWithWorkspaceContext( + workspaceRoot: string, + globs: string[], + exclude?: string[] ) { ensureContextAvailable(workspaceRoot); - return workspaceContext.glob(globs); + return workspaceContext.hashFilesMatchingGlob(globs, exclude); } export function getProjectConfigurationsFromContext(