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/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..d912d11e87 --- /dev/null +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.ts @@ -0,0 +1,26 @@ +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 schematicPath = schema.localPath || '@abp/ng.schematics'; + + const runAngularLibrarySchematic = wrapAngularDevkitSchematic( + schema.localPath ? `${host.root}${schematicPath}` : schematicPath, + 'change-theme', + ); + + await runAngularLibrarySchematic(host, { + ...schema, + }); + + return () => { + const destTheme = Object.values(ThemeOptionsEnum).find( + (theme, index) => index + 1 === schema.name, + ); + console.log(`✅️ Switched to Theme ${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..50caed37f0 --- /dev/null +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/schema.d.ts @@ -0,0 +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 new file mode 100644 index 0000000000..e09d479f1b --- /dev/null +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/schema.json @@ -0,0 +1,41 @@ +{ + "$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 + } + }, + "localPath": { + "description": "If set value schematics will work on given path", + "type": "string" + } + }, + "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'; 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..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,10 +1,17 @@ -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, chain } from '@angular-devkit/schematics'; +import { ProjectDefinition } from '@angular-devkit/core/src/workspace'; +import * as ts from 'typescript'; +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, + findNodes, + getDecoratorMetadata, + getMetadataField, +} from '../../utils/angular/ast-utils'; export default function (_options: ChangeThemeOptions): Rule { return async () => { @@ -14,9 +21,12 @@ export default function (_options: ChangeThemeOptions): Rule { throw new SchematicsException('The theme name does not selected'); } - return updateWorkspace(storedWorkspace => { - updateProjectStyle(selectedProject, storedWorkspace, targetThemeName); - }); + return chain([ + updateWorkspace(storedWorkspace => { + updateProjectStyle(selectedProject, storedWorkspace, targetThemeName); + }), + updateAppModule(selectedProject, targetThemeName), + ]); }; } @@ -48,6 +58,138 @@ function updateProjectStyle( targetOption.styles = [...newStyles, ...sanitizedStyles] as JsonArray; } +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 workspace = JSON.parse(angularJSON.toString()); + const project = workspace.projects[selectedProject]; + + if (!project || !project.sourceRoot) { + throw new SchematicsException('The target project does not found'); + } + + const appModulePath = project.sourceRoot + '/app/app.module.ts'; + + return chain([ + removeImportPath(appModulePath, targetThemeName), + removeImportFromNgModuleMetadata(appModulePath, targetThemeName), + insertImports(appModulePath, targetThemeName), + ]); + }; +} + +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; + } + + filteredNodes.map(importPath => + recorder.remove(importPath.getStart(), importPath.getWidth() + 1), + ); + + host.commitUpdate(recorder); + return host; + }; +} + +function removeImportFromNgModuleMetadata( + 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 node = getDecoratorMetadata(source, 'NgModule', '@angular/core')[0] || {}; + if (!node) { + throw new SchematicsException('The app module does not found'); + } + + 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 || 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), + ); + host.commitUpdate(recorder); + return host; + }; +} + +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( 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()', + }, +]); 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..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 @@ -446,7 +446,6 @@ export function addSymbolToNgModuleMetadata( toInsert = `, ${symbolName}`; } } - if (importPath !== null) { return [ new InsertChange(ngModulePath, position, toInsert),