-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): add move generator (#588)
- Loading branch information
1 parent
fe639d7
commit d2a1d85
Showing
9 changed files
with
327 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# @nx-dotnet/core:move | ||
|
||
## @nx-dotnet/core:move | ||
|
||
Moves {projectName} to {destination}. Renames the Nx project to match the new folder location. Additionally, updates any .csproj, .vbproj, .fsproj, or .sln files which pointed to the project. | ||
|
||
## Options | ||
|
||
### <span className="required">projectName</span> | ||
|
||
- (string): Name of the project to move | ||
|
||
### <span className="required">destination</span> | ||
|
||
- (string): Where should it be moved to? | ||
|
||
### relativeToRoot | ||
|
||
- (boolean): If true, the destination path is relative to the root rather than the workspace layout from nx.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; | ||
import { | ||
Tree, | ||
readProjectConfiguration, | ||
addProjectConfiguration, | ||
joinPathFragments, | ||
names, | ||
} from '@nrwl/devkit'; | ||
import { uniq } from '@nrwl/nx-plugin/testing'; | ||
|
||
import generator from './generator'; | ||
import { basename } from 'path'; | ||
|
||
describe('move generator', () => { | ||
let tree: Tree; | ||
|
||
beforeEach(() => { | ||
tree = createTreeWithEmptyWorkspace({ | ||
layout: 'apps-libs', | ||
}); | ||
}); | ||
|
||
it('should move simple projects successfully', async () => { | ||
const { project } = makeSimpleProject(tree, 'app'); | ||
const destination = uniq('app'); | ||
await generator(tree, { projectName: project, destination }); | ||
const config = readProjectConfiguration(tree, destination); | ||
expect(config).toBeDefined(); | ||
expect(tree.exists(`apps/${destination}/readme.md`)).toBeTruthy(); | ||
}); | ||
|
||
it('should move simple projects down a directory', async () => { | ||
const { project } = makeSimpleProject(tree, 'app', 'apps/libs/test'); | ||
const destination = uniq('app'); | ||
await generator(tree, { projectName: project, destination }); | ||
const config = readProjectConfiguration(tree, destination); | ||
expect(config).toBeDefined(); | ||
expect(tree.exists(`apps/${destination}/readme.md`)).toBeTruthy(); | ||
expect(tree.exists(`apps/libs/test/readme.md`)).toBeFalsy(); | ||
}); | ||
|
||
it('should move simple projects up a directory', async () => { | ||
const { project } = makeSimpleProject(tree, 'app', 'apps/test'); | ||
const destination = joinPathFragments('test', 'nested', uniq('app')); | ||
await generator(tree, { projectName: project, destination }); | ||
const config = readProjectConfiguration( | ||
tree, | ||
destination.replace(/[\\|/]/g, '-'), | ||
); | ||
expect(config).toBeDefined(); | ||
expect(tree.exists(`apps/${destination}/readme.md`)).toBeTruthy(); | ||
expect(tree.exists(`apps/test/readme.md`)).toBeFalsy(); | ||
}); | ||
|
||
it('should update references in .csproj files', async () => { | ||
const { project, root } = makeSimpleProject(tree, 'app', 'apps/test'); | ||
const csProjPath = 'apps/other/Other.csproj'; | ||
tree.write( | ||
csProjPath, | ||
`<Project Sdk="Microsoft.NET.Sdk"> | ||
<ItemGroup> | ||
<ProjectReference Include="../test/${names(project).className}.csproj" /> | ||
</ItemGroup> | ||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<RootNamespace>test_lib2</RootNamespace> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
</Project> | ||
`, | ||
); | ||
const destination = joinPathFragments(uniq('app')); | ||
console.log(tree.read(csProjPath)?.toString()); | ||
await generator(tree, { projectName: project, destination }); | ||
const config = readProjectConfiguration( | ||
tree, | ||
destination.replace(/[\\|/]/g, '-'), | ||
); | ||
expect(config).toBeDefined(); | ||
expect(tree.exists(`apps/${destination}/readme.md`)).toBeTruthy(); | ||
expect(tree.exists(`apps/test/readme.md`)).toBeFalsy(); | ||
const updatedCsProj = tree.read(csProjPath)?.toString(); | ||
expect(updatedCsProj).not.toContain(root); | ||
expect(updatedCsProj).not.toContain(project); | ||
expect(updatedCsProj).toContain(basename(destination)); | ||
}); | ||
}); | ||
|
||
function makeSimpleProject(tree: Tree, type: 'app' | 'lib', path?: string) { | ||
const project = uniq(type); | ||
const root = path ? path.replaceAll('{n}', project) : `${type}s/${project}`; | ||
addProjectConfiguration(tree, project, { | ||
root: root, | ||
projectType: type === 'app' ? 'application' : 'library', | ||
targets: { 'my-target': { executor: 'nx:noop' } }, | ||
}); | ||
tree.write(joinPathFragments(root, 'readme.md'), 'contents'); | ||
return { project, root }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { | ||
addProjectConfiguration, | ||
formatFiles, | ||
getWorkspaceLayout, | ||
joinPathFragments, | ||
names, | ||
normalizePath, | ||
ProjectConfiguration, | ||
readProjectConfiguration, | ||
removeProjectConfiguration, | ||
Tree, | ||
visitNotIgnoredFiles, | ||
} from '@nrwl/devkit'; | ||
import { dirname, extname, relative } from 'path'; | ||
import { MoveGeneratorSchema } from './schema'; | ||
|
||
type NormalizedSchema = { | ||
currentRoot: string; | ||
destinationRoot: string; | ||
currentProject: string; | ||
destinationProject: string; | ||
}; | ||
|
||
function normalizeOptions( | ||
tree: Tree, | ||
options: MoveGeneratorSchema, | ||
): NormalizedSchema { | ||
const { appsDir, libsDir } = getWorkspaceLayout(tree); | ||
const currentRoot = readProjectConfiguration(tree, options.projectName).root; | ||
let destinationRoot = options.destination; | ||
if (!options.relativeToRoot) { | ||
if (currentRoot.startsWith(appsDir)) { | ||
destinationRoot = joinPathFragments( | ||
appsDir, | ||
options.destination.replace(new RegExp(`^${appsDir}`), ''), | ||
); | ||
} else if (currentRoot.startsWith(libsDir)) { | ||
destinationRoot = joinPathFragments( | ||
libsDir, | ||
options.destination.replace(new RegExp(`^${libsDir}`), ''), | ||
); | ||
} | ||
} | ||
|
||
return { | ||
currentRoot, | ||
destinationRoot, | ||
currentProject: options.projectName, | ||
destinationProject: names(options.destination).fileName.replace( | ||
/[\\|/]/g, | ||
'-', | ||
), | ||
}; | ||
} | ||
|
||
export default async function (tree: Tree, options: MoveGeneratorSchema) { | ||
const normalizedOptions = normalizeOptions(tree, options); | ||
const config = readProjectConfiguration( | ||
tree, | ||
normalizedOptions.currentProject, | ||
); | ||
config.root = normalizedOptions.destinationRoot; | ||
config.name = normalizedOptions.destinationProject; | ||
removeProjectConfiguration(tree, normalizedOptions.currentProject); | ||
renameDirectory( | ||
tree, | ||
normalizedOptions.currentRoot, | ||
normalizedOptions.destinationRoot, | ||
); | ||
addProjectConfiguration( | ||
tree, | ||
options.projectName, | ||
transformConfiguration(config, normalizedOptions), | ||
); | ||
updateXmlReferences(tree, normalizedOptions); | ||
await formatFiles(tree); | ||
} | ||
|
||
function transformConfiguration( | ||
config: ProjectConfiguration, | ||
options: NormalizedSchema, | ||
) { | ||
return updateReferencesInObject(config, options); | ||
} | ||
|
||
function updateReferencesInObject< | ||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
T extends Object | Array<unknown>, | ||
>(object: T, options: NormalizedSchema): T { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const newValue: any = Array.isArray(object) | ||
? ([] as unknown as T) | ||
: ({} as T); | ||
for (const key in object) { | ||
if (typeof object[key] === 'string') { | ||
newValue[key] = (object[key] as string).replace( | ||
options.currentProject, | ||
options.destinationRoot, | ||
); | ||
} else if (typeof object[key] === 'object') { | ||
newValue[key] = updateReferencesInObject(object[key] as T, options); | ||
} else { | ||
newValue[key] = object[key]; | ||
} | ||
} | ||
return newValue; | ||
} | ||
|
||
function updateXmlReferences(tree: Tree, options: NormalizedSchema) { | ||
visitNotIgnoredFiles(tree, '.', (path) => { | ||
const extension = extname(path); | ||
const directory = dirname(path); | ||
if (['.csproj', '.vbproj', '.fsproj', '.sln'].includes(extension)) { | ||
const contents = tree.read(path); | ||
if (!contents) { | ||
return; | ||
} | ||
const pathToUpdate = normalizePath( | ||
relative(directory, options.currentRoot), | ||
); | ||
const pathToUpdateWithWindowsSeparators = normalizePath( | ||
relative(directory, options.currentRoot), | ||
).replaceAll('/', '\\'); | ||
const newPath = normalizePath( | ||
relative(directory, options.destinationRoot), | ||
); | ||
|
||
console.log({ pathToUpdate, newPath }); | ||
|
||
tree.write( | ||
path, | ||
contents | ||
.toString() | ||
.replaceAll(pathToUpdate, newPath) | ||
.replaceAll(pathToUpdateWithWindowsSeparators, newPath), | ||
); | ||
} | ||
}); | ||
} | ||
|
||
function renameDirectory(tree: Tree, from: string, to: string) { | ||
const children = tree.children(from); | ||
for (const child of children) { | ||
const childFrom = joinPathFragments(from, child); | ||
const childTo = joinPathFragments(to, child); | ||
if (tree.isFile(childFrom)) { | ||
tree.rename(childFrom, childTo); | ||
} else { | ||
renameDirectory(tree, childFrom, childTo); | ||
} | ||
} | ||
if (!to.startsWith(from)) { | ||
tree.delete(from); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export interface MoveGeneratorSchema { | ||
projectName: string; | ||
destination: string; | ||
relativeToRoot?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"$schema": "http://json-schema.org/schema", | ||
"cli": "nx", | ||
"$id": "Move", | ||
"title": "@nx-dotnet/core:move", | ||
"description": "Moves {projectName} to {destination}. Renames the Nx project to match the new folder location. Additionally, updates any .csproj, .vbproj, .fsproj, or .sln files which pointed to the project.", | ||
"type": "object", | ||
"properties": { | ||
"projectName": { | ||
"type": "string", | ||
"description": "Name of the project to move", | ||
"$default": { | ||
"$source": "argv", | ||
"index": 0 | ||
}, | ||
"x-prompt": "What name would you like to use?", | ||
"x-dropdown": "projects" | ||
}, | ||
"destination": { | ||
"type": "string", | ||
"description": "Where should it be moved to?", | ||
"$default": { | ||
"$source": "argv", | ||
"index": 0 | ||
}, | ||
"x-prompt": "What name would you like to use?" | ||
}, | ||
"relativeToRoot": { | ||
"type": "boolean", | ||
"description": "If true, the destination path is relative to the root rather than the workspace layout from nx.json", | ||
"default": false | ||
} | ||
}, | ||
"required": ["projectName", "destination"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters