diff --git a/.changeset/upgrade-jsondiffpatch.md b/.changeset/upgrade-jsondiffpatch.md new file mode 100644 index 0000000..1244aba --- /dev/null +++ b/.changeset/upgrade-jsondiffpatch.md @@ -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. diff --git a/jest.config.js b/jest.config.js index 12fd568..22c5c26 100644 --- a/jest.config.js +++ b/jest.config.js @@ -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', diff --git a/package.json b/package.json index 2128792..a5685ee 100644 --- a/package.json +++ b/package.json @@ -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/*"], diff --git a/packages/sync-actions/jest.config.js b/packages/sync-actions/jest.config.js index f6d7dc2..e95d03b 100644 --- a/packages/sync-actions/jest.config.js +++ b/packages/sync-actions/jest.config.js @@ -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', diff --git a/packages/sync-actions/package.json b/packages/sync-actions/package.json index 1fba4d2..ee85057 100644 --- a/packages/sync-actions/package.json +++ b/packages/sync-actions/package.json @@ -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", diff --git a/packages/sync-actions/src/utils/diffpatcher.ts b/packages/sync-actions/src/utils/diffpatcher.ts index 701ddd3..a2bb61c 100644 --- a/packages/sync-actions/src/utils/diffpatcher.ts +++ b/packages/sync-actions/src/utils/diffpatcher.ts @@ -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 }; @@ -22,24 +22,14 @@ 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, - }, }); export function diff(oldObj: T, newObj: T): Delta { return diffpatcher.diff(oldObj, newObj); } -export function patch(obj: T, delta: Delta) { - return diffpatcher.patch(obj, delta); +export function patch(obj: T, delta: Delta): T { + return diffpatcher.patch(obj, delta) as T; } export function getDeltaValue( diff --git a/packages/sync-actions/src/utils/types.ts b/packages/sync-actions/src/utils/types.ts index b836ad1..f02dfb6 100644 --- a/packages/sync-actions/src/utils/types.ts +++ b/packages/sync-actions/src/utils/types.ts @@ -1,7 +1,6 @@ -import type { Delta as DiffDelta } from 'jsondiffpatch/dist/jsondiffpatch.cjs'; - export * from '@commercetools/platform-sdk'; -export type Delta = DiffDelta | undefined; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type Delta = Record | undefined; export type SyncActionConfig = { shouldOmitEmptyString?: boolean; diff --git a/packages/sync-actions/test/utils/diffpatcher.spec.ts b/packages/sync-actions/test/utils/diffpatcher.spec.ts new file mode 100644 index 0000000..715a557 --- /dev/null +++ b/packages/sync-actions/test/utils/diffpatcher.spec.ts @@ -0,0 +1,56 @@ +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; + 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/ + ); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 6044905..907acb6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -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" @@ -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" @@ -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" @@ -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 @@ -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 @@ -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": - version: 4.9.5 - resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin::version=4.9.5&hash=289587" +"typescript@patch:typescript@npm%3A^5.8.2#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/e3333f887c6829dfe0ab6c1dbe0dd1e3e2aeb56c66460cb85c5440c566f900c833d370ca34eb47558c0c69e78ced4bfe09b8f4f98b6de7afed9b84b8d1dd06a1 + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 languageName: node linkType: hard