switch to prettier

This commit is contained in:
vsavkin 2017-10-07 12:09:45 -04:00
parent bc548c596e
commit a43fbaeb9a
45 changed files with 913 additions and 573 deletions

View File

@ -1,3 +0,0 @@
Language: JavaScript
BasedOnStyle: Google
ColumnLimit: 120

View File

@ -1,4 +1,4 @@
import {checkFilesExist, cleanup, copyMissingPackages, ngNewBazel, readFile, runCLI, runSchematic} from '../utils'; import { checkFilesExist, cleanup, copyMissingPackages, ngNewBazel, readFile, runCLI, runSchematic } from '../utils';
describe('application', () => { describe('application', () => {
beforeEach(cleanup); beforeEach(cleanup);
@ -9,8 +9,14 @@ describe('application', () => {
runSchematic('@nrwl/bazel:app --name=myApp'); runSchematic('@nrwl/bazel:app --name=myApp');
checkFilesExist( checkFilesExist(
`tsconfig.json`, `WORKSPACE`, `BUILD.bazel`, `apps/my-app/BUILD.bazel`, `apps/my-app/src/index.html`, `tsconfig.json`,
`apps/my-app/src/app/app.module.ts`, `apps/my-app/src/app/app.component.ts`); `WORKSPACE`,
`BUILD.bazel`,
`apps/my-app/BUILD.bazel`,
`apps/my-app/src/index.html`,
`apps/my-app/src/app/app.module.ts`,
`apps/my-app/src/app/app.component.ts`
);
expect(readFile('apps/my-app/src/app/app.module.ts')).toContain('bootstrap: [AppComponent]'); expect(readFile('apps/my-app/src/app/app.module.ts')).toContain('bootstrap: [AppComponent]');

View File

@ -1,4 +1,4 @@
import {checkFilesExist, cleanup, copyMissingPackages, ngNewBazel, readFile, runCLI, runSchematic} from '../utils'; import { checkFilesExist, cleanup, copyMissingPackages, ngNewBazel, readFile, runCLI, runSchematic } from '../utils';
describe('library', () => { describe('library', () => {
beforeEach(cleanup); beforeEach(cleanup);
@ -9,8 +9,13 @@ describe('library', () => {
runSchematic('@nrwl/bazel:lib --name=myLib'); runSchematic('@nrwl/bazel:lib --name=myLib');
checkFilesExist( checkFilesExist(
'tsconfig.json', 'WORKSPACE', 'BUILD.bazel', 'libs/my-lib/BUILD.bazel', 'libs/my-lib/index.ts', 'tsconfig.json',
'libs/my-lib/src/my-lib.ts'); 'WORKSPACE',
'BUILD.bazel',
'libs/my-lib/BUILD.bazel',
'libs/my-lib/index.ts',
'libs/my-lib/src/my-lib.ts'
);
const cliConfig = JSON.parse(readFile('.angular-cli.json')); const cliConfig = JSON.parse(readFile('.angular-cli.json'));
expect(cliConfig.apps[0].name).toEqual('myLib'); expect(cliConfig.apps[0].name).toEqual('myLib');

View File

@ -1,4 +1,15 @@
import {checkFilesExist, cleanup, copyMissingPackages, ngNew, ngNewBazel, readFile, runCLI, runCommand, runSchematic, updateFile} from '../utils'; import {
checkFilesExist,
cleanup,
copyMissingPackages,
ngNew,
ngNewBazel,
readFile,
runCLI,
runCommand,
runSchematic,
updateFile
} from '../utils';
describe('angular library', () => { describe('angular library', () => {
beforeEach(cleanup); beforeEach(cleanup);
@ -9,8 +20,13 @@ describe('angular library', () => {
runSchematic('@nrwl/bazel:nglib --name=myLib'); runSchematic('@nrwl/bazel:nglib --name=myLib');
checkFilesExist( checkFilesExist(
'tsconfig.json', 'WORKSPACE', 'BUILD.bazel', 'libs/my-lib/BUILD.bazel', 'libs/my-lib/index.ts', 'tsconfig.json',
'libs/my-lib/src/my-lib.module.ts'); 'WORKSPACE',
'BUILD.bazel',
'libs/my-lib/BUILD.bazel',
'libs/my-lib/index.ts',
'libs/my-lib/src/my-lib.module.ts'
);
const cliConfig = JSON.parse(readFile('.angular-cli.json')); const cliConfig = JSON.parse(readFile('.angular-cli.json'));
expect(cliConfig.apps[0].name).toEqual('myLib'); expect(cliConfig.apps[0].name).toEqual('myLib');

View File

@ -1,4 +1,15 @@
import {checkFilesExist, cleanup, copyMissingPackages, ngNew, ngNewBazel, readFile, runCLI, runCommand, runSchematic, updateFile} from '../utils'; import {
checkFilesExist,
cleanup,
copyMissingPackages,
ngNew,
ngNewBazel,
readFile,
runCLI,
runCommand,
runSchematic,
updateFile
} from '../utils';
describe('workspace', () => { describe('workspace', () => {
beforeEach(cleanup); beforeEach(cleanup);

View File

@ -1,4 +1,14 @@
import {checkFilesExist, cleanup, copyMissingPackages, newApp, newLib, ngNew, readFile, runCLI, updateFile} from '../utils'; import {
checkFilesExist,
cleanup,
copyMissingPackages,
newApp,
newLib,
ngNew,
readFile,
runCLI,
updateFile
} from '../utils';
describe('Nrwl Workspace', () => { describe('Nrwl Workspace', () => {
beforeEach(cleanup); beforeEach(cleanup);
@ -16,6 +26,7 @@ describe('Nrwl Workspace', () => {
expect(packageJson.dependencies['@ngrx/effects']).toBeDefined(); expect(packageJson.dependencies['@ngrx/effects']).toBeDefined();
expect(packageJson.dependencies['@ngrx/router-store']).toBeDefined(); expect(packageJson.dependencies['@ngrx/router-store']).toBeDefined();
expect(packageJson.dependencies['@ngrx/store-devtools']).toBeDefined(); expect(packageJson.dependencies['@ngrx/store-devtools']).toBeDefined();
checkFilesExist('test.js', 'tsconfig.app.json', 'tsconfig.spec.json', 'tsconfig.e2e.json', 'apps', 'libs'); checkFilesExist('test.js', 'tsconfig.app.json', 'tsconfig.spec.json', 'tsconfig.e2e.json', 'apps', 'libs');
}); });
@ -28,18 +39,25 @@ describe('Nrwl Workspace', () => {
expect(angularCliJson.apps[0].name).toEqual('myapp'); expect(angularCliJson.apps[0].name).toEqual('myapp');
checkFilesExist( checkFilesExist(
'apps/myapp/src/main.ts', 'apps/myapp/src/app/app.module.ts', 'apps/myapp/src/app/app.component.ts', 'apps/myapp/src/main.ts',
'apps/myapp/e2e/app.po.ts'); 'apps/myapp/src/app/app.module.ts',
'apps/myapp/src/app/app.component.ts',
'apps/myapp/e2e/app.po.ts'
);
}); });
it('should build app', () => { it(
ngNew('--collection=@nrwl/schematics'); 'should build app',
copyMissingPackages(); () => {
newApp('myapp'); ngNew('--collection=@nrwl/schematics');
runCLI('build --aot'); copyMissingPackages();
checkFilesExist('dist/apps/myapp/main.bundle.js'); newApp('myapp');
expect(runCLI('test --single-run')).toContain('Executed 1 of 1 SUCCESS'); runCLI('build --aot');
}, 100000); checkFilesExist('dist/apps/myapp/main.bundle.js');
expect(runCLI('test --single-run')).toContain('Executed 1 of 1 SUCCESS');
},
100000
);
}); });
describe('lib', () => { describe('lib', () => {
@ -53,14 +71,18 @@ describe('Nrwl Workspace', () => {
checkFilesExist('libs/mylib/src/mylib.ts', 'libs/mylib/src/mylib.spec.ts', 'libs/mylib/index.ts'); checkFilesExist('libs/mylib/src/mylib.ts', 'libs/mylib/src/mylib.spec.ts', 'libs/mylib/index.ts');
}); });
it('should test a lib', () => { it(
ngNew('--collection=@nrwl/schematics'); 'should test a lib',
copyMissingPackages(); () => {
newApp('myapp'); ngNew('--collection=@nrwl/schematics');
newLib('generate lib mylib'); copyMissingPackages();
newApp('myapp');
newLib('generate lib mylib');
expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS'); expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS');
}, 100000); },
100000
);
}); });
describe('nglib', () => { describe('nglib', () => {
@ -71,22 +93,30 @@ describe('Nrwl Workspace', () => {
checkFilesExist('libs/mylib/src/mylib.module.ts', 'libs/mylib/src/mylib.module.spec.ts', 'libs/mylib/index.ts'); checkFilesExist('libs/mylib/src/mylib.module.ts', 'libs/mylib/src/mylib.module.spec.ts', 'libs/mylib/index.ts');
}); });
it('should test an ng lib', () => { it(
ngNew('--collection=@nrwl/schematics'); 'should test an ng lib',
copyMissingPackages(); () => {
newApp('myapp'); ngNew('--collection=@nrwl/schematics');
newLib('mylib --ngmodule'); copyMissingPackages();
newApp('myapp');
newLib('mylib --ngmodule');
expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS'); expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS');
}, 100000); },
100000
);
it('should resolve dependencies on the lib', () => { it(
ngNew('--collection=@nrwl/schematics --npmScope=nrwl'); 'should resolve dependencies on the lib',
copyMissingPackages(); () => {
newApp('myapp'); ngNew('--collection=@nrwl/schematics --npmScope=nrwl');
newLib('mylib --ngmodule'); copyMissingPackages();
newApp('myapp');
newLib('mylib --ngmodule');
updateFile('apps/myapp/src/app/app.module.ts', ` updateFile(
'apps/myapp/src/app/app.module.ts',
`
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { MylibModule } from '@nrwl/mylib'; import { MylibModule } from '@nrwl/mylib';
@ -98,9 +128,12 @@ describe('Nrwl Workspace', () => {
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule {} export class AppModule {}
`); `
);
runCLI('build --aot'); runCLI('build --aot');
}, 100000); },
100000
);
}); });
}); });

View File

@ -1,4 +1,4 @@
import {checkFilesExist, cleanup, copyMissingPackages, ngNew, readFile, runCLI} from '../utils'; import { checkFilesExist, cleanup, copyMissingPackages, ngNew, readFile, runCLI } from '../utils';
describe('ngrx', () => { describe('ngrx', () => {
beforeEach(cleanup); beforeEach(cleanup);
@ -9,35 +9,48 @@ describe('ngrx', () => {
runCLI('generate ngrx app --module=src/app/app.module.ts --root --collection=@nrwl/schematics'); runCLI('generate ngrx app --module=src/app/app.module.ts --root --collection=@nrwl/schematics');
checkFilesExist( checkFilesExist(
`src/app/+state/app.actions.ts`, `src/app/+state/app.effects.ts`, `src/app/+state/app.effects.spec.ts`, `src/app/+state/app.actions.ts`,
`src/app/+state/app.init.ts`, `src/app/+state/app.interfaces.ts`, `src/app/+state/app.reducer.ts`, `src/app/+state/app.effects.ts`,
`src/app/+state/app.reducer.spec.ts`); `src/app/+state/app.effects.spec.ts`,
`src/app/+state/app.init.ts`,
`src/app/+state/app.interfaces.ts`,
`src/app/+state/app.reducer.ts`,
`src/app/+state/app.reducer.spec.ts`
);
const contents = readFile('src/app/app.module.ts'); const contents = readFile('src/app/app.module.ts');
expect(contents).toContain('StoreModule.forRoot'); expect(contents).toContain('StoreModule.forRoot');
expect(contents).toContain('EffectsModule.forRoot'); expect(contents).toContain('EffectsModule.forRoot');
}); });
it('should build', () => { it(
ngNew(); 'should build',
copyMissingPackages(); () => {
runCLI('generate ngrx app --module=src/app/app.module.ts --root --collection=@nrwl/schematics'); ngNew();
copyMissingPackages();
runCLI('generate ngrx app --module=src/app/app.module.ts --root --collection=@nrwl/schematics');
runCLI('build'); runCLI('build');
runCLI('test --single-run'); runCLI('test --single-run');
}, 100000); },
100000
);
it('should add empty root configuration', () => { it(
ngNew(); 'should add empty root configuration',
copyMissingPackages(); () => {
runCLI('generate ngrx app --module=src/app/app.module.ts --onlyEmptyRoot --collection=@nrwl/schematics'); ngNew();
copyMissingPackages();
runCLI('generate ngrx app --module=src/app/app.module.ts --onlyEmptyRoot --collection=@nrwl/schematics');
const contents = readFile('src/app/app.module.ts'); const contents = readFile('src/app/app.module.ts');
expect(contents).toContain('StoreModule.forRoot'); expect(contents).toContain('StoreModule.forRoot');
expect(contents).toContain('EffectsModule.forRoot'); expect(contents).toContain('EffectsModule.forRoot');
runCLI('build'); runCLI('build');
}, 100000); },
100000
);
}); });
describe('feature', () => { describe('feature', () => {
@ -46,9 +59,14 @@ describe('ngrx', () => {
runCLI('generate ngrx app --module=src/app/app.module.ts --collection=@nrwl/schematics'); runCLI('generate ngrx app --module=src/app/app.module.ts --collection=@nrwl/schematics');
checkFilesExist( checkFilesExist(
`src/app/+state/app.actions.ts`, `src/app/+state/app.effects.ts`, `src/app/+state/app.effects.spec.ts`, `src/app/+state/app.actions.ts`,
`src/app/+state/app.init.ts`, `src/app/+state/app.interfaces.ts`, `src/app/+state/app.reducer.ts`, `src/app/+state/app.effects.ts`,
`src/app/+state/app.reducer.spec.ts`); `src/app/+state/app.effects.spec.ts`,
`src/app/+state/app.init.ts`,
`src/app/+state/app.interfaces.ts`,
`src/app/+state/app.reducer.ts`,
`src/app/+state/app.reducer.spec.ts`
);
const contents = readFile('src/app/app.module.ts'); const contents = readFile('src/app/app.module.ts');
expect(contents).toContain('StoreModule.forFeature'); expect(contents).toContain('StoreModule.forFeature');
@ -61,9 +79,14 @@ describe('ngrx', () => {
runCLI('generate ngrx app --module=src/app/app.module.ts --onlyAddFiles --collection=@nrwl/schematics'); runCLI('generate ngrx app --module=src/app/app.module.ts --onlyAddFiles --collection=@nrwl/schematics');
checkFilesExist( checkFilesExist(
`src/app/+state/app.actions.ts`, `src/app/+state/app.effects.ts`, `src/app/+state/app.effects.spec.ts`, `src/app/+state/app.actions.ts`,
`src/app/+state/app.init.ts`, `src/app/+state/app.interfaces.ts`, `src/app/+state/app.reducer.ts`, `src/app/+state/app.effects.ts`,
`src/app/+state/app.reducer.spec.ts`); `src/app/+state/app.effects.spec.ts`,
`src/app/+state/app.init.ts`,
`src/app/+state/app.interfaces.ts`,
`src/app/+state/app.reducer.ts`,
`src/app/+state/app.reducer.spec.ts`
);
const contents = readFile('src/app/app.module.ts'); const contents = readFile('src/app/app.module.ts');
expect(contents).not.toContain('StoreModule'); expect(contents).not.toContain('StoreModule');

View File

@ -1,26 +1,43 @@
import {checkFilesExist, cleanup, copyMissingPackages, newApp, newLib, ngNew, readFile, runCLI, updateFile} from '../utils'; import {
checkFilesExist,
cleanup,
copyMissingPackages,
newApp,
newLib,
ngNew,
readFile,
runCLI,
updateFile
} from '../utils';
describe('Lint', () => { describe('Lint', () => {
beforeEach(cleanup); beforeEach(cleanup);
it('should ensure module boundaries', () => { it(
ngNew('--collection=@nrwl/schematics'); 'should ensure module boundaries',
copyMissingPackages(); () => {
newApp('myapp'); ngNew('--collection=@nrwl/schematics');
newLib('mylib'); copyMissingPackages();
newLib('lazylib'); newApp('myapp');
newLib('mylib');
newLib('lazylib');
const tslint = JSON.parse(readFile('tslint.json')); const tslint = JSON.parse(readFile('tslint.json'));
tslint.rules['nx-enforce-module-boundaries'][1].lazyLoad.push('lazylib'); tslint.rules['nx-enforce-module-boundaries'][1].lazyLoad.push('lazylib');
updateFile('tslint.json', JSON.stringify(tslint, null, 2)); updateFile('tslint.json', JSON.stringify(tslint, null, 2));
updateFile('apps/myapp/src/main.ts', ` updateFile(
'apps/myapp/src/main.ts',
`
import '../../../libs/mylib'; import '../../../libs/mylib';
import '@proj/lazylib'; import '@proj/lazylib';
`); `
);
const out = runCLI('lint --type-check', {silenceError: true}); const out = runCLI('lint --type-check', { silenceError: true });
expect(out).toContain('relative imports of libraries are forbidden'); expect(out).toContain('relative imports of libraries are forbidden');
expect(out).toContain('import of lazy-loaded libraries are forbidden'); expect(out).toContain('import of lazy-loaded libraries are forbidden');
}, 100000); },
100000
);
}); });

View File

@ -1,39 +1,51 @@
import {cleanup, copyMissingPackages, newApp, ngNew, readFile, runCLI, runSchematic, updateFile} from '../utils'; import { cleanup, copyMissingPackages, newApp, ngNew, readFile, runCLI, runSchematic, updateFile } from '../utils';
describe('Upgrade', () => { describe('Upgrade', () => {
beforeEach(cleanup); beforeEach(cleanup);
it('should generate an upgrade shell', () => { it(
ngNew('--collection=@nrwl/schematics'); 'should generate an upgrade shell',
newApp('myapp'); () => {
ngNew('--collection=@nrwl/schematics');
newApp('myapp');
copyMissingPackages(); copyMissingPackages();
updateFile('apps/myapp/src/legacy.js', ` updateFile(
'apps/myapp/src/legacy.js',
`
const angular = window.angular.module('legacy', []); const angular = window.angular.module('legacy', []);
angular.component('rootLegacyCmp', { angular.component('rootLegacyCmp', {
template: 'Expected Value' template: 'Expected Value'
}); });
`); `
);
updateFile('apps/myapp/src/app/app.component.html', ` updateFile(
'apps/myapp/src/app/app.component.html',
`
EXPECTED [<rootLegacyCmp></rootLegacyCmp>] EXPECTED [<rootLegacyCmp></rootLegacyCmp>]
`); `
);
updateFile('apps/myapp/src/app/app.component.spec.ts', ``); updateFile('apps/myapp/src/app/app.component.spec.ts', ``);
runCLI( runCLI(
'generate upgrade-shell legacy --module=apps/myapp/src/app/app.module.ts --angularJsImport=../legacy ' + 'generate upgrade-shell legacy --module=apps/myapp/src/app/app.module.ts --angularJsImport=../legacy ' +
'--angularJsCmpSelector=rootLegacyCmp'); '--angularJsCmpSelector=rootLegacyCmp'
);
runCLI('build'); runCLI('build');
runCLI('test --single-run'); runCLI('test --single-run');
}, 100000); },
100000
);
it('should update package.json', () => { it('should update package.json', () => {
ngNew('--skip-install'); ngNew('--skip-install');
runCLI( runCLI(
'generate upgrade-shell legacy --module=src/app/app.module.ts --angularJsImport=../legacy ' + 'generate upgrade-shell legacy --module=src/app/app.module.ts --angularJsImport=../legacy ' +
'--angularJsCmpSelector=rootLegacyCmp --collection=@nrwl/schematics'); '--angularJsCmpSelector=rootLegacyCmp --collection=@nrwl/schematics'
);
const contents = JSON.parse(readFile('package.json')); const contents = JSON.parse(readFile('package.json'));
expect(contents.dependencies['@angular/upgrade']).toBeDefined(); expect(contents.dependencies['@angular/upgrade']).toBeDefined();
@ -43,8 +55,9 @@ describe('Upgrade', () => {
it('should not update package.json when --skipPackageJson', () => { it('should not update package.json when --skipPackageJson', () => {
ngNew('--skipInstall'); ngNew('--skipInstall');
runCLI( runCLI(
'generate upgrade-shell legacy --module=src/app/app.module.ts --angularJsImport=../legacy ' + 'generate upgrade-shell legacy --module=src/app/app.module.ts --angularJsImport=../legacy ' +
'--angularJsCmpSelector=rootLegacyCmp --skipPackageJson --collection=@nrwl/schematics'); '--angularJsCmpSelector=rootLegacyCmp --skipPackageJson --collection=@nrwl/schematics'
);
const contents = JSON.parse(readFile('package.json')); const contents = JSON.parse(readFile('package.json'));
expect(contents.dependencies['@angular/upgrade']).not.toBeDefined(); expect(contents.dependencies['@angular/upgrade']).not.toBeDefined();

View File

@ -1,4 +1,13 @@
import {checkFilesExist, cleanup, copyMissingPackages, ngNew, readFile, runCLI, runSchematic, updateFile} from '../utils'; import {
checkFilesExist,
cleanup,
copyMissingPackages,
ngNew,
readFile,
runCLI,
runSchematic,
updateFile
} from '../utils';
describe('Nrwl Convert to Nx Workspace', () => { describe('Nrwl Convert to Nx Workspace', () => {
beforeEach(cleanup); beforeEach(cleanup);
@ -20,7 +29,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
// update tsconfig.json // update tsconfig.json
const tsconfigJson = JSON.parse(readFile('tsconfig.json')); const tsconfigJson = JSON.parse(readFile('tsconfig.json'));
tsconfigJson.compilerOptions.paths = {'a': ['b']}; tsconfigJson.compilerOptions.paths = { a: ['b'] };
updateFile('tsconfig.json', JSON.stringify(tsconfigJson, null, 2)); updateFile('tsconfig.json', JSON.stringify(tsconfigJson, null, 2));
// update angular-cli.json // update angular-cli.json
@ -56,8 +65,10 @@ describe('Nrwl Convert to Nx Workspace', () => {
// check if tsconfig.json get merged // check if tsconfig.json get merged
const updatedTsConfig = JSON.parse(readFile('tsconfig.json')); const updatedTsConfig = JSON.parse(readFile('tsconfig.json'));
expect(updatedTsConfig.compilerOptions.paths).toEqual({'a': ['b'], '@proj/*': ['libs/*']}); expect(updatedTsConfig.compilerOptions.paths).toEqual({
a: ['b'],
'@proj/*': ['libs/*']
});
}); });
it('should generate a workspace and not change dependencies or devDependencies if they already exist', () => { it('should generate a workspace and not change dependencies or devDependencies if they already exist', () => {
@ -87,14 +98,18 @@ describe('Nrwl Convert to Nx Workspace', () => {
expect(packageJson.dependencies['@ngrx/store-devtools']).toEqual(ngrxVersion); expect(packageJson.dependencies['@ngrx/store-devtools']).toEqual(ngrxVersion);
}); });
it('should build and test and support the existing AngularCLI generators', () => { it(
ngNew(); 'should build and test and support the existing AngularCLI generators',
copyMissingPackages(); () => {
ngNew();
copyMissingPackages();
runCLI('generate workspace proj --collection=@nrwl/schematics'); runCLI('generate workspace proj --collection=@nrwl/schematics');
runCLI('generate lib mylib --ngmodule'); runCLI('generate lib mylib --ngmodule');
updateFile('apps/proj/src/app/app.module.ts', ` updateFile(
'apps/proj/src/app/app.module.ts',
`
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { MylibModule } from '@proj/mylib'; import { MylibModule } from '@proj/mylib';
@ -106,20 +121,23 @@ describe('Nrwl Convert to Nx Workspace', () => {
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule {} export class AppModule {}
`); `
);
expect(runCLI('build --aot')).toContain('{main} main.bundle.js'); expect(runCLI('build --aot')).toContain('{main} main.bundle.js');
expect(runCLI('test --single-run')).toContain('Executed 4 of 4 SUCCESS'); expect(runCLI('test --single-run')).toContain('Executed 4 of 4 SUCCESS');
expect(runCLI('e2e')).toContain('Executed 1 of 1 spec SUCCESS'); expect(runCLI('e2e')).toContain('Executed 1 of 1 spec SUCCESS');
const generatorHelpText = runCLI('g -h'); const generatorHelpText = runCLI('g -h');
expect(generatorHelpText).toContain('class'); expect(generatorHelpText).toContain('class');
expect(generatorHelpText).toContain('component'); expect(generatorHelpText).toContain('component');
expect(generatorHelpText).toContain('directive'); expect(generatorHelpText).toContain('directive');
expect(generatorHelpText).toContain('enum'); expect(generatorHelpText).toContain('enum');
expect(generatorHelpText).toContain('guard'); expect(generatorHelpText).toContain('guard');
expect(generatorHelpText).toContain('interface'); expect(generatorHelpText).toContain('interface');
expect(generatorHelpText).toContain('module'); expect(generatorHelpText).toContain('module');
expect(generatorHelpText).toContain('pipe'); expect(generatorHelpText).toContain('pipe');
expect(generatorHelpText).toContain('service'); expect(generatorHelpText).toContain('service');
}, 100000); },
100000
);
}); });

View File

@ -1,11 +1,13 @@
import {execSync} from 'child_process'; import { execSync } from 'child_process';
import {linkSync, mkdirSync, readFileSync, statSync, symlinkSync, writeFileSync} from 'fs'; import { linkSync, mkdirSync, readFileSync, statSync, symlinkSync, writeFileSync } from 'fs';
import * as path from 'path'; import * as path from 'path';
const projectName: string = 'proj'; const projectName: string = 'proj';
export function ngNew(command?: string): string { export function ngNew(command?: string): string {
return execSync(`../node_modules/.bin/ng new proj ${command}`, {cwd: `./tmp`}).toString(); return execSync(`../node_modules/.bin/ng new proj ${command}`, {
cwd: `./tmp`
}).toString();
} }
export function ngNewBazel(command?: string): string { export function ngNewBazel(command?: string): string {
@ -16,13 +18,18 @@ export function ngNewBazel(command?: string): string {
return res; return res;
} }
export function runCLI(command?: string, opts = { export function runCLI(
silenceError: false command?: string,
}): string { opts = {
silenceError: false
}
): string {
try { try {
return execSync(`../../node_modules/.bin/ng ${command}`, {cwd: `./tmp/${projectName}`}) return execSync(`../../node_modules/.bin/ng ${command}`, {
.toString() cwd: `./tmp/${projectName}`
.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''); })
.toString()
.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
} catch (e) { } catch (e) {
if (opts.silenceError) { if (opts.silenceError) {
return e.stdout.toString(); return e.stdout.toString();
@ -35,24 +42,26 @@ export function runCLI(command?: string, opts = {
// switch to ng generate, once CLI is fixed // switch to ng generate, once CLI is fixed
export function newApp(name: string): string { export function newApp(name: string): string {
return runCLI(`generate app ${name}`) return runCLI(`generate app ${name}`);
// return execSync(`../../node_modules/.bin/schematics @nrwl/schematics:app --name=${name} // return execSync(`../../node_modules/.bin/schematics @nrwl/schematics:app --name=${name}
// --collection=@nrwl/schematics`, { cwd: `./tmp/${projectName}` }).toString(); // --collection=@nrwl/schematics`, { cwd: `./tmp/${projectName}` }).toString();
} }
// switch to ng generate, once CLI is fixed // switch to ng generate, once CLI is fixed
export function newLib(name: string): string { export function newLib(name: string): string {
return runCLI(`generate lib ${name}`) return runCLI(`generate lib ${name}`);
// return execSync(`../../node_modules/.bin/schematics @nrwl/schematics:lib --name=${name} // return execSync(`../../node_modules/.bin/schematics @nrwl/schematics:lib --name=${name}
// --collection=@nrwl/schematics`, { cwd: `./tmp/${projectName}` }).toString(); // --collection=@nrwl/schematics`, { cwd: `./tmp/${projectName}` }).toString();
} }
export function runSchematic(command: string): string { export function runSchematic(command: string): string {
return execSync(`../../node_modules/.bin/schematics ${command}`, {cwd: `./tmp/${projectName}`}).toString(); return execSync(`../../node_modules/.bin/schematics ${command}`, {
cwd: `./tmp/${projectName}`
}).toString();
} }
export function runCommand(command: string): string { export function runCommand(command: string): string {
return execSync(command, {cwd: `./tmp/${projectName}`}).toString(); return execSync(command, { cwd: `./tmp/${projectName}` }).toString();
} }
export function updateFile(f: string, content: string): void { export function updateFile(f: string, content: string): void {

View File

@ -38,7 +38,7 @@
"@types/jasmine": "2.5.53", "@types/jasmine": "2.5.53",
"@types/node": "8.0.7", "@types/node": "8.0.7",
"angular": "1.6.6", "angular": "1.6.6",
"clang-format": "1.0.55", "prettier": "1.7.4",
"jasmine-core": "~2.6.2", "jasmine-core": "~2.6.2",
"jest": "20.0.4", "jest": "20.0.4",
"karma": "~1.7.0", "karma": "~1.7.0",

View File

@ -1,9 +1,19 @@
import {apply, chain, mergeWith, move, Rule, externalSchematic, template, url, Tree,} from '@angular-devkit/schematics'; import {
import {Schema} from './schema'; apply,
import {names, toFileName, insert} from '@nrwl/schematics'; chain,
mergeWith,
move,
Rule,
externalSchematic,
template,
url,
Tree
} from '@angular-devkit/schematics';
import { Schema } from './schema';
import { names, toFileName, insert } from '@nrwl/schematics';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {addBootstrapToModule, addImportToModule} from '@schematics/angular/utility/ast-utils'; import { addBootstrapToModule, addImportToModule } from '@schematics/angular/utility/ast-utils';
function addBootstrap(path: string): Rule { function addBootstrap(path: string): Rule {
return (host: Tree) => { return (host: Tree) => {
@ -32,7 +42,10 @@ function addAppToAngularCliJson(fullPath: string, options: Schema): Rule {
styles: ['styles.css'], styles: ['styles.css'],
scripts: [], scripts: [],
environmentSource: 'environments/environment.ts', environmentSource: 'environments/environment.ts',
environments: {'dev': 'environments/environment.ts', 'prod': 'environments/environment.prod.ts'} environments: {
dev: 'environments/environment.ts',
prod: 'environments/environment.prod.ts'
}
}); });
host.overwrite('.angular-cli.json', JSON.stringify(config, null, 2)); host.overwrite('.angular-cli.json', JSON.stringify(config, null, 2));
@ -40,18 +53,17 @@ function addAppToAngularCliJson(fullPath: string, options: Schema): Rule {
}; };
} }
export default function(options: Schema): Rule { export default function(options: Schema): Rule {
const fullPath = path.join(options.directory, toFileName(options.name), options.sourceDir); const fullPath = path.join(options.directory, toFileName(options.name), options.sourceDir);
return chain([ return chain([
mergeWith(apply(url('./files'), [template({...options, ...names(options.name), 'dot': '.', 'tmpl': ''})])), mergeWith(apply(url('./files'), [template({ ...options, ...names(options.name), dot: '.', tmpl: '' })])),
externalSchematic('@schematics/angular', 'module', { externalSchematic('@schematics/angular', 'module', {
name: 'app', name: 'app',
commonModule: false, commonModule: false,
flat: true, flat: true,
routing: options.routing, routing: options.routing,
sourceDir: fullPath, sourceDir: fullPath,
spec: false, spec: false
}), }),
externalSchematic('@schematics/angular', 'component', { externalSchematic('@schematics/angular', 'component', {
name: 'app', name: 'app',
@ -65,6 +77,7 @@ export default function(options: Schema): Rule {
viewEncapsulation: options.viewEncapsulation, viewEncapsulation: options.viewEncapsulation,
changeDetection: options.changeDetection changeDetection: options.changeDetection
}), }),
addBootstrap(fullPath), addAppToAngularCliJson(fullPath, options) addBootstrap(fullPath),
addAppToAngularCliJson(fullPath, options)
]); ]);
} }

View File

@ -4,8 +4,8 @@ export interface Schema {
sourceDir?: string; sourceDir?: string;
inlineStyle?: boolean; inlineStyle?: boolean;
inlineTemplate?: boolean; inlineTemplate?: boolean;
viewEncapsulation?: ('Emulated'|'Native'|'None'); viewEncapsulation?: 'Emulated' | 'Native' | 'None';
changeDetection?: ('Default'|'OnPush'); changeDetection?: 'Default' | 'OnPush';
prefix?: string; prefix?: string;
style?: string; style?: string;
skipTests?: boolean; skipTests?: boolean;

View File

@ -1,12 +1,12 @@
import {apply, branchAndMerge, chain, mergeWith, Rule, template, Tree, url} from '@angular-devkit/schematics'; import { apply, branchAndMerge, chain, mergeWith, Rule, template, Tree, url } from '@angular-devkit/schematics';
import {Schema} from './schema'; import { Schema } from './schema';
import * as path from 'path'; import * as path from 'path';
import {names, toFileName} from '@nrwl/schematics'; import { names, toFileName } from '@nrwl/schematics';
function addLibToAngularCliJson(fullPath: string, schema: Schema): Rule { function addLibToAngularCliJson(fullPath: string, schema: Schema): Rule {
return (host: Tree) => { return (host: Tree) => {
const source = JSON.parse(host.read('.angular-cli.json')!.toString('utf-8')); const source = JSON.parse(host.read('.angular-cli.json')!.toString('utf-8'));
source.apps.push({name: schema.name, root: fullPath, appDir: false}); source.apps.push({ name: schema.name, root: fullPath, appDir: false });
host.overwrite('.angular-cli.json', JSON.stringify(source, null, 2)); host.overwrite('.angular-cli.json', JSON.stringify(source, null, 2));
return host; return host;
}; };
@ -15,9 +15,7 @@ function addLibToAngularCliJson(fullPath: string, schema: Schema): Rule {
export default function(options: any): Rule { export default function(options: any): Rule {
const fullPath = path.join(options.directory, toFileName(options.name), options.sourceDir); const fullPath = path.join(options.directory, toFileName(options.name), options.sourceDir);
const templateSource = apply(url('./files'), [template({...options, ...names(options.name), dot: '.', tmpl: ''})]); const templateSource = apply(url('./files'), [template({ ...options, ...names(options.name), dot: '.', tmpl: '' })]);
return chain([ return chain([branchAndMerge(chain([mergeWith(templateSource), addLibToAngularCliJson(fullPath, options)]))]);
branchAndMerge(chain([mergeWith(templateSource), addLibToAngularCliJson(fullPath, options)])),
]);
} }

View File

@ -1,13 +1,17 @@
import {apply, branchAndMerge, chain, mergeWith, Rule, template, Tree, url} from '@angular-devkit/schematics'; import { apply, branchAndMerge, chain, mergeWith, Rule, template, Tree, url } from '@angular-devkit/schematics';
import {Schema} from './schema'; import { Schema } from './schema';
import * as path from 'path'; import * as path from 'path';
import {names, toFileName} from '@nrwl/schematics'; import { names, toFileName } from '@nrwl/schematics';
function addLibToAngularCliJson(fullPath: string, schema: Schema): Rule { function addLibToAngularCliJson(fullPath: string, schema: Schema): Rule {
return (host: Tree) => { return (host: Tree) => {
const source = JSON.parse(host.read('.angular-cli.json')!.toString('utf-8')); const source = JSON.parse(host.read('.angular-cli.json')!.toString('utf-8'));
source.apps.push( source.apps.push({
{name: schema.name, root: fullPath, appDir: false, prefix: schema.prefix ? schema.prefix : schema.name}); name: schema.name,
root: fullPath,
appDir: false,
prefix: schema.prefix ? schema.prefix : schema.name
});
host.overwrite('.angular-cli.json', JSON.stringify(source, null, 2)); host.overwrite('.angular-cli.json', JSON.stringify(source, null, 2));
return host; return host;
}; };
@ -16,9 +20,7 @@ function addLibToAngularCliJson(fullPath: string, schema: Schema): Rule {
export default function(options: any): Rule { export default function(options: any): Rule {
const fullPath = path.join(options.directory, toFileName(options.name), options.sourceDir); const fullPath = path.join(options.directory, toFileName(options.name), options.sourceDir);
const templateSource = apply(url('./files'), [template({...options, ...names(options.name), dot: '.', tmpl: ''})]); const templateSource = apply(url('./files'), [template({ ...options, ...names(options.name), dot: '.', tmpl: '' })]);
return chain([ return chain([branchAndMerge(chain([mergeWith(templateSource), addLibToAngularCliJson(fullPath, options)]))]);
branchAndMerge(chain([mergeWith(templateSource), addLibToAngularCliJson(fullPath, options)])),
]);
} }

View File

@ -1,8 +1,11 @@
import {apply, chain, mergeWith, move, Rule, schematic, template, url,} from '@angular-devkit/schematics'; import { apply, chain, mergeWith, move, Rule, schematic, template, url } from '@angular-devkit/schematics';
import {Schema} from './schema'; import { Schema } from './schema';
import {names} from '@nrwl/schematics'; import { names } from '@nrwl/schematics';
export default function(options: Schema): Rule { export default function(options: Schema): Rule {
return chain([mergeWith(apply( return chain([
url('./files'), [template({...options, ...names(options.name), 'dot': '.', 'tmpl': ''}), move(options.name!)]))]); mergeWith(
apply(url('./files'), [template({ ...options, ...names(options.name), dot: '.', tmpl: '' }), move(options.name!)])
)
]);
} }

View File

@ -1,2 +1,2 @@
export {DataPersistence} from './src/data-persistence'; export { DataPersistence } from './src/data-persistence';
export {NxModule} from './src/nx.module'; export { NxModule } from './src/nx.module';

View File

@ -1,49 +1,51 @@
import 'rxjs/add/operator/delay'; import 'rxjs/add/operator/delay';
import {Component, Injectable} from '@angular/core'; import { Component, Injectable } from '@angular/core';
import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import {ActivatedRouteSnapshot, Router} from '@angular/router'; import { ActivatedRouteSnapshot, Router } from '@angular/router';
import {RouterTestingModule} from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import {Actions, Effect, EffectsModule} from '@ngrx/effects'; import { Actions, Effect, EffectsModule } from '@ngrx/effects';
import {provideMockActions} from '@ngrx/effects/testing'; import { provideMockActions } from '@ngrx/effects/testing';
import {StoreRouterConnectingModule} from '@ngrx/router-store'; import { StoreRouterConnectingModule } from '@ngrx/router-store';
import {Store, StoreModule} from '@ngrx/store'; import { Store, StoreModule } from '@ngrx/store';
import {Observable} from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import {of} from 'rxjs/observable/of'; import { of } from 'rxjs/observable/of';
import {_throw} from 'rxjs/observable/throw'; import { _throw } from 'rxjs/observable/throw';
import {delay} from 'rxjs/operator/delay'; import { delay } from 'rxjs/operator/delay';
import {Subject} from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject';
import {DataPersistence} from '../index'; import { DataPersistence } from '../index';
import {NxModule} from '../src/nx.module'; import { NxModule } from '../src/nx.module';
import {readAll} from '../testing'; import { readAll } from '../testing';
// interfaces // interfaces
type Todo = { type Todo = {
id: number; user: string; id: number;
user: string;
}; };
type Todos = { type Todos = {
selected: Todo; selected: Todo;
}; };
type TodosState = { type TodosState = {
todos: Todos; user: string; todos: Todos;
user: string;
}; };
// actions // actions
type TodoLoaded = { type TodoLoaded = {
type: 'TODO_LOADED', type: 'TODO_LOADED';
payload: Todo payload: Todo;
}; };
type UpdateTodo = { type UpdateTodo = {
type: 'UPDATE_TODO', type: 'UPDATE_TODO';
payload: {newTitle: string;} payload: { newTitle: string };
}; };
type Action = TodoLoaded; type Action = TodoLoaded;
// reducers // reducers
function todosReducer(state: Todos, action: Action): Todos { function todosReducer(state: Todos, action: Action): Todos {
if (action.type === 'TODO_LOADED') { if (action.type === 'TODO_LOADED') {
return {selected: action.payload}; return { selected: action.payload };
} else { } else {
return state; return state;
} }
@ -53,9 +55,7 @@ function userReducer(state: string, action: Action): string {
return 'bob'; return 'bob';
} }
@Component({ template: `ROOT[<router-outlet></router-outlet>]` })
@Component({template: `ROOT[<router-outlet></router-outlet>]`})
class RootCmp {} class RootCmp {}
@Component({ @Component({
@ -79,8 +79,10 @@ describe('DataPersistence', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [RootCmp, TodoComponent], declarations: [RootCmp, TodoComponent],
imports: [ imports: [
StoreModule.forRoot({todos: todosReducer, user: userReducer}), StoreRouterConnectingModule, StoreModule.forRoot({ todos: todosReducer, user: userReducer }),
RouterTestingModule.withRoutes([{path: 'todo/:id', component: TodoComponent}]), NxModule.forRoot() StoreRouterConnectingModule,
RouterTestingModule.withRoutes([{ path: 'todo/:id', component: TodoComponent }]),
NxModule.forRoot()
] ]
}); });
}); });
@ -91,28 +93,37 @@ describe('DataPersistence', () => {
@Effect() @Effect()
loadTodo = this.s.navigation(TodoComponent, { loadTodo = this.s.navigation(TodoComponent, {
run: (a: ActivatedRouteSnapshot, state: TodosState) => { run: (a: ActivatedRouteSnapshot, state: TodosState) => {
return ({type: 'TODO_LOADED', payload: {id: a.params['id'], user: state.user}}); return {
type: 'TODO_LOADED',
payload: { id: a.params['id'], user: state.user }
};
}, },
onError: () => null onError: () => null
}); });
constructor(private s: DataPersistence<any>) {} constructor(private s: DataPersistence<any>) {}
} }
beforeEach( beforeEach(() => {
() => {TestBed.configureTestingModule( TestBed.configureTestingModule({
{providers: [TodoEffects], imports: [EffectsModule.forRoot([TodoEffects])]})}); providers: [TodoEffects],
imports: [EffectsModule.forRoot([TodoEffects])]
});
});
it('should work', fakeAsync(() => { it(
const root = TestBed.createComponent(RootCmp); 'should work',
fakeAsync(() => {
const root = TestBed.createComponent(RootCmp);
const router: Router = TestBed.get(Router); const router: Router = TestBed.get(Router);
router.navigateByUrl('/todo/123'); router.navigateByUrl('/todo/123');
tick(0); tick(0);
root.detectChanges(false); root.detectChanges(false);
expect(root.elementRef.nativeElement.innerHTML).toContain('ID 123'); expect(root.elementRef.nativeElement.innerHTML).toContain('ID 123');
expect(root.elementRef.nativeElement.innerHTML).toContain('User bob'); expect(root.elementRef.nativeElement.innerHTML).toContain('User bob');
})); })
);
}); });
describe('`run` throwing an error', () => { describe('`run` throwing an error', () => {
@ -124,38 +135,47 @@ describe('DataPersistence', () => {
if (a.params['id'] === '123') { if (a.params['id'] === '123') {
throw new Error('boom'); throw new Error('boom');
} else { } else {
return ({type: 'TODO_LOADED', payload: {id: a.params['id'], user: state.user}}); return {
type: 'TODO_LOADED',
payload: { id: a.params['id'], user: state.user }
};
} }
}, },
onError: (a, e) => ({type: 'ERROR', payload: {error: e}}) onError: (a, e) => ({ type: 'ERROR', payload: { error: e } })
}); });
constructor(private s: DataPersistence<any>) {} constructor(private s: DataPersistence<any>) {}
} }
beforeEach( beforeEach(() => {
() => {TestBed.configureTestingModule( TestBed.configureTestingModule({
{providers: [TodoEffects], imports: [EffectsModule.forRoot([TodoEffects])]})}); providers: [TodoEffects],
imports: [EffectsModule.forRoot([TodoEffects])]
});
});
it('should work', fakeAsync(() => { it(
const root = TestBed.createComponent(RootCmp); 'should work',
fakeAsync(() => {
const root = TestBed.createComponent(RootCmp);
const router: Router = TestBed.get(Router); const router: Router = TestBed.get(Router);
let action; let action;
TestBed.get(Actions).subscribe(a => action = a); TestBed.get(Actions).subscribe(a => (action = a));
router.navigateByUrl('/todo/123'); router.navigateByUrl('/todo/123');
tick(0); tick(0);
root.detectChanges(false); root.detectChanges(false);
expect(root.elementRef.nativeElement.innerHTML).not.toContain('ID 123'); expect(root.elementRef.nativeElement.innerHTML).not.toContain('ID 123');
expect(action.type).toEqual('ERROR'); expect(action.type).toEqual('ERROR');
expect(action.payload.error.message).toEqual('boom'); expect(action.payload.error.message).toEqual('boom');
// can recover after an error // can recover after an error
router.navigateByUrl('/todo/456'); router.navigateByUrl('/todo/456');
tick(0); tick(0);
root.detectChanges(false); root.detectChanges(false);
expect(root.elementRef.nativeElement.innerHTML).toContain('ID 456'); expect(root.elementRef.nativeElement.innerHTML).toContain('ID 456');
})); })
);
}); });
describe('`run` returning an error observable', () => { describe('`run` returning an error observable', () => {
@ -167,43 +187,52 @@ describe('DataPersistence', () => {
if (a.params['id'] === '123') { if (a.params['id'] === '123') {
return _throw('boom'); return _throw('boom');
} else { } else {
return ({type: 'TODO_LOADED', payload: {id: a.params['id'], user: state.user}}); return {
type: 'TODO_LOADED',
payload: { id: a.params['id'], user: state.user }
};
} }
}, },
onError: (a, e) => ({type: 'ERROR', payload: {error: e}}) onError: (a, e) => ({ type: 'ERROR', payload: { error: e } })
}); });
constructor(private s: DataPersistence<any>) {} constructor(private s: DataPersistence<any>) {}
} }
beforeEach( beforeEach(() => {
() => {TestBed.configureTestingModule( TestBed.configureTestingModule({
{providers: [TodoEffects], imports: [EffectsModule.forRoot([TodoEffects])]})}); providers: [TodoEffects],
imports: [EffectsModule.forRoot([TodoEffects])]
});
});
it('should work', fakeAsync(() => { it(
const root = TestBed.createComponent(RootCmp); 'should work',
fakeAsync(() => {
const root = TestBed.createComponent(RootCmp);
const router: Router = TestBed.get(Router); const router: Router = TestBed.get(Router);
let action; let action;
TestBed.get(Actions).subscribe(a => action = a); TestBed.get(Actions).subscribe(a => (action = a));
router.navigateByUrl('/todo/123'); router.navigateByUrl('/todo/123');
tick(0); tick(0);
root.detectChanges(false); root.detectChanges(false);
expect(root.elementRef.nativeElement.innerHTML).not.toContain('ID 123'); expect(root.elementRef.nativeElement.innerHTML).not.toContain('ID 123');
expect(action.type).toEqual('ERROR'); expect(action.type).toEqual('ERROR');
expect(action.payload.error).toEqual('boom'); expect(action.payload.error).toEqual('boom');
router.navigateByUrl('/todo/456'); router.navigateByUrl('/todo/456');
tick(0); tick(0);
root.detectChanges(false); root.detectChanges(false);
expect(root.elementRef.nativeElement.innerHTML).toContain('ID 456'); expect(root.elementRef.nativeElement.innerHTML).toContain('ID 456');
})); })
);
}); });
}); });
describe('fetch', () => { describe('fetch', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({providers: [DataPersistence]}); TestBed.configureTestingModule({ providers: [DataPersistence] });
}); });
describe('no id', () => { describe('no id', () => {
@ -213,7 +242,10 @@ describe('DataPersistence', () => {
loadTodos = this.s.fetch('GET_TODOS', { loadTodos = this.s.fetch('GET_TODOS', {
run: (a: any, state: TodosState) => { run: (a: any, state: TodosState) => {
// we need to introduce the delay to "enable" switchMap // we need to introduce the delay to "enable" switchMap
return of ({type: 'TODOS', payload: {user: state.user, todos: 'some todos'}}).delay(1); return of({
type: 'TODOS',
payload: { user: state.user, todos: 'some todos' }
}).delay(1);
}, },
onError: (a: UpdateTodo, e: any) => null onError: (a: UpdateTodo, e: any) => null
@ -232,16 +264,16 @@ describe('DataPersistence', () => {
actions = new Subject<any>(); actions = new Subject<any>();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [TodoEffects, provideMockActions(() => actions)], providers: [TodoEffects, provideMockActions(() => actions)],
imports: [StoreModule.forRoot({user: userReducer})] imports: [StoreModule.forRoot({ user: userReducer })]
}) });
}); });
it('should work', async (done) => { it('should work', async done => {
actions = of({type: 'GET_TODOS', payload: {}}, {type: 'GET_TODOS', payload: {}}); actions = of({ type: 'GET_TODOS', payload: {} }, { type: 'GET_TODOS', payload: {} });
expect(await readAll(TestBed.get(TodoEffects).loadTodos)).toEqual([ expect(await readAll(TestBed.get(TodoEffects).loadTodos)).toEqual([
{type: 'TODOS', payload: {user: 'bob', todos: 'some todos'}}, { type: 'TODOS', payload: { user: 'bob', todos: 'some todos' } },
{type: 'TODOS', payload: {user: 'bob', todos: 'some todos'}} { type: 'TODOS', payload: { user: 'bob', todos: 'some todos' } }
]); ]);
done(); done();
@ -254,7 +286,7 @@ describe('DataPersistence', () => {
@Effect() @Effect()
loadTodo = this.s.fetch('GET_TODO', { loadTodo = this.s.fetch('GET_TODO', {
id: (a: any, state: TodosState) => a.payload.id, id: (a: any, state: TodosState) => a.payload.id,
run: (a: any, state: TodosState) => of({type: 'TODO', payload: a.payload}).delay(1), run: (a: any, state: TodosState) => of({ type: 'TODO', payload: a.payload }).delay(1),
onError: (a: UpdateTodo, e: any) => null onError: (a: UpdateTodo, e: any) => null
}); });
@ -271,17 +303,20 @@ describe('DataPersistence', () => {
actions = new Subject<any>(); actions = new Subject<any>();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [TodoEffects, provideMockActions(() => actions)], providers: [TodoEffects, provideMockActions(() => actions)],
imports: [StoreModule.forRoot({user: userReducer})] imports: [StoreModule.forRoot({ user: userReducer })]
}) });
}); });
it('should work', async (done) => { it('should work', async done => {
actions = actions = of(
of({type: 'GET_TODO', payload: {id: 1, value: '1'}}, {type: 'GET_TODO', payload: {id: 2, value: '2a'}}, { type: 'GET_TODO', payload: { id: 1, value: '1' } },
{type: 'GET_TODO', payload: {id: 2, value: '2b'}}); { type: 'GET_TODO', payload: { id: 2, value: '2a' } },
{ type: 'GET_TODO', payload: { id: 2, value: '2b' } }
);
expect(await readAll(TestBed.get(TodoEffects).loadTodo)).toEqual([ expect(await readAll(TestBed.get(TodoEffects).loadTodo)).toEqual([
{type: 'TODO', payload: {id: 1, value: '1'}}, {type: 'TODO', payload: {id: 2, value: '2b'}} { type: 'TODO', payload: { id: 1, value: '1' } },
{ type: 'TODO', payload: { id: 2, value: '2b' } }
]); ]);
done(); done();
@ -291,7 +326,7 @@ describe('DataPersistence', () => {
describe('pessimisticUpdate', () => { describe('pessimisticUpdate', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({providers: [DataPersistence]}); TestBed.configureTestingModule({ providers: [DataPersistence] });
}); });
describe('successful', () => { describe('successful', () => {
@ -299,8 +334,10 @@ describe('DataPersistence', () => {
class TodoEffects { class TodoEffects {
@Effect() @Effect()
loadTodo = this.s.pessimisticUpdate('UPDATE_TODO', { loadTodo = this.s.pessimisticUpdate('UPDATE_TODO', {
run: (a: UpdateTodo, state: TodosState) => run: (a: UpdateTodo, state: TodosState) => ({
({type: 'TODO_UPDATED', payload: {user: state.user, newTitle: a.payload.newTitle}}), type: 'TODO_UPDATED',
payload: { user: state.user, newTitle: a.payload.newTitle }
}),
onError: (a: UpdateTodo, e: any) => null onError: (a: UpdateTodo, e: any) => null
}); });
@ -317,15 +354,21 @@ describe('DataPersistence', () => {
actions = new Subject<any>(); actions = new Subject<any>();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [TodoEffects, provideMockActions(() => actions)], providers: [TodoEffects, provideMockActions(() => actions)],
imports: [StoreModule.forRoot({user: userReducer})] imports: [StoreModule.forRoot({ user: userReducer })]
}) });
}); });
it('should work', async (done) => { it('should work', async done => {
actions = of({type: 'UPDATE_TODO', payload: {newTitle: 'newTitle'}}); actions = of({
type: 'UPDATE_TODO',
payload: { newTitle: 'newTitle' }
});
expect(await readAll(TestBed.get(TodoEffects).loadTodo)).toEqual([ expect(await readAll(TestBed.get(TodoEffects).loadTodo)).toEqual([
{type: 'TODO_UPDATED', payload: {user: 'bob', newTitle: 'newTitle'}} {
type: 'TODO_UPDATED',
payload: { user: 'bob', newTitle: 'newTitle' }
}
]); ]);
done(); done();
@ -341,7 +384,10 @@ describe('DataPersistence', () => {
throw new Error('boom'); throw new Error('boom');
}, },
onError: (a: UpdateTodo, e: any) => ({type: 'ERROR', payload: {error: e}}) onError: (a: UpdateTodo, e: any) => ({
type: 'ERROR',
payload: { error: e }
})
}); });
constructor(private s: DataPersistence<any>) {} constructor(private s: DataPersistence<any>) {}
@ -357,12 +403,15 @@ describe('DataPersistence', () => {
actions = new Subject<any>(); actions = new Subject<any>();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [TodoEffects, provideMockActions(() => actions)], providers: [TodoEffects, provideMockActions(() => actions)],
imports: [StoreModule.forRoot({user: userReducer})] imports: [StoreModule.forRoot({ user: userReducer })]
}) });
}); });
it('should work', async (done) => { it('should work', async done => {
actions = of({type: 'UPDATE_TODO', payload: {newTitle: 'newTitle'}}); actions = of({
type: 'UPDATE_TODO',
payload: { newTitle: 'newTitle' }
});
const [a]: any = await readAll(TestBed.get(TodoEffects).loadTodo); const [a]: any = await readAll(TestBed.get(TodoEffects).loadTodo);
@ -382,7 +431,10 @@ describe('DataPersistence', () => {
return _throw('boom'); return _throw('boom');
}, },
onError: (a: UpdateTodo, e: any) => ({type: 'ERROR', payload: {error: e}}) onError: (a: UpdateTodo, e: any) => ({
type: 'ERROR',
payload: { error: e }
})
}); });
constructor(private s: DataPersistence<any>) {} constructor(private s: DataPersistence<any>) {}
@ -398,12 +450,15 @@ describe('DataPersistence', () => {
actions = new Subject<any>(); actions = new Subject<any>();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [TodoEffects, provideMockActions(() => actions)], providers: [TodoEffects, provideMockActions(() => actions)],
imports: [StoreModule.forRoot({user: userReducer})] imports: [StoreModule.forRoot({ user: userReducer })]
}) });
}); });
it('should work', async (done) => { it('should work', async done => {
actions = of({type: 'UPDATE_TODO', payload: {newTitle: 'newTitle'}}); actions = of({
type: 'UPDATE_TODO',
payload: { newTitle: 'newTitle' }
});
const [a]: any = await readAll(TestBed.get(TodoEffects).loadTodo); const [a]: any = await readAll(TestBed.get(TodoEffects).loadTodo);
@ -412,12 +467,12 @@ describe('DataPersistence', () => {
done(); done();
}); });
}) });
}); });
describe('optimisticUpdate', () => { describe('optimisticUpdate', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({providers: [DataPersistence]}); TestBed.configureTestingModule({ providers: [DataPersistence] });
}); });
describe('`run` throws an error', () => { describe('`run` throws an error', () => {
@ -429,7 +484,10 @@ describe('DataPersistence', () => {
throw new Error('boom'); throw new Error('boom');
}, },
undoAction: (a: UpdateTodo, e: any) => ({type: 'UNDO_UPDATE_TODO', payload: a.payload}) undoAction: (a: UpdateTodo, e: any) => ({
type: 'UNDO_UPDATE_TODO',
payload: a.payload
})
}); });
constructor(private s: DataPersistence<any>) {} constructor(private s: DataPersistence<any>) {}
@ -445,12 +503,15 @@ describe('DataPersistence', () => {
actions = new Subject<any>(); actions = new Subject<any>();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [TodoEffects, provideMockActions(() => actions)], providers: [TodoEffects, provideMockActions(() => actions)],
imports: [StoreModule.forRoot({user: userReducer})] imports: [StoreModule.forRoot({ user: userReducer })]
}) });
}); });
it('should work', async (done) => { it('should work', async done => {
actions = of({type: 'UPDATE_TODO', payload: {newTitle: 'newTitle'}}); actions = of({
type: 'UPDATE_TODO',
payload: { newTitle: 'newTitle' }
});
const [a]: any = await readAll(TestBed.get(TodoEffects).loadTodo); const [a]: any = await readAll(TestBed.get(TodoEffects).loadTodo);

View File

@ -1,9 +1,9 @@
import {from} from 'rxjs/observable/from' import { from } from 'rxjs/observable/from';
import {readAll, readFirst} from '../src/testing-utils'; import { readAll, readFirst } from '../src/testing-utils';
describe('TestingUtils', () => { describe('TestingUtils', () => {
describe('readAll', () => { describe('readAll', () => {
it('should transform Observable<T> to Promise<Array<T>>', async (done) => { it('should transform Observable<T> to Promise<Array<T>>', async done => {
const obs = from([1, 2, 3]); const obs = from([1, 2, 3]);
const result = await readAll(obs); const result = await readAll(obs);
@ -14,7 +14,7 @@ describe('TestingUtils', () => {
}); });
describe('readFirst', () => { describe('readFirst', () => {
it('should transform first item emitted from Observable<T> to Promise<T>', async (done) => { it('should transform first item emitted from Observable<T> to Promise<T>', async done => {
const obs = from([1, 2, 3]); const obs = from([1, 2, 3]);
const result = await readFirst(obs); const result = await readFirst(obs);

View File

@ -1,32 +1,32 @@
import {Injectable, Type} from '@angular/core'; import { Injectable, Type } from '@angular/core';
import {ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import {Actions} from '@ngrx/effects'; import { Actions } from '@ngrx/effects';
import {ROUTER_NAVIGATION, RouterNavigationAction} from '@ngrx/router-store'; import { ROUTER_NAVIGATION, RouterNavigationAction } from '@ngrx/router-store';
import {Action, State, Store} from '@ngrx/store'; import { Action, State, Store } from '@ngrx/store';
import {Observable} from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import {of} from 'rxjs/observable/of'; import { of } from 'rxjs/observable/of';
import {_catch} from 'rxjs/operator/catch'; import { _catch } from 'rxjs/operator/catch';
import {concatMap} from 'rxjs/operator/concatMap'; import { concatMap } from 'rxjs/operator/concatMap';
import {filter} from 'rxjs/operator/filter'; import { filter } from 'rxjs/operator/filter';
import {groupBy} from 'rxjs/operator/groupBy'; import { groupBy } from 'rxjs/operator/groupBy';
import {map} from 'rxjs/operator/map'; import { map } from 'rxjs/operator/map';
import {mergeMap} from 'rxjs/operator/mergeMap'; import { mergeMap } from 'rxjs/operator/mergeMap';
import {switchMap} from 'rxjs/operator/switchMap'; import { switchMap } from 'rxjs/operator/switchMap';
import {withLatestFrom} from 'rxjs/operator/withLatestFrom'; import { withLatestFrom } from 'rxjs/operator/withLatestFrom';
/** /**
* See {@link DataPersistence.pessimisticUpdate} for more information. * See {@link DataPersistence.pessimisticUpdate} for more information.
*/ */
export interface PessimisticUpdateOpts { export interface PessimisticUpdateOpts {
run(a: Action, state?: any): Observable<Action>|Action|void; run(a: Action, state?: any): Observable<Action> | Action | void;
onError(a: Action, e: any): Observable<any>|any; onError(a: Action, e: any): Observable<any> | any;
} }
/** /**
* See {@link DataPersistence.pessimisticUpdate} for more information. * See {@link DataPersistence.pessimisticUpdate} for more information.
*/ */
export interface OptimisticUpdateOpts { export interface OptimisticUpdateOpts {
run(a: Action, state?: any): Observable<any>|any; run(a: Action, state?: any): Observable<any> | any;
undoAction(a: Action, e: any): Observable<Action>|Action; undoAction(a: Action, e: any): Observable<Action> | Action;
} }
/** /**
@ -34,16 +34,16 @@ export interface OptimisticUpdateOpts {
*/ */
export interface FetchOpts { export interface FetchOpts {
id?(a: Action, state?: any): any; id?(a: Action, state?: any): any;
run(a: Action, state?: any): Observable<Action>|Action|void; run(a: Action, state?: any): Observable<Action> | Action | void;
onError?(a: Action, e: any): Observable<any>|any; onError?(a: Action, e: any): Observable<any> | any;
} }
/** /**
* See {@link DataPersistence.navigation} for more information. * See {@link DataPersistence.navigation} for more information.
*/ */
export interface HandleNavigationOpts { export interface HandleNavigationOpts {
run(a: ActivatedRouteSnapshot, state?: any): Observable<Action>|Action|void; run(a: ActivatedRouteSnapshot, state?: any): Observable<Action> | Action | void;
onError?(a: ActivatedRouteSnapshot, e: any): Observable<any>|any; onError?(a: ActivatedRouteSnapshot, e: any): Observable<any> | any;
} }
type Pair = [Action, any]; type Pair = [Action, any];
@ -211,10 +211,10 @@ export class DataPersistence<T> {
const allPairs = withLatestFrom.call(nav, this.store); const allPairs = withLatestFrom.call(nav, this.store);
if (opts.id) { if (opts.id) {
const groupedFetches: Observable<Observable<Pair>> = groupBy.call(allPairs, (p) => opts.id(p[0], p[1])); const groupedFetches: Observable<Observable<Pair>> = groupBy.call(allPairs, p => opts.id(p[0], p[1]));
return mergeMap.call( return mergeMap.call(groupedFetches, (pairs: Observable<Pair>) =>
groupedFetches, switchMap.call(pairs, this.runWithErrorHandling(opts.run, opts.onError))
(pairs: Observable<Pair>) => switchMap.call(pairs, this.runWithErrorHandling(opts.run, opts.onError))); );
} else { } else {
return concatMap.call(allPairs, this.runWithErrorHandling(opts.run, opts.onError)); return concatMap.call(allPairs, this.runWithErrorHandling(opts.run, opts.onError));
} }
@ -256,10 +256,11 @@ export class DataPersistence<T> {
*/ */
navigation(component: Type<any>, opts: HandleNavigationOpts): Observable<any> { navigation(component: Type<any>, opts: HandleNavigationOpts): Observable<any> {
const nav = filter.call( const nav = filter.call(
map.call( map.call(this.actions.ofType(ROUTER_NAVIGATION), (a: RouterNavigationAction<RouterStateSnapshot>) =>
this.actions.ofType(ROUTER_NAVIGATION), findSnapshot(component, a.payload.routerState.root)
(a: RouterNavigationAction<RouterStateSnapshot>) => findSnapshot(component, a.payload.routerState.root)), ),
s => !!s); s => !!s
);
const pairs = withLatestFrom.call(nav, this.store); const pairs = withLatestFrom.call(nav, this.store);
return switchMap.call(pairs, this.runWithErrorHandling(opts.run, opts.onError)); return switchMap.call(pairs, this.runWithErrorHandling(opts.run, opts.onError));
@ -269,11 +270,11 @@ export class DataPersistence<T> {
return a => { return a => {
try { try {
const r = wrapIntoObservable(run(a[0], a[1])); const r = wrapIntoObservable(run(a[0], a[1]));
return _catch.call(r, e => wrapIntoObservable(onError(a[0], e))) return _catch.call(r, e => wrapIntoObservable(onError(a[0], e)));
} catch (e) { } catch (e) {
return wrapIntoObservable(onError(a[0], e)); return wrapIntoObservable(onError(a[0], e));
} }
} };
} }
} }
@ -294,8 +295,8 @@ function wrapIntoObservable(obj: any): Observable<any> {
if (!!obj && typeof obj.subscribe === 'function') { if (!!obj && typeof obj.subscribe === 'function') {
return obj; return obj;
} else if (!obj) { } else if (!obj) {
return of (); return of();
} else { } else {
return of (obj); return of(obj);
} }
} }

View File

@ -1,5 +1,5 @@
import {ModuleWithProviders, NgModule} from '@angular/core'; import { ModuleWithProviders, NgModule } from '@angular/core';
import {DataPersistence} from './data-persistence'; import { DataPersistence } from './data-persistence';
/** /**
* @whatItDoes Provides services for enterprise Angular applications. * @whatItDoes Provides services for enterprise Angular applications.
@ -9,6 +9,6 @@ import {DataPersistence} from './data-persistence';
@NgModule({}) @NgModule({})
export class NxModule { export class NxModule {
static forRoot(): ModuleWithProviders { static forRoot(): ModuleWithProviders {
return {ngModule: NxModule, providers: [DataPersistence]}; return { ngModule: NxModule, providers: [DataPersistence] };
} }
} }

View File

@ -1,7 +1,7 @@
import {Observable} from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import {first} from 'rxjs/operator/first'; import { first } from 'rxjs/operator/first';
import {toArray} from 'rxjs/operator/toArray'; import { toArray } from 'rxjs/operator/toArray';
import {toPromise} from 'rxjs/operator/toPromise'; import { toPromise } from 'rxjs/operator/toPromise';
/** /**
* @whatItDoes reads all the values from an observable and returns a promise * @whatItDoes reads all the values from an observable and returns a promise

View File

@ -1,2 +1,2 @@
export {cold, hot} from 'jasmine-marbles'; export { cold, hot } from 'jasmine-marbles';
export {readAll, readFirst} from './src/testing-utils'; export { readAll, readFirst } from './src/testing-utils';

View File

@ -1,2 +1,2 @@
export {addImportToModule, addProviderToModule, insert} from './src/utility/ast-utils'; export { addImportToModule, addProviderToModule, insert } from './src/utility/ast-utils';
export {names, toClassName, toFileName, toPropertyName} from './src/utility/name-utils'; export { names, toClassName, toFileName, toPropertyName } from './src/utility/name-utils';

View File

@ -1,12 +1,26 @@
import {apply, branchAndMerge, chain, externalSchematic, mergeWith, move, Rule, template, Tree, url, MergeStrategy, filter, noop} from '@angular-devkit/schematics'; import {
import {Schema} from './schema'; apply,
branchAndMerge,
chain,
externalSchematic,
mergeWith,
move,
Rule,
template,
Tree,
url,
MergeStrategy,
filter,
noop
} from '@angular-devkit/schematics';
import { Schema } from './schema';
import * as stringUtils from '@schematics/angular/strings'; import * as stringUtils from '@schematics/angular/strings';
import {addImportToModule, insert, toFileName} from '@nrwl/schematics'; import { addImportToModule, insert, toFileName } from '@nrwl/schematics';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {addBootstrapToModule} from '@schematics/angular/utility/ast-utils'; import { addBootstrapToModule } from '@schematics/angular/utility/ast-utils';
import {insertImport} from '@schematics/angular/utility/route-utils'; import { insertImport } from '@schematics/angular/utility/route-utils';
import {addApp} from '../utility/config-file-utils'; import { addApp } from '../utility/config-file-utils';
function addBootstrap(path: string): Rule { function addBootstrap(path: string): Rule {
return (host: Tree) => { return (host: Tree) => {
@ -29,7 +43,7 @@ function addNxModule(path: string): Rule {
const sourceFile = ts.createSourceFile(modulePath, moduleSource, ts.ScriptTarget.Latest, true); const sourceFile = ts.createSourceFile(modulePath, moduleSource, ts.ScriptTarget.Latest, true);
insert(host, modulePath, [ insert(host, modulePath, [
insertImport(sourceFile, modulePath, 'NxModule', '@nrwl/nx'), insertImport(sourceFile, modulePath, 'NxModule', '@nrwl/nx'),
...addImportToModule(sourceFile, modulePath, 'NxModule.forRoot()'), ...addImportToModule(sourceFile, modulePath, 'NxModule.forRoot()')
]); ]);
return host; return host;
}; };
@ -43,21 +57,24 @@ function addAppToAngularCliJson(options: Schema): Rule {
const sourceText = host.read('.angular-cli.json')!.toString('utf-8'); const sourceText = host.read('.angular-cli.json')!.toString('utf-8');
const json = JSON.parse(sourceText); const json = JSON.parse(sourceText);
json.apps = addApp(json.apps, { json.apps = addApp(json.apps, {
'name': options.name, name: options.name,
'root': fullPath(options), root: fullPath(options),
'outDir': `dist/apps/${options.name}`, outDir: `dist/apps/${options.name}`,
'assets': ['assets', 'favicon.ico'], assets: ['assets', 'favicon.ico'],
'index': 'index.html', index: 'index.html',
'main': 'main.ts', main: 'main.ts',
'polyfills': 'polyfills.ts', polyfills: 'polyfills.ts',
'test': '../../../test.js', test: '../../../test.js',
'tsconfig': '../../../tsconfig.app.json', tsconfig: '../../../tsconfig.app.json',
'testTsconfig': '../../../tsconfig.spec.json', testTsconfig: '../../../tsconfig.spec.json',
'prefix': options.prefix, prefix: options.prefix,
'styles': [`styles.${options.style}`], styles: [`styles.${options.style}`],
'scripts': [], scripts: [],
'environmentSource': 'environments/environment.ts', environmentSource: 'environments/environment.ts',
'environments': {'dev': 'environments/environment.ts', 'prod': 'environments/environment.prod.ts'} environments: {
dev: 'environments/environment.ts',
prod: 'environments/environment.prod.ts'
}
}); });
host.overwrite('.angular-cli.json', JSON.stringify(json, null, 2)); host.overwrite('.angular-cli.json', JSON.stringify(json, null, 2));
@ -66,19 +83,21 @@ function addAppToAngularCliJson(options: Schema): Rule {
} }
export default function(schema: Schema): Rule { export default function(schema: Schema): Rule {
const options = {...schema, name: toFileName(schema.name)}; const options = { ...schema, name: toFileName(schema.name) };
const templateSource = const templateSource = apply(url('./files'), [
apply(url('./files'), [template({utils: stringUtils, dot: '.', tmpl: '', ...options as object})]); template({ utils: stringUtils, dot: '.', tmpl: '', ...(options as object) })
]);
const selector = `${options.prefix}-root`; const selector = `${options.prefix}-root`;
return chain([ return chain([
branchAndMerge(chain([mergeWith(templateSource)])), externalSchematic('@schematics/angular', 'module', { branchAndMerge(chain([mergeWith(templateSource)])),
externalSchematic('@schematics/angular', 'module', {
name: 'app', name: 'app',
commonModule: false, commonModule: false,
flat: true, flat: true,
routing: options.routing, routing: options.routing,
sourceDir: fullPath(options), sourceDir: fullPath(options),
spec: false, spec: false
}), }),
externalSchematic('@schematics/angular', 'component', { externalSchematic('@schematics/angular', 'component', {
name: 'app', name: 'app',
@ -94,15 +113,16 @@ export default function(schema: Schema): Rule {
}), }),
mergeWith( mergeWith(
apply( apply(url('./component-files'), [
url('./component-files'), options.inlineTemplate ? filter(path => !path.endsWith('.html')) : noop(),
[ template({ ...options, tmpl: '' }),
options.inlineTemplate ? filter(path => !path.endsWith('.html')) : noop(), move(path.join(fullPath(options), 'app'))
template({...options, tmpl: ''}), ]),
move(path.join(fullPath(options), 'app')), MergeStrategy.Overwrite
]), ),
MergeStrategy.Overwrite), addBootstrap(fullPath(options)),
addBootstrap(fullPath(options)), addNxModule(fullPath(options)), addAppToAngularCliJson(options) addNxModule(fullPath(options)),
addAppToAngularCliJson(options)
]); ]);
} }

View File

@ -3,8 +3,8 @@ export interface Schema {
sourceDir?: string; sourceDir?: string;
inlineStyle?: boolean; inlineStyle?: boolean;
inlineTemplate?: boolean; inlineTemplate?: boolean;
viewEncapsulation?: ('Emulated'|'Native'|'None'); viewEncapsulation?: 'Emulated' | 'Native' | 'None';
changeDetection?: ('Default'|'OnPush'); changeDetection?: 'Default' | 'OnPush';
routing?: boolean; routing?: boolean;
skipTests?: boolean; skipTests?: boolean;
prefix?: string; prefix?: string;

View File

@ -1,3 +0,0 @@
Language: JavaScript
BasedOnStyle: Google
ColumnLimit: 120

View File

@ -9,7 +9,7 @@
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e", "e2e": "ng e2e",
"format": "find apps/ -iname '*.ts' | xargs clang-format -i && find libs/ -iname '*.ts' | xargs clang-format -i" "format": "prettier --single-quote --print-width 120 --write '{apps,libs}/**/*.ts'"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
@ -52,6 +52,6 @@
"ts-node": "~3.2.0", "ts-node": "~3.2.0",
"tslint": "~5.3.2",<% } %> "tslint": "~5.3.2",<% } %>
"typescript": "~2.3.3", "typescript": "~2.3.3",
"clang-format": "1.0.55" "prettier": "<%= prettierVersion %>"
} }
} }

View File

@ -1,12 +1,19 @@
import {apply, branchAndMerge, chain, mergeWith, move, Rule, template, Tree, url} from '@angular-devkit/schematics'; import { apply, branchAndMerge, chain, mergeWith, move, Rule, template, Tree, url } from '@angular-devkit/schematics';
import {Schema} from './schema'; import { Schema } from './schema';
import * as stringUtils from '@schematics/angular/strings'; import * as stringUtils from '@schematics/angular/strings';
import {libVersions} from '../utility/lib-versions'; import { libVersions } from '../utility/lib-versions';
export default function(options: Schema): Rule { export default function(options: Schema): Rule {
const npmScope = options.npmScope ? options.npmScope : options.name; const npmScope = options.npmScope ? options.npmScope : options.name;
const templateSource = const templateSource = apply(url('./files'), [
apply(url('./files'), [template({utils: stringUtils, dot: '.', ...libVersions, ...options as object, npmScope})]); template({
utils: stringUtils,
dot: '.',
...libVersions,
...(options as object),
npmScope
})
]);
return chain([branchAndMerge(chain([mergeWith(templateSource)]))]); return chain([branchAndMerge(chain([mergeWith(templateSource)]))]);
} }

View File

@ -1,8 +1,19 @@
import {apply, branchAndMerge, chain, externalSchematic, mergeWith, move, Rule, template, Tree, url} from '@angular-devkit/schematics'; import {
import {Schema} from './schema'; apply,
import {names, toFileName} from '@nrwl/schematics'; branchAndMerge,
chain,
externalSchematic,
mergeWith,
move,
Rule,
template,
Tree,
url
} from '@angular-devkit/schematics';
import { Schema } from './schema';
import { names, toFileName } from '@nrwl/schematics';
import * as path from 'path'; import * as path from 'path';
import {addApp} from '../utility/config-file-utils'; import { addApp } from '../utility/config-file-utils';
function addLibToAngularCliJson(options: Schema): Rule { function addLibToAngularCliJson(options: Schema): Rule {
return (host: Tree) => { return (host: Tree) => {
@ -12,8 +23,12 @@ function addLibToAngularCliJson(options: Schema): Rule {
const sourceText = host.read('.angular-cli.json')!.toString('utf-8'); const sourceText = host.read('.angular-cli.json')!.toString('utf-8');
const json = JSON.parse(sourceText); const json = JSON.parse(sourceText);
json.apps = json.apps = addApp(json.apps, {
addApp(json.apps, {'name': options.name, 'root': fullPath(options), 'test': '../../../test.js', 'appRoot': ''}); name: options.name,
root: fullPath(options),
test: '../../../test.js',
appRoot: ''
});
host.overwrite('.angular-cli.json', JSON.stringify(json, null, 2)); host.overwrite('.angular-cli.json', JSON.stringify(json, null, 2));
return host; return host;
@ -21,12 +36,17 @@ function addLibToAngularCliJson(options: Schema): Rule {
} }
export default function(schema: Schema): Rule { export default function(schema: Schema): Rule {
const options = {...schema, name: toFileName(schema.name)}; const options = { ...schema, name: toFileName(schema.name) };
const fullPath = path.join('libs', toFileName(options.name), options.sourceDir); const fullPath = path.join('libs', toFileName(options.name), options.sourceDir);
const templateSource = apply( const templateSource = apply(url(options.ngmodule ? './ngfiles' : './files'), [
url(options.ngmodule ? './ngfiles' : './files'), template({
[template({...names(options.name), dot: '.', tmpl: '', ...options as object})]); ...names(options.name),
dot: '.',
tmpl: '',
...(options as object)
})
]);
return chain([branchAndMerge(chain([mergeWith(templateSource)])), addLibToAngularCliJson(options)]); return chain([branchAndMerge(chain([mergeWith(templateSource)])), addLibToAngularCliJson(options)]);
} }

View File

@ -1,13 +1,24 @@
import {apply, branchAndMerge, chain, mergeWith, move, noop, Rule, template, Tree, url} from '@angular-devkit/schematics'; import {
apply,
branchAndMerge,
chain,
mergeWith,
move,
noop,
Rule,
template,
Tree,
url
} from '@angular-devkit/schematics';
import {names, toClassName, toFileName, toPropertyName} from '../utility/name-utils'; import { names, toClassName, toFileName, toPropertyName } from '../utility/name-utils';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {addImportToModule, addProviderToModule, insert, offset} from '../utility/ast-utils'; import { addImportToModule, addProviderToModule, insert, offset } from '../utility/ast-utils';
import {insertImport} from '@schematics/angular/utility/route-utils'; import { insertImport } from '@schematics/angular/utility/route-utils';
import {Schema} from './schema'; import { Schema } from './schema';
import {InsertChange} from '@schematics/angular/utility/change'; import { InsertChange } from '@schematics/angular/utility/change';
import {ngrxVersion} from '../utility/lib-versions'; import { ngrxVersion } from '../utility/lib-versions';
function addImportsToModule(name: string, options: Schema): Rule { function addImportsToModule(name: string, options: Schema): Rule {
return (host: Tree) => { return (host: Tree) => {
@ -35,7 +46,6 @@ function addImportsToModule(name: string, options: Schema): Rule {
...addImportToModule(source, modulePath, `!environment.production ? StoreDevtoolsModule.instrument() : []`) ...addImportToModule(source, modulePath, `!environment.production ? StoreDevtoolsModule.instrument() : []`)
]); ]);
return host; return host;
} else { } else {
const reducerPath = `./+state/${toFileName(name)}.reducer`; const reducerPath = `./+state/${toFileName(name)}.reducer`;
const effectsPath = `./+state/${toFileName(name)}.effects`; const effectsPath = `./+state/${toFileName(name)}.effects`;
@ -61,14 +71,16 @@ function addImportsToModule(name: string, options: Schema): Rule {
insertImport(source, modulePath, 'environment', '../environments/environment'), insertImport(source, modulePath, 'environment', '../environments/environment'),
...addImportToModule(source, modulePath, `StoreModule.forRoot(${reducerName}, {initialState: ${initName}})`), ...addImportToModule(source, modulePath, `StoreModule.forRoot(${reducerName}, {initialState: ${initName}})`),
...addImportToModule(source, modulePath, `EffectsModule.forRoot([${effectsName}])`), ...addImportToModule(source, modulePath, `EffectsModule.forRoot([${effectsName}])`),
...addImportToModule(source, modulePath, `!environment.production ? StoreDevtoolsModule.instrument() : []`), ...addImportToModule(source, modulePath, `!environment.production ? StoreDevtoolsModule.instrument() : []`)
]); ]);
} else { } else {
insert(host, modulePath, [ insert(host, modulePath, [
...common, ...common,
...addImportToModule( ...addImportToModule(
source, modulePath, source,
`StoreModule.forFeature('${toPropertyName(name)}', ${reducerName}, {initialState: ${initName}})`), modulePath,
`StoreModule.forFeature('${toPropertyName(name)}', ${reducerName}, {initialState: ${initName}})`
),
...addImportToModule(source, modulePath, `EffectsModule.forFeature([${effectsName}])`) ...addImportToModule(source, modulePath, `EffectsModule.forFeature([${effectsName}])`)
]); ]);
} }
@ -112,9 +124,10 @@ export default function(options: Schema): Rule {
if (options.onlyEmptyRoot) { if (options.onlyEmptyRoot) {
return chain([addImportsToModule(name, options), options.skipPackageJson ? noop() : addNgRxToPackageJson()]); return chain([addImportsToModule(name, options), options.skipPackageJson ? noop() : addNgRxToPackageJson()]);
} else { } else {
const templateSource = apply(url('./files'), [template({...options, tmpl: '', ...names(name)}), move(moduleDir)]); const templateSource = apply(url('./files'), [template({ ...options, tmpl: '', ...names(name) }), move(moduleDir)]);
return chain([ return chain([
branchAndMerge(chain([mergeWith(templateSource)])), addImportsToModule(name, options), branchAndMerge(chain([mergeWith(templateSource)])),
addImportsToModule(name, options),
options.skipPackageJson ? noop() : addNgRxToPackageJson() options.skipPackageJson ? noop() : addNgRxToPackageJson()
]); ]);
} }

View File

@ -1,14 +1,17 @@
import {RuleFailure} from 'tslint'; import { RuleFailure } from 'tslint';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {Rule} from './nxEnforceModuleBoundariesRule'; import { Rule } from './nxEnforceModuleBoundariesRule';
describe('Enforce Module Boundaries', () => { describe('Enforce Module Boundaries', () => {
it('should not error when everything is in order', () => { it('should not error when everything is in order', () => {
const failures = runRule({npmScope: 'mycompany'}, ` const failures = runRule(
{ npmScope: 'mycompany' },
`
import '@mycompany/mylib'; import '@mycompany/mylib';
import '../blah'; import '../blah';
`); `
);
expect(failures.length).toEqual(0); expect(failures.length).toEqual(0);
}); });
@ -21,14 +24,14 @@ describe('Enforce Module Boundaries', () => {
}); });
it('should error about deep imports into libraries', () => { it('should error about deep imports into libraries', () => {
const failures = runRule({npmScope: 'mycompany'}, `import '@mycompany/mylib/blah';`); const failures = runRule({ npmScope: 'mycompany' }, `import '@mycompany/mylib/blah';`);
expect(failures.length).toEqual(1); expect(failures.length).toEqual(1);
expect(failures[0].getFailure()).toEqual('deep imports into libraries are forbidden'); expect(failures[0].getFailure()).toEqual('deep imports into libraries are forbidden');
}); });
it('should error on importing a lazy-loaded library', () => { it('should error on importing a lazy-loaded library', () => {
const failures = runRule({npmScope: 'mycompany', lazyLoad: ['mylib']}, `import '@mycompany/mylib';`); const failures = runRule({ npmScope: 'mycompany', lazyLoad: ['mylib'] }, `import '@mycompany/mylib';`);
expect(failures.length).toEqual(1); expect(failures.length).toEqual(1);
expect(failures[0].getFailure()).toEqual('import of lazy-loaded libraries are forbidden'); expect(failures[0].getFailure()).toEqual('import of lazy-loaded libraries are forbidden');
@ -36,7 +39,11 @@ describe('Enforce Module Boundaries', () => {
}); });
function runRule(ruleArguments: any, content: string): RuleFailure[] { function runRule(ruleArguments: any, content: string): RuleFailure[] {
const options: any = {ruleArguments: [ruleArguments], ruleSeverity: 'error', ruleName: 'enforceModuleBoundaries'}; const options: any = {
ruleArguments: [ruleArguments],
ruleSeverity: 'error',
ruleName: 'enforceModuleBoundaries'
};
const sourceFile = ts.createSourceFile('proj/apps/myapp/src/main.ts', content, ts.ScriptTarget.Latest, true); const sourceFile = ts.createSourceFile('proj/apps/myapp/src/main.ts', content, ts.ScriptTarget.Latest, true);
const rule = new Rule(options, 'proj'); const rule = new Rule(options, 'proj');

View File

@ -1,6 +1,6 @@
import * as path from 'path'; import * as path from 'path';
import * as Lint from 'tslint'; import * as Lint from 'tslint';
import {IOptions} from 'tslint'; import { IOptions } from 'tslint';
import * as ts from 'typescript'; import * as ts from 'typescript';
export class Rule extends Lint.Rules.AbstractRule { export class Rule extends Lint.Rules.AbstractRule {
@ -29,10 +29,8 @@ class EnforceModuleBoundariesWalker extends Lint.RuleWalker {
if (impParts[0] === npmScope && impParts.length > 2) { if (impParts[0] === npmScope && impParts.length > 2) {
this.addFailureAt(node.getStart(), node.getWidth(), 'deep imports into libraries are forbidden'); this.addFailureAt(node.getStart(), node.getWidth(), 'deep imports into libraries are forbidden');
} else if (impParts[0] === npmScope && impParts.length === 2 && lazyLoad && lazyLoad.indexOf(impParts[1]) > -1) { } else if (impParts[0] === npmScope && impParts.length === 2 && lazyLoad && lazyLoad.indexOf(impParts[1]) > -1) {
this.addFailureAt(node.getStart(), node.getWidth(), 'import of lazy-loaded libraries are forbidden'); this.addFailureAt(node.getStart(), node.getWidth(), 'import of lazy-loaded libraries are forbidden');
} else if (this.isRelative(imp) && this.isRelativeImportIntoAnotherProject(imp)) { } else if (this.isRelative(imp) && this.isRelativeImportIntoAnotherProject(imp)) {
this.addFailureAt(node.getStart(), node.getWidth(), 'relative imports of libraries are forbidden'); this.addFailureAt(node.getStart(), node.getWidth(), 'relative imports of libraries are forbidden');
} }

View File

@ -1,13 +1,34 @@
import {apply, branchAndMerge, chain, mergeWith, move, noop, Rule, SchematicContext, template, Tree, url} from '@angular-devkit/schematics'; import {
apply,
branchAndMerge,
chain,
mergeWith,
move,
noop,
Rule,
SchematicContext,
template,
Tree,
url
} from '@angular-devkit/schematics';
import {names, toClassName, toFileName, toPropertyName} from '../utility/name-utils'; import { names, toClassName, toFileName, toPropertyName } from '../utility/name-utils';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {addDeclarationToModule, addEntryComponents, addImportToModule, addMethod, addParameterToConstructor, addProviderToModule, getBootstrapComponent, insert, removeFromNgModule} from '../utility/ast-utils'; import {
import {insertImport} from '@schematics/angular/utility/route-utils'; addDeclarationToModule,
import {Schema} from './schema'; addEntryComponents,
import {angularJsVersion} from '../utility/lib-versions'; addImportToModule,
addMethod,
addParameterToConstructor,
addProviderToModule,
getBootstrapComponent,
insert,
removeFromNgModule
} from '../utility/ast-utils';
import { insertImport } from '@schematics/angular/utility/route-utils';
import { Schema } from './schema';
import { angularJsVersion } from '../utility/lib-versions';
function addImportsToModule(moduleClassName: string, options: Schema): Rule { function addImportsToModule(moduleClassName: string, options: Schema): Rule {
return (host: Tree) => { return (host: Tree) => {
@ -22,8 +43,11 @@ function addImportsToModule(moduleClassName: string, options: Schema): Rule {
insert(host, modulePath, [ insert(host, modulePath, [
insertImport( insertImport(
source, modulePath, `configure${toClassName(options.name)}, upgradedComponents`, source,
`./${toFileName(options.name)}-setup`), modulePath,
`configure${toClassName(options.name)}, upgradedComponents`,
`./${toFileName(options.name)}-setup`
),
insertImport(source, modulePath, 'UpgradeModule', '@angular/upgrade/static'), insertImport(source, modulePath, 'UpgradeModule', '@angular/upgrade/static'),
...addImportToModule(source, modulePath, 'UpgradeModule'), ...addImportToModule(source, modulePath, 'UpgradeModule'),
...addDeclarationToModule(source, modulePath, '...upgradedComponents'), ...addDeclarationToModule(source, modulePath, '...upgradedComponents'),
@ -34,7 +58,6 @@ function addImportsToModule(moduleClassName: string, options: Schema): Rule {
}; };
} }
function addNgDoBootstrapToModule(moduleClassName: string, options: Schema): Rule { function addNgDoBootstrapToModule(moduleClassName: string, options: Schema): Rule {
return (host: Tree) => { return (host: Tree) => {
if (!host.exists(options.module)) { if (!host.exists(options.module)) {
@ -46,8 +69,10 @@ function addNgDoBootstrapToModule(moduleClassName: string, options: Schema): Rul
const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true); const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
insert(host, modulePath, [ insert(host, modulePath, [
...addParameterToConstructor( ...addParameterToConstructor(source, modulePath, {
source, modulePath, {className: moduleClassName, param: 'private upgrade: UpgradeModule'}), className: moduleClassName,
param: 'private upgrade: UpgradeModule'
}),
...addMethod(source, modulePath, { ...addMethod(source, modulePath, {
className: moduleClassName, className: moduleClassName,
methodHeader: 'ngDoBootstrap(): void', methodHeader: 'ngDoBootstrap(): void',
@ -74,8 +99,9 @@ function createFiles(angularJsImport: string, moduleClassName: string, moduleFil
const moduleSource = ts.createSourceFile(modulePath, moduleSourceText, ts.ScriptTarget.Latest, true); const moduleSource = ts.createSourceFile(modulePath, moduleSourceText, ts.ScriptTarget.Latest, true);
const bootstrapComponentClassName = getBootstrapComponent(moduleSource, moduleClassName); const bootstrapComponentClassName = getBootstrapComponent(moduleSource, moduleClassName);
const bootstrapComponentFileName = const bootstrapComponentFileName = `${toFileName(
`${toFileName(bootstrapComponentClassName.substring(0, bootstrapComponentClassName.length - 9))}.component`; bootstrapComponentClassName.substring(0, bootstrapComponentClassName.length - 9)
)}.component`;
const moduleDir = path.dirname(options.module); const moduleDir = path.dirname(options.module);
const templateSource = apply(url('./files'), [ const templateSource = apply(url('./files'), [
@ -97,7 +123,6 @@ function createFiles(angularJsImport: string, moduleClassName: string, moduleFil
}; };
} }
function addUpgradeToPackageJson() { function addUpgradeToPackageJson() {
return (host: Tree) => { return (host: Tree) => {
if (!host.exists('package.json')) return host; if (!host.exists('package.json')) return host;
@ -127,7 +152,8 @@ export default function(options: Schema): Rule {
return chain([ return chain([
createFiles(angularJsImport, moduleClassName, moduleFileName, options), createFiles(angularJsImport, moduleClassName, moduleFileName, options),
addImportsToModule(moduleClassName, options), addNgDoBootstrapToModule(moduleClassName, options), addImportsToModule(moduleClassName, options),
addNgDoBootstrapToModule(moduleClassName, options),
options.skipPackageJson ? noop() : addUpgradeToPackageJson() options.skipPackageJson ? noop() : addUpgradeToPackageJson()
]); ]);
} }

View File

@ -5,17 +5,20 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Tree} from '@angular-devkit/schematics'; import { Tree } from '@angular-devkit/schematics';
import {getDecoratorMetadata, getSourceNodes, insertAfterLastOccurrence} from '@schematics/angular/utility/ast-utils'; import { getDecoratorMetadata, getSourceNodes, insertAfterLastOccurrence } from '@schematics/angular/utility/ast-utils';
import {Change, InsertChange, NoopChange, RemoveChange} from '@schematics/angular/utility/change'; import { Change, InsertChange, NoopChange, RemoveChange } from '@schematics/angular/utility/change';
import * as ts from 'typescript'; import * as ts from 'typescript';
// This should be moved to @schematics/angular once it allows to pass custom expressions as providers // This should be moved to @schematics/angular once it allows to pass custom expressions as providers
function _addSymbolToNgModuleMetadata( function _addSymbolToNgModuleMetadata(
source: ts.SourceFile, ngModulePath: string, metadataField: string, expression: string): Change[] { source: ts.SourceFile,
ngModulePath: string,
metadataField: string,
expression: string
): Change[] {
const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core'); const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');
let node: any = nodes[0]; // tslint:disable-line:no-any let node: any = nodes[0]; // tslint:disable-line:no-any
// Find the decorator declaration. // Find the decorator declaration.
if (!node) { if (!node) {
@ -23,23 +26,21 @@ function _addSymbolToNgModuleMetadata(
} }
// Get all the children property assignment of object literals. // Get all the children property assignment of object literals.
const matchingProperties: ts.ObjectLiteralElement[] = const matchingProperties: ts.ObjectLiteralElement[] = (node as ts.ObjectLiteralExpression).properties
(node as ts.ObjectLiteralExpression) .filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment)
.properties // Filter out every fields that's not "metadataField". Also handles string literals
.filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment) // (but not expressions).
// Filter out every fields that's not "metadataField". Also handles string literals .filter((prop: ts.PropertyAssignment) => {
// (but not expressions). const name = prop.name;
.filter((prop: ts.PropertyAssignment) => { switch (name.kind) {
const name = prop.name; case ts.SyntaxKind.Identifier:
switch (name.kind) { return (name as ts.Identifier).getText(source) == metadataField;
case ts.SyntaxKind.Identifier: case ts.SyntaxKind.StringLiteral:
return (name as ts.Identifier).getText(source) == metadataField; return (name as ts.StringLiteral).text == metadataField;
case ts.SyntaxKind.StringLiteral: }
return (name as ts.StringLiteral).text == metadataField;
}
return false; return false;
}); });
// Get the last node of the array literal. // Get the last node of the array literal.
if (!matchingProperties) { if (!matchingProperties) {
@ -90,7 +91,7 @@ function _addSymbolToNgModuleMetadata(
} }
if (Array.isArray(node)) { if (Array.isArray(node)) {
const nodeArray = node as {} as Array<ts.Node>; const nodeArray = (node as {}) as Array<ts.Node>;
const symbolsArray = nodeArray.map(node => node.getText()); const symbolsArray = nodeArray.map(node => node.getText());
if (symbolsArray.includes(expression)) { if (symbolsArray.includes(expression)) {
return []; return [];
@ -137,27 +138,37 @@ function _addSymbolToNgModuleMetadata(
} }
export function addParameterToConstructor( export function addParameterToConstructor(
source: ts.SourceFile, modulePath: string, opts: {className: string, param: string}): Change[] { source: ts.SourceFile,
modulePath: string,
opts: { className: string; param: string }
): Change[] {
const clazz = findClass(source, opts.className); const clazz = findClass(source, opts.className);
const constructor = clazz.members.filter(m => m.kind === ts.SyntaxKind.Constructor)[0]; const constructor = clazz.members.filter(m => m.kind === ts.SyntaxKind.Constructor)[0];
if (constructor) { if (constructor) {
throw new Error('Should be tested'); throw new Error('Should be tested');
} else { } else {
const methodHeader = `constructor(${opts.param})`; const methodHeader = `constructor(${opts.param})`;
return addMethod(source, modulePath, {className: opts.className, methodHeader, body: null}); return addMethod(source, modulePath, {
className: opts.className,
methodHeader,
body: null
});
} }
} }
export function addMethod( export function addMethod(
source: ts.SourceFile, modulePath: string, source: ts.SourceFile,
opts: {className: string, methodHeader: string, body: string}): Change[] { modulePath: string,
opts: { className: string; methodHeader: string; body: string }
): Change[] {
const clazz = findClass(source, opts.className); const clazz = findClass(source, opts.className);
const body = opts.body ? ` const body = opts.body
? `
${opts.methodHeader} { ${opts.methodHeader} {
${offset(opts.body, 1, false)} ${offset(opts.body, 1, false)}
} }
` : `
` : `
${opts.methodHeader} {} ${opts.methodHeader} {}
`; `;
@ -167,7 +178,7 @@ ${opts.methodHeader} {}
export function removeFromNgModule(source: ts.SourceFile, modulePath: string, property: string): Change[] { export function removeFromNgModule(source: ts.SourceFile, modulePath: string, property: string): Change[] {
const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core'); const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');
let node: any = nodes[0]; // tslint:disable-line:no-any let node: any = nodes[0]; // tslint:disable-line:no-any
// Find the decorator declaration. // Find the decorator declaration.
if (!node) { if (!node) {
@ -177,7 +188,7 @@ export function removeFromNgModule(source: ts.SourceFile, modulePath: string, pr
// Get all the children property assignment of object literals. // Get all the children property assignment of object literals.
const matchingProperty = getMatchingProperty(source, property); const matchingProperty = getMatchingProperty(source, property);
if (matchingProperty) { if (matchingProperty) {
return [new RemoveChange(modulePath, matchingProperty.pos, matchingProperty.getFullText(source))] return [new RemoveChange(modulePath, matchingProperty.pos, matchingProperty.getFullText(source))];
} else { } else {
return []; return [];
} }
@ -186,8 +197,9 @@ export function removeFromNgModule(source: ts.SourceFile, modulePath: string, pr
function findClass(source: ts.SourceFile, className: string): ts.ClassDeclaration { function findClass(source: ts.SourceFile, className: string): ts.ClassDeclaration {
const nodes = getSourceNodes(source); const nodes = getSourceNodes(source);
const clazz = const clazz = <any>nodes.filter(
<any>nodes.filter(n => n.kind === ts.SyntaxKind.ClassDeclaration && (<any>n).name.text === className)[0]; n => n.kind === ts.SyntaxKind.ClassDeclaration && (<any>n).name.text === className
)[0];
if (!clazz) { if (!clazz) {
throw new Error(`Cannot find class '${className}'`); throw new Error(`Cannot find class '${className}'`);
@ -197,27 +209,23 @@ function findClass(source: ts.SourceFile, className: string): ts.ClassDeclaratio
} }
export function offset(text: string, numberOfTabs: number, wrap: boolean): string { export function offset(text: string, numberOfTabs: number, wrap: boolean): string {
const lines = text.trim() const lines = text
.split('\n') .trim()
.map(line => { .split('\n')
let tabs = ''; .map(line => {
for (let c = 0; c < numberOfTabs; ++c) { let tabs = '';
tabs += ' '; for (let c = 0; c < numberOfTabs; ++c) {
} tabs += ' ';
return `${tabs}${line}`; }
}) return `${tabs}${line}`;
.join('\n'); })
.join('\n');
return wrap ? `\n${lines}\n` : lines; return wrap ? `\n${lines}\n` : lines;
} }
export function addImportToModule(source: ts.SourceFile, modulePath: string, symbolName: string): Change[] { export function addImportToModule(source: ts.SourceFile, modulePath: string, symbolName: string): Change[] {
return _addSymbolToNgModuleMetadata( return _addSymbolToNgModuleMetadata(source, modulePath, 'imports', symbolName);
source,
modulePath,
'imports',
symbolName,
);
} }
export function getBootstrapComponent(source: ts.SourceFile, moduleClassName: string): string { export function getBootstrapComponent(source: ts.SourceFile, moduleClassName: string): string {
@ -238,13 +246,13 @@ export function getBootstrapComponent(source: ts.SourceFile, moduleClassName: st
function getMatchingProperty(source: ts.SourceFile, property: string): ts.ObjectLiteralElement { function getMatchingProperty(source: ts.SourceFile, property: string): ts.ObjectLiteralElement {
const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core'); const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');
let node: any = nodes[0]; // tslint:disable-line:no-any let node: any = nodes[0]; // tslint:disable-line:no-any
if (!node) return null; if (!node) return null;
// Get all the children property assignment of object literals. // Get all the children property assignment of object literals.
return (node as ts.ObjectLiteralExpression) return (
.properties (node as ts.ObjectLiteralExpression).properties
.filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment) .filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment)
// Filter out every fields that's not "metadataField". Also handles string literals // Filter out every fields that's not "metadataField". Also handles string literals
// (but not expressions). // (but not expressions).
@ -257,7 +265,8 @@ function getMatchingProperty(source: ts.SourceFile, property: string): ts.Object
return (name as ts.StringLiteral).text === property; return (name as ts.StringLiteral).text === property;
} }
return false; return false;
})[0]; })[0]
);
} }
export function addProviderToModule(source: ts.SourceFile, modulePath: string, symbolName: string): Change[] { export function addProviderToModule(source: ts.SourceFile, modulePath: string, symbolName: string): Change[] {
@ -272,7 +281,6 @@ export function addEntryComponents(source: ts.SourceFile, modulePath: string, sy
return _addSymbolToNgModuleMetadata(source, modulePath, 'entryComponents', symbolName); return _addSymbolToNgModuleMetadata(source, modulePath, 'entryComponents', symbolName);
} }
export function insert(host: Tree, modulePath: string, changes: Change[]) { export function insert(host: Tree, modulePath: string, changes: Change[]) {
const recorder = host.beginUpdate(modulePath); const recorder = host.beginUpdate(modulePath);
for (const change of changes) { for (const change of changes) {

View File

@ -1,23 +1,30 @@
import {addApp} from './config-file-utils'; import { addApp } from './config-file-utils';
describe('configFileUtils', () => { describe('configFileUtils', () => {
describe('sortApps', () => { describe('sortApps', () => {
it('should handle undefined', () => { it('should handle undefined', () => {
expect(addApp(undefined, {name: 'a'})).toEqual([{name: 'a'}]); expect(addApp(undefined, { name: 'a' })).toEqual([{ name: 'a' }]);
}); });
it('should handle an empty array', () => { it('should handle an empty array', () => {
expect(addApp([], {name: 'a'})).toEqual([{name: 'a'}]); expect(addApp([], { name: 'a' })).toEqual([{ name: 'a' }]);
}); });
it('should sort apps by name', () => { it('should sort apps by name', () => {
expect(addApp([{name: 'a'}, {name: 'b'}], {name: 'c'})).toEqual([{name: 'a'}, {name: 'b'}, {name: 'c'}]); expect(addApp([{ name: 'a' }, { name: 'b' }], { name: 'c' })).toEqual([
{ name: 'a' },
{ name: 'b' },
{ name: 'c' }
]);
}); });
it('should prioritize apps with "main" defined', () => { it('should prioritize apps with "main" defined', () => {
expect(addApp([{name: 'c'}, {name: 'a'}, {name: 'a', main: 'a'}], {name: 'b', main: 'b'})).toEqual([ expect(
{name: 'a', main: 'a'}, {name: 'b', main: 'b'}, {name: 'a'}, {name: 'c'} addApp([{ name: 'c' }, { name: 'a' }, { name: 'a', main: 'a' }], {
]); name: 'b',
main: 'b'
})
).toEqual([{ name: 'a', main: 'a' }, { name: 'b', main: 'b' }, { name: 'a' }, { name: 'c' }]);
}); });
}); });
}); });

View File

@ -1,4 +1,4 @@
export function addApp(apps: any[]|undefined, newApp: any): any[] { export function addApp(apps: any[] | undefined, newApp: any): any[] {
if (!apps) { if (!apps) {
apps = []; apps = [];
} }

View File

@ -4,6 +4,7 @@ export const angularJsVersion = '1.6.6';
export const ngrxVersion = '^4.0.0'; export const ngrxVersion = '^4.0.0';
export const nxVersion = 'nrwl/nx-build'; export const nxVersion = 'nrwl/nx-build';
export const schematicsVersion = 'nrwl/schematics-build'; export const schematicsVersion = 'nrwl/schematics-build';
export const prettierVersion = '1.7.4';
export const libVersions = { export const libVersions = {
angularVersion, angularVersion,
@ -11,5 +12,6 @@ export const libVersions = {
angularJsVersion, angularJsVersion,
ngrxVersion, ngrxVersion,
nxVersion, nxVersion,
schematicsVersion schematicsVersion,
prettierVersion
}; };

View File

@ -1,5 +1,10 @@
export function names(name: string): any { export function names(name: string): any {
return {name, className: toClassName(name), propertyName: toPropertyName(name), fileName: toFileName(name)}; return {
name,
className: toClassName(name),
propertyName: toPropertyName(name),
fileName: toFileName(name)
};
} }
export function toClassName(str: string): string { export function toClassName(str: string): string {
@ -7,12 +12,16 @@ export function toClassName(str: string): string {
} }
export function toPropertyName(s: string): string { export function toPropertyName(s: string): string {
return s.replace(/(-|_|\.|\s)+(.)?/g, (_, __, chr) => chr ? chr.toUpperCase() : '') return s
.replace(/^([A-Z])/, m => m.toLowerCase()); .replace(/(-|_|\.|\s)+(.)?/g, (_, __, chr) => (chr ? chr.toUpperCase() : ''))
.replace(/^([A-Z])/, m => m.toLowerCase());
} }
export function toFileName(s: string): string { export function toFileName(s: string): string {
return s.replace(/([a-z\d])([A-Z])/g, '$1_$2').toLowerCase().replace(/[ _]/g, '-'); return s
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
.toLowerCase()
.replace(/[ _]/g, '-');
} }
function toCapitalCase(s: string): string { function toCapitalCase(s: string): string {

View File

@ -1,3 +0,0 @@
Language: JavaScript
BasedOnStyle: Google
ColumnLimit: 120

View File

@ -1,11 +1,23 @@
import {apply, branchAndMerge, chain, externalSchematic, mergeWith, move, Rule, template, Tree, url, schematic} from '@angular-devkit/schematics'; import {
import {Schema} from './schema'; apply,
branchAndMerge,
chain,
externalSchematic,
mergeWith,
move,
Rule,
template,
Tree,
url,
schematic
} from '@angular-devkit/schematics';
import { Schema } from './schema';
import * as path from 'path'; import * as path from 'path';
import {angularCliVersion, ngrxVersion, nxVersion, schematicsVersion} from '../utility/lib-versions'; import { angularCliVersion, ngrxVersion, nxVersion, prettierVersion, schematicsVersion } from '../utility/lib-versions';
import * as fs from 'fs'; import * as fs from 'fs';
import {join} from 'path'; import { join } from 'path';
import {updateJsonFile} from '../utility/fileutils'; import { updateJsonFile } from '../utility/fileutils';
import {toFileName} from '@nrwl/schematics'; import { toFileName } from '@nrwl/schematics';
function updatePackageJson() { function updatePackageJson() {
return (host: Tree) => { return (host: Tree) => {
@ -43,8 +55,10 @@ function updatePackageJson() {
if (!packageJson.dependencies['@angular/cli']) { if (!packageJson.dependencies['@angular/cli']) {
packageJson.dependencies['@angular/cli'] = angularCliVersion; packageJson.dependencies['@angular/cli'] = angularCliVersion;
} }
packageJson.scripts['format'] = if (!packageJson.devDependencies['prettier']) {
`find apps/ -iname '*.ts' | xargs clang-format -i && find libs/ -iname '*.ts' | xargs clang-format -i`; packageJson.devDependencies['prettier'] = prettierVersion;
}
packageJson.scripts['format'] = `prettier --single-quote --print-width 120 --write '{apps,libs}/**/*.ts'`;
host.overwrite('package.json', JSON.stringify(packageJson, null, 2)); host.overwrite('package.json', JSON.stringify(packageJson, null, 2));
return host; return host;
}; };
@ -60,8 +74,11 @@ function updateAngularCLIJson(options: Schema) {
throw new Error('Can only convert projects with one app'); throw new Error('Can only convert projects with one app');
} }
angularCliJson.lint = angularCliJson.lint = [
[{'project': './tsconfig.app.json'}, {'project': './tsconfig.spec.json'}, {'project': './tsconfig.e2e.json'}]; { project: './tsconfig.app.json' },
{ project: './tsconfig.spec.json' },
{ project: './tsconfig.e2e.json' }
];
const app = angularCliJson.apps[0]; const app = angularCliJson.apps[0];
app.root = path.join('apps', options.name, app.root); app.root = path.join('apps', options.name, app.root);
@ -69,7 +86,7 @@ function updateAngularCLIJson(options: Schema) {
app.test = '../../../test.js'; app.test = '../../../test.js';
app.tsconfig = '../../../tsconfig.app.json'; app.tsconfig = '../../../tsconfig.app.json';
app.testTsconfig = '../../../tsconfig.spec.json'; app.testTsconfig = '../../../tsconfig.spec.json';
app.scripts = app.scripts.map((p) => path.join('../../', p)); app.scripts = app.scripts.map(p => path.join('../../', p));
if (!angularCliJson.defaults) { if (!angularCliJson.defaults) {
angularCliJson.defaults = {}; angularCliJson.defaults = {};
} }
@ -90,16 +107,16 @@ function updateTsConfigsJson(options: Schema) {
return (host: Tree) => { return (host: Tree) => {
const npmScope = options && options.npmScope ? options.npmScope : options.name; const npmScope = options && options.npmScope ? options.npmScope : options.name;
updateJsonFile('tsconfig.json', (json) => setUpCompilerOptions(json, npmScope)); updateJsonFile('tsconfig.json', json => setUpCompilerOptions(json, npmScope));
updateJsonFile('tsconfig.app.json', (json) => { updateJsonFile('tsconfig.app.json', json => {
json['extends'] = './tsconfig.json'; json['extends'] = './tsconfig.json';
if (!json.exclude) json.exclude = []; if (!json.exclude) json.exclude = [];
json.exclude = dedup(json.exclude.concat(['**/*.spec.ts', '**/*.e2e-spec.ts', 'node_modules', 'tmp'])); json.exclude = dedup(json.exclude.concat(['**/*.spec.ts', '**/*.e2e-spec.ts', 'node_modules', 'tmp']));
setUpCompilerOptions(json, npmScope); setUpCompilerOptions(json, npmScope);
}); });
updateJsonFile('tsconfig.spec.json', (json) => { updateJsonFile('tsconfig.spec.json', json => {
json['extends'] = './tsconfig.json'; json['extends'] = './tsconfig.json';
if (!json.exclude) json.exclude = []; if (!json.exclude) json.exclude = [];
json.files = ['test.js']; json.files = ['test.js'];
@ -107,7 +124,7 @@ function updateTsConfigsJson(options: Schema) {
setUpCompilerOptions(json, npmScope); setUpCompilerOptions(json, npmScope);
}); });
updateJsonFile('tsconfig.e2e.json', (json) => { updateJsonFile('tsconfig.e2e.json', json => {
json['extends'] = './tsconfig.json'; json['extends'] = './tsconfig.json';
if (!json.exclude) json.exclude = []; if (!json.exclude) json.exclude = [];
json.exclude = dedup(json.exclude.concat(['**/*.spec.ts', 'node_modules', 'tmp'])); json.exclude = dedup(json.exclude.concat(['**/*.spec.ts', 'node_modules', 'tmp']));
@ -122,11 +139,11 @@ function updateTsLintJson(options: Schema) {
return (host: Tree) => { return (host: Tree) => {
const npmScope = options && options.npmScope ? options.npmScope : options.name; const npmScope = options && options.npmScope ? options.npmScope : options.name;
updateJsonFile('tslint.json', (json) => { updateJsonFile('tslint.json', json => {
['no-trailing-whitespace', 'one-line', 'quotemark', 'typedef-whitespace', 'whitespace'].forEach(key => { ['no-trailing-whitespace', 'one-line', 'quotemark', 'typedef-whitespace', 'whitespace'].forEach(key => {
json[key] = undefined; json[key] = undefined;
}); });
json['nx-enforce-module-boundaries'] = [true, {'npmScope': npmScope, 'lazyLoad': []}]; json['nx-enforce-module-boundaries'] = [true, { npmScope: npmScope, lazyLoad: [] }];
}); });
return host; return host;
}; };
@ -138,8 +155,9 @@ function updateProtractorConf() {
throw new Error('Cannot find protractor.conf.js'); throw new Error('Cannot find protractor.conf.js');
} }
const protractorConf = host.read('protractor.conf.js')!.toString('utf-8'); const protractorConf = host.read('protractor.conf.js')!.toString('utf-8');
const updatedConf = protractorConf.replace(`./e2e/**/*.e2e-spec.ts`, `./apps/**/*.e2e-spec.ts`) const updatedConf = protractorConf
.replace(`e2e/tsconfig.e2e.json`, `./tsconfig.e2e.json`); .replace(`./e2e/**/*.e2e-spec.ts`, `./apps/**/*.e2e-spec.ts`)
.replace(`e2e/tsconfig.e2e.json`, `./tsconfig.e2e.json`);
host.overwrite('protractor.conf.js', updatedConf); host.overwrite('protractor.conf.js', updatedConf);
@ -186,12 +204,14 @@ function dedup(array: any[]): any[] {
} }
export default function(schema: Schema): Rule { export default function(schema: Schema): Rule {
const options = {...schema, name: toFileName(schema.name)}; const options = { ...schema, name: toFileName(schema.name) };
return chain([ return chain([
moveFiles(options), branchAndMerge(chain([ moveFiles(options),
mergeWith(apply(url('./files'), [])), branchAndMerge(chain([mergeWith(apply(url('./files'), []))])),
])), updatePackageJson(),
updatePackageJson(), updateAngularCLIJson(options), updateTsConfigsJson(options), updateProtractorConf(), updateAngularCLIJson(options),
updateTsConfigsJson(options),
updateProtractorConf(),
updateTsLintJson(options) updateTsLintJson(options)
]); ]);
} }

View File

@ -1,15 +1,3 @@
#!/usr/bin/env bash #!/usr/bin/env bash
find packages/ -iname "*.ts" | xargs -n1 clang-format -output-replacements-xml | grep -c "<replacement " >/dev/null prettier --single-quote --print-width 120 --list-different '{packages,e2e}/**/*.ts'
if [ $? -ne 1 ]
then
echo "Please run yarn format"
exit 1;
fi
find e2e/ -iname "*.ts" | xargs -n1 clang-format -output-replacements-xml | grep -c "<replacement " >/dev/null
if [ $? -ne 1 ]
then
echo "Please run yarn format"
exit 1;
fi

View File

@ -1,4 +1,3 @@
#!/usr/bin/env bash #!/usr/bin/env bash
find packages/ -iname "*.ts" | xargs clang-format -i prettier --single-quote --print-width 120 --write '{packages,e2e}/**/*.ts'
find e2e/ -iname "*.ts" | xargs clang-format -i

View File

@ -968,14 +968,6 @@ circular-dependency-plugin@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-3.0.0.tgz#9b68692e35b0e3510998d0164b6ae5011bea5760" resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-3.0.0.tgz#9b68692e35b0e3510998d0164b6ae5011bea5760"
clang-format@1.0.55:
version "1.0.55"
resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.0.55.tgz#8031271329e27a779a5d08fc5dce24d7c52c14d5"
dependencies:
async "^1.5.2"
glob "^7.0.0"
resolve "^1.1.6"
clap@^1.0.9: clap@^1.0.9:
version "1.2.3" version "1.2.3"
resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51"
@ -4532,6 +4524,10 @@ preserve@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@1.7.4:
version "1.7.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.7.4.tgz#5e8624ae9363c80f95ec644584ecdf55d74f93fa"
pretty-error@^2.0.2: pretty-error@^2.0.2:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3"
@ -4902,7 +4898,7 @@ resolve@1.1.7:
version "1.1.7" version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: resolve@^1.1.7, resolve@^1.3.2:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86"
dependencies: dependencies: