Vue Storefront is now Alokai! Learn More
Migration Guide

Migration Guide

Prerequisites

This guide assumes you are using Alokai Ecosystem version 0.1.0. For details about this version, please refer to the changelog.

Project Structure After Migration to Multistore

Before we begin, here's what your project structure will look like after completing this migration:

apps/
├── playwright/
├── storefront-unified-nextjs/
├── storefront-unified-middleware/
└── stores/
    ├── default/
    │   └── playwright/
    │   └── storefront-unified-nextjs/
    │   └── storefront-middleware/
packages/
  eslint-config/
  lint-staged-config/
  prettier-config/
  tailwind-config/

File-based Inheritance

The structure above enables Alokai's powerful file-based inheritance system, allowing you to share code efficiently across multiple stores while maintaining the ability to customize specific parts for each brand. Learn more about file-based inheritance.

Migration Steps

1

Set Up Shared Configuration Packages

The first step in migrating to Alokai Multistore is setting up shared configuration packages. These packages help maintain consistency across multiple stores by:

  • Centralizing common configuration like ESLint, Prettier, and Tailwind settings
  • Reducing duplication of configuration files
  • Making it easier to update configurations across all stores
  • Enforcing consistent code style and quality standards

This approach follows the DRY (Don't Repeat Yourself) principle and makes maintenance more manageable as your multistore setup grows.

  1. Create the following directory structure:
mkdir -p packages/{eslint-config,prettier-config,lint-staged-config,tailwind-config}
  1. Set up ESLint configuration:

Create packages/eslint-config/middleware.mjs:

import { concat, ecma, style, typescript } from "@vue-storefront/eslint-config";
import gitignore from "eslint-config-flat-gitignore";

export default concat(
  gitignore(),
  ecma(),
  typescript(
    {},
    {
      files: ["**/*.{ts,tsx}"],
      rules: {
        "@typescript-eslint/no-empty-object-type": "off",
        "@typescript-eslint/no-magic-numbers": "off",
        "import/no-unresolved": "off",
      },
    },
  ),
  style(),
);

Create packages/eslint-config/playwright.mjs:

import { concat, ecma, playwright, style, typescript } from '@vue-storefront/eslint-config';
import gitignore from 'eslint-config-flat-gitignore';

export default concat(
  gitignore(),
  ecma(),
  typescript(
    {
      files: '**/*.ts',
    },
    {
      files: ['setup/**/*.ts'],
      rules: {
        '@typescript-eslint/no-unused-expressions': 'off',
      },
    },
    {
      files: ['**/*.ts'],
      rules: {
        '@typescript-eslint/explicit-member-accessibility': 'off',
        '@typescript-eslint/explicit-module-boundary-types': 'off',
        '@typescript-eslint/no-empty-object-type': 'off',
        '@typescript-eslint/no-magic-numbers': 'off',
      },
    },
  ),
  style(),
  playwright({
    files: ['**/*.test.ts', 'sf-modules/**/*.test.ts'],
  }),
);

Create packages/eslint-config/nextjs.mjs:

import { concat, ecma, nextjs, style, typescript } from '@vue-storefront/eslint-config';
import gitignore from 'eslint-config-flat-gitignore';

export default concat(
  gitignore(),
  ecma(),
  typescript(
    {
      files: '**/*.{ts,tsx}',
    },
    {
      files: ['**/*.{ts,tsx}'],
      rules: {
        '@typescript-eslint/consistent-type-imports': 'off',
        '@typescript-eslint/explicit-module-boundary-types': 'off',
        '@typescript-eslint/no-empty-object-type': 'off',
        '@typescript-eslint/no-magic-numbers': 'off',
        'import/no-unresolved': 'off',
      },
    },
  ),
  nextjs({
    files: {
      components: ['components/**/*.{js,jsx,ts,tsx}', 'app/**/components/*.{js,jsx,ts,tsx}'],
      general: '**/*.{js,jsx,ts,tsx}',
      hooks: '{hooks,helpers}/**/*.{js,jsx,ts,tsx}',
    },
  }),
  style(),
);

Create packages/eslint-config/index.mjs:

export * from './middleware.mjs';
export * from './nextjs.mjs';
export * from './playwright.mjs';

Create packages/eslint-config/package.json:

{
  "name": "eslint-config",
  "version": "0.0.1",
  "private": true,
  "exports": {
    ".": {
      "import": "./index.mjs"
    },
    "./middleware": {
      "import": "./middleware.mjs"
    },
    "./nextjs": {
      "import": "./nextjs.mjs"
    },
    "./playwright": {
      "import": "./playwright.mjs"
    }
  },
  "devDependencies": {
    "@vue-storefront/eslint-config": "4.1.0",
    "eslint-config-flat-gitignore": "0.3.0"
  }
}
  1. Set up Prettier configuration:

Create packages/prettier-config/middleware.mjs:

export default {
  arrowParens: "always",
  endOfLine: "lf",
  printWidth: 100,
  singleQuote: true,
  semi: true,
  tabWidth: 2,
  trailingComma: "all",
};

Create packages/prettier-config/playwright.mjs:

export default {
  arrowParens: "always",
  endOfLine: "lf",
  printWidth: 100,
  singleQuote: true,
  semi: true,
  tabWidth: 2,
  trailingComma: "all",
};

Create packages/prettier-config/nextjs.mjs:

export default {
  plugins: ['prettier-plugin-tailwindcss'],
  printWidth: 120,
  singleQuote: true,
};

Create packages/prettier-config/index.mjs:

export * from './middleware.mjs';
export * from './nextjs.mjs';
export * from './playwright.mjs';

Create packages/prettier-config/package.json:

{
  "name": "prettier-config",
  "version": "0.0.1",
  "private": true,
  "exports": {
    ".": {
      "import": "./index.mjs"
    },
    "./middleware": {
      "import": "./middleware.mjs"
    },
    "./nextjs": {
      "import": "./nextjs.mjs"
    },
    "./playwright": {
      "import": "./playwright.mjs"
    }
  },
  "dependencies": {
    "prettier-plugin-tailwindcss": "0.6.11"
  }
}
  1. Set up lint-staged configuration:

Create packages/lint-staged-config/middleware.mjs:

export default {
  "*.{js,cjs,mjs,ts,tsx}": ["eslint --fix", "prettier --write"],
};

Create packages/lint-staged-config/playwright.mjs:

export default {
  '*.{js,cjs,mjs,ts,tsx}': ['eslint --fix', 'prettier --write'],
};

Create packages/lint-staged-config/nextjs.mjs:

export default {
  '*.{js,cjs,mjs,ts,tsx}': ['eslint --fix'],
};

Create packages/lint-staged-config/index.mjs:

export * from './middleware.mjs';
export * from './nextjs.mjs';
export * from './playwright.mjs';

Create packages/lint-staged-config/package.json:

{
  "name": "lint-staged-config",
  "version": "0.0.1",
  "private": true,
  "exports": {
    ".": {
      "import": "./index.mjs"
    },
    "./middleware": {
      "import": "./middleware.mjs"
    },
    "./nextjs": {
      "import": "./nextjs.mjs"
    },
    "./playwright": {
      "import": "./playwright.mjs"
    }
  }
}
  1. Set up Tailwind configuration:

Create packages/tailwind-config/tsconfig.json:

{
  "compilerOptions": {
    "module": "preserve",
    "target": "ESNext",
    "outDir": "./dist",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "moduleDetection": "auto",
    "moduleResolution": "bundler",
    "skipLibCheck": true,
    "lib": [
      "DOM",
      "DOM.Iterable",
      "ES2019"
    ]
  },
  "include": ["src/**/*.ts"],
  "exclude": ["./dist/**/*"]
}

Create packages/tailwind-config/src/nextjs.ts:

import { tailwindConfig } from '@storefront-ui/react/tailwind-config';
import sfTypography from '@storefront-ui/typography';
import type { Config } from 'tailwindcss';

const config: Config = {
  content: [],
  corePlugins: {
    preflight: true,
  },
  plugins: [sfTypography],
  presets: [tailwindConfig],
  theme: {
    extend: {
      colors: {
        // Uncomment to customize your primary color
        // primary: {
        //   50: '#f5f9ff',
        //   100: '#e9f3ff',
        //   200: '#c8e0ff',
        //   300: '#a6ccff',
        //   400: '#6ea1ff',
        //   500: '#3375ff',
        //   600: '#2e6ae6',
        //   700: '#264ebf',
        //   800: '#1d3f99',
        //   900: '#132f72',
        // },
      },
      fontFamily: {
        body: 'var(--font-body)',
        headings: 'var(--font-headings)',
      },
      screens: {
        '2-extra-large': '1366px',
        '2-extra-small': '360px',
        '3-extra-large': '1536px',
        '4-extra-large': '1920px',
        'extra-large': '1280px',
        'extra-small': '376px',
        large: '1024px',
        medium: '768px',
        small: '640px',
      },
    },
  },
};
export default config;

Create packages/tailwind-config/src/index.ts:

export * from './nextjs';

Create packages/tailwind-config/package.json:

{
  "name": "tailwind-config",
  "version": "0.0.1",
  "private": true,
  "main": "dist/index.cjs",
  "exports": {
    ".": {
      "import": {
        "default": "./dist/index.mjs",
        "types": "./dist/index.d.mts"
      },
      "require": {
        "default": "./dist/index.cjs",
        "types": "./dist/index.d.cts"
      },
      "types": "./dist/index.d.ts"
    },
    "./nextjs": {
      "import": {
        "default": "./dist/nextjs.mjs",
        "types": "./dist/nextjs.d.mts"
      },
      "require": {
        "default": "./dist/nextjs.cjs",
        "types": "./dist/nextjs.d.cts"
      },
      "types": "./dist/nextjs.d.ts"
    },
  },
  "scripts": {
    "build": "unbuild",
    "prepare": "yarn build"
  },
  "dependencies": {
    "@storefront-ui/react": "2.7.1",
    "@storefront-ui/typography": "2.6.1",
    "tailwindcss": "3.4.4"
  },
  "devDependencies": {
    "typescript": "5.4.5",
    "unbuild": "2.0.0"
  },
  "files": [
    "dist"
  ]
}
  1. Update app configurations to use shared packages:

First, rename the Prettier configuration files to use the .mjs extension:

mv apps/playwright/.prettierrc apps/playwright/.prettierrc.mjs
mv apps/storefront-middleware/.prettierrc apps/storefront-middleware/.prettierrc.mjs
mv apps/storefront-unified-nextjs/.prettierrc apps/storefront-unified-nextjs/.prettierrc.mjs

Then update the configuration files:

Update apps/storefront-unified-nextjs/eslint.config.mjs:

export { default } from 'eslint-config/nextjs';

Update apps/storefront-unified-nextjs/.prettierrc.mjs:

export { default } from 'prettier-config/nextjs';

Update apps/storefront-unified-nextjs/lint-staged.config.mjs:

export { default } from 'lint-staged-config/nextjs';

Update apps/storefront-unified-nextjs/tailwind.config.ts:

import tailwindConfig from 'tailwind-config/nextjs';
import type { Config } from 'tailwindcss';

const config: Config = {
  content: [
    './app/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    './sf-modules/**/*.{js,ts,jsx,tsx}',
    '../../node_modules/@storefront-ui/react/**/*.js',
    // For multistore `.out` folder
    '../../../node_modules/@storefront-ui/react/**/*.js',
  ],
  presets: [tailwindConfig],
};
export default config;

Update apps/playwright/eslint.config.mjs:

export { default } from 'eslint-config/playwright';

Update apps/playwright/.prettierrc.mjs:

export { default } from 'prettier-config/playwright';

Update apps/playwright/lint-staged.config.mjs:

export { default } from 'lint-staged-config/playwright';

Update apps/storefront-middleware/eslint.config.mjs:

export { default } from 'eslint-config/middleware';

Update apps/storefront-middleware/.prettierrc.mjs:

export { default } from 'prettier-config/middleware';

Update apps/storefront-middleware/lint-staged.config.mjs:

export { default } from 'lint-staged-config/middleware';

After setting up the shared configuration packages, run:

yarn install
yarn lint:fix

This will install all the new dependencies and ensure all files matches ESLint rules.

If yarn lint:fix finds any issues that it cannot automatically fix, we recommend addressing these issues before proceeding with the migration. This ensures your codebase is in a consistent state and helps prevent potential problems later in the migration process.

2

Update Turbo

  1. Update the Turbo version in your root package.json:
{
  "dependencies": {
-   "turbo": "1.10.5",
+   "turbo": "2.4.4",
  }
}
  1. Add package manager specification to your root package.json:
{
+ "packageManager": "yarn@1.22.22"
}
  1. Update your turbo.json configuration to use the new tasks format:
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "storefront-middleware#build": {
      "dependsOn": ["^build"],
      "outputs": ["lib/**"]
    },
    "storefront-unified-nextjs#build": {
      "dependsOn": [
        "^build",
        "storefront-middleware#build",
        "tailwind-config#build"
      ],
      "outputs": [".next/**", "!.next/cache/**"]
    },
    "test:unit": {
      "dependsOn": ["^build"]
    },
    "test:integration": {
      "dependsOn": ["^build"],
      "inputs": ["tests/**/*.ts", "mocks/**/*.ts", "setup/**/*.ts"],
      "outputs": ["test-results/**", "playwright-report/**"]
    },
    "playwright#test:integration": {
      "dependsOn": ["^build"],
      "inputs": ["tests/**/*.ts", "mocks/**/*.ts", "setup/**/*.ts"],
      "outputs": ["test-results/**", "playwright-report/**"]
    },
    "playwright#test:integration:ui": {
      "dependsOn": [],
      "cache": false
    },
    "start": {
      "outputs": []
    },
    "start:standalone": {
      "outputs": []
    },
    "lint": {
      "dependsOn": []
    },
    "format": {
      "outputs": []
    },
    "dev": {
      "cache": false
    },
    "prepare": {
      "dependsOn": [],
      "cache": false
    },
    "playwright-default#test:integration": {
      "dependsOn": ["^build"],
      "inputs": ["**"],
      "outputs": ["test-results/**", "playwright-report/**"]
    },
    "playwright-default#test:integration:ui": {
      "dependsOn": [],
      "cache": false,
      "inputs": ["**"]
    },
    "storefront-middleware-default#build": {
      "dependsOn": ["^build"],
      "outputs": ["lib/**"],
      "inputs": ["**"]
    },
    "storefront-unified-nextjs-default#build": {
      "dependsOn": [
        "^build",
        "storefront-middleware-default#build",
        "tailwind-config#build"
      ],
      "outputs": [".next/**", "!.next/cache/**"],
      "inputs": ["**"]
    }
  }
}

