release orchestration strengthening
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// StellaOpsPlugin.kt - JetBrains Plugin
|
||||
// Sprint: SPRINT_20260117_037_ReleaseOrchestrator_developer_experience
|
||||
// Task: TASK-037-07 - JetBrains plugin with tool window and annotators
|
||||
// Description: IntelliJ IDEA / JetBrains plugin for Stella Ops
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
package org.stellaops.intellij
|
||||
|
||||
import com.intellij.openapi.actionSystem.*
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.openapi.wm.ToolWindowFactory
|
||||
import com.intellij.ui.components.*
|
||||
import com.intellij.ui.content.ContentFactory
|
||||
import com.intellij.ui.treeStructure.Tree
|
||||
import javax.swing.*
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
|
||||
/**
|
||||
* Stella Ops Plugin for JetBrains IDEs
|
||||
*
|
||||
* Features:
|
||||
* - Tool window for releases and environments
|
||||
* - File annotations for stella.yaml
|
||||
* - Action menu integrations
|
||||
* - Status bar widget
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Tool Window Factory
|
||||
// ============================================================================
|
||||
|
||||
class StellaToolWindowFactory : ToolWindowFactory {
|
||||
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
||||
val stellaToolWindow = StellaToolWindow(project)
|
||||
val content = ContentFactory.getInstance().createContent(
|
||||
stellaToolWindow.content,
|
||||
"Releases",
|
||||
false
|
||||
)
|
||||
toolWindow.contentManager.addContent(content)
|
||||
}
|
||||
}
|
||||
|
||||
class StellaToolWindow(private val project: Project) {
|
||||
val content: JPanel = JPanel()
|
||||
|
||||
init {
|
||||
content.layout = BoxLayout(content, BoxLayout.Y_AXIS)
|
||||
|
||||
// Create tabbed pane
|
||||
val tabbedPane = JBTabbedPane()
|
||||
|
||||
// Releases tab
|
||||
tabbedPane.addTab("Releases", createReleasesPanel())
|
||||
|
||||
// Environments tab
|
||||
tabbedPane.addTab("Environments", createEnvironmentsPanel())
|
||||
|
||||
// Deployments tab
|
||||
tabbedPane.addTab("Deployments", createDeploymentsPanel())
|
||||
|
||||
content.add(tabbedPane)
|
||||
}
|
||||
|
||||
private fun createReleasesPanel(): JComponent {
|
||||
val root = DefaultMutableTreeNode("Services")
|
||||
|
||||
// Sample data
|
||||
val apiGateway = DefaultMutableTreeNode("api-gateway")
|
||||
apiGateway.add(DefaultMutableTreeNode("v2.3.1 (Production)"))
|
||||
apiGateway.add(DefaultMutableTreeNode("v2.4.0 (Staging)"))
|
||||
apiGateway.add(DefaultMutableTreeNode("v2.5.0-rc1 (Dev)"))
|
||||
|
||||
val userService = DefaultMutableTreeNode("user-service")
|
||||
userService.add(DefaultMutableTreeNode("v1.8.0 (Production)"))
|
||||
userService.add(DefaultMutableTreeNode("v1.9.0 (Staging)"))
|
||||
|
||||
root.add(apiGateway)
|
||||
root.add(userService)
|
||||
|
||||
val tree = Tree(DefaultTreeModel(root))
|
||||
tree.isRootVisible = false
|
||||
|
||||
val panel = JPanel()
|
||||
panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS)
|
||||
|
||||
// Toolbar
|
||||
val toolbar = JPanel()
|
||||
toolbar.add(JButton("Refresh").apply {
|
||||
addActionListener { refreshReleases() }
|
||||
})
|
||||
toolbar.add(JButton("Create Release").apply {
|
||||
addActionListener { showCreateReleaseDialog() }
|
||||
})
|
||||
|
||||
panel.add(toolbar)
|
||||
panel.add(JBScrollPane(tree))
|
||||
|
||||
return panel
|
||||
}
|
||||
|
||||
private fun createEnvironmentsPanel(): JComponent {
|
||||
val panel = JPanel()
|
||||
panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS)
|
||||
|
||||
val envList = listOf(
|
||||
EnvironmentInfo("Production", "prod", "Healthy", "3 services"),
|
||||
EnvironmentInfo("Staging", "staging", "Healthy", "3 services"),
|
||||
EnvironmentInfo("Development", "dev", "Healthy", "3 services")
|
||||
)
|
||||
|
||||
for (env in envList) {
|
||||
val envPanel = JPanel()
|
||||
envPanel.layout = BoxLayout(envPanel, BoxLayout.X_AXIS)
|
||||
envPanel.border = BorderFactory.createEmptyBorder(5, 10, 5, 10)
|
||||
|
||||
val statusIcon = when (env.status) {
|
||||
"Healthy" -> "✓"
|
||||
"Degraded" -> "⚠"
|
||||
else -> "✗"
|
||||
}
|
||||
|
||||
envPanel.add(JBLabel("$statusIcon ${env.name}"))
|
||||
envPanel.add(Box.createHorizontalGlue())
|
||||
envPanel.add(JBLabel(env.services))
|
||||
envPanel.add(JButton("View").apply {
|
||||
addActionListener { openEnvironmentDetails(env.id) }
|
||||
})
|
||||
|
||||
panel.add(envPanel)
|
||||
}
|
||||
|
||||
return JBScrollPane(panel)
|
||||
}
|
||||
|
||||
private fun createDeploymentsPanel(): JComponent {
|
||||
val panel = JPanel()
|
||||
panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS)
|
||||
|
||||
val headers = arrayOf("ID", "Service", "Version", "Environment", "Status")
|
||||
val data = arrayOf(
|
||||
arrayOf("dep-001", "api-gateway", "v2.3.1", "Production", "Completed"),
|
||||
arrayOf("dep-002", "user-service", "v1.9.0", "Staging", "In Progress"),
|
||||
arrayOf("dep-003", "order-service", "v3.0.0", "Development", "Pending")
|
||||
)
|
||||
|
||||
val table = JBTable(data, headers)
|
||||
panel.add(JBScrollPane(table))
|
||||
|
||||
return panel
|
||||
}
|
||||
|
||||
private fun refreshReleases() {
|
||||
// Refresh releases from API
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
// Update tree
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCreateReleaseDialog() {
|
||||
val dialog = CreateReleaseDialog(project)
|
||||
if (dialog.showAndGet()) {
|
||||
// Create release via CLI
|
||||
val service = dialog.serviceName
|
||||
val version = dialog.version
|
||||
executeCliCommand("stella release create $service $version")
|
||||
}
|
||||
}
|
||||
|
||||
private fun openEnvironmentDetails(envId: String) {
|
||||
// Open browser to environment dashboard
|
||||
java.awt.Desktop.getDesktop().browse(
|
||||
java.net.URI("http://localhost:5000/environments/$envId")
|
||||
)
|
||||
}
|
||||
|
||||
private fun executeCliCommand(command: String) {
|
||||
// Execute via terminal
|
||||
val terminal = com.intellij.terminal.JBTerminalWidget.installByDefault(project, null)
|
||||
// terminal.sendCommand(command)
|
||||
}
|
||||
|
||||
data class EnvironmentInfo(
|
||||
val name: String,
|
||||
val id: String,
|
||||
val status: String,
|
||||
val services: String
|
||||
)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Create Release Dialog
|
||||
// ============================================================================
|
||||
|
||||
class CreateReleaseDialog(project: Project) : com.intellij.openapi.ui.DialogWrapper(project) {
|
||||
private val serviceField = JBTextField()
|
||||
private val versionField = JBTextField()
|
||||
private val notesField = JBTextArea()
|
||||
|
||||
val serviceName: String get() = serviceField.text
|
||||
val version: String get() = versionField.text
|
||||
val notes: String get() = notesField.text
|
||||
|
||||
init {
|
||||
title = "Create Release"
|
||||
init()
|
||||
}
|
||||
|
||||
override fun createCenterPanel(): JComponent {
|
||||
val panel = JPanel()
|
||||
panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS)
|
||||
|
||||
panel.add(JBLabel("Service Name:"))
|
||||
panel.add(serviceField)
|
||||
|
||||
panel.add(Box.createVerticalStrut(10))
|
||||
|
||||
panel.add(JBLabel("Version:"))
|
||||
panel.add(versionField)
|
||||
|
||||
panel.add(Box.createVerticalStrut(10))
|
||||
|
||||
panel.add(JBLabel("Release Notes:"))
|
||||
panel.add(JBScrollPane(notesField).apply {
|
||||
preferredSize = java.awt.Dimension(300, 100)
|
||||
})
|
||||
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Actions
|
||||
// ============================================================================
|
||||
|
||||
class CreateReleaseAction : AnAction("Create Release", "Create a new release", null) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.project ?: return
|
||||
val dialog = CreateReleaseDialog(project)
|
||||
if (dialog.showAndGet()) {
|
||||
// Execute create release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PromoteReleaseAction : AnAction("Promote Release", "Promote a release to another environment", null) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.project ?: return
|
||||
// Show promote dialog
|
||||
}
|
||||
}
|
||||
|
||||
class ValidateConfigAction : AnAction("Validate Configuration", "Validate stella.yaml configuration", null) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.project ?: return
|
||||
// Execute validation
|
||||
}
|
||||
}
|
||||
|
||||
class OpenDashboardAction : AnAction("Open Dashboard", "Open Stella Ops dashboard in browser", null) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
java.awt.Desktop.getDesktop().browse(
|
||||
java.net.URI("http://localhost:5000/dashboard")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Annotator for stella.yaml
|
||||
// ============================================================================
|
||||
|
||||
class StellaYamlAnnotator : com.intellij.lang.annotation.Annotator {
|
||||
override fun annotate(element: com.intellij.psi.PsiElement, holder: com.intellij.lang.annotation.AnnotationHolder) {
|
||||
// Skip if not a YAML file
|
||||
val file = element.containingFile ?: return
|
||||
if (!file.name.endsWith("stella.yaml")) return
|
||||
|
||||
val text = element.text
|
||||
|
||||
// Annotate version references
|
||||
if (text.startsWith("version:")) {
|
||||
holder.newAnnotation(
|
||||
com.intellij.lang.annotation.HighlightSeverity.INFORMATION,
|
||||
"Stella version declaration"
|
||||
)
|
||||
.range(element.textRange)
|
||||
.create()
|
||||
}
|
||||
|
||||
// Annotate environment references
|
||||
if (text.matches(Regex("environment:\\s*\\w+"))) {
|
||||
holder.newAnnotation(
|
||||
com.intellij.lang.annotation.HighlightSeverity.INFORMATION,
|
||||
"Target environment"
|
||||
)
|
||||
.range(element.textRange)
|
||||
.create()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Status Bar Widget
|
||||
// ============================================================================
|
||||
|
||||
class StellaStatusBarWidgetFactory : com.intellij.openapi.wm.StatusBarWidgetFactory {
|
||||
override fun getId(): String = "StellaOpsStatus"
|
||||
override fun getDisplayName(): String = "Stella Ops"
|
||||
override fun isAvailable(project: Project): Boolean = true
|
||||
override fun createWidget(project: Project): com.intellij.openapi.wm.StatusBarWidget {
|
||||
return StellaStatusBarWidget()
|
||||
}
|
||||
override fun disposeWidget(widget: com.intellij.openapi.wm.StatusBarWidget) {
|
||||
// Cleanup
|
||||
}
|
||||
override fun canBeEnabledOn(statusBar: com.intellij.openapi.wm.StatusBar): Boolean = true
|
||||
}
|
||||
|
||||
class StellaStatusBarWidget : com.intellij.openapi.wm.StatusBarWidget,
|
||||
com.intellij.openapi.wm.StatusBarWidget.TextPresentation {
|
||||
|
||||
override fun ID(): String = "StellaOpsStatus"
|
||||
override fun getPresentation(): com.intellij.openapi.wm.StatusBarWidget.WidgetPresentation = this
|
||||
override fun install(statusBar: com.intellij.openapi.wm.StatusBar) {}
|
||||
override fun dispose() {}
|
||||
|
||||
override fun getText(): String = "🚀 Stella Ops"
|
||||
override fun getAlignment(): Float = 0f
|
||||
override fun getTooltipText(): String = "Stella Ops - Click to open dashboard"
|
||||
|
||||
override fun getClickConsumer(): com.intellij.util.Consumer<java.awt.event.MouseEvent>? {
|
||||
return com.intellij.util.Consumer {
|
||||
java.awt.Desktop.getDesktop().browse(
|
||||
java.net.URI("http://localhost:5000/dashboard")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
146
src/Extensions/vscode-stella-ops/package.json
Normal file
146
src/Extensions/vscode-stella-ops/package.json
Normal file
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"name": "stella-ops",
|
||||
"displayName": "Stella Ops",
|
||||
"description": "VS Code extension for Stella Ops release control plane",
|
||||
"version": "1.0.0",
|
||||
"publisher": "stella-ops",
|
||||
"engines": {
|
||||
"vscode": "^1.85.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other",
|
||||
"SCM Providers"
|
||||
],
|
||||
"keywords": [
|
||||
"release",
|
||||
"deployment",
|
||||
"devops",
|
||||
"ci-cd",
|
||||
"promotion"
|
||||
],
|
||||
"activationEvents": [
|
||||
"workspaceContains:**/stella.yaml"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "stella.createRelease",
|
||||
"title": "Create Release",
|
||||
"category": "Stella"
|
||||
},
|
||||
{
|
||||
"command": "stella.promote",
|
||||
"title": "Promote Release",
|
||||
"category": "Stella"
|
||||
},
|
||||
{
|
||||
"command": "stella.viewRelease",
|
||||
"title": "View Release Details",
|
||||
"category": "Stella"
|
||||
},
|
||||
{
|
||||
"command": "stella.viewDeployment",
|
||||
"title": "View Deployment",
|
||||
"category": "Stella"
|
||||
},
|
||||
{
|
||||
"command": "stella.refreshReleases",
|
||||
"title": "Refresh Releases",
|
||||
"category": "Stella",
|
||||
"icon": "$(refresh)"
|
||||
},
|
||||
{
|
||||
"command": "stella.validateConfig",
|
||||
"title": "Validate Configuration",
|
||||
"category": "Stella"
|
||||
},
|
||||
{
|
||||
"command": "stella.openDashboard",
|
||||
"title": "Open Dashboard",
|
||||
"category": "Stella"
|
||||
},
|
||||
{
|
||||
"command": "stella.login",
|
||||
"title": "Login",
|
||||
"category": "Stella"
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "stella-ops",
|
||||
"title": "Stella Ops",
|
||||
"icon": "resources/stella-icon.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"stella-ops": [
|
||||
{
|
||||
"id": "stellaReleases",
|
||||
"name": "Releases",
|
||||
"icon": "resources/release-icon.svg"
|
||||
},
|
||||
{
|
||||
"id": "stellaEnvironments",
|
||||
"name": "Environments",
|
||||
"icon": "resources/environment-icon.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"menus": {
|
||||
"view/title": [
|
||||
{
|
||||
"command": "stella.refreshReleases",
|
||||
"when": "view == stellaReleases",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "stella.promote",
|
||||
"when": "viewItem == release",
|
||||
"group": "inline"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "Stella Ops",
|
||||
"properties": {
|
||||
"stella.serverUrl": {
|
||||
"type": "string",
|
||||
"default": "https://localhost:5001",
|
||||
"description": "Stella Ops server URL"
|
||||
},
|
||||
"stella.autoValidate": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Automatically validate stella.yaml on save"
|
||||
}
|
||||
}
|
||||
},
|
||||
"languages": [
|
||||
{
|
||||
"id": "stella-yaml",
|
||||
"extensions": [".stella.yaml"],
|
||||
"aliases": ["Stella Configuration"],
|
||||
"configuration": "./language-configuration.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "tsc -p ./",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"lint": "eslint src --ext ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.85.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"eslint": "^8.0.0"
|
||||
}
|
||||
}
|
||||
367
src/Extensions/vscode-stella-ops/src/extension.ts
Normal file
367
src/Extensions/vscode-stella-ops/src/extension.ts
Normal file
@@ -0,0 +1,367 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// StellaOpsExtension - VS Code Extension
|
||||
// Sprint: SPRINT_20260117_037_ReleaseOrchestrator_developer_experience
|
||||
// Task: TASK-037-06 - VS Code Extension with tree view, commands, and code lens
|
||||
// Description: VS Code extension package definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* VS Code Extension for Stella Ops
|
||||
*
|
||||
* Features:
|
||||
* - Tree view for releases, environments, and deployments
|
||||
* - Code lens for stella.yaml configuration files
|
||||
* - Commands for release management
|
||||
* - Status bar integration
|
||||
* - IntelliSense for configuration files
|
||||
*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
// ============================================================================
|
||||
// Extension Activation
|
||||
// ============================================================================
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
console.log('Stella Ops extension is now active');
|
||||
|
||||
// Register providers
|
||||
const releaseTreeProvider = new ReleaseTreeProvider();
|
||||
const environmentTreeProvider = new EnvironmentTreeProvider();
|
||||
const stellaCodeLensProvider = new StellaCodeLensProvider();
|
||||
|
||||
// Tree views
|
||||
vscode.window.registerTreeDataProvider('stellaReleases', releaseTreeProvider);
|
||||
vscode.window.registerTreeDataProvider('stellaEnvironments', environmentTreeProvider);
|
||||
|
||||
// Code lens for stella.yaml files
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCodeLensProvider(
|
||||
{ pattern: '**/stella.yaml' },
|
||||
stellaCodeLensProvider
|
||||
)
|
||||
);
|
||||
|
||||
// Register commands
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('stella.createRelease', createReleaseCommand),
|
||||
vscode.commands.registerCommand('stella.promote', promoteCommand),
|
||||
vscode.commands.registerCommand('stella.viewRelease', viewReleaseCommand),
|
||||
vscode.commands.registerCommand('stella.viewDeployment', viewDeploymentCommand),
|
||||
vscode.commands.registerCommand('stella.refreshReleases', () => releaseTreeProvider.refresh()),
|
||||
vscode.commands.registerCommand('stella.validateConfig', validateConfigCommand),
|
||||
vscode.commands.registerCommand('stella.openDashboard', openDashboardCommand),
|
||||
vscode.commands.registerCommand('stella.login', loginCommand)
|
||||
);
|
||||
|
||||
// Status bar
|
||||
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
|
||||
statusBarItem.text = '$(rocket) Stella Ops';
|
||||
statusBarItem.command = 'stella.openDashboard';
|
||||
statusBarItem.show();
|
||||
context.subscriptions.push(statusBarItem);
|
||||
|
||||
// File watcher for stella.yaml changes
|
||||
const watcher = vscode.workspace.createFileSystemWatcher('**/stella.yaml');
|
||||
watcher.onDidChange(() => validateConfigCommand());
|
||||
context.subscriptions.push(watcher);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
|
||||
// ============================================================================
|
||||
// Tree Data Providers
|
||||
// ============================================================================
|
||||
|
||||
class ReleaseTreeProvider implements vscode.TreeDataProvider<ReleaseTreeItem> {
|
||||
private _onDidChangeTreeData = new vscode.EventEmitter<ReleaseTreeItem | undefined>();
|
||||
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
|
||||
|
||||
refresh(): void {
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
|
||||
getTreeItem(element: ReleaseTreeItem): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
async getChildren(element?: ReleaseTreeItem): Promise<ReleaseTreeItem[]> {
|
||||
if (!element) {
|
||||
// Root level: show services
|
||||
return [
|
||||
new ReleaseTreeItem('api-gateway', 'service', vscode.TreeItemCollapsibleState.Collapsed),
|
||||
new ReleaseTreeItem('user-service', 'service', vscode.TreeItemCollapsibleState.Collapsed),
|
||||
new ReleaseTreeItem('order-service', 'service', vscode.TreeItemCollapsibleState.Collapsed)
|
||||
];
|
||||
}
|
||||
|
||||
if (element.itemType === 'service') {
|
||||
// Service level: show releases
|
||||
return [
|
||||
new ReleaseTreeItem('v2.3.1 (Production)', 'release', vscode.TreeItemCollapsibleState.None, {
|
||||
status: 'deployed',
|
||||
environment: 'prod'
|
||||
}),
|
||||
new ReleaseTreeItem('v2.4.0 (Staging)', 'release', vscode.TreeItemCollapsibleState.None, {
|
||||
status: 'deployed',
|
||||
environment: 'staging'
|
||||
}),
|
||||
new ReleaseTreeItem('v2.5.0-rc1 (Dev)', 'release', vscode.TreeItemCollapsibleState.None, {
|
||||
status: 'deployed',
|
||||
environment: 'dev'
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class ReleaseTreeItem extends vscode.TreeItem {
|
||||
constructor(
|
||||
public readonly label: string,
|
||||
public readonly itemType: 'service' | 'release',
|
||||
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
|
||||
public readonly metadata?: { status?: string; environment?: string }
|
||||
) {
|
||||
super(label, collapsibleState);
|
||||
|
||||
if (itemType === 'service') {
|
||||
this.iconPath = new vscode.ThemeIcon('package');
|
||||
this.contextValue = 'service';
|
||||
} else {
|
||||
this.iconPath = metadata?.status === 'deployed'
|
||||
? new vscode.ThemeIcon('check', new vscode.ThemeColor('testing.iconPassed'))
|
||||
: new vscode.ThemeIcon('circle-outline');
|
||||
this.contextValue = 'release';
|
||||
this.command = {
|
||||
command: 'stella.viewRelease',
|
||||
title: 'View Release',
|
||||
arguments: [this]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EnvironmentTreeProvider implements vscode.TreeDataProvider<EnvironmentTreeItem> {
|
||||
private _onDidChangeTreeData = new vscode.EventEmitter<EnvironmentTreeItem | undefined>();
|
||||
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
|
||||
|
||||
getTreeItem(element: EnvironmentTreeItem): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
async getChildren(element?: EnvironmentTreeItem): Promise<EnvironmentTreeItem[]> {
|
||||
if (!element) {
|
||||
return [
|
||||
new EnvironmentTreeItem('Production', 'prod', 'healthy'),
|
||||
new EnvironmentTreeItem('Staging', 'staging', 'healthy'),
|
||||
new EnvironmentTreeItem('Development', 'dev', 'healthy')
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class EnvironmentTreeItem extends vscode.TreeItem {
|
||||
constructor(
|
||||
public readonly label: string,
|
||||
public readonly envId: string,
|
||||
public readonly health: 'healthy' | 'degraded' | 'unhealthy'
|
||||
) {
|
||||
super(label, vscode.TreeItemCollapsibleState.None);
|
||||
|
||||
this.iconPath = health === 'healthy'
|
||||
? new vscode.ThemeIcon('check', new vscode.ThemeColor('testing.iconPassed'))
|
||||
: health === 'degraded'
|
||||
? new vscode.ThemeIcon('warning', new vscode.ThemeColor('editorWarning.foreground'))
|
||||
: new vscode.ThemeIcon('error', new vscode.ThemeColor('editorError.foreground'));
|
||||
|
||||
this.description = health;
|
||||
this.contextValue = 'environment';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Code Lens Provider
|
||||
// ============================================================================
|
||||
|
||||
class StellaCodeLensProvider implements vscode.CodeLensProvider {
|
||||
provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] {
|
||||
const codeLenses: vscode.CodeLens[] = [];
|
||||
const text = document.getText();
|
||||
const lines = text.split('\n');
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
// Add code lens for version declarations
|
||||
if (line.match(/^\s*version:/)) {
|
||||
const range = new vscode.Range(index, 0, index, line.length);
|
||||
codeLenses.push(
|
||||
new vscode.CodeLens(range, {
|
||||
title: '$(rocket) Create Release',
|
||||
command: 'stella.createRelease'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Add code lens for environment references
|
||||
if (line.match(/^\s*environment:/)) {
|
||||
const range = new vscode.Range(index, 0, index, line.length);
|
||||
codeLenses.push(
|
||||
new vscode.CodeLens(range, {
|
||||
title: '$(server-environment) View Environment',
|
||||
command: 'stella.openDashboard'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Add code lens for policy references
|
||||
if (line.match(/^\s*policies:/)) {
|
||||
const range = new vscode.Range(index, 0, index, line.length);
|
||||
codeLenses.push(
|
||||
new vscode.CodeLens(range, {
|
||||
title: '$(shield) Validate Policies',
|
||||
command: 'stella.validateConfig'
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return codeLenses;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Commands
|
||||
// ============================================================================
|
||||
|
||||
async function createReleaseCommand() {
|
||||
const service = await vscode.window.showInputBox({
|
||||
prompt: 'Service name',
|
||||
placeHolder: 'e.g., api-gateway'
|
||||
});
|
||||
|
||||
if (!service) return;
|
||||
|
||||
const version = await vscode.window.showInputBox({
|
||||
prompt: 'Version',
|
||||
placeHolder: 'e.g., v1.2.3'
|
||||
});
|
||||
|
||||
if (!version) return;
|
||||
|
||||
const notes = await vscode.window.showInputBox({
|
||||
prompt: 'Release notes (optional)',
|
||||
placeHolder: 'Description of changes'
|
||||
});
|
||||
|
||||
// Execute CLI command
|
||||
const terminal = vscode.window.createTerminal('Stella Ops');
|
||||
terminal.sendText(`stella release create ${service} ${version}${notes ? ` --notes "${notes}"` : ''}`);
|
||||
terminal.show();
|
||||
}
|
||||
|
||||
async function promoteCommand() {
|
||||
const release = await vscode.window.showInputBox({
|
||||
prompt: 'Release ID',
|
||||
placeHolder: 'e.g., rel-abc123'
|
||||
});
|
||||
|
||||
if (!release) return;
|
||||
|
||||
const target = await vscode.window.showQuickPick(
|
||||
['dev', 'staging', 'production'],
|
||||
{ placeHolder: 'Select target environment' }
|
||||
);
|
||||
|
||||
if (!target) return;
|
||||
|
||||
const terminal = vscode.window.createTerminal('Stella Ops');
|
||||
terminal.sendText(`stella promote start ${release} ${target}`);
|
||||
terminal.show();
|
||||
}
|
||||
|
||||
async function viewReleaseCommand(item?: ReleaseTreeItem) {
|
||||
// Open release details in a webview
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
'stellaRelease',
|
||||
`Release: ${item?.label || 'Details'}`,
|
||||
vscode.ViewColumn.One,
|
||||
{ enableScripts: true }
|
||||
);
|
||||
|
||||
panel.webview.html = getReleaseWebviewContent(item?.label || 'Unknown');
|
||||
}
|
||||
|
||||
async function viewDeploymentCommand() {
|
||||
const deploymentId = await vscode.window.showInputBox({
|
||||
prompt: 'Deployment ID',
|
||||
placeHolder: 'e.g., dep-abc123'
|
||||
});
|
||||
|
||||
if (!deploymentId) return;
|
||||
|
||||
const terminal = vscode.window.createTerminal('Stella Ops');
|
||||
terminal.sendText(`stella deploy status ${deploymentId} --watch`);
|
||||
terminal.show();
|
||||
}
|
||||
|
||||
async function validateConfigCommand() {
|
||||
const terminal = vscode.window.createTerminal('Stella Ops');
|
||||
terminal.sendText('stella config validate');
|
||||
terminal.show();
|
||||
}
|
||||
|
||||
async function openDashboardCommand() {
|
||||
vscode.env.openExternal(vscode.Uri.parse('http://localhost:5000/dashboard'));
|
||||
}
|
||||
|
||||
async function loginCommand() {
|
||||
const server = await vscode.window.showInputBox({
|
||||
prompt: 'Stella server URL',
|
||||
placeHolder: 'https://stella.example.com',
|
||||
value: 'https://localhost:5001'
|
||||
});
|
||||
|
||||
if (!server) return;
|
||||
|
||||
const terminal = vscode.window.createTerminal('Stella Ops');
|
||||
terminal.sendText(`stella auth login ${server} --interactive`);
|
||||
terminal.show();
|
||||
}
|
||||
|
||||
function getReleaseWebviewContent(releaseName: string): string {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Release Details</title>
|
||||
<style>
|
||||
body { font-family: var(--vscode-font-family); padding: 20px; }
|
||||
h1 { color: var(--vscode-editor-foreground); }
|
||||
.section { margin: 20px 0; }
|
||||
.label { color: var(--vscode-descriptionForeground); }
|
||||
.value { color: var(--vscode-editor-foreground); font-weight: bold; }
|
||||
.status-deployed { color: var(--vscode-testing-iconPassed); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Release: ${releaseName}</h1>
|
||||
<div class="section">
|
||||
<span class="label">Status: </span>
|
||||
<span class="value status-deployed">Deployed</span>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="label">Environment: </span>
|
||||
<span class="value">Production</span>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="label">Deployed At: </span>
|
||||
<span class="value">2026-01-17 12:00 UTC</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user