Skip to content
Voltar

Introdução ao Clack CLI

Publicado:

clack.cc

clack.cc

Table of contents

Open Table of contents

Clack Prompts

Nem toda interação na command line precisa ser chata e tediosa. Fiquei intrigado na primeira vez que digitei npm create astro@latest.

Parece mágico. Gostaria de fazer uma interação do estilo que eles fazem. Com algumas pesquisas, encontrei uma lib javascript chamada Clack, comecei a procurar materiais que poderiam me guiar nesse estudo. Mas senti a necessidade de algum que fosse o mais direto possível.

E cá estou, aqui não é a doc oficial, mas darei o escopo geral dessa tecnologia, e futuramente um caso de estudo fazendo integração com banco de dados SQLite.

Os benefícios de utilizar uma ferramenta elegante como essa são:

Essas são as funções/componentes principais, aqui vai um exemplo básico com código:

import * as p from "@clack/prompts";

async function main() {
  p.intro("Olá desconhecido");

  const name = await p.text({
	message: "Informe seu nome:",
  });

  p.outro(`Seja bem vindo ${name}`);
}

main();

Para executar o comando acima, crie uma pasta (clack-prompt/, por exemplo), e dentro dela, digite o seguinte comando no terminal:

npm init -y

Esse comando criará nosso package.json, armazenará todas as dependências. Para fazer a instalação do Clack, use npm:

npm install @clack/prompts -E

Digite com a flag -E para pegarmos a versão exata da dependência, e não a faixa válida de versões acima da atual.

Para usarmos ESModules (import/export), digite isso no final do package.json:

{

 },
 "type": "module"
}

Não esqueça da vírgula antes de adicionar a nova linha

Pronto, agora rode o seguinte comando:

node index.js

Output da execução do comando

Output da execução do comando

Estrutura básica

Percebe-se que todo workflow está acoplado em uma função assíncrona. Isso porque todo componente tem a pendência da ação do usuário, ou seja, é esperado alguma interação, até mesmo o cancelamento do workflow.

No exemplo, importei todas as funções e componentes — *import * as p from “@clack/prompts”. Assim, podemos acessar qualquer desses dois a partir de p, como p.text, p.confirm, p.select etc.

Para cada componente declarado no workflow, verifique caso o usuário cancele no meio da interação:

import * as p from "@clack/prompts";

async function main() {
  p.intro("Cardápio");

  const nome = await p.text({
    message: "Digite seu nome:",
  });

  if (p.isCancel(nome)) {
    p.cancel("Operação cancelada");
    return process.exit(0);
  }

  const comidaFavorita = await p.select({
    message: "Selecione sua comida favorita:",
    options: [
      { value: "lasanha", label: "Lasanha" },
      { value: "chocolate", label: "Chocolate" },
      { value: "salada", label: "Salada", hint: "vai lá oh fit" },
    ],
  });

  if (p.isCancel(comidaFavorita)) {
    p.cancel("Operação cancelada");
    return process.exit(0);
  }

  p.outro(`${nome} ama comer ${comidaFavorita}`);
}

main();

Output da execução do comando

Output da execução do comando

Porém, caso eu cancele minha interação com Ctrl + C sem ao menos tratar essa bifurcação no script, irá gerar esse seguinte comportamento:

Output da execução do comando

Output da execução do comando

Não há problema caso não realmente utilize essa informação, pois utilizando a condição no código acima, cancelaria todo workflow.

Principais Funções

Estou diferenciando Funções de Componentes, porque aquelas não possuem ações pendentes do usuário. Pois funções tem o objetivo de informar meramente o usuário de algo.

import * as p from "@clack/prompts";
import { setTimeout } from "node:timers/promises";

async function main() {
  p.intro("Apresentando as funções");

  p.note("Farás teste!\nnpm run test", "Dica do dia");

  p.log.message("esse é o caminho");
  p.log.info("agora é oficial!");
  p.log.success("está correto");
  p.log.step("Passo ...");
  p.log.warn("não me parece certo");
  p.log.error("Ocorreu um erro");

  const s = p.spinner();

  s.start("Iniciada a ação");
  await setTimeout(1000 * 2);

  s.message("Finalizando");
  await setTimeout(1000 * 2);
  s.message("Toques finais");
  await setTimeout(1000 * 2);

  s.stop("De volta ao normal");
  await setTimeout(1000);

  p.outro("É isso");

  p.cancel("Operação cancelada");
}

main();

Output da execução do comando

Output da execução do comando

Principais Componentes

Vou listar as propriedades que esses componentes podem possuir:

Text

Veja o exemplo abaixo:

import * as p from "@clack/prompts";

async function main() {
  p.intro("Olá desconhecido");

  const name = await p.text({
    message: "Informe seu nome:",
    placeholder: "usuario1",
    defaultValue: "indefinido",
    initialValue: "Sr. ",
    validate: value => value === "ric" && "ric não!",
  });

  p.outro(`Seja bem vindo ${name}`);
}

main();

Output da execução do comando

Output da execução do comando

Select

import * as p from "@clack/prompts";

async function main() {
  p.intro("Escolha a opção");

  const option = await p.select({
    message: "Opções válidas:",
    options: [
      { value: "1", label: "Opção 1" },
      { value: "2", label: "Opção 2" },
      { value: "3", label: "Opção 3" },
    ],
    initialValue: "2",
  });

  p.outro(`Opção >> "${option}"`);
}

main();

Output da execução do comando

Output da execução do comando

SelectKey

import * as p from "@clack/prompts";

async function main() {
  p.intro("Escolha a opção");

  const option = await p.selectKey({
    message: "Opções válidas:",
    options: [
      { value: "1", label: "Alternativa A" },
      { value: "2", label: "Alternativa B" },
      { value: "3", label: "Alternativa C" },
      { value: "4", label: "Alternativa D", hint: "leia com cuidado" },
    ],
    initialValue: "3",
  });

  p.outro(`Opção >> "${option}"`);
}

main();

Output da execução do comando

Output da execução do comando

MultiSelect

import * as p from "@clack/prompts";

async function main() {
  p.intro("Escolha algumas opções");

  const options = await p.multiselect({
    message: "Opções válidas:",
    options: [
      { value: "1", label: "Opção A" },
      { value: "2", label: "Opção B" },
      { value: "3", label: "Opção C", hint: "vale a pena" },
      { value: "4", label: "Opção D" },
    ],
    initialValues: "14",
    cursorAt: "2",
    required: true,
  });

  p.outro(`Opções >> "${options}"`);
}

main();

Output da execução do comando

Output da execução do comando

Confirm

import * as p from "@clack/prompts";

async function main() {
  p.intro("Faça a confirmação");

  const hasAccepted = await p.confirm({
    message: "Você deseja aceitar?",
    active: "Claro",
    inactive: "Melhor não",
    initialValue: false,
  });

  p.outro(`Pedido ${hasAccepted ? "aceito" : "recusado"}`);
}

main();

Output da execução do comando

Output da execução do comando

Password

import * as p from "@clack/prompts";

async function main() {
  p.intro("Configure uma senha");

  const pass = await p.password({
    message: "Digite sua senha:",
    mask: "*",
    validate: value => value.length < 8 && "Senha curta demais",
  });

  p.outro(`Senha informada: ${pass}`);
}

main();

Output da execução do comando

Output da execução do comando

Groups

Ainda há Utilities como o de agrupamento. Serve para organizar e estruturar os prompts. Não precisa ser acoplado por uma função, como no exemplo abaixo traduzido do README.md do repositório original:

import * as p from "@clack/prompts";

const grupo = await p.group(
  {
    nome: () => p.text({ message: "Qual é seu nome?" }),
    idade: () => p.text({ message: "Qual é sua idade?" }),
    cor: ({ results }) =>
      p.multiselect({
        message: `Qual é sua cor favorita ${results.name}?`,
        options: [
          { value: "vermelho", label: "Vermelho" },
          { value: "verde", label: "Verde" },
          { value: "azul", label: "Azul" },
        ],
      }),
  },
  {
    onCancel: ({ results }) => {
      p.cancel("Operação cancelada");
      process.exit(0);
    },
  }
);

console.log(grupo.nome, grupo.idade, grupo.cor);

Uma vantagem é a manipulação do cancelamento, pois ao invés de cada componente tiver que verificar se houve ou não uma desistência por parte do usuário, defino um cancelamento geral que poderá servir o workflow por inteiro.

Conclusão

Dei uma passada rápida nas principais (se não quase todas) funcionalidades que Clack nos possibilita fazer, deixando mais atrativo a nossa convivência com o prompt de comando.

O foco foi mais apresentar cada elemento disponível, por isso não me estendi muito no último tópico, no qual pretendo explorar mais a frente.

Se tiverem dúvidas, comentem. Informem-me caso haja erros, atualizarei assim que possível. Para ver todos os exemplos presentes aqui, acesse o repositório do GitHub.



Post Anterior
Django no Render: Descomplicando o Deploy com Blueprints