2019-09-08 14:36:30 -04:00

214 lines
5.9 KiB
TypeScript

import * as ts from 'typescript';
import { join, Path, strings } from '@angular-devkit/core';
import '@nrwl/tao/src/compat/compat';
import {
addDepsToPackageJson,
addGlobal,
getProjectConfig,
insert,
readJsonInTree
} from '@nrwl/workspace/src/utils/ast-utils';
import {
apply,
chain,
mergeWith,
move,
noop,
Rule,
SchematicContext,
template,
Tree,
url
} from '@angular-devkit/schematics';
import { NormalizedSchema, Schema } from './schema';
import { formatFiles, getWorkspace, names, toFileName } from '@nrwl/workspace';
import * as path from 'path';
import { addReduxStoreToMain, updateReduxStore } from '../../utils/ast-utils';
import {
reactReduxTypesVersion,
reactReduxVersion,
reduxStarterKitversion,
reselectVersion
} from '../../utils/versions';
export default function(schema: any): Rule {
return async (host: Tree, context: SchematicContext) => {
const options = await normalizeOptions(host, schema);
return chain([
generateReduxFiles(options),
addExportsToBarrel(options),
addReduxPackageDependencies,
addStoreConfiguration(options, context),
updateReducerConfiguration(options, context),
formatFiles()
]);
};
}
function generateReduxFiles(options: NormalizedSchema) {
const templateSource = apply(url('./files'), [
template({ ...options, tmpl: '' }),
move(options.filesPath)
]);
return mergeWith(templateSource);
}
function addReduxPackageDependencies(): Rule {
return addDepsToPackageJson(
{
'redux-starter-kit': reduxStarterKitversion,
'react-redux': reactReduxVersion,
'@types/react-redux': reactReduxTypesVersion,
reselect: reselectVersion
},
{}
);
}
function addExportsToBarrel(options: NormalizedSchema): Rule {
return (host: Tree) => {
const indexFilePath = path.join(options.projectSourcePath, 'index.ts');
const buffer = host.read(indexFilePath);
if (!!buffer) {
const indexSource = buffer.toString('utf-8');
const indexSourceFile = ts.createSourceFile(
indexFilePath,
indexSource,
ts.ScriptTarget.Latest,
true
);
const statePath = options.directory
? `./lib/${options.directory}/${options.fileName}`
: `./lib/${options.fileName}`;
insert(host, indexFilePath, [
...addGlobal(
indexSourceFile,
indexFilePath,
`export * from '${statePath}.slice';`
)
]);
}
return host;
};
}
function addStoreConfiguration(
options: NormalizedSchema,
context: SchematicContext
) {
return options.appProjectSourcePath
? (host: Tree) => {
const mainSource = host.read(options.appMainFilePath).toString();
if (!mainSource.includes('redux')) {
const mainSourceFile = ts.createSourceFile(
options.appMainFilePath,
mainSource,
ts.ScriptTarget.Latest,
true
);
insert(
host,
options.appMainFilePath,
addReduxStoreToMain(
options.appMainFilePath,
mainSourceFile,
context
)
);
}
return host;
}
: noop();
}
function updateReducerConfiguration(
options: NormalizedSchema,
context: SchematicContext
) {
return options.appProjectSourcePath
? (host: Tree) => {
const mainSource = host.read(options.appMainFilePath).toString();
const mainSourceFile = ts.createSourceFile(
options.appMainFilePath,
mainSource,
ts.ScriptTarget.Latest,
true
);
insert(
host,
options.appMainFilePath,
updateReduxStore(options.appMainFilePath, mainSourceFile, context, {
keyName: `${options.constantName}_FEATURE_KEY`,
reducerName: `${options.propertyName}Reducer`,
modulePath: `${options.projectModulePath}`
})
);
return host;
}
: noop();
}
async function normalizeOptions(
host: Tree,
options: Schema
): Promise<NormalizedSchema> {
let appProjectSourcePath: Path;
let appMainFilePath: string;
const extraNames = names(options.name);
const { sourceRoot } = getProjectConfig(host, options.project);
const workspace = await getWorkspace(host);
const projectType = workspace.projects.get(options.project).extensions
.projectType as string;
const tsConfigJson = readJsonInTree(host, 'tsconfig.json');
const tsPaths: { [module: string]: string[] } = tsConfigJson.compilerOptions
? tsConfigJson.compilerOptions.paths || {}
: {};
const modulePath =
projectType === 'application'
? `./app/${extraNames.fileName}.slice`
: Object.keys(tsPaths).find(k =>
tsPaths[k].some(s => s.includes(sourceRoot))
);
// If --project is set to an app, automatically configure store
// for it without needing to specify --appProject.
options.appProject =
options.appProject ||
(projectType === 'application' ? options.project : undefined);
if (options.appProject) {
const appConfig = getProjectConfig(host, options.appProject);
if (appConfig.projectType !== 'application') {
throw new Error(
`Expected ${options.appProject} to be an application but got ${
appConfig.projectType
}`
);
}
appProjectSourcePath = appConfig.sourceRoot;
appMainFilePath = path.join(appProjectSourcePath, 'main.tsx');
if (!host.exists(appMainFilePath)) {
throw new Error(
`Could not find ${appMainFilePath} during store configuration`
);
}
}
return {
...options,
...extraNames,
constantName: strings.underscore(options.name).toUpperCase(),
directory: toFileName(options.directory),
projectType,
projectSourcePath: sourceRoot,
projectModulePath: modulePath,
appProjectSourcePath,
appMainFilePath,
filesPath: join(sourceRoot, projectType === 'application' ? 'app' : 'lib')
};
}