CLI compatibility layer for older Nx workspaces that serves as a bridge to core nx functionality
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Bridge functionality for running Angular schematics, migrations, and builders within Nx workspaces. Provides compatibility layer for Angular DevKit tools and enables seamless integration with Angular CLI workflows.
Schedule and execute Angular CLI targets (builders) within Nx workspaces.
/**
* Schedules Angular CLI target execution
* @param root - Workspace root directory
* @param opts - Target execution options
* @param verbose - Enable verbose logging
* @returns Promise resolving to Observable of build results
*/
export function scheduleTarget(
root: string,
opts: {
/** Project name to execute target for */
project: string;
/** Target name to execute */
target: string;
/** Configuration name (e.g., 'production', 'development') */
configuration: string;
/** Additional options to pass to the target */
runOptions: any;
},
verbose: boolean
): Promise<Observable<BuilderOutput>>;
/** Build output interface from Angular DevKit */
export interface BuilderOutput {
success: boolean;
error?: string;
target?: Target;
outputPath?: string;
}
/** Target reference interface */
export interface Target {
project: string;
target: string;
configuration?: string;
}
/** Observable interface for reactive programming */
export interface Observable<T> {
subscribe(observer: {
next?: (value: T) => void;
error?: (error: any) => void;
complete?: () => void;
}): { unsubscribe(): void };
}Run Angular schematics for code generation and project setup.
/**
* Runs Angular schematic generation
* @param root - Workspace root directory
* @param opts - Generation options
* @param verbose - Enable verbose logging
* @returns Promise resolving to exit code (0 for success)
*/
export function generate(root: string, opts: GenerateOptions, verbose: boolean): Promise<number>;
/** Options for schematic generation */
export interface GenerateOptions {
/** Collection name (e.g., '@angular/core', '@nx/angular') */
collection: string;
/** Schematic name to run */
name: string;
/** Options to pass to the schematic */
options: Record<string, any>;
/** Whether to perform a dry run */
dryRun?: boolean;
/** Whether to force overwrite existing files */
force?: boolean;
}Execute package migrations for workspace updates and transformations.
/**
* Executes migrations for package updates
* @param root - Workspace root directory
* @param packageName - Name of the package to migrate
* @param migrationName - Name of the specific migration to run
* @param isVerbose - Enable verbose logging
* @returns Promise with migration results
*/
export function runMigration(
root: string,
packageName: string,
migrationName: string,
isVerbose: boolean
): Promise<{
/** Log messages from the migration */
loggingQueue: string[];
/** Whether the migration made any changes */
madeChanges: boolean;
}>;Utilities for wrapping Angular DevKit schematics for use within Nx.
/**
* Wraps Angular DevKit schematic for Nx usage
* @param collectionName - Name of the schematic collection
* @param generatorName - Name of the generator/schematic
* @returns Wrapped schematic function compatible with Nx Tree API
*/
export function wrapAngularDevkitSchematic(
collectionName: string,
generatorName: string
): (host: Tree, generatorOptions: { [k: string]: any }) => Promise<void>;Classes for integrating Angular DevKit with Nx file system operations.
/**
* Virtual filesystem host for Angular DevKit integration
*/
export class NxScopedHost {
constructor(root: string);
/** Read file as string */
readFile(path: string): Promise<string>;
/** Write file content */
writeFile(path: string, content: string): Promise<void>;
/** Check if file exists */
exists(path: string): Promise<boolean>;
/** Delete file */
delete(path: string): Promise<void>;
/** Rename file */
rename(from: string, to: string): Promise<void>;
/** List directory contents */
list(path: string): Promise<string[]>;
}
/**
* Specialized host for wrapped schematics
*/
export class NxScopeHostUsedForWrappedSchematics extends NxScopedHost {
constructor(root: string, host: Tree);
}Utilities for logging and converting data formats.
/**
* Creates Angular DevKit logger
* @param isVerbose - Enable verbose logging
* @returns Logger instance compatible with Angular DevKit
*/
export function getLogger(isVerbose?: boolean): logging.Logger;
/**
* Converts ArrayBuffer to string
* @param buffer - ArrayBuffer to convert
* @returns String representation of the buffer
*/
export function arrayBufferToString(buffer: any): string;Functions for testing and mocking schematic operations.
/**
* Override collection resolution for testing
* @param collections - Map of collection names to paths
*/
export function overrideCollectionResolutionForTesting(collections: { [name: string]: string }): void;
/**
* Mock schematics for testing
* @param schematics - Map of schematic names to mock implementations
*/
export function mockSchematicsForTesting(schematics: {
[name: string]: (host: Tree, generatorOptions: { [k: string]: any }) => Promise<void>
}): void;import { scheduleTarget } from "@nrwl/tao/commands/ngcli-adapter";
import { workspaceRoot } from "@nrwl/tao/utils/app-root";
async function buildProject(projectName: string, configuration: string = 'production') {
try {
const buildResult$ = await scheduleTarget(
workspaceRoot,
{
project: projectName,
target: 'build',
configuration: configuration,
runOptions: {
optimization: true,
sourceMap: false
}
},
true // verbose logging
);
// Subscribe to build results
buildResult$.subscribe({
next: (result) => {
if (result.success) {
console.log(`Build completed successfully`);
if (result.outputPath) {
console.log(`Output: ${result.outputPath}`);
}
} else {
console.error(`Build failed: ${result.error}`);
}
},
error: (error) => {
console.error('Build error:', error);
},
complete: () => {
console.log('Build process completed');
}
});
} catch (error) {
console.error('Failed to schedule build target:', error);
}
}
// Usage
buildProject('my-app', 'production');import { generate, type GenerateOptions } from "@nrwl/tao/commands/ngcli-adapter";
import { workspaceRoot } from "@nrwl/tao/utils/app-root";
import { logger } from "@nrwl/tao/shared/logger";
async function generateComponent(name: string, project: string) {
const options: GenerateOptions = {
collection: '@angular/core',
name: 'component',
options: {
name: name,
project: project,
style: 'scss',
skipTests: false,
changeDetection: 'OnPush'
},
dryRun: false,
force: false
};
try {
const exitCode = await generate(workspaceRoot, options, true);
if (exitCode === 0) {
logger.info(`Component '${name}' generated successfully`);
} else {
logger.error(`Component generation failed with exit code: ${exitCode}`);
}
return exitCode;
} catch (error) {
logger.error('Failed to generate component:', error);
return 1;
}
}
// Generate library
async function generateLibrary(name: string) {
const options: GenerateOptions = {
collection: '@nx/angular',
name: 'library',
options: {
name: name,
buildable: true,
publishable: false,
routing: false,
lazy: false
}
};
const exitCode = await generate(workspaceRoot, options, true);
return exitCode === 0;
}
// Usage
generateComponent('user-profile', 'my-app');
generateLibrary('shared-utils');import { runMigration } from "@nrwl/tao/commands/ngcli-adapter";
import { workspaceRoot } from "@nrwl/tao/utils/app-root";
import { logger } from "@nrwl/tao/shared/logger";
async function migratePackage(packageName: string, migrationName: string) {
try {
logger.info(`Running migration: ${packageName}:${migrationName}`);
const result = await runMigration(
workspaceRoot,
packageName,
migrationName,
true // verbose
);
// Log migration output
result.loggingQueue.forEach(message => {
console.log(message);
});
if (result.madeChanges) {
logger.info('Migration completed with changes');
} else {
logger.info('Migration completed without changes');
}
return result;
} catch (error) {
logger.error(`Migration failed: ${error.message}`);
throw error;
}
}
// Example: Migrate Angular to newer version
async function migrateAngular() {
try {
// Run Angular update migration
await migratePackage('@angular/core', 'migration-v15');
// Run additional migrations if needed
await migratePackage('@angular/cli', 'update-workspace-config');
logger.info('Angular migration completed successfully');
} catch (error) {
logger.error('Angular migration failed:', error);
}
}
migrateAngular();import {
wrapAngularDevkitSchematic,
NxScopeHostUsedForWrappedSchematics
} from "@nrwl/tao/commands/ngcli-adapter";
import { FsTree } from "@nrwl/tao/shared/tree";
import { workspaceRoot } from "@nrwl/tao/utils/app-root";
// Wrap an Angular schematic for use with Nx Tree API
const wrappedComponentSchematic = wrapAngularDevkitSchematic(
'@angular/core',
'component'
);
async function generateComponentWithNxTree(name: string, path: string) {
const tree = new FsTree(workspaceRoot);
try {
// Use the wrapped schematic with Nx Tree
await wrappedComponentSchematic(tree, {
name: name,
path: path,
style: 'scss',
skipTests: false
});
// Apply changes to file system
const changes = tree.listChanges();
console.log(`Generated ${changes.length} files for component '${name}'`);
// You can review changes before applying
changes.forEach(change => {
console.log(`${change.type}: ${change.path}`);
});
// Apply changes
flushChanges(tree.root, changes);
} catch (error) {
console.error('Failed to generate component:', error);
}
}
generateComponentWithNxTree('feature-component', 'src/app/features');import {
mockSchematicsForTesting,
overrideCollectionResolutionForTesting
} from "@nrwl/tao/commands/ngcli-adapter";
import { FsTree } from "@nrwl/tao/shared/tree";
// Set up testing environment
function setupSchematicTesting() {
// Override collection paths for testing
overrideCollectionResolutionForTesting({
'@nx/angular': '/path/to/test/collections/nx-angular',
'@angular/core': '/path/to/test/collections/angular-core'
});
// Mock specific schematics
mockSchematicsForTesting({
'component': async (tree, options) => {
// Mock component generation
const componentPath = `${options.path}/${options.name}.component.ts`;
tree.write(componentPath, `
import { Component } from '@angular/core';
@Component({
selector: 'app-${options.name}',
template: '<div>Mock ${options.name} component</div>'
})
export class ${options.name}Component {}
`);
},
'service': async (tree, options) => {
// Mock service generation
const servicePath = `${options.path}/${options.name}.service.ts`;
tree.write(servicePath, `
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ${options.name}Service {}
`);
}
});
}
// Use in tests
describe('Schematic Integration', () => {
beforeEach(() => {
setupSchematicTesting();
});
it('should generate component using mocked schematic', async () => {
const tree = new FsTree('/test-workspace');
// This will use the mocked schematic
const wrappedSchematic = wrapAngularDevkitSchematic('@angular/core', 'component');
await wrappedSchematic(tree, {
name: 'test-component',
path: 'src/app'
});
expect(tree.exists('src/app/test-component.component.ts')).toBe(true);
});
});import { getLogger, arrayBufferToString } from "@nrwl/tao/commands/ngcli-adapter";
import { logger as nxLogger } from "@nrwl/tao/shared/logger";
// Create Angular DevKit logger that integrates with Nx logging
const angularLogger = getLogger(true); // verbose logging
// Use Angular logger for DevKit operations
angularLogger.info('Starting Angular DevKit operation');
angularLogger.warn('This is a warning from Angular DevKit');
// Convert binary data for logging
const binaryData = new ArrayBuffer(8);
const stringData = arrayBufferToString(binaryData);
nxLogger.debug('Binary data as string:', stringData);
// Custom logging bridge
function bridgeAngularToNxLogging(angularLogger: any, nxLogger: any) {
const originalInfo = angularLogger.info;
const originalWarn = angularLogger.warn;
const originalError = angularLogger.error;
angularLogger.info = (message: string) => {
nxLogger.info(`[Angular] ${message}`);
return originalInfo.call(angularLogger, message);
};
angularLogger.warn = (message: string) => {
nxLogger.warn(`[Angular] ${message}`);
return originalWarn.call(angularLogger, message);
};
angularLogger.error = (message: string) => {
nxLogger.error(`[Angular] ${message}`);
return originalError.call(angularLogger, message);
};
}
bridgeAngularToNxLogging(angularLogger, nxLogger);The Angular CLI adapter works seamlessly with other @nrwl/tao APIs:
import { scheduleTarget } from "@nrwl/tao/commands/ngcli-adapter";
import { Workspaces } from "@nrwl/tao/shared/workspace";
import { logger } from "@nrwl/tao/shared/logger";
import { workspaceRoot } from "@nrwl/tao/utils/app-root";
async function buildAllApps() {
const workspaces = new Workspaces(workspaceRoot);
const projects = workspaces.readProjectsConfigurations();
// Find all application projects
const apps = Object.entries(projects.projects)
.filter(([_, config]) => config.projectType === 'application')
.map(([name]) => name);
logger.info(`Found ${apps.length} applications to build`);
// Build each app
for (const appName of apps) {
try {
logger.info(`Building ${appName}...`);
const buildResult$ = await scheduleTarget(
workspaceRoot,
{
project: appName,
target: 'build',
configuration: 'production',
runOptions: {}
},
false // not verbose for batch operation
);
await new Promise((resolve, reject) => {
buildResult$.subscribe({
next: (result) => {
if (result.success) {
logger.info(`✓ ${appName} built successfully`);
} else {
logger.error(`✗ ${appName} build failed: ${result.error}`);
}
},
error: reject,
complete: resolve
});
});
} catch (error) {
logger.error(`Failed to build ${appName}:`, error);
}
}
logger.info('Batch build completed');
}Install with Tessl CLI
npx tessl i tessl/npm-nrwl--tao