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
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 {
- 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 π» π€ π π§ β οΈ |
 Rob Montague π π |
 Kinga π π» π§ β οΈ π |
+  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

+### 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