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 - yarn install --network-timeout 1000000
script: script:
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then yarn checkformat --head=$TRAVIS_PULL_REQUEST_SHA --base=$(git merge-base HEAD $TRAVIS_BRANCH); fi' - '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: notifications:
email: false email: false

View File

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

View File

@ -23,6 +23,14 @@ Type: `string`
Frontend project that needs to access this application. This sets up proxy configuration. 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 ### name
Type: `string` Type: `string`

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ Run commands
Type: `string` 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 ### commands

View File

@ -17,6 +17,14 @@ Type: `string`
A directory where the app is placed A directory where the app is placed
### linter
Default: `tslint`
Type: `string`
The tool to use for running lint checks.
### name ### name
Type: `string` 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 ## Options
### cli
Default: `nx`
Type: `string`
CLI used for generating code and running tasks
### commit ### commit
Default: `true` Default: `true`

View File

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

View File

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

View File

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

View File

@ -1,11 +1,18 @@
import { ensureProject, uniq, runCommand, checkFilesExist } from './utils'; import {
ensureProject,
uniq,
runCommand,
checkFilesExist,
forEachCli
} from './utils';
forEachCli(() => {
describe('Delegate to CLI', () => { describe('Delegate to CLI', () => {
it('should delegate to the Angular CLI all non-standard commands', async () => { it('should delegate to the cli all non-standard commands', async () => {
ensureProject(); ensureProject();
const appName = uniq('app'); 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}`); runCommand(`npm run nx -- build ${appName}`);
checkFilesExist( checkFilesExist(
@ -21,3 +28,4 @@ describe('Delegate to CLI', () => {
); );
}, 120000); }, 120000);
}); });
});

View File

@ -1,18 +1,19 @@
import { import {
ensureProject, ensureProject,
patchKarmaToWorkOnWSL,
runCLI, runCLI,
uniq, uniq,
updateFile updateFile,
forEachCli,
supportUi
} from './utils'; } from './utils';
describe('DowngradeModule', () => { forEachCli(() => {
xdescribe('DowngradeModule', () => {
it('should generate a downgradeModule setup', async () => { it('should generate a downgradeModule setup', async () => {
ensureProject(); ensureProject();
const myapp = uniq('myapp'); const myapp = uniq('myapp');
runCLI(`generate @nrwl/angular:app ${myapp} --unit-test-runner=karma`); runCLI(`generate @nrwl/angular:app ${myapp} --unit-test-runner=karma`);
patchKarmaToWorkOnWSL();
updateFile( updateFile(
`apps/${myapp}/src/legacy.js`, `apps/${myapp}/src/legacy.js`,
@ -24,6 +25,9 @@ describe('DowngradeModule', () => {
); );
runCLI(`build ${myapp}`); runCLI(`build ${myapp}`);
if (supportUi()) {
expect(runCLI(`test ${myapp} --no-watch`)).toContain('3 SUCCESS'); expect(runCLI(`test ${myapp} --no-watch`)).toContain('3 SUCCESS');
}
}, 1000000); }, 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,5 +1,6 @@
import { runCLIAsync, ensureProject, uniq, runCLI } from './utils'; import { runCLIAsync, ensureProject, uniq, runCLI, forEachCli } from './utils';
forEachCli(() => {
describe('Jest', () => { describe('Jest', () => {
it('should be able test projects using jest', async done => { it('should be able test projects using jest', async done => {
ensureProject(); ensureProject();
@ -14,10 +15,11 @@ describe('Jest', () => {
runCLIAsync(`generate @nrwl/angular:service test --project ${mylib}`), runCLIAsync(`generate @nrwl/angular:service test --project ${mylib}`),
runCLIAsync(`generate @nrwl/angular:component 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'); expect(appResult.stderr).toContain('Test Suites: 3 passed, 3 total');
const libResult = await runCLIAsync(`test ${mylib}`); const libResult = await runCLIAsync(`test ${mylib}`);
expect(libResult.stderr).toContain('Test Suites: 3 passed, 3 total'); expect(libResult.stderr).toContain('Test Suites: 3 passed, 3 total');
done(); done();
}, 45000); }, 45000);
}); });
});

View File

