Skip to main content

Plugins do Yarn 2: uma introdução

Escrito por
Plugin feature

13 de abril de 2020

0 minutos de leitura

Se você tem curiosidade sobre o Yarn 2 e sua arquitetura de plugins, um bom ponto de partida é ler a página da documentação sobre essa arquitetura em yarnpkg.com e acompanhar o tutorial de plugins do Yarn 2 no site.

Se você está começando a usar o Yarn 2, recomendo muito a leitura da minha publicação Introdução ao Yarn 2.

Criando um plugin para o Yarn 2

Vamos começar configurando um ambiente para criar o plugin. Primeiro, criaremos um novo projeto gerenciado pelo Yarn 2:

mkdir my-app
cd my-app
yarn policies set-version berry
yarn init

Com isso, temos um projeto de pacote npm limpo, gerenciado pelo Yarn 2.

Para informar ao Yarn que queremos chamar um plugin durante a execução do processo, vamos criar um arquivo chamado .yarnrc.yml e adicionar a definição do plugin com o caminho até o código que vamos desenvolver:

plugins:
 - ./plugin-hook-install-hello-world.js

Agora, vamos ao código do plugin. Vamos criar algo que se conecte à etapa pela qual o Yarn passa, chamada afterAllInstalled, e, em seguida, imprima uma mensagem no console. O código do plugin ficará assim:

const afterAllInstalled = project => {
 console.log("Everything is installed 🎉");
};

module.exports = {
 name: `plugin-hello-world`,
 factory: require => {
   return {
     default: {
       hooks: {
         afterAllInstalled
       }
     }
   };
 }
};

Para testar o plugin, basta executar o comando yarn na CLI. Ele deverá imprimir o seguinte:

➜ yarn
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed in 0s
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed in 0s
➤ YN0000: ┌ Link step
➤ YN0000: └ Completed in 0.01s
Everything is installed 🎉
➤ YN0000: Done in 0.04s

Muito legal: agora temos nosso próprio plugin do Yarn 2!

Os plugins do Yarn 2 podem fazer muitas coisas: adicionar comandos ao executável yarn, criar novos resolvers ou fetchers, como o Yarn os chama, e até conectar plugins do yarn entre si!

Se quiser aprender a criar um plugin do Yarn 2 que adiciona comandos ao Yarn, acesse a documentação oficial de plugins do Yarn, que mostra como fazer isso.

Usando um plugin do Yarn 2 para acessar informações detalhadas sobre dependências

Como vimos no exemplo anterior, alguns hooks do Yarn nos permitem uma integração mais próxima, disponibilizando um objeto público global com metadados detalhados que o Yarn coleta sobre o projeto.

No exemplo a seguir, vamos nos integrar novamente ao hook afterAllInstalled, que

é chamado com um parâmetro adicional: o objeto público Project. Com esse parâmetro extra, podemos acessar todas as informações que o Yarn coletou sobre o projeto: dependências, manifesto do pacote, informações sobre workspaces e muito mais.

No exemplo de plugin plugin-hook-install-hello-brave-world.js a seguir, capturamos as informações desse objeto na variável _ e gravamos seu conteúdo em um arquivo JSON para inspecioná-lo melhor:

const fs = require("fs");
const util = require("util");

module.exports = {
 name: `plugin-hello-brave-world`,
 factory: require => {
   return {
     default: {
       hooks: {
         afterAllInstalled(_) {
           console.log("🎉 afterAllInstalled hook invoked");
           fs.writeFileSync("afterAll.json", util.inspect(_, false, 10));
         }
       }
     }
   };
 }
};

A estrutura do objeto Project é definida em packages/berry-core/sources/Project.ts. Veja a seguir uma lista parcial das chaves no nível raiz do objeto:

Project {
 configuration: {},
 cwd: {},
 workspaces: Map {},
 storedResolutions: Map {},
 storedDescriptors: Map {},
 storedPackages: Map {},
 storedChecksums: Map {},
 ...
}

Essas chaves stored* vão nos ajudar a entender as dependências e montar a árvore deste projeto.

Para simplificar, vamos executar este plugin em um projeto npm com apenas uma dependência: debug. Por sua vez, debug também depende de ms.

O objeto storedPackages é nosso ponto de partida para obter a lista de dependências do projeto. Observe que ele também inclui uma entrada com o nome do próprio projeto, identificada pela referência workspace: key.

Vamos ver como a dependência debug aparece no Map storedPackages:

