Skip to content

Commit

Permalink
fix(core): do not modify existing files during workspace creation
Browse files Browse the repository at this point in the history
  • Loading branch information
FrozenPandaz authored and vsavkin committed Feb 2, 2021
1 parent 0d6b869 commit a500088
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 57 deletions.
4 changes: 4 additions & 0 deletions packages/devkit/src/generators/format-files.ts
Expand Up @@ -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())
Expand Down
7 changes: 4 additions & 3 deletions packages/devkit/src/utils/package-json.ts
Expand Up @@ -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,
Expand Down
49 changes: 45 additions & 4 deletions packages/workspace/src/generators/new/new.spec.ts
@@ -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';

Expand Down Expand Up @@ -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',
Expand All @@ -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);
});
});
53 changes: 33 additions & 20 deletions packages/workspace/src/generators/new/new.ts
Expand Up @@ -4,7 +4,7 @@ import {
updateJson,
addDependenciesToPackageJson,
installPackagesTask,
getWorkspacePath,
getWorkspacePath as devkitGetWorkspacePath,
convertNxGenerator,
names,
getPackageManagerCommand,
Expand All @@ -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',
Expand Down Expand Up @@ -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' =
Expand All @@ -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);
Expand All @@ -181,11 +188,6 @@ 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);
await generatePreset(host, options);
Expand All @@ -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')
);
}
}
Expand Down Expand Up @@ -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 {
Expand All @@ -271,19 +279,20 @@ 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;
}

switch (linter) {
case 'eslint': {
setESLintDefault(host);
setESLintDefault(host, options);
break;
}
case 'tslint': {
setTSLintDefault(host);
setTSLintDefault(host, options);
break;
}
}
Expand All @@ -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(
Expand All @@ -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');
Expand All @@ -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;
});
}
Expand Down
1 change: 1 addition & 0 deletions packages/workspace/src/generators/workspace/schema.d.ts
@@ -1,5 +1,6 @@
export interface Schema {
name: string;
directory: string;
npmScope?: string;
skipInstall?: boolean;
skipGit?: boolean;
Expand Down
1 change: 0 additions & 1 deletion packages/workspace/src/generators/workspace/schema.json
Expand Up @@ -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": {
Expand Down
40 changes: 24 additions & 16 deletions packages/workspace/src/generators/workspace/workspace.spec.ts
Expand Up @@ -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: {
Expand Down Expand Up @@ -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([
Expand All @@ -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([
Expand All @@ -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'
);
Expand All @@ -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',
Expand Down

0 comments on commit a500088

Please sign in to comment.