feat(nx): standalone version of nx

This commit is contained in:
Victor Savkin 2019-07-23 12:27:29 -04:00
parent 3a2c56f21a
commit 2b646f8eb4
158 changed files with 5268 additions and 3644 deletions

View File

@ -34,7 +34,23 @@ matrix:
- yarn install --network-timeout 1000000
script:
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then yarn checkformat --head=$TRAVIS_PULL_REQUEST_SHA --base=$(git merge-base HEAD $TRAVIS_BRANCH); fi'
- yarn e2e
- yarn e2e --cli nx
- os: linux
language: node_js
node_js: 10
dist: trusty
sudo: required
cache:
npm: false
addons:
chrome: stable
before_install:
- export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start;
install:
- yarn install --network-timeout 1000000
script:
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then yarn checkformat --head=$TRAVIS_PULL_REQUEST_SHA --base=$(git merge-base HEAD $TRAVIS_BRANCH); fi'
- yarn e2e --cli angular
notifications:
email: false

View File

@ -17,6 +17,14 @@ Type: `string`
A directory where the app is placed
### linter
Default: `tslint`
Type: `string`
The tool to use for running lint checks.
### name
Type: `string`

View File

@ -23,6 +23,14 @@ Type: `string`
Frontend project that needs to access this application. This sets up proxy configuration.
### linter
Default: `tslint`
Type: `string`
The tool to use for running lint checks.
### name
Type: `string`

View File

@ -33,6 +33,14 @@ Type: `string`
Test runner to use for end to end (e2e) tests
### linter
Default: `tslint`
Type: `string`
The tool to use for running lint checks.
### name
Type: `string`

View File

@ -45,7 +45,7 @@ Use pascal case component file name (e.g. App.tsx)
Type: `string`
The name of the project (as specified in angular.json).
The name of the project.
### routing

View File

@ -17,6 +17,14 @@ Type: `string`
A directory where the app is placed
### linter
Default: `tslint`
Type: `string`
The tool to use for running lint checks.
### name
Type: `string`

View File

@ -25,6 +25,14 @@ Type: `string`
Test runner to use for end to end (e2e) tests
### linter
Default: `tslint`
Type: `string`
The tool to use for running lint checks.
### name
Type: `string`

View File

@ -8,7 +8,7 @@ Run commands
Type: `string`
Extra arguments. You can pass them as follows: ng run project:target --args='--wait=100'. You can them use {args.wait} syntax to interpolate them in angular.json
Extra arguments. You can pass them as follows: ng run project:target --args='--wait=100'. You can them use {args.wait} syntax to interpolate them in the workspace config file.
### commands

View File

@ -17,6 +17,14 @@ Type: `string`
A directory where the app is placed
### linter
Default: `tslint`
Type: `string`
The tool to use for running lint checks.
### name
Type: `string`

View File

@ -0,0 +1,72 @@
# tao-new [hidden]
Create a workspace
## Usage
```bash
ng generate tao-new ...
```
## Options
### commit
Default: `true`
Type: `boolean`
Initial repository commit information.
### directory
Type: `string`
The directory name to create the workspace in.
### name
Type: `string`
The name of the workspace.
### npmScope
Type: `string`
Npm scope for importing libs.
### preset
Default: `empty`
Type: `string`
What to create in the new workspace
### skipGit
Alias(es): g
Default: `false`
Type: `boolean`
Skip initializing a git repository.
### skipInstall
Default: `false`
Type: `boolean`
Skip installing dependency packages.
### style
Default: `css`
Type: `string`
The file extension to be used for style files.

View File

@ -11,6 +11,14 @@ ng generate workspace ...
## Options
### cli
Default: `nx`
Type: `string`
CLI used for generating code and running tasks
### commit
Default: `true`

View File

