Skip to content

Commit

Permalink
Merge branch 'master' into fixing-project-graph-null
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasBelliard committed Jul 9, 2024
2 parents 6e9abe9 + 92e09d9 commit 33e3e91
Show file tree
Hide file tree
Showing 13 changed files with 369 additions and 117 deletions.
4 changes: 2 additions & 2 deletions docs/generated/cli/create-nx-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ Generate a 'src/' directory for Next.js

Type: `string`

Choices: [yes, github, circleci, skip]
Choices: [github, circleci, gitlab, azure, bitbucket, skip, yes]

Do you want Nx Cloud to make your CI fast?
Which CI provider would you like to use?

### packageManager

Expand Down
4 changes: 2 additions & 2 deletions docs/generated/packages/nx/documents/create-nx-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ Generate a 'src/' directory for Next.js

Type: `string`

Choices: [yes, github, circleci, skip]
Choices: [github, circleci, gitlab, azure, bitbucket, skip, yes]

Do you want Nx Cloud to make your CI fast?
Which CI provider would you like to use?

### packageManager

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,19 @@ function shouldSkipInitialTargetRun(
project: string,
target: string
): boolean {
const allTargetNames = new Set<string>();
for (const projectName in projectGraph.nodes) {
const project = projectGraph.nodes[projectName];
for (const targetName in project.data.targets ?? {}) {
allTargetNames.add(targetName);
}
}

const projectDependencyConfigs = getDependencyConfigs(
{ project, target },
{},
projectGraph
projectGraph,
Array.from(allTargetNames)
);

// if the task runner already ran the target, skip the initial run
Expand Down
60 changes: 41 additions & 19 deletions packages/create-nx-workspace/src/utils/nx/ab-testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,51 @@ import { execSync } from 'node:child_process';
import { isCI } from '../ci/is-ci';
import { getPackageManagerCommand } from '../package-manager';

export const NxCloudChoices = ['yes', 'github', 'circleci', 'skip'];
export const NxCloudChoices = [
'github',
'circleci',
'gitlab',
'azure',
'bitbucket',
'skip',
'yes', // Deprecated but still handled
];

const messageOptions = {
const messageOptions: Record<string, MessageData[]> = {
setupCI: [
{
code: 'enable-nx-cloud',
message: `Do you want Nx Cloud to make your CI fast?`,
initial: 1,
code: 'which-ci-provider',
message: `Which CI provider would you like to use?`,
initial: 0,
choices: [
{ value: 'yes', name: 'Yes, enable Nx Cloud' },
{ value: 'github', name: 'Yes, configure Nx Cloud for GitHub Actions' },
{ value: 'circleci', name: 'Yes, configure Nx Cloud for Circle CI' },
{ value: 'skip', name: 'Skip for now' },
{ value: 'github', name: 'GitHub Actions' },
{ value: 'circleci', name: 'Circle CI' },
{ value: 'gitlab', name: 'Gitlab' },
{ value: 'azure', name: 'Azure DevOps' },
{ value: 'bitbucket', name: 'BitBucket Pipelines' },
{ value: 'skip', name: '\nDo it later' },
],
footer:
'\nWatch a short video on Nx Cloud at https://nx.dev/ci/intro/why-nx-cloud',
hint: `\n(it's free and can be disabled any time)`,
fallback: undefined,
'\nRemote caching, task distribution, and test splitting are provided by Nx Cloud. Read more at https://nx.dev/ci',
fallback: { value: 'skip', key: 'setupNxCloud' },
},
{
code: 'set-up-ci',
message: `Set up CI with caching, distribution and test deflaking`,
initial: 0,
choices: [
{ value: 'github', name: 'Yes, for GitHub Actions with Nx Cloud' },
{ value: 'circleci', name: 'Yes, for CircleCI with Nx Cloud' },
{ value: 'skip', name: 'Skip for now' },
{ value: 'github', name: 'GitHub Actions' },
{ value: 'circleci', name: 'CircleCI' },
{ value: 'gitlab', name: 'Gitlab' },
{ value: 'azure', name: 'Azure DevOps' },
{
value: 'bitbucket',
name: 'BitBucket Pipelines',
},
{ value: 'skip', name: '\nDo it later' },
],
footer:
'\nWatch a short video on Nx Cloud at https://nx.dev/ci/intro/why-nx-cloud',
hint: `\n(it's free and can be disabled any time)`,
'\nRemote caching, task distribution, and test splitting are provided by Nx Cloud. Read more at https://nx.dev/ci',
fallback: { value: 'skip', key: 'setupNxCloud' },
},
],
Expand All @@ -51,10 +65,18 @@ const messageOptions = {
fallback: undefined,
},
],
} as const;
};

export type MessageKey = keyof typeof messageOptions;
type MessageData = (typeof messageOptions)[MessageKey][number];
interface MessageData {
code: string;
message: string;
initial: number;
choices: Array<{ value: string; name: string }>;
footer: string;
hint?: string;
fallback?: { value: string; key: MessageKey };
}

