A VS Code extension that turns any
Jenkinsfileinto an interactive visual node graph ÔÇö edit it, run builds, and stream logs, all without leaving your editor.
- Overview
- Features
- Architecture
- Data Flow
- Node Types
- Message Protocol
- Project Structure
- Installation
- Configuration
- Usage
- Security
- Development
- Testing
- Tech Stack
- Contributing
- License
Jenkins Node Editor renders a Jenkinsfile as a live, editable node graph powered by React Flow. Changes made in the graph are immediately reflected in the source file, and changes made in the text editor instantly update the graph ÔÇö a true bidirectional sync.
The UI is styled after Blue Ocean, Jenkins' own modern pipeline visualization UI.
ÔöîÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÉ
Ôöé VS Code Window Ôöé
Ôöé Ôöé
Ôöé ÔöîÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÉ ÔöîÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÉ Ôöé
Ôöé Ôöé Text Editor (classic) ÔöéÔùäÔöÇÔöÇÔû║ Jenkins Node Editor Ôöé Ôöé
Ôöé Ôöé Ôöé Ôöé (Custom Editor Webview) Ôöé Ôöé
Ôöé Ôöé pipeline { Ôöé Ôöé Ôöé Ôöé
Ôöé Ôöé agent any Ôöé Ôöé ÔöîÔöÇÔöÇÔöÇÔöÇÔöÇÔöÉ ÔöîÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÉ Ôöé Ôöé
Ôöé Ôöé stages { Ôöé Ôöé ÔöéAgentÔöéÔöÇÔû║Ôöé Build Ôöé Ôöé Ôöé
Ôöé Ôöé stage('Build') { Ôöé Ôöé ÔööÔöÇÔöÇÔöÇÔöÇÔöÇÔöÿ ÔööÔöÇÔöÇÔöÇÔö¼ÔöÇÔöÇÔöÇÔöÿ Ôöé Ôöé
Ôöé Ôöé ... Ôöé Ôöé Ôöé Ôöé Ôöé
Ôöé Ôöé } Ôöé Ôöé ÔöîÔöÇÔöÇÔöÇÔû╝ÔöÇÔöÇÔöÇÔöÉ Ôöé Ôöé
Ôöé Ôöé } Ôöé Ôöé Ôöé Test Ôöé Ôöé Ôöé
Ôöé Ôöé } Ôöé Ôöé ÔööÔöÇÔöÇÔöÇÔö¼ÔöÇÔöÇÔöÇÔöÿ Ôöé Ôöé
Ôöé Ôöé Ôöé Ôöé Ôöé Ôöé Ôöé
Ôöé ÔööÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÿ Ôöé ÔöîÔöÇÔöÇÔöÇÔû╝ÔöÇÔöÇÔöÇÔöÉ Ôöé Ôöé
Ôöé Ôöé ÔöéDeploy Ôöé Ôöé Ôöé
Ôöé ÔööÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔö┤ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔö┤ÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÿ Ôöé
ÔööÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÇÔöÿ
| Feature | Description |
|---|---|
| Visual Graph Editor | Drag, drop, and connect pipeline nodes on a Blue OceanÔÇôstyled canvas |
| Bidirectional Sync | Edit text  graph updates; edit graph  text updates |
| Drag-safe Sync | Sync is deliberately skipped while a node is being dragged ÔÇö no mid-drag remounts |
| Undo / Redo | Full undo/redo history for node and edge changes via zundo (Ôå® / Ôå¬ in toolbar) |
| Auto-layout | Dagre-powered automatic node positioning on open |
| Declarative Parser | Full support for pipeline {}, stages, agent, when, environment, parameters, triggers, post |
| Scripted Fallback | Basic node {} scripted pipeline support |
| Node Palette | 20+ node types in collapsible groups ÔÇö drag onto canvas to add |
| Rich Node Inspector | Full property editor for every node type: env vars, parameters, options, triggers, when conditions, post conditions, all step types |
| Validation | Local syntax check + optional remote Jenkins API validation with inline node error markers |
| CSRF-safe Builds | Fetches a Jenkins CSRF crumb before every POST; crumb is cached and invalidated on 403 |
| Secure Token Storage | Jenkins API token stored in VS Code's encrypted SecretStorage, never in settings.json |
| Build Trigger | Trigger Jenkins builds directly from the editor |
| Log Streaming | Real-time build log streaming via Jenkins' progressive text API |
| Theme Support | Follows VS Code light / dark / high-contrast themes |
| Position Memory | Node positions persisted in .vscode/ between sessions |
| Error Boundary | React ErrorBoundary wraps the root ÔÇö crashes show a readable error panel, not a blank screen |
The extension is split into two isolated runtimes that communicate via a typed message bus.
graph TB
subgraph "Extension Host (Node.js)"
EXT["extension.ts\nActivate / Commands"]
EDITOR["JenkinsNodeEditor\nCustomTextEditorProvider"]
BUS["MessageBus\nTyped pub/sub bridge"]
PARSER["JenkinsfileParser\nJenkinsfile  GraphModel"]
GEN["JenkinsfileGenerator\nGraphModel  Jenkinsfile"]
VALIDATOR["JenkinsValidator\nLocal + Remote validation"]
CLIENT["JenkinsClient\nREST API + CSRF crumb"]
POSSTORE["PositionStore\nPersist node positions"]
SECRETS["SecretStorage\nEncrypted token store"]
LOGGER["logger\nOutput channel"]
end
subgraph "Webview (React / Vite)"
APP["App.tsx\nRoot component"]
BOUNDARY["ErrorBoundary\nCrash fallback"]
CANVAS["NodeCanvas\nReact Flow container"]
PALETTE["NodePalette\n20+ node types, collapsible"]
INSPECTOR["NodeInspector\nFull property editor"]
TOOLBAR["Toolbar\nUndo/Redo/Validate/Run/Help"]
LOGPANEL["LogPanel\nBuild log display"]
STORE["graphStore (Zustand + zundo)\nUndo/redo-aware state"]
BRIDGE["useVSCodeBridge\npostMessage + drag guard"]
SYNC["useGraphSync\nDrag-safe debounced sync"]
NODES["Node Components\nStage/Step/Agent/Parallel/Post"]
end
subgraph "Shared (no runtime deps)"
TYPES["shared/types.ts\nAll TypeScript types"]
MESSAGES["shared/messages.ts\nMessage discriminated unions"]
end
EXT --> EDITOR
EDITOR --> BUS
EDITOR --> PARSER
EDITOR --> GEN
EDITOR --> VALIDATOR
EDITOR --> CLIENT
EDITOR --> POSSTORE
EDITOR --> SECRETS
BUS <-->|"postMessage\nonDidReceiveMessage"| BRIDGE
BRIDGE --> STORE
STORE --> CANVAS
STORE --> INSPECTOR
STORE --> TOOLBAR
SYNC --> BUS
CANVAS --> NODES
APP --> BOUNDARY
BOUNDARY --> CANVAS
sequenceDiagram
participant VSCode
participant JNE as JenkinsNodeEditor
participant Secrets as SecretStorage
participant Parser
participant Bus as MessageBus
participant Store as graphStore
participant UI as React UI
VSCode->>JNE: resolveCustomTextEditor(document)
JNE->>JNE: getWebviewHtml()  inject nonce + CSP
JNE->>Secrets: resolveToken() ÔÇö migrate legacy settings token if present
JNE->>Bus: create MessageBus
UI->>Bus: READY
Bus->>JNE: on('READY')
JNE->>Parser: parse(document.getText())
Parser-->>JNE: { graph, errors, mode }
JNE->>JNE: mergePositions(graph, savedPositions)
JNE->>Bus: send INIT { graph, theme, config }
Bus->>Store: dispatch INIT
Store->>Store: setNodes / setEdges / autoLayout
Store->>UI: re-render
sequenceDiagram
participant User
participant RF as React Flow
participant Store as graphStore
participant Sync as useGraphSync
participant Bus as MessageBus
participant JNE as JenkinsNodeEditor
participant Gen as Generator
participant Doc as TextDocument
User->>RF: drag END / edit node / connect edge
RF->>Store: onNodesChange (dragging=false) / onEdgesChange / onConnect
Store->>Store: isDirty = true
Note over Sync: isDragging check ÔÇö skips sync while any node.dragging=true
Sync->>Bus: GRAPH_CHANGED { graph } (after 300ms debounce)
Bus->>JNE: on('GRAPH_CHANGED')
JNE->>JNE: syncDepth++ (depth counter, not boolean flag)
JNE->>Gen: generate(graph)
Gen-->>JNE: Jenkinsfile text
JNE->>Doc: WorkspaceEdit.replace(fullRange, text)
JNE->>JNE: syncDepth-- (in finally)
sequenceDiagram
participant User
participant Doc as TextDocument
participant JNE as JenkinsNodeEditor
participant Parser
participant Bus as MessageBus
participant Bridge as useVSCodeBridge
participant Store as graphStore
User->>Doc: type in text editor
Doc->>JNE: onDidChangeTextDocument
JNE->>JNE: if syncDepth > 0  skip (anti-loop)
JNE->>Parser: parse(document.getText())
Parser-->>JNE: { graph }
JNE->>JNE: mergePositions(graph, saved)
JNE->>Bus: send DOC_CHANGED { graph }
Bus->>Bridge: message event
Bridge->>Bridge: if any node.dragging  drop message (drag guard)
Bridge->>Store: setNodes / setEdges
The graph model uses 5 rendered node kinds and a rich property inspector for each:
graph LR
subgraph "Pipeline Structure"
P([ƒöÁ pipeline]) --> A([ƒƒó agent])
P --> S1([ƒƒú stage: Build])
P --> S2([ƒƒú stage: Test])
P --> S3([ƒƒú stage: Deploy])
P --> POST([ƒö┤ post])
end
subgraph "Stage Children"
S1 --> ST1([ƒöÁ step: sh])
S1 --> ST2([ƒöÁ step: archiveArtifacts])
S2 --> ST3([ƒöÁ step: sh])
S2 --> ST4([ƒöÁ step: junit])
end
subgraph "Parallel Stage"
S3 --> PAR([ƒƒí parallel])
PAR --> B1([ƒƒú stage: Branch A])
PAR --> B2([ƒƒú stage: Branch B])
end
| Kind | Color | Description | Inspector sections |
|---|---|---|---|
pipeline |
ƒöÁ Blue | Root container node | Global agent, environment vars, parameters, options, triggers |
agent |
ƒ®Á Cyan | Execution agent | Type (any/none/label/docker/dockerfile) + type-specific fields |
stage |
ƒƒú Purple | Named pipeline stage | Name, agent override, when condition, failFast, env vars |
step |
ƒöÁ Teal | Individual build step | Type selector + all step-specific fields (sh/echo/git/checkout/archiveArtifacts/junit/timeout/retry/script/withCredentials/input/custom) |
parallel |
ƒƒí Amber | Parallel execution group | failFast toggle |
post |
ƒö┤ Red | Post-build condition | Condition (always/success/failure/unstable/changed/fixed/regression/aborted/cleanup) |
| Step | Generated Groovy |
|---|---|
sh |
sh 'command' |
bat |
bat 'command' |
echo |
echo 'message' |
git |
git url: '', branch: '' |
checkout |
checkout scm |
archiveArtifacts |
archiveArtifacts artifacts: '**/*.jar' |
junit |
junit '**/surefire-reports/*.xml' |
withCredentials |
withCredentials([usernamePassword()]) {  } |
timeout |
timeout(time: 10, unit: 'MINUTES') {  } |
retry |
retry(3) {  } |
input |
input message: '', ok: '' |
sleep |
sleep time: 5, unit: 'SECONDS' |
stash / unstash |
stash name: '' / unstash '' |
slackSend |
slackSend channel: '', message: '' |
script |
Raw Groovy block |
custom |
Any other step ÔÇö raw Groovy preserved |
Communication between the Extension Host and the Webview uses strongly-typed discriminated unions defined in src/shared/messages.ts.
| Message Type | Payload |
|---|---|
INIT |
{ graph: GraphModel, theme: VSCodeTheme, config: ExtensionConfig } |
DOC_CHANGED |
{ graph: GraphModel } |
VALIDATION_RESULT |
{ errors: ValidationError[] } |
STEP_CATALOG |
{ steps: StepDefinition[] } |
LOG_LINE |
{ line: string, stream: 'stdout' | 'stderr' } |
BUILD_STATUS |
{ status: BuildStatus } |
THEME_CHANGED |
{ theme: VSCodeTheme } |
| Message Type | Payload |
|---|---|
READY |
(none) ÔÇö webview mounted |
GRAPH_CHANGED |
{ graph: GraphModel } |
VALIDATE_REQUEST |
{ content?: string } |
RUN_BUILD |
{ jobName?: string, branch?: string, params?: Record<string,string> } |
ABORT_BUILD |
{ jobName?: string, buildNumber?: number } |
ERROR |
{ message: string, stack?: string } |
NodeCi/
Ôö£ÔöÇÔöÇ ƒôä package.json # Extension manifest + scripts
Ôö£ÔöÇÔöÇ ƒôä tsconfig.json # Extension host TypeScript config
Ôö£ÔöÇÔöÇ ƒôä tsconfig.webview.json # Webview TypeScript config
Ôö£ÔöÇÔöÇ ƒôä vite.config.ts # Webview build (Vite)
Ôö£ÔöÇÔöÇ ƒôä esbuild.config.js # Extension build (esbuild)
Ôö£ÔöÇÔöÇ ƒôä vitest.config.ts # Unit test config
Ôöé
Ôö£ÔöÇÔöÇ ƒôü media/
Ôöé ÔööÔöÇÔöÇ icon.png # Extension icon
Ôöé
Ôö£ÔöÇÔöÇ ƒôü src/
Ôöé Ôö£ÔöÇÔöÇ ƒôü extension/ # Extension host (Node.js runtime)
Ôöé Ôöé Ôö£ÔöÇÔöÇ extension.ts # Activate / deactivate + commands (incl. setToken)
Ôöé Ôöé Ôö£ÔöÇÔöÇ JenkinsNodeEditor.ts # CustomTextEditorProvider ÔÇö SecretStorage, syncDepth
Ôöé Ôöé Ôö£ÔöÇÔöÇ MessageBus.ts # Typed pub/sub bridge
Ôöé Ôöé Ôö£ÔöÇÔöÇ JenkinsValidator.ts # Local + REST validation
Ôöé Ôöé Ôö£ÔöÇÔöÇ JenkinsClient.ts # Jenkins REST API + CSRF crumb cache
Ôöé Ôöé Ôö£ÔöÇÔöÇ PositionStore.ts # Persistent node positions
Ôöé Ôöé ÔööÔöÇÔöÇ logger.ts # VS Code output channel
Ôöé Ôöé
Ôöé Ôö£ÔöÇÔöÇ ƒôü parser/ # Jenkinsfile Ôåö GraphModel
   JenkinsfileParser.ts # Jenkinsfile  GraphModel
   JenkinsfileGenerator.ts # GraphModel  Jenkinsfile
