AI-first build platform designed for monorepo development that provides task orchestration, project graph generation, and CLI tools for managing complex software projects
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive APIs for creating custom plugins that extend Nx's project detection, dependency analysis, and task configuration capabilities.
Core plugin interface for extending Nx functionality.
/**
* Main plugin interface for extending Nx
*/
interface NxPlugin {
/** Plugin name */
name: string;
/** Create project configurations from config files */
createNodes?: CreateNodes;
/** Create additional project dependencies */
createDependencies?: CreateDependencies;
/** Process and modify the project graph */
processProjectGraph?: ProcessProjectGraph;
/** Generate workspace files and configurations */
createNodesV2?: CreateNodesV2;
}
/**
* Function to create project nodes from configuration files
*/
type CreateNodes = (
configFilePath: string,
options: any,
context: CreateNodesContext
) => CreateNodesResult | Promise<CreateNodesResult>;
/**
* Enhanced version of CreateNodes with batching support
*/
type CreateNodesV2 = (
configFiles: string[],
options: any,
context: CreateNodesContext
) => Promise<CreateNodesResultV2>;
/**
* Function to create project dependencies
*/
type CreateDependencies = (
opts: CreateDependenciesContext
) => RawProjectGraphDependency[] | Promise<RawProjectGraphDependency[]>;
/**
* Function to process the project graph
*/
type ProcessProjectGraph = (
graph: ProjectGraph,
context: CreateDependenciesContext
) => ProjectGraph | Promise<ProjectGraph>;Context objects provided to plugin functions.
interface CreateNodesContext {
/** Nx workspace configuration */
nxJsonConfiguration: NxJsonConfiguration;
/** Workspace root directory */
workspaceRoot: string;
/** All configuration files found */
configFiles: string[];
/** File map for workspace files */
fileMap?: ProjectFileMap;
}
interface CreateDependenciesContext {
/** All workspace files */
projects: Record<string, ProjectConfiguration>;
/** Nx workspace configuration */
nxJsonConfiguration: NxJsonConfiguration;
/** File map for workspace files */
fileMap: ProjectFileMap;
/** File map for external dependencies */
externalNodes?: Record<string, ProjectGraphExternalNode>;
}
interface CreateNodesResult {
/** Map of project root to project configuration */
projects?: Record<string, CreateNodesProjectConfiguration>;
/** External dependencies discovered */
externalNodes?: Record<string, ProjectGraphExternalNode>;
}
interface CreateNodesResultV2 {
/** Map of config file to create nodes result */
[configFile: string]: CreateNodesResult;
}
interface CreateNodesProjectConfiguration {
/** Project name */
name?: string;
/** Build targets */
targets?: Record<string, TargetConfiguration>;
/** Project metadata */
metadata?: ProjectMetadata;
/** Project tags */
tags?: string[];
/** Named inputs */
namedInputs?: NamedInputs;
}Functions for loading and registering plugins.
/**
* Loads Nx plugins from the workspace configuration
* @param nxJson - Nx workspace configuration
* @param paths - Additional paths to search for plugins
* @returns Array of loaded plugins
*/
function loadNxPlugins(
nxJson: NxJsonConfiguration,
paths?: string[]
): Promise<LoadedNxPlugin[]>;
/**
* Checks if an object implements the NxPlugin interface
* @param plugin - Object to check
* @returns True if object is an NxPlugin
*/
function isNxPlugin(plugin: any): plugin is NxPlugin;
/**
* Reads package.json for a plugin package
* @param pluginName - Name of the plugin package
* @param paths - Paths to search for the plugin
* @returns Plugin package.json content
*/
function readPluginPackageJson(
pluginName: string,
paths?: string[]
): { packageJson: PackageJson; path: string };
interface LoadedNxPlugin {
/** Plugin name */
name: string;
/** Plugin implementation */
plugin: NxPlugin;
/** Plugin options from nx.json */
options: any;
}Types for defining project dependencies through plugins.
interface RawProjectGraphDependency {
/** Source project name */
source: string;
/** Target project name */
target: string;
/** Type of dependency */
type: DependencyType;
/** Source file that created the dependency */
sourceFile?: string;
}
interface ProjectGraphExternalNode {
/** Package type (npm, etc.) */
type: 'npm' | string;
/** Package name */
name: string;
/** Package data */
data: {
version: string;
packageName: string;
hash?: string;
};
}
type DependencyType =
| 'static' // Import/require statements
| 'dynamic' // Dynamic imports
| 'implicit' // Configured dependencies
| 'direct' // Direct project dependencies
| 'indirect'; // Transitive dependenciesUtilities for common plugin development tasks.
/**
* Creates a project node from package.json
* @param packageJsonPath - Path to package.json
* @param context - Create nodes context
* @returns Project configuration
*/
function createNodeFromPackageJson(
packageJsonPath: string,
context: CreateNodesContext
): CreateNodesProjectConfiguration | null;
/**
* Parses target configuration from executor strings
* @param executor - Executor string (e.g., "@nx/webpack:webpack")
* @returns Parsed executor information
*/
function parseExecutor(executor: string): {
package: string;
executor: string;
};
/**
* Resolves workspace relative paths
* @param workspaceRoot - Workspace root directory
* @param path - Path to resolve
* @returns Resolved absolute path
*/
function resolveWorkspacePath(workspaceRoot: string, path: string): string;
/**
* Gets project root from configuration file path
* @param configFilePath - Path to configuration file
* @param workspaceRoot - Workspace root directory
* @returns Project root directory
*/
function getProjectRootFromConfigFile(
configFilePath: string,
workspaceRoot: string
): string;import {
NxPlugin,
CreateNodes,
CreateNodesContext,
CreateNodesResult,
TargetConfiguration,
logger
} from "nx/src/devkit-exports";
const createNodes: CreateNodes = (
configFilePath: string,
options: any,
context: CreateNodesContext
): CreateNodesResult => {
logger.info(`Processing config file: ${configFilePath}`);
// Get project root from config file path
const projectRoot = path.dirname(configFilePath);
const projectName = path.basename(projectRoot);
// Read configuration file
const configContent = readFileSync(configFilePath, 'utf-8');
const config = JSON.parse(configContent);
// Create build target based on configuration
const buildTarget: TargetConfiguration = {
executor: '@my-plugin/executor:build',
options: {
outputPath: `dist/${projectRoot}`,
...config.buildOptions
}
};
// Create test target if tests are configured
const targets: Record<string, TargetConfiguration> = { build: buildTarget };
if (config.testFramework) {
targets.test = {
executor: '@my-plugin/executor:test',
options: {
testFramework: config.testFramework,
...config.testOptions
}
};
}
return {
projects: {
[projectRoot]: {
name: projectName,
targets,
tags: config.tags || [],
metadata: {
framework: config.framework,
version: config.version
}
}
}
};
};
const myPlugin: NxPlugin = {
name: 'my-custom-plugin',
createNodes: [
// Glob pattern to match configuration files
'**/my-config.json',
createNodes
]
};
export default myPlugin;import {
NxPlugin,
CreateNodes,
CreateDependencies,
CreateDependenciesContext,
RawProjectGraphDependency,
logger
} from "nx/src/devkit-exports";
const createDependencies: CreateDependencies = (
context: CreateDependenciesContext
): RawProjectGraphDependency[] => {
const dependencies: RawProjectGraphDependency[] = [];
// Analyze workspace files to find dependencies
for (const [projectName, project] of Object.entries(context.projects)) {
const projectFiles = context.fileMap.projectFileMap[projectName] || [];
// Look for custom import patterns
for (const file of projectFiles) {
if (file.file.endsWith('.ts') || file.file.endsWith('.js')) {
const filePath = path.join(context.workspaceRoot, file.file);
const content = readFileSync(filePath, 'utf-8');
// Find custom import statements
const importRegex = /import.*from\s+['"]@workspace\/([^'"]+)['"]/g;
let match;
while ((match = importRegex.exec(content)) !== null) {
const targetProject = match[1];
if (context.projects[targetProject]) {
dependencies.push({
source: projectName,
target: targetProject,
type: 'static',
sourceFile: file.file
});
}
}
}
}
}
logger.info(`Found ${dependencies.length} custom dependencies`);
return dependencies;
};
const advancedPlugin: NxPlugin = {
name: 'advanced-plugin',
createNodes: ['**/project.config.json', createNodes],
createDependencies
};
export default advancedPlugin;import {
NxPlugin,
ProcessProjectGraph,
ProjectGraph,
CreateDependenciesContext,
logger
} from "nx/src/devkit-exports";
const processProjectGraph: ProcessProjectGraph = (
graph: ProjectGraph,
context: CreateDependenciesContext
): ProjectGraph => {
logger.info('Processing project graph for optimization');
// Add virtual nodes for shared resources
const modifiedGraph = { ...graph };
// Find projects that share common patterns
const sharedLibraries = new Set<string>();
for (const [projectName, dependencies] of Object.entries(graph.dependencies)) {
dependencies.forEach(dep => {
if (dep.target.startsWith('shared-')) {
sharedLibraries.add(dep.target);
}
});
}
// Add metadata to shared library nodes
for (const sharedLib of sharedLibraries) {
if (modifiedGraph.nodes[sharedLib]) {
modifiedGraph.nodes[sharedLib].data = {
...modifiedGraph.nodes[sharedLib].data,
tags: [...(modifiedGraph.nodes[sharedLib].data.tags || []), 'shared']
};
}
}
return modifiedGraph;
};
const graphProcessorPlugin: NxPlugin = {
name: 'graph-processor-plugin',
processProjectGraph
};
export default graphProcessorPlugin;import {
NxPlugin,
CreateNodes,
CreateNodesContext,
CreateNodesResult,
TargetConfiguration
} from "nx/src/devkit-exports";
interface FrameworkConfig {
framework: 'react' | 'vue' | 'angular';
buildTool: 'webpack' | 'vite' | 'rollup';
features: string[];
}
const createNodes: CreateNodes = (
configFilePath: string,
options: any,
context: CreateNodesContext
): CreateNodesResult => {
const projectRoot = path.dirname(configFilePath);
const config: FrameworkConfig = JSON.parse(
readFileSync(configFilePath, 'utf-8')
);
// Create framework-specific targets
const targets: Record<string, TargetConfiguration> = {};
// Build target based on framework and build tool
switch (config.framework) {
case 'react':
targets.build = {
executor: config.buildTool === 'vite'
? '@nx/vite:build'
: '@nx/webpack:webpack',
options: {
outputPath: `dist/${projectRoot}`,
main: `${projectRoot}/src/main.tsx`,
index: `${projectRoot}/src/index.html`
}
};
break;
case 'vue':
targets.build = {
executor: '@nx/vite:build',
options: {
outputPath: `dist/${projectRoot}`,
configFile: `${projectRoot}/vite.config.ts`
}
};
break;
case 'angular':
targets.build = {
executor: '@angular-devkit/build-angular:browser',
options: {
outputPath: `dist/${projectRoot}`,
index: `${projectRoot}/src/index.html`,
main: `${projectRoot}/src/main.ts`,
polyfills: `${projectRoot}/src/polyfills.ts`,
tsConfig: `${projectRoot}/tsconfig.app.json`
}
};
break;
}
// Add feature-specific targets
if (config.features.includes('testing')) {
targets.test = {
executor: '@nx/jest:jest',
options: {
jestConfig: `${projectRoot}/jest.config.ts`
}
};
}
if (config.features.includes('e2e')) {
targets.e2e = {
executor: '@nx/cypress:cypress',
options: {
cypressConfig: `${projectRoot}/cypress.config.ts`
}
};
}
if (config.features.includes('storybook')) {
targets.storybook = {
executor: '@nx/storybook:storybook',
options: {
port: 4400,
configDir: `${projectRoot}/.storybook`
}
};
}
return {
projects: {
[projectRoot]: {
name: path.basename(projectRoot),
targets,
tags: [config.framework, config.buildTool, ...config.features],
metadata: {
framework: config.framework,
buildTool: config.buildTool,
features: config.features
}
}
}
};
};
const multiFrameworkPlugin: NxPlugin = {
name: 'multi-framework-plugin',
createNodes: ['**/framework.config.json', createNodes]
};
export default multiFrameworkPlugin;{
"plugins": [
{
"plugin": "@my-org/nx-custom-plugin",
"options": {
"buildCommand": "custom-build",
"testPattern": "**/*.test.{js,ts}",
"outputDir": "custom-dist"
}
},
{
"plugin": "./tools/local-plugin",
"options": {
"localOption": "value"
}
}
]
}import {
NxPlugin,
CreateNodes,
logger,
workspaceRoot
} from "nx/src/devkit-exports";
// Cache parsed configurations to improve performance
const configCache = new Map<string, any>();
const createNodes: CreateNodes = (configFilePath, options, context) => {
// Use caching for expensive operations
if (configCache.has(configFilePath)) {
const cachedConfig = configCache.get(configFilePath);
return createProjectFromConfig(cachedConfig, configFilePath, options);
}
try {
// Read and parse configuration
const config = parseConfigFile(configFilePath);
configCache.set(configFilePath, config);
return createProjectFromConfig(config, configFilePath, options);
} catch (error) {
logger.warn(`Failed to parse config file ${configFilePath}: ${error.message}`);
return {}; // Return empty result on error
}
};
function createProjectFromConfig(config: any, configFilePath: string, options: any) {
const projectRoot = path.relative(workspaceRoot, path.dirname(configFilePath));
// Validate configuration
if (!config || typeof config !== 'object') {
logger.warn(`Invalid configuration in ${configFilePath}`);
return {};
}
// Create targets with error handling
const targets: Record<string, TargetConfiguration> = {};
try {
if (config.build) {
targets.build = createBuildTarget(config.build, projectRoot, options);
}
if (config.test) {
targets.test = createTestTarget(config.test, projectRoot, options);
}
} catch (error) {
logger.error(`Error creating targets for ${projectRoot}: ${error.message}`);
return {};
}
return {
projects: {
[projectRoot]: {
name: config.name || path.basename(projectRoot),
targets,
tags: config.tags || [],
metadata: {
configFile: configFilePath,
pluginVersion: '1.0.0'
}
}
}
};
}
const robustPlugin: NxPlugin = {
name: 'robust-plugin',
createNodes: ['**/plugin.config.{json,js,ts}', createNodes]
};
export default robustPlugin;Install with Tessl CLI
npx tessl i tessl/npm-nx