Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/upgrade-jsondiffpatch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@commercetools/sync-actions': minor
---

Upgrade the `jsondiffpatch` dependency from `0.5.0` to `^0.7.6`, moving off an unmaintained release onto the current maintained version.

A customer security report referenced `jsonpath-plus` (which is not, and never was, a dependency of this package). The reported version facts — pinned `0.5.0`, fixed in `0.7.6`, not backwards compatible — instead correspond to `jsondiffpatch`, a direct dependency used by the diff/patch utility behind update-action generation. This change adopts `jsondiffpatch@0.7.6`.

The public API and observable behavior of `@commercetools/sync-actions` are unchanged. Internally: `jsondiffpatch` 0.7 is ESM-only, so imports were updated to the package root and the dependency is bundled into the published CommonJS, ESM, and UMD artifacts (CommonJS consumers are unaffected). Fine-grained text diffing remains disabled, so a changed string is still reported as a whole-value replacement.
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ module.exports = {
'^.+\\.js$': 'babel-jest',
'^.+\\.ts?$': 'ts-jest',
},
// Transpile the ESM-only jsondiffpatch package (Jest skips node_modules by default).
transformIgnorePatterns: ['/node_modules/(?!(jsondiffpatch)/)'],
testRegex: '\\.(test|spec)\\.[t]s?$',
moduleFileExtensions: ['ts', 'js', 'json'],
coverageDirectory: 'coverage',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"tmp": "^0.2.3",
"ts-jest": "^29.3.2",
"ts-node": "^10.9.2",
"typescript": "^4.6.2",
"typescript": "^5.8.2",
"typescript-eslint": "^8.32.1"
},
"workspaces": ["packages/*"],
Expand Down
5 changes: 4 additions & 1 deletion packages/sync-actions/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ module.exports = {
testTimeout: 15000,
testEnvironment: 'node',
transform: {
'^.+\\.js$': 'babel-jest',
// rootMode 'upward' finds the repo-root babel.config.js from this package dir
'^.+\\.js$': ['babel-jest', { rootMode: 'upward' }],
'^.+\\.ts?$': 'ts-jest',
},
// transpile ESM-only jsondiffpatch (jest ignores node_modules by default)
transformIgnorePatterns: ['/node_modules/(?!(jsondiffpatch)/)'],
testRegex: '\\.(test|spec)\\.[t]s?$',
moduleFileExtensions: ['ts', 'js', 'json'],
coverageDirectory: 'coverage',
Expand Down
2 changes: 1 addition & 1 deletion packages/sync-actions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"esbuild": "^0.25.4",
"fast-equals": "^2.0.0",
"jest": "^29.7.0",
"jsondiffpatch": "0.5.0",
"jsondiffpatch": "^0.7.6",
"lodash.foreach": "^4.5.0",
"lodash.intersection": "^4.4.0",
"lodash.isequal": "^4.5.0",
Expand Down
24 changes: 11 additions & 13 deletions packages/sync-actions/src/utils/diffpatcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DiffPatcher } from 'jsondiffpatch/dist/jsondiffpatch.cjs';
import { DiffPatcher } from 'jsondiffpatch';
import { Delta } from './types';

type U = { id: string; name: string; url: string };
Expand All @@ -22,24 +22,22 @@ const diffpatcher = new DiffPatcher({
// value of items moved is not included in deltas
includeValueOnMove: false,
},
textDiff: {
/**
* jsondiffpatch uses a very fine-grained diffing algorithm for long strings to easily identify
* what changed between strings. However, we don't actually care about what changed, just
* if the string changed at all. So we set the minimum length to diff to a very large number to avoid
* using the very slow algorithm.
* See https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md#text-diffs.
*/
minLength: Number.MAX_SAFE_INTEGER,
},
// No `textDiff` config: as of jsondiffpatch 0.7, fine-grained text diffing is
// opt-in and requires passing a `diffMatchPatch` instance (or importing
// `jsondiffpatch/with-text-diffs`). We deliberately leave it disabled so a
// changed string is reported as a whole-value replacement rather than a slow
// character-level diff — we only care whether the string changed at all.
// See https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md#text-diffs.
});

export function diff<T>(oldObj: T, newObj: T): Delta {
return diffpatcher.diff(oldObj, newObj);
}

