From e894ee6be4d868c970fb3f8746a2cb28d6635ada Mon Sep 17 00:00:00 2001 From: Masum ULU Date: Thu, 14 Sep 2023 14:42:27 +0300 Subject: [PATCH 1/5] Create change theme generator and options --- .../packages/generators/generators.json | 5 +++ .../generators/change-theme/generator.spec.ts | 20 ++++++++++ .../src/generators/change-theme/generator.ts | 22 +++++++++++ .../src/generators/change-theme/index.ts | 3 ++ .../src/generators/change-theme/schema.d.ts | 4 ++ .../src/generators/change-theme/schema.json | 37 +++++++++++++++++++ .../change-theme/theme-options.enum.ts | 8 ++++ npm/ng-packs/packages/generators/src/index.ts | 4 +- 8 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts create mode 100644 npm/ng-packs/packages/generators/src/generators/change-theme/generator.ts create mode 100644 npm/ng-packs/packages/generators/src/generators/change-theme/index.ts create mode 100644 npm/ng-packs/packages/generators/src/generators/change-theme/schema.d.ts create mode 100644 npm/ng-packs/packages/generators/src/generators/change-theme/schema.json create mode 100644 npm/ng-packs/packages/generators/src/generators/change-theme/theme-options.enum.ts diff --git a/npm/ng-packs/packages/generators/generators.json b/npm/ng-packs/packages/generators/generators.json index 8910e6fdba..4f41032144 100644 --- a/npm/ng-packs/packages/generators/generators.json +++ b/npm/ng-packs/packages/generators/generators.json @@ -9,6 +9,11 @@ "factory": "./src/generators/update-version/generator", "schema": "./src/generators/update-version/schema.json", "description": "update-version generator" + }, + "change-theme": { + "factory": "./src/generators/change-theme/generator", + "schema": "./src/generators/change-theme/schema.json", + "description": "change-theme generator" } } } diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts new file mode 100644 index 0000000000..940c5dbe3b --- /dev/null +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts @@ -0,0 +1,20 @@ +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { Tree, readProjectConfiguration } from '@nx/devkit'; + +import { changeThemeGenerator } from './generator'; +import { ChangeThemeGeneratorSchema } from './schema'; + +describe('change-theme generator', () => { + let tree: Tree; + const options: ChangeThemeGeneratorSchema = { name: 'test' }; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should run successfully', async () => { + await changeThemeGenerator(tree, options); + const config = readProjectConfiguration(tree, 'test'); + expect(config).toBeDefined(); + }); +}); diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/generator.ts b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.ts new file mode 100644 index 0000000000..14d2e8e68e --- /dev/null +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.ts @@ -0,0 +1,22 @@ +import { Tree } from '@nx/devkit'; +import { wrapAngularDevkitSchematic } from '@nx/devkit/ngcli-adapter'; +import { ChangeThemeGeneratorSchema } from './schema'; +import { ThemeOptionsEnum } from './theme-options.enum'; + +export async function changeThemeGenerator(host: Tree, schema: ChangeThemeGeneratorSchema) { + const runAngularLibrarySchematic = wrapAngularDevkitSchematic( + '@abp/ng.schematics', + 'change-theme', + ); + + await runAngularLibrarySchematic(host, { + ...schema, + }); + + return () => { + const destTheme = Object.values(ThemeOptionsEnum).find((t, i) => i + 1 === schema.name); + console.log(`✅️ theme changed to ${destTheme}`); + }; +} + +export default changeThemeGenerator; diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/index.ts b/npm/ng-packs/packages/generators/src/generators/change-theme/index.ts new file mode 100644 index 0000000000..05ffcdcc9b --- /dev/null +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/index.ts @@ -0,0 +1,3 @@ +export * from './theme-options.enum'; +export * from './schema'; +export * from './generator'; diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/schema.d.ts b/npm/ng-packs/packages/generators/src/generators/change-theme/schema.d.ts new file mode 100644 index 0000000000..9ee931564e --- /dev/null +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/schema.d.ts @@ -0,0 +1,4 @@ +export interface ChangeThemeGeneratorSchema { + name: number; + targetOption: string; +} diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/schema.json b/npm/ng-packs/packages/generators/src/generators/change-theme/schema.json new file mode 100644 index 0000000000..fe356e00ae --- /dev/null +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "SchematicsABPThemeChanger", + "title": "ABP Theme Style Generator API Schema", + "type": "object", + "properties": { + "name": { + "description": "The name of theme will change.", + "type": "number", + "$default": { + "$source": "argv", + "index": 0 + }, + "enum": [1, 2, 3, 4], + "x-prompt": { + "message": "Which theme would you like to use?", + "type": "list", + "items": [ + { "value": 1, "label": "Basic" }, + { "value": 2, "label": "Lepton" }, + { "value": 3, "label": "LeptonXLite" }, + { "value": 4, "label": "LeptonX" } + ] + } + }, + "targetProject": { + "description": "The name of the project will change the style.The project type must be 'application'", + "type": "string", + "x-prompt": "Please enter the project name", + "$default": { + "$source": "argv", + "index": 1 + } + } + }, + "required": ["name", "targetProject"] +} diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/theme-options.enum.ts b/npm/ng-packs/packages/generators/src/generators/change-theme/theme-options.enum.ts new file mode 100644 index 0000000000..a64487ed8f --- /dev/null +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/theme-options.enum.ts @@ -0,0 +1,8 @@ +// this enum create by https://raw.githubusercontent.com/abpframework/abp/rel-7.4/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Theme.cs + +export enum ThemeOptionsEnum { + Basic = 1, + Lepton = 2, + LeptonXLite = 3, + LeptonX = 4, +} diff --git a/npm/ng-packs/packages/generators/src/index.ts b/npm/ng-packs/packages/generators/src/index.ts index 9a6fb02c00..a2ce5894d3 100644 --- a/npm/ng-packs/packages/generators/src/index.ts +++ b/npm/ng-packs/packages/generators/src/index.ts @@ -1 +1,3 @@ -export * from './generators/generate-proxy' \ No newline at end of file +export * from './generators/change-theme'; +export * from './generators/generate-proxy'; +export * from './generators/update-version'; From 9000840a8f532a909fa8a2fd0e9a92bc9e18053f Mon Sep 17 00:00:00 2001 From: Masum ULU Date: Wed, 20 Sep 2023 00:26:05 +0300 Subject: [PATCH 2/5] feat: remove current and apply selected theme --- .../src/generators/change-theme/generator.ts | 10 +- .../src/generators/change-theme/schema.d.ts | 1 + .../src/generators/change-theme/schema.json | 4 + .../src/commands/change-theme/index.ts | 161 +++++++++++++++++- .../src/commands/change-theme/style-map.ts | 51 ++++++ 5 files changed, 216 insertions(+), 11 deletions(-) diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/generator.ts b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.ts index 14d2e8e68e..d912d11e87 100644 --- a/npm/ng-packs/packages/generators/src/generators/change-theme/generator.ts +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.ts @@ -4,8 +4,10 @@ import { ChangeThemeGeneratorSchema } from './schema'; import { ThemeOptionsEnum } from './theme-options.enum'; export async function changeThemeGenerator(host: Tree, schema: ChangeThemeGeneratorSchema) { + const schematicPath = schema.localPath || '@abp/ng.schematics'; + const runAngularLibrarySchematic = wrapAngularDevkitSchematic( - '@abp/ng.schematics', + schema.localPath ? `${host.root}${schematicPath}` : schematicPath, 'change-theme', ); @@ -14,8 +16,10 @@ export async function changeThemeGenerator(host: Tree, schema: ChangeThemeGenera }); return () => { - const destTheme = Object.values(ThemeOptionsEnum).find((t, i) => i + 1 === schema.name); - console.log(`✅️ theme changed to ${destTheme}`); + const destTheme = Object.values(ThemeOptionsEnum).find( + (theme, index) => index + 1 === schema.name, + ); + console.log(`✅️ Switched to Theme ${destTheme}`); }; } diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/schema.d.ts b/npm/ng-packs/packages/generators/src/generators/change-theme/schema.d.ts index 9ee931564e..50caed37f0 100644 --- a/npm/ng-packs/packages/generators/src/generators/change-theme/schema.d.ts +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/schema.d.ts @@ -1,4 +1,5 @@ export interface ChangeThemeGeneratorSchema { name: number; targetOption: string; + localPath?: string; } diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/schema.json b/npm/ng-packs/packages/generators/src/generators/change-theme/schema.json index fe356e00ae..e09d479f1b 100644 --- a/npm/ng-packs/packages/generators/src/generators/change-theme/schema.json +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/schema.json @@ -31,6 +31,10 @@ "$source": "argv", "index": 1 } + }, + "localPath": { + "description": "If set value schematics will work on given path", + "type": "string" } }, "required": ["name", "targetProject"] 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 959a3128db..438d6ea26a 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,41 @@ -import { Rule, SchematicsException } from '@angular-devkit/schematics'; -import { isLibrary, updateWorkspace, WorkspaceDefinition } from '../../utils'; -import { allStyles, styleMap } from './style-map'; -import { ProjectDefinition } from '@angular-devkit/core/src/workspace'; import { JsonArray, JsonValue } from '@angular-devkit/core'; +import { Rule, SchematicsException, Tree, UpdateRecorder, 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 { ChangeThemeOptions } from './model'; +import { + addImportToModule, + Change, + InsertChange, + isLibrary, + updateWorkspace, + WorkspaceDefinition, +} from '../../utils'; import { ThemeOptionsEnum } from './theme-options.enum'; +import { + //@ts-ignore + findNodes, + getDecoratorMetadata, + getMetadataField, +} from '../../utils/angular/ast-utils'; export default function (_options: ChangeThemeOptions): Rule { - return async () => { + return async (host: Tree) => { const targetThemeName = _options.name; const selectedProject = _options.targetProject; if (!targetThemeName) { throw new SchematicsException('The theme name does not selected'); } - return updateWorkspace(storedWorkspace => { - updateProjectStyle(selectedProject, storedWorkspace, targetThemeName); - }); + return chain([ + updateWorkspace(storedWorkspace => { + updateProjectStyle(selectedProject, storedWorkspace, targetThemeName); + }), + updateWorkspace(storedWorkspace => { + updateAppModule(host, selectedProject, storedWorkspace, targetThemeName); + }), + ]); }; } @@ -48,6 +67,132 @@ 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); + + const impMap = Array.from(importMap.values()) + ?.filter(f => f !== importMap.get(targetThemeName)) + .reduce((acc, val) => [...acc, ...val], []); + + removeImportPath(source, recorder, impMap); + removeImportFromNgModuleMetadata(source, recorder, impMap); + + insertImports(selectedTheme, source, appModulePath, recorder); + + 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 removeImportPath( + source: ts.SourceFile, + recorder: UpdateRecorder, + arr: ImportDefinition[], +) { + const node = findNodes(source, ts.isImportDeclaration); + for (const importMp of arr) { + const importPath = node.find(f => f.getFullText().match(importMp.path)); + + if (!importPath) { + continue; + } + + /** + * We can add comment and sign as `removed` for the see what is removed + * + * recorder.insertLeft(importPath.getStart(), '//'); + */ + recorder.remove(importPath.getStart(), importPath.getWidth() + 1); + } +} + +function removeImportFromNgModuleMetadata( + source: ts.SourceFile, + recorder: UpdateRecorder, + arr: ImportDefinition[], +) { + /** + * Brings the @NgModule({...}) content + */ + const node = getDecoratorMetadata(source, 'NgModule', '@angular/core')[0] || {}; + if (!node) { + throw new SchematicsException('The app module does not found'); + } + + /** + * Select imports array in the @NgModule({...}) content + */ + const matchingProperties = getMetadataField(node as ts.ObjectLiteralExpression, 'imports'); + const assignment = matchingProperties[0] as ts.PropertyAssignment; + const assignmentInit = assignment.initializer as ts.ArrayLiteralExpression; + const elements = assignmentInit.elements; + + if (elements?.length < 0) { + return; + } + + for (const importMp of arr) { + const willRemoveModule = elements.find(f => f.getText().includes(importMp.importName)); + + if (!willRemoveModule) { + continue; + } + + /** + * We can add comment and sign as `removed` for the see what is removed + * + * recorder.insertLeft(foundModule.getStart(), '//'); + */ + recorder.remove(willRemoveModule.getStart(), willRemoveModule.getWidth() + 1); + } +} + export function getProjectTargetOptions( project: ProjectDefinition, buildTarget: string, diff --git a/npm/ng-packs/packages/schematics/src/commands/change-theme/style-map.ts b/npm/ng-packs/packages/schematics/src/commands/change-theme/style-map.ts index 91ef14a673..99b018703b 100644 --- a/npm/ng-packs/packages/schematics/src/commands/change-theme/style-map.ts +++ b/npm/ng-packs/packages/schematics/src/commands/change-theme/style-map.ts @@ -8,6 +8,11 @@ export type StyleDefinition = } | string; +export type ImportDefinition = { + path: string; + importName: string; +}; + export const styleMap = new Map(); styleMap.set(ThemeOptionsEnum.Basic, [ @@ -241,3 +246,49 @@ styleMap.set(ThemeOptionsEnum.LeptonXLite, [ ]); // the code written by Github co-pilot. thank go-pilot. You are the best sidekick. export const allStyles = Array.from(styleMap.values()).reduce((acc, val) => [...acc, ...val], []); + +export const importMap = new Map(); + +importMap.set(ThemeOptionsEnum.Basic, [ + { + path: '@abp/ng.theme.basic', + importName: 'ThemeBasicModule.forRoot()', + }, +]); + +importMap.set(ThemeOptionsEnum.Lepton, [ + { + path: '@volo/abp.ng.theme.lepton', + importName: 'ThemeLeptonModule.forRoot()', + }, +]); + +importMap.set(ThemeOptionsEnum.LeptonXLite, [ + { + path: '@abp/ng.theme.lepton-x', + importName: 'ThemeLeptonXModule.forRoot()', + }, + { + path: '@abp/ng.theme.lepton-x/layouts', + importName: 'SideMenuLayoutModule.forRoot()', + }, + { + path: '@abp/ng.theme.lepton-x/account', + importName: 'AccountLayoutModule.forRoot()', + }, +]); + +importMap.set(ThemeOptionsEnum.LeptonX, [ + { + path: '@volosoft/abp.ng.theme.lepton-x', + importName: 'ThemeLeptonXModule.forRoot()', + }, + { + path: '@volosoft/abp.ng.theme.lepton-x/layouts', + importName: 'SideMenuLayoutModule.forRoot()', + }, + { + path: '@volosoft/abp.ng.theme.lepton-x/account', + importName: 'AccountLayoutModule.forRoot()', + }, +]); From eacfa3a02f39e666abddce9da46ebb0a2888c200 Mon Sep 17 00:00:00 2001 From: Masum ULU Date: Wed, 20 Sep 2023 14:33:16 +0300 Subject: [PATCH 3/5] refactor: prev import removing --- .../src/commands/change-theme/index.ts | 83 +++++++++---------- 1 file changed, 41 insertions(+), 42 deletions(-) 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 438d6ea26a..c755199360 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 @@ -13,12 +13,7 @@ import { WorkspaceDefinition, } from '../../utils'; import { ThemeOptionsEnum } from './theme-options.enum'; -import { - //@ts-ignore - findNodes, - getDecoratorMetadata, - getMetadataField, -} from '../../utils/angular/ast-utils'; +import { findNodes, getDecoratorMetadata, getMetadataField } from '../../utils/angular/ast-utils'; export default function (_options: ChangeThemeOptions): Rule { return async (host: Tree) => { @@ -110,46 +105,26 @@ function updateAppModule( 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 removeImportPath( source: ts.SourceFile, recorder: UpdateRecorder, arr: ImportDefinition[], ) { - const node = findNodes(source, ts.isImportDeclaration); - for (const importMp of arr) { - const importPath = node.find(f => f.getFullText().match(importMp.path)); + const nodes = findNodes(source, ts.isImportDeclaration); + const filteredNodes = nodes.filter(n => arr.some(f => n.getFullText().match(f.path))); - if (!importPath) { - continue; - } + if (!filteredNodes || filteredNodes.length < 1) { + return; + } + filteredNodes.map(importPath => { /** * We can add comment and sign as `removed` for the see what is removed * * recorder.insertLeft(importPath.getStart(), '//'); */ recorder.remove(importPath.getStart(), importPath.getWidth() + 1); - } + }); } function removeImportFromNgModuleMetadata( @@ -169,27 +144,51 @@ function removeImportFromNgModuleMetadata( * Select imports array in the @NgModule({...}) content */ const matchingProperties = getMetadataField(node as ts.ObjectLiteralExpression, 'imports'); + const assignment = matchingProperties[0] as ts.PropertyAssignment; const assignmentInit = assignment.initializer as ts.ArrayLiteralExpression; - const elements = assignmentInit.elements; - if (elements?.length < 0) { + const elements = assignmentInit.elements; + if (!elements || elements.length < 1) { return; } - for (const importMp of arr) { - const willRemoveModule = elements.find(f => f.getText().includes(importMp.importName)); - - if (!willRemoveModule) { - continue; - } + const filteredElements = elements.filter(f => arr.some(s => f.getText().match(s.importName))); + if (!filteredElements || filteredElements.length < 1) { + return; + } + filteredElements.map(willRemoveModule => { /** * We can add comment and sign as `removed` for the see what is removed * - * recorder.insertLeft(foundModule.getStart(), '//'); + * recorder.insertLeft(willRemoveModule.getStart(), '//'); */ recorder.remove(willRemoveModule.getStart(), willRemoveModule.getWidth() + 1); + }); +} + +/** + * Insert import paths & import names to the NgModule decorator + */ +function insertImports( + selectedTheme: ImportDefinition[], + source: ts.SourceFile, + appModulePath: string, + recorder: UpdateRecorder, +) { + const changes: Change[] = []; + selectedTheme.map(({ importName, path }) => { + const importedModule = addImportToModule(source, appModulePath, importName, path); + changes.push(...importedModule); + }); + + if (changes.length > 0) { + for (const change of changes) { + if (change instanceof InsertChange) { + recorder.insertLeft(change.pos, change.toAdd); + } + } } } From b02cf638bf14c8a239f9e17b10233c34f1cd8a4e Mon Sep 17 00:00:00 2001 From: Masum ULU Date: Thu, 21 Sep 2023 10:15:18 +0300 Subject: [PATCH 4/5] fix: prevent to add comma before module name --- .../src/commands/change-theme/index.ts | 40 +++++-------------- .../schematics/src/utils/angular/ast-utils.ts | 4 +- 2 files changed, 12 insertions(+), 32 deletions(-) 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 c755199360..ac9e420046 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 @@ -93,7 +93,7 @@ function updateAppModule( const recorder = host.beginUpdate(appModulePath); const impMap = Array.from(importMap.values()) - ?.filter(f => f !== importMap.get(targetThemeName)) + .filter(f => f !== importMap.get(targetThemeName)) .reduce((acc, val) => [...acc, ...val], []); removeImportPath(source, recorder, impMap); @@ -117,14 +117,9 @@ function removeImportPath( return; } - filteredNodes.map(importPath => { - /** - * We can add comment and sign as `removed` for the see what is removed - * - * recorder.insertLeft(importPath.getStart(), '//'); - */ - recorder.remove(importPath.getStart(), importPath.getWidth() + 1); - }); + filteredNodes.map(importPath => + recorder.remove(importPath.getStart(), importPath.getWidth() + 1), + ); } function removeImportFromNgModuleMetadata( @@ -132,17 +127,11 @@ function removeImportFromNgModuleMetadata( recorder: UpdateRecorder, arr: ImportDefinition[], ) { - /** - * Brings the @NgModule({...}) content - */ const node = getDecoratorMetadata(source, 'NgModule', '@angular/core')[0] || {}; if (!node) { throw new SchematicsException('The app module does not found'); } - /** - * Select imports array in the @NgModule({...}) content - */ const matchingProperties = getMetadataField(node as ts.ObjectLiteralExpression, 'imports'); const assignment = matchingProperties[0] as ts.PropertyAssignment; @@ -158,19 +147,11 @@ function removeImportFromNgModuleMetadata( return; } - filteredElements.map(willRemoveModule => { - /** - * We can add comment and sign as `removed` for the see what is removed - * - * recorder.insertLeft(willRemoveModule.getStart(), '//'); - */ - recorder.remove(willRemoveModule.getStart(), willRemoveModule.getWidth() + 1); - }); + filteredElements.map(willRemoveModule => + recorder.remove(willRemoveModule.getStart(), willRemoveModule.getWidth() + 1), + ); } -/** - * Insert import paths & import names to the NgModule decorator - */ function insertImports( selectedTheme: ImportDefinition[], source: ts.SourceFile, @@ -178,10 +159,9 @@ function insertImports( recorder: UpdateRecorder, ) { const changes: Change[] = []; - selectedTheme.map(({ importName, path }) => { - const importedModule = addImportToModule(source, appModulePath, importName, path); - changes.push(...importedModule); - }); + selectedTheme.map(({ importName, path }) => + changes.push(...addImportToModule(source, appModulePath, importName, path)), + ); if (changes.length > 0) { for (const change of changes) { 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 bda343f07b..da5085b1b7 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,9 +441,9 @@ 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},`; } } From 3c232259d86feb25fa79597f53b7a3a4b4e7ae1f Mon Sep 17 00:00:00 2001 From: Masum ULU Date: Thu, 21 Sep 2023 11:12:56 +0300 Subject: [PATCH 5/5] 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),