|
| 1 | +import { |
| 2 | + addProjectConfiguration, |
| 3 | + formatFiles, |
| 4 | + getWorkspaceLayout, |
| 5 | + joinPathFragments, |
| 6 | + names, |
| 7 | + normalizePath, |
| 8 | + ProjectConfiguration, |
| 9 | + readProjectConfiguration, |
| 10 | + removeProjectConfiguration, |
| 11 | + Tree, |
| 12 | + visitNotIgnoredFiles, |
| 13 | +} from '@nrwl/devkit'; |
| 14 | +import { dirname, extname, relative } from 'path'; |
| 15 | +import { MoveGeneratorSchema } from './schema'; |
| 16 | + |
| 17 | +type NormalizedSchema = { |
| 18 | + currentRoot: string; |
| 19 | + destinationRoot: string; |
| 20 | + currentProject: string; |
| 21 | + destinationProject: string; |
| 22 | +}; |
| 23 | + |
| 24 | +function normalizeOptions( |
| 25 | + tree: Tree, |
| 26 | + options: MoveGeneratorSchema, |
| 27 | +): NormalizedSchema { |
| 28 | + const { appsDir, libsDir } = getWorkspaceLayout(tree); |
| 29 | + const currentRoot = readProjectConfiguration(tree, options.projectName).root; |
| 30 | + let destinationRoot = options.destination; |
| 31 | + if (!options.relativeToRoot) { |
| 32 | + if (currentRoot.startsWith(appsDir)) { |
| 33 | + destinationRoot = joinPathFragments( |
| 34 | + appsDir, |
| 35 | + options.destination.replace(new RegExp(`^${appsDir}`), ''), |
| 36 | + ); |
| 37 | + } else if (currentRoot.startsWith(libsDir)) { |
| 38 | + destinationRoot = joinPathFragments( |
| 39 | + libsDir, |
| 40 | + options.destination.replace(new RegExp(`^${libsDir}`), ''), |
| 41 | + ); |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + return { |
| 46 | + currentRoot, |
| 47 | + destinationRoot, |
| 48 | + currentProject: options.projectName, |
| 49 | + destinationProject: names(options.destination).fileName.replace( |
| 50 | + /[\\|/]/g, |
| 51 | + '-', |
| 52 | + ), |
| 53 | + }; |
| 54 | +} |
| 55 | + |
| 56 | +export default async function (tree: Tree, options: MoveGeneratorSchema) { |
| 57 | + const normalizedOptions = normalizeOptions(tree, options); |
| 58 | + const config = readProjectConfiguration( |
| 59 | + tree, |
| 60 | + normalizedOptions.currentProject, |
| 61 | + ); |
| 62 | + config.root = normalizedOptions.destinationRoot; |
| 63 | + config.name = normalizedOptions.destinationProject; |
| 64 | + removeProjectConfiguration(tree, normalizedOptions.currentProject); |
| 65 | + renameDirectory( |
| 66 | + tree, |
| 67 | + normalizedOptions.currentRoot, |
| 68 | + normalizedOptions.destinationRoot, |
| 69 | + ); |
| 70 | + addProjectConfiguration( |
| 71 | + tree, |
| 72 | + options.projectName, |
| 73 | + transformConfiguration(config, normalizedOptions), |
| 74 | + ); |
| 75 | + updateXmlReferences(tree, normalizedOptions); |
| 76 | + await formatFiles(tree); |
| 77 | +} |
| 78 | + |
| 79 | +function transformConfiguration( |
| 80 | + config: ProjectConfiguration, |
| 81 | + options: NormalizedSchema, |
| 82 | +) { |
| 83 | + return updateReferencesInObject(config, options); |
| 84 | +} |
| 85 | + |
| 86 | +function updateReferencesInObject< |
| 87 | + // eslint-disable-next-line @typescript-eslint/ban-types |
| 88 | + T extends Object | Array<unknown>, |
| 89 | +>(object: T, options: NormalizedSchema): T { |
| 90 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 91 | + const newValue: any = Array.isArray(object) |
| 92 | + ? ([] as unknown as T) |
| 93 | + : ({} as T); |
| 94 | + for (const key in object) { |
| 95 | + if (typeof object[key] === 'string') { |
| 96 | + newValue[key] = (object[key] as string).replace( |
| 97 | + options.currentProject, |
| 98 | + options.destinationRoot, |
| 99 | + ); |
| 100 | + } else if (typeof object[key] === 'object') { |
| 101 | + newValue[key] = updateReferencesInObject(object[key] as T, options); |
| 102 | + } else { |
| 103 | + newValue[key] = object[key]; |
| 104 | + } |
| 105 | + } |
| 106 | + return newValue; |
| 107 | +} |
| 108 | + |
| 109 | +function updateXmlReferences(tree: Tree, options: NormalizedSchema) { |
| 110 | + visitNotIgnoredFiles(tree, '.', (path) => { |
| 111 | + const extension = extname(path); |
| 112 | + const directory = dirname(path); |
| 113 | + if (['.csproj', '.vbproj', '.fsproj', '.sln'].includes(extension)) { |
| 114 | + const contents = tree.read(path); |
| 115 | + if (!contents) { |
| 116 | + return; |
| 117 | + } |
| 118 | + const pathToUpdate = normalizePath( |
| 119 | + relative(directory, options.currentRoot), |
| 120 | + ); |
| 121 | + const pathToUpdateWithWindowsSeparators = normalizePath( |
| 122 | + relative(directory, options.currentRoot), |
| 123 | + ).replaceAll('/', '\\'); |
| 124 | + const newPath = normalizePath( |
| 125 | + relative(directory, options.destinationRoot), |
| 126 | + ); |
| 127 | + |
| 128 | + console.log({ pathToUpdate, newPath }); |
| 129 | + |
| 130 | + tree.write( |
| 131 | + path, |
| 132 | + contents |
| 133 | + .toString() |
| 134 | + .replaceAll(pathToUpdate, newPath) |
| 135 | + .replaceAll(pathToUpdateWithWindowsSeparators, newPath), |
| 136 | + ); |
| 137 | + } |
| 138 | + }); |
| 139 | +} |
| 140 | + |
| 141 | +function renameDirectory(tree: Tree, from: string, to: string) { |
| 142 | + const children = tree.children(from); |
| 143 | + for (const child of children) { |
| 144 | + const childFrom = joinPathFragments(from, child); |
| 145 | + const childTo = joinPathFragments(to, child); |
| 146 | + if (tree.isFile(childFrom)) { |
| 147 | + tree.rename(childFrom, childTo); |
| 148 | + } else { |
| 149 | + renameDirectory(tree, childFrom, childTo); |
| 150 | + } |
| 151 | + } |
| 152 | + if (!to.startsWith(from)) { |
| 153 | + tree.delete(from); |
| 154 | + } |
| 155 | +} |
0 commit comments