Skip to Content
CubyPlugin-Entwicklung

Plugin-Entwicklung

Diese Anleitung beschreibt Schritt für Schritt, wie ein eigenes Plugin für Cuby entwickelt wird. Für die Referenz aller Interfaces und Methoden siehe Plugin-System.

Paketstruktur

@processcube/mein-plugin.cuby/ ├── package.json # NPM Paket-Definition ├── index.js # Plugin-Hauptmodul (Lifecycle-Methoden) ├── config-component.js # Optional: React-Konfigurationskomponente └── detail-component.js # Optional: React-Detail-Komponente

package.json

{ "name": "@processcube/mein-plugin.cuby", "version": "1.0.0", "description": "Mein Cuby Plugin", "main": "index.js", "type": "module", "cuby": { "type": "npx", "plugin": "index.js", "url": "http://localhost:3000" } }

cuby-Feld

FeldBeschreibungStandard
typeProdukttyp: npx, bpmn oder flowAutomatische Erkennung
pluginPfad zum Plugin-Modulindex.js
urlURL zum Web-Interface (falls vorhanden)

Details zur Typ-Erkennung unter Plattform-Produkte.

Plugin-Modul (index.js)

// index.js // Route-Registrierung (läuft auf dem Main-Thread) export async function init(router, cuby, context) { // HTTP-Routen registrieren router.get('/status', () => Response.json({ running: true })); // Socket.IO-Events registrieren router.onSocket('custom:event', (data) => { console.log('Received:', data); }); } // Deployment (läuft im Worker-Prozess) export async function deploy(productDir, config, onProgress, cuby, context) { onProgress(50, 'Konfiguration wird angewendet...'); // Konfigurationsdatei schreiben const configPath = `${productDir}/config.json`; await Bun.write(configPath, JSON.stringify(config, null, 2)); // ProcessCube-Instanz registrieren await cuby.registerProcessCube({ engineUrl: config.engineUrl, lowCodeUrls: [config.lowCodeUrl], }); onProgress(100, 'Deployment abgeschlossen'); } // Start (läuft im Worker-Prozess) export async function start(productDir, cuby, context) { // Port reservieren const port = await cuby.reservePort(3000); console.log(`Plugin gestartet auf Port ${port}`); } // Stop (läuft im Worker-Prozess) export async function stop(cuby, context) { // Ressourcen freigeben await cuby.releasePort(3000); console.log('Plugin gestoppt'); } // Undeploy (läuft im Worker-Prozess) export async function undeploy(productDir, cuby, context) { // Artefakte entfernen console.log('Plugin entfernt'); } // Konfiguration zurückgeben export function getConfig() { return { engineUrl: 'http://localhost:8000' }; }

Methoden-Parameter

init(router, cuby, context)

Wird beim Laden des Plugins auf dem Main-Thread aufgerufen.

ParameterTypBeschreibung
routerobjectRouter-Objekt mit get, post, put, delete, onSocket, emitSocket
cubyobjectCuby-Kontext API
contextobjectPlugin-Kontext (instanceId, productDir, config)

deploy(productDir, config, onProgress, cuby, context)

Wird nach der Konfiguration im Worker-Prozess aufgerufen.

ParameterTypBeschreibung
productDirstringAbsoluter Pfad zum Plugin-Verzeichnis
configobjectKonfiguration vom Benutzer (aus ConfigComponent)
onProgressfunction(percent, message) — Fortschritt melden (0–100)
cubyobjectCuby-Kontext API
contextobjectPlugin-Kontext

start(productDir, cuby, context)

Wird aufgerufen wenn das Plugin gestartet werden soll (im Worker-Prozess).

stop(cuby, context)

Wird beim Beenden aufgerufen (im Worker-Prozess).

undeploy(productDir, cuby, context)

Wird bei der Deinstallation aufgerufen (im Worker-Prozess).

Worker-Prozesse verstehen

Lifecycle-Methoden (deploy, start, stop, undeploy) laufen nicht im Bun-Hauptprozess, sondern in separaten Node.js v22 Child-Prozessen:

  • Kommunikation: JSON-Lines über stdin/stdout
  • Cuby-Aufrufe: Werden als RPC an den Main-Thread weitergeleitet
  • Logs: Jede Methode schreibt eine eigene Log-Datei (deploy.log, start.log etc.)

Socket.IO-Events in Plugins

Plugins können über dedizierte Namespaces eigene Events registrieren:

export async function init(router, cuby, context) { // Event-Handler registrieren router.onSocket('my:event', (data) => { console.log('Client sendet:', data); }); // Event an alle Clients senden router.emitSocket('my:update', { status: 'ready' }); // Event an bestimmten Raum senden router.emitSocket('my:update', { status: 'ready' }, { room: 'admins' }); }

Der Socket.IO-Namespace für das Plugin ist: /plugins/<instanceId>

Secrets verwenden

Plugins können Secrets sicher speichern und lesen. Secrets sind auf die instanceId des Plugins gescoped:

export async function deploy(productDir, config, onProgress, cuby, context) { // Secret speichern await cuby.setSecret('db-password', config.dbPassword); // Secret lesen const password = await cuby.getSecret('db-password'); }

Konfigurations-Komponente

Die config-component.js wird als React-Komponente im Browser geladen:

function ConfigComponent({ config, onChange, product }) { const [engineUrl, setEngineUrl] = useState(config.engineUrl || 'http://localhost:8000'); useEffect(() => { onChange({ engineUrl }); }, [engineUrl]); return ( <div className="space-y-4"> <h3 className="text-lg font-semibold">Engine-Konfiguration</h3> <div> <label className="block text-sm font-medium mb-1">Engine URL</label> <input className="w-full px-3 py-2 border rounded-lg" value={engineUrl} onChange={(e) => setEngineUrl(e.target.value)} placeholder="http://localhost:8000" /> </div> </div> ); } export default ConfigComponent;

Wichtig:

  • ESM-Format (kein CommonJS)
  • React ist über Cuby-Shims verfügbar (gleiche Instanz wie Host)
  • Tailwind CSS Klassen sind verfügbar
  • Kein Bundling nötig

Veröffentlichung

  1. Plugin als npm-Paket zum ProcessCube® Marketplace veröffentlichen:
    npm publish --access public
  2. Das Paket wird automatisch im Marketplace verfügbar

Best Practices

  1. Fehlerbehandlung — Aussagekräftige Fehler in deploy() und start() werfen
  2. Loggingconsole.log() für wichtige Statusmeldungen verwenden (werden in Log-Dateien erfasst)
  3. Cleanupstop() implementieren wenn Ressourcen (Ports, Verbindungen) freigegeben werden müssen
  4. Konfiguration validieren — Eingaben in deploy() prüfen, bevor sie angewendet werden
  5. Idempotenzdeploy() muss mehrfach aufgerufen werden können (Updates)
  6. Ports reservierencuby.reservePort() statt fester Ports verwenden, um Kollisionen zu vermeiden
  7. Secrets — Passwörter und Tokens über cuby.setSecret() / cuby.getSecret() statt in Dateien

Weiterführend