consolidation of some of the modules, localization fixes, product advisories work, qa work

This commit is contained in:
master
2026-03-05 03:54:22 +02:00
parent 7bafcc3eef
commit 8e1cb9448d
3878 changed files with 72600 additions and 46861 deletions

View File

@@ -27,6 +27,10 @@ src/Integrations/
│ ├── StellaOps.Integrations.Plugin.Gcr/
│ ├── StellaOps.Integrations.Plugin.Acr/
│ └── StellaOps.Integrations.Plugin.InMemory/ # Testing / dev
├── __Extensions/ # Non-.NET IDE plugins (Sprint 214)
│ ├── AGENTS.md # Extension-specific agent instructions
│ ├── vscode-stella-ops/ # TypeScript, npm
│ └── jetbrains-stella-ops/ # Kotlin, Gradle
└── __Tests/
└── StellaOps.Integrations.Tests/
```

View File

@@ -0,0 +1,79 @@
# IDE Extensions -- Agent Instructions
## Module Identity
**Module:** Integrations / __Extensions
**Purpose:** Developer-facing IDE plugins (VS Code + JetBrains) that consume the Orchestrator and Router APIs.
**Deployable:** None (distributed as IDE marketplace packages, not Docker services).
---
## Important: Non-.NET Projects
These are **not** .NET projects. They do not have `.csproj`, `.sln`, or `Dockerfile` files.
| Plugin | Technology | Build Tool | Entry Point |
| --- | --- | --- | --- |
| `vscode-stella-ops/` | TypeScript | npm (`npm run compile`) | `src/extension.ts` |
| `jetbrains-stella-ops/` | Kotlin | Gradle (`./gradlew build`) | `src/main/kotlin/org/stellaops/intellij/StellaOpsPlugin.kt` |
---
## Directory Layout
```
src/Integrations/__Extensions/
+-- AGENTS.md # This file
+-- vscode-stella-ops/
| +-- package.json # Extension manifest
| +-- src/
| +-- extension.ts # VS Code extension entry point
+-- jetbrains-stella-ops/
+-- src/main/kotlin/
+-- org/stellaops/intellij/
+-- StellaOpsPlugin.kt # JetBrains plugin entry point
```
---
## Roles & Responsibilities
| Role | Expectations |
| --- | --- |
| Frontend/Extension Engineer | Implement IDE features, manage extension manifests, ensure offline-tolerant behavior |
| QA Engineer | Verify extension builds (npm/gradle), test in IDE environments |
| PM/Architect | Coordinate API surface consumed by extensions with Orchestrator team |
---
## Constraints
1. **No business logic:** Extensions are thin clients; all state and decisions reside in backend services.
2. **No secrets in code:** OAuth tokens are stored in IDE secure credential stores only.
3. **TLS enforcement:** All HTTP communication uses HTTPS.
4. **Offline-tolerant:** Extensions must degrade gracefully when the backend is unreachable.
5. **No .NET coupling:** These projects must not introduce .NET dependencies or be added to any `.sln` or `.csproj` files.
---
## API Surface Consumed
- `GET /api/v1/releases/*` (Orchestrator)
- `GET /api/v1/environments/*` (Orchestrator)
- `POST /api/v1/promotions/*` (Orchestrator)
- `POST /oauth/token` (Authority)
---
## Build Notes
- **VS Code:** Requires Node.js and npm. Build with `npm run compile`. Package with `vsce package`.
- **JetBrains:** Requires JDK and Gradle. Build with `./gradlew build`. Package with `./gradlew buildPlugin`.
- Neither build is part of the .NET CI pipeline. Separate CI workflows would be needed if automated builds are required.
---
## Required Reading
- `docs/modules/integrations/architecture.md` (includes IDE Extensions section)
- `src/Integrations/AGENTS.md` (parent module instructions)

View File

@@ -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")
)
}
}
}

View 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"
}
}

View 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>
`;
}