diff --git a/packages/react/src/migrations/update-8-5-0/update-workspace-8-5-0.spec.ts b/packages/react/src/migrations/update-8-5-0/update-workspace-8-5-0.spec.ts index d2071766d1..82fea02c5d 100644 --- a/packages/react/src/migrations/update-8-5-0/update-workspace-8-5-0.spec.ts +++ b/packages/react/src/migrations/update-8-5-0/update-workspace-8-5-0.spec.ts @@ -1,12 +1,8 @@ import { Tree } from '@angular-devkit/schematics'; import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; -import { - updateJsonInTree, - readJsonInTree, - updateWorkspaceInTree, - readWorkspace, - getWorkspacePath, -} from '@nrwl/workspace'; +import { readWorkspace } from '@nrwl/workspace'; + +import * as taoWorkspace from '@nrwl/tao/src/shared/workspace'; import * as path from 'path'; @@ -14,6 +10,12 @@ describe('Update 8-5-0', () => { let tree: Tree; let schematicRunner: SchematicTestRunner; + beforeAll(() => { + jest + .spyOn(taoWorkspace, 'workspaceConfigName') + .mockReturnValue('workspace.json'); + }); + beforeEach(async () => { tree = Tree.empty(); schematicRunner = new SchematicTestRunner( diff --git a/packages/tao/src/commands/ngcli-adapter.ts b/packages/tao/src/commands/ngcli-adapter.ts index b76f7a751e..afaf96ed1b 100644 --- a/packages/tao/src/commands/ngcli-adapter.ts +++ b/packages/tao/src/commands/ngcli-adapter.ts @@ -238,6 +238,11 @@ export class NxScopedHost extends virtualFs.ScopedHost { nxJson, staticProjects.filter((x) => basename(x) !== 'package.json') ); + Object.entries(this.__nxInMemoryWorkspace.projects).forEach( + ([project, config]) => { + this.__nxInMemoryWorkspace.projects[project] = config.root as any; + } + ); return of(this.__nxInMemoryWorkspace); } } @@ -359,7 +364,8 @@ export class NxScopedHost extends virtualFs.ScopedHost { return of({ isWorkspaceConfig: true, actualConfigFileName: null, - isNewFormat: false, + // AngularJson / WorkspaceJson v2 is always used for standalone project config + isNewFormat: true, }); } const actualConfigFileName = isAngularJson @@ -463,7 +469,7 @@ export class NxScopedHost extends virtualFs.ScopedHost { config ) { // copy to avoid removing inlined config files. - let writeObservable: Observable; + const writeObservables: Observable[] = []; const configToWrite = { ...config, projects: { ...config.projects }, @@ -471,7 +477,7 @@ export class NxScopedHost extends virtualFs.ScopedHost { const projects: [string, any][] = Object.entries(configToWrite.projects); for (const [project, projectConfig] of projects) { if (projectConfig.configFilePath) { - if (!isNewFormat) { + if (workspaceFileName && !isNewFormat) { throw new Error( 'Attempted to write standalone project configuration into a v1 workspace' ); @@ -484,9 +490,7 @@ export class NxScopedHost extends virtualFs.ScopedHost { configPath, Buffer.from(serializeJson(fileConfigObject)) ); // write back to the project.json file - writeObservable = writeObservable - ? merge(writeObservable, projectJsonWrite) - : projectJsonWrite; + writeObservables.push(projectJsonWrite); configToWrite.projects[project] = normalize(dirname(configPath)); // update the config object to point to the written file. } } @@ -496,11 +500,9 @@ export class NxScopedHost extends virtualFs.ScopedHost { workspaceFileName, Buffer.from(serializeJson(configToWrite)) ); - writeObservable = writeObservable - ? merge(writeObservable, workspaceJsonWrite) - : workspaceJsonWrite; + writeObservables.push(workspaceJsonWrite); } - return writeObservable; + return merge(...writeObservables); } protected resolveInlineProjectConfigurations( diff --git a/packages/tao/src/compat/compat.ts b/packages/tao/src/compat/compat.ts index 4d6364538d..27cf13f949 100644 --- a/packages/tao/src/compat/compat.ts +++ b/packages/tao/src/compat/compat.ts @@ -11,6 +11,7 @@ const Module = require('module'); const originalRequire = Module.prototype.require; let patched = false; +let loggedWriteWorkspaceWarning = false; if (!patched) { Module.prototype.require = function () { @@ -21,14 +22,34 @@ if (!patched) { `@angular-devkit/core/src/workspace/core`, ]); core._test_addWorkspaceFile('workspace.json', core.WorkspaceFormat.JSON); + const originalReadWorkspace = core.readWorkspace; + core.readWorkspace = (path, ...rest) => { + const configFile = workspaceConfigName(appRootPath); + if (!configFile) { + path = 'workspace.json'; + } + return originalReadWorkspace.apply(this, [path, ...rest]); + }; const originalWriteWorkspace = core.writeWorkspace; core.writeWorkspace = (...args) => { const configFile = workspaceConfigName(appRootPath); - logger.warn( - `[NX] An Angular builder called \`writeWorkspace\`, this may have had unintended consequences in ${configFile}` - ); - logger.warn(`[NX] Double check ${configFile} before proceeding`); - originalWriteWorkspace.apply(this, args); + if (!loggedWriteWorkspaceWarning) { + if (configFile) { + logger.warn( + `[NX] Angular devkit called \`writeWorkspace\`, this may have had unintended consequences in ${configFile}` + ); + logger.warn(`[NX] Double check ${configFile} before proceeding`); + } else { + logger.warn( + `[NX] Angular devkit called \`writeWorkspace\`, this may have created 'workspace.json' or 'angular.json` + ); + logger.warn( + `[NX] Double check workspace configuration before proceeding` + ); + } + loggedWriteWorkspaceWarning = true; + } + return originalWriteWorkspace.apply(this, args); }; // Patch readJsonWorkspace to inline project configurations @@ -37,20 +58,32 @@ if (!patched) { `@angular-devkit/core/src/workspace/json/reader`, ]); const originalReadJsonWorkspace = readJsonUtils.readJsonWorkspace; - readJsonUtils.readJsonWorkspace = () => { - // Read our v1 workspace schema - const w = resolveOldFormatWithInlineProjects( - new Workspaces(appRootPath).readWorkspaceConfiguration() - ); - // readJsonWorkspace actually has AST parsing + more, so we - // still need to call it rather than just return our file - return originalReadJsonWorkspace.apply(this, [ - 'workspace.json', // path name, doesn't matter - { - // second arg is a host, only method used is readFile - readFile: () => JSON.stringify(w), - }, - ]); + readJsonUtils.readJsonWorkspace = ( + path, + host: { readFile: (p) => Promise } + ) => { + try { + return originalReadJsonWorkspace(path, host); + } catch { + logger.debug( + '[NX] Angular devkit readJsonWorkspace fell back to Nx workspaces logic' + ); + const w = new Workspaces(appRootPath); + + // Read our v1 workspace schema + const workspaceConfiguration = resolveOldFormatWithInlineProjects( + w.readWorkspaceConfiguration() + ); + // readJsonWorkspace actually has AST parsing + more, so we + // still need to call it rather than just return our file + return originalReadJsonWorkspace.apply(this, [ + 'workspace.json', // path name, doesn't matter + { + // second arg is a host, only method used is readFile + readFile: () => JSON.stringify(workspaceConfiguration), + }, + ]); + } }; } return result; diff --git a/packages/tao/src/shared/workspace.ts b/packages/tao/src/shared/workspace.ts index c03a8eba6e..2e898b2dad 100644 --- a/packages/tao/src/shared/workspace.ts +++ b/packages/tao/src/shared/workspace.ts @@ -157,8 +157,10 @@ export interface TargetConfiguration { export function workspaceConfigName(root: string) { if (existsSync(path.join(root, 'angular.json'))) { return 'angular.json'; - } else { + } else if (existsSync(path.join(root, 'workspace.json'))) { return 'workspace.json'; + } else { + return null; } } @@ -290,14 +292,18 @@ export class Workspaces { const nxJson = existsSync(nxJsonPath) ? readJsonFile(nxJsonPath) : ({} as NxJsonConfiguration); - const workspacePath = path.join(this.root, workspaceConfigName(this.root)); - const workspace = existsSync(workspacePath) - ? this.readFromWorkspaceJson() - : buildWorkspaceConfigurationFromGlobs( - nxJson, - globForProjectFiles(this.root), - (path) => readJsonFile(join(this.root, path)) - ); + const workspaceFile = workspaceConfigName(this.root); + const workspacePath = workspaceFile + ? path.join(this.root, workspaceFile) + : null; + const workspace = + workspacePath && existsSync(workspacePath) + ? this.readFromWorkspaceJson() + : buildWorkspaceConfigurationFromGlobs( + nxJson, + globForProjectFiles(this.root), + (path) => readJsonFile(join(this.root, path)) + ); assertValidWorkspaceConfiguration(nxJson); return { ...workspace, ...nxJson }; @@ -701,6 +707,7 @@ export function deduplicateProjectFiles(files: string[], ig?: Ignore) { ) { return; } + filtered.set(projectFolder, projectFile); }); return Array.from(filtered.entries()).map(([folder, file]) => diff --git a/packages/workspace/src/migrations/update-8-12-0/add-implicit-e2e-deps.spec.ts b/packages/workspace/src/migrations/update-8-12-0/add-implicit-e2e-deps.spec.ts index c5e54046d0..ab169c20af 100644 --- a/packages/workspace/src/migrations/update-8-12-0/add-implicit-e2e-deps.spec.ts +++ b/packages/workspace/src/migrations/update-8-12-0/add-implicit-e2e-deps.spec.ts @@ -2,7 +2,6 @@ import { Tree } from '@angular-devkit/schematics'; import { createEmptyWorkspace } from '../../utils/testing-utils'; import { callRule, runMigration } from '../../utils/testing'; import { readJsonInTree, updateJsonInTree } from '../../utils/ast-utils'; -import type { NxJsonConfiguration } from '@nrwl/devkit'; describe('Update 8.12.0', () => { let tree: Tree; diff --git a/packages/workspace/src/migrations/update-8-12-0/add-implicit-e2e-deps.ts b/packages/workspace/src/migrations/update-8-12-0/add-implicit-e2e-deps.ts index ff0422c9ad..e6c219b690 100644 --- a/packages/workspace/src/migrations/update-8-12-0/add-implicit-e2e-deps.ts +++ b/packages/workspace/src/migrations/update-8-12-0/add-implicit-e2e-deps.ts @@ -9,11 +9,10 @@ import { stripIndents } from '@angular-devkit/core/src/utils/literals'; import { updateJsonInTree } from '../../utils/ast-utils'; import type { WorkspaceJsonConfiguration } from '@nrwl/devkit'; import { formatFiles } from '@nrwl/workspace/src/utils/rules/format-files'; -import { workspaceConfigName } from '@nrwl/tao/src/shared/workspace'; const addE2eImplicitDependencies: Rule = (tree: Tree) => updateJsonInTree( - workspaceConfigName(tree.root.path), + 'workspace.json', // ngcli-adapter should handle conversion of workspace file names (json) => { Object.keys(json.projects).forEach((proj) => { const implicitE2eName = proj.replace(/-e2e$/, ''); diff --git a/packages/workspace/src/utils/rules/format-files.spec.ts b/packages/workspace/src/utils/rules/format-files.spec.ts index a44798d109..a819e92a4b 100644 --- a/packages/workspace/src/utils/rules/format-files.spec.ts +++ b/packages/workspace/src/utils/rules/format-files.spec.ts @@ -3,12 +3,21 @@ import { Tree } from '@angular-devkit/schematics'; import * as prettier from 'prettier'; import * as path from 'path'; +import * as taoWorkspace from '@nrwl/tao/src/shared/workspace'; + import { formatFiles } from './format-files'; import { appRootPath } from '@nrwl/tao/src/utils/app-root'; describe('formatFiles', () => { let tree: Tree; let schematicRunner: SchematicTestRunner; + + beforeAll(() => { + jest + .spyOn(taoWorkspace, 'workspaceConfigName') + .mockReturnValue('workspace.json'); + }); + beforeEach(() => { schematicRunner = new SchematicTestRunner( '@nrwl/workspace', @@ -106,7 +115,7 @@ describe('formatFiles', () => { }); it('should have a readable error when workspace file cannot be formatted', async () => { - tree.delete('workspace.json'); + tree.overwrite('workspace.json', '{ invalidJson: true'); const errorSpy = jest.spyOn(console, 'error');