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-Komponentepackage.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
| Feld | Beschreibung | Standard |
|---|---|---|
type | Produkttyp: npx, bpmn oder flow | Automatische Erkennung |
plugin | Pfad zum Plugin-Modul | index.js |
url | URL 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.
| Parameter | Typ | Beschreibung |
|---|---|---|
router | object | Router-Objekt mit get, post, put, delete, onSocket, emitSocket |
cuby | object | Cuby-Kontext API |
context | object | Plugin-Kontext (instanceId, productDir, config) |
deploy(productDir, config, onProgress, cuby, context)
Wird nach der Konfiguration im Worker-Prozess aufgerufen.
| Parameter | Typ | Beschreibung |
|---|---|---|
productDir | string | Absoluter Pfad zum Plugin-Verzeichnis |
config | object | Konfiguration vom Benutzer (aus ConfigComponent) |
onProgress | function | (percent, message) — Fortschritt melden (0–100) |
cuby | object | Cuby-Kontext API |
context | object | Plugin-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.logetc.)
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
- Plugin als npm-Paket zum ProcessCube® Marketplace veröffentlichen:
npm publish --access public - Das Paket wird automatisch im Marketplace verfügbar
Best Practices
- Fehlerbehandlung — Aussagekräftige Fehler in
deploy()undstart()werfen - Logging —
console.log()für wichtige Statusmeldungen verwenden (werden in Log-Dateien erfasst) - Cleanup —
stop()implementieren wenn Ressourcen (Ports, Verbindungen) freigegeben werden müssen - Konfiguration validieren — Eingaben in
deploy()prüfen, bevor sie angewendet werden - Idempotenz —
deploy()muss mehrfach aufgerufen werden können (Updates) - Ports reservieren —
cuby.reservePort()statt fester Ports verwenden, um Kollisionen zu vermeiden - Secrets — Passwörter und Tokens über
cuby.setSecret()/cuby.getSecret()statt in Dateien
Weiterführend
- Plugin-System — Vollständige Referenz aller Interfaces und Methoden
- Plattform-Produkte — Produkttypen und Abhängigkeiten
- App SDK — SDK für ProcessCube®-Anwendungen mit Next.js