|
|
|
@ -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, |
|
|
|
|