Compare commits

...

11 Commits

Author SHA1 Message Date
Jason Jean 13c2984827 Release 11.2.10 2021-02-03 19:42:41 -05:00
Juri 24dfa76531 fix(core): value coercion for schemas with oneOf string enums 2021-02-03 18:46:40 -05:00
Jason Jean ef42f14883 fix(core): fix workspace creation not working on windows 2021-02-03 18:43:41 -05:00
Jason Jean d4db9f4684 Release 11.2.9 2021-02-02 19:10:02 -05:00
Jason Jean c7208541fe Revert "cleanup(core): remove unused npm deps"
This reverts commit beb5f139f4.
2021-02-02 19:07:54 -05:00
Jason Jean 3d0e3f190f Release 11.2.8 2021-02-02 14:15:25 -05:00
Jason Jean 613a6dfac8 fix(core): do not modify existing files during workspace creation 2021-02-02 14:03:17 -05:00
Jason Jean 3236c46f35 fix(core): fix move on windows (#4667) 2021-02-02 10:46:28 -05:00
Victor Savkin 7f7433d2b7 docs(misc): add docs on adding nx to monorepo 2021-02-02 10:46:27 -05:00
Tasos Bekos 4ed3d043f1 chore(repo): fix e2e-cli for Windows 2021-02-02 10:46:27 -05:00
Victor Savkin beb5f139f4 cleanup(core): remove unused npm deps 2021-02-02 10:44:14 -05:00
18 changed files with 438 additions and 76 deletions
+20
View File
@@ -146,6 +146,11 @@
"name": "From AngularJS",
"id": "migration-angularjs"
},
{
"name": "Adding to Monorepo",
"id": "adding-to-monorepo",
"file": "shared/migration/adding-to-monorepo"
},
{
"name": "Preserving Git Histories",
"id": "preserving-git-histories",
@@ -1127,6 +1132,11 @@
"name": "From CRA",
"id": "migration-cra"
},
{
"name": "Adding to Monorepo",
"id": "adding-to-monorepo",
"file": "shared/migration/adding-to-monorepo"
},
{
"name": "Preserving Git Histories",
"id": "preserving-git-histories",
@@ -2164,6 +2174,16 @@
"name": "Overview",
"id": "overview",
"file": "shared/migration/overview"
},
{
"name": "Adding to Monorepo",
"id": "adding-to-monorepo",
"file": "shared/migration/adding-to-monorepo"
},
{
"name": "Preserving Git Histories",
"id": "preserving-git-histories",
"file": "shared/migration/preserving-git-histories"
}
]
},
Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