After updating Turbo, run:

yarn install

Test if the Turbo update works correctly:

yarn dev

If you encounter any issues with the yarn dev command, make sure all dependencies are properly installed and the Turbo configuration is correct. Fix any reported errors before proceeding to the next step.

3

Update Apps

  1. Update Frontend Configuration:

Update apps/storefront-unified-nextjs/next.config.mjs:

experimental: {
- outputFileTracingRoot: join(fileURLToPath(import.meta.url), '..', '..'),
+ outputFileTracingRoot: join(fileURLToPath(import.meta.url), '..', '..', '..', '..'),
  typedRoutes: true,
},

Update apps/storefront-unified-nextjs/tsconfig.json:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./*"],
      "@sf-modules-middleware/*": ["../storefront-middleware/sf-modules/*"],
      "@sf-modules/*": ["./sf-modules/*"],
+     "storefront-middleware/*": ["../storefront-middleware/*"]
    }
  }
}
  1. Update Middleware Configuration:

Update apps/storefront-middleware/tsconfig.json:

{
  "compilerOptions": {
    "esModuleInterop": true,
    "skipLibCheck": true,
    "target": "es2022",
    "allowJs": true,
    "resolveJsonModule": true,
    "moduleDetection": "force",
    "isolatedModules": true,
    "verbatimModuleSyntax": false,
    "strict": true,
    "noImplicitOverride": true,
    "lib": ["es2022"],
    "forceConsistentCasingInFileNames": true,
    "module": "Node16",
    "moduleResolution": "Node16",
    "rootDir": ".",
    "outDir": "lib",
    "paths": {
      "@sf-modules-middleware/*": ["./sf-modules/*"]
    },
    "plugins": [
      { "transform": "typescript-transform-paths" },
      { "transform": "typescript-transform-paths", "afterDeclarations": true }
    ]
  },
  "include": ["**/*.ts"],
  "exclude": ["dist", "lib", "node_modules", ".turbo"]
}
  1. Update Playwright Configuration:

