dsh-diff-approval 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +138 -78
- package/lib/index.js +39 -8
- package/lib/types/client/whole-file-diff.d.ts +4 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -643,15 +643,21 @@ window.__ModuleLoader__.load({
|
|
|
643
643
|
* Compute the whole-file view between two contents. The context budget is the
|
|
644
644
|
* side lengths, so every hunk covers the file and unchanged lines survive as
|
|
645
645
|
* context rows; the `` patch marker is annotation
|
|
646
|
-
* and never becomes a row.
|
|
646
|
+
* and never becomes a row. Both sides are line-ending normalized (`\r\n?` →
|
|
647
|
+
* `\n`) first, so a repo baseline stored with one EOL and a worktree with
|
|
648
|
+
* another never show as a whole-file delete+add — the diff is about approved
|
|
649
|
+
* content, not line-ending noise.
|
|
647
650
|
* @param oldText - the file content before the pending change.
|
|
648
651
|
* @param newText - the file content after the pending change.
|
|
649
652
|
* @returns the complete row list with totals.
|
|
650
653
|
*/
|
|
651
654
|
function computeWholeFileDiff(oldText, newText) {
|
|
655
|
+
oldText = oldText.replace(/\r\n?/g, "\n");
|
|
656
|
+
newText = newText.replace(/\r\n?/g, "\n");
|
|
652
657
|
const oldLines = contentLines(oldText);
|
|
653
658
|
const newLines = contentLines(newText);
|
|
654
|
-
const
|
|
659
|
+
const context = Math.max(1, oldLines.length, newLines.length);
|
|
660
|
+
const patch = structuredPatch("", "", oldText, newText, void 0, void 0, { context });
|
|
655
661
|
const rows = [];
|
|
656
662
|
let removed = 0;
|
|
657
663
|
let added = 0;
|
|
@@ -11692,99 +11698,99 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
11692
11698
|
document.head.appendChild(tag);
|
|
11693
11699
|
}
|
|
11694
11700
|
var PendingPanel_module_css_default = {
|
|
11695
|
-
"
|
|
11696
|
-
"panel": "F1KBNa_panel",
|
|
11697
|
-
"settingsRow": "F1KBNa_settingsRow",
|
|
11698
|
-
"searchBar": "F1KBNa_searchBar",
|
|
11701
|
+
"divergedHint": "F1KBNa_divergedHint",
|
|
11699
11702
|
"detail": "F1KBNa_detail",
|
|
11700
|
-
"
|
|
11703
|
+
"fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
|
|
11704
|
+
"diffBody": "F1KBNa_diffBody",
|
|
11705
|
+
"langSelect": "F1KBNa_langSelect",
|
|
11706
|
+
"note": "F1KBNa_note",
|
|
11707
|
+
"code": "F1KBNa_code",
|
|
11708
|
+
"diffHeader": "F1KBNa_diffHeader",
|
|
11709
|
+
"overviewRuler": "F1KBNa_overviewRuler",
|
|
11710
|
+
"readError": "F1KBNa_readError",
|
|
11711
|
+
"gutter": "F1KBNa_gutter",
|
|
11701
11712
|
"markerAdd": "F1KBNa_markerAdd",
|
|
11713
|
+
"del": "F1KBNa_del",
|
|
11714
|
+
"fileList": "F1KBNa_fileList",
|
|
11715
|
+
"delCount": "F1KBNa_delCount",
|
|
11716
|
+
"diffBodyWrap": "F1KBNa_diffBodyWrap",
|
|
11717
|
+
"blockFlash": "F1KBNa_blockFlash",
|
|
11718
|
+
"context": "F1KBNa_context",
|
|
11719
|
+
"fileListFloat": "F1KBNa_fileListFloat",
|
|
11702
11720
|
"diff": "F1KBNa_diff",
|
|
11703
|
-
"
|
|
11721
|
+
"rowMeta": "F1KBNa_rowMeta",
|
|
11722
|
+
"panel": "F1KBNa_panel",
|
|
11723
|
+
"title": "F1KBNa_title",
|
|
11724
|
+
"expand": "F1KBNa_expand",
|
|
11725
|
+
"lines": "F1KBNa_lines",
|
|
11704
11726
|
"missingHint": "F1KBNa_missingHint",
|
|
11727
|
+
"headerActions": "F1KBNa_headerActions",
|
|
11728
|
+
"settingsRowDesc": "F1KBNa_settingsRowDesc",
|
|
11729
|
+
"close": "F1KBNa_close",
|
|
11730
|
+
"iconAction": "F1KBNa_iconAction",
|
|
11731
|
+
"rail": "F1KBNa_rail",
|
|
11705
11732
|
"wrap": "F1KBNa_wrap",
|
|
11706
|
-
"
|
|
11707
|
-
"context": "F1KBNa_context",
|
|
11733
|
+
"diffPath": "F1KBNa_diffPath",
|
|
11708
11734
|
"importNote": "F1KBNa_importNote",
|
|
11709
|
-
"
|
|
11710
|
-
"
|
|
11711
|
-
"
|
|
11712
|
-
"
|
|
11713
|
-
"
|
|
11714
|
-
"
|
|
11735
|
+
"missing": "F1KBNa_missing",
|
|
11736
|
+
"footerButtons": "F1KBNa_footerButtons",
|
|
11737
|
+
"resizeHandle": "F1KBNa_resizeHandle",
|
|
11738
|
+
"line": "F1KBNa_line",
|
|
11739
|
+
"kindTag": "F1KBNa_kindTag",
|
|
11740
|
+
"bulkActions": "F1KBNa_bulkActions",
|
|
11741
|
+
"vSpacer": "F1KBNa_vSpacer",
|
|
11715
11742
|
"emptyState": "F1KBNa_emptyState",
|
|
11716
|
-
"
|
|
11717
|
-
"
|
|
11718
|
-
"
|
|
11719
|
-
"addCount": "F1KBNa_addCount",
|
|
11720
|
-
"subline": "F1KBNa_subline",
|
|
11743
|
+
"badgeLabel": "F1KBNa_badgeLabel",
|
|
11744
|
+
"rowFailed": "F1KBNa_rowFailed",
|
|
11745
|
+
"layer": "F1KBNa_layer",
|
|
11721
11746
|
"badgeCount": "F1KBNa_badgeCount",
|
|
11722
|
-
"
|
|
11747
|
+
"noteCentered": "F1KBNa_noteCentered",
|
|
11748
|
+
"settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
|
|
11749
|
+
"importButton": "F1KBNa_importButton",
|
|
11723
11750
|
"header": "F1KBNa_header",
|
|
11724
|
-
"
|
|
11725
|
-
"blockActions": "F1KBNa_blockActions",
|
|
11751
|
+
"action": "F1KBNa_action",
|
|
11726
11752
|
"actionError": "F1KBNa_actionError",
|
|
11727
|
-
"
|
|
11728
|
-
"
|
|
11729
|
-
"
|
|
11730
|
-
"
|
|
11731
|
-
"
|
|
11732
|
-
"
|
|
11733
|
-
"
|
|
11734
|
-
"
|
|
11735
|
-
"
|
|
11736
|
-
"
|
|
11737
|
-
"diffStats": "F1KBNa_diffStats",
|
|
11753
|
+
"flexSpacer": "F1KBNa_flexSpacer",
|
|
11754
|
+
"settingsRowText": "F1KBNa_settingsRowText",
|
|
11755
|
+
"actionQuietDisabled": "F1KBNa_actionQuietDisabled",
|
|
11756
|
+
"blockActions": "F1KBNa_blockActions",
|
|
11757
|
+
"listScroll": "F1KBNa_listScroll",
|
|
11758
|
+
"hint": "F1KBNa_hint",
|
|
11759
|
+
"kindHint": "F1KBNa_kindHint",
|
|
11760
|
+
"diffActions": "F1KBNa_diffActions",
|
|
11761
|
+
"expandExpanded": "F1KBNa_expandExpanded",
|
|
11762
|
+
"settingsRow": "F1KBNa_settingsRow",
|
|
11738
11763
|
"blockPosition": "F1KBNa_blockPosition",
|
|
11764
|
+
"statusAction": "F1KBNa_statusAction",
|
|
11765
|
+
"searchBar": "F1KBNa_searchBar",
|
|
11739
11766
|
"searchInput": "F1KBNa_searchInput",
|
|
11740
|
-
"
|
|
11741
|
-
"
|
|
11742
|
-
"
|
|
11743
|
-
"actionQuietDisabled": "F1KBNa_actionQuietDisabled",
|
|
11744
|
-
"code": "F1KBNa_code",
|
|
11745
|
-
"langSelect": "F1KBNa_langSelect",
|
|
11746
|
-
"kindTag": "F1KBNa_kindTag",
|
|
11747
|
-
"diffFlash": "F1KBNa_diffFlash",
|
|
11748
|
-
"footerButtons": "F1KBNa_footerButtons",
|
|
11749
|
-
"missing": "F1KBNa_missing",
|
|
11750
|
-
"layer": "F1KBNa_layer",
|
|
11751
|
-
"noticeButton": "F1KBNa_noticeButton",
|
|
11752
|
-
"fileList": "F1KBNa_fileList",
|
|
11753
|
-
"add": "F1KBNa_add",
|
|
11754
|
-
"split": "F1KBNa_split",
|
|
11767
|
+
"rows": "F1KBNa_rows",
|
|
11768
|
+
"statusBar": "F1KBNa_statusBar",
|
|
11769
|
+
"settingsPage": "F1KBNa_settingsPage",
|
|
11755
11770
|
"overviewMarker": "F1KBNa_overviewMarker",
|
|
11756
|
-
"
|
|
11757
|
-
"
|
|
11758
|
-
"badgeLabel": "F1KBNa_badgeLabel",
|
|
11759
|
-
"fileListFloat": "F1KBNa_fileListFloat",
|
|
11760
|
-
"expand": "F1KBNa_expand",
|
|
11761
|
-
"diffBodyWrap": "F1KBNa_diffBodyWrap",
|
|
11762
|
-
"lines": "F1KBNa_lines",
|
|
11763
|
-
"action": "F1KBNa_action",
|
|
11764
|
-
"statusAction": "F1KBNa_statusAction",
|
|
11771
|
+
"add": "F1KBNa_add",
|
|
11772
|
+
"langLabel": "F1KBNa_langLabel",
|
|
11765
11773
|
"notice": "F1KBNa_notice",
|
|
11766
|
-
"fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
|
|
11767
|
-
"settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
|
|
11768
|
-
"delCount": "F1KBNa_delCount",
|
|
11769
|
-
"flexSpacer": "F1KBNa_flexSpacer",
|
|
11770
|
-
"markerDel": "F1KBNa_markerDel",
|
|
11771
|
-
"note": "F1KBNa_note",
|
|
11772
11774
|
"states": "F1KBNa_states",
|
|
11773
|
-
"rowFailed": "F1KBNa_rowFailed",
|
|
11774
|
-
"settingsPage": "F1KBNa_settingsPage",
|
|
11775
|
-
"rowHead": "F1KBNa_rowHead",
|
|
11776
|
-
"headerActions": "F1KBNa_headerActions",
|
|
11777
|
-
"group": "F1KBNa_group",
|
|
11778
|
-
"settingsRowTitle": "F1KBNa_settingsRowTitle",
|
|
11779
11775
|
"rowPath": "F1KBNa_rowPath",
|
|
11776
|
+
"searchCount": "F1KBNa_searchCount",
|
|
11777
|
+
"diffStats": "F1KBNa_diffStats",
|
|
11778
|
+
"badge": "F1KBNa_badge",
|
|
11779
|
+
"markerDel": "F1KBNa_markerDel",
|
|
11780
|
+
"addCount": "F1KBNa_addCount",
|
|
11781
|
+
"settingsSelector": "F1KBNa_settingsSelector",
|
|
11782
|
+
"diffFlash": "F1KBNa_diffFlash",
|
|
11783
|
+
"subline": "F1KBNa_subline",
|
|
11784
|
+
"split": "F1KBNa_split",
|
|
11780
11785
|
"detailEmpty": "F1KBNa_detailEmpty",
|
|
11781
|
-
"
|
|
11782
|
-
"
|
|
11783
|
-
"
|
|
11784
|
-
"
|
|
11785
|
-
"
|
|
11786
|
-
"
|
|
11787
|
-
"
|
|
11786
|
+
"noticeButton": "F1KBNa_noticeButton",
|
|
11787
|
+
"row": "F1KBNa_row",
|
|
11788
|
+
"wrapActive": "F1KBNa_wrapActive",
|
|
11789
|
+
"settingsRowTitle": "F1KBNa_settingsRowTitle",
|
|
11790
|
+
"group": "F1KBNa_group",
|
|
11791
|
+
"rowHead": "F1KBNa_rowHead",
|
|
11792
|
+
"actionPrimary": "F1KBNa_actionPrimary",
|
|
11793
|
+
"noticeText": "F1KBNa_noticeText"
|
|
11788
11794
|
};
|
|
11789
11795
|
//#endregion
|
|
11790
11796
|
//#region lib/types/client/PendingPanel.js
|
|
@@ -14284,10 +14290,64 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
|
|
|
14284
14290
|
"sessions"
|
|
14285
14291
|
];
|
|
14286
14292
|
/**
|
|
14293
|
+
* The dsh web-react renderer gives `div[data-slot="sidebar.footer.action"]` an
|
|
14294
|
+
* inline `display: contents`, so every plugin's footer entry root participates
|
|
14295
|
+
* directly in the `.footerActions` flex row — several plugins (e.g. this one
|
|
14296
|
+
* plus a file-browser) get crammed into one row and a full-width badge
|
|
14297
|
+
* overflows. Stack the slot entries vertically instead (the same fix the
|
|
14298
|
+
* dsh-footer-order plugin injects), so each plugin gets its own row.
|
|
14299
|
+
*
|
|
14300
|
+
* We defer to the dedicated dsh-footer-order plugin when it is present, so the
|
|
14301
|
+
* two never fight.
|
|
14302
|
+
*
|
|
14303
|
+
* Why we detect it by its injected stylesheet's `--dsh-footer-order-gap` custom
|
|
14304
|
+
* property rather than by `ctx.slots.entriesOfSlot('settings.plugin.item')`
|
|
14305
|
+
* (where footer-order registers its settings card, id `footer-order`): that slot
|
|
14306
|
+
* key is not in our SlotMap type (would need a cast) and, more importantly, it
|
|
14307
|
+
* may not be an active slot in this dsh version at all — footer-order targets a
|
|
14308
|
+
* newer dsh settings API, so the detection could miss footer-order and we would
|
|
14309
|
+
* then both inject, fighting over the same `!important` rule. The CSS marker is
|
|
14310
|
+
* footer-order's own, and its presence is exactly "footer-order's rule is really
|
|
14311
|
+
* applied", which is what decides the layout. So only when the marker is absent
|
|
14312
|
+
* do we inject our own rule.
|
|
14313
|
+
*/
|
|
14314
|
+
function injectFooterStackStyle() {
|
|
14315
|
+
if (typeof document === "undefined") return;
|
|
14316
|
+
if (document.querySelector("style[data-diff-approval-footer-stack]") !== null) return;
|
|
14317
|
+
const CSS = "div[data-slot=\"sidebar.footer.action\"]{display:flex!important;flex-direction:column!important;flex:1 1 auto!important;align-items:stretch!important;}";
|
|
14318
|
+
const footerOrderInstalled = () => {
|
|
14319
|
+
for (const style of document.querySelectorAll("style")) if ((style.textContent ?? "").includes("--dsh-footer-order-gap")) return true;
|
|
14320
|
+
return false;
|
|
14321
|
+
};
|
|
14322
|
+
const inject = () => {
|
|
14323
|
+
if (document.querySelector("style[data-diff-approval-footer-stack]") !== null) return;
|
|
14324
|
+
if (footerOrderInstalled()) return;
|
|
14325
|
+
const style = document.createElement("style");
|
|
14326
|
+
style.setAttribute("data-diff-approval-footer-stack", "");
|
|
14327
|
+
style.textContent = CSS;
|
|
14328
|
+
document.head.appendChild(style);
|
|
14329
|
+
};
|
|
14330
|
+
if (document.querySelector("div[data-slot=\"sidebar.footer.action\"]") !== null) {
|
|
14331
|
+
inject();
|
|
14332
|
+
return;
|
|
14333
|
+
}
|
|
14334
|
+
const observer = new MutationObserver(() => {
|
|
14335
|
+
if (document.querySelector("div[data-slot=\"sidebar.footer.action\"]") !== null) {
|
|
14336
|
+
observer.disconnect();
|
|
14337
|
+
inject();
|
|
14338
|
+
}
|
|
14339
|
+
});
|
|
14340
|
+
observer.observe(document.documentElement, {
|
|
14341
|
+
childList: true,
|
|
14342
|
+
subtree: true
|
|
14343
|
+
});
|
|
14344
|
+
}
|
|
14345
|
+
/**
|
|
14287
14346
|
* Mount the pending-edit review panel.
|
|
14288
14347
|
* @param ctx - Client Cordis context carrying the wire and slot services.
|
|
14289
14348
|
*/
|
|
14290
14349
|
function apply(ctx) {
|
|
14350
|
+
injectFooterStackStyle();
|
|
14291
14351
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
14292
14352
|
zh,
|
|
14293
14353
|
en
|
package/lib/index.js
CHANGED
|
@@ -694,6 +694,35 @@ const inject = [
|
|
|
694
694
|
];
|
|
695
695
|
/** The connection RPC channel this plugin serves. */
|
|
696
696
|
const DIFF_APPROVAL_CHANNEL = "/diff-approval";
|
|
697
|
+
/** The dominant line ending of `text`: CRLF when `\r\n` meets or beats lone
|
|
698
|
+
* `\n`, else LF; a text with no line breaks falls back to LF.
|
|
699
|
+
* @param text - the content to inspect.
|
|
700
|
+
* @returns the dominant line-ending sequence.
|
|
701
|
+
*/
|
|
702
|
+
function detectEol(text) {
|
|
703
|
+
let crlf = 0;
|
|
704
|
+
let lf = 0;
|
|
705
|
+
for (let i = 0; i < text.length; i++) {
|
|
706
|
+
const c = text.charCodeAt(i);
|
|
707
|
+
if (c === 13) {
|
|
708
|
+
if (i + 1 < text.length && text.charCodeAt(i + 1) === 10) {
|
|
709
|
+
crlf++;
|
|
710
|
+
i++;
|
|
711
|
+
}
|
|
712
|
+
} else if (c === 10) lf++;
|
|
713
|
+
}
|
|
714
|
+
if (crlf === 0 && lf === 0) return "\n";
|
|
715
|
+
return crlf >= lf ? "\r\n" : "\n";
|
|
716
|
+
}
|
|
717
|
+
/** Normalize any line ending (`\r\n`, `\r`) to `\n`. */
|
|
718
|
+
function normalizeEol(text) {
|
|
719
|
+
return text.replace(/\r\n?/g, "\n");
|
|
720
|
+
}
|
|
721
|
+
/** Re-encode `text`'s line endings to `eol` (its content is unchanged). */
|
|
722
|
+
function reencodeEol(text, eol) {
|
|
723
|
+
const normalized = normalizeEol(text);
|
|
724
|
+
return eol === "\n" ? normalized : normalized.replace(/\n/g, "\r\n");
|
|
725
|
+
}
|
|
697
726
|
/**
|
|
698
727
|
* Narrow a successful `edit` result value to an operation outcome. The edit
|
|
699
728
|
* tool's output schema declares exactly `{ path, before, after }`; anything
|
|
@@ -1275,7 +1304,8 @@ function apply(ctx, config) {
|
|
|
1275
1304
|
if (entry.kind === "create") await rm(ctx.fs.processPath(resolved), { force: true });
|
|
1276
1305
|
else {
|
|
1277
1306
|
const preWrite = await ctx.fs.readText(resolved, void 0) ?? entry.newText;
|
|
1278
|
-
|
|
1307
|
+
const content = reencodeEol(entry.oldText, detectEol(entry.newText));
|
|
1308
|
+
await writeRevert(resolved, content, target.sessionId, signal);
|
|
1279
1309
|
undo = {
|
|
1280
1310
|
before: {
|
|
1281
1311
|
id: entry.id,
|
|
@@ -1287,7 +1317,7 @@ function apply(ctx, config) {
|
|
|
1287
1317
|
id: entry.id,
|
|
1288
1318
|
path: entry.path,
|
|
1289
1319
|
entry: void 0,
|
|
1290
|
-
fileText:
|
|
1320
|
+
fileText: content
|
|
1291
1321
|
}
|
|
1292
1322
|
};
|
|
1293
1323
|
}
|
|
@@ -1353,25 +1383,26 @@ function apply(ctx, config) {
|
|
|
1353
1383
|
};
|
|
1354
1384
|
const restored = contentRangeOf(entry.oldText, blockTarget.block.oldStart, blockTarget.block.oldEnd);
|
|
1355
1385
|
const updatedNew = replaceContentLines(entry.newText, blockTarget.block.newStart, blockTarget.block.newEnd, restored);
|
|
1386
|
+
const content = reencodeEol(updatedNew, detectEol(entry.newText));
|
|
1356
1387
|
let afterEntry;
|
|
1357
|
-
if (updatedNew === entry.oldText) {
|
|
1388
|
+
if (normalizeEol(updatedNew) === normalizeEol(entry.oldText)) {
|
|
1358
1389
|
store.remove(blockTarget.sessionId, blockTarget.id);
|
|
1359
1390
|
afterEntry = void 0;
|
|
1360
1391
|
} else {
|
|
1361
|
-
store.update(blockTarget.sessionId, blockTarget.id, { newText:
|
|
1392
|
+
store.update(blockTarget.sessionId, blockTarget.id, { newText: content });
|
|
1362
1393
|
afterEntry = {
|
|
1363
1394
|
...entry,
|
|
1364
|
-
newText:
|
|
1395
|
+
newText: content,
|
|
1365
1396
|
updatedAt: Date.now()
|
|
1366
1397
|
};
|
|
1367
1398
|
}
|
|
1368
1399
|
let undo;
|
|
1369
1400
|
try {
|
|
1370
1401
|
const resolved = await ctx.fs.resolve(entry.path, { signal });
|
|
1371
|
-
if (entry.kind === "create" &&
|
|
1402
|
+
if (entry.kind === "create" && content === "") await rm(ctx.fs.processPath(resolved), { force: true });
|
|
1372
1403
|
else {
|
|
1373
1404
|
const preWrite = await ctx.fs.readText(resolved, void 0) ?? entry.newText;
|
|
1374
|
-
await writeRevert(resolved,
|
|
1405
|
+
await writeRevert(resolved, content, blockTarget.sessionId, signal);
|
|
1375
1406
|
undo = {
|
|
1376
1407
|
before: {
|
|
1377
1408
|
id: entry.id,
|
|
@@ -1383,7 +1414,7 @@ function apply(ctx, config) {
|
|
|
1383
1414
|
id: entry.id,
|
|
1384
1415
|
path: entry.path,
|
|
1385
1416
|
entry: afterEntry,
|
|
1386
|
-
fileText:
|
|
1417
|
+
fileText: content
|
|
1387
1418
|
}
|
|
1388
1419
|
};
|
|
1389
1420
|
}
|
|
@@ -29,7 +29,10 @@ export interface WholeFileDiff {
|
|
|
29
29
|
* Compute the whole-file view between two contents. The context budget is the
|
|
30
30
|
* side lengths, so every hunk covers the file and unchanged lines survive as
|
|
31
31
|
* context rows; the `` patch marker is annotation
|
|
32
|
-
* and never becomes a row.
|
|
32
|
+
* and never becomes a row. Both sides are line-ending normalized (`\r\n?` →
|
|
33
|
+
* `\n`) first, so a repo baseline stored with one EOL and a worktree with
|
|
34
|
+
* another never show as a whole-file delete+add — the diff is about approved
|
|
35
|
+
* content, not line-ending noise.
|
|
33
36
|
* @param oldText - the file content before the pending change.
|
|
34
37
|
* @param newText - the file content after the pending change.
|
|
35
38
|
* @returns the complete row list with totals.
|
package/package.json
CHANGED