lerna + CI =? Ou comment ne pas s'emmêler dans trois pins

Au lieu d'une préface

Bonne journée! Je m'appelle Sergey et je suis chef d'équipe chez Medpoint24-Lab. Je développe dans nodejs depuis un peu plus d'un an et demi - avant ça j'avais C #, et même avant ça, tout est différent et pas très sérieux. Eh bien, c'est-à-dire que je n'ai pas autant d'expérience en tant que voiture, et parfois je dois sérieusement me casser la tête pour résoudre des problèmes qui surviennent. Une fois ce problème résolu, vous souhaitez toujours partager vos découvertes avec vos coéquipiers.





Et il y a quelques jours, ils m'ont conseillé de créer un blog ... et j'ai pensé, peut-être alors juste écrire sur Habr?





Peut-être que des exemples de situations pratiques à partir desquelles un développeur intelligent, mais pas très expérimenté rampe avec un grincement, intéresseront peut-être ceux qui sont tout aussi intelligents et inexpérimentés)) Et peut-être que quelqu'un d'autre sera utile.





Je vais essayer de vous dire sans immersion dans la théorie, mais avec des liens vers elle.





De quoi s'agit-il?

Le pilote se concentrera sur un problème intéressant que nous avons rencontré en essayant d'organiser un CI / CD pour un dépôt mono avec lerna . Je vais vous dire tout de suite que ce post:





  • pas sur les monorépôts . Les avantages et les inconvénients du monorepa, en tant que concept, ont longtemps été décrits dans de nombreux articles, y compris sur Habré (celui-ci est assez holivar, d'ailleurs)





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





  • Mais surtout, il y a toujours de la lumière au bout du tunnel! Et pendant que vous résolvez de tels problèmes, vous parvenez souvent à acquérir une bonne couche de nouvelles connaissances.









Je suis sûr que ce n'est pas la dernière itération. Je ne laisse pas le sentiment que tout peut être fait plus facilement, plus propre - je serai ravi des opinions et des idées dans les commentaires.





Je dois encore jouer avec la commande npm shrinkwrap, par exemple ...









Un grand merci à ceux qui ont lu jusqu'au bout ... Y a-t-il quelqu'un d'autre ici?





Si ce format «histoire de la pratique» est intéressant, veuillez écrire ce qui est «tellement», ce qui ne l'est pas. Parce que les histoires ... je les ai.









Merci pour l'attention!












All Articles