fix(core): fix json diff and implicitJsonChanges
This commit is contained in:
parent
fef54fae4b
commit
dbf6fd525d
@ -65,5 +65,5 @@ function getTouchedProjects(path: string[], implicitDependencyConfig: any) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return found ? curr : [];
|
return found && Array.isArray(curr) ? curr : [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,6 +90,37 @@ describe('getTouchedNpmPackages', () => {
|
|||||||
expect(result).toEqual(['proj1', 'proj2']);
|
expect(result).toEqual(['proj1', 'proj2']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle package addition', () => {
|
||||||
|
const result = getTouchedNpmPackages(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
file: 'package.json',
|
||||||
|
mtime: 0,
|
||||||
|
ext: '.json',
|
||||||
|
getChanges: () => [
|
||||||
|
{
|
||||||
|
type: DiffType.Added,
|
||||||
|
path: ['dependencies', 'awesome-nrwl'],
|
||||||
|
value: {
|
||||||
|
lhs: undefined,
|
||||||
|
rhs: '0.0.1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
workspaceJson,
|
||||||
|
nxJson,
|
||||||
|
{
|
||||||
|
dependencies: {
|
||||||
|
'happy-nrwl': '0.0.2',
|
||||||
|
'awesome-nrwl': '0.0.1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toEqual(['awesome-nrwl']);
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle whole file changes', () => {
|
it('should handle whole file changes', () => {
|
||||||
const result = getTouchedNpmPackages(
|
const result = getTouchedNpmPackages(
|
||||||
[
|
[
|
||||||
|
|||||||
@ -14,7 +14,8 @@ export const getTouchedNpmPackages: TouchedProjectLocator<
|
|||||||
for (const c of changes) {
|
for (const c of changes) {
|
||||||
if (
|
if (
|
||||||
isJsonChange(c) &&
|
isJsonChange(c) &&
|
||||||
(c.path[0] === 'dependencies' || c.path[0] === 'devDependencies')
|
(c.path[0] === 'dependencies' || c.path[0] === 'devDependencies') &&
|
||||||
|
c.path.length === 2
|
||||||
) {
|
) {
|
||||||
// A package was deleted so mark all workspace projects as touched.
|
// A package was deleted so mark all workspace projects as touched.
|
||||||
if (c.type === DiffType.Deleted) {
|
if (c.type === DiffType.Deleted) {
|
||||||
|
|||||||
@ -95,6 +95,16 @@ describe('getTouchedProjectsInNxJson', () => {
|
|||||||
ext: '.json',
|
ext: '.json',
|
||||||
mtime: 0,
|
mtime: 0,
|
||||||
getChanges: () => [
|
getChanges: () => [
|
||||||
|
{
|
||||||
|
type: DiffType.Added,
|
||||||
|
path: ['projects', 'proj1'],
|
||||||
|
value: {
|
||||||
|
lhs: undefined,
|
||||||
|
rhs: {
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: DiffType.Added,
|
type: DiffType.Added,
|
||||||
path: ['projects', 'proj1', 'tags'],
|
path: ['projects', 'proj1', 'tags'],
|
||||||
@ -122,7 +132,7 @@ describe('getTouchedProjectsInNxJson', () => {
|
|||||||
expect(result).toEqual(['proj1']);
|
expect(result).toEqual(['proj1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return projects removed in nx.json', () => {
|
it('should return all projects when a project is removed', () => {
|
||||||
const result = getTouchedProjectsInNxJson(
|
const result = getTouchedProjectsInNxJson(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -132,9 +142,11 @@ describe('getTouchedProjectsInNxJson', () => {
|
|||||||
getChanges: () => [
|
getChanges: () => [
|
||||||
{
|
{
|
||||||
type: DiffType.Deleted,
|
type: DiffType.Deleted,
|
||||||
path: ['projects', 'proj3', 'tags'],
|
path: ['projects', 'proj3'],
|
||||||
value: {
|
value: {
|
||||||
lhs: [],
|
lhs: {
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
rhs: undefined
|
rhs: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,6 +177,24 @@ describe('getTouchedProjectsInNxJson', () => {
|
|||||||
ext: '.json',
|
ext: '.json',
|
||||||
mtime: 0,
|
mtime: 0,
|
||||||
getChanges: () => [
|
getChanges: () => [
|
||||||
|
{
|
||||||
|
type: DiffType.Modified,
|
||||||
|
path: ['projects', 'proj1'],
|
||||||
|
value: {
|
||||||
|
lhs: { tags: ['scope:feat'] },
|
||||||
|
rhs: {
|
||||||
|
tags: ['scope:shared']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: DiffType.Modified,
|
||||||
|
path: ['projects', 'proj1', 'tags'],
|
||||||
|
value: {
|
||||||
|
lhs: ['scope:feat'],
|
||||||
|
rhs: ['scope:shared']
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: DiffType.Modified,
|
type: DiffType.Modified,
|
||||||
path: ['projects', 'proj1', 'tags', '0'],
|
path: ['projects', 'proj1', 'tags', '0'],
|
||||||
|
|||||||
@ -33,13 +33,21 @@ export const getTouchedProjectsInNxJson: TouchedProjectLocator<
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nxJson.projects[change.path[1]]) {
|
// Only look for changes that are changes to the whole project definition
|
||||||
|
if (change.path.length !== 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (change.type) {
|
||||||
|
case DiffType.Deleted: {
|
||||||
|
// We are not sure which projects used to depend on a deleted project
|
||||||
|
// so return all projects to be safe
|
||||||
|
return Object.keys(nxJson.projects);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// Add the project name
|
||||||
touched.push(change.path[1]);
|
touched.push(change.path[1]);
|
||||||
} else {
|
}
|
||||||
// The project was deleted so affect all projects
|
|
||||||
touched.push(...Object.keys(nxJson.projects));
|
|
||||||
// Break out of the loop after all projects have been added.
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return touched;
|
return touched;
|
||||||
|
|||||||
@ -95,10 +95,21 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
|
|||||||
getChanges: () => [
|
getChanges: () => [
|
||||||
{
|
{
|
||||||
type: DiffType.Added,
|
type: DiffType.Added,
|
||||||
path: ['projects', 'proj1', 'tags'],
|
path: ['projects', 'proj1'],
|
||||||
value: {
|
value: {
|
||||||
lhs: undefined,
|
lhs: undefined,
|
||||||
rhs: []
|
rhs: {
|
||||||
|
root: 'proj1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
type: DiffType.Added,
|
||||||
|
path: ['projects', 'proj1', 'root'],
|
||||||
|
value: {
|
||||||
|
lhs: undefined,
|
||||||
|
rhs: 'proj1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -125,9 +136,11 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
|
|||||||
getChanges: () => [
|
getChanges: () => [
|
||||||
{
|
{
|
||||||
type: DiffType.Deleted,
|
type: DiffType.Deleted,
|
||||||
path: ['projects', 'proj3', 'root'],
|
path: ['projects', 'proj3'],
|
||||||
value: {
|
value: {
|
||||||
lhs: 'proj3',
|
lhs: {
|
||||||
|
root: 'proj3'
|
||||||
|
},
|
||||||
rhs: undefined
|
rhs: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,6 +169,18 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
|
|||||||
ext: '.json',
|
ext: '.json',
|
||||||
mtime: 0,
|
mtime: 0,
|
||||||
getChanges: () => [
|
getChanges: () => [
|
||||||
|
{
|
||||||
|
type: DiffType.Modified,
|
||||||
|
path: ['projects', 'proj1'],
|
||||||
|
value: {
|
||||||
|
lhs: {
|
||||||
|
root: 'proj3'
|
||||||
|
},
|
||||||
|
rhs: {
|
||||||
|
root: 'proj1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: DiffType.Modified,
|
type: DiffType.Modified,
|
||||||
path: ['projects', 'proj1', 'root'],
|
path: ['projects', 'proj1', 'root'],
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import {
|
|||||||
WholeFileChange,
|
WholeFileChange,
|
||||||
workspaceFileName
|
workspaceFileName
|
||||||
} from '../../file-utils';
|
} from '../../file-utils';
|
||||||
import { isJsonChange, JsonChange } from '../../../utils/json-diff';
|
import { DiffType, isJsonChange, JsonChange } from '../../../utils/json-diff';
|
||||||
import { TouchedProjectLocator } from '../affected-project-graph-models';
|
import { TouchedProjectLocator } from '../affected-project-graph-models';
|
||||||
|
|
||||||
export const getTouchedProjectsInWorkspaceJson: TouchedProjectLocator<
|
export const getTouchedProjectsInWorkspaceJson: TouchedProjectLocator<
|
||||||
@ -39,13 +39,21 @@ export const getTouchedProjectsInWorkspaceJson: TouchedProjectLocator<
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workspaceJson.projects[change.path[1]]) {
|
// Only look for changes that are changes to the whole project definition
|
||||||
|
if (change.path.length !== 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (change.type) {
|
||||||
|
case DiffType.Deleted: {
|
||||||
|
// We are not sure which projects used to depend on a deleted project
|
||||||
|
// so return all projects to be safe
|
||||||
|
return Object.keys(workspaceJson.projects);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// Add the project name
|
||||||
touched.push(change.path[1]);
|
touched.push(change.path[1]);
|
||||||
} else {
|
}
|
||||||
// The project was deleted so affect all projects
|
|
||||||
touched.push(...Object.keys(workspaceJson.projects));
|
|
||||||
// Break out of the loop after all projects have been added.
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return touched;
|
return touched;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { jsonDiff, DiffType } from './json-diff';
|
import { jsonDiff, DiffType } from './json-diff';
|
||||||
|
|
||||||
describe('jsonDiff', () => {
|
describe('jsonDiff', () => {
|
||||||
it('should return deep diffs of two JSON objects', () => {
|
it('should return deep diffs of two JSON objects (including parents of children changes)', () => {
|
||||||
const result = jsonDiff(
|
const result = jsonDiff(
|
||||||
{ x: 1, a: { b: { c: 1 } } },
|
{ x: 1, a: { b: { c: 1 } } },
|
||||||
{ y: 2, a: { b: { c: 2, d: 2 } } }
|
{ y: 2, a: { b: { c: 2, d: 2 } } }
|
||||||
@ -9,6 +9,36 @@ describe('jsonDiff', () => {
|
|||||||
|
|
||||||
expect(result).toEqual(
|
expect(result).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
type: DiffType.Modified,
|
||||||
|
path: ['a'],
|
||||||
|
value: {
|
||||||
|
lhs: {
|
||||||
|
b: {
|
||||||
|
c: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rhs: {
|
||||||
|
b: {
|
||||||
|
c: 2,
|
||||||
|
d: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: DiffType.Modified,
|
||||||
|
path: ['a', 'b'],
|
||||||
|
value: {
|
||||||
|
lhs: {
|
||||||
|
c: 1
|
||||||
|
},
|
||||||
|
rhs: {
|
||||||
|
c: 2,
|
||||||
|
d: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: DiffType.Modified,
|
type: DiffType.Modified,
|
||||||
path: ['a', 'b', 'c'],
|
path: ['a', 'b', 'c'],
|
||||||
@ -33,6 +63,83 @@ describe('jsonDiff', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have diffs for objects as well', () => {
|
||||||
|
const result = jsonDiff(
|
||||||
|
{
|
||||||
|
a: { b: 0 }
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
a: { b: 1 }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toContainEqual({
|
||||||
|
type: DiffType.Modified,
|
||||||
|
path: ['a'],
|
||||||
|
value: {
|
||||||
|
lhs: {
|
||||||
|
b: 0
|
||||||
|
},
|
||||||
|
rhs: {
|
||||||
|
b: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(result).toContainEqual({
|
||||||
|
type: DiffType.Modified,
|
||||||
|
path: ['a', 'b'],
|
||||||
|
value: {
|
||||||
|
lhs: 0,
|
||||||
|
rhs: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result2 = jsonDiff(
|
||||||
|
{},
|
||||||
|
|
||||||
|
{
|
||||||
|
a: {}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result2).toContainEqual({
|
||||||
|
type: DiffType.Added,
|
||||||
|
path: ['a'],
|
||||||
|
value: { lhs: undefined, rhs: {} }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for added array items', () => {
|
||||||
|
const result = jsonDiff(
|
||||||
|
{
|
||||||
|
rules: ['rule1']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: ['rule1', 'rule2']
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
type: DiffType.Modified,
|
||||||
|
path: ['rules'],
|
||||||
|
value: {
|
||||||
|
lhs: ['rule1'],
|
||||||
|
rhs: ['rule1', 'rule2']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: DiffType.Added,
|
||||||
|
path: ['rules', '1'],
|
||||||
|
value: {
|
||||||
|
lhs: undefined,
|
||||||
|
rhs: 'rule2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should work well for package.json', () => {
|
it('should work well for package.json', () => {
|
||||||
const result = jsonDiff(
|
const result = jsonDiff(
|
||||||
{
|
{
|
||||||
|
|||||||
@ -29,9 +29,6 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
|
|||||||
|
|
||||||
walkJsonTree(lhs, [], (path, lhsValue) => {
|
walkJsonTree(lhs, [], (path, lhsValue) => {
|
||||||
seenInLhs.add(hashArray(path));
|
seenInLhs.add(hashArray(path));
|
||||||
if (typeof lhsValue === 'object') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const rhsValue = getJsonValue(path, rhs);
|
const rhsValue = getJsonValue(path, rhs);
|
||||||
if (rhsValue === undefined) {
|
if (rhsValue === undefined) {
|
||||||
result.push({
|
result.push({
|
||||||
@ -42,7 +39,7 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
|
|||||||
rhs: undefined
|
rhs: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (lhsValue !== rhsValue) {
|
} else if (!deepEquals(lhsValue, rhsValue)) {
|
||||||
result.push({
|
result.push({
|
||||||
type: DiffType.Modified,
|
type: DiffType.Modified,
|
||||||
path,
|
path,
|
||||||
@ -52,13 +49,10 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return false;
|
return typeof lhsValue === 'object';
|
||||||
});
|
});
|
||||||
|
|
||||||
walkJsonTree(rhs, [], (path, rhsValue) => {
|
walkJsonTree(rhs, [], (path, rhsValue) => {
|
||||||
if (typeof rhsValue === 'object') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const addedInRhs = !seenInLhs.has(hashArray(path));
|
const addedInRhs = !seenInLhs.has(hashArray(path));
|
||||||
if (addedInRhs) {
|
if (addedInRhs) {
|
||||||
result.push({
|
result.push({
|
||||||
@ -71,6 +65,7 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return typeof rhsValue === 'object';
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -85,7 +80,6 @@ export function walkJsonTree(
|
|||||||
if (!json || typeof json !== 'object') {
|
if (!json || typeof json !== 'object') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(json).forEach(key => {
|
Object.keys(json).forEach(key => {
|
||||||
const path = currPath.concat([key]);
|
const path = currPath.concat([key]);
|
||||||
const shouldContinue = visitor(path, json[key]);
|
const shouldContinue = visitor(path, json[key]);
|
||||||
@ -109,3 +103,32 @@ function getJsonValue(path: string[], json: any): void | any {
|
|||||||
}
|
}
|
||||||
return curr;
|
return curr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deepEquals(a: any, b: any): boolean {
|
||||||
|
if (a === b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values do not need to be checked for deep equality and the above is false
|
||||||
|
if (
|
||||||
|
// Values are different types
|
||||||
|
typeof a !== typeof b ||
|
||||||
|
// Values are the same type but not an object or array
|
||||||
|
(typeof a !== 'object' && !Array.isArray(a)) ||
|
||||||
|
// Objects are the same type, objects or arrays, but do not have the same number of keys
|
||||||
|
Object.keys(a).length !== Object.keys(b).length
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values need to be checked for deep equality
|
||||||
|
return Object.entries(a).reduce((equal, [key, aValue]) => {
|
||||||
|
// Skip other keys if it is already not equal.
|
||||||
|
if (!equal) {
|
||||||
|
return equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the object
|
||||||
|
return deepEquals(aValue, b[key]);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user