mirror of
https://github.com/hanzo-fi/payments.git
synced 2026-10-30 20:31:20 +00:00
* wip: connector workbench * wip: improve workbench * feat(workbench): improve UI with sidebar, routing, and syntax highlighting - Replace connector dropdown with Slack-style sidebar for connector selection - Add URL-based routing with react-router-dom for browser navigation support - Add JSON syntax highlighting with react-syntax-highlighter - Rework State tab layout to list/detail view with filtering - Update terminology from "create" to "instantiate" for connectors - Fix duplicate selection bug in lists (ID generation collision) - Unify data table row heights across tabs - Increase code block font sizes for readability - Hide navigation tabs on homepage when no connector selected - Make CW logo clickable to return to home view Co-authored-by: Cursor <[email protected]> * fix(workbench): use textarea for PEM/certificate config fields Fields containing 'privateKey', 'certificate', or 'pem' in their name now use a textarea instead of single-line input, preserving newlines in PEM content that would otherwise be stripped. Co-authored-by: Cursor <[email protected]> * fix(workbench): address PR review issues - Fix data race on idCounter using atomic.Int64 - Fix path traversal vulnerability in introspect.go - Fix pointer comparison for DefaultAsset in baseline.go Co-authored-by: Cursor <[email protected]> * feat(workbench): add generic connector server for integration testing Expose the generic connector REST API from the workbench so staging services can install a "generic connector" pointing to the local workbench, enabling end-to-end integration testing. - Add /generic/* routes (accounts, balances, beneficiaries, transactions) - Return cached data from storage instead of making fresh API calls - Add connector selection and optional API key authentication - Add UI panel in Debug tab for configuration Co-authored-by: Cursor <[email protected]> * fix(workbench): use internal registry to avoid pkg dependency Change imports from pkg/registry to internal/connectors/plugins/registry so the workbench can be merged before the pkg refactoring. The internal registry will automatically delegate to pkg/registry once that refactor lands. Also adds light/dark mode toggle to the UI with system preference detection. Co-authored-by: Cursor <[email protected]> * fix(workbench): address PR review feedback - Add ctx.Done() checks in fetch loops to exit early on client disconnect - Refactor generic server to get connector directly instead of via context, providing clearer data flow as suggested in review Co-authored-by: Cursor <[email protected]> * fix(workbench): include built UI files for embed directive The //go:embed ui/dist/* directive requires files to exist at build time. Add exception to .gitignore and commit the built UI distribution files. Co-authored-by: Cursor <[email protected]> * fix(workbench): address golangci-lint errors - Remove unused globalDebugTransport variable - Remove unused parseIntParam function - Fix printf format for models.TaskType (use %v instead of %s) - Add error return value handling for various function calls Co-authored-by: Cursor <[email protected]> * fix: remove .vite cache folder from git Co-authored-by: Cursor <[email protected]> * fix(workbench): fix remaining errcheck issue Co-authored-by: Cursor <[email protected]> * fix(workbench): fix all remaining lint issues - Add error handling for Body.Close() calls - Remove unnecessary fmt.Sprintf wrapper - Simplify hex character check condition Co-authored-by: Cursor <[email protected]> * fix(workbench): fix errcheck in cmd/workbench.go Co-authored-by: Cursor <[email protected]> * chore: replace ui dist build artifacts with placeholder The //go:embed directive needs at least one file to exist. Instead of committing the entire built UI, commit only a .gitkeep placeholder. The server has fallback code that displays instructions when the full UI isn't available. Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor <[email protected]>
193 lines
5.3 KiB
Go
193 lines
5.3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/formancehq/go-libs/v3/logging"
|
|
"github.com/formancehq/payments/internal/connectors/plugins/registry"
|
|
"github.com/formancehq/payments/tools/workbench"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
workbenchProviderFlag = "provider"
|
|
workbenchConfigFlag = "config"
|
|
workbenchConfigFileFlag = "config-file"
|
|
workbenchListenFlag = "listen"
|
|
workbenchPageSizeFlag = "page-size"
|
|
)
|
|
|
|
func newWorkbench() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "workbench",
|
|
Short: "Run the connector workbench for local development",
|
|
Long: `The connector workbench provides a lightweight development environment
|
|
for building and testing payment connectors without requiring Temporal,
|
|
PostgreSQL, or other heavy infrastructure.
|
|
|
|
Features:
|
|
- In-memory storage (no database required)
|
|
- Multi-connector support - create multiple instances from the UI
|
|
- Step-by-step execution of connector operations
|
|
- HTTP API for triggering operations
|
|
- Web UI for debugging and inspection
|
|
- No Docker required
|
|
|
|
Examples:
|
|
# Run workbench without any connector (create from UI)
|
|
payments workbench
|
|
|
|
# Run workbench with a pre-configured connector
|
|
payments workbench --provider=stripe --config='{"apiKey":"sk_test_..."}'
|
|
|
|
# Run workbench with config file
|
|
payments workbench --provider=wise --config-file=./wise-config.json
|
|
|
|
# List available providers
|
|
payments workbench --list-providers`,
|
|
RunE: runWorkbench,
|
|
}
|
|
|
|
cmd.Flags().StringP(workbenchProviderFlag, "p", "", "Connector provider name (e.g., stripe, wise, adyen)")
|
|
cmd.Flags().StringP(workbenchConfigFlag, "c", "", "Connector configuration as JSON string")
|
|
cmd.Flags().StringP(workbenchConfigFileFlag, "f", "", "Path to connector configuration JSON file")
|
|
cmd.Flags().String(workbenchListenFlag, "127.0.0.1:8080", "HTTP server listen address")
|
|
cmd.Flags().Int(workbenchPageSizeFlag, 25, "Page size for fetch operations")
|
|
cmd.Flags().Bool("list-providers", false, "List available connector providers")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func runWorkbench(cmd *cobra.Command, args []string) error {
|
|
// Check if listing providers
|
|
listProviders, _ := cmd.Flags().GetBool("list-providers")
|
|
if listProviders {
|
|
return printProviders()
|
|
}
|
|
|
|
// Get optional provider and config for initial connector
|
|
provider, _ := cmd.Flags().GetString(workbenchProviderFlag)
|
|
configJSON, _ := cmd.Flags().GetString(workbenchConfigFlag)
|
|
configFile, _ := cmd.Flags().GetString(workbenchConfigFileFlag)
|
|
|
|
var connectorConfig json.RawMessage
|
|
if provider != "" {
|
|
provider = strings.ToLower(provider)
|
|
|
|
if configFile != "" {
|
|
data, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read config file: %w", err)
|
|
}
|
|
connectorConfig = data
|
|
} else if configJSON != "" {
|
|
connectorConfig = json.RawMessage(configJSON)
|
|
} else {
|
|
return fmt.Errorf("connector config is required when provider is specified (use --config or --config-file)")
|
|
}
|
|
|
|
// Validate JSON
|
|
var validateJSON map[string]interface{}
|
|
if err := json.Unmarshal(connectorConfig, &validateJSON); err != nil {
|
|
return fmt.Errorf("invalid config JSON: %w", err)
|
|
}
|
|
}
|
|
|
|
// Get other flags
|
|
listenAddr, _ := cmd.Flags().GetString(workbenchListenFlag)
|
|
pageSize, _ := cmd.Flags().GetInt(workbenchPageSizeFlag)
|
|
|
|
// Create logger
|
|
logger := logging.NewDefaultLogger(os.Stdout, true, false, false)
|
|
|
|
// Create workbench config
|
|
cfg := workbench.Config{
|
|
ListenAddr: listenAddr,
|
|
EnableUI: true,
|
|
DefaultPageSize: pageSize,
|
|
}
|
|
|
|
// Create workbench
|
|
wb, err := workbench.New(cfg, logger)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create workbench: %w", err)
|
|
}
|
|
|
|
// Handle signals
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
<-sigChan
|
|
fmt.Println("\nShutting down...")
|
|
cancel()
|
|
_ = wb.Stop(context.Background())
|
|
}()
|
|
|
|
// Start workbench
|
|
if err := wb.Start(ctx); err != nil {
|
|
return fmt.Errorf("failed to start workbench: %w", err)
|
|
}
|
|
|
|
// If provider was specified, create and install a connector
|
|
if provider != "" {
|
|
conn, err := wb.CreateConnector(ctx, workbench.CreateConnectorRequest{
|
|
Provider: provider,
|
|
Name: "default",
|
|
Config: connectorConfig,
|
|
})
|
|
if err != nil {
|
|
logger.Error(fmt.Sprintf("Failed to create connector: %v", err))
|
|
} else {
|
|
logger.Info(fmt.Sprintf("Created connector instance: %s", conn.ID))
|
|
|
|
// Install the connector
|
|
if err := wb.InstallConnector(ctx, conn.ID); err != nil {
|
|
logger.Error(fmt.Sprintf("Failed to install connector: %v", err))
|
|
} else {
|
|
logger.Info("Connector installed successfully")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wait for shutdown
|
|
wb.Wait()
|
|
|
|
return nil
|
|
}
|
|
|
|
func printProviders() error {
|
|
configs := registry.GetConfigs(true) // include debug connectors
|
|
|
|
fmt.Println("Available connector providers:")
|
|
fmt.Println()
|
|
|
|
for provider, config := range configs {
|
|
fmt.Printf(" %s\n", provider)
|
|
fmt.Printf(" Config parameters:\n")
|
|
for paramName, param := range config {
|
|
required := ""
|
|
if param.Required {
|
|
required = " (required)"
|
|
}
|
|
fmt.Printf(" - %s: %s%s\n", paramName, param.DataType, required)
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
// This will be added to root in NewRootCommand
|
|
}
|