[{"data":1,"prerenderedAt":619},["ShallowReactive",2],{"project-cmdo":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"name":10,"featured":11,"year":12,"topics":13,"heroImage":15,"archived":6,"languages":18,"homepage":7,"githubUrl":24,"tools":25,"body":29,"_type":613,"_id":614,"_source":615,"_file":616,"_stem":617,"_extension":618},"/projects/cmdo","projects",false,"","Cmdo","Command runner and log viewer 🔫","cmdo",true,2023,[14],"command-line tool",{"remote":16,"local":17},"https://raw.githubusercontent.com/ryanachten/cmdo/main/docs/cmdo_promo.png","hero-images/cmdo.webp",[19,20,21,22,23],"Go","JavaScript","CSS","PowerShell","HTML","https://github.com/ryanachten/cmdo",[26,27,28],"github-actions","preact","websockets",{"type":30,"children":31,"toc":596},"root",[32,43,49,60,65,70,78,85,90,115,122,127,145,153,159,164,170,176,181,186,198,204,220,225,233,239,248,450,456,461,472,478,491,505,516,522,528,536,549,562,567,573,581,587],{"type":33,"tag":34,"props":35,"children":36},"element","p",{},[37],{"type":33,"tag":38,"props":39,"children":42},"img",{"alt":40,"src":41},"cmdo banner","https://github.com/ryanachten/cmdo/raw/main/docs/cmdo_promo.png",[],{"type":33,"tag":44,"props":45,"children":46},"h2",{"id":10},[47],{"type":48,"value":10},"text",{"type":33,"tag":34,"props":50,"children":51},{},[52,58],{"type":33,"tag":53,"props":54,"children":56},"code",{"className":55},[],[57],{"type":48,"value":10},{"type":48,"value":59}," (\"commando\") is a command-line tool which runs multiple commands in parallel.",{"type":33,"tag":34,"props":61,"children":62},{},[63],{"type":48,"value":64},"A common problem when working with microservices is the need to run multiple applications in unison when developing locally; often using different languages and frameworks.",{"type":33,"tag":34,"props":66,"children":67},{},[68],{"type":48,"value":69},"Existing solutions like Docker Compose exist, however, come with the overhead of creating and using Docker containers. Additionally, local development features such as hot-reloading can prove hard to implement when relying on containerisation.",{"type":33,"tag":34,"props":71,"children":72},{},[73],{"type":33,"tag":38,"props":74,"children":77},{"alt":75,"src":76},"command view","https://github.com/ryanachten/cmdo/raw/main/docs/cmdo_commands.jpg",[],{"type":33,"tag":79,"props":80,"children":82},"h3",{"id":81},"features",[83],{"type":48,"value":84},"Features",{"type":33,"tag":34,"props":86,"children":87},{},[88],{"type":48,"value":89},"cmdo provides a simple configuration-driven approach to running services in parallel without the need for containerisation. The key features currently provided are:",{"type":33,"tag":91,"props":92,"children":93},"ul",{},[94,100,105,110],{"type":33,"tag":95,"props":96,"children":97},"li",{},[98],{"type":48,"value":99},"Run multiple commands in different working directories in parallel",{"type":33,"tag":95,"props":101,"children":102},{},[103],{"type":48,"value":104},"Stdout/stderr output view",{"type":33,"tag":95,"props":106,"children":107},{},[108],{"type":48,"value":109},"Web output view (see below)",{"type":33,"tag":95,"props":111,"children":112},{},[113],{"type":48,"value":114},"Easily include and exclude different commands at runtime using tags",{"type":33,"tag":116,"props":117,"children":119},"h4",{"id":118},"web-view",[120],{"type":48,"value":121},"Web view",{"type":33,"tag":34,"props":123,"children":124},{},[125],{"type":48,"value":126},"The cmdo has an optional web view for displaying command stdout and stderr output. This web view provides the following additional capabilities:",{"type":33,"tag":91,"props":128,"children":129},{},[130,135,140],{"type":33,"tag":95,"props":131,"children":132},{},[133],{"type":48,"value":134},"Command grid view",{"type":33,"tag":95,"props":136,"children":137},{},[138],{"type":48,"value":139},"Inline unified command view",{"type":33,"tag":95,"props":141,"children":142},{},[143],{"type":48,"value":144},"Global log searching and command-specific log searching",{"type":33,"tag":34,"props":146,"children":147},{},[148],{"type":33,"tag":38,"props":149,"children":152},{"alt":150,"src":151},"terminal view","https://github.com/ryanachten/cmdo/raw/main/docs/cmdo_terminal.jpg",[],{"type":33,"tag":44,"props":154,"children":156},{"id":155},"usage",[157],{"type":48,"value":158},"Usage",{"type":33,"tag":34,"props":160,"children":161},{},[162],{"type":48,"value":163},"Supply a configuration file containing different commands you want to execute in parallel",{"type":33,"tag":79,"props":165,"children":167},{"id":166},"installation",[168],{"type":48,"value":169},"Installation",{"type":33,"tag":116,"props":171,"children":173},{"id":172},"via-go-recommended",[174],{"type":48,"value":175},"Via Go (recommended)",{"type":33,"tag":34,"props":177,"children":178},{},[179],{"type":48,"value":180},"The simplest way to use and stay up to date with the latest version of cmdo is to retrieve the latest version via a go installation.",{"type":33,"tag":34,"props":182,"children":183},{},[184],{"type":48,"value":185},"This automatically adds it to your path for use:",{"type":33,"tag":187,"props":188,"children":193},"pre",{"className":189,"code":191,"language":192,"meta":7},[190],"language-bash","go install github.com/ryanachten/cmdo@main # install latest from main branch\ncmdo --config .\\example-config.json # use cmdo!\n","bash",[194],{"type":33,"tag":53,"props":195,"children":196},{"__ignoreMap":7},[197],{"type":48,"value":191},{"type":33,"tag":116,"props":199,"children":201},{"id":200},"release-builds",[202],{"type":48,"value":203},"Release builds",{"type":33,"tag":34,"props":205,"children":206},{},[207,209,218],{"type":48,"value":208},"If you prefer using prebuilt executables, the latest release can be downloaded from ",{"type":33,"tag":210,"props":211,"children":215},"a",{"href":212,"rel":213},"https://github.com/ryanachten/cmdo/releases",[214],"nofollow",[216],{"type":48,"value":217},"GitHub",{"type":48,"value":219},".",{"type":33,"tag":34,"props":221,"children":222},{},[223],{"type":48,"value":224},"You'll need to add this to your path yourself and download the latest release to receive updates.",{"type":33,"tag":34,"props":226,"children":227},{},[228],{"type":33,"tag":38,"props":229,"children":232},{"alt":230,"src":231},"unified view","https://github.com/ryanachten/cmdo/raw/main/docs/cmdo_unified.jpg",[],{"type":33,"tag":79,"props":234,"children":236},{"id":235},"arguments",[237],{"type":48,"value":238},"Arguments",{"type":33,"tag":187,"props":240,"children":243},{"className":241,"code":242,"language":192,"meta":7},[190],"cmdo --config .\\example-config.json --tags backend --exclusions EnsembleApi --web=false\n",[244],{"type":33,"tag":53,"props":245,"children":246},{"__ignoreMap":7},[247],{"type":48,"value":242},{"type":33,"tag":249,"props":250,"children":251},"table",{},[252,286],{"type":33,"tag":253,"props":254,"children":255},"thead",{},[256],{"type":33,"tag":257,"props":258,"children":259},"tr",{},[260,266,271,276,281],{"type":33,"tag":261,"props":262,"children":263},"th",{},[264],{"type":48,"value":265},"command",{"type":33,"tag":261,"props":267,"children":268},{},[269],{"type":48,"value":270},"required",{"type":33,"tag":261,"props":272,"children":273},{},[274],{"type":48,"value":275},"type",{"type":33,"tag":261,"props":277,"children":278},{},[279],{"type":48,"value":280},"default",{"type":33,"tag":261,"props":282,"children":283},{},[284],{"type":48,"value":285},"description",{"type":33,"tag":287,"props":288,"children":289},"tbody",{},[290,321,373,420],{"type":33,"tag":257,"props":291,"children":292},{},[293,303,308,313,316],{"type":33,"tag":294,"props":295,"children":296},"td",{},[297],{"type":33,"tag":53,"props":298,"children":300},{"className":299},[],[301],{"type":48,"value":302},"--config",{"type":33,"tag":294,"props":304,"children":305},{},[306],{"type":48,"value":307},"true",{"type":33,"tag":294,"props":309,"children":310},{},[311],{"type":48,"value":312},"string",{"type":33,"tag":294,"props":314,"children":315},{},[],{"type":33,"tag":294,"props":317,"children":318},{},[319],{"type":48,"value":320},"points to a configuration file using the schema below",{"type":33,"tag":257,"props":322,"children":323},{},[324,333,338,346,352],{"type":33,"tag":294,"props":325,"children":326},{},[327],{"type":33,"tag":53,"props":328,"children":330},{"className":329},[],[331],{"type":48,"value":332},"--tags",{"type":33,"tag":294,"props":334,"children":335},{},[336],{"type":48,"value":337},"false",{"type":33,"tag":294,"props":339,"children":340},{},[341,342],{"type":48,"value":312},{"type":33,"tag":343,"props":344,"children":345},"span",{},[],{"type":33,"tag":294,"props":347,"children":348},{},[349],{"type":33,"tag":343,"props":350,"children":351},{},[],{"type":33,"tag":294,"props":353,"children":354},{},[355,357,363,365,371],{"type":48,"value":356},"when defined, only commands ",{"type":33,"tag":358,"props":359,"children":360},"em",{},[361],{"type":48,"value":362},"with",{"type":48,"value":364}," supplied ",{"type":33,"tag":53,"props":366,"children":368},{"className":367},[],[369],{"type":48,"value":370},"tags",{"type":48,"value":372}," in configuration will be run",{"type":33,"tag":257,"props":374,"children":375},{},[376,385,389,396,402],{"type":33,"tag":294,"props":377,"children":378},{},[379],{"type":33,"tag":53,"props":380,"children":382},{"className":381},[],[383],{"type":48,"value":384},"--exclusions",{"type":33,"tag":294,"props":386,"children":387},{},[388],{"type":48,"value":337},{"type":33,"tag":294,"props":390,"children":391},{},[392,393],{"type":48,"value":312},{"type":33,"tag":343,"props":394,"children":395},{},[],{"type":33,"tag":294,"props":397,"children":398},{},[399],{"type":33,"tag":343,"props":400,"children":401},{},[],{"type":33,"tag":294,"props":403,"children":404},{},[405,406,411,412,418],{"type":48,"value":356},{"type":33,"tag":358,"props":407,"children":408},{},[409],{"type":48,"value":410},"without",{"type":48,"value":364},{"type":33,"tag":53,"props":413,"children":415},{"className":414},[],[416],{"type":48,"value":417},"name",{"type":48,"value":419}," will be run",{"type":33,"tag":257,"props":421,"children":422},{},[423,432,436,441,445],{"type":33,"tag":294,"props":424,"children":425},{},[426],{"type":33,"tag":53,"props":427,"children":429},{"className":428},[],[430],{"type":48,"value":431},"--web",{"type":33,"tag":294,"props":433,"children":434},{},[435],{"type":48,"value":337},{"type":33,"tag":294,"props":437,"children":438},{},[439],{"type":48,"value":440},"bool",{"type":33,"tag":294,"props":442,"children":443},{},[444],{"type":48,"value":307},{"type":33,"tag":294,"props":446,"children":447},{},[448],{"type":48,"value":449},"opts out of web view and only outputs using stdout and stderror",{"type":33,"tag":79,"props":451,"children":453},{"id":452},"configuration",[454],{"type":48,"value":455},"Configuration",{"type":33,"tag":34,"props":457,"children":458},{},[459],{"type":48,"value":460},"The configuration file must conform to the following API:",{"type":33,"tag":187,"props":462,"children":467},{"className":463,"code":465,"language":466,"meta":7},[464],"language-typescript","  commands: {\n    name: string, // label to be presented in the UI\n    executable: string, // command to to be executed\n    arguments: string[], // arguments to be passed to the command\n    workingDirectory: string, // working directory where the command should be executed\n    tags?: string[] // (optional) tags used to include exclude commands\n  }[],\n","typescript",[468],{"type":33,"tag":53,"props":469,"children":470},{"__ignoreMap":7},[471],{"type":48,"value":465},{"type":33,"tag":116,"props":473,"children":475},{"id":474},"examples",[476],{"type":48,"value":477},"Examples",{"type":33,"tag":34,"props":479,"children":480},{},[481,483,489],{"type":48,"value":482},"Examples of different configuration can be found in the ",{"type":33,"tag":210,"props":484,"children":487},{"href":485,"rel":486},"https://github.com/ryanachten/cmdo/raw/main/examples/",[214],[488],{"type":48,"value":474},{"type":48,"value":490}," directory.",{"type":33,"tag":34,"props":492,"children":493},{},[494,496,503],{"type":48,"value":495},"One example using the ",{"type":33,"tag":210,"props":497,"children":500},{"href":498,"rel":499},"https://github.com/ryanachten/ensemble",[214],[501],{"type":48,"value":502},"ensemble",{"type":48,"value":504}," project is as follows:",{"type":33,"tag":187,"props":506,"children":511},{"className":507,"code":509,"language":510,"meta":7},[508],"language-json","{\n  \"commands\": [\n    {\n      \"name\": \"EnsembleFrontend\",\n      \"executable\": \"yarn\",\n      \"arguments\": [\"run\", \"dev\"],\n      \"workingDirectory\": \"C:\\\\dev\\\\ensemble\\\\client\",\n      \"tags\": [\"frontend\"]\n    },\n    {\n      \"name\": \"EnsembleApi\",\n      \"executable\": \"go\",\n      \"arguments\": [\"run\", \".\"],\n      \"workingDirectory\": \"C:\\\\dev\\\\ensemble\\\\api\",\n      \"tags\": [\"backend\"]\n    }\n  ]\n}\n","json",[512],{"type":33,"tag":53,"props":513,"children":514},{"__ignoreMap":7},[515],{"type":48,"value":509},{"type":33,"tag":44,"props":517,"children":519},{"id":518},"development",[520],{"type":48,"value":521},"Development",{"type":33,"tag":79,"props":523,"children":525},{"id":524},"architecture",[526],{"type":48,"value":527},"Architecture",{"type":33,"tag":34,"props":529,"children":530},{},[531],{"type":33,"tag":38,"props":532,"children":535},{"alt":533,"src":534},"cmdo model","https://github.com/ryanachten/cmdo/raw/main/docs/cmdo_model.png",[],{"type":33,"tag":34,"props":537,"children":538},{},[539,541,547],{"type":48,"value":540},"cmdo is comprised of two main parts; a command runner (",{"type":33,"tag":53,"props":542,"children":544},{"className":543},[],[545],{"type":48,"value":546},"commander",{"type":48,"value":548}," in the diagram above), which executes a terminal process for each command the configuration.",{"type":33,"tag":34,"props":550,"children":551},{},[552,554,560],{"type":48,"value":553},"The command output is piped is broadcasted via the ",{"type":33,"tag":53,"props":555,"children":557},{"className":556},[],[558],{"type":48,"value":559},"webserver",{"type":48,"value":561}," using websockets and piped to stdout/stderr. Commands can be individually stopped or started via the web view and the REST API, the resulting state update is then broadcasted via websockets. The command state and log history is also stored and served via a REST API so it can be persisted on page refresh.",{"type":33,"tag":34,"props":563,"children":564},{},[565],{"type":48,"value":566},"A web app listening to websocket and REST API endpoints is served by cmdo. This intentionally uses a no-build process to keep the dev experience simple. Preact, JS modules, JSDoc and CSS imports are used in favour of alternatives requiring a frontend build step. The web app provides different view and searching capabilities not found in the CLI tool alone.",{"type":33,"tag":79,"props":568,"children":570},{"id":569},"prerequisites",[571],{"type":48,"value":572},"Prerequisites",{"type":33,"tag":91,"props":574,"children":575},{},[576],{"type":33,"tag":95,"props":577,"children":578},{},[579],{"type":48,"value":580},"Go installed locally (>v1.21)",{"type":33,"tag":79,"props":582,"children":584},{"id":583},"running-locally",[585],{"type":48,"value":586},"Running locally",{"type":33,"tag":187,"props":588,"children":591},{"className":589,"code":590,"language":192,"meta":7},[190],"# from project root directory\ngo run . --config .\\examples\\ensemble.cmdo.json\n",[592],{"type":33,"tag":53,"props":593,"children":594},{"__ignoreMap":7},[595],{"type":48,"value":590},{"title":7,"searchDepth":597,"depth":597,"links":598},2,[599,603,608],{"id":10,"depth":597,"text":10,"children":600},[601],{"id":81,"depth":602,"text":84},3,{"id":155,"depth":597,"text":158,"children":604},[605,606,607],{"id":166,"depth":602,"text":169},{"id":235,"depth":602,"text":238},{"id":452,"depth":602,"text":455},{"id":518,"depth":597,"text":521,"children":609},[610,611,612],{"id":524,"depth":602,"text":527},{"id":569,"depth":602,"text":572},{"id":583,"depth":602,"text":586},"markdown","content:projects:cmdo.md","content","projects/cmdo.md","projects/cmdo","md",1776573205287]