feat(core): remove readFile argument from createProjectGraph (#5206)

* feat(core): deprecate creating a project graph from a host

* feat(core): remove readFile argument from createProjectGraph
This commit is contained in:
Jason Jean 2021-04-06 13:44:09 -04:00 committed by GitHub
parent 4dac0a2122
commit bfb194843f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 384 additions and 244 deletions

View File

@ -6,6 +6,16 @@ import {
} from '@nrwl/workspace'; } from '@nrwl/workspace';
import { callRule, createEmptyWorkspace } from '@nrwl/workspace/testing'; import { callRule, createEmptyWorkspace } from '@nrwl/workspace/testing';
import { runMigration } from '../../utils/testing'; import { runMigration } from '../../utils/testing';
import {
DependencyType,
ProjectGraph,
} from '@nrwl/workspace/src/core/project-graph';
let projectGraph: ProjectGraph;
jest.mock('@nrwl/workspace/src/core/project-graph', () => ({
...jest.requireActual('@nrwl/workspace/src/core/project-graph'),
createProjectGraph: jest.fn().mockImplementation(() => projectGraph),
}));
describe('add-template-support-and-presets-to-eslint', () => { describe('add-template-support-and-presets-to-eslint', () => {
describe('tslint-only workspace', () => { describe('tslint-only workspace', () => {
@ -71,6 +81,66 @@ describe('add-template-support-and-presets-to-eslint', () => {
}), }),
tree tree
); );
projectGraph = {
nodes: {
app1: {
name: 'app1',
type: 'app',
data: {
files: [],
root: 'apps/app1',
},
},
app2: {
name: 'app2',
type: 'app',
data: {
files: [],
root: 'apps/app2',
},
},
lib1: {
name: 'lib1',
type: 'app',
data: {
files: [],
root: 'apps/lib1',
},
},
'npm:@angular/core': {
name: 'npm:@angular/core',
type: 'npm',
data: {
files: [],
},
},
},
dependencies: {
app1: [
{
type: DependencyType.static,
source: 'app1',
target: 'npm:@angular/core',
},
],
app2: [
{
type: DependencyType.static,
source: 'app2',
target: 'npm:@angular/core',
},
],
lib1: [
{
type: DependencyType.static,
source: 'lib1',
target: 'npm:@angular/core',
},
],
'npm:@angular/core': [],
},
};
}); });
it('should do nothing', async () => { it('should do nothing', async () => {

View File

@ -11,7 +11,7 @@ import {
} from '@nrwl/workspace'; } from '@nrwl/workspace';
import { join } from 'path'; import { join } from 'path';
import { offsetFromRoot } from '@nrwl/devkit'; import { offsetFromRoot } from '@nrwl/devkit';
import { getFullProjectGraphFromHost } from '@nrwl/workspace/src/utils/ast-utils'; import { createProjectGraph } from '@nrwl/workspace/src/core/project-graph';
/** /**
* It was decided with Jason that we would do a simple replacement in this migration * It was decided with Jason that we would do a simple replacement in this migration
@ -40,7 +40,7 @@ function addHTMLPatternToBuilderConfig(
} }
function updateProjectESLintConfigsAndBuilders(host: Tree): Rule { function updateProjectESLintConfigsAndBuilders(host: Tree): Rule {
const graph = getFullProjectGraphFromHost(host); const graph = createProjectGraph(undefined, undefined, undefined, false);
/** /**
* Make sure user is already using ESLint and is up to date with * Make sure user is already using ESLint and is up to date with

View File

@ -13,7 +13,7 @@ import enforceModuleBoundaries, {
import { TargetProjectLocator } from '@nrwl/workspace/src/core/target-project-locator'; import { TargetProjectLocator } from '@nrwl/workspace/src/core/target-project-locator';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
jest.mock('fs', () => require('memfs').fs); jest.mock('fs', () => require('memfs').fs);
jest.mock('@nrwl/workspace/src/utils/app-root', () => ({ jest.mock('../../../workspace/src/utilities/app-root', () => ({
appRootPath: '/root', appRootPath: '/root',
})); }));
@ -1166,8 +1166,7 @@ function runRule(
(global as any).npmScope = 'mycompany'; (global as any).npmScope = 'mycompany';
(global as any).projectGraph = projectGraph; (global as any).projectGraph = projectGraph;
(global as any).targetProjectLocator = new TargetProjectLocator( (global as any).targetProjectLocator = new TargetProjectLocator(
projectGraph.nodes, projectGraph.nodes
(path) => readFileSync(join('/root', path)).toString()
); );
const config = { const config = {

View File

@ -3,6 +3,16 @@ import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import * as path from 'path'; import * as path from 'path';
import { createEmptyWorkspace } from '@nrwl/workspace/testing'; import { createEmptyWorkspace } from '@nrwl/workspace/testing';
import { createApp, createLib, createWebApp } from '../utils/testing'; import { createApp, createLib, createWebApp } from '../utils/testing';
import {
DependencyType,
ProjectGraph,
} from '@nrwl/workspace/src/core/project-graph';
let projectGraph: ProjectGraph;
jest.mock('@nrwl/workspace/src/core/project-graph', () => ({
...jest.requireActual('@nrwl/workspace/src/core/project-graph'),
createProjectGraph: jest.fn().mockImplementation(() => projectGraph),
}));
describe('Migrate babel setup', () => { describe('Migrate babel setup', () => {
let tree: Tree; let tree: Tree;
@ -24,6 +34,51 @@ describe('Migrate babel setup', () => {
}, },
}) })
); );
projectGraph = {
nodes: {
demo: {
name: 'demo',
type: 'app',
data: {
root: 'apps/demo',
files: [],
},
},
ui: {
name: 'ui',
type: 'lib',
data: {
root: 'libs/ui',
files: [],
},
},
'npm:react': {
name: 'npm:react',
type: 'npm',
data: {
files: [],
packageName: 'react',
},
},
},
dependencies: {
demo: [
{
type: DependencyType.static,
source: 'demo',
target: 'npm:react',
},
],
ui: [
{
type: DependencyType.static,
source: 'ui',
target: 'npm:react',
},
],
'npm:react': [],
},
};
}); });
it(`should create .babelrc for projects without them`, async () => { it(`should create .babelrc for projects without them`, async () => {
@ -54,6 +109,8 @@ describe('Migrate babel setup', () => {
it(`should not migrate non-React projects`, async () => { it(`should not migrate non-React projects`, async () => {
tree = await createWebApp(tree, 'demo'); tree = await createWebApp(tree, 'demo');
projectGraph.dependencies.demo = [];
tree = await schematicRunner tree = await schematicRunner
.runSchematicAsync('babelrc-9.4.0', {}, tree) .runSchematicAsync('babelrc-9.4.0', {}, tree)
.toPromise(); .toPromise();

View File

@ -4,13 +4,13 @@ import {
SchematicContext, SchematicContext,
Tree, Tree,
} from '@angular-devkit/schematics'; } from '@angular-devkit/schematics';
import { getFullProjectGraphFromHost } from '@nrwl/workspace/src/utils/ast-utils';
import { import {
stripIndent, stripIndent,
stripIndents, stripIndents,
} from '@angular-devkit/core/src/utils/literals'; } from '@angular-devkit/core/src/utils/literals';
import { initRootBabelConfig } from '../utils/rules'; import { initRootBabelConfig } from '../utils/rules';
import { addDepsToPackageJson, formatFiles } from '@nrwl/workspace'; import { addDepsToPackageJson, formatFiles } from '@nrwl/workspace';
import { createProjectGraph } from '@nrwl/workspace/src/core/project-graph';
let addedEmotionPreset = false; let addedEmotionPreset = false;
@ -25,7 +25,12 @@ export default function update(): Rule {
return (host: Tree, context: SchematicContext) => { return (host: Tree, context: SchematicContext) => {
const updates = []; const updates = [];
const conflicts: Array<[string, string]> = []; const conflicts: Array<[string, string]> = [];
const projectGraph = getFullProjectGraphFromHost(host); const projectGraph = createProjectGraph(
undefined,
undefined,
undefined,
false
);
if (host.exists('/babel.config.json')) { if (host.exists('/babel.config.json')) {
context.logger.info( context.logger.info(
` `

View File

@ -1,6 +1,16 @@
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { readJson, Tree } from '@nrwl/devkit'; import { readJson, Tree } from '@nrwl/devkit';
import { createBabelrcForWorkspaceLibs } from './create-babelrc-for-workspace-libs'; import { createBabelrcForWorkspaceLibs } from './create-babelrc-for-workspace-libs';
import {
DependencyType,
ProjectGraph,
} from '@nrwl/workspace/src/core/project-graph';
let projectGraph: ProjectGraph;
jest.mock('@nrwl/workspace/src/core/project-graph', () => ({
...jest.requireActual('@nrwl/workspace/src/core/project-graph'),
createProjectGraph: jest.fn().mockImplementation(() => projectGraph),
}));
describe('Create missing .babelrc files', () => { describe('Create missing .babelrc files', () => {
let tree: Tree; let tree: Tree;
@ -53,6 +63,52 @@ describe('Create missing .babelrc files', () => {
); );
tree.write('apps/webapp/index.ts', `import '@proj/weblib';`); tree.write('apps/webapp/index.ts', `import '@proj/weblib';`);
projectGraph = {
nodes: {
webapp: {
name: 'webapp',
type: 'app',
data: {
files: [],
root: 'apps/webapp',
},
},
nodeapp: {
name: 'nodeapp',
type: 'app',
data: {
files: [],
root: 'apps/nodeapp',
},
},
weblib: {
name: 'weblib',
type: 'lib',
data: {
files: [],
root: 'libs/weblib',
},
},
nodelib: {
name: 'nodelib',
type: 'lib',
data: {
files: [],
root: 'libs/nodelib',
},
},
},
dependencies: {
webapp: [
{
type: DependencyType.static,
source: 'webapp',
target: 'weblib',
},
],
},
};
await createBabelrcForWorkspaceLibs(tree); await createBabelrcForWorkspaceLibs(tree);
expect(readJson(tree, 'libs/weblib/.babelrc')).toMatchObject({ expect(readJson(tree, 'libs/weblib/.babelrc')).toMatchObject({

View File

@ -1,11 +1,15 @@
import { formatFiles, getProjects, Tree } from '@nrwl/devkit'; import { formatFiles, getProjects, Tree } from '@nrwl/devkit';
import { reverse } from '@nrwl/workspace/src/core/project-graph'; import {
import { createProjectGraphFromTree } from '@nrwl/workspace/src/utilities/create-project-graph-from-tree'; createProjectGraph,
import { hasDependentAppUsingWebBuild } from '@nrwl/web/src/migrations/update-11-5-2/utils'; reverse,
} from '@nrwl/workspace/src/core/project-graph';
import { hasDependentAppUsingWebBuild } from './utils';
export async function createBabelrcForWorkspaceLibs(host: Tree) { export async function createBabelrcForWorkspaceLibs(host: Tree) {
const projects = getProjects(host); const projects = getProjects(host);
const graph = reverse(createProjectGraphFromTree(host)); const graph = reverse(
createProjectGraph(undefined, undefined, undefined, false)
);
for (const [name, p] of projects.entries()) { for (const [name, p] of projects.entries()) {
if (!hasDependentAppUsingWebBuild(name, graph, projects)) { if (!hasDependentAppUsingWebBuild(name, graph, projects)) {

View File

@ -3,13 +3,11 @@ import {
ProjectGraphContext, ProjectGraphContext,
ProjectGraphNodeRecords, ProjectGraphNodeRecords,
} from '../project-graph-models'; } from '../project-graph-models';
import { FileRead } from '../../file-utils';
export interface BuildDependencies { export interface BuildDependencies {
( (
ctx: ProjectGraphContext, ctx: ProjectGraphContext,
nodes: ProjectGraphNodeRecords, nodes: ProjectGraphNodeRecords,
addDependency: AddProjectDependency, addDependency: AddProjectDependency
fileRead: FileRead
): void; ): void;
} }

View File

@ -1,5 +1,5 @@
import { buildExplicitPackageJsonDependencies } from '@nrwl/workspace/src/core/project-graph/build-dependencies/explicit-package-json-dependencies'; import { buildExplicitPackageJsonDependencies } from '@nrwl/workspace/src/core/project-graph/build-dependencies/explicit-package-json-dependencies';
import { fs, vol } from 'memfs'; import { vol } from 'memfs';
import { import {
AddProjectDependency, AddProjectDependency,
DependencyType, DependencyType,
@ -8,7 +8,6 @@ import {
} from '../project-graph-models'; } from '../project-graph-models';
import { createProjectFileMap } from '../../file-graph'; import { createProjectFileMap } from '../../file-graph';
import { readWorkspaceFiles } from '../../file-utils'; import { readWorkspaceFiles } from '../../file-utils';
import { appRootPath } from '../../../utilities/app-root';
jest.mock('../../../utilities/app-root', () => ({ jest.mock('../../../utilities/app-root', () => ({
appRootPath: '/root', appRootPath: '/root',
@ -107,9 +106,7 @@ describe('explicit package json dependencies', () => {
} }
); );
buildExplicitPackageJsonDependencies(ctx, projects, addDependency, (s) => { buildExplicitPackageJsonDependencies(ctx, projects, addDependency);
return fs.readFileSync(`${appRootPath}/${s}`).toString();
});
expect(dependencyMap).toEqual({ expect(dependencyMap).toEqual({
proj: [ proj: [

View File

@ -4,20 +4,19 @@ import {
ProjectGraphContext, ProjectGraphContext,
ProjectGraphNodeRecords, ProjectGraphNodeRecords,
} from '../project-graph-models'; } from '../project-graph-models';
import { FileRead } from '../../file-utils'; import { defaultFileRead } from '../../file-utils';
import { parseJsonWithComments } from '@nrwl/workspace/src/utilities/fileutils'; import { parseJsonWithComments } from '@nrwl/workspace/src/utilities/fileutils';
import { joinPathFragments } from '@nrwl/devkit'; import { joinPathFragments } from '@nrwl/devkit';
export function buildExplicitPackageJsonDependencies( export function buildExplicitPackageJsonDependencies(
ctx: ProjectGraphContext, ctx: ProjectGraphContext,
nodes: ProjectGraphNodeRecords, nodes: ProjectGraphNodeRecords,
addDependency: AddProjectDependency, addDependency: AddProjectDependency
fileRead: FileRead
) { ) {
Object.keys(ctx.fileMap).forEach((source) => { Object.keys(ctx.fileMap).forEach((source) => {
Object.values(ctx.fileMap[source]).forEach((f) => { Object.values(ctx.fileMap[source]).forEach((f) => {
if (isPackageJsonAtProjectRoot(nodes, f.file)) { if (isPackageJsonAtProjectRoot(nodes, f.file)) {
processPackageJson(source, f.file, nodes, addDependency, fileRead); processPackageJson(source, f.file, nodes, addDependency);
} }
}); });
}); });
@ -38,11 +37,10 @@ function processPackageJson(
sourceProject: string, sourceProject: string,
fileName: string, fileName: string,
nodes: ProjectGraphNodeRecords, nodes: ProjectGraphNodeRecords,
addDependency: AddProjectDependency, addDependency: AddProjectDependency
fileRead: FileRead
) { ) {
try { try {
const deps = readDeps(parseJsonWithComments(fileRead(fileName))); const deps = readDeps(parseJsonWithComments(defaultFileRead(fileName)));
deps.forEach((d) => { deps.forEach((d) => {
// package.json refers to another project in the monorepo // package.json refers to another project in the monorepo
if (nodes[d]) { if (nodes[d]) {

View File

@ -196,9 +196,7 @@ describe('explicit project dependencies', () => {
} }
); );
buildExplicitTypeScriptDependencies(ctx, projects, addDependency, (s) => { buildExplicitTypeScriptDependencies(ctx, projects, addDependency);
return fs.readFileSync(`${appRootPath}/${s}`).toString();
});
expect(dependencyMap).toEqual({ expect(dependencyMap).toEqual({
proj1234: [ proj1234: [

View File

@ -6,16 +6,14 @@ import {
} from '../project-graph-models'; } from '../project-graph-models';
import { TypeScriptImportLocator } from './typescript-import-locator'; import { TypeScriptImportLocator } from './typescript-import-locator';
import { TargetProjectLocator } from '../../target-project-locator'; import { TargetProjectLocator } from '../../target-project-locator';
import { FileRead } from '../../file-utils';
export function buildExplicitTypeScriptDependencies( export function buildExplicitTypeScriptDependencies(
ctx: ProjectGraphContext, ctx: ProjectGraphContext,
nodes: ProjectGraphNodeRecords, nodes: ProjectGraphNodeRecords,
addDependency: AddProjectDependency, addDependency: AddProjectDependency
fileRead: FileRead
) { ) {
const importLocator = new TypeScriptImportLocator(fileRead); const importLocator = new TypeScriptImportLocator();
const targetProjectLocator = new TargetProjectLocator(nodes, fileRead); const targetProjectLocator = new TargetProjectLocator(nodes);
Object.keys(ctx.fileMap).forEach((source) => { Object.keys(ctx.fileMap).forEach((source) => {
Object.values(ctx.fileMap[source]).forEach((f) => { Object.values(ctx.fileMap[source]).forEach((f) => {
importLocator.fromFile( importLocator.fromFile(

View File

@ -2,14 +2,14 @@ import type * as ts from 'typescript';
import * as path from 'path'; import * as path from 'path';
import { DependencyType } from '../project-graph-models'; import { DependencyType } from '../project-graph-models';
import { stripSourceCode } from '../../../utilities/strip-source-code'; import { stripSourceCode } from '../../../utilities/strip-source-code';
import { FileRead } from '../../file-utils'; import { defaultFileRead } from '../../file-utils';
let tsModule: any; let tsModule: any;
export class TypeScriptImportLocator { export class TypeScriptImportLocator {
private readonly scanner: ts.Scanner; private readonly scanner: ts.Scanner;
constructor(private readonly fileRead: FileRead) { constructor() {
tsModule = require('typescript'); tsModule = require('typescript');
this.scanner = tsModule.createScanner(tsModule.ScriptTarget.Latest, false); this.scanner = tsModule.createScanner(tsModule.ScriptTarget.Latest, false);
} }
@ -31,7 +31,7 @@ export class TypeScriptImportLocator {
) { ) {
return; return;
} }
const content = this.fileRead(filePath); const content = defaultFileRead(filePath);
const strippedContent = stripSourceCode(this.scanner, content); const strippedContent = stripSourceCode(this.scanner, content);
if (strippedContent !== '') { if (strippedContent !== '') {
const tsFile = tsModule.createSourceFile( const tsFile = tsModule.createSourceFile(

View File

@ -1,6 +1,5 @@
import { AddProjectNode, ProjectGraphContext } from '../project-graph-models'; import { AddProjectNode, ProjectGraphContext } from '../project-graph-models';
import { FileRead } from '../../file-utils';
export interface BuildNodes { export interface BuildNodes {
(ctx: ProjectGraphContext, addNode: AddProjectNode, fileRead: FileRead): void; (ctx: ProjectGraphContext, addNode: AddProjectNode): void;
} }

View File

@ -1,13 +1,14 @@
import * as stripJsonComments from 'strip-json-comments'; import * as stripJsonComments from 'strip-json-comments';
import { ProjectGraphContext, AddProjectNode } from '../project-graph-models'; import { AddProjectNode, ProjectGraphContext } from '../project-graph-models';
import { FileRead } from '../../file-utils'; import { defaultFileRead } from '../../file-utils';
export function buildNpmPackageNodes( export function buildNpmPackageNodes(
ctx: ProjectGraphContext, ctx: ProjectGraphContext,
addNode: AddProjectNode, addNode: AddProjectNode
fileRead: FileRead
) { ) {
const packageJson = JSON.parse(stripJsonComments(fileRead('package.json'))); const packageJson = JSON.parse(
stripJsonComments(defaultFileRead('package.json'))
);
const deps = { const deps = {
...packageJson.dependencies, ...packageJson.dependencies,
...packageJson.devDependencies, ...packageJson.devDependencies,

View File

@ -1,9 +1,11 @@
import { AddProjectNode, ProjectGraphContext } from '../project-graph-models'; import { AddProjectNode, ProjectGraphContext } from '../project-graph-models';
import { FileRead } from '../../file-utils'; import { defaultFileRead } from '../../file-utils';
function convertNpmScriptsToTargets(projectRoot: string, fileRead: FileRead) { function convertNpmScriptsToTargets(projectRoot: string) {
try { try {
const packageJsonString = fileRead(`${projectRoot}/package.json`); const packageJsonString = defaultFileRead(
`${projectRoot}/package.json`
).toString();
const parsedPackagedJson = JSON.parse(packageJsonString); const parsedPackagedJson = JSON.parse(packageJsonString);
const res = {}; const res = {};
// handle no scripts // handle no scripts
@ -21,14 +23,16 @@ function convertNpmScriptsToTargets(projectRoot: string, fileRead: FileRead) {
} }
} }
export function buildWorkspaceProjectNodes(fileRead: FileRead) { export function buildWorkspaceProjectNodes(
return (ctx: ProjectGraphContext, addNode: AddProjectNode) => { ctx: ProjectGraphContext,
addNode: AddProjectNode
) {
const toAdd = []; const toAdd = [];
Object.keys(ctx.fileMap).forEach((key) => { Object.keys(ctx.fileMap).forEach((key) => {
const p = ctx.workspaceJson.projects[key]; const p = ctx.workspaceJson.projects[key];
if (!p.targets) { if (!p.targets) {
p.targets = convertNpmScriptsToTargets(p.root, fileRead); p.targets = convertNpmScriptsToTargets(p.root);
} }
// TODO, types and projectType should allign // TODO, types and projectType should allign
@ -68,5 +72,4 @@ export function buildWorkspaceProjectNodes(fileRead: FileRead) {
data: n.data, data: n.data,
}); });
}); });
};
} }

View File

@ -1,9 +1,7 @@
import { assertWorkspaceValidity } from '../assert-workspace-validity'; import { assertWorkspaceValidity } from '../assert-workspace-validity';
import { createProjectFileMap, ProjectFileMap } from '../file-graph'; import { createProjectFileMap, ProjectFileMap } from '../file-graph';
import { import {
defaultFileRead,
FileData, FileData,
FileRead,
filesChanged, filesChanged,
readNxJson, readNxJson,
readWorkspaceFiles, readWorkspaceFiles,
@ -37,7 +35,6 @@ export function createProjectGraph(
workspaceJson = readWorkspaceJson(), workspaceJson = readWorkspaceJson(),
nxJson = readNxJson(), nxJson = readNxJson(),
workspaceFiles = readWorkspaceFiles(), workspaceFiles = readWorkspaceFiles(),
fileRead: FileRead = defaultFileRead,
cache: false | ProjectGraphCache = readCache(), cache: false | ProjectGraphCache = readCache(),
shouldCache: boolean = true shouldCache: boolean = true
): ProjectGraph { ): ProjectGraph {
@ -63,7 +60,6 @@ export function createProjectGraph(
}; };
const projectGraph = buildProjectGraph( const projectGraph = buildProjectGraph(
ctx, ctx,
fileRead,
diff.partiallyConstructedProjectGraph diff.partiallyConstructedProjectGraph
); );
if (shouldCache) { if (shouldCache) {
@ -76,7 +72,7 @@ export function createProjectGraph(
nxJson: normalizedNxJson, nxJson: normalizedNxJson,
fileMap: projectFileMap, fileMap: projectFileMap,
}; };
const projectGraph = buildProjectGraph(ctx, fileRead, null); const projectGraph = buildProjectGraph(ctx, null);
if (shouldCache) { if (shouldCache) {
writeCache(rootFiles, projectGraph); writeCache(rootFiles, projectGraph);
} }
@ -97,13 +93,12 @@ function buildProjectGraph(
workspaceJson: any; workspaceJson: any;
fileMap: ProjectFileMap; fileMap: ProjectFileMap;
}, },
fileRead: FileRead,
projectGraph: ProjectGraph projectGraph: ProjectGraph
) { ) {
performance.mark('build project graph:start'); performance.mark('build project graph:start');
const builder = new ProjectGraphBuilder(projectGraph); const builder = new ProjectGraphBuilder(projectGraph);
const buildNodesFns: BuildNodes[] = [ const buildNodesFns: BuildNodes[] = [
buildWorkspaceProjectNodes(fileRead), buildWorkspaceProjectNodes,
buildNpmPackageNodes, buildNpmPackageNodes,
]; ];
const buildDependenciesFns: BuildDependencies[] = [ const buildDependenciesFns: BuildDependencies[] = [
@ -111,9 +106,9 @@ function buildProjectGraph(
buildImplicitProjectDependencies, buildImplicitProjectDependencies,
buildExplicitPackageJsonDependencies, buildExplicitPackageJsonDependencies,
]; ];
buildNodesFns.forEach((f) => f(ctx, builder.addNode.bind(builder), fileRead)); buildNodesFns.forEach((f) => f(ctx, builder.addNode.bind(builder)));
buildDependenciesFns.forEach((f) => buildDependenciesFns.forEach((f) =>
f(ctx, builder.nodes, builder.addDependency.bind(builder), fileRead) f(ctx, builder.nodes, builder.addDependency.bind(builder))
); );
const r = builder.build(); const r = builder.build();
performance.mark('build project graph:end'); performance.mark('build project graph:end');

View File

@ -1,5 +1,5 @@
import { resolveModuleByImport } from '../utilities/typescript'; import { resolveModuleByImport } from '../utilities/typescript';
import { defaultFileRead, FileRead, normalizedProjectRoot } from './file-utils'; import { defaultFileRead, normalizedProjectRoot } from './file-utils';
import { import {
ProjectGraphNode, ProjectGraphNode,
ProjectGraphNodeRecords, ProjectGraphNodeRecords,
@ -31,15 +31,12 @@ export class TargetProjectLocator {
private npmProjects = this.sortedProjects.filter(isNpmProject); private npmProjects = this.sortedProjects.filter(isNpmProject);
private tsConfigPath = this.getRootTsConfigPath(); private tsConfigPath = this.getRootTsConfigPath();
private absTsConfigPath = join(appRootPath, this.tsConfigPath); private absTsConfigPath = join(appRootPath, this.tsConfigPath);
private paths = parseJsonWithComments(this.fileRead(this.tsConfigPath)) private paths = parseJsonWithComments(defaultFileRead(this.tsConfigPath))
?.compilerOptions?.paths; ?.compilerOptions?.paths;
private typescriptResolutionCache = new Map<string, string | null>(); private typescriptResolutionCache = new Map<string, string | null>();
private npmResolutionCache = new Map<string, string | null>(); private npmResolutionCache = new Map<string, string | null>();
constructor( constructor(private nodes: ProjectGraphNodeRecords) {}
private nodes: ProjectGraphNodeRecords,
private fileRead: FileRead = defaultFileRead
) {}
/** /**
* Find a project based on its import * Find a project based on its import
@ -132,7 +129,7 @@ export class TargetProjectLocator {
private getRootTsConfigPath() { private getRootTsConfigPath() {
try { try {
this.fileRead('tsconfig.base.json'); defaultFileRead('tsconfig.base.json');
return 'tsconfig.base.json'; return 'tsconfig.base.json';
} catch (e) { } catch (e) {
return 'tsconfig.json'; return 'tsconfig.json';

View File

@ -7,6 +7,12 @@ import { Schema } from '../schema';
import { checkDependencies } from './check-dependencies'; import { checkDependencies } from './check-dependencies';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { libraryGenerator } from '../../library/library'; import { libraryGenerator } from '../../library/library';
import { DependencyType, ProjectGraph } from '../../../core/project-graph';
let projectGraph: ProjectGraph;
jest.mock('../../../core/project-graph', () => ({
...jest.requireActual('../../../core/project-graph'),
createProjectGraph: jest.fn().mockImplementation(() => projectGraph),
}));
describe('checkDependencies', () => { describe('checkDependencies', () => {
let tree: Tree; let tree: Tree;
@ -27,6 +33,36 @@ describe('checkDependencies', () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
}); });
projectGraph = {
nodes: {
'my-source': {
name: 'my-source',
type: 'lib',
data: {
files: [],
root: 'libs/my-source',
},
},
'my-dependent': {
name: 'my-dependent',
type: 'lib',
data: {
files: [],
root: 'libs/my-dependent',
},
},
},
dependencies: {
'my-source': [
{
type: DependencyType.static,
source: 'my-dependent',
target: 'my-source',
},
],
},
};
}); });
describe('static dependencies', () => { describe('static dependencies', () => {
@ -90,6 +126,10 @@ describe('checkDependencies', () => {
}); });
it('should not error if there are no dependents', async () => { it('should not error if there are no dependents', async () => {
projectGraph = {
nodes: projectGraph.nodes,
dependencies: {},
};
expect(() => { expect(() => {
checkDependencies(tree, schema); checkDependencies(tree, schema);
}).not.toThrow(); }).not.toThrow();

View File

@ -1,32 +1,33 @@
import { Tree } from '@nrwl/devkit';
import { import {
createProjectGraph,
onlyWorkspaceProjects, onlyWorkspaceProjects,
ProjectGraph, ProjectGraph,
reverse, reverse,
} from '../../../core/project-graph'; } from '../../../core/project-graph';
import { Schema } from '../schema'; import { Schema } from '../schema';
import { createProjectGraphFromTree } from '../../../utilities/create-project-graph-from-tree';
/** /**
* Check whether the project to be removed is depended on by another project * Check whether the project to be removed is depended on by another project
* *
* Throws an error if the project is in use, unless the `--forceRemove` option is used. * Throws an error if the project is in use, unless the `--forceRemove` option is used.
*/ */
export function checkDependencies(tree: Tree, schema: Schema) { export function checkDependencies(_, schema: Schema) {
if (schema.forceRemove) { if (schema.forceRemove) {
return; return;
} }
const graph: ProjectGraph = createProjectGraphFromTree(tree); const graph: ProjectGraph = createProjectGraph(
undefined,
undefined,
undefined,
false
);
const reverseGraph = onlyWorkspaceProjects(reverse(graph)); const reverseGraph = onlyWorkspaceProjects(reverse(graph));
const deps = reverseGraph.dependencies[schema.projectName] || []; const deps = reverseGraph.dependencies[schema.projectName] || [];
if (deps.length === 0) { if (deps.length > 0) {
return;
}
throw new Error( throw new Error(
`${ `${
schema.projectName schema.projectName
@ -35,3 +36,4 @@ export function checkDependencies(tree: Tree, schema: Schema) {
.join('\n')}` .join('\n')}`
); );
} }
}

View File

@ -1068,9 +1068,7 @@ function runRule(
`${process.cwd()}/proj`, `${process.cwd()}/proj`,
'mycompany', 'mycompany',
projectGraph, projectGraph,
new TargetProjectLocator(projectGraph.nodes, (path) => new TargetProjectLocator(projectGraph.nodes)
readFileSync(join('/root', path)).toString()
)
); );
return rule.apply(sourceFile); return rule.apply(sourceFile);
} }

View File

@ -1,37 +1,10 @@
import { import { Tree } from '@nrwl/devkit';
getWorkspacePath,
readJson,
Tree,
visitNotIgnoredFiles,
} from '@nrwl/devkit';
import { createProjectGraph } from '../core/project-graph/project-graph'; import { createProjectGraph } from '../core/project-graph/project-graph';
import { FileData } from '../core/file-utils';
import { extname } from 'path';
// TODO(v13): remove this deprecated method
/**
* @deprecated This method is deprecated and is synonymous to {@link createProjectGraph}()
*/
export function createProjectGraphFromTree(tree: Tree) { export function createProjectGraphFromTree(tree: Tree) {
const workspaceJson = readJson(tree, getWorkspacePath(tree)); return createProjectGraph(undefined, undefined, undefined, false);
const nxJson = readJson(tree, 'nx.json');
const files: FileData[] = [];
visitNotIgnoredFiles(tree, '', (file) => {
files.push({
file,
ext: extname(file),
hash: '',
});
});
const readFile = (path) => {
return tree.read(path).toString('utf-8');
};
return createProjectGraph(
workspaceJson,
nxJson,
files,
readFile,
false,
false
);
} }

View File

@ -26,7 +26,7 @@ import {
onlyWorkspaceProjects, onlyWorkspaceProjects,
ProjectGraph, ProjectGraph,
} from '../core/project-graph'; } from '../core/project-graph';
import { FileData, FileRead } from '../core/file-utils'; import { FileData } from '../core/file-utils';
import { extname, join, normalize, Path } from '@angular-devkit/core'; import { extname, join, normalize, Path } from '@angular-devkit/core';
import { NxJson, NxJsonProjectConfig } from '../core/shared-interfaces'; import { NxJson, NxJsonProjectConfig } from '../core/shared-interfaces';
import { addInstallTask } from './rules/add-install-task'; import { addInstallTask } from './rules/add-install-task';
@ -357,57 +357,27 @@ export function readJsonInTree<T = any>(host: Tree, path: string): T {
} }
} }
// TODO(v13): remove this deprecated method
/** /**
* @deprecated This method is deprecated and is synonymous to {@link onlyWorkspaceProjects}({@link createProjectGraph}())
* Method for utilizing the project graph in schematics * Method for utilizing the project graph in schematics
*/ */
export function getProjectGraphFromHost(host: Tree): ProjectGraph { export function getProjectGraphFromHost(host: Tree): ProjectGraph {
return onlyWorkspaceProjects(getFullProjectGraphFromHost(host)); return onlyWorkspaceProjects(createProjectGraph());
} }
// TODO(v13): remove this deprecated method
/**
* @deprecated This method is deprecated and is synonymous to {@link createProjectGraph}()
*/
export function getFullProjectGraphFromHost(host: Tree): ProjectGraph { export function getFullProjectGraphFromHost(host: Tree): ProjectGraph {
const workspaceJson = readJsonInTree(host, getWorkspacePath(host)); return createProjectGraph(undefined, undefined, undefined, false);
const nxJson = readJsonInTree<NxJson>(host, '/nx.json');
const fileRead: FileRead = (f: string) => {
try {
return host.read(f).toString();
} catch (e) {
throw new Error(`${f} does not exist`);
}
};
const workspaceFiles: FileData[] = [];
workspaceFiles.push(
...allFilesInDirInHost(host, normalize(''), { recursive: false }).map((f) =>
getFileDataInHost(host, f)
)
);
workspaceFiles.push(
...allFilesInDirInHost(host, normalize('tools')).map((f) =>
getFileDataInHost(host, f)
)
);
// Add files for workspace projects
Object.keys(workspaceJson.projects).forEach((projectName) => {
const project = workspaceJson.projects[projectName];
workspaceFiles.push(
...allFilesInDirInHost(host, normalize(project.root)).map((f) =>
getFileDataInHost(host, f)
)
);
});
return createProjectGraph(
workspaceJson,
nxJson,
workspaceFiles,
fileRead,
false
);
} }
// TODO(v13): remove this deprecated method
/**
* @deprecated This method is deprecated
*/
export function getFileDataInHost(host: Tree, path: Path): FileData { export function getFileDataInHost(host: Tree, path: Path): FileData {
return { return {
file: path, file: path,

View File

@ -1,70 +1,49 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import { SchematicContext, Tree } from '@angular-devkit/schematics';
import { getWorkspace } from '@nrwl/workspace';
import { import {
getFullProjectGraphFromHost, chain,
SchematicContext,
Tree,
Rule,
} from '@angular-devkit/schematics';
import { getWorkspace, visitNotIgnoredFiles } from '@nrwl/workspace';
import {
findNodes, findNodes,
insert, insert,
ReplaceChange, ReplaceChange,
} from '@nrwl/workspace/src/utils/ast-utils'; } from '@nrwl/workspace/src/utils/ast-utils';
import { normalize } from '@angular-devkit/core';
export interface PackageNameMapping { export interface PackageNameMapping {
[packageName: string]: string; [packageName: string]: string;
} }
const getProjectNamesWithDepsToRename = (
packageNameMapping: PackageNameMapping,
tree: Tree
) => {
const packagesToRename = Object.entries(packageNameMapping);
const projectGraph = getFullProjectGraphFromHost(tree);
return Object.entries(projectGraph.dependencies)
.filter(([, deps]) =>
deps.some(
(dep) =>
dep.type === 'static' &&
packagesToRename.some(
([packageName]) => packageName === dep.target.replace('npm:', '')
)
)
)
.map(([projectName]) => projectName);
};
/** /**
* Updates all the imports found in the workspace * Updates all the imports found in the workspace
* *
* @param packageNameMapping The packageNameMapping provided to the schematic * @param packageNameMapping The packageNameMapping provided to the schematic
*/ */
export function renamePackageImports(packageNameMapping: PackageNameMapping) { export function renamePackageImports(
return async (tree: Tree, _context: SchematicContext): Promise<void> => { packageNameMapping: PackageNameMapping
): Rule {
return async (tree: Tree, _context: SchematicContext) => {
const workspace = await getWorkspace(tree); const workspace = await getWorkspace(tree);
const projectNamesThatImportAPackageToRename = getProjectNamesWithDepsToRename( const rules = [];
packageNameMapping, workspace.projects.forEach((project) => {
tree rules.push(
); visitNotIgnoredFiles((file) => {
const projectsThatImportPackage = [...workspace.projects].filter(([name]) =>
projectNamesThatImportAPackageToRename.includes(name)
);
projectsThatImportPackage
.map(([, definition]) => tree.getDir(definition.root))
.forEach((projectDir) => {
projectDir.visit((file) => {
// only look at .(j|t)s(x) files
if (!/([jt])sx?$/.test(file)) { if (!/([jt])sx?$/.test(file)) {
return; return;
} }
// if it doesn't contain at least 1 reference to the packages to be renamed bail out
const contents = tree.read(file).toString('utf-8'); const contents = tree.read(file).toString('utf-8');
if ( const fileIncludesPackageToRename = Object.keys(
!Object.keys(packageNameMapping).some((packageName) => packageNameMapping
contents.includes(packageName) ).some((packageName) => {
) return contents.includes(packageName);
) { });
if (!fileIncludesPackageToRename) {
return; return;
} }
@ -107,7 +86,10 @@ export function renamePackageImports(packageNameMapping: PackageNameMapping) {
// update the file in the tree // update the file in the tree
insert(tree, file, changes); insert(tree, file, changes);
} }
}, normalize(project.root))
);
}); });
});
return chain(rules);
}; };
} }