export class PromptMessages {
private selectedMessages: { [key in MessageKey]?: number } = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,11 @@ describe('eslint-utils', () => {
});

describe('ESLint Flat Config', () => {
it('should throw if a non eslint.config.js file is used with ESLint Flat Config', async () => {
it('should throw if a non eslint.config.js or eslint.config.cjs file is used with ESLint Flat Config', async () => {
await expect(
resolveAndInstantiateESLint('./.eslintrc.json', {} as any, true)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"When using the new Flat Config with ESLint, all configs must be named eslint.config.js and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files-new"`
`"When using the new Flat Config with ESLint, all configs must be named eslint.config.js or eslint.config.cjs and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files"`
);
});

Expand Down
3 changes: 2 additions & 1 deletion packages/eslint/src/executors/lint/utility/eslint-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export async function resolveAndInstantiateESLint(
) {
if (useFlatConfig && eslintConfigPath && !isFlatConfig(eslintConfigPath)) {
throw new Error(
'When using the new Flat Config with ESLint, all configs must be named eslint.config.js and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files-new'
// todo: add support for eslint.config.mjs,
'When using the new Flat Config with ESLint, all configs must be named eslint.config.js or eslint.config.cjs and .eslintrc files may not be used. See https://eslint.org/docs/latest/use/configure/configuration-files'
);
}
const ESLint = await resolveESLintClass(useFlatConfig);
Expand Down
19 changes: 11 additions & 8 deletions packages/eslint/src/generators/utils/eslint-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
} from '@nx/devkit';
import type { Tree } from '@nx/devkit';
import type { Linter } from 'eslint';
import { useFlatConfig } from '../../utils/flat-config';
import {
flatConfigEslintFilename,
useFlatConfig,
} from '../../utils/flat-config';
import {
addBlockToFlatConfigExport,
addCompatToFlatConfig,
Expand Down Expand Up @@ -174,7 +177,7 @@ export function addOverrideToLintConfig(
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(
root,
isBase ? baseEsLintFlatConfigFile : 'eslint.config.js'
isBase ? baseEsLintFlatConfigFile : flatConfigEslintFilename(tree)
);
const flatOverride = generateFlatOverride(override);
let content = tree.read(fileName, 'utf8');
Expand Down Expand Up @@ -220,7 +223,7 @@ export function updateOverrideInLintConfig(
) => Linter.ConfigOverride<Linter.RulesRecord>
) {
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(root, 'eslint.config.js');
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
let content = tree.read(fileName, 'utf8');
content = replaceOverride(content, root, lookup, update);
tree.write(fileName, content);
Expand Down Expand Up @@ -262,7 +265,7 @@ export function lintConfigHasOverride(
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(
root,
isBase ? baseEsLintFlatConfigFile : 'eslint.config.js'
isBase ? baseEsLintFlatConfigFile : flatConfigEslintFilename(tree)
);
const content = tree.read(fileName, 'utf8');
return hasOverride(content, lookup);
Expand All @@ -282,7 +285,7 @@ export function replaceOverridesInLintConfig(
overrides: Linter.ConfigOverride<Linter.RulesRecord>[]
) {
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(root, 'eslint.config.js');
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
let content = tree.read(fileName, 'utf8');
// we will be using compat here so we need to make sure it's added
if (overrides.some(overrideNeedsCompat)) {
Expand Down Expand Up @@ -311,7 +314,7 @@ export function addExtendsToLintConfig(
) {
const plugins = Array.isArray(plugin) ? plugin : [plugin];
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(root, 'eslint.config.js');
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
const pluginExtends = generatePluginExtendsElement(plugins);
let content = tree.read(fileName, 'utf8');
content = addCompatToFlatConfig(content);
Expand Down Expand Up @@ -341,7 +344,7 @@ export function addPluginsToLintConfig(
) {
const plugins = Array.isArray(plugin) ? plugin : [plugin];
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(root, 'eslint.config.js');
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
let content = tree.read(fileName, 'utf8');
const mappedPlugins: { name: string; varName: string; imp: string }[] = [];
plugins.forEach((name) => {
Expand Down Expand Up @@ -369,7 +372,7 @@ export function addIgnoresToLintConfig(
ignorePatterns: string[]
) {
if (useFlatConfig(tree)) {
const fileName = joinPathFragments(root, 'eslint.config.js');
const fileName = joinPathFragments(root, flatConfigEslintFilename(tree));
const block = generateAst<ts.ObjectLiteralExpression>({
ignores: ignorePatterns.map((path) => mapFilePath(path)),
});
Expand Down
5 changes: 3 additions & 2 deletions packages/eslint/src/utils/config-file.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { existsSync, statSync } from 'fs';
import { basename, dirname, join, resolve } from 'path';
import { eslintFlatConfigFilenames } from './flat-config';

// TODO(leo): add support for eslint.config.mjs and eslint.config.cjs
export const ESLINT_FLAT_CONFIG_FILENAMES = ['eslint.config.js'];
export const ESLINT_FLAT_CONFIG_FILENAMES = eslintFlatConfigFilenames;

export const ESLINT_OLD_CONFIG_FILENAMES = [
'.eslintrc',
Expand Down Expand Up @@ -40,6 +40,7 @@ export function findFlatConfigFile(
ESLINT_FLAT_CONFIG_FILENAMES
);
if (configFilePath) {
console.log(`Found eslint flat config file at: ${configFilePath}`);
return configFilePath;
}
if (currentDir === workspaceRoot) {
Expand Down
21 changes: 20 additions & 1 deletion packages/eslint/src/utils/flat-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
import { Tree } from '@nx/devkit';

// todo: add support for eslint.config.mjs,
export const eslintFlatConfigFilenames = [
'eslint.config.js',
'eslint.config.cjs',
];

export function flatConfigEslintFilename(tree: Tree): string {
for (const file of eslintFlatConfigFilenames) {
if (tree.exists(file)) {
return file;
}
}
throw new Error('Could not find flat config file');
}

export function useFlatConfig(tree: Tree): boolean {
return tree.exists('eslint.config.js');
try {
return !!flatConfigEslintFilename(tree);
} catch {
return false;
}
}
68 changes: 18 additions & 50 deletions packages/nx/src/tasks-runner/create-task-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,27 @@ import {
import { Task, TaskGraph } from '../config/task-graph';
import { TargetDefaults, TargetDependencies } from '../config/nx-json';
import { TargetDependencyConfig } from '../devkit-exports';
import { findMatchingProjects } from '../utils/find-matching-projects';
import { output } from '../utils/output';

export class ProcessTasks {
private readonly seen = new Set<string>();
readonly tasks: { [id: string]: Task } = {};
readonly dependencies: { [k: string]: string[] } = {};
private readonly allTargetNames: string[];

constructor(
private readonly extraTargetDependencies: TargetDependencies,
private readonly projectGraph: ProjectGraph
) {}
) {
const allTargetNames = new Set<string>();
for (const projectName in projectGraph.nodes) {
const project = projectGraph.nodes[projectName];
for (const targetName in project.data.targets ?? {}) {
allTargetNames.add(targetName);
}
}
this.allTargetNames = Array.from(allTargetNames);
}

processTasks(
projectNames: string[],
Expand Down Expand Up @@ -100,48 +109,16 @@ export class ProcessTasks {
const dependencyConfigs = getDependencyConfigs(
{ project: task.target.project, target: task.target.target },
this.extraTargetDependencies,
this.projectGraph
this.projectGraph,
this.allTargetNames
);
for (const dependencyConfig of dependencyConfigs) {
const taskOverrides =
dependencyConfig.params === 'forward'
? overrides
: { __overrides_unparsed__: [] };
if (dependencyConfig.projects) {
/** LERNA SUPPORT START - Remove in v20 */
// Lerna uses `dependencies` in `prepNxOptions`, so we need to maintain
// support for it until lerna can be updated to use the syntax.
//
// This should have been removed in v17, but the updates to lerna had not
// been made yet.
//
// TODO(@agentender): Remove this part in v20
if (typeof dependencyConfig.projects === 'string') {
if (dependencyConfig.projects === 'self') {
this.processTasksForSingleProject(
task,
task.target.project,
dependencyConfig,
configuration,
taskOverrides,
overrides
);
continue;
} else if (dependencyConfig.projects === 'dependencies') {
this.processTasksForDependencies(
projectUsedToDeriveDependencies,
dependencyConfig,
configuration,
task,
taskOverrides,
overrides
);
continue;
}
}
/** LERNA SUPPORT END - Remove in v17 */

this.processTasksForMatchingProjects(
this.processTasksForMultipleProjects(
dependencyConfig,
configuration,
task,
Expand Down Expand Up @@ -170,31 +147,22 @@ export class ProcessTasks {
}
}

private processTasksForMatchingProjects(
private processTasksForMultipleProjects(
dependencyConfig: TargetDependencyConfig,
configuration: string,
task: Task,
taskOverrides: Object | { __overrides_unparsed__: any[] },
overrides: Object
) {
const targetProjectSpecifiers =
typeof dependencyConfig.projects === 'string'
? [dependencyConfig.projects]
: dependencyConfig.projects;
const matchingProjects = findMatchingProjects(
targetProjectSpecifiers,
this.projectGraph.nodes
);

if (matchingProjects.length === 0) {
if (dependencyConfig.projects.length === 0) {
output.warn({
title: `\`dependsOn\` is misconfigured for ${task.target.project}:${task.target.target}`,
bodyLines: [
`Project patterns "${targetProjectSpecifiers}" does not match any projects.`,
`Project patterns "${dependencyConfig.projects}" does not match any projects.`,
],
});
}
for (const projectName of matchingProjects) {
for (const projectName of dependencyConfig.projects) {
this.processTasksForSingleProject(
task,
projectName,
Expand Down
Loading

0 comments on commit 33e3e91

Please sign in to comment.