diff --git a/docs/content/guides/7.multistore/3.migration-guide.md b/docs/content/guides/7.multistore/3.migration-guide.md new file mode 100644 index 0000000000..be94b83596 --- /dev/null +++ b/docs/content/guides/7.multistore/3.migration-guide.md @@ -0,0 +1,1592 @@ +--- +navigation: + icon: tabler:refresh +--- + +# Migration Guide + +::info Prerequisites +This guide assumes you are using Alokai Ecosystem version 0.1.0. For details about this version, please refer to the [changelog](/storefront/change-log/alokai-changelog#_010). +:: + +## Project Structure After Migration to Multistore + +Before we begin, here's what your project structure will look like after completing this migration: + +::tabs{:titles='["Next.js", "Nuxt"]'} + +#tab-1 +```ascii +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/ +``` + +#tab-2 +```ascii +apps/ +├── playwright/ +├── storefront-unified-nuxt/ +├── storefront-unified-middleware/ +└── stores/ + ├── default/ + │ └── playwright/ + │ └── storefront-unified-nuxt/ + │ └── storefront-middleware/ +packages/ + eslint-config/ + lint-staged-config/ + prettier-config/ + tailwind-config/ +``` + +:: + +::tip 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](/guides/multistore/tooling-and-concepts). +:: + +## Migration Steps + +::steps + +#step-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: +```bash +mkdir -p packages/{eslint-config,prettier-config,lint-staged-config,tailwind-config} +``` + +2. Set up ESLint configuration: + +Create `packages/eslint-config/middleware.mjs`: +```js +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`: +```js +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'], + }), +); +``` + +::tabs{:titles='["Next.js", "Nuxt"]'} + +#tab-1 +Create `packages/eslint-config/nextjs.mjs`: +```js +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`: +```js +export * from './middleware.mjs'; +export * from './nextjs.mjs'; +export * from './playwright.mjs'; +``` + +Create `packages/eslint-config/package.json`: +```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" + } +} +``` + +#tab-2 +Create `packages/eslint-config/nuxt.mjs`: +```js +import { architecture, ecma, style, typescript } from '@vue-storefront/eslint-config'; +import { createConfigForNuxt } from '@nuxt/eslint-config/flat' + +export default createConfigForNuxt( + ecma({ + withImport: false, + }), + style(), + architecture( + { + maxComplexity: 10, + maxDepth: 5, + maxLines: 500, + maxLinesPerFunction: 150, + maxNestedCallbacks: 3, + maxParams: 4, + maxStatements: 15, + maxStatementsPerLine: 1, + }, + { rules: { 'arrow-parens': 'off' } }, + ), +) + .override('nuxt/typescript/rules', typescript()) + .override('nuxt/typescript/rules', { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, + }) + .override('nuxt/vue/single-root', { + rules: { + 'vue/no-multiple-template-root': 'off', + }, + }); +``` + +Create `packages/eslint-config/index.mjs`: +```js +export * from './middleware.mjs'; +export * from './nuxt.mjs'; +export * from './playwright.mjs'; +``` + +Create `packages/eslint-config/package.json`: +```json +{ + "name": "eslint-config", + "version": "0.0.1", + "private": true, + "exports": { + ".": { + "import": "./index.mjs" + }, + "./middleware": { + "import": "./middleware.mjs" + }, + "./nuxt": { + "import": "./nuxt.mjs" + }, + "./playwright": { + "import": "./playwright.mjs" + } + }, + "devDependencies": { + "@nuxt/eslint-config": "^0.6.1", + "@vue-storefront/eslint-config": "4.1.0", + "eslint-config-flat-gitignore": "0.3.0" + } +} +``` +:: + +3. Set up Prettier configuration: + +Create `packages/prettier-config/middleware.mjs`: +```js +export default { + arrowParens: "always", + endOfLine: "lf", + printWidth: 100, + singleQuote: true, + semi: true, + tabWidth: 2, + trailingComma: "all", +}; +``` + +Create `packages/prettier-config/playwright.mjs`: +```js +export default { + arrowParens: "always", + endOfLine: "lf", + printWidth: 100, + singleQuote: true, + semi: true, + tabWidth: 2, + trailingComma: "all", +}; +``` + +::tabs{:titles='["Next.js", "Nuxt"]'} + +#tab-1 +Create `packages/prettier-config/nextjs.mjs`: +```js +export default { + plugins: ['prettier-plugin-tailwindcss'], + printWidth: 120, + singleQuote: true, +}; +``` + +Create `packages/prettier-config/index.mjs`: +```js +export * from './middleware.mjs'; +export * from './nextjs.mjs'; +export * from './playwright.mjs'; +``` + +Create `packages/prettier-config/package.json`: +```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" + } +} +``` + +#tab-2 +Create `packages/prettier-config/nuxt.mjs`: +```js +export default { + arrowParens: 'always', + endOfLine: 'lf', + jsxSingleQuote: false, + printWidth: 120, + semi: true, + singleQuote: true, + tabWidth: 2, + trailingComma: 'all', +}; +``` + +Create `packages/prettier-config/index.mjs`: +```js +export * from './middleware.mjs'; +export * from './nuxt.mjs'; +export * from './playwright.mjs'; +``` + +Create `packages/prettier-config/package.json`: +```json +{ + "name": "prettier-config", + "version": "0.0.1", + "private": true, + "exports": { + ".": { + "import": "./index.mjs" + }, + "./middleware": { + "import": "./middleware.mjs" + }, + "./nuxt": { + "import": "./nuxt.mjs" + }, + "./playwright": { + "import": "./playwright.mjs" + } + }, + "dependencies": { + "prettier-plugin-tailwindcss": "0.6.11" + } +} +``` + +:: + +4. Set up lint-staged configuration: + +Create `packages/lint-staged-config/middleware.mjs`: +```js +export default { + "*.{js,cjs,mjs,ts,tsx}": ["eslint --fix", "prettier --write"], +}; +``` + +Create `packages/lint-staged-config/playwright.mjs`: +```js +export default { + '*.{js,cjs,mjs,ts,tsx}': ['eslint --fix', 'prettier --write'], +}; +``` + +::tabs{:titles='["Next.js", "Nuxt"]'} + +#tab-1 +Create `packages/lint-staged-config/nextjs.mjs`: +```js +export default { + '*.{js,cjs,mjs,ts,tsx}': ['eslint --fix'], +}; +``` + +Create `packages/lint-staged-config/index.mjs`: +```js +export * from './middleware.mjs'; +export * from './nextjs.mjs'; +export * from './playwright.mjs'; +``` + +Create `packages/lint-staged-config/package.json`: +```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" + } + } +} +``` + +#tab-2 +Create `packages/lint-staged-config/nuxt.mjs`: +```js +export default { + '*.{js,cjs,mjs,ts,tsx,vue}': ['eslint --fix', 'prettier --write'], + '*.{ts,vue}': () => 'yarn run typecheck', +}; +``` + +Create `packages/lint-staged-config/index.mjs`: +```js +export * from './middleware.mjs'; +export * from './nuxt.mjs'; +export * from './playwright.mjs'; +``` + +Create `packages/lint-staged-config/package.json`: +```json +{ + "name": "lint-staged-config", + "version": "0.0.1", + "private": true, + "exports": { + ".": { + "import": "./index.mjs" + }, + "./middleware": { + "import": "./middleware.mjs" + }, + "./nuxt": { + "import": "./nuxt.mjs" + }, + "./playwright": { + "import": "./playwright.mjs" + } + } +} +``` + +:: + +5. Set up Tailwind configuration: + +Create `packages/tailwind-config/tsconfig.json`: +```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/**/*"] +} +``` + +::tabs{:titles='["Next.js", "Nuxt"]'} + +#tab-1 +Create `packages/tailwind-config/src/nextjs.ts`: +```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`: +```ts +export * from './nextjs'; +``` + +Create `packages/tailwind-config/package.json`: +```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" + ] +} +``` + +#tab-2 +Create `packages/tailwind-config/src/nuxt.ts`: +```ts +import sfTypography from '@storefront-ui/typography'; +import type { Config } from 'tailwindcss'; + +export default { + content: [], + plugins: [sfTypography], + theme: { + extend: { + screens: { + '2xl': '1366px', + '2xs': '360px', + '3xl': '1536px', + '4xl': '1920px', + lg: '1024px', + md: '768px', + sm: '640px', + xl: '1280px', + xs: '376px', + }, + }, + }, +} as Config; +``` + +Create `packages/tailwind-config/src/index.ts`: +```ts +export * from './nuxt'; +``` + +Create `packages/tailwind-config/package.json`: +```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" + }, + "./nuxt": { + "import": { + "default": "./dist/nuxt.mjs", + "types": "./dist/nuxt.d.mts" + }, + "require": { + "default": "./dist/nuxt.cjs", + "types": "./dist/nuxt.d.cts" + }, + "types": "./dist/nuxt.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" + ] +} +``` +:: + +6. Update app configurations to use shared packages: + +First, rename the Prettier configuration files to use the `.mjs` extension: + +::tabs{:titles='["Next.js", "Nuxt"]'} + +#tab-1 +```bash +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`: +```js +export { default } from 'eslint-config/nextjs'; +``` + +Update `apps/storefront-unified-nextjs/.prettierrc.mjs`: +```js +export { default } from 'prettier-config/nextjs'; +``` + +Update `apps/storefront-unified-nextjs/lint-staged.config.mjs`: +```js +export { default } from 'lint-staged-config/nextjs'; +``` + +Update `apps/storefront-unified-nextjs/tailwind.config.ts`: +```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; +``` + +#tab-2 +```bash +mv apps/playwright/.prettierrc apps/playwright/.prettierrc.mjs +mv apps/storefront-middleware/.prettierrc apps/storefront-middleware/.prettierrc.mjs +mv apps/storefront-unified-nuxt/.prettierrc apps/storefront-unified-nuxt/.prettierrc.mjs +``` + +Then update the configuration files: + +Update `apps/storefront-unified-nuxt/eslint.config.mjs`: +```js +export { default } from 'eslint-config/nuxt'; +``` + +Update `apps/storefront-unified-nuxt/.prettierrc.mjs`: +```js +export { default } from 'prettier-config/nuxt'; +``` + +Update `apps/storefront-unified-nuxt/lint-staged.config.mjs`: +```js +export { default } from 'lint-staged-config/nuxt'; +``` + +Update `apps/storefront-unified-nuxt/tailwind.config.ts`: +```ts +import tailwindConfig from 'tailwind-config/nuxt'; +import type { Config } from 'tailwindcss'; + +export default { + content: [ + './**/*.vue', + '../../node_modules/@storefront-ui/vue/**/*.{js,mjs}', + // For multistore `.out` folder + '../../../node_modules/@storefront-ui/vue/**/*.{js,mjs}', + ], + presets: [tailwindConfig], +} as Config; +``` +:: + +Update `apps/playwright/eslint.config.mjs`: +```js +export { default } from 'eslint-config/playwright'; +``` + +Update `apps/playwright/.prettierrc.mjs`: +```js +export { default } from 'prettier-config/playwright'; +``` + +Update `apps/playwright/lint-staged.config.mjs`: +```js +export { default } from 'lint-staged-config/playwright'; +``` + +Update `apps/storefront-middleware/eslint.config.mjs`: +```js +export { default } from 'eslint-config/middleware'; +``` + +Update `apps/storefront-middleware/.prettierrc.mjs`: +```js +export { default } from 'prettier-config/middleware'; +``` + +Update `apps/storefront-middleware/lint-staged.config.mjs`: +```js +export { default } from 'lint-staged-config/middleware'; +``` + +After setting up the shared configuration packages, run: +```bash +yarn install +yarn lint:fix +``` + +This will install all the new dependencies and ensure all files matches ESLint rules. + +::info +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. +:: + +#step-2 +### Update Turbo + +1. Update the Turbo version in your root `package.json`: +```diff +{ + "dependencies": { +- "turbo": "1.10.5", ++ "turbo": "2.4.4", + } +} +``` + +2. Add package manager specification to your root `package.json`: +```diff +{ ++ "packageManager": "yarn@1.22.22" +} +``` + +3. Update your `turbo.json` configuration to use the new tasks format: + +::tabs{:titles='["Next.js", "Nuxt"]'} + +#tab-1 +```json +{ + "$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": ["**"] + } + } +} +``` + +#tab-2 +```json +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "storefront-middleware#build": { + "dependsOn": ["^build"], + "outputs": ["lib/**"] + }, + "storefront-unified-nuxt#build": { + "dependsOn": ["^build", "storefront-middleware#build", "tailwind-config#build"], + "outputs": [".deploy/**", ".nuxt/**"] + }, + "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": [] + }, + "lint:fix": { + "dependsOn": [] + }, + "format": {}, + "typecheck": { + "dependsOn": [] + }, + "publish": { + "dependsOn": ["build", "test", "lint"] + }, + "dev": { + "cache": false + }, + "multistore:dev": { + "cache": false + }, + "prepare": { + "dependsOn": [], + "cache": false + } + } +} +``` + +:: + +After updating Turbo, run: +```bash +yarn install +``` + +Test if the Turbo update works correctly: +```bash +yarn dev +``` + +::info +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. +:: + +#step-3 +### Update Apps + +1. Update Frontend Configuration: + +::tabs{:titles='["Next.js", "Nuxt"]'} + +#tab-1 +Update `apps/storefront-unified-nextjs/next.config.mjs`: +```diff +experimental: { +- outputFileTracingRoot: join(fileURLToPath(import.meta.url), '..', '..'), ++ outputFileTracingRoot: join(fileURLToPath(import.meta.url), '..', '..', '..', '..'), + typedRoutes: true, +}, +``` + +Update `apps/storefront-unified-nextjs/tsconfig.json`: +```diff +{ + "compilerOptions": { + "paths": { + "@/*": ["./*"], + "@sf-modules-middleware/*": ["../storefront-middleware/sf-modules/*"], + "@sf-modules/*": ["./sf-modules/*"], ++ "storefront-middleware/*": ["../storefront-middleware/*"] + } + } +} +``` + +#tab-2 +Update `apps/storefront-unified-nuxt/.gitignore` +```diff ++.deploy +``` + +Update `apps/storefront-unified-nuxt/nuxt.config.ts` +```diff +{ + nitro: { + compressPublicAssets: true, ++ output: { ++ dir: '.deploy', ++ }, + } +} +``` + +Update `apps/storefront-unified-nuxt/tsconfig.json` +```diff + alias: { + '@sf-modules': path.resolve(__dirname, '.', 'sf-modules'), + '@sf-modules-middleware': path.resolve(__dirname, '..', 'storefront-middleware', 'sf-modules'), ++ 'storefront-middleware': path.resolve(__dirname, '..', 'storefront-middleware'), + }, +``` +:: + +2. Update Middleware Configuration: + +Update `apps/storefront-middleware/tsconfig.json`: +```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"] +} +``` + +3. Update Playwright Configuration: + +Update `apps/playwright/package.json`: +```diff +{ + "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" + } +} +``` + +::tabs{:titles='["Next.js", "Nuxt"]'} + +#tab-1 +Update server configurations in `apps/playwright/setup/framework/nextjs/server-dev.ts` and `server.ts` to include envs and error handling: +```ts +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}`); + } + } +); +``` + +#tab-2 +Update `apps/playwright/setup/framework/nuxt/server.ts` +```diff +- const server = exec(`node ./.output/server/index.mjs`, { ++ const server = exec(`node ./.deploy/server/index.mjs`, { + cwd: nuxtRootDir, + env: { + ...process.env, +``` + +Update `apps/playwright/setup/framework/nuxt/setup.ts` +```diff +- execSync(`rm -rf .output`, { cwd: nuxtRootDir }); ++ execSync(`rm -rf .deploy`, { cwd: nuxtRootDir }); +``` + +:: + +#step-4 +### Add Alokai CLI + +1. Update your `.npmrc` file: +```diff ++@alokai:registry=https://registrynpm.storefrontcloud.io/ +@vsf-enterprise:registry=https://registrynpm.storefrontcloud.io/ +auto-install-peers=true +``` + +2. Update your root `package.json` workspaces. Include `.out/*/*` directory which will include the output directories for all stores. +```diff +{ + "workspaces": [ + "apps/*", + "packages/*", ++ ".out/*/*" + ] +} +``` + +3. Install `@alokai/cli`. It requires `cross-env` to be present in the dependencies. + +```bash +yarn add @alokai/cli@0.2.0 cross-env -W +``` + +#step-5 +### Set Up Alokai Configuration + +1. Create `alokai.config.json`: +```json +{ + "$schema": "node_modules/@alokai/cli/dist/static/alokaiConfigSchema.json", + "stores": {} +} +``` + +2. Update `.gitignore`: +```diff +// ... existing code ... ++.out/ +``` + +After setting up the Alokai configuration, create your first store: +```bash +yarn exec alokai-cli store add +``` + +::tip 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. +:: + +#step-6 +### Update Scripts + +1. Add `scripts/clean-out-dir.mjs` which will be make sure that `.out` directory doesn't contain outdated files: +```javascript +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!"); + } + }); +} +``` + +2. Update the root `package.json` scripts: +```json +{ + "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: +```bash +yarn dev +``` + +Once the development server is running and everything looks good, you can check also if the build succeeds: +```bash +yarn build +``` + +#step-7 +### Set Up CI/CD Pipeline + +1. Create `.github/actions/affected-stores/action.yml`: +```yaml +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 +``` + +2. Create `.github/actions/setup/action.yml`: +```yaml +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 +``` + +3. Create `.github/workflows/continuous-integration.yml`: +```yaml +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 }} +``` + +4. Create `.github/workflows/continuous-delivery.yml`: +```yaml +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 }} +``` + +::tip Learn More About Deployment +For more details about how deployment works in Alokai Multistore, check out our [Deployment guide](/guides/multistore/tooling-and-concepts/deployment/deployment). +:: + +:: \ No newline at end of file