Ôöé Ôöé ÔööÔöÇÔöÇ layout.ts # Dagre layout (extension-side)
Ôöé Ôöé
Ôöé Ôö£ÔöÇÔöÇ ƒôü shared/ # Zero-dependency shared types
Ôöé Ôöé Ôö£ÔöÇÔöÇ types.ts # All domain types
Ôöé Ôöé ÔööÔöÇÔöÇ messages.ts # Message protocol discriminated unions
Ôöé Ôöé
Ôöé ÔööÔöÇÔöÇ ƒôü webview/ # React UI (browser runtime)
Ôöé Ôö£ÔöÇÔöÇ main.tsx # React entry point + ErrorBoundary
Ôöé Ôö£ÔöÇÔöÇ App.tsx # Root layout component
Ôöé Ôö£ÔöÇÔöÇ ƒôü components/
Ôöé Ôöé Ôö£ÔöÇÔöÇ NodeCanvas.tsx # React Flow canvas + drag/drop + welcome state
Ôöé Ôöé Ôö£ÔöÇÔöÇ NodePalette.tsx # 20+ draggable node types in collapsible groups
   NodeInspector.tsx # Full property editor (env vars, when, params, options)
Ôöé Ôöé Ôö£ÔöÇÔöÇ Toolbar.tsx # Undo/Redo + Validate/Run/Abort + Help panel
Ôöé Ôöé ÔööÔöÇÔöÇ LogPanel.tsx # Streaming build log display
Ôöé Ôö£ÔöÇÔöÇ ƒôü nodes/
Ôöé Ôöé Ôö£ÔöÇÔöÇ BaseNode.tsx # Blue Ocean card chrome (glow on select, status dot)
Ôöé Ôöé Ôö£ÔöÇÔöÇ StageNode.tsx # Stage node ÔÇö when badge, failFast indicator
Ôöé Ôöé Ôö£ÔöÇÔöÇ StepNode.tsx # Step node ÔÇö type label + script preview
Ôöé Ôöé Ôö£ÔöÇÔöÇ AgentNode.tsx # Agent node ÔÇö type + detail
Ôöé Ôöé Ôö£ÔöÇÔöÇ ParallelNode.tsx # Parallel node ÔÇö branch count
Ôöé Ôöé Ôö£ÔöÇÔöÇ PostNode.tsx # Post node ÔÇö condition badge
Ôöé Ôöé ÔööÔöÇÔöÇ index.ts # Module-level nodeTypes map (avoids remount bug)
Ôöé Ôö£ÔöÇÔöÇ ƒôü hooks/
Ôöé Ôöé Ôö£ÔöÇÔöÇ useVSCodeBridge.ts # postMessage bridge + drag guard on DOC_CHANGED
Ôöé Ôöé Ôö£ÔöÇÔöÇ useGraphSync.ts # Drag-safe debounced sync (skips while dragging)
Ôöé Ôöé ÔööÔöÇÔöÇ useJenkinsAPI.ts # Validate / run / abort hooks
Ôöé Ôö£ÔöÇÔöÇ ƒôü store/
Ôöé Ôöé ÔööÔöÇÔöÇ graphStore.ts # Zustand + immer + zundo (undo/redo, 50-state limit)
Ôöé Ôö£ÔöÇÔöÇ ƒôü utils/
Ôöé Ôöé Ôö£ÔöÇÔöÇ layout.ts # Dagre auto-layout (webview-side)
   theme.ts # VS Code theme  CSS vars
