dsh-devship: Integrating service startup, packaging, and multi-environment deployment into the DSH right sidebar

Preface

Doing daily development in DSH, one troublesome aspect is that the lifecycles of Maven modules and frontend projects in the workspace are scattered everywhere: configure a set of run configurations in IDEA, open a separate terminal to run mvn package, and manually open scp or write scripts to transfer to the server. After switching DSH sessions, these actions are no longer in front of you.

The approach taken by dsh-devship is to consolidate this into a panel: add a “App Runtime” tab to the right sidebar of DSH, automatically recognize projects in the current workspace that can be started, display one card per line, and provide Start/Stop/Package/Log/Deploy/Release actions on the card, so you don’t need to leave DSH. Below is an introduction to the plugin’s installation, panel behavior, and configuration options.

What is it

dsh-devship (referred to as “R&D Direct Dispatch Console” within the plugin) is maintained by dragoncdj and is a DSH client-side plugin. Its functionality can be broken down into three parts:

  1. Scan the workspace to find Java (modules where Spring Boot main classes reside) and Frontend (projects with scripts.dev in package.json) projects;
  2. Provide Start/Stop/Package/Log in the sidebar panel, supporting batch operations and detached mode;
  3. Send artifacts to multiple named deployment environments via SSH/SFTP, restart containers, and verify status.

The plugin is categorized as “Client” in the community directory, shows 1 star on the GitHub repository, and has an MIT license. The “Client” mentioned here refers to it being attached to the DSH client interface, not an independent command-line tool.

