From db0a868605f181f7ec858f652fc82add88569a07 Mon Sep 17 00:00:00 2001 From: Patryk Ziemkowski Date: Thu, 15 Feb 2024 11:37:28 +0100 Subject: [PATCH 1/3] fix: Prevent NX from loading .env files when running CLI scripts. (#477) --- package.json | 9 +++++++-- packages/backend/scripts/build.js | 3 ++- packages/internal/cli/src/config/env.ts | 4 ++++ packages/internal/cli/src/config/init.ts | 3 ++- packages/workers/Dockerfile | 1 + patches/nx@17.1.3.patch | 13 +++++++++++++ pnpm-lock.yaml | 20 +++++++++++++------- 7 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 patches/nx@17.1.3.patch diff --git a/package.json b/package.json index ecb46f158..2658e6d90 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@graphql-codegen/cli": "^5.0.0", "@graphql-typed-document-node/core": "^3.2.0", "@nx/devkit": "17.1.3", + "@nx/eslint": "17.1.3", "@nx/eslint-plugin": "17.1.3", "@nx/jest": "17.1.3", "@nx/js": "17.1.3", @@ -83,8 +84,7 @@ "vite": "^4.5.0", "vite-plugin-eslint": "^1.8.1", "vite-plugin-svgr": "^3.3.0", - "vite-tsconfig-paths": "^4.2.1", - "@nx/eslint": "17.1.3" + "vite-tsconfig-paths": "^4.2.1" }, "dependencies": { "@aws-sdk/client-cloudformation": "^3.462.0", @@ -113,5 +113,10 @@ "react-router-dom": "6.16.0", "regenerator-runtime": "^0.14.0", "styled-components": "6.0.8" + }, + "pnpm": { + "patchedDependencies": { + "nx@17.1.3": "patches/nx@17.1.3.patch" + } } } diff --git a/packages/backend/scripts/build.js b/packages/backend/scripts/build.js index 8e9a1437e..41d14a2ca 100644 --- a/packages/backend/scripts/build.js +++ b/packages/backend/scripts/build.js @@ -7,7 +7,8 @@ const AWS_DEFAULT_REGION = process.env.AWS_DEFAULT_REGION; const PROJECT_NAME = process.env.PROJECT_NAME; const VERSION = process.env.VERSION; const SB_MIRROR_REPOSITORY = process.env.SB_MIRROR_REPOSITORY ?? ''; -const SB_PULL_THROUGH_CACHE_REPOSITORY = process.env.SB_PULL_THROUGH_CACHE_REPOSITORY ?? ''; +const SB_PULL_THROUGH_CACHE_REPOSITORY = + process.env.SB_PULL_THROUGH_CACHE_REPOSITORY ?? ''; const stsClient = new STSClient(); diff --git a/packages/internal/cli/src/config/env.ts b/packages/internal/cli/src/config/env.ts index 7d0c9349d..33d43dad2 100644 --- a/packages/internal/cli/src/config/env.ts +++ b/packages/internal/cli/src/config/env.ts @@ -48,6 +48,10 @@ export async function loadVersionEnv() { return version; } +export const disableNxEnvFiles = () => { + process.env.NX_LOAD_DOT_ENV_FILES = 'false'; +}; + export async function validateStageEnv() { return envalid.cleanEnv(process.env, { PROJECT_NAME: envalid.str({ diff --git a/packages/internal/cli/src/config/init.ts b/packages/internal/cli/src/config/init.ts index acf499a19..a1ba83ead 100644 --- a/packages/internal/cli/src/config/init.ts +++ b/packages/internal/cli/src/config/init.ts @@ -2,7 +2,7 @@ import { Command } from '@oclif/core'; import { color } from '@oclif/color'; import { trace } from '@opentelemetry/api'; -import { ENV_STAGE_LOCAL, getRootPath, loadVersionEnv } from './env'; +import { ENV_STAGE_LOCAL, getRootPath, loadVersionEnv, disableNxEnvFiles } from './env'; import { initAWS } from './aws'; import { loadEnvStage } from './storage'; @@ -26,6 +26,7 @@ export const initConfig = async ( const rootPath = await getRootPath(); const version = await loadVersionEnv(); const envStage = await loadEnvStage(); + disableNxEnvFiles(); const projectName = process.env.PROJECT_NAME; if (!projectName) { diff --git a/packages/workers/Dockerfile b/packages/workers/Dockerfile index b56b52a04..19e981db7 100644 --- a/packages/workers/Dockerfile +++ b/packages/workers/Dockerfile @@ -34,6 +34,7 @@ COPY packages/workers/pdm.lock packages/workers/pyproject.toml packages/workers/ RUN pdm sync WORKDIR $APP_PATH +COPY /patches/ $APP_PATH/patches/ COPY package.json pnpm*.yaml $APP_PATH/ COPY $SRC_CORE_PATH/package.json $DEST_CORE_PATH/ COPY $SRC_WORKERS_PATH/package.json $DEST_WORKERS_PATH/ diff --git a/patches/nx@17.1.3.patch b/patches/nx@17.1.3.patch new file mode 100644 index 000000000..dee8f071f --- /dev/null +++ b/patches/nx@17.1.3.patch @@ -0,0 +1,13 @@ +diff --git a/src/tasks-runner/run-command.js b/src/tasks-runner/run-command.js +index cd830d3c34639a5d8a52c12b3975e8c309ba8f9b..8300356271db5e2a815d3b51443a5f6682858181 100644 +--- a/src/tasks-runner/run-command.js ++++ b/src/tasks-runner/run-command.js +@@ -114,7 +114,7 @@ function setEnvVarsBasedOnArgs(nxArgs, loadDotEnvFiles) { + if (nxArgs.outputStyle == 'stream-without-prefixes') { + process.env.NX_STREAM_OUTPUT = 'true'; + } +- if (loadDotEnvFiles) { ++ if (loadDotEnvFiles && !process.env.NX_LOAD_DOT_ENV_FILES) { + process.env.NX_LOAD_DOT_ENV_FILES = 'true'; + } + } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5dfaacbc5..116a96939 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,14 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + nx@17.1.3: + hash: gccie2giwxnrau2zf7la4up2fa + path: patches/nx@17.1.3.patch + importers: .: @@ -272,7 +277,7 @@ importers: version: 14.0.1 nx: specifier: 17.1.3 - version: 17.1.3 + version: 17.1.3(patch_hash=gccie2giwxnrau2zf7la4up2fa) nx-cloud: specifier: 16.5.2 version: 16.5.2 @@ -8623,7 +8628,7 @@ packages: resolution: {integrity: sha512-9YpfEkUpVqOweqgQvMDcWApNx4jhCqBNH5IByZj302Enp3TLnQSvhuX5Dfr8hNQRQokIpEn6tW8SGTctTM5LXw==} hasBin: true dependencies: - nx: 17.1.3 + nx: 17.1.3(patch_hash=gccie2giwxnrau2zf7la4up2fa) tslib: 2.6.2 transitivePeerDependencies: - '@swc-node/register' @@ -8699,7 +8704,7 @@ packages: ejs: 3.1.9 enquirer: 2.3.6 ignore: 5.3.0 - nx: 17.1.3 + nx: 17.1.3(patch_hash=gccie2giwxnrau2zf7la4up2fa) semver: 7.5.3 tmp: 0.2.1 tslib: 2.6.2 @@ -9135,7 +9140,7 @@ packages: '@nx/devkit': 17.1.3(nx@17.1.3) chalk: 4.1.2 enquirer: 2.3.6 - nx: 17.1.3 + nx: 17.1.3(patch_hash=gccie2giwxnrau2zf7la4up2fa) tslib: 2.6.2 yargs-parser: 21.1.1 transitivePeerDependencies: @@ -15241,7 +15246,7 @@ packages: peerDependencies: postcss: ^8.1.0 dependencies: - browserslist: 4.22.2 + browserslist: 4.21.10 caniuse-lite: 1.0.30001538 fraction.js: 4.3.6 normalize-range: 0.1.2 @@ -26632,7 +26637,7 @@ packages: - debug dev: true - /nx@17.1.3: + /nx@17.1.3(patch_hash=gccie2giwxnrau2zf7la4up2fa): resolution: {integrity: sha512-6LYoTt01nS1d/dvvYtRs+pEAMQmUVsd2fr/a8+X1cDjWrb8wsf1O3DwlBTqKOXOazpS3eOr0Ukc9N1svbu7uXA==} hasBin: true requiresBuild: true @@ -26694,6 +26699,7 @@ packages: transitivePeerDependencies: - debug dev: true + patched: true /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} From d4d5d042dd848d97a2bd385afdaf8d24ae83b23b Mon Sep 17 00:00:00 2001 From: mkleszcz Date: Mon, 26 Feb 2024 12:38:08 +0100 Subject: [PATCH 2/3] feat: #479 Introduce CI mode (#482) --- .../infra/infra-core/src/lib/env-config.ts | 17 +++++++++++ .../src/lib/patterns/serviceCiConfig.ts | 11 ++++++++ .../infra-shared/src/stacks/ci/ciBackend.ts | 17 +++++------ .../src/stacks/ci/ciComponents.ts | 2 +- .../infra-shared/src/stacks/ci/ciDocs.ts | 3 +- .../infra-shared/src/stacks/ci/ciPipeline.ts | 4 +-- .../src/stacks/ci/ciServerless.ts | 3 +- .../src/stacks/ci/ciUploadVersion.ts | 2 +- .../infra-shared/src/stacks/ci/ciWebApp.ts | 3 +- .../create-env-stage-in-repo.mdx | 28 ++++++++++++++++++- 10 files changed, 74 insertions(+), 16 deletions(-) diff --git a/packages/infra/infra-core/src/lib/env-config.ts b/packages/infra/infra-core/src/lib/env-config.ts index a8bc63ed8..3071c9abe 100644 --- a/packages/infra/infra-core/src/lib/env-config.ts +++ b/packages/infra/infra-core/src/lib/env-config.ts @@ -22,6 +22,7 @@ declare const process: { SB_TOOLS_HOSTED_ZONE_NAME: string; SB_TOOLS_HOSTED_ZONE_ID: string; SB_TOOLS_DOMAIN_VERSION_MATRIX: string; + SB_CI_MODE: string; }; }; @@ -64,6 +65,15 @@ interface WebAppConfig { envVariables: EnvironmentVariables; } +export enum CI_MODE { + PARALLEL = 'parallel', + SIMPLE = 'simple', +} + +interface CIConfig { + mode: CI_MODE; +} + export interface EnvironmentSettings { appBasicAuth: string | null | undefined; deployBranches: Array; @@ -77,11 +87,13 @@ export interface EnvironmentSettings { version: string; webAppEnvVariables: EnvironmentVariables; certificates: CertificatesConfig; + CIConfig: CIConfig; } interface ConfigFileContent { toolsConfig: ToolsConfig; webAppConfig: WebAppConfig; + CIConfig: CIConfig; } export interface EnvConfigFileContent { @@ -109,6 +121,10 @@ async function readConfig(): Promise { versionMatrix: process.env.SB_TOOLS_DOMAIN_VERSION_MATRIX, }, }, + CIConfig: { + mode: process.env.SB_CI_MODE === CI_MODE.SIMPLE + ? CI_MODE.SIMPLE : CI_MODE.PARALLEL, + } }; } @@ -176,5 +192,6 @@ export async function loadEnvSettings(): Promise { ...(envConfig?.webAppConfig?.envVariables || {}), }, certificates: envConfig.certificates, + CIConfig: config.CIConfig, }; } diff --git a/packages/infra/infra-core/src/lib/patterns/serviceCiConfig.ts b/packages/infra/infra-core/src/lib/patterns/serviceCiConfig.ts index 20ff6a289..0d967e4b5 100644 --- a/packages/infra/infra-core/src/lib/patterns/serviceCiConfig.ts +++ b/packages/infra/infra-core/src/lib/patterns/serviceCiConfig.ts @@ -1,6 +1,8 @@ import { Construct } from 'constructs'; import * as codebuild from 'aws-cdk-lib/aws-codebuild'; import { EnvConstructProps } from '../constructs'; +import { CI_MODE } from '../env-config'; +import { IStage } from 'aws-cdk-lib/aws-codepipeline'; export interface IServiceCiConfig { defaultEnvVariables: { @@ -22,9 +24,11 @@ export enum PnpmWorkspaceFilters { export class ServiceCiConfig extends Construct implements IServiceCiConfig { defaultEnvVariables: { [p: string]: codebuild.BuildEnvironmentVariable }; defaultCachePaths: string[]; + props: EnvConstructProps; constructor(scope: Construct, id: string, props: EnvConstructProps) { super(scope, id); + this.props = props; this.defaultEnvVariables = { CI: { @@ -69,4 +73,11 @@ export class ServiceCiConfig extends Construct implements IServiceCiConfig { 'export AWS_SESSION_TOKEN=$(echo "${TEMP_ROLE}" | jq -r \'.Credentials.SessionToken\')', ]; } + + protected getRunOrder(stage: IStage, defaultRunOrder?: number) { + if (this.props.envSettings.CIConfig.mode === CI_MODE.PARALLEL) { + return defaultRunOrder; + } + return stage.actions.length + 1; + } } diff --git a/packages/infra/infra-shared/src/stacks/ci/ciBackend.ts b/packages/infra/infra-shared/src/stacks/ci/ciBackend.ts index c4d6ac9fa..1991cee25 100644 --- a/packages/infra/infra-shared/src/stacks/ci/ciBackend.ts +++ b/packages/infra/infra-shared/src/stacks/ci/ciBackend.ts @@ -36,25 +36,25 @@ export class BackendCiConfig extends ServiceCiConfig { ), ); - const apiDeployProject = this.createApiDeployProject(props); + const migrationsDeployProject = this.createMigrationsDeployProject(props); props.deployStage.addAction( this.createDeployAction( - 'api', + 'migrations', { - project: apiDeployProject, - runOrder: 2, + project: migrationsDeployProject, + runOrder: this.getRunOrder(props.deployStage, 2), }, props, ), ); - const migrationsDeployProject = this.createMigrationsDeployProject(props); + const apiDeployProject = this.createApiDeployProject(props); props.deployStage.addAction( this.createDeployAction( - 'migrations', + 'api', { - project: migrationsDeployProject, - runOrder: 2, + project: apiDeployProject, + runOrder: this.getRunOrder(props.deployStage, 2), }, props, ), @@ -72,6 +72,7 @@ export class BackendCiConfig extends ServiceCiConfig { actionName: `${props.envSettings.projectEnvName}-build-${name}`, project: actionProps.project, input: props.inputArtifact, + runOrder: this.getRunOrder(props.buildStage), }); } diff --git a/packages/infra/infra-shared/src/stacks/ci/ciComponents.ts b/packages/infra/infra-shared/src/stacks/ci/ciComponents.ts index 02a3ca6a3..ea8feca45 100644 --- a/packages/infra/infra-shared/src/stacks/ci/ciComponents.ts +++ b/packages/infra/infra-shared/src/stacks/ci/ciComponents.ts @@ -51,7 +51,7 @@ export class ComponentsCiConfig extends ServiceCiConfig { project: actionProps.project, actionName: `${props.envSettings.projectEnvName}-deploy-components`, input: props.inputArtifact, - runOrder: 1, + runOrder: this.getRunOrder(props.deployStage, 1), }); } diff --git a/packages/infra/infra-shared/src/stacks/ci/ciDocs.ts b/packages/infra/infra-shared/src/stacks/ci/ciDocs.ts index 2ed8b2a3e..6d85db4ab 100644 --- a/packages/infra/infra-shared/src/stacks/ci/ciDocs.ts +++ b/packages/infra/infra-shared/src/stacks/ci/ciDocs.ts @@ -45,7 +45,7 @@ export class DocsCiConfig extends ServiceCiConfig { { project: deployProject, input: buildArtifact, - runOrder: 2, + runOrder: this.getRunOrder(props.deployStage, 2), }, props, ), @@ -61,6 +61,7 @@ export class DocsCiConfig extends ServiceCiConfig { >{ ...actionProps, actionName: `${props.envSettings.projectEnvName}-build-docs`, + runOrder: this.getRunOrder(props.buildStage) }); } diff --git a/packages/infra/infra-shared/src/stacks/ci/ciPipeline.ts b/packages/infra/infra-shared/src/stacks/ci/ciPipeline.ts index 3f6fb4b16..131e210ef 100644 --- a/packages/infra/infra-shared/src/stacks/ci/ciPipeline.ts +++ b/packages/infra/infra-shared/src/stacks/ci/ciPipeline.ts @@ -66,14 +66,14 @@ export class CiPipeline extends Construct { inputArtifact: sourceOutputArtifact, }); - new DocsCiConfig(this, 'DocsConfig', { + new ServerlessCiConfig(this, 'WorkersConfig', { envSettings: props.envSettings, buildStage, deployStage, inputArtifact: sourceOutputArtifact, }); - new ServerlessCiConfig(this, 'WorkersConfig', { + new DocsCiConfig(this, 'DocsConfig', { envSettings: props.envSettings, buildStage, deployStage, diff --git a/packages/infra/infra-shared/src/stacks/ci/ciServerless.ts b/packages/infra/infra-shared/src/stacks/ci/ciServerless.ts index 4be98ac5a..49d0dc85a 100644 --- a/packages/infra/infra-shared/src/stacks/ci/ciServerless.ts +++ b/packages/infra/infra-shared/src/stacks/ci/ciServerless.ts @@ -45,7 +45,7 @@ export class ServerlessCiConfig extends ServiceCiConfig { { project: deployProject, input: buildArtifact, - runOrder: 2, + runOrder: this.getRunOrder(props.buildStage, 2), }, props, ), @@ -61,6 +61,7 @@ export class ServerlessCiConfig extends ServiceCiConfig { >{ ...actionProps, actionName: `${props.envSettings.projectEnvName}-build-workers`, + runOrder: this.getRunOrder(props.deployStage), }); } diff --git a/packages/infra/infra-shared/src/stacks/ci/ciUploadVersion.ts b/packages/infra/infra-shared/src/stacks/ci/ciUploadVersion.ts index ab309930c..bfda2fee7 100644 --- a/packages/infra/infra-shared/src/stacks/ci/ciUploadVersion.ts +++ b/packages/infra/infra-shared/src/stacks/ci/ciUploadVersion.ts @@ -26,7 +26,7 @@ export class UploadVersionCiConfig extends ServiceCiConfig { { project: deployProject, input: props.inputArtifact, - runOrder: 3, + runOrder: this.getRunOrder(props.stage, 3), }, props ) diff --git a/packages/infra/infra-shared/src/stacks/ci/ciWebApp.ts b/packages/infra/infra-shared/src/stacks/ci/ciWebApp.ts index 522d950e6..e54575031 100644 --- a/packages/infra/infra-shared/src/stacks/ci/ciWebApp.ts +++ b/packages/infra/infra-shared/src/stacks/ci/ciWebApp.ts @@ -45,7 +45,7 @@ export class WebappCiConfig extends ServiceCiConfig { { project: deployProject, input: buildArtifact, - runOrder: 2, + runOrder: this.getRunOrder(props.deployStage, 2), }, props, ), @@ -61,6 +61,7 @@ export class WebappCiConfig extends ServiceCiConfig { >{ ...actionProps, actionName: `${props.envSettings.projectEnvName}-build-webapp`, + runOrder: this.getRunOrder(props.buildStage) }); } diff --git a/packages/internal/docs/docs/aws/deploy-to-aws/create-env-stage-in-repo.mdx b/packages/internal/docs/docs/aws/deploy-to-aws/create-env-stage-in-repo.mdx index 2790532c3..c93d1ae9c 100644 --- a/packages/internal/docs/docs/aws/deploy-to-aws/create-env-stage-in-repo.mdx +++ b/packages/internal/docs/docs/aws/deploy-to-aws/create-env-stage-in-repo.mdx @@ -87,7 +87,7 @@ CNAME DNS records pointing to CloudFront distribution and Load Balancer need to ### \[Optional\] Configure production domain -SaaS Boilerplate creates a certificate that supports `*.[ENV_STAGE].example.com` domains, like + creates a certificate that supports `*.[ENV_STAGE].example.com` domains, like `app.production.example.com`. You most likely don't want such domain and need straight up `app.example.com` for your production environment. Set a following variable to override the default behaviour: @@ -101,6 +101,32 @@ Resource handler returned message: "Invalid request provided: AWS::CloudFront::D attached to your distribution doesn't cover the alternate domain name (CNAME) that you're trying to add. ``` +### [Optional] Configuring CI mode + +:::caution +Deploying `` on a **new AWS account** may result in reaching the AWS CodeBuild concurrent build limit. +This is a common issue with newly created AWS accounts. To mitigate potential issues during CI builds, it is advisable +to configure the CI mode to `simple`. For more information about potential issues with new AWS accounts, see +[Configuring AWS Credentials for New Accounts](./configure-aws-credentials#for-users-with-newly-created-aws-accounts). +::: + +Changing the CI mode affects the execution behavior of CI processes: +- `parallel` (the default setting): Enables actions in the AWS CodePipeline stages to execute concurrently. +- `simple`: Ensures actions in the AWS CodePipeline stages are executed sequentially, with only one CodeBuild process +running at a time. + +We recommend utilizing the `parallel` mode for optimal performance and efficiency, provided that the concurrent build +limits of AWS CodeBuild have been adjusted accordingly. + +```shell +pnpm saas aws set-var SB_CI_MODE +``` + +:::info +Note: If you modify the settings for an already deployed environment, it is necessary to +[redeploy the CI stack](./deploy-infrastructure#deploy-the-infrastructure) to ensure that the changes take effect. +::: + ### \[Optional\] Protect the website with basic auth It's a good idea to prevent unauthorized access to staging environments so we suggest to set a basic auth password From d50c53bb913e89acb1b3e7b5d140d034bd81d51d Mon Sep 17 00:00:00 2001 From: mkleszcz Date: Mon, 26 Feb 2024 12:39:34 +0100 Subject: [PATCH 3/3] fix: #478 Fix CI pipeline failing on version upload step when tools are disabled (status dashboard stack is not deployed) (#481) --- .../infra/infra-shared/src/stacks/ci/ciPipeline.ts | 12 +++++++----- .../aws/deploy-to-aws/create-env-stage-in-repo.mdx | 12 ++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/infra/infra-shared/src/stacks/ci/ciPipeline.ts b/packages/infra/infra-shared/src/stacks/ci/ciPipeline.ts index 131e210ef..5d6e14654 100644 --- a/packages/infra/infra-shared/src/stacks/ci/ciPipeline.ts +++ b/packages/infra/infra-shared/src/stacks/ci/ciPipeline.ts @@ -80,11 +80,13 @@ export class CiPipeline extends Construct { inputArtifact: sourceOutputArtifact, }); - new UploadVersionCiConfig(this, 'UploadVersionConfig', { - envSettings: props.envSettings, - stage: deployStage, - inputArtifact: sourceOutputArtifact, - }); + if (props.envSettings.tools.enabled) { + new UploadVersionCiConfig(this, 'UploadVersionConfig', { + envSettings: props.envSettings, + stage: deployStage, + inputArtifact: sourceOutputArtifact, + }); + } } private selectStage(name: string, pipeline: Pipeline) { diff --git a/packages/internal/docs/docs/aws/deploy-to-aws/create-env-stage-in-repo.mdx b/packages/internal/docs/docs/aws/deploy-to-aws/create-env-stage-in-repo.mdx index c93d1ae9c..d83df1dc0 100644 --- a/packages/internal/docs/docs/aws/deploy-to-aws/create-env-stage-in-repo.mdx +++ b/packages/internal/docs/docs/aws/deploy-to-aws/create-env-stage-in-repo.mdx @@ -147,6 +147,10 @@ pnpm saas aws set-var SB_DEPLOY_BRANCHES master ### \[Optional\] Set tools env variables +The `tools` package during CI deployment is responsible for uploading the information of the version of deployed +application on specific environment. Then, you will be able to check currently deployed state in the +[version matrix](../../../working-with-sb/dev-tools/version-matrix). + To configure `tools` package env variables follow the example below. ```shell @@ -157,6 +161,14 @@ pnpm saas aws set-var SB_TOOLS_HOSTED_ZONE_NAME example.com pnpm saas aws set-var SB_TOOLS_DOMAIN_VERSION_MATRIX status.example.com ``` +:::info + +If you set `SB_TOOLS_ENABLED` to `true` make sure to deploy **version matrix** component before first CI pipeline run. + +Follow the [deployment instructions](../../../working-with-sb/dev-tools/version-matrix#deployment) for more details. + +::: + ## Env variable validation You can look up the validator in `packages/internal/cli/src/config/env.ts`, which runs on every `nx` command that