chore(devkit): add util for getting named-inputs (#19924)

This commit is contained in:
Jason Jean 2023-10-30 10:55:25 -04:00 committed by GitHub
parent c2aa6ef4f4
commit 44f2f0ce2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 506 additions and 0 deletions

View File

@ -0,0 +1,36 @@
import { join } from 'path';
import { existsSync } from 'fs';
import type {
CreateNodesContext,
ProjectConfiguration,
} from 'nx/src/devkit-exports';
import type { PackageJson } from 'nx/src/utils/package-json';
import type { InputDefinition } from 'nx/src/config/workspace-json-project-json';
import { requireNx } from '../../nx';
const { readJsonFile } = requireNx();
/**
* Get the named inputs available for a directory
*/
export function getNamedInputs(
directory: string,
context: CreateNodesContext
): { [inputName: string]: (string | InputDefinition)[] } {
const projectJsonPath = join(directory, 'project.json');
const projectJson: ProjectConfiguration = existsSync(projectJsonPath)
? readJsonFile(projectJsonPath)
: null;
const packageJsonPath = join(directory, 'package.json');
const packageJson: PackageJson = existsSync(packageJsonPath)
? readJsonFile(packageJsonPath)
: null;
return {
...context.nxJsonConfiguration.namedInputs,
...packageJson?.nx?.namedInputs,
...projectJson?.namedInputs,
};
}

View File

@ -4,6 +4,11 @@
"factory": "./src/generators/remove-migrations/generator", "factory": "./src/generators/remove-migrations/generator",
"schema": "./src/generators/remove-migrations/schema.json", "schema": "./src/generators/remove-migrations/schema.json",
"description": "remove-migrations generator" "description": "remove-migrations generator"
},
"create-nodes-plugin": {
"factory": "./src/generators/create-nodes-plugin/generator",
"schema": "./src/generators/create-nodes-plugin/schema.json",
"description": "Workspace Generator to create a create-nodes-plugin"
} }
} }
} }

View File

@ -0,0 +1,210 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`create-nodes-plugin/generator generator should run successfully 1`] = `
"import {
CreateNodes,
CreateNodesContext,
TargetConfiguration,
} from '@nx/devkit';
import { basename, dirname, extname, join, resolve } from "path";
import { registerTsProject } from '@nx/js/src/internal';
import { getRootTsConfigPath } from '@nx/js';
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';
export interface EslintPluginOptions {
targetName?: string;
}
export const createNodes: CreateNodes<EslintPluginOptions> = [
'**/TODO',
(configFilePath, options, context) => {
const projectRoot = dirname(configFilePath);
// Do not create a project if package.json and project.json isn't there.
const siblingFiles = readdirSync(projectRoot);
if (
!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')
) {
return {};
}
options = normalizeOptions(options);
const projectName = basename(projectRoot);
return {
projects: {
[projectName]: {
root: projectRoot,
projectType: 'library',
targets: buildEslintTargets(
configFilePath,
projectRoot,
options,
context,
),
},
},
};
},
];
function buildEslintTargets(
configFilePath: string,
projectRoot: string,
options: EslintPluginOptions,
context: CreateNodesContext,
) {
const eslintConfig = getEslintConfig(
configFilePath,
context
);
const targetDefaults = readTargetDefaultsForTarget(
options.targetName,
context.nxJsonConfiguration.targetDefaults,
'executorName'
);
const namedInputs = getNamedInputs(projectRoot, context);
const targets: Record<
string,
TargetConfiguration<ExecutorOptions>
> = {};
const baseTargetConfig: TargetConfiguration<ExecutorOptions> = {
executor: 'executorName',
options: {
config: configFilePath,
...targetDefaults?.options,
},
};
targets[options.targetName] = {
...baseTargetConfig,
cache: targetDefaults?.cache ?? true,
inputs:
targetDefaults?.inputs ?? 'production' in namedInputs
? ['default', '^production']
: ['default', '^default'],
outputs:
targetDefaults?.outputs ??
getOutputs(projectRoot),
options: {
...baseTargetConfig.options,
},
};
return targets;
}
function getEslintConfig(
configFilePath: string,
context: CreateNodesContext
): any {
const resolvedPath = join(context.workspaceRoot, configFilePath);
let module: any;
if (extname(configFilePath) === '.ts') {
const tsConfigPath = getRootTsConfigPath();
if (tsConfigPath) {
const unregisterTsProject = registerTsProject(tsConfigPath);
try {
module = require(resolvedPath);
} finally {
unregisterTsProject();
}
} else {
module = require(resolvedPath);
}
} else {
module = require(resolvedPath);
}
return module.default ?? module;
}
function getOutputs(
projectRoot: string,
): string[] {
function getOutput(path: string): string {
if (path.startsWith('..')) {
return join('{workspaceRoot}', join(projectRoot, path));
} else {
return join('{projectRoot}', path);
}
}
const outputs = [];
return outputs;
}
function normalizeOptions(options: EslintPluginOptions): EslintPluginOptions {
options ??= {};
options.targetName ??= 'TODO';
return options;
}
"
`;
exports[`create-nodes-plugin/generator generator should run successfully 2`] = `
"import { CreateNodesContext } from '@nx/devkit';
import { createNodes } from './plugin';
describe('@nx/eslint/plugin', () => {
let createNodesFunction = createNodes[1];
let context: CreateNodesContext;
beforeEach(async () => {
context = {
nxJsonConfiguration: {
namedInputs: {
default: ['{projectRoot}/**/*'],
production: ['!{projectRoot}/**/*.spec.ts'],
},
},
workspaceRoot: '',
};
});
afterEach(() => {
jest.resetModules();
});
it('should create nodes', () => {
mockEslintConfig({})
const nodes = createNodesFunction(
'TODO',
{
targetName: 'target',
},
context
);
expect(nodes).toMatchInlineSnapshot();
});
});
function mockEslintConfig(
config: any
) {
jest.mock(
'TODO',
() => ({
default: config,
}),
{
virtual: true,
}
);
}
"
`;

View File

@ -0,0 +1 @@
export { createNodes, <%= className %>PluginOptions } from './src/plugins/plugin';

View File

@ -0,0 +1,52 @@
import { CreateNodesContext } from '@nx/devkit';
import { createNodes } from './plugin';
describe('@nx/<%= dirName %>/plugin', () => {
let createNodesFunction = createNodes[1];
let context: CreateNodesContext;
beforeEach(async () => {
context = {
nxJsonConfiguration: {
namedInputs: {
default: ['{projectRoot}/**/*'],
production: ['!{projectRoot}/**/*.spec.ts'],
},
},
workspaceRoot: '',
};
});
afterEach(() => {
jest.resetModules();
});
it('should create nodes', () => {
mock<%= className %>Config({})
const nodes = createNodesFunction(
'TODO',
{
targetName: 'target',
},
context
);
expect(nodes).toMatchInlineSnapshot();
});
});
function mock<%= className %>Config(
config: any
) {
jest.mock(
'TODO',
() => ({
default: config,
}),
{
virtual: true,
}
);
}

View File

@ -0,0 +1,149 @@
import {
CreateNodes,
CreateNodesContext,
TargetConfiguration,
} from '@nx/devkit';
import { basename, dirname, extname, join, resolve } from "path";
import { registerTsProject } from '@nx/js/src/internal';
import { getRootTsConfigPath } from '@nx/js';
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';
export interface <%= className %>PluginOptions {
targetName?: string;
}
export const createNodes: CreateNodes<<%= className %>PluginOptions> = [
'**/TODO',
(configFilePath, options, context) => {
const projectRoot = dirname(configFilePath);
// Do not create a project if package.json and project.json isn't there.
const siblingFiles = readdirSync(projectRoot);
if (
!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')
) {
return {};
}
options = normalizeOptions(options);
const projectName = basename(projectRoot);
return {
projects: {
[projectName]: {
root: projectRoot,
projectType: 'library',
targets: build<%= className %>Targets(
configFilePath,
projectRoot,
options,
context,
),
},
},
};
},
];
function build<%= className %>Targets(
configFilePath: string,
projectRoot: string,
options: <%= className %>PluginOptions,
context: CreateNodesContext,
) {
const <%= propertyName %>Config = get<%= className %>Config(
configFilePath,
context
);
const targetDefaults = readTargetDefaultsForTarget(
options.targetName,
context.nxJsonConfiguration.targetDefaults,
'executorName'
);
const namedInputs = getNamedInputs(projectRoot, context);
const targets: Record<
string,
TargetConfiguration<ExecutorOptions>
> = {};
const baseTargetConfig: TargetConfiguration<ExecutorOptions> = {
executor: 'executorName',
options: {
config: configFilePath,
...targetDefaults?.options,
},
};
targets[options.targetName] = {
...baseTargetConfig,
cache: targetDefaults?.cache ?? true,
inputs:
targetDefaults?.inputs ?? 'production' in namedInputs
? ['default', '^production']
: ['default', '^default'],
outputs:
targetDefaults?.outputs ??
getOutputs(projectRoot),
options: {
...baseTargetConfig.options,
},
};
return targets;
}
function get<%= className %>Config(
configFilePath: string,
context: CreateNodesContext
): any {
const resolvedPath = join(context.workspaceRoot, configFilePath);
let module: any;
if (extname(configFilePath) === '.ts') {
const tsConfigPath = getRootTsConfigPath();
if (tsConfigPath) {
const unregisterTsProject = registerTsProject(tsConfigPath);
try {
module = require(resolvedPath);
} finally {
unregisterTsProject();
}
} else {
module = require(resolvedPath);
}
} else {
module = require(resolvedPath);
}
return module.default ?? module;
}
function getOutputs(
projectRoot: string,
): string[] {
function getOutput(path: string): string {
if (path.startsWith('..')) {
return join('{workspaceRoot}', join(projectRoot, path));
} else {
return join('{projectRoot}', path);
}
}
const outputs = [];
return outputs;
}
function normalizeOptions(options: <%= className %>PluginOptions): <%= className %>PluginOptions {
options ??= {};
options.targetName ??= 'TODO';
return options;
}

View File

@ -0,0 +1,25 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Tree } from '@nx/devkit';
import { generatorGenerator, GeneratorGeneratorSchema } from './generator';
describe('create-nodes-plugin/generator generator', () => {
let tree: Tree;
const options: GeneratorGeneratorSchema = {};
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
jest.spyOn(process, 'cwd').mockReturnValue('/virtual/packages/eslint');
});
it('should run successfully', async () => {
await generatorGenerator(tree, options);
expect(
tree.read('packages/eslint/src/plugins/plugin.ts').toString()
).toMatchSnapshot();
expect(
tree.read('packages/eslint/src/plugins/plugin.spec.ts').toString()
).toMatchSnapshot();
});
});

View File

@ -0,0 +1,20 @@
import { generateFiles, names, Tree } from '@nx/devkit';
import { basename, join, relative } from 'path';
export interface GeneratorGeneratorSchema {}
export async function generatorGenerator(
tree: Tree,
options: GeneratorGeneratorSchema
) {
const cwd = process.cwd();
const { className, propertyName } = names(basename(cwd));
generateFiles(tree, join(__dirname, 'files'), relative(tree.root, cwd), {
dirName: basename(cwd),
className,
propertyName,
});
}
export default generatorGenerator;

View File

@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "Generator",
"title": "",
"type": "object",
"properties": {},
"required": []
}