Articles

Flytte fra flere repositorier til en lerna-js mono-repo

På mitter.io, vi har et par public-facing npm pakker som vi trenger å publisere, og vi har nylig flyttet til en mono-repo struktur administrert Av Lerna fra å ha separate repositories for hver enkelt av dem. I dag vil jeg dele vår erfaring med denne migrasjonen og vårt oppsett med den nye monorepo-strukturen. Alle våre pakker er ENTEN SDK-mål eller er avhengigheter for VÅRE SDK-mål:

  • @mitter-io/core – kjernefunksjonaliteten til mitter.io SDKs
  • @mitter-io/models – typescript-modellene (klasser, type aliaser, grensesnitt etc.) For Sdkene
  • @mitter-io/web – web-SDK
  • @mitter-io/react-native – Den Native SDK-React

  • @mitter-io/node – noden.js SDK (brukes til node.js backends)
  • @mitter-io/react-scl – standard komponentbiblioteket For ReactJS-applikasjoner

alle våre pakker er skrevet I TypeScript og typings er buntet med selve pakkene, og vi distribuerer ikke separate skrivepakker (de du vanligvis ser begynner med @types/). Vi bruker samleoppdatering til å pakke disse pakkene i BÅDE UMD-og ES5-modulformatene. I tillegg til Dette bruker Vi TypeDoc til å generere vår dokumentasjon som deretter publiseres på EN offentlig bøtte I AWS S3.

Før Vi brukte Lerna, hadde vi et eget lager for hver av våre pakker, og det fungerte fint mens vi bare hadde web SDK. Da VI utviklet seg og hadde flere utviklere som jobbet MED SDK, begynte vi å møte noen problemer med oppsettet vårt:

  1. Gitt at DET meste AV SDK-logikken ligger i @mitter-io/core, nesten hver endring som skjedde i core pakken og alle andre pakker måtte oppdateres for å peke på den nye versjonen. Så, selv om Det var en feil som skulle løses for, si React Native, ville endringen gå i core, men oppdateringen måtte nå reflektere i alle andre mål, dvs. webnode og react-native. Det var ganske vanlig for en utvikler å savne et mål.
  2. Nesten hver endring I SDK vil resultere i endringer over minst 3 av de 5 pakkene.
  3. Vi så en stor fordel i å holde den samme versjonen på tvers av pakker (gjør det lettere for utviklere å gjette hva den nyeste versjonen av target ville være), men manuelt sporing av dette ble tungvint.
  4. npm link(elleryarn link hvis du foretrekker) hadde sitt eget sett med problemer med å sørge for at alle avhengighetene var koblet til, og deretter koblet fra for å bruke den riktige fra npm og tilbake til den lokale lenken for utvikling.

  5. det var ganske vanlig å kjøre skript på tvers av pakker (f. eks., for å publisere typescript docs), og vi brukte en skjør stillas av symlinks og bash skript for å administrere det samme.

Rundt den tiden kom Vi Over Lerna, og det syntes å være den perfekte passformen for våre krav.

vi bestemte oss for å følge den enkleste banen der er, og prøver å bruke standardverdier så mye som mulig. Fra det vi opplevde, var det en bris å migrere Til Lerna. Start med å lage en ny lerna repo:

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

Svar på et par enkle spørsmål (hvor vi alltid tok til standard) og du er klar. Flytte våre gamle pakker fra sine repos til den nye (som vi gruer som vi trodde det ville være en massiv smerte) var måten enklere enn forventet:

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

MERK --flatten kan eller ikke være nødvendig, men vi møtte problemer uten det.

det som er utrolig Med Lerna er at Det bringer inn alle git-forpliktelsene sammen med Det (du kan miste litt historie med --flatten), slik at for den nye repoen ser historien ut til at utviklingen alltid har skjedd i denne monorepo. Dette er helt avgjørende fordi du må git blame noen for en feil du oppdaget etter å ha flyttet til monorepo.

Nåværende oppsett

Med Lerna administrerer vi nå et enkelt lager for alle våre pakker, med en katalogstruktur som ser slik ut:

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

for å publisere de endrede pakkene må vi nå bare:

lerna boostrap
lerna publish

du trenger ikke å gjøre lerna bootstrap hver gang; bare hvis dette er første gang du sjekker ut repo. Hva den gjør er bare å installere alle avhengigheter av hver av pakkene under denne repo.

samtidig bestemte vi oss også for å effektivisere prosessen litt og la til alle emballasjeoppgavene i selve livssyklusen npm. Vær oppmerksom på at dette ikke har Noe Å gjøre Med Lerna; dette er noe som ideelt sett bør være der i noen npm-pakke uavhengig av repo-strukturen. For hver av pakkene er følgende skript til stede i den enkelte pacakge.json:

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

dette bygger pakken med typescript-kompilatoren, pakker den med samleoppdatering og genererer dokumenter med typedoc:

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

Å Ha en enkelt repostruktur lar deg også beholde vanlige skript på ett sted, slik at endringer gjelder på tvers av alle pakker(vi bør også flytte build-skriptet til et eget skript, gitt at det nå har blitt ganske komplisert bash-kommando).

utviklerflyten

utviklerflyten bortsett fra utgivelser er uendret. En utvikler oppretter et problem på GitLab (eller er tildelt en), oppretter en ny gren for problemet, og slår deretter sammen endringene til master etter en kodegjennomgang. Release lifecycle følger nå en ekstremt strukturert prosess:

  1. når en milepæl er fullført og vi planlegger å lage en ny utgivelse, oppretter en av utviklerne (ansvarlig for den aktuelle utgivelsen) en ny versjon ved å kjøre lerna version.
  2. Lerna gir en svært nyttig og enkel å bruke spørsmål for å finne ut den neste versjonen
