-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs(TF-410): multibrand ci/cd #7401
Changes from 6 commits
288d752
a35ad2b
6684651
53f5377
6f44a36
45a8fd2
b140bed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,14 +7,382 @@ navigation: | |
|
||
# Deployment - CI/CD | ||
|
||
Running tests and deployments on your local machine works great during development, but let's be honest - what we really want is for these processes to happen automatically when we push our code. This guide explains how Continuous Integration (CI) and Continuous Deployment (CD) work in a multistore setup, helping you create an efficient and reliable deployment process that runs without manual intervention. | ||
|
||
::card{title="Next: CLI Reference" icon="tabler:number-3-small" } | ||
**What You'll Learn** | ||
|
||
::list{type="success"} | ||
- How to set up CI/CD pipeline using GitHub Actions | ||
- How to detect and process only changed stores | ||
- How to efficiently deploy multiple stores in parallel | ||
- Advanced topics like change detection mechanism and CLI-driven approach | ||
:: | ||
|
||
## Core Concepts | ||
|
||
### What is CI/CD in Multistore? | ||
|
||
In a multistore setup, CI/CD is designed to: | ||
1. Automatically detect which stores have changed | ||
2. Run tests only for affected stores | ||
3. Build and deploy only the changed stores | ||
|
||
This targeted approach ensures: | ||
- Faster deployments (only changed stores are processed) | ||
- Efficient resource usage | ||
- Less redundancy (unchanged stores are skipped in CI/CD pipeline) | ||
|
||
## Continuous Integration (CI) | ||
|
||
Let's look at how continuous integration works in a multistore setup, using GitHub Actions as an example while explaining how to adapt each step for other CI platforms. | ||
|
||
### 1. Setting Up Dependencies | ||
|
||
First, we need to set up the environment. Our setup action handles several crucial steps: | ||
|
||
::tip Why a Separate Setup Action? | ||
We use a separate setup action to follow the DRY (Don't Repeat Yourself) principle, as these setup steps are used in multiple workflows. We've implemented it as a [composite action](https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action) to make it reusable across different workflows. | ||
:: | ||
|
||
The Setup action performs these steps in sequence: | ||
|
||
1. **Node.js Setup** | ||
- Uses Node.js version specified in `.nvmrc` file | ||
|
||
2. **Cache Configuration** | ||
- Sets up Yarn cache for `node_modules` directory to speed up installations | ||
- Configures Turbo build cache for faster builds. Read more about Turbo CI caching [here](https://turbo.build/repo/docs/guides/ci-vendors). | ||
|
||
3. **NPM Registry Access** | ||
- Configures access to private npm registry | ||
- Uses provided credentials for authentication | ||
```bash | ||
# Install npm-cli-login globally | ||
npm install -g npm-cli-login | ||
|
||
# Configure access to Alokai Enterprise Registry | ||
npm-cli-login \ | ||
-u $NPM_USER \ | ||
-p $NPM_PASSWORD \ | ||
-e $NPM_EMAIL \ | ||
-r "https://registrynpm.storefrontcloud.io" | ||
|
||
# You can verify the configuration by running the following command: | ||
npm config get @alokai:registry | ||
# Should return: https://registrynpm.storefrontcloud.i | ||
``` | ||
|
||
4. **Dependencies Installation** | ||
- Installs project dependencies using Yarn | ||
- Uses frozen lockfile for consistency | ||
- Runs initialization script | ||
```bash | ||
# Install dependencies with frozen lockfile | ||
yarn install --frozen-lockfile --cache-folder .yarn | ||
|
||
# Run initialization script | ||
node init.mjs | ||
``` | ||
|
||
Here's how to use it in your workflow: | ||
|
||
```yaml | ||
steps: | ||
- name: Install dependencies | ||
uses: ./.github/actions/setup | ||
with: | ||
npm_user: ${{ vars.NPM_USER }} | ||
npm_password: ${{ secrets.NPM_PASS }} | ||
npm_email: ${{ vars.NPM_EMAIL }} | ||
``` | ||
|
||
::tip Adapting for Other CI Platforms | ||
Your CI platform needs to support: full git history access for change detection, Node.js environment setup, Yarn cache configuration, Turbo build cache setup for faster builds, and access to Alokai Enterprise Registry. | ||
:: | ||
|
||
### 2. Change Detection | ||
|
||
The change detection step identifies which stores have changed. Here's a simplified version of our approach: | ||
|
||
::tip | ||
While we recommend using change detection for efficiency, you can skip it and run actions for all stores using the `--all` flag: | ||
```bash | ||
# Run tests for all stores regardless of changes | ||
yarn store test --all | ||
|
||
# Deploy all stores | ||
yarn store deploy --all | ||
``` | ||
:: | ||
|
||
```yaml | ||
name: Affected Stores | ||
description: Determines which stores are affected | ||
|
||
inputs: | ||
since: | ||
description: Base commit for changes | ||
required: false | ||
to: | ||
description: Head commit for changes | ||
required: false | ||
|
||
runs: | ||
using: composite | ||
steps: | ||
- name: Find affected stores | ||
run: | | ||
# Basic change detection | ||
# Determine affected stores using the storefront CLI | ||
yarn store changed --condensed --since ${{ inputs.since }} --to ${{ inputs.to }} > changed_stores.json | ||
|
||
# Extract store IDs from the output | ||
STORES=$(jq -r '. | join(" ")' changed_stores.json) | ||
|
||
# Format output for CI system | ||
# Example: storeIds=["store1", "store2"] | ||
echo "storeIds=$(jq -c '.' changed_stores.json)" >> "$GITHUB_OUTPUT" | ||
# Example: storeIdsFlag=--store-id store1 store2 | ||
echo "storeIdsFlag=--store-id $STORES" >> "$GITHUB_OUTPUT" | ||
``` | ||
|
||
Important considerations when using `store changed` command: | ||
|
||
1. `--condensed` flag prints just store IDs of changed stores. To see detailed change information in logs: | ||
```bash | ||
# Remove --condensed flag to see why stores changed | ||
yarn store changed --since $SINCE_SHA --to $TO_SHA >> changed_stores.json | ||
echo "Changed stores report:" | ||
cat changed_stores.json | ||
``` | ||
Please note that as the format of the output changed, you should adjust the script to parse the store IDs from the new output. | ||
|
||
2. Handle cases when no stores changed: | ||
```bash | ||
if [ -z "$STORES" ]; then | ||
echo "No stores were affected by these changes" | ||
echo "storeIds=[]" >> "$GITHUB_OUTPUT" | ||
exit 0 | ||
fi | ||
``` | ||
|
||
3. Use correct commit references for pull requests: | ||
```bash | ||
# Base branch commit as starting point | ||
--since ${{ github.event.pull_request.base.sha }} | ||
# Branch HEAD as end point | ||
--to ${{ github.event.pull_request.head.sha }} | ||
``` | ||
|
||
### 3. Running Tests | ||
|
||
```yaml | ||
steps: | ||
- name: Run tests for affected stores | ||
run: | | ||
yarn store test --store-id $STORE_IDS | ||
``` | ||
|
||
## Continuous Deployment (CD) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We decided with @mateuszo that the points above this one are too detailed and we should keep them internally for our team. It could indeed make it much easier to leave Alokai. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So should then make a CI workflow a blackbox? Right now, all the code, which I describe here, will be delivered to the customer anyway There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want to ensure that you understand that the client will still receive this code, so if necessary it can be migrated when they understand it. Unless it’s us who don’t understand the case you’re referring to, in which case that’s fine. In any event, we just want confirmation that we are all on the same page and understand the consequences both of removing this from the docs and that the client still receives this code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fine with keeping the code white-boxed. I hope that the consequences of leaving this part undocumented would be that nobody touches that. Anyway, I don't have a strong opinion on removing this. It just makes the feature look more complex and non-service-like to me ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess we can black-box it later on, for now this seems to be an easy fix for what @mateuszo raised. Let's keep it on confluence tho There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, I removed the CI part and moved it to the Confluence |
||
|
||
::tip | ||
To learn how deployment works in detail, check out the [Deployment](/guides/multistore/tooling-and-concepts/deployment/deployment) guide. | ||
:: | ||
|
||
The continuous deployment process ensures that your stores are built and deployed efficiently. Here's how it works: | ||
|
||
### Deployment Triggers | ||
|
||
There are several ways to configure when your stores get deployed: | ||
|
||
#### Automatic Deployment | ||
Deploy automatically whenever code is pushed to the main branch: | ||
```yaml | ||
on: | ||
push: | ||
branches: [main] # Deploy on every push to main | ||
``` | ||
|
||
#### Manual Deployment | ||
Allow manual deployments with optional store selection: | ||
```yaml | ||
on: | ||
workflow_dispatch: | ||
inputs: | ||
store_ids: | ||
description: 'Space separated list of store IDs to deploy' | ||
required: false # When not provided, detect changed stores | ||
``` | ||
|
||
You can use the automatic deployment for staging and manual deployment for production environment. | ||
|
||
::tip Deployment Protection | ||
For Github Actions, you can use deployment protection rules to require manual approval before deployments. Check out the Github [docs](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments) for more details. | ||
grixu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:: | ||
|
||
### 1. Building and Deploying Stores | ||
|
||
The `store deploy` command handles both building and deploying your stores. There's no need to run a separate build step: | ||
|
||
```yaml | ||
steps: | ||
- name: Deploy stores | ||
run: | | ||
yarn store deploy \ | ||
--cloud-username ${{ vars.CLOUD_USERNAME }} \ | ||
--cloud-password ${{ secrets.CLOUD_PASSWORD }} \ | ||
$storeIdsFlag \ | ||
--verbose | ||
``` | ||
|
||
### 2. Parallel Deployment | ||
|
||
For efficient deployment of multiple stores, use a matrix strategy: | ||
|
||
```yaml | ||
jobs: | ||
deploy: | ||
strategy: | ||
matrix: | ||
store_id: ${{ fromJson(storeIds) }} | ||
steps: | ||
- name: Deploy store | ||
run: | | ||
yarn store deploy \ | ||
--cloud-username ${{ vars.CLOUD_USERNAME }} \ | ||
--cloud-password ${{ secrets.CLOUD_PASSWORD }} \ | ||
--store-id ${{ matrix.store_id }} \ | ||
--verbose | ||
``` | ||
|
||
## Advanced Topics | ||
|
||
### Understanding Change Detection | ||
|
||
The change detection system is a core part of our CI/CD pipeline, implemented in the CLI, with `yarn store changed` command, to ensure consistent behavior across different CI platforms. Here's how it works: | ||
|
||
The change detection system analyzes git differences and determines which stores are affected based on several rules: | ||
|
||
1. **Root Changes** | ||
When files in the base applications change (e.g., `/apps/storefront-middleware/src/index.ts`), all stores inheriting from that application are affected, unless they override the changed file. | ||
|
||
Example: A change in base middleware: | ||
- Changed file: `/apps/storefront-middleware/src/index.ts` | ||
- Affected stores: All stores which don't have their own version of the file | ||
|
||
2. **Parent Store Changes** | ||
When a parent store changes, all its child stores are affected, unless they override the changed file. | ||
|
||
Example: A change in parent store: | ||
- Changed file: `/apps/stores/fashion-brand/middleware/index.ts` | ||
- Affected stores: All stores which don't have their own version of the file | ||
|
||
3. **Direct Store Changes** | ||
When files within a store directory change, only that store is affected. | ||
|
||
Example: A change in specific store: | ||
- Changed file: `/apps/stores/sports-brand/middleware/index.ts` | ||
- Affected store: only `sports-brand` | ||
|
||
4. **Dependency Changes** | ||
Changes to dependency files (e.g., `yarn.lock`) affect all stores. | ||
|
||
Example: A change in dependencies: | ||
- Changed file: `yarn.lock` | ||
- Affected: All stores | ||
|
||
5. **Global Dependencies** | ||
Changes to globally configured paths (e.g., shared packages) affect all stores. You can mark global dependencies for the `yarn store changed` command by adding the `--global-dependencies` flag. For example: | ||
```bash | ||
# Mark all packages in the packages directory as global dependencies | ||
yarn store changed --global-dependencies="packages/**" | ||
``` | ||
|
||
6. **File Overrides** | ||
If a store overrides a file from its parent or base application, changes to the original file won't affect that store. | ||
|
||
Example: A file override: | ||
- Changed file: `/apps/stores/fashion-brand/middleware/index.ts` | ||
- Skipped: `fashion-brand-us` (has its own version of the file) | ||
- Affected: Other child stores without overrides | ||
|
||
The system provides detailed information about why each store was affected: | ||
- `STORE_CHANGED`: Direct changes to the store | ||
- `ANCESTOR_CHANGED`: Changes in parent store or base application | ||
- `PACKAGE_LOCK_CHANGED`: Dependency file changes | ||
- `GLOBAL_DEPENDENCIES_CHANGED`: Changes in globally configured paths | ||
|
||
::tip | ||
When debugging why a particular store was affected, run the `yarn store changed` command without the `--condensed` flag to see the detailed change report. You can also run the command locally to debug the changes. | ||
:: | ||
|
||
::info Why not use Turbo for change detection? | ||
While Turborepo is great for monorepo task orchestration, it can't handle our dynamic store composition. Our stores are composed into the `.out` directory, which is git-ignored. Turbo relies on git history for change detection, but it can't track how these dynamically composed stores in `.out` directory change. That's why we've implemented our own change detection system that understands store inheritance and file overrides. | ||
:: | ||
|
||
### Why Parallel Store Deployments? | ||
|
||
We recommend running deployments in parallel (one job per store) for several reasons: | ||
|
||
1. **Isolation** | ||
- Each store deployment runs in its own environment | ||
- Failures in one store don't affect others | ||
- Resource limits are per-store, preventing one store from consuming all resources | ||
|
||
2. **Performance** | ||
- Multiple stores deploy simultaneously | ||
- Overall deployment time is significantly reduced | ||
|
||
3. **Maintainability** | ||
- Easy to retry failed deployments | ||
- Clear logs per store | ||
- Simpler debugging and monitoring | ||
|
||
### CLI-Driven Approach | ||
|
||
Our CI/CD heavily relies on the CLI for several important reasons: | ||
|
||
1. **Platform Independence** | ||
- The same commands work across all CI providers | ||
- No need to rewrite logic for different platforms | ||
- Consistent behavior everywhere | ||
|
||
2. **Local Build and Development Parity** | ||
- Developers can run the same commands locally | ||
- Easy to debug CI/CD issues | ||
- No surprises between local and CI environments | ||
|
||
3. **Encapsulated Logic** | ||
- Complex operations are packaged in simple commands | ||
- CI configuration focuses on workflow, not implementation | ||
- Updates to deployment logic don't require CI changes | ||
|
||
For example, instead of implementing store change detection in CI: | ||
```yaml | ||
# Don't do this | ||
- name: Detect changes | ||
run: | | ||
# Complex git diff logic | ||
# Parse file paths | ||
# Check store inheritance | ||
# Handle overrides | ||
``` | ||
|
||
We use a single CLI command: | ||
```yaml | ||
# Do this instead | ||
- name: Detect changes | ||
run: yarn store changed --since $SINCE_SHA --to $TO_SHA | ||
``` | ||
|
||
This approach makes it easy to implement our CI/CD pipeline in any CI system that can run shell commands. | ||
|
||
::card{title="Next: CLI Reference" icon="tabler:number-3-small" } | ||
#description | ||
TODO | ||
Learn more about the CLI commands used in CI/CD pipelines. | ||
|
||
#cta | ||
:::docs-arrow-link{to="/guides/multistore/tooling-and-concepts/cli-reference"} | ||
Next | ||
::: | ||
:: | ||
:: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having all the steps listed here and explained briefly would be nice. An image visualizing this would be superb ;)