Articles

áthelyezés több tárolóból egy lerna-js monórepóba

At mitter.io, van egy pár nyilvános felületűnpm csomagok, amelyeket közzé kell tennünk, és nemrégiben egy Lerna által kezelt mono-repo struktúrába költöztünk, hogy mindegyikhez külön tárolók legyenek. Ma szeretném megosztani tapasztalatainkat erről a migrációról és a telepítésünkről az új monorepo struktúrával. Minden csomagunk vagy SDK cél, vagy az SDK célok függősége:

  • @mitter-io/core – a mitter alapvető funkciója.Io SDK-k
  • @mitter-io/models – a typescript modellek (osztályok, típus álnevek, interfészek stb.) az SDK-khoz
  • @mitter-io/web – a webes SDK
  • @mitter-io/react-native – a React natív SDK
  • @mitter-io/node – a csomópont.js SDK (használt csomópont.js backends)
  • @mitter-io/react-scl – a ReactJS alkalmazások szabványos komponenskönyvtára

minden csomagunk gépírással van megírva, és a gépelés magával a csomagokkal együtt történik, és nem terjesztünk külön gépelési csomagokat (amelyeket általában a @types/kezdetűekkel látunk). A rollup segítségével ezeket a csomagokat mind UMD, mind ES5 modul formátumban csomagoljuk. Ezen felül a typedoc segítségével generáljuk dokumentációnkat, amelyet azután közzéteszünk egy nyilvános vödörben az AWS S3-ban.

a Lerna használata előtt külön tárolónk volt minden csomagunkhoz, és jól működött, miközben csak a web SDK volt. Ahogy haladtunk, és egyre több fejlesztő dolgozott az SDK-n, néhány problémával szembesültünk a beállításunkkal:

  1. mivel az SDK logikájának nagy része a @mitter-io/core, szinte minden változást, ami a core csomagban történt, és az összes többi csomagot frissíteni kellett, hogy az új verzióra mutasson. Tehát még akkor is, ha volt egy hiba, amelyet javítani kellett, mondjuk a React Native számára, a változás core, de a frissítésnek tükröznie kell az összes többi célt, azaz webnode and react-native. Elég gyakori volt, hogy egy fejlesztő elmulasztott egy célt.
  2. az SDK szinte minden változása az 5 csomagból legalább 3-ban változásokat eredményez.
  3. hatalmas előnyt láttunk abban, hogy ugyanazt a verziót megtartottuk a csomagok között (megkönnyíti a fejlesztők számára, hogy kitalálják, mi lenne a target legújabb verziója), de ennek kézi nyomon követése nehézkessé vált.
  4. npm link(vagyyarn linkha szeretné) saját problémái voltak a függőségek összekapcsolásának biztosításával, majd anpm helyes használatához a npm és vissza a helyi hivatkozáshoz a fejlesztéshez.
  5. meglehetősen gyakori volt a parancsfájlok futtatása csomagok között (pl., hogy tegye közzé a typescript docs), és mi egy törékeny állvány symlinks és bash szkriptek kezelni ugyanazt.

abban az időben találkoztunk Lerna-val, és úgy tűnt, hogy tökéletesen illeszkedik az igényeinkhez.

úgy döntöttünk, hogy a legegyszerűbb utat követjük, megpróbálva az alapértelmezett értékeket a lehető legnagyobb mértékben használni. Abból, amit tapasztaltunk, vándorló Lerna volt a szél. Kezdje egy új Lerna repo létrehozásával:

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

válaszoljon néhány egyszerű kérdésre (ahol mindig az alapértelmezetthez fordultunk), és minden készen áll. A régi csomagok áthelyezése a repóikból az újba (amitől rettegtünk, mivel azt gondoltuk, hogy hatalmas fájdalmat okozna) sokkal könnyebb volt a vártnál:

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

vegye figyelembe a --flatten lehet, hogy szükség van rá, de anélkül is szembesültünk a problémákkal.

a Lerna-ban az a csodálatos, hogy az összes git-kötelezettséget magával hozza (elveszíthet némi előzményt a --flatten segítségével), így az új repo esetében a történelem úgy néz ki, mintha a fejlődés mindig is ebben a monorepo-ban történt volna. Ez feltétlenül szükséges, mert szükséged lesz git blame valakinek egy hibára, amelyet a monorepo-ba költözés után fedeztél fel.

jelenlegi beállítás

a Lerna-val most egyetlen adattárat kezelünk az összes csomagunkhoz, egy könyvtárszerkezettel, amely így néz ki:

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

a módosított csomagok közzétételéhez most egyszerűen meg kell tennünk:

lerna boostrap
lerna publish

