From 40bb631ba721fed3fa7209228c86c50d72d3c312 Mon Sep 17 00:00:00 2001 From: Power-Maverick Date: Sun, 12 Oct 2025 05:37:22 -0400 Subject: [PATCH 1/8] Add tool icons and implement command registration for tools - Added SVG icon for generic tool and PNG icons for PRT and PD tools. - Implemented `registerToolsCommands` function to register commands for opening the Dataverse REST Builder (DRB), launching PRT, CMT, and PD tools. - Created `ToolsHelper` class to manage tool interactions, including opening the DRB and launching tools based on user selection. - Included error handling for command execution failures. --- package-lock.json | 4 +- resources/toolIcons/{ => dark}/cmt.png | Bin resources/toolIcons/{ => dark}/drb.png | Bin resources/toolIcons/dark/erd.svg | 23 ++++++++ resources/toolIcons/dark/generic.svg | 8 +++ resources/toolIcons/{ => dark}/pd.png | Bin resources/toolIcons/{ => dark}/prt.png | Bin resources/toolIcons/light/cmt.png | Bin 0 -> 2010 bytes resources/toolIcons/light/drb.png | Bin 0 -> 1386 bytes resources/toolIcons/light/erd.svg | 23 ++++++++ resources/toolIcons/light/generic.svg | 1 + resources/toolIcons/light/pd.png | Bin 0 -> 6283 bytes resources/toolIcons/light/prt.png | Bin 0 -> 6283 bytes src/commands/registerCommands.ts | 61 ++------------------- src/commands/registerToolsCommands.ts | 71 +++++++++++++++++++++++++ src/extension.ts | 23 ++++---- src/helpers/drbHelper.ts | 28 ---------- src/helpers/toolsHelper.ts | 65 ++++++++++++++++++++++ src/tools/tools.json | 30 +++++++++-- src/tools/toolsDataProvider.ts | 30 ++++------- src/utils/ErrorMessages.ts | 1 + src/utils/Interfaces.ts | 7 +-- 22 files changed, 248 insertions(+), 127 deletions(-) rename resources/toolIcons/{ => dark}/cmt.png (100%) rename resources/toolIcons/{ => dark}/drb.png (100%) create mode 100644 resources/toolIcons/dark/erd.svg create mode 100644 resources/toolIcons/dark/generic.svg rename resources/toolIcons/{ => dark}/pd.png (100%) rename resources/toolIcons/{ => dark}/prt.png (100%) create mode 100644 resources/toolIcons/light/cmt.png create mode 100644 resources/toolIcons/light/drb.png create mode 100644 resources/toolIcons/light/erd.svg create mode 100644 resources/toolIcons/light/generic.svg create mode 100644 resources/toolIcons/light/pd.png create mode 100644 resources/toolIcons/light/prt.png create mode 100644 src/commands/registerToolsCommands.ts delete mode 100644 src/helpers/drbHelper.ts create mode 100644 src/helpers/toolsHelper.ts diff --git a/package-lock.json b/package-lock.json index 365b495..71b63e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dataverse-devtools", - "version": "2.2.5", + "version": "2.2.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dataverse-devtools", - "version": "2.2.5", + "version": "2.2.6", "dependencies": { "@azure/identity": "^3.0.0", "@azure/identity-vscode": "^1.0.0", diff --git a/resources/toolIcons/cmt.png b/resources/toolIcons/dark/cmt.png similarity index 100% rename from resources/toolIcons/cmt.png rename to resources/toolIcons/dark/cmt.png diff --git a/resources/toolIcons/drb.png b/resources/toolIcons/dark/drb.png similarity index 100% rename from resources/toolIcons/drb.png rename to resources/toolIcons/dark/drb.png diff --git a/resources/toolIcons/dark/erd.svg b/resources/toolIcons/dark/erd.svg new file mode 100644 index 0000000..93f20a8 --- /dev/null +++ b/resources/toolIcons/dark/erd.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/toolIcons/dark/generic.svg b/resources/toolIcons/dark/generic.svg new file mode 100644 index 0000000..ae8f055 --- /dev/null +++ b/resources/toolIcons/dark/generic.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/resources/toolIcons/pd.png b/resources/toolIcons/dark/pd.png similarity index 100% rename from resources/toolIcons/pd.png rename to resources/toolIcons/dark/pd.png diff --git a/resources/toolIcons/prt.png b/resources/toolIcons/dark/prt.png similarity index 100% rename from resources/toolIcons/prt.png rename to resources/toolIcons/dark/prt.png diff --git a/resources/toolIcons/light/cmt.png b/resources/toolIcons/light/cmt.png new file mode 100644 index 0000000000000000000000000000000000000000..a880dcf095c478f0b55a1347db19e590002aec64 GIT binary patch literal 2010 zcmXw)dpy&N8^@pTcbi%Njurek}iH;WHLsy<+9KXV=hPdoe@RlR(=kt7?zdy-tEXE>DJxu^uw4LeT zp(6HU)YVk`sZBtG3P_X(!yZbT^#@e~A7bZf2T;z_nh7ANv_>S;D+*xo(;tJ>McfVo zSP-$@!ERR!XE;NcI_#i*dh;@qG4V4)eAp8)37?xItH-n>Wl4x8_ai@l{0{d{{uaZ@ z#({zzZwkAFmxAM}MEi%U6{$}79)-J%)BZ&lOEf3{-e0%(gK%b8VS8-uTfbj+L;h>s ziB~P61f+W?;usUMZi?oo6tB>Wy4?Y}ZaNrK<56HcuY)Z}M&r9wF$eklyt5H(ZkH(Q z?g42->=fnAR(9{I78EOdw(*wbZv$ik%R(l2TDS?v1wj+z^O%6_Q(xJky zKKhd2^HLC8XKK(dQa#F-Dikzs+OqWv&?&;aiLV}?SYceOCvyQ(C533&GoE`r(+d!6=Sx@Fs}r5^MI zxJHzjEAZma1?@xo?|Ab|qN6?JGrzZKK^Zk^)M96MsG^q%o&^uSAJ_;2zjF};vGf$1 zfkfi}F@Tubx=Qp%f&vm9PH!kY41Bx@mUi(B!BWqe45M_xPsu|ggC-i>;kC=(Y|wr) ztE7PW_T&BP@MrHM0=Reac%V(_aR+abz#3A!cnVb8bh^(F+}t9X%>4|o=OcCTNa$eI z5h)IXu=5|=z~P9k6bA~%@>P}S-AbkMr?a1ceRACT7R6FVOLq1)pxRxB4r&48vgorn ztd@(&Ak^WpAt_;$-466~OE33df(a+YjkHV4B!jNn3 z34ei!9(8&jf-z2ptOv@4Beb>xHUDi3ydgN+wpl|aQb7&mHaOsAf#KrODVH*=j)i5^ z+rC=YovQ0E8Ay*pU5H527wbkTnQSD+H+{9D8i~nG2D23el98CS6izhi&mLr98krei z+n(9kA4C`GT+a@eXrLl$Dd!n((m*Xb3v15I_+;LX|CMZUBdvQqj$Gb{AD~$1+bIlG zCdq=)9Kl#{SjN)6Ya6~98Yo27DLB;lYx^>4q z9evhStS>h44?Z-VNq__K?S?DW;Ose)}0WT!$_WR`PslC)0TCW(n;pMy8XbUH`GWP^^7m*6q8F! z%5Da-gZPQJ602XlHynCQ?CdZ1jpy#5MbyZeYiXYplMfylg!jB7z?redt%1u~8c(>6 z{br?uD}K-F?t2i_P#oYpQeM}WX|b)g&@)`XDXz+HsW-J{)IJWD8)ofw=Q?8gZEV?% zH1gsQT{nZ*(#orJTG7pMm(f$hxBElW`|oUy|FRcH4-gQnFOyT&t~*g&lW3d}+r{QK#Ib^p za{q%P2BrL6E1zoXtKR3s`5IyxatF=%OnRU9tn-xYo-4W3_1q=TQX{Uft4`wy>x~SJ z)XM7fb_Y7lI?V=rq#2A%Wztcqq)JNQ??#`r2<1hBdxLbECezXH&m? z)P@(iUgNP%W}MXYl8CY+{u8Hqv>{;+k%Po50#?cjYfIBk+xt^QoC}J$&G1mE>6Bfz z-1_WM7osL|A}xxY?LCT$iJsR(YU-RfUD3@YBtC_^|0B-md(1hL!fKp1<1RXi67f%n z3t!*Y1J&m-S|Nw1R_wi@GCyl|&E1)6|2E=}e*E%sTB%$Teypm%KY8;CRUgB4N0vjW Iz5mJo18WeJcK`qY literal 0 HcmV?d00001 diff --git a/resources/toolIcons/light/drb.png b/resources/toolIcons/light/drb.png new file mode 100644 index 0000000000000000000000000000000000000000..969330a990dba22c8f6cb984f96bdf58d4c3f36e GIT binary patch literal 1386 zcmV-w1(o`VP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!T% z7Nes7+TATlX?NEj0r7#Y)D~Y*s#5VmYm%DmJdWqioJ@9RcavGu{gKZ7VD_AIA3OJ+ zx%b>NBNPfnq5+lxh{#$p&{!rGe)C$0$1S#1H9 ziABW#mI2Wi5ZyBijW4b|IqQ7X3Sb$~Xa$TudW~l<6uNi)bE718#K$lUx7n>Dl* zK3l=6hh|TWPcAWjqx;!h6ryE8P%o5kLmiGGx<`Wx#CMMFoG{PAn3*q*nz`bG+&o0v zzS1(+#-Brd`7&|x4D}n)bLA{I4={4Y_oM9{-IVvUj9lplBVXFeO`E1^2WYN)39Ai& z)dJu{8uZ?_TQF))D-iI0kpQh zaRF{e&{6_$2MTvLSJA+r9|gY7W_?_zQ-F3OP2zcKdyGqI!uTSqb-?XjKXkjOxJJ-c>|`}I^L9WK3Nm}>X}-fI4HzF|ZB{A+ z>EpDdrr4DGCDzzzwOFhQSBO)r#^#(mKmwY!i&YvRENjmlxSGv|nE68+sBt@+r^u1t zvfdE0P<(*yuuAHP__foYE_oGA&=Ln&)|un1dbkcOW;FyIxv!Y{^2-#(Kc%63%*8Uc z>}0hT#JB0BnV`N7t@)3wKGoXdeaw@oxeuA-6|CJ8dJH_Z|O@1*Kt#Lr1AdjsM-=F~+Fu`hw>d4R7 zLc=4h*M<7LORHcLC0`rKS;JK1C0?}E?lDeR-jjMWw8R0XwP=7xEQlZV0MJIQzVruHfA5gpm%0PN?c*wnw_=0XU%rgZShU0eXt*=k9EvZ+ z-D}3!s*44eKS}MpEkpUk4Mwi~h*uFn@x1|drngLegB4~VEN3+q=iC8CzWB6Uj%?B$ z;8oVbR3CYn<)q`AY!Pn60gWMNb<_G^#HO&VN+@8Bw$ zqZqO>Jwt3pz!?V+;EL)j@+LiBx|7Wih)#;$88-P>=>-t(KCY6Ch`Vn2o%LAE%K@}` zJ3(lJa?*5B21xG#j7*E|OBc(`un{2#_=yk}*rfl)&8&K!oQCeVkvz1=zr=GSFP7!h zaRq}~ioJAj^m|DG8I@h|%MoJR5nO@57+7I$?8Sz0Ynb2O3@XI6pSM#IDY(gyLP zC!-+#LB9tn3EDDHN!0hbGo{|H~y| zKH;-GT0dPr&C)5W(YF9e-0uPZ&jAYn^;I|~$^k4Bi*f+V#1i`ySg5E|U=HS70p}ZP s1w>E|V5v|j6bgkxp-?Ck2||ef09J_gOkXcI5C8xG07*qoM6N<$f;th0k^lez literal 0 HcmV?d00001 diff --git a/resources/toolIcons/light/erd.svg b/resources/toolIcons/light/erd.svg new file mode 100644 index 0000000..9c4044b --- /dev/null +++ b/resources/toolIcons/light/erd.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/toolIcons/light/generic.svg b/resources/toolIcons/light/generic.svg new file mode 100644 index 0000000..02919c7 --- /dev/null +++ b/resources/toolIcons/light/generic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/toolIcons/light/pd.png b/resources/toolIcons/light/pd.png new file mode 100644 index 0000000000000000000000000000000000000000..d6fc1248bbfb5733c9c289cddd7f512d5868b97a GIT binary patch literal 6283 zcmb7Jby(C-(Epw%M|pxu9GxO5h$kQo%F!V$jYvvJNXLnkq=1x?qJ-2D(s6=>ba#qy zoOB)>2fX)t|9}5@|Crrpc4udvo&C(SGrR9~w3ML~j1&L>Kvh+q>Joe8e?$f$wjHw8 zeZ&srsjI9Aln*jviOh9-1x*D2s7#gAZz_VvFkPikB#D^XpG8GUw1;hmg zgSmS}pFm?d1Q{bHco_}FsTJfC6-CB@FIymMNW{u|5S}o&IfglD*=<>B#+_d*?k>u; zPSn+nEfi|!$S%~mEj_NFtDq736k`Z5#{Yi^L5^M>k~Ye`$gvdmmf1V{6GIj5U@E#3zA;)pStK=+O6a*3V{RHhMkZQO;G<;NYzAe)`*-i8K?wl z2!^Ww@v_J2otbeyH~$lfLXe@=Hz(o1s4I&hy2zS7!34ktc z7?@h}w%m`<^l^P84~e5j-tD`e1G^}L?4=)H+v}%%6}>IDU$Kx_;o3w&-R}>wx?($C zazaYmwE&r3gWp{-b&R>O&jkSEt7c$!ef6FfiTtn=5c9kYWEiv>(3z=ei*e&|K$f_j zD)^EF^=->h)T-Ve(6^Z+NA6ryJhu!Rlf!Q0$U@O`JuD~Zfz-`7XP=ku=Lu~61)rVz zO`zzrHlN_rvAi++P)Pmx4!(8|+VXhE79Nl;< zOkbFdvs{pD4o>##{2{)Op(!s1^Zg= z|CFKk&R}_P%xHy;7I=p|t1@6Wfr`A8z97U{SFhn}r%U=aEOv^BUjDJ!#Fp#y+@zfB zSMwwUCZ^NSjH5@SDL3O1bit+d1Rf)cXyQ0%u<7V?=c~n&%KORNDit2ed7x}Uo8=oH z_8+BYXJtg)Td()9=62eJ5R@B1uoNI7`Nl0~eBf#TKIeRbr50i})7=x%nwqz!6uSuV z-59Y98xXOGaA(uKAJ=J22{gInJokEQb2RP*7_vK`U5@Q^^Vojuqm3PfSaEHI>0RZY zuHuBq>^Ux9_XSmB1-ncD-?0KqLc5t%dq6|SIAD4CuddPSN9=bG_ed8o>afRZ`8vEJ z>l^b7)$eb0FjLU7&KhCpGQi3qx_FET9Shmc5x2fkXt9?K#SK%i$N{}@8&3^is&LQ$ zN?Yw*Wes@mihAq#z!;GW$`&%`v{9+acVtC0pz$smWBT|wumsY+#37WMa^Eeai(gmhZen2NM3iHqj zV4BVCtO?w7^wHbEZ-ix$f4N=2m2cgdb5_~${D3;i%%ann0!Y0NrO_yZAoE~mMdlXL z-)cx&J0_@jnVgFt^{3yNI%pgmF(3L~J@CHvdTv2Z=rZs;U$H|ELACB01zFCGG`bkp z$KNVOIuIge0s7L$$IO0r{B1eLJzjgB33aIf_1INZa;i_9oWky{-3t*r9VoiTzTwSk znGbD9XH<5qAJ zwOH5PL_4h8n0@vSw}yKK-cw~LfnHx)a_2tMJN2DsHjk;KMxGnVJ!AHqRaywRbk4uG z%BBwM~f~H3Fm7 zn&%YM?=VUSgk&)(V2o90-c!oa2+c=JDV%sbRNCTeMfH#H=V=AK7Fb~S(q>46*D!t0SP?Lkcg zcR5DndAMi5mRde@w&+r-{emPD@Wbs#7P?kWh${lKP&p3hGd9=nO#DTD%k4+f$hK}p zK4^g>FPg}(DaC5}J~B02_&QMiXlsrSrJ6FqBq1;;kb(7~A-H+(0KfO8L(IFXoD*My z1M#M9njU%I(yU-{bi{i!o9@u$t-1g=KkmIyy{>iew^ubs@8@@q?n#K%P6F=S`i_xv)J4r=M+*&m3TwqS}gWTw_|B+K6d$(H*Lq14^ zaQfk=*kq&Z{CQbP(a8{{+f@2B<98VG&tF%iq3qVJ`HylmKjTwa)1DSXrm;x9Mt3pu zvj#H@xt-W40N?x2wfQK?8Jh_gRAjQmkU9Wi@qD?00K1U#;Tfv2@hi1I$50qV9tfS> z{H2UfYSl2!3Gy>o31uDPuq% zKzb-pyRCQo%a|b?2p!VoR5c_0c676)w{j32OVLilQF;W{r5l2B508Dy8@krp zTb3H5CEH{-MX8}5ch1!rwV!ls5)87EMw5ep31Cj>bpENYns&C$QFseg^<}WtVJ@ird{9OKjH37;VY$oi&~#2Y zw7>LXmKb{FmU(eFjiQ4>*S7C49}6D=i9!%u`%?v@<73;Y4kx4wPNw({kL?kEh0){3 zJ_skk8*SBS2v-u0I6qamXoppRdf%uX-f|mz8uDZL`L83E5PRBOM)n1A!NRy2h}*p$ zqMcq6DlN6X6OrX#mD<79|= zShleEsT3npE=-`|;8;G|;RV8cKwOUecgDsF>aGJ zZ4+MV@)`4j18!hHdUWEUnaSsS374~3o0Kt$lz?fA^C@Z$GgN=xknXaLrc;JSgh!k$xY7z!$}JaI8@ zVRAGLfq!*ooNtD*yMNMJxwt^DSHj)5-{&4^zad-e}mW&Mymwk z`!`LSE~YhCx$H%!?vb4@g&b$^eRr67U_h!rgNSZN6;)p^N!DLK{ACGd}M?6*z=Dw-u9hxd-FbcjCg5>QD? z#f2f%l_11WFcsk-E?L$Kh+opgp)kfo5 z+|-kQE`8DPG5ZqGA1$i}X4Q%n+151TsWQbRk^z_n@v9Gk<9TtlD2?CY0jEbTW|ZVH zEcwB&h%uluq^~Xat$0OD`z)iT^qM~SbZPF+Qc0%5Jl_<+yqS&r-MOGPO+hZ5d`?9# zIdSRq)v>30JPi0{ARk+Od^{v(F)!*8m*5Du82t{hx~F3VdThcMXeLbbx-#7pTI9X# zphdIAOv4`0g27|dzFPHc&gdvA(=s9KIrPJj&4hn8_Lotis)D35ut0XOdksE-n&%q> zGO-vNt@B_@fh0~*UN$O~e5~}7!{EG>**nr;*q{(o=7qfzeltPnhEcP}3OD2OlK>?s zwd0Jaf5-ZW$3=BJD}_)SRAW2JiC9mvPZ`lYxdpTqt=%2kZM8{o{W%&{qnpZi+fGVY z(Eyr_pb(+E2dPK2s&;Z?e)#Z{{W- zxw78(hR3S?oC)kZJ&%ZRR(Tk*^+jH2-jG|V?RGh@dSmL7NSmLB@oy6(8)TB-@rZa6 ztJugOvh0x8yF;P9YbHLj9Xvwn`sh9wJgh?RsQZAs)a(krh^hNfYXxQpCLzQ1=@s)k zKMA2`InSL@0D5n)FLe|%&Mr#TW*-f$@UOL=T_Me?=k($ZRX#HwLq5~Gm-FM`({O{? zy`f^$L6(EAgv7x(HbL#BnzhiK6gOS1E#qAZ2g_N+pFT&Aq4@P*etMIv`l7`^w^cuiQ=3)1jLFc!8 z$)h4{_7`GM!!vhv;hXuNtFleG=*2T@0}bjQtDnIBa%txnnFsM?;cbj*b$JZGRTS79 z9%fGQd$R!nonzIGw6e{XKHQARsiwaB=Q*g4qoH4QtdWND)EP+;h0BGM1TZpjAo2^$ z5$0edY1T?oN+gOpj#_HylP@SK(6etSfWbpaNhO%M;c%4W|TPZ8yf5zPoak^MVv z^_g9QAKrgxvip(rAQob!bQ=fiomI>UI%VI{c?8hO#SXh+f3Sz+C`Th6e1Zk#$k^Ii z@pk_*Rt)|#?HQVfxR-o0m;b?Jy`pX3c03uKl!j&1*1?~uS8n8(e;Zbhu7SoN{hhFv z255{W;b%1Yw^HIi zRPlw&TMHEFV3jmX#GyuQ?OmJtp(oZKZM<}E3fzFhLfdOaaTJ_^d*pfY@AfnT-Ybjv zsv;N*o{er?0K|NTjY+mhiUtbYVpLweh?=xFMUil2zmcqaof+_3y!{~~5j^IANXoO& zZ$k+;ty4u4st>(H^508&KEr*YA8y#c&vCY{9VTMXW+>wMrlzO!PC?CG*+!0m`K-pV zJNe8jPokdflCpYg87Eb7gfCY`gZ60TOjLS*CoDUi9-R>yE^9W_fe$aX13AnLW_o-P zqUy}|t#j`@E9+0rq=pTk0M&&2BUrk zbxe|5o^`z!JmPkK`k>ucuCxAQhg-FFZVea8)_<$tlpcD1x9IEFYL}XPln(_zD+CY> zieSBrs|?-K3@*$zwP3tO{@e86bTvVp5m-5I6X)K6w&g-nbF64mWL1MMtpyg@*+U&Bj`zGOuF3x;T!wX3ow$ zs7u5qqmOwYkB(qAHy993$`%Y4Bm0ad7=X;R< z2#Rxr98=Z=85Gd~u)c&obY1=Nz!C_x+bKCltcOb}J0_4_A){`s_`ShcPxgGA+C{KUhAS2bg zU2?qu=3Cqp*ZWGU=U~tMtj45`e|#1=7nT;*=Qq^QXSh9}&Ag#9x#ZQV7LYzBMb{MX zu&(>ZD=StAc&O9YEC1%)8ZV(p^48nQG~@fQE3AE5V_IpCrj>&_brTKuSf>|ZhcxF_s*}otfPda$`5@y<^EVgUc^w(b8M^)kcC~L$*288a__T+E zrx~_tvaeE@qMyjtiNdNrUv3wM%JW`v6{vt;8(&lG-!(o%W=Wm=sZ4qhDzaz%fPEy^ zm9g5c1zzfyCCiY@Q91xxUa`@Zxg+*#`1Tg(ed3EQ%*si9JTCB!xC$7h(T?o6DNpfy zbOh@hBAT$zr7W4%D>{@|xV`r?_av0+T>?K1K|=k}^ z=${ef7b~{-!krcE-I%g$NEQJWfbsK^fz{1Y3rZ!Xo%3^yWr{_vLx)FhYy3_g1!nX5 zmyFAeH)NsvF3Jh4+1-m3U@E2ba#qy zoOB)>2fX)t|9}5@|Crrpc4udvo&C(SGrR9~w3ML~j1&L>Kvh+q>Joe8e?$f$wjHw8 zeZ&srsjI9Aln*jviOh9-1x*D2s7#gAZz_VvFkPikB#D^XpG8GUw1;hmg zgSmS}pFm?d1Q{bHco_}FsTJfC6-CB@FIymMNW{u|5S}o&IfglD*=<>B#+_d*?k>u; zPSn+nEfi|!$S%~mEj_NFtDq736k`Z5#{Yi^L5^M>k~Ye`$gvdmmf1V{6GIj5U@E#3zA;)pStK=+O6a*3V{RHhMkZQO;G<;NYzAe)`*-i8K?wl z2!^Ww@v_J2otbeyH~$lfLXe@=Hz(o1s4I&hy2zS7!34ktc z7?@h}w%m`<^l^P84~e5j-tD`e1G^}L?4=)H+v}%%6}>IDU$Kx_;o3w&-R}>wx?($C zazaYmwE&r3gWp{-b&R>O&jkSEt7c$!ef6FfiTtn=5c9kYWEiv>(3z=ei*e&|K$f_j zD)^EF^=->h)T-Ve(6^Z+NA6ryJhu!Rlf!Q0$U@O`JuD~Zfz-`7XP=ku=Lu~61)rVz zO`zzrHlN_rvAi++P)Pmx4!(8|+VXhE79Nl;< zOkbFdvs{pD4o>##{2{)Op(!s1^Zg= z|CFKk&R}_P%xHy;7I=p|t1@6Wfr`A8z97U{SFhn}r%U=aEOv^BUjDJ!#Fp#y+@zfB zSMwwUCZ^NSjH5@SDL3O1bit+d1Rf)cXyQ0%u<7V?=c~n&%KORNDit2ed7x}Uo8=oH z_8+BYXJtg)Td()9=62eJ5R@B1uoNI7`Nl0~eBf#TKIeRbr50i})7=x%nwqz!6uSuV z-59Y98xXOGaA(uKAJ=J22{gInJokEQb2RP*7_vK`U5@Q^^Vojuqm3PfSaEHI>0RZY zuHuBq>^Ux9_XSmB1-ncD-?0KqLc5t%dq6|SIAD4CuddPSN9=bG_ed8o>afRZ`8vEJ z>l^b7)$eb0FjLU7&KhCpGQi3qx_FET9Shmc5x2fkXt9?K#SK%i$N{}@8&3^is&LQ$ zN?Yw*Wes@mihAq#z!;GW$`&%`v{9+acVtC0pz$smWBT|wumsY+#37WMa^Eeai(gmhZen2NM3iHqj zV4BVCtO?w7^wHbEZ-ix$f4N=2m2cgdb5_~${D3;i%%ann0!Y0NrO_yZAoE~mMdlXL z-)cx&J0_@jnVgFt^{3yNI%pgmF(3L~J@CHvdTv2Z=rZs;U$H|ELACB01zFCGG`bkp z$KNVOIuIge0s7L$$IO0r{B1eLJzjgB33aIf_1INZa;i_9oWky{-3t*r9VoiTzTwSk znGbD9XH<5qAJ zwOH5PL_4h8n0@vSw}yKK-cw~LfnHx)a_2tMJN2DsHjk;KMxGnVJ!AHqRaywRbk4uG z%BBwM~f~H3Fm7 zn&%YM?=VUSgk&)(V2o90-c!oa2+c=JDV%sbRNCTeMfH#H=V=AK7Fb~S(q>46*D!t0SP?Lkcg zcR5DndAMi5mRde@w&+r-{emPD@Wbs#7P?kWh${lKP&p3hGd9=nO#DTD%k4+f$hK}p zK4^g>FPg}(DaC5}J~B02_&QMiXlsrSrJ6FqBq1;;kb(7~A-H+(0KfO8L(IFXoD*My z1M#M9njU%I(yU-{bi{i!o9@u$t-1g=KkmIyy{>iew^ubs@8@@q?n#K%P6F=S`i_xv)J4r=M+*&m3TwqS}gWTw_|B+K6d$(H*Lq14^ zaQfk=*kq&Z{CQbP(a8{{+f@2B<98VG&tF%iq3qVJ`HylmKjTwa)1DSXrm;x9Mt3pu zvj#H@xt-W40N?x2wfQK?8Jh_gRAjQmkU9Wi@qD?00K1U#;Tfv2@hi1I$50qV9tfS> z{H2UfYSl2!3Gy>o31uDPuq% zKzb-pyRCQo%a|b?2p!VoR5c_0c676)w{j32OVLilQF;W{r5l2B508Dy8@krp zTb3H5CEH{-MX8}5ch1!rwV!ls5)87EMw5ep31Cj>bpENYns&C$QFseg^<}WtVJ@ird{9OKjH37;VY$oi&~#2Y zw7>LXmKb{FmU(eFjiQ4>*S7C49}6D=i9!%u`%?v@<73;Y4kx4wPNw({kL?kEh0){3 zJ_skk8*SBS2v-u0I6qamXoppRdf%uX-f|mz8uDZL`L83E5PRBOM)n1A!NRy2h}*p$ zqMcq6DlN6X6OrX#mD<79|= zShleEsT3npE=-`|;8;G|;RV8cKwOUecgDsF>aGJ zZ4+MV@)`4j18!hHdUWEUnaSsS374~3o0Kt$lz?fA^C@Z$GgN=xknXaLrc;JSgh!k$xY7z!$}JaI8@ zVRAGLfq!*ooNtD*yMNMJxwt^DSHj)5-{&4^zad-e}mW&Mymwk z`!`LSE~YhCx$H%!?vb4@g&b$^eRr67U_h!rgNSZN6;)p^N!DLK{ACGd}M?6*z=Dw-u9hxd-FbcjCg5>QD? z#f2f%l_11WFcsk-E?L$Kh+opgp)kfo5 z+|-kQE`8DPG5ZqGA1$i}X4Q%n+151TsWQbRk^z_n@v9Gk<9TtlD2?CY0jEbTW|ZVH zEcwB&h%uluq^~Xat$0OD`z)iT^qM~SbZPF+Qc0%5Jl_<+yqS&r-MOGPO+hZ5d`?9# zIdSRq)v>30JPi0{ARk+Od^{v(F)!*8m*5Du82t{hx~F3VdThcMXeLbbx-#7pTI9X# zphdIAOv4`0g27|dzFPHc&gdvA(=s9KIrPJj&4hn8_Lotis)D35ut0XOdksE-n&%q> zGO-vNt@B_@fh0~*UN$O~e5~}7!{EG>**nr;*q{(o=7qfzeltPnhEcP}3OD2OlK>?s zwd0Jaf5-ZW$3=BJD}_)SRAW2JiC9mvPZ`lYxdpTqt=%2kZM8{o{W%&{qnpZi+fGVY z(Eyr_pb(+E2dPK2s&;Z?e)#Z{{W- zxw78(hR3S?oC)kZJ&%ZRR(Tk*^+jH2-jG|V?RGh@dSmL7NSmLB@oy6(8)TB-@rZa6 ztJugOvh0x8yF;P9YbHLj9Xvwn`sh9wJgh?RsQZAs)a(krh^hNfYXxQpCLzQ1=@s)k zKMA2`InSL@0D5n)FLe|%&Mr#TW*-f$@UOL=T_Me?=k($ZRX#HwLq5~Gm-FM`({O{? zy`f^$L6(EAgv7x(HbL#BnzhiK6gOS1E#qAZ2g_N+pFT&Aq4@P*etMIv`l7`^w^cuiQ=3)1jLFc!8 z$)h4{_7`GM!!vhv;hXuNtFleG=*2T@0}bjQtDnIBa%txnnFsM?;cbj*b$JZGRTS79 z9%fGQd$R!nonzIGw6e{XKHQARsiwaB=Q*g4qoH4QtdWND)EP+;h0BGM1TZpjAo2^$ z5$0edY1T?oN+gOpj#_HylP@SK(6etSfWbpaNhO%M;c%4W|TPZ8yf5zPoak^MVv z^_g9QAKrgxvip(rAQob!bQ=fiomI>UI%VI{c?8hO#SXh+f3Sz+C`Th6e1Zk#$k^Ii z@pk_*Rt)|#?HQVfxR-o0m;b?Jy`pX3c03uKl!j&1*1?~uS8n8(e;Zbhu7SoN{hhFv z255{W;b%1Yw^HIi zRPlw&TMHEFV3jmX#GyuQ?OmJtp(oZKZM<}E3fzFhLfdOaaTJ_^d*pfY@AfnT-Ybjv zsv;N*o{er?0K|NTjY+mhiUtbYVpLweh?=xFMUil2zmcqaof+_3y!{~~5j^IANXoO& zZ$k+;ty4u4st>(H^508&KEr*YA8y#c&vCY{9VTMXW+>wMrlzO!PC?CG*+!0m`K-pV zJNe8jPokdflCpYg87Eb7gfCY`gZ60TOjLS*CoDUi9-R>yE^9W_fe$aX13AnLW_o-P zqUy}|t#j`@E9+0rq=pTk0M&&2BUrk zbxe|5o^`z!JmPkK`k>ucuCxAQhg-FFZVea8)_<$tlpcD1x9IEFYL}XPln(_zD+CY> zieSBrs|?-K3@*$zwP3tO{@e86bTvVp5m-5I6X)K6w&g-nbF64mWL1MMtpyg@*+U&Bj`zGOuF3x;T!wX3ow$ zs7u5qqmOwYkB(qAHy993$`%Y4Bm0ad7=X;R< z2#Rxr98=Z=85Gd~u)c&obY1=Nz!C_x+bKCltcOb}J0_4_A){`s_`ShcPxgGA+C{KUhAS2bg zU2?qu=3Cqp*ZWGU=U~tMtj45`e|#1=7nT;*=Qq^QXSh9}&Ag#9x#ZQV7LYzBMb{MX zu&(>ZD=StAc&O9YEC1%)8ZV(p^48nQG~@fQE3AE5V_IpCrj>&_brTKuSf>|ZhcxF_s*}otfPda$`5@y<^EVgUc^w(b8M^)kcC~L$*288a__T+E zrx~_tvaeE@qMyjtiNdNrUv3wM%JW`v6{vt;8(&lG-!(o%W=Wm=sZ4qhDzaz%fPEy^ zm9g5c1zzfyCCiY@Q91xxUa`@Zxg+*#`1Tg(ed3EQ%*si9JTCB!xC$7h(T?o6DNpfy zbOh@hBAT$zr7W4%D>{@|xV`r?_av0+T>?K1K|=k}^ z=${ef7b~{-!krcE-I%g$NEQJWfbsK^fz{1Y3rZ!Xo%3^yWr{_vLx)FhYy3_g1!nX5 zmyFAeH)NsvF3Jh4+1-m3U@E2 { - try { - drbHelper.openDRB(views); - } catch (error) { - errorHandler.log(error, "openDRB"); - } - }, - }, - { - command: "dvdt.commands.launchPRT", - callback: async () => { - try { - cliHelper.launchPRT(); - } catch (error) { - errorHandler.log(error, "launchPRT"); - } - }, - }, - { - command: "dvdt.commands.launchCMT", - callback: async () => { - try { - cliHelper.launchCMT(); - } catch (error) { - errorHandler.log(error, "launchCMT"); - } - }, - }, - { - command: "dvdt.commands.launchPD", - callback: async () => { - try { - cliHelper.launchPD(); - } catch (error) { - errorHandler.log(error, "launchPD"); - } - }, - }, { command: "dvdt.explorer.webresources.linkExistingWebResource", callback: async (uri: vscode.Uri) => { @@ -257,22 +217,7 @@ export async function registerCommands(vscontext: vscode.ExtensionContext, tr: T command: "dvdt.explorer.tools.launchTool", callback: async (toolItem: ToolsTreeItem) => { try { - switch (toolItem.toolShortName) { - case "drb": - drbHelper.openDRB(views); - break; - case "prt": - cliHelper.launchPRT(); - break; - case "cmt": - cliHelper.launchCMT(); - break; - case "pd": - cliHelper.launchPD(); - break; - default: - break; - } + toolsHelper.openTool(toolItem); } catch (error) { errorHandler.log(error, "launchTool"); } diff --git a/src/commands/registerToolsCommands.ts b/src/commands/registerToolsCommands.ts new file mode 100644 index 0000000..30b425a --- /dev/null +++ b/src/commands/registerToolsCommands.ts @@ -0,0 +1,71 @@ +import TelemetryReporter from "@vscode/extension-telemetry"; +import * as vscode from "vscode"; +import { CLIHelper } from "../helpers/cliHelper"; +import { ErrorHandler } from "../helpers/errorHandler"; +import { ToolsHelper } from "../helpers/toolsHelper"; +import { ICommand } from "../utils/Interfaces"; +import { ViewBase } from "../views/ViewBase"; + +export async function registerToolsCommands(vscontext: vscode.ExtensionContext, tr: TelemetryReporter): Promise { + const toolHelper = new ToolsHelper(vscontext); + const views = new ViewBase(vscontext); + const errorHandler = new ErrorHandler(tr); + const cliHelper = new CLIHelper(vscontext); + + const cmds: Array = new Array( + { + command: "dvdt.commands.openDRB", + callback: async () => { + try { + toolHelper.openDRB(views); + } catch (error) { + errorHandler.log(error, "openDRB"); + } + }, + }, + // { + // command: "dvdt.commands.openERDGenerator", + // callback: async () => { + // try { + // toolHelper.openERDGenerator(); + // } catch (error) { + // errorHandler.log(error, "openERDGenerator"); + // } + // }, + // }, + { + command: "dvdt.commands.launchPRT", + callback: async () => { + try { + cliHelper.launchPRT(); + } catch (error) { + errorHandler.log(error, "launchPRT"); + } + }, + }, + { + command: "dvdt.commands.launchCMT", + callback: async () => { + try { + cliHelper.launchCMT(); + } catch (error) { + errorHandler.log(error, "launchCMT"); + } + }, + }, + { + command: "dvdt.commands.launchPD", + callback: async () => { + try { + cliHelper.launchPD(); + } catch (error) { + errorHandler.log(error, "launchPD"); + } + }, + }, + ); + + cmds.forEach((c) => { + vscontext.subscriptions.push(vscode.commands.registerCommand(c.command, c.callback)); + }); +} diff --git a/src/extension.ts b/src/extension.ts index 7ae5d89..85e7e34 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,6 +3,7 @@ import TelemetryReporter from "@vscode/extension-telemetry"; import * as vscode from "vscode"; import { registerCommands } from "./commands/registerCommands"; +import { registerToolsCommands } from "./commands/registerToolsCommands"; import { registerTreeDataProviders } from "./commands/registerTreeDataProviders"; import { DataverseHelper } from "./helpers/dataverseHelper"; import * as config from "./utils/Config"; @@ -32,6 +33,9 @@ export function activate(context: vscode.ExtensionContext) { registerTreeDataProviders(context, reporter); registerCommands(context, reporter); + registerToolsCommands(context, reporter); + + console.log(`Extension ${extensionId} v${extensionVersion} is now active!`); // Start periodic token expiration check startTokenExpirationCheck(context); @@ -59,20 +63,17 @@ export function deactivate() { */ function startTokenExpirationCheck(context: vscode.ExtensionContext) { let lastNotifiedExpiration = false; - + tokenExpirationTimer = setInterval(() => { const dvHelper = new DataverseHelper(context); const isExpired = dvHelper.isCurrentConnectionTokenExpired(); - + if (isExpired && !lastNotifiedExpiration) { // Get current connection to create tree item for reconnect const conn = dvHelper.getCurrentWorkspaceConnection(); - + // Show notification to user - vscode.window.showWarningMessage( - "Your Dataverse connection token has expired. Please reconnect to continue working.", - "Reconnect" - ).then(selection => { + vscode.window.showWarningMessage("Your Dataverse connection token has expired. Please reconnect to continue working.", "Reconnect").then((selection) => { if (selection === "Reconnect" && conn) { // Create a tree item with the connection name to pass to the command const connItem = { @@ -81,17 +82,17 @@ function startTokenExpirationCheck(context: vscode.ExtensionContext) { collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, level: 2, current: true, - expired: true + expired: true, }; vscode.commands.executeCommand("dvdt.explorer.connections.connectDataverse", connItem); } }); - + lastNotifiedExpiration = true; - + // Refresh the connection tree to show expired icon vscode.commands.executeCommand("dvdt.explorer.connections.refreshConnection"); - + // Update status bar to show expired state if (conn) { vscode.commands.executeCommand("dvdt.explorer.connections.updateStatusBar", conn); diff --git a/src/helpers/drbHelper.ts b/src/helpers/drbHelper.ts deleted file mode 100644 index 86e2996..0000000 --- a/src/helpers/drbHelper.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as vscode from "vscode"; -import { connectionCurrentStoreKey } from "../utils/Constants"; -import { ErrorMessages } from "../utils/ErrorMessages"; -import { IConnection } from "../utils/Interfaces"; -import { State } from "../utils/State"; -import { DataverseRestBuilderView } from "../views/DataverseRestBuilderView"; -import { ViewBase } from "../views/ViewBase"; - -export class DRBHelper { - private vsstate: State; - - /** - * Initialization constructor for VS Code Context - */ - constructor(private vscontext: vscode.ExtensionContext) { - this.vsstate = new State(vscontext); - } - - public async openDRB(view: ViewBase): Promise { - const connFromWS: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey); - if (connFromWS && connFromWS.currentAccessToken) { - const webview = await view.getWebView({ type: "openDRB", title: "Dataverse REST Builder" }); - new DataverseRestBuilderView(webview, this.vscontext, connFromWS.currentAccessToken); - } else { - vscode.window.showErrorMessage(ErrorMessages.drbConnectionError); - } - } -} diff --git a/src/helpers/toolsHelper.ts b/src/helpers/toolsHelper.ts new file mode 100644 index 0000000..aba0047 --- /dev/null +++ b/src/helpers/toolsHelper.ts @@ -0,0 +1,65 @@ +import { showERDPanel } from "@dvdt-tools/erd-generator"; +import * as vscode from "vscode"; +import { ToolsTreeItem } from "../tools/toolsDataProvider"; +import { connectionCurrentStoreKey } from "../utils/Constants"; +import { ErrorMessages } from "../utils/ErrorMessages"; +import { IConnection } from "../utils/Interfaces"; +import { State } from "../utils/State"; +import { DataverseRestBuilderView } from "../views/DataverseRestBuilderView"; +import { ViewBase } from "../views/ViewBase"; +import { CLIHelper } from "./cliHelper"; + +export class ToolsHelper { + private vsstate: State; + + /** + * Initialization constructor for VS Code Context + */ + constructor(private vscontext: vscode.ExtensionContext) { + this.vsstate = new State(vscontext); + } + + public openTool(toolItem: ToolsTreeItem) { + const views = new ViewBase(this.vscontext); + const cliHelper = new CLIHelper(this.vscontext); + + switch (toolItem.toolShortName) { + case "drb": + this.openDRB(views); + break; + // case "erd": + // this.openERDGenerator(); + // break; + case "prt": + cliHelper.launchPRT(); + break; + case "cmt": + cliHelper.launchCMT(); + break; + case "pd": + cliHelper.launchPD(); + break; + default: + break; + } + } + + public async openDRB(view: ViewBase): Promise { + const connFromWS: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey); + if (connFromWS && connFromWS.currentAccessToken) { + const webview = await view.getWebView({ type: "openDRB", title: "Dataverse REST Builder" }); + new DataverseRestBuilderView(webview, this.vscontext, connFromWS.currentAccessToken); + } else { + vscode.window.showErrorMessage(ErrorMessages.drbConnectionError); + } + } + + public openERDGenerator(): void { + const connFromWS: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey); + if (connFromWS && connFromWS.currentAccessToken) { + showERDPanel(this.vscontext.extensionUri, connFromWS.environmentUrl, connFromWS.currentAccessToken); + } else { + vscode.window.showErrorMessage(ErrorMessages.commonToolsError); + } + } +} diff --git a/src/tools/tools.json b/src/tools/tools.json index 49e3778..af36cb3 100644 --- a/src/tools/tools.json +++ b/src/tools/tools.json @@ -1,8 +1,28 @@ { "tools": [ - { "toolName": "Dataverse REST Builder", "toolShortName": "drb", "toolAuthor": "Guido Preite" }, - { "toolName": "Plugin Registration", "toolShortName": "prt", "toolAuthor": "Microsoft" }, - { "toolName": "Configuration Migration", "toolShortName": "cmt", "toolAuthor": "Microsoft" }, - { "toolName": "Package Deployer", "toolShortName": "pd", "toolAuthor": "Microsoft" } + { + "name": "Dataverse REST Builder", + "shortName": "drb", + "author": "Guido Preite", + "icon": "drb.png" + }, + { + "name": "Plugin Registration", + "shortName": "prt", + "author": "Microsoft", + "icon": "prt.png" + }, + { + "name": "Configuration Migration", + "shortName": "cmt", + "author": "Microsoft", + "icon": "cmt.png" + }, + { + "name": "Package Deployer", + "shortName": "pd", + "author": "Microsoft", + "icon": "pd.png" + } ] -} +} \ No newline at end of file diff --git a/src/tools/toolsDataProvider.ts b/src/tools/toolsDataProvider.ts index 87ecda4..ab02f63 100644 --- a/src/tools/toolsDataProvider.ts +++ b/src/tools/toolsDataProvider.ts @@ -16,7 +16,7 @@ export class ToolsDataProvider implements vscode.TreeDataProvider let toolsTree: ToolsTreeItem[] = []; toolsArray.tools.map((tool) => { - toolsTree.push(new ToolsTreeItem(tool.toolName, tool.toolShortName, tool.toolAuthor, vscode.TreeItemCollapsibleState.None)); + toolsTree.push(new ToolsTreeItem(tool.name, tool.shortName, tool.author, vscode.TreeItemCollapsibleState.None, tool.icon)); }); return Promise.resolve(toolsTree); @@ -24,28 +24,18 @@ export class ToolsDataProvider implements vscode.TreeDataProvider } export class ToolsTreeItem extends ToolsItemBase { - constructor(public readonly toolName: string, public readonly toolShortName: string, public readonly authorName: string, public readonly collapsibleState: vscode.TreeItemCollapsibleState) { + constructor( + public readonly toolName: string, + public readonly toolShortName: string, + public readonly authorName: string, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + public readonly toolIcon?: string, + ) { super(toolName, authorName, collapsibleState); } iconPath = { - light: vscode.Uri.file( - path.join( - __filename, - "..", - "resources", - "toolIcons", - this.toolShortName === "drb" ? "drb.png" : this.toolShortName === "prt" ? "prt.png" : this.toolShortName === "cmt" ? "cmt.png" : this.toolShortName === "pd" ? "pd.png" : "generic.svg", - ), - ), - dark: vscode.Uri.file( - path.join( - __filename, - "..", - "resources", - "toolIcons", - this.toolShortName === "drb" ? "drb.png" : this.toolShortName === "prt" ? "prt.png" : this.toolShortName === "cmt" ? "cmt.png" : this.toolShortName === "pd" ? "pd.png" : "generic.svg", - ), - ), + light: vscode.Uri.file(path.join(__filename, "..", "resources", "toolIcons", "light", this.toolIcon ?? "generic.svg")), + dark: vscode.Uri.file(path.join(__filename, "..", "resources", "toolIcons", "dark", this.toolIcon ?? "generic.svg")), }; } diff --git a/src/utils/ErrorMessages.ts b/src/utils/ErrorMessages.ts index c4a0aa6..f1d1f2c 100644 --- a/src/utils/ErrorMessages.ts +++ b/src/utils/ErrorMessages.ts @@ -15,4 +15,5 @@ export class ErrorMessages { public static wrUploadError: string = "Failed to upload web resource. Please check your connection and try again."; public static drbConnectionError: string = "Connect to an environment before trying to load Dataverse REST Builder."; public static invalidLoginType: string = "Invalid Login Type."; + public static commonToolsError: string = "An error occurred while trying to open the tool. Please try again."; } diff --git a/src/utils/Interfaces.ts b/src/utils/Interfaces.ts index 7197155..3e5debc 100644 --- a/src/utils/Interfaces.ts +++ b/src/utils/Interfaces.ts @@ -294,9 +294,10 @@ export interface ITools { } export interface IToolDetails { - toolName: string; - toolShortName: string; - toolAuthor: string; + name: string; + shortName: string; + author: string; + icon?: string; } export interface ICliCommandList { commands: ICliCommand[]; From 429d4b1500ca682325a455a64edb47ac40a2fa57 Mon Sep 17 00:00:00 2001 From: Power-Maverick Date: Sun, 12 Oct 2025 05:37:54 -0400 Subject: [PATCH 2/8] Remove ERD generator function and its import from ToolsHelper --- src/helpers/toolsHelper.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/helpers/toolsHelper.ts b/src/helpers/toolsHelper.ts index aba0047..2853281 100644 --- a/src/helpers/toolsHelper.ts +++ b/src/helpers/toolsHelper.ts @@ -1,4 +1,3 @@ -import { showERDPanel } from "@dvdt-tools/erd-generator"; import * as vscode from "vscode"; import { ToolsTreeItem } from "../tools/toolsDataProvider"; import { connectionCurrentStoreKey } from "../utils/Constants"; @@ -54,12 +53,12 @@ export class ToolsHelper { } } - public openERDGenerator(): void { - const connFromWS: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey); - if (connFromWS && connFromWS.currentAccessToken) { - showERDPanel(this.vscontext.extensionUri, connFromWS.environmentUrl, connFromWS.currentAccessToken); - } else { - vscode.window.showErrorMessage(ErrorMessages.commonToolsError); - } - } + // public openERDGenerator(): void { + // const connFromWS: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey); + // if (connFromWS && connFromWS.currentAccessToken) { + // showERDPanel(this.vscontext.extensionUri, connFromWS.environmentUrl, connFromWS.currentAccessToken); + // } else { + // vscode.window.showErrorMessage(ErrorMessages.commonToolsError); + // } + // } } From cb6168b64bef23cc963af377cb98e6f28f5abc3e Mon Sep 17 00:00:00 2001 From: Power-Maverick Date: Sun, 12 Oct 2025 18:01:48 -0400 Subject: [PATCH 3/8] Add "DVDT" to keywords in package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 4d2c219..278d0a4 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "Power Platform", "Dynamics 365", "Xrm", + "DVDT", "Dev Tools" ], "qna": "https://github.com/Power-Maverick/DataverseDevTools-VSCode/discussions", From 294fcdc42b286f559f3161960c1caa3cf7aec01f Mon Sep 17 00:00:00 2001 From: Mosh-K <158998622+Mosh-K@users.noreply.github.com> Date: Sun, 24 May 2026 23:46:03 +0300 Subject: [PATCH 4/8] Add plugin sync via pac plugin push (#324) --- package.json | 22 ++ package.nls.json | 1 + resources/templates/dvdt.linker.xml | 2 + src/commands/registerCommands.ts | 23 +++ src/helpers/dataverseHelper.ts | 28 ++- src/helpers/pluginHelper.ts | 310 ++++++++++++++++++++++++++++ src/utils/Constants.ts | 7 + src/utils/ErrorMessages.ts | 2 + src/utils/Interfaces.ts | 31 +++ src/utils/Placeholders.ts | 16 ++ 10 files changed, 439 insertions(+), 3 deletions(-) create mode 100644 src/helpers/pluginHelper.ts diff --git a/package.json b/package.json index 278d0a4..6d457ea 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,16 @@ "command": "dvdt.explorer.webresources.compareWebResource", "when": "resourceExtname in devtools.resourcesExtn && resourceFilename in devtools.linkedResources", "group": "%dvdt.menus.group.wr%@3" + }, + { + "command": "dvdt.explorer.plugins.linkPlugin", + "when": "resourceExtname == .csproj", + "group": "%dvdt.menus.group.plugin%@1" + }, + { + "command": "dvdt.explorer.plugins.pushPlugin", + "when": "resourceExtname == .csproj", + "group": "%dvdt.menus.group.plugin%@2" } ], "view/title": [ @@ -523,6 +533,18 @@ "light": "resources/light/play.svg", "dark": "resources/dark/play.svg" } + }, + { + "command": "dvdt.explorer.plugins.linkPlugin", + "title": "Link to Existing Dataverse Plugin", + "when": "false", + "category": "%dvdt.explorer.category%" + }, + { + "command": "dvdt.explorer.plugins.pushPlugin", + "title": "Push Plugin to Dataverse", + "when": "false", + "category": "%dvdt.explorer.category%" } ], "configuration": { diff --git a/package.nls.json b/package.nls.json index 1a6f031..4303044 100644 --- a/package.nls.json +++ b/package.nls.json @@ -3,6 +3,7 @@ "dvdt.explorer.category": "Explorer", "dvdt.menus.group.ts": "dvdevtools-ts", "dvdt.menus.group.wr": "dvdevtools-wr", + "dvdt.menus.group.plugin": "dvdevtools-plugin", "dvdt.config.title": "Dataverse DevTools", "dvdt.config.enablePreview": "Enable a preview features for Dataverse DevTools. See [documentation for all available preview features](https://github.com/Power-Maverick/DataverseDevTools-VSCode#-early-access-preview).\n\n> _Note: these features are not fully tested; so if you encounter any bugs please report them on GitHub (your name will appear on the contributors list)._", "dvdt.config.defaultTSTemplate": "Specifies how Dataverse DevTools should behave when you initiate a TypeScript project. This will skip the extra question during TS project initialization when you either select `Plain TypeScript` or `Webpack`" diff --git a/resources/templates/dvdt.linker.xml b/resources/templates/dvdt.linker.xml index 584213a..c9d5ed0 100644 --- a/resources/templates/dvdt.linker.xml +++ b/resources/templates/dvdt.linker.xml @@ -2,6 +2,8 @@ + + \ No newline at end of file diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 522781e..df10a9c 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -4,6 +4,7 @@ import { CliCommandTreeItem } from "../cliCommands/cliCommandsDataProvider"; import { CLIHelper } from "../helpers/cliHelper"; import { DataverseHelper } from "../helpers/dataverseHelper"; import { ErrorHandler } from "../helpers/errorHandler"; +import { PluginHelper } from "../helpers/pluginHelper"; import { TemplateHelper } from "../helpers/templateHelper"; import { ToolsHelper } from "../helpers/toolsHelper"; import { TypingsHelper } from "../helpers/typingsHelper"; @@ -29,6 +30,7 @@ export async function registerCommands(vscontext: vscode.ExtensionContext, tr: T const cliHelper = new CLIHelper(vscontext); const templateHelper = new TemplateHelper(vscontext); const wrHelper = new WebResourceHelper(vscontext, dvHelper); + const pluginHelper = new PluginHelper(vscontext); const typingHelper = new TypingsHelper(vscontext, dvHelper); const errorHandler = new ErrorHandler(tr); const toolsHelper = new ToolsHelper(vscontext); @@ -253,6 +255,26 @@ export async function registerCommands(vscontext: vscode.ExtensionContext, tr: T } }, }, + { + command: "dvdt.explorer.plugins.linkPlugin", + callback: async (uri: vscode.Uri) => { + try { + await pluginHelper.linkPlugin(uri.fsPath); + } catch (error) { + errorHandler.log(error, "linkPlugin"); + } + }, + }, + { + command: "dvdt.explorer.plugins.pushPlugin", + callback: async (uri: vscode.Uri) => { + try { + await pluginHelper.pushPlugin(uri.fsPath); + } catch (error) { + errorHandler.log(error, "pushPlugin"); + } + }, + }, ); cmds.forEach((c) => { vscontext.subscriptions.push(vscode.commands.registerCommand(c.command, c.callback)); @@ -260,6 +282,7 @@ export async function registerCommands(vscontext: vscode.ExtensionContext, tr: T updateConnectionStatusBar(await dvHelper.reloadWorkspaceConnection()); vscode.commands.executeCommand("setContext", `${extensionPrefix}.linkedResources`, await wrHelper.getLinkedResourceStrings("@_localFileName")); + vscode.commands.executeCommand("setContext", `${extensionPrefix}.linkedPlugins`, await pluginHelper.getLinkedProjectPaths()); } /** diff --git a/src/helpers/dataverseHelper.ts b/src/helpers/dataverseHelper.ts index ec96e09..1c4733a 100644 --- a/src/helpers/dataverseHelper.ts +++ b/src/helpers/dataverseHelper.ts @@ -10,6 +10,8 @@ import { customDataverseClientId, entityDefinitionsStoreKey, environmentTypes, + pluginAssembliesStoreKey, + pluginPackagesStoreKey, reservedWords, solDefinitionsStoreKey, wrDefinitionsStoreKey, @@ -24,6 +26,8 @@ import { IEntityMetadata, IOptionSet, IOptionSetMetadata, + IPluginAssemblies, + IPluginPackages, ISolutionComponents, ISolutions, IWebResource, @@ -145,8 +149,8 @@ export class DataverseHelper { this.vsstate.saveInWorkspace(connectionCurrentStoreKey, conn); progress.report({ increment: 30, message: "Getting entity metadata..." }); await this.getEntityDefinitions(); - progress.report({ increment: 70, message: "Getting web resources..." }); - await this.getWebResources(); + progress.report({ increment: 70, message: "Getting web resources & plugins..." }); + await Promise.all([this.getWebResources(), this.getPluginAssemblies(), this.getPluginPackages()]); vscode.commands.executeCommand("dvdt.explorer.connections.refreshConnection"); return new Promise((resolve) => { @@ -178,7 +182,7 @@ export class DataverseHelper { const connFromWS: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey); if (connFromWS) { await this.getEntityDefinitions(); - await this.getWebResources(); + await Promise.all([this.getWebResources(), this.getPluginAssemblies(), this.getPluginPackages()]); return connFromWS; } return undefined; @@ -341,6 +345,24 @@ export class DataverseHelper { vscode.commands.executeCommand("dvdt.explorer.webresources.loadWebResources"); } + /** + * Get plugin assemblies (unmanaged) from the current connection. + */ + public async getPluginAssemblies(): Promise { + const respData = await this.request.requestData("pluginassemblies?$select=pluginassemblyid,name&$filter=ismanaged%20eq%20false%20and%20_packageid_value%20eq%20null"); + this.vsstate.saveInWorkspace(pluginAssembliesStoreKey, respData); + return respData; + } + + /** + * Get plugin packages (unmanaged) from the current connection. + */ + public async getPluginPackages(): Promise { + const respData = await this.request.requestData("pluginpackages?$select=pluginpackageid,name&$filter=ismanaged%20eq%20false"); + this.vsstate.saveInWorkspace(pluginPackagesStoreKey, respData); + return respData; + } + /** * Gets the web resource content * @param {string} wrId - The ID of the web resource to retrieve. diff --git a/src/helpers/pluginHelper.ts b/src/helpers/pluginHelper.ts new file mode 100644 index 0000000..d0b5743 --- /dev/null +++ b/src/helpers/pluginHelper.ts @@ -0,0 +1,310 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { spawn } from "child_process"; +import * as path from "path"; +import * as vscode from "vscode"; +import { connectionCurrentStoreKey, extensionPrefix, pluginAssembliesStoreKey, pluginPackagesStoreKey, PluginType } from "../utils/Constants"; +import { ErrorMessages } from "../utils/ErrorMessages"; +import { copyFolderOrFile, getRelativeFilePath, getWorkspaceFolder, readFileSync, writeFileSync } from "../utils/FileSystem"; +import { IConnection, ILinkerFile, ILinkerPlugin, IPluginAssemblies, IPluginPackages } from "../utils/Interfaces"; +import { jsonToXML, xmlToJSON } from "../utils/Parsers"; +import { Placeholders } from "../utils/Placeholders"; +import { State } from "../utils/State"; + +interface IPluginQuickPickItem extends vscode.QuickPickItem { + id: string; + name: string; + type: PluginType; +} + +interface ILookupResult { + entry: ILinkerPlugin; + index: number; +} + +export class PluginHelper { + private vsstate: State; + private static outputChannel: vscode.OutputChannel | undefined; + + constructor(private vscontext: vscode.ExtensionContext) { + this.vsstate = new State(vscontext); + } + + //#region Public + + public async linkPlugin(csprojFullPath: string): Promise { + const assemblies: IPluginAssemblies = this.vsstate.getFromWorkspace(pluginAssembliesStoreKey); + const packages: IPluginPackages = this.vsstate.getFromWorkspace(pluginPackagesStoreKey); + const conn: IConnection = this.vsstate.getFromWorkspace(connectionCurrentStoreKey); + + if (!conn) { + vscode.window.showErrorMessage("Connect to a Dataverse environment before linking a plugin."); + return undefined; + } + + const items: IPluginQuickPickItem[] = []; + if (assemblies && assemblies.value) { + assemblies.value.forEach((a) => items.push({ label: a.name, description: "Assembly", id: a.pluginassemblyid, name: a.name, type: PluginType.assembly })); + } + if (packages && packages.value) { + packages.value.forEach((p) => items.push({ label: p.name, description: "Package", id: p.pluginpackageid, name: p.name, type: PluginType.nuget })); + } + + if (items.length === 0) { + vscode.window.showErrorMessage(ErrorMessages.pluginListUnavailable); + return undefined; + } + + const qpOptions = Placeholders.getQuickPickOptions(Placeholders.pluginSelection); + const picked = await vscode.window.showQuickPick(items, qpOptions); + if (!picked) { + return undefined; + } + + const localRelativePath = getRelativeFilePath(csprojFullPath, getWorkspaceFolder()?.fsPath!); + const existing = await this.lookupLinkedPlugin(csprojFullPath); + + const newEntry: ILinkerPlugin = { + "@_Id": picked.id, + "@_dvName": picked.name, + "@_type": picked.type, + "@_localProjectPath": localRelativePath, + "@_environment": conn.environmentUrl.replace(/\/+$/, ""), + }; + + if (existing) { + const confirm = await vscode.window.showWarningMessage("This project is already linked. Re-link?", { detail: "Confirm your selection", modal: true }, "Yes", "No"); + if (confirm !== "Yes") { + return undefined; + } + return await this.updateInLinkerFile(existing.index, newEntry); + } + + return await this.addInLinkerFile(newEntry); + } + + public async pushPlugin(csprojFullPath: string): Promise { + let linked = await this.lookupLinkedPlugin(csprojFullPath); + if (!linked) { + const linkOptions = ["Link to existing plugin & push"]; + const qpOptions = Placeholders.getQuickPickOptions(Placeholders.pluginLinkSelection); + const choice = await vscode.window.showQuickPick(linkOptions, qpOptions); + if (!choice) { + return; + } + const newEntry = await this.linkPlugin(csprojFullPath); + if (!newEntry) { + return; + } + linked = { entry: newEntry, index: 0 }; + } + + const projectDir = path.dirname(csprojFullPath); + const projectName = path.basename(csprojFullPath); + const entry = linked.entry; + + const authed = await this.ensurePacAuth(entry["@_environment"]); + if (!authed) { + return; + } + + const args = ["plugin", "push", "--pluginId", entry["@_Id"], "--type", entry["@_type"], "--environment", entry["@_environment"]]; + + const channel = PluginHelper.getOutputChannel(); + channel.clear(); + channel.appendLine(`> pac ${args.join(" ")}`); + channel.appendLine(`> cwd: ${projectDir}`); + channel.appendLine(""); + + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: `Pushing ${projectName}`, + cancellable: false, + }, + async (progress) => { + progress.report({ message: "Running pac plugin push..." }); + + const exitCode = await new Promise((resolve) => { + const child = spawn("pac", args, { cwd: projectDir, shell: true }); + + child.stdout.on("data", (data: Buffer) => channel.append(data.toString())); + child.stderr.on("data", (data: Buffer) => channel.append(data.toString())); + child.on("error", (err) => { + channel.appendLine(`\nFailed to start pac: ${err.message}`); + resolve(-1); + }); + child.on("close", (code) => resolve(code ?? -1)); + }); + + if (exitCode === 0) { + vscode.window.showInformationMessage(`${projectName} pushed.`); + } else { + channel.show(true); + vscode.window.showErrorMessage(ErrorMessages.pluginPushFailed); + } + }, + ); + } + + public async getLinkedProjectPaths(): Promise { + const linkerFile = await this.getLinkerFile(); + if (!linkerFile) { + return undefined; + } + const data = readFileSync(linkerFile.fsPath).toString(); + const json = xmlToJSON(data); + if (!json.DVDT.Plugins || !json.DVDT.Plugins.Plugin) { + return []; + } + if (Array.isArray(json.DVDT.Plugins.Plugin)) { + return json.DVDT.Plugins.Plugin.map((p) => p["@_localProjectPath"]); + } + return [json.DVDT.Plugins.Plugin["@_localProjectPath"]]; + } + + //#endregion Public + + //#region Private + + private async ensurePacAuth(environment: string): Promise { + if (!(await this.isPacInstalled())) { + const choice = await vscode.window.showErrorMessage("Power Platform CLI (pac) was not found. Install it and try again.", "Open install docs"); + if (choice === "Open install docs") { + vscode.env.openExternal(vscode.Uri.parse("https://aka.ms/PowerPlatformCLI")); + } + return false; + } + + if (await this.hasPacProfileForEnv(environment)) { + return true; + } + + const choice = await vscode.window.showWarningMessage(`pac is not authenticated to ${environment}. Authenticate first, then try Push again.`, "Authenticate"); + if (choice === "Authenticate") { + const terminal = vscode.window.createTerminal("DVDT - pac auth"); + terminal.show(); + terminal.sendText(`pac auth create --environment "${environment}"`, false); + } + return false; + } + + private async isPacInstalled(): Promise { + return new Promise((resolve) => { + const child = spawn("pac", [], { shell: true }); + child.on("close", (code) => resolve(code === 0)); + child.on("error", () => resolve(false)); + }); + } + + private async hasPacProfileForEnv(environment: string): Promise { + const output = await new Promise((resolve) => { + let buf = ""; + const child = spawn("pac", ["auth", "list"], { shell: true }); + child.stdout.on("data", (d: Buffer) => (buf += d.toString())); + child.stderr.on("data", (d: Buffer) => (buf += d.toString())); + child.on("close", () => resolve(buf)); + child.on("error", () => resolve("")); + }); + const normalize = (u: string) => u.replace(/\/+$/, "").toLowerCase(); + return normalize(output).includes(normalize(environment)); + } + + private async lookupLinkedPlugin(csprojFullPath: string): Promise { + const linkerFile = await this.getLinkerFile(); + if (!linkerFile) { + return undefined; + } + const localRelativePath = getRelativeFilePath(csprojFullPath, getWorkspaceFolder()?.fsPath!); + const data = readFileSync(linkerFile.fsPath).toString(); + const json = xmlToJSON(data); + + if (!json.DVDT.Plugins || !json.DVDT.Plugins.Plugin) { + return undefined; + } + + if (Array.isArray(json.DVDT.Plugins.Plugin)) { + const idx = json.DVDT.Plugins.Plugin.findIndex((p) => p["@_localProjectPath"] === localRelativePath); + return idx >= 0 ? { entry: json.DVDT.Plugins.Plugin[idx], index: idx } : undefined; + } else { + return json.DVDT.Plugins.Plugin["@_localProjectPath"] === localRelativePath ? { entry: json.DVDT.Plugins.Plugin, index: 0 } : undefined; + } + } + + private async addInLinkerFile(entry: ILinkerPlugin): Promise { + const linkerFile = await this.createLinkerFile(); + if (!linkerFile) { + return undefined; + } + const data = readFileSync(linkerFile.fsPath).toString(); + const json = xmlToJSON(data); + + if (!json.DVDT.Plugins) { + json.DVDT.Plugins = { Plugin: [] }; + } + if (Array.isArray(json.DVDT.Plugins.Plugin)) { + json.DVDT.Plugins.Plugin.push(entry); + } else { + const existing = json.DVDT.Plugins.Plugin; + json.DVDT.Plugins.Plugin = [existing, entry]; + } + writeFileSync(linkerFile.fsPath, jsonToXML(json)); + await this.updateLinkedPluginsContext(); + return entry; + } + + private async updateInLinkerFile(oldIndex: number, newEntry: ILinkerPlugin): Promise { + const linkerFile = await this.getLinkerFile(); + if (!linkerFile) { + return undefined; + } + const data = readFileSync(linkerFile.fsPath).toString(); + const json = xmlToJSON(data); + + if (!json.DVDT.Plugins) { + return undefined; + } + if (Array.isArray(json.DVDT.Plugins.Plugin)) { + json.DVDT.Plugins.Plugin[oldIndex] = newEntry; + } else { + json.DVDT.Plugins.Plugin = newEntry; + } + writeFileSync(linkerFile.fsPath, jsonToXML(json)); + await this.updateLinkedPluginsContext(); + return newEntry; + } + + private async getLinkerFile(): Promise { + const files = await vscode.workspace.findFiles("**/dvdt.linker.xml", "**/node_modules/**"); + return files.length > 0 ? files[0] : undefined; + } + + private async createLinkerFile(): Promise { + if (!vscode.workspace.workspaceFolders) { + return undefined; + } + const existing = await this.getLinkerFile(); + if (existing) { + return existing; + } + const workspaceFolder = vscode.workspace.workspaceFolders[0].uri; + const extPath = this.vscontext.extensionUri.fsPath; + const linkerTemplate = path.join(extPath, "resources", "templates", "dvdt.linker.xml"); + const newLinkerFileUri = vscode.Uri.joinPath(workspaceFolder, "dvdt.linker.xml"); + await copyFolderOrFile(linkerTemplate, newLinkerFileUri.fsPath); + return newLinkerFileUri; + } + + private async updateLinkedPluginsContext(): Promise { + const paths = await this.getLinkedProjectPaths(); + await vscode.commands.executeCommand("setContext", `${extensionPrefix}.linkedPlugins`, paths); + } + + private static getOutputChannel(): vscode.OutputChannel { + if (!PluginHelper.outputChannel) { + PluginHelper.outputChannel = vscode.window.createOutputChannel("Dataverse DevTools - Plugin Push"); + } + return PluginHelper.outputChannel; + } + + //#endregion Private +} diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index 28cd194..529d1aa 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -15,6 +15,8 @@ export const connectionStoreKey: string = `DataverseConnections`; export const connectionCurrentStoreKey: string = `LiveDVConnection`; export const entityDefinitionsStoreKey: string = `CurrentEntityDefinitions`; export const wrDefinitionsStoreKey: string = `CurrentWRs`; +export const pluginAssembliesStoreKey: string = `CurrentPluginAssemblies`; +export const pluginPackagesStoreKey: string = `CurrentPluginPackages`; export const solDefinitionsStoreKey: string = `CurrentSolutions`; export const smartMatchStoreKey: string = `CurrentSmartMatch`; export const environmentTypes: string[] = ["Dev", "QA", "UAT", "Prod"]; @@ -52,6 +54,11 @@ export enum WebResourceType { others = -1, } +export enum PluginType { + assembly = "Assembly", + nuget = "Nuget", +} + export enum ConfidenceScores { _90 = 90, _80 = 80, diff --git a/src/utils/ErrorMessages.ts b/src/utils/ErrorMessages.ts index f1d1f2c..2c3f83a 100644 --- a/src/utils/ErrorMessages.ts +++ b/src/utils/ErrorMessages.ts @@ -16,4 +16,6 @@ export class ErrorMessages { public static drbConnectionError: string = "Connect to an environment before trying to load Dataverse REST Builder."; public static invalidLoginType: string = "Invalid Login Type."; public static commonToolsError: string = "An error occurred while trying to open the tool. Please try again."; + public static pluginPushFailed: string = "Failed to push plugin. See the 'Dataverse DevTools - Plugin Push' output channel for details."; + public static pluginListUnavailable: string = "No plugin assemblies or packages found in the connected environment."; } diff --git a/src/utils/Interfaces.ts b/src/utils/Interfaces.ts index 3e5debc..8eb4e11 100644 --- a/src/utils/Interfaces.ts +++ b/src/utils/Interfaces.ts @@ -193,6 +193,7 @@ export interface ILinkerFile { export interface ILinkerRoot { WebResources: ILinkerResources; + Plugins?: ILinkerPlugins; Settings: any[]; } @@ -208,6 +209,36 @@ export interface ILinkerRes { "@_Id": string; } +export interface ILinkerPlugins { + Plugin: Array | ILinkerPlugin; +} + +export interface ILinkerPlugin { + "@_Id": string; + "@_dvName": string; + "@_type": string; + "@_localProjectPath": string; + "@_environment": string; +} + +export interface IPluginAssemblies { + value: IPluginAssembly[]; +} + +export interface IPluginAssembly { + pluginassemblyid: string; + name: string; +} + +export interface IPluginPackages { + value: IPluginPackage[]; +} + +export interface IPluginPackage { + pluginpackageid: string; + name: string; +} + export interface IOptionSetMetadata { LogicalName: string; MetadataId: string; diff --git a/src/utils/Placeholders.ts b/src/utils/Placeholders.ts index 236ae8f..341858c 100644 --- a/src/utils/Placeholders.ts +++ b/src/utils/Placeholders.ts @@ -122,6 +122,12 @@ export class Placeholders { case this.wrSearch: qpOptions = { placeHolder: this.ipWRSearch.placeHolderText, title: this.ipWRSearch.prompt, ignoreFocusOut: ignoreFocus }; break; + case this.pluginSelection: + qpOptions = { placeHolder: this.ipPluginSelection.placeHolderText, title: this.ipPluginSelection.prompt, ignoreFocusOut: ignoreFocus }; + break; + case this.pluginLinkSelection: + qpOptions = { placeHolder: this.ipPluginLinkSelection.placeHolderText, title: this.ipPluginLinkSelection.prompt, ignoreFocusOut: ignoreFocus }; + break; default: qpOptions = { placeHolder: "", ignoreFocusOut: ignoreFocus }; break; @@ -151,6 +157,8 @@ export class Placeholders { public static tsTemplateType: string = "TS-TemplateType"; public static entitiesSearch: string = "EntitiesSearch"; public static wrSearch: string = "WRSearch"; + public static pluginSelection: string = "PluginSelection"; + public static pluginLinkSelection: string = "PluginLinkSelection"; public static required: string = "[Required]"; public static optional: string = "[Optional]"; @@ -239,4 +247,12 @@ export class Placeholders { placeHolderText: `Search & Select Web Resources`, prompt: `Pick multiple web resources`, }; + private static ipPluginSelection: IPlaceholder = { + placeHolderText: `Select the plugin assembly or package`, + prompt: `Pick the Dataverse plugin to link with this project`, + }; + private static ipPluginLinkSelection: IPlaceholder = { + placeHolderText: `Select your option`, + prompt: `This project isn't linked. Link to an existing plugin?`, + }; } From 86cd04c7c5eb5254f48181c2948f02713e0b0d7c Mon Sep 17 00:00:00 2001 From: Mosh-K <158998622+Mosh-K@users.noreply.github.com> Date: Sun, 24 May 2026 23:48:44 +0300 Subject: [PATCH 5/8] Fix garbled non-ASCII text in Compare with Server (#322) --- src/helpers/webResourceHelper.ts | 4 ++-- src/utils/ExtensionMethods.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/helpers/webResourceHelper.ts b/src/helpers/webResourceHelper.ts index 8300fcf..ec1f64e 100644 --- a/src/helpers/webResourceHelper.ts +++ b/src/helpers/webResourceHelper.ts @@ -4,7 +4,7 @@ import * as path from "path"; import * as vscode from "vscode"; import { extensionPrefix, WebResourceType, wrDefinitionsStoreKey } from "../utils/Constants"; import { ErrorMessages } from "../utils/ErrorMessages"; -import { decodeFromBase64, encodeToBase64, extractGuid } from "../utils/ExtensionMethods"; +import { decodeFromBase64ToUTF8, encodeToBase64, extractGuid } from "../utils/ExtensionMethods"; import { copyFolderOrFile, createTempDirectory, getFileExtension, getFileName, getRelativeFilePath, getWorkspaceFolder, readFileAsBase64Sync, readFileSync, writeFileSync } from "../utils/FileSystem"; import { ILinkerFile, ILinkerRes, ISmartMatchRecord, IWebResource, IWebResources } from "../utils/Interfaces"; import { jsonToXML, xmlToJSON } from "../utils/Parsers"; @@ -63,7 +63,7 @@ export class WebResourceHelper { if (resourceToCompare) { const base64Content = await this.dvHelper.getWebResourceContent(resourceToCompare["@_Id"]); if (base64Content) { - const parsedContent = decodeFromBase64(base64Content); + const parsedContent = decodeFromBase64ToUTF8(base64Content); const tempDirUri = await createTempDirectory(); const tempFilePath = vscode.Uri.joinPath(tempDirUri, `temp-${resourceToCompare["@_localFileName"]}`); writeFileSync(tempFilePath.fsPath, parsedContent); diff --git a/src/utils/ExtensionMethods.ts b/src/utils/ExtensionMethods.ts index 4047bdf..9fbf1c2 100644 --- a/src/utils/ExtensionMethods.ts +++ b/src/utils/ExtensionMethods.ts @@ -79,6 +79,8 @@ export const extractGuid = (str: string): string | undefined => { } }; +export const decodeFromBase64ToUTF8 = (str: string): string => Buffer.from(str, "base64").toString("utf8"); + export const decodeFromBase64 = (str: string): string => Buffer.from(str, "base64").toString("binary"); export const encodeToBase64 = (str: string): string => Buffer.from(str, "binary").toString("base64"); From b2922cf057e5e8a2a4ab75de46e4727e33cef5e6 Mon Sep 17 00:00:00 2001 From: Power-Maverick Date: Sun, 24 May 2026 23:16:10 -0400 Subject: [PATCH 6/8] Docs: Add @Mosh-K as a contributor --- .all-contributorsrc | 7 +++++++ README.md | 1 + 2 files changed, 8 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 01f343b..99aa83a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -133,6 +133,13 @@ "avatar_url": "https://avatars.githubusercontent.com/u/22429087?v=4", "profile": "https://dev.to/kkazala", "contributions": ["bug", "code", "maintenance", "test", "userTesting"] + }, + { + "login": "Mosh-K", + "name": "Mosh-K", + "avatar_url": "https://avatars.githubusercontent.com/u/158998622?v=4", + "profile": "https://github.com/Mosh-K", + "contributions": ["code", "design", "userTesting"] } ], "contributorsPerLine": 7 diff --git a/README.md b/README.md index ba5978b..a55dc57 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Lars Hildebrandt
Lars Hildebrandt