'a1f870a4a95fff67eeac2388e06345982ebf1149c710f8efe18fe5d1967fec40db60987022a5c6fe55708167d1add4b63bc8ad6f755e20c6fba140d721180595' => { identHash:
       'd027b0b474dd440d333c0ae6200111acff30aa5931aeacf1841b1eb9212edea377606a118ff0f8675b69eabe9ff00db4f2f16659519c82810c6b534f9b8ad82d',
      scope: undefined,
      name: 'debug',
      locatorHash:
       'a1f870a4a95fff67eeac2388e06345982ebf1149c710f8efe18fe5d1967fec40db60987022a5c6fe55708167d1add4b63bc8ad6f755e20c6fba140d721180595',
      reference: 'npm:4.1.1',
      version: '4.1.1',
      languageName: 'node',
      linkType: 'hard',
      dependencies:
       Map {
         '299701b4a21f15498c990a6ec8bf49b0331f01e1d610cefaa6f7040bab1be634be89d1462245207c21d1334b31690861f303a0a71aa94f80445d80b9ee37eaf6' => { identHash:
            '299701b4a21f15498c990a6ec8bf49b0331f01e1d610cefaa6f7040bab1be634be89d1462245207c21d1334b31690861f303a0a71aa94f80445d80b9ee37eaf6',
           scope: undefined,
           name: 'ms',
           descriptorHash:
            '0742408cf974a8f1cd5081a9aec19656dc8016eec3c6e2358f302902e6f1f87241f601911cb45e855f3b0d26160c1520715ae79904c5ed1d29f5383ab906440e',
           range: 'npm:^2.1.1' } },
      peerDependencies: Map {},
      dependenciesMeta: Map {},
      peerDependenciesMeta: Map {},
      bin: Map {} },
    '9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33' => { identHash:
       '299701b4a21f15498c990a6ec8bf49b0331f01e1d610cefaa6f7040bab1be634be89d1462245207c21d1334b31690861f303a0a71aa94f80445d80b9ee37eaf6',
      scope: undefined,
      name: 'ms',
      locatorHash:
       '9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33',
      reference: 'npm:2.1.2',
      version: '2.1.2',
      languageName: 'node',
      linkType: 'hard',
      dependencies: Map {},
      peerDependencies: Map {},
      dependenciesMeta: Map {},
      peerDependenciesMeta: Map {},
      bin: Map {} },

A entrada de debug tem um hash exclusivo para identificá-la, alguns metadados, como a versão resolvida, e outro objeto aninhado de dependências, que lista as dependências de debug. Mas você vai notar que ms, que aparece no objeto de dependências aninhado, ainda não foi resolvido e está indicado apenas por um intervalo de versões.

Para resolver as dependências aninhadas, precisamos usar descriptorHash e consultar o Map storedResolutions, que tem a seguinte estrutura:

storedResolutions:
  Map {
    '35f50d92512bedba8fbf78bdeae4f2bce60934a798f5e0a0ab58b087fb7dc73880c7ffee2e135c15e48ca70687336bbdf4163e75da3f90b55da5ba5e41d36051' => '35f50d92512bedba8fbf78bdeae4f2bce60934a798f5e0a0ab58b087fb7dc73880c7ffee2e135c15e48ca70687336bbdf4163e75da3f90b55da5ba5e41d36051',
    'ee78c55248c8a07a4079ce749bc462124c923d7b990785b50150c417b2821ca3363767b8dc304d80d4feae3faf9f95e1c2c46cebc582b81fe70a8f45a69b6377' => 'a1f870a4a95fff67eeac2388e06345982ebf1149c710f8efe18fe5d1967fec40db60987022a5c6fe55708167d1add4b63bc8ad6f755e20c6fba140d721180595',
    '0742408cf974a8f1cd5081a9aec19656dc8016eec3c6e2358f302902e6f1f87241f601911cb45e855f3b0d26160c1520715ae79904c5ed1d29f5383ab906440e' => '9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33' },

Encontramos o descriptorHash de ms, 0742408cf974a8f1cd5081a9aec19656dc8016eec3c6e2358f302902e6f1f87241f601911cb45e855f3b0d26160c1520715ae79904c5ed1d29f5383ab906440e, como o terceiro elemento em storedResolutions. Ele aponta para o hash 9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33, que, por sua vez, aponta para uma entrada de dependência no Map storedPackages. Dessa vez, porém, são os metadados da dependência ms já totalmente resolvida:

'9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33' => { identHash:
       '299701b4a21f15498c990a6ec8bf49b0331f01e1d610cefaa6f7040bab1be634be89d1462245207c21d1334b31690861f303a0a71aa94f80445d80b9ee37eaf6',
      scope: undefined,
      name: 'ms',
      locatorHash:
       '9455a02525b0e2c50eca4e204d71900a775107249d4245c1ea1f95e3f124c8a1d27484b29ccea934895da4d0273b22dc95cefd0561c39490cd7c86ab4404ca33',
      reference: 'npm:2.1.2',
      version: '2.1.2',
      languageName: 'node',
      linkType: 'hard',
      dependencies: Map {},
      peerDependencies: Map {},
      dependenciesMeta: Map {},
      peerDependenciesMeta: Map {},
      bin: Map {} },

Resumo: plugins do Yarn 2

O Yarn 2 ainda é recente e está conquistando usuários e recebendo feedback da comunidade. Por isso, este é um ótimo momento para criar plugins.

Comece a resolver desafios de capture the flag

Aprenda a resolver desafios de capture the flag assistindo sob demanda ao nosso workshop virtual introdutório.

Publicado em:

Leia mais

Blog

Verão nos estádios: a turnê Fan Zone do Snyk Connect

A turnê Fan Zone da Snyk levou workshops de segurança de IA, networking e competição amistosa a 8 cidades e 3 sessões virtuais. Os participantes desenvolveram habilidades, trocaram ideias e evoluíram juntos.

Blog

Snyk VulnBench JS 1.0: LLMs conseguem encontrar os mesmos bugs duas vezes?

Snyk VulnBench JS 1.0: 300 varreduras repetidas mostram que as descobertas de segurança dos LLMs variam a cada execução, enquanto SAST e modelos detectam diferentes lacunas de vulnerabilidade.

feature insights context
Blog

Construindo a segurança de IA com nossos clientes: 5 lições do programa de parceiros de design do Evo

Conheça 5 lições importantes do programa de parceiros de design do Evo, da Snyk. Descubra como a descoberta de IA, a inteligência de risco e a automação de políticas ajudam as equipes a proteger a IA generativa e controlar a proliferação de IA em grande escala.