Update apps/playwright/package.json:

{
  "scripts": {
    "lint:fix": "eslint --fix .",
    "format": "prettier --write .",
    "test:integration:dev": "PW_DEV=true playwright test --ui",
    "test:integration": "playwright test",
+   "test:integration:ui": "playwright test --ui"
  }
}

Update server configurations in apps/playwright/setup/framework/nextjs/server-dev.ts and server.ts to include envs and error handling:

const server = exec(
  'yarn dev',
  {
    cwd: nextRootDir,
    env: {
      ...process.env,
      NEXT_PUBLIC_ALOKAI_MIDDLEWARE_API_URL: `http://localhost:${middlewarePort}`,
      NEXT_PUBLIC_ALOKAI_MIDDLEWARE_SSR_API_URL: `http://localhost:${middlewarePort}`,
      PORT: String(port),
      TEST_BUILD_DIR: join('.next', 'test', `${workerInfo.workerIndex}`),
    },
  },
  (error) => {
    if (error !== null) {
      debug && console.log(`[WORKER ${workerInfo.workerIndex}] Next.js server encountered an error: ${error}`);
    }
  }
);

4

Add Alokai CLI

  1. Update your .npmrc file:
+@alokai:registry=https://registrynpm.storefrontcloud.io/
@vsf-enterprise:registry=https://registrynpm.storefrontcloud.io/
auto-install-peers=true
  1. Update your root package.json workspaces. Include .out/*/* directory which will include the output directories for all stores.
{
  "workspaces": [
    "apps/*",
    "packages/*",
+   ".out/*/*"
  ]
}
  1. Install @alokai/cli. It requires cross-env to be present in the dependencies.
