lerna + CI =? Or how not to get tangled in three pines

Instead of a preface

Good day! My name is Sergey, and I am a team lead at Medpoint24-Lab. I have been developing in nodejs for a little over a year and a half - before that I had C #, and even before that, everything is different and not very serious. Well, that is, I have not so much experience as a car, and sometimes I have to seriously break my head when solving problems that arise. Having solved this, you always want to share your findings with your teammates.





And a few days ago, they advised me to start a blog ... and I thought, maybe then just write on Habr?





Perhaps examples of practical situations from which an intelligent, but not very experienced developer crawls out with a creak, will be of interest to the equally intelligent and inexperienced)) And maybe someone else will come in handy.





I will try to tell you without immersion in theory, but with links to it.





What will it be about?

The pilot will focus on an interesting problem we encountered while trying to organize a CI / CD for a mono repository with lerna . I'll tell you right away that this post:





  • not about monorepositories . The pros and cons of monorepa, as a concept, have long been described in many posts, including on Habrรฉ (this one is quite holivar, by the way)





  • not about tools for managing monorepositories. Monorepa can be implemented using Nx , rush , even just yarn workspaces. But it so happened that we chose lerna and will live with her for a while.





  • not about package managers . I can recommend a good vidos comparing npm, yarn and pnpm and an awesome series of posts in which working with npm is explained from the very beginning and very thoroughly. And we have npm (for now) ...





  • not about nestjs . But he's cool!









All this will be discussed only to the extent that is needed to understand the problem.





Then what about?





Given:





, 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 - .





  • But most importantly, there is always light at the end of the tunnel! And while you are solving such problems, you often manage to raise a good layer of new knowledge.









I am sure this is not the last iteration. I have the feeling that everything can be made simpler, cleaner - I will be glad to have opinions and ideas in the comments.





I still have to play with the npm shrinkwrap command, for example ...









Many thanks to those who have read to the end ... Is there anyone else here?





If this format "story from practice" is interesting, please write what is "so", what is "not so". Because the stories ... I have them.









Thanks for attention!












All Articles