From 6b9d81a746e847bfb900334eb3325de36e9a7add Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Sun, 16 Feb 2025 19:14:46 -0700 Subject: [PATCH] fix: ensure leavesFirst option works correctly in traverseFields utility (#11219) We have to ensure the arguments are handled wherever we push to the callback stack, not when we execute the callback stack. --- .../payload/src/utilities/traverseFields.ts | 56 ++++++---- pnpm-lock.yaml | 104 ++++++++++++++++-- test/helpers/autoDedupeBlocksPlugin/index.ts | 5 +- test/package.json | 3 + 4 files changed, 138 insertions(+), 30 deletions(-) diff --git a/packages/payload/src/utilities/traverseFields.ts b/packages/payload/src/utilities/traverseFields.ts index 189e6f26a1..6ab548a82a 100644 --- a/packages/payload/src/utilities/traverseFields.ts +++ b/packages/payload/src/utilities/traverseFields.ts @@ -15,8 +15,8 @@ const traverseArrayOrBlocksField = ({ parentRef, }: { callback: TraverseFieldsCallback - callbackStack: TraverseFieldsCallback[] - config: Config | SanitizedConfig + callbackStack: (() => ReturnType)[] + config?: Config | SanitizedConfig data: Record[] field: ArrayField | BlocksField fillEmpty: boolean @@ -39,16 +39,18 @@ const traverseArrayOrBlocksField = ({ for (const _block of field.blockReferences ?? field.blocks) { // TODO: iterate over blocks mapped to block slug in v4, or pass through payload.blocks const block = - typeof _block === 'string' ? config.blocks.find((b) => b.slug === _block) : _block - traverseFields({ - callback, - callbackStack, - config, - fields: block.fields, - isTopLevel: false, - leavesFirst, - parentRef, - }) + typeof _block === 'string' ? config?.blocks?.find((b) => b.slug === _block) : _block + if (block) { + traverseFields({ + callback, + callbackStack, + config, + fields: block.fields, + isTopLevel: false, + leavesFirst, + parentRef, + }) + } } } return @@ -58,7 +60,7 @@ const traverseArrayOrBlocksField = ({ if (field.type === 'blocks' && typeof ref?.blockType === 'string') { // TODO: iterate over blocks mapped to block slug in v4, or pass through payload.blocks const block = field.blockReferences - ? ((config.blocks.find((b) => b.slug === ref.blockType) ?? + ? ((config?.blocks?.find((b) => b.slug === ref.blockType) ?? field.blockReferences.find( (b) => typeof b !== 'string' && b.slug === ref.blockType, )) as Block) @@ -106,8 +108,8 @@ export type TraverseFieldsCallback = (args: { type TraverseFieldsArgs = { callback: TraverseFieldsCallback - callbackStack?: TraverseFieldsCallback[] - config: Config | SanitizedConfig + callbackStack?: (() => ReturnType)[] + config?: Config | SanitizedConfig fields: (Field | TabAsField)[] fillEmpty?: boolean isTopLevel?: boolean @@ -143,7 +145,7 @@ export const traverseFields = ({ ref = {}, }: TraverseFieldsArgs): void => { fields.some((field) => { - let callbackStack: TraverseFieldsCallback[] = [] + let callbackStack: (() => ReturnType)[] = [] if (!isTopLevel) { callbackStack = _callbackStack } @@ -159,7 +161,7 @@ export const traverseFields = ({ if (!leavesFirst && callback && callback({ field, next, parentRef, ref })) { return true } else if (leavesFirst) { - callbackStack.push(callback) + callbackStack.push(() => callback({ field, next, parentRef, ref })) } if (skip) { @@ -203,7 +205,14 @@ export const traverseFields = ({ ) { return true } else if (leavesFirst) { - callbackStack.push(callback) + callbackStack.push(() => + callback({ + field: { ...tab, type: 'tab' }, + next, + parentRef: currentParentRef, + ref: tabRef, + }), + ) } tabRef = tabRef[tab.name] @@ -238,7 +247,14 @@ export const traverseFields = ({ ) { return true } else if (leavesFirst) { - callbackStack.push(callback) + callbackStack.push(() => + callback({ + field: { ...tab, type: 'tab' }, + next, + parentRef: currentParentRef, + ref: tabRef, + }), + ) } } @@ -373,7 +389,7 @@ export const traverseFields = ({ if (isTopLevel) { callbackStack.reverse().forEach((cb) => { - cb({ field, next, parentRef, ref }) + cb() }) } }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94a8681041..d154105391 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,7 +45,7 @@ importers: version: 1.50.0 '@sentry/nextjs': specifier: ^8.33.1 - version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(react@19.0.0)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) + version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(react@19.0.0)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) '@sentry/node': specifier: ^8.33.1 version: 8.37.1 @@ -135,7 +135,7 @@ importers: version: 10.1.3(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3) next: specifier: 15.1.5 - version: 15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) + version: 15.1.5(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) open: specifier: ^10.1.0 version: 10.1.0 @@ -1076,7 +1076,7 @@ importers: dependencies: '@sentry/nextjs': specifier: ^8.33.1 - version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(react@19.0.0)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) + version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(react@19.0.0)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) '@sentry/types': specifier: ^8.33.1 version: 8.37.1 @@ -1598,6 +1598,10 @@ importers: version: link:../payload test: + dependencies: + dequal: + specifier: 2.0.3 + version: 2.0.3 devDependencies: '@aws-sdk/client-s3': specifier: ^3.614.0 @@ -1706,7 +1710,7 @@ importers: version: link:../packages/ui '@sentry/nextjs': specifier: ^8.33.1 - version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(react@19.0.0)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) + version: 8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.1.5(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(react@19.0.0)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12)) '@sentry/react': specifier: ^7.77.0 version: 7.119.2(react@19.0.0) @@ -1757,7 +1761,7 @@ importers: version: 8.9.5(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3) next: specifier: 15.1.5 - version: 15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) + version: 15.1.5(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) nodemailer: specifier: 6.9.16 version: 6.9.16 @@ -7838,6 +7842,7 @@ packages: libsql@0.4.7: resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==} + cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lie@3.1.1: @@ -13677,7 +13682,36 @@ snapshots: '@sentry/utils': 7.119.2 localforage: 1.10.0 - '@sentry/nextjs@8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(react@19.0.0)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15)))': + '@sentry/nextjs@8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.1.5(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(react@19.0.0)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12))': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 + '@rollup/plugin-commonjs': 26.0.1(rollup@3.29.5) + '@sentry-internal/browser-utils': 8.37.1 + '@sentry/core': 8.37.1 + '@sentry/node': 8.37.1 + '@sentry/opentelemetry': 8.37.1(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0) + '@sentry/react': 8.37.1(react@19.0.0) + '@sentry/types': 8.37.1 + '@sentry/utils': 8.37.1 + '@sentry/vercel-edge': 8.37.1 + '@sentry/webpack-plugin': 2.22.6(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12)) + chalk: 3.0.0 + next: 15.1.5(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) + resolve: 1.22.8 + rollup: 3.29.5 + stacktrace-parser: 0.1.10 + transitivePeerDependencies: + - '@opentelemetry/core' + - '@opentelemetry/instrumentation' + - '@opentelemetry/sdk-trace-base' + - encoding + - react + - supports-color + - webpack + + '@sentry/nextjs@8.37.1(@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0))(next@15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(react@19.0.0)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15)))': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) @@ -13693,7 +13727,7 @@ snapshots: '@sentry/vercel-edge': 8.37.1 '@sentry/webpack-plugin': 2.22.6(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) chalk: 3.0.0 - next: 15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) + next: 15.1.5(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) resolve: 1.22.8 rollup: 3.29.5 stacktrace-parser: 0.1.10 @@ -13801,6 +13835,16 @@ snapshots: '@sentry/types': 8.37.1 '@sentry/utils': 8.37.1 + '@sentry/webpack-plugin@2.22.6(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12))': + dependencies: + '@sentry/bundler-plugin-core': 2.22.6 + unplugin: 1.0.1 + uuid: 9.0.0 + webpack: 5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12) + transitivePeerDependencies: + - encoding + - supports-color + '@sentry/webpack-plugin@2.22.6(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15)))': dependencies: '@sentry/bundler-plugin-core': 2.22.6 @@ -18213,7 +18257,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4): + next@15.1.5(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4): dependencies: '@next/env': 15.1.5 '@swc/counter': 0.1.3 @@ -19619,6 +19663,18 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 + terser-webpack-plugin@5.3.10(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12)): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.36.0 + webpack: 5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12) + optionalDependencies: + '@swc/core': 1.10.12(@swc/helpers@0.5.15) + esbuild: 0.19.12 + terser-webpack-plugin@5.3.10(@swc/core@1.10.12(@swc/helpers@0.5.15))(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -19916,7 +19972,7 @@ snapshots: '@uploadthing/shared': 7.1.1 effect: 3.10.3 optionalDependencies: - next: 15.1.5(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) + next: 15.1.5(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.0.0-beta-714736e-20250131)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4) uri-js@4.4.1: dependencies: @@ -20044,6 +20100,36 @@ snapshots: - esbuild - uglify-js + webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.14.0 + browserslist: 4.24.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.17.1 + es-module-lexer: 1.5.4 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12)(webpack@5.96.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(esbuild@0.19.12)) + watchpack: 2.4.2 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + whatwg-url@13.0.0: dependencies: tr46: 4.1.1 diff --git a/test/helpers/autoDedupeBlocksPlugin/index.ts b/test/helpers/autoDedupeBlocksPlugin/index.ts index 992a40924a..1072ca4358 100644 --- a/test/helpers/autoDedupeBlocksPlugin/index.ts +++ b/test/helpers/autoDedupeBlocksPlugin/index.ts @@ -1,3 +1,4 @@ +import { dequal } from 'dequal/lite' import { type Block, type BlockSlug, type Config, traverseFields } from 'payload' export const autoDedupeBlocksPlugin = @@ -68,7 +69,9 @@ export const deduplicateBlock = ({ // Check if the fields are the same const jsonExistingBlock = JSON.stringify(existingBlock, null, 2) const jsonBlockToDeduplicate = JSON.stringify(dedupedBlock, null, 2) - if (jsonExistingBlock !== jsonBlockToDeduplicate) { + // dequal check of blocks with functions removed (through JSON.stringify+JSON.parse). We cannot check the strings, + // as the order of keys in the object is not guaranteed, yet it doesn't matter for the block fields. + if (!dequal(JSON.parse(jsonExistingBlock), JSON.parse(jsonBlockToDeduplicate))) { console.error('Block with the same slug but different fields found', { slug: dedupedBlock.slug, existingBlock: jsonExistingBlock, diff --git a/test/package.json b/test/package.json index 4817eb326f..d00be72111 100644 --- a/test/package.json +++ b/test/package.json @@ -86,5 +86,8 @@ "ts-essentials": "10.0.3", "typescript": "5.7.3", "uuid": "10.0.0" + }, + "dependencies": { + "dequal": "2.0.3" } }