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:
- Mensagens no começo e final de um workflow de prompts
- Manipulação de cancelamento (Ctrl + C) por parte do usuário
- Input para textos + validação
- Seleção de escolha única
- Seleção de múltipla escolha
- Confirmação de ações
- Spinner para eventuais ações pendentes
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
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();
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:
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.
- Note: pode ser usado para apresentar qualquer informação relevante. Um quadrado cobrirá o aviso.
- Cancel: interrompe a execução do Clack.
- Intro: faz a introdução do workflow.
- Outro: finaliza a interação com o usuário.
- Log: alguma informação que deseja apresentar durante a interação, sendo: message, info, success, step, warn/warning, error.
- Spinner: mostra ícone de carregamento, avisando o usuário que algo está sendo processado pelo prompt: start (inicia o carregamento), message (troca da mensagem), stop (finaliza o carregamento).
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();
Principais Componentes
Vou listar as propriedades que esses componentes podem possuir:
- message: Mensagem requisitando algum valor do usuário
- placeholder: Texto que pode fornecer exemplos de valores
- defaultValue: Valor padrão caso o usuário não forneça nenhum dado
- initialValue: Valor já pré-inserido no input
- initialValues: Opções já pré-selecionadas
- validate: Uma função que valida se o campo é válido ou não
- options: Opções a serem escolhidas
- maxItems: Limita o número de opções
- required: Se a escolha de opção é obrigatório
- cursorAt: Opção que estará o cursor
- active: Texto para aceitar uma confirmação
- inactive: Texto para recusar uma confirmação
- mask: Caractere substituto para campo de senha
Text
- Propriedades: message, placeholder, defaultValue, initialValue, validate.
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();
Select
- Propriedades: message, options, initialValue, maxItems.
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();
SelectKey
- Propriedades: message, options, initialValue, maxItems.
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();
MultiSelect
- Propriedades: message, options, initialValues, required, cursorAt.
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();
Confirm
- Propriedades: message, active, inactive, initialValue.
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();
Password
- Propriedades: message, mask, validate.
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();
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.