Articles

a passagem de vários repositórios para um lerna-js mono-repo

No mitter.io, temos um par de público npm pacotes que precisamos publicar, e que se mudou recentemente para um mono-repo estrutura gerenciada por Lerna de ter repositórios diferentes para cada um deles. Hoje, gostaria de compartilhar nossa experiência desta migração e nossa configuração com a nova estrutura monorepo. Todos os nossos pacotes são alvos SDK ou são dependências para os nossos alvos SDK:

  • @mitter-io/core – a funcionalidade principal do mitter.io SDKs
  • @mitter-io/models – os modelos typescript (classes, pseudónimos de tipo, interfaces, etc.) para os SDKs
  • @mitter-io/web – O SDK web
  • @mitter-io/react-native – A Reagir SDK Nativo
  • @mitter-io/node – O nó.js SDK (usado para o nó.js infra-estruturas)
  • @mitter-io/react-scl – A biblioteca de componentes para ReactJS aplicações

Todos os nossos pacotes são escritos no arquivo TypeScript e tipagens estão agrupados com os pacotes próprio, e nós não distribuir separado de digitação pacotes (as que você costuma ver começando com @types/). Usamos rollup para empacotar estes pacotes em ambos os formatos de Módulo UMD e ES5. Além disso, nós usamos TypeDoc para gerar nossa documentação que é então publicada em um balde público em AWS S3.

Antes de usar Lerna, tínhamos um repositório separado para cada um dos nossos pacotes e funcionava bem enquanto só tínhamos o SDK web. Como que evoluiu e teve mais programadores a trabalhar no SDK, começamos a enfrentar alguns problemas com o nosso programa de configuração:

  1. Dado que a maioria dos SDK lógica reside no @mitter-io/core, quase todas as mudanças que ocorreram em core pacote e todos os outros pacotes tinham que ser atualizados para apontar para a nova versão. Assim, mesmo se não foi um erro que deveria ser corrigido para, digamos Reagir Nativo, a alteração deve ir em core, mas a atualização necessária para agora refletir em todos os outros destinos, por exemplo, webnode e react-native. Era bastante comum um desenvolvedor falhar um alvo.
  2. quase todas as alterações no SDK resultariam em alterações em pelo menos 3 dos 5 pacotes.
  3. vimos um enorme benefício em manter a mesma versão através de pacotes (torna mais fácil para os desenvolvedores adivinhar qual seria a última versão do target), mas rastrear manualmente isso estava se tornando complicado.
  4. npm link (ou yarn link se você preferir) tinha seu próprio conjunto de problemas certificando-se de que todas as dependências estão ligadas, em seguida, desvinculado uso correto do npm e volta para o local de ligação para o desenvolvimento.
  5. era bastante comum executar scripts através de pacotes (e.g.), e nós estávamos usando um cadafalso frágil de symlinks e scripts bash para gerenciar o mesmo.nessa altura, deparámo-nos com o Lerna e parecia ser o ideal para as nossas necessidades.

    decidimos seguir o caminho mais simples que existe, tentando usar defaults tanto quanto possível. Pelo que experimentamos, migrar para Lerna foi uma brisa. Comece por criar um novo repo Lerna:

    mkdir my-new-monorepo && cd my-new-monorepo
    git init .
    lerna init

    responde a um par de perguntas simples (onde sempre recorremos ao padrão) e está tudo pronto. Mover o nosso velho pacotes a partir de seus acordos de recompra para o novo (o que nós estávamos temendo como nós pensamos que seria uma enorme dor) foi mais fácil do que o esperado:

    lerna import ~/projects/my-single-repo-package-1 --flatten

    NOTA --flatten podem ou não ser necessários, mas enfrentamos problemas sem ele.

    o Que é surpreendente sobre Lerna é que ele traz em todas as git compromete-se junto com ele (você pode perder um pouco de história com --flatten), de tal forma que para o novo acordo de recompra, a história parece desenvolvimento sempre foi acontecendo neste monorepo. Isto é absolutamente essencial porque você vai precisar de git blame alguém para um bug que você descobriu depois de se mudar para o monorepo.

    configuração actual

    com Lerna, agora gerimos um único repositório para todos os nossos pacotes, com uma estrutura de directórios que se parece com esta:

    packages/
    core/
    models/
    node/
    react-native/
    web/
    lerna.json
    package.json

    Para publicar os pacotes alterados, agora nós simplesmente temos que:

    lerna boostrap
    lerna publish

    Você não tem que fazer lerna bootstrap cada tempo; apenas se esta for a primeira vez que você está verificando o repo. O que ele faz é simplesmente instalar todas as dependências de cada um dos pacotes sob este repo.

    ao mesmo tempo, também decidimos racionalizar o nosso processo um pouco e adicionamos todas as tarefas de embalagem dentro do npm ciclo de vida em si. Note que isto não tem nada a ver com Lerna.; isto é algo que deve idealmente estar lá em qualquer pacote npm, independentemente da estrutura do repo. Para cada um dos pacotes, os scripts a seguir estão presentes no indivíduo pacakge.json:

    "scripts": {
    ...
    "prepare": "yarn run build",
    "prepublishOnly": "./../../ci-scripts/publish-tsdocs.sh"
    ...
    }

    Este constrói o pacote com o compilador do typescript, pacotes com cumulativo e gera documentos com typedoc:

    "scripts": {
    ...
    "build": "tsc --module commonjs && rollup -c rollup.config.ts && typedoc --out docs --target es6 --theme minimal --mode file src"
    ...
    }

    Tendo um único repositório estrutura também permite que você mantenha comum de scripts em um único lugar para que as alterações se aplicam a todos os pacotes (também devemos mover o script de compilação para um script separado, dado que, atualmente, tornou-se bastante complexo bash comando).

    o fluxo do programador

    o fluxo do programador para além das versões é inalterado. Um desenvolvedor cria um problema no GitLab (ou é atribuído um), cria um novo branch para o problema, e então funde as alterações para dominar após uma revisão de código. O ciclo de vida do lançamento segue agora um processo extremamente estruturado:

    1. Quando um marco é concluído e estamos planejando fazer uma nova versão, um dos desenvolvedores (responsável por essa versão em particular) cria uma nova versão, executando .
    2. Lerna fornece uma extremamente útil e fácil de usar a linha de comandos para descobrir a próxima versão
    (master) mitter-js-sdk ツ lerna version --force-publish
    lerna notice cli v3.8.1
    lerna info current version 0.6.2
    lerna info Looking for changed packages since v0.6.2
    ? Select a new version (currently 0.6.2) (Use arrow keys)
    ❯ Patch (0.6.3)
    Minor (0.7.0)
    Major (1.0.0)
    Prepatch (0.6.3-alpha.0)
    Preminor (0.7.0-alpha.0)
    Premajor (1.0.0-alpha.0)
    Custom Prerelease
    Custom Version

    uma Vez que uma nova versão é selecionada, Lerna alterações de versões de pacotes, cria uma tag no repo remoto e envia as alterações para o nosso GitLab instância. Além disso, os desenvolvedores não são obrigados a fazer qualquer outra coisa. Nosso IC é configurado para construir todas as tags que têm um nome semelhante a um número semântico versionado.

    NOTE que executar o lerna version com --force-publish porque queremos que todos os pacotes têm a mesma linhagem de versões. Então, às vezes, teremos pacotes que não diferem entre versões diferentes. Dependendo da sua preferência, você pode optar por não fazê-lo.

    the CI setup

    we use Gitlab’s integrated CI for building, testing and publishing across all of our projects (JS and Java). Para o novo JS monorepo, temos duas etapas:

    1. Criar
    2. Publish

    A fase de construção é extremamente simples e executa os dois scripts seguintes:

    lerna bootstrap
    lerna run build

    Esta fase é executada em cada commit essencialmente validar a sanidade do pacote. Por outro lado, a fase de publicação é a seguinte::

    git checkout master
    lerna bootstrap
    git reset --hard
    lerna publish from-package --yes

    Nós descobrimos que nós tivemos que fazer uma git checkout master e uma git reset --hard porque GitLab clones (ou busca, dependendo da configuração) do repositório e, em seguida, verifica a confirmação de que está para ser construído. Isto define a pasta de trabalho num modo de ‘cabeça destacada’, ou seja, a ref HEAD não está a apontar para lado nenhum. Lerna usa HEAD para descobrir a versão atual do pacote e erros no estado de cabeça.

    Nós também precisamos executar o lerna publish from-package como oposição a lerna publish, como a execução de um simples lerna publish teria Lerna, queixando-se de que a versão atual já é publicado, como os metadados foi atualizado quando o desenvolvedor executou lerna version localmente. O argumentofrom-package diz a Lerna para publicar todas as versões que não estão atualmente presentes em npm para um determinado pacote. Isso também ajuda se uma publicação falhou por alguma razão e você está a refazer o pipeline.

    a fase de publicação está configurada para ser executada apenas em marcas que correspondam ao seguinte crédito regex:

    ^v(0|\d*)\.(0|\d*)\.(0|\d*)(-(0|\d*|\d**)(\.(0|\d*|\d**))*)?(\++(\.+)*)?$

    isto é um pouco extravagante, e para a maioria das equipas e para a maioria dos fins, simplesmente ^v*$ deve funcionar. 🙂

    Nota Embora ainda não o tenhamos feito, uma vez que somos uma pequena equipa, também se pode marcar quaisquer marcas que sigam o regex acima, como protegido no GitLab, para restringir quem pode publicar pacotes para npm.

    pode verificar o nosso monorepo em https://github.com/mitterio/js-sdk (isto é reflectido no nosso Acordo Interno de GitLab).

    notas rápidas

    ao executar scripts comuns (como nós fazemos para publicar docs typescript), é bastante útil saber os detalhes do pacote executando o script. Isto aplica-se a scripts no ciclo de vida do npm, bem como scripts que podem ser executados usando lerna run ou lerna exec. Para um determinado pacote em npm, npm torna todo o package.json disponível para um script usando variáveis de ambiente. Assim, para um dado pacote com o seguinte package.json:

    {
    "name": "@mitter-io/core",
    "version": "0.6.28",
    "repository": {
    "type": "git"
    }
    }

    As seguintes variáveis estarão disponíveis durante a execução de qualquer ciclo de vida do script:

    npm_package_name=@mitter-io/core
    npm_package_version=0.6.28
    npm_package_repository_type=git

    Peculiaridades/Problemas

    Um par de coisas que ainda estão a trabalhar com a nova configuração (alguns deles são problemas, enquanto alguns de nós provavelmente só não sei melhor):

    • Não tenho certeza se é possível, mas gostaria de ser capaz de ter comuns do ciclo de vida de scripts para todos os nossos pacotes. Declarar estes na raiz package.json não funciona.
    • é extremamente difícil testar a sua configuração Lerna completamente sem realmente publicar algo para npm. Não sei se existe um --dry-run algures.
    • Lerna tem uma maneira de manter um comum config-bloco devDependencies para que todos os devDependencies são da mesma versão para cada um dos sub-pacotes. Esta é uma característica bem legal,mas levaria algum tempo para eliminar todos os comuns.
    • o mesmo pode aplicar-se a outras dependências também, por isso, embora não queiramos uma id comumdependencies bloco de configuração, ter uma forma de expressar as variáveis disponíveis através dos projectos seria bom. Por exemplo, em nossa Java/Kotlin monorepo, nós usamos gradle.properties para conter variáveis como o springBootVersionspringCoreVersion, etc., que são então usados pelos scripts de graple individuais.

    os Nossos pensamentos sobre monorepos
    tem sido bastante acalorado debate recentemente com monorepos e se estamos vendo um número enorme de saltar sobre o bandwagon novamente, lembra o momento microservices era toda a raiva.

    A estrutura que seguimos aqui está tendo vários monorepos, e esta não é a nossa primeira vez gerindo monorepos. Toda a nossa plataforma e infra-estrutura é um monorepo que contém código privado, implementável e vários pacotes voltados para o público que são publicados para bintray. Nós também temos o nosso site principal em execução com uma Mola de back-end, com o frontend junto com webpack apoio quente recarga (webpack watch), etc. Nós nunca decidimos ir com uma única mono-repo em toda a organização porque a ferramenta simplesmente não estava lá.

    para Ter a maior parte de nosso código Java em um único repositório funciona muito bem porque gradle fornece todas as ferramentas necessárias para o Java monorepo e lerna e o npm ciclo de vida de fornecer as ferramentas para a JS SDK monorepo. Então, simplificando, os monorepos são ótimos quando você identifica a cobertura das mudanças que vão em seu repo. Para a nossa infra-estrutura Java, vimos vários projetos MRs através de um único recurso, o que nos inclinou a mudar para um monorepo apenas para este projeto em particular, com todo o nosso outro código ainda em acordos separados. E uma vez que vimos um padrão semelhante emergir para os nossos SDKs JS também, nos mudamos para Lerna.

    note que somos uma pequena equipe de cerca de 9 engenheiros; então o que funciona para nós pode não funcionar para equipes de diferentes tamanhos. O que gostaríamos sobretudo de salientar é que a adopção de qualquer solução não tem de ser binária, em que ou o fazemos como prescrito ou não o fazemos de todo.algumas das motivações que vimos para um monorepo definitivamente se aplicaram a nós e muitas delas não. Por exemplo, nós simplesmente não podemos gastar o tempo para construir a ferramenta se toda a nossa base de código foi movida para um único repo — independentemente do benefício que podemos ou não experimentar. Então o debate realmente não é sobre ter um “único repo” — por si só, não é nada mais do que uma nova estrutura de diretório. A prescrição deles é para aliviar certas questões e como com cada “bala de prata”, há ressalvas.

    O debate é sobre questões comuns enfrentadas na indústria de software e que soluções têm sido comumente tomadas;” comum ” é a palavra-chave. A área onde você se desvia da aplicação “comum” é onde você começa a inovar, fazer mudanças e construir um pouco.