feat(schematics): add lint checks ensuring the integrity of the workspace
This commit is contained in:
parent
fea4f48dec
commit
ce553b732e
@ -44,14 +44,22 @@ describe('Nrwl Workspace', () => {
|
||||
newApp('myapp');
|
||||
|
||||
try {
|
||||
runCommand('npm run test -- --app myapp --single-run', true);
|
||||
runCommand('npm run test -- --app myapp --single-run');
|
||||
fail('boom');
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
expect(e.stderr.toString()).toContain(
|
||||
'Nx only supports running unit tests for all apps and libs.'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
runCommand('npm run e2e', true);
|
||||
runCommand('npm run e2e');
|
||||
fail('boom');
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
expect(e.stderr.toString()).toContain(
|
||||
'Please provide the app name using --app or -a.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it(
|
||||
|
||||
@ -53,6 +53,26 @@ describe('Command line', () => {
|
||||
1000000
|
||||
);
|
||||
|
||||
it('should run nx lint', () => {
|
||||
newProject();
|
||||
newApp('myapp');
|
||||
newApp('app_before');
|
||||
runCommand('mv apps/app-before apps/app-after');
|
||||
|
||||
try {
|
||||
runCommand('npm run lint');
|
||||
fail('Boom!');
|
||||
} catch (e) {
|
||||
const errorOutput = e.stderr.toString();
|
||||
expect(errorOutput).toContain(
|
||||
`Cannot find project 'app-before' in 'apps/app-before'`
|
||||
);
|
||||
expect(errorOutput).toContain(
|
||||
`The 'apps/app-after/e2e/app.e2e-spec.ts' file doesn't belong to any project.`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it(
|
||||
'update should run migrations',
|
||||
() => {
|
||||
|
||||
@ -106,10 +106,10 @@ export function runSchematic(command: string): string {
|
||||
}).toString();
|
||||
}
|
||||
|
||||
export function runCommand(command: string, silent?: boolean): string {
|
||||
export function runCommand(command: string): string {
|
||||
return execSync(command, {
|
||||
cwd: `./tmp/${projectName}`,
|
||||
...(silent ? { stdio: ['ignore', 'ignore', 'ignore'] } : {})
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
}).toString();
|
||||
}
|
||||
|
||||
|
||||
13
packages/schematics/migrations/20180328-add-nx-lint.ts
Normal file
13
packages/schematics/migrations/20180328-add-nx-lint.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { updateJsonFile } from '@nrwl/schematics/src/utils/fileutils';
|
||||
|
||||
export default {
|
||||
description: 'Run lint checks ensuring the integrity of the workspace',
|
||||
run: () => {
|
||||
updateJsonFile('package.json', json => {
|
||||
json.scripts = {
|
||||
...json.scripts,
|
||||
lint: './node_modules/.bin/nx lint && ng lint'
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -7,7 +7,7 @@
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"lint": "./node_modules/.bin/nx lint && ng lint",
|
||||
"e2e": "ng e2e",
|
||||
|
||||
"affected:apps": "./node_modules/.bin/nx affected apps",
|
||||
|
||||
@ -89,6 +89,7 @@ function updatePackageJson() {
|
||||
packageJson.scripts['update:check'] = './node_modules/.bin/nx update check';
|
||||
packageJson.scripts['update:skip'] = './node_modules/.bin/nx update skip';
|
||||
|
||||
packageJson.scripts['lint'] = './node_modules/.bin/nx lint && ng lint';
|
||||
packageJson.scripts['postinstall'] = './node_modules/.bin/nx postinstall';
|
||||
|
||||
return packageJson;
|
||||
|
||||
33
packages/schematics/src/command-line/lint.ts
Normal file
33
packages/schematics/src/command-line/lint.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { getProjectNodes, readCliConfig, allFilesInDir } from './shared';
|
||||
import { WorkspaceIntegrityChecks } from './workspace-integrity-checks';
|
||||
import * as appRoot from 'app-root-path';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export function lint() {
|
||||
const nodes = getProjectNodes(readCliConfig());
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(`${appRoot.path}/package.json`, 'utf-8')
|
||||
);
|
||||
|
||||
const errorGroups = new WorkspaceIntegrityChecks(
|
||||
nodes,
|
||||
readAllFilesFromAppsAndLibs(),
|
||||
packageJson
|
||||
).run();
|
||||
if (errorGroups.length > 0) {
|
||||
errorGroups.forEach(g => {
|
||||
console.error(`${g.header}:`);
|
||||
g.errors.forEach(e => console.error(e));
|
||||
console.log('');
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function readAllFilesFromAppsAndLibs() {
|
||||
return [
|
||||
...allFilesInDir(`${appRoot.path}/apps`),
|
||||
...allFilesInDir(`${appRoot.path}/libs`)
|
||||
].filter(f => !path.basename(f).startsWith('.'));
|
||||
}
|
||||
@ -5,6 +5,7 @@ import { affected } from './affected';
|
||||
import { format } from './format';
|
||||
import { update } from './update';
|
||||
import { patchNg } from './patch-ng';
|
||||
import { lint } from './lint';
|
||||
|
||||
const processedArgs = yargsParser(process.argv, {
|
||||
alias: {
|
||||
@ -28,6 +29,9 @@ switch (command) {
|
||||
case 'update':
|
||||
update(args);
|
||||
break;
|
||||
case 'lint':
|
||||
lint();
|
||||
break;
|
||||
case 'postinstall':
|
||||
patchNg();
|
||||
update(['check']);
|
||||
|
||||
@ -8,6 +8,11 @@ if (process.argv.indexOf('update') > -1) {
|
||||
console.log('Please run "npm run update" or "yarn update" instead.');
|
||||
process.exit(1);
|
||||
}
|
||||
if (process.argv.indexOf('lint') > -1) {
|
||||
console.log("This is an Nx workspace, and it provides an enhanced 'lint' command.");
|
||||
console.log('Please run "npm run lint" or "yarn lint" instead.');
|
||||
process.exit(1);
|
||||
}
|
||||
// nx-check-end
|
||||
`;
|
||||
|
||||
|
||||
@ -135,19 +135,21 @@ export function getProjectRoots(projectNames: string[]): string[] {
|
||||
);
|
||||
}
|
||||
|
||||
function allFilesInDir(dirName: string): string[] {
|
||||
export function allFilesInDir(dirName: string): string[] {
|
||||
let res = [];
|
||||
fs.readdirSync(dirName).forEach(c => {
|
||||
const child = path.join(dirName, c);
|
||||
try {
|
||||
if (!fs.statSync(child).isDirectory()) {
|
||||
// add starting with "apps/myapp/..." or "libs/mylib/..."
|
||||
res.push(normalizePath(child.substring(appRoot.path.length + 1)));
|
||||
} else if (fs.statSync(child).isDirectory()) {
|
||||
res = [...res, ...allFilesInDir(child)];
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
try {
|
||||
fs.readdirSync(dirName).forEach(c => {
|
||||
const child = path.join(dirName, c);
|
||||
try {
|
||||
if (!fs.statSync(child).isDirectory()) {
|
||||
// add starting with "apps/myapp/..." or "libs/mylib/..."
|
||||
res.push(normalizePath(child.substring(appRoot.path.length + 1)));
|
||||
} else if (fs.statSync(child).isDirectory()) {
|
||||
res = [...res, ...allFilesInDir(child)];
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
} catch (e) {}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,106 @@
|
||||
import { WorkspaceIntegrityChecks } from './workspace-integrity-checks';
|
||||
import { ProjectType } from './affected-apps';
|
||||
|
||||
describe('WorkspaceIntegrityChecks', () => {
|
||||
const packageJson = {
|
||||
dependencies: {
|
||||
'@nrwl/nx': '1.2.3'
|
||||
},
|
||||
devDependencies: {
|
||||
'@nrwl/schematics': '1.2.3'
|
||||
}
|
||||
};
|
||||
|
||||
describe('.angular-cli.json is in sync with the filesystem', () => {
|
||||
it('should not error when they are in sync', () => {
|
||||
const c = new WorkspaceIntegrityChecks(
|
||||
[
|
||||
{
|
||||
name: 'project1',
|
||||
type: ProjectType.lib,
|
||||
root: 'libs/project1/src',
|
||||
tags: [],
|
||||
files: ['libs/project1/index.ts']
|
||||
}
|
||||
],
|
||||
['libs/project1/index.ts'],
|
||||
packageJson
|
||||
);
|
||||
expect(c.run().length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should error when there are projects without files', () => {
|
||||
const c = new WorkspaceIntegrityChecks(
|
||||
[
|
||||
{
|
||||
name: 'project1',
|
||||
type: ProjectType.lib,
|
||||
root: 'libs/project1/src',
|
||||
tags: [],
|
||||
files: []
|
||||
},
|
||||
{
|
||||
name: 'project2',
|
||||
type: ProjectType.lib,
|
||||
root: 'libs/project2/src',
|
||||
tags: [],
|
||||
files: ['libs/project2/index.ts']
|
||||
}
|
||||
],
|
||||
['libs/project2/index.ts'],
|
||||
packageJson
|
||||
);
|
||||
|
||||
const errors = c.run();
|
||||
expect(errors.length).toEqual(1);
|
||||
expect(errors[0].errors[0]).toEqual(
|
||||
`Cannot find project 'project1' in 'libs/project1'`
|
||||
);
|
||||
});
|
||||
|
||||
it('should error when there are files in apps or libs without projects', () => {
|
||||
const c = new WorkspaceIntegrityChecks(
|
||||
[
|
||||
{
|
||||
name: 'project1',
|
||||
type: ProjectType.lib,
|
||||
root: 'libs/project1/src',
|
||||
tags: [],
|
||||
files: ['libs/project1/index.ts']
|
||||
}
|
||||
],
|
||||
['libs/project1/index.ts', 'libs/project2/index.ts'],
|
||||
packageJson
|
||||
);
|
||||
|
||||
const errors = c.run();
|
||||
expect(errors.length).toEqual(1);
|
||||
expect(errors[0].errors[0]).toEqual(
|
||||
`The 'libs/project2/index.ts' file doesn't belong to any project.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('package.json is consistent', () => {
|
||||
it('should not error when @nrwl/nx and @nrwl/schematics are in sync', () => {
|
||||
const c = new WorkspaceIntegrityChecks([], [], packageJson);
|
||||
expect(c.run().length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should error when @nrwl/nx and @nrwl/schematics are not in sync', () => {
|
||||
const c = new WorkspaceIntegrityChecks([], [], {
|
||||
dependencies: {
|
||||
'@nrwl/nx': '1.2.3'
|
||||
},
|
||||
devDependencies: {
|
||||
'@nrwl/schematics': '4.5.6'
|
||||
}
|
||||
});
|
||||
const errors = c.run();
|
||||
expect(errors.length).toEqual(1);
|
||||
expect(errors[0].errors[0]).toEqual(
|
||||
`The versions of the @nrwl/nx and @nrwl/schematics packages must be the same.`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,80 @@
|
||||
import { ProjectNode } from './affected-apps';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface ErrorGroup {
|
||||
header: string;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
export class WorkspaceIntegrityChecks {
|
||||
constructor(
|
||||
private projectNodes: ProjectNode[],
|
||||
private files: string[],
|
||||
private packageJson: any
|
||||
) {}
|
||||
|
||||
run(): ErrorGroup[] {
|
||||
return [
|
||||
...this.packageJsonConsistencyCheck(),
|
||||
...this.projectWithoutFilesCheck(),
|
||||
...this.filesWithoutProjects()
|
||||
];
|
||||
}
|
||||
|
||||
private packageJsonConsistencyCheck(): ErrorGroup[] {
|
||||
const nx = this.packageJson.dependencies['@nrwl/nx'];
|
||||
const schematics = this.packageJson.devDependencies['@nrwl/schematics'];
|
||||
if (nx !== schematics) {
|
||||
return [
|
||||
{
|
||||
header: 'The package.json is inconsistent',
|
||||
errors: [
|
||||
'The versions of the @nrwl/nx and @nrwl/schematics packages must be the same.'
|
||||
]
|
||||
}
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private projectWithoutFilesCheck(): ErrorGroup[] {
|
||||
const errors = this.projectNodes
|
||||
.filter(n => n.files.length === 0)
|
||||
.map(p => `Cannot find project '${p.name}' in '${path.dirname(p.root)}'`);
|
||||
|
||||
return errors.length === 0
|
||||
? []
|
||||
: [{ header: 'The .angular-cli.json file is out of sync', errors }];
|
||||
}
|
||||
|
||||
private filesWithoutProjects(): ErrorGroup[] {
|
||||
const allFilesFromProjects = this.allProjectFiles();
|
||||
const allFilesWithoutProjects = minus(this.files, allFilesFromProjects);
|
||||
const first5FilesWithoutProjects =
|
||||
allFilesWithoutProjects.length > 5
|
||||
? allFilesWithoutProjects.slice(0, 5)
|
||||
: allFilesWithoutProjects;
|
||||
|
||||
const errors = first5FilesWithoutProjects.map(
|
||||
p => `The '${p}' file doesn't belong to any project.`
|
||||
);
|
||||
|
||||
return errors.length === 0
|
||||
? []
|
||||
: [
|
||||
{
|
||||
header: `All files in 'apps' and 'libs' must be part of a project.`,
|
||||
errors
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private allProjectFiles() {
|
||||
return this.projectNodes.reduce((m, c) => [...m, ...c.files], []);
|
||||
}
|
||||
}
|
||||
|
||||
function minus(a: string[], b: string[]): string[] {
|
||||
return a.filter(aa => b.indexOf(aa) === -1);
|
||||
}
|
||||
@ -8,7 +8,7 @@ export const nxVersion = '*';
|
||||
export const schematicsVersion = '*';
|
||||
export const angularCliSchema =
|
||||
'./node_modules/@nrwl/schematics/src/schema.json';
|
||||
export const latestMigration = '20180313-add-tags';
|
||||
export const latestMigration = '20180328-add-nx-lint';
|
||||
export const prettierVersion = '1.10.2';
|
||||
export const typescriptVersion = '2.6.2';
|
||||
export const rxjsVersion = '^5.5.6';
|
||||
|
||||
@ -25,7 +25,6 @@ export function addApp(apps: any[] | undefined, newApp: any): any[] {
|
||||
if (a.name > b.name) return 1;
|
||||
return -1;
|
||||
});
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
},
|
||||
"exclude": [
|
||||
"tmp",
|
||||
"build",
|
||||
"node_modules",
|
||||
"packages/schematics/src/*/files/**/*"
|
||||
],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user