fix(linter): refactor pcv3 plugin, expose configFiles on context (#21677)
This commit is contained in:
parent
cea7e93c86
commit
1fe5b98f45
@ -1,6 +1,6 @@
|
|||||||
# Type alias: CreateNodes\<T\>
|
# Type alias: CreateNodes\<T\>
|
||||||
|
|
||||||
Ƭ **CreateNodes**\<`T`\>: readonly [projectFilePattern: string, createNodesFunction: CreateNodesFunction\<T\>]
|
Ƭ **CreateNodes**\<`T`\>: readonly [configFilePattern: string, createNodesFunction: CreateNodesFunction\<T\>]
|
||||||
|
|
||||||
A pair of file patterns and [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
|
A pair of file patterns and [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
|
||||||
|
|
||||||
|
|||||||
@ -6,11 +6,20 @@ Context for [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
|
|||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
|
- [configFiles](../../devkit/documents/CreateNodesContext#configfiles): string[]
|
||||||
- [nxJsonConfiguration](../../devkit/documents/CreateNodesContext#nxjsonconfiguration): NxJsonConfiguration<string[] | "\*">
|
- [nxJsonConfiguration](../../devkit/documents/CreateNodesContext#nxjsonconfiguration): NxJsonConfiguration<string[] | "\*">
|
||||||
- [workspaceRoot](../../devkit/documents/CreateNodesContext#workspaceroot): string
|
- [workspaceRoot](../../devkit/documents/CreateNodesContext#workspaceroot): string
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
|
### configFiles
|
||||||
|
|
||||||
|
• `Readonly` **configFiles**: `string`[]
|
||||||
|
|
||||||
|
The subset of configuration files which match the createNodes pattern
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### nxJsonConfiguration
|
### nxJsonConfiguration
|
||||||
|
|
||||||
• `Readonly` **nxJsonConfiguration**: [`NxJsonConfiguration`](../../devkit/documents/NxJsonConfiguration)\<`string`[] \| `"*"`\>
|
• `Readonly` **nxJsonConfiguration**: [`NxJsonConfiguration`](../../devkit/documents/NxJsonConfiguration)\<`string`[] \| `"*"`\>
|
||||||
|
|||||||
@ -34,6 +34,7 @@ describe('@nx/cypress/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -48,6 +48,7 @@ export async function replaceProjectConfigurationsWithPlugin<T = unknown>(
|
|||||||
const nodes = await createNodesFunction(configFile, pluginOptions, {
|
const nodes = await createNodesFunction(configFile, pluginOptions, {
|
||||||
workspaceRoot: tree.root,
|
workspaceRoot: tree.root,
|
||||||
nxJsonConfiguration: readNxJson(tree),
|
nxJsonConfiguration: readNxJson(tree),
|
||||||
|
configFiles,
|
||||||
});
|
});
|
||||||
const node = nodes.projects[Object.keys(nodes.projects)[0]];
|
const node = nodes.projects[Object.keys(nodes.projects)[0]];
|
||||||
|
|
||||||
|
|||||||
@ -25,11 +25,18 @@ export async function updatePackageScripts(
|
|||||||
const nxJson = readNxJson(tree);
|
const nxJson = readNxJson(tree);
|
||||||
|
|
||||||
const [pattern, createNodes] = createNodesTuple;
|
const [pattern, createNodes] = createNodesTuple;
|
||||||
const files = glob(tree, [pattern]);
|
const matchingFiles = glob(tree, [pattern]);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of matchingFiles) {
|
||||||
const projectRoot = getProjectRootFromConfigFile(file);
|
const projectRoot = getProjectRootFromConfigFile(file);
|
||||||
await processProject(tree, projectRoot, file, createNodes, nxJson);
|
await processProject(
|
||||||
|
tree,
|
||||||
|
projectRoot,
|
||||||
|
file,
|
||||||
|
createNodes,
|
||||||
|
nxJson,
|
||||||
|
matchingFiles
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +45,8 @@ async function processProject(
|
|||||||
projectRoot: string,
|
projectRoot: string,
|
||||||
projectConfigurationFile: string,
|
projectConfigurationFile: string,
|
||||||
createNodesFunction: CreateNodesFunction,
|
createNodesFunction: CreateNodesFunction,
|
||||||
nxJsonConfiguration: NxJsonConfiguration
|
nxJsonConfiguration: NxJsonConfiguration,
|
||||||
|
configFiles: string[]
|
||||||
) {
|
) {
|
||||||
const packageJsonPath = `${projectRoot}/package.json`;
|
const packageJsonPath = `${projectRoot}/package.json`;
|
||||||
if (!tree.exists(packageJsonPath)) {
|
if (!tree.exists(packageJsonPath)) {
|
||||||
@ -52,7 +60,11 @@ async function processProject(
|
|||||||
const result = await createNodesFunction(
|
const result = await createNodesFunction(
|
||||||
projectConfigurationFile,
|
projectConfigurationFile,
|
||||||
{},
|
{},
|
||||||
{ nxJsonConfiguration, workspaceRoot }
|
{
|
||||||
|
nxJsonConfiguration,
|
||||||
|
workspaceRoot,
|
||||||
|
configFiles,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetCommands = getInferredTargetCommands(result);
|
const targetCommands = getInferredTargetCommands(result);
|
||||||
|
|||||||
@ -1,23 +1,30 @@
|
|||||||
import { CreateNodesContext } from '@nx/devkit';
|
import 'nx/src/internal-testing-utils/mock-fs';
|
||||||
import { createNodes } from './plugin';
|
|
||||||
import { vol } from 'memfs';
|
|
||||||
|
|
||||||
jest.mock('fs', () => {
|
jest.mock(
|
||||||
const memFs = require('memfs').fs;
|
'nx/src/utils/workspace-context',
|
||||||
return {
|
(): Partial<typeof import('nx/src/utils/workspace-context')> => {
|
||||||
...memFs,
|
const glob = require('fast-glob');
|
||||||
existsSync: (p) => (p.endsWith('.node') ? true : memFs.existsSync(p)),
|
return {
|
||||||
};
|
globWithWorkspaceContext(workspaceRoot: string, patterns: string[]) {
|
||||||
});
|
// This glob will operate on memfs thanks to 'nx/src/internal-testing-utils/mock-fs'
|
||||||
|
return glob.sync(patterns, { cwd: workspaceRoot });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
import { CreateNodesContext } from '@nx/devkit';
|
||||||
|
import { vol } from 'memfs';
|
||||||
|
import { minimatch } from 'minimatch';
|
||||||
|
import { createNodes } from './plugin';
|
||||||
|
|
||||||
describe('@nx/eslint/plugin', () => {
|
describe('@nx/eslint/plugin', () => {
|
||||||
let createNodesFunction = createNodes[1];
|
|
||||||
let context: CreateNodesContext;
|
let context: CreateNodesContext;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
context = {
|
context = {
|
||||||
nxJsonConfiguration: {
|
nxJsonConfiguration: {
|
||||||
// These defaults should be overridden by plugin
|
// These defaults should be overridden by the plugin
|
||||||
targetDefaults: {
|
targetDefaults: {
|
||||||
lint: {
|
lint: {
|
||||||
cache: false,
|
cache: false,
|
||||||
@ -30,6 +37,7 @@ describe('@nx/eslint/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: '',
|
workspaceRoot: '',
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -38,117 +46,433 @@ describe('@nx/eslint/plugin', () => {
|
|||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create nodes with default configuration for nested project', () => {
|
it('should not create any nodes when there are no eslint configs', async () => {
|
||||||
const fileSys = {
|
applyFilesToVolAndContext(
|
||||||
'apps/my-app/.eslintrc.json': `{}`,
|
|
||||||
'apps/my-app/project.json': `{}`,
|
|
||||||
'.eslintrc.json': `{}`,
|
|
||||||
'package.json': `{}`,
|
|
||||||
};
|
|
||||||
vol.fromJSON(fileSys, '');
|
|
||||||
const nodes = createNodesFunction(
|
|
||||||
'apps/my-app/project.json',
|
|
||||||
{
|
{
|
||||||
targetName: 'lint',
|
'package.json': `{}`,
|
||||||
|
'project.json': `{}`,
|
||||||
},
|
},
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
expect(nodes).toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"projects": {
|
"projects": {},
|
||||||
"apps/my-app": {
|
|
||||||
"targets": {
|
|
||||||
"lint": {
|
|
||||||
"cache": true,
|
|
||||||
"command": "eslint .",
|
|
||||||
"inputs": [
|
|
||||||
"default",
|
|
||||||
"{workspaceRoot}/.eslintrc.json",
|
|
||||||
"{workspaceRoot}/apps/my-app/.eslintrc.json",
|
|
||||||
"{workspaceRoot}/tools/eslint-rules/**/*",
|
|
||||||
{
|
|
||||||
"externalDependencies": [
|
|
||||||
"eslint",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"options": {
|
|
||||||
"cwd": "apps/my-app",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create nodes with default configuration for standalone project', () => {
|
describe('root eslint config only', () => {
|
||||||
const fileSys = {
|
it('should not create any nodes for just a package.json and root level eslint config', async () => {
|
||||||
'apps/my-app/eslint.config.js': `module.exports = []`,
|
applyFilesToVolAndContext(
|
||||||
'apps/my-app/project.json': `{}`,
|
{
|
||||||
'eslint.config.js': `module.exports = []`,
|
'.eslintrc.json': `{}`,
|
||||||
'src/index.ts': `console.log('hello world')`,
|
'package.json': `{}`,
|
||||||
'package.json': `{}`,
|
},
|
||||||
};
|
context
|
||||||
vol.fromJSON(fileSys, '');
|
);
|
||||||
const nodes = createNodesFunction(
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
'package.json',
|
.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
targetName: 'lint',
|
"projects": {},
|
||||||
},
|
}
|
||||||
context
|
`);
|
||||||
);
|
});
|
||||||
|
|
||||||
expect(nodes).toMatchInlineSnapshot(`
|
it('should not create a node for a root level eslint config when accompanied by a project.json, if no src directory is present', async () => {
|
||||||
{
|
applyFilesToVolAndContext(
|
||||||
"projects": {
|
{
|
||||||
".": {
|
'eslint.config.js': `module.exports = {};`,
|
||||||
"targets": {
|
'project.json': `{}`,
|
||||||
"lint": {
|
},
|
||||||
"cache": true,
|
context
|
||||||
"command": "eslint ./src",
|
);
|
||||||
"inputs": [
|
// NOTE: It should set ESLINT_USE_FLAT_CONFIG to true because of the use of eslint.config.js
|
||||||
"default",
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
"{workspaceRoot}/eslint.config.js",
|
.toMatchInlineSnapshot(`
|
||||||
"{workspaceRoot}/tools/eslint-rules/**/*",
|
{
|
||||||
{
|
"projects": {},
|
||||||
"externalDependencies": [
|
}
|
||||||
"eslint",
|
`);
|
||||||
],
|
});
|
||||||
},
|
|
||||||
],
|
// Standalone Nx workspace style setup
|
||||||
"options": {
|
it('should create a node for just a package.json and root level eslint config if accompanied by a src directory', async () => {
|
||||||
"cwd": ".",
|
applyFilesToVolAndContext(
|
||||||
"env": {
|
{
|
||||||
"ESLINT_USE_FLAT_CONFIG": "true",
|
'.eslintrc.json': `{}`,
|
||||||
|
'package.json': `{}`,
|
||||||
|
'src/index.ts': `console.log('hello world')`,
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
// NOTE: The command is specifically targeting the src directory in the case of a standalone Nx workspace
|
||||||
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
".": {
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "eslint ./src",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"^default",
|
||||||
|
"{projectRoot}/eslintrc.json",
|
||||||
|
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": ".",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a node for a nested project (with a project.json and any lintable file) which does not have its own eslint config if accompanied by a root level eslint config', async () => {
|
||||||
|
applyFilesToVolAndContext(
|
||||||
|
{
|
||||||
|
'.eslintrc.json': `{}`,
|
||||||
|
'apps/my-app/project.json': `{}`,
|
||||||
|
// This file is lintable so create the target
|
||||||
|
'apps/my-app/index.ts': `console.log('hello world')`,
|
||||||
},
|
},
|
||||||
}
|
context
|
||||||
`);
|
);
|
||||||
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
"apps/my-app": {
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "eslint .",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"^default",
|
||||||
|
"{workspaceRoot}/.eslintrc.json",
|
||||||
|
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "apps/my-app",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a node for a nested project (with a package.json and any lintable file) which does not have its own eslint config if accompanied by a root level eslint config', async () => {
|
||||||
|
applyFilesToVolAndContext(
|
||||||
|
{
|
||||||
|
'.eslintrc.json': `{}`,
|
||||||
|
'apps/my-app/package.json': `{}`,
|
||||||
|
// This file is lintable so create the target
|
||||||
|
'apps/my-app/index.ts': `console.log('hello world')`,
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
"apps/my-app": {
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "eslint .",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"^default",
|
||||||
|
"{workspaceRoot}/.eslintrc.json",
|
||||||
|
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "apps/my-app",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create a node for a nested project (with a package.json and no lintable files) which does not have its own eslint config if accompanied by a root level eslint config', async () => {
|
||||||
|
applyFilesToVolAndContext(
|
||||||
|
{
|
||||||
|
'.eslintrc.json': `{}`,
|
||||||
|
'apps/my-app/package.json': `{}`,
|
||||||
|
// These files are not lintable so do not create the target
|
||||||
|
'apps/my-app/one.png': `...`,
|
||||||
|
'apps/my-app/two.mov': `...`,
|
||||||
|
'apps/my-app/three.css': `...`,
|
||||||
|
'apps/my-app/config-one.yaml': `...`,
|
||||||
|
'apps/my-app/config-two.yml': `...`,
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"projects": {},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create a node for a nested project (with a project.json and no lintable files) which does not have its own eslint config if accompanied by a root level eslint config', async () => {
|
||||||
|
applyFilesToVolAndContext(
|
||||||
|
{
|
||||||
|
'.eslintrc.json': `{}`,
|
||||||
|
'apps/my-app/project.json': `{}`,
|
||||||
|
// These files are not lintable so do not create the target
|
||||||
|
'apps/my-app/one.png': `...`,
|
||||||
|
'apps/my-app/two.mov': `...`,
|
||||||
|
'apps/my-app/three.css': `...`,
|
||||||
|
'apps/my-app/config-one.yaml': `...`,
|
||||||
|
'apps/my-app/config-two.yml': `...`,
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"projects": {},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not create nodes if no src folder for root', () => {
|
describe('nested eslint configs only', () => {
|
||||||
const fileSys = {
|
it('should create appropriate nodes for nested projects without a root level eslint config', async () => {
|
||||||
'apps/my-app/eslint.config.js': `module.exports = []`,
|
applyFilesToVolAndContext(
|
||||||
'apps/my-app/project.json': `{}`,
|
{
|
||||||
'eslint.config.js': `module.exports = []`,
|
'apps/my-app/.eslintrc.json': `{}`,
|
||||||
'package.json': `{}`,
|
'apps/my-app/project.json': `{}`,
|
||||||
};
|
'apps/my-app/index.ts': `console.log('hello world')`,
|
||||||
vol.fromJSON(fileSys, '');
|
'libs/my-lib/.eslintrc.json': `{}`,
|
||||||
const nodes = createNodesFunction(
|
'libs/my-lib/project.json': `{}`,
|
||||||
'package.json',
|
'libs/my-lib/index.ts': `console.log('hello world')`,
|
||||||
{
|
},
|
||||||
targetName: 'lint',
|
context
|
||||||
},
|
);
|
||||||
context
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
);
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
"apps/my-app": {
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "eslint .",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"^default",
|
||||||
|
"{projectRoot}/.eslintrc.json",
|
||||||
|
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "apps/my-app",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"libs/my-lib": {
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "eslint .",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"^default",
|
||||||
|
"{projectRoot}/.eslintrc.json",
|
||||||
|
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "libs/my-lib",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
expect(nodes).toMatchInlineSnapshot(`{}`);
|
describe('root eslint config and nested eslint configs', () => {
|
||||||
|
it('should create appropriate nodes for just a package.json and root level eslint config combined with nested eslint configs', async () => {
|
||||||
|
applyFilesToVolAndContext(
|
||||||
|
{
|
||||||
|
'.eslintrc.json': `{}`,
|
||||||
|
'package.json': `{}`,
|
||||||
|
'apps/my-app/.eslintrc.json': `{}`,
|
||||||
|
'apps/my-app/project.json': `{}`,
|
||||||
|
'apps/my-app/index.ts': `console.log('hello world')`,
|
||||||
|
'libs/my-lib/.eslintrc.json': `{}`,
|
||||||
|
'libs/my-lib/project.json': `{}`,
|
||||||
|
'libs/my-lib/index.ts': `console.log('hello world')`,
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
// NOTE: The nested projects have the root level config as an input to their lint targets
|
||||||
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
"apps/my-app": {
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "eslint .",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"^default",
|
||||||
|
"{workspaceRoot}/.eslintrc.json",
|
||||||
|
"{projectRoot}/.eslintrc.json",
|
||||||
|
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "apps/my-app",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"libs/my-lib": {
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "eslint .",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"^default",
|
||||||
|
"{workspaceRoot}/.eslintrc.json",
|
||||||
|
"{projectRoot}/.eslintrc.json",
|
||||||
|
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "libs/my-lib",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create appropriate nodes for a nested project without its own eslint config but with an orphaned eslint config in its parent hierarchy', async () => {
|
||||||
|
applyFilesToVolAndContext(
|
||||||
|
{
|
||||||
|
'.eslintrc.json': '{}',
|
||||||
|
'apps/.eslintrc.json': '{}',
|
||||||
|
'apps/myapp/project.json': '{}',
|
||||||
|
'apps/myapp/index.ts': 'console.log("hello world")',
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
// NOTE: The nested projects have the root level config as an input to their lint targets
|
||||||
|
expect(await invokeCreateNodesOnMatchingFiles(context, 'lint'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
"apps/myapp": {
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"cache": true,
|
||||||
|
"command": "eslint .",
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"^default",
|
||||||
|
"{workspaceRoot}/.eslintrc.json",
|
||||||
|
"{workspaceRoot}/apps/.eslintrc.json",
|
||||||
|
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||||
|
{
|
||||||
|
"externalDependencies": [
|
||||||
|
"eslint",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "apps/myapp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getMatchingFiles(allConfigFiles: string[]): string[] {
|
||||||
|
return allConfigFiles.filter((file) =>
|
||||||
|
minimatch(file, createNodes[0], { dot: true })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilesToVolAndContext(
|
||||||
|
fileSys: Record<string, string>,
|
||||||
|
context: CreateNodesContext
|
||||||
|
) {
|
||||||
|
vol.fromJSON(fileSys, '');
|
||||||
|
// @ts-expect-error update otherwise readonly property for testing
|
||||||
|
context.configFiles = getMatchingFiles(Object.keys(fileSys));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function invokeCreateNodesOnMatchingFiles(
|
||||||
|
context: CreateNodesContext,
|
||||||
|
targetName: string
|
||||||
|
) {
|
||||||
|
const aggregateProjects: Record<string, any> = {};
|
||||||
|
for (const file of context.configFiles) {
|
||||||
|
const nodes = await createNodes[1](file, { targetName }, context);
|
||||||
|
Object.assign(aggregateProjects, nodes.projects);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
projects: aggregateProjects,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -1,110 +1,158 @@
|
|||||||
import { CreateNodes, TargetConfiguration } from '@nx/devkit';
|
import {
|
||||||
import { dirname, join } from 'path';
|
CreateNodes,
|
||||||
import { readdirSync } from 'fs';
|
CreateNodesContext,
|
||||||
|
CreateNodesResult,
|
||||||
|
TargetConfiguration,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { existsSync } from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
import { combineGlobPatterns } from 'nx/src/utils/globs';
|
import { combineGlobPatterns } from 'nx/src/utils/globs';
|
||||||
|
import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context';
|
||||||
import {
|
import {
|
||||||
ESLINT_CONFIG_FILENAMES,
|
ESLINT_CONFIG_FILENAMES,
|
||||||
findBaseEslintFile,
|
baseEsLintConfigFile,
|
||||||
|
baseEsLintFlatConfigFile,
|
||||||
isFlatConfig,
|
isFlatConfig,
|
||||||
} from '../utils/config-file';
|
} from '../utils/config-file';
|
||||||
|
|
||||||
export interface EslintPluginOptions {
|
export interface EslintPluginOptions {
|
||||||
targetName?: string;
|
targetName?: string;
|
||||||
|
extensions?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createNodes: CreateNodes<EslintPluginOptions> = [
|
const DEFAULT_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'html', 'vue'];
|
||||||
combineGlobPatterns(['**/project.json', '**/package.json']),
|
|
||||||
(configFilePath, options, context) => {
|
|
||||||
const projectRoot = dirname(configFilePath);
|
|
||||||
|
|
||||||
|
export const createNodes: CreateNodes<EslintPluginOptions> = [
|
||||||
|
combineGlobPatterns([
|
||||||
|
...ESLINT_CONFIG_FILENAMES.map((f) => `**/${f}`),
|
||||||
|
baseEsLintConfigFile,
|
||||||
|
baseEsLintFlatConfigFile,
|
||||||
|
]),
|
||||||
|
(configFilePath, options, context) => {
|
||||||
options = normalizeOptions(options);
|
options = normalizeOptions(options);
|
||||||
|
|
||||||
const eslintConfigs = getEslintConfigsForProject(
|
// Ensure that configFiles are set, e2e-run fails due to them being undefined in CI (does not occur locally)
|
||||||
projectRoot,
|
// TODO(JamesHenry): Further troubleshoot this in CI
|
||||||
context.workspaceRoot
|
(context as any).configFiles = context.configFiles ?? [];
|
||||||
);
|
|
||||||
if (!eslintConfigs.length) {
|
// Create a Set of all the directories containing eslint configs
|
||||||
return {};
|
const eslintRoots = new Set(context.configFiles.map(dirname));
|
||||||
}
|
const configDir = dirname(configFilePath);
|
||||||
|
|
||||||
|
const childProjectRoots = globWithWorkspaceContext(
|
||||||
|
context.workspaceRoot,
|
||||||
|
[
|
||||||
|
'project.json',
|
||||||
|
'package.json',
|
||||||
|
'**/project.json',
|
||||||
|
'**/package.json',
|
||||||
|
].map((f) => join(configDir, f))
|
||||||
|
)
|
||||||
|
.map((f) => dirname(f))
|
||||||
|
.filter((childProjectRoot) => {
|
||||||
|
// Filter out projects under other eslint configs
|
||||||
|
let root = childProjectRoot;
|
||||||
|
// Traverse up from the childProjectRoot to either the workspaceRoot or the dir of this config file
|
||||||
|
while (root !== dirname(root) && root !== dirname(configFilePath)) {
|
||||||
|
if (eslintRoots.has(root)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
root = dirname(root);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.filter((dir) => {
|
||||||
|
// Ignore project roots where the project does not contain any lintable files
|
||||||
|
const lintableFiles = globWithWorkspaceContext(context.workspaceRoot, [
|
||||||
|
join(dir, `**/*.{${options.extensions.join(',')}}`),
|
||||||
|
]);
|
||||||
|
return lintableFiles.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const uniqueChildProjectRoots = Array.from(new Set(childProjectRoots));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projects: {
|
projects: getProjectsUsingESLintConfig(
|
||||||
[projectRoot]: {
|
configFilePath,
|
||||||
targets: buildEslintTargets(eslintConfigs, projectRoot, options),
|
uniqueChildProjectRoots,
|
||||||
},
|
options,
|
||||||
},
|
context
|
||||||
|
),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getEslintConfigsForProject(
|
function getProjectsUsingESLintConfig(
|
||||||
projectRoot: string,
|
configFilePath: string,
|
||||||
workspaceRoot: string
|
childProjectRoots: string[],
|
||||||
): string[] {
|
options: EslintPluginOptions,
|
||||||
const detectedConfigs = new Set<string>();
|
context: CreateNodesContext
|
||||||
const baseConfig = findBaseEslintFile(workspaceRoot);
|
): CreateNodesResult['projects'] {
|
||||||
if (baseConfig) {
|
const projects: CreateNodesResult['projects'] = {};
|
||||||
detectedConfigs.add(baseConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
let siblingFiles = readdirSync(join(workspaceRoot, projectRoot));
|
const rootEslintConfig = context.configFiles.find(
|
||||||
|
(f) =>
|
||||||
if (projectRoot === '.') {
|
f === baseEsLintConfigFile ||
|
||||||
// If there's no src folder, it's not a standalone project
|
f === baseEsLintFlatConfigFile ||
|
||||||
if (!siblingFiles.includes('src')) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
// If it's standalone but doesn't have eslint config, it's not a lintable
|
|
||||||
const config = siblingFiles.find((f) =>
|
|
||||||
ESLINT_CONFIG_FILENAMES.includes(f)
|
ESLINT_CONFIG_FILENAMES.includes(f)
|
||||||
);
|
|
||||||
if (!config) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
detectedConfigs.add(config);
|
|
||||||
return Array.from(detectedConfigs);
|
|
||||||
}
|
|
||||||
while (projectRoot !== '.') {
|
|
||||||
// if it has an eslint config it's lintable
|
|
||||||
const config = siblingFiles.find((f) =>
|
|
||||||
ESLINT_CONFIG_FILENAMES.includes(f)
|
|
||||||
);
|
|
||||||
if (config) {
|
|
||||||
detectedConfigs.add(`${projectRoot}/${config}`);
|
|
||||||
return Array.from(detectedConfigs);
|
|
||||||
}
|
|
||||||
projectRoot = dirname(projectRoot);
|
|
||||||
siblingFiles = readdirSync(join(workspaceRoot, projectRoot));
|
|
||||||
}
|
|
||||||
// check whether the root has an eslint config
|
|
||||||
const config = readdirSync(workspaceRoot).find((f) =>
|
|
||||||
ESLINT_CONFIG_FILENAMES.includes(f)
|
|
||||||
);
|
);
|
||||||
if (config) {
|
|
||||||
detectedConfigs.add(config);
|
// Add a lint target for each child project without an eslint config, with the root level config as an input
|
||||||
return Array.from(detectedConfigs);
|
for (const projectRoot of childProjectRoots) {
|
||||||
|
// If there's no src folder, it's not a standalone project, do not add the target at all
|
||||||
|
const isStandaloneWorkspace =
|
||||||
|
projectRoot === '.' &&
|
||||||
|
existsSync(join(context.workspaceRoot, projectRoot, 'src')) &&
|
||||||
|
existsSync(join(context.workspaceRoot, projectRoot, 'package.json'));
|
||||||
|
if (projectRoot === '.' && !isStandaloneWorkspace) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eslintConfigs = [configFilePath];
|
||||||
|
|
||||||
|
if (rootEslintConfig && !eslintConfigs.includes(rootEslintConfig)) {
|
||||||
|
eslintConfigs.unshift(rootEslintConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
projects[projectRoot] = {
|
||||||
|
targets: buildEslintTargets(
|
||||||
|
eslintConfigs,
|
||||||
|
projectRoot,
|
||||||
|
options,
|
||||||
|
isStandaloneWorkspace
|
||||||
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return [];
|
|
||||||
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildEslintTargets(
|
function buildEslintTargets(
|
||||||
eslintConfigs: string[],
|
eslintConfigs: string[],
|
||||||
projectRoot: string,
|
projectRoot: string,
|
||||||
options: EslintPluginOptions
|
options: EslintPluginOptions,
|
||||||
|
isStandaloneWorkspace = false
|
||||||
) {
|
) {
|
||||||
const isRootProject = projectRoot === '.';
|
const isRootProject = projectRoot === '.';
|
||||||
|
|
||||||
const targets: Record<string, TargetConfiguration> = {};
|
const targets: Record<string, TargetConfiguration> = {};
|
||||||
|
|
||||||
const targetConfig: TargetConfiguration = {
|
const targetConfig: TargetConfiguration = {
|
||||||
command: `eslint ${isRootProject ? './src' : '.'}`,
|
command: `eslint ${isRootProject && isStandaloneWorkspace ? './src' : '.'}`,
|
||||||
cache: true,
|
cache: true,
|
||||||
options: {
|
options: {
|
||||||
cwd: projectRoot,
|
cwd: projectRoot,
|
||||||
},
|
},
|
||||||
inputs: [
|
inputs: [
|
||||||
'default',
|
'default',
|
||||||
...eslintConfigs.map((config) => `{workspaceRoot}/${config}`),
|
// Certain lint rules can be impacted by changes to dependencies
|
||||||
|
'^default',
|
||||||
|
...eslintConfigs.map((config) =>
|
||||||
|
`{workspaceRoot}/${config}`.replace(
|
||||||
|
`{workspaceRoot}/${projectRoot}`,
|
||||||
|
isRootProject ? '{projectRoot}/' : '{projectRoot}'
|
||||||
|
)
|
||||||
|
),
|
||||||
'{workspaceRoot}/tools/eslint-rules/**/*',
|
'{workspaceRoot}/tools/eslint-rules/**/*',
|
||||||
{ externalDependencies: ['eslint'] },
|
{ externalDependencies: ['eslint'] },
|
||||||
],
|
],
|
||||||
@ -123,5 +171,13 @@ function buildEslintTargets(
|
|||||||
function normalizeOptions(options: EslintPluginOptions): EslintPluginOptions {
|
function normalizeOptions(options: EslintPluginOptions): EslintPluginOptions {
|
||||||
options ??= {};
|
options ??= {};
|
||||||
options.targetName ??= 'lint';
|
options.targetName ??= 'lint';
|
||||||
|
|
||||||
|
// Normalize user input for extensions (strip leading . characters)
|
||||||
|
if (Array.isArray(options.extensions)) {
|
||||||
|
options.extensions = options.extensions.map((f) => f.replace(/^\.+/, ''));
|
||||||
|
} else {
|
||||||
|
options.extensions = DEFAULT_EXTENSIONS;
|
||||||
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,22 +14,6 @@ export const ESLINT_CONFIG_FILENAMES = [
|
|||||||
export const baseEsLintConfigFile = '.eslintrc.base.json';
|
export const baseEsLintConfigFile = '.eslintrc.base.json';
|
||||||
export const baseEsLintFlatConfigFile = 'eslint.base.config.js';
|
export const baseEsLintFlatConfigFile = 'eslint.base.config.js';
|
||||||
|
|
||||||
export function findBaseEslintFile(workspaceRoot = ''): string | null {
|
|
||||||
if (existsSync(joinPathFragments(workspaceRoot, baseEsLintConfigFile))) {
|
|
||||||
return baseEsLintConfigFile;
|
|
||||||
}
|
|
||||||
if (existsSync(joinPathFragments(workspaceRoot, baseEsLintFlatConfigFile))) {
|
|
||||||
return baseEsLintFlatConfigFile;
|
|
||||||
}
|
|
||||||
for (const file of ESLINT_CONFIG_FILENAMES) {
|
|
||||||
if (existsSync(joinPathFragments(workspaceRoot, file))) {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFlatConfig(configFilePath: string): boolean {
|
export function isFlatConfig(configFilePath: string): boolean {
|
||||||
return configFilePath.endsWith('.config.js');
|
return configFilePath.endsWith('.config.js');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ describe('@nx/jest/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
await tempFs.createFiles({
|
await tempFs.createFiles({
|
||||||
|
|||||||
@ -18,6 +18,7 @@ describe('@nx/next/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: '',
|
workspaceRoot: '',
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,6 +54,7 @@ describe('@nx/next/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
tempFs.createFileSync(
|
tempFs.createFileSync(
|
||||||
|
|||||||
@ -38,6 +38,7 @@ describe('@nx/nuxt/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: '',
|
workspaceRoot: '',
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,6 +73,7 @@ describe('@nx/nuxt/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
tempFs.createFileSync(
|
tempFs.createFileSync(
|
||||||
|
|||||||
@ -14,6 +14,7 @@ describe('nx project.json plugin', () => {
|
|||||||
context = {
|
context = {
|
||||||
nxJsonConfiguration: {},
|
nxJsonConfiguration: {},
|
||||||
workspaceRoot: '/root',
|
workspaceRoot: '/root',
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ describe('nx project.json plugin', () => {
|
|||||||
context = {
|
context = {
|
||||||
nxJsonConfiguration: {},
|
nxJsonConfiguration: {},
|
||||||
workspaceRoot: '/root',
|
workspaceRoot: '/root',
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ describe('target-defaults plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: '/root',
|
workspaceRoot: '/root',
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -109,6 +110,7 @@ describe('target-defaults plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: '/root',
|
workspaceRoot: '/root',
|
||||||
|
configFiles: [],
|
||||||
})
|
})
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
@ -156,6 +158,7 @@ describe('target-defaults plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: '/root',
|
workspaceRoot: '/root',
|
||||||
|
configFiles: [],
|
||||||
})
|
})
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
@ -200,6 +203,7 @@ describe('target-defaults plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: '/root',
|
workspaceRoot: '/root',
|
||||||
|
configFiles: [],
|
||||||
})
|
})
|
||||||
).toMatchInlineSnapshot(`{}`);
|
).toMatchInlineSnapshot(`{}`);
|
||||||
});
|
});
|
||||||
@ -230,6 +234,7 @@ describe('target-defaults plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: '/root',
|
workspaceRoot: '/root',
|
||||||
|
configFiles: [],
|
||||||
})
|
})
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
@ -278,6 +283,7 @@ describe('target-defaults plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: '/root',
|
workspaceRoot: '/root',
|
||||||
|
configFiles: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { targets } = result.projects['.'];
|
const { targets } = result.projects['.'];
|
||||||
@ -321,6 +327,7 @@ describe('target-defaults plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: '/root',
|
workspaceRoot: '/root',
|
||||||
|
configFiles: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { targets } = result.projects['.'];
|
const { targets } = result.projects['.'];
|
||||||
|
|||||||
@ -182,7 +182,7 @@ export { readNxJson, workspaceLayout } from '../config/configuration';
|
|||||||
* TODO(v19): Remove this function.
|
* TODO(v19): Remove this function.
|
||||||
*/
|
*/
|
||||||
function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) {
|
function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) {
|
||||||
const projectFiles = retrieveProjectConfigurationPaths(
|
const allConfigFiles = retrieveProjectConfigurationPaths(
|
||||||
root,
|
root,
|
||||||
getDefaultPluginsSync(root)
|
getDefaultPluginsSync(root)
|
||||||
);
|
);
|
||||||
@ -199,11 +199,15 @@ function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) {
|
|||||||
if (!pattern) {
|
if (!pattern) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const file of projectFiles) {
|
const matchingConfigFiles = allConfigFiles.filter((file) =>
|
||||||
|
minimatch(file, pattern, { dot: true })
|
||||||
|
);
|
||||||
|
for (const file of matchingConfigFiles) {
|
||||||
if (minimatch(file, pattern, { dot: true })) {
|
if (minimatch(file, pattern, { dot: true })) {
|
||||||
let r = createNodes(file, options, {
|
let r = createNodes(file, options, {
|
||||||
nxJsonConfiguration: nxJson,
|
nxJsonConfiguration: nxJson,
|
||||||
workspaceRoot: root,
|
workspaceRoot: root,
|
||||||
|
configFiles: matchingConfigFiles,
|
||||||
}) as CreateNodesResult;
|
}) as CreateNodesResult;
|
||||||
for (const node in r.projects) {
|
for (const node in r.projects) {
|
||||||
const project = {
|
const project = {
|
||||||
|
|||||||
@ -193,13 +193,13 @@ export type ConfigurationResult = {
|
|||||||
* Transforms a list of project paths into a map of project configurations.
|
* Transforms a list of project paths into a map of project configurations.
|
||||||
*
|
*
|
||||||
* @param nxJson The NxJson configuration
|
* @param nxJson The NxJson configuration
|
||||||
* @param projectFiles A list of files identified as projects
|
* @param workspaceFiles A list of non-ignored workspace files
|
||||||
* @param plugins The plugins that should be used to infer project configuration
|
* @param plugins The plugins that should be used to infer project configuration
|
||||||
* @param root The workspace root
|
* @param root The workspace root
|
||||||
*/
|
*/
|
||||||
export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
|
export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
|
||||||
nxJson: NxJsonConfiguration,
|
nxJson: NxJsonConfiguration,
|
||||||
projectFiles: string[], // making this parameter allows devkit to pick up newly created projects
|
workspaceFiles: string[], // making this parameter allows devkit to pick up newly created projects
|
||||||
plugins: LoadedNxPlugin[],
|
plugins: LoadedNxPlugin[],
|
||||||
root: string = workspaceRoot
|
root: string = workspaceRoot
|
||||||
): Promise<ConfigurationResult> {
|
): Promise<ConfigurationResult> {
|
||||||
@ -222,54 +222,57 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of projectFiles) {
|
const matchingConfigFiles: string[] = workspaceFiles.filter(
|
||||||
performance.mark(`${plugin.name}:createNodes:${file} - start`);
|
minimatch.filter(pattern, { dot: true })
|
||||||
if (minimatch(file, pattern, { dot: true })) {
|
);
|
||||||
try {
|
|
||||||
let r = createNodes(file, options, {
|
|
||||||
nxJsonConfiguration: nxJson,
|
|
||||||
workspaceRoot: root,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (r instanceof Promise) {
|
for (const file of matchingConfigFiles) {
|
||||||
pluginResults.push(
|
performance.mark(`${plugin.name}:createNodes:${file} - start`);
|
||||||
r
|
try {
|
||||||
.catch((e) => {
|
let r = createNodes(file, options, {
|
||||||
performance.mark(`${plugin.name}:createNodes:${file} - end`);
|
nxJsonConfiguration: nxJson,
|
||||||
throw new CreateNodesError(
|
workspaceRoot: root,
|
||||||
`Unable to create nodes for ${file} using plugin ${plugin.name}.`,
|
configFiles: matchingConfigFiles,
|
||||||
e
|
});
|
||||||
);
|
|
||||||
})
|
if (r instanceof Promise) {
|
||||||
.then((r) => {
|
pluginResults.push(
|
||||||
performance.mark(`${plugin.name}:createNodes:${file} - end`);
|
r
|
||||||
performance.measure(
|
.catch((e) => {
|
||||||
`${plugin.name}:createNodes:${file}`,
|
performance.mark(`${plugin.name}:createNodes:${file} - end`);
|
||||||
`${plugin.name}:createNodes:${file} - start`,
|
throw new CreateNodesError(
|
||||||
`${plugin.name}:createNodes:${file} - end`
|
`Unable to create nodes for ${file} using plugin ${plugin.name}.`,
|
||||||
);
|
e
|
||||||
return { ...r, file, pluginName: plugin.name };
|
);
|
||||||
})
|
})
|
||||||
);
|
.then((r) => {
|
||||||
} else {
|
performance.mark(`${plugin.name}:createNodes:${file} - end`);
|
||||||
performance.mark(`${plugin.name}:createNodes:${file} - end`);
|
performance.measure(
|
||||||
performance.measure(
|
`${plugin.name}:createNodes:${file}`,
|
||||||
`${plugin.name}:createNodes:${file}`,
|
`${plugin.name}:createNodes:${file} - start`,
|
||||||
`${plugin.name}:createNodes:${file} - start`,
|
`${plugin.name}:createNodes:${file} - end`
|
||||||
`${plugin.name}:createNodes:${file} - end`
|
);
|
||||||
);
|
return { ...r, file, pluginName: plugin.name };
|
||||||
pluginResults.push({
|
})
|
||||||
...r,
|
|
||||||
file,
|
|
||||||
pluginName: plugin.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw new CreateNodesError(
|
|
||||||
`Unable to create nodes for ${file} using plugin ${plugin.name}.`,
|
|
||||||
e
|
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
performance.mark(`${plugin.name}:createNodes:${file} - end`);
|
||||||
|
performance.measure(
|
||||||
|
`${plugin.name}:createNodes:${file}`,
|
||||||
|
`${plugin.name}:createNodes:${file} - start`,
|
||||||
|
`${plugin.name}:createNodes:${file} - end`
|
||||||
|
);
|
||||||
|
pluginResults.push({
|
||||||
|
...r,
|
||||||
|
file,
|
||||||
|
pluginName: plugin.name,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new CreateNodesError(
|
||||||
|
`Unable to create nodes for ${file} using plugin ${plugin.name}.`,
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If there are no promises (counter undefined) or all promises have resolved (counter === 0)
|
// If there are no promises (counter undefined) or all promises have resolved (counter === 0)
|
||||||
|
|||||||
@ -116,12 +116,12 @@ function _retrieveProjectConfigurations(
|
|||||||
plugins: LoadedNxPlugin[]
|
plugins: LoadedNxPlugin[]
|
||||||
): Promise<RetrievedGraphNodes> {
|
): Promise<RetrievedGraphNodes> {
|
||||||
const globPatterns = configurationGlobs(plugins);
|
const globPatterns = configurationGlobs(plugins);
|
||||||
const projectFiles = globWithWorkspaceContext(workspaceRoot, globPatterns);
|
const workspaceFiles = globWithWorkspaceContext(workspaceRoot, globPatterns);
|
||||||
|
|
||||||
return createProjectConfigurations(
|
return createProjectConfigurations(
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
nxJson,
|
nxJson,
|
||||||
projectFiles,
|
workspaceFiles,
|
||||||
plugins
|
plugins
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -152,7 +152,8 @@ export async function retrieveProjectConfigurationsWithoutPluginInference(
|
|||||||
return projectsWithoutPluginCache.get(cacheKey);
|
return projectsWithoutPluginCache.get(cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectFiles = globWithWorkspaceContext(root, projectGlobPatterns);
|
const projectFiles =
|
||||||
|
globWithWorkspaceContext(root, projectGlobPatterns) ?? [];
|
||||||
const { projects } = await createProjectConfigurations(
|
const { projects } = await createProjectConfigurations(
|
||||||
root,
|
root,
|
||||||
nxJson,
|
nxJson,
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export function getIgnoredGlobs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getAlwaysIgnore(root?: string) {
|
export function getAlwaysIgnore(root?: string) {
|
||||||
const paths = ['node_modules', '**/node_modules', '.git'];
|
const paths = ['node_modules', '**/node_modules', '.git', '.nx', '.vscode'];
|
||||||
return root ? paths.map((x) => joinPathFragments(root, x)) : paths;
|
return root ? paths.map((x) => joinPathFragments(root, x)) : paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,6 +50,10 @@ import { TargetDefaultsPlugin } from '../plugins/target-defaults/target-defaults
|
|||||||
export interface CreateNodesContext {
|
export interface CreateNodesContext {
|
||||||
readonly nxJsonConfiguration: NxJsonConfiguration;
|
readonly nxJsonConfiguration: NxJsonConfiguration;
|
||||||
readonly workspaceRoot: string;
|
readonly workspaceRoot: string;
|
||||||
|
/**
|
||||||
|
* The subset of configuration files which match the createNodes pattern
|
||||||
|
*/
|
||||||
|
readonly configFiles: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,7 +82,7 @@ export interface CreateNodesResult {
|
|||||||
* A pair of file patterns and {@link CreateNodesFunction}
|
* A pair of file patterns and {@link CreateNodesFunction}
|
||||||
*/
|
*/
|
||||||
export type CreateNodes<T = unknown> = readonly [
|
export type CreateNodes<T = unknown> = readonly [
|
||||||
projectFilePattern: string,
|
configFilePattern: string,
|
||||||
createNodesFunction: CreateNodesFunction<T>
|
createNodesFunction: CreateNodesFunction<T>
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ describe('@nx/playwright/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,7 @@ describe('@nx/remix/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
tempFs.createFileSync(
|
tempFs.createFileSync(
|
||||||
'package.json',
|
'package.json',
|
||||||
@ -89,6 +90,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
tempFs.createFileSync(
|
tempFs.createFileSync(
|
||||||
|
|||||||
@ -25,6 +25,7 @@ describe('@nx/rollup/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
tempFs.createFileSync('package.json', JSON.stringify({ name: 'mylib' }));
|
tempFs.createFileSync('package.json', JSON.stringify({ name: 'mylib' }));
|
||||||
@ -93,6 +94,7 @@ module.exports = config;
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
tempFs.createFileSync(
|
tempFs.createFileSync(
|
||||||
|
|||||||
@ -17,6 +17,7 @@ describe('@nx/storybook/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
tempFs.createFileSync(
|
tempFs.createFileSync(
|
||||||
'my-app/project.json',
|
'my-app/project.json',
|
||||||
|
|||||||
@ -19,6 +19,7 @@ describe('@nx/webpack/plugin', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspaceRoot: tempFs.tempDir,
|
workspaceRoot: tempFs.tempDir,
|
||||||
|
configFiles: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
tempFs.createFileSync(
|
tempFs.createFileSync(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user