lerna + CI =? Oder wie man sich nicht in drei Kiefern verheddert

Anstelle eines Vorworts

Schönen Tag! Mein Name ist Sergey und ich bin Teamleiter bei Medpoint24-Lab. Ich habe mich seit etwas mehr als anderthalb Jahren in NodeJs entwickelt - davor hatte ich C #, und selbst davor ist alles anders und nicht sehr ernst. Das heißt, ich habe nicht so viel Erfahrung wie ein Auto, und manchmal muss ich mir ernsthaft den Kopf brechen, wenn ich auftretende Probleme löse. Nachdem Sie dies gelöst haben, möchten Sie Ihre Erkenntnisse immer mit Ihren Teamkollegen teilen.





Und vor ein paar Tagen haben sie mir geraten, einen Blog zu starten ... und ich dachte, vielleicht schreiben Sie dann einfach über Habr?





Vielleicht sind Beispiele für praktische Situationen, aus denen ein intelligenter, aber nicht sehr erfahrener Entwickler mit einem Knarren herauskriecht, für den ebenso intelligenten wie unerfahrenen Interessanten von Interesse.)) Und vielleicht ist jemand anderes nützlich.





Ich werde versuchen, es Ihnen zu sagen, ohne in die Theorie einzutauchen, aber mit Links dazu.





Worum geht es?

Der Pilot wird sich auf ein interessantes Problem konzentrieren, das beim Versuch, eine CI / CD für ein Mono-Repository mit lerna zu organisieren, aufgetreten ist . Ich werde Ihnen sofort sagen, dass dieser Beitrag:





  • nicht über Monorepositories . Die Vor- und Nachteile von Monorepa als Konzept wurden schon lange in vielen Beiträgen beschrieben, auch auf Habré (dieser ist übrigens ziemlich holivar).





  • . Nx, rush, yarn workspaces. , lerna .





  • . npm, yarn pnpm c npm . npm ()...





  • nestjs. !









, .





?





:





, npm-, , .





packages
+-- @contract
|		+-- src
|		+-- package.json
|   ...
|
+-- application
|   +-- src
|   +-- package.json
|   ...
|
+-- package.json
+-- lerna.json
...
      
      



?

, , "" .





, axios.post(....) (any), .





import { Client } from '@contract/some-service';

const client = new Client(options);

const filters: StronglyTypedObject = ...
const data = await client.getSomeData(filters)
/*
*      .
*    getSomeData()     ,
*     , axios.
*/
      
      



, , , . .





, :





const query = new SomeQuery({ ... });
const data = await client.call(query);
/*
*        ,      -
*    ,  .     rabbitMQ.
*/

      
      







http-, , RabbitMQ, redis. .









, , ? , . - , lerna bootstrap.





lerna bootstrap --hoist
      
      



--hoist



- . , , , node_modules . + , .





lerna bootstrap



. , application/package.json





"dependencies": {
	"@contract/core": "^1.0.0"
}
      
      



, npm-, node_modules packages. , , .





CI/CD. . , 1000 - .





, issues github, Stackoverflow . . .. , , "" (, ).





, :





  1. PR , , .





  2. , , unit-.





  3. ( - ).





  4. @contract npm registry ( , ).





  5. , , . (, , - docker, . , )





  6. , , . node_modules - , .





!

CI/CD .





:





lerna : lerna version lerna publish ( ). :





lerna publish --conventional-commits --yes
#  :  publish     version.
#    ,      .
      
      



conventional commits.

lerna publish



, , CI- . Conventional Commits. commit-, lerna , semver (, ). , ( )! .









4 .lerna publish



, - (, , ), lerna version



npm publish



. , npm publish --registry



, , . lerna publish



, lerna.json (. 7):





{
  "version": "1.2.2",
  "npmClient": "npm",
  "command": {
    "publish": {
      "message": "chore(release): publish",
      "registry": ....
    }
  },
  "packages": [
    "packages/@contract",
    "packages/application"
  ]
}

      
      



.npmrc ( npm) .





, CI- ( CI/CD):





# Pull  checkout
lerna bootsrap --hoist 
lerna run build #   npm run build   .
lerna publish --conventional-commits --yes
cp packages/application/build /tmp/place/for/artifact
...
      
      



node_modules.









№1. node_modules /tmp/place/for/artifact. :





  • ( jest, typescript ). 2 , 22, node_modules .





  • , , , . . lerna . - - , .









№2. . package.json packages/application. , ! package.json , npm i



- ! :





, , CI npm install npm ci



. npm install , package.json, package-lock.json shrinkwrap.json ( ). lock- .





:





  • lock- . dependencies "~" "^" - , . . ( ) .





  • lock- package.json. , package.json ( ), package-lock.json , npm ci :





