chore(devkit): add util for getting named-inputs (#19924)
This commit is contained in:
parent
c2aa6ef4f4
commit
44f2f0ce2c
36
packages/devkit/src/utils/get-named-inputs.ts
Normal file
36
packages/devkit/src/utils/get-named-inputs.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@ -4,6 +4,11 @@
|
||||
"factory": "./src/generators/remove-migrations/generator",
|
||||
"schema": "./src/generators/remove-migrations/schema.json",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
"
|
||||
`;
|
||||
@ -0,0 +1 @@
|
||||
export { createNodes, <%= className %>PluginOptions } from './src/plugins/plugin';
|
||||
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "Generator",
|
||||
"title": "",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user