feat(core): hash only relevant parts of global config files
This commit is contained in:
parent
76bc890f34
commit
b5efa6bfee
@ -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',
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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: {} };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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[]
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -148,7 +148,6 @@ Object {
|
||||
"dependencies": "*",
|
||||
"devDependencies": "*",
|
||||
},
|
||||
"tslint.json": "*",
|
||||
},
|
||||
"npmScope": "npmScope",
|
||||
"projects": Object {},
|
||||
|
||||
@ -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",
|
||||
]
|
||||
`;
|
||||
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
"dependencies": "*",
|
||||
"devDependencies": "*"
|
||||
},
|
||||
"tslint.json": "*",
|
||||
".eslintrc.json": "*"
|
||||
},
|
||||
"tasksRunnerOptions": {
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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' ||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user