, , - npm install.





, : lerna bootstrap --hoist



package-lock.json . , .





, package.json packages/application lock- - . , ! application lock- . :





№3. "". , , lock- . :





lerna bootstrap
      
      



lock- . ! npm ci



, . ?





package-lock.json .. @contract/core! , , ...





№4. , npm install . :





lerna exec -- npm i
      
      



, lock- ! npm ci



! !





...





, @contract- . ! npm i



npm registry. - . , , , (, build publish). , .. , . , , .





, publish



. - , , - , , .





№4. , , ...





:





lerna exec -- npm i      #  lock-  .
lerna link               #  .
lerna run build
lerna publish --conventional-commits ...
cp packages/application/build /path/to/artifact
#      production 
# -  sourceMaps  .
cp packages/application/package*.json /path/to/artifact
(cd /path/to/artifact && npm ci --production)
      
      



! - .. jest - 3- 4- ...





... , . . , , , lerna bootstrap --hoist



.





- . , . - (, , ...) - , . . , . , .





, lerna bootstrap --hoist



lerna exec -- npm i && lerna link



- ? - lerna bootstrap



, --hoist



. hoist... . - .





, , :





packages
+-- @contract
|   +-- node_modules
|       +-- class-transformer
|		+-- src
|		+-- package.json
|   ...
|
+-- application
|   +-- node_modules
|       +-- class-transformer
|       +-- @contract ->  
|   +-- src
|   +-- package.json
|   ...
|
+-- package.json
+-- lerna.json
...
      
      



. application contract class-transformer. -, , , , , node_modules .





class-transformer - , .





,

class-transformer - . nestjs (ValidationPipe). :





import { Type } from 'class-transformer';
import { IsInt, IsPositive } from 'class-validator';

export class Query {
  @IsInt()
  @IsPositive()
  @Type(() => Number)
  id: number;
}

      
      



GET (?id=100500) , nest , . IsInt() ( , IsPositive() 100%).





: . @Type() - . , return Number(id)



@Transform() .





class-validator class-transformer.





- . ( - 3 )





:





. @Type(), class-transformer : " ". , nest plainToClass , Query. .





" " . , plainToClass , @Type() !





. , . , import



, .







- - , - .





Query , , , @contract class-transformer.





, class-validator . , ( global?). .





. , - , ( node_modules, , node_modules... ) --hoist. registry, ( ...) - , .





, - ...





?

( ), :





  • ( ), :





lerna bootstrap --hoist #  npm i  !    lock-file!
lerna run build
jest
#   ...
      
      



  • CI , lerna publish



    , :





# Makefile

#   .
BUILD:=build.$(shell jq .version packages/application/package.json | sed 's/"//g')

artifact:
  #  build/prod  sourceMap'  ,     
	(cd packages/application && npm run build:prod -- --outDir ../../deploy/$(BUILD))
	cp -r packages/application/package*.json deploy/$(BUILD)
  #   -  package-lock.json
	(cd deploy/$(BUILD) && npm ci --production)
  
  #   -  ,   package*.json 
  #  tar.gz .
	rm deploy/$(BUILD)/package*.jsosdf
      
      



make, . , Dockerfile, .





  • lock-, ?





lerna exec -- npm i
lerna clean --yes
#  .  ,   .     
# lock-
lerna bootstrap -- hoist
      
      



  • , , . application ( @contract) , lock-:





# Makefile

add:
	# ( )    package.json
	lerna add --scope=$(scope) $(package) --no-bootstrap
	#  package-lock.json  
	lerna exec --scope=$(scope) -- npm i
	# node_modules  units/application   !
	lerna clean --yes
	#          package-lock.json
	lerna bootstrap --hoist
  
#   ( scope      package.json):
$ make add scope=app_name package left-pad
      
      



? lerna add package-lock.json, . . -. ...









:





  • - .





  • CI/CD - .





  • Vor allem aber ist am Ende des Tunnels immer Licht! Und während Sie solche Probleme lösen, schaffen Sie es oft, eine gute Schicht neuen Wissens aufzubauen.









Ich bin sicher, dass dies nicht die letzte Iteration ist. Ich habe das Gefühl, dass alles einfacher und sauberer gemacht werden kann - ich freue mich über die Meinungen und Ideen in den Kommentaren.





Ich muss zum Beispiel immer noch mit dem Befehl npm shrinkwrap spielen ...









Vielen Dank an diejenigen, die bis zum Ende gelesen haben ... Ist noch jemand hier?





Wenn dieses Format "Geschichte aus der Praxis" interessant ist, schreiben Sie bitte, was "so" ist, was "nicht so" ist. Weil die Geschichten ... ich habe sie.









Vielen Dank für Ihre Aufmerksamkeit!












All Articles