Skip to content

Commit

Permalink
feat(StoreDevtools): Add ng-add support
Browse files Browse the repository at this point in the history
  • Loading branch information
martzmakes authored and brandonroberts committed Jun 24, 2018
1 parent 70ae0e4 commit be28d8d
Show file tree
Hide file tree
Showing 10 changed files with 359 additions and 4 deletions.
4 changes: 2 additions & 2 deletions modules/effects/schematics/ng-add/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('Effect ng-add Schematic', () => {
const options = { ...defaultOptions };

const tree = schematicRunner.runSchematic('ng-add', options, appTree);
const packageJson = JSON.parse(getFileContent(tree, '/package.json'));
const packageJson = JSON.parse(tree.readContent('/package.json'));

expect(packageJson.dependencies['@ngrx/effects']).toBeDefined();
});
Expand All @@ -48,7 +48,7 @@ describe('Effect ng-add Schematic', () => {
const options = { ...defaultOptions, skipPackageJson: true };

const tree = schematicRunner.runSchematic('ng-add', options, appTree);
const packageJson = JSON.parse(getFileContent(tree, '/package.json'));
const packageJson = JSON.parse(tree.readContent('/package.json'));

expect(packageJson.dependencies['@ngrx/effects']).toBeUndefined();
});
Expand Down
1 change: 1 addition & 0 deletions modules/store-devtools/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ng_package(
entry_point = "modules/store-devtools/index.js",
packages = [
"//modules/store-devtools/migrations:npm_package",
"//modules/store-devtools/schematics:npm_package",
"//modules/store-devtools/schematics-core:npm_package",
],
deps = [
Expand Down
1 change: 1 addition & 0 deletions modules/store-devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@ngrx/store": "0.0.0-PLACEHOLDER",
"rxjs": "RXJS_VERSION"
},
"schematics": "MODULE_SCHEMATICS_COLLECTION",
"ng-update": {
"packageGroup": "NG_UPDATE_PACKAGE_GROUP",
"migrations": "NG_UPDATE_MIGRATIONS"
Expand Down
33 changes: 33 additions & 0 deletions modules/store-devtools/schematics/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])

load("//tools:defaults.bzl", "ts_library", "npm_package")

ts_library(
name = "schematics",
srcs = glob(
[
"**/*.ts",
],
exclude = [
"**/*.spec.ts",
"**/files/**/*",
],
),
module_name = "@ngrx/store-devtools/schematics",
deps = [
"//modules/store-devtools/schematics-core",
],
)

npm_package(
name = "npm_package",
srcs = [
":collection.json",
] + glob([
"**/files/**/*",
"**/schema.json",
]),
deps = [
":schematics",
],
)
10 changes: 10 additions & 0 deletions modules/store-devtools/schematics/collection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"schematics": {
"ng-add": {
"aliases": ["init"],
"factory": "./ng-add",
"schema": "./ng-add/schema.json",
"description": "Adds initial setup for store-devtools"
}
}
}
129 changes: 129 additions & 0 deletions modules/store-devtools/schematics/ng-add/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {
SchematicTestRunner,
UnitTestTree,
} from '@angular-devkit/schematics/testing';
import { getFileContent } from '@schematics/angular/utility/test';
import * as path from 'path';
import { Schema as StoreDevtoolsOptions } from './schema';
import {
getTestProjectPath,
createWorkspace,
} from '../../../schematics-core/testing';

describe('Store-Devtools ng-add Schematic', () => {
const schematicRunner = new SchematicTestRunner(
'@ngrx/store-devtools',
path.join(__dirname, '../collection.json')
);
const defaultOptions: StoreDevtoolsOptions = {
skipPackageJson: false,
project: 'bar',
module: 'app',
};

const projectPath = getTestProjectPath();

let appTree: UnitTestTree;

beforeEach(() => {
appTree = createWorkspace(schematicRunner, appTree);
});

it('should update package.json', () => {
const options = { ...defaultOptions };

const tree = schematicRunner.runSchematic('ng-add', options, appTree);
const packageJson = JSON.parse(tree.readContent('/package.json'));

expect(packageJson.dependencies['@ngrx/store-devtools']).toBeDefined();
});

it('should skip package.json update', () => {
const options = { ...defaultOptions, skipPackageJson: true };

const tree = schematicRunner.runSchematic('ng-add', options, appTree);
const packageJson = JSON.parse(tree.readContent('/package.json'));

expect(packageJson.dependencies['@ngrx/store-devtools']).toBeUndefined();
});

it('should be provided by default', () => {
const options = { ...defaultOptions };

const tree = schematicRunner.runSchematic('ng-add', options, appTree);
const content = tree.readContent(`${projectPath}/src/app/app.module.ts`);
expect(content).toMatch(
/import { StoreDevtoolsModule } from '@ngrx\/store-devtools';/
);
expect(content).toMatch(
/StoreDevtoolsModule.instrument\({ maxAge: 25, logOnly: environment.production }\)/
);
});

it('should import into a specified module', () => {
const options = { ...defaultOptions };

const tree = schematicRunner.runSchematic('ng-add', options, appTree);
const content = tree.readContent(`${projectPath}/src/app/app.module.ts`);
expect(content).toMatch(
/import { StoreDevtoolsModule } from '@ngrx\/store-devtools';/
);
});

it('should import the environments correctly', () => {
const options = { ...defaultOptions, module: 'app.module.ts' };

const tree = schematicRunner.runSchematic('ng-add', options, appTree);
const content = tree.readContent(`${projectPath}/src/app/app.module.ts`);
expect(content).toMatch(
/import { environment } from '..\/environments\/environment';/
);
});

it('should fail if specified module does not exist', () => {
const options = { ...defaultOptions, module: '/src/app/app.moduleXXX.ts' };
let thrownError: Error | null = null;
try {
schematicRunner.runSchematic('ng-add', options, appTree);
} catch (err) {
thrownError = err;
}
expect(thrownError).toBeDefined();
});

it('should fail if negative maxAges', () => {
const options = { ...defaultOptions, maxAge: -4 };

let thrownError: Error | null = null;
try {
schematicRunner.runSchematic('ng-add', options, appTree);
} catch (err) {
thrownError = err;
}
expect(thrownError).toBeDefined();
});

it('should fail if maxAge of 1', () => {
const options = { ...defaultOptions, maxAge: -4 };

let thrownError: Error | null = null;
try {
schematicRunner.runSchematic('ng-add', options, appTree);
} catch (err) {
thrownError = err;
}
expect(thrownError).toBeDefined();
});

it('should support a custom maxAge', () => {
const options = {
...defaultOptions,
name: 'State',
maxAge: 5,
};

const tree = schematicRunner.runSchematic('ng-add', options, appTree);
const content = tree.readContent(`${projectPath}/src/app/app.module.ts`);
expect(content).toMatch(/maxAge: 5/);
});
});
140 changes: 140 additions & 0 deletions modules/store-devtools/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import {
Rule,
SchematicContext,
SchematicsException,
Tree,
branchAndMerge,
chain,
filter,
noop,
} from '@angular-devkit/schematics';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import {
InsertChange,
addImportToModule,
buildRelativePath,
findModuleFromOptions,
getProjectPath,
insertImport,
addPackageToPackageJson,
platformVersion,
parseName,
} from '@ngrx/store-devtools/schematics-core';
import { Path, dirname } from '@angular-devkit/core';
import * as ts from 'typescript';
import { Schema as StoreDevtoolsOptions } from './schema';

