import { normalize, strings } from '@angular-devkit/core'; import { workspace } from '@angular-devkit/core/src/experimental'; import { apply, branchAndMerge, chain, mergeWith, move, renameTemplateFiles, Rule, SchematicContext, template, Tree, url } from '@angular-devkit/schematics'; import { addDeclarationToModule, addEntryComponentToModule, addExportToModule, addModuleImportToModule, findModuleFromOptions } from '@angular/cdk/schematics'; import { InsertChange } from '@schematics/angular/utility/change'; import { buildRelativePath } from '@schematics/angular/utility/find-module'; import { NotValidAngularWorkspace } from './exceptions/exception-not-workspace'; import { FormJsonNotFoundError } from './exceptions/form-json-not-found'; import { FormJson } from './models/form-json.model'; import { OptionsFormSchema } from './models/options-schema'; import { ProjetTypeEnum } from './models/project-type.enum'; import { readIntoSourceFile } from './utils/file'; import { parseName } from './utils/parsers'; // You don't have to export the function as default. You can also have more than one rule factory // per file. export function forms(_options: OptionsFormSchema): Rule { return (tree: Tree, _context: SchematicContext) => { // Log // context.logger.info('Info message'); // context.logger.warn('Warn message'); // context.logger.error('Error message'); const workspaceConfig = tree.read('/angular.json'); if (!workspaceConfig) { throw new NotValidAngularWorkspace(); } const workspaceContent = workspaceConfig.toString(); const workspace: workspace.WorkspaceSchema = JSON.parse( workspaceContent ); if (!_options.project) { _options.project = workspace.defaultProject || ''; } const projectName = _options.project; const project = workspace.projects[projectName]; const jsonFormConfig = tree.read(`${_options.config}`); if (!jsonFormConfig) { throw new FormJsonNotFoundError(); } const jsonFormContent = jsonFormConfig.toString(); const formJsonObj = new FormJson(JSON.parse(jsonFormContent)); const projectType = project.projectType === 'application' ? ProjetTypeEnum.APP : ProjetTypeEnum.LIB; if (!_options.path) { _options.path = `${project.sourceRoot}/${projectType}`; } const parsedOptions = parseName(_options.path, _options.name); _options = { ..._options, ...parsedOptions }; const templateSource = apply(url('./templates/forms'), [ renameTemplateFiles(), template({ ...strings, ..._options, formJsonObj }), move(normalize((_options.path + '/' + _options.name) as string)) ]); return chain([ branchAndMerge(chain([mergeWith(templateSource)])), addTreeModulesToModule(_options), addDeclarationsToModule(_options) ])(tree, _context); }; } function addTreeModulesToModule(options: any) { return (host: Tree) => { const modulePath = findModuleFromOptions(host, options)!; addModuleImportToModule( host, modulePath, 'ReactiveFormsModule', '@angular/forms' ); addModuleImportToModule( host, modulePath, 'FormsModule', '@angular/forms' ); addModuleImportToModule( host, modulePath, 'NgbModule', '@ng-bootstrap/ng-bootstrap' ); return host; }; } function addDeclarationsToModule(options: any) { return (host: Tree) => { const modulePath = findModuleFromOptions(host, options)!; let source = readIntoSourceFile(host, modulePath); const formPath = `/${options.path}/` + strings.dasherize(options.name) + '/' + strings.dasherize(options.name) + '-form.component'; const relativePath = buildRelativePath(modulePath, formPath); const declarationChanges = addDeclarationToModule( source, modulePath, strings.classify(`${options.name}FormComponent`), relativePath ); const declarationRecorder = host.beginUpdate(modulePath); for (const change of declarationChanges) { if (change instanceof InsertChange) { declarationRecorder.insertLeft(change.pos, change.toAdd); } } host.commitUpdate(declarationRecorder); if (options.export) { // Need to refresh the AST because we overwrote the file in the host. source = readIntoSourceFile(host, modulePath); const exportRecorder = host.beginUpdate(modulePath); const exportChanges = addExportToModule( source, modulePath, strings.classify(`${options.name}Component`), relativePath ); for (const change of exportChanges) { if (change instanceof InsertChange) { exportRecorder.insertLeft(change.pos, change.toAdd); } } host.commitUpdate(exportRecorder); } if (options.entryComponent) { // Need to refresh the AST because we overwrote the file in the host. source = readIntoSourceFile(host, modulePath); const entryComponentRecorder = host.beginUpdate(modulePath); const entryComponentChanges = addEntryComponentToModule( source, modulePath, strings.classify(`${options.name}Component`), relativePath ); for (const change of entryComponentChanges) { if (change instanceof InsertChange) { entryComponentRecorder.insertLeft(change.pos, change.toAdd); } } host.commitUpdate(entryComponentRecorder); } return host; }; }