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', () => {
beforeEach(cleanup);
@ -9,8 +9,14 @@ describe('application', () => {
runSchematic('@nrwl/bazel:app --name=myApp');
checkFilesExist(
`tsconfig.json`, `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`);
`tsconfig.json`,
`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]');

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', () => {
beforeEach(cleanup);
@ -9,8 +9,13 @@ describe('library', () => {
runSchematic('@nrwl/bazel:lib --name=myLib');
checkFilesExist(
'tsconfig.json', 'WORKSPACE', 'BUILD.bazel', 'libs/my-lib/BUILD.bazel', 'libs/my-lib/index.ts',
'libs/my-lib/src/my-lib.ts');
'tsconfig.json',
'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'));
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', () => {
beforeEach(cleanup);
@ -9,8 +20,13 @@ describe('angular library', () => {
runSchematic('@nrwl/bazel:nglib --name=myLib');
checkFilesExist(
'tsconfig.json', 'WORKSPACE', 'BUILD.bazel', 'libs/my-lib/BUILD.bazel', 'libs/my-lib/index.ts',
'libs/my-lib/src/my-lib.module.ts');
'tsconfig.json',
'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'));
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', () => {
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', () => {
beforeEach(cleanup);
@ -16,6 +26,7 @@ describe('Nrwl Workspace', () => {
expect(packageJson.dependencies['@ngrx/effects']).toBeDefined();
expect(packageJson.dependencies['@ngrx/router-store']).toBeDefined();
expect(packageJson.dependencies['@ngrx/store-devtools']).toBeDefined();
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');
checkFilesExist(
'apps/myapp/src/main.ts', 'apps/myapp/src/app/app.module.ts', 'apps/myapp/src/app/app.component.ts',
'apps/myapp/e2e/app.po.ts');
'apps/myapp/src/main.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', () => {
ngNew('--collection=@nrwl/schematics');
copyMissingPackages();
newApp('myapp');
runCLI('build --aot');
checkFilesExist('dist/apps/myapp/main.bundle.js');
expect(runCLI('test --single-run')).toContain('Executed 1 of 1 SUCCESS');
}, 100000);
it(
'should build app',
() => {
ngNew('--collection=@nrwl/schematics');
copyMissingPackages();
newApp('myapp');
runCLI('build --aot');
checkFilesExist('dist/apps/myapp/main.bundle.js');
expect(runCLI('test --single-run')).toContain('Executed 1 of 1 SUCCESS');
},
100000
);
});
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');
});
it('should test a lib', () => {
ngNew('--collection=@nrwl/schematics');
copyMissingPackages();
newApp('myapp');
newLib('generate lib mylib');
it(
'should test a lib',
() => {
ngNew('--collection=@nrwl/schematics');
copyMissingPackages();
newApp('myapp');
newLib('generate lib mylib');
expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS');
}, 100000);
expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS');
},
100000
);
});
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');
});
it('should test an ng lib', () => {
ngNew('--collection=@nrwl/schematics');
copyMissingPackages();
newApp('myapp');
newLib('mylib --ngmodule');
it(
'should test an ng lib',
() => {
ngNew('--collection=@nrwl/schematics');
copyMissingPackages();
newApp('myapp');
newLib('mylib --ngmodule');
expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS');
}, 100000);
expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS');
},
100000
);
it('should resolve dependencies on the lib', () => {
ngNew('--collection=@nrwl/schematics --npmScope=nrwl');
copyMissingPackages();
newApp('myapp');
newLib('mylib --ngmodule');
it(
'should resolve dependencies on the lib',
() => {
ngNew('--collection=@nrwl/schematics --npmScope=nrwl');
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 { BrowserModule } from '@angular/platform-browser';
import { MylibModule } from '@nrwl/mylib';
@ -98,9 +128,12 @@ describe('Nrwl Workspace', () => {
bootstrap: [AppComponent]
})
export class AppModule {}
`);
`
);
runCLI('build --aot');
}, 100000);
runCLI('build --aot');
},
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', () => {
beforeEach(cleanup);
@ -9,35 +9,48 @@ describe('ngrx', () => {
runCLI('generate ngrx app --module=src/app/app.module.ts --root --collection=@nrwl/schematics');
checkFilesExist(
`src/app/+state/app.actions.ts`, `src/app/+state/app.effects.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`);
`src/app/+state/app.actions.ts`,
`src/app/+state/app.effects.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');
expect(contents).toContain('StoreModule.forRoot');
expect(contents).toContain('EffectsModule.forRoot');
});
it('should build', () => {
ngNew();
copyMissingPackages();
runCLI('generate ngrx app --module=src/app/app.module.ts --root --collection=@nrwl/schematics');
it(
'should build',
() => {
ngNew();
copyMissingPackages();
runCLI('generate ngrx app --module=src/app/app.module.ts --root --collection=@nrwl/schematics');
runCLI('build');
runCLI('test --single-run');
}, 100000);
runCLI('build');
runCLI('test --single-run');
},
100000
);
it('should add empty root configuration', () => {
ngNew();
copyMissingPackages();
runCLI('generate ngrx app --module=src/app/app.module.ts --onlyEmptyRoot --collection=@nrwl/schematics');
it(
'should add empty root configuration',
() => {
ngNew();
copyMissingPackages();
runCLI('generate ngrx app --module=src/app/app.module.ts --onlyEmptyRoot --collection=@nrwl/schematics');
const contents = readFile('src/app/app.module.ts');
expect(contents).toContain('StoreModule.forRoot');
expect(contents).toContain('EffectsModule.forRoot');
const contents = readFile('src/app/app.module.ts');
expect(contents).toContain('StoreModule.forRoot');
expect(contents).toContain('EffectsModule.forRoot');
runCLI('build');
}, 100000);
runCLI('build');
},
100000
);
});
describe('feature', () => {
@ -46,9 +59,14 @@ describe('ngrx', () => {
runCLI('generate ngrx app --module=src/app/app.module.ts --collection=@nrwl/schematics');
checkFilesExist(
`src/app/+state/app.actions.ts`, `src/app/+state/app.effects.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`);
`src/app/+state/app.actions.ts`,
`src/app/+state/app.effects.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');
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');
checkFilesExist(
`src/app/+state/app.actions.ts`, `src/app/+state/app.effects.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`);
`src/app/+state/app.actions.ts`,
`src/app/+state/app.effects.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');
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', () => {
beforeEach(cleanup);
it('should ensure module boundaries', () => {
ngNew('--collection=@nrwl/schematics');
copyMissingPackages();
newApp('myapp');
newLib('mylib');
newLib('lazylib');
it(
'should ensure module boundaries',
() => {
ngNew('--collection=@nrwl/schematics');
copyMissingPackages();
newApp('myapp');
newLib('mylib');
newLib('lazylib');
const tslint = JSON.parse(readFile('tslint.json'));
tslint.rules['nx-enforce-module-boundaries'][1].lazyLoad.push('lazylib');
updateFile('tslint.json', JSON.stringify(tslint, null, 2));
const tslint = JSON.parse(readFile('tslint.json'));
tslint.rules['nx-enforce-module-boundaries'][1].lazyLoad.push('lazylib');
updateFile('tslint.json', JSON.stringify(tslint, null, 2));
updateFile('apps/myapp/src/main.ts', `
updateFile(
'apps/myapp/src/main.ts',
`
import '../../../libs/mylib';
import '@proj/lazylib';
`);
`
);
const out = runCLI('lint --type-check', {silenceError: true});
expect(out).toContain('relative imports of libraries are forbidden');
expect(out).toContain('import of lazy-loaded libraries are forbidden');
}, 100000);
const out = runCLI('lint --type-check', { silenceError: true });
expect(out).toContain('relative imports of libraries are forbidden');
expect(out).toContain('import of lazy-loaded libraries are forbidden');
},
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', () => {
beforeEach(cleanup);
it('should generate an upgrade shell', () => {
ngNew('--collection=@nrwl/schematics');
newApp('myapp');
it(
'should generate an upgrade shell',
() => {
ngNew('--collection=@nrwl/schematics');
newApp('myapp');
copyMissingPackages();
updateFile('apps/myapp/src/legacy.js', `
copyMissingPackages();
updateFile(
'apps/myapp/src/legacy.js',
`
const angular = window.angular.module('legacy', []);
angular.component('rootLegacyCmp', {
template: 'Expected Value'
});
`);
`
);
updateFile('apps/myapp/src/app/app.component.html', `
updateFile(
'apps/myapp/src/app/app.component.html',
`
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 ' +
'--angularJsCmpSelector=rootLegacyCmp');
'--angularJsCmpSelector=rootLegacyCmp'
);
runCLI('build');
runCLI('test --single-run');
}, 100000);
runCLI('build');
runCLI('test --single-run');
},
100000
);
it('should update package.json', () => {
ngNew('--skip-install');
runCLI(
'generate upgrade-shell legacy --module=src/app/app.module.ts --angularJsImport=../legacy ' +
'--angularJsCmpSelector=rootLegacyCmp --collection=@nrwl/schematics');
'generate upgrade-shell legacy --module=src/app/app.module.ts --angularJsImport=../legacy ' +
'--angularJsCmpSelector=rootLegacyCmp --collection=@nrwl/schematics'
);
const contents = JSON.parse(readFile('package.json'));
expect(contents.dependencies['@angular/upgrade']).toBeDefined();
@ -43,8 +55,9 @@ describe('Upgrade', () => {
it('should not update package.json when --skipPackageJson', () => {
ngNew('--skipInstall');
runCLI(
'generate upgrade-shell legacy --module=src/app/app.module.ts --angularJsImport=../legacy ' +
'--angularJsCmpSelector=rootLegacyCmp --skipPackageJson --collection=@nrwl/schematics');
'generate upgrade-shell legacy --module=src/app/app.module.ts --angularJsImport=../legacy ' +
'--angularJsCmpSelector=rootLegacyCmp --skipPackageJson --collection=@nrwl/schematics'
);
const contents = JSON.parse(readFile('package.json'));
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', () => {
beforeEach(cleanup);
@ -20,7 +29,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
// update 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));
// update angular-cli.json
@ -56,8 +65,10 @@ describe('Nrwl Convert to Nx Workspace', () => {
// check if tsconfig.json get merged
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', () => {
@ -87,14 +98,18 @@ describe('Nrwl Convert to Nx Workspace', () => {
expect(packageJson.dependencies['@ngrx/store-devtools']).toEqual(ngrxVersion);
});
it('should build and test and support the existing AngularCLI generators', () => {
ngNew();
copyMissingPackages();
it(
'should build and test and support the existing AngularCLI generators',
() => {
ngNew();
copyMissingPackages();
runCLI('generate workspace proj --collection=@nrwl/schematics');
runCLI('generate lib mylib --ngmodule');
runCLI('generate workspace proj --collection=@nrwl/schematics');
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 { BrowserModule } from '@angular/platform-browser';
import { MylibModule } from '@proj/mylib';
@ -106,20 +121,23 @@ describe('Nrwl Convert to Nx Workspace', () => {
bootstrap: [AppComponent]
})
export class AppModule {}
`);
`
);
expect(runCLI('build --aot')).toContain('{main} main.bundle.js');
expect(runCLI('test --single-run')).toContain('Executed 4 of 4 SUCCESS');
expect(runCLI('e2e')).toContain('Executed 1 of 1 spec SUCCESS');
const generatorHelpText = runCLI('g -h');
expect(generatorHelpText).toContain('class');
expect(generatorHelpText).toContain('component');
expect(generatorHelpText).toContain('directive');
expect(generatorHelpText).toContain('enum');
expect(generatorHelpText).toContain('guard');
expect(generatorHelpText).toContain('interface');
expect(generatorHelpText).toContain('module');
expect(generatorHelpText).toContain('pipe');
expect(generatorHelpText).toContain('service');
}, 100000);
expect(runCLI('build --aot')).toContain('{main} main.bundle.js');
expect(runCLI('test --single-run')).toContain('Executed 4 of 4 SUCCESS');
expect(runCLI('e2e')).toContain('Executed 1 of 1 spec SUCCESS');
const generatorHelpText = runCLI('g -h');
expect(generatorHelpText).toContain('class');
expect(generatorHelpText).toContain('component');
expect(generatorHelpText).toContain('directive');
expect(generatorHelpText).toContain('enum');
expect(generatorHelpText).toContain('guard');
expect(generatorHelpText).toContain('interface');
expect(generatorHelpText).toContain('module');
expect(generatorHelpText).toContain('pipe');
expect(generatorHelpText).toContain('service');
},
100000
);
});

View File

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

View File

@ -38,7 +38,7 @@
"@types/jasmine": "2.5.53",
"@types/node": "8.0.7",
"angular": "1.6.6",
"clang-format": "1.0.55",
"prettier": "1.7.4",
"jasmine-core": "~2.6.2",
"jest": "20.0.4",
"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 {Schema} from './schema';
import {names, toFileName, insert} from '@nrwl/schematics';
import {
apply,
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 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 {
return (host: Tree) => {
@ -32,7 +42,10 @@ function addAppToAngularCliJson(fullPath: string, options: Schema): Rule {
styles: ['styles.css'],
scripts: [],
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));
@ -40,18 +53,17 @@ function addAppToAngularCliJson(fullPath: string, options: Schema): Rule {
};
}
export default function(options: Schema): Rule {
const fullPath = path.join(options.directory, toFileName(options.name), options.sourceDir);
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', {
name: 'app',
commonModule: false,
flat: true,
routing: options.routing,
sourceDir: fullPath,
spec: false,
spec: false
}),
externalSchematic('@schematics/angular', 'component', {
name: 'app',
@ -65,6 +77,7 @@ export default function(options: Schema): Rule {
viewEncapsulation: options.viewEncapsulation,
changeDetection: options.changeDetection
}),
addBootstrap(fullPath), addAppToAngularCliJson(fullPath, options)
addBootstrap(fullPath),
addAppToAngularCliJson(fullPath, options)
]);
}

View File

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

View File

@ -1,12 +1,12 @@
import {apply, branchAndMerge, chain, mergeWith, Rule, template, Tree, url} from '@angular-devkit/schematics';
import {Schema} from './schema';
import { apply, branchAndMerge, chain, mergeWith, Rule, template, Tree, url } from '@angular-devkit/schematics';
import { Schema } from './schema';
import * as path from 'path';
import {names, toFileName} from '@nrwl/schematics';
import { names, toFileName } from '@nrwl/schematics';
function addLibToAngularCliJson(fullPath: string, schema: Schema): Rule {
return (host: Tree) => {
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));
return host;
};
@ -15,9 +15,7 @@ function addLibToAngularCliJson(fullPath: string, schema: Schema): Rule {
export default function(options: any): Rule {
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([
branchAndMerge(chain([mergeWith(templateSource), addLibToAngularCliJson(fullPath, options)])),
]);
return chain([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 {Schema} from './schema';
import { apply, branchAndMerge, chain, mergeWith, Rule, template, Tree, url } from '@angular-devkit/schematics';
import { Schema } from './schema';
import * as path from 'path';
import {names, toFileName} from '@nrwl/schematics';
import { names, toFileName } from '@nrwl/schematics';
function addLibToAngularCliJson(fullPath: string, schema: Schema): Rule {
return (host: Tree) => {
const source = JSON.parse(host.read('.angular-cli.json')!.toString('utf-8'));
source.apps.push(
{name: schema.name, root: fullPath, appDir: false, prefix: schema.prefix ? schema.prefix : schema.name});
source.apps.push({
name: schema.name,
root: fullPath,
appDir: false,
prefix: schema.prefix ? schema.prefix : schema.name
});
host.overwrite('.angular-cli.json', JSON.stringify(source, null, 2));
return host;
};
@ -16,9 +20,7 @@ function addLibToAngularCliJson(fullPath: string, schema: Schema): Rule {
export default function(options: any): Rule {
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([
branchAndMerge(chain([mergeWith(templateSource), addLibToAngularCliJson(fullPath, options)])),
]);
return chain([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 {Schema} from './schema';
import {names} from '@nrwl/schematics';
import { apply, chain, mergeWith, move, Rule, schematic, template, url } from '@angular-devkit/schematics';
import { Schema } from './schema';
import { names } from '@nrwl/schematics';
export default function(options: Schema): Rule {
return chain([mergeWith(apply(
url('./files'), [template({...options, ...names(options.name), 'dot': '.', 'tmpl': ''}), move(options.name!)]))]);
return chain([
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 {NxModule} from './src/nx.module';
export { DataPersistence } from './src/data-persistence';
export { NxModule } from './src/nx.module';

View File

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

View File

@ -1,9 +1,9 @@
import {from} from 'rxjs/observable/from'
import {readAll, readFirst} from '../src/testing-utils';
import { from } from 'rxjs/observable/from';
import { readAll, readFirst } from '../src/testing-utils';
describe('TestingUtils', () => {
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 result = await readAll(obs);
@ -14,7 +14,7 @@ describe('TestingUtils', () => {
});
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 result = await readFirst(obs);

View File

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

View File

@ -1,5 +1,5 @@
import {ModuleWithProviders, NgModule} from '@angular/core';
import {DataPersistence} from './data-persistence';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { DataPersistence } from './data-persistence';
/**
* @whatItDoes Provides services for enterprise Angular applications.
@ -9,6 +9,6 @@ import {DataPersistence} from './data-persistence';
@NgModule({})
export class NxModule {
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 {first} from 'rxjs/operator/first';
import {toArray} from 'rxjs/operator/toArray';
import {toPromise} from 'rxjs/operator/toPromise';
import { Observable } from 'rxjs/Observable';
import { first } from 'rxjs/operator/first';
import { toArray } from 'rxjs/operator/toArray';
import { toPromise } from 'rxjs/operator/toPromise';
/**
* @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 {readAll, readFirst} from './src/testing-utils';
export { cold, hot } from 'jasmine-marbles';
export { readAll, readFirst } from './src/testing-utils';

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@
"test": "ng test",
"lint": "ng lint",
"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,
"dependencies": {
@ -52,6 +52,6 @@
"ts-node": "~3.2.0",
"tslint": "~5.3.2",<% } %>
"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 {Schema} from './schema';
import { apply, branchAndMerge, chain, mergeWith, move, Rule, template, Tree, url } from '@angular-devkit/schematics';
import { Schema } from './schema';
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 {
const npmScope = options.npmScope ? options.npmScope : options.name;
const templateSource =
apply(url('./files'), [template({utils: stringUtils, dot: '.', ...libVersions, ...options as object, npmScope})]);
const templateSource = apply(url('./files'), [
template({
utils: stringUtils,
dot: '.',
...libVersions,
...(options as object),
npmScope
})
]);
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 {Schema} from './schema';
import {names, toFileName} from '@nrwl/schematics';
import {
apply,
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 {addApp} from '../utility/config-file-utils';
import { addApp } from '../utility/config-file-utils';
function addLibToAngularCliJson(options: Schema): Rule {
return (host: Tree) => {
@ -12,8 +23,12 @@ function addLibToAngularCliJson(options: Schema): Rule {
const sourceText = host.read('.angular-cli.json')!.toString('utf-8');
const json = JSON.parse(sourceText);
json.apps =
addApp(json.apps, {'name': options.name, 'root': fullPath(options), 'test': '../../../test.js', 'appRoot': ''});
json.apps = addApp(json.apps, {
name: options.name,
root: fullPath(options),
test: '../../../test.js',
appRoot: ''
});
host.overwrite('.angular-cli.json', JSON.stringify(json, null, 2));
return host;
@ -21,12 +36,17 @@ function addLibToAngularCliJson(options: 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 templateSource = apply(
url(options.ngmodule ? './ngfiles' : './files'),
[template({...names(options.name), dot: '.', tmpl: '', ...options as object})]);
const templateSource = apply(url(options.ngmodule ? './ngfiles' : './files'), [
template({
...names(options.name),
dot: '.',
tmpl: '',
...(options as object)
})
]);
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 ts from 'typescript';
import {addImportToModule, addProviderToModule, insert, offset} from '../utility/ast-utils';
import {insertImport} from '@schematics/angular/utility/route-utils';
import {Schema} from './schema';
import {InsertChange} from '@schematics/angular/utility/change';
import {ngrxVersion} from '../utility/lib-versions';
import { addImportToModule, addProviderToModule, insert, offset } from '../utility/ast-utils';
import { insertImport } from '@schematics/angular/utility/route-utils';
import { Schema } from './schema';
import { InsertChange } from '@schematics/angular/utility/change';
import { ngrxVersion } from '../utility/lib-versions';
function addImportsToModule(name: string, options: Schema): Rule {
return (host: Tree) => {
@ -35,7 +46,6 @@ function addImportsToModule(name: string, options: Schema): Rule {
...addImportToModule(source, modulePath, `!environment.production ? StoreDevtoolsModule.instrument() : []`)
]);
return host;
} else {
const reducerPath = `./+state/${toFileName(name)}.reducer`;
const effectsPath = `./+state/${toFileName(name)}.effects`;
@ -61,14 +71,16 @@ function addImportsToModule(name: string, options: Schema): Rule {
insertImport(source, modulePath, 'environment', '../environments/environment'),
...addImportToModule(source, modulePath, `StoreModule.forRoot(${reducerName}, {initialState: ${initName}})`),
...addImportToModule(source, modulePath, `EffectsModule.forRoot([${effectsName}])`),
...addImportToModule(source, modulePath, `!environment.production ? StoreDevtoolsModule.instrument() : []`),
...addImportToModule(source, modulePath, `!environment.production ? StoreDevtoolsModule.instrument() : []`)
]);
} else {
insert(host, modulePath, [
...common,
...addImportToModule(
source, modulePath,
`StoreModule.forFeature('${toPropertyName(name)}', ${reducerName}, {initialState: ${initName}})`),
source,
modulePath,
`StoreModule.forFeature('${toPropertyName(name)}', ${reducerName}, {initialState: ${initName}})`
),
...addImportToModule(source, modulePath, `EffectsModule.forFeature([${effectsName}])`)
]);
}
@ -112,9 +124,10 @@ export default function(options: Schema): Rule {
if (options.onlyEmptyRoot) {
return chain([addImportsToModule(name, options), options.skipPackageJson ? noop() : addNgRxToPackageJson()]);
} 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([
branchAndMerge(chain([mergeWith(templateSource)])), addImportsToModule(name, options),
branchAndMerge(chain([mergeWith(templateSource)])),
addImportsToModule(name, options),
options.skipPackageJson ? noop() : addNgRxToPackageJson()
]);
}

View File

@ -1,14 +1,17 @@
import {RuleFailure} from 'tslint';
import { RuleFailure } from 'tslint';
import * as ts from 'typescript';
import {Rule} from './nxEnforceModuleBoundariesRule';
import { Rule } from './nxEnforceModuleBoundariesRule';
describe('Enforce Module Boundaries', () => {
it('should not error when everything is in order', () => {
const failures = runRule({npmScope: 'mycompany'}, `
const failures = runRule(
{ npmScope: 'mycompany' },
`
import '@mycompany/mylib';
import '../blah';
`);
`
);
expect(failures.length).toEqual(0);
});
@ -21,14 +24,14 @@ describe('Enforce Module Boundaries', () => {
});
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[0].getFailure()).toEqual('deep imports into libraries are forbidden');
});
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[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[] {
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 rule = new Rule(options, 'proj');

View File

@ -1,6 +1,6 @@
import * as path from 'path';
import * as Lint from 'tslint';
import {IOptions} from 'tslint';
import { IOptions } from 'tslint';
import * as ts from 'typescript';
export class Rule extends Lint.Rules.AbstractRule {
@ -29,10 +29,8 @@ class EnforceModuleBoundariesWalker extends Lint.RuleWalker {
if (impParts[0] === npmScope && impParts.length > 2) {
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) {
this.addFailureAt(node.getStart(), node.getWidth(), 'import of lazy-loaded libraries are forbidden');
} else if (this.isRelative(imp) && this.isRelativeImportIntoAnotherProject(imp)) {
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 ts from 'typescript';
import {addDeclarationToModule, addEntryComponents, 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';
import {
addDeclarationToModule,
addEntryComponents,
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 {
return (host: Tree) => {
@ -22,8 +43,11 @@ function addImportsToModule(moduleClassName: string, options: Schema): Rule {
insert(host, modulePath, [
insertImport(
source, modulePath, `configure${toClassName(options.name)}, upgradedComponents`,
`./${toFileName(options.name)}-setup`),
source,
modulePath,
`configure${toClassName(options.name)}, upgradedComponents`,
`./${toFileName(options.name)}-setup`
),
insertImport(source, modulePath, 'UpgradeModule', '@angular/upgrade/static'),
...addImportToModule(source, modulePath, 'UpgradeModule'),
...addDeclarationToModule(source, modulePath, '...upgradedComponents'),
@ -34,7 +58,6 @@ function addImportsToModule(moduleClassName: string, options: Schema): Rule {
};
}
function addNgDoBootstrapToModule(moduleClassName: string, options: Schema): Rule {
return (host: Tree) => {
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);
insert(host, modulePath, [
...addParameterToConstructor(
source, modulePath, {className: moduleClassName, param: 'private upgrade: UpgradeModule'}),
...addParameterToConstructor(source, modulePath, {
className: moduleClassName,
param: 'private upgrade: UpgradeModule'
}),
...addMethod(source, modulePath, {
className: moduleClassName,
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 bootstrapComponentClassName = getBootstrapComponent(moduleSource, moduleClassName);
const bootstrapComponentFileName =
`${toFileName(bootstrapComponentClassName.substring(0, bootstrapComponentClassName.length - 9))}.component`;
const bootstrapComponentFileName = `${toFileName(
bootstrapComponentClassName.substring(0, bootstrapComponentClassName.length - 9)
)}.component`;
const moduleDir = path.dirname(options.module);
const templateSource = apply(url('./files'), [
@ -97,7 +123,6 @@ function createFiles(angularJsImport: string, moduleClassName: string, moduleFil
};
}
function addUpgradeToPackageJson() {
return (host: Tree) => {
if (!host.exists('package.json')) return host;
@ -127,7 +152,8 @@ export default function(options: Schema): Rule {
return chain([
createFiles(angularJsImport, moduleClassName, moduleFileName, options),
addImportsToModule(moduleClassName, options), addNgDoBootstrapToModule(moduleClassName, options),
addImportsToModule(moduleClassName, options),
addNgDoBootstrapToModule(moduleClassName, options),
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
* found in the LICENSE file at https://angular.io/license
*/
import {Tree} from '@angular-devkit/schematics';
import {getDecoratorMetadata, getSourceNodes, insertAfterLastOccurrence} from '@schematics/angular/utility/ast-utils';
import {Change, InsertChange, NoopChange, RemoveChange} from '@schematics/angular/utility/change';
import { Tree } from '@angular-devkit/schematics';
import { getDecoratorMetadata, getSourceNodes, insertAfterLastOccurrence } from '@schematics/angular/utility/ast-utils';
import { Change, InsertChange, NoopChange, RemoveChange } from '@schematics/angular/utility/change';
import * as ts from 'typescript';
// This should be moved to @schematics/angular once it allows to pass custom expressions as providers
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');
let node: any = nodes[0]; // tslint:disable-line:no-any
let node: any = nodes[0]; // tslint:disable-line:no-any
// Find the decorator declaration.
if (!node) {
@ -23,23 +26,21 @@ function _addSymbolToNgModuleMetadata(
}
// Get all the children property assignment of object literals.
const matchingProperties: ts.ObjectLiteralElement[] =
(node as ts.ObjectLiteralExpression)
.properties
.filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment)
// Filter out every fields that's not "metadataField". Also handles string literals
// (but not expressions).
.filter((prop: ts.PropertyAssignment) => {
const name = prop.name;
switch (name.kind) {
case ts.SyntaxKind.Identifier:
return (name as ts.Identifier).getText(source) == metadataField;
case ts.SyntaxKind.StringLiteral:
return (name as ts.StringLiteral).text == metadataField;
}
const matchingProperties: ts.ObjectLiteralElement[] = (node as ts.ObjectLiteralExpression).properties
.filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment)
// Filter out every fields that's not "metadataField". Also handles string literals
// (but not expressions).
.filter((prop: ts.PropertyAssignment) => {
const name = prop.name;
switch (name.kind) {
case ts.SyntaxKind.Identifier:
return (name as ts.Identifier).getText(source) == metadataField;
case ts.SyntaxKind.StringLiteral:
return (name as ts.StringLiteral).text == metadataField;
}
return false;
});
return false;
});
// Get the last node of the array literal.
if (!matchingProperties) {
@ -90,7 +91,7 @@ function _addSymbolToNgModuleMetadata(
}
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());
if (symbolsArray.includes(expression)) {
return [];
@ -137,27 +138,37 @@ function _addSymbolToNgModuleMetadata(
}
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 constructor = clazz.members.filter(m => m.kind === ts.SyntaxKind.Constructor)[0];
if (constructor) {
throw new Error('Should be tested');
} else {
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(
source: ts.SourceFile, modulePath: string,
opts: {className: string, methodHeader: string, body: string}): Change[] {
source: ts.SourceFile,
modulePath: string,
opts: { className: string; methodHeader: string; body: string }
): Change[] {
const clazz = findClass(source, opts.className);
const body = opts.body ? `
const body = opts.body
? `
${opts.methodHeader} {
${offset(opts.body, 1, false)}
}
` :
`
`
: `
${opts.methodHeader} {}
`;
@ -167,7 +178,7 @@ ${opts.methodHeader} {}
export function removeFromNgModule(source: ts.SourceFile, modulePath: string, property: string): Change[] {
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.
if (!node) {
@ -177,7 +188,7 @@ export function removeFromNgModule(source: ts.SourceFile, modulePath: string, pr
// Get all the children property assignment of object literals.
const matchingProperty = getMatchingProperty(source, property);
if (matchingProperty) {
return [new RemoveChange(modulePath, matchingProperty.pos, matchingProperty.getFullText(source))]
return [new RemoveChange(modulePath, matchingProperty.pos, matchingProperty.getFullText(source))];
} else {
return [];
}
@ -186,8 +197,9 @@ export function removeFromNgModule(source: ts.SourceFile, modulePath: string, pr
function findClass(source: ts.SourceFile, className: string): ts.ClassDeclaration {
const nodes = getSourceNodes(source);
const clazz =
<any>nodes.filter(n => n.kind === ts.SyntaxKind.ClassDeclaration && (<any>n).name.text === className)[0];
const clazz = <any>nodes.filter(
n => n.kind === ts.SyntaxKind.ClassDeclaration && (<any>n).name.text === className
)[0];
if (!clazz) {
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 {
const lines = text.trim()
.split('\n')
.map(line => {
let tabs = '';
for (let c = 0; c < numberOfTabs; ++c) {
tabs += ' ';
}
return `${tabs}${line}`;
})
.join('\n');
const lines = text
.trim()
.split('\n')
.map(line => {
let tabs = '';
for (let c = 0; c < numberOfTabs; ++c) {
tabs += ' ';
}
return `${tabs}${line}`;
})
.join('\n');
return wrap ? `\n${lines}\n` : lines;
}
export function addImportToModule(source: ts.SourceFile, modulePath: string, symbolName: string): Change[] {
return _addSymbolToNgModuleMetadata(
source,
modulePath,
'imports',
symbolName,
);
return _addSymbolToNgModuleMetadata(source, modulePath, 'imports', symbolName);
}
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 {
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;
// Get all the children property assignment of object literals.
return (node as ts.ObjectLiteralExpression)
.properties
return (
(node as ts.ObjectLiteralExpression).properties
.filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment)
// Filter out every fields that's not "metadataField". Also handles string literals
// (but not expressions).
@ -257,7 +265,8 @@ function getMatchingProperty(source: ts.SourceFile, property: string): ts.Object
return (name as ts.StringLiteral).text === property;
}
return false;
})[0];
})[0]
);
}
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);
}
export function insert(host: Tree, modulePath: string, changes: Change[]) {
const recorder = host.beginUpdate(modulePath);
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('sortApps', () => {
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', () => {
expect(addApp([], {name: 'a'})).toEqual([{name: 'a'}]);
expect(addApp([], { name: 'a' })).toEqual([{ name: 'a' }]);
});
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', () => {
expect(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'}
]);
expect(
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) {
apps = [];
}

View File

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

View File

@ -1,5 +1,10 @@
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 {
@ -7,12 +12,16 @@ export function toClassName(str: string): string {
}
export function toPropertyName(s: string): string {
return s.replace(/(-|_|\.|\s)+(.)?/g, (_, __, chr) => chr ? chr.toUpperCase() : '')
.replace(/^([A-Z])/, m => m.toLowerCase());
return s
.replace(/(-|_|\.|\s)+(.)?/g, (_, __, chr) => (chr ? chr.toUpperCase() : ''))
.replace(/^([A-Z])/, m => m.toLowerCase());
}
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 {

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

View File

@ -1,15 +1,3 @@
#!/usr/bin/env bash
find packages/ -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
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
prettier --single-quote --print-width 120 --list-different '{packages,e2e}/**/*.ts'

View File

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

View File

@ -968,14 +968,6 @@ circular-dependency-plugin@^3.0.0:
version "3.0.0"
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:
version "1.2.3"
resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51"
@ -4532,6 +4524,10 @@ preserve@^0.2.0:
version "0.2.0"
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:
version "2.1.1"
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"
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"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86"
dependencies: