DEV Community

Romildo Junior
Romildo Junior

Posted on

Como criar uma CLI com React

Não é novidade que existem CLIs. Nem que elas podem ser criadas das mais diversas formas, desde algo pequeno e objetivo como um simples ls, até editores complexos e poderosos como o Neovim.

Recentemente estive pesquisando sobre as novas ferramentas de IA e percebi um detalhe bem interessante sobre o Claude Code, Codex, Copilot... Todos utilizam React!

Ink's repository section with information of where the framework is been used

E é isso que vou mostrar como fazer nesse artigo.

TL;DR

Nesse artigo vamos focar no mínimo necessário e o código final está disponível em jrmmendes/ink-cli-minimal-app.

Caso procure uma aplicação um pouco mais completa, com alguns padrões adicionais e testes unitários, há um template disponível em jrmmendes/create-cli-app, que pode ser testado localmente:

npm i -g @jrmmendes/create-cli-app 
Enter fullscreen mode Exit fullscreen mode
create-cli-app --shell 
Enter fullscreen mode Exit fullscreen mode

Veja a demo.

Learn Once, Write Anywhere

O meu objetivo não é falar sobre react de forma filosófica aqui, porem é útil refletir sobre a ideia central dessa tecnologia: leve o conhecimento sobre como criar interfaces de um lugar, para outro.

Dado que a tecnologia é a mesma (React), em cada plataforma existe uma forma de utilizar os componentes básicos sobre os quais algo pode ser construído.

Se está trabalhando com web, você utiliza o react-dom e elementos HTML como div e span; se está criando algo mobile, React Native e e suas tags serão Views. CLI? É aí que entra o ink - e vale comentar: não se trata de uma versão adaptada ou menor do React.

Veja um exemplo:

import { Box, Text } from 'ink'; export const Banner = (props: { name: string, version: string, cwd: string }) => { const ORANGE_COLOR = "#FE9900"; return ( <Box borderStyle="round" paddingX={1} borderColor={ORANGE_COLOR} flexDirection="column" > <Box paddingBottom={1}> <Text bold color={ORANGE_COLOR}>React + Ink Example Application</Text> </Box> <Text>cwd: {props.cwd}</Text> <Text>{props.name}</Text> <Text>version: {props.version}</Text> </Box> ) }; 
Enter fullscreen mode Exit fullscreen mode

Construindo a aplicação

Vamos começar da forma mais direta possível: qual é o mínimo necessário para executar algo com Ink?

Bem, você certamente vai precisar de um projeto javascript/jsx (e typescript, nesse caso), o que implica em ter um sistema de build com todas as etapas configuradas - a magia que o create-react-app e seus sucessores entregam em um único comando.

Felizmente, estamos em 2025. Isso significa que o 7x1 foi há 11 anos, mas também que o bun, que resolve tudo de forma simples e performática, pode ser utilizado. Com efeito, executando:

bun init -y 
Enter fullscreen mode Exit fullscreen mode

Temos o esqueleto do projeto:

. ├── bun.lock ├── index.ts ├── package.json ├── README.md └── tsconfig.json 
Enter fullscreen mode Exit fullscreen mode

A partir disso, alguns itens precisam ser ajustados para que o build funcione corretamente no caso de uma aplicação CLI:

  • Instalar dependências do React/Ink.
  • Definir um (ou mais) caminhos de execução, através da chave bin;
  • Definir quais arquivos serão utilizados para gerar o pacote npm durante a fase de publicação;

Dependências

É necessário instalar, além de ink e react, alguns pacotes para escrita de testes unitários (ink-testing-library) e as respectivas definições de tipo. Também utilizaremos o rimraf, para permitir limpar arquivos de build no futuro:

bun i ink react@18 
Enter fullscreen mode Exit fullscreen mode
bun i -D @types/{ink,ink-testing-library,react} ink-testing-library rimraf react-devtools-core 
Enter fullscreen mode Exit fullscreen mode

Configurações

Crie um arquivo bin.js com o seguinte conteúdo:

#!/bin/env node import('./dist/bundle.js'); 
Enter fullscreen mode Exit fullscreen mode

Após isso, marque o arquivo como executável, o que pode ser feito em sistemas unix por meio do chmod:

chmod +x bin.js 
Enter fullscreen mode Exit fullscreen mode

Ajuste o package.json, adicionando o nome do executável e os arquivos do build:

{ "bin": { "ink-cli-app": "./bin.js" }, "files": [ "dist", "bin.js" ] } 
Enter fullscreen mode Exit fullscreen mode

Também inclua alguns scripts para build, test e execução da aplicação:

{ "scripts": { "clear": "rimraf dist", "test": "FORCE_COLOR=0 NODE_ENV=test bun test", "build": "bun run clear && bun build src/application.tsx --outfile=./dist/bundle.js --target=node --format=esm --minify", "start": "node ./bin.js", "start:dev": "bun ./src/application.tsx", "prepublishOnly": "bun run build", } } 
Enter fullscreen mode Exit fullscreen mode

Remova o arquivo index.ts e crie uma nova pasta src, com o arquivo application.tsx:

import { exit } from 'process'; import { Box, render, Text } from 'ink'; import pkg from '../package.json'; type ApplicationProps = { name: string; version: string; } export const Application = ({ name, version }: ApplicationProps) => { const ORANGE_COLOR = "#FE9900"; return ( <Box width={80} paddingX={1} flexDirection="column"> <Box borderStyle="round" paddingX={1} borderColor={ORANGE_COLOR} flexDirection="column" > <Box paddingBottom={1}> <Text bold color={ORANGE_COLOR}>React + Ink Example Application</Text> </Box> <Text>{name}</Text> <Text>version: {version}</Text> </Box> </Box> ) } try { const { waitUntilExit } = render( <Application name={pkg.name} version={pkg.version} /> ) await waitUntilExit(); } catch(error) { console.error({ status: 'app exited with error', error }); exit(1); } 
Enter fullscreen mode Exit fullscreen mode

Por fim, basta executar os scripts build e start para ter a aplicação funcionando:

Demo da aplicação

Conclusão

A partir desse ponto, não existe mais nada muito específico do ink: você pode utilizar o que já conhece de react para construir a aplicação.

Talvez seja útil dar um passo atrás e ler este guideline sobre como criar aplicações CLI com qualidade (escrito por autores de ferramentas populares, como docker compose).

Um outro componente que pode ser útil é um parser para os argumentos, bem como geração de documentação (o famoso --help). Uma das soluções mais completas é o Commander, que abstrai grande parte desse trabalho.

Top comments (0)