nem kell tennie lerna bootstrap minden alkalommal; csak akkor, ha először ellenőrzi a repót. Amit csinál, egyszerűen telepíti az egyes csomagok összes függőségét e repo alatt.

ugyanakkor úgy döntöttünk, hogy egy kicsit egyszerűsítjük a folyamatot, és hozzáadtuk az összes csomagolási feladatot a npm életciklusán belül. Ne feledje, hogy ennek semmi köze a Lerna-hoz; ennek ideális esetben minden npm csomagban ott kell lennie, függetlenül a repo struktúrától. Az egyes csomagok esetében a következő szkriptek vannak jelen az egyes pacakge.json:

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

Ez építi a csomagot a typescript fordítóval, csomagolja össze az összesítővel, és generál docs typedoc:

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

Az egyetlen repo struktúra lehetővé teszi a közös szkriptek egyetlen helyen tartását is, hogy a változások minden csomagra vonatkozzanak (a build szkriptet is külön szkriptbe kell helyeznünk, tekintettel arra, hogy mára meglehetősen összetett bash parancs lett).

A fejlesztői folyamat

A fejlesztői folyamat a kiadásokon kívül változatlan. A fejlesztő létrehoz egy problémát a GitLab – on (vagy hozzá van rendelve), létrehoz egy új ágat a problémához, majd a kód felülvizsgálata után egyesíti a módosításokat a masterbe. A kiadási életciklus most egy rendkívül strukturált folyamatot követ:

  1. amikor egy mérföldkő befejeződött, és új kiadást tervezünk, az egyik fejlesztő (az adott kiadásért felelős) létrehoz egy új verziót a lerna version futtatásával.
  2. Lerna egy rendkívül hasznos és könnyen használható prompt kitalálni a következő verzió
(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

Ha egy új verzió van kiválasztva, Lerna megváltoztatja a verziók a csomagok, létrehoz egy tag a távoli repo, és kitolja a módosításokat a GitLab példány. Ezen túlmenően a fejlesztőknek nem kell mást tenniük. A CI-t úgy állítottuk be, hogy minden olyan címkét felépítsen, amelynek neve hasonló a szemantikai verziószámhoz.

megjegyzés futtatjuk lerna versiona --force-publish mert azt akarjuk, hogy minden csomagnak pontosan ugyanaz legyen a verziója. Tehát néha olyan csomagok lesznek, amelyek nem különböznek a különböző verziók között. Az Ön preferenciájától függően dönthet úgy, hogy nem teszi meg.

A CI beállítás

a GitLab integrált CI-jét használjuk az összes projektünk (JS és Java) felépítéséhez, teszteléséhez és közzétételéhez. Az új js monorepo esetében két szakaszunk van:

  1. Build
  2. Publish

a build fázis rendkívül egyszerű, és a következő két szkriptet futtatja:

lerna bootstrap
lerna run build

Ez a fázis minden egyes kötelezettségvállaláson fut, hogy lényegében érvényesítse a csomag épelméjűségét. A közzétételi szakasz viszont a következőket futtatja:

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

rájöttünk, hogy egy git checkout master és egy git reset --hard mert a GitLab klónozza (vagy lekéri, a konfigurációtól függően) a repo, majd ellenőrzi a megépítendő elkötelezettséget. Ez a munkakönyvtárat “leválasztott fej” módba állítja, azaz a ref HEAD nem mutat sehova. Lerna használja HEAD, hogy kitaláljuk, az aktuális változata a csomag és a hibák ki a leválasztott fej állapotban.

azt is meg kell futtatni lerna publish from-package szemben lerna publish, mint végrehajtó egy egyszerű lerna publish volna Lerna panaszkodik, hogy az aktuális verzió már megjelent, mint a metaadatok frissült, amikor a fejlesztő helyben futtatta a lerna version parancsot. A from-package argumentum azt mondja a Lerna-nak, hogy tegyen közzé minden olyan verziót, amely jelenleg nincs jelen az npm-ben egy adott csomaghoz. Ez akkor is segít, ha egy közzététel valamilyen okból sikertelen, és újra megpróbálja a folyamatot.

a közzétételi fázis úgy van beállítva, hogy csak olyan címkéken fusson, amelyek megfelelnek a következő regex jóváírásnak:

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

Ez egy kicsit divatos, és a legtöbb csapat számára és a legtöbb célra egyszerűen ^v*$ működnie kell. 🙂

Megjegyzés Bár még nem tettük meg, mivel kis csapat vagyunk, a fenti regexet követő címkéket is meg lehet jelölni a GitLab-ban védettként, hogy korlátozzuk, ki tehet közzé csomagokat az npm-ben.

a monorepo-t ahttps://github.com/mitterio/js-sdk címen tekintheti meg (ezt a belső GitLab repo tükrözi).

gyors megjegyzések

gyakori szkriptek futtatásakor (mint a typescript dokumentumok közzétételénél), nagyon hasznos tudni a szkriptet futtató csomag adatait. Ez vonatkozik az npm életciklusában lévő parancsfájlokra, valamint a lerna run vagy lerna execparancsfájlokra. Egy adott csomag npm, npm teszi a teljes package.json elérhető egy script segítségével környezeti változók. Tehát egy adott csomag esetében a következő package.json:

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

a következő változók lesznek elérhetők bármilyen életciklus-szkript futtatása közben:

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

Quirks/Issues

néhány dolog, amin még dolgozunk az új beállítással (ezek közül néhány probléma, míg néhányat valószínűleg csak nem tudunk jobban):

  • nem biztos benne, hogy lehetséges-e, de szeretnénk, ha minden csomagunkhoz közös életciklus-szkriptek lennének. Ezek deklarálása a package.json gyökérben nem működik.
  • rendkívül nehéz teljesen tesztelni a Lerna beállítását anélkül, hogy ténylegesen közzétennénk valamit az npm-nek. Nem biztos benne, hogy van-e valahol --dry-run.
  • Lerna van egy módja annak, hogy egy közös config-blokkdevDependenciesúgy, hogy az összesdevDependencies azonos verziójú minden alcsomag. Ez egy nagyon jó funkció, de eltart egy kis időt, hogy kigyomlálja az összes közös is.
  • ugyanez vonatkozhat más függőségekre is, tehát bár nem akarunk közös dependencies konfigurációs blokkot, jó lenne, ha a projektekben elérhető változók kifejezhetők lennének. Például a Java / Kotlin monorepo – ban a gradle.properties változókat használjuk, mint például springBootVersionspringCoreVersion stb., amelyeket aztán az egyes gradle szkriptek használnak.

gondolataink a monorepos-ról
az utóbbi időben heves vita folyt a monorepos-szal, és hogy látunk-e ismét hatalmas számot ugrálni a kocsira, ami eléggé emlékeztet arra az időre, amikor a mikroszolgáltatások voltak a divat.

az itt követett struktúra több monorepóval rendelkezik, és ez nem az első alkalom, hogy monorepókat kezelünk. A teljes platform és backend egy monorepo, amely privát, telepíthető kódot és több nyilvános néző csomagokat, amelyek közzé bintray. A fő weboldalunk rugós háttérrel is fut, a frontend webcsomaggal van ellátva, amely támogatja a forró újratöltést (webpack watch) stb. Soha nem döntöttünk úgy, hogy egyetlen mono-repóval megyünk a szervezeten keresztül, mert a szerszámok egyszerűen nem voltak ott.

a Java kódunk nagy része egyetlen repóban működik, mert agradlebiztosítja a Java monorepo és alerna szerszámokat, valamint az npm életciklus biztosítja a JS SDK monorepo szerszámait. Tehát egyszerűen fogalmazva, a monorepos nagyszerű, ha azonosítja a repóban bekövetkező változások lefedettségét. A mi Java backend, láttuk több MRs egész projektek egyetlen funkció, amely hajlik arra, hogy menjen egy monorepo csak az adott projekt, az összes többi kódot még külön repó. És miután láttuk, hogy hasonló minta jelenik meg a JS SDK-k esetében is, Lerna-ba költöztünk.

vegye figyelembe, hogy körülbelül 9 mérnökből álló kis csapat vagyunk; tehát ami nekünk működik, Lehet, hogy nem működik a különböző méretű csapatoknál. Amit leginkább szeretnénk rámutatni, hogy bármely megoldás elfogadásának nem kell binárisnak lennie, ahol vagy az előírt módon csináljuk, vagy egyáltalán nem.

néhány motivációt láttunk egy monorepo határozottan alkalmazni minket, és sok közülük nem. Például, egyszerűen nem szabad időt szánnunk a szerszámok felépítésére, ha a teljes kódbázisunkat egyetlen repóra helyezték át-függetlenül attól, hogy milyen előnyöket tapasztalhatunk vagy nem. Tehát a vita valójában nem arról szól, hogy van — e “egyetlen repó” – önmagában ez nem más, mint egy új könyvtárszerkezet. Ezek felírása bizonyos problémák enyhítésére szolgál, és mint minden “ezüst golyó” esetében, vannak figyelmeztetések.

a vita a szoftveriparban felmerülő közös kérdésekről szól, és arról, hogy milyen megoldásokat hoztak általában; a “közös” a kulcsszó. Az a terület, ahol eltér a “közös” alkalmazástól, az az, ahol innovációt, változtatásokat és egy kicsit építhet.