feat(core): hash only relevant parts of global config files

This commit is contained in:
vsavkin 2021-04-05 14:26:15 -04:00 committed by Victor Savkin
parent 76bc890f34
commit b5efa6bfee
16 changed files with 128 additions and 103 deletions

View File

@ -787,19 +787,3 @@ describe('cache', () => {
expect(matchingProjects).toEqual(expectedProjects);
}
});
describe('workspace structure', () => {
beforeEach(() => newProject());
afterEach(() => removeProject({ onlyOnCI: true }));
it('should have a vscode/extensions.json file created', () => {
const extensions = readJson('.vscode/extensions.json');
expect(extensions).toEqual({
recommendations: [
'ms-vscode.vscode-typescript-tslint-plugin',
'esbenp.prettier-vscode',
],
});
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@nrwl/nx-source",
"version": "11.6.0",
"version": "12.0.0-beta.1",
"description": "Powerful, Extensible Dev Tools",
"homepage": "https://nx.dev",
"private": true,

View File

@ -30,6 +30,7 @@ import {
} from '@nrwl/workspace/src/core/file-utils';
import { TargetProjectLocator } from '@nrwl/workspace/src/core/target-project-locator';
import { checkCircularPath } from '@nrwl/workspace/src/utils/graph-utils';
import { readCurrentProjectGraph } from '@nrwl/workspace/src/core/project-graph/project-graph';
type Options = [
{
@ -106,10 +107,9 @@ export default createESLintRule<Options, MessageIds>({
(global as any).projectPath || appRootPath
);
if (!(global as any).projectGraph) {
const workspaceJson = readWorkspaceJson();
const nxJson = readNxJson();
(global as any).npmScope = nxJson.npmScope;
(global as any).projectGraph = createProjectGraph(workspaceJson, nxJson);
(global as any).projectGraph = readCurrentProjectGraph();
}
const npmScope = (global as any).npmScope;
const projectGraph = (global as any).projectGraph as ProjectGraph;
@ -123,6 +123,8 @@ export default createESLintRule<Options, MessageIds>({
.targetProjectLocator as TargetProjectLocator;
function run(node: TSESTree.ImportDeclaration | TSESTree.ImportExpression) {
if (!projectGraph) return;
// accept only literals because template literals have no value
if (node.source.type !== AST_NODE_TYPES.Literal) {
return;

View File

@ -1,5 +1,4 @@
import { dirname, extname } from 'path';
import * as ts from 'typescript';
import defaultResolver from 'jest-resolve/build/defaultResolver';
interface ResolveOptions {
@ -12,6 +11,9 @@ interface ResolveOptions {
defaultResolver: typeof defaultResolver;
}
let compilerSetup;
let ts;
function getCompilerSetup(rootDir: string) {
const tsConfigPath =
ts.findConfigFile(rootDir, ts.sys.fileExists, 'tsconfig.spec.json') ||
@ -35,8 +37,6 @@ function getCompilerSetup(rootDir: string) {
return { compilerOptions, host };
}
let compilerSetup;
module.exports = function (path: string, options: ResolveOptions) {
const ext = extname(path);
if (
@ -52,7 +52,14 @@ module.exports = function (path: string, options: ResolveOptions) {
try {
return defaultResolver(path, options);
} catch (e) {
if (
path === 'jest-sequencer-@jest/test-sequencer' ||
path === '@jest/test-sequencer'
) {
return;
}
// Fallback to using typescript
ts = ts || require('typescript');
compilerSetup = compilerSetup || getCompilerSetup(options.rootDir);
const { compilerOptions, host } = compilerSetup;
return ts.resolveModuleName(path, options.basedir, compilerOptions, host)

View File

@ -6,6 +6,8 @@
# Nx CLI
This is an alias of the [@nrwl/cli](https://www.npmjs.com/package/@nrwl/cli) package. Please see [@nrwl/cli](https://www.npmjs.com/package/@nrwl/cli) for more information.
{{what-is-nx}}
{{getting-started}}

View File

@ -25,6 +25,17 @@ describe('Hasher', () => {
}
it('should create project hash', async (done) => {
fs.readFileSync = (file) => {
if (file === 'workspace.json') {
return JSON.stringify({
projects: { proj: 'proj-from-workspace.json' },
});
}
if (file === 'nx.json') {
return JSON.stringify({ projects: { proj: 'proj-from-nx.json' } });
}
return file;
};
hashes['/file'] = 'file.hash';
const hasher = new Hasher(
{
@ -69,16 +80,14 @@ describe('Hasher', () => {
expect(hash.details.command).toEqual('proj|build||{"prop":"prop-value"}');
expect(hash.details.sources).toEqual({
proj: '/file|file.hash',
proj: '/file|file.hash|"proj-from-workspace.json"|"proj-from-nx.json"',
});
expect(hash.details.implicitDeps).toEqual({
'yarn.lock': 'yarn.lock.hash',
'nx.json': 'nx.json.hash',
'nx.json': '{}',
'package-lock.json': 'package-lock.json.hash',
'package.json': 'package.json.hash',
'pnpm-lock.yaml': 'pnpm-lock.yaml.hash',
'tsconfig.base.json': 'tsconfig.base.json.hash',
'workspace.json': 'workspace.json.hash',
});
expect(hash.details.runtime).toEqual({
'echo runtime123': 'runtime123',
@ -175,8 +184,8 @@ describe('Hasher', () => {
// note that the parent hash is based on parent source files only!
expect(hash.details.sources).toEqual({
parent: '/filea|a.hash',
child: '/fileb|b.hash',
parent: '/filea|a.hash|""|""',
child: '/fileb|b.hash|""|""',
});
done();
@ -232,8 +241,8 @@ describe('Hasher', () => {
expect(tasksHash.value).toContain('proj'); //project
expect(tasksHash.value).toContain('build'); //target
expect(tasksHash.details.sources).toEqual({
proja: '/filea|a.hash',
projb: '/fileb|b.hash',
proja: '/filea|a.hash|""|""',
projb: '/fileb|b.hash|""|""',
});
const hashb = (
@ -253,8 +262,8 @@ describe('Hasher', () => {
expect(hashb.value).toContain('proj'); //project
expect(hashb.value).toContain('build'); //target
expect(hashb.details.sources).toEqual({
proja: '/filea|a.hash',
projb: '/fileb|b.hash',
proja: '/filea|a.hash|""|""',
projb: '/fileb|b.hash|""|""',
});
done();

View File

@ -2,18 +2,13 @@ import { ProjectGraph } from '../project-graph';
import { NxJson } from '../shared-interfaces';
import { Task } from '../../tasks-runner/tasks-runner';
import { readFileSync } from 'fs';
import { rootWorkspaceFileNames } from '../file-utils';
import { exec, execSync } from 'child_process';
import {
defaultFileHasher,
extractNameAndVersion,
FileHasher,
} from './file-hasher';
import { workspaceFileName } from '../file-utils';
import { exec } from 'child_process';
import { defaultFileHasher, FileHasher } from './file-hasher';
import { defaultHashing, HashingImp } from './hashing-impl';
import * as minimatch from 'minimatch';
import { performance } from 'perf_hooks';
const resolve = require('resolve');
import * as stripJsonComments from 'strip-json-comments';
export interface Hash {
value: string;
@ -47,7 +42,6 @@ interface NodeModulesResult {
export class Hasher {
static version = '1.0';
private implicitDependencies: Promise<ImplicitHashResult>;
private nodeModules: Promise<NodeModulesResult>;
private runtimeInputs: Promise<RuntimeHashResult>;
private fileHasher: FileHasher;
private projectHashes: ProjectHasher;
@ -93,7 +87,6 @@ export class Hasher {
]),
this.implicitDepsHash(),
this.runtimeInputsHash(),
// this.nodeModulesHash(),
])) as [
ProjectHashResult,
ImplicitHashResult,
@ -192,18 +185,22 @@ export class Hasher {
const fileNames = [
...filesWithoutPatterns,
...implicitDepsFromPatterns,
...rootWorkspaceFileNames(),
//TODO: vsavkin move the special cases into explicit ts support
'tsconfig.base.json',
'package-lock.json',
'yarn.lock',
'pnpm-lock.yaml',
];
this.implicitDependencies = Promise.resolve().then(async () => {
const fileHashes = fileNames.map((file) => {
const hash = this.fileHasher.hashFile(file);
return { file, hash };
});
const fileHashes = [
...fileNames.map((file) => {
const hash = this.fileHasher.hashFile(file);
return { file, hash };
}),
...this.hashGlobalConfig(),
];
const combinedHash = this.hashing.hashArray(
fileHashes.map((v) => v.hash)
);
@ -214,6 +211,7 @@ export class Hasher {
'hasher:implicit deps hash:start',
'hasher:implicit deps hash:end'
);
return {
value: combinedHash,
sources: fileHashes.reduce((m, c) => ((m[c.file] = c.hash), m), {}),
@ -222,43 +220,36 @@ export class Hasher {
return this.implicitDependencies;
}
private async nodeModulesHash() {
if (this.nodeModules) return this.nodeModules;
this.nodeModules = Promise.resolve().then(async () => {
try {
const j = JSON.parse(readFileSync('package.json').toString());
const allPackages = [
...Object.keys(j.dependencies),
...Object.keys(j.devDependencies),
];
const packageJsonHashes = allPackages.map((d) => {
private hashGlobalConfig() {
return [
{
hash: this.fileHasher.hashFile('nx.json', (file) => {
try {
const path = resolve.sync(`${d}/package.json`, {
basedir: process.cwd(),
});
return this.fileHasher.hashFile(path, extractNameAndVersion);
const r = JSON.parse(stripJsonComments(file));
delete r.projects;
return JSON.stringify(r);
} catch (e) {
return '';
}
});
return { value: this.hashing.hashArray(packageJsonHashes) };
} catch (e) {
return { value: '' };
}
});
return this.nodeModules;
}),
file: 'nx.json',
},
];
}
}
class ProjectHasher {
private sourceHashes: { [projectName: string]: Promise<string> } = {};
private workspaceJson: any;
private nxJson: any;
constructor(
private readonly projectGraph: ProjectGraph,
private readonly hashing: HashingImp
) {}
) {
this.workspaceJson = this.readConfigFile(workspaceFileName());
this.nxJson = this.readConfigFile('nx.json');
}
async hashProject(
projectName: string,
@ -299,9 +290,32 @@ class ProjectHasher {
const p = this.projectGraph.nodes[projectName];
const fileNames = p.data.files.map((f) => f.file);
const values = p.data.files.map((f) => f.hash);
res(this.hashing.hashArray([...fileNames, ...values]));
const workspaceJson = JSON.stringify(
this.workspaceJson.projects[projectName] || ''
);
const nxJson = JSON.stringify(this.nxJson.projects[projectName] || '');
res(
this.hashing.hashArray([
...fileNames,
...values,
workspaceJson,
nxJson,
])
);
});
}
return this.sourceHashes[projectName];
}
private readConfigFile(name: string) {
try {
const res = JSON.parse(stripJsonComments(readFileSync(name).toString()));
if (!res.projects) res.projects = {};
return res;
} catch (e) {
return { projects: {} };
}
}
}

View File

@ -80,6 +80,11 @@ export function createProjectGraph(
}
}
export function readCurrentProjectGraph(): ProjectGraph | null {
const cache = readCache();
return cache === false ? null : cache;
}
function addWorkspaceFiles(
projectGraph: ProjectGraph,
allWorkspaceFiles: FileData[]

View File

@ -3,6 +3,9 @@ import { relative } from 'path';
import { dirSync, fileSync } from 'tmp';
import runCommands, { LARGE_BUFFER } from './run-commands.impl';
function normalize(p: string) {
return p.startsWith('/private') ? p.substring(8) : p;
}
function readFile(f: string) {
return readFileSync(f).toString().replace(/\s/g, '');
}
@ -59,7 +62,7 @@ describe('Command Runner Builder', () => {
});
});
it('ssss should forward args by default when using commands (plural)', async () => {
it('should forward args by default when using commands (plural)', async () => {
const exec = spyOn(require('child_process'), 'exec').and.callThrough();
await runCommands(
@ -285,7 +288,7 @@ describe('Command Runner Builder', () => {
);
expect(result).toEqual(jasmine.objectContaining({ success: true }));
expect(readFile(f)).toBe(root);
expect(normalize(readFile(f))).toBe(root);
});
it('should run the task in the specified cwd relative to the workspace root when cwd is not an absolute path', async () => {
@ -308,7 +311,7 @@ describe('Command Runner Builder', () => {
);
expect(result).toEqual(jasmine.objectContaining({ success: true }));
expect(readFile(f)).toBe(childFolder);
expect(normalize(readFile(f))).toBe(childFolder);
});
it('should run the task in the specified absolute cwd', async () => {
@ -330,7 +333,7 @@ describe('Command Runner Builder', () => {
);
expect(result).toEqual(jasmine.objectContaining({ success: true }));
expect(readFile(f)).toBe(childFolder);
expect(normalize(readFile(f))).toBe(childFolder);
});
});

View File

@ -148,7 +148,6 @@ Object {
"dependencies": "*",
"devDependencies": "*",
},
"tslint.json": "*",
},
"npmScope": "npmScope",
"projects": Object {},

View File

@ -5,3 +5,18 @@ exports[`@nrwl/workspace:workspace should create a prettierrc file 1`] = `
\\"singleQuote\\": true
}"
`;
exports[`@nrwl/workspace:workspace should recommend vscode extensions (angular) 1`] = `
Array [
"angular.ng-template",
"nrwl.angular-console",
"esbenp.prettier-vscode",
]
`;
exports[`@nrwl/workspace:workspace should recommend vscode extensions 1`] = `
Array [
"nrwl.angular-console",
"esbenp.prettier-vscode",
]
`;

View File

@ -1,10 +1,9 @@
{
"recommendations": [
<% if(cli === 'angular') { %>
"nrwl.angular-console",
<% if(cli === 'angular') { %>
"angular.ng-template",<% }
%>
"ms-vscode.vscode-typescript-tslint-plugin",
"nrwl.angular-console",
"esbenp.prettier-vscode"
]
}

View File

@ -8,7 +8,6 @@
"dependencies": "*",
"devDependencies": "*"
},
"tslint.json": "*",
".eslintrc.json": "*"
},
"tasksRunnerOptions": {

View File

@ -43,7 +43,6 @@ describe('@nrwl/workspace:workspace', () => {
dependencies: '*',
devDependencies: '*',
},
'tslint.json': '*',
'.eslintrc.json': '*',
},
tasksRunnerOptions: {
@ -82,10 +81,7 @@ describe('@nrwl/workspace:workspace', () => {
'proj/.vscode/extensions.json'
).recommendations;
expect(recommendations).toEqual([
'ms-vscode.vscode-typescript-tslint-plugin',
'esbenp.prettier-vscode',
]);
expect(recommendations).toMatchSnapshot();
});
it('should recommend vscode extensions (angular)', async () => {
@ -101,12 +97,7 @@ describe('@nrwl/workspace:workspace', () => {
'proj/.vscode/extensions.json'
).recommendations;
expect(recommendations).toEqual([
'nrwl.angular-console',
'angular.ng-template',
'ms-vscode.vscode-typescript-tslint-plugin',
'esbenp.prettier-vscode',
]);
expect(recommendations).toMatchSnapshot();
});
it('should add decorate-angular-cli when used with angular cli', async () => {

View File

@ -22,12 +22,10 @@ import {
onlyLoadChildren,
} from '../utils/runtime-lint-utils';
import { normalize } from 'path';
import {
readNxJson,
readWorkspaceJson,
} from '@nrwl/workspace/src/core/file-utils';
import { readNxJson } from '@nrwl/workspace/src/core/file-utils';
import { TargetProjectLocator } from '../core/target-project-locator';
import { checkCircularPath } from '@nrwl/workspace/src/utils/graph-utils';
import { readCurrentProjectGraph } from '@nrwl/workspace/src/core/project-graph/project-graph';
export class Rule extends Lint.Rules.AbstractRule {
constructor(
@ -42,13 +40,9 @@ export class Rule extends Lint.Rules.AbstractRule {
if (!projectPath) {
this.projectPath = normalize(appRootPath);
if (!(global as any).projectGraph) {
const workspaceJson = readWorkspaceJson();
const nxJson = readNxJson();
(global as any).npmScope = nxJson.npmScope;
(global as any).projectGraph = createProjectGraph(
workspaceJson,
nxJson
);
(global as any).projectGraph = readCurrentProjectGraph();
}
this.npmScope = (global as any).npmScope;
this.projectGraph = (global as any).projectGraph;
@ -63,6 +57,7 @@ export class Rule extends Lint.Rules.AbstractRule {
}
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (!this.projectGraph) return [];
return this.applyWithWalker(
new EnforceModuleBoundariesWalker(
sourceFile,

View File

@ -38,6 +38,7 @@ if (
}
module.exports = function (path, options) {
if (path === 'jest-sequencer-@jest/test-sequencer') return;
const ext = path_1.extname(path);
if (
ext === '.css' ||