Technically, state queries and command execution happen on the Host side (routes.js / lib/*.js), the sidebar UI is rendered by client.js, and they use the same interface of the plugin process.

Installation and Enablement

Installation commands come directly from the README; choose one based on the DSH form you use:

# DSH Desktop
dsh plugin --profile desktop add dsh-devship

# dsh web
dsh plugin --profile web add dsh-devship

After installation, you need to restart DSH. A “App Runtime” tab will appear in the right sidebar and is automatically opened by default after the plugin is activated. If the panel is closed, it can be reopened from the “+” menu at the top of the right sidebar.

When developing the plugin itself, you can point directly to a local directory; changes take effect after restarting:

dsh plugin --profile desktop add E:\workspace\dsh-devship

Panel and Project Recognition

Scanning Rules

The plugin discovers projects based on fixed rules without manual registration:

  1. Java: Modules where <modules> in the root pom.xml exists and *Application.java exists under src/main/java. Sub-modules (parent modules / sub-modules-server) also count; the port is read from server.port at the top level of application-dev.yaml.
  2. Frontend: package.json with scripts.dev in the root directory or two levels of subdirectories (typical examples like ui/*, web/*), started with pnpm dev.

Scan results are cached for 5 minutes (SCAN_TTL_MS), and repeated scans for the same workspace are merged. The “Refresh” button in the top-right of the panel forces a rescan; newly added modules do not require a plugin restart.

Cards and Batch Operations

Each project has a card, containing a checkbox, status dot, type badge (Java / Frontend), port, the actual command to execute, and Start / Stop / Package / Log / Clear / Hide buttons.

Batch operations are divided into three layers:

  1. Top “Select All / Start Selected / Stop Selected” combined with card checkboxes; starting executes sequentially one by one to avoid multiple Maven instances writing to the local repository simultaneously; failed items are summarized with a prompt indicating “which project + why”;
  2. Bottom “Execute Tasks” list has “Clear All” in the top-right, clearing all Package/Deploy/Release records at once. When a task is running, the button changes in place to “Confirm Clear? This will interrupt the task” + Yes/No (auto-retract after 3 seconds). Clicking “Yes” interrupts subprocesses / SSH uploads first before removing the entry, preventing the situation where the list is gone but the process is still running in the background;
  3. “Auto Clear on Completion” has a switch for each of Package/Deploy/Release, plus a delay (Immediate / 5 / 15 / 30 / 60 seconds). The switch exists in <workspace>/.dsh-devship/prefs.json and is timed by the Host side; it works even if the panel is closed. Failed tasks are not auto-cleared; for tasks with auto-clear enabled, a countdown “Collapsing in N seconds” is displayed after the status.

Additionally, each row has “Hide / Unhide”, and the hidden list is also persisted in prefs.json; a “Show Hidden (n)” switch appears at the top as needed, with hidden items displayed semi-transparently.

When a port is occupied by an external instance (started by IDEA / Terminal), that row will be marked “Port Occupied”. Clicking Start will first give a clear prompt instead of running Maven in vain.

Polling and Connection Usage

The panel pulls the running status and log tail every 2.5 seconds, dropping to 1.2 seconds when a file is being transferred. Polling is “only one request allowed at a time”: if the previous /status hasn’t returned, the next poll is skipped. Each request has a 9-second timeout, and continuous failures back off at 2.5 → 5 → 10 → 15 seconds. Polling is disabled when the browser tab is in the background.

This limitation is intentional: Browsers allow a maximum of 6 HTTP/1.1 connections to the same source. Once the panel fills these connections, DSH’s own interfaces must queue up, and connection generation exceeding 15 seconds is considered a timeout and reconnection is attempted. This is most likely to be hit when accessing via HTTPS on a LAN, manifesting as the page repeatedly “Reconnecting → Connected”. The tab is set with keepMounted, so switching away and back won’t lose the polling state.

The log area is fixed at 260px height with internal scrolling and does not stretch with content.

Start and Stop

Java Startup Process

Starting a Java project consists of two stages, sharing the log tail. When entering the running phase, build logs are cleared, leaving only the application output:

  1. mvn -pl <module> -am -DskipTests -Dspring-boot.repackage.skip=true install
    - -am builds upstream modules first to avoid stale snapshots in the local repository;
    - -Dspring-boot.repackage.skip=true keeps the module jar as a regular jar, as nested BOOT-INF packages will cause missing Beans when the aggregate shell starts.
  2. mvn -pl <module> spring-boot:run, can be with -Dspring-boot.run.profiles=<profile>.

Before starting, a port probe is done for 800ms. If already occupied, it directly prompts “May already be running” instead of running Maven in vain.

Note that build and run share the log tail: output during the build phase is not left in the running log; to troubleshoot startup issues, you need to go back to the “Execute Tasks” list to view the log file.

Windows Windows and Processes

On Windows, starting does not pop up a black console window: .cmd is executed by powershell -WindowStyle Hidden, and subprocesses started by DSH are also hidden. Exit codes are returned normally.

When stopping, the two modes correspond to two cleanup methods:

Mode Stop Method
Attached (Ends with DSH) handle.terminate(), DSH’s job object ends the entire powershell → cmd → mvn → java / node tree together
Detached (DSH closed, service keeps running) taskkill /T /F to end the entire tree

Startup Mode: Stop DSH, Don’t Stop Service

Next to “clean / quick package” on the panel is a “Stop DSH, don’t stop service” checkbox, checked by default. It determines whether the service exits together with DSH.

  1. Checked (default, detached mode): A .cmd is written, output is redirected to logs/run-*.detached.log, wrapped in a .ps1 to claim the pid, and then started with wscript.exe via WMI Win32_Process.Create in a hidden way. The parent process of the process started by WMI is the system service WmiPrvSE, not in DSH’s job object, so it is not affected when DSH closes. The startup script uses WScript.Shell.Run(..., 0, False) (window style 0 = Hidden) to start powershell, with no console window popping up at any time. The process to track is that powershell, whose first task is to write its PID into run-*-detached.pid, which the Host polls to claim. When WSH is disabled by policy, it falls back to starting powershell directly (-WindowStyle Hidden), and testing shows no window appears.
  2. Unchecked (attached mode): Goes through ctx.subprocess pipe, executed by hidden window powershell .cmd, and the service ends together when DSH exits; logs go through the pipe.

In detached mode, logs are changed to reading log files (incremental).

Build and Release

Package Command

  1. Java: mvn -pl <module> -am package -DskipTests -T 1C (multithreading), checked clean becomes clean package;
  2. When “Quick Package” is checked, -am is removed, only compiling the target module (dependencies must have been installed), keeping -T 1C;
  3. Frontend: Directly node ./node_modules/vite/bin/vite.js build --mode prod (bypassing pnpm to avoid opening visible windows);
  4. Full package: Root reactor executes mvn package -DskipTests -T 1C.

The reason frontend packaging bypasses pnpm is that it opens another visible window, while the plugin’s goal is to never show windows during the entire process.

One-Click Release

The “Release” button on the card combines four steps into one task entry: Package → Deploy → Restart Container → Verify. The logs include phase numbers:

15:22:10 ① Package (Quick, no -am) ...
15:22:10 ① Package Complete
15:22:10 ② Deploy ...
15:22:10 Artifact: ...\target\xxx.jar
15:22:10 Deploy Complete: 1 file, 155.88 MB
15:22:10 Container xxx restarted, verifying status ...
15:22:10 Container Status: Up 8 seconds
✔ Release Complete

During release, three cards will appear in the task list: Package (build), Deploy (deploy), and Release (release). The Release card is the parent card, mirroring the logs and upload progress of the child cards, therefore:

  1. There is only one progress bar, drawn on the “Release” card; the “Deploy” child card has the progressInParent flag and does not redraw;
  2. Status text is distinguished by task type: Package card “Packaging / Package Complete”, Deploy card “Deploying / Deploy Complete / Deploy Failed”, Release card “Releasing / Release Complete / Release Failed”;
  3. Child cards are kept for their respective logs and “Check out / Interrupt”, and are collapsed after completion according to the “Auto Clear on Completion” setting.

Deployment to Multiple Servers

Clicking “Deployment Config” in the top-right of the panel opens a multi-environment editor; configuration is saved in <workspace>/.dsh-devship/deploy.json. One server is one environment, up to 12. Each has a name, id, address, port, user, password, dsh-ssh alias, restart command, “Protected” checkbox, “Set as Default”, and “Delete”.

Environment Selection and Protected

The way to push to environments is “select then send”:

  1. When configured with multiple, clicking “Deploy / Release” expands a row of environment buttons in place (Send to which? [Dev] [Test] [Prod], auto-retract after 10 seconds if not clicked); clicking which one actually sends;
  2. When only one is configured (old deploy.json), nothing pops up; clicking sends directly;
  3. Holding Shift and clicking the button skips the selector and sends directly to the “Default Release Environment” selected in the dropdown; Shift direct send, the remote directory on the card, and user@host:port all point to this machine; the button hover title will name it Prod · root@10.0.0.9 → /data/apps/demo/server;
  4. After checking “Protected”, clicking “Prod” in the selector turns red “Confirm send to Prod” the first time, and you must click again to actually send; this blocks all four paths for Deploy / Release / Batch; it resets automatically after sending or switching environments.

“Splitting into Dev / Test / Prod” generates three machines at once (now fill this one into “Dev”, “Prod” defaults to checked Protected), or you can “Add an Environment” to add your own. When there is only one old configuration, it is read as one environment with id default.

Remote Directory and Artifact

The directory resolution order is servers[environment].targets[project].remoteDir → top-level targets[project].remoteDir. Each environment has its own override table; if left empty, the top-level targets is used. If neither side is configured, the “Deploy” button will not appear on the card. The environment id can only use lowercase letters, numbers, and -, max 24 characters.

Corresponding JSON:

{
  "defaultServer": "dev",
  "servers": [
    {
      "id": "dev", "label": "Development", "host": "10.0.0.9", "port": 22, "user": "root", "password": "***",
      "targets": { "app-server": { "remoteDir": "/data/apps/dev/server" } }
    },
    {
      "id": "prod", "label": "Production", "alias": "prod-ssh", "protected": true,
      "restartCommand": "docker restart {container}",
      "targets": { "app-server": { "remoteDir": "/data/apps/demo/server", "container": "demo-server" } }
    }
  ],
  "targets": {
    "app-server": { "remoteDir": "/data/apps/demo/server" },
    "ui/admin-web": { "remoteDir": "/data/apps/demo/web/admin" }
  }
}

Artifacts default to Java = <module>/target/<artifactId>.jar, Frontend = dist-prod / dist, and can be overridden with artifact (one copy per environment). {container} in the restart command will be replaced with the target container name.

Deployment uses ssh2 / SFTP: directories are first tarred locally (no compression, LAN bandwidth is sufficient, faster than gzip) → send a package → unpack on the remote side. When tar is unavailable, it falls back to 8-way concurrent file upload. Single jars are put directly, with a cp x.jar x.jar.bak backup copy before upload. When BOOT-INF/ is not found at the end of the jar, a ⚠ warning is given in the logs (not a Spring Boot executable jar). After restart, docker ps is used to verify container status; if not in the running list, an error with docker logs --tail 20 is reported. During batch deployment, multiple projects can be uploaded simultaneously (no longer serial), and one SSH session can be reused.

Upload progress is reflected on the task card: percentage, “Uploaded / Total”, real-time speed; logs record a line every 2 seconds ↑ 42% 12.3 MB / 29.1 MB · 4.8 MB/s, and the last line is definitely recorded after completion. Speed is calculated based on the last 5 seconds window, not the entire round average. The first line of deployment logs is always Target: Prod → root@10.0.0.9:22 · Protected (alias only shows alias, actual connection is determined by dsh-ssh), and after connection, there are two lines Connecting to root@… / SSH Connected. You can check which machine was sent by scrolling the logs.

Tasks and logs are separate: the default machine uses deploy:<project> → logs/deploy-<project>.log; other environments use deploy_<environment>_<project> → logs/deploy_<environment>_<project>.log; sending the same project to two machines are two independent tasks, and logs do not overwrite each other. Batch “Deploy Selected / Release Selected” also expands the environment selector first, sending all selected projects to the machine you clicked (not fan-out across environments), executing sequentially one by one, with failed items summarized.

Old shape configurations (one top-level + one targets) are read and synthesized into the default machine; task id, log file name, and request behavior without serverId are byte-for-byte consistent with the past. There is one limit to note: old panels in 2.1.x will only send top-level fields; using “Save Config” with it will write multi-environment config back into a single-machine shape (password and restart command of the default machine are inherited, credentials are not lost); saving with the current version panel will not fall back.

Optional Configuration

Workspace Root Directory Configuration

The workspace root directory places .dsh-devship.json to override automatic detection results:

{
  "maven": "D:/ProgramFiles/apache-maven-3.9.15/bin/mvn.cmd",
  "pnpm": "C:/Users/me/AppData/Roaming/npm/pnpm.cmd",
  "javaHome": "C:/Users/me/.jdks/ms-25.0.4"
}

It works without writing: Maven searches PATH → M2HOME / MAVEN_HOME bin → common installation directories → IDEA bundled Maven sequentially. JDK finds a matching version based on java.version in the root pom; common directories are given by platform: Windows is ~/.jdks / C:\Program Files\Java / Adoptium / Zulu / Corretto, etc.; macOS is /Library/Java/JavaVirtualMachines (including the Contents/Home layer) and Homebrew; Linux is /usr/lib/jvm and sdkman (automatically switches when JAVA_HOME version is inappropriate).

Startup scripts generated at runtime are placed in <workspace>/.dsh-devship/.

Preferences and Deployment Configuration

prefs.json can be edited manually:

{
  "hidden": ["ui/admin"],
  "autoClear": { "build": true, "deploy": true, "release": false, "delayMs": 15000 }
}

delayMs only recognizes 0 / 5000 / 15000 / 30000 / 60000; other values fall back to 15000. Auto-clear only handles the cleanup of successful and stopped tasks; failed tasks remain in the list and can still be manually cleared or “Clear All”.

Suitable Scenarios and Notes

Suitable scenarios: Workspaces with Java multi-modules + Vite frontend, where daily operations involve repeatedly starting/stopping local services, packaging, sending packages to dev/test/prod machines and restarting containers, and wanting these actions not to leave the DSH interface. Batch start on the panel, deployment split by environment, protected environments, and detached mode (stop DSH without stopping service) are all prepared for such repetitive operations.

A few things to know:

  1. This plugin runs under the permissions of the current DSH process, can start subprocesses, write workspace files, and send packages to your specified servers via SSH. Its behavior determines which directories and machines you entrust it with;
  2. Before installing, read the source code and license (MIT) once to confirm that startup commands, deployment configuration, and command execution paths comply with your workspace conventions;
  3. Deployment configuration is empty by default; server addresses in the README are examples and need to be filled in by yourself;
  4. Port occupancy prompts only target externally started instances; if the service is pulled up by other tools, the plugin will not take over for you;
  5. SFTP uses ssh2, the remote side needs a usable shell and tar; when tar is unavailable it falls back to file-by-file upload, speed depends on the environment;
  6. Detached mode relies on Windows WMI and WSH; when disabled by policy, it falls back to starting powershell directly (hidden window).

Conclusion

Returning to the initial problem: The value of dsh-devship lies not in writing another set of startup scripts, but in centralizing the state of “which services are running in a workspace, where packages were sent, and if containers were restarted” into a single tab in the DSH sidebar, and managing Dev/Test/Production separately with the same configuration. The recognition rules are convention-based (*Application.java counts as Java, scripts.dev counts as Frontend), and you can use .dsh-devship.json to override when encountering non-standard structures.

Directory page: https://www.skillhub.cn/plugins/dragoncdj/dsh-devship
Source code and docs: https://github.com/dragoncdj/dsh-devship

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye