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",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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