Add history action - #86
Conversation
|
Complex PR? Review this PR in Change Stack to move by importance, not file order. WalkthroughThis PR implements a complete record history feature for the recordlist module. A new AJAX endpoint in Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
Tests/Playwright/news/news-history.spec.ts (2)
97-97: ⚡ Quick winPrefer non-localized selector for modal close action.
Selecting by button text
"Close"is translation- and copy-dependent. A stable hook (data-testid/ dedicated class) will make this test resilient across locales.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Tests/Playwright/news/news-history.spec.ts` at line 97, The test uses a localized text selector page.locator('.modal .modal-footer button').filter({ hasText: 'Close' }).click(); which is fragile across locales; change the test to target a stable hook (e.g., replace the locator with something like '.modal .modal-footer [data-testid="modal-close"]' or a dedicated class) and update the modal component to add that data-testid or class to the close button (e.g., data-testid="modal-close"), then use page.locator('<that selector>').click() in the test to make it locale-independent.
55-57: ⚡ Quick winUse a stable selector for the “hide system fields” toggle.
input[type="checkbox"]is too broad and can bind to the wrong control if additional checkboxes appear in the modal. Scope to a dedicated class/data-testid for the history toggle.Also applies to: 69-69
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Tests/Playwright/news/news-history.spec.ts` around lines 55 - 57, The selector for the "hide system fields" toggle is too broad (modal.locator('input[type="checkbox"]')) and may match the wrong checkbox; update the locator used where `toggle` is defined (and the similar use at line 69) to a stable, specific selector such as a dedicated class or data-testid (e.g. modal.locator('[data-testid="history-hide-system-fields-toggle"]') or modal.locator('.history-hide-system-toggle')) and assert visibility/checked state against that locator instead of the generic input[type="checkbox"].
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Classes/Controller/AjaxController.php`:
- Around line 165-168: The current AjaxController code writes
(string)json_encode($entries) directly which can produce an empty body when
json_encode returns false for invalid UTF-8; update the method that uses
$this->responseFactory->createResponse() and $response->getBody()->write(...) to
explicitly JSON-encode $entries with error handling—either call json_encode with
JSON_THROW_ON_ERROR inside a try/catch and return a 500 response with an
informative JSON error on exception, or check if json_encode($entries) ===
false, inspect json_last_error_msg(), and then set an appropriate non-200 status
and JSON error body; ensure you still set Content-Type to application/json;
charset=utf-8 and reference the $entries variable and the response creation flow
(responseFactory->createResponse, getBody()->write) when implementing the fix.
- Around line 107-126: recordHistory currently returns history for any
client-supplied table/uid without permission checks; add an authorization gate
before building the query: in recordHistory validate that the current backend
user has read/access rights to the given table and record (or the page the
record belongs to) using the TYPO3 backend permission APIs (e.g.
BackendUserAuthentication/BackendUtility permission checks) and deny with a 403
response if unauthorized; only proceed to create the ConnectionPool/queryBuilder
and leftJoin be_users when the permission check passes to avoid leaking
sensitive history/user info.
In `@Resources/Public/JavaScript/recordlist-action-history.js`:
- Around line 178-179: Replace hard-coded English UI strings (e.g. the
assignment text.textContent = "Hide system fields", the "Only system fields were
changed..." message, and the action label strings around the history/action
rendering block) with localized lookups that read keys defined in locallang.xlf;
add corresponding keys to locallang.xlf and then use the JS localization object
(e.g. TYPO3.lang['your_extension.keyHideSystemFields'],
TYPO3.lang['your_extension.keyOnlySystemFieldsChanged'], and similarly named
keys for action labels) wherever text.textContent or action label variables are
set so the UI uses TYPO3 translations instead of hard-coded English.
- Around line 110-133: Wrap the
AjaxRequest(TYPO3.settings.ajaxUrls.xima_recordlist_history).post(...) call with
error handling: add a .catch() to handle network/server failures and in the
.then() validate the resolved value from response.resolve() before using it
(ensure entries is an Array or fallback to an empty array or an error path),
calling this.buildContent(entries) only for valid data; on error or invalid
payload show a Modal (use Modal.advanced with SeverityEnum.warning/error and a
localized title/body) to surface the failure instead of letting a rejected
promise or unexpected payload crash the flow.
In `@Tests/Playwright/news/news-history.spec.ts`:
- Around line 63-65: The test currently uses
contentFrame.locator('a[data-action="history"]').nth(2).click(), which couples
it to fixture ordering; instead locate the specific deterministic record (e.g.,
by known UID or title) and then click its history action. Replace the nth(2)
usage by first finding the row or item via contentFrame.locator('tr', { hasText:
'KNOWN_TITLE' }) or by matching a data-uid/data-title attribute, then call
.locator('a[data-action="history"]').click() on that scoped locator so the test
targets the specific record reliably.
---
Nitpick comments:
In `@Tests/Playwright/news/news-history.spec.ts`:
- Line 97: The test uses a localized text selector page.locator('.modal
.modal-footer button').filter({ hasText: 'Close' }).click(); which is fragile
across locales; change the test to target a stable hook (e.g., replace the
locator with something like '.modal .modal-footer [data-testid="modal-close"]'
or a dedicated class) and update the modal component to add that data-testid or
class to the close button (e.g., data-testid="modal-close"), then use
page.locator('<that selector>').click() in the test to make it
locale-independent.
- Around line 55-57: The selector for the "hide system fields" toggle is too
broad (modal.locator('input[type="checkbox"]')) and may match the wrong
checkbox; update the locator used where `toggle` is defined (and the similar use
at line 69) to a stable, specific selector such as a dedicated class or
data-testid (e.g.
modal.locator('[data-testid="history-hide-system-fields-toggle"]') or
modal.locator('.history-hide-system-toggle')) and assert visibility/checked
state against that locator instead of the generic input[type="checkbox"].
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 81807f3d-5c02-4db4-95e3-6e01a48e9735
📒 Files selected for processing (9)
Classes/Controller/AbstractBackendController.phpClasses/Controller/AjaxController.phpConfiguration/Backend/AjaxRoutes.phpREADME.mdResources/Private/Language/locallang.xlfResources/Private/Partials/Actions/History.htmlResources/Public/JavaScript/contrib/diff.jsResources/Public/JavaScript/recordlist-action-history.jsTests/Playwright/news/news-history.spec.ts
| $response = $this->responseFactory->createResponse() | ||
| ->withHeader('Content-Type', 'application/json; charset=utf-8'); | ||
| $response->getBody()->write((string)json_encode($entries)); | ||
| return $response; |
There was a problem hiding this comment.
Handle JSON encoding failures explicitly.
If one history value contains invalid UTF-8, json_encode($entries) returns false; casting that to string writes an empty body with application/json, which breaks the frontend contract.
🛠️ Suggested fix
- $response = $this->responseFactory->createResponse()
- ->withHeader('Content-Type', 'application/json; charset=utf-8');
- $response->getBody()->write((string)json_encode($entries));
+ $json = json_encode($entries, JSON_INVALID_UTF8_SUBSTITUTE);
+ if ($json === false) {
+ return $this->responseFactory->createResponse(500);
+ }
+ $response = $this->responseFactory->createResponse()
+ ->withHeader('Content-Type', 'application/json; charset=utf-8');
+ $response->getBody()->write($json);
return $response;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $response = $this->responseFactory->createResponse() | |
| ->withHeader('Content-Type', 'application/json; charset=utf-8'); | |
| $response->getBody()->write((string)json_encode($entries)); | |
| return $response; | |
| $json = json_encode($entries, JSON_INVALID_UTF8_SUBSTITUTE); | |
| if ($json === false) { | |
| return $this->responseFactory->createResponse(500); | |
| } | |
| $response = $this->responseFactory->createResponse() | |
| ->withHeader('Content-Type', 'application/json; charset=utf-8'); | |
| $response->getBody()->write($json); | |
| return $response; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Classes/Controller/AjaxController.php` around lines 165 - 168, The current
AjaxController code writes (string)json_encode($entries) directly which can
produce an empty body when json_encode returns false for invalid UTF-8; update
the method that uses $this->responseFactory->createResponse() and
$response->getBody()->write(...) to explicitly JSON-encode $entries with error
handling—either call json_encode with JSON_THROW_ON_ERROR inside a try/catch and
return a 500 response with an informative JSON error on exception, or check if
json_encode($entries) === false, inspect json_last_error_msg(), and then set an
appropriate non-200 status and JSON error body; ensure you still set
Content-Type to application/json; charset=utf-8 and reference the $entries
variable and the response creation flow (responseFactory->createResponse,
getBody()->write) when implementing the fix.
| new AjaxRequest(TYPO3.settings.ajaxUrls.xima_recordlist_history) | ||
| .post("", { body: payload }) | ||
| .then(async (response) => { | ||
| const entries = await response.resolve(); | ||
| const content = this.buildContent(entries); | ||
|
|
||
| Modal.advanced({ | ||
| type: Modal.types.default, | ||
| title: TYPO3.lang["modal.history.title"], | ||
| content: content, | ||
| severity: SeverityEnum.info, | ||
| size: Modal.sizes.large, | ||
| buttons: [ | ||
| { | ||
| text: TYPO3.lang["modal.history.close"], | ||
| active: true, | ||
| btnClass: "btn-default", | ||
| name: "close", | ||
| trigger: (evt, modal) => modal.hideModal() | ||
| } | ||
| ] | ||
| }); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Handle AJAX failures and unexpected payload shapes.
The current flow has no .catch() and assumes response.resolve() returns an array. Network/server failures or non-array responses can currently lead to unhandled promise rejections and no modal feedback.
🧩 Suggested fix
new AjaxRequest(TYPO3.settings.ajaxUrls.xima_recordlist_history)
.post("", { body: payload })
.then(async (response) => {
- const entries = await response.resolve();
+ const resolved = await response.resolve();
+ const entries = Array.isArray(resolved) ? resolved : [];
const content = this.buildContent(entries);
Modal.advanced({
type: Modal.types.default,
title: TYPO3.lang["modal.history.title"],
@@
]
});
- });
+ })
+ .catch(() => {
+ Modal.advanced({
+ type: Modal.types.default,
+ title: TYPO3.lang["modal.history.title"],
+ content: TYPO3.lang["modal.history.noEntries"],
+ severity: SeverityEnum.error,
+ size: Modal.sizes.small,
+ buttons: [{
+ text: TYPO3.lang["modal.history.close"],
+ active: true,
+ btnClass: "btn-default",
+ name: "close",
+ trigger: (evt, modal) => modal.hideModal()
+ }]
+ });
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| new AjaxRequest(TYPO3.settings.ajaxUrls.xima_recordlist_history) | |
| .post("", { body: payload }) | |
| .then(async (response) => { | |
| const entries = await response.resolve(); | |
| const content = this.buildContent(entries); | |
| Modal.advanced({ | |
| type: Modal.types.default, | |
| title: TYPO3.lang["modal.history.title"], | |
| content: content, | |
| severity: SeverityEnum.info, | |
| size: Modal.sizes.large, | |
| buttons: [ | |
| { | |
| text: TYPO3.lang["modal.history.close"], | |
| active: true, | |
| btnClass: "btn-default", | |
| name: "close", | |
| trigger: (evt, modal) => modal.hideModal() | |
| } | |
| ] | |
| }); | |
| }); | |
| } | |
| new AjaxRequest(TYPO3.settings.ajaxUrls.xima_recordlist_history) | |
| .post("", { body: payload }) | |
| .then(async (response) => { | |
| const resolved = await response.resolve(); | |
| const entries = Array.isArray(resolved) ? resolved : []; | |
| const content = this.buildContent(entries); | |
| Modal.advanced({ | |
| type: Modal.types.default, | |
| title: TYPO3.lang["modal.history.title"], | |
| content: content, | |
| severity: SeverityEnum.info, | |
| size: Modal.sizes.large, | |
| buttons: [ | |
| { | |
| text: TYPO3.lang["modal.history.close"], | |
| active: true, | |
| btnClass: "btn-default", | |
| name: "close", | |
| trigger: (evt, modal) => modal.hideModal() | |
| } | |
| ] | |
| }); | |
| }) | |
| .catch(() => { | |
| Modal.advanced({ | |
| type: Modal.types.default, | |
| title: TYPO3.lang["modal.history.title"], | |
| content: TYPO3.lang["modal.history.noEntries"], | |
| severity: SeverityEnum.error, | |
| size: Modal.sizes.small, | |
| buttons: [{ | |
| text: TYPO3.lang["modal.history.close"], | |
| active: true, | |
| btnClass: "btn-default", | |
| name: "close", | |
| trigger: (evt, modal) => modal.hideModal() | |
| }] | |
| }); | |
| }); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Resources/Public/JavaScript/recordlist-action-history.js` around lines 110 -
133, Wrap the
AjaxRequest(TYPO3.settings.ajaxUrls.xima_recordlist_history).post(...) call with
error handling: add a .catch() to handle network/server failures and in the
.then() validate the resolved value from response.resolve() before using it
(ensure entries is an Array or fallback to an empty array or an error path),
calling this.buildContent(entries) only for valid data; on error or invalid
payload show a Modal (use Modal.advanced with SeverityEnum.warning/error and a
localized title/body) to surface the failure instead of letting a rejected
promise or unexpected payload crash the flow.
| text.textContent = "Hide system fields"; | ||
|
|
There was a problem hiding this comment.
Localize remaining hard-coded history UI texts.
Several user-facing strings are still hard-coded in English (Hide system fields, Only system fields were changed..., action labels). This bypasses locallang.xlf and breaks backend localization consistency.
Also applies to: 203-203, 340-346
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Resources/Public/JavaScript/recordlist-action-history.js` around lines 178 -
179, Replace hard-coded English UI strings (e.g. the assignment text.textContent
= "Hide system fields", the "Only system fields were changed..." message, and
the action label strings around the history/action rendering block) with
localized lookups that read keys defined in locallang.xlf; add corresponding
keys to locallang.xlf and then use the JS localization object (e.g.
TYPO3.lang['your_extension.keyHideSystemFields'],
TYPO3.lang['your_extension.keyOnlySystemFieldsChanged'], and similarly named
keys for action labels) wherever text.textContent or action label variables are
set so the UI uses TYPO3 translations instead of hard-coded English.
| // Use the 3rd record which is known to have history entries | ||
| await contentFrame.locator('a[data-action="history"]').nth(2).click(); | ||
|
|
There was a problem hiding this comment.
Avoid fixture-order coupling in history toggle test.
Using .nth(2) (Line 64) makes the test depend on row ordering and seed shape, which is prone to CI flakiness. Select a deterministic record (e.g., by known UID/title/data attribute) before opening history.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Tests/Playwright/news/news-history.spec.ts` around lines 63 - 65, The test
currently uses contentFrame.locator('a[data-action="history"]').nth(2).click(),
which couples it to fixture ordering; instead locate the specific deterministic
record (e.g., by known UID or title) and then click its history action. Replace
the nth(2) usage by first finding the row or item via contentFrame.locator('tr',
{ hasText: 'KNOWN_TITLE' }) or by matching a data-uid/data-title attribute, then
call .locator('a[data-action="history"]').click() on that scoped locator so the
test targets the specific record reliably.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Resources/Public/JavaScript/contrib/diff.js`:
- Line 7: The bundled diff.js includes a vulnerable version of the diff library;
update the bundle to diff@8.0.3 or newer to fix the parsePatch/applyPatch DoS
vulnerability. Replace the current generated code with a re-bundled build from
diff@8.0.3+ (or latest 9.x), keeping the public API intact (functions like
parsePatch/T, applyPatch/I and exports such as createPatch/D, applyPatches/z,
diffLines/y, diffChars/t should remain available), then run the repo build to
regenerate Resources/Public/JavaScript/contrib/diff.js and verify no
API-breaking changes; commit the updated bundled file and update any
bundle/version comment if present.
- Around line 1-6: The bundled file's header comment (the jsDelivr/Rollup/Terser
notice) omits the original diff@8.0.2 BSD-3-Clause license; fix this by
inserting a license comment block immediately after that header comment that
includes the full BSD-3-Clause text and the copyright line "Copyright (c)
2009-2015, Kevin Decker <kpdecker@gmail.com>" and a brief reference to the
original package (diff@8.0.2, libesm/index.js); ensure the comment is a standard
multi-line JS comment so it remains with the bundled file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e09ddd23-21ed-4ee9-88ac-73d4fdb80619
📒 Files selected for processing (4)
Classes/Controller/AjaxController.phpResources/Private/Language/de.locallang.xlfResources/Public/JavaScript/contrib/diff.jsResources/Public/JavaScript/recordlist-action-history.js
🚧 Files skipped from review as they are similar to previous changes (2)
- Classes/Controller/AjaxController.php
- Resources/Public/JavaScript/recordlist-action-history.js
| /** | ||
| * Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.39.0. | ||
| * Original file: /npm/diff@8.0.2/libesm/index.js | ||
| * | ||
| * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files | ||
| */ |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Fetch license information for diff@8.0.2
echo "Fetching package metadata from npm registry..."
curl -s https://registry.npmjs.org/diff/8.0.2 | jq '{
name: .name,
version: .version,
license: .license,
repository: .repository.url,
author: .author
}'Repository: xima-media/xima-typo3-recordlist
Length of output: 269
🏁 Script executed:
#!/bin/bash
set -euo pipefail
PKG="diff"
VER="8.0.2"
TMPDIR="$(mktemp -d)"
echo "Downloading ${PKG}-${VER}.tgz from npm..."
curl -fsSL "https://registry.npmjs.org/${PKG}/-/items/${VER}" >/tmp/npm_items.json
# Also fetch the tarball directly via registry metadata
TARBALL_URL="$(jq -r '.items[0].package.dist.tarball' /tmp/npm_items.json)"
echo "Tarball: $TARBALL_URL"
curl -fsSL "$TARBALL_URL" -o "${TMPDIR}/${PKG}-${VER}.tgz"
mkdir -p "${TMPDIR}/unpacked"
tar -xzf "${TMPDIR}/${PKG}-${VER}.tgz" -C "${TMPDIR}/unpacked" --strip-components=1
echo "package.json:"
cat "${TMPDIR}/unpacked/package.json" | jq '{name, version, license, repository, author, contributors}'
echo
echo "LICENSE files (top-level):"
ls -la "${TMPDIR}/unpacked" | rg -i 'license|copy' || true
echo
echo "LICENSE content (first 120 lines):"
( sed -n '1,120p' "${TMPDIR}/unpacked/LICENSE" 2>/dev/null || true
sed -n '1,120p' "${TMPDIR}/unpacked/LICENSE.md" 2>/dev/null || true
sed -n '1,120p' "${TMPDIR}/unpacked/COPYRIGHT" 2>/dev/null || true
sed -n '1,120p' "${TMPDIR}/unpacked/COPYING" 2>/dev/null || true
) | head -n 120
echo
echo "Searching for copyright lines inside the package:"
rg -n "Copyright|©" "${TMPDIR}/unpacked" | head -n 20 || trueRepository: xima-media/xima-typo3-recordlist
Length of output: 183
🏁 Script executed:
#!/bin/bash
set -euo pipefail
PKG="diff"
VER="8.0.2"
TMPDIR="$(mktemp -d)"
echo "Fetching registry metadata for ${PKG}@${VER}..."
META="$(curl -fsSL "https://registry.npmjs.org/${PKG}/${VER}")"
TARBALL_URL="$(echo "$META" | jq -r '.dist.tarball')"
echo "Tarball: $TARBALL_URL"
echo "Downloading ${PKG}-${VER}.tgz..."
curl -fsSL "$TARBALL_URL" -o "${TMPDIR}/${PKG}-${VER}.tgz"
mkdir -p "${TMPDIR}/unpacked"
tar -xzf "${TMPDIR}/${PKG}-${VER}.tgz" -C "${TMPDIR}/unpacked" --strip-components=1
echo "package.json (key fields):"
jq '{name, version, license, repository, author, contributors}' "${TMPDIR}/unpacked/package.json"
echo
echo "License/COPY* files found:"
find "${TMPDIR}/unpacked" -maxdepth 2 -type f \( -iname 'license*' -o -iname 'copy*' -o -iname 'notice*' \) -print || true
echo
echo "First 120 lines of likely license file(s):"
for f in \
"${TMPDIR}/unpacked/LICENSE" \
"${TMPDIR}/unpacked/LICENSE.md" \
"${TMPDIR}/unpacked/COPYRIGHT" \
"${TMPDIR}/unpacked/COPYING" \
; do
if [ -f "$f" ]; then
echo "---- $f ----"
sed -n '1,120p' "$f" | head -n 120
fi
done
echo
echo "Searching for copyright statements inside the package:"
rg -n "Copyright|©" "${TMPDIR}/unpacked" | head -n 30 || true
echo
echo "Searching for jsdiff/Kevin Decker attribution text:"
rg -n "Kevin Decker|kpdecker|jsdiff" "${TMPDIR}/unpacked" | head -n 30 || trueRepository: xima-media/xima-typo3-recordlist
Length of output: 12699
Add BSD-3-Clause copyright/license attribution for bundled diff@8.0.2
The checked-in bundled file only contains the jsDelivr bundling/SRI note and omits the required copyright/license attribution. diff@8.0.2 is BSD-3-Clause with copyright (c) 2009-2015, Kevin Decker kpdecker@gmail.com (see LICENSE in the npm package).
📋 Recommended addition
Add a comment block after line 6 with the library's license information:
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
+/*
+ * diff v8.0.2
+ * Copyright (c) 2009-2015, Kevin Decker <kpdecker@gmail.com>
+ * License: BSD-3-Clause
+ * Repository: https://github.com/kpdecker/jsdiff
+ */
class e{diff(e,n,t={}){let o;"function"==typeof t?(o=t,t={}):"callback"in t&&(o=t.callback);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.39.0. | |
| * Original file: /npm/diff@8.0.2/libesm/index.js | |
| * | |
| * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files | |
| */ | |
| /** | |
| * Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.39.0. | |
| * Original file: /npm/diff@8.0.2/libesm/index.js | |
| * | |
| * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files | |
| */ | |
| /* | |
| * diff v8.0.2 | |
| * Copyright (c) 2009-2015, Kevin Decker <kpdecker@gmail.com> | |
| * License: BSD-3-Clause | |
| * Repository: https://github.com/kpdecker/jsdiff | |
| */ |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Resources/Public/JavaScript/contrib/diff.js` around lines 1 - 6, The bundled
file's header comment (the jsDelivr/Rollup/Terser notice) omits the original
diff@8.0.2 BSD-3-Clause license; fix this by inserting a license comment block
immediately after that header comment that includes the full BSD-3-Clause text
and the copyright line "Copyright (c) 2009-2015, Kevin Decker
<kpdecker@gmail.com>" and a brief reference to the original package (diff@8.0.2,
libesm/index.js); ensure the comment is a standard multi-line JS comment so it
remains with the bundled file.
| * | ||
| * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files | ||
| */ | ||
| class e{diff(e,n,t={}){let o;"function"==typeof t?(o=t,t={}):"callback"in t&&(o=t.callback);const s=this.castInput(e,t),r=this.castInput(n,t),l=this.removeEmpty(this.tokenize(s,t)),i=this.removeEmpty(this.tokenize(r,t));return this.diffWithOptionsObj(l,i,t,o)}diffWithOptionsObj(e,n,t,o){var s;const r=e=>(e=this.postProcess(e,t),o?void setTimeout((function(){o(e)}),0):e),l=n.length,i=e.length;let u=1,a=l+i;null!=t.maxEditLength&&(a=Math.min(a,t.maxEditLength));const c=null!==(s=t.timeout)&&void 0!==s?s:1/0,f=Date.now()+c,h=[{oldPos:-1,lastComponent:void 0}];let d=this.extractCommon(h[0],n,e,0,t);if(h[0].oldPos+1>=i&&d+1>=l)return r(this.buildValues(h[0].lastComponent,n,e));let p=-1/0,g=1/0;const m=()=>{for(let o=Math.max(p,-u);o<=Math.min(g,u);o+=2){let s;const u=h[o-1],a=h[o+1];u&&(h[o-1]=void 0);let c=!1;if(a){const e=a.oldPos-o;c=a&&0<=e&&e<l}const f=u&&u.oldPos+1<i;if(c||f){if(s=!f||c&&u.oldPos<a.oldPos?this.addToPath(a,!0,!1,0,t):this.addToPath(u,!1,!0,1,t),d=this.extractCommon(s,n,e,o,t),s.oldPos+1>=i&&d+1>=l)return r(this.buildValues(s.lastComponent,n,e))||!0;h[o]=s,s.oldPos+1>=i&&(g=Math.min(g,o-1)),d+1>=l&&(p=Math.max(p,o+1))}else h[o]=void 0}u++};if(o)!function e(){setTimeout((function(){if(u>a||Date.now()>f)return o(void 0);m()||e()}),0)}();else for(;u<=a&&Date.now()<=f;){const e=m();if(e)return e}}addToPath(e,n,t,o,s){const r=e.lastComponent;return r&&!s.oneChangePerToken&&r.added===n&&r.removed===t?{oldPos:e.oldPos+o,lastComponent:{count:r.count+1,added:n,removed:t,previousComponent:r.previousComponent}}:{oldPos:e.oldPos+o,lastComponent:{count:1,added:n,removed:t,previousComponent:r}}}extractCommon(e,n,t,o,s){const r=n.length,l=t.length;let i=e.oldPos,u=i-o,a=0;for(;u+1<r&&i+1<l&&this.equals(t[i+1],n[u+1],s);)u++,i++,a++,s.oneChangePerToken&&(e.lastComponent={count:1,previousComponent:e.lastComponent,added:!1,removed:!1});return a&&!s.oneChangePerToken&&(e.lastComponent={count:a,previousComponent:e.lastComponent,added:!1,removed:!1}),e.oldPos=i,u}equals(e,n,t){return t.comparator?t.comparator(e,n):e===n||!!t.ignoreCase&&e.toLowerCase()===n.toLowerCase()}removeEmpty(e){const n=[];for(let t=0;t<e.length;t++)e[t]&&n.push(e[t]);return n}castInput(e,n){return e}tokenize(e,n){return Array.from(e)}join(e){return e.join("")}postProcess(e,n){return e}get useLongestToken(){return!1}buildValues(e,n,t){const o=[];let s;for(;e;)o.push(e),s=e.previousComponent,delete e.previousComponent,e=s;o.reverse();const r=o.length;let l=0,i=0,u=0;for(;l<r;l++){const e=o[l];if(e.removed)e.value=this.join(t.slice(u,u+e.count)),u+=e.count;else{if(!e.added&&this.useLongestToken){let o=n.slice(i,i+e.count);o=o.map((function(e,n){const o=t[u+n];return o.length>e.length?o:e})),e.value=this.join(o)}else e.value=this.join(n.slice(i,i+e.count));i+=e.count,e.added||(u+=e.count)}}return o}}const n=new class extends e{};function t(e,t,o){return n.diff(e,t,o)}function o(e,n){let t;for(t=0;t<e.length&&t<n.length;t++)if(e[t]!=n[t])return e.slice(0,t);return e.slice(0,t)}function s(e,n){let t;if(!e||!n||e[e.length-1]!=n[n.length-1])return"";for(t=0;t<e.length&&t<n.length;t++)if(e[e.length-(t+1)]!=n[n.length-(t+1)])return e.slice(-t);return e.slice(-t)}function r(e,n,t){if(e.slice(0,n.length)!=n)throw Error(`string ${JSON.stringify(e)} doesn't start with prefix ${JSON.stringify(n)}; this is a bug`);return t+e.slice(n.length)}function l(e,n,t){if(!n)return e+t;if(e.slice(-n.length)!=n)throw Error(`string ${JSON.stringify(e)} doesn't end with suffix ${JSON.stringify(n)}; this is a bug`);return e.slice(0,-n.length)+t}function i(e,n){return r(e,n,"")}function u(e,n){return l(e,n,"")}function a(e,n){return n.slice(0,function(e,n){let t=0;e.length>n.length&&(t=e.length-n.length);let o=n.length;e.length<n.length&&(o=e.length);const s=Array(o);let r=0;s[0]=0;for(let e=1;e<o;e++){for(n[e]==n[r]?s[e]=s[r]:s[e]=r;r>0&&n[e]!=n[r];)r=s[r];n[e]==n[r]&&r++}r=0;for(let o=t;o<e.length;o++){for(;r>0&&e[o]!=n[r];)r=s[r];e[o]==n[r]&&r++}return r}(e,n))}function c(e){let n;for(n=e.length-1;n>=0&&e[n].match(/\s/);n--);return e.substring(n+1)}function f(e){const n=e.match(/^\s*/);return n?n[0]:""}const h="a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}",d=new RegExp(`[${h}]+|\\s+|[^${h}]`,"ug");const p=new class extends e{equals(e,n,t){return t.ignoreCase&&(e=e.toLowerCase(),n=n.toLowerCase()),e.trim()===n.trim()}tokenize(e,n={}){let t;if(n.intlSegmenter){const o=n.intlSegmenter;if("word"!=o.resolvedOptions().granularity)throw new Error('The segmenter passed must have a granularity of "word"');t=Array.from(o.segment(e),(e=>e.segment))}else t=e.match(d)||[];const o=[];let s=null;return t.forEach((e=>{/\s/.test(e)?null==s?o.push(e):o.push(o.pop()+e):null!=s&&/\s/.test(s)?o[o.length-1]==s?o.push(o.pop()+e):o.push(s+e):o.push(e),s=e})),o}join(e){return e.map(((e,n)=>0==n?e:e.replace(/^\s+/,""))).join("")}postProcess(e,n){if(!e||n.oneChangePerToken)return e;let t=null,o=null,s=null;return e.forEach((e=>{e.added?o=e:e.removed?s=e:((o||s)&&m(t,s,o,e),t=e,o=null,s=null)})),(o||s)&&m(t,s,o,null),e}};function g(e,n,t){return null==(null==t?void 0:t.ignoreWhitespace)||t.ignoreWhitespace?p.diff(e,n,t):w(e,n,t)}function m(e,n,t,h){if(n&&t){const a=f(n.value),d=c(n.value),p=f(t.value),g=c(t.value);if(e){const s=o(a,p);e.value=l(e.value,p,s),n.value=i(n.value,s),t.value=i(t.value,s)}if(h){const e=s(d,g);h.value=r(h.value,g,e),n.value=u(n.value,e),t.value=u(t.value,e)}}else if(t){if(e){const e=f(t.value);t.value=t.value.substring(e.length)}if(h){const e=f(h.value);h.value=h.value.substring(e.length)}}else if(e&&h){const t=f(h.value),a=f(n.value),d=c(n.value),p=o(t,a);n.value=i(n.value,p);const g=s(i(t,p),d);n.value=u(n.value,g),h.value=r(h.value,t,g),e.value=l(e.value,t,t.slice(0,t.length-g.length))}else if(h){const e=f(h.value),t=a(c(n.value),e);n.value=u(n.value,t)}else if(e){const t=a(c(e.value),f(n.value));n.value=i(n.value,t)}}const v=new class extends e{tokenize(e){const n=new RegExp(`(\\r?\\n)|[${h}]+|[^\\S\\n\\r]+|[^${h}]`,"ug");return e.match(n)||[]}};function w(e,n,t){return v.diff(e,n,t)}const k=new class extends e{constructor(){super(...arguments),this.tokenize=L}equals(e,n,t){return t.ignoreWhitespace?(t.newlineIsToken&&e.includes("\n")||(e=e.trim()),t.newlineIsToken&&n.includes("\n")||(n=n.trim())):t.ignoreNewlineAtEof&&!t.newlineIsToken&&(e.endsWith("\n")&&(e=e.slice(0,-1)),n.endsWith("\n")&&(n=n.slice(0,-1))),super.equals(e,n,t)}};function y(e,n,t){return k.diff(e,n,t)}function b(e,n,t){return t=function(e,n){if("function"==typeof e)n.callback=e;else if(e)for(const t in e)Object.prototype.hasOwnProperty.call(e,t)&&(n[t]=e[t]);return n}(t,{ignoreWhitespace:!0}),k.diff(e,n,t)}function L(e,n){n.stripTrailingCr&&(e=e.replace(/\r\n/g,"\n"));const t=[],o=e.split(/(\n|\r\n)/);o[o.length-1]||o.pop();for(let e=0;e<o.length;e++){const s=o[e];e%2&&!n.newlineIsToken?t[t.length-1]+=s:t.push(s)}return t}const C=new class extends e{tokenize(e){var n;const t=[];let o=0;for(let r=0;r<e.length;r++){if(r==e.length-1){t.push(e.slice(o));break}if(("."==(s=e[r])||"!"==s||"?"==s)&&e[r+1].match(/\s/)){for(t.push(e.slice(o,r+1)),r=o=r+1;null===(n=e[r+1])||void 0===n?void 0:n.match(/\s/);)r++;t.push(e.slice(o,r+1)),o=r+1}}var s;return t}};function j(e,n,t){return C.diff(e,n,t)}const O=new class extends e{tokenize(e){return e.split(/([{}:;,]|\s+)/)}};function x(e,n,t){return O.diff(e,n,t)}const S=new class extends e{constructor(){super(...arguments),this.tokenize=L}get useLongestToken(){return!0}castInput(e,n){const{undefinedReplacement:t,stringifyReplacer:o=(e,n)=>void 0===n?t:n}=n;return"string"==typeof e?e:JSON.stringify(E(e,null,null,o),null," ")}equals(e,n,t){return super.equals(e.replace(/,([\r\n])/g,"$1"),n.replace(/,([\r\n])/g,"$1"),t)}};function P(e,n,t){return S.diff(e,n,t)}function E(e,n,t,o,s){let r,l;for(n=n||[],t=t||[],o&&(e=o(void 0===s?"":s,e)),r=0;r<n.length;r+=1)if(n[r]===e)return t[r];if("[object Array]"===Object.prototype.toString.call(e)){for(n.push(e),l=new Array(e.length),t.push(l),r=0;r<e.length;r+=1)l[r]=E(e[r],n,t,o,String(r));return n.pop(),t.pop(),l}if(e&&e.toJSON&&(e=e.toJSON()),"object"==typeof e&&null!==e){n.push(e),l={},t.push(l);const s=[];let i;for(i in e)Object.prototype.hasOwnProperty.call(e,i)&&s.push(i);for(s.sort(),r=0;r<s.length;r+=1)i=s[r],l[i]=E(e[i],n,t,o,i);n.pop(),t.pop()}else l=e;return l}const F=new class extends e{tokenize(e){return e.slice()}join(e){return e}removeEmpty(e){return e}};function N(e,n,t){return F.diff(e,n,t)}function W(e){return Array.isArray(e)?e.map((e=>W(e))):Object.assign(Object.assign({},e),{hunks:e.hunks.map((e=>Object.assign(Object.assign({},e),{lines:e.lines.map(((n,t)=>{var o;return n.startsWith("\\")||n.endsWith("\r")||(null===(o=e.lines[t+1])||void 0===o?void 0:o.startsWith("\\"))?n:n+"\r"}))})))})}function A(e){return Array.isArray(e)?e.map((e=>A(e))):Object.assign(Object.assign({},e),{hunks:e.hunks.map((e=>Object.assign(Object.assign({},e),{lines:e.lines.map((e=>e.endsWith("\r")?e.substring(0,e.length-1):e))})))})}function T(e){const n=e.split(/\n/),t=[];let o=0;function s(){const e={};for(t.push(e);o<n.length;){const t=n[o];if(/^(---|\+\+\+|@@)\s/.test(t))break;const s=/^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(t);s&&(e.index=s[1]),o++}for(r(e),r(e),e.hunks=[];o<n.length;){const t=n[o];if(/^(Index:\s|diff\s|---\s|\+\+\+\s|===================================================================)/.test(t))break;if(/^@@/.test(t))e.hunks.push(l());else{if(t)throw new Error("Unknown line "+(o+1)+" "+JSON.stringify(t));o++}}}function r(e){const t=/^(---|\+\+\+)\s+(.*)\r?$/.exec(n[o]);if(t){const n=t[2].split("\t",2),s=(n[1]||"").trim();let r=n[0].replace(/\\\\/g,"\\");/^".*"$/.test(r)&&(r=r.substr(1,r.length-2)),"---"===t[1]?(e.oldFileName=r,e.oldHeader=s):(e.newFileName=r,e.newHeader=s),o++}}function l(){var e;const t=o,s=n[o++].split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/),r={oldStart:+s[1],oldLines:void 0===s[2]?1:+s[2],newStart:+s[3],newLines:void 0===s[4]?1:+s[4],lines:[]};0===r.oldLines&&(r.oldStart+=1),0===r.newLines&&(r.newStart+=1);let l=0,i=0;for(;o<n.length&&(i<r.oldLines||l<r.newLines||(null===(e=n[o])||void 0===e?void 0:e.startsWith("\\")));o++){const e=0==n[o].length&&o!=n.length-1?" ":n[o][0];if("+"!==e&&"-"!==e&&" "!==e&&"\\"!==e)throw new Error(`Hunk at line ${t+1} contained invalid line ${n[o]}`);r.lines.push(n[o]),"+"===e?l++:"-"===e?i++:" "===e&&(l++,i++)}if(l||1!==r.newLines||(r.newLines=0),i||1!==r.oldLines||(r.oldLines=0),l!==r.newLines)throw new Error("Added line count did not match for hunk at line "+(t+1));if(i!==r.oldLines)throw new Error("Removed line count did not match for hunk at line "+(t+1));return r}for(;o<n.length;)s();return t}function $(e,n,t){let o=!0,s=!1,r=!1,l=1;return function i(){if(o&&!r){if(s?l++:o=!1,e+l<=t)return e+l;r=!0}if(!s)return r||(o=!0),n<=e-l?e-l++:(s=!0,i())}}function I(e,n,t={}){let o;if(o="string"==typeof n?T(n):Array.isArray(n)?n:[n],o.length>1)throw new Error("applyPatch only works with a single input.");return function(e,n,t={}){(t.autoConvertLineEndings||null==t.autoConvertLineEndings)&&((o=e).includes("\r\n")&&!o.startsWith("\n")&&!o.match(/[^\r]\n/)&&function(e){return Array.isArray(e)||(e=[e]),!e.some((e=>e.hunks.some((e=>e.lines.some((e=>!e.startsWith("\\")&&e.endsWith("\r")))))))}(n)?n=W(n):function(e){return!e.includes("\r\n")&&e.includes("\n")}(e)&&function(e){return Array.isArray(e)||(e=[e]),e.some((e=>e.hunks.some((e=>e.lines.some((e=>e.endsWith("\r")))))))&&e.every((e=>e.hunks.every((e=>e.lines.every(((n,t)=>{var o;return n.startsWith("\\")||n.endsWith("\r")||(null===(o=e.lines[t+1])||void 0===o?void 0:o.startsWith("\\"))}))))))}(n)&&(n=A(n)));var o;const s=e.split("\n"),r=n.hunks,l=t.compareLine||((e,n,t,o)=>n===o),i=t.fuzzFactor||0;let u=0;if(i<0||!Number.isInteger(i))throw new Error("fuzzFactor must be a non-negative integer");if(!r.length)return e;let a="",c=!1,f=!1;for(let e=0;e<r[r.length-1].lines.length;e++){const n=r[r.length-1].lines[e];"\\"==n[0]&&("+"==a[0]?c=!0:"-"==a[0]&&(f=!0)),a=n}if(c){if(f){if(!i&&""==s[s.length-1])return!1}else if(""==s[s.length-1])s.pop();else if(!i)return!1}else if(f)if(""!=s[s.length-1])s.push("");else if(!i)return!1;function h(e,n,t,o=0,r=!0,i=[],u=0){let a=0,c=!1;for(;o<e.length;o++){const f=e[o],d=f.length>0?f[0]:" ",p=f.length>0?f.substr(1):f;if("-"===d){if(!l(n+1,s[n],d,p))return t&&null!=s[n]?(i[u]=s[n],h(e,n+1,t-1,o,!1,i,u+1)):null;n++,a=0}if("+"===d){if(!r)return null;i[u]=p,u++,a=0,c=!0}if(" "===d){if(a++,i[u]=s[n],!l(n+1,s[n],d,p))return c||!t?null:s[n]&&(h(e,n+1,t-1,o+1,!1,i,u+1)||h(e,n+1,t-1,o,!1,i,u+1))||h(e,n,t-1,o+1,!1,i,u);u++,r=!0,c=!1,n++}}return u-=a,n-=a,i.length=u,{patchedLines:i,oldLineLastI:n-1}}const d=[];let p=0;for(let e=0;e<r.length;e++){const n=r[e];let t;const o=s.length-n.oldLines+i;let l;for(let e=0;e<=i;e++){l=n.oldStart+p-1;const s=$(l,u,o);for(;void 0!==l&&(t=h(n.lines,l,e),!t);l=s());if(t)break}if(!t)return!1;for(let e=u;e<l;e++)d.push(s[e]);for(let e=0;e<t.patchedLines.length;e++){const n=t.patchedLines[e];d.push(n)}u=t.oldLineLastI+1,p=l+1-n.oldStart}for(let e=u;e<s.length;e++)d.push(s[e]);return d.join("\n")}(e,o[0],t)}function z(e,n){const t="string"==typeof e?T(e):e;let o=0;!function e(){const s=t[o++];if(!s)return n.complete();n.loadFile(s,(function(t,o){if(t)return n.complete(t);const r=I(o,s,n);n.patched(s,r,(function(t){if(t)return n.complete(t);e()}))}))}()}function H(e){return Array.isArray(e)?e.map((e=>H(e))).reverse():Object.assign(Object.assign({},e),{oldFileName:e.newFileName,oldHeader:e.newHeader,newFileName:e.oldFileName,newHeader:e.oldHeader,hunks:e.hunks.map((e=>({oldLines:e.newLines,oldStart:e.newStart,newLines:e.oldLines,newStart:e.oldStart,lines:e.lines.map((e=>e.startsWith("-")?`+${e.slice(1)}`:e.startsWith("+")?`-${e.slice(1)}`:e))})))})}function q(e,n,t,o,s,r,l){let i;i=l?"function"==typeof l?{callback:l}:l:{},void 0===i.context&&(i.context=4);const u=i.context;if(i.newlineIsToken)throw new Error("newlineIsToken may not be used with patch-generation functions, only with diffing functions");if(!i.callback)return a(y(t,o,i));{const{callback:e}=i;y(t,o,Object.assign(Object.assign({},i),{callback:n=>{const t=a(n);e(t)}}))}function a(t){if(!t)return;function o(e){return e.map((function(e){return" "+e}))}t.push({value:"",lines:[]});const l=[];let i=0,a=0,c=[],f=1,h=1;for(let e=0;e<t.length;e++){const n=t[e],s=n.lines||R(n.value);if(n.lines=s,n.added||n.removed){if(!i){const n=t[e-1];i=f,a=h,n&&(c=u>0?o(n.lines.slice(-u)):[],i-=c.length,a-=c.length)}for(const e of s)c.push((n.added?"+":"-")+e);n.added?h+=s.length:f+=s.length}else{if(i)if(s.length<=2*u&&e<t.length-2)for(const e of o(s))c.push(e);else{const e=Math.min(s.length,u);for(const n of o(s.slice(0,e)))c.push(n);const n={oldStart:i,oldLines:f-i+e,newStart:a,newLines:h-a+e,lines:c};l.push(n),i=0,a=0,c=[]}f+=s.length,h+=s.length}}for(const e of l)for(let n=0;n<e.lines.length;n++)e.lines[n].endsWith("\n")?e.lines[n]=e.lines[n].slice(0,-1):(e.lines.splice(n+1,0,"\\ No newline at end of file"),n++);return{oldFileName:e,newFileName:n,oldHeader:s,newHeader:r,hunks:l}}}function J(e){if(Array.isArray(e))return e.map(J).join("\n");const n=[];e.oldFileName==e.newFileName&&n.push("Index: "+e.oldFileName),n.push("==================================================================="),n.push("--- "+e.oldFileName+(void 0===e.oldHeader?"":"\t"+e.oldHeader)),n.push("+++ "+e.newFileName+(void 0===e.newHeader?"":"\t"+e.newHeader));for(let t=0;t<e.hunks.length;t++){const o=e.hunks[t];0===o.oldLines&&(o.oldStart-=1),0===o.newLines&&(o.newStart-=1),n.push("@@ -"+o.oldStart+","+o.oldLines+" +"+o.newStart+","+o.newLines+" @@");for(const e of o.lines)n.push(e)}return n.join("\n")+"\n"}function D(e,n,t,o,s,r,l){if("function"==typeof l&&(l={callback:l}),!(null==l?void 0:l.callback)){const i=q(e,n,t,o,s,r,l);if(!i)return;return J(i)}{const{callback:i}=l;q(e,n,t,o,s,r,Object.assign(Object.assign({},l),{callback:e=>{i(e?J(e):void 0)}}))}}function M(e,n,t,o,s,r){return D(e,e,n,t,o,s,r)}function R(e){const n=e.endsWith("\n"),t=e.split("\n").map((e=>e+"\n"));return n?t.pop():t.push(t.pop().slice(0,-1)),t}function V(e){const n=[];let t,o;for(let s=0;s<e.length;s++)t=e[s],o=t.added?1:t.removed?-1:0,n.push([o,t.value]);return n}function U(e){const n=[];for(let t=0;t<e.length;t++){const o=e[t];o.added?n.push("<ins>"):o.removed&&n.push("<del>"),n.push(Z(o.value)),o.added?n.push("</ins>"):o.removed&&n.push("</del>")}return n.join("")}function Z(e){let n=e;return n=n.replace(/&/g,"&"),n=n.replace(/</g,"<"),n=n.replace(/>/g,">"),n=n.replace(/"/g,"""),n}export{e as Diff,I as applyPatch,z as applyPatches,F as arrayDiff,E as canonicalize,n as characterDiff,V as convertChangesToDMP,U as convertChangesToXML,M as createPatch,D as createTwoFilesPatch,O as cssDiff,N as diffArrays,t as diffChars,x as diffCss,P as diffJson,y as diffLines,j as diffSentences,b as diffTrimmedLines,g as diffWords,w as diffWordsWithSpace,J as formatPatch,S as jsonDiff,k as lineDiff,T as parsePatch,H as reversePatch,C as sentenceDiff,q as structuredPatch,p as wordDiff,v as wordsWithSpaceDiff};export default null; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check for security advisories and latest version of diff library
echo "=== Latest version ==="
curl -s https://registry.npmjs.org/diff | jq -r '.["dist-tags"].latest'
echo ""
echo "=== Security advisories for diff package ==="
gh api graphql -f query='
{
securityVulnerabilities(first: 10, ecosystem: NPM, package: "diff") {
nodes {
advisory {
summary
severity
publishedAt
}
vulnerableVersionRange
firstPatchedVersion {
identifier
}
}
}
}'Repository: xima-media/xima-typo3-recordlist
Length of output: 1376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Locate JS lockfiles / manifests ==="
ls -1 package.json 2>/dev/null || true
find . -maxdepth 4 -name package-lock.json -o -name yarn.lock -o -name pnpm-lock.yaml -o -name npm-shrinkwrap.json | sed 's|^\./||' || true
echo
echo "=== Find resolved version of NPM package 'diff' ==="
# Try lockfiles first
for f in $(find . -maxdepth 4 -name package-lock.json -o -name yarn.lock -o -name pnpm-lock.yaml -o -name npm-shrinkwrap.json 2>/dev/null); do
echo "--- $f ---"
if [[ "$f" == *package-lock.json || "$f" == *npm-shrinkwrap.json ]]; then
node -e "const fs=require('fs');const j=JSON.parse(fs.readFileSync('$f','utf8'));
const pkg=j.packages?.['node_modules/diff'] || j.dependencies?.diff;
const v=pkg?.version;
console.log(v?`diff version: ${v}`:'diff version: (not found)');" 2>/dev/null || true
elif [[ "$f" == *yarn.lock ]]; then
rg -n "diff@|\"diff@|diff\":" "$f" | head -n 30 || true
rg -n "diff@" "$f" | head -n 5 || true
elif [[ "$f" == *pnpm-lock.yaml ]]; then
rg -n "diff:" "$f" | head -n 30 || true
fi
echo
done
# Fallback: scan package.json dependencies
echo "=== package.json dependency ranges ==="
find . -maxdepth 4 -name package.json 2>/dev/null | head -n 20 | while read -r pf; do
echo "--- $pf ---"
rg -n '"diff"\s*:\s*"' "$pf" || true
doneRepository: xima-media/xima-typo3-recordlist
Length of output: 208
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for diff version markers in diff.js ==="
rg -n "8\.0\.2|diff@|jsdiff|version" Resources/Public/JavaScript/contrib/diff.js || true
echo
echo "=== Search for parsePatch / applyPatch usage within the repo ==="
rg -n "parsePatch|applyPatch|applyPatches" . || trueRepository: xima-media/xima-typo3-recordlist
Length of output: 17451
Fix bundled diff (v8.0.2) known DoS vulnerability
Resources/Public/JavaScript/contrib/diff.js contains diff@8.0.2 (“Original file: /npm/diff@8.0.2/libesm/index.js”). The diff package has a known Denial-of-Service issue affecting parsePatch/applyPatch for versions >= 6.0.0, < 8.0.3 (first patched in 8.0.3), so this bundle is in the vulnerable range. Upgrade and re-bundle to diff@8.0.3 or newer (latest is 9.0.0).
🧰 Tools
🪛 OpenGrep (1.22.0)
[ERROR] 7-7: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
[ERROR] 7-7: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Resources/Public/JavaScript/contrib/diff.js` at line 7, The bundled diff.js
includes a vulnerable version of the diff library; update the bundle to
diff@8.0.3 or newer to fix the parsePatch/applyPatch DoS vulnerability. Replace
the current generated code with a re-bundled build from diff@8.0.3+ (or latest
9.x), keeping the public API intact (functions like parsePatch/T, applyPatch/I
and exports such as createPatch/D, applyPatches/z, diffLines/y, diffChars/t
should remain available), then run the repo build to regenerate
Resources/Public/JavaScript/contrib/diff.js and verify no API-breaking changes;
commit the updated bundled file and update any bundle/version comment if
present.
resolves #82
Summary by CodeRabbit
New Features
Documentation
Tests
Localization