(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

Når en ny versjon er valgt, endrer Lerna versjoner av pakkene, skaper en tag i den eksterne repo, og skyver endringene til Vår GitLab eksempel. Utover dette er utviklere ikke pålagt å gjøre noe annet. VÅR CI er satt opp for å bygge alle koder som har et navn som ligner på en semantisk versjonert nummer.

Merk at vi kjørerlerna version med--force-publish fordi vi vil at alle pakker skal ha nøyaktig samme linje av versjoner. Så noen ganger har vi pakker som ikke varierer mellom forskjellige versjoner. Avhengig av dine preferanser, kan du velge å ikke gjøre det.

ci-oppsettet

Vi bruker Gitlabs integrerte CI for bygging, testing og publisering på tvers av alle våre prosjekter (JS og Java). For den nye js monorepo har vi to faser:

  1. Build
  2. Publiser

byggfasen er ekstremt enkel og kjører følgende to skript:

lerna bootstrap
lerna run build

denne fasen kjører på hver enkelt forplikte seg til å validere pakkenes sunnhet. Publiseringsfasen derimot kjører følgende:

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

Vi fant ut at vi måtte gjøre en git checkout master og en git reset --hard fordi GitLab kloner (eller henter, avhengig av konfigurasjonen) repo, og deretter sjekker ut forplikte som skal bygges. Dette setter arbeidskatalogen i en»frittliggende HODE» – modus, dvs. ref HEAD peker ikke hvor som helst. Lerna bruker HEAD for å finne ut den nåværende versjonen av pakken og feil ut i frittliggende hodestatus.

Vi må også kjøre lerna publish from-package i motsetning til lerna publish, som utfører en enkel lerna publish Ville Lerna klage på at den nåværende versjonen allerede er publisert, da metadataene var oppdatert når utvikleren kjørte lerna version lokalt. Argumentetfrom-package forteller Lerna å publisere alle versjoner som ikke finnes i npm for en gitt pakke. Dette hjelper også hvis en publisering mislyktes av en eller annen grunn, og du prøver på nytt.

publiseringsfasen er konfigurert til å kjøre bare på koder som samsvarer med følgende regex-kreditt:

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

dette er litt fancy, og for de fleste lag og for de fleste formål, bare^v*$ skal fungere. 🙂

MERK selv om vi ikke har gjort det ennå, siden vi er et lite lag, kan man også merke noen koder som følger regexen ovenfor som beskyttet I GitLab for å begrense hvem som kan publisere pakker til npm.

du kan sjekke ut vår monorepo på https://github.com/mitterio/js-sdk (dette er speilet fra vår interne GitLab repo).

Quick notes

når du kjører vanlige skript (som vi gjør for publisering av typescript docs), er det ganske nyttig å vite detaljene i pakken som kjører skriptet. Dette gjelder for skript i npm-livssyklusen, samt skript man kan kjøre med lerna run eller lerna exec. For en gitt pakke i npm gjør npm hele package.json tilgjengelig for et skript ved hjelp av miljøvariabler. Så, for en gitt pakke med følgende package.json:

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

følgende variabler vil være tilgjengelige mens du kjører et livssyklusskript:

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

Quirks/Issues

Et par ting vi fortsatt jobber med med det nye oppsettet (noen av dem er problemer, mens noen vi sannsynligvis bare ikke vet bedre):

  • ikke sikker på om det er mulig, men vi vil gjerne kunne ha felles livssyklusskript for alle våre pakker. Deklarere disse i roten package.json virker ikke.
  • Det er ekstremt vanskelig å teste lerna-oppsettet helt uten å faktisk publisere noe til npm. Ikke sikker på om det er en --dry-run et sted.
  • Lerna har en måte å holde en felles config-blokk for devDependencies slik at alle devDependencies er av samme versjon for hver av underpakkene. Dette er ganske kul funksjon, men vil ta oss litt tid til å luke ut alle de vanlige.Det samme kan også gjelde for andre avhengigheter, så mens vi ikke vil ha en fellesdependencies config-blokk, ville det være fint å ha en måte å uttrykke variabler tilgjengelig på tvers av prosjektene. For Eksempel, i Vår Java/Kotlin monorepo bruker vi gradle.properties for å inneholde variabler som springBootVersionspringCoreVersion, etc., som deretter brukes av de enkelte gradle-skriptene.

våre tanker om monorepos
det har vært ganske opphetet debatt nylig med monorepos og om vi ser et stort antall hoppe på bandwagon igjen, ganske minner om den tiden da microservices var alle raseri.

strukturen vi følger her har flere monorepos, og dette er ikke vår første gang å administrere monorepos. Hele vår plattform og backend er en monorepo som inneholder privat, distribusjonskode og flere offentlige pakker som publiseres til bintray. Vi har også vår viktigste nettsted kjører Med En Fjær backend, med frontend sammen med webpack støtter hot reloading (webpack watch), etc. Vi bestemte oss aldri for å gå med en enkelt mono-repo på tvers av organisasjonen fordi verktøyet ganske enkelt ikke var der.

Å Ha det meste av Vår Java-kode i en enkelt repo fungerer bra fordi gradle gir alle verktøy som trengs For Java monorepo og lerna og npm lifecycle gir verktøy for JS SDK monorepo. Så, enkelt sagt, monorepos er stor når du identifisere dekning av endringer som går i repo. For Vår Java backend, vi så flere MRs tvers prosjekter for en enkelt funksjon, som tilbøyelig oss til å flytte til en monorepo bare for dette prosjektet, med alle våre andre koden fortsatt i separate repos. Og når vi så et lignende mønster dukke opp for VÅRE Js Sdk også, flyttet vi til Lerna.

Vær oppmerksom på at vi er et lite team på ca 9 ingeniører; så det som fungerer for oss, fungerer kanskje ikke for lag av forskjellige størrelser. Det vi mest vil påpeke er at vedtakelsen av en løsning ikke trenger å være binær, hvor vi enten gjør det som foreskrevet eller ikke gjør det i det hele tatt.

Noen av motivasjonene vi så for en monorepo, ble definitivt brukt på oss, og mange av dem gjorde det ikke. For eksempel kan vi ganske enkelt ikke spare tid til å bygge verktøyet hvis hele kodebasen ble flyttet til en enkelt repo-uavhengig av hvilken fordel vi kan eller ikke kan oppleve. Så debatten handler egentlig ikke om å ha en «single repo» — i seg selv er det ikke noe mer enn en ny katalogstruktur. Reseptet av dem er å lindre visse problemer, og som med alle «silver bullet», er det advarsler.

debatten handler om vanlige problemer som står overfor i programvareindustrien og hvilke løsninger som ofte er tatt; «vanlig» er søkeordet. Området der du avviker fra det» vanlige » programmet er hvor du kommer til å innovere, gjøre endringer og bygge litt.