🌈 Priceit-UI library
Priceit-ui is a monorepo containing all the components of the Priceit UI library.
You can find for example a Button component, an Icon component, a Modal component, etc.
The production documentation of the components is available at this storybook.
👀 Getting started
Branches and instances
The main branches of the monorepo are the following:
- dev: the development branch
- prod: the production branch
Note that
- every single branch will trigger the build of the storybook as an artifact.
- the dev branch will trigger the build of the storybook and publish the storybook to the dev instance.
- the prod branch will trigger the build of the storybook, publish the storybook to the
prod instance
and publish the updated packages to the Gitlab package registry.
The instances of the monorepo are the following:
- dev.storybook.pricingpact.com: the development instance
- storybook.pricingpact.com: the production instance
Tools
This project uses the following tools:
- NPM Workspaces to manage the monorepo
- Lerna to manage the monorepo
- Rollup to build the packages
- Jest to run the tests
- Storybook to display the components
- Husky to run the pre-commit hook
- Lint-staged to run the linter on the staged files
- Commitizen to generate the changelog
- Semantic-release to publish the packages with meaningful versions
- Gitlab CI to run the tests, build the packages and publish them
- Eslint to lint the code
- Prettier to format the code
- Typescript to type the code
Folder structure
The folder structure of the monorepo is the following:
├── .husky/ - Husky configuration
├── .storybook/ - Storybook configuration
├── .gitlab-ci.yml - Gitlab CI configuration
├── packages/ - Packages - Icon, Button, etc.
└── priceit-ui.icon/ - Icon package
├── __tests__/ - Unit tests
├── src/ - Source code
│ ├── Icon.tsx - Component
│ ├── Icon.type.tsx - Types
│ ├── Icon.style.tsx - Styles
│ └── index.ts - Export
├── dist/ - Build output
├── stories/ - Storybook stories
├── CHANGELOG.md - Changelog of the package
├── jest.config.js - Jest configuration specific to the package
├── package.json - Package.json specific to the package
├── README.md - Readme of the package, how to use the component
├── tsconfig.json - Typescript configuration specific to the package
└── tsconfig.test.json - Typescript configuration specific to the tests
└── priceit-ui.*/ - Other packages
├── stories/ - Storybook introduction stories
├── template/ - Template for new packages
├── .eslintrc.json - Root eslint configuration
├── .lintstagedrc.json - Lint-staged configuration (pre-commit hook)
├── .gitlab-ci.yml - Gitlab CI configuration (storybook + registry)
├── babel.config.js - Babel configuration (transpilation)
├── create-package.sh - Script to create a new package
├── package.json - Root package.json
├── tsconfig.json - Root typescript configuration
├── jest.config.json - Jest configuration (tests runner)
├── lerna.json - Lerna configuration (monorepo manager)
└── rollup.config.js - Rollup configuration (bundler)
📚 Storybook
You can run the storybook locally with the following command:
npm start
👨💻 Development
⚠️ Development note:
The only command you need to care to update a package isnpm run commit
The entire process with lint, tests, build and publish is automated in the gitlab-ci.To create a new package, you can use the
create-package
script:npm run create-package <package-name>Then in the all package:
- add the dependency to the new package in the
package.json
- add the export of the new package and his types in the
index.ts
- reference the package in the
readme.md
If you want to manually execute the commands, you can find later in this section.
In order to be sure that the pipeline will pass, you can run the following commands, in that order, before pushing your changes:
npm run validate
⚠️ Commit note:
It is mandatory to use thenpm run commit
command to commit your changes. To commit, we use semantic-versioning and conventional commits.The commit command will ask you to choose a commit type (feat, fix, etc.) and a commit message.
The commit message will be used to generate the changelog.
The commit type will be used to determine the next version of the package.The version will be changed according to the following rules:
anything with breaking changed
: major versionfeat
: minor versionother
: patch versionVersions are defined this way for example for version 1.2.3:
- 1: major version
- 2: minor version
- 3: patch version
🔻 Install dependencies
npm install
🚨 Lint all packages
npm run lint:cmd
you can also lint a specific package:
npm run lint:cmd -- --scope @pricing-pact/priceit-ui.icon
🧪 Run tests
npm run test:cmd
you can also run tests in a specific package:
npm run test:cmd -- --scope @pricing-pact/priceit-ui.icon
🛠️ Build
npm run build:cmd
you can also build a specific package:
npm run build:cmd -- --scope @pricing-pact/priceit-ui.icon
note that the build command will automatically run the tests, the linting and prettier before building.
📦 Publish (used by the CI)
npm run publish:cmd:ci
🗒 Create a new package
To create a new package, you can use the create-package
script:
npm run create-package <package-name>
it will copy the template package and rename it to the given name.
Note about Changelog
When committing a new change, Commitizen will prompt you with the following questions which we'll go over below:
-
Select the type of change that you're committing
Keep the changes in each commit focused on 1 of these change types at a time.
For instance, instead of sprinkling in a new feature, some bug fixes, and documentation changes into 1 commit - try breaking these up into three smaller more focused commits.
Depending on which type of change you select, the end result may be not bumping a version at all, bumping a PATCH version (X.X.PATCH), or bumping a MINOR version (X.MINOR.X).| Change | Description | |----------|-------------------------------------------------------------------------------------------------------------| | feat | A new feature | | fix | A bug fix | | docs | Documentation only changes | | style | Changes that do not affect the meaning of the code (formatting) | | refactor | A code change that neither fixes a bug nor adds a feature | | perf | A code change that improves performance | | test | Adding missing tests or correct existing tests | | build | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) | | ci | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) | | chore | Other changes that don't modify src or test files | | revert | Reverts a previous commit |
-
What is the scope of this change (e.g. component or file name)
The answer to this question is not strict, this could be a specific file, a directory, or some abstract component in your codebase. This answer will show up in the commit logs and CHANGELOG.md which is useful for communicating what is the scope of the impact of the change. -
Write a short, imperative tense description of the change (max 86 chars)
The answer to this is really important for the CHANGELOG.md. Here you want to be succint in describing the change. -
Provide a longer description of the change
This should include the motivation for the change and contrast with previous behavior. This can be important especially later in a projects future for understanding the motivation of original contributers and can be useful in preventing tribal knowledge. -
Are there any breaking changes?
This one is critical. Whether your change is a bug fix, new feature, refactor, or any other type - if your change results in existing users needing to make code changes in order to install the new update, this would be indicative of a BREAKING CHANGE.An example of this in Typescript world would be adding an additional required argument to an existing function.
If you choose Yes, that this is a breaking change, the end result of merging to the main branch will be a MAJOR version update to the relevant package.
-
Does this change affect any open issues?
Here is where you can tag any open issues.
Note about publishing
The CI is configured to publish the packages automatically when a new tag is pushed on the allowed branches.
This is the recommended way to publish.
The files are published to the Gitlab package registry.
Anyway, it is still possible to publish manually if needed.
To do so, configure your .npmrc
file to use the @pricing-pact
scope.
The auth token is LERNA_PUBLISH_TOKEN
and is available in the CI/CD variables.
Your .npmrc
file should look like:
@pricing-pact:registry=https://gitlab.com/api/v4/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken=<authToken>
//gitlab.com/api/v4/projects/13170794/packages/npm/:_authToken=<authToken>
legacy-peer-deps=true
To publish, you can issue the following command (again, this is not recommended to do it manually):
npm run publish:cmd
Note about Lerna
Lerna is basically a tool that can run npm scripts in multiple packages at the same time with the run
command:
npx lerna run <command>
It is also possible to run a npm script in a specific package with the --scope
option:
npx lerna run <command> --scope @pricing-pact/priceit-ui.icon
You can also execute a more general command in all packages with the exec
command:
npx lerna exec -- <command> [..args]
For more information, you can check the Lerna documentation.
You also have the npx lerna --help
command.
Troubleshooting
-
If pre-commit file is not found, just change a line in the file and commit again.
-
Git HEAD detached in CI - had to do a
git checkout
in the CI script to fix this. -
If you see this kind of error while running
npm run create-package <package_name>
:> bash ./create-package.sh select ./create-package.sh: line 2: $'\r': command not found ./create-package.sh: line 5: $'\r': command not found ./create-package.sh: line 7: $'\r': command not found find: missing argument to `-exec' find: missing argument to `-exec' ./create-package.sh: line 11: $'\r': command not found ./create-package.sh: line 21: syntax error: unexpected end of file
you can, depending on your os, run the following command to fix it:
dos2unix create-package.sh
Resources
Some useful resources used to build this monorepo can be found here:
- Lerna walkthrough: https://www.youtube.com/watch?v=1oxFYphTS4Y
- Lerna + rollup + Typescript: https://github.com/eisberg-labs/react-components
- Husky: https://duncanlew.medium.com/getting-started-with-husky-and-lint-staged-for-pre-commit-hooks-c2764d8c9ae
- Storybook in CI: https://gist.github.com/donaldpipowitch/2590b20520b2cf6ae01aab4f7b55f8fa
- Git in docker: https://medium.com/@amreshy29/install-git-in-docker-10c07bad776d
- Publish to private npm registry: https://stackoverflow.com/questions/64367880/configure-lerna-to-publish-npm-packages-to-private-gitlab-repo
- Monorepo template: https://github.com/DmacMcgreg/Component-Library-Monorepo-Template-RollupJS-Lerna-Yarn-Workspaces
- Use git and authenticate in docker executor CI/CD: https://docs.gitlab.com/ee/ci/ssh_keys/
- Link storybook themes with our mode (light, dark): https://www.bekk.christmas/post/2021/3/storybook-background-change-on-prop-change
- Lerna in CI: https://www.delightfulengineering.com/blog/lerna-turbo-gitlab
Troubleshooting
Some issues that you can came accross during installation or start:
- In case you have an issue with
createSnapshot of undefined
, try issuing:npm i --save-dev html-webpack-plugin --legacy-peer-deps
source