feat(schematics): fail gracefully and warn when project is missing a format npm script

This commit is contained in:
Jason Jean 2018-06-01 11:07:38 -04:00 committed by Victor Savkin
parent e18f94e566
commit d2350c4e86
36 changed files with 481 additions and 346 deletions

View File

@ -75,7 +75,7 @@
"jest": {
"modulePathIgnorePatterns": [
"tmp",
"files"
"collection/.*/files"
]
}
}

View File

@ -24,7 +24,7 @@ import { addBootstrapToModule } from '@schematics/angular/utility/ast-utils';
import { insertImport } from '@schematics/angular/utility/route-utils';
import { addApp, readCliConfigFile } from '../../utils/fileutils';
import { offsetFromRoot } from '../../utils/common';
import { wrapIntoFormat } from '../../utils/tasks';
import { formatFiles } from '../../utils/rules/format-files';
interface NormalizedSchema extends Schema {
fullName: string;
@ -236,57 +236,56 @@ ts_web_test(
}
export default function(schema: Schema): Rule {
return wrapIntoFormat(() => {
let npmScope = schema.npmScope;
if (!npmScope) {
npmScope = readCliConfigFile().project.npmScope;
}
let npmScope = schema.npmScope;
if (!npmScope) {
npmScope = readCliConfigFile().project.npmScope;
}
const options = normalizeOptions(schema);
const templateSource = apply(url('./files'), [
template({
utils: strings,
dot: '.',
tmpl: '',
offsetFromRoot: offsetFromRoot(options.fullPath),
...(options as object),
npmScope
})
]);
const options = normalizeOptions(schema);
const templateSource = apply(url('./files'), [
template({
utils: strings,
dot: '.',
tmpl: '',
offsetFromRoot: offsetFromRoot(options.fullPath),
...(options as object),
npmScope
})
]);
const selector = `${options.prefix}-root`;
const selector = `${options.prefix}-root`;
return chain([
branchAndMerge(chain([mergeWith(templateSource)])),
externalSchematic('@schematics/angular', 'module', {
name: 'app',
commonModule: false,
flat: true,
routing: false,
sourceDir: options.fullPath,
spec: false
}),
externalSchematic('@schematics/angular', 'component', {
name: 'app',
selector: selector,
sourceDir: options.fullPath,
flat: true,
inlineStyle: options.inlineStyle,
inlineTemplate: options.inlineTemplate,
spec: !options.skipTests,
styleext: options.style,
viewEncapsulation: options.viewEncapsulation,
changeDetection: options.changeDetection
}),
updateComponentTemplate(options),
addBootstrap(options.fullPath),
addNxModule(options.fullPath),
addAppToAngularCliJson(options),
addBazelBuildFile(options.fullPath),
addAppToAngularCliJson(options),
options.routing ? addRouterRootConfiguration(options.fullPath) : noop()
]);
});
return chain([
branchAndMerge(chain([mergeWith(templateSource)])),
externalSchematic('@schematics/angular', 'module', {
name: 'app',
commonModule: false,
flat: true,
routing: false,
sourceDir: options.fullPath,
spec: false
}),
externalSchematic('@schematics/angular', 'component', {
name: 'app',
selector: selector,
sourceDir: options.fullPath,
flat: true,
inlineStyle: options.inlineStyle,
inlineTemplate: options.inlineTemplate,
spec: !options.skipTests,
styleext: options.style,
viewEncapsulation: options.viewEncapsulation,
changeDetection: options.changeDetection
}),
updateComponentTemplate(options),
addBootstrap(options.fullPath),
addNxModule(options.fullPath),
addAppToAngularCliJson(options),
addBazelBuildFile(options.fullPath),
addAppToAngularCliJson(options),
options.routing ? addRouterRootConfiguration(options.fullPath) : noop(),
formatFiles(options)
]);
}
function normalizeOptions(options: Schema): NormalizedSchema {

View File

@ -1,5 +1,6 @@
export interface Schema {
name: string;
skipFormat: boolean;
npmScope?: string;
directory?: string;
sourceDir?: string;

View File

@ -43,6 +43,11 @@
"type": "boolean",
"default": false
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"skipTests": {
"description": "Skip creating spec files.",
"type": "boolean",

View File

@ -29,8 +29,8 @@ import {
toFileName,
toPropertyName
} from '../../utils/name-utils';
import { wrapIntoFormat } from '../../utils/tasks';
import { Schema } from './schema';
import { formatFiles } from '../../utils/rules/format-files';
interface NormalizedSchema extends Schema {
name: string;
@ -279,12 +279,11 @@ function validateLibSchema(schema) {
}
export default function(schema: Schema): Rule {
return wrapIntoFormat(() => {
const { templateSource, routingRules } = validateLibSchema(schema);
const { templateSource, routingRules } = validateLibSchema(schema);
return chain([
branchAndMerge(chain([mergeWith(templateSource)])),
...routingRules
]);
});
return chain([
branchAndMerge(chain([mergeWith(templateSource)])),
...routingRules,
formatFiles(schema)
]);
}

View File

@ -1,5 +1,6 @@
export interface Schema {
name: string;
skipFormat: boolean;
directory?: string;
sourceDir?: string;
nomodule: boolean;

View File

@ -1,38 +1,43 @@
{
"$schema": "http://json-schema.org/schema",
"id": "library",
"title": "Create a library",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Library name"
},
"directory": {
"type": "string",
"description": "A directory where the app is placed"
},
"nomodule": {
"type": "boolean",
"default": false,
"description": "Generate a simple TS library when set to true."
},
"routing": {
"type": "boolean",
"default": false,
"description": "Add router configuration. See lazy for more information."
},
"lazy": {
"type": "boolean",
"default": false,
"description": "Add RouterModule.forChild when set to true, and a simple array of routes when set to false."
},
"parentModule": {
"type": "string",
"description": "Update the router configuration of the parent module using loadChildren or children, depending on what `lazy` is set to."
}
"$schema": "http://json-schema.org/schema",
"id": "library",
"title": "Create a library",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Library name"
},
"required": [
"name"
]
}
"directory": {
"type": "string",
"description": "A directory where the app is placed"
},
"nomodule": {
"type": "boolean",
"default": false,
"description": "Generate a simple TS library when set to true."
},
"routing": {
"type": "boolean",
"default": false,
"description": "Add router configuration. See lazy for more information."
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"lazy": {
"type": "boolean",
"default": false,
"description":
"Add RouterModule.forChild when set to true, and a simple array of routes when set to false."
},
"parentModule": {
"type": "string",
"description":
"Update the router configuration of the parent module using loadChildren or children, depending on what `lazy` is set to."
}
},
"required": ["name"]
}

View File

@ -17,46 +17,27 @@ import {
} from '@angular-devkit/schematics';
import { Schema } from './schema';
class FormatFiles implements TaskConfigurationGenerator<any> {
toConfiguration(): TaskConfiguration<any> {
return {
name: 'node-package',
options: {
command: 'run format',
quiet: true
}
};
}
}
function wrapIntoFormat(fn: Function): any {
return (host: Tree, context: SchematicContext) => {
context.addTask(new FormatFiles());
return fn(context)(host, context);
};
}
import { formatFiles } from '../../utils/rules/format-files';
export default function(schema: Schema): Rule {
return wrapIntoFormat(() => {
schema.path = schema.path ? normalize(schema.path) : schema.path;
const sourceDir = schema.sourceDir;
if (!sourceDir) {
throw new SchematicsException(`sourceDir option is required.`);
}
schema.path = schema.path ? normalize(schema.path) : schema.path;
const sourceDir = schema.sourceDir;
if (!sourceDir) {
throw new SchematicsException(`sourceDir option is required.`);
}
const templateSource = apply(url('./files'), [
template({
...strings,
'if-flat': (s: string) => (schema.flat ? '' : s),
...schema
}),
move(sourceDir)
]);
const templateSource = apply(url('./files'), [
template({
...strings,
'if-flat': (s: string) => (schema.flat ? '' : s),
...schema
}),
move(sourceDir)
]);
return chain([
branchAndMerge(chain([mergeWith(templateSource)])),
externalSchematic('@schematics/angular', 'module', schema)
]);
});
return chain([
branchAndMerge(chain([mergeWith(templateSource)])),
externalSchematic('@schematics/angular', 'module', schema),
formatFiles(schema)
]);
}

View File

@ -11,6 +11,11 @@ export interface Schema {
* The name of the module.
*/
name: string;
/**
* Skip formatting of files
*/
skipFormat: boolean;
/**
* The path to create the module.
*/

View File

@ -1,70 +1,70 @@
{
"$schema": "http://json-schema.org/schema",
"id": "module",
"title": "NX Module Options Schema",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the module."
},
"path": {
"type": "string",
"format": "path",
"description": "The path to create the module.",
"default": "app",
"visible": false
},
"sourceDir": {
"type": "string",
"format": "path",
"description": "The path of the source directory.",
"default": "src",
"visible": false
},
"appRoot": {
"type": "string",
"format": "path",
"description": "The root of the application.",
"visible": false
},
"routing": {
"type": "boolean",
"description": "Generates a routing module.",
"default": false
},
"routingScope": {
"enum": [
"Child",
"Root"
],
"type": "string",
"description": "The scope for the generated routing.",
"default": "Child"
},
"spec": {
"type": "boolean",
"description": "Specifies if a spec file is generated.",
"default": true
},
"flat": {
"type": "boolean",
"description": "Flag to indicate if a dir is created.",
"default": false
},
"commonModule": {
"type": "boolean",
"description": "Flag to control whether the CommonModule is imported.",
"default": true,
"visible": false
},
"module": {
"type": "string",
"description": "Allows specification of the declaring module.",
"alias": "m"
}
"$schema": "http://json-schema.org/schema",
"id": "module",
"title": "NX Module Options Schema",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the module."
},
"required": [
"name"
]
}
"path": {
"type": "string",
"format": "path",
"description": "The path to create the module.",
"default": "app",
"visible": false
},
"sourceDir": {
"type": "string",
"format": "path",
"description": "The path of the source directory.",
"default": "src",
"visible": false
},
"appRoot": {
"type": "string",
"format": "path",
"description": "The root of the application.",
"visible": false
},
"routing": {
"type": "boolean",
"description": "Generates a routing module.",
"default": false
},
"routingScope": {
"enum": ["Child", "Root"],
"type": "string",
"description": "The scope for the generated routing.",
"default": "Child"
},
"spec": {
"type": "boolean",
"description": "Specifies if a spec file is generated.",
"default": true
},
"flat": {
"type": "boolean",
"description": "Flag to indicate if a dir is created.",
"default": false
},
"commonModule": {
"type": "boolean",
"description": "Flag to control whether the CommonModule is imported.",
"default": true,
"visible": false
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"module": {
"type": "string",
"description": "Allows specification of the declaring module.",
"alias": "m"
}
},
"required": ["name"]
}

View File

@ -0,0 +1,40 @@
import { readJsonInTree } from '../ast-utils';
import {
TaskConfigurationGenerator,
TaskConfiguration,
Tree,
SchematicContext,
Rule,
noop
} from '@angular-devkit/schematics';
class FormatFiles implements TaskConfigurationGenerator<any> {
toConfiguration(): TaskConfiguration<any> {
return {
name: 'node-package',
options: {
packageName: 'run format -- --untracked', // workaround. we should define a custom task executor.
quiet: true
}
};
}
}
export function formatFiles(options: { skipFormat: boolean }): Rule {
if (options.skipFormat) {
return noop();
}
return (host: Tree, context: SchematicContext) => {
const packageJson = readJsonInTree(host, 'package.json');
if (packageJson.scripts && packageJson.scripts.format) {
context.addTask(new FormatFiles());
} else {
context.logger.warn(
'The "format" npm script is missing in your package.json'
);
context.logger.warn(
'Your files were not formated during this code generation'
);
}
};
}

View File

@ -1,25 +0,0 @@
import {
TaskConfigurationGenerator,
TaskConfiguration,
Tree,
SchematicContext
} from '@angular-devkit/schematics';
export class FormatFiles implements TaskConfigurationGenerator<any> {
toConfiguration(): TaskConfiguration<any> {
return {
name: 'node-package',
options: {
command: 'run format -- --untracked',
quiet: true
}
};
}
}
export function wrapIntoFormat(fn: Function): any {
return (host: Tree, context: SchematicContext) => {
context.addTask(new FormatFiles());
return fn(context)(host, context);
};
}

View File

@ -8,11 +8,11 @@ import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import { readJsonInTree, updateJsonInTree } from '../../src/utils/ast-utils';
import { FormatFiles } from '../../src/utils/tasks';
import { serializeJson, renameSync } from '../../src/utils/fileutils';
import { parseTarget, serializeTarget } from '../../src/utils/cli-config-utils';
import * as fs from 'fs';
import { offsetFromRoot } from '@nrwl/schematics/src/utils/common';
import { offsetFromRoot } from '../../src/utils/common';
import { formatFiles } from '../../src/utils/rules/format-files';
function createKarma(host: Tree, project: any) {
const offset = offsetFromRoot(project.root);
@ -648,9 +648,8 @@ const updateAngularJson = updateJsonInTree('angular.json', json => {
return json;
});
function addTasks(host: Tree, context: SchematicContext) {
function addInstallTask(host: Tree, context: SchematicContext) {
context.addTask(new NodePackageInstallTask());
context.addTask(new FormatFiles());
}
function checkCli6Upgraded(host: Tree) {
@ -688,6 +687,7 @@ export default function(): Rule {
createAdditionalFiles,
deleteUnneededFiles,
patchLibIndexFiles,
addTasks
addInstallTask,
formatFiles()
]);
}

View File

@ -4,7 +4,8 @@ import {
move,
noop,
Rule,
Tree
Tree,
SchematicContext
} from '@angular-devkit/schematics';
import { Schema } from './schema';
import * as ts from 'typescript';
@ -15,7 +16,6 @@ import {
insert,
updateJsonInTree
} from '../../utils/ast-utils';
import { wrapIntoFormat } from '../../utils/tasks';
import { toFileName } from '../../utils/name-utils';
import { offsetFromRoot } from '@nrwl/schematics/src/utils/common';
import {
@ -23,7 +23,7 @@ import {
getWorkspacePath,
replaceAppNameWithPath
} from '@nrwl/schematics/src/utils/cli-config-utils';
import { k } from '@angular/core/src/render3';
import { formatFiles } from '../../utils/rules/format-files';
interface NormalizedSchema extends Schema {
appProjectRoot: string;
@ -232,7 +232,7 @@ function updateE2eProject(options: NormalizedSchema): Rule {
}
export default function(schema: Schema): Rule {
return wrapIntoFormat((host: Tree) => {
return (host: Tree, context: SchematicContext) => {
const options = normalizeOptions(host, schema);
return chain([
externalSchematic('@schematics/angular', 'application', {
@ -248,9 +248,10 @@ export default function(schema: Schema): Rule {
updateComponentTemplate(options),
addNxModule(options),
options.routing ? addRouterRootConfiguration(options) : noop()
]);
});
options.routing ? addRouterRootConfiguration(options) : noop(),
formatFiles(options)
])(host, context);
};
}
function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {

View File

@ -1,5 +1,6 @@
export interface Schema {
name: string;
skipFormat: boolean;
inlineStyle?: boolean;
inlineTemplate?: boolean;
viewEncapsulation?: 'Emulated' | 'Native' | 'None';

View File

@ -55,6 +55,11 @@
"default": false,
"alias": "S"
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"skipPackageJson": {
"type": "boolean",
"default": false,

View File

@ -8,7 +8,7 @@ import {
} from '../../utils/ast-utils';
import { Schema } from './schema';
import { addUpgradeToPackageJson } from '../../utils/common';
import { wrapIntoFormat } from '../../utils/tasks';
import { formatFiles } from '../../utils/rules/format-files';
function updateMain(angularJsImport: string, options: Schema): Rule {
return (host: Tree) => {
@ -89,16 +89,15 @@ function addEntryComponentsToModule(options: Schema): Rule {
}
export default function(options: Schema): Rule {
return wrapIntoFormat(() => {
const angularJsImport = options.angularJsImport
? options.angularJsImport
: options.name;
const angularJsImport = options.angularJsImport
? options.angularJsImport
: options.name;
return chain([
updateMain(angularJsImport, options),
addEntryComponentsToModule(options),
rewriteBootstrapLogic(options),
options.skipPackageJson ? noop() : addUpgradeToPackageJson()
]);
});
return chain([
updateMain(angularJsImport, options),
addEntryComponentsToModule(options),
rewriteBootstrapLogic(options),
options.skipPackageJson ? noop() : addUpgradeToPackageJson(),
formatFiles(options)
]);
}

View File

@ -1,6 +1,7 @@
export interface Schema {
angularJsImport: string;
name: string;
skipFormat: boolean;
skipPackageJson: boolean;
project: string;
}

View File

@ -18,15 +18,20 @@
},
"angularJsImport": {
"type": "string",
"description": "Import expression of the AngularJS application (e.g., --angularJsImport=some_node_module/my_app)."
"description":
"Import expression of the AngularJS application (e.g., --angularJsImport=some_node_module/my_app)."
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"skipPackageJson": {
"type": "boolean",
"default": false,
"description": "Do not add @angular/upgrade to package.json (e.g., --skipPackageJson)"
"description":
"Do not add @angular/upgrade to package.json (e.g., --skipPackageJson)"
}
},
"required": [
"project"
]
"required": ["project"]
}

View File

@ -20,7 +20,6 @@ import {
updateJsonInTree
} from '../../utils/ast-utils';
import { offsetFromRoot } from '../../utils/common';
import { wrapIntoFormat } from '../../utils/tasks';
import {
toClassName,
toFileName,
@ -32,6 +31,7 @@ import {
replaceAppNameWithPath
} from '@nrwl/schematics/src/utils/cli-config-utils';
import * as fs from 'fs';
import { formatFiles } from '../../utils/rules/format-files';
interface NormalizedSchema extends Schema {
name: string;
@ -337,7 +337,7 @@ function updateTsConfig(options: NormalizedSchema): Rule {
}
export default function(schema: Schema): Rule {
return wrapIntoFormat((host: Tree) => {
return (host: Tree, context: SchematicContext) => {
const options = normalizeOptions(host, schema);
if (!options.routing && options.lazy) {
throw new Error(`routing must be set`);
@ -402,9 +402,10 @@ export default function(schema: Schema): Rule {
: noop(),
options.routing && !options.lazy && options.parentModule
? addChildren(options)
: noop()
]);
});
: noop(),
formatFiles(options)
])(host, context);
};
}
function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {

View File

@ -17,8 +17,6 @@ describe('lib', () => {
beforeEach(() => {
appTree = new VirtualTree();
appTree = createEmptyWorkspace(appTree);
schematicRunner.logger.subscribe(s => console.log(s));
});
describe('not nested', () => {

View File

@ -1,5 +1,6 @@
export interface Schema {
name: string;
skipFormat: boolean;
directory?: string;
sourceDir?: string;
publishable: boolean;

View File

@ -27,6 +27,11 @@
"description": "The prefix to apply to generated selectors.",
"alias": "p"
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"skipPackageJson": {
"type": "boolean",
"default": false,
@ -45,11 +50,13 @@
"lazy": {
"type": "boolean",
"default": false,
"description": "Add RouterModule.forChild when set to true, and a simple array of routes when set to false."
"description":
"Add RouterModule.forChild when set to true, and a simple array of routes when set to false."
},
"parentModule": {
"type": "string",
"description": "Update the router configuration of the parent module using loadChildren or children, depending on what `lazy` is set to."
"description":
"Update the router configuration of the parent module using loadChildren or children, depending on what `lazy` is set to."
},
"tags": {
"type": "string",

View File

@ -3,14 +3,14 @@ import {
externalSchematic,
move,
Rule,
Tree
Tree,
SchematicContext
} from '@angular-devkit/schematics';
import { Schema } from './schema';
import * as path from 'path';
import { names, toFileName } from '../../utils/name-utils';
import { wrapIntoFormat } from '../../utils/tasks';
import {
addImportsToModule,
@ -20,6 +20,7 @@ import {
updateNgrxEffects,
updateNgrxReducers
} from './rules';
import { formatFiles } from '../../utils/rules/format-files';
function effectsSpec(className: string, fileName: string) {
return `
@ -88,9 +89,9 @@ describe('${propertyName}Reducer', () => {
* Rule to generate the Nx 'ngrx' Collection
*/
export default function generateNgrxCollection(_options: Schema): Rule {
return wrapIntoFormat((host: Tree) => {
return (host: Tree, context: SchematicContext) => {
const options = normalizeOptions(_options);
const context: RequestContext = {
const requestContext: RequestContext = {
featureName: options.name,
moduleDir: path.dirname(options.module),
options,
@ -100,16 +101,16 @@ export default function generateNgrxCollection(_options: Schema): Rule {
const fileGeneration = options.onlyEmptyRoot
? []
: [
generateNgrxFiles(context),
generateNxFiles(context),
updateNgrxActions(context),
updateNgrxReducers(context),
updateNgrxEffects(context)
generateNgrxFiles(requestContext),
generateNxFiles(requestContext),
updateNgrxActions(requestContext),
updateNgrxReducers(requestContext),
updateNgrxEffects(requestContext)
];
const moduleModification = options.onlyAddFiles
? []
: [addImportsToModule(context)];
: [addImportsToModule(requestContext)];
const packageJsonModification = options.skipPackageJson
? []
: [addNgRxToPackageJson()];
@ -117,9 +118,10 @@ export default function generateNgrxCollection(_options: Schema): Rule {
return chain([
...fileGeneration,
...moduleModification,
...packageJsonModification
]);
});
...packageJsonModification,
formatFiles(options)
])(host, context);
};
}
// ********************************************************

View File

@ -2,6 +2,7 @@ export interface Schema {
name: string;
onlyEmptyRoot: boolean;
root: boolean;
skipFormat: boolean;
onlyAddFiles: boolean;
module: string;
skipPackageJson: boolean;

View File

@ -14,35 +14,44 @@
},
"module": {
"type": "string",
"description": "Path to ngModule; host directory will contain the new '+state' directory (e.g., src/libs/mylib/mylib.module.ts)."
"description":
"Path to ngModule; host directory will contain the new '+state' directory (e.g., src/libs/mylib/mylib.module.ts)."
},
"onlyAddFiles": {
"type": "boolean",
"default": false,
"description": "Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles)."
"description":
"Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles)."
},
"directory": {
"type": "string",
"default": "+state",
"description": "The directory name for the ngrx files: contains actions, effects, reducers. (e.g., +state)"
"description":
"The directory name for the ngrx files: contains actions, effects, reducers. (e.g., +state)"
},
"root": {
"type": "boolean",
"default": false,
"description": "Add StoreModule.forRoot and EffectsModule.forRoot instead of forFeature (e.g., --root)."
"description":
"Add StoreModule.forRoot and EffectsModule.forRoot instead of forFeature (e.g., --root)."
},
"onlyEmptyRoot": {
"type": "boolean",
"default": false,
"description": "Do not generate any files. Only generate StoreModule.forRoot and EffectsModule.forRoot (e.g., --onlyEmptyRoot)."
"description":
"Do not generate any files. Only generate StoreModule.forRoot and EffectsModule.forRoot (e.g., --onlyEmptyRoot)."
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"skipPackageJson": {
"type": "boolean",
"default": false,
"description": "Do not add ngrx dependencies to package.json (e.g., --skipPackageJson)"
"description":
"Do not add ngrx dependencies to package.json (e.g., --skipPackageJson)"
}
},
"required": [
"module"
]
"required": ["module"]
}

View File

@ -28,7 +28,7 @@ import {
import { insertImport } from '@schematics/angular/utility/route-utils';
import { Schema } from './schema';
import { addUpgradeToPackageJson } from '../../utils/common';
import { wrapIntoFormat } from '../../utils/tasks';
import { formatFiles } from '../../utils/rules/format-files';
function addImportsToModule(options: Schema): Rule {
return (host: Tree) => {
@ -125,16 +125,15 @@ function createFiles(angularJsImport: string, options: Schema): Rule {
}
export default function(options: Schema): Rule {
return wrapIntoFormat(() => {
const angularJsImport = options.angularJsImport
? options.angularJsImport
: options.name;
const angularJsImport = options.angularJsImport
? options.angularJsImport
: options.name;
return chain([
createFiles(angularJsImport, options),
addImportsToModule(options),
addNgDoBootstrapToModule(options),
options.skipPackageJson ? noop() : addUpgradeToPackageJson()
]);
});
return chain([
createFiles(angularJsImport, options),
addImportsToModule(options),
addNgDoBootstrapToModule(options),
options.skipPackageJson ? noop() : addUpgradeToPackageJson(),
formatFiles(options)
]);
}

View File

@ -3,6 +3,7 @@ export interface Schema {
angularJsImport: string;
angularJsCmpSelector: string;
name: string;
skipFormat: boolean;
skipPackageJson: boolean;
router: boolean;
}

View File

@ -18,16 +18,24 @@
},
"angularJsImport": {
"type": "string",
"description": "Import expression of the AngularJS application (e.g., --angularJsImport=some_node_module/my_app)."
"description":
"Import expression of the AngularJS application (e.g., --angularJsImport=some_node_module/my_app)."
},
"angularJsCmpSelector": {
"type": "string",
"description": "The selector of an AngularJS component (e.g., --angularJsCmpSelector=myComponent)"
"description":
"The selector of an AngularJS component (e.g., --angularJsCmpSelector=myComponent)"
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
},
"skipPackageJson": {
"type": "boolean",
"default": false,
"description": "Do not add @angular/upgrade to package.json (e.g., --skipPackageJson)"
"description":
"Do not add @angular/upgrade to package.json (e.g., --skipPackageJson)"
},
"router": {
"type": "boolean",
@ -35,7 +43,5 @@
"description": "Sets up router synchronization (e.g., --router)"
}
},
"required": [
"project"
]
"required": ["project"]
}

View File

@ -9,22 +9,23 @@ import {
move
} from '@angular-devkit/schematics';
import { Schema } from './schema';
import { wrapIntoFormat } from '@nrwl/schematics/src/utils/tasks';
import { toFileName } from '@nrwl/schematics/src/utils/name-utils';
import { toFileName } from '../../utils/name-utils';
import { formatFiles } from '../../utils/rules/format-files';
export default function(schema: Schema): Rule {
return wrapIntoFormat(() => {
const options = normalizeOptions(schema);
const templateSource = apply(url('./files'), [
template({
dot: '.',
tmpl: '',
...(options as any)
}),
move('tools/schematics')
]);
return chain([branchAndMerge(chain([mergeWith(templateSource)]))]);
});
const options = normalizeOptions(schema);
const templateSource = apply(url('./files'), [
template({
dot: '.',
tmpl: '',
...(options as any)
}),
move('tools/schematics')
]);
return chain([
branchAndMerge(chain([mergeWith(templateSource)])),
formatFiles(options)
]);
}
function normalizeOptions(options: Schema): Schema {

View File

@ -1,3 +1,4 @@
export interface Schema {
name: string;
skipFormat: boolean;
}

View File

@ -11,8 +11,12 @@
"$source": "argv",
"index": 0
}
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",
"default": false
}
},
"required": [
]
"required": []
}

View File

@ -14,8 +14,6 @@ describe('workspace-schematic', () => {
beforeEach(() => {
appTree = new VirtualTree();
appTree = createEmptyWorkspace(appTree);
schematicRunner.logger.subscribe(s => console.log(s));
});
it('should generate files', () => {

View File

@ -0,0 +1,66 @@
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { Tree } from '@angular-devkit/schematics';
import * as path from 'path';
import { createEmptyWorkspace } from '../testing-utils';
import { formatFiles } from './format-files';
import { serializeJson } from '../fileutils';
describe('formatFiles', () => {
let tree: Tree;
let schematicRunner: SchematicTestRunner;
beforeEach(() => {
schematicRunner = new SchematicTestRunner(
'@nrwl/schematics',
path.join(__dirname, '../../collection.json')
);
tree = createEmptyWorkspace(Tree.empty());
tree.overwrite(
'package.json',
serializeJson({
scripts: {
format: 'prettier'
}
})
);
});
it('should format files', done => {
schematicRunner.callRule(formatFiles(), tree).subscribe(result => {
expect(schematicRunner.tasks.length).toBe(1);
expect(schematicRunner.tasks[0]).toEqual({
name: 'node-package',
options: {
packageName: 'run format -- --untracked',
quiet: true
}
});
done();
});
});
it('should not format files if there is no format npm script', done => {
tree.overwrite(
'package.json',
serializeJson({
scripts: {}
})
);
schematicRunner.callRule(formatFiles(), tree).subscribe(result => {
expect(schematicRunner.tasks.length).toBe(0);
//TODO: test that a warning is emitted.
done();
});
});
it('should not format files if skipFormat is passed', done => {
schematicRunner
.callRule(formatFiles({ skipFormat: true }), tree)
.subscribe(result => {
expect(schematicRunner.tasks.length).toBe(0);
//TODO: test that a warning is not emitted.
done();
});
});
});

View File

@ -0,0 +1,42 @@
import { readJsonInTree } from '../ast-utils';
import {
TaskConfigurationGenerator,
TaskConfiguration,
Tree,
SchematicContext,
Rule,
noop
} from '@angular-devkit/schematics';
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
class FormatFiles implements TaskConfigurationGenerator<any> {
toConfiguration(): TaskConfiguration<any> {
return {
name: 'node-package',
options: {
packageName: 'run format -- --untracked', // workaround. we should define a custom task executor.
quiet: true
}
};
}
}
export function formatFiles(
options: { skipFormat: boolean } = { skipFormat: false }
): Rule {
if (options.skipFormat) {
return noop();
}
return (host: Tree, context: SchematicContext) => {
const packageJson = readJsonInTree(host, 'package.json');
if (packageJson.scripts && packageJson.scripts.format) {
context.addTask(new FormatFiles());
} else {
context.logger.warn(stripIndents`
Files were not formated during this code generation.
The "format" npm script is missing in your package.json.
Please either add a format script or pass --skip-format.
`);
}
};
}

View File

@ -1,25 +0,0 @@
import {
TaskConfigurationGenerator,
TaskConfiguration,
Tree,
SchematicContext
} from '@angular-devkit/schematics';
export class FormatFiles implements TaskConfigurationGenerator<any> {
toConfiguration(): TaskConfiguration<any> {
return {
name: 'node-package',
options: {
packageName: 'run format -- --untracked', // workaround. we should define a custom task executor.
quiet: true
}
};
}
}
export function wrapIntoFormat(fn: Function): any {
return (host: Tree, context: SchematicContext) => {
context.addTask(new FormatFiles());
return fn(host, context)(host, context);
};
}