Packaging & Installing Plugins
Learn to bundle, test locally with npm link, publish to npm, and install plugins directly via cordis.yml.
During local development, developers typically mount plugins directly using the --patch parameter. However, in cross-team collaboration, enterprise artifact registry publication, or automated CI/CD deployment, plugins must be packaged as standardized Bundles and installed into runnable Profiles using the dsh plugin command.
This tutorial breaks down the Bundle vs. Profile architecture, the four-tier layer composition hierarchy, and walks through packaging an “Enterprise CI/CD Automation Pipeline Bundle (dsh-cicd-runner)”.
Conceptual Foundation: The Dual Contract of Bundles and Profiles
The Harness distribution system separates capabilities from execution environments:
┌─────────────────────────────────────────────────────────────┐
│ Bundle vs. Profile Architecture │
├─────────────────────┬───────────────────────────────────────┤
│ 1. Bundle (Package) │ A published npm package declaring │
│ │ dsh.bundle in package.json (Patch layer)│
├─────────────────────┼───────────────────────────────────────┤
│ 2. Profile (Runtime)│ An executable environment declaring │
│ │ dsh.profile in package.json (Bundles) │
└─────────────────────┴───────────────────────────────────────┘
- Bundle: Authored and published by extension developers. Contains compiled code and a
cordis.patch.ymllayer specifying contributed plugin nodes; - Profile: Stored under
$DSH_HOME/profiles/<name>. Represents a concrete execution target recording the active bundle dependency list and their loading sequence.
Step 1: Author the Bundle Package
Establish the standard directory tree for dsh-cicd-runner:
dsh-cicd-runner/
├── package.json # Declares dsh.bundle metadata
├── cordis.patch.yml # Patch layer applied when the bundle is included
└── index.js # Compiled plugin entry point
1) Write package.json Manifest
{
"name": "dsh-cicd-runner",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"files": ["index.js", "cordis.patch.yml"],
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
}
}
}
2) Implement Plugin Logic index.js
export const name = 'cicd-runner';
export function apply(ctx) {
console.log('[CI/CD] Continuous integration plugin loaded successfully into Profile!');
}
3) Write Configuration Patch cordis.patch.yml
Production Packaging Rule: Bundle patch files must reference the plugin by its npm package name rather than a relative path, allowing standard Node.js module resolution to locate files across any host environment:
- insert:
- id: cicd-service
name: dsh-cicd-runner
config:
autoTriggerOnPush: true
Step 2: Installing into a Profile and Live Verification
Use dsh plugin --profile <name> to install bundles:
# Install local bundle into a profile named prod-agent
dsh plugin --profile prod-agent add ./dsh-cicd-runner
First use initializes the profile directory, appending the new package to dsh.profile.bundles:
{
"name": "dsh-profile-prod-agent",
"private": true,
"dependencies": {
"dsh-cicd-runner": "link:./dsh-cicd-runner"
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"dsh-cicd-runner"
]
}
}
}
Inspection and Execution:
# 1. Preview the merged YAML configuration tree
dsh --profile prod-agent --dump-config
# 2. Boot the profile
dsh --profile prod-agent
To remove the bundle:
dsh plugin --profile prod-agent remove dsh-cicd-runner
Four-Tier Layer Composition Order
Configuration layers are composed bottom-up in strict priority:
┌─────────────────────────────────────────────────────────────┐
│ Tier 4: CLI Overrides (--patch <path>) │ ◀ Highest Priority
├─────────────────────────────────────────────────────────────┤
│ Tier 3: Global Home Patch ($DSH_HOME/cordis.patch.yml) │
├─────────────────────────────────────────────────────────────┤
│ Tier 2: Profile Patch (profile/cordis.patch.yml) │
├─────────────────────────────────────────────────────────────┤
│ Tier 1: Bundle Layers (ordered by dsh.profile.bundles) │ ◀ Base Floor
└─────────────────────────────────────────────────────────────┘
Whole-Row Replacement Invariant: Higher tiers perform whole-row replacements for matching plugin IDs rather than recursive object merges. If adjusting a single property, specify all required fields for that row in your patch.
Frequently Asked Questions (FAQ)
Q1: Can a project be both a Bundle and a Profile?
Answer: No. A Bundle is an installable package declaring dsh.bundle; a Profile is an executable environment declaring dsh.profile. Their architectural roles are mutually exclusive.
Q2: Why must cordis.patch.yml reference the npm package name?
Answer: When installed via npm or dsh plugin add, files reside in node_modules. Referencing the package name ensures that Node module resolution locates compiled code deterministically across any environment.
Q3: Why does Patch execute "whole-row replacement" instead of deep recursive merging?
Answer: Whole-row replacement guarantees predictable configuration states, preventing hidden ghost properties from lingering after overrides. Use dsh --dump-config to inspect the final composite tree.
Q4: How do I install custom Bundles in air-gapped environments without internet access?
Answer: Package the bundle into a tarball using npm pack, then run dsh plugin --profile prod add ./dsh-cicd-runner-1.0.0.tgz. The package manager links directly from the local archive.