diff --git a/src/cdk/schematics/ng-update/devkit-migration-rule.ts b/src/cdk/schematics/ng-update/devkit-migration-rule.ts index 788f64be8a26..63e19c73d767 100644 --- a/src/cdk/schematics/ng-update/devkit-migration-rule.ts +++ b/src/cdk/schematics/ng-update/devkit-migration-rule.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; +import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks'; import {WorkspaceProject} from '@schematics/angular/utility/workspace-models'; import {sync as globSync} from 'glob'; @@ -17,6 +17,7 @@ import {MigrationCtor} from '../update-tool/migration'; import {TargetVersion} from '../update-tool/target-version'; import {WorkspacePath} from '../update-tool/file-system'; import {getTargetTsconfigPath, getWorkspaceConfigGracefully} from '../utils/project-tsconfig-paths'; +import {getWorkspaceFileSystemPath} from '../utils/workspace-fs-path'; import {DevkitFileSystem} from './devkit-file-system'; import {DevkitContext, DevkitMigration, DevkitMigrationCtor} from './devkit-migration'; @@ -70,12 +71,21 @@ export function createMigrationSchematicRule( return; } + const workspaceFsPath = getWorkspaceFileSystemPath(tree); + + // The workspace path could be `null` if the CLI changed the internals of the + // virtual file system tree. We would want users to create an issue for this + // if we for some reason did not catch the internal API change. + if (workspaceFsPath === null) { + throw new SchematicsException( + 'Could not determine workspace file system path. Please report an issue ' + + 'in the Angular Components repository with details on your Angular CLI version.'); + } + // Keep track of all project source files which have been checked/migrated. This is // necessary because multiple TypeScript projects can contain the same source file and // we don't want to check these again, as this would result in duplicated failure messages. const analyzedFiles = new Set(); - // The CLI uses the working directory as the base directory for the virtual file system tree. - const workspaceFsPath = process.cwd(); const fileSystem = new DevkitFileSystem(tree, workspaceFsPath); const projectNames = Object.keys(workspace.projects); const migrations: NullableDevkitMigration[] = [...cdkMigrations, ...extraMigrations]; @@ -124,11 +134,11 @@ export function createMigrationSchematicRule( /** Runs the migrations for the specified workspace project. */ function runMigrations(project: WorkspaceProject, projectName: string, tsconfigPath: string, isTestTarget: boolean) { - const projectRootFsPath = join(workspaceFsPath, project.root); - const tsconfigFsPath = join(workspaceFsPath, tsconfigPath); + const projectRootFsPath = join(workspaceFsPath!, project.root); + const tsconfigFsPath = join(workspaceFsPath!, tsconfigPath); const program = UpdateProject.createProgramFromTsconfig(tsconfigFsPath, fileSystem); const updateContext: DevkitContext = { - workspaceFsPath, + workspaceFsPath: workspaceFsPath!, isTestTarget, projectName, project, diff --git a/src/cdk/schematics/ng-update/test-cases/misc/working-directory.spec.ts b/src/cdk/schematics/ng-update/test-cases/misc/working-directory.spec.ts new file mode 100644 index 000000000000..972b75dcf40f --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/misc/working-directory.spec.ts @@ -0,0 +1,30 @@ +import {join} from 'path'; +import {MIGRATION_PATH} from '../../../index.spec'; +import {createTestCaseSetup} from '../../../testing'; + +describe('ng-update working directory', () => { + + it('should migrate project if working directory is not workspace root', async () => { + const {runFixers, writeFile, removeTempDir, tempPath, appTree} = await createTestCaseSetup( + 'migration-v6', MIGRATION_PATH, []); + const stylesheetPath = 'projects/cdk-testing/src/test-cases/global-stylesheets-test.scss'; + + // Write a stylesheet that will be captured by the migration. + writeFile(stylesheetPath, ` + [cdkPortalHost] { + color: red; + } + `); + + // We want to switch into a given project directory. This means that the devkit + // file system tree is at workspace root while the working directory is not the + // workspace directory as previously assumed. See the following issue for more details: + // https://github.com/angular/components/issues/19779 + await runFixers(join(tempPath, 'projects/cdk-testing')); + + expect(appTree.readContent(stylesheetPath)).not + .toContain('[cdkPortalHost]', 'Expected migration to change stylesheet contents.'); + + removeTempDir(); + }); +}); diff --git a/src/cdk/schematics/testing/test-case-setup.ts b/src/cdk/schematics/testing/test-case-setup.ts index 7d828ff1df7e..74dfaf21c207 100644 --- a/src/cdk/schematics/testing/test-case-setup.ts +++ b/src/cdk/schematics/testing/test-case-setup.ts @@ -104,10 +104,10 @@ export async function createTestCaseSetup(migrationName: string, collectionPath: writeFile(testAppTsconfigPath, JSON.stringify(testAppTsconfig, null, 2)); - const runFixers = async function() { - // Switch to the new temporary directory to simulate that "ng update" is ran - // from within the project. - process.chdir(tempPath); + const runFixers = async function(workingDir = tempPath) { + // Switch to the given working directory. By default, switches to the workspace + // root to simulate the common `ng update` usage pattern. + process.chdir(workingDir); // Patch "executePostTasks" to do nothing. This is necessary since // we cannot run the node install task in unit tests. Rather we just diff --git a/src/cdk/schematics/utils/workspace-fs-path.ts b/src/cdk/schematics/utils/workspace-fs-path.ts new file mode 100644 index 000000000000..9352ad797342 --- /dev/null +++ b/src/cdk/schematics/utils/workspace-fs-path.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {getSystemPath} from '@angular-devkit/core'; +import {HostDirEntry, HostTree, Tree} from '@angular-devkit/schematics'; + +/** + * Gets the workspace file system path from the given tree. Returns + * `null` if the path could not be determined. + */ +// TODO: Remove this once we have a fully virtual TypeScript compiler host: COMP-387 +export function getWorkspaceFileSystemPath(tree: Tree): string|null { + if (!(tree.root instanceof HostDirEntry)) { + return null; + } + const hostTree = tree.root['_tree']; + if (!(hostTree instanceof HostTree) || hostTree['_backend'] === undefined) { + return null; + } + const backend = hostTree['_backend']; + if (backend['_root'] !== undefined) { + return getSystemPath(backend['_root']); + } + return null; +}