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