function addImportToNgModule(options: StoreDevtoolsOptions): Rule {
return (host: Tree) => {
const modulePath = options.module;

if (!modulePath) {
return host;
}

if (!host.exists(modulePath)) {
throw new Error('Specified module does not exist');
}

const text = host.read(modulePath);
if (text === null) {
throw new SchematicsException(`File ${modulePath} does not exist.`);
}
const sourceText = text.toString('utf-8');

const source = ts.createSourceFile(
modulePath,
sourceText,
ts.ScriptTarget.Latest,
true
);

const [instrumentNgModuleImport] = addImportToModule(
source,
modulePath,
`StoreDevtoolsModule.instrument({ maxAge: ${
options.maxAge
}, logOnly: environment.production })`,
modulePath
);

const srcPath = dirname(options.path as Path);
const environmentsPath = buildRelativePath(
modulePath,
`/${srcPath}/environments/environment`
);

const changes = [
insertImport(
source,
modulePath,
'StoreDevtoolsModule',
'@ngrx/store-devtools'
),
insertImport(source, modulePath, 'environment', environmentsPath),
instrumentNgModuleImport,
];
const recorder = host.beginUpdate(modulePath);

for (const change of changes) {
if (change instanceof InsertChange) {
recorder.insertLeft(change.pos, change.toAdd);
}
}
host.commitUpdate(recorder);

return host;
};
}

function addNgRxStoreDevToolsToPackageJson() {
return (host: Tree, context: SchematicContext) => {
addPackageToPackageJson(
host,
'dependencies',
'@ngrx/store-devtools',
platformVersion
);
context.addTask(new NodePackageInstallTask());
return host;
};
}

export default function(options: StoreDevtoolsOptions): Rule {
return (host: Tree, context: SchematicContext) => {
options.path = getProjectPath(host, options);

if (options.module) {
options.module = findModuleFromOptions(host, {
name: '',
module: options.module,
path: options.path,
});
}

const parsedPath = parseName(options.path, '');
options.path = parsedPath.path;

if (options.maxAge! < 0 || options.maxAge === 1) {
throw new SchematicsException(
`maxAge should be an integer greater than 1.`
);
}

return chain([
branchAndMerge(
chain([
filter(
path =>
path.endsWith('.module.ts') &&
!path.endsWith('-routing.module.ts')
),
addImportToNgModule(options),
])
),
options && options.skipPackageJson
? noop()
: addNgRxStoreDevToolsToPackageJson(),
])(host, context);
};
}
34 changes: 34 additions & 0 deletions modules/store-devtools/schematics/ng-add/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsNgRxRootState",
"title": "NgRx Root State Management Options Schema",
"type": "object",
"properties": {
"skipPackageJson": {
"type": "boolean",
"default": false,
"description":
"Do not add @ngrx/store as dependency to package.json (e.g., --skipPackageJson)."
},
"path": {
"type": "string",
"format": "path",
"description": "The path to create the state.",
"visible": false
},
"module": {
"type": "string",
"default": "app",
"description": "Allows specification of the declaring module.",
"alias": "m",
"subtype": "filepath"
},
"maxAge": {
"type": "number",
"default": 25,
"description":
"number (>1) | 0 - maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance. 0 is infinite. Default is 25 for performance reasons."
}
},
"required": []
}
7 changes: 7 additions & 0 deletions modules/store-devtools/schematics/ng-add/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Schema {
skipPackageJson?: boolean;
path?: string;
project?: string;
module?: string;
maxAge?: number;
}
Loading

0 comments on commit be28d8d

Please sign in to comment.