@ -3,15 +3,16 @@ import {
readFile,
readJson,
runCommand,
runsInWSL,
uniq,
updateFile,
runCLI
runCLI,
forEachCli,
supportUi
} from './utils';
let originalCIValue;
describe('Affected', () => {
forEachCli(() => {
/**
* Setting CI=true makes it simpler to configure assertions around output, as there
* won't be any colors.
@ -24,6 +25,7 @@ describe('Affected', () => {
process.env.CI = originalCIValue;
});
describe('Affected', () => {
it('should print, build, and test affected apps', () => {
ensureProject();
const myapp = uniq('myapp');
@ -104,9 +106,9 @@ describe('Affected', () => {
`npm run affected:build -- --files="libs/${mylib}/src/index.ts"`
);
expect(build).toContain(`Running target build for projects:`);
expect(build).toContain(myapp);
expect(build).toContain(mypublishablelib);
expect(build).toContain(`- ${myapp}`);
expect(build).toContain(`- ${mypublishablelib}`);
expect(build).not.toContain('is not registered with the build command');
expect(build).not.toContain('with flags:');
@ -115,8 +117,8 @@ describe('Affected', () => {
`npm run affected:build -- --files="libs/${mylib}/src/index.ts" --parallel`
);
expect(buildParallel).toContain(`Running target build for projects:`);
expect(buildParallel).toContain(myapp);
expect(buildParallel).toContain(mypublishablelib);
expect(buildParallel).toContain(`- ${myapp}`);
expect(buildParallel).toContain(`- ${mypublishablelib}`);
expect(buildParallel).toContain(
'Running target "build" for affected projects succeeded'
);
@ -125,20 +127,21 @@ describe('Affected', () => {
`npm run affected:build -- --files="libs/${mylib}/src/index.ts" --exclude ${myapp}`
);
expect(buildExcluded).toContain(`Running target build for projects:`);
expect(buildExcluded).toContain(mypublishablelib);
expect(buildExcluded).toContain(`- ${mypublishablelib}`);
// affected:build should pass non-nx flags to the CLI
const buildWithFlags = runCommand(
`npm run affected:build -- --files="libs/${mylib}/src/index.ts" --stats-json`
);
expect(buildWithFlags).toContain(`Running target build for projects:`);
expect(buildWithFlags).toContain(myapp);
expect(buildWithFlags).toContain(mypublishablelib);
expect(buildWithFlags).toContain(`- ${myapp}`);
expect(buildWithFlags).toContain(`- ${mypublishablelib}`);
expect(buildWithFlags).toContain('With flags: --stats-json=true');
if (!runsInWSL()) {
if (supportUi()) {
const e2e = runCommand(
`npm run affected:e2e -- --files="libs/${mylib}/src/index.ts" --headless --no-watch`
`npm run affected:e2e -- --files="libs/${mylib}/src/index.ts" --headless`
);
expect(e2e).toContain('should display welcome message');
}
@ -147,10 +150,9 @@ describe('Affected', () => {
`npm run affected:test -- --files="libs/${mylib}/src/index.ts"`
);
expect(unitTests).toContain(`Running target test for projects:`);
expect(unitTests).toContain(mylib);
expect(unitTests).toContain(myapp);
expect(unitTests).toContain(mypublishablelib);
expect(unitTests).toContain(`- ${mylib}`);
expect(unitTests).toContain(`- ${myapp}`);
expect(unitTests).toContain(`- ${mypublishablelib}`);
// Fail a Unit Test
updateFile(
`apps/${myapp}/src/app/app.component.spec.ts`,
@ -164,12 +166,10 @@ describe('Affected', () => {
`npm run affected:test -- --files="libs/${mylib}/src/index.ts"`
);
expect(failedTests).toContain(`Running target test for projects:`);
expect(failedTests).toContain(mylib);
expect(failedTests).toContain(myapp);
expect(failedTests).toContain(mypublishablelib);
expect(failedTests).toContain(`- ${mylib}`);
expect(failedTests).toContain(`- ${myapp}`);
expect(failedTests).toContain(`- ${mypublishablelib}`);
expect(failedTests).toContain(`Failed projects:`);
expect(failedTests).toContain(myapp);
expect(failedTests).toContain(
'You can isolate the above projects by passing: --only-failed'
);
@ -194,17 +194,17 @@ describe('Affected', () => {
const isolatedTests = runCommand(
`npm run affected:test -- --files="libs/${mylib}/src/index.ts" --only-failed`
);
expect(isolatedTests).toContain(`Running target test for projects:`);
expect(isolatedTests).toContain(myapp);
expect(isolatedTests).toContain(`Running target test for projects`);
expect(isolatedTests).toContain(`- ${myapp}`);
const linting = runCommand(
`npm run affected:lint -- --files="libs/${mylib}/src/index.ts"`
);
expect(linting).toContain(`Running target lint for projects:`);
expect(linting).toContain(mylib);
expect(linting).toContain(myapp);
expect(linting).toContain(`${myapp}-e2e`);
expect(linting).toContain(mypublishablelib);
expect(linting).toContain(`- ${mylib}`);
expect(linting).toContain(`- ${myapp}`);
expect(linting).toContain(`- ${myapp}-e2e`);
expect(linting).toContain(`- ${mypublishablelib}`);
const lintWithJsonFormating = runCommand(
`npm run affected:lint -- --files="libs/${mylib}/src/index.ts" -- --format json`
@ -215,19 +215,20 @@ describe('Affected', () => {
`npm run affected:test -- --files="libs/${mylib}/src/index.ts" --exclude=${myapp},${mypublishablelib}`
);
expect(unitTestsExcluded).toContain(`Running target test for projects:`);
expect(unitTestsExcluded).toContain(mylib);
expect(unitTestsExcluded).toContain(`- ${mylib}`);
const i18n = runCommand(
`npm run affected -- --target extract-i18n --files="libs/${mylib}/src/index.ts"`
);
expect(i18n).toContain(`Running target extract-i18n for projects:`);
expect(i18n).toContain(myapp);
expect(i18n).toContain(`- ${myapp}`);
const interpolatedTests = runCommand(
`npm run affected -- --target test --files="libs/${mylib}/src/index.ts" -- --jest-config {project.root}/jest.config.js`
);
expect(interpolatedTests).toContain(
`Running target "test" for affected projects succeeded`
`Running target \"test\" for affected projects succeeded`
);
}, 1000000);
});
});

View File

@ -8,10 +8,13 @@ import {
updateFile,
exists,
ensureProject,
uniq
uniq,
forEachCli,
workspaceConfigName
} from './utils';
describe('Command line', () => {
forEachCli(() => {
describe('Command line', () => {
it('lint should ensure module boundaries', () => {
ensureProject();
@ -81,8 +84,12 @@ describe('Command line', () => {
'The following file(s) do not belong to any projects:'
);
expect(stdout).toContain(`- apps/${appAfter}/browserslist`);
expect(stdout).toContain(`- apps/${appAfter}/src/app/app.component.css`);
expect(stdout).toContain(`- apps/${appAfter}/src/app/app.component.html`);
expect(stdout).toContain(
`- apps/${appAfter}/src/app/app.component.css`
);
expect(stdout).toContain(
`- apps/${appAfter}/src/app/app.component.html`
);
expect(stdout).toContain(
`- apps/${appAfter}/src/app/app.component.spec.ts`
);
@ -169,7 +176,10 @@ describe('Command line', () => {
type: 'string',
description: 'lib directory'
};
updateFile(`tools/schematics/${custom}/schema.json`, JSON.stringify(json));
updateFile(
`tools/schematics/${custom}/schema.json`,
JSON.stringify(json)
);
const indexFile = readFile(`tools/schematics/${custom}/index.ts`);
updateFile(
@ -185,14 +195,14 @@ describe('Command line', () => {
`npm run workspace-schematic ${custom} ${workspace} -- --no-interactive --directory=dir -d`
);
expect(exists(`libs/dir/${workspace}/src/index.ts`)).toEqual(false);
expect(dryRunOutput).toContain('update angular.json');
expect(dryRunOutput).toContain(`update ${workspaceConfigName()}`);
expect(dryRunOutput).toContain('update nx.json');
const output = runCommand(
`npm run workspace-schematic ${custom} ${workspace} -- --no-interactive --directory=dir`
);
checkFilesExist(`libs/dir/${workspace}/src/index.ts`);
expect(output).toContain('update angular.json');
expect(output).toContain(`update ${workspaceConfigName()}`);
expect(output).toContain('update nx.json');
const another = uniq('another');
@ -349,4 +359,5 @@ describe('Command line', () => {
expect(fileContents).toContain('<title>mylib&#45;&gt;mylib2</title>');
}, 1000000);
});
});
});

View File

@ -6,11 +6,13 @@ import {
readFile,
ensureProject,
uniq,
runsInWSL,
newProject
newProject,
forEachCli,
supportUi
} from './utils';
describe('Cypress E2E Test runner', () => {
forEachCli(() => {
describe('Cypress E2E Test runner', () => {
describe('project scaffolding', () => {
it('should generate an app with the Cypress as e2e test runner', () => {
ensureProject();
@ -34,16 +36,16 @@ describe('Cypress E2E Test runner', () => {
}, 1000000);
});
if (!runsInWSL()) {
if (supportUi()) {
describe('running Cypress', () => {
it('should execute e2e tests using Cypress', () => {
fit('should execute e2e tests using Cypress', () => {
newProject();
const myapp = uniq('myapp');
runCLI(`generate @nrwl/angular:app ${myapp} --e2eTestRunner=cypress`);
expect(
runCLI(`e2e --project=${myapp}-e2e --headless --watch=false`)
).toContain('All specs passed!');
expect(runCLI(`e2e ${myapp}-e2e --headless --no-watch`)).toContain(
'All specs passed!'
);
const originalContents = JSON.parse(
readFile(`apps/${myapp}-e2e/cypress.json`)
@ -54,10 +56,11 @@ describe('Cypress E2E Test runner', () => {
JSON.stringify(originalContents)
);
expect(
runCLI(`e2e --project=${myapp}-e2e --headless --watch=false`)
).toContain('All specs passed!');
expect(runCLI(`e2e ${myapp}-e2e --headless --no-watch`)).toContain(
'All specs passed!'
);
}, 1000000);
});
}
});
});

View File

@ -1,11 +1,18 @@
import { ensureProject, uniq, runCommand, checkFilesExist } from './utils';
import {
ensureProject,
uniq,
runCommand,
checkFilesExist,
forEachCli
} from './utils';
describe('Delegate to CLI', () => {
it('should delegate to the Angular CLI all non-standard commands', async () => {
forEachCli(() => {
describe('Delegate to CLI', () => {
it('should delegate to the cli all non-standard commands', async () => {
ensureProject();
const appName = uniq('app');
runCommand(`npm run nx -- g app ${appName}`);
runCommand(`npm run nx -- g @nrwl/web:app ${appName}`);
runCommand(`npm run nx -- build ${appName}`);
checkFilesExist(
@ -20,4 +27,5 @@ describe('Delegate to CLI', () => {
`dist/apps/${appName}/styles-es5.js`
);
}, 120000);
});
});

View File

@ -1,18 +1,19 @@
import {
ensureProject,
patchKarmaToWorkOnWSL,
runCLI,
uniq,
updateFile
updateFile,
forEachCli,
supportUi
} from './utils';
describe('DowngradeModule', () => {
forEachCli(() => {
xdescribe('DowngradeModule', () => {
it('should generate a downgradeModule setup', async () => {
ensureProject();
const myapp = uniq('myapp');
runCLI(`generate @nrwl/angular:app ${myapp} --unit-test-runner=karma`);
patchKarmaToWorkOnWSL();
updateFile(
`apps/${myapp}/src/legacy.js`,
@ -24,6 +25,9 @@ describe('DowngradeModule', () => {
);
runCLI(`build ${myapp}`);
if (supportUi()) {
expect(runCLI(`test ${myapp} --no-watch`)).toContain('3 SUCCESS');
}
}, 1000000);
});
});

47
e2e/help.test.ts Normal file
View File

@ -0,0 +1,47 @@
import { forEachCli, ensureProject, runCommand, runCLI, cli } from './utils';
forEachCli('nx', () => {
describe('Help', () => {
it('should should help', async () => {
ensureProject();
const mainHelp = runCLI(`--help`);
expect(mainHelp).toContain('Run a target for a project');
expect(mainHelp).toContain('Run task for affected projects');
const genHelp = runCLI(`g @nrwl/web:app --help`);
expect(genHelp).toContain(
'The file extension to be used for style files. (default: css)'
);
const affectedHelp = runCLI(`affected --help`);
expect(affectedHelp).toContain('Run task for affected projects');
const version = runCLI(`--version`);
expect(version).toContain('*'); // stub value
}, 120000);
});
});
forEachCli('angular', () => {
describe('Help', () => {
it('should should help', async () => {
ensureProject();
const mainHelp = runCLI(`--help`);
expect(mainHelp).toContain('Run a target for a project');
expect(mainHelp).toContain('Run task for affected projects');
const genHelp = runCLI(`g @nrwl/web:app --help`);
expect(genHelp).toContain(
'The file extension to be used for style files.'
);
const affectedHelp = runCLI(`affected --help`);
expect(affectedHelp).toContain('Run task for affected projects');
const version = runCLI(`--version`);
expect(version).toContain('*'); // stub value
}, 120000);
});
});

View File

@ -1,6 +1,7 @@
import { runCLIAsync, ensureProject, uniq, runCLI } from './utils';
import { runCLIAsync, ensureProject, uniq, runCLI, forEachCli } from './utils';
describe('Jest', () => {
forEachCli(() => {
describe('Jest', () => {
it('should be able test projects using jest', async done => {
ensureProject();
const mylib = uniq('mylib');
@ -14,10 +15,11 @@ describe('Jest', () => {
runCLIAsync(`generate @nrwl/angular:service test --project ${mylib}`),
runCLIAsync(`generate @nrwl/angular:component test --project ${mylib}`)
]);
const appResult = await runCLIAsync(`test ${myapp}`);
const appResult = await runCLIAsync(`test ${myapp} --no-watch`);
expect(appResult.stderr).toContain('Test Suites: 3 passed, 3 total');
const libResult = await runCLIAsync(`test ${mylib}`);
expect(libResult.stderr).toContain('Test Suites: 3 passed, 3 total');
done();
}, 45000);
});
});

View File

@ -3,22 +3,25 @@ import {
runCLIAsync,
ensureProject,
uniq,
patchKarmaToWorkOnWSL
forEachCli,
supportUi
} from './utils';
describe('Karma', () => {
forEachCli(() => {
xdescribe('Karma', () => {
it('should be able to generate a testable library using karma', async done => {
ensureProject();
const mylib = uniq('mylib');
runCLI(`generate @nrwl/angular:lib ${mylib} --unit-test-runner karma`);
patchKarmaToWorkOnWSL();
await Promise.all([
runCLIAsync(`generate @nrwl/angular:service test --project ${mylib}`),
runCLIAsync(`generate @nrwl/angular:component test --project ${mylib}`)
]);
if (supportUi()) {
const karmaResult = await runCLIAsync(`test ${mylib}`);
expect(karmaResult.stdout).toContain('3 SUCCESS');
}
done();
}, 30000);
@ -26,14 +29,16 @@ describe('Karma', () => {
ensureProject();
const myapp = uniq('myapp');
runCLI(`generate @nrwl/angular:app ${myapp} --unit-test-runner karma`);
patchKarmaToWorkOnWSL();
await Promise.all([
runCLIAsync(`generate @nrwl/angular:service test --project ${myapp}`),
runCLIAsync(`generate @nrwl/angular:component test --project ${myapp}`)
]);
if (supportUi()) {
const karmaResult = await runCLIAsync(`test ${myapp}`);
expect(karmaResult.stdout).toContain('5 SUCCESS');
}
done();
}, 30000);
});
});

111
e2e/new.test.ts Normal file
View File

@ -0,0 +1,111 @@
import {
ensureProject,
exists,
expectTestsPass,
getSize,
runCLI,
runCLIAsync,
uniq,
updateFile,
forEachCli,
checkFilesExist,
tmpProjPath,
supportUi
} from './utils';
import { toClassName } from '@nrwl/workspace';
forEachCli(() => {
describe('Create New Workspace', () => {
beforeEach(() => {
ensureProject();
});
it('should work', async () => {
const myapp = uniq('myapp');
const mylib = uniq('mylib');
runCLI(
`generate @nrwl/angular:app ${myapp} --directory=myDir --no-interactive`
);
runCLI(
`generate @nrwl/angular:lib ${mylib} --directory=myDir --no-interactive`
);
updateFile(
`apps/my-dir/${myapp}/src/app/app.module.ts`,
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MyDir${toClassName(
mylib
)}Module } from '@proj/my-dir/${mylib}';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule, MyDir${toClassName(mylib)}Module],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
`
);
runCLI(`build my-dir-${myapp} --prod --output-hashing none`);
checkFilesExist(
`dist/apps/my-dir/${myapp}/main-es2015.js`,
`dist/apps/my-dir/${myapp}/main-es5.js`
);
// This is a loose requirement because there are a lot of
// influences external from this project that affect this.
const es2015BundleSize = getSize(
tmpProjPath(`dist/apps/my-dir/${myapp}/main-es2015.js`)
);
console.log(
`The current es2015 bundle size is ${es2015BundleSize / 1000} KB`
);
expect(es2015BundleSize).toBeLessThanOrEqual(150000);
const es5BundleSize = getSize(
tmpProjPath(`dist/apps/my-dir/${myapp}/main-es5.js`)
);
console.log(`The current es5 bundle size is ${es5BundleSize / 1000} KB`);
expect(es5BundleSize).toBeLessThanOrEqual(175000);
// running tests for the app
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
// running tests for the lib
expectTestsPass(await runCLIAsync(`test my-dir-${mylib} --no-watch`));
if (supportUi()) {
expect(
runCLI(`e2e my-dir-${myapp}-e2e --headless --no-watch`)
).toContain('All specs passed!');
}
}, 1000000);
it('should support router config generation (lazy)', async () => {
const myapp = uniq('myapp');
const mylib = uniq('mylib');
runCLI(`generate @nrwl/angular:app ${myapp} --directory=myDir --routing`);
runCLI(
`generate @nrwl/angular:lib ${mylib} --directory=myDir --routing --lazy --parentModule=apps/my-dir/${myapp}/src/app/app.module.ts`
);
runCLI(`build my-dir-${myapp} --aot`);
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
}, 1000000);
it('should support router config generation (eager)', async () => {
const myapp = uniq('myapp');
runCLI(`generate @nrwl/angular:app ${myapp} --directory=myDir --routing`);
const mylib = uniq('mylib');
runCLI(
`generate @nrwl/angular:lib ${mylib} --directory=myDir --routing --parentModule=apps/my-dir/${myapp}/src/app/app.module.ts`
);
runCLI(`build my-dir-${myapp} --aot`);
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
}, 1000000);
});
});

View File

@ -6,16 +6,19 @@ import {
runCLI,
runCLIAsync,
runCommand,
runNgNew,
updateFile
runNew,
updateFile,
forEachCli,
runNgAdd
} from './utils';
describe('Nrwl Convert to Nx Workspace', () => {
forEachCli('angular', () => {
describe('Nrwl Convert to Nx Workspace', () => {
beforeEach(cleanup);
afterAll(cleanup);
it('should generate a workspace', () => {
runNgNew();
runNew('', false, false);
// update package.json
const packageJson = readJson('package.json');
@ -48,7 +51,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
updateFile('angular.json', JSON.stringify(angularCLIJson, null, 2));
// run the command
runCLI('add @nrwl/workspace --npmScope projscope --skip-install');
runNgAdd('add @nrwl/workspace --npmScope projscope --skip-install');
copyMissingPackages();
// check that prettier config exits and that files have been moved!
@ -94,7 +97,9 @@ describe('Nrwl Convert to Nx Workspace', () => {
'workspace-schematic': 'nx workspace-schematic',
help: 'nx help'
});
expect(updatedPackageJson.devDependencies['@nrwl/workspace']).toBeDefined();
expect(
updatedPackageJson.devDependencies['@nrwl/workspace']
).toBeDefined();
expect(updatedPackageJson.devDependencies['@angular/cli']).toBeDefined();
const nxJson = readJson('nx.json');
@ -127,6 +132,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
expect(updatedAngularCLIJson.projects.proj.architect.build).toEqual({
builder: '@angular-devkit/build-angular:browser',
options: {
aot: false,
outputPath: 'dist/apps/proj',
index: 'apps/proj/src/index.html',
main: 'apps/proj/src/main.ts',
@ -214,13 +220,15 @@ describe('Nrwl Convert to Nx Workspace', () => {
devServerTarget: 'proj:serve'
}
});
expect(updatedAngularCLIJson.projects['proj-e2e'].architect.lint).toEqual({
expect(updatedAngularCLIJson.projects['proj-e2e'].architect.lint).toEqual(
{
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: 'apps/proj-e2e/tsconfig.json',
exclude: ['**/node_modules/**']
}
});
}
);
const updatedTslint = readJson('tslint.json');
expect(updatedTslint.rules['nx-enforce-module-boundaries']).toEqual([
@ -237,13 +245,15 @@ describe('Nrwl Convert to Nx Workspace', () => {
it('should generate a workspace and not change dependencies, devDependencies, or vscode extensions if they already exist', () => {
// create a new AngularCLI app
runNgNew();
runNew();
const nxVersion = '0.0.0';
const schematicsVersion = '0.0.0';
const ngrxVersion = '0.0.0';
// update package.json
const existingPackageJson = readJson('package.json');
existingPackageJson.devDependencies['@nrwl/workspace'] = schematicsVersion;
existingPackageJson.devDependencies[
'@nrwl/workspace'
] = schematicsVersion;
existingPackageJson.dependencies['@ngrx/store'] = ngrxVersion;
existingPackageJson.dependencies['@ngrx/effects'] = ngrxVersion;
existingPackageJson.dependencies['@ngrx/router-store'] = ngrxVersion;
@ -257,7 +267,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
})
);
// run the command
runCLI('add @nrwl/workspace --npmScope projscope --skip-install');
runNgAdd('add @nrwl/workspace --npmScope projscope --skip-install');
// check that dependencies and devDependencies remained the same
const packageJson = readJson('package.json');
@ -266,7 +276,9 @@ describe('Nrwl Convert to Nx Workspace', () => {
);
expect(packageJson.dependencies['@ngrx/store']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/effects']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/router-store']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/router-store']).toEqual(
ngrxVersion
);
expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual(
ngrxVersion
);
@ -282,53 +294,27 @@ describe('Nrwl Convert to Nx Workspace', () => {
it('should convert a project with common libraries in the ecosystem', () => {
// create a new AngularCLI app
runNgNew();
runNew();
// Add some Angular libraries
runCLI('add @angular/elements');
runCLI('add @angular/material');
runCLI('add @angular/pwa');
runCLI('add @ngrx/store');
runCLI('add @ngrx/effects');
runNgAdd('add @angular/elements');
runNgAdd('add @angular/material');
runNgAdd('add @angular/pwa');
runNgAdd('add @ngrx/store');
runNgAdd('add @ngrx/effects');
// Add Nx
runCLI('add @nrwl/workspace --skip-install');
});
it('should handle workspaces with no e2e project', async () => {
// create a new AngularCLI app
runNgNew();
// Remove e2e
runCommand('rm -rf e2e');
const existingAngularJson = readJson('angular.json');
delete existingAngularJson.projects['proj'].architect.e2e;
updateFile('angular.json', JSON.stringify(existingAngularJson, null, 2));
// Add @nrwl/workspace
const result = await runCLIAsync(
'add @nrwl/workspace --npmScope projscope --skip-install'
);
checkFilesExist(
'.prettierrc',
'apps/proj/src/main.ts',
'apps/proj/src/app/app.module.ts'
);
expect(result.stderr).toContain(
'No e2e project was migrated because there was none declared in angular.json'
);
runNgAdd('add @nrwl/workspace --skip-install');
});
it('should handle different types of errors', () => {
// create a new AngularCLI app
runNgNew();
runNew();
// Only remove e2e directory
runCommand('mv e2e e2e-bak');
try {
runCLI('add @nrwl/workspace --npmScope projscope --skip-install');
runNgAdd('add @nrwl/workspace --npmScope projscope --skip-install');
fail('Did not handle not having a e2e directory');
} catch (e) {
expect(e.stderr.toString()).toContain(
@ -342,7 +328,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
// Remove package.json
runCommand('mv package.json package.json.bak');
try {
runCLI('add @nrwl/workspace --npmScope projscope --skip-install');
runNgAdd('add @nrwl/workspace --npmScope projscope --skip-install');
fail('Did not handle not having a package.json');
} catch (e) {
expect(e.stderr.toString()).toContain(
@ -356,7 +342,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
// Remove src
runCommand('mv src src-bak');
try {
runCLI('add @nrwl/workspace --npmScope projscope --skip-install');
runNgAdd('add @nrwl/workspace --npmScope projscope --skip-install');
fail('Did not handle not having a src directory');
} catch (e) {
expect(e.stderr.toString()).toContain('Path: src does not exist');
@ -365,4 +351,11 @@ describe('Nrwl Convert to Nx Workspace', () => {
// Put src back
runCommand('mv src-bak src');
});
});
});
forEachCli('nx', () => {
describe('ng-add', () => {
it('is not supported', () => {});
});
});

View File

@ -1,115 +0,0 @@
import {
ensureProject,
exists,
expectTestsPass,
getSize,
runCLI,
runCLIAsync,
runsInWSL,
uniq,
updateFile
} from './utils';
import { toClassName } from '@nrwl/workspace';
describe('Nrwl Workspace', () => {
beforeEach(() => {
ensureProject();
});
it('should work', async () => {
const myapp = uniq('myapp');
const mylib = uniq('mylib');
runCLI(
`generate @nrwl/angular:app ${myapp} --directory=myDir --no-interactive`
);
runCLI(
`generate @nrwl/angular:lib ${mylib} --directory=myDir --no-interactive`
);
updateFile(
`apps/my-dir/${myapp}/src/app/app.module.ts`,
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MyDir${toClassName(
mylib
)}Module } from '@proj/my-dir/${mylib}';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule, MyDir${toClassName(mylib)}Module],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
`
);
runCLI(`build --prod --project=my-dir-${myapp} --output-hashing none`);
expect(
exists(`./tmp/proj/dist/apps/my-dir/${myapp}/main-es2015.js`)
).toEqual(true);
expect(exists(`./tmp/proj/dist/apps/my-dir/${myapp}/main-es5.js`)).toEqual(
true
);
// This is a loose requirement because there are a lot of
// influences external from this project that affect this.
const es2015BundleSize = getSize(
`./tmp/proj/dist/apps/my-dir/${myapp}/main-es2015.js`
);
console.log(
`The current es2015 bundle size is ${es2015BundleSize / 1000} KB`
);
expect(es2015BundleSize).toBeLessThanOrEqual(150000);
const es5BundleSize = getSize(
`./tmp/proj/dist/apps/my-dir/${myapp}/main-es5.js`
);
console.log(`The current es5 bundle size is ${es5BundleSize / 1000} KB`);
expect(es5BundleSize).toBeLessThanOrEqual(175000);
// running tests for the app
expectTestsPass(
await runCLIAsync(`test --project=my-dir-${myapp} --no-watch`)
);
// running tests for the lib
expectTestsPass(
await runCLIAsync(`test --project=my-dir-${mylib} --no-watch`)
);
if (!runsInWSL()) {
expect(
runCLI(`e2e --project=my-dir-${myapp}-e2e --headless --watch=false`)
).toContain('All specs passed!');
}
}, 1000000);
it('should support router config generation (lazy)', async () => {
const myapp = uniq('myapp');
const mylib = uniq('mylib');
runCLI(`generate @nrwl/angular:app ${myapp} --directory=myDir --routing`);
runCLI(
`generate @nrwl/angular:lib ${mylib} --directory=myDir --routing --lazy --parentModule=apps/my-dir/${myapp}/src/app/app.module.ts`
);
runCLI(`build --aot --project=my-dir-${myapp}`);
expectTestsPass(
await runCLIAsync(`test --project=my-dir-${myapp} --no-watch`)
);
}, 1000000);
it('should support router config generation (eager)', async () => {
const myapp = uniq('myapp');
runCLI(`generate @nrwl/angular:app ${myapp} --directory=myDir --routing`);
const mylib = uniq('mylib');
runCLI(
`generate @nrwl/angular:lib ${mylib} --directory=myDir --routing --parentModule=apps/my-dir/${myapp}/src/app/app.module.ts`
);
runCLI(`build --aot --project=my-dir-${myapp}`);
expectTestsPass(
await runCLIAsync(`test --project=my-dir-${myapp} --no-watch`)
);
}, 1000000);
});

View File

@ -4,10 +4,12 @@ import {
runCLIAsync,
uniq,
ensureProject,
readJson
readJson,
forEachCli
} from './utils';
describe('ngrx', () => {
forEachCli(() => {
describe('ngrx', () => {
it('should work', async () => {
ensureProject();
@ -31,7 +33,9 @@ describe('ngrx', () => {
`generate @nrwl/angular:ngrx flights --module=libs/${mylib}/src/lib/${mylib}.module.ts --facade`
);
expect(runCLI(`build ${myapp}`)).toContain('chunk {main} main-es2015.js,');
expect(runCLI(`build ${myapp}`)).toContain(
'chunk {main} main-es2015.js,'
);
expect(runCLI(`build ${myapp}`)).toContain('chunk {main} main-es5.js,');
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
expectTestsPass(await runCLIAsync(`test ${mylib} --no-watch`));
@ -62,9 +66,12 @@ describe('ngrx', () => {
`generate @nrwl/angular:ngrx flights --module=libs/${mylib}/src/lib/${mylib}.module.ts --facade --syntax=creators`
);
expect(runCLI(`build ${myapp}`)).toContain('chunk {main} main-es2015.js,');
expect(runCLI(`build ${myapp}`)).toContain(
'chunk {main} main-es2015.js,'
);
expect(runCLI(`build ${myapp}`)).toContain('chunk {main} main-es5.js,');
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
expectTestsPass(await runCLIAsync(`test ${mylib} --no-watch`));
}, 1000000);
});
});

View File

@ -9,7 +9,11 @@ import {
runCLI,
runCLIAsync,
uniq,
updateFile
updateFile,
forEachCli,
checkFilesExist,
tmpProjPath,
workspaceConfigName
} from './utils';
function getData(): Promise<any> {
@ -27,8 +31,9 @@ function getData(): Promise<any> {
});
}
describe('Node Applications', () => {
fit('should be able to generate an express application', async done => {
forEachCli(() => {
describe('Node Applications', () => {
it('should be able to generate an express application', async done => {
ensureProject();
const nodeapp = uniq('nodeapp');
runCLI(`generate @nrwl/express:app ${nodeapp}`);
@ -49,23 +54,22 @@ describe('Node Applications', () => {
expect(jestResult.stderr).toContain('Test Suites: 1 passed, 1 total');
await runCLIAsync(`build ${nodeapp}`);
expect(exists(`./tmp/proj/dist/apps/${nodeapp}/main.js`)).toBeTruthy();
expect(
exists(`./tmp/proj/dist/apps/${nodeapp}/assets/file.txt`)
).toBeTruthy();
expect(exists(`./tmp/proj/dist/apps/${nodeapp}/main.js.map`)).toBeTruthy();
const server = fork(
path.join(__dirname, '../../tmp/proj', `./dist/apps/${nodeapp}/main.js`),
[],
{
cwd: './tmp/proj',
silent: true
}
checkFilesExist(
`dist/apps/${nodeapp}/main.js`,
`dist/apps/${nodeapp}/assets/file.txt`,
`dist/apps/${nodeapp}/main.js.map`
);
const server = fork(`./dist/apps/${nodeapp}/main.js`, [], {
cwd: tmpProjPath(),
silent: true
});
expect(server).toBeTruthy();
await new Promise(resolve => {
server.stdout.once('data', async data => {
expect(data.toString()).toContain('Listening at http://localhost:3333');
expect(data.toString()).toContain(
'Listening at http://localhost:3333'
);
const result = await getData();
expect(result.message).toEqual(`Welcome to ${nodeapp}!`);
@ -75,7 +79,7 @@ describe('Node Applications', () => {
});
});
});
const config = readJson('angular.json');
const config = readJson(workspaceConfigName());
config.projects[nodeapp].architect.waitAndPrint = {
builder: '@nrwl/workspace:run-commands',
options: {
@ -87,15 +91,16 @@ describe('Node Applications', () => {
readyWhen: 'DONE'
}
};
config.projects[nodeapp].architect.serve.options.waitUntilTargets = [
`${nodeapp}:waitAndPrint`
];
updateFile('angular.json', JSON.stringify(config));
updateFile(workspaceConfigName(), JSON.stringify(config));
const process = spawn(
'node',
['./node_modules/.bin/ng', 'serve', nodeapp],
['./node_modules/.bin/nx', 'serve', nodeapp],
{
cwd: './tmp/proj'
cwd: tmpProjPath()
}
);
let collectedOutput = '';
@ -126,20 +131,16 @@ describe('Node Applications', () => {
await runCLIAsync(`build ${nestapp}`);
expect(exists(`./tmp/proj/dist/apps/${nestapp}/main.js`)).toBeTruthy();
expect(
exists(`./tmp/proj/dist/apps/${nestapp}/assets/file.txt`)
).toBeTruthy();
expect(exists(`./tmp/proj/dist/apps/${nestapp}/main.js.map`)).toBeTruthy();
const server = fork(
path.join(__dirname, '../../tmp/proj', `./dist/apps/${nestapp}/main.js`),
[],
{
cwd: './tmp/proj',
silent: true
}
checkFilesExist(
`dist/apps/${nestapp}/main.js`,
`dist/apps/${nestapp}/assets/file.txt`,
`dist/apps/${nestapp}/main.js.map`
);
const server = fork(`./dist/apps/${nestapp}/main.js`, [], {
cwd: tmpProjPath(),
silent: true
});
expect(server).toBeTruthy();
await new Promise(resolve => {
@ -159,9 +160,9 @@ describe('Node Applications', () => {
const process = spawn(
'node',
['./node_modules/.bin/ng', 'serve', nestapp],
['./node_modules/.bin/nx', 'serve', nestapp],
{
cwd: './tmp/proj'
cwd: tmpProjPath()
}
);
@ -185,10 +186,12 @@ describe('Node Applications', () => {
runCLI(`generate @nrwl/node:app ${nodeapp}`);
updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`);
await runCLIAsync(`build ${nodeapp}`);
expect(exists(`./tmp/proj/dist/apps/${nodeapp}/main.js`)).toBeTruthy();
checkFilesExist(`dist/apps/${nodeapp}/main.js`);
const result = execSync(`node dist/apps/${nodeapp}/main.js`, {
cwd: './tmp/proj'
cwd: tmpProjPath()
}).toString();
expect(result).toContain('Hello World!');
}, 30000);
});
});

View File

@ -7,11 +7,15 @@ import {
runCLIAsync,
checkFilesExist,
renameFile,
readJson
readJson,
forEachCli,
supportUi,
workspaceConfigName
} from './utils';
import { serializeJson } from '@nrwl/workspace';
describe('React Applications', () => {
forEachCli(() => {
describe('React Applications', () => {
it('should be able to generate a react app + lib', async () => {
ensureProject();
const appName = uniq('app');
@ -52,7 +56,10 @@ describe('React Applications', () => {
runCLI(`generate @nrwl/react:app ${appName} --no-interactive`);
runCLI(`generate @nrwl/react:lib ${libName} --no-interactive`);
renameFile(`apps/${appName}/src/main.tsx`, `apps/${appName}/src/main.jsx`);
renameFile(
`apps/${appName}/src/main.tsx`,
`apps/${appName}/src/main.jsx`
);
renameFile(
`apps/${appName}/src/app/app.tsx`,
`apps/${appName}/src/app/app.jsx`
@ -65,7 +72,7 @@ describe('React Applications', () => {
`apps/${appName}/src/polyfills.ts`,
`apps/${appName}/src/polyfills.js`
);
const angularJson = readJson('angular.json');
const angularJson = readJson(workspaceConfigName());
angularJson.projects[
appName
@ -73,7 +80,7 @@ describe('React Applications', () => {
angularJson.projects[
appName
].architect.build.options.polyfills = `apps/${appName}/src/polyfills.js`;
updateFile('angular.json', serializeJson(angularJson));
updateFile(workspaceConfigName(), serializeJson(angularJson));
const mainPath = `apps/${appName}/src/main.jsx`;
updateFile(mainPath, `import '@proj/${libName}';\n` + readFile(mainPath));
@ -120,7 +127,11 @@ describe('React Applications', () => {
expect(testResults.stderr).toContain('Test Suites: 1 passed, 1 total');
const lintE2eResults = runCLI(`lint ${appName}-e2e`);
expect(lintE2eResults).toContain('All files pass linting.');
if (supportUi()) {
const e2eResults = runCLI(`e2e ${appName}-e2e`);
expect(e2eResults).toContain('All specs passed!');
}
}
});
});

View File

@ -1,17 +1,18 @@
import {
ensureProject,
patchKarmaToWorkOnWSL,
runCLI,
uniq,
updateFile
updateFile,
forEachCli,
supportUi
} from './utils';
describe('Upgrade', () => {
forEachCli(() => {
xdescribe('Upgrade', () => {
it('should generate an UpgradeModule setup', async () => {
ensureProject();
const myapp = uniq('myapp');
runCLI(`generate @nrwl/angular:app ${myapp} --unit-test-runner=karma`);
patchKarmaToWorkOnWSL();
updateFile(
`apps/${myapp}/src/legacy.js`,
@ -38,6 +39,9 @@ describe('Upgrade', () => {
);
runCLI(`build ${myapp}`);
if (supportUi()) {
expect(runCLI(`test ${myapp} --no-watch`)).toContain('1 SUCCESS');
}
}, 1000000);
});
});

View File

@ -3,57 +3,107 @@ import { readFileSync, statSync, writeFileSync, renameSync } from 'fs';
import { ensureDirSync } from 'fs-extra';
import * as path from 'path';
const projectName: string = 'proj';
export let cli;
export function uniq(prefix: string) {
return `${prefix}${Math.floor(Math.random() * 10000000)}`;
}
function patchPackageJsonDeps() {
const p = readFileSync('./tmp/proj/package.json').toString();
export function forEachCli(
selectedCliOrFunction: string | Function,
callback?: Function
) {
let clis;
if (process.env.SELECTED_CLI && selectedCliOrFunction && callback) {
if (selectedCliOrFunction == process.env.SELECTED_CLI) {
clis = [process.env.SELECTED_CLI];
} else {
clis = [];
}
} else if (process.env.SELECTED_CLI) {
clis = [process.env.SELECTED_CLI];
} else {
clis = callback ? [selectedCliOrFunction] : ['nx', 'angular'];
}
const cb: any = callback ? callback : selectedCliOrFunction;
clis.forEach(c => {
describe(`[${c}]`, () => {
beforeEach(() => {
cli = c;
});
cb();
});
});
}
export function workspaceConfigName() {
return cli === 'angular' ? 'angular.json' : 'workspace.json';
}
function patchPackageJsonDeps(addWorkspace = true) {
const p = JSON.parse(readFileSync(tmpProjPath('package.json')).toString());
const workspacePath = path.join(getCwd(), 'build', 'packages', 'workspace');
const angularPath = path.join(getCwd(), 'build', 'packages', 'angular');
writeFileSync(
'./tmp/proj/package.json',
p
.replace(
'"@nrwl/workspace": "*"',
`"@nrwl/workspace": "file:${workspacePath}"`
)
.replace('"@nrwl/angular": "*"', `"@nrwl/angular": "file:${angularPath}"`)
);
const reactPath = path.join(getCwd(), 'build', 'packages', 'react');
if (addWorkspace) {
p.devDependencies['@nrwl/workspace'] = `file:${workspacePath}`;
}
p.devDependencies['@nrwl/angular'] = `file:${angularPath}`;
p.devDependencies['@nrwl/react'] = `file:${reactPath}`;
writeFileSync(tmpProjPath('package.json'), JSON.stringify(p, null, 2));
}
function runYarnInstall(silent: boolean = true) {
const install = execSync('yarn install', {
cwd: './tmp/proj',
cwd: tmpProjPath(),
...(silent ? { stdio: ['ignore', 'ignore', 'ignore'] } : {})
});
return install ? install.toString() : '';
}
export function runNgNew(command?: string, silent?: boolean): string {
const gen = execSync(
`../node_modules/.bin/ng new proj --no-interactive --skip-install ${command ||
export function runNew(
command?: string,
silent?: boolean,
addWorkspace = true
): string {
let gen;
if (cli === 'angular') {
gen = execSync(
`../../node_modules/.bin/ng new proj --no-interactive --skip-install ${command ||
''}`,
{
cwd: `./tmp`,
cwd: `./tmp/${cli}`,
...(silent ? { stdio: ['ignore', 'ignore', 'ignore'] } : {})
}
);
patchPackageJsonDeps();
const install = runYarnInstall(silent);
} else {
gen = execSync(
`node ../../node_modules/@nrwl/tao/index.js new proj --no-interactive --skip-install ${command ||
''}`,
{
cwd: `./tmp/${cli}`,
...(silent && false ? { stdio: ['ignore', 'ignore', 'ignore'] } : {})
}
);
}
patchPackageJsonDeps(addWorkspace);
const install = runYarnInstall(silent && false);
return silent ? null : `${gen ? gen.toString() : ''}${install}`;
}
export function newProject(): void {
cleanup();
if (!directoryExists('./tmp/proj_backup')) {
runNgNew('--collection=@nrwl/workspace --npmScope=proj', true);
if (!directoryExists(tmpBackupProjPath())) {
runNew('--collection=@nrwl/workspace --npmScope=proj', true);
copyMissingPackages();
writeFileSync(
'./tmp/proj/node_modules/@angular-devkit/schematics/tasks/node-package/executor.js',
tmpProjPath(
'node_modules/@angular-devkit/schematics/tasks/node-package/executor.js'
),
`
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@ -67,45 +117,28 @@ function default_1(factoryOptions = {}) {
}
exports.default = default_1;`
);
runCLI('add @nrwl/jest');
runCLI('add @nrwl/cypress');
runCLI('add @nrwl/web');
runCLI('add @nrwl/react');
runCLI('add @nrwl/angular');
runCLI('add @nrwl/node');
runCLI('add @nrwl/express');
runCLI('add @nrwl/nest');
execSync('mv ./tmp/proj ./tmp/proj_backup');
execSync(`mv ${tmpProjPath()} ${tmpBackupProjPath()}`);
}
execSync('cp -a ./tmp/proj_backup ./tmp/proj');
execSync(`cp -a ${tmpBackupProjPath()} ${tmpProjPath()}`);
}
export function ensureProject(): void {
if (!directoryExists('./tmp/proj')) {
if (!directoryExists(tmpProjPath())) {
newProject();
}
}
export function runsInWSL() {
return !!process.env['WINDOWSTMP'];
}
export function patchKarmaToWorkOnWSL(): void {
export function supportUi() {
// powershell => wsl => no ui for now
try {
const karma = readFile('karma.conf.js');
if (process.env['WINDOWSTMP']) {
updateFile(
'karma.conf.js',
karma.replace(
`const { constants } = require('karma');`,
`
const { constants } = require('karma');
process.env['TMPDIR']="${process.env['WINDOWSTMP']}";
`
)
);
execSync(`powershell.exe echo 1`, {
stdio: ['ignore', 'ignore', 'ignore']
});
return false;
} catch (e) {
return true;
}
} catch (e) {}
}
export function copyMissingPackages(): void {
@ -150,7 +183,7 @@ export function copyMissingPackages(): void {
'document-register-element'
];
modulesToCopy.forEach(m => copyNodeModule(projectName, m));
modulesToCopy.forEach(m => copyNodeModule(m));
updateFile(
'node_modules/@angular-devkit/schematics/tasks/node-package/executor.js',
`
@ -167,17 +200,18 @@ export function copyMissingPackages(): void {
`
);
execSync('rm -rf tmp/proj/node_modules/.bin/webpack');
execSync(`rm -rf ${tmpProjPath('node_modules/.bin/webpack')}`);
execSync(
`cp -a node_modules/.bin/webpack tmp/proj/node_modules/.bin/webpack`
`cp -a node_modules/.bin/webpack ${tmpProjPath(
'node_modules/.bin/webpack'
)}`
);
execSync(`rm -rf ./tmp/proj/node_modules/cypress/node_modules/@types`);
execSync(`rm -rf ./tmp/proj/@types/sinon-chai/node_modules/@types`);
execSync(`rm -rf ${tmpProjPath('node_modules/cypress/node_modules/@types')}`);
}
function copyNodeModule(path: string, name: string) {
execSync(`rm -rf tmp/${path}/node_modules/${name}`);
execSync(`cp -a node_modules/${name} tmp/${path}/node_modules/${name}`);
function copyNodeModule(name: string) {
execSync(`rm -rf ${tmpProjPath('node_modules/' + name)}`);
execSync(`cp -a node_modules/${name} ${tmpProjPath('node_modules/' + name)}`);
}
export function runCommandAsync(
@ -190,7 +224,7 @@ export function runCommandAsync(
exec(
command,
{
cwd: `./tmp/proj`
cwd: tmpProjPath()
},
(err, stdout, stderr) => {
if (!opts.silenceError && err) {
@ -208,7 +242,35 @@ export function runCLIAsync(
silenceError: false
}
): Promise<{ stdout: string; stderr: string }> {
return runCommandAsync(`./node_modules/.bin/ng ${command}`, opts);
return runCommandAsync(
`node ./node_modules/@nrwl/cli/bin/nx.js ${command}`,
opts
);
}
export function runNgAdd(
command?: string,
opts = {
silenceError: false
}
): string {
try {
return execSync(`./node_modules/.bin/ng ${command}`, {
cwd: tmpProjPath()
})
.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();
} else {
console.log(e.stdout.toString(), e.stderr.toString());
throw e;
}
}
}
export function runCLI(
@ -218,8 +280,8 @@ export function runCLI(
}
): string {
try {
return execSync(`./node_modules/.bin/ng ${command}`, {
cwd: `./tmp/${projectName}`
return execSync(`node ./node_modules/@nrwl/cli/bin/nx.js ${command}`, {
cwd: tmpProjPath()
})
.toString()
.replace(
@ -244,7 +306,7 @@ export function expectTestsPass(v: { stdout: string; stderr: string }) {
export function runCommand(command: string): string {
try {
return execSync(command, {
cwd: `./tmp/${projectName}`,
cwd: tmpProjPath(),
stdio: ['pipe', 'pipe', 'pipe']
}).toString();
} catch (e) {
@ -253,23 +315,18 @@ export function runCommand(command: string): string {
}
export function updateFile(f: string, content: string): void {
ensureDirSync(path.dirname(path.join(getCwd(), 'tmp', 'proj', f)));
writeFileSync(path.join(getCwd(), 'tmp', 'proj', f), content);
ensureDirSync(path.dirname(tmpProjPath(f)));
writeFileSync(tmpProjPath(f), content);
}
export function renameFile(f: string, newPath: string): void {
ensureDirSync(path.dirname(path.join(getCwd(), 'tmp', 'proj', newPath)));
renameSync(
path.join(getCwd(), 'tmp', 'proj', f),
path.join(getCwd(), 'tmp', 'proj', newPath)
);
ensureDirSync(path.dirname(tmpProjPath(newPath)));
renameSync(tmpProjPath(f), tmpProjPath(newPath));
}
export function checkFilesExist(...expectedFiles: string[]) {
expectedFiles.forEach(f => {
const ff = f.startsWith('/')
? f
: path.join(getCwd(), 'tmp', projectName, f);
const ff = f.startsWith('/') ? f : tmpProjPath(f);
if (!exists(ff)) {
throw new Error(`File '${ff}' does not exist`);
}
@ -281,12 +338,12 @@ export function readJson(f: string): any {
}
export function readFile(f: string) {
const ff = f.startsWith('/') ? f : path.join(getCwd(), 'tmp', projectName, f);
const ff = f.startsWith('/') ? f : tmpProjPath(f);
return readFileSync(ff).toString();
}
export function cleanup() {
execSync('rm -rf ./tmp/proj');
execSync(`rm -rf ${tmpProjPath()}`);
}
export function getCwd(): string {
@ -316,3 +373,11 @@ export function exists(filePath: string): boolean {
export function getSize(filePath: string): number {
return statSync(filePath).size;
}
export function tmpProjPath(path?: string) {
return path ? `./tmp/${cli}/proj/${path}` : `./tmp/${cli}/proj`;
}
function tmpBackupProjPath(path?: string) {
return path ? `./tmp/${cli}/proj-backup/${path}` : `./tmp/${cli}/proj-backup`;
}

View File

@ -4,10 +4,13 @@ import {
readFile,
runCLI,
runCLIAsync,
uniq
uniq,
forEachCli,
supportUi
} from './utils';
describe('Web Components Applications', () => {
forEachCli(() => {
describe('Web Components Applications', () => {
it('should be able to generate a web app', async () => {
ensureProject();
const appName = uniq('app');
@ -53,4 +56,5 @@ describe('Web Components Applications', () => {
const e2eResults = runCLI(`e2e ${appName}-e2e`);
expect(e2eResults).toContain('All specs passed!');
}, 120000);
});
});

View File

@ -49,7 +49,7 @@
"@ngrx/schematics": "8.1.0",
"@ngrx/store": "8.1.0",
"@ngrx/store-devtools": "8.1.0",
"@schematics/angular": "8.0.0",
"@schematics/angular": "8.1.1",
"@testing-library/react": "8.0.5",
"@types/express": "4.16.0",
"@types/jasmine": "~2.8.6",
@ -94,7 +94,7 @@
"karma-jasmine-html-reporter": "^0.2.2",
"karma-webpack": "2.0.4",
"license-webpack-plugin": "^1.4.0",
"ng-packagr": "5.1.0",
"ng-packagr": "5.3.0",
"ngrx-store-freeze": "0.2.4",
"npm-run-all": "^4.1.5",
"opn": "^5.3.0",

View File

@ -13,27 +13,27 @@ describe('app', () => {
});
describe('not nested', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-app'].root).toEqual('apps/my-app');
expect(angularJson.projects['my-app-e2e'].root).toEqual(
expect(workspaceJson.projects['my-app'].root).toEqual('apps/my-app');
expect(workspaceJson.projects['my-app-e2e'].root).toEqual(
'apps/my-app-e2e'
);
expect(
angularJson.projects['my-app'].architect.lint.options.exclude
workspaceJson.projects['my-app'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!apps/my-app/**']);
expect(
angularJson.projects['my-app-e2e'].architect.lint.options.exclude
workspaceJson.projects['my-app-e2e'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!apps/my-app-e2e/**']);
});
it('should remove the e2e target on the application', async () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree);
const angularJson = readJsonInTree(tree, '/angular.json');
expect(angularJson.projects['my-app'].architect.e2e).not.toBeDefined();
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-app'].architect.e2e).not.toBeDefined();
});
it('should update nx.json', async () => {
@ -107,8 +107,10 @@ describe('app', () => {
let appE2eSpec = noPrefix
.read('apps/my-app-e2e/src/app.e2e-spec.ts')
.toString();
let angularJson = JSON.parse(noPrefix.read('angular.json').toString());
let myAppPrefix = angularJson.projects['my-app'].prefix;
let workspaceJson = JSON.parse(
noPrefix.read('workspace.json').toString()
);
let myAppPrefix = workspaceJson.projects['my-app'].prefix;
expect(myAppPrefix).toEqual('proj');
expect(appE2eSpec).toContain('Welcome to my-app!');
@ -118,8 +120,8 @@ describe('app', () => {
appE2eSpec = withPrefix
.read('apps/my-app-e2e/src/app.e2e-spec.ts')
.toString();
angularJson = JSON.parse(withPrefix.read('angular.json').toString());
myAppPrefix = angularJson.projects['my-app'].prefix;
workspaceJson = JSON.parse(withPrefix.read('workspace.json').toString());
myAppPrefix = workspaceJson.projects['my-app'].prefix;
expect(myAppPrefix).toEqual('custom');
expect(appE2eSpec).toContain('Welcome to my-app!');
@ -127,7 +129,7 @@ describe('app', () => {
xit('should work if the new project root is changed', async () => {
appTree = await callRule(
updateJsonInTree('/angular.json', json => ({
updateJsonInTree('/workspace.json', json => ({
...json,
newProjectRoot: 'newProjectRoot'
})),
@ -141,26 +143,27 @@ describe('app', () => {
});
describe('nested', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir' },
appTree
);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-dir-my-app'].root).toEqual(
expect(workspaceJson.projects['my-dir-my-app'].root).toEqual(
'apps/my-dir/my-app'
);
expect(angularJson.projects['my-dir-my-app-e2e'].root).toEqual(
expect(workspaceJson.projects['my-dir-my-app-e2e'].root).toEqual(
'apps/my-dir/my-app-e2e'
);
expect(
angularJson.projects['my-dir-my-app'].architect.lint.options.exclude
workspaceJson.projects['my-dir-my-app'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!apps/my-dir/my-app/**']);
expect(
angularJson.projects['my-dir-my-app-e2e'].architect.lint.options.exclude
workspaceJson.projects['my-dir-my-app-e2e'].architect.lint.options
.exclude
).toEqual(['**/node_modules/**', '!apps/my-dir/my-app-e2e/**']);
});
@ -314,9 +317,9 @@ describe('app', () => {
{ name: 'myApp', style: 'scss' },
appTree
);
const angularJson = readJsonInTree(result, 'angular.json');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(angularJson.projects['my-app'].schematics).toEqual({
expect(workspaceJson.projects['my-app'].schematics).toEqual({
'@nrwl/workspace:component': {
style: 'scss'
}
@ -334,12 +337,12 @@ describe('app', () => {
expect(tree.exists('apps/my-app/tsconfig.spec.json')).toBeTruthy();
expect(tree.exists('apps/my-app/karma.conf.js')).toBeTruthy();
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['my-app'].architect.test.builder).toEqual(
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app'].architect.test.builder).toEqual(
'@angular-devkit/build-angular:karma'
);
expect(
angularJson.projects['my-app'].architect.lint.options.tsConfig
workspaceJson.projects['my-app'].architect.lint.options.tsConfig
).toEqual([
'apps/my-app/tsconfig.app.json',
'apps/my-app/tsconfig.spec.json'
@ -367,26 +370,28 @@ describe('app', () => {
expect(tree.exists('apps/my-app/tsconfig.spec.json')).toBeFalsy();
expect(tree.exists('apps/my-app/jest.config.js')).toBeFalsy();
expect(tree.exists('apps/my-app/karma.config.js')).toBeFalsy();
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['my-app'].architect.test).toBeUndefined();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app'].architect.test).toBeUndefined();
expect(
angularJson.projects['my-app'].architect.lint.options.tsConfig
workspaceJson.projects['my-app'].architect.lint.options.tsConfig
).toEqual(['apps/my-app/tsconfig.app.json']);
});
});
describe('--e2e-test-runner', () => {
describe('protractor', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', e2eTestRunner: 'protractor' },
appTree
);
expect(tree.exists('apps/my-app-e2e')).toBeFalsy();
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['my-app'].architect.e2e).not.toBeDefined();
expect(angularJson.projects['my-app-e2e']).toEqual({
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(
workspaceJson.projects['my-app'].architect.e2e
).not.toBeDefined();
expect(workspaceJson.projects['my-app-e2e']).toEqual({
root: 'apps/my-app-e2e',
projectType: 'application',
architect: {
@ -422,30 +427,30 @@ describe('app', () => {
appTree
);
expect(tree.exists('apps/my-app-e2e')).toBeFalsy();
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['my-app-e2e']).toBeUndefined();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app-e2e']).toBeUndefined();
});
});
});
describe('replaceAppNameWithPath', () => {
it('should protect `angular.json` commands and properties', async () => {
it('should protect `workspace.json` commands and properties', async () => {
const tree = await runSchematic('app', { name: 'ui' }, appTree);
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['ui']).toBeDefined();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['ui']).toBeDefined();
expect(
angularJson.projects['ui']['architect']['build']['builder']
workspaceJson.projects['ui']['architect']['build']['builder']
).toEqual('@angular-devkit/build-angular:browser');
});
it('should protect `angular.json` sensible properties value to be renamed', async () => {
it('should protect `workspace.json` sensible properties value to be renamed', async () => {
const tree = await runSchematic(
'app',
{ name: 'ui', prefix: 'ui' },
appTree
);
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['ui'].prefix).toEqual('ui');
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['ui'].prefix).toEqual('ui');
});
});
});

View File

@ -15,7 +15,6 @@ import {
import { Schema } from './schema';
import * as ts from 'typescript';
import {
angularSchematicNames,
formatFiles,
getNpmScope,
getWorkspacePath,
@ -26,7 +25,8 @@ import {
replaceNodeValue,
toFileName,
updateJsonInTree,
updateWorkspace
updateWorkspace,
addGlobalLint
} from '@nrwl/workspace';
import { join, normalize } from '@angular-devkit/core';
import ngAdd from '../ng-add/ng-add';
@ -202,6 +202,16 @@ function updateProject(options: NormalizedSchema): Rule {
options.appProjectRoot
);
const angularSchematicNames = [
'class',
'component',
'directive',
'guard',
'module',
'pipe',
'service'
];
if (fixedProject.schematics) {
angularSchematicNames.forEach(type => {
const schematic = `@schematics/angular:${type}`;
@ -345,13 +355,13 @@ export default function(schema: Schema): Rule {
// Determine the roots where @schematics/angular will place the projects
// This is not where the projects actually end up
const angularJson = readJsonInTree(host, getWorkspacePath(host));
const workspaceJson = readJsonInTree(host, getWorkspacePath(host));
const appProjectRoot = angularJson.newProjectRoot
? `${angularJson.newProjectRoot}/${options.name}`
const appProjectRoot = workspaceJson.newProjectRoot
? `${workspaceJson.newProjectRoot}/${options.name}`
: options.name;
const e2eProjectRoot = angularJson.newProjectRoot
? `${angularJson.newProjectRoot}/${options.e2eProjectName}`
const e2eProjectRoot = workspaceJson.newProjectRoot
? `${workspaceJson.newProjectRoot}/${options.e2eProjectName}`
: `${options.name}/e2e`;
return chain([
@ -359,6 +369,7 @@ export default function(schema: Schema): Rule {
...options,
skipFormat: true
}),
addGlobalLint('tslint'),
externalSchematic('@schematics/angular', 'application', {
name: options.name,
inlineStyle: options.inlineStyle,
@ -373,11 +384,9 @@ export default function(schema: Schema): Rule {
skipPackageJson: false
}),
addTsconfigs(options),
options.e2eTestRunner === 'protractor'
? move(e2eProjectRoot, options.e2eProjectRoot)
: removeE2e(options, e2eProjectRoot),
options.e2eTestRunner === 'protractor'
? updateE2eProject(options)
: noop(),
@ -388,10 +397,8 @@ export default function(schema: Schema): Rule {
project: options.name
})
: noop(),
move(appProjectRoot, options.appProjectRoot),
updateProject(options),
updateComponentTemplate(options),
options.routing ? addRouterRootConfiguration(options) : noop(),
updateLinting(options),

View File

@ -81,7 +81,7 @@ module.exports = function(config) {
});
describe('library', () => {
it('should alter angular.json', async () => {
it('should alter workspace.json', async () => {
const resultTree = await runSchematic(
'karma-project',
{
@ -89,8 +89,8 @@ module.exports = function(config) {
},
appTree
);
const angularJson = readJsonInTree(resultTree, 'angular.json');
expect(angularJson.projects.lib1.architect.test).toEqual({
const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(workspaceJson.projects.lib1.architect.test).toEqual({
builder: '@angular-devkit/build-angular:karma',
options: {
main: 'libs/lib1/src/test.ts',
@ -99,7 +99,7 @@ module.exports = function(config) {
}
});
expect(
angularJson.projects.lib1.architect.lint.options.tsConfig
workspaceJson.projects.lib1.architect.lint.options.tsConfig
).toContain('libs/lib1/tsconfig.spec.json');
});
@ -141,7 +141,7 @@ module.exports = function(config) {
});
describe('applications', () => {
it('should alter angular.json', async () => {
it('should alter workspace.json', async () => {
const resultTree = await runSchematic(
'karma-project',
{
@ -149,8 +149,8 @@ module.exports = function(config) {
},
appTree
);
const angularJson = readJsonInTree(resultTree, 'angular.json');
expect(angularJson.projects.app1.architect.test).toEqual({
const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(workspaceJson.projects.app1.architect.test).toEqual({
builder: '@angular-devkit/build-angular:karma',
options: {
main: 'apps/app1/src/test.ts',
@ -163,7 +163,7 @@ module.exports = function(config) {
}
});
expect(
angularJson.projects.app1.architect.lint.options.tsConfig
workspaceJson.projects.app1.architect.lint.options.tsConfig
).toContain('apps/app1/tsconfig.spec.json');
});

View File

@ -13,7 +13,8 @@ import {
import {
readJsonInTree,
updateJsonInTree,
offsetFromRoot
offsetFromRoot,
updateWorkspaceInTree
} from '@nrwl/workspace';
import { join, normalize } from '@angular-devkit/core';
import { getProjectConfig } from '@nrwl/workspace';
@ -76,8 +77,8 @@ function updateTsSpecConfig(options: KarmaProjectSchema): Rule {
};
}
function updateAngularJson(options: KarmaProjectSchema): Rule {
return updateJsonInTree('angular.json', json => {
function updateworkspaceJson(options: KarmaProjectSchema): Rule {
return updateWorkspaceInTree(json => {
const projectConfig = json.projects[options.project];
projectConfig.architect.test = {
builder: '@angular-devkit/build-angular:karma',
@ -124,6 +125,6 @@ export default function(options: KarmaProjectSchema): Rule {
generateFiles(options),
updateTsConfig(options),
updateTsSpecConfig(options),
updateAngularJson(options)
updateworkspaceJson(options)
]);
}

View File

@ -5,5 +5,5 @@ This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `ng test <%= name %>` to execute the unit tests.
Run `nx test <%= name %>` to execute the unit tests.
<% } %>

View File

@ -79,37 +79,39 @@ describe('lib', () => {
expect(packageJson.name).toEqual('@proj/my-lib');
});
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', framework: 'angular', publishable: true },
appTree
);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(angularJson.projects['my-lib'].architect.build).toBeDefined();
expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(workspaceJson.projects['my-lib'].architect.build).toBeDefined();
expect(
angularJson.projects['my-lib'].architect.lint.options.tsConfig
workspaceJson.projects['my-lib'].architect.lint.options.tsConfig
).toEqual([
'libs/my-lib/tsconfig.lib.json',
'libs/my-lib/tsconfig.spec.json'
]);
expect(
angularJson.projects['my-lib'].architect.lint.options.exclude
workspaceJson.projects['my-lib'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!libs/my-lib/**']);
});
it('should remove "build" target from angular.json when a library is not publishable', async () => {
it('should remove "build" target from workspace.json when a library is not publishable', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', publishable: false },
appTree
);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(angularJson.projects['my-lib'].architect.build).not.toBeDefined();
expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(
workspaceJson.projects['my-lib'].architect.build
).not.toBeDefined();
});
it('should update nx.json', async () => {
@ -216,8 +218,9 @@ describe('lib', () => {
it('should default the prefix to npmScope', async () => {
const noPrefix = await runSchematic('lib', { name: 'myLib' }, appTree);
expect(
JSON.parse(noPrefix.read('angular.json').toString()).projects['my-lib']
.prefix
JSON.parse(noPrefix.read('workspace.json').toString()).projects[
'my-lib'
].prefix
).toEqual('proj');
const withPrefix = await runSchematic(
@ -226,7 +229,7 @@ describe('lib', () => {
appTree
);
expect(
JSON.parse(withPrefix.read('angular.json').toString()).projects[
JSON.parse(withPrefix.read('workspace.json').toString()).projects[
'my-lib'
].prefix
).toEqual('custom');
@ -381,26 +384,26 @@ describe('lib', () => {
expect(ngPackage.dest).toEqual('../../../dist/libs/my-dir/my-lib');
});
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir' },
appTree
);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-dir-my-lib'].root).toEqual(
expect(workspaceJson.projects['my-dir-my-lib'].root).toEqual(
'libs/my-dir/my-lib'
);
expect(
angularJson.projects['my-dir-my-lib'].architect.lint.options.tsConfig
workspaceJson.projects['my-dir-my-lib'].architect.lint.options.tsConfig
).toEqual([
'libs/my-dir/my-lib/tsconfig.lib.json',
'libs/my-dir/my-lib/tsconfig.spec.json'
]);
expect(
angularJson.projects['my-dir-my-lib'].architect.lint.options.exclude
workspaceJson.projects['my-dir-my-lib'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!libs/my-dir/my-lib/**']);
});
@ -763,9 +766,9 @@ describe('lib', () => {
appTree
);
const angularJson = readJsonInTree(result, 'angular.json');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(angularJson.projects['my-lib'].schematics).toEqual({
expect(workspaceJson.projects['my-lib'].schematics).toEqual({
'@nrwl/angular:component': {
styleext: 'scss'
}
@ -785,18 +788,18 @@ describe('lib', () => {
expect(resultTree.exists('libs/my-lib/tsconfig.spec.json')).toBeTruthy();
expect(resultTree.exists('libs/my-lib/karma.conf.js')).toBeTruthy();
expect(resultTree.exists('karma.conf.js')).toBeTruthy();
const angularJson = readJsonInTree(resultTree, 'angular.json');
expect(angularJson.projects['my-lib'].architect.test.builder).toEqual(
const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(workspaceJson.projects['my-lib'].architect.test.builder).toEqual(
'@angular-devkit/build-angular:karma'
);
expect(
angularJson.projects['my-lib'].architect.lint.options.tsConfig
workspaceJson.projects['my-lib'].architect.lint.options.tsConfig
).toEqual([
'libs/my-lib/tsconfig.lib.json',
'libs/my-lib/tsconfig.spec.json'
]);
expect(
angularJson.projects['my-lib'].architect.lint.options.exclude
workspaceJson.projects['my-lib'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!libs/my-lib/**']);
});
});
@ -816,10 +819,10 @@ describe('lib', () => {
expect(resultTree.exists('libs/my-lib/tsconfig.spec.json')).toBeFalsy();
expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy();
expect(resultTree.exists('libs/my-lib/karma.conf.js')).toBeFalsy();
const angularJson = readJsonInTree(resultTree, 'angular.json');
expect(angularJson.projects['my-lib'].architect.test).toBeUndefined();
const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(workspaceJson.projects['my-lib'].architect.test).toBeUndefined();
expect(
angularJson.projects['my-lib'].architect.lint.options.tsConfig
workspaceJson.projects['my-lib'].architect.lint.options.tsConfig
).toEqual(['libs/my-lib/tsconfig.lib.json']);
});
});

View File

@ -22,7 +22,8 @@ import {
NxJson,
updateJsonInTree,
readJsonInTree,
offsetFromRoot
offsetFromRoot,
addGlobalLint
} from '@nrwl/workspace';
import { addGlobal, addIncludeToTsConfig, insert } from '@nrwl/workspace';
import { toClassName, toFileName, toPropertyName } from '@nrwl/workspace';
@ -430,6 +431,7 @@ export default function(schema: Schema): Rule {
}
return chain([
addGlobalLint('tslint'),
addUnitTestRunner(options),
externalSchematic('@schematics/angular', 'library', {
name: options.name,

View File

@ -74,7 +74,7 @@ describe('ng-add', () => {
},
appTree
);
const { schematics } = readJsonInTree(tree, 'angular.json');
const { schematics } = readJsonInTree(tree, 'workspace.json');
expect(schematics['@nrwl/angular:application'].unitTestRunner).toEqual(
'karma'
);
@ -118,7 +118,7 @@ describe('ng-add', () => {
},
appTree
);
const { schematics } = readJsonInTree(tree, 'angular.json');
const { schematics } = readJsonInTree(tree, 'workspace.json');
expect(schematics['@nrwl/angular:application'].unitTestRunner).toEqual(
'jest'
);
@ -153,7 +153,7 @@ describe('ng-add', () => {
},
appTree
);
const { schematics } = readJsonInTree(tree, 'angular.json');
const { schematics } = readJsonInTree(tree, 'workspace.json');
expect(schematics['@nrwl/angular:application'].e2eTestRunner).toEqual(
'cypress'
);
@ -185,7 +185,7 @@ describe('ng-add', () => {
},
appTree
);
const { schematics } = readJsonInTree(tree, 'angular.json');
const { schematics } = readJsonInTree(tree, 'workspace.json');
expect(schematics['@nrwl/angular:application'].e2eTestRunner).toEqual(
'protractor'
);
@ -196,13 +196,13 @@ describe('ng-add', () => {
describe('defaultCollection', () => {
it('should be set if none was set before', async () => {
const result = await runSchematic('ng-add', {}, appTree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/angular');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/angular');
});
it('should be set if @nrwl/workspace was set before', async () => {
appTree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/workspace'
};
@ -212,13 +212,13 @@ describe('ng-add', () => {
appTree
);
const result = await runSchematic('ng-add', {}, appTree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/angular');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/angular');
});
it('should not be set if something else was set before', async () => {
appTree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/react'
};
@ -228,8 +228,8 @@ describe('ng-add', () => {
appTree
);
const result = await runSchematic('ng-add', {}, appTree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/react');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/react');
});
});
});

View File

@ -94,7 +94,7 @@ export function createApp(
})
);
tree.overwrite(
'/angular.json',
'/workspace.json',
JSON.stringify({
newProjectRoot: '',
version: 1,

View File

@ -1,6 +1,6 @@
export const nxVersion = '*';
export const angularVersion = '^8.0.0';
export const angularDevkitVersion = '^0.800.0';
export const angularDevkitVersion = '^0.800.1';
export const angularJsVersion = '1.6.6';
export const ngrxVersion = '8.1.0';
export const rxjsVersion = '~6.4.0';

View File

@ -29,6 +29,7 @@
"dependencies": {
"tmp": "0.0.33",
"yargs-parser": "10.0.0",
"yargs": "^11.0.0"
"yargs": "^11.0.0",
"@nrwl/tao": "*"
}
}

View File

@ -4,58 +4,109 @@
import { output } from '@nrwl/workspace/src/command-line/output';
import { execSync } from 'child_process';
import { writeFileSync } from 'fs';
import * as inquirer from 'inquirer';
import * as path from 'path';
import { dirSync } from 'tmp';
import * as yargsParser from 'yargs-parser';
const presetOptions = [
{
value: 'empty',
name: 'empty [an empty workspace]'
},
{
value: 'angular',
name: 'angular [a workspace with a single Angular application]'
},
{
value: 'react',
name: 'react [a workspace with a single React application]'
},
{
value: 'web-components',
name:
'web components [a workspace with a single app built using web components]'
},
{
value: 'full-stack',
name:
'full-stack [a workspace with a full stack application (NestJS + Angular Ivy)]'
}
];
const tsVersion = 'TYPESCRIPT_VERSION';
const cliVersion = 'NX_VERSION';
const nxVersion = 'NX_VERSION';
const angularCliVersion = 'ANGULAR_CLI_VERSION';
const parsedArgs = yargsParser(process.argv, {
string: ['directory'],
string: ['cli', 'preset'],
boolean: ['help']
});
if (parsedArgs.help) {
showHelp();
process.exit(0);
}
validateInput(parsedArgs);
const packageManager = determinePackageManager();
determinePreset(parsedArgs).then(preset => {
return determineCli(preset, parsedArgs).then(cli => {
const tmpDir = createSandbox(packageManager, cli);
createApp(tmpDir, cli, parsedArgs, preset);
showNxWarning();
showCliWarning(preset, parsedArgs);
});
});
function showHelp() {
console.log(`
Usage: create-nx-workspace <directory> [options] [ng new options]
Usage: create-nx-workspace <name> [options] [new workspace options]
Create a new Nx workspace
Options:
directory path to the workspace root directory
name workspace name
[ng new options] any 'ng new' options
run 'ng new --help' for more information
`);
process.exit(0);
preset What to create in a new workspace (options: ${presetOptions
.map(o => '"' + o.value + '"')
.join(', ')})
cli CLI to power the Nx workspace (options: "nx", "angular")
[new workspace options] any 'new workspace' options
`);
}
const nxTool = {
name: 'Schematics',
packageName: '@nrwl/workspace'
};
let packageManager: string;
try {
function determinePackageManager() {
// If you have Angular CLI installed, read Angular CLI config.
// If it isn't not installed, default to 'yarn'.
let packageManager: string;
try {
packageManager = execSync('ng config -g cli.packageManager', {
stdio: ['ignore', 'pipe', 'ignore']
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 500
})
.toString()
.trim();
} catch (e) {
} catch (e) {
packageManager = 'yarn';
}
try {
}
try {
execSync(`${packageManager} --version`, {
stdio: ['ignore', 'ignore', 'ignore']
});
} catch (e) {
} catch (e) {
packageManager = 'npm';
}
return packageManager;
}
const projectName = parsedArgs._[2];
function validateInput(parsedArgs: any) {
const projectName = parsedArgs._[2];
// check that the workspace name is passed in
if (!projectName) {
if (!projectName) {
output.error({
title: 'A project name is required when creating a new workspace',
bodyLines: [
@ -65,69 +116,177 @@ if (!projectName) {
]
});
process.exit(1);
}
return projectName;
}
// creating the sandbox
output.logSingleLine(`Creating a sandbox...`);
const tmpDir = dirSync().name;
function determinePreset(parsedArgs: any): Promise<string> {
if (parsedArgs.preset) {
if (presetOptions.map(o => o.value).indexOf(parsedArgs.preset) === -1) {
console.error(
`Invalid preset. It must be one of the following: ${presetOptions
.map(o => '"' + o.value + '"')
.join(', ')}.`
);
process.exit(1);
} else {
return Promise.resolve(parsedArgs.preset);
}
} else {
return inquirer
.prompt([
{
name: 'Preset',
message: `What to create in the new workspace`,
default: 'empty',
type: 'list',
choices: presetOptions
}
])
.then(a => a.Preset);
}
}
const nxVersion = 'NX_VERSION';
const cliVersion = 'ANGULAR_CLI_VERSION';
const typescriptVersion = 'TYPESCRIPT_VERSION';
function determineCli(preset: string, parsedArgs: any) {
const angular = {
package: '@angular/cli',
version: angularCliVersion,
command: 'ng'
};
writeFileSync(
const nx = {
package: '@nrwl/tao',
version: cliVersion,
command: 'tao'
};
if (parsedArgs.cli) {
if (['nx', 'angular'].indexOf(parsedArgs.cli) === -1) {
console.error(
`Invalid cli. It must be one of the following: "nx", "angular".`
);
process.exit(1);
}
return Promise.resolve(parsedArgs.cli === 'angular' ? angular : nx);
}
if (preset == 'angular' || preset == 'full-stack') {
return Promise.resolve(angular);
} else if (preset === 'web-components' || preset === 'react') {
return Promise.resolve(nx);
} else {
return inquirer
.prompt([
{
name: 'CLI',
message: `CLI to power the Nx workspace`,
default: 'nx',
type: 'list',
choices: [
{
value: 'nx',
name:
'Nx [Extensible CLI for JavaScript and TypeScript applications]'
},
{
value: 'angular',
name: 'Angular CLI [Extensible CLI for Angular applications]'
}
]
}
])
.then(a => (a.CLI === 'angular' ? angular : nx));
}
}
function createSandbox(
packageManager: string,
cli: { package: string; version: string }
) {
console.log(`Creating a sandbox with Nx...`);
const tmpDir = dirSync().name;
writeFileSync(
path.join(tmpDir, 'package.json'),
JSON.stringify({
dependencies: {
[nxTool.packageName]: nxVersion,
'@angular/cli': cliVersion,
typescript: typescriptVersion
'@nrwl/workspace': nxVersion,
[cli.package]: cli.version,
typescript: tsVersion
},
license: 'MIT'
})
);
);
execSync(`${packageManager} install --silent`, {
execSync(`${packageManager} install --silent`, {
cwd: tmpDir,
stdio: [0, 1, 2]
});
});
// creating the app itself
const args = process.argv
return tmpDir;
}
function createApp(
tmpDir: string,
cli: { command: string },
parsedArgs: any,
preset: string
) {
// creating the app itself
const args = process.argv
.slice(2)
.filter(a => !a.startsWith('--cli')) // not used by the new command
.map(a => `"${a}"`)
.join(' ');
output.logSingleLine(
`${output.colors.gray('Running:')} ng new ${args} --collection=${
nxTool.packageName
}`
);
const presetArg = parsedArgs.preset ? '' : ` --preset=${preset}`;
execSync(
console.log(`new ${args}${presetArg} --collection=@nrwl/workspace`);
execSync(
`"${path.join(
tmpDir,
'node_modules',
'.bin',
'ng'
)}" new ${args} --collection=${nxTool.packageName}`,
cli.command
)}" new ${args}${presetArg} --collection=@nrwl/workspace`,
{
stdio: [0, 1, 2]
}
);
);
}
// TODO: vsavkin: reenable for 8.4
// try {
// execSync('nx --version');
// } catch (e) {
// // no nx found
// console.log('-----------------------------------------------------------');
// console.log(`It looks like you don't have the Nx CLI installed globally.`);
// console.log(
// `This means that you might have to use "yarn nx" or "npm nx" to execute commands in your workspace.`
// );
// console.log(
// `If you want to execute the nx command directly, run "yarn global add @nrwl/cli" or "npm install -g @nrwl/cli"`
// );
// console.log('-----------------------------------------------------------');
// }
function showNxWarning() {
try {
execSync('nx --version', { stdio: ['ignore', 'ignore', 'ignore'] });
} catch (e) {
// no nx found
console.log('-----------------------------------------------------------');
console.log(`It looks like you don't have the Nx CLI installed globally.`);
console.log(
`This means that you might have to use "yarn nx" or "npm nx" to execute commands in your workspace.`
);
console.log(
`If you want to execute the nx command directly, run "yarn global add @nrwl/cli" or "npm install -g @nrwl/cli"`
);
console.log('-----------------------------------------------------------');
}
}
function showCliWarning(preset: string, parsedArgs: any) {
if (!parsedArgs.cli) {
if (preset == 'angular' || preset == 'full-stack') {
console.log(
'Because you selected an Angular-specific preset, we generated an Nx workspace powered by the Angular CLi.'
);
console.log(
`If you want want to power the workspace using a different CLI, you can pass it using '--cli'. Find out more by running 'create-nx-workspace --help'.`
);
} else if (preset === 'web-components' || preset === 'react') {
console.log('We generated an Nx workspace powered by the Nx CLi.');
console.log(
`If you want want to power the workspace using a different CLI, you can pass it using '--cli'. Find out more by running 'create-nx-workspace --help'.`
);
}
}
}

View File

@ -30,6 +30,7 @@
"@nrwl/workspace": "*",
"tmp": "0.0.33",
"yargs-parser": "10.0.0",
"yargs": "^11.0.0"
"yargs": "^11.0.0",
"inquirer": "^6.3.1"
}
}

View File

@ -37,21 +37,21 @@ describe('schematic:cypress-project', () => {
expect(tree.exists('apps/my-app-e2e/src/support/index.ts')).toBeTruthy();
});
it('should add update `angular.json` file', async () => {
it('should add update `workspace.json` file', async () => {
const tree = await runSchematic(
'cypress-project',
{ name: 'my-app-e2e', project: 'my-app' },
appTree
);
const angularJson = readJsonInTree(tree, 'angular.json');
const project = angularJson.projects['my-app-e2e'];
const workspaceJson = readJsonInTree(tree, 'workspace.json');
const project = workspaceJson.projects['my-app-e2e'];
expect(project.root).toEqual('apps/my-app-e2e');
expect(project.architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: 'apps/my-app-e2e/tsconfig.e2e.json',
tsConfig: ['apps/my-app-e2e/tsconfig.e2e.json'],
exclude: ['**/node_modules/**', '!apps/my-app-e2e/**']
}
});
@ -107,13 +107,13 @@ describe('schematic:cypress-project', () => {
});
describe('nested', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'cypress-project',
{ name: 'my-app-e2e', project: 'my-dir-my-app', directory: 'my-dir' },
appTree
);
const projectConfig = readJsonInTree(tree, 'angular.json').projects[
const projectConfig = readJsonInTree(tree, 'workspace.json').projects[
'my-dir-my-app-e2e'
];
@ -121,7 +121,7 @@ describe('schematic:cypress-project', () => {
expect(projectConfig.architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: 'apps/my-dir/my-app-e2e/tsconfig.e2e.json',
tsConfig: ['apps/my-dir/my-app-e2e/tsconfig.e2e.json'],
exclude: ['**/node_modules/**', '!apps/my-dir/my-app-e2e/**']
}
});

View File

@ -9,7 +9,13 @@ import {
} from '@angular-devkit/schematics';
import { join, normalize } from '@angular-devkit/core';
// app
import { updateJsonInTree, NxJson } from '@nrwl/workspace';
import {
updateJsonInTree,
NxJson,
updateWorkspaceInTree,
generateProjectLint,
addGlobalLint
} from '@nrwl/workspace';
import { offsetFromRoot } from '@nrwl/workspace';
import { toFileName } from '@nrwl/workspace';
import { Schema } from './schema';
@ -44,8 +50,8 @@ function updateNxJson(options: CypressProjectSchema): Rule {
});
}
function updateAngularJson(options: CypressProjectSchema): Rule {
return updateJsonInTree('angular.json', json => {
function updateWorkspaceJson(options: CypressProjectSchema): Rule {
return updateWorkspaceInTree(json => {
const architect: any = {};
architect.e2e = {
@ -61,16 +67,13 @@ function updateAngularJson(options: CypressProjectSchema): Rule {
}
}
};
architect.lint = {
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: join(normalize(options.projectRoot), 'tsconfig.e2e.json'),
exclude: [
'**/node_modules/**',
'!' + join(normalize(options.projectRoot), '**')
]
}
};
architect.lint = generateProjectLint(
normalize(options.projectRoot),
join(normalize(options.projectRoot), 'tsconfig.e2e.json'),
options.linter
);
json.projects[options.projectName] = {
root: options.projectRoot,
sourceRoot: join(normalize(options.projectRoot), 'src'),
@ -84,8 +87,9 @@ function updateAngularJson(options: CypressProjectSchema): Rule {
export default function(options: CypressProjectSchema): Rule {
options = normalizeOptions(options);
return chain([
addGlobalLint(options.linter),
generateFiles(options),
updateAngularJson(options),
updateWorkspaceJson(options),
updateNxJson(options)
]);
}

View File

@ -2,4 +2,5 @@ export interface Schema {
project: string;
name: string;
directory: string;
linter: 'eslint' | 'tslint';
}

View File

@ -24,6 +24,12 @@
"type": "string",
"description": "A directory where the app is placed",
"x-prompt": "In which directory should the library be generated?"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "tslint"],
"default": "tslint"
}
},
"required": ["name"]

View File

@ -32,13 +32,13 @@ describe('ng-add', () => {
describe('defaultCollection', () => {
it('should be set if none was set before', async () => {
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/express');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/express');
});
it('should be set if @nrwl/workspace was set before', async () => {
tree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/workspace'
};
@ -48,13 +48,13 @@ describe('ng-add', () => {
tree
);
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/express');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/express');
});
it('should not be set if something else was set before', async () => {
tree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/angular'
};
@ -64,8 +64,8 @@ describe('ng-add', () => {
tree
);
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/angular');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/angular');
});
});
});

View File

@ -10,7 +10,7 @@ describe('jestProject', () => {
appTree = Tree.empty();
appTree = createEmptyWorkspace(appTree);
appTree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.projects.lib1 = {
root: 'libs/lib1',
architect: {
@ -52,7 +52,7 @@ describe('jestProject', () => {
expect(resultTree.exists('/libs/lib1/tsconfig.spec.json')).toBeTruthy();
});
it('should alter angular.json', async () => {
it('should alter workspace.json', async () => {
const resultTree = await runSchematic(
'jest-project',
{
@ -61,8 +61,8 @@ describe('jestProject', () => {
},
appTree
);
const angularJson = readJsonInTree(resultTree, 'angular.json');
expect(angularJson.projects.lib1.architect.test).toEqual({
const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(workspaceJson.projects.lib1.architect.test).toEqual({
builder: '@nrwl/jest:jest',
options: {
jestConfig: 'libs/lib1/jest.config.js',
@ -70,9 +70,9 @@ describe('jestProject', () => {
tsConfig: 'libs/lib1/tsconfig.spec.json'
}
});
expect(angularJson.projects.lib1.architect.lint.options.tsConfig).toContain(
'libs/lib1/tsconfig.spec.json'
);
expect(
workspaceJson.projects.lib1.architect.lint.options.tsConfig
).toContain('libs/lib1/tsconfig.spec.json');
});
it('should create a jest.config.js', async () => {
@ -144,7 +144,7 @@ describe('jestProject', () => {
expect(resultTree.exists('src/test-setup.ts')).toBeFalsy();
});
it('should not list the setup file in angular.json', async () => {
it('should not list the setup file in workspace.json', async () => {
const resultTree = await runSchematic(
'jest-project',
{
@ -153,9 +153,9 @@ describe('jestProject', () => {
},
appTree
);
const angularJson = readJsonInTree(resultTree, 'angular.json');
const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(
angularJson.projects.lib1.architect.test.options.setupFile
workspaceJson.projects.lib1.architect.test.options.setupFile
).toBeUndefined();
});
@ -189,7 +189,7 @@ describe('jestProject', () => {
expect(resultTree.exists('src/test-setup.ts')).toBeFalsy();
});
it('should not list the setup file in angular.json', async () => {
it('should not list the setup file in workspace.json', async () => {
const resultTree = await runSchematic(
'jest-project',
{
@ -198,9 +198,9 @@ describe('jestProject', () => {
},
appTree
);
const angularJson = readJsonInTree(resultTree, 'angular.json');
const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(
angularJson.projects.lib1.architect.test.options.setupFile
workspaceJson.projects.lib1.architect.test.options.setupFile
).toBeUndefined();
});

View File

@ -11,7 +11,11 @@ import {
noop,
filter
} from '@angular-devkit/schematics';
import { readJsonInTree, updateJsonInTree } from '@nrwl/workspace';
import {
readJsonInTree,
updateJsonInTree,
updateWorkspaceInTree
} from '@nrwl/workspace';
import { getProjectConfig, addDepsToPackageJson } from '@nrwl/workspace';
import { offsetFromRoot } from '@nrwl/workspace';
import { join, normalize } from '@angular-devkit/core';
@ -69,8 +73,8 @@ function updateTsConfig(options: JestProjectSchema): Rule {
};
}
function updateAngularJson(options: JestProjectSchema): Rule {
return updateJsonInTree('angular.json', json => {
function updateWorkspaceJson(options: JestProjectSchema): Rule {
return updateWorkspaceInTree(json => {
const projectConfig = json.projects[options.project];
projectConfig.architect.test = {
builder: '@nrwl/jest:jest',
@ -106,7 +110,9 @@ function check(options: JestProjectSchema): Rule {
const packageJson = readJsonInTree(host, 'package.json');
if (!packageJson.devDependencies.jest) {
context.logger.warn(`"jest" is not installed as a dependency.`);
context.logger.info(`Add "jest" via "ng add @nrwl/jest"`);
context.logger.info(
`Add "jest" via "yarn add --dev @nrwl/jest" or "npm install -D @nrwl/jest"`
);
}
return host;
};
@ -128,6 +134,6 @@ export default function(options: JestProjectSchema): Rule {
check(options),
generateFiles(options),
updateTsConfig(options),
updateAngularJson(options)
updateWorkspaceJson(options)
]);
}

View File

@ -23,13 +23,13 @@ describe('ng-add', () => {
describe('defaultCollection', () => {
it('should be set if none was set before', async () => {
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/nest');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/nest');
});
it('should be set if @nrwl/workspace was set before', async () => {
tree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/workspace'
};
@ -39,13 +39,13 @@ describe('ng-add', () => {
tree
);
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/nest');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/nest');
});
it('should not be set if something else was set before', async () => {
tree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/angular'
};
@ -55,8 +55,8 @@ describe('ng-add', () => {
tree
);
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/angular');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/angular');
});
});
});

View File

@ -14,10 +14,10 @@ describe('app', () => {
});
describe('not nested', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic('app', { name: 'myNodeApp' }, appTree);
const angularJson = readJsonInTree(tree, '/angular.json');
const project = angularJson.projects['my-node-app'];
const workspaceJson = readJsonInTree(tree, '/workspace.json');
const project = workspaceJson.projects['my-node-app'];
expect(project.root).toEqual('apps/my-node-app');
expect(project.architect).toEqual(
jasmine.objectContaining({
@ -52,7 +52,7 @@ describe('app', () => {
}
})
);
expect(angularJson.projects['my-node-app'].architect.lint).toEqual({
expect(workspaceJson.projects['my-node-app'].architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: [
@ -62,8 +62,8 @@ describe('app', () => {
exclude: ['**/node_modules/**', '!apps/my-node-app/**']
}
});
expect(angularJson.projects['my-node-app-e2e']).toBeUndefined();
expect(angularJson.defaultProject).toEqual('my-node-app');
expect(workspaceJson.projects['my-node-app-e2e']).toBeUndefined();
expect(workspaceJson.defaultProject).toEqual('my-node-app');
});
it('should update nx.json', async () => {
@ -109,20 +109,21 @@ describe('app', () => {
});
describe('nested', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myNodeApp', directory: 'myDir' },
appTree
);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-dir-my-node-app'].root).toEqual(
expect(workspaceJson.projects['my-dir-my-node-app'].root).toEqual(
'apps/my-dir/my-node-app'
);
expect(angularJson.projects['my-dir-my-node-app'].architect.lint).toEqual(
{
expect(
workspaceJson.projects['my-dir-my-node-app'].architect.lint
).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: [
@ -131,11 +132,10 @@ describe('app', () => {
],
exclude: ['**/node_modules/**', '!apps/my-dir/my-node-app/**']
}
}
);
});
expect(angularJson.projects['my-dir-my-node-app-e2e']).toBeUndefined();
expect(angularJson.defaultProject).toEqual('my-dir-my-node-app');
expect(workspaceJson.projects['my-dir-my-node-app-e2e']).toBeUndefined();
expect(workspaceJson.defaultProject).toEqual('my-dir-my-node-app');
});
it('should update nx.json', async () => {
@ -213,12 +213,12 @@ describe('app', () => {
expect(tree.exists('apps/my-node-app/src/test.ts')).toBeFalsy();
expect(tree.exists('apps/my-node-app/tsconfig.spec.json')).toBeFalsy();
expect(tree.exists('apps/my-node-app/jest.config.js')).toBeFalsy();
const angularJson = readJsonInTree(tree, 'angular.json');
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(
angularJson.projects['my-node-app'].architect.test
workspaceJson.projects['my-node-app'].architect.test
).toBeUndefined();
expect(
angularJson.projects['my-node-app'].architect.lint.options.tsConfig
workspaceJson.projects['my-node-app'].architect.lint.options.tsConfig
).toEqual(['apps/my-node-app/tsconfig.app.json']);
});
});
@ -234,7 +234,7 @@ describe('app', () => {
);
expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy();
const serve = JSON.parse(tree.readContent('angular.json')).projects[
const serve = JSON.parse(tree.readContent('workspace.json')).projects[
'my-frontend'
].architect.serve;
expect(serve.options.proxyConfig).toEqual(
@ -252,7 +252,7 @@ describe('app', () => {
);
expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy();
const serve = JSON.parse(tree.readContent('angular.json')).projects[
const serve = JSON.parse(tree.readContent('workspace.json')).projects[
'my-frontend'
].architect.serve;
expect(serve.options.proxyConfig).toEqual(

View File

@ -13,7 +13,12 @@ import {
} from '@angular-devkit/schematics';
import { join, normalize, Path } from '@angular-devkit/core';
import { Schema } from './schema';
import { updateJsonInTree } from '@nrwl/workspace';
import {
updateJsonInTree,
updateWorkspaceInTree,
generateProjectLint,
addGlobalLint
} from '@nrwl/workspace';
import { toFileName } from '@nrwl/workspace';
import { getProjectConfig } from '@nrwl/workspace';
import { offsetFromRoot } from '@nrwl/workspace';
@ -61,16 +66,6 @@ function getBuildConfig(project: any, options: NormalizedSchema) {
};
}
function getLintConfig(project: any) {
return {
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: [join(project.root, 'tsconfig.app.json')],
exclude: ['**/node_modules/**', '!' + join(project.root, '**')]
}
};
}
function getServeConfig(options: NormalizedSchema) {
return {
builder: '@nrwl/node:execute',
@ -80,8 +75,8 @@ function getServeConfig(options: NormalizedSchema) {
};
}
function updateAngularJson(options: NormalizedSchema): Rule {
return updateJsonInTree('angular.json', angularJson => {
function updateWorkspaceJson(options: NormalizedSchema): Rule {
return updateWorkspaceInTree(workspaceJson => {
const project = {
root: options.appProjectRoot,
sourceRoot: join(options.appProjectRoot, 'src'),
@ -93,12 +88,17 @@ function updateAngularJson(options: NormalizedSchema): Rule {
project.architect.build = getBuildConfig(project, options);
project.architect.serve = getServeConfig(options);
project.architect.lint = getLintConfig(project);
angularJson.projects[options.name] = project;
project.architect.lint = generateProjectLint(
normalize(project.root),
join(normalize(project.root), 'tsconfig.app.json'),
options.linter
);
angularJson.defaultProject = angularJson.defaultProject || options.name;
workspaceJson.projects[options.name] = project;
return angularJson;
workspaceJson.defaultProject = workspaceJson.defaultProject || options.name;
return workspaceJson;
});
}
@ -135,7 +135,7 @@ function addProxy(options: NormalizedSchema): Rule {
)
);
updateJsonInTree('angular.json', json => {
updateWorkspaceInTree(json => {
projectConfig.architect.serve.options.proxyConfig = pathToProxyFile;
json.projects[options.frontendProject] = projectConfig;
return json;
@ -151,8 +151,9 @@ export default function(schema: Schema): Rule {
ngAdd({
skipFormat: true
}),
addGlobalLint(options.linter),
addAppFiles(options),
updateAngularJson(options),
updateWorkspaceJson(options),
updateNxJson(options),
options.unitTestRunner === 'jest'
? externalSchematic('@nrwl/jest', 'jest-project', {

View File

@ -1,7 +1,3 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};

View File

@ -1,10 +1,10 @@
import { UnitTestRunner } from '../../utils/test-runners';
export interface Schema {
name: string;
skipFormat: boolean;
skipPackageJson: boolean;
directory?: string;
unitTestRunner: UnitTestRunner;
unitTestRunner: 'jest' | 'none';
linter: 'eslint' | 'tslint';
tags?: string;
frontendProject?: string;
}

View File

@ -28,6 +28,12 @@
"default": false,
"description": "Do not add dependencies to package.json."
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "tslint"],
"default": "tslint"
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],

View File

@ -21,13 +21,13 @@ describe('ng-add', () => {
describe('defaultCollection', () => {
it('should be set if none was set before', async () => {
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/node');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/node');
});
it('should be set if @nrwl/workspace was set before', async () => {
tree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/workspace'
};
@ -37,13 +37,13 @@ describe('ng-add', () => {
tree
);
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/node');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/node');
});
it('should not be set if something else was set before', async () => {
tree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/angular'
};
@ -53,8 +53,8 @@ describe('ng-add', () => {
tree
);
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/angular');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/angular');
});
});
});

View File

@ -13,15 +13,15 @@ describe('app', () => {
});
describe('not nested', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-app'].root).toEqual('apps/my-app');
expect(angularJson.projects['my-app-e2e'].root).toEqual(
expect(workspaceJson.projects['my-app'].root).toEqual('apps/my-app');
expect(workspaceJson.projects['my-app-e2e'].root).toEqual(
'apps/my-app-e2e'
);
expect(angularJson.defaultProject).toEqual('my-app');
expect(workspaceJson.defaultProject).toEqual('my-app');
});
it('should update nx.json', async () => {
@ -76,18 +76,18 @@ describe('app', () => {
});
describe('nested', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir' },
appTree
);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-dir-my-app'].root).toEqual(
expect(workspaceJson.projects['my-dir-my-app'].root).toEqual(
'apps/my-dir/my-app'
);
expect(angularJson.projects['my-dir-my-app-e2e'].root).toEqual(
expect(workspaceJson.projects['my-dir-my-app-e2e'].root).toEqual(
'apps/my-dir/my-app-e2e'
);
});
@ -225,8 +225,8 @@ describe('app', () => {
},
appTree
);
const angularJson = readJsonInTree(tree, 'angular.json');
const architectConfig = angularJson.projects['my-app'].architect;
const workspaceJson = readJsonInTree(tree, 'workspace.json');
const architectConfig = workspaceJson.projects['my-app'].architect;
expect(architectConfig.build.builder).toEqual('@nrwl/web:build');
expect(architectConfig.build.options).toEqual({
assets: ['apps/my-app/src/favicon.ico', 'apps/my-app/src/assets'],
@ -270,8 +270,8 @@ describe('app', () => {
},
appTree
);
const angularJson = readJsonInTree(tree, 'angular.json');
const architectConfig = angularJson.projects['my-app'].architect;
const workspaceJson = readJsonInTree(tree, 'workspace.json');
const architectConfig = workspaceJson.projects['my-app'].architect;
expect(architectConfig.serve.builder).toEqual('@nrwl/web:dev-server');
expect(architectConfig.serve.options).toEqual({
buildTarget: 'my-app:build'
@ -289,8 +289,8 @@ describe('app', () => {
},
appTree
);
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['my-app'].architect.lint).toEqual({
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app'].architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
exclude: ['**/node_modules/**', '!apps/my-app/**'],
@ -312,10 +312,10 @@ describe('app', () => {
expect(tree.exists('apps/my-app/src/app/app.spec.tsx')).toBeFalsy();
expect(tree.exists('apps/my-app/tsconfig.spec.json')).toBeFalsy();
expect(tree.exists('apps/my-app/jest.config.js')).toBeFalsy();
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['my-app'].architect.test).toBeUndefined();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app'].architect.test).toBeUndefined();
expect(
angularJson.projects['my-app'].architect.lint.options.tsConfig
workspaceJson.projects['my-app'].architect.lint.options.tsConfig
).toEqual(['apps/my-app/tsconfig.app.json']);
});
});
@ -328,8 +328,8 @@ describe('app', () => {
appTree
);
expect(tree.exists('apps/my-app-e2e')).toBeFalsy();
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['my-app-e2e']).toBeUndefined();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app-e2e']).toBeUndefined();
});
});
@ -417,17 +417,17 @@ describe('app', () => {
expect(content).toContain('<StyledApp>');
});
it('should exclude styles from angular.json', async () => {
it('should exclude styles from workspace.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', style: '@emotion/styled' },
appTree
);
const angularJSON = readJsonInTree(tree, 'angular.json');
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(
angularJSON.projects['my-app'].architect.build.options.styles
workspaceJson.projects['my-app'].architect.build.options.styles
).toEqual([]);
});

View File

@ -19,9 +19,14 @@ import {
NxJson,
offsetFromRoot,
toFileName,
updateJsonInTree
updateJsonInTree,
generateProjectLint,
addGlobalLint
} from '@nrwl/workspace';
import { addDepsToPackageJson } from '@nrwl/workspace/src/utils/ast-utils';
import {
addDepsToPackageJson,
updateWorkspaceInTree
} from '@nrwl/workspace/src/utils/ast-utils';
import ngAdd from '../ng-add/ng-add';
import * as ts from 'typescript';
@ -48,6 +53,7 @@ export default function(schema: Schema): Rule {
ngAdd({
skipFormat: true
}),
addGlobalLint(options.linter),
createApplicationFiles(options),
updateNxJson(options),
addProject(options),
@ -102,7 +108,7 @@ function updateNxJson(options: NormalizedSchema): Rule {
}
function addProject(options: NormalizedSchema): Rule {
return updateJsonInTree('angular.json', json => {
return updateWorkspaceInTree(json => {
const architect: { [key: string]: any } = {};
architect.build = {
@ -166,16 +172,11 @@ function addProject(options: NormalizedSchema): Rule {
}
};
architect.lint = {
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: [join(options.appProjectRoot, 'tsconfig.app.json')],
exclude: [
'**/node_modules/**',
'!' + join(options.appProjectRoot, '**')
]
}
};
architect.lint = generateProjectLint(
normalize(options.appProjectRoot),
join(normalize(options.appProjectRoot), 'tsconfig.app.json'),
options.linter
);
json.projects[options.projectName] = {
root: options.appProjectRoot,

View File

@ -1,13 +1,12 @@
import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners';
export interface Schema {
name: string;
style?: string;
skipFormat: boolean;
directory?: string;
tags?: string;
unitTestRunner: UnitTestRunner;
e2eTestRunner: E2eTestRunner;
unitTestRunner: 'jest' | 'none';
e2eTestRunner: 'cypress' | 'none';
linter: 'eslint' | 'tslint';
pascalCaseFiles?: boolean;
classComponent?: boolean;
routing?: boolean;

View File

@ -50,6 +50,12 @@
]
}
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "tslint"],
"default": "tslint"
},
"routing": {
"type": "boolean",
"description": "Generate application with routes",

View File

@ -6,7 +6,7 @@
"properties": {
"project": {
"type": "string",
"description": "The name of the project (as specified in angular.json).",
"description": "The name of the project.",
"$default": {
"$source": "projectName"
},

View File

@ -4,4 +4,4 @@ This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `ng test <%= name %>` to execute the unit tests via [Jest](https://jestjs.io).
Run `yarn test <%= name %>` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -13,13 +13,12 @@ describe('lib', () => {
});
describe('not nested', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
const angularJson = readJsonInTree(tree, '/angular.json');
expect(angularJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(angularJson.projects['my-lib'].architect.build).toBeUndefined();
expect(angularJson.projects['my-lib'].architect.lint).toEqual({
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(workspaceJson.projects['my-lib'].architect.build).toBeUndefined();
expect(workspaceJson.projects['my-lib'].architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
exclude: ['**/node_modules/**', '!libs/my-lib/**'],
@ -172,18 +171,18 @@ describe('lib', () => {
).toBeTruthy();
});
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'lib',
{ name: 'myLib', directory: 'myDir' },
appTree
);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-dir-my-lib'].root).toEqual(
expect(workspaceJson.projects['my-dir-my-lib'].root).toEqual(
'libs/my-dir/my-lib'
);
expect(angularJson.projects['my-dir-my-lib'].architect.lint).toEqual({
expect(workspaceJson.projects['my-dir-my-lib'].architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
exclude: ['**/node_modules/**', '!libs/my-dir/my-lib/**'],
@ -256,10 +255,10 @@ describe('lib', () => {
);
expect(resultTree.exists('libs/my-lib/tsconfig.spec.json')).toBeFalsy();
expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy();
const angularJson = readJsonInTree(resultTree, 'angular.json');
expect(angularJson.projects['my-lib'].architect.test).toBeUndefined();
const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(workspaceJson.projects['my-lib'].architect.test).toBeUndefined();
expect(
angularJson.projects['my-lib'].architect.lint.options.tsConfig
workspaceJson.projects['my-lib'].architect.lint.options.tsConfig
).toEqual(['libs/my-lib/tsconfig.lib.json']);
});
});

View File

@ -22,7 +22,10 @@ import {
readJsonInTree,
toClassName,
toFileName,
updateJsonInTree
updateJsonInTree,
updateWorkspaceInTree,
addGlobalLint,
generateProjectLint
} from '@nrwl/workspace';
import { join, normalize, Path } from '@angular-devkit/core';
import * as ts from 'typescript';
@ -44,6 +47,7 @@ export default function(schema: Schema): Rule {
const options = normalizeOptions(schema);
return chain([
addGlobalLint(options.linter),
createFiles(options),
!options.skipTsConfig ? updateTsConfig(options) : noop(),
addProject(options),
@ -71,19 +75,14 @@ export default function(schema: Schema): Rule {
}
function addProject(options: NormalizedSchema): Rule {
return updateJsonInTree('angular.json', json => {
return updateWorkspaceInTree(json => {
const architect: { [key: string]: any } = {};
architect.lint = {
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: [join(normalize(options.projectRoot), 'tsconfig.lib.json')],
exclude: [
'**/node_modules/**',
'!' + join(normalize(options.projectRoot), '**')
]
}
};
architect.lint = generateProjectLint(
normalize(options.projectRoot),
join(normalize(options.projectRoot), 'tsconfig.lib.json'),
options.linter
);
json.projects[options.name] = {
root: options.projectRoot,

View File

@ -1,5 +1,3 @@
import { UnitTestRunner } from '../../utils/test-runners';
export interface Schema {
name: string;
directory?: string;
@ -11,5 +9,6 @@ export interface Schema {
pascalCaseFiles?: boolean;
routing?: boolean;
parentRoute?: string;
unitTestRunner: UnitTestRunner;
unitTestRunner: 'jest' | 'none';
linter: 'eslint' | 'tslint';
}

View File

@ -50,6 +50,12 @@
]
}
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "tslint"],
"default": "tslint"
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],

View File

@ -27,13 +27,13 @@ describe('ng-add', () => {
describe('defaultCollection', () => {
it('should be set if none was set before', async () => {
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/react');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/react');
});
it('should be set if @nrwl/workspace was set before', async () => {
tree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/workspace'
};
@ -43,13 +43,13 @@ describe('ng-add', () => {
tree
);
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/react');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/react');
});
it('should not be set if something else was set before', async () => {
tree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/angular'
};
@ -59,8 +59,8 @@ describe('ng-add', () => {
tree
);
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/angular');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/angular');
});
});
});

View File

@ -1,4 +1,4 @@
import { readCliConfigFile, updateJsonFile } from '@nrwl/workspace';
import { readWorkspaceConfigPath, updateJsonFile } from '@nrwl/workspace';
import { writeFileSync, unlinkSync } from 'fs';
import { offsetFromRoot } from '@nrwl/workspace';
import * as path from 'path';
@ -6,7 +6,7 @@ import * as path from 'path';
export default {
description: 'Create tsconfig.app.json for every app',
run: () => {
const config = readCliConfigFile();
const config = readWorkspaceConfigPath();
config.apps.forEach(app => {
if (!app.root.startsWith('apps/')) return;
const offset = offsetFromRoot(app.root);

View File

@ -5,7 +5,7 @@ import { stripIndents } from '@angular-devkit/core/src/utils/literals';
export default {
description: `Create nx.json before migrating to Angular CLI 6.`,
run: () => {
if (!existsSync('.angular-cli.json') && existsSync('angular.json')) {
if (!existsSync('.angular-cli.json') && existsSync('workspace.json')) {
console.warn(stripIndents`
You have already upgraded to Angular CLI 6.
We will not be able to recover information about your project's tags for you.
@ -13,8 +13,8 @@ export default {
return;
}
const angularJson = readJsonFile('.angular-cli.json');
const projects = angularJson.apps.reduce((projects, app) => {
const workspaceJson = readJsonFile('.angular-cli.json');
const projects = workspaceJson.apps.reduce((projects, app) => {
if (app.name === '$workspaceRoot') {
return projects;
}
@ -33,7 +33,7 @@ export default {
writeFileSync(
'nx.json',
serializeJson({
npmScope: angularJson.project.npmScope,
npmScope: workspaceJson.project.npmScope,
projects: projects
})
);

View File

@ -7,7 +7,7 @@ import { join } from 'path';
export default {
description: `Switch to Nx 6.0`,
run: () => {
if (!existsSync('.angular-cli.json') && existsSync('angular.json')) {
if (!existsSync('.angular-cli.json') && existsSync('workspace.json')) {
console.warn(stripIndents`
You have already upgraded to Angular CLI 6.
We will not be able to recover information about your project's tags for you.

View File

@ -10,7 +10,8 @@ import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import {
createOrUpdate,
readJsonInTree,
updateJsonInTree
updateJsonInTree,
updateWorkspaceInTree
} from '@nrwl/workspace';
import { serializeJson, renameSync } from '@nrwl/workspace';
import { parseTarget, serializeTarget } from '@nrwl/workspace';
@ -256,8 +257,8 @@ function createTsconfigLibJson(host: Tree, project: any) {
}
function createAdditionalFiles(host: Tree) {
const angularJson = readJsonInTree(host, 'angular.json');
Object.entries<any>(angularJson.projects).forEach(([key, project]) => {
const workspaceJson = readJsonInTree(host, 'workspace.json');
Object.entries<any>(workspaceJson.projects).forEach(([key, project]) => {
if (project.architect.test) {
createTsconfigSpecJson(host, project);
createKarma(host, project);
@ -282,9 +283,9 @@ function createAdditionalFiles(host: Tree) {
}
function moveE2eTests(host: Tree, context: SchematicContext) {
const angularJson = readJsonInTree(host, 'angular.json');
const workspaceJson = readJsonInTree(host, 'workspace.json');
Object.entries<any>(angularJson.projects).forEach(([key, p]) => {
Object.entries<any>(workspaceJson.projects).forEach(([key, p]) => {
if (p.projectType === 'application' && !p.architect.e2e) {
renameSync(`${p.root}/e2e`, `${p.root}-e2e/src`, err => {
if (!err) {
@ -320,9 +321,9 @@ function deleteUnneededFiles(host: Tree) {
}
function patchLibIndexFiles(host: Tree, context: SchematicContext) {
const angularJson = readJsonInTree(host, 'angular.json');
const workspaceJson = readJsonInTree(host, 'workspace.json');
Object.entries<any>(angularJson.projects).forEach(([key, p]) => {
Object.entries<any>(workspaceJson.projects).forEach(([key, p]) => {
if (p.projectType === 'library') {
try {
// TODO: incorporate this into fileutils.renameSync
@ -489,8 +490,8 @@ function createDefaultE2eTsConfig(host: Tree, project: any) {
}
function updateTsConfigs(host: Tree) {
const angularJson = readJsonInTree(host, 'angular.json');
Object.entries<any>(angularJson.projects).forEach(([key, project]) => {
const workspaceJson = readJsonInTree(host, 'workspace.json');
Object.entries<any>(workspaceJson.projects).forEach(([key, project]) => {
if (
project.architect.build &&
project.architect.build.options.main.startsWith('apps')
@ -562,7 +563,7 @@ function updateTsConfigs(host: Tree) {
return host;
}
const updateAngularJson = updateJsonInTree('angular.json', json => {
const updateworkspaceJson = updateWorkspaceInTree(json => {
json.newProjectRoot = '';
json.cli = {
...json.cli,
@ -681,7 +682,7 @@ function addInstallTask(host: Tree, context: SchematicContext) {
}
function checkCli6Upgraded(host: Tree) {
if (!host.exists('angular.json') && host.exists('.angular-cli.json')) {
if (!host.exists('workspace.json') && host.exists('.angular-cli.json')) {
throw new Error(
'Please install the latest version and run ng update @angular/cli first'
);
@ -701,7 +702,7 @@ export default function(): Rule {
return chain([
checkCli6Upgraded,
updatePackageJson,
updateAngularJson,
updateworkspaceJson,
moveE2eTests,
updateTsConfigs,
createAdditionalFiles,

View File

@ -25,7 +25,7 @@ const addImplicitDependencies = updateJsonInTree<NxJson>('nx.json', nxJson => {
return {
...nxJson,
implicitDependencies: {
'angular.json': '*',
'workspace.json': '*',
'package.json': '*',
'tsconfig.json': '*',
'tslint.json': '*',

View File

@ -15,7 +15,7 @@ describe('Update 7.2.0', () => {
scripts: {}
});
createJson('tsconfig.json', {});
createJson('angular.json', {
createJson('workspace.json', {
projects: {
app1: {
root: 'apps/app1',
@ -321,7 +321,7 @@ describe('Update 7.2.0', () => {
it('should fix cypress lint configs', async () => {
initialTree = await schematicRunner
.callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.projects['app2-e2e'].architect.lint.options.tsConfig =
'e2e/tsconfig.e2e.json';
return json;
@ -333,8 +333,8 @@ describe('Update 7.2.0', () => {
.runSchematicAsync('update-7.2.0', {}, initialTree)
.toPromise();
expect(
readJsonInTree(result, 'angular.json').projects['app2-e2e'].architect.lint
.options.tsConfig
readJsonInTree(result, 'workspace.json').projects['app2-e2e'].architect
.lint.options.tsConfig
).toEqual('apps/app2-e2e/tsconfig.e2e.json');
[
'/apps/app1/tsconfig.app.json',
@ -358,7 +358,7 @@ describe('Update 7.2.0', () => {
it('should not fail for non-existing tsconfigs', async () => {
initialTree = await schematicRunner
.callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.projects['app2'].architect.lint.options.tsConfig =
'apps/nonexistent/tsconfig.app.json';
return json;

View File

@ -10,7 +10,11 @@ import { normalize, join, Path, dirname } from '@angular-devkit/core';
import { relative } from 'path';
import { updateJsonInTree, readJsonInTree } from '@nrwl/workspace';
import {
updateJsonInTree,
readJsonInTree,
updateWorkspaceInTree
} from '@nrwl/workspace';
import { getWorkspacePath } from '@nrwl/workspace';
import { offsetFromRoot, addUpdateTask } from '@nrwl/workspace';
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
@ -159,9 +163,9 @@ function updateTsConfigs(project: any): Rule {
}
function fixCypressConfigs(host: Tree, context: SchematicContext): Rule {
const angularJson = readJsonInTree(host, 'angular.json');
const workspaceJson = readJsonInTree(host, 'workspace.json');
return chain(
Object.entries<any>(angularJson.projects)
Object.entries<any>(workspaceJson.projects)
.filter(
([key, project]) =>
project.architect.e2e &&
@ -175,12 +179,12 @@ function fixCypressConfigs(host: Tree, context: SchematicContext): Rule {
}
function fixCypressConfig(project: any, projectKey: string): Rule {
return updateJsonInTree('angular.json', angularJson => {
angularJson.projects[projectKey].architect.lint.options.tsConfig = join(
return updateWorkspaceInTree(workspaceJson => {
workspaceJson.projects[projectKey].architect.lint.options.tsConfig = join(
project.root,
'tsconfig.e2e.json'
);
return angularJson;
return workspaceJson;
});
}

View File

@ -5,15 +5,16 @@ import * as path from 'path';
import { serializeJson } from '@nrwl/workspace';
import { readJsonInTree } from '@nrwl/workspace';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
describe('Update 7.5.0', () => {
let initialTree: Tree;
let schematicRunner: SchematicTestRunner;
beforeEach(() => {
initialTree = Tree.empty();
initialTree = createEmptyWorkspace(Tree.empty());
initialTree.create(
initialTree.overwrite(
'package.json',
serializeJson({
devDependencies: {

View File

@ -9,6 +9,7 @@ import { join } from 'path';
import { serializeJson } from '@nrwl/workspace';
import { readJsonInTree, updateJsonInTree } from '@nrwl/workspace';
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
const effectContents = `
import { Injectable } from '@angular/core';
@ -77,9 +78,9 @@ describe('Update 7.6.0', () => {
let schematicRunner: SchematicTestRunner;
beforeEach(() => {
initialTree = new UnitTestTree(Tree.empty());
initialTree = createEmptyWorkspace(Tree.empty());
initialTree.create(
initialTree.overwrite(
'package.json',
serializeJson({
dependencies: {
@ -159,22 +160,22 @@ describe('Update 7.6.0', () => {
.toPromise();
expect(
readJsonInTree(result, 'angular.json').schematics[
readJsonInTree(result, 'workspace.json').schematics[
'@nrwl/schematics:library'
].unitTestRunner
).toEqual('karma');
expect(
readJsonInTree(result, 'angular.json').schematics[
readJsonInTree(result, 'workspace.json').schematics[
'@nrwl/schematics:application'
].unitTestRunner
).toEqual('karma');
expect(
readJsonInTree(result, 'angular.json').schematics[
readJsonInTree(result, 'workspace.json').schematics[
'@nrwl/schematics:application'
].e2eTestRunner
).toEqual('protractor');
expect(
readJsonInTree(result, 'angular.json').schematics[
readJsonInTree(result, 'workspace.json').schematics[
'@nrwl/schematics:node-application'
].framework
).toEqual('express');

View File

@ -8,7 +8,8 @@ import {
formatFiles,
insert,
readJsonInTree,
updateJsonInTree
updateJsonInTree,
updateWorkspaceInTree
} from '@nrwl/workspace';
import {
getSourceNodes,
@ -371,7 +372,7 @@ const addDotEnv = updateJsonInTree('package.json', json => {
return json;
});
const setDefaults = updateJsonInTree('angular.json', json => {
const setDefaults = updateWorkspaceInTree(json => {
if (!json.schematics) {
json.schematics = {};
}

View File

@ -7,13 +7,14 @@ import {
import { join } from 'path';
import { readJsonInTree } from '@nrwl/workspace';
import { serializeJson } from '@nrwl/workspace';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
describe('Update 7.7.0', () => {
let initialTree: Tree;
let schematicRunner: SchematicTestRunner;
beforeEach(() => {
initialTree = new UnitTestTree(Tree.empty());
initialTree = createEmptyWorkspace(Tree.empty());
schematicRunner = new SchematicTestRunner(
'@nrwl/schematics',
@ -28,7 +29,7 @@ describe('Update 7.7.0', () => {
.toPromise();
expect(
readJsonInTree(result, 'angular.json').schematics[
readJsonInTree(result, 'workspace.json').schematics[
'@nrwl/schematics:library'
].framework
).toEqual('angular');
@ -37,7 +38,7 @@ describe('Update 7.7.0', () => {
describe('jest update', () => {
beforeEach(() => {
initialTree.create(
initialTree.overwrite(
'package.json',
serializeJson({
devDependencies: {

View File

@ -1,7 +1,7 @@
import { chain, Rule, Tree } from '@angular-devkit/schematics';
import { updateJsonInTree, insert } from '@nrwl/workspace';
import { formatFiles } from '@nrwl/workspace';
import { formatFiles, updateWorkspaceInTree } from '@nrwl/workspace';
import * as ts from 'typescript';
import {
@ -9,7 +9,7 @@ import {
ReplaceChange
} from '@nrwl/workspace/src/utils/ast-utils';
const setDefaults = updateJsonInTree('angular.json', json => {
const setDefaults = updateWorkspaceInTree(json => {
if (!json.schematics) {
json.schematics = {};
}

View File

@ -3,14 +3,15 @@ import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { serializeJson } from '@nrwl/workspace';
import * as path from 'path';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
describe('Update 7.8.1', () => {
let initialTree: Tree;
let schematicRunner: SchematicTestRunner;
beforeEach(() => {
initialTree = Tree.empty();
initialTree.create(
initialTree = createEmptyWorkspace(Tree.empty());
initialTree.overwrite(
'package.json',
serializeJson({
scripts: {}

View File

@ -3,13 +3,14 @@ import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { updateJsonInTree, readJsonInTree } from '@nrwl/workspace';
import * as path from 'path';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
describe('Update 8-0-0', () => {
let initialTree: Tree;
let schematicRunner: SchematicTestRunner;
beforeEach(async () => {
initialTree = Tree.empty();
initialTree = createEmptyWorkspace(Tree.empty());
schematicRunner = new SchematicTestRunner(
'@nrwl/schematics',
path.join(__dirname, '../migrations.json')
@ -56,7 +57,7 @@ describe('Update 8-0-0', () => {
.toPromise();
initialTree = await schematicRunner
.callRule(
updateJsonInTree('angular.json', json => ({
updateJsonInTree('workspace.json', json => ({
projects: {
'my-app': {
architect: {
@ -158,7 +159,7 @@ describe('Update 8-0-0', () => {
const tree = await schematicRunner
.runSchematicAsync('update-8.0.0', {}, initialTree)
.toPromise();
const { projects } = readJsonInTree(tree, 'angular.json');
const { projects } = readJsonInTree(tree, 'workspace.json');
const { architect } = projects['my-app'];
expect(architect.cypress.builder).toEqual('@nrwl/cypress:cypress');
expect(architect.jest.builder).toEqual('@nrwl/jest:jest');
@ -281,7 +282,7 @@ describe('Update 8-0-0', () => {
.runSchematicAsync('update-8.0.0', {}, initialTree)
.toPromise();
const defaultCollection = readJsonInTree(tree, 'angular.json').cli
const defaultCollection = readJsonInTree(tree, 'workspace.json').cli
.defaultCollection;
expect(defaultCollection).toEqual('@nrwl/angular');
});
@ -304,7 +305,7 @@ describe('Update 8-0-0', () => {
.runSchematicAsync('update-8.0.0', {}, initialTree)
.toPromise();
const defaultCollection = readJsonInTree(tree, 'angular.json').cli
const defaultCollection = readJsonInTree(tree, 'workspace.json').cli
.defaultCollection;
expect(defaultCollection).toEqual('@nrwl/react');
});
@ -326,7 +327,7 @@ describe('Update 8-0-0', () => {
.runSchematicAsync('update-8.0.0', {}, initialTree)
.toPromise();
const defaultCollection = readJsonInTree(tree, 'angular.json').cli
const defaultCollection = readJsonInTree(tree, 'workspace.json').cli
.defaultCollection;
expect(defaultCollection).toEqual('@nrwl/nest');
});
@ -347,7 +348,7 @@ describe('Update 8-0-0', () => {
.runSchematicAsync('update-8.0.0', {}, initialTree)
.toPromise();
const defaultCollection = readJsonInTree(tree, 'angular.json').cli
const defaultCollection = readJsonInTree(tree, 'workspace.json').cli
.defaultCollection;
expect(defaultCollection).toEqual('@nrwl/express');
});
@ -368,7 +369,7 @@ describe('Update 8-0-0', () => {
.runSchematicAsync('update-8.0.0', {}, initialTree)
.toPromise();
const defaultCollection = readJsonInTree(tree, 'angular.json').cli
const defaultCollection = readJsonInTree(tree, 'workspace.json').cli
.defaultCollection;
expect(defaultCollection).toEqual('@nrwl/express');
});
@ -385,7 +386,7 @@ describe('Update 8-0-0', () => {
.toPromise();
initialTree = await schematicRunner
.callRule(
updateJsonInTree('angular.json', json => ({
updateJsonInTree('workspace.json', json => ({
...json,
projects: {}
})),
@ -396,7 +397,7 @@ describe('Update 8-0-0', () => {
.runSchematicAsync('update-8.0.0', {}, initialTree)
.toPromise();
const defaultCollection = readJsonInTree(tree, 'angular.json').cli
const defaultCollection = readJsonInTree(tree, 'workspace.json').cli
.defaultCollection;
expect(defaultCollection).toEqual('@nrwl/workspace');
});

View File

@ -11,7 +11,8 @@ import {
insert,
readJsonInTree,
updateJsonInTree,
addUpdateTask
addUpdateTask,
updateWorkspaceInTree
} from '@nrwl/workspace';
import {
createSourceFile,
@ -31,7 +32,7 @@ function addDependencies() {
return (host: Tree, context: SchematicContext) => {
const dependencies = readJsonInTree(host, 'package.json').dependencies;
const builders = new Set<string>();
const projects = readJsonInTree(host, 'angular.json').projects;
const projects = readJsonInTree(host, 'workspace.json').projects;
Object.values<any>(projects)
.filter(
project =>
@ -110,7 +111,7 @@ const updateUpdateScript = updateJsonInTree('package.json', json => {
return json;
});
const updateBuilders = updateJsonInTree('angular.json', json => {
const updateBuilders = updateWorkspaceInTree(json => {
if (!json.projects) {
return json;
}
@ -285,7 +286,7 @@ const updateDefaultCollection = (host: Tree, context: SchematicContext) => {
'package.json'
);
return updateJsonInTree('angular.json', json => {
return updateWorkspaceInTree(json => {
json.cli = json.cli || {};
if (dependencies['@nrwl/angular']) {
json.cli.defaultCollection = '@nrwl/angular';

View File

@ -1,7 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import { updateJsonFile, readCliConfigFile } from '@nrwl/workspace';
import { updateJsonFile, readWorkspaceConfigPath } from '@nrwl/workspace';
type Migration = { description: string; run(): void };
type MigrationName = { name: string; migration: Migration };
@ -32,7 +32,7 @@ updateLatestMigration();
console.log('All migrations run successfully');
function readLatestMigration(): string {
const angularCli = readCliConfigFile();
const angularCli = readWorkspaceConfigPath();
return angularCli.project.latestMigration;
}

44
packages/tao/index.ts Normal file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env node
import './src/compat/angular-cli-compat';
export async function invokeCommand(
command: string,
root: string,
commandArgs: string[]
) {
if (command === undefined) {
command = 'help';
}
switch (command) {
case 'new':
return (await import('./src/commands/generate')).taoNew(
root,
commandArgs
);
case 'generate':
case 'g':
return (await import('./src/commands/generate')).generate(
root,
commandArgs
);
case 'run':
case 'r':
return (await import('./src/commands/run')).run(root, commandArgs);
case 'help':
case '--help':
return (await import('./src/commands/help')).printHelp();
default:
// this is to make `tao test mylib` same as `tao run mylib:test`
return (await import('./src/commands/run')).run(root, [
`${commandArgs[0]}:${command}`,
...commandArgs.slice(1)
]);
}
}
export async function invokeCli(root: string, args: string[]) {
const [command, ...commandArgs] = args;
process.exit(await invokeCommand(command, root, commandArgs));
}
invokeCli(process.cwd(), process.argv.slice(2));

41
packages/tao/package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "@nrwl/tao",
"version": "0.0.1",
"description": "CLI for generating code and running commands",
"repository": {
"type": "git",
"url": "git+https://github.com/nrwl/nx.git"
},
"keywords": [
"Monorepo",
"Angular",
"React",
"Web",
"Node",
"Nest",
"Jest",
"Cypress",
"CLI"
],
"main": "index.js",
"types": "index.d.ts",
"author": "Victor Savkin",
"license": "MIT",
"bugs": {
"url": "https://github.com/nrwl/nx/issues"
},
"bin": {
"tao": "./index.js"
},
"homepage": "https://nx.dev",
"peerDependencies": {
"@nrwl/workspace": "*"
},
"dependencies": {
"@angular-devkit/schematics": "8.1.1",
"@angular-devkit/core": "8.1.1",
"@angular-devkit/architect": "0.801.1",
"inquirer": "^6.3.1",
"minimist": "^1.2.0"
}
}

View File

@ -0,0 +1,354 @@
import {
convertToCamelCase,
handleErrors,
Schema,
coerceTypes
} from '../shared/params';
import {
JsonObject,
logging,
normalize,
schema,
tags,
terminal,
virtualFs,
experimental
} from '@angular-devkit/core';
import { DryRunEvent, HostTree, Schematic } from '@angular-devkit/schematics';
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { NodeWorkflow } from '@angular-devkit/schematics/tools';
import * as inquirer from 'inquirer';
import { logger } from '../shared/logger';
import { printHelp, commandName } from '../shared/print-help';
import * as fs from 'fs';
import minimist = require('minimist');
interface GenerateOptions {
collectionName: string;
schematicName: string;
schematicOptions: { [k: string]: string };
help: boolean;
debug: boolean;
dryRun: boolean;
force: boolean;
interactive: boolean;
defaults: boolean;
}
function throwInvalidInvocation() {
throw new Error(
`Specify the schematic name (e.g., ${commandName} generate collection-name:schematic-name)`
);
}
function parseGenerateOpts(
args: string[],
mode: 'generate' | 'new',
defaultCollection: string | null
): GenerateOptions {
const schematicOptions = convertToCamelCase(
minimist(args, {
boolean: ['help', 'dryRun', 'debug', 'force', 'interactive'],
alias: {
dryRun: 'dry-run'
},
default: {
debug: false,
dryRun: false,
interactive: true
}
})
);
let collectionName = null;
let schematicName = null;
if (mode === 'generate') {
if (!schematicOptions['_'] || schematicOptions['_'].length === 0) {
throwInvalidInvocation();
}
[collectionName, schematicName] = schematicOptions['_'].shift()!.split(':');
if (!schematicName) {
schematicName = collectionName;
collectionName = defaultCollection;
}
} else {
collectionName = schematicOptions.collection;
schematicName = '';
}
if (!collectionName) {
throwInvalidInvocation();
}
const res = {
collectionName,
schematicName,
schematicOptions,
help: schematicOptions.help,
debug: schematicOptions.debug,
dryRun: schematicOptions.dryRun,
force: schematicOptions.force,
interactive: schematicOptions.interactive,
defaults: schematicOptions.defaults
};
delete schematicOptions.debug;
delete schematicOptions.dryRun;
delete schematicOptions.force;
delete schematicOptions.interactive;
delete schematicOptions.defaults;
delete schematicOptions.help;
delete schematicOptions['--'];
return res;
}
function createRecorder(record: any, logger: logging.Logger) {
return (event: DryRunEvent) => {
const eventPath = event.path.startsWith('/')
? event.path.substr(1)
: event.path;
if (event.kind === 'error') {
record.error = true;
logger.warn(
`ERROR! ${eventPath} ${
event.description == 'alreadyExist'
? 'already exists'
: 'does not exist.'
}.`
);
} else if (event.kind === 'update') {
record.loggingQueue.push(
tags.oneLine`${terminal.white('UPDATE')} ${eventPath} (${
event.content.length
} bytes)`
);
} else if (event.kind === 'create') {
record.loggingQueue.push(
tags.oneLine`${terminal.green('CREATE')} ${eventPath} (${
event.content.length
} bytes)`
);
} else if (event.kind === 'delete') {
record.loggingQueue.push(`${terminal.yellow('DELETE')} ${eventPath}`);
} else if (event.kind === 'rename') {
record.loggingQueue.push(
`${terminal.blue('RENAME')} ${eventPath} => ${event.to}`
);
}
};
}
function createWorkflow(
fsHost: virtualFs.Host<fs.Stats>,
root: string,
opts: GenerateOptions
) {
const workflow = new NodeWorkflow(fsHost, {
force: opts.force,
dryRun: opts.dryRun,
packageManager: 'yarn',
root: normalize(root)
});
const _params = opts.schematicOptions._;
delete opts.schematicOptions._;
workflow.registry.addSmartDefaultProvider('argv', (schema: JsonObject) => {
if ('index' in schema) {
return _params[Number(schema['index'])];
} else {
return _params;
}
});
if (opts.interactive !== false && isTTY()) {
workflow.registry.usePromptProvider(
(definitions: Array<schema.PromptDefinition>) => {
const questions: inquirer.Questions = definitions.map(definition => {
const question = {
name: definition.id,
message: definition.message,
default: definition.default as any
} as any;
const validator = definition.validator;
if (validator) {
question.validate = (input: any) => validator(input);
}
switch (definition.type) {
case 'confirmation':
question.type = 'confirm';
break;
case 'list':
question.type = !!definition.multiselect ? 'checkbox' : 'list';
question.choices =
definition.items &&
definition.items.map(item => {
if (typeof item == 'string') {
return item;
} else {
return {
name: item.label,
value: item.value
};
}
});
break;
default:
question.type = definition.type;
break;
}
return question;
});
return inquirer.prompt(questions);
}
);
}
return workflow;
}
function getCollection(workflow: NodeWorkflow, name: string) {
const collection = workflow.engine.createCollection(name);
if (!collection) throw new Error(`Cannot find collection '${name}'`);
return collection;
}
function printGenHelp(opts: GenerateOptions, schema: Schema) {
printHelp(
`${commandName} generate ${opts.collectionName}:${opts.schematicName}`,
schema
);
}
async function getSchematicDefaults(
root: string,
collection: string,
schematic: string
) {
const workspace = await new experimental.workspace.Workspace(
normalize(root) as any,
new NodeJsSyncHost()
)
.loadWorkspaceFromHost('workspace.json' as any)
.toPromise();
let result = {};
if (workspace.getSchematics()) {
const schematicObject = workspace.getSchematics()[
`${collection}:${schematic}`
];
if (schematicObject) {
result = { ...result, ...(schematicObject as {}) };
}
const collectionObject = workspace.getSchematics()[collection];
if (
typeof collectionObject == 'object' &&
!Array.isArray(collectionObject)
) {
result = { ...result, ...(collectionObject[schematic] as {}) };
}
}
return result;
}
async function runSchematic(
root: string,
workflow: NodeWorkflow,
logger: logging.Logger,
opts: GenerateOptions,
schematic: Schematic<any, any>
): Promise<number> {
const flattenedSchema = await workflow.registry
.flatten(schematic.description.schemaJson!)
.toPromise();
if (opts.help) {
printGenHelp(opts, flattenedSchema as any);
} else {
const defaults =
opts.schematicName === 'tao-new'
? {}
: await getSchematicDefaults(
root,
opts.collectionName,
opts.schematicName
);
const record = { loggingQueue: [] as string[], error: false };
workflow.reporter.subscribe(createRecorder(record, logger));
const schematicOptions = coerceTypes(
opts.schematicOptions,
flattenedSchema as any
);
await workflow
.execute({
collection: opts.collectionName,
schematic: opts.schematicName,
options: { ...defaults, ...schematicOptions },
debug: opts.debug,
logger
})
.toPromise();
if (!record.error) {
record.loggingQueue.forEach(log => logger.info(log));
}
if (opts.dryRun) {
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
}
}
return 0;
}
export async function generate(root: string, args: string[]) {
return handleErrors(logger, async () => {
const fsHost = new virtualFs.ScopedHost(
new NodeJsSyncHost(),
normalize(root)
);
const opts = parseGenerateOpts(
args,
'generate',
await readDefaultCollection(fsHost)
);
const workflow = createWorkflow(fsHost, root, opts);
const collection = getCollection(workflow, opts.collectionName);
const schematic = collection.createSchematic(opts.schematicName, true);
return runSchematic(
root,
workflow,
logger,
{ ...opts, schematicName: schematic.description.name },
schematic
);
});
}
async function readDefaultCollection(host: virtualFs.Host<any>) {
const workspaceJson = JSON.parse(
new HostTree(host).read('workspace.json')!.toString()
);
return workspaceJson.cli ? workspaceJson.cli.defaultCollection : null;
}
export async function taoNew(root: string, args: string[]) {
return handleErrors(logger, async () => {
const fsHost = new virtualFs.ScopedHost(
new NodeJsSyncHost(),
normalize(root)
);
const opts = parseGenerateOpts(args, 'new', null);
const workflow = createWorkflow(fsHost, root, opts);
const collection = getCollection(workflow, opts.collectionName);
const schematic = collection.createSchematic('tao-new', true);
return runSchematic(
root,
workflow,
logger,
{ ...opts, schematicName: schematic.description.name },
schematic
);
});
}
function isTTY(): boolean {
return !!process.stdout.isTTY && !!process.env['CI'];
}

View File

@ -0,0 +1,35 @@
import { tags } from '@angular-devkit/core';
import { logger } from '../shared/logger';
import { toolDescription, commandName } from '../shared/print-help';
import { terminal } from '@angular-devkit/core';
export function printHelp() {
logger.info(tags.stripIndent`
${terminal.bold(toolDescription)}
${terminal.bold('Create a new project.')}
${commandName} new ${terminal.grey(
'[project-name] [--collection=schematic-collection] [options, ...]'
)}
${terminal.bold('Generate code.')}
${commandName} generate ${terminal.grey(
'[schematic-collection:][schematic] [options, ...]'
)}
${commandName} g ${terminal.grey(
'[schematic-collection:][schematic] [options, ...]'
)}
${terminal.bold('Run target.')}
${commandName} run ${terminal.grey(
'[project][:target][:configuration] [options, ...]'
)}
${commandName} r ${terminal.grey(
'[project][:target][:configuration] [options, ...]'
)}
You can also use the infix notation to run a target:
${commandName} [target] [project] [options, ...]
`);
return 0;
}

View File

@ -0,0 +1,123 @@
import {
convertToCamelCase,
handleErrors,
Schema,
coerceTypes
} from '../shared/params';
import {
experimental,
json,
normalize,
schema,
tags
} from '@angular-devkit/core';
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node';
import { Architect } from '@angular-devkit/architect';
import { logger } from '../shared/logger';
import minimist = require('minimist');
import { printHelp, commandName } from '../shared/print-help';
export interface RunOptions {
project: string;
target: string;
configuration: string;
help: boolean;
runOptions: { [k: string]: any };
}
function throwInvalidInvocation() {
throw new Error(
`Specify the project name and the target (e.g., ${commandName} run proj:build)`
);
}
function parseRunOpts(
args: string[],
defaultProjectName: string | null
): RunOptions {
const runOptions = convertToCamelCase(
minimist(args, {
boolean: ['help', 'prod'],
string: ['configuration', 'project']
})
);
const help = runOptions.help;
if (!runOptions._ || !runOptions._[0]) {
throwInvalidInvocation();
}
let [project, target, configuration] = runOptions._[0].split(':');
if (!project && defaultProjectName) project = defaultProjectName;
if (!project || !target) {
throwInvalidInvocation();
}
if (runOptions.configuration) {
configuration = runOptions.configuration;
}
if (runOptions.prod) {
configuration = 'production';
}
if (runOptions.project) {
project = runOptions.project;
}
const res = { project, target, configuration, help, runOptions };
delete runOptions['help'];
delete runOptions['_'];
delete runOptions['configuration'];
delete runOptions['prod'];
delete runOptions['project'];
return res;
}
function printRunHelp(opts: RunOptions, schema: Schema) {
printHelp(`${commandName} run ${opts.project}:${opts.target}`, schema);
}
export async function run(root: string, args: string[]) {
return handleErrors(logger, async () => {
const fsHost = new NodeJsSyncHost();
const workspace = await new experimental.workspace.Workspace(
normalize(root) as any,
fsHost
)
.loadWorkspaceFromHost('workspace.json' as any)
.toPromise();
const opts = parseRunOpts(args, workspace.getDefaultProjectName());
const registry = new json.schema.CoreSchemaRegistry();
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
const architectHost = new WorkspaceNodeModulesArchitectHost(
workspace,
root
);
const architect = new Architect(architectHost, registry);
const builderConf = await architectHost.getBuilderNameForTarget({
project: opts.project,
target: opts.target
});
const builderDesc = await architectHost.resolveBuilder(builderConf);
const flattenedSchema = await registry
.flatten(builderDesc.optionSchema! as json.JsonObject)
.toPromise();
if (opts.help) {
printRunHelp(opts, flattenedSchema as any);
return 0;
} else {
const runOptions = coerceTypes(opts.runOptions, flattenedSchema as any);
const run = await architect.scheduleTarget(
{
project: opts.project,
target: opts.target,
configuration: opts.configuration
},
runOptions,
{ logger }
);
const result = await run.output.toPromise();
await run.stop();
return result.success ? 0 : 1;
}
});
}

View File

@ -0,0 +1,24 @@
const Module = require('module');
const originalRequire = Module.prototype.require;
Module.prototype.require = function() {
const result = originalRequire.apply(this, arguments);
if (arguments[0].startsWith('@angular-devkit/core')) {
const Workspace = originalRequire.apply(this, [
`@angular-devkit/core/src/experimental/workspace`
]).Workspace;
Workspace._workspaceFileNames = [
'workspace.json',
...Workspace._workspaceFileNames
];
const core = originalRequire.apply(this, [
`@angular-devkit/core/src/workspace/core`
]);
core._test_addWorkspaceFile('workspace.json', core.WorkspaceFormat.JSON);
}
return result;
};
try {
require('@angular-devkit/build-angular/src/utils/version').Version.assertCompatibleAngularVersion = () => {};
} catch (e) {}

View File

@ -0,0 +1,13 @@
import { createConsoleLogger } from '@angular-devkit/core/node';
import { terminal } from '@angular-devkit/core';
export const logger = createConsoleLogger(
false,
process.stdout,
process.stderr,
{
warn: s => terminal.bold(terminal.yellow(s)),
error: s => terminal.bold(terminal.red(s)),
fatal: s => terminal.bold(terminal.red(s))
}
);

View File

@ -0,0 +1,35 @@
import { convertToCamelCase } from './params';
describe('params', () => {
describe('convertToCamelCase', () => {
it('should convert dash case to camel case', () => {
expect(
convertToCamelCase({
'one-two': 1
})
).toEqual({
oneTwo: 1
});
});
it('should not convert camel case', () => {
expect(
convertToCamelCase({
oneTwo: 1
})
).toEqual({
oneTwo: 1
});
});
it('should handle mixed case', () => {
expect(
convertToCamelCase({
'one-Two': 1
})
).toEqual({
oneTwo: 1
});
});
});
});

View File

@ -0,0 +1,50 @@
import { logging } from '@angular-devkit/core';
import { UnsuccessfulWorkflowExecution } from '@angular-devkit/schematics';
export type Schema = {
properties: { [p: string]: any };
required: string[];
description: string;
};
export async function handleErrors(logger: logging.Logger, fn: Function) {
try {
return await fn();
} catch (err) {
if (err instanceof UnsuccessfulWorkflowExecution) {
logger.fatal('The Schematic workflow failed. See above.');
} else {
logger.fatal(err.message);
}
return 1;
}
}
export function convertToCamelCase(parsed: {
[k: string]: any;
}): { [k: string]: any } {
return Object.keys(parsed).reduce(
(m, c) => ({ ...m, [camelCase(c)]: parsed[c] }),
{}
);
}
function camelCase(input: string): string {
if (input.indexOf('-') > 1) {
return input
.toLowerCase()
.replace(/-(.)/g, (match, group1) => group1.toUpperCase());
} else {
return input;
}
}
export function coerceTypes(opts: { [k: string]: any }, schema: Schema) {
Object.keys(opts).forEach(k => {
if (schema.properties[k] && schema.properties[k].type == 'boolean') {
opts[k] = opts[k] === true || opts[k] === 'true';
} else if (schema.properties[k] && schema.properties[k].type == 'number') {
opts[k] = Number(opts[k]);
}
});
return opts;
}

View File

@ -0,0 +1,44 @@
import { Schema } from './params';
import { logger } from './logger';
import { tags } from '@angular-devkit/core';
import { terminal } from '@angular-devkit/core';
export function printHelp(header: string, schema: Schema) {
const allPositional = Object.keys(schema.properties).filter(key => {
const p = schema.properties[key];
return p['$default'] && p['$default']['$source'] === 'argv';
});
const positional = allPositional.length > 0 ? ` [${allPositional[0]}]` : '';
const args = Object.keys(schema.properties)
.map(name => {
const d = schema.properties[name];
const def = d.default ? ` (default: ${d.default})` : '';
return formatOption(name, `${d.description}${def}`);
})
.join('\n');
logger.info(tags.stripIndent`
${terminal.bold(header + positional + ' [options,...]')}
${terminal.bold('Options')}:
${args}
${formatOption('help', 'Show available options for project target.')}
`);
}
function formatOption(name: string, description: string) {
return ` --${(name + ' ').substr(0, 22)}${terminal.grey(
description
)}`;
}
export let commandName = 'nx';
export let toolDescription = 'Nx - Extensible Dev Tools for Monorepos.';
export function setCommandNameAndDescription(
name: string,
description: string
) {
commandName = name;
toolDescription = description;
}

View File

@ -13,15 +13,15 @@ describe('app', () => {
});
describe('not nested', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-app'].root).toEqual('apps/my-app');
expect(angularJson.projects['my-app-e2e'].root).toEqual(
expect(workspaceJson.projects['my-app'].root).toEqual('apps/my-app');
expect(workspaceJson.projects['my-app-e2e'].root).toEqual(
'apps/my-app-e2e'
);
expect(angularJson.defaultProject).toEqual('my-app');
expect(workspaceJson.defaultProject).toEqual('my-app');
});
it('should update nx.json', async () => {
@ -78,18 +78,18 @@ describe('app', () => {
});
describe('nested', () => {
it('should update angular.json', async () => {
it('should update workspace.json', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', directory: 'myDir' },
appTree
);
const angularJson = readJsonInTree(tree, '/angular.json');
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-dir-my-app'].root).toEqual(
expect(workspaceJson.projects['my-dir-my-app'].root).toEqual(
'apps/my-dir/my-app'
);
expect(angularJson.projects['my-dir-my-app-e2e'].root).toEqual(
expect(workspaceJson.projects['my-dir-my-app-e2e'].root).toEqual(
'apps/my-dir/my-app-e2e'
);
});
@ -217,8 +217,8 @@ describe('app', () => {
},
appTree
);
const angularJson = readJsonInTree(tree, 'angular.json');
const architectConfig = angularJson.projects['my-app'].architect;
const workspaceJson = readJsonInTree(tree, 'workspace.json');
const architectConfig = workspaceJson.projects['my-app'].architect;
expect(architectConfig.build.builder).toEqual('@nrwl/web:build');
expect(architectConfig.build.options).toEqual({
assets: ['apps/my-app/src/favicon.ico', 'apps/my-app/src/assets'],
@ -262,8 +262,8 @@ describe('app', () => {
},
appTree
);
const angularJson = readJsonInTree(tree, 'angular.json');
const architectConfig = angularJson.projects['my-app'].architect;
const workspaceJson = readJsonInTree(tree, 'workspace.json');
const architectConfig = workspaceJson.projects['my-app'].architect;
expect(architectConfig.serve.builder).toEqual('@nrwl/web:dev-server');
expect(architectConfig.serve.options).toEqual({
buildTarget: 'my-app:build'
@ -281,11 +281,12 @@ describe('app', () => {
},
appTree
);
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['my-app'].architect.lint).toEqual({
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app'].architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint',
options: {
exclude: ['**/node_modules/**'],
exclude: ['**/node_modules/**', '!apps/my-app/**'],
tsConfig: [
'apps/my-app/tsconfig.app.json',
'apps/my-app/tsconfig.spec.json'
@ -318,10 +319,10 @@ describe('app', () => {
expect(tree.exists('apps/my-app/src/app/app.spec.ts')).toBeFalsy();
expect(tree.exists('apps/my-app/tsconfig.spec.json')).toBeFalsy();
expect(tree.exists('apps/my-app/jest.config.js')).toBeFalsy();
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['my-app'].architect.test).toBeUndefined();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app'].architect.test).toBeUndefined();
expect(
angularJson.projects['my-app'].architect.lint.options.tsConfig
workspaceJson.projects['my-app'].architect.lint.options.tsConfig
).toEqual(['apps/my-app/tsconfig.app.json']);
});
});
@ -334,8 +335,8 @@ describe('app', () => {
appTree
);
expect(tree.exists('apps/my-app-e2e')).toBeFalsy();
const angularJson = readJsonInTree(tree, 'angular.json');
expect(angularJson.projects['my-app-e2e']).toBeUndefined();
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(workspaceJson.projects['my-app-e2e']).toBeUndefined();
});
});
});

View File

@ -21,7 +21,10 @@ import {
names,
offsetFromRoot,
getNpmScope,
formatFiles
formatFiles,
updateWorkspaceInTree,
generateProjectLint,
addGlobalLint
} from '@nrwl/workspace';
import ngAdd from '../ng-add/ng-add';
@ -58,7 +61,7 @@ function updateNxJson(options: NormalizedSchema): Rule {
}
function addProject(options: NormalizedSchema): Rule {
return updateJsonInTree('angular.json', json => {
return updateWorkspaceInTree(json => {
const architect: { [key: string]: any } = {};
architect.build = {
@ -122,15 +125,11 @@ function addProject(options: NormalizedSchema): Rule {
}
};
architect.lint = {
builder: '@angular-devkit/build-angular:tslint',
options: {
tsConfig: [
join(normalize(options.appProjectRoot), 'tsconfig.app.json')
],
exclude: ['**/node_modules/**']
}
};
architect.lint = generateProjectLint(
normalize(options.appProjectRoot),
join(normalize(options.appProjectRoot), 'tsconfig.app.json'),
options.linter
);
json.projects[options.projectName] = {
root: options.appProjectRoot,
@ -154,6 +153,7 @@ export default function(schema: Schema): Rule {
ngAdd({
skipFormat: true
}),
addGlobalLint(options.linter),
createApplicationFiles(options),
updateNxJson(options),
addProject(options),

View File

@ -1,5 +1,3 @@
import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners';
export interface Schema {
name: string;
prefix?: string;
@ -7,6 +5,7 @@ export interface Schema {
skipFormat: boolean;
directory?: string;
tags?: string;
unitTestRunner: UnitTestRunner;
e2eTestRunner: E2eTestRunner;
unitTestRunner: 'jest' | 'none';
e2eTestRunner: 'cypress' | 'none';
linter: 'eslint' | 'tslint';
}

View File

@ -42,6 +42,12 @@
]
}
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "tslint"],
"default": "tslint"
},
"skipFormat": {
"description": "Skip formatting files",
"type": "boolean",

View File

@ -23,13 +23,13 @@ describe('ng-add', () => {
describe('defaultCollection', () => {
it('should be set if none was set before', async () => {
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/web');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/web');
});
it('should be set if @nrwl/workspace was set before', async () => {
tree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/workspace'
};
@ -39,13 +39,13 @@ describe('ng-add', () => {
tree
);
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/web');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/web');
});
it('should not be set if something else was set before', async () => {
tree = await callRule(
updateJsonInTree('angular.json', json => {
updateJsonInTree('workspace.json', json => {
json.cli = {
defaultCollection: '@nrwl/angular'
};
@ -55,8 +55,8 @@ describe('ng-add', () => {
tree
);
const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/angular');
const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/angular');
});
});
});

View File

@ -26,12 +26,12 @@ export function _findDefaultServePath(
/^(\w+:)?\/\//.test(baseHref || '') ||
/^(\w+:)?\/\//.test(deployUrl || '')
) {
// If baseHref or deployUrl is absolute, unsupported by ng serve
// If baseHref or deployUrl is absolute, unsupported by nx serve
return null;
}
// normalize baseHref
// for ng serve the starting base is always `/` so a relative
// for nx serve the starting base is always `/` so a relative
// and root relative value are identical
const baseHrefParts = (baseHref || '').split('/').filter(part => part !== '');
if (baseHref && !baseHref.endsWith('/')) {
@ -42,7 +42,7 @@ export function _findDefaultServePath(
if (deployUrl && deployUrl[0] === '/') {
if (baseHref && baseHref[0] === '/' && normalizedBaseHref !== deployUrl) {
// If baseHref and deployUrl are root relative and not equivalent, unsupported by ng serve
// If baseHref and deployUrl are root relative and not equivalent, unsupported by nx serve
return null;
}

View File

@ -30,6 +30,13 @@
"hidden": true
},
"tao-new": {
"factory": "./src/schematics/tao-new/tao-new",
"schema": "./src/schematics/tao-new/schema.json",
"description": "Create a workspace",
"hidden": true
},
"library": {
"factory": "./src/schematics/library/library",
"schema": "./src/schematics/library/schema.json",

Some files were not shown because too many files have changed in this diff Show More