From 3c232259d86feb25fa79597f53b7a3a4b4e7ae1f Mon Sep 17 00:00:00 2001 From: Masum ULU Date: Thu, 21 Sep 2023 11:12:56 +0300 Subject: [PATCH] Refactor change-theme schematic --- npm/ng-packs/package.json | 2 +- .../src/commands/change-theme/index.ts | 222 ++++++++++-------- .../schematics/src/utils/angular/ast-utils.ts | 5 +- 3 files changed, 123 insertions(+), 106 deletions(-) diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index d49d6b5375..b5f5bddbf7 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -34,7 +34,7 @@ "build:schematics": "cd scripts && yarn && yarn build:schematics && cd ..", "dev:schematics": "tsc -p packages/schematics/tsconfig.json -w", "mock:schematics": "cd scripts/mock-schematic && yarn && yarn start", - "debug:schematics": "./node_modules/.bin/ng g ./packages/schematics/src/collection.json:proxy-add --module __default --apiName __default --source __default --target __default --url https://localhost:44305 --serviceType application --entryPoint __default ", + "debug:schematics": "./node_modules/.bin/nx g ./packages/schematics/src/collection.json:proxy-add --module identity --apiName __default --source __default --target __default --url https://localhost:44305 --serviceType application --entryPoint __default ", "debug:schematics-dist": "./node_modules/.bin/ng g ./dist/packages/schematics/collection.json:proxy-add --module __default --apiName __default --source __default --target __default --url http://localhost:4300 --service-type application --entryPoint __default", "ci": "yarn affected:lint && yarn affected:build && yarn affected:test", "lerna": "lerna", diff --git a/npm/ng-packs/packages/schematics/src/commands/change-theme/index.ts b/npm/ng-packs/packages/schematics/src/commands/change-theme/index.ts index ac9e420046..30a29ed7dc 100644 --- a/npm/ng-packs/packages/schematics/src/commands/change-theme/index.ts +++ b/npm/ng-packs/packages/schematics/src/commands/change-theme/index.ts @@ -1,22 +1,20 @@ import { JsonArray, JsonValue } from '@angular-devkit/core'; -import { Rule, SchematicsException, Tree, UpdateRecorder, chain } from '@angular-devkit/schematics'; +import { Rule, SchematicsException, Tree, chain } from '@angular-devkit/schematics'; import { ProjectDefinition } from '@angular-devkit/core/src/workspace'; import * as ts from 'typescript'; -import { ImportDefinition, allStyles, importMap, styleMap } from './style-map'; +import { allStyles, importMap, styleMap } from './style-map'; import { ChangeThemeOptions } from './model'; +import { Change, InsertChange, isLibrary, updateWorkspace, WorkspaceDefinition } from '../../utils'; +import { ThemeOptionsEnum } from './theme-options.enum'; import { addImportToModule, - Change, - InsertChange, - isLibrary, - updateWorkspace, - WorkspaceDefinition, -} from '../../utils'; -import { ThemeOptionsEnum } from './theme-options.enum'; -import { findNodes, getDecoratorMetadata, getMetadataField } from '../../utils/angular/ast-utils'; + findNodes, + getDecoratorMetadata, + getMetadataField, +} from '../../utils/angular/ast-utils'; export default function (_options: ChangeThemeOptions): Rule { - return async (host: Tree) => { + return async () => { const targetThemeName = _options.name; const selectedProject = _options.targetProject; if (!targetThemeName) { @@ -27,9 +25,7 @@ export default function (_options: ChangeThemeOptions): Rule { updateWorkspace(storedWorkspace => { updateProjectStyle(selectedProject, storedWorkspace, targetThemeName); }), - updateWorkspace(storedWorkspace => { - updateAppModule(host, selectedProject, storedWorkspace, targetThemeName); - }), + updateAppModule(selectedProject, targetThemeName), ]); }; } @@ -62,114 +58,136 @@ function updateProjectStyle( targetOption.styles = [...newStyles, ...sanitizedStyles] as JsonArray; } -function updateAppModule( - host: Tree, - projectName: string, - workspace: WorkspaceDefinition, - targetThemeName: ThemeOptionsEnum, -) { - const selectedTheme = importMap.get(targetThemeName); - if (!selectedTheme) { - throw new SchematicsException('The theme does not found'); - } - - const project = workspace.projects.get(projectName); - const appModulePath = `${project?.sourceRoot}/app/app.module.ts`; - - const text = host.read(appModulePath); - if (!text) { - throw new SchematicsException('The app module does not found'); - } - - const sourceText = text.toString('utf-8'); - const source = ts.createSourceFile( - appModulePath, - sourceText, - ts.ScriptTarget.Latest, - true, - ts.ScriptKind.TS, - ); - - const recorder = host.beginUpdate(appModulePath); +function updateAppModule(selectedProject: string, targetThemeName: ThemeOptionsEnum): Rule { + return (host: Tree) => { + const angularJSON = host.read('angular.json'); + if (!angularJSON) { + throw new SchematicsException('The angular.json does not found'); + } - const impMap = Array.from(importMap.values()) - .filter(f => f !== importMap.get(targetThemeName)) - .reduce((acc, val) => [...acc, ...val], []); + const workspace = JSON.parse(angularJSON.toString()); + const project = workspace.projects[selectedProject]; - removeImportPath(source, recorder, impMap); - removeImportFromNgModuleMetadata(source, recorder, impMap); + if (!project || !project.sourceRoot) { + throw new SchematicsException('The target project does not found'); + } - insertImports(selectedTheme, source, appModulePath, recorder); + const appModulePath = project.sourceRoot + '/app/app.module.ts'; - host.commitUpdate(recorder); - return host; + return chain([ + removeImportPath(appModulePath, targetThemeName), + removeImportFromNgModuleMetadata(appModulePath, targetThemeName), + insertImports(appModulePath, targetThemeName), + ]); + }; } -function removeImportPath( - source: ts.SourceFile, - recorder: UpdateRecorder, - arr: ImportDefinition[], -) { - const nodes = findNodes(source, ts.isImportDeclaration); - const filteredNodes = nodes.filter(n => arr.some(f => n.getFullText().match(f.path))); +function removeImportPath(appModulePath: string, selectedTheme: ThemeOptionsEnum): Rule { + return (host: Tree) => { + const recorder = host.beginUpdate(appModulePath); + const sourceText = host.read(appModulePath)?.toString('utf-8'); + const source = ts.createSourceFile( + appModulePath, + sourceText!, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TS, + ); + const impMap = Array.from(importMap.values()) + .filter(f => f !== importMap.get(selectedTheme)) + .reduce((acc, val) => [...acc, ...val], []); + + const nodes = findNodes(source, ts.isImportDeclaration); + const filteredNodes = nodes.filter(n => impMap.some(f => n.getFullText().match(f.path))); + if (!filteredNodes || filteredNodes.length < 1) { + return; + } - if (!filteredNodes || filteredNodes.length < 1) { - return; - } + filteredNodes.map(importPath => + recorder.remove(importPath.getStart(), importPath.getWidth() + 1), + ); - filteredNodes.map(importPath => - recorder.remove(importPath.getStart(), importPath.getWidth() + 1), - ); + host.commitUpdate(recorder); + return host; + }; } function removeImportFromNgModuleMetadata( - source: ts.SourceFile, - recorder: UpdateRecorder, - arr: ImportDefinition[], -) { - const node = getDecoratorMetadata(source, 'NgModule', '@angular/core')[0] || {}; - if (!node) { - throw new SchematicsException('The app module does not found'); - } + appModulePath: string, + selectedTheme: ThemeOptionsEnum, +): Rule { + return (host: Tree) => { + const recorder = host.beginUpdate(appModulePath); + const sourceText = host.read(appModulePath)?.toString('utf-8'); + const source = ts.createSourceFile( + appModulePath, + sourceText!, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TS, + ); + const impMap = Array.from(importMap.values()) + .filter(f => f !== importMap.get(selectedTheme)) + .reduce((acc, val) => [...acc, ...val], []); - const matchingProperties = getMetadataField(node as ts.ObjectLiteralExpression, 'imports'); + const node = getDecoratorMetadata(source, 'NgModule', '@angular/core')[0] || {}; + if (!node) { + throw new SchematicsException('The app module does not found'); + } - const assignment = matchingProperties[0] as ts.PropertyAssignment; - const assignmentInit = assignment.initializer as ts.ArrayLiteralExpression; + const matchingProperties = getMetadataField(node as ts.ObjectLiteralExpression, 'imports'); - const elements = assignmentInit.elements; - if (!elements || elements.length < 1) { - return; - } + const assignment = matchingProperties[0] as ts.PropertyAssignment; + const assignmentInit = assignment.initializer as ts.ArrayLiteralExpression; - const filteredElements = elements.filter(f => arr.some(s => f.getText().match(s.importName))); - if (!filteredElements || filteredElements.length < 1) { - return; - } + const elements = assignmentInit.elements; + if (!elements || elements.length < 1) { + return; + } + + const filteredElements = elements.filter(f => + impMap.some(s => f.getText().match(s.importName)), + ); + if (!filteredElements || filteredElements.length < 1) { + return; + } - filteredElements.map(willRemoveModule => - recorder.remove(willRemoveModule.getStart(), willRemoveModule.getWidth() + 1), - ); + filteredElements.map(willRemoveModule => + recorder.remove(willRemoveModule.getStart(), willRemoveModule.getWidth() + 1), + ); + host.commitUpdate(recorder); + return host; + }; } -function insertImports( - selectedTheme: ImportDefinition[], - source: ts.SourceFile, - appModulePath: string, - recorder: UpdateRecorder, -) { - const changes: Change[] = []; - selectedTheme.map(({ importName, path }) => - changes.push(...addImportToModule(source, appModulePath, importName, path)), - ); - - if (changes.length > 0) { - for (const change of changes) { - if (change instanceof InsertChange) { - recorder.insertLeft(change.pos, change.toAdd); +function insertImports(appModulePath: string, selectedTheme: ThemeOptionsEnum): Rule { + return (host: Tree) => { + const recorder = host.beginUpdate(appModulePath); + const sourceText = host.read(appModulePath)?.toString('utf-8'); + const source = ts.createSourceFile( + appModulePath, + sourceText!, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TS, + ); + const selected = importMap.get(selectedTheme); + + const changes: Change[] = []; + selected!.map(({ importName, path }) => + changes.push(...addImportToModule(source, appModulePath, importName, path)), + ); + + if (changes.length > 0) { + for (const change of changes) { + if (change instanceof InsertChange) { + recorder.insertLeft(change.pos, change.toAdd); + } } } - } + host.commitUpdate(recorder); + return host; + }; } export function getProjectTargetOptions( diff --git a/npm/ng-packs/packages/schematics/src/utils/angular/ast-utils.ts b/npm/ng-packs/packages/schematics/src/utils/angular/ast-utils.ts index da5085b1b7..55477b41d3 100644 --- a/npm/ng-packs/packages/schematics/src/utils/angular/ast-utils.ts +++ b/npm/ng-packs/packages/schematics/src/utils/angular/ast-utils.ts @@ -441,12 +441,11 @@ export function addSymbolToNgModuleMetadata( const text = expresssion.getFullText(source); const matches = text.match(/^(\r?\n)(\s*)/); if (matches) { - toInsert = `${matches[1]}${tags.indentBy(matches[2].length)`${symbolName},`}`; + toInsert = `,${matches[1]}${tags.indentBy(matches[2].length)`${symbolName}`}`; } else { - toInsert = `${symbolName},`; + toInsert = `, ${symbolName}`; } } - if (importPath !== null) { return [ new InsertChange(ngModulePath, position, toInsert),