export function patch<T>(obj: T, delta: Delta) {
return diffpatcher.patch(obj, delta);
export function patch<T>(obj: T, delta: Delta): T {
// jsondiffpatch 0.7 types `patch` as returning `unknown`; the patched value is
// structurally the same shape as the input, so narrow back to `T`.
return diffpatcher.patch(obj, delta) as T;
}

export function getDeltaValue<T extends object = object>(
Expand Down
9 changes: 6 additions & 3 deletions packages/sync-actions/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { Delta as DiffDelta } from 'jsondiffpatch/dist/jsondiffpatch.cjs';

export * from '@commercetools/platform-sdk';
export type Delta = DiffDelta | undefined;

// The action builders read a delta both by key (`delta.shippingInfo`) and by
// position (`delta[2]`), so we model it as a loose index-able record rather than
// jsondiffpatch's strict Delta union (which would require narrowing everywhere).
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Delta = Record<string | number, any> | undefined;

export type SyncActionConfig = {
shouldOmitEmptyString?: boolean;
Expand Down
58 changes: 58 additions & 0 deletions packages/sync-actions/test/utils/diffpatcher.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { diff, getDeltaValue, objectHash } from '../../src/utils/diffpatcher';

describe('diffpatcher (jsondiffpatch 0.7)', () => {
describe('long-string diffing', () => {
test('a changed long string is a whole-value replacement, not a text diff', () => {
const long = 'x'.repeat(5000);
const delta = diff({ desc: long }, { desc: `${long}CHANGED` }) as Record<
string,
unknown[]
>;
const stringDelta = delta.desc;
// text-diff deltas are length 3 with a trailing `2`; we require the slow
// text-diff path to stay disabled so the change is a [old, new] pair.
expect(stringDelta).toHaveLength(2);
expect(stringDelta[0]).toBe(long);
expect(stringDelta[1]).toBe(`${long}CHANGED`);
});
});

describe('objectHash', () => {
test('prefers id, then name, then url, then falls back to index key', () => {
expect(objectHash({ id: 'a', name: 'n', url: 'u' }, 0)).toBe('a');
expect(objectHash({ name: 'n', url: 'u' } as never, 1)).toBe('n');
expect(objectHash({ url: 'u' } as never, 2)).toBe('u');
expect(objectHash(null as never, 3)).toBe('$$index:3');
});
});

describe('getDeltaValue', () => {
test('length 1 returns the added value', () => {
expect(getDeltaValue([{ a: 1 }])).toEqual({ a: 1 });
});

test('length 2 returns the updated value', () => {
expect(getDeltaValue([{ a: 1 }, { a: 2 }])).toEqual({ a: 2 });
});

test('length 3 ending in 0 is a delete and returns undefined', () => {
expect(getDeltaValue([{ a: 1 }, 0, 0])).toBeUndefined();
});

test('text diff (trailing 2) without an original object throws', () => {
expect(() => getDeltaValue(['@@ -1 +1 @@', 0, 2])).toThrow(
/Missing original object/
);
});

test('array move (trailing 3) throws because includeValueOnMove is false', () => {
expect(() => getDeltaValue(['', 1, 3])).toThrow(/array move/);
});

test('a non-array delta throws', () => {
expect(() => getDeltaValue('not-an-array')).toThrow(
/Expected array to extract delta value/
);
});
});
});
57 changes: 23 additions & 34 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1643,7 +1643,7 @@ __metadata:
esbuild: "npm:^0.25.4"
fast-equals: "npm:^2.0.0"
jest: "npm:^29.7.0"
jsondiffpatch: "npm:0.5.0"
jsondiffpatch: "npm:^0.7.6"
lodash.foreach: "npm:^4.5.0"
lodash.intersection: "npm:^4.4.0"
lodash.isequal: "npm:^4.5.0"
Expand Down Expand Up @@ -1873,6 +1873,13 @@ __metadata:
languageName: node
linkType: hard

"@dmsnell/diff-match-patch@npm:^1.1.0":
version: 1.1.0
resolution: "@dmsnell/diff-match-patch@npm:1.1.0"
checksum: 10c0/8547bf4a62dfca0a61b9c6490ea0ea468ec108879892f2e1ebe14dc12e9e6c50ad91b184a186ab5119f47baf631ab4f1f152372b8b89ba4d8d42a8ae5dcb6e44
languageName: node
linkType: hard

"@esbuild/aix-ppc64@npm:0.25.10":
version: 0.25.10
resolution: "@esbuild/aix-ppc64@npm:0.25.10"
Expand Down Expand Up @@ -3903,16 +3910,6 @@ __metadata:
languageName: node
linkType: hard

"chalk@npm:^3.0.0":
version: 3.0.0
resolution: "chalk@npm:3.0.0"
dependencies:
ansi-styles: "npm:^4.1.0"
supports-color: "npm:^7.1.0"
checksum: 10c0/ee650b0a065b3d7a6fda258e75d3a86fc8e4effa55871da730a9e42ccb035bf5fd203525e5a1ef45ec2582ecc4f65b47eb11357c526b84dd29a14fb162c414d2
languageName: node
linkType: hard

"chalk@npm:^4.0.0":
version: 4.1.2
resolution: "chalk@npm:4.1.2"
Expand Down Expand Up @@ -4415,13 +4412,6 @@ __metadata:
languageName: node
linkType: hard

"diff-match-patch@npm:^1.0.0":
version: 1.0.5
resolution: "diff-match-patch@npm:1.0.5"
checksum: 10c0/142b6fad627b9ef309d11bd935e82b84c814165a02500f046e2773f4ea894d10ed3017ac20454900d79d4a0322079f5b713cf0986aaf15fce0ec4a2479980c86
languageName: node
linkType: hard

"diff-sequences@npm:^29.6.3":
version: 29.6.3
resolution: "diff-sequences@npm:29.6.3"
Expand Down Expand Up @@ -6382,15 +6372,14 @@ __metadata:
languageName: node
linkType: hard

"jsondiffpatch@npm:0.5.0":
version: 0.5.0
resolution: "jsondiffpatch@npm:0.5.0"
"jsondiffpatch@npm:^0.7.6":
version: 0.7.6
resolution: "jsondiffpatch@npm:0.7.6"
dependencies:
chalk: "npm:^3.0.0"
diff-match-patch: "npm:^1.0.0"
"@dmsnell/diff-match-patch": "npm:^1.1.0"
bin:
jsondiffpatch: bin/jsondiffpatch
checksum: 10c0/272271b92b20ebd303c002b4ffad6bcbf52dadb667c338509296b3c25e954535e5aa86c3675df5ae1525edad9ea7f7191c8beb921f0ee87569a58b4810299d87
jsondiffpatch: bin/jsondiffpatch.js
checksum: 10c0/c82ea96bcce309fa6d399ad27d773bcfc8a99c817e78fbd6be5d8149e60f13016a777acca3aa93e073a8bb06eca905fda8f9ea3f846188b0914251a7b3d417a5
languageName: node
linkType: hard

Expand Down Expand Up @@ -9185,7 +9174,7 @@ __metadata:
tmp: "npm:^0.2.3"
ts-jest: "npm:^29.3.2"
ts-node: "npm:^10.9.2"
typescript: "npm:^4.6.2"
typescript: "npm:^5.8.2"
typescript-eslint: "npm:^8.32.1"
languageName: unknown
linkType: soft
Expand All @@ -9205,23 +9194,23 @@ __metadata:
languageName: node
linkType: hard

"typescript@npm:^4.6.2":
version: 4.9.5
resolution: "typescript@npm:4.9.5"
"typescript@npm:^5.8.2":
version: 5.9.3
resolution: "typescript@npm:5.9.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/5f6cad2e728a8a063521328e612d7876e12f0d8a8390d3b3aaa452a6a65e24e9ac8ea22beb72a924fd96ea0a49ea63bb4e251fb922b12eedfb7f7a26475e5c56
checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5
languageName: node
linkType: hard

"typescript@patch:typescript@npm%3A^4.6.2#optional!builtin<compat/typescript>":
version: 4.9.5
resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin<compat/typescript>::version=4.9.5&hash=289587"
"typescript@patch:typescript@npm%3A^5.8.2#optional!builtin<compat/typescript>":
version: 5.9.3
resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/e3333f887c6829dfe0ab6c1dbe0dd1e3e2aeb56c66460cb85c5440c566f900c833d370ca34eb47558c0c69e78ced4bfe09b8f4f98b6de7afed9b84b8d1dd06a1
checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430
languageName: node
linkType: hard

Expand Down
Loading