feat(core): add remove schematic (#2484)

* feat(core): add remove schematic

* fix(core): update remove schematic to use project graph
This commit is contained in:
Jo Hanna Pearce 2020-03-18 11:07:25 +00:00 committed by GitHub
parent e73a6a0046
commit d834e79dc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 881 additions and 40 deletions

View File

@ -0,0 +1,61 @@
# remove
Remove an application or library
## Usage
```bash
ng generate remove ...
```
```bash
ng g rm ... # same
```
By default, Nx will search for `remove` in the default collection provisioned in `angular.json`.
You can specify the collection explicitly as follows:
```bash
ng g @nrwl/workspace:remove ...
```
Show what will be generated without writing to disk:
```bash
ng g remove ... --dry-run
```
### Examples
Remove my-feature-lib from the workspace:
```bash
ng g @nrwl/workspace:remove my-feature-lib
```
Force removal of my-feature-lib from the workspace:
```bash
ng g @nrwl/workspace:remove my-feature-lib --forceRemove
```
## Options
### forceRemove
Alias(es): force-remove
Default: `false`
Type: `boolean`
When true, forces removal even if the project is still in use.
### projectName
Alias(es): project
Type: `string`
The name of the project to remove

View File

@ -0,0 +1,61 @@
# remove
Remove an application or library
## Usage
```bash
nx generate remove ...
```
```bash
nx g rm ... # same
```
By default, Nx will search for `remove` in the default collection provisioned in `workspace.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/workspace:remove ...
```
Show what will be generated without writing to disk:
```bash
nx g remove ... --dry-run
```
### Examples
Remove my-feature-lib from the workspace:
```bash
nx g @nrwl/workspace:remove my-feature-lib
```
Force removal of my-feature-lib from the workspace:
```bash
nx g @nrwl/workspace:remove my-feature-lib --forceRemove
```
## Options
### forceRemove
Alias(es): force-remove
Default: `false`
Type: `boolean`
When true, forces removal even if the project is still in use.
### projectName
Alias(es): project
Type: `string`
The name of the project to remove

View File

@ -0,0 +1,61 @@
# remove
Remove an application or library
## Usage
```bash
nx generate remove ...
```
```bash
nx g rm ... # same
```
By default, Nx will search for `remove` in the default collection provisioned in `workspace.json`.
You can specify the collection explicitly as follows:
```bash
nx g @nrwl/workspace:remove ...
```
Show what will be generated without writing to disk:
```bash
nx g remove ... --dry-run
```
### Examples
Remove my-feature-lib from the workspace:
```bash
nx g @nrwl/workspace:remove my-feature-lib
```
Force removal of my-feature-lib from the workspace:
```bash
nx g @nrwl/workspace:remove my-feature-lib --forceRemove
```
## Options
### forceRemove
Alias(es): force-remove
Default: `false`
Type: `boolean`
When true, forces removal even if the project is still in use.
### projectName
Alias(es): project
Type: `string`
The name of the project to remove

44
e2e/remove.test.ts Normal file
View File

@ -0,0 +1,44 @@
import { NxJson } from '@nrwl/workspace';
import {
exists,
forEachCli,
newProject,
readFile,
readJson,
runCLI,
tmpProjPath,
uniq
} from './utils';
forEachCli(cli => {
describe('Remove Project', () => {
const workspace: string = cli === 'angular' ? 'angular' : 'workspace';
/**
* Tries creating then deleting a lib
*/
it('should work', () => {
const lib = uniq('mylib');
newProject();
runCLI(`generate @nrwl/workspace:lib ${lib}`);
expect(exists(tmpProjPath(`libs/${lib}`))).toBeTruthy();
const removeOutput = runCLI(
`generate @nrwl/workspace:remove --project ${lib}`
);
expect(removeOutput).toContain(`DELETE libs/${lib}`);
expect(exists(tmpProjPath(`libs/${lib}`))).toBeFalsy();
expect(removeOutput).toContain(`UPDATE nx.json`);
const nxJson = JSON.parse(readFile('nx.json')) as NxJson;
expect(nxJson.projects[`${lib}`]).toBeUndefined();
expect(removeOutput).toContain(`UPDATE ${workspace}.json`);
const workspaceJson = readJson(`${workspace}.json`);
expect(workspaceJson.projects[`${lib}`]).toBeUndefined();
});
});
});

View File

@ -1,8 +1,13 @@
import { exec, execSync } from 'child_process';
import { readFileSync, renameSync, statSync, writeFileSync } from 'fs';
import {
readdirSync,
readFileSync,
renameSync,
statSync,
writeFileSync
} from 'fs';
import { ensureDirSync } from 'fs-extra';
import * as path from 'path';
import * as fs from 'fs';
export let cli;
@ -486,7 +491,7 @@ export function checkFilesDoNotExist(...expectedFiles: string[]) {
}
export function listFiles(dirName: string) {
return fs.readdirSync(tmpProjPath(dirName));
return readdirSync(tmpProjPath(dirName));
}
export function readJson(f: string): any {

View File

@ -30,6 +30,13 @@
"description": "Move an application or library to another folder"
},
"remove": {
"factory": "./src/schematics/remove/remove",
"schema": "./src/schematics/remove/schema.json",
"aliases": ["rm"],
"description": "Remove an application or library"
},
"ng-new": {
"factory": "./src/schematics/ng-new/ng-new",
"schema": "./src/schematics/ng-new/schema.json",

View File

@ -1,15 +1,15 @@
import * as path from 'path';
import * as fs from 'fs';
import { appRootPath } from '../utils/app-root';
import { extname } from 'path';
import { jsonDiff } from '../utils/json-diff';
import { readFileSync } from 'fs';
import { execSync } from 'child_process';
import { readJsonFile } from '../utils/fileutils';
import { Environment, NxJson } from './shared-interfaces';
import { ProjectGraphNode } from './project-graph';
import { WorkspaceResults } from '../command-line/workspace-results';
import * as fs from 'fs';
import { readFileSync } from 'fs';
import * as path from 'path';
import { extname } from 'path';
import { NxArgs } from '../command-line/utils';
import { WorkspaceResults } from '../command-line/workspace-results';
import { appRootPath } from '../utils/app-root';
import { readJsonFile } from '../utils/fileutils';
import { jsonDiff } from '../utils/json-diff';
import { ProjectGraphNode } from './project-graph';
import { Environment, NxJson } from './shared-interfaces';
const ignore = require('ignore');
@ -204,10 +204,6 @@ export function rootWorkspaceFileNames(): string[] {
return [`package.json`, workspaceFileName(), `nx.json`, `tsconfig.json`];
}
export function rootWorkspaceFileData(): FileData[] {
return rootWorkspaceFileNames().map(f => getFileData(`${appRootPath}/${f}`));
}
export function readWorkspaceFiles(): FileData[] {
const workspaceJson = readWorkspaceJson();
const files = [];

View File

@ -1,6 +1,4 @@
import { mkdirSync } from 'fs';
import { ProjectGraph } from './project-graph-models';
import { ProjectGraphBuilder } from './project-graph-builder';
import { appRootPath } from '../../utils/app-root';
import {
directoryExists,
@ -8,42 +6,43 @@ import {
readJsonFile,
writeJsonFile
} from '../../utils/fileutils';
import { assertWorkspaceValidity } from '../assert-workspace-validity';
import { createFileMap, FileMap } from '../file-graph';
import {
defaultFileRead,
FileData,
mtime,
readNxJson,
readWorkspaceFiles,
readWorkspaceJson,
rootWorkspaceFileData,
rootWorkspaceFileNames
readWorkspaceJson
} from '../file-utils';
import { createFileMap, FileMap } from '../file-graph';
import {
BuildNodes,
buildNpmPackageNodes,
buildWorkspaceProjectNodes
} from './build-nodes';
import { normalizeNxJson } from '../normalize-nx-json';
import {
BuildDependencies,
buildExplicitNpmDependencies,
buildExplicitTypeScriptDependencies,
buildImplicitProjectDependencies
} from './build-dependencies';
import { assertWorkspaceValidity } from '../assert-workspace-validity';
import { normalizeNxJson } from '../normalize-nx-json';
import {
BuildNodes,
buildNpmPackageNodes,
buildWorkspaceProjectNodes
} from './build-nodes';
import { ProjectGraphBuilder } from './project-graph-builder';
import { ProjectGraph } from './project-graph-models';
export function createProjectGraph(
workspaceJson = readWorkspaceJson(),
nxJson = readNxJson(),
workspaceFiles = readWorkspaceFiles(),
fileRead: (s: string) => string = defaultFileRead,
cache: false | { data: ProjectGraphCache; mtime: number } = readCache()
cache: false | { data: ProjectGraphCache; mtime: number } = readCache(),
shouldCache: boolean = true
): ProjectGraph {
assertWorkspaceValidity(workspaceJson, nxJson);
const normalizedNxJson = normalizeNxJson(nxJson);
if (cache && maxMTime(rootWorkspaceFileData()) > cache.mtime) {
if (cache && maxMTime(rootWorkspaceFileData(workspaceFiles)) > cache.mtime) {
cache = false;
}
@ -73,10 +72,12 @@ export function createProjectGraph(
);
const projectGraph = builder.build();
writeCache({
projectGraph,
fileMap
});
if (shouldCache) {
writeCache({
projectGraph,
fileMap
});
}
return projectGraph;
} else {
// Cache file was modified _after_ all workspace files.
@ -136,6 +137,22 @@ function maxMTime(files: FileData[]) {
return Math.max(...files.map(f => f.mtime));
}
function rootWorkspaceFileData(workspaceFiles: FileData[]): FileData[] {
return [
`/package.json`,
'/workspace.json',
'/angular.json',
`/nx.json`,
`/tsconfig.json`
].reduce((acc: FileData[], curr: string) => {
const fileData = workspaceFiles.find(x => x.file === curr);
if (fileData) {
acc.push(fileData);
}
return acc;
}, []);
}
function modifiedSinceCache(
fileMap: FileMap,
c: false | { data: ProjectGraphCache; mtime: number }

View File

@ -1,4 +1,4 @@
import { chain } from '@angular-devkit/schematics';
import { chain, Rule } from '@angular-devkit/schematics';
import { checkDestination } from './lib/check-destination';
import { checkProjectExists } from './lib/check-project-exists';
import { moveProject } from './lib/move-project';
@ -10,7 +10,7 @@ import { updateProjectRootFiles } from './lib/update-project-root-files';
import { updateWorkspace } from './lib/update-workspace';
import { Schema } from './schema';
export default function(schema: Schema) {
export default function(schema: Schema): Rule {
return chain([
checkProjectExists(schema),
checkDestination(schema),

View File

@ -0,0 +1,90 @@
import { Tree } from '@angular-devkit/schematics';
import { UnitTestTree } from '@angular-devkit/schematics/testing';
import { updateJsonInTree } from '@nrwl/workspace';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { callRule, runSchematic } from '../../../utils/testing';
import { Schema } from '../schema';
import { checkDependencies } from './check-dependencies';
describe('updateImports Rule', () => {
let tree: UnitTestTree;
let schema: Schema;
beforeEach(async () => {
tree = new UnitTestTree(Tree.empty());
tree = createEmptyWorkspace(tree) as UnitTestTree;
schema = {
projectName: 'my-source'
};
tree = await runSchematic('lib', { name: 'my-dependent' }, tree);
tree = await runSchematic('lib', { name: 'my-source' }, tree);
});
describe('static dependencies', () => {
beforeEach(() => {
const sourceFilePath = 'libs/my-source/src/lib/my-source.ts';
tree.overwrite(
sourceFilePath,
`export class MyClass {}
`
);
const dependentFilePath = 'libs/my-dependent/src/lib/my-dependent.ts';
tree.overwrite(
dependentFilePath,
`import { MyClass } from '@proj/my-source';
export MyExtendedClass extends MyClass {};
`
);
});
it('should fatally error if any dependent exists', async () => {
await expect(callRule(checkDependencies(schema), tree)).rejects.toThrow(
`${schema.projectName} is still depended on by the following projects:\nmy-dependent`
);
});
it('should not error if forceRemove is true', async () => {
schema.forceRemove = true;
await expect(
callRule(checkDependencies(schema), tree)
).resolves.not.toThrow();
});
});
describe('implicit dependencies', () => {
beforeEach(async () => {
tree = (await callRule(
updateJsonInTree('nx.json', json => {
json.projects['my-dependent'].implicitDependencies = ['my-source'];
return json;
}),
tree
)) as UnitTestTree;
});
it('should fatally error if any dependent exists', async () => {
await expect(callRule(checkDependencies(schema), tree)).rejects.toThrow(
`${schema.projectName} is still depended on by the following projects:\nmy-dependent`
);
});
it('should not error if forceRemove is true', async () => {
schema.forceRemove = true;
await expect(
callRule(checkDependencies(schema), tree)
).resolves.not.toThrow();
});
});
it('should not error if there are no dependents', async () => {
await expect(
callRule(checkDependencies(schema), tree)
).resolves.not.toThrow();
});
});

View File

@ -0,0 +1,66 @@
import { Rule, Tree } from '@angular-devkit/schematics';
import { FileData } from '@nrwl/workspace/src/core/file-utils';
import {
readNxJsonInTree,
readWorkspace
} from '@nrwl/workspace/src/utils/ast-utils';
import { getWorkspacePath } from '@nrwl/workspace/src/utils/cli-config-utils';
import * as path from 'path';
import {
createProjectGraph,
onlyWorkspaceProjects,
ProjectGraph,
reverse
} from '../../../core/project-graph';
import { Schema } from '../schema';
/**
* Check whether the project to be removed is depended on by another project
*
* Throws an error if the project is in use, unless the `--forceRemove` option is used.
*
* @param schema The options provided to the schematic
*/
export function checkDependencies(schema: Schema): Rule {
if (schema.forceRemove) {
return (tree: Tree) => tree;
}
return (tree: Tree): Tree => {
const files: FileData[] = [];
const mtime = Date.now(); //can't get mtime data from the tree :(
const workspaceDir = path.dirname(getWorkspacePath(tree));
tree.visit(file => {
files.push({
file: path.relative(workspaceDir, file),
ext: path.extname(file),
mtime
});
});
const graph: ProjectGraph = createProjectGraph(
readWorkspace(tree),
readNxJsonInTree(tree),
files,
file => tree.read(file).toString('utf-8'),
false,
false
);
const reverseGraph = onlyWorkspaceProjects(reverse(graph));
const deps = reverseGraph.dependencies[schema.projectName] || [];
if (deps.length === 0) {
return tree;
}
throw new Error(
`${
schema.projectName
} is still depended on by the following projects:\n${deps
.map(x => x.target)
.join('\n')}`
);
};
}

View File

@ -0,0 +1,76 @@
import { Tree } from '@angular-devkit/schematics';
import { UnitTestTree } from '@angular-devkit/schematics/testing';
import { updateWorkspaceInTree } from '@nrwl/workspace/src/utils/ast-utils';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { callRule } from '../../../utils/testing';
import { Schema } from '../schema';
import { checkTargets } from './check-targets';
describe('checkTargets Rule', () => {
let tree: UnitTestTree;
let schema: Schema;
beforeEach(async () => {
tree = new UnitTestTree(Tree.empty());
tree = createEmptyWorkspace(tree) as UnitTestTree;
schema = {
projectName: 'ng-app'
};
tree = (await callRule(
updateWorkspaceInTree(workspace => {
return {
version: 1,
projects: {
'ng-app': {
projectType: 'application',
schematics: {},
root: 'apps/ng-app',
sourceRoot: 'apps/ng-app/src',
prefix: 'happyorg',
architect: {
build: {
builder: '@angular-devkit/build-angular:browser',
options: {}
}
}
},
'ng-app-e2e': {
root: 'apps/ng-app-e2e',
sourceRoot: 'apps/ng-app-e2e/src',
projectType: 'application',
architect: {
e2e: {
builder: '@nrwl/cypress:cypress',
options: {
cypressConfig: 'apps/ng-app-e2e/cypress.json',
tsConfig: 'apps/ng-app-e2e/tsconfig.e2e.json',
devServerTarget: 'ng-app:serve'
}
}
}
}
}
};
}),
tree
)) as UnitTestTree;
});
it('should throw an error if another project targets', async () => {
await expect(callRule(checkTargets(schema), tree)).rejects.toThrow();
});
it('should NOT throw an error if no other project targets', async () => {
schema.projectName = 'ng-app-e2e';
await expect(callRule(checkTargets(schema), tree)).resolves.not.toThrow();
});
it('should not error if forceRemove is true', async () => {
schema.forceRemove = true;
await expect(callRule(checkTargets(schema), tree)).resolves.not.toThrow();
});
});

View File

@ -0,0 +1,44 @@
import { Tree } from '@angular-devkit/schematics';
import { updateWorkspaceInTree } from '@nrwl/workspace';
import { Schema } from '../schema';
/**
* Check whether the project to be removed has builders targetted by another project
*
* Throws an error if the project is in use, unless the `--forceRemove` option is used.
*
* @param schema The options provided to the schematic
*/
export function checkTargets(schema: Schema) {
if (schema.forceRemove) {
return (tree: Tree) => tree;
}
return updateWorkspaceInTree(workspace => {
const findTarget = new RegExp(`${schema.projectName}:`);
const usedIn = [];
for (const name of Object.keys(workspace.projects)) {
if (name === schema.projectName) {
continue;
}
const projectStr = JSON.stringify(workspace.projects[name]);
if (findTarget.test(projectStr)) {
usedIn.push(name);
}
}
if (usedIn.length > 0) {
let message = `${schema.projectName} is still targeted by the following projects:\n\n`;
for (let project of usedIn) {
message += `${project}\n`;
}
throw new Error(message);
}
return workspace;
});
}

View File

@ -0,0 +1,32 @@
import { Tree } from '@angular-devkit/schematics';
import { UnitTestTree } from '@angular-devkit/schematics/testing';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { runSchematic } from '../../../utils/testing';
import { Schema } from '../schema';
describe('moveProject Rule', () => {
let tree: UnitTestTree;
let schema: Schema;
beforeEach(async () => {
tree = createEmptyWorkspace(Tree.empty()) as UnitTestTree;
tree = await runSchematic('lib', { name: 'my-lib' }, tree);
schema = {
projectName: 'my-lib'
};
});
it('should delete the project folder', async () => {
// TODO - Currently this test will fail due to
// https://github.com/angular/angular-cli/issues/16527
// tree = (await callRule(removeProject(schema), tree)) as UnitTestTree;
//
// const libDir = tree.getDir('libs/my-lib');
// let filesFound = false;
// libDir.visit(_file => {
// filesFound = true;
// });
// expect(filesFound).toBeFalsy();
});
});

View File

@ -0,0 +1,22 @@
import { SchematicContext, Tree } from '@angular-devkit/schematics';
import { getWorkspace } from '@nrwl/workspace';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Schema } from '../schema';
/**
* Removes (deletes) a project from the folder tree
*
* @param schema The options provided to the schematic
*/
export function removeProject(schema: Schema) {
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
return from(getWorkspace(tree)).pipe(
map(workspace => {
const project = workspace.projects.get(schema.projectName);
tree.delete(project.root);
return tree;
})
);
};
}

View File

@ -0,0 +1,32 @@
import { Tree } from '@angular-devkit/schematics';
import { UnitTestTree } from '@angular-devkit/schematics/testing';
import { readJsonInTree } from '@nrwl/workspace';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { callRule, runSchematic } from '../../../utils/testing';
import { Schema } from '../schema';
import { updateNxJson } from './update-nx-json';
describe('updateNxJson Rule', () => {
let tree: UnitTestTree;
beforeEach(async () => {
tree = new UnitTestTree(Tree.empty());
tree = createEmptyWorkspace(tree) as UnitTestTree;
});
it('should update nx.json', async () => {
tree = await runSchematic('lib', { name: 'my-lib' }, tree);
let nxJson = readJsonInTree(tree, '/nx.json');
expect(nxJson.projects['my-lib']).toBeDefined();
const schema: Schema = {
projectName: 'my-lib'
};
tree = (await callRule(updateNxJson(schema), tree)) as UnitTestTree;
nxJson = readJsonInTree(tree, '/nx.json');
expect(nxJson.projects['my-lib']).toBeUndefined();
});
});

View File

@ -0,0 +1,14 @@
import { NxJson, updateJsonInTree } from '@nrwl/workspace';
import { Schema } from '../schema';
/**
* Updates the nx.json file to remove the project
*
* @param schema The options provided to the schematic
*/
export function updateNxJson(schema: Schema) {
return updateJsonInTree<NxJson>('nx.json', json => {
delete json.projects[schema.projectName];
return json;
});
}

View File

@ -0,0 +1,35 @@
import { Tree } from '@angular-devkit/schematics';
import { UnitTestTree } from '@angular-devkit/schematics/testing';
import { readJsonInTree } from '@nrwl/workspace';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { callRule, runSchematic } from '../../../utils/testing';
import { Schema } from '../schema';
import { updateTsconfig } from './update-tsconfig';
describe('updateTsconfig Rule', () => {
let tree: UnitTestTree;
let schema: Schema;
beforeEach(async () => {
tree = new UnitTestTree(Tree.empty());
tree = createEmptyWorkspace(tree) as UnitTestTree;
schema = {
projectName: 'my-lib'
};
});
it('should delete project ref from the tsconfig', async () => {
tree = await runSchematic('lib', { name: 'my-lib' }, tree);
let tsConfig = readJsonInTree(tree, '/tsconfig.json');
expect(tsConfig.compilerOptions.paths).toEqual({
'@proj/my-lib': ['libs/my-lib/src/index.ts']
});
tree = (await callRule(updateTsconfig(schema), tree)) as UnitTestTree;
tsConfig = readJsonInTree(tree, '/tsconfig.json');
expect(tsConfig.compilerOptions.paths).toEqual({});
});
});

View File

@ -0,0 +1,37 @@
import { SchematicContext, Tree } from '@angular-devkit/schematics';
import {
getWorkspace,
NxJson,
readJsonInTree,
serializeJson
} from '@nrwl/workspace';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Schema } from '../schema';
/**
* Updates the tsconfig paths to remove the project.
*
* @param schema The options provided to the schematic
*/
export function updateTsconfig(schema: Schema) {
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
return from(getWorkspace(tree)).pipe(
map(workspace => {
const nxJson = readJsonInTree<NxJson>(tree, 'nx.json');
const project = workspace.projects.get(schema.projectName);
const tsConfigPath = 'tsconfig.json';
if (tree.exists(tsConfigPath)) {
let contents = JSON.parse(tree.read(tsConfigPath).toString('utf-8'));
delete contents.compilerOptions.paths[
`@${nxJson.npmScope}/${project.root.substr(5)}`
];
tree.overwrite(tsConfigPath, serializeJson(contents));
}
return tree;
})
);
};
}

View File

@ -0,0 +1,70 @@
import { Tree } from '@angular-devkit/schematics';
import { UnitTestTree } from '@angular-devkit/schematics/testing';
import { updateWorkspaceInTree } from '@nrwl/workspace';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { callRule } from '../../../utils/testing';
import { Schema } from '../schema';
import { updateWorkspace } from './update-workspace';
describe('updateWorkspace Rule', () => {
let tree: UnitTestTree;
let schema: Schema;
beforeEach(async () => {
tree = new UnitTestTree(Tree.empty());
tree = createEmptyWorkspace(tree) as UnitTestTree;
schema = {
projectName: 'ng-app'
};
tree = (await callRule(
updateWorkspaceInTree(workspace => {
return {
version: 1,
projects: {
'ng-app': {
projectType: 'application',
schematics: {},
root: 'apps/ng-app',
sourceRoot: 'apps/ng-app/src',
prefix: 'happyorg',
architect: {
build: {
builder: '@angular-devkit/build-angular:browser',
options: {}
}
}
},
'ng-app-e2e': {
root: 'apps/ng-app-e2e',
sourceRoot: 'apps/ng-app-e2e/src',
projectType: 'application',
architect: {
e2e: {
builder: '@nrwl/cypress:cypress',
options: {
cypressConfig: 'apps/ng-app-e2e/cypress.json',
tsConfig: 'apps/ng-app-e2e/tsconfig.e2e.json',
devServerTarget: 'ng-app:serve'
}
}
}
}
}
};
}),
tree
)) as UnitTestTree;
});
it('should delete the project', async () => {
let workspace = JSON.parse(tree.read('workspace.json').toString());
expect(workspace.projects['ng-app']).toBeDefined();
tree = (await callRule(updateWorkspace(schema), tree)) as UnitTestTree;
workspace = JSON.parse(tree.read('workspace.json').toString());
expect(workspace.projects['ng-app']).toBeUndefined();
});
});

View File

@ -0,0 +1,14 @@
import { updateWorkspaceInTree } from '@nrwl/workspace';
import { Schema } from '../schema';
/**
* Deletes the project from the workspace file
*
* @param schema The options provided to the schematic
*/
export function updateWorkspace(schema: Schema) {
return updateWorkspaceInTree(workspace => {
delete workspace.projects[schema.projectName];
return workspace;
});
}

View File

@ -0,0 +1,19 @@
import { chain, Rule } from '@angular-devkit/schematics';
import { checkDependencies } from './lib/check-dependencies';
import { checkTargets } from './lib/check-targets';
import { removeProject } from './lib/remove-project';
import { updateNxJson } from './lib/update-nx-json';
import { updateTsconfig } from './lib/update-tsconfig';
import { updateWorkspace } from './lib/update-workspace';
import { Schema } from './schema';
export default function(schema: Schema): Rule {
return chain([
checkDependencies(schema),
checkTargets(schema),
removeProject(schema),
updateNxJson(schema),
updateTsconfig(schema),
updateWorkspace(schema)
]);
}

View File

@ -0,0 +1,4 @@
export interface Schema extends json.JsonObject {
projectName: string;
forceRemove?: boolean;
}

View File

@ -0,0 +1,35 @@
{
"$schema": "http://json-schema.org/schema",
"id": "NxWorkspaceRemove",
"title": "Nx Remove",
"description": "Remove a project from the workspace",
"type": "object",
"examples": [
{
"command": "g @nrwl/workspace:remove my-feature-lib",
"description": "Remove my-feature-lib from the workspace"
},
{
"command": "g @nrwl/workspace:remove my-feature-lib --forceRemove",
"description": "Force removal of my-feature-lib from the workspace"
}
],
"properties": {
"projectName": {
"type": "string",
"alias": "project",
"description": "The name of the project to remove",
"$default": {
"$source": "argv",
"index": 0
}
},
"forceRemove": {
"type": "boolean",
"aliases": ["force-remove"],
"description": "When true, forces removal even if the project is still in use.",
"default": false
}
},
"required": ["projectName"]
}

View File

@ -1,8 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import { ensureDirSync } from 'fs-extra';
import * as path from 'path';
import * as stripJsonComments from 'strip-json-comments';
import { appRootPath } from './app-root';
const ignore = require('ignore');
export function writeToFile(filePath: string, str: string) {