diff --git a/packages/devkit/src/utils/invoke-nx-generator.ts b/packages/devkit/src/utils/invoke-nx-generator.ts index 249c16ed5670d..b55ddb63cc232 100644 --- a/packages/devkit/src/utils/invoke-nx-generator.ts +++ b/packages/devkit/src/utils/invoke-nx-generator.ts @@ -4,10 +4,13 @@ import type { Tree, TreeWriteOptions, } from '@nrwl/tao/src/shared/tree'; -import type { +import { Generator, GeneratorCallback, + toNewFormat, + toOldFormatOrNull, } from '@nrwl/tao/src/shared/workspace'; +import { parseJson, serializeJson } from '@nrwl/tao/src/utils/json'; import { join, relative } from 'path'; class RunCallbackTask { @@ -79,6 +82,8 @@ const actionToFileChangeMap = { }; class DevkitTreeFromAngularDevkitTree implements Tree { + private configFileName: string; + constructor(private tree, private _root: string) {} get root(): string { @@ -144,9 +149,16 @@ class DevkitTreeFromAngularDevkitTree implements Tree { read(filePath: string): Buffer; read(filePath: string, encoding: BufferEncoding): string; read(filePath: string, encoding?: BufferEncoding) { - return encoding + const rawResult = encoding ? this.tree.read(filePath).toString(encoding) : this.tree.read(filePath); + if (isWorkspaceJsonChange(filePath)) { + const formatted = toNewFormat( + parseJson(Buffer.isBuffer(rawResult) ? rawResult.toString() : rawResult) + ); + return encoding ? serializeJson(formatted) : serializeJson(formatted); + } + return rawResult; } rename(from: string, to: string): void { @@ -162,6 +174,20 @@ class DevkitTreeFromAngularDevkitTree implements Tree { this.warnUnsupportedFilePermissionsChange(filePath, options.mode); } + if (isWorkspaceJsonChange(filePath)) { + const w = parseJson(content.toString()); + for (const [project, configuration] of Object.entries(w.projects)) { + if (typeof configuration === 'string') { + w.projects[project] = parseJson( + this.tree.read(`${configuration}/project.json`) + ); + w.projects[project].configFilePath = `${configuration}/project.json`; + } + } + const formatted = toOldFormatOrNull(w); + content = serializeJson(formatted ? formatted : w); + } + if (this.tree.exists(filePath)) { this.tree.overwrite(filePath, content); } else { @@ -183,3 +209,12 @@ class DevkitTreeFromAngularDevkitTree implements Tree { ); } } + +function isWorkspaceJsonChange(path) { + return ( + path === 'workspace.json' || + path === '/workspace.json' || + path === 'angular.json' || + path === '/angular.json' + ); +} diff --git a/packages/tao/src/commands/ngcli-adapter.ts b/packages/tao/src/commands/ngcli-adapter.ts index 6e9f74d6dc6dd..9f4132109ae95 100644 --- a/packages/tao/src/commands/ngcli-adapter.ts +++ b/packages/tao/src/commands/ngcli-adapter.ts @@ -279,11 +279,7 @@ export class NxScopedHost extends virtualFs.ScopedHost { .toPromise(); } - protected context(path: string): Observable<{ - isWorkspaceConfig: boolean; - actualConfigFileName: any; - isNewFormat: boolean; - }> { + protected context(path: string): Observable { if (isWorkspaceConfigPath(path)) { return super.exists('/angular.json' as any).pipe( switchMap((isAngularJson) => { @@ -319,60 +315,67 @@ export class NxScopedHost extends virtualFs.ScopedHost { } } - private writeWorkspaceConfiguration(context, content): Observable { + private writeWorkspaceConfiguration( + context: ChangeContext, + content + ): Observable { const config = parseJson(Buffer.from(content).toString()); if (context.isNewFormat) { try { const w = parseJson(Buffer.from(content).toString()); const formatted = toNewFormatOrNull(w); if (formatted) { - return this.writeWorkspaceConfigFiles( - context.actualConfigFileName, - formatted - ); + return this.writeWorkspaceConfigFiles(context, formatted); } else { - return this.writeWorkspaceConfigFiles( - context.actualConfigFileName, - config - ); + return this.writeWorkspaceConfigFiles(context, config); } } catch (e) { - return this.writeWorkspaceConfigFiles( - context.actualConfigFileName, - config - ); + return this.writeWorkspaceConfigFiles(context, config); } } else { - return this.writeWorkspaceConfigFiles( - context.actualConfigFileName, - config - ); + return this.writeWorkspaceConfigFiles(context, config); } } - private writeWorkspaceConfigFiles(workspaceFileName, config) { + private writeWorkspaceConfigFiles( + { actualConfigFileName: workspaceFileName, isNewFormat }: ChangeContext, + config + ) { // copy to avoid removing inlined config files. + let writeObservable: Observable; const configToWrite = { ...config, projects: { ...config.projects }, }; - - Object.entries(configToWrite.projects as Record).forEach( - ([project, projectConfig]) => { - if (projectConfig.configFilePath) { - // project was read from a project.json file - const configPath = projectConfig.configFilePath; - const fileConfigObject = { ...projectConfig }; - delete fileConfigObject.configFilePath; // remove the configFilePath before writing - super.write(configPath, Buffer.from(serializeJson(fileConfigObject))); // write back to the project.json file - configToWrite.projects[project] = normalize(dirname(configPath)); // update the config object to point to the written file. + const projects: [string, any][] = Object.entries(configToWrite.projects); + for (const [project, projectConfig] of projects) { + if (projectConfig.configFilePath) { + if (!isNewFormat) { + throw new Error( + 'Attempted to write standalone project configuration into a v1 workspace' + ); } + // project was read from a project.json file + const configPath = projectConfig.configFilePath; + const fileConfigObject = { ...projectConfig }; + delete fileConfigObject.configFilePath; // remove the configFilePath before writing + const projectJsonWrite = super.write( + configPath, + Buffer.from(serializeJson(fileConfigObject)) + ); // write back to the project.json file + writeObservable = writeObservable + ? concat(writeObservable, projectJsonWrite) + : projectJsonWrite; + configToWrite.projects[project] = normalize(dirname(configPath)); // update the config object to point to the written file. } - ); - return super.write( + } + const workspaceJsonWrite = super.write( workspaceFileName, Buffer.from(serializeJson(configToWrite)) ); + return writeObservable + ? concat(writeObservable, workspaceJsonWrite) + : workspaceJsonWrite; } protected resolveInlineProjectConfigurations(config: { @@ -419,6 +422,12 @@ export class NxScopedHost extends virtualFs.ScopedHost { } } +type ChangeContext = { + isWorkspaceConfig: boolean; + actualConfigFileName: any; + isNewFormat: boolean; +}; + /** * This host contains the workaround needed to run Angular migrations */ diff --git a/packages/tao/src/shared/workspace.ts b/packages/tao/src/shared/workspace.ts index 0be6409e27d60..16fcc7caf5e24 100644 --- a/packages/tao/src/shared/workspace.ts +++ b/packages/tao/src/shared/workspace.ts @@ -572,9 +572,12 @@ export function toOldFormatOrNull(w: any) { export function resolveOldFormatWithInlineProjects( w: any, - root: string = appRootPath + root: string = appRootPath, + includeConfigFilePath = false ) { - return toOldFormatOrNull(inlineProjectConfigurations(w, root)); + const inlined = inlineProjectConfigurations(w, root, includeConfigFilePath); + const formatted = toOldFormatOrNull(inlined); + return formatted ? formatted : inlined; } export function resolveNewFormatWithInlineProjects( @@ -586,13 +589,17 @@ export function resolveNewFormatWithInlineProjects( export function inlineProjectConfigurations( w: any, - root: string = appRootPath + root: string = appRootPath, + includeConfigFilePath = false ) { Object.entries(w.projects || {}).forEach( ([project, config]: [string, any]) => { if (typeof config === 'string') { const configFilePath = path.join(root, config, 'project.json'); const fileConfig = readJsonFile(configFilePath); + if (includeConfigFilePath) { + fileConfig.configFilePath = configFilePath; + } w.projects[project] = fileConfig; } }