yarn add @alokai/cli@0.2.0 cross-env -W

5

Set Up Alokai Configuration

  1. Create alokai.config.json:
{
  "$schema": "node_modules/@alokai/cli/dist/static/alokaiConfigSchema.json",
  "stores": {}
}
  1. Update .gitignore:
// ... existing code ...
+.out/

After setting up the Alokai configuration, create your first store:

yarn exec alokai-cli store add

Store Name

When creating your first store, you can use any name you prefer. If you plan to have just one store, using the name default is recommended.

6

Update Scripts

  1. Add scripts/clean-out-dir.mjs which will be make sure that .out directory doesn't contain outdated files:
import { existsSync, rm } from "node:fs";
import { join } from "node:path";

// This script is intended to be run before installing packages, as the .out directory is now part of the workspace.
// To avoid installing outdated packages, the script removes the .out directory, which may contain outdated packages.

const outPath = join(process.cwd(), ".out");

if (existsSync(outPath)) {
  console.log("Removing temporary .out directory...");

  rm(outPath, { recursive: true }, (error) => {
    if (error) {
      console.error(`Error removing temporary .out directory: ${error}`);
    } else {
      console.log("Done!");
    }
  });
}
  1. Update the root package.json scripts:
{
  "scripts": {
    "build": "turbo run build --filter=!\"./apps/**\" --filter=!\"./.out/**\" && alokai-cli store build --all",
    "postinstall": "yarn build:packages",
    "build:packages": "turbo run build --filter='./packages/*'",
    "dev": "alokai-cli store dev --all --verbose",
    "format": "alokai-cli store build --all --compose-only && turbo run format",
    "init": "yarn install && node init.mjs && mkdir -p .out",
    "lint": "alokai-cli store build --all --compose-only && turbo run lint --filter=!\"./apps/**\"",
    "lint:fix": "alokai-cli store build --all --compose-only && turbo run lint:fix --filter=!\"./apps/**\"",
    "preinstall": "node scripts/clean-out-dir.mjs",
    "prepare": "husky install && turbo run prepare",
    "start": "alokai-cli store start --all --verbose",
    "store": "alokai-cli store",
    "test:integration:pw": "alokai-cli store test --all",
    "typecheck": "alokai-cli store build --all --compose-only && turbo run typecheck --filter=!\"./apps/**\""
  }
}