πŸ’» πŸ€” πŸ› 🚧 ⚠️ Rob Montague
Rob Montague

πŸ› πŸ““ Kinga
Kinga

πŸ› πŸ’» 🚧 ⚠️ πŸ““ + Mosh-K
Mosh-K

πŸ’» 🎨 πŸ““ From ad4eff660f90b78c7f9a0576b141618c39996207 Mon Sep 17 00:00:00 2001 From: Power-Maverick Date: Sun, 24 May 2026 23:16:38 -0400 Subject: [PATCH 7/8] Bump version to 2.2.7 in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d457ea..4d1e1e5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "dataverse-devtools", "displayName": "Dataverse DevTools", - "version": "2.2.6", + "version": "2.2.7", "description": "The all-in-one tool to develop code for Dataverse/Dynamics 365. Helps you connect to a Dataverse environment, generate TypeScript definitions for entities, create different type of Dataverse-specific projects and much more.", "publisher": "danish-naglekar", "author": { From 7be628c08018bc3a99be1665919e8f99cf6f15cc Mon Sep 17 00:00:00 2001 From: Power-Maverick Date: Sun, 24 May 2026 23:22:15 -0400 Subject: [PATCH 8/8] Update CHANGELOG for v2.2.7 release with merged features and commits --- CHANGELOG.md | 17 ++++++++++++++++- README.md | 29 +++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c11c2d..3cdf061 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). -## [v2.2.6](https://github.com/Power-Maverick/DataverseDevTools-VSCode/compare/v2.2.5...v2.2.6) +## [v2.2.7](https://github.com/Power-Maverick/DataverseDevTools-VSCode/compare/v2.2.6...v2.2.7) ### Merged +- Fix garbled non-ASCII text in Compare with Server [`#322`](https://github.com/Power-Maverick/DataverseDevTools-VSCode/pull/322) +- Add plugin sync via pac plugin push [`#324`](https://github.com/Power-Maverick/DataverseDevTools-VSCode/pull/324) +- Feature: tools realignment [`#297`](https://github.com/Power-Maverick/DataverseDevTools-VSCode/pull/297) + +### Commits + +- Add tool icons and implement command registration for tools [`40bb631`](https://github.com/Power-Maverick/DataverseDevTools-VSCode/commit/40bb631ba721fed3fa7209228c86c50d72d3c312) +- Remove ERD generator function and its import from ToolsHelper [`429d4b1`](https://github.com/Power-Maverick/DataverseDevTools-VSCode/commit/429d4b1500ca682325a455a64edb47ac40a2fa57) +- Docs: Add @Mosh-K as a contributor [`b2922cf`](https://github.com/Power-Maverick/DataverseDevTools-VSCode/commit/b2922cf057e5e8a2a4ab75de46e4727e33cef5e6) + +## [v2.2.6](https://github.com/Power-Maverick/DataverseDevTools-VSCode/compare/v2.2.5...v2.2.6) - 2025-10-10 + +### Merged + +- Merge pull request #293 for release [`#294`](https://github.com/Power-Maverick/DataverseDevTools-VSCode/pull/294) - docs: Update README for v2.2.5 release with new features and bug fixes [`#293`](https://github.com/Power-Maverick/DataverseDevTools-VSCode/pull/293) - Fix: Context menu options not appearing after linking/initialization until VS Code restart [`#289`](https://github.com/Power-Maverick/DataverseDevTools-VSCode/pull/289) - Fix: Update command registration and improve status bar text for conn… [`#291`](https://github.com/Power-Maverick/DataverseDevTools-VSCode/pull/291) diff --git a/README.md b/README.md index a55dc57..43106fc 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,11 @@ - - -