Ôöé ÔööÔöÇÔöÇ ƒôü styles/
Ôöé ÔööÔöÇÔöÇ globals.css # Blue Ocean CSS variables + utility classes
Ôöé
Ôö£ÔöÇÔöÇ ƒôü test/
Ôöé Ôö£ÔöÇÔöÇ runTests.js # E2E test runner
Ôöé Ôö£ÔöÇÔöÇ ƒôü fixtures/
Ôöé Ôöé Ôö£ÔöÇÔöÇ simple.Jenkinsfile # 3-stage declarative pipeline
Ôöé Ôöé Ôö£ÔöÇÔöÇ parallel.Jenkinsfile # Parallel stages example
Ôöé Ôöé ÔööÔöÇÔöÇ complex.Jenkinsfile # Full-featured pipeline
Ôöé ÔööÔöÇÔöÇ ƒôü suite/
Ôöé ÔööÔöÇÔöÇ parser.test.ts # 19 Vitest unit tests
Ôöé
Ôö£ÔöÇÔöÇ ƒôü docs/
Ôöé Ôö£ÔöÇÔöÇ PHASE1.md ÔÇö PHASE6.md # Phase-by-phase build notes
Ôöé
ÔööÔöÇÔöÇ ƒôü dist/ # Build output (git-ignored)
Ôö£ÔöÇÔöÇ extension.js # Bundled extension host
ÔööÔöÇÔöÇ ƒôü webview/
Ôö£ÔöÇÔöÇ main.js # Bundled React app
ÔööÔöÇÔöÇ main.css # Bundled styles
Search for "Jenkins Node Editor" in the Extensions panel (Ctrl+Shift+X), or install by ID:
PlanesZwalker.vscode-jenkins-node-editor
Prerequisites: Node.js  20, npm  10, VS Code  1.85
git clone https://github.com/PlanesZwalker/vscode-jenkins-node-editor.git
cd vscode-jenkins-node-editor
npm install
npm run build
# Launch Extension Development Host:
# press F5 in VS Code, or install the .vsix:
npm run package
code --install-extension vscode-jenkins-node-editor-0.1.0.vsixOpen VS Code Settings (Ctrl+,) and search for Jenkins Node Editor:
| Setting | Type | Default | Description |
|---|---|---|---|
jenkinsNodeEditor.jenkinsUrl |
string | "" |
Jenkins server URL, e.g. http://localhost:8080 |
jenkinsNodeEditor.jenkinsUser |
string | "" |
Jenkins username for API auth |
jenkinsNodeEditor.autoLayout |
boolean | true |
Auto-layout graph when opening a file |
jenkinsNodeEditor.syncDelay |
number | 300 |
Debounce delay (ms) before syncing graph  text |
ÔÜá´©Å
jenkinsNodeEditor.jenkinsTokenhas been deprecated. Use the secure command below instead.
The token is stored in VS Code's encrypted SecretStorage, not in settings.json:
Ctrl+Shift+P  Jenkins: Set Jenkins API Token (Secure)
On first launch, any token already in settings.json is automatically migrated to SecretStorage and removed from the file.
{
"jenkinsNodeEditor.jenkinsUrl": "http://jenkins.example.com:8080",
"jenkinsNodeEditor.jenkinsUser": "alice",
"jenkinsNodeEditor.autoLayout": true,
"jenkinsNodeEditor.syncDelay": 300
}- Open any file named
Jenkinsfile,*.jenkinsfile,*.Jenkinsfile, orJenkinsfile.* - Click the Jenkins Node Editor icon in the editor title bar, or right-click the file in the Explorer  Open Jenkins Node Editor
- The graph panel opens beside the text editor
| Action | How |
|---|---|
| Select node | Click any node |
| Move node | Drag the node |
| Edit properties | Select node  Inspector panel (right) |
| Add node | Drag from the Node Palette (left) |
| Delete node | Select + Delete key |
| Connect nodes | Drag from a node's output handle (ÔùÅ) to another's input |
| Undo / Redo | Ôå® Undo / Ôå¬ Redo buttons in Toolbar (or use the toolbar buttons) |
| Auto-layout | Ôè× Layout button in Toolbar |
| Fit view | Ôèí Fit button in Toolbar |
| Zoom | Scroll wheel / pinch |
| Pan | Middle-click drag |
| Keyboard help | ? Help button in Toolbar |
[ Ôè× Layout ] [ Ôèí Fit ] | [ Ôå® Undo ] [ Ôå¬ Redo ] | [ Ô£ô Validate ] ┬À┬À┬À Ôû Run Build | [ Ôÿ░ Logs ] [ ? Help ]
When a build is running, the Log Panel shows at the bottom and streams log lines in real-time. The build status indicator in the Toolbar pulses blue while running, turns green on success, red on failure.
| Concern | Implementation |
|---|---|
| API token | Stored in VS Code SecretStorage (OS-level encrypted), never in settings.json or committed to source control |
| Token migration | Existing settings.json tokens are auto-migrated to SecretStorage on first open, then removed from the file |
| CSRF protection | JenkinsClient fetches /crumbIssuer/api/json before every POST; crumb cached per client instance, invalidated on HTTP 403 |
| Content Security Policy | Webview uses a strict CSP with a per-session nonce ÔÇö no unsafe-eval, no plain unsafe-inline for scripts |
| No external network | The webview has no internet access; all Jenkins calls go through the extension host |
npm run build # Build everything (extension + webview)
npm run build:ext # Build only the extension host (esbuild)
npm run build:web # Build only the webview (Vite)
npm run watch # Watch mode for both (use with F5)
npm run test:unit # Vitest unit tests
npm run package # Bundle as .vsix
npm run publish # Publish to Marketplace (requires vsce login)- Open the workspace in VS Code
- Press
F5 launches Extension Development Host - In the new window, open a
Jenkinsfile - Set breakpoints in
src/extension/for extension host code - For webview debugging: open Developer Tools (
Ctrl+Shift+I) and inspect the<iframe>
- Add the new
NodeKindliteral tosrc/shared/types.ts - Create
src/webview/nodes/MyNewNode.tsxextendingBaseNode - Register it in
src/webview/nodes/index.ts(module-level constant ÔÇö not inside a component) - Add a palette entry in
src/webview/components/NodePalette.tsx - Add an inspector section in
NodeInspector.tsx - Handle the kind in
JenkinsfileParser.tsandJenkinsfileGenerator.ts
nodeTypesmust be a module-level constant ÔÇö declaring it inside a component causes React Flow to remount all nodes on every render- Sync is gated on drag end ÔÇö
onNodesChangemarks dirty only whenchange.dragging === false;useGraphSyncskips while any node hasdragging: true;useVSCodeBridgedropsDOC_CHANGEDmessages during active drags-useTemporalStoreis a React hook —useGraphStore.temporal(fromzundo) is a plainStoreApi, not callable. It is wrapped withuseStore()so components can reactively subscribe topastStates/futureStates. Calling it directly as a function throwsTypeError: Xi is not a function
npm run test:unit19 tests covering the parser and generator:
Ô£ô test/suite/parser.test.ts (19 tests)
JenkinsfileParser ÔÇö simple.Jenkinsfile
Ô£ô parses without fatal errors
Ô£ô detects declarative mode
Ô£ô extracts 3 stage nodes (Build, Test, Deploy)
Ô£ô stage names match Jenkinsfile
Ô£ô extracts agent node (type: any)
Ô£ô extracts post nodes
Ô£ô all nodes have valid positions after layout
Ô£ô edges connect stages in sequence
JenkinsfileParser ÔÇö parallel.Jenkinsfile
Ô£ô parses without fatal errors
Ô£ô detects parallel node
Ô£ô parallel branches are present
JenkinsfileParser ÔÇö error cases
 empty string  error
 unbalanced braces  error
 partial input  partial graph
