dsh-ui-fortifier 0.5.1 → 0.5.2

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/README.md CHANGED
@@ -38,7 +38,7 @@ Clone this repository into a dsh workspace package directory, then install and b
38
38
  ```sh
39
39
  git clone https://github.com/pgnqukezrdxmhjso/dsh-ui-fortifier packages/my/dsh-ui-fortifier
40
40
  pnpm install
41
- pnpm --filter dsh-ui-fortifier bundle
41
+ pnpm --filter dsh-ui-fortifier build
42
42
  ```
43
43
 
44
44
  ---
package/README.zh.md CHANGED
@@ -38,7 +38,7 @@ dsh plugin --profile web add dsh-ui-fortifier
38
38
  ```sh
39
39
  git clone https://github.com/pgnqukezrdxmhjso/dsh-ui-fortifier packages/my/dsh-ui-fortifier
40
40
  pnpm install
41
- pnpm --filter dsh-ui-fortifier bundle
41
+ pnpm --filter dsh-ui-fortifier build
42
42
  ```
43
43
 
44
44
  ---
package/lib/client.js CHANGED
@@ -622,7 +622,7 @@ window.__ModuleLoader__.load({
622
622
  }
623
623
  //#endregion
624
624
  //#region \0dsh-css:D:\data\code\deepseek-harness\packages\my\dsh-ui-fortifier\src\client\model-picker\ModelPicker.module.css.mjs
625
- const css = ".AGI7WG_root{flex:none;display:inline-flex}.AGI7WG_trigger{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:14px;justify-content:center;align-items:center;padding:0;display:inline-flex}.AGI7WG_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.AGI7WG_triggerIcon{flex:none;width:14px;height:14px}.AGI7WG_menu{z-index:1000;box-sizing:border-box;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);min-width:420px;max-width:min(520px,100vw - 32px);height:min(420px,100vh - 96px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border:0;border-radius:16px;flex-direction:column;padding:6px;display:flex;position:absolute;bottom:calc(100% + 8px);right:0;overflow:hidden;transform:translate(-72px)}.AGI7WG_search{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;flex:none;align-items:center;gap:6px;height:28px;margin:0 2px 6px;padding:0 8px;display:flex}.AGI7WG_search:focus-within{border-color:var(--dsw-alias-brand-primary)}.AGI7WG_searchIcon{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex}.AGI7WG_searchIcon svg{width:13px;height:13px}.AGI7WG_searchInput{min-width:0;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border:none;outline:none;flex:1;padding:0;font-size:13px;line-height:20px}.AGI7WG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.AGI7WG_columns{flex:1;min-height:0;display:flex;overflow:hidden}.AGI7WG_providers{flex:none;width:160px;padding-right:4px;overflow-y:auto}.AGI7WG_models{flex:1;min-width:0;padding-left:4px;overflow-y:auto}.AGI7WG_columnTitle{color:var(--dsw-alias-label-tertiary);padding:5px 8px 3px;font-size:12px;font-weight:500;line-height:18px}.AGI7WG_cell{box-sizing:border-box;width:100%;min-height:34px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;padding:6px 8px;display:flex}.AGI7WG_cell:hover:not(:disabled),.AGI7WG_cell:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_cellActive{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary)}.AGI7WG_cellSelected{color:var(--dsw-alias-label-primary);font-weight:500}.AGI7WG_cell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.AGI7WG_modelName{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.AGI7WG_check{color:var(--dsw-alias-brand-primary);flex:none}.AGI7WG_status{color:var(--dsw-alias-label-tertiary);padding:10px;font-size:13px;line-height:20px}.AGI7WG_error{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);border-radius:8px;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:4px;padding:7px 8px;font-size:12px;line-height:18px;display:flex}.AGI7WG_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600}";
625
+ const css = ".AGI7WG_root{flex:none;display:inline-flex}.AGI7WG_trigger{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:14px;justify-content:center;align-items:center;padding:0;display:inline-flex}.AGI7WG_trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.AGI7WG_triggerIcon{flex:none;width:14px;height:14px}.AGI7WG_menu{z-index:1100;box-sizing:border-box;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);min-width:min(420px,100vw - 32px);max-width:min(520px,100vw - 32px);height:min(420px,100vh - 96px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border:0;border-radius:16px;flex-direction:column;padding:6px;display:flex;position:fixed;overflow:hidden}.AGI7WG_search{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;flex:none;align-items:center;gap:6px;height:28px;margin:0 2px 6px;padding:0 8px;display:flex}.AGI7WG_search:focus-within{border-color:var(--dsw-alias-brand-primary)}.AGI7WG_searchIcon{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex}.AGI7WG_searchIcon svg{width:13px;height:13px}.AGI7WG_searchInput{min-width:0;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border:none;outline:none;flex:1;padding:0;font-size:13px;line-height:20px}.AGI7WG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.AGI7WG_columns{flex:1;min-height:0;display:flex;overflow:hidden}.AGI7WG_providers{flex:none;width:160px;padding-right:4px;overflow-y:auto}.AGI7WG_models{flex:1;min-width:0;padding-left:4px;overflow-y:auto}.AGI7WG_columnTitle{color:var(--dsw-alias-label-tertiary);padding:5px 8px 3px;font-size:12px;font-weight:500;line-height:18px}.AGI7WG_cell{box-sizing:border-box;width:100%;min-height:34px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;padding:6px 8px;display:flex}.AGI7WG_cell:hover:not(:disabled),.AGI7WG_cell:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.AGI7WG_cellActive{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary)}.AGI7WG_cellSelected{color:var(--dsw-alias-label-primary);font-weight:500}.AGI7WG_cell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.AGI7WG_modelName{overflow-wrap:anywhere;flex:1;min-width:0}.AGI7WG_check{color:var(--dsw-alias-brand-primary);flex:none}.AGI7WG_status{color:var(--dsw-alias-label-tertiary);padding:10px;font-size:13px;line-height:20px}.AGI7WG_error{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);border-radius:8px;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:4px;padding:7px 8px;font-size:12px;line-height:18px;display:flex}.AGI7WG_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-weight:600}";
626
626
  const tagId = "dsh-ui-fortifier/ModelPicker.module.css";
627
627
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
628
628
  const tag = document.createElement("style");
@@ -664,6 +664,86 @@ window.__ModuleLoader__.load({
664
664
  return query === "" || name.toLowerCase().includes(query);
665
665
  }
666
666
  /**
667
+ * 在滚动容器内把目标滚进视野。只调整容器自身的 scrollTop,不联动页面滚动,
668
+ * 因此面板浮在 body 上时不会带动背景页面。
669
+ * @param container - 滚动容器,缺失时不处理。
670
+ * @param target - 目标元素,缺失(如已被搜索过滤掉)时不处理。
671
+ */
672
+ function scrollIntoViewWithin(container, target) {
673
+ if (container === null || target === null) return;
674
+ const containerRect = container.getBoundingClientRect();
675
+ const targetRect = target.getBoundingClientRect();
676
+ if (targetRect.top < containerRect.top) {
677
+ container.scrollTop -= containerRect.top - targetRect.top;
678
+ return;
679
+ }
680
+ if (targetRect.bottom > containerRect.bottom) container.scrollTop += targetRect.bottom - containerRect.bottom;
681
+ }
682
+ /**
683
+ * 未定位态:隐藏但按原点参与布局,使首帧 offsetWidth/offsetHeight 为真实尺寸,
684
+ * 定位不存在时也不会闪现在原点(与官方 ModelSelect 同法)。
685
+ */
686
+ const MEASURE_STYLE = {
687
+ visibility: "hidden",
688
+ left: 0,
689
+ top: 0
690
+ };
691
+ /**
692
+ * 面板坐标:右边缘对齐触发按钮右边缘,仅当这样越出视口时才平移回边界内。
693
+ * 官方 useAnchoredPosition 固定以触发器左边缘为起点且无对齐选项,故本地实现。
694
+ * @param open - 面板是否展开;关闭时清空坐标。
695
+ * @param anchorRef - 触发按钮。
696
+ * @param panelRef - 面板,用于量取自身尺寸做视口钳位。
697
+ * @param gap - 面板下边缘与触发器上边缘的间距。
698
+ * @param margin - 面板与视口各边的最小距离。
699
+ * @returns 面板的 fixed 坐标;首次量取前为 null。
700
+ */
701
+ function useRightAlignedMenuPosition(open, anchorRef, panelRef, gap, margin) {
702
+ const [position, setPosition] = (0, react.useState)(null);
703
+ (0, react.useLayoutEffect)(() => {
704
+ if (!open) {
705
+ setPosition(null);
706
+ return;
707
+ }
708
+ const place = () => {
709
+ const rect = anchorRef.current?.getBoundingClientRect();
710
+ if (rect === void 0) return;
711
+ const panel = panelRef.current;
712
+ const width = panel?.offsetWidth ?? 0;
713
+ const height = panel?.offsetHeight ?? 0;
714
+ let left = rect.right - width;
715
+ let top = rect.top - gap - height;
716
+ if (width > 0) left = Math.min(Math.max(left, margin), window.innerWidth - width - margin);
717
+ if (height > 0) top = Math.min(Math.max(top, margin), window.innerHeight - height - margin);
718
+ setPosition({
719
+ left,
720
+ top
721
+ });
722
+ };
723
+ place();
724
+ window.addEventListener("scroll", place, true);
725
+ window.addEventListener("resize", place);
726
+ const panel = panelRef.current;
727
+ let observer = null;
728
+ if (typeof ResizeObserver !== "undefined" && panel !== null) {
729
+ observer = new ResizeObserver(place);
730
+ observer.observe(panel);
731
+ }
732
+ return () => {
733
+ observer?.disconnect();
734
+ window.removeEventListener("scroll", place, true);
735
+ window.removeEventListener("resize", place);
736
+ };
737
+ }, [
738
+ open,
739
+ anchorRef,
740
+ panelRef,
741
+ gap,
742
+ margin
743
+ ]);
744
+ return position;
745
+ }
746
+ /**
667
747
  * 渲染级联模型选择器(左列提供商、右列模型)。
668
748
  * @param props - owner 参数、文案与注入面。
669
749
  * @returns 图标触发器按钮与展开时的两列面板。
@@ -680,7 +760,15 @@ window.__ModuleLoader__.load({
680
760
  const [loading, setLoading] = (0, react.useState)(false);
681
761
  const rootRef = (0, react.useRef)(null);
682
762
  const triggerRef = (0, react.useRef)(null);
763
+ const menuRef = (0, react.useRef)(null);
764
+ const providersRef = (0, react.useRef)(null);
765
+ const modelsRef = (0, react.useRef)(null);
766
+ const currentProviderRef = (0, react.useRef)(null);
767
+ const currentModelRef = (0, react.useRef)(null);
683
768
  const searchRef = (0, react.useRef)(null);
769
+ const positionedRef = (0, react.useRef)(false);
770
+ const pendingCurrentScrollRef = (0, react.useRef)(false);
771
+ const position = useRightAlignedMenuPosition(picker.open, triggerRef, menuRef, 8, 12);
684
772
  const groups = state.groups;
685
773
  const current = state.current;
686
774
  const query = picker.query.trim().toLowerCase();
@@ -706,10 +794,38 @@ window.__ModuleLoader__.load({
706
794
  query,
707
795
  hasModelHit
708
796
  ]);
797
+ (0, react.useLayoutEffect)(() => {
798
+ if (!picker.open) {
799
+ positionedRef.current = false;
800
+ pendingCurrentScrollRef.current = false;
801
+ return;
802
+ }
803
+ if (pendingCurrentScrollRef.current) {
804
+ if (currentModelRef.current === null) return;
805
+ scrollIntoViewWithin(modelsRef.current, currentModelRef.current);
806
+ pendingCurrentScrollRef.current = false;
807
+ return;
808
+ }
809
+ if (positionedRef.current) return;
810
+ if (currentProviderRef.current === null && currentModelRef.current === null) return;
811
+ scrollIntoViewWithin(providersRef.current, currentProviderRef.current);
812
+ scrollIntoViewWithin(modelsRef.current, currentModelRef.current);
813
+ positionedRef.current = true;
814
+ }, [
815
+ picker.open,
816
+ activeProvider,
817
+ current?.provider,
818
+ current?.model,
819
+ visibleGroups,
820
+ activeModels
821
+ ]);
709
822
  (0, react.useEffect)(() => {
710
823
  if (!picker.open) return;
711
824
  const closeOutside = (event) => {
712
- if (!rootRef.current?.contains(event.target)) setPicker((p) => ({
825
+ const target = event.target;
826
+ if (rootRef.current?.contains(target) === true) return;
827
+ if (menuRef.current?.contains(target) === true) return;
828
+ setPicker((p) => ({
713
829
  ...p,
714
830
  open: false
715
831
  }));
@@ -745,6 +861,17 @@ window.__ModuleLoader__.load({
745
861
  ...p,
746
862
  provider
747
863
  }));
864
+ if (provider === current?.provider) {
865
+ if (provider === activeProvider) {
866
+ scrollIntoViewWithin(modelsRef.current, currentModelRef.current);
867
+ pendingCurrentScrollRef.current = false;
868
+ return;
869
+ }
870
+ pendingCurrentScrollRef.current = true;
871
+ return;
872
+ }
873
+ pendingCurrentScrollRef.current = false;
874
+ if (modelsRef.current !== null) modelsRef.current.scrollTop = 0;
748
875
  };
749
876
  const chooseModel = (modelId) => {
750
877
  if (activeProvider === null) return;
@@ -828,10 +955,12 @@ window.__ModuleLoader__.load({
828
955
  })
829
956
  ]
830
957
  })
831
- }), picker.open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
958
+ }), picker.open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
959
+ ref: menuRef,
832
960
  className: ModelPicker_module_css_default.menu,
833
961
  role: "dialog",
834
962
  "aria-label": t("modelPicker.trigger"),
963
+ style: position ?? MEASURE_STYLE,
835
964
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
836
965
  className: ModelPicker_module_css_default.search,
837
966
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -870,6 +999,7 @@ window.__ModuleLoader__.load({
870
999
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
871
1000
  className: ModelPicker_module_css_default.columns,
872
1001
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
1002
+ ref: providersRef,
873
1003
  className: ModelPicker_module_css_default.providers,
874
1004
  "aria-label": t("modelPicker.providers"),
875
1005
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -885,6 +1015,7 @@ window.__ModuleLoader__.load({
885
1015
  className: ModelPicker_module_css_default.status,
886
1016
  children: t("modelPicker.emptySearch")
887
1017
  }) : visibleGroups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1018
+ ref: group.id === activeProvider ? currentProviderRef : null,
888
1019
  type: "button",
889
1020
  className: `${ModelPicker_module_css_default.cell} ${group.id === activeProvider ? ModelPicker_module_css_default.cellActive : ""}`,
890
1021
  onClick: () => {
@@ -893,6 +1024,7 @@ window.__ModuleLoader__.load({
893
1024
  children: group.name
894
1025
  }, group.id))]
895
1026
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1027
+ ref: modelsRef,
896
1028
  className: ModelPicker_module_css_default.models,
897
1029
  "aria-label": t("modelPicker.models"),
898
1030
  children: [
@@ -925,6 +1057,7 @@ window.__ModuleLoader__.load({
925
1057
  }) : activeModels.map((model) => {
926
1058
  const selected = current?.provider === activeProvider && current.model === model.id;
927
1059
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1060
+ ref: selected ? currentModelRef : null,
928
1061
  type: "button",
929
1062
  className: `${ModelPicker_module_css_default.cell} ${selected ? ModelPicker_module_css_default.cellSelected : ""}`,
930
1063
  disabled: state.status === "selecting",
@@ -944,7 +1077,7 @@ window.__ModuleLoader__.load({
944
1077
  ]
945
1078
  })]
946
1079
  })]
947
- })]
1080
+ }), document.body)]
948
1081
  });
949
1082
  }
950
1083
  //#endregion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-ui-fortifier",
3
3
  "description": "UI 强化",
4
- "version": "0.5.1",
4
+ "version": "0.5.2",
5
5
  "license": "MIT",
6
6
  "contributors": [
7
7
  "pgnqukezrdxmhjso"
@@ -44,10 +44,10 @@
44
44
  "lib/types/**/*.d.ts"
45
45
  ],
46
46
  "peerDependencies": {
47
- "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
48
- "@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.2",
49
47
  "@deepseek-ai/dsh-native-command": "^0.1.5-rc.2",
48
+ "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
50
49
  "@deepseek-ai/cordis": "^4.0.2",
50
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.2",
51
51
  "@deepseek-ai/schemastery": "^3.18.2"
52
52
  },
53
53
  "devDependencies": {
@@ -56,21 +56,21 @@
56
56
  "react": "^18.2.0",
57
57
  "react-dom": "^18.2.0",
58
58
  "@deepseek-ai/cordis": "^4.0.2",
59
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
60
59
  "@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.2",
61
- "@deepseek-ai/dsh-client-ui-model-selection": "^0.1.5-rc.2",
60
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
62
61
  "@deepseek-ai/dsh-client-connection": "^0.1.5-rc.2",
63
- "@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
62
+ "@deepseek-ai/dsh-client-ui-model-selection": "^0.1.5-rc.2",
64
63
  "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
64
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
65
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
65
66
  "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
66
67
  "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
67
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
68
68
  "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
69
69
  "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
70
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
70
71
  "@deepseek-ai/dsh-native-command": "^0.1.5-rc.2",
71
72
  "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
72
73
  "@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.2",
73
- "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
74
74
  "@deepseek-ai/schemastery": "^3.18.2"
75
75
  },
76
76
  "dsh": {
@@ -97,8 +97,8 @@
97
97
  "verify": "node ../dsh-pgnqukezrdxmhjso-base/verify.mjs",
98
98
  "pub": "pnpm run build && pnpm login --registry=https://registry.npmjs.org && pnpm publish --registry=https://registry.npmjs.org --no-git-checks",
99
99
  "pack": "pnpm run build && pnpm pack --pack-destination lib",
100
- "pack-test": "pnpm run pack && (pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add ./lib/%npm_package_name%-%npm_package_version%.tgz)",
101
- "npm-test": "pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add %npm_package_name%",
100
+ "pack-test-i": "pnpm run pack && (pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add ./lib/%npm_package_name%-%npm_package_version%.tgz)",
101
+ "npm-test-i": "pnpm dsh plugin --profile pkgtest remove %npm_package_name% & pnpm dsh plugin --profile pkgtest add %npm_package_name%@%npm_package_version%",
102
102
  "test-create": "pnpm dsh --profile pkgtest --from-default-profile web",
103
103
  "test-start": "pnpm dsh --profile pkgtest --port 3081 --no-open"
104
104
  }