- - - - - + + @@ -66,6 +60,7 @@ - [Generate Typings](#generate-typings) - [Intellisense for type generated](#intellisense-for-type-generated) - [Upload Web Resources](#upload-web-resources) + - [Upload Plugins (pac plugin push)](#upload-plugins-pac-plugin-push) - [Filter by solution](#filter-by-solution) - [Entities](#entities) - [Web Resources](#web-resources) @@ -161,6 +156,24 @@ Once you build your project you can upload your script directly from the VS Code ![WebResourceUpload](https://github.com/Power-Maverick/DataverseDevTools-VSCode/blob/main/assets/WebResourceUpload.gif?raw=true) +### Upload Plugins (pac plugin push) + +You can push Dataverse plugins (assembly / NuGet package) directly from VS Code using the Power Platform CLI. + +**How it works** + +1. In VS Code Explorer, right-click your plugin project `.csproj` file. +2. Select **Dataverse DevTools: Link to Existing Dataverse Plugin** and pick the target plugin from your connected environment. + - This creates/updates a `dvdt.linker.xml` file in your workspace to remember the link (plugin id, type and environment). +3. Right-click the same `.csproj` file and select **Dataverse DevTools: Push Plugin to Dataverse**. + - Dataverse DevTools runs `pac plugin push --pluginId --type --environment ` in the project folder. + +**Prerequisites** + +- Power Platform CLI (`pac`) must be installed. +- `pac` must be authenticated to the target environment. + - If no auth profile is found, the extension will prompt you to run `pac auth create --environment ""`. + ### Filter by solution #### Entities