JenkinsfileGenerator
Ô£ô output contains 'pipeline' and 'stages'
Ô£ô output ends with '}'
Ô£ô indentation is divisible by 2
Ô£ô round-trip preserves stage count
Ô£ô generates agent block correctly
Test Files 1 passed (1)
Tests 19 passed (19)
Duration ~500ms
| Fixture | Description |
|---|---|
simple.Jenkinsfile |
3 stages (Build/Test/Deploy), agent any, post block |
parallel.Jenkinsfile |
Parallel stages, failFast |
complex.Jenkinsfile |
Environment vars, parameters, triggers, when conditions, Docker agent |
| Layer | Technology | Version | Role |
|---|---|---|---|
| Extension host | TypeScript | 5.x | Type-safe extension code |
| Extension build | esbuild | 0.20 | Fast CJS bundle for Node.js |
| Webview UI | React | 18 | Component-based UI |
| Webview build | Vite | 5 | Fast ESM webview bundle |
| Node graph | @xyflow/react |
12 | Interactive canvas |
| State | Zustand + immer | 4.x | Immutable reactive store |
| Undo/redo | zundo | 2.x | Temporal middleware for Zustand |
| Layout | dagre | 0.8.5 | Directed-graph auto-layout |
| Unit tests | Vitest | 1.x | Fast test runner |
| E2E tests | @vscode/test-electron |
2.x | Real VS Code instance |
- Fork and clone the repository
- Create a feature branch:
git checkout -b feat/my-feature - Install dependencies:
npm install - Make your changes and add tests
- Ensure all tests pass:
npm run test:unit - Ensure the build is clean:
npm run build - Open a Pull Request
Apache 2.0 ┬® 2026 PlanesZwalker ÔÇö see LICENSE for details.