After updating the build scripts, test your setup:

yarn dev

Once the development server is running and everything looks good, you can check also if the build succeeds:

yarn build

7

Set Up CI/CD Pipeline

  1. Create .github/actions/affected-stores/action.yml:
name: Affected Stores
description: Determines which stores are affected based on the GitHub event type and commits.
inputs:
  since:
    description: Optional base commit SHA for determining changes.
    required: false
  to:
    description: Optional head commit SHA for determining changes.
    required: false
  cli_bin_path:
    description: Path to Alokai CLI bin
    default: ./node_modules/.bin/alokai-cli
    required: false

outputs:
  storeIds:
    description: A JSON array of store ids that are affected.
    value: ${{ steps.affectedStores.outputs.storeIds }}
  storeIdsFlag:
    description: A CLI-ready flag for the Alokai CLI. This will be empty when all or no stores are affected.
    value: ${{ steps.affectedStores.outputs.storeIdsFlag }}

runs:
  using: composite
  steps:
    - name: Determine affected stores
      id: affectedStores
      shell: bash
      run: |
        if [ ${{ github.event_name }} == "workflow_dispatch" ]; then
          echo "Triggered by workflow dispatch. All stores are affected."
          echo "storeIds=$(jq -c '.stores | keys' alokai.config.json)" >> "$GITHUB_OUTPUT"
          echo "storeIdsFlag=--all" >> "$GITHUB_OUTPUT"
          exit 0
        fi

        OPTIONAL_SINCE_FLAG=""
        OPTIONAL_TO_FLAG=""

        if [ -n "${{ inputs.since }}" ]; then
          OPTIONAL_SINCE_FLAG="--since=${{ inputs.since }}"
        fi

        if [ -n "${{ inputs.to }}" ]; then
          OPTIONAL_TO_FLAG="--to=${{ inputs.to }}"
        fi

        ${{ inputs.cli_bin_path }} store changed $OPTIONAL_SINCE_FLAG $OPTIONAL_TO_FLAG > changed_stores.json

        echo "Changed stores report:"
        cat changed_stores.json

        STORES=$(jq -r '.[].storeId' changed_stores.json | tr '\n' ' ' | sed 's/ $//')

        if [ -z "$STORES" ]; then
          echo "No stores affected."
          echo "storeIds=[]" >> "$GITHUB_OUTPUT"
          echo "storeIdsFlag=" >> "$GITHUB_OUTPUT"
        else
          echo "Stores that have changed: $STORES"
          echo "storeIds=$(jq -c '[.[].storeId]' changed_stores.json)" >> "$GITHUB_OUTPUT"
          echo "storeIdsFlag=--store-id $STORES" >> "$GITHUB_OUTPUT"
        fi
  1. Create .github/actions/setup/action.yml:
