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 . . .. , , "" (, ).
, :
PR , , .
, , unit-.
( - ).
@contract npm registry ( , ).
, , . (, , - docker, . , )
, , . node_modules - , .
!
CI/CD .
:
lerna : lerna version lerna publish ( ). :
lerna publish --conventional-commits --yes
# : publish version.
# , .
conventional commits.
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!