packages feed

synapse-cc-0.3.2: src/SynapseCC/CLI.hs

-- | Command-line interface parsing
module SynapseCC.CLI
  ( -- * Legacy build-only parser (for tests)
    parseArgs
  , synapseCCParserInfo

    -- * Command parser (with subcommands)
  , parseCommand
  , synapseCCCommandParserInfo

    -- * Version
  , versionInfo
  ) where

import Data.Text (Text)
import qualified Data.Text as T
import Options.Applicative

import SynapseCC.Types

-- ============================================================================
-- Legacy Build Parser (backward-compatible, used by tests)
-- ============================================================================

-- | Full parser info (usable with execParserPure for in-process testing)
synapseCCParserInfo :: ParserInfo Config
synapseCCParserInfo = info (configParser <**> helper <**> simpleVersioner (T.unpack versionInfo))
  ( fullDesc
 <> progDesc "Unified compiler toolchain for Plexus backends"
 <> header "synapse-cc - from schema to compiled client in one command"
  )

-- | Parse command-line arguments into Config (legacy build-only)
parseArgs :: IO Config
parseArgs = execParser synapseCCParserInfo

-- | Main config parser
configParser :: Parser Config
configParser = Config
  <$> targetParser
  <*> backendParser
  <*> hostParser
  <*> portParser
  <*> optionsParser
  <*> pure Nothing

-- ============================================================================
-- Command Parser (with subcommands: build, watch, init)
-- ============================================================================

-- | Full command parser info with subcommands
synapseCCCommandParserInfo :: ParserInfo Command
synapseCCCommandParserInfo = info (commandParser <**> helper <**> simpleVersioner (T.unpack versionInfo))
  ( fullDesc
 <> progDesc "Unified compiler toolchain for Plexus backends"
 <> header "synapse-cc - from schema to compiled client in one command"
  )

-- | Parse a top-level Command
parseCommand :: IO Command
parseCommand = execParser synapseCCCommandParserInfo

commandParser :: Parser Command
commandParser = subparser
  ( command "build" (info buildCommandParser
      (progDesc "Build client from backend schema (default)"))
 <> command "watch" (info watchCommandParser
      (progDesc "Watch backend for changes and incrementally rebuild"))
 <> command "wait"  (info waitCommandParser
      (progDesc "Block until all configured backends are reachable"))
 <> command "init"  (info initCommandParser
      (progDesc "Scaffold a synapse.config.json in the current directory"))
  )

buildCommandParser :: Parser Command
buildCommandParser = mkCmd
  <$> optional targetParser
  <*> optional backendParser
  <*> hostParser
  <*> portParser
  <*> optionsParser
  where
    mkCmd (Just t) (Just b) host port opts = CmdBuild (Config t b host port opts Nothing)
    mkCmd _        _        _    _    opts = CmdBuildFromConfig opts

watchCommandParser :: Parser Command
watchCommandParser = fmap CmdWatch $ WatchArgs
  <$> (T.pack <$> argument str
        ( metavar "BACKEND"
       <> help "Backend identifier (substrate, plexus, etc.)"
        ))
  <*> many (T.pack <$> argument str
        ( metavar "PLUGIN..."
       <> help "Plugin prefix filters (e.g. echo health). Empty = all plugins."
        ))
  <*> optional (T.pack <$> option str
        ( long "target"
       <> short 't'
       <> metavar "NAME"
       <> help "Pin to a single named target from synapse.config.json"
        ))
  <*> optional (option auto
        ( long "interval"
       <> short 'i'
       <> metavar "MS"
       <> help "Poll interval in milliseconds (overrides synapse.config.json)"
        ))
  <*> watchOptionsParser

waitCommandParser :: Parser Command
waitCommandParser = fmap CmdWait $ WaitArgs
  <$> optional (T.pack <$> argument str
        ( metavar "BACKEND"
       <> help "Backend to wait for (omit to wait for all from config)"
        ))
  <*> optional (T.pack <$> option str
        ( long "url"
       <> short 'u'
       <> metavar "URL"
       <> help "WebSocket URL (e.g. ws://127.0.0.1:4448). Requires BACKEND arg."
        ))
  <*> option auto
        ( long "timeout"
       <> short 't'
       <> metavar "SECONDS"
       <> value 30
       <> showDefault
       <> help "Max seconds to wait before giving up"
        )
  <*> option auto
        ( long "interval"
       <> short 'i'
       <> metavar "MS"
       <> value 500
       <> showDefault
       <> help "Poll interval in milliseconds"
        )
  <*> switch (long "debug" <> help "Enable debug logging")