@ -3,22 +3,25 @@ import {
runCLIAsync, runCLIAsync,
ensureProject, ensureProject,
uniq, uniq,
patchKarmaToWorkOnWSL forEachCli,
supportUi
} from './utils'; } from './utils';
describe('Karma', () => { forEachCli(() => {
xdescribe('Karma', () => {
it('should be able to generate a testable library using karma', async done => { it('should be able to generate a testable library using karma', async done => {
ensureProject(); ensureProject();
const mylib = uniq('mylib'); const mylib = uniq('mylib');
runCLI(`generate @nrwl/angular:lib ${mylib} --unit-test-runner karma`); runCLI(`generate @nrwl/angular:lib ${mylib} --unit-test-runner karma`);
patchKarmaToWorkOnWSL();
await Promise.all([ await Promise.all([
runCLIAsync(`generate @nrwl/angular:service test --project ${mylib}`), runCLIAsync(`generate @nrwl/angular:service test --project ${mylib}`),
runCLIAsync(`generate @nrwl/angular:component test --project ${mylib}`) runCLIAsync(`generate @nrwl/angular:component test --project ${mylib}`)
]); ]);
if (supportUi()) {
const karmaResult = await runCLIAsync(`test ${mylib}`); const karmaResult = await runCLIAsync(`test ${mylib}`);
expect(karmaResult.stdout).toContain('3 SUCCESS'); expect(karmaResult.stdout).toContain('3 SUCCESS');
}
done(); done();
}, 30000); }, 30000);
@ -26,14 +29,16 @@ describe('Karma', () => {
ensureProject(); ensureProject();
const myapp = uniq('myapp'); const myapp = uniq('myapp');
runCLI(`generate @nrwl/angular:app ${myapp} --unit-test-runner karma`); runCLI(`generate @nrwl/angular:app ${myapp} --unit-test-runner karma`);
patchKarmaToWorkOnWSL();
await Promise.all([ await Promise.all([
runCLIAsync(`generate @nrwl/angular:service test --project ${myapp}`), runCLIAsync(`generate @nrwl/angular:service test --project ${myapp}`),
runCLIAsync(`generate @nrwl/angular:component test --project ${myapp}`) runCLIAsync(`generate @nrwl/angular:component test --project ${myapp}`)
]); ]);
if (supportUi()) {
const karmaResult = await runCLIAsync(`test ${myapp}`); const karmaResult = await runCLIAsync(`test ${myapp}`);
expect(karmaResult.stdout).toContain('5 SUCCESS'); expect(karmaResult.stdout).toContain('5 SUCCESS');
}
done(); done();
}, 30000); }, 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, runCLI,
runCLIAsync, runCLIAsync,
runCommand, runCommand,
runNgNew, runNew,
updateFile updateFile,
forEachCli,
runNgAdd
} from './utils'; } from './utils';
forEachCli('angular', () => {
describe('Nrwl Convert to Nx Workspace', () => { describe('Nrwl Convert to Nx Workspace', () => {
beforeEach(cleanup); beforeEach(cleanup);
afterAll(cleanup); afterAll(cleanup);
it('should generate a workspace', () => { it('should generate a workspace', () => {
runNgNew(); runNew('', false, false);
// update package.json // update package.json
const packageJson = readJson('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)); updateFile('angular.json', JSON.stringify(angularCLIJson, null, 2));
// run the command // run the command
runCLI('add @nrwl/workspace --npmScope projscope --skip-install'); runNgAdd('add @nrwl/workspace --npmScope projscope --skip-install');
copyMissingPackages(); copyMissingPackages();
// check that prettier config exits and that files have been moved! // 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', 'workspace-schematic': 'nx workspace-schematic',
help: 'nx help' help: 'nx help'
}); });
expect(updatedPackageJson.devDependencies['@nrwl/workspace']).toBeDefined(); expect(
updatedPackageJson.devDependencies['@nrwl/workspace']
).toBeDefined();
expect(updatedPackageJson.devDependencies['@angular/cli']).toBeDefined(); expect(updatedPackageJson.devDependencies['@angular/cli']).toBeDefined();
const nxJson = readJson('nx.json'); const nxJson = readJson('nx.json');
@ -127,6 +132,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
expect(updatedAngularCLIJson.projects.proj.architect.build).toEqual({ expect(updatedAngularCLIJson.projects.proj.architect.build).toEqual({
builder: '@angular-devkit/build-angular:browser', builder: '@angular-devkit/build-angular:browser',
options: { options: {
aot: false,
outputPath: 'dist/apps/proj', outputPath: 'dist/apps/proj',
index: 'apps/proj/src/index.html', index: 'apps/proj/src/index.html',
main: 'apps/proj/src/main.ts', main: 'apps/proj/src/main.ts',
@ -214,13 +220,15 @@ describe('Nrwl Convert to Nx Workspace', () => {
devServerTarget: 'proj:serve' 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', builder: '@angular-devkit/build-angular:tslint',
options: { options: {
tsConfig: 'apps/proj-e2e/tsconfig.json', tsConfig: 'apps/proj-e2e/tsconfig.json',
exclude: ['**/node_modules/**'] exclude: ['**/node_modules/**']
} }
}); }
);
const updatedTslint = readJson('tslint.json'); const updatedTslint = readJson('tslint.json');
expect(updatedTslint.rules['nx-enforce-module-boundaries']).toEqual([ 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', () => { it('should generate a workspace and not change dependencies, devDependencies, or vscode extensions if they already exist', () => {
// create a new AngularCLI app // create a new AngularCLI app
runNgNew(); runNew();
const nxVersion = '0.0.0'; const nxVersion = '0.0.0';
const schematicsVersion = '0.0.0'; const schematicsVersion = '0.0.0';
const ngrxVersion = '0.0.0'; const ngrxVersion = '0.0.0';
// update package.json // update package.json
const existingPackageJson = readJson('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/store'] = ngrxVersion;
existingPackageJson.dependencies['@ngrx/effects'] = ngrxVersion; existingPackageJson.dependencies['@ngrx/effects'] = ngrxVersion;
existingPackageJson.dependencies['@ngrx/router-store'] = ngrxVersion; existingPackageJson.dependencies['@ngrx/router-store'] = ngrxVersion;
@ -257,7 +267,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
}) })
); );
// run the command // 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 // check that dependencies and devDependencies remained the same
const packageJson = readJson('package.json'); 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/store']).toEqual(ngrxVersion);
expect(packageJson.dependencies['@ngrx/effects']).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( expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual(
ngrxVersion ngrxVersion
); );
@ -282,53 +294,27 @@ describe('Nrwl Convert to Nx Workspace', () => {
it('should convert a project with common libraries in the ecosystem', () => { it('should convert a project with common libraries in the ecosystem', () => {
// create a new AngularCLI app // create a new AngularCLI app
runNgNew(); runNew();
// Add some Angular libraries // Add some Angular libraries
runCLI('add @angular/elements'); runNgAdd('add @angular/elements');
runCLI('add @angular/material'); runNgAdd('add @angular/material');
runCLI('add @angular/pwa'); runNgAdd('add @angular/pwa');
runCLI('add @ngrx/store'); runNgAdd('add @ngrx/store');
runCLI('add @ngrx/effects'); runNgAdd('add @ngrx/effects');
// Add Nx // Add Nx
runCLI('add @nrwl/workspace --skip-install'); runNgAdd('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'
);
}); });
it('should handle different types of errors', () => { it('should handle different types of errors', () => {
// create a new AngularCLI app // create a new AngularCLI app
runNgNew(); runNew();
// Only remove e2e directory // Only remove e2e directory
runCommand('mv e2e e2e-bak'); runCommand('mv e2e e2e-bak');
try { 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'); fail('Did not handle not having a e2e directory');
} catch (e) { } catch (e) {
expect(e.stderr.toString()).toContain( expect(e.stderr.toString()).toContain(
@ -342,7 +328,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
// Remove package.json // Remove package.json
runCommand('mv package.json package.json.bak'); runCommand('mv package.json package.json.bak');
try { 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'); fail('Did not handle not having a package.json');
} catch (e) { } catch (e) {
expect(e.stderr.toString()).toContain( expect(e.stderr.toString()).toContain(
@ -356,7 +342,7 @@ describe('Nrwl Convert to Nx Workspace', () => {
// Remove src // Remove src
runCommand('mv src src-bak'); runCommand('mv src src-bak');
try { 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'); fail('Did not handle not having a src directory');
} catch (e) { } catch (e) {
expect(e.stderr.toString()).toContain('Path: src does not exist'); expect(e.stderr.toString()).toContain('Path: src does not exist');
@ -366,3 +352,10 @@ describe('Nrwl Convert to Nx Workspace', () => {
runCommand('mv src-bak src'); 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,9 +4,11 @@ import {
runCLIAsync, runCLIAsync,
uniq, uniq,
ensureProject, ensureProject,
readJson readJson,
forEachCli
} from './utils'; } from './utils';
forEachCli(() => {
describe('ngrx', () => { describe('ngrx', () => {
it('should work', async () => { it('should work', async () => {
ensureProject(); ensureProject();
@ -31,7 +33,9 @@ describe('ngrx', () => {
`generate @nrwl/angular:ngrx flights --module=libs/${mylib}/src/lib/${mylib}.module.ts --facade` `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,'); expect(runCLI(`build ${myapp}`)).toContain('chunk {main} main-es5.js,');
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`)); expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
expectTestsPass(await runCLIAsync(`test ${mylib} --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` `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,'); expect(runCLI(`build ${myapp}`)).toContain('chunk {main} main-es5.js,');
expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`)); expectTestsPass(await runCLIAsync(`test ${myapp} --no-watch`));
expectTestsPass(await runCLIAsync(`test ${mylib} --no-watch`)); expectTestsPass(await runCLIAsync(`test ${mylib} --no-watch`));
}, 1000000); }, 1000000);
}); });
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,8 @@ import {
import { import {
readJsonInTree, readJsonInTree,
updateJsonInTree, updateJsonInTree,
offsetFromRoot offsetFromRoot,
updateWorkspaceInTree
} from '@nrwl/workspace'; } from '@nrwl/workspace';
import { join, normalize } from '@angular-devkit/core'; import { join, normalize } from '@angular-devkit/core';
import { getProjectConfig } from '@nrwl/workspace'; import { getProjectConfig } from '@nrwl/workspace';
@ -76,8 +77,8 @@ function updateTsSpecConfig(options: KarmaProjectSchema): Rule {
}; };
} }
function updateAngularJson(options: KarmaProjectSchema): Rule { function updateworkspaceJson(options: KarmaProjectSchema): Rule {
return updateJsonInTree('angular.json', json => { return updateWorkspaceInTree(json => {
const projectConfig = json.projects[options.project]; const projectConfig = json.projects[options.project];
projectConfig.architect.test = { projectConfig.architect.test = {
builder: '@angular-devkit/build-angular:karma', builder: '@angular-devkit/build-angular:karma',
@ -124,6 +125,6 @@ export default function(options: KarmaProjectSchema): Rule {
generateFiles(options), generateFiles(options),
updateTsConfig(options), updateTsConfig(options),
updateTsSpecConfig(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 ## 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'); expect(packageJson.name).toEqual('@proj/my-lib');
}); });
it('should update angular.json', async () => { it('should update workspace.json', async () => {
const tree = await runSchematic( const tree = await runSchematic(
'lib', 'lib',
{ name: 'myLib', framework: 'angular', publishable: true }, { name: 'myLib', framework: 'angular', publishable: true },
appTree appTree
); );
const angularJson = readJsonInTree(tree, '/angular.json'); const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-lib'].root).toEqual('libs/my-lib'); expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(angularJson.projects['my-lib'].architect.build).toBeDefined(); expect(workspaceJson.projects['my-lib'].architect.build).toBeDefined();
expect( expect(
angularJson.projects['my-lib'].architect.lint.options.tsConfig workspaceJson.projects['my-lib'].architect.lint.options.tsConfig
).toEqual([ ).toEqual([
'libs/my-lib/tsconfig.lib.json', 'libs/my-lib/tsconfig.lib.json',
'libs/my-lib/tsconfig.spec.json' 'libs/my-lib/tsconfig.spec.json'
]); ]);
expect( expect(
angularJson.projects['my-lib'].architect.lint.options.exclude workspaceJson.projects['my-lib'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!libs/my-lib/**']); ).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( const tree = await runSchematic(
'lib', 'lib',
{ name: 'myLib', publishable: false }, { name: 'myLib', publishable: false },
appTree appTree
); );
const angularJson = readJsonInTree(tree, '/angular.json'); const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(angularJson.projects['my-lib'].root).toEqual('libs/my-lib'); expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(angularJson.projects['my-lib'].architect.build).not.toBeDefined(); expect(
workspaceJson.projects['my-lib'].architect.build
).not.toBeDefined();
}); });
it('should update nx.json', async () => { it('should update nx.json', async () => {
@ -216,8 +218,9 @@ describe('lib', () => {
it('should default the prefix to npmScope', async () => { it('should default the prefix to npmScope', async () => {
const noPrefix = await runSchematic('lib', { name: 'myLib' }, appTree); const noPrefix = await runSchematic('lib', { name: 'myLib' }, appTree);
expect( expect(
JSON.parse(noPrefix.read('angular.json').toString()).projects['my-lib'] JSON.parse(noPrefix.read('workspace.json').toString()).projects[
.prefix 'my-lib'
].prefix
).toEqual('proj'); ).toEqual('proj');
const withPrefix = await runSchematic( const withPrefix = await runSchematic(
@ -226,7 +229,7 @@ describe('lib', () => {
appTree appTree
); );
expect( expect(
JSON.parse(withPrefix.read('angular.json').toString()).projects[ JSON.parse(withPrefix.read('workspace.json').toString()).projects[
'my-lib' 'my-lib'
].prefix ].prefix
).toEqual('custom'); ).toEqual('custom');
@ -381,26 +384,26 @@ describe('lib', () => {
expect(ngPackage.dest).toEqual('../../../dist/libs/my-dir/my-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( const tree = await runSchematic(
'lib', 'lib',
{ name: 'myLib', directory: 'myDir' }, { name: 'myLib', directory: 'myDir' },
appTree 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' 'libs/my-dir/my-lib'
); );
expect( expect(
angularJson.projects['my-dir-my-lib'].architect.lint.options.tsConfig workspaceJson.projects['my-dir-my-lib'].architect.lint.options.tsConfig
).toEqual([ ).toEqual([
'libs/my-dir/my-lib/tsconfig.lib.json', 'libs/my-dir/my-lib/tsconfig.lib.json',
'libs/my-dir/my-lib/tsconfig.spec.json' 'libs/my-dir/my-lib/tsconfig.spec.json'
]); ]);
expect( 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/**']); ).toEqual(['**/node_modules/**', '!libs/my-dir/my-lib/**']);
}); });
@ -763,9 +766,9 @@ describe('lib', () => {
appTree 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': { '@nrwl/angular:component': {
styleext: 'scss' styleext: 'scss'
} }
@ -785,18 +788,18 @@ describe('lib', () => {
expect(resultTree.exists('libs/my-lib/tsconfig.spec.json')).toBeTruthy(); expect(resultTree.exists('libs/my-lib/tsconfig.spec.json')).toBeTruthy();
expect(resultTree.exists('libs/my-lib/karma.conf.js')).toBeTruthy(); expect(resultTree.exists('libs/my-lib/karma.conf.js')).toBeTruthy();
expect(resultTree.exists('karma.conf.js')).toBeTruthy(); expect(resultTree.exists('karma.conf.js')).toBeTruthy();
const angularJson = readJsonInTree(resultTree, 'angular.json'); const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(angularJson.projects['my-lib'].architect.test.builder).toEqual( expect(workspaceJson.projects['my-lib'].architect.test.builder).toEqual(
'@angular-devkit/build-angular:karma' '@angular-devkit/build-angular:karma'
); );
expect( expect(
angularJson.projects['my-lib'].architect.lint.options.tsConfig workspaceJson.projects['my-lib'].architect.lint.options.tsConfig
).toEqual([ ).toEqual([
'libs/my-lib/tsconfig.lib.json', 'libs/my-lib/tsconfig.lib.json',
'libs/my-lib/tsconfig.spec.json' 'libs/my-lib/tsconfig.spec.json'
]); ]);
expect( expect(
angularJson.projects['my-lib'].architect.lint.options.exclude workspaceJson.projects['my-lib'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!libs/my-lib/**']); ).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/tsconfig.spec.json')).toBeFalsy();
expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy(); expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy();
expect(resultTree.exists('libs/my-lib/karma.conf.js')).toBeFalsy(); expect(resultTree.exists('libs/my-lib/karma.conf.js')).toBeFalsy();
const angularJson = readJsonInTree(resultTree, 'angular.json'); const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(angularJson.projects['my-lib'].architect.test).toBeUndefined(); expect(workspaceJson.projects['my-lib'].architect.test).toBeUndefined();
expect( expect(
angularJson.projects['my-lib'].architect.lint.options.tsConfig workspaceJson.projects['my-lib'].architect.lint.options.tsConfig
).toEqual(['libs/my-lib/tsconfig.lib.json']); ).toEqual(['libs/my-lib/tsconfig.lib.json']);
}); });
}); });

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
export const nxVersion = '*'; export const nxVersion = '*';
export const angularVersion = '^8.0.0'; 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 angularJsVersion = '1.6.6';
export const ngrxVersion = '8.1.0'; export const ngrxVersion = '8.1.0';
export const rxjsVersion = '~6.4.0'; export const rxjsVersion = '~6.4.0';

View File

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

View File

@ -4,40 +4,89 @@
import { output } from '@nrwl/workspace/src/command-line/output'; import { output } from '@nrwl/workspace/src/command-line/output';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { writeFileSync } from 'fs'; import { writeFileSync } from 'fs';
import * as inquirer from 'inquirer';
import * as path from 'path'; import * as path from 'path';
import { dirSync } from 'tmp'; import { dirSync } from 'tmp';
import * as yargsParser from 'yargs-parser'; 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, { const parsedArgs = yargsParser(process.argv, {
string: ['directory'], string: ['cli', 'preset'],
boolean: ['help'] boolean: ['help']
}); });
if (parsedArgs.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(` 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 Create a new Nx workspace
Options: Options:
directory path to the workspace root directory name workspace name
[ng new options] any 'ng new' options preset What to create in a new workspace (options: ${presetOptions
run 'ng new --help' for more information .map(o => '"' + o.value + '"')
.join(', ')})
cli CLI to power the Nx workspace (options: "nx", "angular")
[new workspace options] any 'new workspace' options
`); `);
process.exit(0);
} }
const nxTool = { function determinePackageManager() {
name: 'Schematics', // If you have Angular CLI installed, read Angular CLI config.
packageName: '@nrwl/workspace' // If it isn't not installed, default to 'yarn'.
};
let packageManager: string; let packageManager: string;
try { try {
packageManager = execSync('ng config -g cli.packageManager', { packageManager = execSync('ng config -g cli.packageManager', {
stdio: ['ignore', 'pipe', 'ignore'] stdio: ['ignore', 'pipe', 'ignore'],
timeout: 500
}) })
.toString() .toString()
.trim(); .trim();
@ -51,10 +100,12 @@ try {
} catch (e) { } catch (e) {
packageManager = 'npm'; packageManager = 'npm';
} }
return packageManager;
}
function validateInput(parsedArgs: any) {
const projectName = parsedArgs._[2]; const projectName = parsedArgs._[2];
// check that the workspace name is passed in
if (!projectName) { if (!projectName) {
output.error({ output.error({
title: 'A project name is required when creating a new workspace', title: 'A project name is required when creating a new workspace',
@ -67,21 +118,102 @@ if (!projectName) {
process.exit(1); process.exit(1);
} }
// creating the sandbox return projectName;
output.logSingleLine(`Creating a sandbox...`); }
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);
}
}
function determineCli(preset: string, parsedArgs: any) {
const angular = {
package: '@angular/cli',
version: angularCliVersion,
command: 'ng'
};
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; const tmpDir = dirSync().name;
const nxVersion = 'NX_VERSION';
const cliVersion = 'ANGULAR_CLI_VERSION';
const typescriptVersion = 'TYPESCRIPT_VERSION';
writeFileSync( writeFileSync(
path.join(tmpDir, 'package.json'), path.join(tmpDir, 'package.json'),
JSON.stringify({ JSON.stringify({
dependencies: { dependencies: {
[nxTool.packageName]: nxVersion, '@nrwl/workspace': nxVersion,
'@angular/cli': cliVersion, [cli.package]: cli.version,
typescript: typescriptVersion typescript: tsVersion
}, },
license: 'MIT' license: 'MIT'
}) })
@ -92,42 +224,69 @@ execSync(`${packageManager} install --silent`, {
stdio: [0, 1, 2] stdio: [0, 1, 2]
}); });
return tmpDir;
}
function createApp(
tmpDir: string,
cli: { command: string },
parsedArgs: any,
preset: string
) {
// creating the app itself // creating the app itself
const args = process.argv const args = process.argv
.slice(2) .slice(2)
.filter(a => !a.startsWith('--cli')) // not used by the new command
.map(a => `"${a}"`) .map(a => `"${a}"`)
.join(' '); .join(' ');
output.logSingleLine( const presetArg = parsedArgs.preset ? '' : ` --preset=${preset}`;
`${output.colors.gray('Running:')} ng new ${args} --collection=${
nxTool.packageName
}`
);
console.log(`new ${args}${presetArg} --collection=@nrwl/workspace`);
execSync( execSync(
`"${path.join( `"${path.join(
tmpDir, tmpDir,
'node_modules', 'node_modules',
'.bin', '.bin',
'ng' cli.command
)}" new ${args} --collection=${nxTool.packageName}`, )}" new ${args}${presetArg} --collection=@nrwl/workspace`,
{ {
stdio: [0, 1, 2] stdio: [0, 1, 2]
} }
); );
}
// TODO: vsavkin: reenable for 8.4 function showNxWarning() {
// try { try {
// execSync('nx --version'); execSync('nx --version', { stdio: ['ignore', 'ignore', 'ignore'] });
// } catch (e) { } catch (e) {
// // no nx found // no nx found
// console.log('-----------------------------------------------------------'); console.log('-----------------------------------------------------------');
// console.log(`It looks like you don't have the Nx CLI installed globally.`); console.log(`It looks like you don't have the Nx CLI installed globally.`);
// console.log( console.log(
// `This means that you might have to use "yarn nx" or "npm nx" to execute commands in your workspace.` `This means that you might have to use "yarn nx" or "npm nx" to execute commands in your workspace.`
// ); );
// console.log( console.log(
// `If you want to execute the nx command directly, run "yarn global add @nrwl/cli" or "npm install -g @nrwl/cli"` `If you want to execute the nx command directly, run "yarn global add @nrwl/cli" or "npm install -g @nrwl/cli"`
// ); );
// console.log('-----------------------------------------------------------'); 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": "*", "@nrwl/workspace": "*",
"tmp": "0.0.33", "tmp": "0.0.33",
"yargs-parser": "10.0.0", "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(); 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( const tree = await runSchematic(
'cypress-project', 'cypress-project',
{ name: 'my-app-e2e', project: 'my-app' }, { name: 'my-app-e2e', project: 'my-app' },
appTree appTree
); );
const angularJson = readJsonInTree(tree, 'angular.json'); const workspaceJson = readJsonInTree(tree, 'workspace.json');
const project = angularJson.projects['my-app-e2e']; const project = workspaceJson.projects['my-app-e2e'];
expect(project.root).toEqual('apps/my-app-e2e'); expect(project.root).toEqual('apps/my-app-e2e');
expect(project.architect.lint).toEqual({ expect(project.architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint', builder: '@angular-devkit/build-angular:tslint',
options: { options: {
tsConfig: 'apps/my-app-e2e/tsconfig.e2e.json', tsConfig: ['apps/my-app-e2e/tsconfig.e2e.json'],
exclude: ['**/node_modules/**', '!apps/my-app-e2e/**'] exclude: ['**/node_modules/**', '!apps/my-app-e2e/**']
} }
}); });
@ -107,13 +107,13 @@ describe('schematic:cypress-project', () => {
}); });
describe('nested', () => { describe('nested', () => {
it('should update angular.json', async () => { it('should update workspace.json', async () => {
const tree = await runSchematic( const tree = await runSchematic(
'cypress-project', 'cypress-project',
{ name: 'my-app-e2e', project: 'my-dir-my-app', directory: 'my-dir' }, { name: 'my-app-e2e', project: 'my-dir-my-app', directory: 'my-dir' },
appTree appTree
); );
const projectConfig = readJsonInTree(tree, 'angular.json').projects[ const projectConfig = readJsonInTree(tree, 'workspace.json').projects[
'my-dir-my-app-e2e' 'my-dir-my-app-e2e'
]; ];
@ -121,7 +121,7 @@ describe('schematic:cypress-project', () => {
expect(projectConfig.architect.lint).toEqual({ expect(projectConfig.architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint', builder: '@angular-devkit/build-angular:tslint',
options: { 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/**'] exclude: ['**/node_modules/**', '!apps/my-dir/my-app-e2e/**']
} }
}); });

View File

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

View File

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

View File

@ -24,6 +24,12 @@
"type": "string", "type": "string",
"description": "A directory where the app is placed", "description": "A directory where the app is placed",
"x-prompt": "In which directory should the library be generated?" "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"] "required": ["name"]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,12 @@
"default": false, "default": false,
"description": "Do not add dependencies to package.json." "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": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "none"],

View File

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

View File

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

View File

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

View File

@ -1,13 +1,12 @@
import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners';
export interface Schema { export interface Schema {
name: string; name: string;
style?: string; style?: string;
skipFormat: boolean; skipFormat: boolean;
directory?: string; directory?: string;
tags?: string; tags?: string;
unitTestRunner: UnitTestRunner; unitTestRunner: 'jest' | 'none';
e2eTestRunner: E2eTestRunner; e2eTestRunner: 'cypress' | 'none';
linter: 'eslint' | 'tslint';
pascalCaseFiles?: boolean; pascalCaseFiles?: boolean;
classComponent?: boolean; classComponent?: boolean;
routing?: 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": { "routing": {
"type": "boolean", "type": "boolean",
"description": "Generate application with routes", "description": "Generate application with routes",

View File

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

View File

@ -4,4 +4,4 @@ This library was generated with [Nx](https://nx.dev).
## Running unit tests ## 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', () => { describe('not nested', () => {
it('should update angular.json', async () => { it('should update workspace.json', async () => {
const tree = await runSchematic('lib', { name: 'myLib' }, appTree); const tree = await runSchematic('lib', { name: 'myLib' }, appTree);
const angularJson = readJsonInTree(tree, '/angular.json'); const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib');
expect(angularJson.projects['my-lib'].root).toEqual('libs/my-lib'); expect(workspaceJson.projects['my-lib'].architect.build).toBeUndefined();
expect(angularJson.projects['my-lib'].architect.build).toBeUndefined(); expect(workspaceJson.projects['my-lib'].architect.lint).toEqual({
expect(angularJson.projects['my-lib'].architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint', builder: '@angular-devkit/build-angular:tslint',
options: { options: {
exclude: ['**/node_modules/**', '!libs/my-lib/**'], exclude: ['**/node_modules/**', '!libs/my-lib/**'],
@ -172,18 +171,18 @@ describe('lib', () => {
).toBeTruthy(); ).toBeTruthy();
}); });
it('should update angular.json', async () => { it('should update workspace.json', async () => {
const tree = await runSchematic( const tree = await runSchematic(
'lib', 'lib',
{ name: 'myLib', directory: 'myDir' }, { name: 'myLib', directory: 'myDir' },
appTree 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' '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', builder: '@angular-devkit/build-angular:tslint',
options: { options: {
exclude: ['**/node_modules/**', '!libs/my-dir/my-lib/**'], 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/tsconfig.spec.json')).toBeFalsy();
expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy(); expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy();
const angularJson = readJsonInTree(resultTree, 'angular.json'); const workspaceJson = readJsonInTree(resultTree, 'workspace.json');
expect(angularJson.projects['my-lib'].architect.test).toBeUndefined(); expect(workspaceJson.projects['my-lib'].architect.test).toBeUndefined();
expect( expect(
angularJson.projects['my-lib'].architect.lint.options.tsConfig workspaceJson.projects['my-lib'].architect.lint.options.tsConfig
).toEqual(['libs/my-lib/tsconfig.lib.json']); ).toEqual(['libs/my-lib/tsconfig.lib.json']);
}); });
}); });

View File

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

View File

@ -1,5 +1,3 @@
import { UnitTestRunner } from '../../utils/test-runners';
export interface Schema { export interface Schema {
name: string; name: string;
directory?: string; directory?: string;
@ -11,5 +9,6 @@ export interface Schema {
pascalCaseFiles?: boolean; pascalCaseFiles?: boolean;
routing?: boolean; routing?: boolean;
parentRoute?: string; 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": { "unitTestRunner": {
"type": "string", "type": "string",
"enum": ["jest", "none"], "enum": ["jest", "none"],

View File

@ -27,13 +27,13 @@ describe('ng-add', () => {
describe('defaultCollection', () => { describe('defaultCollection', () => {
it('should be set if none was set before', async () => { it('should be set if none was set before', async () => {
const result = await runSchematic('ng-add', {}, tree); const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json'); const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/react'); expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/react');
}); });
it('should be set if @nrwl/workspace was set before', async () => { it('should be set if @nrwl/workspace was set before', async () => {
tree = await callRule( tree = await callRule(
updateJsonInTree('angular.json', json => { updateJsonInTree('workspace.json', json => {
json.cli = { json.cli = {
defaultCollection: '@nrwl/workspace' defaultCollection: '@nrwl/workspace'
}; };
@ -43,13 +43,13 @@ describe('ng-add', () => {
tree tree
); );
const result = await runSchematic('ng-add', {}, tree); const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json'); const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/react'); expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/react');
}); });
it('should not be set if something else was set before', async () => { it('should not be set if something else was set before', async () => {
tree = await callRule( tree = await callRule(
updateJsonInTree('angular.json', json => { updateJsonInTree('workspace.json', json => {
json.cli = { json.cli = {
defaultCollection: '@nrwl/angular' defaultCollection: '@nrwl/angular'
}; };
@ -59,8 +59,8 @@ describe('ng-add', () => {
tree tree
); );
const result = await runSchematic('ng-add', {}, tree); const result = await runSchematic('ng-add', {}, tree);
const angularJson = readJsonInTree(result, 'angular.json'); const workspaceJson = readJsonInTree(result, 'workspace.json');
expect(angularJson.cli.defaultCollection).toEqual('@nrwl/angular'); 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 { writeFileSync, unlinkSync } from 'fs';
import { offsetFromRoot } from '@nrwl/workspace'; import { offsetFromRoot } from '@nrwl/workspace';
import * as path from 'path'; import * as path from 'path';
@ -6,7 +6,7 @@ import * as path from 'path';
export default { export default {
description: 'Create tsconfig.app.json for every app', description: 'Create tsconfig.app.json for every app',
run: () => { run: () => {
const config = readCliConfigFile(); const config = readWorkspaceConfigPath();
config.apps.forEach(app => { config.apps.forEach(app => {
if (!app.root.startsWith('apps/')) return; if (!app.root.startsWith('apps/')) return;
const offset = offsetFromRoot(app.root); const offset = offsetFromRoot(app.root);

View File

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

View File

@ -7,7 +7,7 @@ import { join } from 'path';
export default { export default {
description: `Switch to Nx 6.0`, description: `Switch to Nx 6.0`,
run: () => { run: () => {
if (!existsSync('.angular-cli.json') && existsSync('angular.json')) { if (!existsSync('.angular-cli.json') && existsSync('workspace.json')) {
console.warn(stripIndents` console.warn(stripIndents`
You have already upgraded to Angular CLI 6. You have already upgraded to Angular CLI 6.
We will not be able to recover information about your project's tags for you. 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 { import {
createOrUpdate, createOrUpdate,
readJsonInTree, readJsonInTree,
updateJsonInTree updateJsonInTree,
updateWorkspaceInTree
} from '@nrwl/workspace'; } from '@nrwl/workspace';
import { serializeJson, renameSync } from '@nrwl/workspace'; import { serializeJson, renameSync } from '@nrwl/workspace';
import { parseTarget, serializeTarget } from '@nrwl/workspace'; import { parseTarget, serializeTarget } from '@nrwl/workspace';
@ -256,8 +257,8 @@ function createTsconfigLibJson(host: Tree, project: any) {
} }
function createAdditionalFiles(host: Tree) { function createAdditionalFiles(host: Tree) {
const angularJson = readJsonInTree(host, 'angular.json'); const workspaceJson = readJsonInTree(host, 'workspace.json');
Object.entries<any>(angularJson.projects).forEach(([key, project]) => { Object.entries<any>(workspaceJson.projects).forEach(([key, project]) => {
if (project.architect.test) { if (project.architect.test) {
createTsconfigSpecJson(host, project); createTsconfigSpecJson(host, project);
createKarma(host, project); createKarma(host, project);
@ -282,9 +283,9 @@ function createAdditionalFiles(host: Tree) {
} }
function moveE2eTests(host: Tree, context: SchematicContext) { 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) { if (p.projectType === 'application' && !p.architect.e2e) {
renameSync(`${p.root}/e2e`, `${p.root}-e2e/src`, err => { renameSync(`${p.root}/e2e`, `${p.root}-e2e/src`, err => {
if (!err) { if (!err) {
@ -320,9 +321,9 @@ function deleteUnneededFiles(host: Tree) {
} }
function patchLibIndexFiles(host: Tree, context: SchematicContext) { 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') { if (p.projectType === 'library') {
try { try {
// TODO: incorporate this into fileutils.renameSync // TODO: incorporate this into fileutils.renameSync
@ -489,8 +490,8 @@ function createDefaultE2eTsConfig(host: Tree, project: any) {
} }
function updateTsConfigs(host: Tree) { function updateTsConfigs(host: Tree) {
const angularJson = readJsonInTree(host, 'angular.json'); const workspaceJson = readJsonInTree(host, 'workspace.json');
Object.entries<any>(angularJson.projects).forEach(([key, project]) => { Object.entries<any>(workspaceJson.projects).forEach(([key, project]) => {
if ( if (
project.architect.build && project.architect.build &&
project.architect.build.options.main.startsWith('apps') project.architect.build.options.main.startsWith('apps')
@ -562,7 +563,7 @@ function updateTsConfigs(host: Tree) {
return host; return host;
} }
const updateAngularJson = updateJsonInTree('angular.json', json => { const updateworkspaceJson = updateWorkspaceInTree(json => {
json.newProjectRoot = ''; json.newProjectRoot = '';
json.cli = { json.cli = {
...json.cli, ...json.cli,
@ -681,7 +682,7 @@ function addInstallTask(host: Tree, context: SchematicContext) {
} }
function checkCli6Upgraded(host: Tree) { 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( throw new Error(
'Please install the latest version and run ng update @angular/cli first' 'Please install the latest version and run ng update @angular/cli first'
); );
@ -701,7 +702,7 @@ export default function(): Rule {
return chain([ return chain([
checkCli6Upgraded, checkCli6Upgraded,
updatePackageJson, updatePackageJson,
updateAngularJson, updateworkspaceJson,
moveE2eTests, moveE2eTests,
updateTsConfigs, updateTsConfigs,
createAdditionalFiles, createAdditionalFiles,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { updateJsonFile, readCliConfigFile } from '@nrwl/workspace'; import { updateJsonFile, readWorkspaceConfigPath } from '@nrwl/workspace';
type Migration = { description: string; run(): void }; type Migration = { description: string; run(): void };
type MigrationName = { name: string; migration: Migration }; type MigrationName = { name: string; migration: Migration };
@ -32,7 +32,7 @@ updateLatestMigration();
console.log('All migrations run successfully'); console.log('All migrations run successfully');
function readLatestMigration(): string { function readLatestMigration(): string {
const angularCli = readCliConfigFile(); const angularCli = readWorkspaceConfigPath();
return angularCli.project.latestMigration; 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', () => { describe('not nested', () => {
it('should update angular.json', async () => { it('should update workspace.json', async () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree); 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(workspaceJson.projects['my-app'].root).toEqual('apps/my-app');
expect(angularJson.projects['my-app-e2e'].root).toEqual( expect(workspaceJson.projects['my-app-e2e'].root).toEqual(
'apps/my-app-e2e' 'apps/my-app-e2e'
); );
expect(angularJson.defaultProject).toEqual('my-app'); expect(workspaceJson.defaultProject).toEqual('my-app');
}); });
it('should update nx.json', async () => { it('should update nx.json', async () => {
@ -78,18 +78,18 @@ describe('app', () => {
}); });
describe('nested', () => { describe('nested', () => {
it('should update angular.json', async () => { it('should update workspace.json', async () => {
const tree = await runSchematic( const tree = await runSchematic(
'app', 'app',
{ name: 'myApp', directory: 'myDir' }, { name: 'myApp', directory: 'myDir' },
appTree 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' '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' 'apps/my-dir/my-app-e2e'
); );
}); });
@ -217,8 +217,8 @@ describe('app', () => {
}, },
appTree appTree
); );
const angularJson = readJsonInTree(tree, 'angular.json'); const workspaceJson = readJsonInTree(tree, 'workspace.json');
const architectConfig = angularJson.projects['my-app'].architect; const architectConfig = workspaceJson.projects['my-app'].architect;
expect(architectConfig.build.builder).toEqual('@nrwl/web:build'); expect(architectConfig.build.builder).toEqual('@nrwl/web:build');
expect(architectConfig.build.options).toEqual({ expect(architectConfig.build.options).toEqual({
assets: ['apps/my-app/src/favicon.ico', 'apps/my-app/src/assets'], assets: ['apps/my-app/src/favicon.ico', 'apps/my-app/src/assets'],
@ -262,8 +262,8 @@ describe('app', () => {
}, },
appTree appTree
); );
const angularJson = readJsonInTree(tree, 'angular.json'); const workspaceJson = readJsonInTree(tree, 'workspace.json');
const architectConfig = angularJson.projects['my-app'].architect; const architectConfig = workspaceJson.projects['my-app'].architect;
expect(architectConfig.serve.builder).toEqual('@nrwl/web:dev-server'); expect(architectConfig.serve.builder).toEqual('@nrwl/web:dev-server');
expect(architectConfig.serve.options).toEqual({ expect(architectConfig.serve.options).toEqual({
buildTarget: 'my-app:build' buildTarget: 'my-app:build'
@ -281,11 +281,12 @@ describe('app', () => {
}, },
appTree appTree
); );
const angularJson = readJsonInTree(tree, 'angular.json'); const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(angularJson.projects['my-app'].architect.lint).toEqual({
expect(workspaceJson.projects['my-app'].architect.lint).toEqual({
builder: '@angular-devkit/build-angular:tslint', builder: '@angular-devkit/build-angular:tslint',
options: { options: {
exclude: ['**/node_modules/**'], exclude: ['**/node_modules/**', '!apps/my-app/**'],
tsConfig: [ tsConfig: [
'apps/my-app/tsconfig.app.json', 'apps/my-app/tsconfig.app.json',
'apps/my-app/tsconfig.spec.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/src/app/app.spec.ts')).toBeFalsy();
expect(tree.exists('apps/my-app/tsconfig.spec.json')).toBeFalsy(); 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/jest.config.js')).toBeFalsy();
const angularJson = readJsonInTree(tree, 'angular.json'); const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(angularJson.projects['my-app'].architect.test).toBeUndefined(); expect(workspaceJson.projects['my-app'].architect.test).toBeUndefined();
expect( expect(
angularJson.projects['my-app'].architect.lint.options.tsConfig workspaceJson.projects['my-app'].architect.lint.options.tsConfig
).toEqual(['apps/my-app/tsconfig.app.json']); ).toEqual(['apps/my-app/tsconfig.app.json']);
}); });
}); });
@ -334,8 +335,8 @@ describe('app', () => {
appTree appTree
); );
expect(tree.exists('apps/my-app-e2e')).toBeFalsy(); expect(tree.exists('apps/my-app-e2e')).toBeFalsy();
const angularJson = readJsonInTree(tree, 'angular.json'); const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(angularJson.projects['my-app-e2e']).toBeUndefined(); expect(workspaceJson.projects['my-app-e2e']).toBeUndefined();
}); });
}); });
}); });

View File

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

View File

@ -1,5 +1,3 @@
import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners';
export interface Schema { export interface Schema {
name: string; name: string;
prefix?: string; prefix?: string;
@ -7,6 +5,7 @@ export interface Schema {
skipFormat: boolean; skipFormat: boolean;
directory?: string; directory?: string;
tags?: string; tags?: string;
unitTestRunner: UnitTestRunner; unitTestRunner: 'jest' | 'none';
e2eTestRunner: E2eTestRunner; 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": { "skipFormat": {
"description": "Skip formatting files", "description": "Skip formatting files",
"type": "boolean", "type": "boolean",

View File

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

View File

@ -26,12 +26,12 @@ export function _findDefaultServePath(
/^(\w+:)?\/\//.test(baseHref || '') || /^(\w+:)?\/\//.test(baseHref || '') ||
/^(\w+:)?\/\//.test(deployUrl || '') /^(\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; return null;
} }
// normalize baseHref // 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 // and root relative value are identical
const baseHrefParts = (baseHref || '').split('/').filter(part => part !== ''); const baseHrefParts = (baseHref || '').split('/').filter(part => part !== '');
if (baseHref && !baseHref.endsWith('/')) { if (baseHref && !baseHref.endsWith('/')) {
@ -42,7 +42,7 @@ export function _findDefaultServePath(
if (deployUrl && deployUrl[0] === '/') { if (deployUrl && deployUrl[0] === '/') {
if (baseHref && baseHref[0] === '/' && normalizedBaseHref !== deployUrl) { 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; return null;
} }

View File

@ -30,6 +30,13 @@
"hidden": true "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": { "library": {
"factory": "./src/schematics/library/library", "factory": "./src/schematics/library/library",
"schema": "./src/schematics/library/schema.json", "schema": "./src/schematics/library/schema.json",

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