feat(core): migrate move to devkit (#4558)
This commit is contained in:
parent
958c302c17
commit
aeec4bd4d9
@ -56,6 +56,16 @@ Type: `string`
|
||||
|
||||
The name of the project to move
|
||||
|
||||
### skipFormat
|
||||
|
||||
Alias(es): skip-format
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Skip formatting files.
|
||||
|
||||
### updateImportPath
|
||||
|
||||
Default: `true`
|
||||
|
||||
@ -56,6 +56,16 @@ Type: `string`
|
||||
|
||||
The name of the project to move
|
||||
|
||||
### skipFormat
|
||||
|
||||
Alias(es): skip-format
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Skip formatting files.
|
||||
|
||||
### updateImportPath
|
||||
|
||||
Default: `true`
|
||||
|
||||
@ -56,6 +56,16 @@ Type: `string`
|
||||
|
||||
The name of the project to move
|
||||
|
||||
### skipFormat
|
||||
|
||||
Alias(es): skip-format
|
||||
|
||||
Default: `false`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Skip formatting files.
|
||||
|
||||
### updateImportPath
|
||||
|
||||
Default: `true`
|
||||
|
||||
@ -750,9 +750,7 @@ describe('Move Project', () => {
|
||||
const jestConfig = readFile(jestConfigPath);
|
||||
expect(jestConfig).toContain(`displayName: 'shared-${lib1}-data-access'`);
|
||||
expect(jestConfig).toContain(`preset: '../../../../jest.preset.js'`);
|
||||
expect(jestConfig).toContain(
|
||||
`coverageDirectory: '../../../../coverage/${newPath}'`
|
||||
);
|
||||
expect(jestConfig).toContain(`'../../../../coverage/${newPath}'`);
|
||||
|
||||
const tsConfigPath = `${newPath}/tsconfig.json`;
|
||||
expect(moveOutput).toContain(`CREATE ${tsConfigPath}`);
|
||||
@ -888,9 +886,7 @@ describe('Move Project', () => {
|
||||
const jestConfig = readFile(jestConfigPath);
|
||||
expect(jestConfig).toContain(`displayName: 'shared-${lib1}-data-access'`);
|
||||
expect(jestConfig).toContain(`preset: '../../../../jest.preset.js'`);
|
||||
expect(jestConfig).toContain(
|
||||
`coverageDirectory: '../../../../coverage/${newPath}'`
|
||||
);
|
||||
expect(jestConfig).toContain(`'../../../../coverage/${newPath}'`);
|
||||
|
||||
const tsConfigPath = `${newPath}/tsconfig.json`;
|
||||
expect(moveOutput).toContain(`CREATE ${tsConfigPath}`);
|
||||
@ -1029,9 +1025,7 @@ describe('Move Project', () => {
|
||||
const jestConfig = readFile(jestConfigPath);
|
||||
expect(jestConfig).toContain(`displayName: 'shared-${lib1}-data-access'`);
|
||||
expect(jestConfig).toContain(`preset: '../../../../jest.preset.js'`);
|
||||
expect(jestConfig).toContain(
|
||||
`coverageDirectory: '../../../../coverage/${newPath}'`
|
||||
);
|
||||
expect(jestConfig).toContain(`'../../../../coverage/${newPath}'`);
|
||||
|
||||
const tsConfigPath = `${newPath}/tsconfig.json`;
|
||||
expect(moveOutput).toContain(`CREATE ${tsConfigPath}`);
|
||||
|
||||
@ -21,9 +21,14 @@ export { generateFiles } from './src/generators/generate-files';
|
||||
export {
|
||||
addProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
removeProjectConfiguration,
|
||||
updateProjectConfiguration,
|
||||
readWorkspaceConfiguration,
|
||||
updateWorkspaceConfiguration,
|
||||
getProjects,
|
||||
} from './src/generators/project-configuration';
|
||||
export { toJS } from './src/generators/to-js';
|
||||
export { visitNotIgnoredFiles } from './src/generators/visit-not-ignored-files';
|
||||
|
||||
export { readJson, writeJson, updateJson } from './src/utils/json';
|
||||
export { addDependenciesToPackageJson } from './src/utils/package-json';
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
"dependencies": {
|
||||
"@nrwl/tao": "*",
|
||||
"ejs": "^3.1.5",
|
||||
"ignore": "^5.0.4",
|
||||
"semver": "6.3.0",
|
||||
"strip-json-comments": "2.0.1",
|
||||
"tslib": "^2.0.0"
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
toNewFormat,
|
||||
WorkspaceConfiguration,
|
||||
} from '@nrwl/tao/src/shared/workspace';
|
||||
import { readJson } from '../utils/json';
|
||||
import { readJson, updateJson } from '../utils/json';
|
||||
import {
|
||||
NxJsonConfiguration,
|
||||
NxJsonProjectConfiguration,
|
||||
@ -47,6 +47,62 @@ export function updateProjectConfiguration(
|
||||
setProjectConfiguration(host, projectName, projectConfiguration, 'update');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the configuration of an existing project.
|
||||
*
|
||||
* The project configuration is stored in workspace.json and nx.json.
|
||||
* The utility will update both files.
|
||||
*/
|
||||
export function removeProjectConfiguration(host: Tree, projectName: string) {
|
||||
setProjectConfiguration(host, projectName, undefined, 'delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map of all projects in a workspace.
|
||||
*
|
||||
* Use {@link readProjectConfiguration} if only one project is needed.
|
||||
*/
|
||||
export function getProjects(host: Tree) {
|
||||
const workspace = readWorkspace(host);
|
||||
const nxJson = readJson<NxJsonConfiguration>(host, 'nx.json');
|
||||
|
||||
return new Map(
|
||||
Object.keys(workspace.projects).map((projectName) => {
|
||||
return [
|
||||
projectName,
|
||||
getProjectConfiguration(projectName, workspace, nxJson),
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read general workspace configuration such as the default project or cli settings
|
||||
*
|
||||
* This does _not_ provide projects configuration, use {@link readProjectConfiguration} instead.
|
||||
*/
|
||||
export function readWorkspaceConfiguration(
|
||||
host: Tree
|
||||
): Omit<WorkspaceConfiguration, 'projects'> {
|
||||
const workspace = readWorkspace(host);
|
||||
delete workspace.projects;
|
||||
return workspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update general workspace configuration such as the default project or cli settings.
|
||||
*
|
||||
* This does _not_ update projects configuration, use {@link updateProjectConfiguration} or {@link addProjectConfiguration} instead.
|
||||
*/
|
||||
export function updateWorkspaceConfiguration(
|
||||
host: Tree,
|
||||
workspace: Omit<WorkspaceConfiguration, 'projects'>
|
||||
) {
|
||||
updateJson<WorkspaceConfiguration>(host, getWorkspacePath(host), (json) => {
|
||||
return { ...workspace, projects: json.projects };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a project configuration.
|
||||
*
|
||||
@ -56,36 +112,45 @@ export function updateProjectConfiguration(
|
||||
* @param host - the file system tree
|
||||
* @param projectName - unique name. Often directories are part of the name (e.g., mydir-mylib)
|
||||
*/
|
||||
export function readProjectConfiguration(
|
||||
host: Tree,
|
||||
projectName: string
|
||||
): ProjectConfiguration & NxJsonProjectConfiguration {
|
||||
return {
|
||||
...readWorkspaceSection(host, projectName),
|
||||
...readNxJsonSection(host, projectName),
|
||||
};
|
||||
}
|
||||
|
||||
function readWorkspaceSection(host: Tree, projectName: string) {
|
||||
const path = getWorkspacePath(host);
|
||||
const workspaceJson = readJson<WorkspaceConfiguration>(host, path);
|
||||
const newFormat = toNewFormat(workspaceJson);
|
||||
|
||||
if (!newFormat.projects[projectName]) {
|
||||
export function readProjectConfiguration(host: Tree, projectName: string) {
|
||||
const workspace = readWorkspace(host);
|
||||
if (!workspace.projects[projectName]) {
|
||||
throw new Error(
|
||||
`Cannot find configuration for '${projectName}' in ${path}.`
|
||||
`Cannot find configuration for '${projectName}' in ${getWorkspacePath(
|
||||
host
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
return newFormat.projects[projectName] as ProjectConfiguration;
|
||||
}
|
||||
|
||||
function readNxJsonSection(host: Tree, projectName: string) {
|
||||
const nxJson = readJson<NxJsonConfiguration>(host, 'nx.json');
|
||||
if (!nxJson.projects[projectName]) {
|
||||
throw new Error(
|
||||
`Cannot find configuration for '${projectName}' in nx.json`
|
||||
);
|
||||
}
|
||||
|
||||
return getProjectConfiguration(projectName, workspace, nxJson);
|
||||
}
|
||||
|
||||
function getProjectConfiguration(
|
||||
projectName: string,
|
||||
workspace: WorkspaceConfiguration,
|
||||
nxJson: NxJsonConfiguration
|
||||
): ProjectConfiguration & NxJsonProjectConfiguration {
|
||||
return {
|
||||
...readWorkspaceSection(workspace, projectName),
|
||||
...readNxJsonSection(nxJson, projectName),
|
||||
};
|
||||
}
|
||||
|
||||
function readWorkspaceSection(
|
||||
workspace: WorkspaceConfiguration,
|
||||
projectName: string
|
||||
) {
|
||||
return workspace.projects[projectName] as ProjectConfiguration;
|
||||
}
|
||||
|
||||
function readNxJsonSection(nxJson: NxJsonConfiguration, projectName: string) {
|
||||
return nxJson.projects[projectName];
|
||||
}
|
||||
|
||||
@ -93,25 +158,42 @@ function setProjectConfiguration(
|
||||
host: Tree,
|
||||
projectName: string,
|
||||
projectConfiguration: ProjectConfiguration & NxJsonProjectConfiguration,
|
||||
mode: 'create' | 'update'
|
||||
mode: 'create' | 'update' | 'delete'
|
||||
) {
|
||||
if (mode === 'delete') {
|
||||
addProjectToNxJson(host, projectName, undefined, mode);
|
||||
addProjectToWorkspaceJson(host, projectName, undefined, mode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!projectConfiguration) {
|
||||
throw new Error(
|
||||
`Cannot ${mode} "${projectName}" with value ${projectConfiguration}`
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
tags,
|
||||
implicitDependencies,
|
||||
...workspaceConfiguration
|
||||
} = projectConfiguration;
|
||||
addProjectToWorkspaceJson(host, projectName, workspaceConfiguration, mode);
|
||||
addProjectToNxJson(host, projectName, {
|
||||
tags,
|
||||
implicitDependencies,
|
||||
});
|
||||
addProjectToNxJson(
|
||||
host,
|
||||
projectName,
|
||||
{
|
||||
tags,
|
||||
implicitDependencies,
|
||||
},
|
||||
mode
|
||||
);
|
||||
}
|
||||
|
||||
function addProjectToWorkspaceJson(
|
||||
host: Tree,
|
||||
projectName: string,
|
||||
project: ProjectConfiguration,
|
||||
mode: 'create' | 'update'
|
||||
mode: 'create' | 'update' | 'delete'
|
||||
) {
|
||||
const path = getWorkspacePath(host);
|
||||
const workspaceJson = readJson<WorkspaceConfiguration>(host, path);
|
||||
@ -125,6 +207,11 @@ function addProjectToWorkspaceJson(
|
||||
`Cannot update Project '${projectName}'. It does not exist.`
|
||||
);
|
||||
}
|
||||
if (mode == 'delete' && !workspaceJson.projects[projectName]) {
|
||||
throw new Error(
|
||||
`Cannot update Project '${projectName}'. It does not exist.`
|
||||
);
|
||||
}
|
||||
workspaceJson.projects[projectName] = project;
|
||||
host.write(path, JSON.stringify(workspaceJson));
|
||||
}
|
||||
@ -132,14 +219,29 @@ function addProjectToWorkspaceJson(
|
||||
function addProjectToNxJson(
|
||||
host: Tree,
|
||||
projectName: string,
|
||||
config: NxJsonProjectConfiguration
|
||||
config: NxJsonProjectConfiguration,
|
||||
mode: 'create' | 'update' | 'delete'
|
||||
) {
|
||||
const nxJson = readJson<NxJsonConfiguration>(host, 'nx.json');
|
||||
nxJson.projects[projectName] = {
|
||||
...{
|
||||
tags: [],
|
||||
},
|
||||
...(config || {}),
|
||||
};
|
||||
if (mode === 'delete') {
|
||||
delete nxJson.projects[projectName];
|
||||
} else {
|
||||
nxJson.projects[projectName] = {
|
||||
...{
|
||||
tags: [],
|
||||
},
|
||||
...(config || {}),
|
||||
};
|
||||
}
|
||||
host.write('nx.json', JSON.stringify(nxJson));
|
||||
}
|
||||
|
||||
function readWorkspace(host: Tree): WorkspaceConfiguration {
|
||||
const path = getWorkspacePath(host);
|
||||
const workspaceJson = readJson<WorkspaceConfiguration>(host, path);
|
||||
const originalVersion = workspaceJson.version;
|
||||
return {
|
||||
...toNewFormat(workspaceJson),
|
||||
version: originalVersion,
|
||||
};
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
import { createTree } from '@nrwl/devkit/testing';
|
||||
import { Tree, visitNotIgnoredFiles } from '@nrwl/devkit';
|
||||
|
||||
describe('visitNotIgnoredFiles', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTree();
|
||||
});
|
||||
|
||||
it('should visit files recursively in a directory', () => {
|
||||
tree.write('dir/file1.ts', '');
|
||||
tree.write('dir/dir2/file2.ts', '');
|
||||
|
||||
const visitor = jest.fn();
|
||||
visitNotIgnoredFiles(tree, 'dir', visitor);
|
||||
|
||||
expect(visitor).toHaveBeenCalledWith('dir/file1.ts');
|
||||
expect(visitor).toHaveBeenCalledWith('dir/dir2/file2.ts');
|
||||
});
|
||||
|
||||
it('should not visit ignored files in a directory', () => {
|
||||
tree.write('.gitignore', 'node_modules');
|
||||
|
||||
tree.write('dir/file1.ts', '');
|
||||
tree.write('dir/node_modules/file1.ts', '');
|
||||
tree.write('dir/dir2/file2.ts', '');
|
||||
|
||||
const visitor = jest.fn();
|
||||
visitNotIgnoredFiles(tree, 'dir', visitor);
|
||||
|
||||
expect(visitor).toHaveBeenCalledWith('dir/file1.ts');
|
||||
expect(visitor).toHaveBeenCalledWith('dir/dir2/file2.ts');
|
||||
expect(visitor).not.toHaveBeenCalledWith('dir/node_modules/file1.ts');
|
||||
});
|
||||
|
||||
it('should be able to visit the root', () => {
|
||||
tree.write('.gitignore', 'node_modules');
|
||||
|
||||
tree.write('dir/file1.ts', '');
|
||||
tree.write('dir/node_modules/file1.ts', '');
|
||||
tree.write('dir/dir2/file2.ts', '');
|
||||
|
||||
const visitor = jest.fn();
|
||||
visitNotIgnoredFiles(tree, '', visitor);
|
||||
|
||||
expect(visitor).toHaveBeenCalledWith('.gitignore');
|
||||
expect(visitor).toHaveBeenCalledWith('dir/file1.ts');
|
||||
expect(visitor).toHaveBeenCalledWith('dir/dir2/file2.ts');
|
||||
expect(visitor).not.toHaveBeenCalledWith('dir/node_modules/file1.ts');
|
||||
});
|
||||
});
|
||||
34
packages/devkit/src/generators/visit-not-ignored-files.ts
Normal file
34
packages/devkit/src/generators/visit-not-ignored-files.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Tree } from '@nrwl/tao/src/shared/tree';
|
||||
|
||||
import { join } from 'path';
|
||||
|
||||
import ignore from 'ignore';
|
||||
|
||||
/**
|
||||
* Utility to act on all files in a tree that are not ignored by git.
|
||||
*/
|
||||
export function visitNotIgnoredFiles(
|
||||
tree: Tree,
|
||||
dirPath: string = tree.root,
|
||||
visitor: (path: string) => void
|
||||
) {
|
||||
let ig;
|
||||
if (tree.exists('.gitignore')) {
|
||||
ig = ignore();
|
||||
ig.add(tree.read('.gitignore').toString());
|
||||
}
|
||||
if (dirPath !== '' && ig?.ignores(dirPath)) {
|
||||
return;
|
||||
}
|
||||
for (const child of tree.children(dirPath)) {
|
||||
const fullPath = join(dirPath, child);
|
||||
if (ig?.ignores(fullPath)) {
|
||||
continue;
|
||||
}
|
||||
if (tree.isFile(fullPath)) {
|
||||
visitor(fullPath);
|
||||
} else {
|
||||
visitNotIgnoredFiles(tree, fullPath, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,5 +4,5 @@ import { FsTree } from '@nrwl/tao/src/shared/tree';
|
||||
* Creates a host for testing.
|
||||
*/
|
||||
export function createTree() {
|
||||
return new FsTree('/', false);
|
||||
return new FsTree('/virtual', false);
|
||||
}
|
||||
|
||||
@ -77,10 +77,7 @@ class DevkitTreeFromAngularDevkitTree {
|
||||
|
||||
children(dirPath: string): string[] {
|
||||
const { subdirs, subfiles } = this.tree.getDir(dirPath);
|
||||
return [
|
||||
...subdirs.map((fragment) => join(this.root, fragment)),
|
||||
...subfiles.map((fragment) => join(this.root, fragment)),
|
||||
];
|
||||
return [...subdirs, ...subfiles];
|
||||
}
|
||||
|
||||
delete(filePath: string): void {
|
||||
@ -100,12 +97,12 @@ class DevkitTreeFromAngularDevkitTree {
|
||||
for (const action of this.tree.actions) {
|
||||
if (action.kind === 'r') {
|
||||
fileChanges.push({
|
||||
path: this.normalize(action.path),
|
||||
path: this.normalize(action.to),
|
||||
type: 'CREATE',
|
||||
content: this.read(action.path),
|
||||
content: this.read(action.to),
|
||||
});
|
||||
fileChanges.push({
|
||||
path: this.normalize(action.to),
|
||||
path: this.normalize(action.path),
|
||||
type: 'DELETE',
|
||||
content: null,
|
||||
});
|
||||
|
||||
@ -134,6 +134,35 @@ describe('applyChangesToString', () => {
|
||||
expect(result).toEqual('Updated Text');
|
||||
});
|
||||
|
||||
it('should be able to replace text twice', () => {
|
||||
const original = 'Original Text';
|
||||
|
||||
const result = applyChangesToString(original, [
|
||||
{
|
||||
type: ChangeType.Delete,
|
||||
start: 0,
|
||||
length: 8,
|
||||
},
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: 0,
|
||||
text: 'Updated',
|
||||
},
|
||||
{
|
||||
type: ChangeType.Delete,
|
||||
start: 9,
|
||||
length: 4,
|
||||
},
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: 9,
|
||||
text: 'Updated',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result).toEqual('Updated Updated');
|
||||
});
|
||||
|
||||
it('should sort changes when replacing text', () => {
|
||||
const original = 'Original Text';
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export enum ChangeType {
|
||||
Delete = 'DELETE',
|
||||
Delete = 'Delete',
|
||||
Insert = 'Insert',
|
||||
}
|
||||
|
||||
@ -68,22 +68,29 @@ export function applyChangesToString(
|
||||
changes: StringChange[]
|
||||
): string {
|
||||
assertChangesValid(changes);
|
||||
const sortedChanges = changes.sort(
|
||||
(a, b) => getChangeIndex(a) - getChangeIndex(b)
|
||||
);
|
||||
const sortedChanges = changes.sort((a, b) => {
|
||||
const diff = getChangeIndex(a) - getChangeIndex(b);
|
||||
if (diff === 0) {
|
||||
if (a.type === b.type) {
|
||||
return 0;
|
||||
} else {
|
||||
// When at the same place, Insert before Delete
|
||||
return a.type === ChangeType.Insert ? -1 : 1;
|
||||
}
|
||||
}
|
||||
return diff;
|
||||
});
|
||||
let offset = 0;
|
||||
for (const change of sortedChanges) {
|
||||
const index = getChangeIndex(change) + offset;
|
||||
switch (change.type) {
|
||||
case ChangeType.Insert: {
|
||||
const index = change.index + Math.max(offset, 0);
|
||||
text = text.substr(0, index) + change.text + text.substr(index);
|
||||
offset += change.text.length;
|
||||
break;
|
||||
}
|
||||
case ChangeType.Delete: {
|
||||
text =
|
||||
text.substr(0, change.start + offset) +
|
||||
text.substr(change.start + change.length + offset);
|
||||
text = text.substr(0, index) + text.substr(index + change.length);
|
||||
offset -= change.length;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ describe('Jest Executor', () => {
|
||||
root: '/root',
|
||||
projectName: 'proj',
|
||||
workspace: {
|
||||
version: 2,
|
||||
projects: {
|
||||
proj: {
|
||||
root: 'proj',
|
||||
|
||||
@ -216,25 +216,77 @@ describe('tree', () => {
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
it('should return the list of children of a dir', () => {
|
||||
tree.write('parent/new-child/new-child-file.txt', 'new child content');
|
||||
describe('children', () => {
|
||||
it('should return the list of children of a dir', () => {
|
||||
expect(tree.children('parent')).toEqual(['child', 'parent-file.txt']);
|
||||
expect(tree.children('parent/child')).toEqual(['child-file.txt']);
|
||||
});
|
||||
|
||||
expect(tree.children('parent/child')).toEqual(['child-file.txt']);
|
||||
expect(tree.children('parent/new-child')).toEqual(['new-child-file.txt']);
|
||||
it('should add new children after writing new files', () => {
|
||||
tree.write('parent/child/child-file2.txt', 'new child content');
|
||||
tree.write('parent/new-child/new-child-file.txt', 'new child content');
|
||||
|
||||
tree.rename(
|
||||
'parent/child/child-file.txt',
|
||||
'parent/child/renamed-child-file.txt'
|
||||
);
|
||||
tree.rename(
|
||||
'parent/new-child/new-child-file.txt',
|
||||
'parent/new-child/renamed-new-child-file.txt'
|
||||
);
|
||||
expect(tree.children('parent')).toEqual([
|
||||
'child',
|
||||
'parent-file.txt',
|
||||
'new-child',
|
||||
]);
|
||||
expect(tree.children('parent/child')).toEqual([
|
||||
'child-file.txt',
|
||||
'child-file2.txt',
|
||||
]);
|
||||
expect(tree.children('parent/new-child')).toEqual([
|
||||
'new-child-file.txt',
|
||||
]);
|
||||
});
|
||||
|
||||
expect(tree.children('parent/child')).toEqual(['renamed-child-file.txt']);
|
||||
expect(tree.children('parent/new-child')).toEqual([
|
||||
'renamed-new-child-file.txt',
|
||||
]);
|
||||
it('should return the list of children after renaming', () => {
|
||||
tree.rename(
|
||||
'parent/child/child-file.txt',
|
||||
'parent/child/renamed-child-file.txt'
|
||||
);
|
||||
|
||||
expect(tree.children('parent/child')).toEqual([
|
||||
'renamed-child-file.txt',
|
||||
]);
|
||||
|
||||
tree.rename(
|
||||
'parent/child/renamed-child-file.txt',
|
||||
'parent/renamed-child/renamed-child-file.txt'
|
||||
);
|
||||
|
||||
expect(tree.children('parent')).toEqual([
|
||||
'parent-file.txt',
|
||||
'renamed-child',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('at the root', () => {
|
||||
it('should return a list of children', () => {
|
||||
expect(tree.children('')).toEqual(['parent', 'root-file.txt']);
|
||||
});
|
||||
|
||||
it('should add a child after writing a file', () => {
|
||||
tree.write('root-file2.txt', '');
|
||||
|
||||
expect(tree.children('')).toEqual([
|
||||
'parent',
|
||||
'root-file.txt',
|
||||
'root-file2.txt',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should remove a child after deleting a file', () => {
|
||||
tree.delete('parent/child/child-file.txt');
|
||||
tree.delete('parent/parent-file.txt');
|
||||
|
||||
expect(tree.children('')).not.toContain('parent');
|
||||
|
||||
tree.delete('root-file.txt');
|
||||
|
||||
expect(tree.children('')).not.toContain('root-file.txt');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to rename dirs', () => {
|
||||
|
||||
@ -122,6 +122,27 @@ export class FsTree implements Tree {
|
||||
this.write(filePath, content);
|
||||
}
|
||||
|
||||
delete(filePath: string): void {
|
||||
filePath = this.normalize(filePath);
|
||||
if (this.filesForDir(this.rp(filePath)).length > 0) {
|
||||
this.filesForDir(this.rp(filePath)).forEach(
|
||||
(f) => (this.recordedChanges[f] = { content: null, isDeleted: true })
|
||||
);
|
||||
}
|
||||
this.recordedChanges[this.rp(filePath)] = {
|
||||
content: null,
|
||||
isDeleted: true,
|
||||
};
|
||||
|
||||
// Delete directories when
|
||||
if (
|
||||
this.exists(dirname(this.rp(filePath))) &&
|
||||
this.children(dirname(this.rp(filePath))).length < 1
|
||||
) {
|
||||
this.delete(dirname(this.rp(filePath)));
|
||||
}
|
||||
}
|
||||
|
||||
exists(filePath: string): boolean {
|
||||
filePath = this.normalize(filePath);
|
||||
try {
|
||||
@ -137,25 +158,12 @@ export class FsTree implements Tree {
|
||||
}
|
||||
}
|
||||
|
||||
delete(filePath: string): void {
|
||||
filePath = this.normalize(filePath);
|
||||
if (this.filesForDir(this.rp(filePath)).length > 0) {
|
||||
this.filesForDir(this.rp(filePath)).forEach(
|
||||
(f) => (this.recordedChanges[f] = { content: null, isDeleted: true })
|
||||
);
|
||||
}
|
||||
this.recordedChanges[this.rp(filePath)] = {
|
||||
content: null,
|
||||
isDeleted: true,
|
||||
};
|
||||
}
|
||||
|
||||
rename(from: string, to: string): void {
|
||||
from = this.normalize(from);
|
||||
to = this.normalize(to);
|
||||
const content = this.read(this.rp(from));
|
||||
this.recordedChanges[this.rp(from)] = { content: null, isDeleted: true };
|
||||
this.recordedChanges[this.rp(to)] = { content: content, isDeleted: false };
|
||||
this.delete(this.rp(from));
|
||||
this.write(this.rp(to), content);
|
||||
}
|
||||
|
||||
isFile(filePath: string): boolean {
|
||||
@ -176,11 +184,12 @@ export class FsTree implements Tree {
|
||||
let res = this.fsReadDir(dirPath);
|
||||
|
||||
res = [...res, ...this.directChildrenOfDir(this.rp(dirPath))];
|
||||
return res.filter((q) => {
|
||||
res = res.filter((q) => {
|
||||
const r = this.recordedChanges[join(this.rp(dirPath), q)];
|
||||
if (r && r.isDeleted) return false;
|
||||
return true;
|
||||
return !r?.isDeleted;
|
||||
});
|
||||
// Dedupe
|
||||
return Array.from(new Set(res));
|
||||
}
|
||||
|
||||
listChanges(): FileChange[] {
|
||||
@ -255,12 +264,18 @@ export class FsTree implements Tree {
|
||||
|
||||
private directChildrenOfDir(path: string): string[] {
|
||||
const res = {};
|
||||
if (path === '') {
|
||||
return Object.keys(this.recordedChanges).map(
|
||||
(file) => file.split('/')[0]
|
||||
);
|
||||
}
|
||||
Object.keys(this.recordedChanges).forEach((f) => {
|
||||
if (f.startsWith(path + '/')) {
|
||||
const [_, file] = f.split(path + '/');
|
||||
res[file.split('/')[0]] = true;
|
||||
}
|
||||
});
|
||||
|
||||
return Object.keys(res);
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,10 @@ import '../compat/compat';
|
||||
* Workspace configuration
|
||||
*/
|
||||
export interface WorkspaceConfiguration {
|
||||
/**
|
||||
* Version of the configuration format
|
||||
*/
|
||||
version: number;
|
||||
/**
|
||||
* Projects' configurations
|
||||
*/
|
||||
@ -386,7 +390,7 @@ export function reformattedWorkspaceJsonOrNull(w: any) {
|
||||
return w.version === 2 ? toNewFormatOrNull(w) : toOldFormatOrNull(w);
|
||||
}
|
||||
|
||||
export function toNewFormat(w: any) {
|
||||
export function toNewFormat(w: any): WorkspaceConfiguration {
|
||||
const f = toNewFormatOrNull(w);
|
||||
return f ? f : w;
|
||||
}
|
||||
|
||||
@ -24,14 +24,14 @@
|
||||
},
|
||||
|
||||
"move": {
|
||||
"factory": "./src/schematics/move/move",
|
||||
"factory": "./src/schematics/move/move#moveSchematic",
|
||||
"schema": "./src/schematics/move/schema.json",
|
||||
"aliases": ["mv"],
|
||||
"description": "Move an application or library to another folder"
|
||||
},
|
||||
|
||||
"remove": {
|
||||
"factory": "./src/schematics/remove/remove",
|
||||
"factory": "./src/schematics/remove/remove#moveSchematic",
|
||||
"schema": "./src/schematics/remove/schema.json",
|
||||
"aliases": ["rm"],
|
||||
"description": "Remove an application or library"
|
||||
@ -88,14 +88,14 @@
|
||||
},
|
||||
|
||||
"move": {
|
||||
"factory": "./src/schematics/move/move",
|
||||
"factory": "./src/schematics/move/move#moveGenerator",
|
||||
"schema": "./src/schematics/move/schema.json",
|
||||
"aliases": ["mv"],
|
||||
"description": "Move an application or library to another folder"
|
||||
},
|
||||
|
||||
"remove": {
|
||||
"factory": "./src/schematics/remove/remove",
|
||||
"factory": "./src/schematics/remove/remove#removeGenerator",
|
||||
"schema": "./src/schematics/remove/schema.json",
|
||||
"aliases": ["rm"],
|
||||
"description": "Remove an application or library"
|
||||
|
||||
@ -9,12 +9,9 @@ import {
|
||||
onlyWorkspaceProjects,
|
||||
ProjectGraph,
|
||||
ProjectGraphNode,
|
||||
ProjectGraphDependency,
|
||||
} from '../core/project-graph';
|
||||
import { appRootPath } from '../utils/app-root';
|
||||
import { output } from '../utils/output';
|
||||
import { checkProjectExists } from '../utils/rules/check-project-exists';
|
||||
import { filter } from '@angular-devkit/schematics';
|
||||
|
||||
// maps file extention to MIME types
|
||||
const mimeType = {
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`updateImports should correctly update deep imports 1`] = `
|
||||
"
|
||||
import { Table } from '@proj/table/components';
|
||||
import { Tab } from '@proj/tabs/components';
|
||||
|
||||
export class MyTable extends Table {};
|
||||
export class MyTab extends Tab {};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`updateImports should not update import paths when they contain a partial match 1`] = `
|
||||
"
|
||||
import { Table } from '@proj/table';
|
||||
import { Tab } from '@proj/tabs';
|
||||
|
||||
export class MyTable extends Table {};
|
||||
export class MyTab extends Tab {};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`updateImports should update dynamic imports 1`] = `
|
||||
"
|
||||
import('@proj/table').then(m => m.Table);
|
||||
import('@proj/table/components').then(m => m.Table);
|
||||
import('@proj/tabs').then(m => m.Tab);
|
||||
import('@proj/tabs/components').then(m => m.Tab);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`updateImports should update project refs 1`] = `
|
||||
"
|
||||
import { MyClass } from '@proj/my-destination';
|
||||
|
||||
export class MyExtendedClass extends MyClass {};
|
||||
"
|
||||
`;
|
||||
@ -1,15 +1,21 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
|
||||
import { callRule, runSchematic } from '../../../utils/testing';
|
||||
import {
|
||||
ProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { Schema } from '../schema';
|
||||
import { checkDestination } from './check-destination';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
|
||||
describe('checkDestination Rule', () => {
|
||||
describe('checkDestination', () => {
|
||||
let tree: Tree;
|
||||
let projectConfig: ProjectConfiguration;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = createEmptyWorkspace(Tree.empty());
|
||||
tree = await runSchematic('lib', { name: 'my-lib' }, tree);
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
await libraryGenerator(tree, { name: 'my-lib' });
|
||||
projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
});
|
||||
|
||||
it('should throw an error if the path is not explicit', async () => {
|
||||
@ -20,13 +26,15 @@ describe('checkDestination Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
await expect(callRule(checkDestination(schema), tree)).rejects.toThrow(
|
||||
expect(() => {
|
||||
checkDestination(tree, schema, projectConfig);
|
||||
}).toThrow(
|
||||
`Invalid destination: [${schema.destination}] - Please specify explicit path.`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the path already exists', async () => {
|
||||
tree = await runSchematic('lib', { name: 'my-other-lib' }, tree);
|
||||
await libraryGenerator(tree, { name: 'my-other-lib' });
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-lib',
|
||||
@ -35,7 +43,9 @@ describe('checkDestination Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
await expect(callRule(checkDestination(schema), tree)).rejects.toThrow(
|
||||
expect(() => {
|
||||
checkDestination(tree, schema, projectConfig);
|
||||
}).toThrow(
|
||||
`Invalid destination: [${schema.destination}] - Path is not empty.`
|
||||
);
|
||||
});
|
||||
@ -48,9 +58,9 @@ describe('checkDestination Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
await expect(
|
||||
callRule(checkDestination(schema), tree)
|
||||
).resolves.not.toThrow();
|
||||
expect(() => {
|
||||
checkDestination(tree, schema, projectConfig);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should normalize the destination', async () => {
|
||||
@ -61,7 +71,7 @@ describe('checkDestination Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
await callRule(checkDestination(schema), tree);
|
||||
checkDestination(tree, schema, projectConfig);
|
||||
|
||||
expect(schema.destination).toBe('my-other-lib/wibble');
|
||||
});
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
import { Rule, SchematicContext } from '@angular-devkit/schematics';
|
||||
import { Tree } from '@angular-devkit/schematics/src/tree/interface';
|
||||
import { getWorkspace } from '@nrwl/workspace';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { Schema } from '../schema';
|
||||
import { getDestination, normalizeSlashes } from './utils';
|
||||
|
||||
@ -14,30 +10,24 @@ import { getDestination, normalizeSlashes } from './utils';
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function checkDestination(schema: Schema): Rule {
|
||||
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
|
||||
return from(getWorkspace(tree)).pipe(
|
||||
map((workspace) => {
|
||||
const INVALID_DESTINATION = `Invalid destination: [${schema.destination}]`;
|
||||
export function checkDestination(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
projectConfig: ProjectConfiguration
|
||||
) {
|
||||
const INVALID_DESTINATION = `Invalid destination: [${schema.destination}]`;
|
||||
|
||||
if (schema.destination.includes('..')) {
|
||||
throw new Error(
|
||||
`${INVALID_DESTINATION} - Please specify explicit path.`
|
||||
);
|
||||
}
|
||||
if (schema.destination.includes('..')) {
|
||||
throw new Error(`${INVALID_DESTINATION} - Please specify explicit path.`);
|
||||
}
|
||||
|
||||
const destination = getDestination(schema, workspace, tree);
|
||||
const destination = getDestination(tree, schema, projectConfig);
|
||||
|
||||
if (tree.getDir(destination).subfiles.length > 0) {
|
||||
throw new Error(`${INVALID_DESTINATION} - Path is not empty.`);
|
||||
}
|
||||
if (tree.children(destination).length > 0) {
|
||||
throw new Error(`${INVALID_DESTINATION} - Path is not empty.`);
|
||||
}
|
||||
|
||||
if (schema.destination.startsWith('/')) {
|
||||
schema.destination = normalizeSlashes(schema.destination.substr(1));
|
||||
}
|
||||
|
||||
return tree;
|
||||
})
|
||||
);
|
||||
};
|
||||
if (schema.destination.startsWith('/')) {
|
||||
schema.destination = normalizeSlashes(schema.destination.substr(1));
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,205 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readJson,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
updateJson,
|
||||
} from '@nrwl/devkit';
|
||||
import { NxJson } from '@nrwl/workspace';
|
||||
import { Schema } from '../schema';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { moveProjectConfiguration } from '@nrwl/workspace/src/schematics/move/lib/move-project-configuration';
|
||||
|
||||
describe('moveProjectConfiguration', () => {
|
||||
let tree: Tree;
|
||||
let projectConfig;
|
||||
let schema: Schema;
|
||||
beforeEach(async () => {
|
||||
schema = {
|
||||
projectName: 'my-source',
|
||||
destination: 'subfolder/my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
addProjectConfiguration(tree, 'my-source', {
|
||||
projectType: 'application',
|
||||
root: 'apps/my-source',
|
||||
sourceRoot: 'apps/my-source/src',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/my-source',
|
||||
index: 'apps/my-source/src/index.html',
|
||||
main: 'apps/my-source/src/main.ts',
|
||||
polyfills: 'apps/my-source/src/polyfills.ts',
|
||||
tsConfig: 'apps/my-source/tsconfig.app.json',
|
||||
aot: false,
|
||||
assets: [
|
||||
'apps/my-source/src/favicon.ico',
|
||||
'apps/my-source/src/assets',
|
||||
],
|
||||
styles: ['apps/my-source/src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
fileReplacements: [
|
||||
{
|
||||
replace: 'apps/my-source/src/environments/environment.ts',
|
||||
with: 'apps/my-source/src/environments/environment.prod.ts',
|
||||
},
|
||||
],
|
||||
optimization: true,
|
||||
outputHashing: 'all',
|
||||
sourceMap: false,
|
||||
extractCss: true,
|
||||
namedChunks: false,
|
||||
aot: true,
|
||||
extractLicenses: true,
|
||||
vendorChunk: false,
|
||||
buildOptimizer: true,
|
||||
budgets: [
|
||||
{
|
||||
type: 'initial',
|
||||
maximumWarning: '2mb',
|
||||
maximumError: '5mb',
|
||||
},
|
||||
{
|
||||
type: 'anyComponentStyle',
|
||||
maximumWarning: '6kb',
|
||||
maximumError: '10kb',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
serve: {
|
||||
executor: '@angular-devkit/build-angular:dev-server',
|
||||
options: {
|
||||
browserTarget: 'my-source:build',
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
browserTarget: 'my-source:build:production',
|
||||
},
|
||||
},
|
||||
},
|
||||
'extract-i18n': {
|
||||
executor: '@angular-devkit/build-angular:extract-i18n',
|
||||
options: {
|
||||
browserTarget: 'my-source:build',
|
||||
},
|
||||
},
|
||||
lint: {
|
||||
executor: '@angular-devkit/build-angular:tslint',
|
||||
options: {
|
||||
tsConfig: [
|
||||
'apps/my-source/tsconfig.app.json',
|
||||
'apps/my-source/tsconfig.spec.json',
|
||||
],
|
||||
exclude: ['**/node_modules/**', '!apps/my-source/**/*'],
|
||||
},
|
||||
},
|
||||
test: {
|
||||
executor: '@nrwl/jest:jest',
|
||||
options: {
|
||||
jestConfig: 'apps/my-source/jest.config.js',
|
||||
tsConfig: 'apps/my-source/tsconfig.spec.json',
|
||||
setupFile: 'apps/my-source/src/test-setup.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['type:ui'],
|
||||
implicitDependencies: ['my-other-lib'],
|
||||
});
|
||||
|
||||
addProjectConfiguration(tree, 'my-source-e2e', {
|
||||
root: 'apps/my-source-e2e',
|
||||
sourceRoot: 'apps/my-source-e2e/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
e2e: {
|
||||
executor: '@nrwl/cypress:cypress',
|
||||
options: {
|
||||
cypressConfig: 'apps/my-source-e2e/cypress.json',
|
||||
tsConfig: 'apps/my-source-e2e/tsconfig.e2e.json',
|
||||
devServerTarget: 'my-source:serve',
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
devServerTarget: 'my-source:serve:production',
|
||||
},
|
||||
},
|
||||
},
|
||||
lint: {
|
||||
executor: '@angular-devkit/build-angular:tslint',
|
||||
options: {
|
||||
tsConfig: ['apps/my-source-e2e/tsconfig.e2e.json'],
|
||||
exclude: ['**/node_modules/**', '!apps/my-source-e2e/**/*'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
});
|
||||
it('should rename the project', async () => {
|
||||
moveProjectConfiguration(tree, schema, projectConfig);
|
||||
|
||||
expect(() => {
|
||||
readProjectConfiguration(tree, 'my-source');
|
||||
}).toThrow();
|
||||
|
||||
expect(
|
||||
readProjectConfiguration(tree, 'subfolder-my-destination')
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('should update paths in only the intended project', async () => {
|
||||
moveProjectConfiguration(tree, schema, projectConfig);
|
||||
|
||||
const actualProject = readProjectConfiguration(
|
||||
tree,
|
||||
'subfolder-my-destination'
|
||||
);
|
||||
expect(actualProject).toBeDefined();
|
||||
expect(actualProject.root).toBe('apps/subfolder/my-destination');
|
||||
expect(actualProject.root).toBe('apps/subfolder/my-destination');
|
||||
|
||||
const similarProject = readProjectConfiguration(tree, 'my-source-e2e');
|
||||
expect(similarProject).toBeDefined();
|
||||
expect(similarProject.root).toBe('apps/my-source-e2e');
|
||||
});
|
||||
|
||||
it('honor custom workspace layouts', async () => {
|
||||
updateJson<NxJson>(tree, 'nx.json', (json) => {
|
||||
json.workspaceLayout = { appsDir: 'e2e', libsDir: 'packages' };
|
||||
return json;
|
||||
});
|
||||
|
||||
moveProjectConfiguration(tree, schema, projectConfig);
|
||||
|
||||
const project = readProjectConfiguration(tree, 'subfolder-my-destination');
|
||||
expect(project).toBeDefined();
|
||||
expect(project.root).toBe('e2e/subfolder/my-destination');
|
||||
expect(project.sourceRoot).toBe('e2e/subfolder/my-destination/src');
|
||||
});
|
||||
|
||||
it('should update nx.json', () => {
|
||||
moveProjectConfiguration(tree, schema, projectConfig);
|
||||
|
||||
const actualProject = readProjectConfiguration(
|
||||
tree,
|
||||
'subfolder-my-destination'
|
||||
);
|
||||
|
||||
expect(actualProject.tags).toEqual(['type:ui']);
|
||||
expect(actualProject.implicitDependencies).toEqual(['my-other-lib']);
|
||||
|
||||
expect(readJson(tree, 'nx.json').projects['my-source']).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,36 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
removeProjectConfiguration,
|
||||
NxJsonProjectConfiguration,
|
||||
ProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
import { Schema } from '../schema';
|
||||
import { getDestination, getNewProjectName } from './utils';
|
||||
|
||||
export function moveProjectConfiguration(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
projectConfig: ProjectConfiguration & NxJsonProjectConfiguration
|
||||
) {
|
||||
let destination = getDestination(tree, schema, projectConfig);
|
||||
const projectString = JSON.stringify(projectConfig);
|
||||
const newProjectString = projectString.replace(
|
||||
new RegExp(projectConfig.root, 'g'),
|
||||
destination
|
||||
);
|
||||
|
||||
// rename
|
||||
const newProject: ProjectConfiguration = JSON.parse(newProjectString);
|
||||
|
||||
// Delete the original project
|
||||
removeProjectConfiguration(tree, schema.projectName);
|
||||
|
||||
// Create a new project with the root replaced
|
||||
addProjectConfiguration(
|
||||
tree,
|
||||
getNewProjectName(schema.destination),
|
||||
newProject
|
||||
);
|
||||
}
|
||||
@ -1,14 +1,21 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
|
||||
import { runSchematic } from '../../../utils/testing';
|
||||
import {
|
||||
ProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { Schema } from '../schema';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
import { moveProject } from '@nrwl/workspace/src/schematics/move/lib/move-project';
|
||||
|
||||
describe('moveProject Rule', () => {
|
||||
describe('moveProject', () => {
|
||||
let tree: Tree;
|
||||
let projectConfig: ProjectConfiguration;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = createEmptyWorkspace(Tree.empty());
|
||||
tree = await runSchematic('lib', { name: 'my-lib' }, tree);
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
await libraryGenerator(tree, { name: 'my-lib' });
|
||||
projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
});
|
||||
|
||||
it('should copy all files and delete the source folder', async () => {
|
||||
@ -19,22 +26,12 @@ describe('moveProject Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
// TODO - Currently this test will fail due to
|
||||
// https://github.com/angular/angular-cli/issues/16527
|
||||
// host = await callRule(moveProject(schema), host);
|
||||
moveProject(tree, schema, projectConfig);
|
||||
|
||||
// const destinationDir = host.getDir('libs/my-destination');
|
||||
// let filesFound = false;
|
||||
// destinationDir.visit(_file => {
|
||||
// filesFound = true;
|
||||
// });
|
||||
// expect(filesFound).toBeTruthy();
|
||||
const destinationChildren = tree.children('libs/my-destination');
|
||||
expect(destinationChildren.length).toBeGreaterThan(0);
|
||||
|
||||
// const sourceDir = host.getDir('libs/my-lib');
|
||||
// filesFound = false;
|
||||
// sourceDir.visit(_file => {
|
||||
// filesFound = true;
|
||||
// });
|
||||
// expect(filesFound).toBeFalsy();
|
||||
expect(tree.exists('libs/my-lib')).toBeFalsy();
|
||||
expect(tree.children('libs')).not.toContain('my-lib');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { SchematicContext, Tree } from '@angular-devkit/schematics';
|
||||
import { SchematicContext } from '@angular-devkit/schematics';
|
||||
import { ProjectConfiguration, Tree, visitNotIgnoredFiles } from '@nrwl/devkit';
|
||||
import { getWorkspace } from '@nrwl/workspace';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
@ -10,23 +11,18 @@ import { getDestination } from './utils';
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function moveProject(schema: Schema) {
|
||||
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
|
||||
return from(getWorkspace(tree)).pipe(
|
||||
map((workspace) => {
|
||||
const project = workspace.projects.get(schema.projectName);
|
||||
export function moveProject(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
const destination = getDestination(tree, schema, project);
|
||||
visitNotIgnoredFiles(tree, project.root, (file) => {
|
||||
// This is a rename but Angular Devkit isn't capable of writing to a file after it's renamed so this is a workaround
|
||||
const content = tree.read(file);
|
||||
tree.write(file.replace(project.root, destination), content);
|
||||
tree.delete(file);
|
||||
});
|
||||
|
||||
const destination = getDestination(schema, workspace, tree);
|
||||
const dir = tree.getDir(project.root);
|
||||
dir.visit((file) => {
|
||||
const newPath = file.replace(project.root, destination);
|
||||
tree.create(newPath, tree.read(file));
|
||||
});
|
||||
|
||||
tree.delete(project.root);
|
||||
|
||||
return tree;
|
||||
})
|
||||
);
|
||||
};
|
||||
tree.delete(project.root);
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
import { Schema } from '../schema';
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
|
||||
import { updateBuildTargets } from './update-build-targets';
|
||||
|
||||
describe('updateBuildTargets', () => {
|
||||
let tree: Tree;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(async () => {
|
||||
schema = {
|
||||
projectName: 'my-source',
|
||||
destination: 'subfolder/my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'my-source', {
|
||||
root: 'libs/my-source',
|
||||
targets: {},
|
||||
});
|
||||
addProjectConfiguration(tree, 'my-source-e2e', {
|
||||
root: 'libs/my-source',
|
||||
targets: {
|
||||
e2e: {
|
||||
executor: 'test-executor:hi',
|
||||
options: {
|
||||
devServerTarget: 'my-source:serve',
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
devServerTarget: 'my-source:serve:production',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should update build targets', async () => {
|
||||
updateBuildTargets(tree, schema);
|
||||
|
||||
const e2eProject = readProjectConfiguration(tree, 'my-source-e2e');
|
||||
expect(e2eProject).toBeDefined();
|
||||
expect(e2eProject.targets.e2e.options.devServerTarget).toBe(
|
||||
'subfolder-my-destination:serve'
|
||||
);
|
||||
expect(
|
||||
e2eProject.targets.e2e.configurations.production.devServerTarget
|
||||
).toBe('subfolder-my-destination:serve:production');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,21 @@
|
||||
import { getWorkspacePath, Tree, updateJson } from '@nrwl/devkit';
|
||||
import { Schema } from '../schema';
|
||||
import { getNewProjectName } from './utils';
|
||||
|
||||
/**
|
||||
* Update other references to the source project's targets
|
||||
*/
|
||||
export function updateBuildTargets(tree: Tree, schema: Schema) {
|
||||
const newProjectName = getNewProjectName(schema.destination);
|
||||
|
||||
updateJson(tree, getWorkspacePath(tree), (json) => {
|
||||
const strWorkspace = JSON.stringify(json);
|
||||
json = JSON.parse(
|
||||
strWorkspace.replace(
|
||||
new RegExp(`${schema.projectName}:`, 'g'),
|
||||
`${newProjectName}:`
|
||||
)
|
||||
);
|
||||
return json;
|
||||
});
|
||||
}
|
||||
@ -1,34 +1,37 @@
|
||||
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 {
|
||||
ProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
readJson,
|
||||
writeJson,
|
||||
} from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { Schema } from '../schema';
|
||||
import { updateCypressJson } from './update-cypress-json';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
|
||||
describe('updateCypressJson Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
describe('updateCypressJson', () => {
|
||||
let tree: Tree;
|
||||
let schema: Schema;
|
||||
let projectConfig: ProjectConfiguration;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
});
|
||||
|
||||
it('should handle cypress.json not existing', async () => {
|
||||
tree = await runSchematic('lib', { name: 'my-lib' }, tree);
|
||||
|
||||
expect(tree.files).not.toContain('/libs/my-destination/cypress.json');
|
||||
|
||||
const schema: Schema = {
|
||||
schema = {
|
||||
projectName: 'my-lib',
|
||||
destination: 'my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
await expect(
|
||||
callRule(updateCypressJson(schema), tree)
|
||||
).resolves.not.toThrow();
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
await libraryGenerator(tree, { name: 'my-lib' });
|
||||
projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
});
|
||||
|
||||
it('should handle cypress.json not existing', async () => {
|
||||
expect(() => {
|
||||
updateCypressJson(tree, schema, projectConfig);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should update the videos and screenshots folders', async () => {
|
||||
@ -44,24 +47,11 @@ describe('updateCypressJson Rule', () => {
|
||||
chromeWebSecurity: false,
|
||||
};
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-lib' }, tree);
|
||||
tree.create(
|
||||
'/libs/my-destination/cypress.json',
|
||||
JSON.stringify(cypressJson)
|
||||
);
|
||||
writeJson(tree, '/libs/my-destination/cypress.json', cypressJson);
|
||||
|
||||
expect(tree.files).toContain('/libs/my-destination/cypress.json');
|
||||
updateCypressJson(tree, schema, projectConfig);
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-lib',
|
||||
destination: 'my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(updateCypressJson(schema), tree)) as UnitTestTree;
|
||||
|
||||
expect(readJsonInTree(tree, '/libs/my-destination/cypress.json')).toEqual({
|
||||
expect(readJson(tree, '/libs/my-destination/cypress.json')).toEqual({
|
||||
...cypressJson,
|
||||
videosFolder: '../../dist/cypress/libs/my-destination/videos',
|
||||
screenshotsFolder: '../../dist/cypress/libs/my-destination/screenshots',
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
import { Rule, SchematicContext } from '@angular-devkit/schematics';
|
||||
import { Tree } from '@angular-devkit/schematics/src/tree/interface';
|
||||
import { getWorkspace } from '@nrwl/workspace';
|
||||
import { Tree } from '@nrwl/devkit';
|
||||
import * as path from 'path';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Schema } from '../schema';
|
||||
import { getDestination } from './utils';
|
||||
import { ProjectConfiguration } from '@nrwl/tao/src/shared/workspace';
|
||||
|
||||
interface PartialCypressJson {
|
||||
videosFolder: string;
|
||||
@ -19,36 +16,31 @@ interface PartialCypressJson {
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updateCypressJson(schema: Schema): Rule {
|
||||
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
|
||||
return from(getWorkspace(tree)).pipe(
|
||||
map((workspace) => {
|
||||
const project = workspace.projects.get(schema.projectName);
|
||||
const destination = getDestination(schema, workspace, tree);
|
||||
export function updateCypressJson(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
const destination = getDestination(tree, schema, project);
|
||||
|
||||
const cypressJsonPath = path.join(destination, 'cypress.json');
|
||||
const cypressJsonPath = path.join(destination, 'cypress.json');
|
||||
|
||||
if (!tree.exists(cypressJsonPath)) {
|
||||
// nothing to do
|
||||
return tree;
|
||||
}
|
||||
if (!tree.exists(cypressJsonPath)) {
|
||||
// nothing to do
|
||||
return tree;
|
||||
}
|
||||
|
||||
const cypressJson = JSON.parse(
|
||||
tree.read(cypressJsonPath).toString('utf-8')
|
||||
) as PartialCypressJson;
|
||||
cypressJson.videosFolder = cypressJson.videosFolder.replace(
|
||||
project.root,
|
||||
destination
|
||||
);
|
||||
cypressJson.screenshotsFolder = cypressJson.screenshotsFolder.replace(
|
||||
project.root,
|
||||
destination
|
||||
);
|
||||
const cypressJson = JSON.parse(
|
||||
tree.read(cypressJsonPath).toString('utf-8')
|
||||
) as PartialCypressJson;
|
||||
cypressJson.videosFolder = cypressJson.videosFolder.replace(
|
||||
project.root,
|
||||
destination
|
||||
);
|
||||
cypressJson.screenshotsFolder = cypressJson.screenshotsFolder.replace(
|
||||
project.root,
|
||||
destination
|
||||
);
|
||||
|
||||
tree.overwrite(cypressJsonPath, JSON.stringify(cypressJson));
|
||||
|
||||
return tree;
|
||||
})
|
||||
);
|
||||
};
|
||||
tree.write(cypressJsonPath, JSON.stringify(cypressJson));
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
import { Schema } from '@nrwl/workspace/src/schematics/move/schema';
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readWorkspaceConfiguration,
|
||||
Tree,
|
||||
updateWorkspaceConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { updateDefaultProject } from '@nrwl/workspace/src/schematics/move/lib/update-default-project';
|
||||
|
||||
describe('updateDefaultProject', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
addProjectConfiguration(tree, 'my-source', {
|
||||
root: 'libs/my-source',
|
||||
targets: {},
|
||||
});
|
||||
|
||||
const workspace = readWorkspaceConfiguration(tree);
|
||||
|
||||
updateWorkspaceConfiguration(tree, {
|
||||
...workspace,
|
||||
defaultProject: 'my-source',
|
||||
});
|
||||
});
|
||||
|
||||
it('should update the default project', async () => {
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
destination: 'subfolder/my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
updateDefaultProject(tree, schema);
|
||||
|
||||
const { defaultProject } = readWorkspaceConfiguration(tree);
|
||||
|
||||
expect(defaultProject).toBe('subfolder-my-destination');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
import {
|
||||
readWorkspaceConfiguration,
|
||||
Tree,
|
||||
updateWorkspaceConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { Schema } from '../schema';
|
||||
import { getNewProjectName } from './utils';
|
||||
|
||||
/**
|
||||
* Updates the project in the workspace file
|
||||
*
|
||||
* - update all references to the old root path
|
||||
* - change the project name
|
||||
* - change target references
|
||||
*/
|
||||
export function updateDefaultProject(tree: Tree, schema: Schema) {
|
||||
const workspaceConfiguration = readWorkspaceConfiguration(tree);
|
||||
|
||||
// update default project (if necessary)
|
||||
if (
|
||||
workspaceConfiguration.defaultProject &&
|
||||
workspaceConfiguration.defaultProject === schema.projectName
|
||||
) {
|
||||
workspaceConfiguration.defaultProject = getNewProjectName(
|
||||
schema.destination
|
||||
);
|
||||
updateWorkspaceConfiguration(tree, workspaceConfiguration);
|
||||
}
|
||||
}
|
||||
@ -1,109 +1,62 @@
|
||||
import { UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import {
|
||||
callRule,
|
||||
createEmptyWorkspace,
|
||||
runSchematic,
|
||||
} from '@nrwl/workspace/testing';
|
||||
import { Schema } from '@nrwl/workspace/src/schematics/move/schema';
|
||||
import { Linter, readJsonInTree } from '@nrwl/workspace';
|
||||
import { updateEslintrcJson } from '@nrwl/workspace/src/schematics/move/lib/update-eslintrc-json';
|
||||
import { readJson, readProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
|
||||
describe('updateEslint Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
import { Linter } from '@nrwl/workspace';
|
||||
|
||||
import { Schema } from '../schema';
|
||||
import { updateEslintrcJson } from './update-eslintrc-json';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
|
||||
describe('updateEslint', () => {
|
||||
let tree: Tree;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
schema = {
|
||||
projectName: 'my-lib',
|
||||
destination: 'shared/my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
});
|
||||
|
||||
it('should handle .eslintrc.json not existing', async () => {
|
||||
tree = await runSchematic(
|
||||
'lib',
|
||||
{ name: 'my-lib', linter: Linter.TsLint },
|
||||
tree
|
||||
);
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
linter: Linter.TsLint,
|
||||
});
|
||||
|
||||
expect(tree.files).not.toContain('/libs/my-destination/.estlintrc.json');
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-lib',
|
||||
destination: 'my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
await expect(
|
||||
callRule(updateEslintrcJson(schema), tree)
|
||||
).resolves.not.toThrow();
|
||||
expect(() => {
|
||||
updateEslintrcJson(tree, schema, projectConfig);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should update .eslintrc.json extends path when project is moved to subdirectory', async () => {
|
||||
const eslintRc = {
|
||||
extends: '../../.eslintrc.json',
|
||||
rules: {},
|
||||
ignorePatterns: ['!**/*'],
|
||||
};
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
linter: Linter.EsLint,
|
||||
});
|
||||
|
||||
tree = await runSchematic(
|
||||
'lib',
|
||||
{ name: 'my-lib', linter: Linter.EsLint },
|
||||
tree
|
||||
// This step is usually handled elsewhere
|
||||
tree.rename(
|
||||
'libs/my-lib/.eslintrc.json',
|
||||
'libs/shared/my-destination/.eslintrc.json'
|
||||
);
|
||||
|
||||
tree.create('/libs/core/my-lib/.eslintrc.json', JSON.stringify(eslintRc));
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
expect(tree.files).toContain('/libs/core/my-lib/.eslintrc.json');
|
||||
updateEslintrcJson(tree, schema, projectConfig);
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-lib',
|
||||
destination: 'core/my-lib',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(updateEslintrcJson(schema), tree)) as UnitTestTree;
|
||||
|
||||
expect(readJsonInTree(tree, '/libs/core/my-lib/.eslintrc.json')).toEqual(
|
||||
expect(
|
||||
readJson(tree, '/libs/shared/my-destination/.eslintrc.json')
|
||||
).toEqual(
|
||||
jasmine.objectContaining({
|
||||
extends: '../../../.eslintrc.json',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should update .eslintrc.json extends path when is renamed', async () => {
|
||||
const eslintRc = {
|
||||
extends: '../../.eslintrc.json',
|
||||
rules: {},
|
||||
ignorePatterns: ['!**/*'],
|
||||
};
|
||||
|
||||
tree = await runSchematic(
|
||||
'lib',
|
||||
{ name: 'my-lib', linter: Linter.EsLint },
|
||||
tree
|
||||
);
|
||||
|
||||
tree.create(
|
||||
'/libs/my-destination/.eslintrc.json',
|
||||
JSON.stringify(eslintRc)
|
||||
);
|
||||
|
||||
expect(tree.files).toContain('/libs/my-destination/.eslintrc.json');
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-lib',
|
||||
destination: 'my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(updateEslintrcJson(schema), tree)) as UnitTestTree;
|
||||
|
||||
expect(readJsonInTree(tree, '/libs/my-destination/.eslintrc.json')).toEqual(
|
||||
jasmine.objectContaining({
|
||||
extends: '../../.eslintrc.json',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
|
||||
import { Schema } from '../schema';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { getWorkspace } from '@nrwl/workspace';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { join } from 'path';
|
||||
import { offsetFromRoot } from '@nrwl/devkit';
|
||||
import { getDestination } from '@nrwl/workspace/src/schematics/move/lib/utils';
|
||||
import {
|
||||
offsetFromRoot,
|
||||
ProjectConfiguration,
|
||||
readJson,
|
||||
Tree,
|
||||
updateJson,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
import { Schema } from '../schema';
|
||||
import { getDestination } from './utils';
|
||||
|
||||
interface PartialEsLintRcJson {
|
||||
extends: string;
|
||||
@ -16,30 +19,24 @@ interface PartialEsLintRcJson {
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updateEslintrcJson(schema: Schema): Rule {
|
||||
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
|
||||
return from(getWorkspace(tree)).pipe(
|
||||
map((workspace) => {
|
||||
const destination = getDestination(schema, workspace, tree);
|
||||
const eslintRcPath = join(destination, '.eslintrc.json');
|
||||
export function updateEslintrcJson(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
const destination = getDestination(tree, schema, project);
|
||||
const eslintRcPath = join(destination, '.eslintrc.json');
|
||||
|
||||
if (!tree.exists(eslintRcPath)) {
|
||||
// no .eslintrc found. nothing to do
|
||||
return tree;
|
||||
}
|
||||
if (!tree.exists(eslintRcPath)) {
|
||||
// no .eslintrc found. nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
const eslintRcJson = JSON.parse(
|
||||
tree.read(eslintRcPath).toString('utf-8')
|
||||
) as PartialEsLintRcJson;
|
||||
const offset = offsetFromRoot(destination);
|
||||
|
||||
const offset = offsetFromRoot(destination);
|
||||
updateJson<PartialEsLintRcJson>(tree, eslintRcPath, (eslintRcJson) => {
|
||||
eslintRcJson.extends = offset + '.eslintrc.json';
|
||||
|
||||
eslintRcJson.extends = offset + '.eslintrc.json';
|
||||
|
||||
tree.overwrite(eslintRcPath, JSON.stringify(eslintRcJson));
|
||||
|
||||
return tree;
|
||||
})
|
||||
);
|
||||
};
|
||||
return eslintRcJson;
|
||||
});
|
||||
}
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
|
||||
import { Schema } from '../schema';
|
||||
import { updateImplicitDependencies } from './update-implicit-dependencies';
|
||||
|
||||
describe('updateImplicitDepenencies', () => {
|
||||
let tree: Tree;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(async () => {
|
||||
schema = {
|
||||
projectName: 'my-lib',
|
||||
destination: 'my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
addProjectConfiguration(tree, 'my-lib', {
|
||||
root: 'libs/my-lib',
|
||||
targets: {},
|
||||
});
|
||||
addProjectConfiguration(tree, 'my-other-lib', {
|
||||
root: 'libs/my-other-lib',
|
||||
targets: {},
|
||||
implicitDependencies: ['my-lib'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should update implicit dependencies onto the moved project', () => {
|
||||
updateImplicitDependencies(tree, schema);
|
||||
|
||||
const { implicitDependencies } = readProjectConfiguration(
|
||||
tree,
|
||||
'my-other-lib'
|
||||
);
|
||||
expect(implicitDependencies).toEqual(['my-destination']);
|
||||
});
|
||||
});
|
||||
@ -1,4 +1,6 @@
|
||||
import { NxJson, updateJsonInTree } from '@nrwl/workspace';
|
||||
import { Tree, updateJson } from '@nrwl/devkit';
|
||||
|
||||
import { NxJson } from '../../../core/shared-interfaces';
|
||||
import { Schema } from '../schema';
|
||||
import { getNewProjectName } from './utils';
|
||||
|
||||
@ -7,8 +9,8 @@ import { getNewProjectName } from './utils';
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updateNxJson(schema: Schema) {
|
||||
return updateJsonInTree<NxJson>('nx.json', (json) => {
|
||||
export function updateImplicitDependencies(tree: Tree, schema: Schema) {
|
||||
updateJson<NxJson>(tree, 'nx.json', (json) => {
|
||||
Object.values(json.projects).forEach((project) => {
|
||||
if (project.implicitDependencies) {
|
||||
const index = project.implicitDependencies.indexOf(schema.projectName);
|
||||
@ -19,10 +21,6 @@ export function updateNxJson(schema: Schema) {
|
||||
}
|
||||
}
|
||||
});
|
||||
json.projects[getNewProjectName(schema.destination)] = {
|
||||
...json.projects[schema.projectName],
|
||||
};
|
||||
delete json.projects[schema.projectName];
|
||||
return json;
|
||||
});
|
||||
}
|
||||
@ -1,18 +1,16 @@
|
||||
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 { readProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { callRule, runSchematic } from '../../../utils/testing';
|
||||
import { Schema } from '../schema';
|
||||
import { updateImports } from './update-imports';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
|
||||
describe('updateImports Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
describe('updateImports', () => {
|
||||
let tree: Tree;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
schema = {
|
||||
projectName: 'my-source',
|
||||
@ -26,25 +24,24 @@ describe('updateImports Rule', () => {
|
||||
// this is a bit of a cheat - we expect to run this rule on an intermediate state
|
||||
// tree where the workspace hasn't been updated yet, so just create libs representing
|
||||
// source and destination to make sure that the workspace has libraries with those names.
|
||||
tree = await runSchematic('lib', { name: 'my-destination' }, tree);
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
await libraryGenerator(tree, { name: 'my-destination' });
|
||||
await libraryGenerator(tree, { name: 'my-source' });
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-importer' }, tree);
|
||||
await libraryGenerator(tree, { name: 'my-importer' });
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
tree.create(
|
||||
tree.write(
|
||||
importerFilePath,
|
||||
`
|
||||
import { MyClass } from '@proj/my-source';
|
||||
|
||||
|
||||
export class MyExtendedClass extends MyClass {};
|
||||
`
|
||||
);
|
||||
|
||||
tree = (await callRule(updateImports(schema), tree)) as UnitTestTree;
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
updateImports(tree, schema, projectConfig);
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`import { MyClass } from '@proj/my-destination';`
|
||||
);
|
||||
expect(tree.read(importerFilePath).toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
/**
|
||||
@ -53,31 +50,33 @@ describe('updateImports Rule', () => {
|
||||
* be updated.
|
||||
*/
|
||||
it('should not update import paths when they contain a partial match', async () => {
|
||||
tree = await runSchematic('lib', { name: 'table' }, tree);
|
||||
tree = await runSchematic('lib', { name: 'tab' }, tree);
|
||||
await libraryGenerator(tree, { name: 'table' });
|
||||
await libraryGenerator(tree, { name: 'tab' });
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-importer' }, tree);
|
||||
await libraryGenerator(tree, { name: 'my-importer' });
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
tree.create(
|
||||
tree.write(
|
||||
importerFilePath,
|
||||
`
|
||||
import { Table } from '@proj/table';
|
||||
import { Tab } from '@proj/tab';
|
||||
|
||||
|
||||
export class MyTable extends Table {};
|
||||
export class MyTab extends Tab {};
|
||||
`
|
||||
);
|
||||
|
||||
tree = (await callRule(
|
||||
updateImports({
|
||||
const projectConfig = readProjectConfiguration(tree, 'tab');
|
||||
updateImports(
|
||||
tree,
|
||||
{
|
||||
projectName: 'tab',
|
||||
destination: 'tabs',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
}),
|
||||
tree
|
||||
)) as UnitTestTree;
|
||||
},
|
||||
projectConfig
|
||||
);
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`import { Table } from '@proj/table';`
|
||||
@ -86,34 +85,38 @@ describe('updateImports Rule', () => {
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`import { Tab } from '@proj/tabs';`
|
||||
);
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should correctly update deep imports', async () => {
|
||||
tree = await runSchematic('lib', { name: 'table' }, tree);
|
||||
tree = await runSchematic('lib', { name: 'tab' }, tree);
|
||||
await libraryGenerator(tree, { name: 'table' });
|
||||
await libraryGenerator(tree, { name: 'tab' });
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-importer' }, tree);
|
||||
await libraryGenerator(tree, { name: 'my-importer' });
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
tree.create(
|
||||
tree.write(
|
||||
importerFilePath,
|
||||
`
|
||||
import { Table } from '@proj/table/components';
|
||||
import { Tab } from '@proj/tab/components';
|
||||
|
||||
|
||||
export class MyTable extends Table {};
|
||||
export class MyTab extends Tab {};
|
||||
`
|
||||
);
|
||||
|
||||
tree = (await callRule(
|
||||
updateImports({
|
||||
const projectConfig = readProjectConfiguration(tree, 'tab');
|
||||
updateImports(
|
||||
tree,
|
||||
{
|
||||
projectName: 'tab',
|
||||
destination: 'tabs',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
}),
|
||||
tree
|
||||
)) as UnitTestTree;
|
||||
},
|
||||
projectConfig
|
||||
);
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`import { Table } from '@proj/table/components';`
|
||||
@ -122,15 +125,17 @@ describe('updateImports Rule', () => {
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`import { Tab } from '@proj/tabs/components';`
|
||||
);
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update dynamic imports', async () => {
|
||||
tree = await runSchematic('lib', { name: 'table' }, tree);
|
||||
tree = await runSchematic('lib', { name: 'tab' }, tree);
|
||||
await libraryGenerator(tree, { name: 'table' });
|
||||
await libraryGenerator(tree, { name: 'tab' });
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-importer' }, tree);
|
||||
await libraryGenerator(tree, { name: 'my-importer' });
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
tree.create(
|
||||
tree.write(
|
||||
importerFilePath,
|
||||
`
|
||||
import('@proj/table').then(m => m.Table);
|
||||
@ -140,15 +145,17 @@ describe('updateImports Rule', () => {
|
||||
`
|
||||
);
|
||||
|
||||
tree = (await callRule(
|
||||
updateImports({
|
||||
const projectConfig = readProjectConfiguration(tree, 'tab');
|
||||
updateImports(
|
||||
tree,
|
||||
{
|
||||
projectName: 'tab',
|
||||
destination: 'tabs',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
}),
|
||||
tree
|
||||
)) as UnitTestTree;
|
||||
},
|
||||
projectConfig
|
||||
);
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`import('@proj/table').then(m => m.Table);`
|
||||
@ -165,133 +172,135 @@ describe('updateImports Rule', () => {
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`import('@proj/tabs/components').then(m => m.Tab);`
|
||||
);
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update require imports', async () => {
|
||||
tree = await runSchematic('lib', { name: 'table' }, tree);
|
||||
tree = await runSchematic('lib', { name: 'tab' }, tree);
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-importer' }, tree);
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
tree.create(
|
||||
importerFilePath,
|
||||
`
|
||||
require('@proj/table');
|
||||
require('@proj/table/components');
|
||||
require('@proj/tab');
|
||||
require('@proj/tab/components');
|
||||
`
|
||||
);
|
||||
|
||||
tree = (await callRule(
|
||||
updateImports({
|
||||
projectName: 'tab',
|
||||
destination: 'tabs',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
}),
|
||||
tree
|
||||
)) as UnitTestTree;
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`require('@proj/table');`
|
||||
);
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`require('@proj/table/components');`
|
||||
);
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`require('@proj/tabs');`
|
||||
);
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`require('@proj/tabs/components');`
|
||||
);
|
||||
});
|
||||
|
||||
it('should not update project refs when --updateImportPath=false', async () => {
|
||||
// this is a bit of a cheat - we expect to run this rule on an intermediate state
|
||||
// tree where the workspace hasn't been updated yet, so just create libs representing
|
||||
// source and destination to make sure that the workspace has libraries with those names.
|
||||
tree = await runSchematic('lib', { name: 'my-destination' }, tree);
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-importer' }, tree);
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
tree.create(
|
||||
importerFilePath,
|
||||
`
|
||||
import { MyClass } from '@proj/my-source';
|
||||
|
||||
export MyExtendedClass extends MyClass {};
|
||||
`
|
||||
);
|
||||
|
||||
schema.updateImportPath = false;
|
||||
tree = (await callRule(updateImports(schema), tree)) as UnitTestTree;
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`import { MyClass } from '@proj/my-source';`
|
||||
);
|
||||
});
|
||||
|
||||
it('should update project refs to --importPath when provided', async () => {
|
||||
// this is a bit of a cheat - we expect to run this rule on an intermediate state
|
||||
// tree where the workspace hasn't been updated yet, so just create libs representing
|
||||
// source and destination to make sure that the workspace has libraries with those names.
|
||||
tree = await runSchematic('lib', { name: 'my-destination' }, tree);
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-importer' }, tree);
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
tree.create(
|
||||
importerFilePath,
|
||||
`
|
||||
import { MyClass } from '@proj/my-source';
|
||||
|
||||
export class MyExtendedClass extends MyClass {};
|
||||
`
|
||||
);
|
||||
|
||||
schema.importPath = '@proj/wibble';
|
||||
tree = (await callRule(updateImports(schema), tree)) as UnitTestTree;
|
||||
|
||||
expect(tree.read(importerFilePath).toString()).toContain(
|
||||
`import { MyClass } from '${schema.importPath}';`
|
||||
);
|
||||
});
|
||||
|
||||
it('should update project ref in the tsconfig file', async () => {
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
|
||||
let tsConfig = readJsonInTree(tree, '/tsconfig.base.json');
|
||||
expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
'@proj/my-source': ['libs/my-source/src/index.ts'],
|
||||
});
|
||||
|
||||
tree = (await callRule(updateImports(schema), tree)) as UnitTestTree;
|
||||
|
||||
tsConfig = readJsonInTree(tree, '/tsconfig.base.json');
|
||||
expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
'@proj/my-destination': ['libs/my-destination/src/index.ts'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should only update the project ref paths in the tsconfig file when --updateImportPath=false', async () => {
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
|
||||
let tsConfig = readJsonInTree(tree, '/tsconfig.base.json');
|
||||
expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
'@proj/my-source': ['libs/my-source/src/index.ts'],
|
||||
});
|
||||
|
||||
schema.updateImportPath = false;
|
||||
tree = (await callRule(updateImports(schema), tree)) as UnitTestTree;
|
||||
|
||||
tsConfig = readJsonInTree(tree, '/tsconfig.base.json');
|
||||
expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
'@proj/my-source': ['libs/my-destination/src/index.ts'],
|
||||
});
|
||||
});
|
||||
//
|
||||
// it('should update require imports', async () => {
|
||||
// tree = await runSchematic('lib', { name: 'table' }, tree);
|
||||
// tree = await runSchematic('lib', { name: 'tab' }, tree);
|
||||
//
|
||||
// tree = await runSchematic('lib', { name: 'my-importer' }, tree);
|
||||
// const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
// tree.create(
|
||||
// importerFilePath,
|
||||
// `
|
||||
// require('@proj/table');
|
||||
// require('@proj/table/components');
|
||||
// require('@proj/tab');
|
||||
// require('@proj/tab/components');
|
||||
// `
|
||||
// );
|
||||
//
|
||||
// tree = (await callRule(
|
||||
// updateImports({
|
||||
// projectName: 'tab',
|
||||
// destination: 'tabs',
|
||||
// importPath: undefined,
|
||||
// updateImportPath: true,
|
||||
// }),
|
||||
// tree
|
||||
// )) as UnitTestTree;
|
||||
//
|
||||
// expect(tree.read(importerFilePath).toString()).toContain(
|
||||
// `require('@proj/table');`
|
||||
// );
|
||||
//
|
||||
// expect(tree.read(importerFilePath).toString()).toContain(
|
||||
// `require('@proj/table/components');`
|
||||
// );
|
||||
//
|
||||
// expect(tree.read(importerFilePath).toString()).toContain(
|
||||
// `require('@proj/tabs');`
|
||||
// );
|
||||
//
|
||||
// expect(tree.read(importerFilePath).toString()).toContain(
|
||||
// `require('@proj/tabs/components');`
|
||||
// );
|
||||
// });
|
||||
//
|
||||
// it('should not update project refs when --updateImportPath=false', async () => {
|
||||
// // this is a bit of a cheat - we expect to run this rule on an intermediate state
|
||||
// // tree where the workspace hasn't been updated yet, so just create libs representing
|
||||
// // source and destination to make sure that the workspace has libraries with those names.
|
||||
// tree = await runSchematic('lib', { name: 'my-destination' }, tree);
|
||||
// tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
//
|
||||
// tree = await runSchematic('lib', { name: 'my-importer' }, tree);
|
||||
// const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
// tree.create(
|
||||
// importerFilePath,
|
||||
// `
|
||||
// import { MyClass } from '@proj/my-source';
|
||||
//
|
||||
// export MyExtendedClass extends MyClass {};
|
||||
// `
|
||||
// );
|
||||
//
|
||||
// schema.updateImportPath = false;
|
||||
// tree = (await callRule(updateImports(schema), tree)) as UnitTestTree;
|
||||
//
|
||||
// expect(tree.read(importerFilePath).toString()).toContain(
|
||||
// `import { MyClass } from '@proj/my-source';`
|
||||
// );
|
||||
// });
|
||||
//
|
||||
// it('should update project refs to --importPath when provided', async () => {
|
||||
// // this is a bit of a cheat - we expect to run this rule on an intermediate state
|
||||
// // tree where the workspace hasn't been updated yet, so just create libs representing
|
||||
// // source and destination to make sure that the workspace has libraries with those names.
|
||||
// tree = await runSchematic('lib', { name: 'my-destination' }, tree);
|
||||
// tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
//
|
||||
// tree = await runSchematic('lib', { name: 'my-importer' }, tree);
|
||||
// const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
// tree.create(
|
||||
// importerFilePath,
|
||||
// `
|
||||
// import { MyClass } from '@proj/my-source';
|
||||
//
|
||||
// export class MyExtendedClass extends MyClass {};
|
||||
// `
|
||||
// );
|
||||
//
|
||||
// schema.importPath = '@proj/wibble';
|
||||
// tree = (await callRule(updateImports(schema), tree)) as UnitTestTree;
|
||||
//
|
||||
// expect(tree.read(importerFilePath).toString()).toContain(
|
||||
// `import { MyClass } from '${schema.importPath}';`
|
||||
// );
|
||||
// });
|
||||
//
|
||||
// it('should update project ref in the tsconfig file', async () => {
|
||||
// tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
//
|
||||
// let tsConfig = readJsonInTree(tree, '/tsconfig.base.json');
|
||||
// expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
// '@proj/my-source': ['libs/my-source/src/index.ts'],
|
||||
// });
|
||||
//
|
||||
// tree = (await callRule(updateImports(schema), tree)) as UnitTestTree;
|
||||
//
|
||||
// tsConfig = readJsonInTree(tree, '/tsconfig.base.json');
|
||||
// expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
// '@proj/my-destination': ['libs/my-destination/src/index.ts'],
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should only update the project ref paths in the tsconfig file when --updateImportPath=false', async () => {
|
||||
// tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
//
|
||||
// let tsConfig = readJsonInTree(tree, '/tsconfig.base.json');
|
||||
// expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
// '@proj/my-source': ['libs/my-source/src/index.ts'],
|
||||
// });
|
||||
//
|
||||
// schema.updateImportPath = false;
|
||||
// tree = (await callRule(updateImports(schema), tree)) as UnitTestTree;
|
||||
//
|
||||
// tsConfig = readJsonInTree(tree, '/tsconfig.base.json');
|
||||
// expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
// '@proj/my-source': ['libs/my-destination/src/index.ts'],
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
import { findNodes, serializeJson } from '@nrwl/workspace';
|
||||
import {
|
||||
SchematicContext,
|
||||
applyChangesToString,
|
||||
ChangeType,
|
||||
getProjects,
|
||||
getWorkspaceLayout,
|
||||
ProjectConfiguration,
|
||||
StringChange,
|
||||
Tree,
|
||||
UpdateRecorder,
|
||||
} from '@angular-devkit/schematics';
|
||||
import {
|
||||
findNodes,
|
||||
getWorkspace,
|
||||
NxJson,
|
||||
readJsonInTree,
|
||||
serializeJson,
|
||||
} from '@nrwl/workspace';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
visitNotIgnoredFiles,
|
||||
} from '@nrwl/devkit';
|
||||
import * as ts from 'typescript';
|
||||
import { Schema } from '../schema';
|
||||
import { normalizeSlashes } from './utils';
|
||||
@ -21,116 +18,98 @@ import { normalizeSlashes } from './utils';
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updateImports(schema: Schema) {
|
||||
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
|
||||
return from(getWorkspace(tree)).pipe(
|
||||
map((workspace) => {
|
||||
const nxJson = readJsonInTree<NxJson>(tree, 'nx.json');
|
||||
const libsDir = nxJson.workspaceLayout?.libsDir
|
||||
? nxJson.workspaceLayout.libsDir
|
||||
: 'libs';
|
||||
const project = workspace.projects.get(schema.projectName);
|
||||
export function updateImports(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
if (project.projectType === 'application') {
|
||||
// These shouldn't be imported anywhere?
|
||||
return;
|
||||
}
|
||||
|
||||
if (project.extensions['projectType'] === 'application') {
|
||||
// These shouldn't be imported anywhere?
|
||||
return tree;
|
||||
}
|
||||
const { npmScope, libsDir } = getWorkspaceLayout(tree);
|
||||
const projects = getProjects(tree);
|
||||
|
||||
// use the source root to find the from location
|
||||
// this attempts to account for libs that have been created with --importPath
|
||||
const tsConfigPath = 'tsconfig.base.json';
|
||||
let tsConfig: any;
|
||||
let fromPath: string;
|
||||
if (tree.exists(tsConfigPath)) {
|
||||
tsConfig = JSON.parse(tree.read(tsConfigPath).toString('utf-8'));
|
||||
fromPath = Object.keys(tsConfig.compilerOptions.paths).find((path) =>
|
||||
tsConfig.compilerOptions.paths[path].some((x) =>
|
||||
x.startsWith(project.sourceRoot)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const projectRef = {
|
||||
from:
|
||||
fromPath ||
|
||||
normalizeSlashes(
|
||||
`@${nxJson.npmScope}/${project.root.substr(libsDir.length + 1)}`
|
||||
),
|
||||
to:
|
||||
schema.importPath ||
|
||||
normalizeSlashes(`@${nxJson.npmScope}/${schema.destination}`),
|
||||
};
|
||||
|
||||
if (schema.updateImportPath) {
|
||||
const replaceProjectRef = new RegExp(projectRef.from, 'g');
|
||||
|
||||
for (const [name, definition] of workspace.projects.entries()) {
|
||||
if (name === schema.projectName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const projectDir = tree.getDir(definition.root);
|
||||
projectDir.visit((file) => {
|
||||
const contents = tree.read(file).toString('utf-8');
|
||||
if (!replaceProjectRef.test(contents)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateImportPaths(
|
||||
tree,
|
||||
file,
|
||||
contents,
|
||||
projectRef.from,
|
||||
projectRef.to
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const projectRoot = {
|
||||
from: project.root.substr(libsDir.length + 1),
|
||||
to: schema.destination,
|
||||
};
|
||||
|
||||
if (tsConfig) {
|
||||
const path = tsConfig.compilerOptions.paths[
|
||||
projectRef.from
|
||||
] as string[];
|
||||
if (!path) {
|
||||
throw new Error(
|
||||
[
|
||||
`unable to find "${projectRef.from}" in`,
|
||||
`${tsConfigPath} compilerOptions.paths`,
|
||||
].join(' ')
|
||||
);
|
||||
}
|
||||
const updatedPath = path.map((x) =>
|
||||
x.replace(new RegExp(projectRoot.from, 'g'), projectRoot.to)
|
||||
);
|
||||
|
||||
if (schema.updateImportPath) {
|
||||
tsConfig.compilerOptions.paths[projectRef.to] = updatedPath;
|
||||
delete tsConfig.compilerOptions.paths[projectRef.from];
|
||||
} else {
|
||||
tsConfig.compilerOptions.paths[projectRef.from] = updatedPath;
|
||||
}
|
||||
|
||||
tree.overwrite(tsConfigPath, serializeJson(tsConfig));
|
||||
}
|
||||
|
||||
return tree;
|
||||
})
|
||||
// use the source root to find the from location
|
||||
// this attempts to account for libs that have been created with --importPath
|
||||
const tsConfigPath = 'tsconfig.base.json';
|
||||
let tsConfig: any;
|
||||
let fromPath: string;
|
||||
if (tree.exists(tsConfigPath)) {
|
||||
tsConfig = JSON.parse(tree.read(tsConfigPath).toString('utf-8'));
|
||||
fromPath = Object.keys(tsConfig.compilerOptions.paths).find((path) =>
|
||||
tsConfig.compilerOptions.paths[path].some((x) =>
|
||||
x.startsWith(project.sourceRoot)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const projectRef = {
|
||||
from:
|
||||
fromPath ||
|
||||
normalizeSlashes(
|
||||
`@${npmScope}/${project.root.substr(libsDir.length + 1)}`
|
||||
),
|
||||
to:
|
||||
schema.importPath ||
|
||||
normalizeSlashes(`@${npmScope}/${schema.destination}`),
|
||||
};
|
||||
|
||||
if (schema.updateImportPath) {
|
||||
const replaceProjectRef = new RegExp(projectRef.from, 'g');
|
||||
|
||||
for (const [name, definition] of projects.entries()) {
|
||||
if (name === schema.projectName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
visitNotIgnoredFiles(tree, definition.root, (file) => {
|
||||
const contents = tree.read(file).toString('utf-8');
|
||||
if (!replaceProjectRef.test(contents)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateImportPaths(tree, file, projectRef.from, projectRef.to);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const projectRoot = {
|
||||
from: project.root.substr(libsDir.length + 1),
|
||||
to: schema.destination,
|
||||
};
|
||||
|
||||
if (tsConfig) {
|
||||
const path = tsConfig.compilerOptions.paths[projectRef.from] as string[];
|
||||
if (!path) {
|
||||
throw new Error(
|
||||
[
|
||||
`unable to find "${projectRef.from}" in`,
|
||||
`${tsConfigPath} compilerOptions.paths`,
|
||||
].join(' ')
|
||||
);
|
||||
}
|
||||
const updatedPath = path.map((x) =>
|
||||
x.replace(new RegExp(projectRoot.from, 'g'), projectRoot.to)
|
||||
);
|
||||
|
||||
if (schema.updateImportPath) {
|
||||
tsConfig.compilerOptions.paths[projectRef.to] = updatedPath;
|
||||
delete tsConfig.compilerOptions.paths[projectRef.from];
|
||||
} else {
|
||||
tsConfig.compilerOptions.paths[projectRef.from] = updatedPath;
|
||||
}
|
||||
|
||||
tree.write(tsConfigPath, serializeJson(tsConfig));
|
||||
}
|
||||
}
|
||||
|
||||
function updateImportPaths(
|
||||
tree: Tree,
|
||||
path: string,
|
||||
contents: string,
|
||||
from: string,
|
||||
to: string
|
||||
) {
|
||||
/**
|
||||
* Changes imports in a file from one import to another
|
||||
*/
|
||||
function updateImportPaths(tree: Tree, path: string, from: string, to: string) {
|
||||
const contents = tree.read(path).toString('utf-8');
|
||||
const sourceFile = ts.createSourceFile(
|
||||
path,
|
||||
contents,
|
||||
@ -138,50 +117,54 @@ function updateImportPaths(
|
||||
true
|
||||
);
|
||||
|
||||
const recorder = tree.beginUpdate(path);
|
||||
// Apply changes on the various types of imports
|
||||
const newContents = applyChangesToString(contents, [
|
||||
...updateImportDeclarations(sourceFile, from, to),
|
||||
...updateDynamicImports(sourceFile, from, to),
|
||||
]);
|
||||
|
||||
// perform transformations on the various types of imports
|
||||
updateImportDeclarations(recorder, sourceFile, from, to);
|
||||
updateDynamicImports(recorder, sourceFile, from, to);
|
||||
|
||||
tree.commitUpdate(recorder);
|
||||
tree.write(path, newContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the module specifiers on static imports
|
||||
*/
|
||||
function updateImportDeclarations(
|
||||
recorder: UpdateRecorder,
|
||||
sourceFile: ts.SourceFile,
|
||||
from: string,
|
||||
to: string
|
||||
) {
|
||||
): StringChange[] {
|
||||
const importDecls = findNodes(
|
||||
sourceFile,
|
||||
ts.SyntaxKind.ImportDeclaration
|
||||
) as ts.ImportDeclaration[];
|
||||
|
||||
const changes: StringChange[] = [];
|
||||
|
||||
for (const { moduleSpecifier } of importDecls) {
|
||||
if (ts.isStringLiteral(moduleSpecifier)) {
|
||||
updateModuleSpecifier(recorder, moduleSpecifier, from, to);
|
||||
changes.push(...updateModuleSpecifier(moduleSpecifier, from, to));
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the module specifiers on dynamic imports and require statements
|
||||
*/
|
||||
function updateDynamicImports(
|
||||
recorder: UpdateRecorder,
|
||||
sourceFile: ts.SourceFile,
|
||||
from: string,
|
||||
to: string
|
||||
) {
|
||||
): StringChange[] {
|
||||
const expressions = findNodes(
|
||||
sourceFile,
|
||||
ts.SyntaxKind.CallExpression
|
||||
) as ts.CallExpression[];
|
||||
|
||||
const changes: StringChange[] = [];
|
||||
|
||||
for (const { expression, arguments: args } of expressions) {
|
||||
const moduleSpecifier = args[0];
|
||||
|
||||
@ -191,7 +174,7 @@ function updateDynamicImports(
|
||||
moduleSpecifier &&
|
||||
ts.isStringLiteral(moduleSpecifier)
|
||||
) {
|
||||
updateModuleSpecifier(recorder, moduleSpecifier, from, to);
|
||||
changes.push(...updateModuleSpecifier(moduleSpecifier, from, to));
|
||||
}
|
||||
|
||||
// handle require statements
|
||||
@ -201,33 +184,38 @@ function updateDynamicImports(
|
||||
moduleSpecifier &&
|
||||
ts.isStringLiteral(moduleSpecifier)
|
||||
) {
|
||||
updateModuleSpecifier(recorder, moduleSpecifier, from, to);
|
||||
changes.push(...updateModuleSpecifier(moduleSpecifier, from, to));
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the old module specifier with a the new path
|
||||
*/
|
||||
function updateModuleSpecifier(
|
||||
recorder: UpdateRecorder,
|
||||
moduleSpecifier: ts.StringLiteral,
|
||||
from: string,
|
||||
to: string
|
||||
) {
|
||||
): StringChange[] {
|
||||
if (
|
||||
moduleSpecifier.text === from ||
|
||||
moduleSpecifier.text.startsWith(from + '/')
|
||||
) {
|
||||
recorder.remove(
|
||||
moduleSpecifier.getStart() + 1,
|
||||
moduleSpecifier.text.length
|
||||
);
|
||||
|
||||
// insert the new module specifier
|
||||
recorder.insertLeft(
|
||||
moduleSpecifier.getStart() + 1,
|
||||
moduleSpecifier.text.replace(new RegExp(from, 'g'), to)
|
||||
);
|
||||
return [
|
||||
{
|
||||
type: ChangeType.Delete,
|
||||
start: moduleSpecifier.getStart() + 1,
|
||||
length: moduleSpecifier.text.length,
|
||||
},
|
||||
{
|
||||
type: ChangeType.Insert,
|
||||
index: moduleSpecifier.getStart() + 1,
|
||||
text: moduleSpecifier.text.replace(new RegExp(from, 'g'), to),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
|
||||
import { callRule, runSchematic } from '../../../utils/testing';
|
||||
import { readProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
|
||||
import { Schema } from '../schema';
|
||||
import { updateJestConfig } from './update-jest-config';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
|
||||
describe('updateJestConfig Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
describe('updateJestConfig', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
});
|
||||
|
||||
it('should handle jest config not existing', async () => {
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
@ -23,9 +25,7 @@ describe('updateJestConfig Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
await expect(
|
||||
callRule(updateJestConfig(schema), tree)
|
||||
).resolves.not.toThrow();
|
||||
updateJestConfig(tree, schema, projectConfig);
|
||||
});
|
||||
|
||||
it('should update the name and coverage directory', async () => {
|
||||
@ -42,8 +42,11 @@ describe('updateJestConfig Rule', () => {
|
||||
|
||||
const rootJestConfigPath = '/jest.config.js';
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
tree.create(jestConfigPath, jestConfig);
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
tree.write(jestConfigPath, jestConfig);
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
@ -52,7 +55,7 @@ describe('updateJestConfig Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(updateJestConfig(schema), tree)) as UnitTestTree;
|
||||
updateJestConfig(tree, schema, projectConfig);
|
||||
|
||||
const jestConfigAfter = tree.read(jestConfigPath).toString();
|
||||
const rootJestConfigAfter = tree.read(rootJestConfigPath).toString();
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import { Rule, SchematicContext } from '@angular-devkit/schematics';
|
||||
import { Tree } from '@angular-devkit/schematics/src/tree/interface';
|
||||
import { getWorkspace } from '@nrwl/workspace';
|
||||
import { Tree, ProjectConfiguration, getWorkspaceLayout } from '@nrwl/devkit';
|
||||
|
||||
import * as path from 'path';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { Schema } from '../schema';
|
||||
import { getDestination, getNewProjectName, getWorkspaceLayout } from './utils';
|
||||
import { getDestination, getNewProjectName } from './utils';
|
||||
|
||||
/**
|
||||
* Updates the project name and coverage folder in the jest.config.js if it exists
|
||||
@ -14,57 +12,52 @@ import { getDestination, getNewProjectName, getWorkspaceLayout } from './utils';
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updateJestConfig(schema: Schema): Rule {
|
||||
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
|
||||
return from(getWorkspace(tree)).pipe(
|
||||
map((workspace) => {
|
||||
const project = workspace.projects.get(schema.projectName);
|
||||
const destination = getDestination(schema, workspace, tree);
|
||||
const newProjectName = getNewProjectName(schema.destination);
|
||||
export function updateJestConfig(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
const destination = getDestination(tree, schema, project);
|
||||
const newProjectName = getNewProjectName(schema.destination);
|
||||
|
||||
const jestConfigPath = path.join(destination, 'jest.config.js');
|
||||
const jestConfigPath = path.join(destination, 'jest.config.js');
|
||||
|
||||
if (!tree.exists(jestConfigPath)) {
|
||||
// nothing to do
|
||||
return tree;
|
||||
}
|
||||
if (!tree.exists(jestConfigPath)) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
const oldContent = tree.read(jestConfigPath).toString('utf-8');
|
||||
const oldContent = tree.read(jestConfigPath).toString('utf-8');
|
||||
|
||||
const findName = new RegExp(`'${schema.projectName}'`, 'g');
|
||||
const findDir = new RegExp(project.root, 'g');
|
||||
const findName = new RegExp(`'${schema.projectName}'`, 'g');
|
||||
const findDir = new RegExp(project.root, 'g');
|
||||
|
||||
const newContent = oldContent
|
||||
.replace(findName, `'${newProjectName}'`)
|
||||
.replace(findDir, destination);
|
||||
tree.overwrite(jestConfigPath, newContent);
|
||||
const newContent = oldContent
|
||||
.replace(findName, `'${newProjectName}'`)
|
||||
.replace(findDir, destination);
|
||||
tree.write(jestConfigPath, newContent);
|
||||
|
||||
// update root jest.config.js
|
||||
const rootJestConfigPath = '/jest.config.js';
|
||||
// update root jest.config.js
|
||||
const rootJestConfigPath = '/jest.config.js';
|
||||
|
||||
if (!tree.exists(rootJestConfigPath)) {
|
||||
return tree;
|
||||
}
|
||||
if (!tree.exists(rootJestConfigPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldRootJestConfigContent = tree
|
||||
.read(rootJestConfigPath)
|
||||
.toString('utf-8');
|
||||
const { libsDir, appsDir } = getWorkspaceLayout(tree);
|
||||
const findProject = new RegExp(
|
||||
`<rootDir>\/(${libsDir}|${appsDir})\/${schema.projectName}`,
|
||||
'g'
|
||||
);
|
||||
|
||||
const { libsDir, appsDir } = getWorkspaceLayout(tree);
|
||||
const findProject = new RegExp(
|
||||
`<rootDir>\/(${libsDir}|${appsDir})\/${schema.projectName}`,
|
||||
'g'
|
||||
);
|
||||
const oldRootJestConfigContent = tree
|
||||
.read(rootJestConfigPath)
|
||||
.toString('utf-8');
|
||||
|
||||
const newRootJestConfigContent = oldRootJestConfigContent.replace(
|
||||
findProject,
|
||||
`<rootDir>/${destination}`
|
||||
);
|
||||
const newRootJestConfigContent = oldRootJestConfigContent.replace(
|
||||
findProject,
|
||||
`<rootDir>/${destination}`
|
||||
);
|
||||
|
||||
tree.overwrite(rootJestConfigPath, newRootJestConfigContent);
|
||||
|
||||
return tree;
|
||||
})
|
||||
);
|
||||
};
|
||||
tree.write(rootJestConfigPath, newRootJestConfigContent);
|
||||
}
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
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-source' }, tree);
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
destination: 'my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(updateNxJson(schema), tree)) as UnitTestTree;
|
||||
|
||||
const nxJson = readJsonInTree(tree, '/nx.json');
|
||||
|
||||
expect(nxJson.projects['my-source']).toBeUndefined();
|
||||
expect(nxJson.projects['my-destination']).toEqual({
|
||||
tags: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,16 +1,14 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
|
||||
import { callRule, runSchematic } from '../../../utils/testing';
|
||||
import { readProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { Schema } from '../schema';
|
||||
import { updateProjectRootFiles } from './update-project-root-files';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
|
||||
describe('updateProjectRootFiles Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
describe('updateProjectRootFiles', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
});
|
||||
|
||||
it('should update the relative root in files at the root of the project', async () => {
|
||||
@ -25,8 +23,11 @@ describe('updateProjectRootFiles Rule', () => {
|
||||
};`;
|
||||
const testFilePath = '/libs/subfolder/my-destination/jest.config.js';
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
tree.create(testFilePath, testFile);
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
tree.write(testFilePath, testFile);
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
@ -35,10 +36,7 @@ describe('updateProjectRootFiles Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(
|
||||
updateProjectRootFiles(schema),
|
||||
tree
|
||||
)) as UnitTestTree;
|
||||
updateProjectRootFiles(tree, schema, projectConfig);
|
||||
|
||||
const testFileAfter = tree.read(testFilePath).toString();
|
||||
expect(testFileAfter).toContain(`preset: '../../../jest.config.js'`);
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { Rule, SchematicContext } from '@angular-devkit/schematics';
|
||||
import { Tree } from '@angular-devkit/schematics/src/tree/interface';
|
||||
import { getWorkspace } from '@nrwl/workspace';
|
||||
import { appRootPath } from '@nrwl/workspace/src/utils/app-root';
|
||||
import * as path from 'path';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { ProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
|
||||
import { appRootPath } from '../../../utils/app-root';
|
||||
import { Schema } from '../schema';
|
||||
import { getDestination } from './utils';
|
||||
import { extname, join } from 'path';
|
||||
|
||||
/**
|
||||
* Updates the files in the root of the project
|
||||
@ -15,44 +14,37 @@ import { getDestination } from './utils';
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updateProjectRootFiles(schema: Schema): Rule {
|
||||
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
|
||||
return from(getWorkspace(tree)).pipe(
|
||||
map((workspace) => {
|
||||
const project = workspace.projects.get(schema.projectName);
|
||||
const destination = getDestination(schema, workspace, tree);
|
||||
export function updateProjectRootFiles(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
const destination = getDestination(tree, schema, project);
|
||||
|
||||
const newRelativeRoot = path
|
||||
.relative(path.join(appRootPath, destination), appRootPath)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
const oldRelativeRoot = path
|
||||
.relative(path.join(appRootPath, project.root), appRootPath)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
const newRelativeRoot = path
|
||||
.relative(path.join(appRootPath, destination), appRootPath)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
const oldRelativeRoot = path
|
||||
.relative(path.join(appRootPath, project.root), appRootPath)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
|
||||
if (newRelativeRoot === oldRelativeRoot) {
|
||||
// nothing to do
|
||||
return tree;
|
||||
}
|
||||
if (newRelativeRoot === oldRelativeRoot) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
const dots = /\./g;
|
||||
const regex = new RegExp(oldRelativeRoot.replace(dots, '\\.'), 'g');
|
||||
const dots = /\./g;
|
||||
const regex = new RegExp(oldRelativeRoot.replace(dots, '\\.'), 'g');
|
||||
|
||||
const isRootFile = new RegExp(`${schema.destination}/[^/]+.js*`);
|
||||
const projectDir = tree.getDir(destination);
|
||||
projectDir.visit((file) => {
|
||||
if (!isRootFile.test(file)) {
|
||||
return;
|
||||
}
|
||||
for (const file of tree.children(destination)) {
|
||||
if (!extname(file).startsWith('.js')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const oldContent = tree.read(file).toString();
|
||||
const newContent = oldContent.replace(regex, newRelativeRoot);
|
||||
tree.overwrite(file, newContent);
|
||||
});
|
||||
|
||||
return tree;
|
||||
})
|
||||
);
|
||||
};
|
||||
const oldContent = tree.read(join(destination, file)).toString();
|
||||
const newContent = oldContent.replace(regex, newRelativeRoot);
|
||||
tree.write(join(destination, file), newContent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,21 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
|
||||
import { callRule, runSchematic } from '../../../utils/testing';
|
||||
import { readProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { Schema } from '../schema';
|
||||
import { updateStorybookConfig } from './update-storybook-config';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
|
||||
describe('updateStorybookConfig Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
describe('updateStorybookConfig', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
});
|
||||
|
||||
it('should handle storybook config not existing', async () => {
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
@ -23,9 +24,7 @@ describe('updateStorybookConfig Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
await expect(
|
||||
callRule(updateStorybookConfig(schema), tree)
|
||||
).resolves.not.toThrow();
|
||||
updateStorybookConfig(tree, schema, projectConfig);
|
||||
});
|
||||
|
||||
it('should update the import path for main.js', async () => {
|
||||
@ -37,8 +36,11 @@ describe('updateStorybookConfig Rule', () => {
|
||||
const storybookMainPath =
|
||||
'/libs/namespace/my-destination/.storybook/main.js';
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
tree.create(storybookMainPath, storybookMain);
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
tree.write(storybookMainPath, storybookMain);
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
@ -47,10 +49,7 @@ describe('updateStorybookConfig Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(
|
||||
updateStorybookConfig(schema),
|
||||
tree
|
||||
)) as UnitTestTree;
|
||||
updateStorybookConfig(tree, schema, projectConfig);
|
||||
|
||||
const storybookMainAfter = tree.read(storybookMainPath).toString();
|
||||
expect(storybookMainAfter).toContain(
|
||||
@ -66,8 +65,11 @@ describe('updateStorybookConfig Rule', () => {
|
||||
const storybookWebpackConfigPath =
|
||||
'/libs/namespace/my-destination/.storybook/webpack.config.js';
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
tree.create(storybookWebpackConfigPath, storybookWebpackConfig);
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
tree.write(storybookWebpackConfigPath, storybookWebpackConfig);
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
@ -76,10 +78,7 @@ describe('updateStorybookConfig Rule', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(
|
||||
updateStorybookConfig(schema),
|
||||
tree
|
||||
)) as UnitTestTree;
|
||||
updateStorybookConfig(tree, schema, projectConfig);
|
||||
|
||||
const storybookWebpackConfigAfter = tree
|
||||
.read(storybookWebpackConfigPath)
|
||||
|
||||
@ -1,61 +1,44 @@
|
||||
import { Rule, SchematicContext } from '@angular-devkit/schematics';
|
||||
import { Tree } from '@angular-devkit/schematics/src/tree/interface';
|
||||
import { getWorkspace } from '@nrwl/workspace';
|
||||
import { ProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
|
||||
import * as path from 'path';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { appRootPath } from '../../../utils/app-root';
|
||||
import { Schema } from '../schema';
|
||||
import { getDestination } from './utils';
|
||||
import { appRootPath } from '@nrwl/workspace/src/utils/app-root';
|
||||
import { allFilesInDirInHost } from '@nrwl/workspace/src/utils/ast-utils';
|
||||
import { Path, normalize } from '@angular-devkit/core';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* Updates relative path to root storybook config for `main.js` & `webpack.config.js`
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updateStorybookConfig(schema: Schema): Rule {
|
||||
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
|
||||
return from(getWorkspace(tree)).pipe(
|
||||
map((workspace) => {
|
||||
const project = workspace.projects.get(schema.projectName);
|
||||
const destination = getDestination(schema, workspace, tree);
|
||||
export function updateStorybookConfig(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
const destination = getDestination(tree, schema, project);
|
||||
|
||||
const oldRelativeRoot = path
|
||||
.relative(
|
||||
path.join(appRootPath, `${project.root}/.storybook`),
|
||||
appRootPath
|
||||
)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
const newRelativeRoot = path
|
||||
.relative(
|
||||
path.join(appRootPath, `${destination}/.storybook`),
|
||||
appRootPath
|
||||
)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
const oldRelativeRoot = path
|
||||
.relative(path.join(appRootPath, `${project.root}/.storybook`), appRootPath)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
const newRelativeRoot = path
|
||||
.relative(path.join(appRootPath, `${destination}/.storybook`), appRootPath)
|
||||
.split(path.sep)
|
||||
.join('/');
|
||||
|
||||
const storybookDir = path.join(destination, '.storybook');
|
||||
const storybookDir = path.join(destination, '.storybook');
|
||||
|
||||
if (!storybookDir) {
|
||||
return tree;
|
||||
}
|
||||
if (!storybookDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace relative import path to root storybook folder for each file under project storybook
|
||||
tree.getDir(storybookDir).visit((file) => {
|
||||
const oldContent = tree.read(file).toString('utf-8');
|
||||
const newContent = oldContent.replace(
|
||||
oldRelativeRoot,
|
||||
newRelativeRoot
|
||||
);
|
||||
// Replace relative import path to root storybook folder for each file under project storybook
|
||||
for (const file of tree.children(storybookDir)) {
|
||||
const oldContent = tree.read(join(storybookDir, file)).toString('utf-8');
|
||||
const newContent = oldContent.replace(oldRelativeRoot, newRelativeRoot);
|
||||
|
||||
tree.overwrite(file, newContent);
|
||||
});
|
||||
|
||||
return tree;
|
||||
})
|
||||
);
|
||||
};
|
||||
tree.write(join(storybookDir, file), newContent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,245 +0,0 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { NxJson, updateJsonInTree } 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;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
|
||||
const workspace = {
|
||||
version: 1,
|
||||
projects: {
|
||||
'my-source': {
|
||||
projectType: 'application',
|
||||
root: 'apps/my-source',
|
||||
sourceRoot: 'apps/my-source/src',
|
||||
prefix: 'app',
|
||||
architect: {
|
||||
build: {
|
||||
builder: '@angular-devkit/build-angular:browser',
|
||||
options: {
|
||||
outputPath: 'dist/apps/my-source',
|
||||
index: 'apps/my-source/src/index.html',
|
||||
main: 'apps/my-source/src/main.ts',
|
||||
polyfills: 'apps/my-source/src/polyfills.ts',
|
||||
tsConfig: 'apps/my-source/tsconfig.app.json',
|
||||
aot: false,
|
||||
assets: [
|
||||
'apps/my-source/src/favicon.ico',
|
||||
'apps/my-source/src/assets',
|
||||
],
|
||||
styles: ['apps/my-source/src/styles.scss'],
|
||||
scripts: [],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
fileReplacements: [
|
||||
{
|
||||
replace: 'apps/my-source/src/environments/environment.ts',
|
||||
with:
|
||||
'apps/my-source/src/environments/environment.prod.ts',
|
||||
},
|
||||
],
|
||||
optimization: true,
|
||||
outputHashing: 'all',
|
||||
sourceMap: false,
|
||||
extractCss: true,
|
||||
namedChunks: false,
|
||||
aot: true,
|
||||
extractLicenses: true,
|
||||
vendorChunk: false,
|
||||
buildOptimizer: true,
|
||||
budgets: [
|
||||
{
|
||||
type: 'initial',
|
||||
maximumWarning: '2mb',
|
||||
maximumError: '5mb',
|
||||
},
|
||||
{
|
||||
type: 'anyComponentStyle',
|
||||
maximumWarning: '6kb',
|
||||
maximumError: '10kb',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
serve: {
|
||||
builder: '@angular-devkit/build-angular:dev-server',
|
||||
options: {
|
||||
browserTarget: 'my-source:build',
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
browserTarget: 'my-source:build:production',
|
||||
},
|
||||
},
|
||||
},
|
||||
'extract-i18n': {
|
||||
builder: '@angular-devkit/build-angular:extract-i18n',
|
||||
options: {
|
||||
browserTarget: 'my-source:build',
|
||||
},
|
||||
},
|
||||
lint: {
|
||||
builder: '@angular-devkit/build-angular:tslint',
|
||||
options: {
|
||||
tsConfig: [
|
||||
'apps/my-source/tsconfig.app.json',
|
||||
'apps/my-source/tsconfig.spec.json',
|
||||
],
|
||||
exclude: ['**/node_modules/**', '!apps/my-source/**/*'],
|
||||
},
|
||||
},
|
||||
test: {
|
||||
builder: '@nrwl/jest:jest',
|
||||
options: {
|
||||
jestConfig: 'apps/my-source/jest.config.js',
|
||||
tsConfig: 'apps/my-source/tsconfig.spec.json',
|
||||
setupFile: 'apps/my-source/src/test-setup.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'my-source-e2e': {
|
||||
root: 'apps/my-source-e2e',
|
||||
sourceRoot: 'apps/my-source-e2e/src',
|
||||
projectType: 'application',
|
||||
architect: {
|
||||
e2e: {
|
||||
builder: '@nrwl/cypress:cypress',
|
||||
options: {
|
||||
cypressConfig: 'apps/my-source-e2e/cypress.json',
|
||||
tsConfig: 'apps/my-source-e2e/tsconfig.e2e.json',
|
||||
devServerTarget: 'my-source:serve',
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
devServerTarget: 'my-source:serve:production',
|
||||
},
|
||||
},
|
||||
},
|
||||
lint: {
|
||||
builder: '@angular-devkit/build-angular:tslint',
|
||||
options: {
|
||||
tsConfig: ['apps/my-source-e2e/tsconfig.e2e.json'],
|
||||
exclude: ['**/node_modules/**', '!apps/my-source-e2e/**/*'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultProject: 'my-source',
|
||||
};
|
||||
|
||||
tree.overwrite('workspace.json', JSON.stringify(workspace));
|
||||
});
|
||||
|
||||
it('should rename the project', async () => {
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
destination: 'subfolder/my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(updateWorkspace(schema), tree)) as UnitTestTree;
|
||||
|
||||
const workspace = JSON.parse(tree.read('workspace.json').toString());
|
||||
|
||||
expect(workspace.projects['my-source']).toBeUndefined();
|
||||
expect(workspace.projects['subfolder-my-destination']).toBeDefined();
|
||||
});
|
||||
|
||||
it('should update the default project', async () => {
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
destination: 'subfolder/my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(updateWorkspace(schema), tree)) as UnitTestTree;
|
||||
|
||||
const workspace = JSON.parse(tree.read('workspace.json').toString());
|
||||
|
||||
expect(workspace.defaultProject).toBe('subfolder-my-destination');
|
||||
});
|
||||
|
||||
it('should update paths in only the intended project', async () => {
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
destination: 'subfolder/my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(updateWorkspace(schema), tree)) as UnitTestTree;
|
||||
|
||||
const workspace = JSON.parse(tree.read('workspace.json').toString());
|
||||
|
||||
const actualProject = workspace.projects['subfolder-my-destination'];
|
||||
expect(actualProject).toBeDefined();
|
||||
expect(actualProject.root).toBe('apps/subfolder/my-destination');
|
||||
expect(actualProject.root).toBe('apps/subfolder/my-destination');
|
||||
|
||||
const similarProject = workspace.projects['my-source-e2e'];
|
||||
expect(similarProject).toBeDefined();
|
||||
expect(similarProject.root).toBe('apps/my-source-e2e');
|
||||
});
|
||||
|
||||
it('should update build targets', async () => {
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
destination: 'subfolder/my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(updateWorkspace(schema), tree)) as UnitTestTree;
|
||||
|
||||
const workspace = JSON.parse(tree.read('workspace.json').toString());
|
||||
|
||||
const e2eProject = workspace.projects['my-source-e2e'];
|
||||
expect(e2eProject).toBeDefined();
|
||||
expect(e2eProject.architect.e2e.options.devServerTarget).toBe(
|
||||
'subfolder-my-destination:serve'
|
||||
);
|
||||
expect(
|
||||
e2eProject.architect.e2e.configurations.production.devServerTarget
|
||||
).toBe('subfolder-my-destination:serve:production');
|
||||
});
|
||||
|
||||
it('honor custom workspace layouts', async () => {
|
||||
const schema: Schema = {
|
||||
projectName: 'my-source',
|
||||
destination: 'subfolder/my-destination',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
tree = (await callRule(
|
||||
updateJsonInTree<NxJson>('nx.json', (json) => {
|
||||
json.workspaceLayout = { appsDir: 'e2e', libsDir: 'packages' };
|
||||
return json;
|
||||
}),
|
||||
tree
|
||||
)) as UnitTestTree;
|
||||
|
||||
tree = (await callRule(updateWorkspace(schema), tree)) as UnitTestTree;
|
||||
|
||||
const workspace = JSON.parse(tree.read('workspace.json').toString());
|
||||
|
||||
const project = workspace.projects['subfolder-my-destination'];
|
||||
expect(project).toBeDefined();
|
||||
expect(project.root).toBe('e2e/subfolder/my-destination');
|
||||
expect(project.sourceRoot).toBe('e2e/subfolder/my-destination/src');
|
||||
});
|
||||
});
|
||||
@ -1,52 +0,0 @@
|
||||
import { SchematicContext, Tree } from '@angular-devkit/schematics';
|
||||
import { updateWorkspaceInTree } from '@nrwl/workspace';
|
||||
import { Schema } from '../schema';
|
||||
import { getDestination, getNewProjectName } from './utils';
|
||||
|
||||
/**
|
||||
* Updates the project in the workspace file
|
||||
*
|
||||
* - update all references to the old root path
|
||||
* - change the project name
|
||||
* - change target references
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updateWorkspace(schema: Schema) {
|
||||
return (tree: Tree, _context: SchematicContext) => {
|
||||
return updateWorkspaceInTree((workspace) => {
|
||||
const project = workspace.projects[schema.projectName];
|
||||
const newProjectName = getNewProjectName(schema.destination);
|
||||
|
||||
// update root path refs in that project only
|
||||
const oldProject = JSON.stringify(project);
|
||||
const newProject = oldProject.replace(
|
||||
new RegExp(project.root, 'g'),
|
||||
getDestination(schema, workspace, tree)
|
||||
);
|
||||
|
||||
// rename
|
||||
delete workspace.projects[schema.projectName];
|
||||
workspace.projects[newProjectName] = JSON.parse(newProject);
|
||||
|
||||
// update target refs
|
||||
const strWorkspace = JSON.stringify(workspace);
|
||||
workspace = JSON.parse(
|
||||
strWorkspace.replace(
|
||||
new RegExp(`${schema.projectName}:`, 'g'),
|
||||
`${newProjectName}:`
|
||||
)
|
||||
);
|
||||
|
||||
// update default project (if necessary)
|
||||
if (
|
||||
workspace.defaultProject &&
|
||||
workspace.defaultProject === schema.projectName
|
||||
) {
|
||||
workspace.defaultProject = newProjectName;
|
||||
}
|
||||
|
||||
return workspace;
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -1,28 +1,9 @@
|
||||
import { WorkspaceDefinition } from '@angular-devkit/core/src/workspace';
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { NxJson } from '@nrwl/workspace/src/core/shared-interfaces';
|
||||
import { readJsonInTree } from '@nrwl/workspace/src/utils/ast-utils';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ProjectConfiguration, Tree, getWorkspaceLayout } from '@nrwl/devkit';
|
||||
|
||||
import { Schema } from '../schema';
|
||||
|
||||
/**
|
||||
* This helper function retrieves the users workspace layout from
|
||||
* `nx.json`. If the user does not have this property defined then
|
||||
* we assume the default `apps/` and `libs/` layout.
|
||||
*
|
||||
* @param host The host tree
|
||||
*/
|
||||
export function getWorkspaceLayout(
|
||||
host: Tree
|
||||
): { appsDir?: string; libsDir?: string } {
|
||||
const nxJson = readJsonInTree<NxJson>(host, 'nx.json');
|
||||
const workspaceLayout = nxJson.workspaceLayout
|
||||
? nxJson.workspaceLayout
|
||||
: { appsDir: 'apps', libsDir: 'libs' };
|
||||
|
||||
return workspaceLayout;
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper function ensures that we don't move libs or apps
|
||||
* outside of the folders they should be in.
|
||||
@ -34,16 +15,11 @@ export function getWorkspaceLayout(
|
||||
* @param workspace
|
||||
*/
|
||||
export function getDestination(
|
||||
host: Tree,
|
||||
schema: Schema,
|
||||
workspace: WorkspaceDefinition | any,
|
||||
host: Tree
|
||||
project: ProjectConfiguration
|
||||
): string {
|
||||
const project = workspace.projects.get
|
||||
? workspace.projects.get(schema.projectName)
|
||||
: workspace.projects[schema.projectName];
|
||||
const projectType = project.extensions
|
||||
? project.extensions['projectType']
|
||||
: project.projectType;
|
||||
const projectType = project.projectType;
|
||||
|
||||
const workspaceLayout = getWorkspaceLayout(host);
|
||||
|
||||
@ -51,7 +27,7 @@ export function getDestination(
|
||||
if (projectType === 'application') {
|
||||
rootFolder = workspaceLayout.appsDir;
|
||||
}
|
||||
return path.join(rootFolder, schema.destination).split(path.sep).join('/');
|
||||
return path.join(rootFolder, schema.destination);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,35 +1,44 @@
|
||||
import { chain, Rule } from '@angular-devkit/schematics';
|
||||
import { checkProjectExists } from '../../utils/rules/check-project-exists';
|
||||
import {
|
||||
convertNxGenerator,
|
||||
formatFiles,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
import { checkDestination } from './lib/check-destination';
|
||||
import { moveProject } from './lib/move-project';
|
||||
import { updateCypressJson } from './lib/update-cypress-json';
|
||||
import { updateImports } from './lib/update-imports';
|
||||
import { updateJestConfig } from './lib/update-jest-config';
|
||||
import { updateStorybookConfig } from './lib/update-storybook-config';
|
||||
import { updateNxJson } from './lib/update-nx-json';
|
||||
import { updateImplicitDependencies } from './lib/update-implicit-dependencies';
|
||||
import { updateProjectRootFiles } from './lib/update-project-root-files';
|
||||
import { updateWorkspace } from './lib/update-workspace';
|
||||
import { updateDefaultProject } from './lib/update-default-project';
|
||||
import { Schema } from './schema';
|
||||
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
|
||||
import { updateEslintrcJson } from './lib/update-eslintrc-json';
|
||||
import { moveProjectConfiguration } from './lib/move-project-configuration';
|
||||
import { updateBuildTargets } from './lib/update-build-targets';
|
||||
|
||||
export default function (schema: Schema): Rule {
|
||||
return chain([
|
||||
checkProjectExists(schema),
|
||||
checkDestination(schema),
|
||||
moveProject(schema), // we MUST move the project first, if we don't we get a "This should never happen" error 🤦♀️
|
||||
updateProjectRootFiles(schema),
|
||||
updateCypressJson(schema),
|
||||
updateJestConfig(schema),
|
||||
updateStorybookConfig(schema),
|
||||
updateNxJson(schema),
|
||||
updateImports(schema),
|
||||
updateEslintrcJson(schema),
|
||||
updateWorkspace(schema), // Have to do this last because all previous rules need the information in here
|
||||
]);
|
||||
export async function moveGenerator(tree: Tree, schema: Schema) {
|
||||
const projectConfig = readProjectConfiguration(tree, schema.projectName);
|
||||
checkDestination(tree, schema, projectConfig);
|
||||
moveProject(tree, schema, projectConfig); // we MUST move the project first, if we don't we get a "This should never happen" error 🤦♀️
|
||||
updateImports(tree, schema, projectConfig);
|
||||
updateProjectRootFiles(tree, schema, projectConfig);
|
||||
updateCypressJson(tree, schema, projectConfig);
|
||||
updateJestConfig(tree, schema, projectConfig);
|
||||
updateStorybookConfig(tree, schema, projectConfig);
|
||||
updateEslintrcJson(tree, schema, projectConfig);
|
||||
moveProjectConfiguration(tree, schema, projectConfig);
|
||||
updateBuildTargets(tree, schema);
|
||||
updateDefaultProject(tree, schema);
|
||||
updateImplicitDependencies(tree, schema);
|
||||
|
||||
if (!schema.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
}
|
||||
|
||||
export const moveGenerator = wrapAngularDevkitSchematic(
|
||||
'@nrwl/workspace',
|
||||
'move'
|
||||
);
|
||||
export default moveGenerator;
|
||||
|
||||
export const moveSchematic = convertNxGenerator(moveGenerator);
|
||||
|
||||
@ -3,4 +3,5 @@ export interface Schema {
|
||||
destination: string;
|
||||
importPath?: string;
|
||||
updateImportPath: boolean;
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"id": "NxWorkspaceMove",
|
||||
"cli": "nx",
|
||||
"title": "Nx Move",
|
||||
"description": "Move a project to another folder in the workspace",
|
||||
"type": "object",
|
||||
@ -32,6 +33,12 @@
|
||||
"type": "boolean",
|
||||
"description": "Should the generator update the import path to reflect the new location?",
|
||||
"default": true
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"aliases": ["skip-format"],
|
||||
"description": "Skip formatting files.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": ["projectName", "destination"]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`updateRootJestConfig Rule should delete lib project ref from root jest config 1`] = `
|
||||
exports[`updateRootJestConfig should delete lib project ref from root jest config 1`] = `
|
||||
"module.exports = {
|
||||
projects: [
|
||||
'<rootDir>/apps/my-app/',
|
||||
@ -12,12 +12,12 @@ exports[`updateRootJestConfig Rule should delete lib project ref from root jest
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`updateRootJestConfig Rule should delete lib project ref from root jest config 2`] = `
|
||||
exports[`updateRootJestConfig should delete lib project ref from root jest config 2`] = `
|
||||
"module.exports = {
|
||||
projects: [
|
||||
'<rootDir>/apps/my-app/',
|
||||
'<rootDir>/apps/my-other-app',
|
||||
'<rootDir>/libs/my-other-lib/',
|
||||
'<rootDir>/libs/my-other-lib/',
|
||||
],
|
||||
};
|
||||
"
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
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 {
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
updateProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { Schema } from '../schema';
|
||||
import { checkDependencies } from './check-dependencies';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
|
||||
describe('updateImports Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
describe('checkDependencies', () => {
|
||||
let tree: Tree;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
schema = {
|
||||
projectName: 'my-source',
|
||||
@ -20,21 +21,25 @@ describe('updateImports Rule', () => {
|
||||
forceRemove: false,
|
||||
};
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-dependent' }, tree);
|
||||
tree = await runSchematic('lib', { name: 'my-source' }, tree);
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-dependent',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
});
|
||||
});
|
||||
|
||||
describe('static dependencies', () => {
|
||||
beforeEach(() => {
|
||||
const sourceFilePath = 'libs/my-source/src/lib/my-source.ts';
|
||||
tree.overwrite(
|
||||
tree.write(
|
||||
sourceFilePath,
|
||||
`export class MyClass {}
|
||||
`
|
||||
);
|
||||
|
||||
const dependentFilePath = 'libs/my-dependent/src/lib/my-dependent.ts';
|
||||
tree.overwrite(
|
||||
tree.write(
|
||||
dependentFilePath,
|
||||
`import { MyClass } from '@proj/my-source';
|
||||
|
||||
@ -44,33 +49,33 @@ describe('updateImports Rule', () => {
|
||||
});
|
||||
|
||||
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`
|
||||
);
|
||||
expect(() => {
|
||||
checkDependencies(tree, schema);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should not error if forceRemove is true', async () => {
|
||||
schema.forceRemove = true;
|
||||
|
||||
await expect(
|
||||
callRule(checkDependencies(schema), tree)
|
||||
).resolves.not.toThrow();
|
||||
expect(() => {
|
||||
checkDependencies(tree, schema);
|
||||
}).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;
|
||||
const config = readProjectConfiguration(tree, 'my-dependent');
|
||||
updateProjectConfiguration(tree, 'my-dependent', {
|
||||
...config,
|
||||
implicitDependencies: ['my-source'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should fatally error if any dependent exists', async () => {
|
||||
await expect(callRule(checkDependencies(schema), tree)).rejects.toThrow(
|
||||
expect(() => {
|
||||
checkDependencies(tree, schema);
|
||||
}).toThrow(
|
||||
`${schema.projectName} is still depended on by the following projects:\nmy-dependent`
|
||||
);
|
||||
});
|
||||
@ -78,15 +83,15 @@ describe('updateImports Rule', () => {
|
||||
it('should not error if forceRemove is true', async () => {
|
||||
schema.forceRemove = true;
|
||||
|
||||
await expect(
|
||||
callRule(checkDependencies(schema), tree)
|
||||
).resolves.not.toThrow();
|
||||
expect(() => {
|
||||
checkDependencies(tree, schema);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not error if there are no dependents', async () => {
|
||||
await expect(
|
||||
callRule(checkDependencies(schema), tree)
|
||||
).resolves.not.toThrow();
|
||||
expect(() => {
|
||||
checkDependencies(tree, schema);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,83 +1,37 @@
|
||||
import { Rule, Tree } from '@angular-devkit/schematics';
|
||||
import { FileData } from '@nrwl/workspace/src/core/file-utils';
|
||||
import { Tree } from '@nrwl/devkit';
|
||||
import {
|
||||
readNxJsonInTree,
|
||||
readWorkspace,
|
||||
} from '@nrwl/workspace/src/utils/ast-utils';
|
||||
import { getWorkspacePath } from '@nrwl/workspace/src/utils/cli-config-utils';
|
||||
import ignore from 'ignore';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
createProjectGraph,
|
||||
onlyWorkspaceProjects,
|
||||
ProjectGraph,
|
||||
reverse,
|
||||
} from '../../../core/project-graph';
|
||||
import { Schema } from '../schema';
|
||||
import { createProjectGraphFromTree } from '../../../utils/create-project-graph-from-tree';
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
export function checkDependencies(tree: Tree, schema: Schema) {
|
||||
if (schema.forceRemove) {
|
||||
return (tree: Tree) => tree;
|
||||
return;
|
||||
}
|
||||
let ig = ignore();
|
||||
|
||||
return (tree: Tree): Tree => {
|
||||
if (tree.exists('.gitignore')) {
|
||||
ig = ig.add(tree.read('.gitignore').toString());
|
||||
}
|
||||
const files: FileData[] = [];
|
||||
const workspaceDir = path.dirname(getWorkspacePath(tree));
|
||||
const graph: ProjectGraph = createProjectGraphFromTree(tree);
|
||||
|
||||
for (const dir of tree.getDir('/').subdirs) {
|
||||
if (ig.ignores(dir)) {
|
||||
continue;
|
||||
}
|
||||
const reverseGraph = onlyWorkspaceProjects(reverse(graph));
|
||||
|
||||
tree.getDir(dir).visit((file: string) => {
|
||||
files.push({
|
||||
file: path.relative(workspaceDir, file),
|
||||
ext: path.extname(file),
|
||||
hash: '',
|
||||
});
|
||||
});
|
||||
}
|
||||
const deps = reverseGraph.dependencies[schema.projectName] || [];
|
||||
|
||||
const graph: ProjectGraph = createProjectGraph(
|
||||
readWorkspace(tree),
|
||||
readNxJsonInTree(tree),
|
||||
files,
|
||||
(file) => {
|
||||
try {
|
||||
return tree.read(file).toString('utf-8');
|
||||
} catch (e) {
|
||||
throw new Error(`Could not read ${file}`);
|
||||
}
|
||||
},
|
||||
false,
|
||||
false
|
||||
);
|
||||
if (deps.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
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')}`
|
||||
);
|
||||
};
|
||||
throw new Error(
|
||||
`${
|
||||
schema.projectName
|
||||
} is still depended on by the following projects:\n${deps
|
||||
.map((x) => x.target)
|
||||
.join('\n')}`
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
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 { addProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { Schema } from '../schema';
|
||||
import { checkTargets } from './check-targets';
|
||||
|
||||
describe('checkTargets Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
describe('checkTargets', () => {
|
||||
let tree: Tree;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
schema = {
|
||||
projectName: 'ng-app',
|
||||
@ -20,59 +16,54 @@ describe('checkTargets Rule', () => {
|
||||
forceRemove: false,
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
addProjectConfiguration(tree, 'ng-app', {
|
||||
projectType: 'application',
|
||||
root: 'apps/ng-app',
|
||||
sourceRoot: 'apps/ng-app/src',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
addProjectConfiguration(tree, 'ng-app-e2e', {
|
||||
root: 'apps/ng-app-e2e',
|
||||
sourceRoot: 'apps/ng-app-e2e/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
e2e: {
|
||||
executor: '@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();
|
||||
expect(() => {
|
||||
checkTargets(tree, schema);
|
||||
}).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();
|
||||
expect(() => {
|
||||
checkTargets(tree, schema);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not error if forceRemove is true', async () => {
|
||||
schema.forceRemove = true;
|
||||
|
||||
await expect(callRule(checkTargets(schema), tree)).resolves.not.toThrow();
|
||||
expect(() => {
|
||||
checkTargets(tree, schema);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { updateWorkspaceInTree } from '@nrwl/workspace';
|
||||
import { getProjects, Tree } from '@nrwl/devkit';
|
||||
import { Schema } from '../schema';
|
||||
|
||||
/**
|
||||
@ -9,36 +8,30 @@ import { Schema } from '../schema';
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function checkTargets(schema: Schema) {
|
||||
export function checkTargets(tree: Tree, schema: Schema) {
|
||||
if (schema.forceRemove) {
|
||||
return (tree: Tree) => tree;
|
||||
return;
|
||||
}
|
||||
|
||||
return updateWorkspaceInTree((workspace) => {
|
||||
const usedIn = [];
|
||||
getProjects(tree).forEach((project, projectName) => {
|
||||
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 (projectName === schema.projectName) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
if (findTarget.test(JSON.stringify(project))) {
|
||||
usedIn.push(projectName);
|
||||
}
|
||||
|
||||
return workspace;
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
readWorkspaceConfiguration,
|
||||
Tree,
|
||||
updateWorkspaceConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
|
||||
import { Schema } from '../schema';
|
||||
import { removeProjectConfig } from './remove-project-config';
|
||||
|
||||
describe('removeProjectConfig', () => {
|
||||
let tree: Tree;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
addProjectConfiguration(tree, 'ng-app', {
|
||||
projectType: 'application',
|
||||
root: 'apps/ng-app',
|
||||
sourceRoot: 'apps/ng-app/src',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
addProjectConfiguration(tree, 'ng-app-e2e', {
|
||||
root: 'apps/ng-app-e2e',
|
||||
sourceRoot: 'apps/ng-app-e2e/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
e2e: {
|
||||
executor: '@nrwl/cypress:cypress',
|
||||
options: {
|
||||
cypressConfig: 'apps/ng-app-e2e/cypress.json',
|
||||
tsConfig: 'apps/ng-app-e2e/tsconfig.e2e.json',
|
||||
devServerTarget: 'ng-app:serve',
|
||||
},
|
||||
},
|
||||
},
|
||||
implicitDependencies: ['ng-app'],
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete project', async () => {
|
||||
beforeEach(async () => {
|
||||
schema = {
|
||||
projectName: 'ng-app',
|
||||
skipFormat: false,
|
||||
forceRemove: false,
|
||||
};
|
||||
});
|
||||
|
||||
it('should delete the project', async () => {
|
||||
removeProjectConfig(tree, schema);
|
||||
|
||||
expect(() => {
|
||||
readProjectConfiguration(tree, schema.projectName);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultProject', () => {
|
||||
beforeEach(async () => {
|
||||
const workspaceConfig = readWorkspaceConfiguration(tree);
|
||||
updateWorkspaceConfiguration(tree, {
|
||||
...workspaceConfig,
|
||||
defaultProject: 'ng-app',
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove defaultProject if it matches the project being deleted', async () => {
|
||||
schema = {
|
||||
projectName: 'ng-app',
|
||||
skipFormat: false,
|
||||
forceRemove: false,
|
||||
};
|
||||
|
||||
removeProjectConfig(tree, schema);
|
||||
|
||||
const { defaultProject } = readWorkspaceConfiguration(tree);
|
||||
expect(defaultProject).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not remove defaultProject if it does not match the project being deleted', async () => {
|
||||
schema = {
|
||||
projectName: 'ng-app-e2e',
|
||||
skipFormat: false,
|
||||
forceRemove: false,
|
||||
};
|
||||
|
||||
removeProjectConfig(tree, schema);
|
||||
|
||||
const { defaultProject } = readWorkspaceConfiguration(tree);
|
||||
expect(defaultProject).toEqual('ng-app');
|
||||
});
|
||||
|
||||
it('should remove implicit dependencies onto the removed project', () => {
|
||||
schema = {
|
||||
projectName: 'ng-app',
|
||||
skipFormat: false,
|
||||
forceRemove: false,
|
||||
};
|
||||
|
||||
removeProjectConfig(tree, schema);
|
||||
|
||||
const { implicitDependencies } = readProjectConfiguration(
|
||||
tree,
|
||||
'ng-app-e2e'
|
||||
);
|
||||
|
||||
expect(implicitDependencies).not.toContain('ng-app');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,46 @@
|
||||
import { Schema } from '../schema';
|
||||
import {
|
||||
getProjects,
|
||||
Tree,
|
||||
updateProjectConfiguration,
|
||||
updateWorkspaceConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import {
|
||||
readWorkspaceConfiguration,
|
||||
removeProjectConfiguration,
|
||||
getWorkspacePath,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
/**
|
||||
* Deletes the project from the workspace file
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function removeProjectConfig(tree: Tree, schema: Schema) {
|
||||
removeProjectConfiguration(tree, schema.projectName);
|
||||
|
||||
// Unset default project if deleting the default project
|
||||
const workspaceConfiguration = readWorkspaceConfiguration(tree);
|
||||
if (
|
||||
workspaceConfiguration.defaultProject &&
|
||||
workspaceConfiguration.defaultProject === schema.projectName
|
||||
) {
|
||||
const workspacePath = getWorkspacePath(tree);
|
||||
delete workspaceConfiguration.defaultProject;
|
||||
console.warn(
|
||||
`Default project was removed in ${workspacePath} because it was "${schema.projectName}". If you want a default project you should define a new one.`
|
||||
);
|
||||
|
||||
updateWorkspaceConfiguration(tree, workspaceConfiguration);
|
||||
}
|
||||
|
||||
// Remove implicit dependencies onto removed project
|
||||
getProjects(tree).forEach((project, projectName) => {
|
||||
if (project.implicitDependencies) {
|
||||
project.implicitDependencies = project.implicitDependencies.filter(
|
||||
(projectName) => projectName !== schema.projectName
|
||||
);
|
||||
}
|
||||
updateProjectConfiguration(tree, projectName, project);
|
||||
});
|
||||
}
|
||||
@ -1,16 +1,18 @@
|
||||
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 { readProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { Schema } from '../schema';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
import { removeProject } from '@nrwl/workspace/src/schematics/remove/lib/remove-project';
|
||||
|
||||
describe('moveProject Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
describe('moveProject', () => {
|
||||
let schema: Schema;
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = createEmptyWorkspace(Tree.empty()) as UnitTestTree;
|
||||
tree = await runSchematic('lib', { name: 'my-lib' }, tree);
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
});
|
||||
|
||||
schema = {
|
||||
projectName: 'my-lib',
|
||||
@ -20,15 +22,7 @@ describe('moveProject Rule', () => {
|
||||
});
|
||||
|
||||
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();
|
||||
removeProject(tree, readProjectConfiguration(tree, 'my-lib'));
|
||||
expect(tree.children('libs')).not.toContain('my-lib');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,22 +1,11 @@
|
||||
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';
|
||||
import { ProjectConfiguration, Tree, visitNotIgnoredFiles } from '@nrwl/devkit';
|
||||
|
||||
/**
|
||||
* Removes (deletes) a project from the folder tree
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
* Removes (deletes) a project's files from the folder tree
|
||||
*/
|
||||
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;
|
||||
})
|
||||
);
|
||||
};
|
||||
export function removeProject(tree: Tree, project: ProjectConfiguration) {
|
||||
visitNotIgnoredFiles(tree, project.root, (file) => {
|
||||
tree.delete(file);
|
||||
});
|
||||
tree.delete(project.root);
|
||||
}
|
||||
|
||||
@ -1,22 +1,19 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import {
|
||||
callRule,
|
||||
createEmptyWorkspace,
|
||||
runSchematic,
|
||||
} from '@nrwl/workspace/testing';
|
||||
import { Schema } from '../schema';
|
||||
import { Tree } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { updateJestConfig } from './update-jest-config';
|
||||
|
||||
describe('updateRootJestConfig Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
import { Schema } from '../schema';
|
||||
import { updateJestConfig } from './update-jest-config';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
|
||||
describe('updateRootJestConfig', () => {
|
||||
let tree: Tree;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
schema = {
|
||||
projectName: 'my-lib',
|
||||
@ -24,22 +21,24 @@ describe('updateRootJestConfig Rule', () => {
|
||||
forceRemove: false,
|
||||
};
|
||||
|
||||
tree = await runSchematic('lib', { name: 'my-lib' }, tree);
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
});
|
||||
|
||||
tree.overwrite(
|
||||
tree.write(
|
||||
'jest.config.js',
|
||||
readFileSync(join(__dirname, './test-files/jest.config.js')).toString()
|
||||
);
|
||||
});
|
||||
|
||||
it('should delete lib project ref from root jest config', async () => {
|
||||
const jestConfig = tree.readContent('jest.config.js');
|
||||
const jestConfig = tree.read('jest.config.js').toString();
|
||||
|
||||
expect(jestConfig).toMatchSnapshot();
|
||||
|
||||
tree = (await callRule(updateJestConfig(schema), tree)) as UnitTestTree;
|
||||
updateJestConfig(tree, schema);
|
||||
|
||||
const updatedJestConfig = tree.readContent('jest.config.js');
|
||||
const updatedJestConfig = tree.read('jest.config.js').toString();
|
||||
|
||||
expect(updatedJestConfig).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@ -1,54 +1,46 @@
|
||||
import { SchematicContext, Tree } from '@angular-devkit/schematics';
|
||||
import { getWorkspace, insert, RemoveChange } from '@nrwl/workspace';
|
||||
import {
|
||||
applyChangesToString,
|
||||
ChangeType,
|
||||
StringChange,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
import * as ts from 'typescript';
|
||||
import { getSourceNodes } from '@nrwl/workspace/src/utils/ast-utils';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { Schema } from '../schema';
|
||||
|
||||
/**
|
||||
* Updates the root jest config projects array and removes the project.
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updateJestConfig(schema) {
|
||||
return (tree: Tree, _context: SchematicContext): Observable<Tree> => {
|
||||
return from(getWorkspace(tree)).pipe(
|
||||
map((_) => {
|
||||
const projectToRemove = schema.projectName;
|
||||
export function updateJestConfig(tree: Tree, schema: Schema) {
|
||||
const projectToRemove = schema.projectName;
|
||||
|
||||
if (!tree.exists('jest.config.js')) {
|
||||
return tree;
|
||||
}
|
||||
if (!tree.exists('jest.config.js')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contents = tree.read('jest.config.js').toString();
|
||||
const sourceFile = ts.createSourceFile(
|
||||
'jest.config.js',
|
||||
contents,
|
||||
ts.ScriptTarget.Latest
|
||||
);
|
||||
const contents = tree.read('jest.config.js').toString();
|
||||
const sourceFile = ts.createSourceFile(
|
||||
'jest.config.js',
|
||||
contents,
|
||||
ts.ScriptTarget.Latest
|
||||
);
|
||||
|
||||
const changes: RemoveChange[] = [];
|
||||
const sourceNodes = getSourceNodes(sourceFile);
|
||||
const changes: StringChange[] = [];
|
||||
const sourceNodes = getSourceNodes(sourceFile);
|
||||
|
||||
sourceNodes.forEach((node, index) => {
|
||||
if (
|
||||
ts.isToken(node) &&
|
||||
ts.isStringLiteral(node) &&
|
||||
node.text.includes(projectToRemove)
|
||||
) {
|
||||
changes.push(
|
||||
new RemoveChange(
|
||||
'jest.config.js',
|
||||
node.getStart(sourceFile),
|
||||
node.getFullText(sourceFile)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
insert(tree, 'jest.config.js', changes);
|
||||
|
||||
return tree;
|
||||
})
|
||||
);
|
||||
};
|
||||
sourceNodes.forEach((node) => {
|
||||
if (
|
||||
ts.isToken(node) &&
|
||||
ts.isStringLiteral(node) &&
|
||||
node.text.includes(projectToRemove)
|
||||
) {
|
||||
changes.push({
|
||||
type: ChangeType.Delete,
|
||||
start: node.getStart(sourceFile),
|
||||
length: node.getFullText(sourceFile).length,
|
||||
});
|
||||
}
|
||||
});
|
||||
tree.write('jest.config.js', applyChangesToString(contents, changes));
|
||||
}
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
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';
|
||||
import { updateJsonInTree } from '@nrwl/workspace/src/utils/ast-utils';
|
||||
|
||||
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-lib1' }, tree);
|
||||
tree = await runSchematic('lib', { name: 'my-lib2' }, tree);
|
||||
|
||||
let nxJson = readJsonInTree(tree, '/nx.json');
|
||||
expect(nxJson.projects['my-lib1']).toBeDefined();
|
||||
|
||||
tree = (await callRule(
|
||||
updateJsonInTree('nx.json', (json) => {
|
||||
json.projects['my-lib2'].implicitDependencies = [
|
||||
'my-lib1',
|
||||
'my-other-lib',
|
||||
];
|
||||
return json;
|
||||
}),
|
||||
tree
|
||||
)) as UnitTestTree;
|
||||
|
||||
const schema: Schema = {
|
||||
projectName: 'my-lib1',
|
||||
skipFormat: false,
|
||||
forceRemove: false,
|
||||
};
|
||||
|
||||
tree = (await callRule(updateNxJson(schema), tree)) as UnitTestTree;
|
||||
|
||||
nxJson = readJsonInTree(tree, '/nx.json');
|
||||
expect(nxJson.projects['my-lib1']).toBeUndefined();
|
||||
expect(nxJson.projects['my-lib2'].implicitDependencies).toEqual([
|
||||
'my-other-lib',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -1,23 +0,0 @@
|
||||
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];
|
||||
|
||||
Object.values(json.projects).forEach((project) => {
|
||||
if (project.implicitDependencies) {
|
||||
project.implicitDependencies = project.implicitDependencies.filter(
|
||||
(dep) => dep !== schema.projectName
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return json;
|
||||
});
|
||||
}
|
||||
@ -1,18 +1,15 @@
|
||||
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 { readJson, readProjectConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { Schema } from '../schema';
|
||||
import { updateTsconfig } from './update-tsconfig';
|
||||
import { libraryGenerator } from '../../library/library';
|
||||
|
||||
describe('updateTsconfig Rule', () => {
|
||||
let tree: UnitTestTree;
|
||||
describe('updateTsconfig', () => {
|
||||
let tree: Tree;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = new UnitTestTree(Tree.empty());
|
||||
tree = createEmptyWorkspace(tree) as UnitTestTree;
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
|
||||
schema = {
|
||||
projectName: 'my-lib',
|
||||
@ -22,16 +19,14 @@ describe('updateTsconfig Rule', () => {
|
||||
});
|
||||
|
||||
it('should delete project ref from the tsconfig', async () => {
|
||||
tree = await runSchematic('lib', { name: 'my-lib' }, tree);
|
||||
|
||||
let tsConfig = readJsonInTree(tree, '/tsconfig.base.json');
|
||||
expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
'@proj/my-lib': ['libs/my-lib/src/index.ts'],
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
});
|
||||
const project = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
tree = (await callRule(updateTsconfig(schema), tree)) as UnitTestTree;
|
||||
updateTsconfig(tree, schema, project);
|
||||
|
||||
tsConfig = readJsonInTree(tree, '/tsconfig.base.json');
|
||||
const tsConfig = readJson(tree, '/tsconfig.base.json');
|
||||
expect(tsConfig.compilerOptions.paths).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
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';
|
||||
ProjectConfiguration,
|
||||
Tree,
|
||||
updateJson,
|
||||
getWorkspaceLayout,
|
||||
} from '@nrwl/devkit';
|
||||
import { NxJson, readJsonInTree } from '@nrwl/workspace';
|
||||
import { Schema } from '../schema';
|
||||
|
||||
/**
|
||||
@ -14,24 +12,21 @@ import { Schema } from '../schema';
|
||||
*
|
||||
* @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);
|
||||
export function updateTsconfig(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
const { npmScope } = getWorkspaceLayout(tree);
|
||||
|
||||
const tsConfigPath = 'tsconfig.base.json';
|
||||
if (tree.exists(tsConfigPath)) {
|
||||
const tsConfigJson = readJsonInTree(tree, tsConfigPath);
|
||||
delete tsConfigJson.compilerOptions.paths[
|
||||
`@${nxJson.npmScope}/${project.root.substr(5)}`
|
||||
];
|
||||
tree.overwrite(tsConfigPath, serializeJson(tsConfigJson));
|
||||
}
|
||||
const tsConfigPath = 'tsconfig.base.json';
|
||||
if (tree.exists(tsConfigPath)) {
|
||||
updateJson(tree, tsConfigPath, (json) => {
|
||||
delete json.compilerOptions.paths[
|
||||
`@${npmScope}/${project.root.substr(5)}`
|
||||
];
|
||||
|
||||
return tree;
|
||||
})
|
||||
);
|
||||
};
|
||||
return json;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,165 +0,0 @@
|
||||
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;
|
||||
});
|
||||
|
||||
describe('delete project', async () => {
|
||||
beforeEach(async () => {
|
||||
schema = {
|
||||
projectName: 'ng-app',
|
||||
skipFormat: false,
|
||||
forceRemove: false,
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultProject', () => {
|
||||
beforeEach(async () => {
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'ng-other-app': {
|
||||
projectType: 'application',
|
||||
schematics: {},
|
||||
root: 'apps/ng-app',
|
||||
sourceRoot: 'apps/ng-app/src',
|
||||
prefix: 'happyorg',
|
||||
architect: {
|
||||
build: {
|
||||
builder: '@angular-devkit/build-angular:browser',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultProject: 'ng-app',
|
||||
};
|
||||
}),
|
||||
tree
|
||||
)) as UnitTestTree;
|
||||
});
|
||||
|
||||
it('should remove defaultProject if it matches the project being deleted', async () => {
|
||||
schema = {
|
||||
projectName: 'ng-app',
|
||||
skipFormat: false,
|
||||
forceRemove: false,
|
||||
};
|
||||
|
||||
let workspace = JSON.parse(tree.read('workspace.json').toString());
|
||||
expect(workspace.defaultProject).toBeDefined();
|
||||
|
||||
tree = (await callRule(updateWorkspace(schema), tree)) as UnitTestTree;
|
||||
|
||||
workspace = JSON.parse(tree.read('workspace.json').toString());
|
||||
expect(workspace.defaultProject).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not remove defaultProject if it does not match the project being deleted', async () => {
|
||||
schema = {
|
||||
projectName: 'ng-other-app',
|
||||
skipFormat: false,
|
||||
forceRemove: false,
|
||||
};
|
||||
|
||||
let workspace = JSON.parse(tree.read('workspace.json').toString());
|
||||
expect(workspace.defaultProject).toBeDefined();
|
||||
|
||||
tree = (await callRule(updateWorkspace(schema), tree)) as UnitTestTree;
|
||||
|
||||
workspace = JSON.parse(tree.read('workspace.json').toString());
|
||||
expect(workspace.defaultProject).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,27 +0,0 @@
|
||||
import { Schema } from '../schema';
|
||||
import { SchematicContext, Tree } from '@angular-devkit/schematics';
|
||||
import { updateWorkspaceInTree, getWorkspacePath } from '@nrwl/workspace';
|
||||
|
||||
/**
|
||||
* Deletes the project from the workspace file
|
||||
*
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updateWorkspace(schema: Schema) {
|
||||
return updateWorkspaceInTree(
|
||||
(workspace, context: SchematicContext, host: Tree) => {
|
||||
delete workspace.projects[schema.projectName];
|
||||
if (
|
||||
workspace.defaultProject &&
|
||||
workspace.defaultProject === schema.projectName
|
||||
) {
|
||||
delete workspace.defaultProject;
|
||||
const workspacePath = getWorkspacePath(host);
|
||||
context.logger.warn(
|
||||
`Default project was removed in ${workspacePath} because it was "${schema.projectName}". If you want a default project you should define a new one.`
|
||||
);
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1,31 +1,31 @@
|
||||
import { chain, Rule } from '@angular-devkit/schematics';
|
||||
import { checkProjectExists } from '../../utils/rules/check-project-exists';
|
||||
import { formatFiles } from '../../utils/rules/format-files';
|
||||
import {
|
||||
convertNxGenerator,
|
||||
formatFiles,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
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 { removeProjectConfig } from './lib/remove-project-config';
|
||||
import { Schema } from './schema';
|
||||
import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter';
|
||||
import { updateJestConfig } from './lib/update-jest-config';
|
||||
|
||||
export default function (schema: Schema): Rule {
|
||||
return chain([
|
||||
checkProjectExists(schema),
|
||||
checkDependencies(schema),
|
||||
checkTargets(schema),
|
||||
removeProject(schema),
|
||||
updateNxJson(schema),
|
||||
updateTsconfig(schema),
|
||||
updateWorkspace(schema),
|
||||
updateJestConfig(schema),
|
||||
formatFiles(schema),
|
||||
]);
|
||||
export async function removeGenerator(tree: Tree, schema: Schema) {
|
||||
const project = readProjectConfiguration(tree, schema.projectName);
|
||||
checkDependencies(tree, schema);
|
||||
checkTargets(tree, schema);
|
||||
removeProject(tree, project);
|
||||
removeProjectConfig(tree, schema);
|
||||
updateTsconfig(tree, schema, project);
|
||||
updateJestConfig(tree, schema);
|
||||
if (!schema.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
}
|
||||
|
||||
export const removeGenerator = wrapAngularDevkitSchematic(
|
||||
'@nrwl/workspace',
|
||||
'remove'
|
||||
);
|
||||
export default removeGenerator;
|
||||
|
||||
export const removeSchematic = convertNxGenerator(removeGenerator);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"id": "NxWorkspaceRemove",
|
||||
"cli": "nx",
|
||||
"title": "Nx Remove",
|
||||
"description": "Remove a project from the workspace",
|
||||
"type": "object",
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import {
|
||||
getWorkspacePath,
|
||||
readJson,
|
||||
Tree,
|
||||
visitNotIgnoredFiles,
|
||||
} from '@nrwl/devkit';
|
||||
import { createProjectGraph } from '../core/project-graph/project-graph';
|
||||
import { FileData } from '../core/file-utils';
|
||||
import { extname } from 'path';
|
||||
|
||||
export function createProjectGraphFromTree(tree: Tree) {
|
||||
const workspaceJson = readJson(tree, getWorkspacePath(tree));
|
||||
const nxJson = readJson(tree, 'nx.json');
|
||||
|
||||
const files: FileData[] = [];
|
||||
|
||||
visitNotIgnoredFiles(tree, '', (file) => {
|
||||
files.push({
|
||||
file: file,
|
||||
ext: extname(file),
|
||||
hash: '',
|
||||
});
|
||||
});
|
||||
|
||||
const readFile = (path) => {
|
||||
return tree.read(path).toString('utf-8');
|
||||
};
|
||||
|
||||
return createProjectGraph(
|
||||
workspaceJson,
|
||||
nxJson,
|
||||
files,
|
||||
readFile,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user