initCommandParser :: Parser Command
initCommandParser = pure CmdInit

-- | Options relevant to watch mode.
-- host/port come from synapse.config.json (not CLI flags for watch).
watchOptionsParser :: Parser Options
watchOptionsParser = (\cacheDir debug synapse hub ->
  defaultOptions
    { optInstallDeps    = False
    , optBuild          = False
    , optRunTests       = False
    , optCacheDir       = cacheDir
    , optDebug          = debug
    , optSynapsePath    = synapse
    , optHubCodegenPath = hub
    })
  <$> option str
      ( long "cache-dir"
     <> metavar "DIR"
     <> value (optCacheDir defaultOptions)
     <> showDefault
     <> help "Cache directory"
      )
  <*> switch (long "debug" <> help "Enable debug logging")
  <*> optional (option str (long "synapse"     <> metavar "PATH" <> help "synapse binary path"))
  <*> optional (option str (long "hub-codegen" <> metavar "PATH" <> help "hub-codegen binary path"))

-- ============================================================================
-- Shared Parsers
-- ============================================================================

-- | Parse target language
targetParser :: Parser Target
targetParser = argument readTarget
  ( metavar "TARGET"
 <> help "Target language (typescript, python, rust)"
  )
  where
    readTarget = maybeReader $ \case
      "typescript" -> Just TypeScript
      "ts"         -> Just TypeScript
      "python"     -> Just Python
      "py"         -> Just Python
      "rust"       -> Just Rust
      "rs"         -> Just Rust
      _            -> Nothing

-- | Parse backend name
backendParser :: Parser Backend
backendParser = Backend . T.pack <$> argument str
  ( metavar "BACKEND"
 <> help "Backend identifier (substrate, plexus, synapse, etc.)"
  )

-- | Parse registry host
hostParser :: Parser Text
hostParser = T.pack <$> option str
  ( long "host"
 <> short 'H'
 <> metavar "HOST"
 <> value "127.0.0.1"
 <> showDefault
 <> help "Registry/discovery host"
  )

-- | Parse registry port
portParser :: Parser Text
portParser = T.pack <$> option str
  ( long "port"
 <> short 'P'
 <> metavar "PORT"
 <> value "4444"
 <> showDefault
 <> help "Registry/discovery port"
  )

parseTransport :: String -> Either String TransportType
parseTransport "ws"      = Right WsTransport
parseTransport "browser" = Right BrowserTransport
parseTransport s         = Left $ "Unknown transport: " <> s

-- | Parse options
optionsParser :: Parser Options
optionsParser = Options
  <$> option str
      ( long "output"
     <> short 'o'
     <> metavar "DIR"
     <> value (optOutput defaultOptions)
     <> showDefault
     <> help "Output directory"
      )
  <*> option (eitherReader parseTransport)
      ( long "transport"
     <> metavar "ws|browser"
     <> value WsTransport
     <> help "Transport: ws (Node.js, default) or browser (native WebSocket, for Tauri/WebView)"
      )
  <*> flag True False
      ( long "no-install"
     <> help "Skip dependency installation"
      )
  <*> flag True False
      ( long "no-build"
     <> help "Skip compilation step"
      )
  <*> flag True False
      ( long "no-tests"
     <> help "Skip running smoke tests"
      )
  <*> option str
      ( long "cache-dir"
     <> metavar "DIR"
     <> value (optCacheDir defaultOptions)
     <> showDefault
     <> help "Cache directory"
      )
  <*> switch
      ( long "force"
     <> help "Force regeneration (ignore cache)"
      )
  <*> switch
      ( long "debug"
     <> help "Enable debug logging"
      )
  <*> optional (option str
      ( long "synapse"
     <> metavar "PATH"
     <> help "Path to synapse binary (overrides discovery)"
      ))
  <*> optional (option str
      ( long "hub-codegen"
     <> metavar "PATH"
     <> help "Path to hub-codegen binary (overrides discovery)"
      ))

-- ============================================================================
-- Version Info
-- ============================================================================

-- | Version information
versionInfo :: Text
versionInfo = "synapse-cc v" <> synapseCCVersion