ohayolibs
Version:
Ohayo is a set of essential modules for ohayojp.
219 lines (191 loc) • 7.65 kB
text/typescript
import { strings } from '@angular-devkit/core';
import {
apply,
branchAndMerge,
chain,
filter,
mergeWith,
move,
noop,
Rule,
SchematicContext,
SchematicsException,
template,
Tree,
url,
} from '@angular-devkit/schematics';
import { findNode, insertImport } from '@schematics/angular/utility/ast-utils';
import { InsertChange } from '@schematics/angular/utility/change';
import { buildRelativePath, findModuleFromOptions } from '@schematics/angular/utility/find-module';
import { parseName } from '@schematics/angular/utility/parse-name';
import { validateHtmlSelector, validateName } from '@schematics/angular/utility/validation';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import { getSourceFile } from './ast';
import { getProject, Project } from './project';
export interface CommonSchema {
[key: string]: any;
_filesPath?: string;
schematicName?: string;
name?: string;
path?: string;
module?: string;
target?: string;
componentName?: string;
importModulePath?: string;
routerModulePath?: string;
selector?: string;
prefix?: string;
withoutPrefix?: boolean;
}
function buildSelector(schema: CommonSchema, projectPrefix: string): string {
const ret: string[] = [];
if (!schema.withoutPrefix) {
if (schema.prefix) {
ret.push(schema.prefix);
} else if (schema.prefix === undefined && projectPrefix) {
ret.push(projectPrefix);
}
}
// module name
if (schema.module) {
ret.push(schema.module);
}
// target name
if (schema.target) {
ret.push(...schema.target.split('/'));
}
// name
ret.push(strings.dasherize(schema.name!));
return ret.join('-');
}
function buildComponentName(schema: CommonSchema, _projectPrefix: string): string {
const ret: string[] = [schema.module!];
if (schema.target && schema.target.length > 0) {
ret.push(...schema.target.split('/'));
}
ret.push(schema.name!);
ret.push(`Component`);
return strings.classify(ret.join('-'));
}
function resolveSchema(host: Tree, project: Project, schema: CommonSchema): void {
// module name
if (!schema.module) {
throw new SchematicsException(`Must specify module name. (e.g: ng g ohayojp:list <list name> -m=<module name>)`);
}
// path
if (schema.path === undefined) {
const projectDirName = project.projectType === 'application' ? 'app' : 'lib';
schema.path = `/${project.sourceRoot}/${projectDirName}/routes`;
}
schema.path += `/${schema.module}`;
const parsedPath = parseName(schema.path, schema.name!);
schema.name = parsedPath.name;
schema.path = parsedPath.path;
const fullPath = path.join(process.cwd(), schema.path, schema.name);
if (fs.existsSync(fullPath)) {
throw new SchematicsException(`The directory (${fullPath}) already exists`);
}
schema.importModulePath = findModuleFromOptions(host, schema as any);
if (!schema._filesPath) {
// 若基础页尝试从 `_cli-tpl/_${schema.schematicName!}` 下查找该目录,若存在则优先使用
if (['list', 'edit', 'view', 'empty'].includes(schema.schematicName!)) {
const overrideDir = '/' + [project.root, `_cli-tpl/_${schema.schematicName}`].filter(i => !!i).join('/');
const overridePath = `${overrideDir}/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts`;
if (host.exists(overridePath)) {
// 所在目录与命令目录同属一个目录结构,因此无须特殊处理
schema._filesPath = path.relative(__dirname, process.cwd()) + overrideDir;
}
}
schema._filesPath = schema._filesPath || './files';
}
// fill target
if (schema.target) {
schema.path += '/' + schema.target;
}
schema.routerModulePath = schema.importModulePath!.replace('.module.ts', '-routing.module.ts');
// html selector
schema.selector = schema.selector || buildSelector(schema, (project as any).prefix);
validateName(schema.name);
validateHtmlSelector(schema.selector);
}
function addImportToModule(host: Tree, filePath: string, symbolName: string, fileName: string): void {
const source = getSourceFile(host, filePath);
const change = insertImport(source, filePath, symbolName, fileName) as InsertChange;
const declarationRecorder = host.beginUpdate(filePath);
declarationRecorder.insertLeft(change.pos, change.toAdd);
host.commitUpdate(declarationRecorder);
}
export function addValueToVariable(host: Tree, filePath: string, variableName: string, text: string, needWrap: boolean = true): void {
const source = getSourceFile(host, filePath);
const node = findNode(source, ts.SyntaxKind.Identifier, variableName);
if (!node) {
throw new SchematicsException(`Could not find any [${variableName}] variable in path '${filePath}'.`);
}
const arr = (node.parent as any).initializer as ts.ArrayLiteralExpression;
const change = new InsertChange(
filePath,
arr.end - 1,
`${arr.elements && arr.elements.length > 0 ? ',' : ''}${needWrap ? '\n ' : ''}${text}`,
);
const declarationRecorder = host.beginUpdate(filePath);
declarationRecorder.insertLeft(change.pos, change.toAdd);
host.commitUpdate(declarationRecorder);
}
function getRelativePath(filePath: string, schema: CommonSchema): string {
const importPath = `/${schema.path}/${schema.flat ? '' : strings.dasherize(schema.name!) + '/'}${strings.dasherize(
schema.name!,
)}.component`;
return buildRelativePath(filePath, importPath);
}
function addDeclaration(schema: CommonSchema): (host: Tree) => Tree {
return (host: Tree) => {
if (schema.skipImport || !schema.module) {
return host;
}
// imports
addImportToModule(host, schema.importModulePath!, schema.componentName!, getRelativePath(schema.importModulePath!, schema));
// component
if (schema.modal === true) {
addValueToVariable(host, schema.importModulePath!, 'COMPONENTS_NOROUNT', schema.componentName!);
} else {
addValueToVariable(host, schema.importModulePath!, 'COMPONENTS', schema.componentName!);
// routing
addImportToModule(host, schema.routerModulePath!, schema.componentName!, getRelativePath(schema.routerModulePath!, schema));
addValueToVariable(host, schema.routerModulePath!, 'routes', `{ path: '${schema.name}', component: ${schema.componentName} }`);
}
return host;
};
}
export function buildOhayo(schema: CommonSchema): Rule {
return (host: Tree, context: SchematicContext) => {
const project = getProject(host, schema.project);
resolveSchema(host, project, schema);
schema.componentName = buildComponentName(schema, (project as any).prefix);
// Don't support inline
schema.inlineTemplate = false;
const templateSource = apply(url(schema._filesPath!), [
filter(filePath => !filePath.endsWith('.DS_Store')),
schema.spec ? noop() : filter(filePath => !filePath.endsWith('.spec.ts')),
schema.inlineStyle ? filter(filePath => !filePath.endsWith('.__styleext__')) : noop(),
schema.inlineTemplate ? filter(filePath => !filePath.endsWith('.html')) : noop(),
template({
...strings,
'if-flat': (s: string) => (schema.flat ? '' : s),
...schema,
}),
move(null!, schema.path + '/'),
]);
return chain([branchAndMerge(chain([addDeclaration(schema), mergeWith(templateSource)]))])(host, context);
};
}
export function tryDelFile(host: Tree, filePath: string): void {
if (host.exists(filePath)) {
host.delete(filePath);
}
}
export function tryAddFile(host: Tree, filePath: string, content: string): void {
tryDelFile(host, filePath);
host.create(filePath, content);
}