name: Setup repository
description: Installs all the packages and initializes environment variables
inputs:
  npm_user:
    description: Enterprise NPM registry user
    type: string
    required: true
  npm_password:
    description: Enterprise NPM registry password
    type: string
    required: true
  npm_email:
    description: Enterprise NPM registry email
    type: string
    required: true

runs:
  using: composite
  steps:
    - name: Use Node.js based on .nvmrc file
      uses: actions/setup-node@v4
      with:
        node-version-file: ./.nvmrc

    - name: Yarn cache
      uses: actions/cache@v4
      with:
        path: .yarn
        key: storefront-demo-yarn-cache

    - name: Cache turbo build setup
      uses: actions/cache@v4
      with:
        path: .turbo
        key: starter-turbo-${{ github.sha }}
        restore-keys: |
          starter-turbo-

    - name: Configure NPM registry
      shell: bash
      run: |
        npm install -g npm-cli-login;
        npm-cli-login -u ${{ inputs.npm_user }} -p ${{ inputs.npm_password }} -e ${{ inputs.npm_email }} -r https://registrynpm.storefrontcloud.io || exit 1;

    - name: Install dependencies
      shell: bash
      run: yarn install --frozen-lockfile --cache-folder .yarn && node init.mjs
  1. Create .github/workflows/continuous-integration.yml:
name: CI

