feat(angular): add pipe generator (#15659)

This commit is contained in:
Colum Ferry 2023-03-14 17:16:32 +00:00 committed by GitHub
parent ca8d24b3d4
commit 9cd28583f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 815 additions and 83 deletions

View File

@ -3779,6 +3779,14 @@
"isExternal": false, "isExternal": false,
"disableCollapsible": false "disableCollapsible": false
}, },
{
"id": "pipe",
"path": "/packages/angular/generators/pipe",
"name": "pipe",
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{ {
"id": "scam-to-standalone", "id": "scam-to-standalone",
"path": "/packages/angular/generators/scam-to-standalone", "path": "/packages/angular/generators/scam-to-standalone",

View File

@ -279,6 +279,15 @@
"path": "/packages/angular/generators/ngrx", "path": "/packages/angular/generators/ngrx",
"type": "generator" "type": "generator"
}, },
"/packages/angular/generators/pipe": {
"description": "Generate an Angular Pipe",
"file": "generated/packages/angular/generators/pipe.json",
"hidden": false,
"name": "pipe",
"originalFilePath": "/packages/angular/src/generators/pipe/schema.json",
"path": "/packages/angular/generators/pipe",
"type": "generator"
},
"/packages/angular/generators/scam-to-standalone": { "/packages/angular/generators/scam-to-standalone": {
"description": "Convert an existing Single Component Angular Module (SCAM) to a Standalone Component.", "description": "Convert an existing Single Component Angular Module (SCAM) to a Standalone Component.",
"file": "generated/packages/angular/generators/scam-to-standalone.json", "file": "generated/packages/angular/generators/scam-to-standalone.json",

View File

@ -273,6 +273,15 @@
"path": "angular/generators/ngrx", "path": "angular/generators/ngrx",
"type": "generator" "type": "generator"
}, },
{
"description": "Generate an Angular Pipe",
"file": "generated/packages/angular/generators/pipe.json",
"hidden": false,
"name": "pipe",
"originalFilePath": "/packages/angular/src/generators/pipe/schema.json",
"path": "angular/generators/pipe",
"type": "generator"
},
{ {
"description": "Convert an existing Single Component Angular Module (SCAM) to a Standalone Component.", "description": "Convert an existing Single Component Angular Module (SCAM) to a Standalone Component.",
"file": "generated/packages/angular/generators/scam-to-standalone.json", "file": "generated/packages/angular/generators/scam-to-standalone.json",

View File

@ -26,6 +26,7 @@
"project": { "project": {
"type": "string", "type": "string",
"description": "The name of the project.", "description": "The name of the project.",
"$default": { "$source": "projectName" },
"x-dropdown": "projects" "x-dropdown": "projects"
}, },
"prefix": { "prefix": {

View File

@ -0,0 +1,77 @@
{
"name": "pipe",
"factory": "./src/generators/pipe/pipe",
"schema": {
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "SchematicsAngularPipe",
"title": "Angular Pipe Options Schema",
"type": "object",
"cli": "nx",
"additionalProperties": false,
"description": "Creates a new, generic pipe definition in the given project.",
"properties": {
"name": {
"type": "string",
"description": "The name of the pipe.",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use for the pipe?"
},
"path": {
"type": "string",
"format": "path",
"$default": { "$source": "workingDirectory" },
"description": "The path at which to create the pipe, relative to the workspace root.",
"visible": false
},
"project": {
"type": "string",
"description": "The name of the project.",
"$default": { "$source": "projectName" },
"x-dropdown": "projects"
},
"flat": {
"type": "boolean",
"default": true,
"description": "When true (the default) creates files at the top level of the project."
},
"skipTests": {
"type": "boolean",
"description": "Do not create \"spec.ts\" test files for the new pipe.",
"default": false
},
"skipImport": {
"type": "boolean",
"default": false,
"description": "Do not import this pipe into the owning NgModule."
},
"standalone": {
"description": "Whether the generated pipe is standalone.",
"type": "boolean",
"default": false
},
"module": {
"type": "string",
"description": "The filename of the declaring NgModule.",
"alias": "m"
},
"export": {
"type": "boolean",
"default": false,
"description": "The declaring NgModule exports this pipe."
},
"skipFormat": {
"type": "boolean",
"default": false,
"description": "Skip formatting of files."
}
},
"required": ["name", "project"],
"presets": []
},
"description": "Generate an Angular Pipe",
"aliases": ["p"],
"implementation": "/packages/angular/src/generators/pipe/pipe.ts",
"hidden": false,
"path": "/packages/angular/src/generators/pipe/schema.json",
"type": "generator"
}

View File

@ -254,6 +254,12 @@
"schema": "./src/generators/ngrx/schema.json", "schema": "./src/generators/ngrx/schema.json",
"description": "Adds NgRx support to an application or library." "description": "Adds NgRx support to an application or library."
}, },
"pipe": {
"factory": "./src/generators/pipe/pipe",
"schema": "./src/generators/pipe/schema.json",
"description": "Generate an Angular Pipe",
"aliases": ["p"]
},
"scam-to-standalone": { "scam-to-standalone": {
"factory": "./src/generators/scam-to-standalone/scam-to-standalone", "factory": "./src/generators/scam-to-standalone/scam-to-standalone",
"schema": "./src/generators/scam-to-standalone/schema.json", "schema": "./src/generators/scam-to-standalone/schema.json",

View File

@ -11,6 +11,7 @@ export * from './src/generators/library-secondary-entry-point/library-secondary-
export * from './src/generators/library/library'; export * from './src/generators/library/library';
export * from './src/generators/move/move'; export * from './src/generators/move/move';
export * from './src/generators/ngrx/ngrx'; export * from './src/generators/ngrx/ngrx';
export * from './src/generators/pipe/pipe';
export * from './src/generators/remote/remote'; export * from './src/generators/remote/remote';
export * from './src/generators/scam-directive/scam-directive'; export * from './src/generators/scam-directive/scam-directive';
export * from './src/generators/scam-pipe/scam-pipe'; export * from './src/generators/scam-pipe/scam-pipe';

View File

@ -10,10 +10,7 @@ import {
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import type { Schema } from './schema'; import type { Schema } from './schema';
import { checkPathUnderProjectRoot } from '../utils/path'; import { checkPathUnderProjectRoot } from '../utils/path';
import { dirname } from 'path'; import { addToNgModule, findModule } from '../utils';
import { insertNgModuleProperty } from '../utils';
import { insertImport } from '@nrwl/js';
import { ensureTypescript } from '@nrwl/js/src/utils/typescript/ensure-typescript';
let tsModule: typeof import('typescript'); let tsModule: typeof import('typescript');
@ -65,7 +62,17 @@ export async function directiveGenerator(tree: Tree, schema: Schema) {
if (!schema.skipImport && !schema.standalone) { if (!schema.skipImport && !schema.standalone) {
const modulePath = findModule(tree, path, schema.module); const modulePath = findModule(tree, path, schema.module);
addImportToNgModule(path, modulePath, schema, directiveNames, tree); addToNgModule(
tree,
path,
modulePath,
directiveNames.fileName,
`${directiveNames.className}Directive`,
`${directiveNames.fileName}.directive`,
'declarations',
schema.flat,
schema.export
);
} }
if (!schema.skipFormat) { if (!schema.skipFormat) {
@ -80,81 +87,4 @@ function buildSelector(tree: Tree, name: string, prefix: string) {
return names(`${selectorPrefix}-${selector}`).propertyName; return names(`${selectorPrefix}-${selector}`).propertyName;
} }
function findModule(tree: Tree, path: string, module?: string) {
let modulePath = '';
let pathToSearch = path;
while (pathToSearch !== '/') {
if (module) {
const pathToModule = joinPathFragments(pathToSearch, module);
if (tree.exists(pathToModule)) {
modulePath = pathToModule;
break;
}
} else {
const potentialOptions = tree
.children(pathToSearch)
.filter((f) => f.endsWith('.module.ts'));
if (potentialOptions.length > 1) {
throw new Error(
`More than one NgModule was found. Please provide the NgModule you wish to use.`
);
} else if (potentialOptions.length === 1) {
modulePath = joinPathFragments(pathToSearch, potentialOptions[0]);
break;
}
}
pathToSearch = dirname(pathToSearch);
}
const moduleContents = tree.read(modulePath, 'utf-8');
if (!moduleContents.includes('@NgModule')) {
throw new Error(
`Declaring module file (${modulePath}) does not contain an @NgModule Declaration.`
);
}
return modulePath;
}
function addImportToNgModule(
path: string,
modulePath: string,
schema: Schema,
directiveNames: {
name: string;
className: string;
propertyName: string;
constantName: string;
fileName: string;
},
tree: Tree
) {
if (!tsModule) {
tsModule = ensureTypescript();
}
let relativePath = `${joinPathFragments(
path.replace(dirname(modulePath), ''),
!schema.flat ? directiveNames.fileName : '',
`${directiveNames.fileName}.directive`
)}`;
relativePath = relativePath.startsWith('/')
? `.${relativePath}`
: `./${relativePath}`;
const directiveClassName = `${directiveNames.className}Directive`;
const moduleContents = tree.read(modulePath, 'utf-8');
const source = tsModule.createSourceFile(
modulePath,
moduleContents,
tsModule.ScriptTarget.Latest,
true
);
insertImport(tree, source, modulePath, directiveClassName, relativePath);
insertNgModuleProperty(tree, modulePath, directiveClassName, 'declarations');
if (schema.export) {
insertNgModuleProperty(tree, modulePath, directiveClassName, 'exports');
}
}
export default directiveGenerator; export default directiveGenerator;

View File

@ -28,6 +28,9 @@
"project": { "project": {
"type": "string", "type": "string",
"description": "The name of the project.", "description": "The name of the project.",
"$default": {
"$source": "projectName"
},
"x-dropdown": "projects" "x-dropdown": "projects"
}, },
"prefix": { "prefix": {

View File

@ -0,0 +1,261 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`pipe generator should export the pipe correctly when flat=false and path is nested deeper 1`] = `
"import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'test'
})
export class TestPipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}
"
`;
exports[`pipe generator should export the pipe correctly when flat=false and path is nested deeper 2`] = `
"import { TestPipe } from './test.pipe';
describe('TestPipe', () => {
it('create an instance', () => {
const pipe = new TestPipe();
expect(pipe).toBeTruthy();
});
});
"
`;
exports[`pipe generator should export the pipe correctly when flat=false and path is nested deeper 3`] = `
"import {NgModule} from \\"@angular/core\\";
import { TestPipe } from './my-pipes/test/test.pipe';
@NgModule({
imports: [],
declarations: [, TestPipe],
exports: [, TestPipe]
})
export class TestModule {}"
`;
exports[`pipe generator should generate a pipe with test files and attach to the NgModule automatically 1`] = `
"import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'test'
})
export class TestPipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}
"
`;
exports[`pipe generator should generate a pipe with test files and attach to the NgModule automatically 2`] = `
"import { TestPipe } from './test.pipe';
describe('TestPipe', () => {
it('create an instance', () => {
const pipe = new TestPipe();
expect(pipe).toBeTruthy();
});
});
"
`;
exports[`pipe generator should generate a pipe with test files and attach to the NgModule automatically 3`] = `
"import {NgModule} from \\"@angular/core\\";
import { TestPipe } from './test.pipe';
@NgModule({
imports: [],
declarations: [, TestPipe],
exports: []
})
export class TestModule {}"
`;
exports[`pipe generator should import the pipe correctly when flat=false 1`] = `
"import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'test'
})
export class TestPipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}
"
`;
exports[`pipe generator should import the pipe correctly when flat=false 2`] = `
"import { TestPipe } from './test.pipe';
describe('TestPipe', () => {
it('create an instance', () => {
const pipe = new TestPipe();
expect(pipe).toBeTruthy();
});
});
"
`;
exports[`pipe generator should import the pipe correctly when flat=false 3`] = `
"import {NgModule} from \\"@angular/core\\";
import { TestPipe } from './test/test.pipe';
@NgModule({
imports: [],
declarations: [, TestPipe],
exports: []
})
export class TestModule {}"
`;
exports[`pipe generator should import the pipe correctly when flat=false and path is nested deeper 1`] = `
"import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'test'
})
export class TestPipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}
"
`;
exports[`pipe generator should import the pipe correctly when flat=false and path is nested deeper 2`] = `
"import { TestPipe } from './test.pipe';
describe('TestPipe', () => {
it('create an instance', () => {
const pipe = new TestPipe();
expect(pipe).toBeTruthy();
});
});
"
`;
exports[`pipe generator should import the pipe correctly when flat=false and path is nested deeper 3`] = `
"import {NgModule} from \\"@angular/core\\";
import { TestPipe } from './my-pipes/test/test.pipe';
@NgModule({
imports: [],
declarations: [, TestPipe],
exports: []
})
export class TestModule {}"
`;
exports[`pipe generator should not generate test file when skipTests=true 1`] = `
"import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'test'
})
export class TestPipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}
"
`;
exports[`pipe generator should not generate test file when skipTests=true 2`] = `
"import {NgModule} from \\"@angular/core\\";
import { TestPipe } from './my-pipes/test/test.pipe';
@NgModule({
imports: [],
declarations: [, TestPipe],
exports: []
})
export class TestModule {}"
`;
exports[`pipe generator should not import the pipe when skipImport=true 1`] = `
"import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'test'
})
export class TestPipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}
"
`;
exports[`pipe generator should not import the pipe when skipImport=true 2`] = `
"import { TestPipe } from './test.pipe';
describe('TestPipe', () => {
it('create an instance', () => {
const pipe = new TestPipe();
expect(pipe).toBeTruthy();
});
});
"
`;
exports[`pipe generator should not import the pipe when skipImport=true 3`] = `
"import {NgModule} from \\"@angular/core\\";
@NgModule({
imports: [],
declarations: [],
exports: []
})
export class TestModule {}"
`;
exports[`pipe generator should not import the pipe when standalone=true 1`] = `
"import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'test',
standalone: true
})
export class TestPipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}
"
`;
exports[`pipe generator should not import the pipe when standalone=true 2`] = `
"import { TestPipe } from './test.pipe';
describe('TestPipe', () => {
it('create an instance', () => {
const pipe = new TestPipe();
expect(pipe).toBeTruthy();
});
});
"
`;
exports[`pipe generator should not import the pipe when standalone=true 3`] = `
"import {NgModule} from \\"@angular/core\\";
@NgModule({
imports: [],
declarations: [],
exports: []
})
export class TestModule {}"
`;

View File

@ -0,0 +1,8 @@
import { <%= pipeClassName %>Pipe } from './<%= pipeFileName %>.pipe';
describe('<%= pipeClassName %>Pipe', () => {
it('create an instance', () => {
const pipe = new <%= pipeClassName %>Pipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -0,0 +1,13 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: '<%= pipePropertyName %>'<% if(standalone) {%>,
standalone: true<%}%>
})
export class <%= pipeClassName %>Pipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}

View File

@ -0,0 +1,155 @@
import { addProjectConfiguration, Tree } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { pipeGenerator } from './pipe';
import type { Schema } from './schema';
describe('pipe generator', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
addProjectConfiguration(tree, 'test', {
root: 'test',
sourceRoot: 'test/src',
});
tree.write(
'test/src/test.module.ts',
`import {NgModule} from "@angular/core";
@NgModule({
imports: [],
declarations: [],
exports: []
})
export class TestModule {}`
);
});
it('should generate a pipe with test files and attach to the NgModule automatically', async () => {
// ARRANGE
// ACT
await generatePipeWithDefaultOptions(tree);
// ASSERT
expect(tree.read('test/src/test.pipe.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/test.pipe.spec.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should import the pipe correctly when flat=false', async () => {
// ARRANGE
// ACT
await generatePipeWithDefaultOptions(tree, { flat: false });
// ASSERT
expect(tree.read('test/src/test/test.pipe.ts', 'utf-8')).toMatchSnapshot();
expect(
tree.read('test/src/test/test.pipe.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should not import the pipe when standalone=true', async () => {
// ARRANGE
// ACT
await generatePipeWithDefaultOptions(tree, { standalone: true });
// ASSERT
expect(tree.read('test/src/test.pipe.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/test.pipe.spec.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should import the pipe correctly when flat=false and path is nested deeper', async () => {
// ARRANGE
// ACT
await generatePipeWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-pipes',
});
// ASSERT
expect(
tree.read('test/src/my-pipes/test/test.pipe.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/my-pipes/test/test.pipe.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should export the pipe correctly when flat=false and path is nested deeper', async () => {
// ARRANGE
// ACT
await generatePipeWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-pipes',
export: true,
});
// ASSERT
expect(
tree.read('test/src/my-pipes/test/test.pipe.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/my-pipes/test/test.pipe.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should not import the pipe when skipImport=true', async () => {
// ARRANGE
// ACT
await generatePipeWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-pipes',
skipImport: true,
});
// ASSERT
expect(
tree.read('test/src/my-pipes/test/test.pipe.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/my-pipes/test/test.pipe.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should not generate test file when skipTests=true', async () => {
// ARRANGE
// ACT
await generatePipeWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-pipes',
skipTests: true,
});
// ASSERT
expect(
tree.read('test/src/my-pipes/test/test.pipe.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.exists('test/src/my-pipes/test/test.pipe.spec.ts')).toBeFalsy();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
});
});
async function generatePipeWithDefaultOptions(
tree: Tree,
overrides: Partial<Schema> = {}
) {
await pipeGenerator(tree, {
name: 'test',
project: 'test',
flat: true,
...overrides,
});
}

View File

@ -0,0 +1,79 @@
import type { ProjectConfiguration, Tree } from '@nrwl/devkit';
import {
formatFiles,
generateFiles,
getProjects,
joinPathFragments,
names,
readProjectConfiguration,
} from '@nrwl/devkit';
import type { Schema } from './schema';
import { checkPathUnderProjectRoot } from '../utils/path';
import { addToNgModule, findModule } from '../utils';
let tsModule: typeof import('typescript');
export async function pipeGenerator(tree: Tree, schema: Schema) {
const projects = getProjects(tree);
if (!projects.has(schema.project)) {
throw new Error(`Project "${schema.project}" does not exist!`);
}
checkPathUnderProjectRoot(tree, schema.project, schema.path);
const project = readProjectConfiguration(
tree,
schema.project
) as ProjectConfiguration & { prefix?: string };
const path = schema.path ?? `${project.sourceRoot}`;
const pipeNames = names(schema.name);
const pathToGenerateFiles = schema.flat
? './files/__pipeFileName__'
: './files';
await generateFiles(
tree,
joinPathFragments(__dirname, pathToGenerateFiles),
path,
{
pipeClassName: pipeNames.className,
pipeFileName: pipeNames.fileName,
pipePropertyName: pipeNames.propertyName,
standalone: schema.standalone,
tpl: '',
}
);
if (schema.skipTests) {
const pathToSpecFile = joinPathFragments(
path,
`${!schema.flat ? `${pipeNames.fileName}/` : ``}${
pipeNames.fileName
}.pipe.spec.ts`
);
tree.delete(pathToSpecFile);
}
if (!schema.skipImport && !schema.standalone) {
const modulePath = findModule(tree, path, schema.module);
addToNgModule(
tree,
path,
modulePath,
pipeNames.fileName,
`${pipeNames.className}Pipe`,
`${pipeNames.fileName}.pipe`,
'declarations',
schema.flat,
schema.export
);
}
if (!schema.skipFormat) {
await formatFiles(tree);
}
}
export default pipeGenerator;

View File

@ -0,0 +1,12 @@
export interface Schema {
name: string;
project: string;
path?: string;
flat?: boolean;
skipTests?: boolean;
skipImport?: boolean;
standalone?: boolean;
module?: string;
export?: boolean;
skipFormat?: boolean;
}

View File

@ -0,0 +1,73 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "SchematicsAngularPipe",
"title": "Angular Pipe Options Schema",
"type": "object",
"cli": "nx",
"additionalProperties": false,
"description": "Creates a new, generic pipe definition in the given project.",
"properties": {
"name": {
"type": "string",
"description": "The name of the pipe.",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What name would you like to use for the pipe?"
},
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the pipe, relative to the workspace root.",
"visible": false
},
"project": {
"type": "string",
"description": "The name of the project.",
"$default": {
"$source": "projectName"
},
"x-dropdown": "projects"
},
"flat": {
"type": "boolean",
"default": true,
"description": "When true (the default) creates files at the top level of the project."
},
"skipTests": {
"type": "boolean",
"description": "Do not create \"spec.ts\" test files for the new pipe.",
"default": false
},
"skipImport": {
"type": "boolean",
"default": false,
"description": "Do not import this pipe into the owning NgModule."
},
"standalone": {
"description": "Whether the generated pipe is standalone.",
"type": "boolean",
"default": false
},
"module": {
"type": "string",
"description": "The filename of the declaring NgModule.",
"alias": "m"
},
"export": {
"type": "boolean",
"default": false,
"description": "The declaring NgModule exports this pipe."
},
"skipFormat": {
"type": "boolean",
"default": false,
"description": "Skip formatting of files."
}
},
"required": ["name", "project"]
}

View File

@ -0,0 +1,86 @@
import type { Tree } from '@nrwl/devkit';
import { joinPathFragments } from '@nrwl/devkit';
import { dirname } from 'path';
import { ensureTypescript } from '@nrwl/js/src/utils/typescript/ensure-typescript';
import { insertImport } from '@nrwl/js';
import {
insertNgModuleProperty,
ngModuleDecoratorProperty,
} from './insert-ngmodule-import';
let tsModule: typeof import('typescript');
export function findModule(tree: Tree, path: string, module?: string) {
let modulePath = '';
let pathToSearch = path;
while (pathToSearch !== '/') {
if (module) {
const pathToModule = joinPathFragments(pathToSearch, module);
if (tree.exists(pathToModule)) {
modulePath = pathToModule;
break;
}
} else {
const potentialOptions = tree
.children(pathToSearch)
.filter((f) => f.endsWith('.module.ts'));
if (potentialOptions.length > 1) {
throw new Error(
`More than one NgModule was found. Please provide the NgModule you wish to use.`
);
} else if (potentialOptions.length === 1) {
modulePath = joinPathFragments(pathToSearch, potentialOptions[0]);
break;
}
}
pathToSearch = dirname(pathToSearch);
}
const moduleContents = tree.read(modulePath, 'utf-8');
if (!moduleContents.includes('@NgModule')) {
throw new Error(
`Declaring module file (${modulePath}) does not contain an @NgModule Declaration.`
);
}
return modulePath;
}
export function addToNgModule(
tree: Tree,
path: string,
modulePath: string,
name: string,
className: string,
fileName: string,
ngModuleProperty: ngModuleDecoratorProperty,
isFlat = true,
isExported = false
) {
if (!tsModule) {
tsModule = ensureTypescript();
}
let relativePath = `${joinPathFragments(
path.replace(dirname(modulePath), ''),
!isFlat ? name : '',
`${fileName}`
)}`;
relativePath = relativePath.startsWith('/')
? `.${relativePath}`
: `./${relativePath}`;
const moduleContents = tree.read(modulePath, 'utf-8');
const source = tsModule.createSourceFile(
modulePath,
moduleContents,
tsModule.ScriptTarget.Latest,
true
);
insertImport(tree, source, modulePath, className, relativePath);
insertNgModuleProperty(tree, modulePath, className, ngModuleProperty);
if (isExported) {
insertNgModuleProperty(tree, modulePath, className, 'exports');
}
}

View File

@ -1 +1,2 @@
export * from './insert-ngmodule-import'; export * from './insert-ngmodule-import';
export * from './find-module';

View File

@ -13,7 +13,7 @@ import { ensureTypescript } from '@nrwl/js/src/utils/typescript/ensure-typescrip
let tsModule: typeof import('typescript'); let tsModule: typeof import('typescript');
type ngModuleDecoratorProperty = export type ngModuleDecoratorProperty =
| 'imports' | 'imports'
| 'providers' | 'providers'
| 'declarations' | 'declarations'