+104
View File
@@ -0,0 +1,104 @@
# Next Steps After Adding Nx to Monorepo
If you have a monorepo that is powered by Lerna, Yarn, PNPM, or NPM, you can transform it into an Nx workspace by
running this command: `npx add-nx-to-monorepo`.
See it in action (3-minute video):
<iframe width="560" height="315" src="https://www.youtube.com/embed/jkPeUFhH5h4" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
`npx add-nx-to-monorepo` will do the following:
1. Add Nx to your package.json
2. Create `workspace.json` and `nx.json` listing all the projects in the workspace
3. Set up a `tsconfig` file mapping all projects in there
4. Set up Nx Cloud (if you say "yes")
## What You Get Right Away
After you run the command above, you can any npm script using Nx. For instance, if `myproj` has a `build` script, you can invoke it using `npx nx build myproj`. If you pass any flags, they will be forwarded to the underlying script.
### Caching Works
Nx supports computation caching. If it has seen the computation you are trying to perform, it's going extract the result from its cache instead of running it. An easy way to see it in action is to run the same command
twice: `npx nx build myproj` and then again `npx nx build myproj`. In addition to restoring all the files, Nx will
replay the terminal output as well, so you don't lose any information when running a cached command.
### Distributed Caching Works
If you said "yes" to Nx Cloud, you can now clone the repo on a different machine (e.g., CI) and run the same command against the same commit and the results will be retrieved from cache. Never compute the same thing twice in your org or CI.
### Affected Works
Nx will automatically analyze your workspace to know what projects are affected by your commit. Simply run: `npx nx affected --target=test --base=main` to see it in action. Often, Nx is able to do a better job detecting
affected than other tools because it looks not just at the changed files but also at the nature of the changes.
### Workspace Visualization
Run `npx nx dep-graph` to see a visualization of your workspace. `npx nx affected:dep-graph` will show what is affected
by your commit.
<iframe width="560" height="315" src="https://www.youtube.com/embed/cMZ-ReC-jWU" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
### Github Integration Works
If you said "yes" to Nx Cloud, you can enable Nx Cloud - Github integration to get a much better overview of what
happens in your PRs.
![Nx Console screenshot](/shared/github.png)
### VS Code Plugin Works
![Nx Console screenshot](/shared/nx-console-screenshot.png)
## Next Steps
All this works without your having to change your repo in any way. Whatever setup you have still works the same way but
faster and with better dev ergonomics. But Nx enables much more than that.
Nx is like a VS Code of build tools. It has a very powerful core but it's really the plugins and extra capabilities that
really transform how you develop.
Nx has first class support for React, Next.js, Gatsby, React Native, Angular, Node, NestJS, Jest, Cypress, Storybook and
many more. All the plugins are designed to work together and create a cohesive and pleasant to use dev environment.
In addition, Nx makes a lot of things much easier, like building large apps incrementally, distributing CI (no point in doing caching unless you can do that), enforcing best practices, building design systems.
If you want to explore what it feels like to develop with Nx, check out:
- [nx.dev/react](https://nx.dev/react)
- [nx.dev/angular](https://nx.dev/angular)
- [nx.dev/node](https://nx.dev/node)
## Troubleshooting
The `add-nx-to-monorepo` command will do its best to figure out what projects you have in the repo, but you can always update the list yourself.
For instance, you can add/remove/update projects in `workspace.json`.
```json
{
"version": 2,
"projects": {
"one": { "root": "packages/one", "type": "library" },
"two": { "root": "packages/two", "type": "library" }
}
}
```
Nx will a root tsconfig with something like this:
```json
{
"compilerOptions": {
"paths": {
"one": ["packages/one/index"],
"one/*": ["packages/one/*"],
"two": ["packages/two/index"],
"two/*": ["packages/two/*"]
}
}
}
```
This tsconfig isn't used for building or testing. It's only used to teach Nx how to resolve imports, so Nx can do its import source code analysis. If the path mappings are deduced incorrectly, feel free to chang them.
+7 -4
View File
@@ -5,6 +5,7 @@ import {
readFile,
readJson,
runCLI,
runCLIAsync,
tmpProjPath,
uniq,
updateFile,
@@ -35,7 +36,7 @@ describe('Cli', () => {
expect(() => runCLI(`counter ${myapp} --result=false`)).toThrowError();
});
it('should run npm scripts', () => {
it('should run npm scripts', async () => {
newProject();
const mylib = uniq('mylib');
runCLI(`generate @nrwl/node:lib ${mylib}`);
@@ -50,12 +51,14 @@ describe('Cli', () => {
`libs/${mylib}/package.json`,
JSON.stringify({
name: 'mylib1',
scripts: { echo: `echo ECHOED:$1` },
scripts: { echo: `echo ECHOED` },
})
);
const output = runCLI(`echo ${mylib} --a=123`);
expect(output).toContain(`ECHOED: --a=123`);
const { stdout } = await runCLIAsync(`echo ${mylib} --a=123`, {
silent: true,
});
expect(stdout).toMatch(/ECHOED "?--a=123"?/);
}, 1000000);
it('should show help', async () => {
+1 -1
View File
@@ -273,7 +273,7 @@ describe('Node Libraries', () => {
runCLI(`build ${nglib}`);
runCLI(`build ${nodelib}`);
checkFilesExist(`./dist/libs/${nodelib}/esm2015/index.js`);
}, 60000);
}, 120000);
it('should fail when trying to compile typescript files that are invalid', () => {
const proj = newProject();
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nrwl/nx-source",
"version": "11.2.6",
"version": "11.2.10",
"description": "Extensible Dev Tools for Monorepos",
"homepage": "https://nx.dev",
"main": "index.js",
@@ -57,6 +57,10 @@ export async function formatFiles(host: Tree) {
function updateWorkspaceJsonToMatchFormatVersion(host: Tree) {
const path = getWorkspacePath(host);
if (!path) {
return;
}
try {
const workspaceJson = JSON.parse(
stripJsonComments(host.read(path).toString())
@@ -1,7 +1,11 @@
import { Tree } from '@nrwl/tao/src/shared/tree';
import { execSync } from 'child_process';
import { getPackageManagerCommand } from '@nrwl/tao/src/shared/package-manager';
import { join } from 'path';
import {
detectPackageManager,
getPackageManagerCommand,
} from '@nrwl/tao/src/shared/package-manager';
import { joinPathFragments } from '../utils/path';
let storedPackageJsonValue;
@@ -15,18 +19,26 @@ let storedPackageJsonValue;
export function installPackagesTask(
host: Tree,
alwaysRun: boolean = false,
cwd: string = ''
cwd: string = '',
packageManager?: string
) {
const packageJsonValue = host.read(join(cwd, 'package.json')).toString();
const packageJsonValue = host
.read(joinPathFragments(cwd, 'package.json'))
.toString();
if (
host.listChanges().find((f) => f.path === join(cwd, 'package.json')) ||
host
.listChanges()
.find((f) => f.path === joinPathFragments(cwd, 'package.json')) ||
alwaysRun
) {
if (storedPackageJsonValue != packageJsonValue || alwaysRun) {
storedPackageJsonValue = host.read(join(cwd, 'package.json')).toString();
const pmc = getPackageManagerCommand();
storedPackageJsonValue = host
.read(joinPathFragments(cwd, 'package.json'))
.toString();
const pm = packageManager || detectPackageManager(cwd);
const pmc = getPackageManagerCommand(pm);
execSync(pmc.install, {
cwd: join(host.root, cwd),
cwd: joinPathFragments(host.root, cwd),
stdio: [0, 1, 2],
});
}
+4 -3
View File
@@ -17,14 +17,15 @@ import { GeneratorCallback } from '@nrwl/tao/src/shared/workspace';
export function addDependenciesToPackageJson(
host: Tree,
dependencies: Record<string, string>,
devDependencies: Record<string, string>
devDependencies: Record<string, string>,
packageJsonPath: string = 'package.json'
): GeneratorCallback | undefined {
const currentPackageJson = readJson(host, 'package.json');
const currentPackageJson = readJson(host, packageJsonPath);
if (
requiresAddingOfPackages(currentPackageJson, dependencies, devDependencies)
) {
updateJson(host, 'package.json', (json) => {
updateJson(host, packageJsonPath, (json) => {
json.dependencies = {
...(json.dependencies || {}),
...dependencies,
+96
View File
@@ -90,6 +90,34 @@ describe('params', () => {
});
});
it('should handle oneOf with enums inside', () => {
const opts = coerceTypesInOptions(
{ inspect: 'inspect' } as any,
{
properties: {
inspect: {
oneOf: [
{
type: 'string',
enum: ['inspect', 'inspect-brk'],
},
{
type: 'number',
},
{
type: 'boolean',
},
],
},
},
} as Schema
);
expect(opts).toEqual({
inspect: 'inspect',
});
});
it('should only coerce string values', () => {
const opts = coerceTypesInOptions(
{ a: true } as any,
@@ -535,6 +563,74 @@ describe('params', () => {
).not.toThrow();
});
it('should handle oneOf properties with enums', () => {
// matching enum value
expect(() =>
validateOptsAgainstSchema(
{ a: 'inspect' },
{
properties: {
a: {
oneOf: [
{
type: 'string',
enum: ['inspect', 'inspect-brk'],
},
{
type: 'boolean',
},
],
},
},
}
)
).not.toThrow();
// matching oneOf value
expect(() =>
validateOptsAgainstSchema(
{ a: true },
{
properties: {
a: {
oneOf: [
{
type: 'string',
enum: ['inspect', 'inspect-brk'],
},
{
type: 'boolean',
},
],
},
},
}
)
).not.toThrow();
// non-matching enum value
expect(() =>
validateOptsAgainstSchema(
{ a: 'abc' },
{
properties: {
a: {
oneOf: [
{
type: 'string',
enum: ['inspect', 'inspect-brk'],
},
{
type: 'boolean',
},
],
},
},
}
)
).toThrow();
});
it("should throw if the type doesn't match (arrays)", () => {
expect(() =>
validateOptsAgainstSchema(
+37 -2
View File
@@ -100,9 +100,15 @@ function coerceType(prop: PropertyDescription | undefined, value: any) {
}
}
return value;
} else if (normalizedPrimitiveType(prop.type) == 'boolean') {
} else if (
normalizedPrimitiveType(prop.type) == 'boolean' &&
isConvertibleToBoolean(value)
) {
return value === true || value == 'true';
} else if (normalizedPrimitiveType(prop.type) == 'number') {
} else if (
normalizedPrimitiveType(prop.type) == 'number' &&
isConvertibleToNumber(value)
) {
return Number(value);
} else if (prop.type == 'array') {
return value.split(',').map((v) => coerceType(prop.items, v));
@@ -577,3 +583,32 @@ function levenshtein(a: string, b: string) {
function isTTY(): boolean {
return !!process.stdout.isTTY && process.env['CI'] !== 'true';
}
/**
* Verifies whether the given value can be converted to a boolean
* @param value
*/
function isConvertibleToBoolean(value) {
if ('boolean' === typeof value) {
return true;
}
if ('string' === typeof value && /true|false/.test(value)) {
return true;
}
return false;
}
/**
* Verifies whether the given value can be converted to a number
* @param value
*/
function isConvertibleToNumber(value) {
// exclude booleans explicitly
if ('boolean' === typeof value) {
return false;
}
return !isNaN(+value);
}
+2 -2
View File
@@ -7,7 +7,7 @@ import {
} from 'fs';
import { mkdirpSync, rmdirSync } from 'fs-extra';
import { logger } from './logger';
import { dirname, join, relative } from 'path';
import { dirname, join, relative, sep } from 'path';
const chalk = require('chalk');
/**
@@ -219,7 +219,7 @@ export class FsTree implements Tree {
}
private normalize(path: string) {
return relative(this.root, join(this.root, path));
return relative(this.root, join(this.root, path)).split(sep).join('/');
}
private fsReadDir(dirPath: string) {
@@ -1,5 +1,5 @@
import { createTree } from '@nrwl/devkit/testing';
import { readJson, Tree } from '@nrwl/devkit';
import { readJson, Tree, writeJson } from '@nrwl/devkit';
import { newGenerator, Preset, Schema } from './new';
import { Linter } from '../../utils/lint';
@@ -45,7 +45,7 @@ describe('new', () => {
describe.each([[Preset.Empty], [Preset.Angular], [Preset.React]])(
'%s',
(preset) => {
beforeEach(async () => {
it('should generate necessary npm dependencies', async () => {
await newGenerator(tree, {
...defaultOptions,
name: 'my-workspace',
@@ -54,12 +54,53 @@ describe('new', () => {
appName: 'app',
preset,
});
});
it('should generate necessary npm dependencies', () => {
expect(readJson(tree, 'my-workspace/package.json')).toMatchSnapshot();
});
}
);
});
describe('--packageManager', () => {
describe.each([['npm'], ['yarn'], ['pnpm']])('%s', (packageManager) => {
it('should set the packageManager in workspace.json', async () => {
await newGenerator(tree, {
...defaultOptions,
name: 'my-workspace',
directory: 'my-workspace',
npmScope: 'npmScope',
appName: 'app',
cli: 'angular',
packageManager,
});
const workspaceJson = readJson(tree, 'my-workspace/angular.json');
expect(workspaceJson.cli.packageManager).toEqual(packageManager);
});
});
});
it('should not modify any existing files', async () => {
const packageJson = {
dependencies: {
existing: 'latest',
},
};
const eslintConfig = {
rules: {},
};
writeJson(tree, 'package.json', packageJson);
writeJson(tree, '.eslintrc.json', eslintConfig);
await newGenerator(tree, {
...defaultOptions,
name: 'my-workspace',
directory: 'my-workspace',
npmScope: 'npmScope',
appName: 'app',
});
expect(readJson(tree, 'package.json')).toEqual(packageJson);
expect(readJson(tree, '.eslintrc.json')).toEqual(eslintConfig);
});
});
+34 -21
View File
@@ -4,7 +4,7 @@ import {
updateJson,
addDependenciesToPackageJson,
installPackagesTask,
getWorkspacePath,
getWorkspacePath as devkitGetWorkspacePath,
convertNxGenerator,
names,
getPackageManagerCommand,
@@ -16,6 +16,7 @@ import { spawn, SpawnOptions } from 'child_process';
import { workspaceGenerator } from '../workspace/workspace';
import { nxVersion } from '../../utils/versions';
import { reformattedWorkspaceJsonOrNull } from '@nrwl/tao/src/shared/workspace';
export enum Preset {
Empty = 'empty',
@@ -161,6 +162,12 @@ export async function newGenerator(host: Tree, options: Schema) {
throw new Error(`Cannot select nxCloud when skipInstall is set to true.`);
}
if (devkitGetWorkspacePath(host)) {
throw new Error(
'Cannot generate a new workspace within an existing workspace'
);
}
options = normalizeOptions(options);
const layout: 'packages' | 'apps-and-libs' =
@@ -171,7 +178,7 @@ export async function newGenerator(host: Tree, options: Schema) {
preset: undefined,
nxCloud: undefined,
};
workspaceGenerator(host, workspaceOpts);
await workspaceGenerator(host, workspaceOpts);
if (options.cli === 'angular') {
setDefaultPackageManager(host, options);
@@ -181,13 +188,8 @@ export async function newGenerator(host: Tree, options: Schema) {
addCloudDependencies(host, options);
await formatFiles(host);
host.listChanges().forEach((change) => {
if (change.type !== 'DELETE') {
host.rename(change.path, join(options.directory, change.path));
}
});
return async () => {
installPackagesTask(host, false, options.directory);
installPackagesTask(host, false, options.directory, options.packageManager);
await generatePreset(host, options);
if (!options.skipGit) {
await initializeGitRepo(host, options.directory, options);
@@ -203,7 +205,8 @@ function addCloudDependencies(host: Tree, options: Schema) {
return addDependenciesToPackageJson(
host,
{},
{ '@nrwl/nx-cloud': 'latest' }
{ '@nrwl/nx-cloud': 'latest' },
join(options.directory, 'package.json')
);
}
}
@@ -259,7 +262,12 @@ function addPresetDependencies(host: Tree, options: Schema) {
return;
}
const { dependencies, dev } = presetDependencies[options.preset];
return addDependenciesToPackageJson(host, dependencies, dev);
return addDependenciesToPackageJson(
host,
dependencies,
dev,
join(options.directory, 'package.json')
);
}
function normalizeOptions(options: Schema): Schema {
@@ -271,7 +279,8 @@ function normalizeOptions(options: Schema): Schema {
return options;
}
function setDefaultLinter(host: Tree, { linter, preset }: Schema) {
function setDefaultLinter(host: Tree, options: Schema) {
const { linter, preset } = options;
// Don't do anything if someone doesn't pick angular
if (preset !== 'angular' && preset !== 'angular-nest') {
return;
@@ -279,11 +288,11 @@ function setDefaultLinter(host: Tree, { linter, preset }: Schema) {
switch (linter) {
case 'eslint': {
setESLintDefault(host);
setESLintDefault(host, options);
break;
}
case 'tslint': {
setTSLintDefault(host);
setTSLintDefault(host, options);
break;
}
}
@@ -292,8 +301,8 @@ function setDefaultLinter(host: Tree, { linter, preset }: Schema) {
/**
* This sets ESLint as the default for any schematics that default to TSLint
*/
function setESLintDefault(host: Tree) {
updateJson(host, getWorkspacePath(host), (json) => {
function setESLintDefault(host: Tree, options: Schema) {
updateJson(host, getWorkspacePath(host, options), (json) => {
setDefault(json, '@nrwl/angular', 'application', 'linter', 'eslint');
setDefault(json, '@nrwl/angular', 'library', 'linter', 'eslint');
setDefault(
@@ -310,8 +319,8 @@ function setESLintDefault(host: Tree) {
/**
* This sets TSLint as the default for any schematics that default to ESLint
*/
function setTSLintDefault(host: Tree) {
updateJson(host, getWorkspacePath(host), (json) => {
function setTSLintDefault(host: Tree, options: Schema) {
updateJson(host, getWorkspacePath(host, options), (json) => {
setDefault(json, '@nrwl/workspace', 'library', 'linter', 'tslint');
setDefault(json, '@nrwl/cypress', 'cypress-project', 'linter', 'tslint');
setDefault(json, '@nrwl/cypress', 'cypress-project', 'linter', 'tslint');
@@ -326,16 +335,20 @@ function setTSLintDefault(host: Tree) {
});
}
function setDefaultPackageManager(host: Tree, { packageManager }: Schema) {
if (!packageManager) {
function getWorkspacePath(host: Tree, { directory, cli }: Schema) {
return join(directory, cli === 'angular' ? 'angular.json' : 'workspace.json');
}
function setDefaultPackageManager(host: Tree, options: Schema) {
if (!options.packageManager) {
return;
}
updateJson(host, getWorkspacePath(host), (json) => {
updateJson(host, getWorkspacePath(host, options), (json) => {
if (!json.cli) {
json.cli = {};
}
json.cli['packageManager'] = packageManager;
json.cli['packageManager'] = options.packageManager;
return json;
});
}
@@ -1,5 +1,6 @@
export interface Schema {
name: string;
directory: string;
npmScope?: string;
skipInstall?: boolean;
skipGit?: boolean;
@@ -42,7 +42,6 @@
"type": "string",
"format": "path",
"description": "The directory name to create the workspace in.",
"x-deprecated": "This option is no longer used.",
"default": ""
},
"layout": {
@@ -13,24 +13,26 @@ describe('@nrwl/workspace:workspace', () => {
it('should create files', async () => {
await workspaceGenerator(tree, {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'apps-and-libs',
defaultBase: 'main',
});
expect(tree.exists('/nx.json')).toBe(true);
expect(tree.exists('/workspace.json')).toBe(true);
expect(tree.exists('/.prettierrc')).toBe(true);
expect(tree.exists('/.prettierignore')).toBe(true);
expect(tree.exists('/proj/nx.json')).toBe(true);
expect(tree.exists('/proj/workspace.json')).toBe(true);
expect(tree.exists('/proj/.prettierrc')).toBe(true);
expect(tree.exists('/proj/.prettierignore')).toBe(true);
});
it('should create nx.json', async () => {
await workspaceGenerator(tree, {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'apps-and-libs',
defaultBase: 'master',
});
const nxJson = readJson<NxJson>(tree, '/nx.json');
const nxJson = readJson<NxJson>(tree, '/proj/nx.json');
expect(nxJson).toEqual({
npmScope: 'proj',
affected: {
@@ -62,23 +64,25 @@ describe('@nrwl/workspace:workspace', () => {
it('should create a prettierrc file', async () => {
await workspaceGenerator(tree, {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'apps-and-libs',
defaultBase: 'main',
});
expect(tree.read('.prettierrc').toString()).toMatchSnapshot();
expect(tree.read('proj/.prettierrc').toString()).toMatchSnapshot();
});
it('should recommend vscode extensions', async () => {
await workspaceGenerator(tree, {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'apps-and-libs',
defaultBase: 'main',
});
const recommendations = readJson<{ recommendations: string[] }>(
tree,
'/.vscode/extensions.json'
'proj/.vscode/extensions.json'
).recommendations;
expect(recommendations).toEqual([
@@ -90,13 +94,14 @@ describe('@nrwl/workspace:workspace', () => {
it('should recommend vscode extensions (angular)', async () => {
await workspaceGenerator(tree, {
name: 'proj',
directory: 'proj',
cli: 'angular',
layout: 'apps-and-libs',
defaultBase: 'main',
});
const recommendations = readJson<{ recommendations: string[] }>(
tree,
'/.vscode/extensions.json'
'proj/.vscode/extensions.json'
).recommendations;
expect(recommendations).toEqual([
@@ -110,12 +115,13 @@ describe('@nrwl/workspace:workspace', () => {
it('should add decorate-angular-cli when used with angular cli', async () => {
await workspaceGenerator(tree, {
name: 'proj',
directory: 'proj',
cli: 'angular',
layout: 'apps-and-libs',
defaultBase: 'main',
});
expect(tree.exists('/decorate-angular-cli.js')).toBe(true);
const packageJson = readJson(tree, '/package.json');
expect(tree.exists('/proj/decorate-angular-cli.js')).toBe(true);
const packageJson = readJson(tree, '/proj/package.json');
expect(packageJson.scripts.postinstall).toEqual(
'node ./decorate-angular-cli.js'
);
@@ -124,26 +130,28 @@ describe('@nrwl/workspace:workspace', () => {
it('should not add decorate-angular-cli when used with nx cli', async () => {
await workspaceGenerator(tree, {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'apps-and-libs',
defaultBase: 'main',
});
expect(tree.exists('/decorate-angular-cli.js')).toBe(false);
const packageJson = readJson(tree, '/package.json');
expect(tree.exists('/proj/decorate-angular-cli.js')).toBe(false);
const packageJson = readJson(tree, '/proj/package.json');
expect(packageJson.scripts.postinstall).toBeUndefined();
});
it('should create a workspace using package layout', async () => {
await workspaceGenerator(tree, {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'packages',
defaultBase: 'main',
});
expect(tree.exists('/packages/.gitkeep')).toBe(true);
expect(tree.exists('/apps/.gitkeep')).toBe(false);
expect(tree.exists('/libs/.gitkeep')).toBe(false);
const nx = readJson(tree, '/nx.json');
expect(tree.exists('/proj/packages/.gitkeep')).toBe(true);
expect(tree.exists('/proj/apps/.gitkeep')).toBe(false);
expect(tree.exists('/proj/libs/.gitkeep')).toBe(false);
const nx = readJson(tree, '/proj/nx.json');
expect(nx.workspaceLayout).toEqual({
appsDir: 'packages',
libsDir: 'packages',
@@ -15,21 +15,22 @@ import {
typescriptVersion,
} from '../../utils/versions';
import { readFileSync } from 'fs';
import { join as pathJoin } from 'path';
import { join, join as pathJoin } from 'path';
import { reformattedWorkspaceJsonOrNull } from '@nrwl/tao/src/shared/workspace';
export const DEFAULT_NRWL_PRETTIER_CONFIG = {
singleQuote: true,
};
const decorateAngularClI = (host: Tree) => {
function decorateAngularClI(host: Tree, options: Schema) {
const decorateCli = readFileSync(
pathJoin(__dirname as any, '..', 'utils', 'decorate-angular-cli.js__tmpl__')
).toString();
host.write('decorate-angular-cli.js', decorateCli);
};
host.write(join(options.directory, 'decorate-angular-cli.js'), decorateCli);
}
function setWorkspaceLayoutProperties(tree: Tree, options: Schema) {
updateJson(tree, 'nx.json', (json) => {
updateJson(tree, join(options.directory, 'nx.json'), (json) => {
if (options.layout === 'packages') {
json.workspaceLayout = {
appsDir: 'packages',
@@ -42,17 +43,17 @@ function setWorkspaceLayoutProperties(tree: Tree, options: Schema) {
function createAppsAndLibsFolders(host: Tree, options: Schema) {
if (options.layout === 'packages') {
host.write('packages/.gitkeep', '');
host.write(join(options.directory, 'packages/.gitkeep'), '');
} else {
host.write('apps/.gitkeep', '');
host.write('libs/.gitkeep', '');
host.write(join(options.directory, 'apps/.gitkeep'), '');
host.write(join(options.directory, 'libs/.gitkeep'), '');
}
}
function createFiles(host: Tree, options: Schema) {
const npmScope = options.npmScope ? options.npmScope : options.name;
const formattedNames = names(options.name);
generateFiles(host, pathJoin(__dirname, './files'), '', {
generateFiles(host, pathJoin(__dirname, './files'), options.directory, {
formattedNames,
dot: '.',
tmpl: '',
@@ -69,8 +70,31 @@ function createFiles(host: Tree, options: Schema) {
});
}
function createPrettierrc(host: Tree) {
writeJson(host, '.prettierrc', DEFAULT_NRWL_PRETTIER_CONFIG);
function createPrettierrc(host: Tree, options: Schema) {
writeJson(
host,
join(options.directory, '.prettierrc'),
DEFAULT_NRWL_PRETTIER_CONFIG
);
}
function formatWorkspaceJson(host: Tree, options: Schema) {
const path = join(
options.directory,
options.cli === 'angular' ? 'angular.json' : 'workspace.json'
);
try {
updateJson(host, path, (workspaceJson) => {
const reformatted = reformattedWorkspaceJsonOrNull(workspaceJson);
if (reformatted) {
host.write(path, JSON.stringify(reformatted, null, 2));
}
});
} catch (e) {
console.error(`Failed to format: ${path}`);
console.error(e);
}
}
export async function workspaceGenerator(host: Tree, options: Schema) {
@@ -78,14 +102,15 @@ export async function workspaceGenerator(host: Tree, options: Schema) {
throw new Error(`Invalid options, "name" is required.`);
}
createFiles(host, options);
createPrettierrc(host);
createPrettierrc(host, options);
if (options.cli === 'angular') {
decorateAngularClI(host);
decorateAngularClI(host, options);
}
setWorkspaceLayoutProperties(host, options);
createAppsAndLibsFolders(host, options);
await formatFiles(host);
formatWorkspaceJson(host, options);
}
export const workspaceSchematic = convertNxGenerator(workspaceGenerator);