on:
  pull_request:
    branches:
      - main
  push:
    branches:
      - main

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node_version: [18]
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install dependencies
        uses: ./.github/actions/setup
        with:
          npm_user: ${{ vars.NPM_USER }}
          npm_password: ${{ secrets.NPM_PASS }}
          npm_email: ${{ vars.NPM_EMAIL }}

      - name: Find affected stores
        id: affectedStores
        uses: ./.github/actions/affected-stores
        with:
          since: ${{ github.event.pull_request.base.sha }}
          to: ${{ github.event.pull_request.head.sha }}

      - name: Build affected stores
        if: ${{ steps.affectedStores.outputs.storeIds != '[]' }}
        run: |
          yarn store build ${{ steps.affectedStores.outputs.storeIdsFlag }} --cache-dir=.turbo

      - name: Lint project
        run: yarn lint

      - name: Install Playwright Browsers
        run: yarn playwright install --with-deps chromium

      - name: Run integration tests in Playwright
        if: ${{ steps.affectedStores.outputs.storeIds != '[]' }}
        run: yarn store test ${{ steps.affectedStores.outputs.storeIdsFlag }}
  1. Create .github/workflows/continuous-delivery.yml:
name: Deployment

on:
  push:
    branches:
      - main
  workflow_dispatch:
    inputs:
      store_ids:
        description: "Space separated list of store IDs to deploy (optional). Example: store1 store2"
        required: false
        default: ""
      verbose:
        description: "Enable verbose output (true/false)."
        required: false
        default: "false"

jobs:
  chooseStoresToDeploy:
    name: Choose stores to deploy
    runs-on: ubuntu-latest
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}
      cancel-in-progress: true
    outputs:
      storeIds: ${{ steps.outputStoresToDeploy.outputs.storeIds }}
      storeIdsFlag: ${{ steps.outputStoresToDeploy.outputs.storeIdsFlag }}
      verbose: ${{ github.event.inputs.verbose == 'true' }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Configure NPM registry
        run: |
          npm install -g npm-cli-login;
          npm-cli-login -u ${{ vars.NPM_USER }} -p ${{ secrets.NPM_PASS }} -e ${{ vars.NPM_EMAIL }} -r https://registrynpm.storefrontcloud.io/

      - name: Choose CLI version
        id: cli_version
        run: |
          echo CLI_VERSION=$(jq -r '.dependencies["@alokai/cli"]' package.json) >> $GITHUB_OUTPUT

      - name: Find affected stores
        if: ${{ github.event.inputs.store_ids == '' }}
        id: affectedStores
        uses: ./.github/actions/affected-stores
        with:
          cli_bin_path: npx @alokai/cli@${{ steps.cli_version.outputs.cli_version }}

      - name: Output stores to deploy
        id: outputStoresToDeploy
        run: |
          if [ -n "${{ github.event.inputs.store_ids }}" ]; then
            echo "storeIds=$(echo ${{ github.event.inputs.store_ids }} | jq -R 'split(" ")' -c)" >> "$GITHUB_OUTPUT"
            echo "storeIdsFlag=--store-id ${{ github.event.inputs.store_ids }}" >> "$GITHUB_OUTPUT"
          else
            echo 'storeIds=${{ steps.affectedStores.outputs.storeIds }}' >> "$GITHUB_OUTPUT"
            echo "storeIdsFlag=${{ steps.affectedStores.outputs.storeIdsFlag }}" >> "$GITHUB_OUTPUT"
          fi

  deploy:
    name: Deploy ${{ matrix.store_id }} store
    needs: chooseStoresToDeploy
    if: needs.chooseStoresToDeploy.outputs.storeIds != '[]'
    runs-on: ubuntu-latest
    strategy:
      matrix:
        store_id: ${{ fromJson(needs.chooseStoresToDeploy.outputs.storeIds) }}
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.store_id }}
      cancel-in-progress: true
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install dependencies
        uses: ./.github/actions/setup
        with:
          npm_user: ${{ vars.NPM_USER }}
          npm_password: ${{ secrets.NPM_PASS }}
          npm_email: ${{ vars.NPM_EMAIL }}

      - name: 🚀 Deploy store
        run: |
          echo "Deploying store: ${{ matrix.store_id }}"
          yarn store deploy \
            --cloud-username ${{ vars.CLOUD_USERNAME }} \
            --cloud-password ${{ secrets.CLOUD_PASSWORD }} \
            --docker-registry-url ${{ vars.DOCKER_REGISTRY_URL }} \
            --store-id ${{ matrix.store_id }} \
            --verbose ${{ needs.chooseStoresToDeploy.outputs.verbose }}

Learn More About Deployment

For more details about how deployment works in Alokai Multistore, check out our Deployment guide.