lerna + CI =? O como no enredarse en tres pinos

En lugar de un prefacio

¡Buen día! Mi nombre es Sergey y soy líder de equipo en Medpoint24-Lab. He estado desarrollando en nodejs durante poco más de un año y medio; antes tenía C #, e incluso antes de eso, todo es diferente y no muy serio. Bueno, es decir, no tengo tanta experiencia como coche, y en ocasiones me tengo que romper seriamente la cabeza a la hora de solucionar problemas que surgen. Una vez resuelto esto, siempre querrá compartir sus hallazgos con sus compañeros de equipo.





Y hace unos días, me aconsejaron que abriera un blog ... y pensé, ¿tal vez entonces escribir en Habr?





Quizás los ejemplos de situaciones prácticas de las que un desarrollador inteligente, pero no muy experimentado, se arrastra con un crujido, sean de interés para los igualmente inteligentes e inexpertos)) Y tal vez alguien más sea útil.





Intentaré decírtelo sin inmersión en teoría, pero con enlaces a ella.





¿De qué se tratará?

El piloto se centrará en un problema interesante que encontramos al intentar organizar un CI / CD para un repositorio mono con lerna . Te diré de inmediato que esta publicación:





  • no sobre monorepositorios . Los pros y los contras de monorepa, como concepto, se han descrito durante mucho tiempo en muchas publicaciones, incluso en Habré (esta es bastante holivar, por cierto)





  • no se trata de herramientas para gestionar monorrepositorios. Monorepa se puede implementar usando Nx , rush , incluso solo espacios de trabajo de hilo. Pero resultó que elegimos a lerna y viviremos con ella por un tiempo.





  • no sobre administradores de paquetes . Puedo recomendar un buen video comparando npm, yarn y pnpm y una serie de publicaciones asombrosas en las que se explica el trabajo con npm desde el principio y muy a fondo. Y tenemos npm (por ahora) ...





  • no se trata de nestjs . ¡Pero es genial!









Todo esto se discutirá solo en la medida necesaria para comprender el problema.





¿Entonces qué pasa?





Dado:





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





  • Pero lo más importante, ¡siempre hay luz al final del túnel! Y mientras resuelve estos problemas, a menudo logra obtener una buena capa de nuevos conocimientos.









Estoy seguro de que esta no es la última iteración. No dejo la sensación de que todo se puede hacer más fácil, más limpio, estaré encantado de recibir opiniones e ideas en los comentarios.





Todavía tengo que jugar con el comando npm shrinkwrap, por ejemplo ...









Muchas gracias a los que han leído hasta el final ... ¿Hay alguien más aquí?





Si este formato de "historia de la práctica" es interesante, por favor escriba lo que es "así", lo que es "no es así". Porque las historias ... las tengo.









¡Gracias por la atención!












All Articles