@salesforce/storefront-next-dev 1.0.1 → 1.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["LEVEL_PRIORITY: Record<LogLevel, number>","overrideLevel: LogLevel | undefined","resolvedConfig: ResolvedConfig","path","__dirname","path","resolvedConfig: ResolvedConfig","buildDirectory: string","traverse","componentRegistry: TargetComponentRegistry","contextProviders: TargetContextProviderConfig[]","actionHookRegistry: ActionHookRegistry","path","componentRegistry: TargetComponentRegistry","contextProviders: TargetContextProviderConfig[]","sourceDir: string","path","err: unknown","imports: string[]","registryEntries: string[]","handlerNames: string[]","actionHookRegistry: ActionHookRegistry","sourceDir: string","path","viteConfig: ResolvedConfig","glob","path","baseComponentId: string","components: ComponentInfo[]","existingContent: string","projectRoot: string","resolvedConfig: ResolvedConfig","missingInstrumentation: Array<{ adapter: string; event: string }>","resolvedConfig: ResolvedConfig","buildDirectory: string","appDirectory: string","fs","serverEntryFilePath: string | undefined","clientEntryFilePath: string | undefined","appDirectory: string | undefined","userServerEntryPath: string | undefined","userClientEntryPath: string | undefined","ctx: ReactRouterPluginContext | undefined","relative","path","traverse","_traverse","generate","_generate","path","environmentName: string | undefined","ssrEnv: DevEnvironment | undefined","source: string | null","plugins: Plugin[]","targetIds: string[]","path","locationUrl: URL","requestQuery: URLSearchParams","redirectHeaders: Record<string, string | string[]>","chunks: Buffer[]","body: Buffer<ArrayBufferLike>","patterns: string[]","match: RegExpExecArray | null"],"sources":["../src/utils/logger.ts","../src/plugins/fixReactRouterManifestUrls.ts","../src/utils/paths.ts","../src/plugins/readableChunkFileNames.ts","../src/mrt/utils.ts","../src/plugins/managedRuntimeBundle.ts","../src/plugins/patchReactRouter.ts","../src/extensibility/target-utils.ts","../src/plugins/transformTargets.ts","../src/plugins/actionHooks.ts","../src/plugins/watchConfigFiles.ts","../src/plugins/staticRegistry.ts","../src/plugins/configLoader.ts","../src/plugins/eventInstrumentationValidator.ts","../src/plugins/buildMiddlewareRegistry.ts","../src/plugins/platformEntry.ts","../src/plugins/workspace.ts","../src/plugins/componentLoaders.ts","../src/plugins/ssrSourcemapFix.ts","../src/plugins/i18n.ts","../src/plugins/baseConfig.ts","../src/storefront-next-targets.ts","../src/plugins/uiTargetDevMode.ts","../src/plugins/hybridProxy.ts","../src/plugins/ecdnMatcher.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/* eslint-disable no-console */\nimport os from 'os';\nimport chalk from 'chalk';\nimport { createRequire } from 'module';\nimport type { ServerMode } from '../server/modes';\nimport pkg from '../../package.json' with { type: 'json' };\n\n/**\n * Get the local network IPv4 address\n */\nexport function getNetworkAddress(): string | undefined {\n const interfaces = os.networkInterfaces();\n for (const name of Object.keys(interfaces)) {\n const iface = interfaces[name];\n if (!iface) continue;\n for (const alias of iface) {\n if (alias.family === 'IPv4' && !alias.internal) {\n return alias.address;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Get the version of a package from the project's package.json\n */\nexport function getPackageVersion(packageName: string, projectDir: string): string {\n try {\n const require = createRequire(import.meta.url);\n const pkgPath = require.resolve(`${packageName}/package.json`, { paths: [projectDir] });\n const pkgJson = require(pkgPath) as { version: string };\n return pkgJson.version;\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Centralized, level-gated logger for the SDK.\n *\n * Log level is controlled by `SFCC_LOG_LEVEL` env var (`error` | `warn` | `info` | `debug`).\n * Falls back to: `DEBUG` targeting sfnext -> `debug`, `NODE_ENV=production` -> `warn`, otherwise `info`.\n */\n\nexport type LogLevel = 'error' | 'warn' | 'info' | 'debug';\n\nconst LEVEL_PRIORITY: Record<LogLevel, number> = {\n error: 0,\n warn: 1,\n info: 2,\n debug: 3,\n};\n\nlet overrideLevel: LogLevel | undefined;\n\n/**\n * Returns true when the `DEBUG` env var targets sfnext or is a general enable flag.\n * Avoids accidentally enabling debug mode when DEBUG is set for unrelated libraries\n * (e.g. `DEBUG=express:*`).\n */\nfunction debugEnablesSfnext(): boolean {\n const raw = process.env.DEBUG?.trim();\n if (!raw) return false;\n const normalized = raw.toLowerCase();\n if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;\n return raw.split(',').some((token) => {\n const value = token.trim();\n return value === '*' || value === 'sfnext' || value === 'sfnext:*';\n });\n}\n\nfunction resolveLevel(): LogLevel {\n if (overrideLevel) return overrideLevel;\n const envLevel = process.env.MRT_LOG_LEVEL ?? process.env.SFCC_LOG_LEVEL;\n if (envLevel && envLevel in LEVEL_PRIORITY) return envLevel as LogLevel;\n if (debugEnablesSfnext()) return 'debug';\n if (process.env.NODE_ENV === 'production') return 'warn';\n return 'info';\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVEL_PRIORITY[level] <= LEVEL_PRIORITY[resolveLevel()];\n}\n\nexport const logger = {\n error(msg: string, ...args: unknown[]): void {\n if (!shouldLog('error')) return;\n console.error(chalk.red('[sfnext:error]'), msg, ...args);\n },\n warn(msg: string, ...args: unknown[]): void {\n if (!shouldLog('warn')) return;\n console.warn(chalk.yellow('[sfnext:warn]'), msg, ...args);\n },\n info(msg: string, ...args: unknown[]): void {\n if (!shouldLog('info')) return;\n console.log(chalk.cyan('[sfnext:info]'), msg, ...args);\n },\n debug(msg: string, ...args: unknown[]): void {\n if (!shouldLog('debug')) return;\n console.log(chalk.gray('[sfnext:debug]'), msg, ...args);\n },\n setLevel(level: LogLevel | undefined): void {\n overrideLevel = level;\n },\n getLevel(): LogLevel {\n return resolveLevel();\n },\n};\n\n/**\n * Print the server information banner with URLs and versions\n */\nexport function printServerInfo(mode: ServerMode, port: number, startTime: number, projectDir: string): void {\n const elapsed = Date.now() - startTime;\n const sfnextVersion = pkg.version;\n const reactVersion = getPackageVersion('react', projectDir);\n const reactRouterVersion = getPackageVersion('react-router', projectDir);\n const viteVersion = getPackageVersion('vite', projectDir);\n\n const modeLabel = mode === 'development' ? 'Development Mode' : 'Preview Mode';\n\n console.log();\n console.log(` ${chalk.cyan.bold('⚡ SFCC Storefront Next')} ${chalk.dim(`v${sfnextVersion}`)}`);\n console.log(` ${chalk.green.bold(modeLabel)}`);\n console.log();\n const logLevel = resolveLevel();\n const logLevelColors: Record<LogLevel, (s: string) => string> = {\n error: chalk.red,\n warn: chalk.yellow,\n info: chalk.cyan,\n debug: chalk.gray,\n };\n\n console.log(\n ` ${chalk.dim('react')} ${chalk.green(`v${reactVersion}`)} ${chalk.dim('|')} ` +\n `${chalk.dim('react-router')} ${chalk.green(`v${reactRouterVersion}`)} ${chalk.dim('|')} ` +\n `${chalk.dim('vite')} ${chalk.green(`v${viteVersion}`)}`\n );\n console.log(\n ` ${chalk.dim('log level')} ${logLevelColors[logLevel](logLevel)} ${chalk.dim('|')} ` +\n `${chalk.green(`ready in ${elapsed}ms`)}`\n );\n console.log();\n}\n\n/**\n * Print server configuration details (proxy, static, etc.)\n */\nexport function printServerConfig(config: {\n mode: ServerMode;\n port: number;\n enableProxy?: boolean;\n enableStaticServing?: boolean;\n enableCompression?: boolean;\n proxyPath?: string;\n proxyHost?: string;\n shortCode?: string;\n organizationId?: string;\n clientId?: string;\n}): void {\n const {\n port,\n enableProxy,\n enableStaticServing,\n enableCompression,\n proxyPath,\n proxyHost,\n shortCode,\n organizationId,\n clientId,\n } = config;\n\n console.log(` ${chalk.bold('Environment Configuration:')}`);\n\n if (enableProxy && proxyPath && proxyHost && shortCode) {\n console.log(\n ` ${chalk.green('✓')} ${chalk.bold('Proxy:')} ${chalk.cyan(`localhost:${port}${proxyPath}`)} ${chalk.dim('→')} ${chalk.cyan(proxyHost)}`\n );\n console.log(` ${chalk.dim('Short Code: ')}${chalk.dim(shortCode)}`);\n if (organizationId) {\n console.log(` ${chalk.dim('Organization ID: ')}${chalk.dim(organizationId)}`);\n }\n if (clientId) {\n console.log(` ${chalk.dim('Client ID: ')}${chalk.dim(clientId)}`);\n }\n } else {\n console.log(` ${chalk.bold('Proxy: ')} ${chalk.dim('disabled')}`);\n }\n\n if (enableStaticServing) {\n console.log(` ${chalk.bold('Static: ')} ${chalk.dim('enabled')}`);\n }\n\n if (enableCompression) {\n console.log(` ${chalk.bold('Compression: ')} ${chalk.dim('enabled')}`);\n }\n\n // URLs\n const localUrl = `http://localhost:${port}`;\n const showNetwork = process.env.SHOW_NETWORK === 'true';\n const networkAddress = showNetwork ? getNetworkAddress() : undefined;\n const networkUrl = networkAddress ? `http://${networkAddress}:${port}` : undefined;\n\n console.log();\n console.log(` ${chalk.bold('Local: ')} ${chalk.cyan(localUrl)}`);\n if (networkUrl) {\n console.log(` ${chalk.bold('Network:')} ${chalk.cyan(networkUrl)}`);\n }\n\n console.log();\n console.log(` ${chalk.dim('Press')} ${chalk.bold('Ctrl+C')} ${chalk.dim('to stop the server')}`);\n console.log();\n}\n\n/**\n * Print shutdown message\n */\nexport function printShutdownMessage(): void {\n console.log(`\\n ${chalk.yellow('⚡')} ${chalk.dim('Server shutting down...')}\\n`);\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport path from 'node:path';\nimport fs from 'fs-extra';\nimport { logger } from '../logger';\n\nfunction patchAssetsPaths(dir: string) {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n patchAssetsPaths(fullPath);\n } else if (entry.isFile() && entry.name.endsWith('.js')) {\n const content = fs.readFileSync(fullPath, 'utf-8');\n if (content.includes('\"/assets/') || content.includes(\"'/assets/\")) {\n // Transform asset URLs that start with /assets/ to use dynamic bundle path\n fs.writeFileSync(\n fullPath,\n content.replace(/[\"']\\/assets\\//g, '(window._BUNDLE_PATH || \"/\") + \"assets/')\n );\n logger.debug(`patched /assets/ references in ${fullPath}`);\n }\n }\n }\n}\n\n/**\n * Plugin to transform React Router client manifest URLs to use dynamic bundle paths\n */\nexport function fixReactRouterManifestUrlsPlugin(): Plugin {\n let resolvedConfig: ResolvedConfig;\n\n return {\n name: 'storefront-next:fix-react-router-manifest-urls',\n enforce: 'post', // Run after React Router plugin\n\n configResolved(config: ResolvedConfig) {\n resolvedConfig = config;\n },\n\n // Post-process client manifest files after they're written to disk\n closeBundle() {\n const clientBuildDir = resolvedConfig.environments.client.build.outDir;\n if (fs.existsSync(clientBuildDir)) {\n // Patch references to `/assets/` in files within React Router's client build directory\n patchAssetsPaths(clientBuildDir);\n }\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Normalize a file path to use forward slashes.\n * On Windows, Node APIs return backslash-separated paths, but ESM import\n * specifiers and Vite module IDs require forward slashes.\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replace(/\\\\/g, '/');\n}\n\n/**\n * Get the Commerce Cloud API URL from a short code\n */\nexport function getCommerceCloudApiUrl(shortCode: string, proxyHost?: string): string {\n return proxyHost || `https://${shortCode}.api.commercecloud.salesforce.com`;\n}\n\n/**\n * Get the configurable base path for the application.\n * Reads from MRT_ENV_BASE_PATH environment variable.\n *\n * The base path is used for CDN routing to the correct MRT environment.\n * It is prepended to all URLs: page routes, /mobify/bundle/ assets, and /mobify/proxy/api.\n *\n * Validation rules:\n * - Must be a single path segment starting with '/'\n * - Max 63 characters after the leading slash\n * - Only URL-safe characters allowed\n * - Returns empty string if not set\n *\n * @returns The sanitized base path (e.g., '/site-a' or '')\n *\n * @example\n * // No base path configured\n * getBasePath() // → ''\n *\n * // With base path '/storefront'\n * getBasePath() // → '/storefront'\n *\n * // Automatically sanitizes\n * // MRT_ENV_BASE_PATH='storefront/' → '/storefront'\n */\nexport function getBasePath(): string {\n const basePath = process.env.MRT_ENV_BASE_PATH?.trim();\n\n // Return empty string if not set or empty\n if (!basePath) {\n return '';\n }\n\n // Base path prefix must be a single path segment starting with '/', max 63 chars,\n // using only URL-safe characters (alphanumeric, hyphens, underscores, dots, and other safe symbols)\n // This aligns with the regex used by MRT\n if (!/^\\/[a-zA-Z0-9_.+$~\"'@:-]{1,63}$/.test(basePath)) {\n throw new Error(\n `Invalid base path: \"${basePath}\". ` +\n \"Base path must be a single segment starting with '/' (e.g., '/site-a'), \" +\n 'contain only URL-safe characters, and be at most 63 characters after the leading slash.'\n );\n }\n\n return basePath;\n}\n\n/**\n * Get the bundle path for static assets\n */\nexport function getBundlePath(bundleId: string): string {\n const basePath = getBasePath();\n return `${basePath}/mobify/bundle/${bundleId}/client/`;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin, Rollup } from 'vite';\nimport path from 'path';\nimport { toPosixPath } from '../utils/paths';\n\n/**\n * Generates human-readable chunk file names for better debugging in production builds.\n *\n * Transforms Rollup's default hash-based chunk names into structured paths that reflect\n * the original source location, making it easier to identify and debug specific chunks.\n *\n * @param chunkInfo - Rollup's pre-rendered chunk information containing module IDs and metadata\n * @returns A formatted string pattern for the chunk filename with one of these formats:\n * - `assets/(folder1)-(folder2)-filename.[hash].js` for source files in /src/\n * - `assets/(package)-(pkg-name)-(subfolder)-filename.[hash].js` for node_modules\n * - `assets/(chunk)-[name].[hash].js` as fallback for chunks without identifiable paths\n *\n * @example\n * // Source file: /src/components/ui/Button.tsx\n * // Output: assets/(components)-(ui)-Button.[hash].js\n *\n * @example\n * // Node module: /node_modules/@radix-ui/react-dialog/dist/index.js\n * // Output: assets/(package)-(@radix-ui)-(react-dialog)-(dist)-index.[hash].js\n */\nexport const readableChunkFileNames = (chunkInfo: Rollup.PreRenderedChunk) => {\n const moduleIds = chunkInfo.moduleIds;\n const defaultName = 'assets/(chunk)-[name].[hash].js';\n if (!moduleIds || moduleIds.length === 0) {\n return defaultName;\n }\n\n // Rollup moduleIds are in reverse order, the last moduleId is the one that was first in the source code\n const lastModuleId = moduleIds[moduleIds.length - 1];\n\n const getFileName = (pathname: string) => {\n const posixPath = toPosixPath(pathname);\n const parsed = path.posix.parse(posixPath);\n const withoutQuery = parsed.base.split('?')[0];\n return withoutQuery.replace(/\\.(tsx?|jsx?|mjs|js)$/, '');\n };\n\n const cleanPath = (pathname: string) => {\n return pathname?.split('?')[0];\n };\n\n const normalizedModuleId = toPosixPath(lastModuleId);\n\n // Check if the module is from the application source code (under /src/ directory)\n // This generates chunk names based on the folder structure within src/\n // Example: src/components/ui/Button.tsx → assets/(components)-(ui)-Button.[hash].js\n if (normalizedModuleId.includes('/src/')) {\n const cleanedPath = toPosixPath(cleanPath(lastModuleId));\n const match = cleanedPath.match(/\\/src\\/(.+)$/);\n if (match) {\n const pathAfterSrc = match[1];\n const parts = pathAfterSrc.split('/');\n\n const fileName = getFileName(parts[parts.length - 1]);\n\n const folders = parts.slice(0, -1);\n\n const segments = folders.map((f) => `(${f})`).join('-');\n return `assets/${segments}-${fileName}.[hash].js`;\n }\n }\n\n // Check if the module is from an external package (under /node_modules/ directory)\n // This generates chunk names that include the package name and internal path\n // Example: node_modules/@radix-ui/react-dialog/dist/index.js → assets/(package)-(@radix-ui)-(react-dialog)-(dist)-index.[hash].js\n if (normalizedModuleId.includes('/node_modules/')) {\n const cleanedPath = toPosixPath(cleanPath(lastModuleId));\n\n const parts = cleanedPath.split('/node_modules/');\n const afterNodeModules = parts[parts.length - 1];\n\n const pathParts = afterNodeModules.split('/');\n\n // Handle scoped packages (@org/package)\n let packageName;\n let remainingPath;\n\n if (pathParts[0].startsWith('@')) {\n // Scoped package: @org/package/rest/of/path\n packageName = `${pathParts[0]}-${pathParts[1]}`;\n remainingPath = pathParts.slice(2);\n } else {\n // Regular package: package/rest/of/path\n packageName = pathParts[0];\n remainingPath = pathParts.slice(1);\n }\n\n const fileName = getFileName(remainingPath[remainingPath.length - 1]);\n\n const folders = remainingPath.slice(0, -1);\n\n const segments = ['package', packageName, ...folders].map((s) => `(${s})`).join('-');\n\n return `assets/${segments}-${fileName}.[hash].js`;\n }\n\n return defaultName;\n};\n\n/**\n * Vite plugin that configures Rollup to use human-readable chunk file names in production builds.\n *\n * Applies the `readableChunkFileNames` naming strategy to both code-split chunks and entry files,\n * making it easier to identify the source of specific chunks when debugging production builds.\n *\n * @returns A Vite plugin that configures chunk naming for the client build environment\n *\n * @example\n * // In vite.config.ts\n * export default defineConfig({\n * plugins: [readableChunkFileNamesPlugin()]\n * })\n */\nexport const readableChunkFileNamesPlugin = (): Plugin => {\n return {\n name: 'storefront-next:readable-chunk-file-names',\n apply: 'build',\n config() {\n return {\n environments: {\n client: {\n build: {\n rollupOptions: {\n output: {\n chunkFileNames: readableChunkFileNames,\n entryFileNames: readableChunkFileNames,\n },\n },\n },\n },\n },\n };\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { APIGatewayProxyEvent } from 'aws-lambda';\n\nconst MRT_BUNDLE_TYPE_SSR = 'ssr' as const;\nconst MRT_STREAMING_ENTRY_FILE = 'streamingHandler' as const;\nexport type MrtBundleType = typeof MRT_BUNDLE_TYPE_SSR | typeof MRT_STREAMING_ENTRY_FILE;\n/**\n * Gets the MRT entry file for the given mode\n * @param mode - The mode to get the MRT entry file for\n * @returns The MRT entry file for the given mode\n */\nexport const getMrtEntryFile = (mode: string): MrtBundleType => {\n // TODO: Move the MRT_BUNDLE_TYPE env var to a command line option with sfnext\n // Streaming is enabled by default in production unless explicitly set to 'ssr'\n const enableStreaming = process.env.MRT_BUNDLE_TYPE !== MRT_BUNDLE_TYPE_SSR && mode === 'production';\n return enableStreaming ? MRT_STREAMING_ENTRY_FILE : MRT_BUNDLE_TYPE_SSR;\n};\n\n/**\n * Merges headers from event.headers into event.multiValueHeaders.\n *\n * @codegenie/serverless-express prefers multiValueHeaders over headers when both exist.\n * However, some headers (like x-correlation-id added by CloudFront/MRT) may only exist\n * in event.headers and not in event.multiValueHeaders, causing them to be lost.\n *\n * This function ensures all headers from event.headers are present in multiValueHeaders.\n */\nexport function mergeHeadersIntoMultiValueHeaders(event: APIGatewayProxyEvent): APIGatewayProxyEvent {\n if (!event.headers || !event.multiValueHeaders) {\n return event;\n }\n\n const mergedMultiValueHeaders = { ...event.multiValueHeaders };\n\n for (const [key, value] of Object.entries(event.headers)) {\n // Only add if not already in multiValueHeaders (case-insensitive check)\n const existingKey = Object.keys(mergedMultiValueHeaders).find((k) => k.toLowerCase() === key.toLowerCase());\n\n if (!existingKey && value !== undefined) {\n mergedMultiValueHeaders[key] = [value];\n }\n }\n\n return {\n ...event,\n multiValueHeaders: mergedMultiValueHeaders,\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport path from 'path';\nimport fs from 'fs-extra';\nimport { fileURLToPath } from 'url';\nimport { getMrtEntryFile } from '../mrt/utils';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n/**\n * This is a Vite plugin specifically for building the Managed Runtime production bundle.\n * This plugin relies on the @react-router/dev/vite plugin to work.\n * This plugin creates the Managed Runtime production bundle from the build output of the @react-router/dev/vite plugin.\n *\n * @returns {Plugin} A Vite plugin for building the Managed Runtime production react-router bundle\n */\nexport const managedRuntimeBundlePlugin = (): Plugin => {\n let resolvedConfig: ResolvedConfig;\n\n // Note: The react-router vite plugin does not use/respect vite's config.build.outDir\n // We must not use the resolvedConfig.build.outDir\n // Instead, react-router has a \"buildDirectory\" option from react-router.config.ts\n // Should we infer the build directory from the react-router config OR let the user configure it?\n let buildDirectory: string;\n\n /**\n * Creates the Managed Runtime production bundle assets\n * - ssr.mjs or streamingHandler.mjs\n * - loader.js\n * - package.json\n *\n * @returns {Promise<void>}\n */\n const createManagedRuntimeBundleAssets = async (): Promise<void> => {\n const loaderPath = path.resolve(buildDirectory, 'loader.js');\n // TODO: Move the MRT_BUNDLE_TYPE env var to a command line option with sfnext\n const mrtEntryFile = `${getMrtEntryFile(resolvedConfig?.mode)}.mjs`;\n const mrtEntryPath = path.resolve(buildDirectory, mrtEntryFile);\n\n await fs.ensureDir(buildDirectory);\n await fs.outputFile(loaderPath, '// This file is intentionally empty');\n\n const prebuiltMrtEntryPath = path.resolve(__dirname, `./mrt/${mrtEntryFile}`);\n await fs.copy(prebuiltMrtEntryPath, mrtEntryPath);\n\n // Copy source map for MRT entry point (if available)\n const prebuiltMrtEntryMapPath = `${prebuiltMrtEntryPath}.map`;\n if (await fs.pathExists(prebuiltMrtEntryMapPath)) {\n await fs.copy(prebuiltMrtEntryMapPath, `${mrtEntryPath}.map`);\n }\n\n // Copy shared chunks generated by the build process\n // These are identified by the 'sfnext-server-' prefix\n const mrtDir = path.resolve(__dirname, './mrt');\n if (await fs.pathExists(mrtDir)) {\n const files = await fs.readdir(mrtDir);\n for (const file of files) {\n if (file.startsWith('sfnext-server-') && file.endsWith('.mjs')) {\n await fs.copy(path.join(mrtDir, file), path.resolve(buildDirectory, file));\n // Copy source map for shared chunk (if available)\n const mapFile = `${file}.map`;\n if (files.includes(mapFile)) {\n await fs.copy(path.join(mrtDir, mapFile), path.resolve(buildDirectory, mapFile));\n }\n }\n }\n }\n\n const packageJsonPath = path.resolve(resolvedConfig.root, 'package.json');\n const buildPackageJsonPath = path.resolve(buildDirectory, 'package.json');\n\n const packageJson = await fs.readJson(packageJsonPath);\n\n // Currently MRT only supports CJS modules, and we are building a CJS bundle.\n // But we need to make sure the package.json doesn't have the \"type\" key to use ESM\n // otherwise the MRT environment will break because the node ESM resolution will not work\n // for our CJS bundle.\n delete packageJson.type;\n await fs.writeJson(buildPackageJsonPath, packageJson, { spaces: 2 });\n };\n\n return {\n name: 'storefront-next:managed-runtime-bundle',\n apply: 'build',\n config({ mode }) {\n return {\n build: {\n rollupOptions: {\n // Suppress benign sourcemap warnings emitted by Rollup during\n // the transform phase. These occur when Rollup tries to trace a\n // warning's location back through the sourcemap chain but the\n // intermediate sourcemaps (from TypeScript, Tailwind, etc.)\n // don't map position (1:0). The warnings are purely cosmetic —\n // actual sourcemaps in the build output are unaffected.\n //\n // This is the same approach used by @vitejs/plugin-react (PR #369)\n // and @react-router/dev's plugin.\n // See: https://github.com/vitejs/vite-plugin-react/pull/369\n // See: https://github.com/vitejs/vite/issues/15012\n onLog(level, log, defaultHandler) {\n if (log.code === 'SOURCEMAP_ERROR' && log.message.includes('resolve original location')) {\n return;\n }\n defaultHandler(level, log);\n },\n },\n },\n environments: {\n ssr: {\n resolve: {\n noExternal: true,\n },\n },\n },\n experimental: {\n // MRT serves each deploy's bundle under a per-deploy prefix:\n // {MRT_ENV_BASE_PATH}/mobify/bundle/{BUNDLE_ID}/client/...\n // Asset references must resolve under that prefix, and BUNDLE_ID\n // changes on every deploy, so we can't bake a static URL at build\n // time. The strategy differs between CSS and JS:\n //\n // - CSS url() resolves against the stylesheet's own URL per the CSS\n // spec. The emitted stylesheet already lives under the bundle\n // prefix, so a relative URL like `url(../fonts/x.woff2)` inherits\n // the correct BUNDLE_ID at request time — no runtime code needed.\n // (Vite also rejects runtime expressions inside CSS url(), so\n // relative is the only option for CSS regardless.)\n //\n // - JS strings have no equivalent \"relative to my module\" semantic\n // when handed to the DOM. A bare string in `href=` / `src=` would\n // resolve against document.baseURI — the current page URL — which\n // does NOT contain the bundle prefix, 404-ing the asset. And we\n // can't use `new URL(x, import.meta.url)` either: the SSR bundle\n // runs as CJS on MRT (see packageJson.type deletion below), where\n // import.meta.url is undefined. So JS has to produce a fully\n // qualified bundle-prefixed URL itself, by reading the prefix at\n // runtime from window._BUNDLE_PATH (client) or MRT_ENV_BASE_PATH +\n // BUNDLE_ID env vars (server SSR).\n renderBuiltUrl(filename, { type, hostType }) {\n if (mode !== 'preview' && (type === 'asset' || type === 'public')) {\n if (hostType === 'css') {\n return { relative: true };\n }\n\n // WARNING: This runtime code is embedded in the bundle for EVERY asset/public file.\n // At large scale (hundreds of assets), this code snippet is duplicated hundreds of times.\n // Keep this code as minimal as possible to avoid significant bundle size bloat.\n const runtimeCode = `(typeof window !== 'undefined' ? window._BUNDLE_PATH : ((process.env.MRT_ENV_BASE_PATH??'')+'/mobify/bundle/'+(process.env.BUNDLE_ID??'local')+'/client/')) + ${JSON.stringify(filename)}`;\n\n return {\n runtime: runtimeCode,\n };\n }\n },\n },\n };\n },\n configResolved(config) {\n resolvedConfig = config;\n\n // @ts-expect-error: react-router plugin context is not typed\n buildDirectory = config.__reactRouterPluginContext.reactRouterConfig.buildDirectory;\n },\n buildApp: {\n order: 'post',\n handler: async () => {\n await createManagedRuntimeBundleAssets();\n },\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\n\nconst VIRTUAL_MODULE_ID = '\\0patched-react-router';\nconst MODULE_TO_PATCH = 'react-router';\n\n/**\n * This plugin intercepts imports of 'react-router' and provides patched versions\n * of specific components (like Scripts) with custom logic.\n *\n * @returns {Plugin} A Vite plugin for patching react-router components\n */\nexport const patchReactRouterPlugin = (): Plugin => {\n let isTestMode = false;\n let isDevMode = false;\n\n return {\n name: 'storefront-next:patch-react-router',\n // must be enforce: 'pre'\n // otherwise the react-router plugin will resolve the module first\n // and we will not be able to enhance the module with our custom logic\n enforce: 'pre',\n config(_config, { mode }) {\n // Detect test mode to disable patching\n // Virtual module IDs with \\0 prefix cause path resolution errors on Windows\n // when Vitest tries to resolve them with vi.importActual\n isTestMode = mode === 'test';\n // Detect dev mode to avoid duplicate React Router instances\n isDevMode = mode === 'development';\n },\n configEnvironment(name) {\n if (isTestMode) {\n return;\n }\n // Skip noExternal in dev mode to avoid duplicate React Router instances\n // This is acceptable because bundle config injection is only needed for\n // MRT production deployments, not local development\n if (isDevMode) {\n return;\n }\n if (name === 'ssr') {\n // By default, on dev mode, Vite does not process external modules like react-router\n // but we need to patch it, so we mark react-router as noExternal\n // so that it is included in the Vite plugin pipeline, and we can patch it\n return {\n resolve: {\n noExternal: ['react-router'],\n },\n };\n }\n },\n resolveId(id, importer) {\n // Skip patching in test mode to avoid Windows path resolution errors\n // Skip patching in dev mode to avoid duplicate React Router instances\n if (isTestMode || isDevMode) {\n return null;\n }\n if (id === MODULE_TO_PATCH) {\n // In the virtual module, we need to import the same react-router module\n // and then re-export everything from it, and override a subset of the exports.\n // This following code is to make sure that the import from the virtual module\n // imports the same react-router module and not causing infinite loop.\n if (importer === VIRTUAL_MODULE_ID || importer?.includes('storefront-next-dev')) {\n return null;\n }\n return VIRTUAL_MODULE_ID;\n }\n return null;\n },\n\n load(id) {\n // Skip patching in test mode\n // Skip patching in dev mode to avoid duplicate React Router instances\n if (isTestMode || isDevMode) {\n return null;\n }\n if (id === VIRTUAL_MODULE_ID) {\n const scriptsImportPath = '@salesforce/storefront-next-dev/react-router/Scripts';\n\n const code = `\n export * from 'react-router';\n export { Scripts } from '${scriptsImportPath}';\n `;\n return code;\n }\n return null;\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { parse } from '@babel/parser';\nimport {\n isJSXIdentifier,\n isJSXAttribute,\n jsxText,\n isJSXElement,\n isJSXFragment,\n jsxElement,\n jsxOpeningElement,\n jsxIdentifier,\n jsxClosingElement,\n jsxFragment,\n jsxOpeningFragment,\n jsxClosingFragment,\n type JSXElement as BabelJSXElement,\n type File,\n type VariableDeclaration as BabelVariableDeclaration,\n type ReturnStatement as BabelReturnStatement,\n type ImportDeclaration as BabelImportDeclaration,\n} from '@babel/types';\nimport fs from 'fs-extra';\nimport { generate } from '@babel/generator';\nimport path from 'path';\n\nimport traverseModule, { type NodePath } from '@babel/traverse';\nimport { logger } from '../utils/logger';\n\nexport interface TargetComponentConfig {\n targetId: string;\n path: string;\n namespace: string;\n componentName: string;\n order: number;\n /** Optional label used by UITarget dev mode overlay for grouping/filtering (e.g. \"t/my-branch/W-123\", \"pr:1384\"). */\n hint?: string;\n}\n\nexport interface TargetContextProviderConfig {\n path: string;\n namespace: string;\n componentName: string;\n order: number;\n}\n\nexport interface ActionHookConfig {\n hookId: string;\n path: string;\n namespace: string;\n handlerName: string;\n order: number;\n}\n\nexport type TargetComponentRegistry = Record<string, TargetComponentConfig[]>;\nexport type ActionHookRegistry = Record<string, ActionHookConfig[]>;\n\nconst traverse = (traverseModule as unknown as { default: typeof traverseModule }).default || traverseModule;\n\nconst TARGET_COMPONENT_TAG = 'UITarget';\nconst TARGET_PROVIDERS_TAG = 'UITargetProviders';\nconst TARGET_ID_ATTRIBUTE = 'targetId';\n\n// Pre-compiled regex for JSX element detection — avoids false substring matches\n// (e.g. 'UITargetProviders'.includes('UITarget') === true).\n// Matches only when the tag is followed by whitespace, '/', or '>' as in valid JSX.\nconst TARGET_COMPONENT_JSX_RE = new RegExp(`<${TARGET_COMPONENT_TAG}[\\\\s/>]`);\n\n/**\n * Find and replace the TargetProviders tags with the corresponding context providers\n * @param element - the AST element to replace\n * @param contextProviders - the context providers to replace\n */\nfunction findAndReplaceProviders(\n element: NodePath<BabelJSXElement>,\n contextProviders: TargetContextProviderConfig[]\n): void {\n if (isJSXIdentifier(element.node.openingElement.name, { name: TARGET_PROVIDERS_TAG })) {\n // if there are context providers, replace the TargetProviders tag with the corresponding context providers in reverse order\n if (contextProviders.length > 0) {\n let nested = element.node.children;\n for (let i = contextProviders.length - 1; i >= 0; i--) {\n const contextProvider = contextProviders[i];\n const componentName = contextProvider.componentName;\n const providerElement = jsxElement(\n jsxOpeningElement(jsxIdentifier(componentName), [], false),\n jsxClosingElement(jsxIdentifier(componentName)),\n nested,\n false\n );\n nested = [providerElement];\n }\n element.replaceWithMultiple(nested);\n } else {\n // no replacement needed, just remove the TargetProviders tag,\n // but wrap the children in a JSXFragment\n element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), element.node.children));\n }\n }\n}\n\n/**\n * Find and replace the target component with the replacement code\n * @param componentName - the name of the component to replace\n * @param element - the AST element as the replacement candidate\n * @param targetRegistry - the target registry\n * @returns the targetId that was replaced, or null if no replacement was found\n */\nfunction findAndReplaceComponent(\n componentName: string,\n element: NodePath<BabelJSXElement>,\n targetRegistry: TargetComponentRegistry\n): string | null {\n let targetIdReplaced = null;\n if (isJSXIdentifier(element.node.openingElement.name, { name: componentName })) {\n let replaced = false;\n // Find the \"targetId\" property value from the JSX node, then replace the element with the replacement code\n // if no matching replacement is found, remove the element\n if (Array.isArray(element.node.openingElement.attributes)) {\n const attr = element.node.openingElement.attributes.find(\n (a) => isJSXAttribute(a) && isJSXIdentifier(a.name, { name: TARGET_ID_ATTRIBUTE })\n );\n const targetId =\n attr && isJSXAttribute(attr) && attr.value && 'value' in attr.value ? attr.value.value : undefined;\n if (targetId == null) {\n throw new Error(`UITarget must contain a targetId attribute`);\n }\n if (targetRegistry[targetId] && targetRegistry[targetId].length > 0) {\n const hasChildren = element.node.children.length > 0;\n if (hasChildren) {\n // Preserve wrapped children and nest replacement components in order,\n // so wrappers continue to work and remain valid in JSX expression contexts.\n let nestedChildren = element.node.children;\n for (let i = targetRegistry[targetId].length - 1; i >= 0; i--) {\n const targetComponent = targetRegistry[targetId][i];\n const replacementElement = jsxElement(\n jsxOpeningElement(jsxIdentifier(targetComponent.componentName), [], false),\n jsxClosingElement(jsxIdentifier(targetComponent.componentName)),\n nestedChildren,\n false\n );\n nestedChildren = [replacementElement];\n }\n element.replaceWith(nestedChildren[0] as BabelJSXElement);\n } else {\n // Keep historical behavior for self-closing UITarget:\n // emit each replacement as a sibling component.\n const components = targetRegistry[targetId].map((targetComponent: TargetComponentConfig) => {\n return jsxElement(\n jsxOpeningElement(jsxIdentifier(targetComponent.componentName), [], true),\n null,\n [],\n true\n );\n });\n if (components.length > 1) {\n element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), components));\n } else {\n element.replaceWith(components[0]);\n }\n }\n targetIdReplaced = targetId;\n replaced = true;\n }\n }\n if (!replaced) {\n if (element.node.children && element.node.children.length > 0) {\n // Keep children in a fragment so replacement is valid in all contexts,\n // including JSX expression containers (e.g. prop values).\n element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), element.node.children));\n } else {\n // No children and no target replacement:\n // use an empty fragment instead of remove() so UITarget inside\n // JSX expression containers (e.g. prop values) remains valid JSX.\n element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), []));\n }\n }\n }\n return targetIdReplaced;\n}\n\n/**\n * Run a replacement pass on the AST\n * @param ast - the AST to traverse\n * @param tagName - the name of the tag to replace\n * @param targetRegistry - the target registry\n * @param contextProviders - the context providers to replace\n * @returns a set of targetIds that were replaced\n */\nfunction runReplacementPass(\n ast: File,\n tagName: string,\n targetRegistry: TargetComponentRegistry | null = null,\n contextProviders: TargetContextProviderConfig[] | null = null\n): Set<string> {\n const targetIdsReplaced = new Set<string>();\n // Helper function to apply the replacement for a given path, targeting either the UITarget or TargetProviders tag\n const applyReplacement = (pathToReplace: NodePath<BabelJSXElement>) => {\n if (targetRegistry) {\n const replacedId = findAndReplaceComponent(tagName, pathToReplace, targetRegistry);\n if (replacedId) targetIdsReplaced.add(replacedId);\n } else if (contextProviders) {\n findAndReplaceProviders(pathToReplace, contextProviders);\n }\n };\n traverse(ast, {\n // look for variable declarations that contains <UITarget .../> in JSX fragment\n VariableDeclaration(nodePath: NodePath<BabelVariableDeclaration>) {\n const declarationPaths = nodePath.get('declarations');\n const declarationsArray = Array.isArray(declarationPaths) ? declarationPaths : [declarationPaths];\n\n for (const declarationPath of declarationsArray) {\n const initPath = declarationPath.get('init');\n if (initPath && isJSXElement(initPath.node)) {\n const content = generate(initPath.node).code;\n if (new RegExp(`<(${tagName})(\\\\s|\\\\/|>)`).test(content)) {\n // Handle the init node itself\n applyReplacement(initPath as NodePath<BabelJSXElement>);\n\n // Traverse nested JSX elements with the same handler\n initPath.traverse({\n JSXElement(inner: NodePath<BabelJSXElement>) {\n applyReplacement(inner);\n },\n });\n }\n }\n }\n },\n // look for return statements that contains the tag\n ReturnStatement(nodePath: NodePath<BabelReturnStatement>) {\n const arg = nodePath.node.argument;\n if (!isJSXElement(arg) && !isJSXFragment(arg)) {\n return;\n }\n nodePath.traverse({\n JSXElement(inner: NodePath<BabelJSXElement>) {\n applyReplacement(inner);\n },\n });\n },\n });\n return targetIdsReplaced;\n}\n\n/**\n * Build the import statements for the target components\n * @param targetIds - the targetIds that were replaced\n * @param targetRegistry - the target registry\n * @returns the import statements\n */\nfunction buildReplacementImportStatements(targetIds: Set<string>, targetRegistry: TargetComponentRegistry): string {\n const importStatements = new Set<string>();\n for (const targetId of targetIds) {\n const targetComponents = targetRegistry[targetId];\n for (const targetComponent of targetComponents) {\n importStatements.add(\n `import ${targetComponent.componentName} from '@/${targetComponent.path.replace('.tsx', '')}';`\n );\n }\n }\n return Array.from(importStatements).join('\\n');\n}\n\nexport function transformTargets(\n code: string,\n targetRegistry: TargetComponentRegistry,\n contextProviders: TargetContextProviderConfig[]\n): string | null {\n if (!TARGET_COMPONENT_JSX_RE.test(code) && !code.includes(TARGET_PROVIDERS_TAG)) {\n return null;\n }\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n });\n\n // replace UITarget tags with the corresponding components\n if (TARGET_COMPONENT_JSX_RE.test(code)) {\n const targetIdsReplaced = runReplacementPass(ast, TARGET_COMPONENT_TAG, targetRegistry, null);\n const replacementImportStatements = buildReplacementImportStatements(targetIdsReplaced, targetRegistry);\n // Single import rewrite pass — use exact equality to avoid matching '@/targets/ui-target-providers'\n traverse(ast, {\n ImportDeclaration(nodePath: NodePath<BabelImportDeclaration>) {\n const source = nodePath.node.source.value;\n if (source === '@/targets/ui-target') {\n nodePath.replaceWith(jsxText(replacementImportStatements));\n }\n },\n });\n }\n\n // replace TargetProviders tags with the corresponding components\n if (code.includes(TARGET_PROVIDERS_TAG)) {\n // add import statements for the context providers\n const importStatements = new Set<string>();\n for (const contextProvider of contextProviders) {\n importStatements.add(\n `import ${contextProvider.componentName} from '@/${contextProvider.path.replace('.tsx', '')}';`\n );\n }\n const replacementImportStatements = Array.from(importStatements).join('\\n');\n // Single import rewrite pass\n traverse(ast, {\n ImportDeclaration(nodePath: NodePath<BabelImportDeclaration>) {\n const source = nodePath.node.source.value;\n if (source === '@/targets/ui-target-providers') {\n nodePath.replaceWith(jsxText(replacementImportStatements));\n }\n },\n });\n runReplacementPass(ast, TARGET_PROVIDERS_TAG, null, contextProviders);\n }\n return generate(ast).code;\n}\n\n/**\n * Build the target registry from the extension directories\n * @param rootDir - the root directory of the project\n * @param sourceDir - the source directory of the project\n * @returns the target registry\n */\nexport function buildTargetRegistry(\n rootDir: string,\n options: { isProduction?: boolean } = {}\n): {\n componentRegistry: TargetComponentRegistry;\n contextProviders: TargetContextProviderConfig[];\n actionHookRegistry: ActionHookRegistry;\n} {\n const componentRegistry: TargetComponentRegistry = {};\n const contextProviders: TargetContextProviderConfig[] = [];\n const actionHookRegistry: ActionHookRegistry = {};\n const extensionDirPath = path.join(rootDir, 'extensions');\n const extensionDirs = fs.readdirSync(extensionDirPath, { withFileTypes: true });\n\n const getNamespaceAndComponentName = (dir: fs.Dirent, filePath: string) => {\n const namespace = dir.name\n .split('-')\n .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join('');\n const fileName = filePath\n .split('/')\n .pop()\n ?.replace(/\\.(tsx|ts|jsx|js)$/, '');\n const baseComponentName = fileName\n ?.split(/[-.]/)\n .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join('');\n const componentName = `${namespace}_${baseComponentName}`;\n return { namespace, componentName };\n };\n const TARGET_CONFIG_FILENAME = 'target-config.json';\n\n for (const dir of extensionDirs) {\n if (dir.isDirectory()) {\n const configPath = path.join(extensionDirPath, dir.name, TARGET_CONFIG_FILENAME);\n if (fs.existsSync(configPath)) {\n const extensionConfig = fs.readJsonSync(configPath);\n // Skip dev-only extensions in production builds\n if (options.isProduction && extensionConfig.devOnly === true) {\n continue;\n }\n if (extensionConfig && extensionConfig.components) {\n for (const component of extensionConfig.components) {\n if (component.enabled === false) continue;\n const { targetId, path: componentPath, order = 0 } = component;\n if (targetId && componentPath) {\n if (!componentRegistry[targetId]) {\n componentRegistry[targetId] = [];\n }\n const { namespace, componentName } = getNamespaceAndComponentName(dir, componentPath);\n componentRegistry[targetId].push({\n targetId,\n path: componentPath,\n order,\n namespace,\n componentName,\n });\n }\n }\n }\n if (extensionConfig && extensionConfig.contextProviders) {\n for (const contextProvider of extensionConfig.contextProviders) {\n const { path: providerPath, order = 0 } = contextProvider;\n if (providerPath) {\n const { namespace, componentName } = getNamespaceAndComponentName(dir, providerPath);\n contextProviders.push({ path: providerPath, namespace, componentName, order });\n }\n }\n }\n if (extensionConfig && extensionConfig.actionHooks) {\n for (const hook of extensionConfig.actionHooks) {\n const { hookId, handler, order = 0 } = hook;\n if (hookId && handler) {\n if (!actionHookRegistry[hookId]) {\n actionHookRegistry[hookId] = [];\n }\n const { namespace, componentName: handlerName } = getNamespaceAndComponentName(\n dir,\n handler\n );\n actionHookRegistry[hookId].push({\n hookId,\n path: handler,\n order,\n namespace,\n handlerName,\n });\n }\n }\n }\n }\n }\n }\n // Sort each extension's components by order\n for (const targetId in componentRegistry) {\n componentRegistry[targetId].sort((a, b) => a.order - b.order);\n }\n contextProviders.sort((a, b) => a.order - b.order);\n for (const hookId in actionHookRegistry) {\n actionHookRegistry[hookId].sort((a, b) => a.order - b.order);\n }\n\n // Warn about duplicate order values — execution order is non-deterministic\n for (const targetId in componentRegistry) {\n const entries = componentRegistry[targetId];\n const seen = new Map<number, string>();\n for (const entry of entries) {\n const existing = seen.get(entry.order);\n if (existing) {\n logger.warn(\n `[storefront-next] UITarget \"${targetId}\": components \"${existing}\" and \"${entry.componentName}\" have the same order (${entry.order}). Execution order between them is non-deterministic. Assign distinct order values.`\n );\n }\n seen.set(entry.order, entry.componentName);\n }\n }\n for (const hookId in actionHookRegistry) {\n const entries = actionHookRegistry[hookId];\n const seen = new Map<number, string>();\n for (const entry of entries) {\n const existing = seen.get(entry.order);\n if (existing) {\n logger.warn(\n `[storefront-next] Action hook \"${hookId}\": handlers \"${existing}\" and \"${entry.handlerName}\" have the same order (${entry.order}). Execution order between them is non-deterministic. Assign distinct order values.`\n );\n }\n seen.set(entry.order, entry.handlerName);\n }\n }\n\n return { componentRegistry, contextProviders, actionHookRegistry };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n buildTargetRegistry,\n transformTargets,\n type TargetContextProviderConfig,\n type TargetComponentRegistry,\n} from '../extensibility/target-utils';\nimport path from 'path';\nimport type { ResolvedConfig } from 'vite';\nimport { logger } from '../logger';\n\n// --- Vite Plugin --------------------------------------------------------------\n\nexport function transformTargetPlaceholderPlugin() {\n // Memoize the extension registry - build it once and reuse across all file transformations\n let componentRegistry: TargetComponentRegistry;\n let contextProviders: TargetContextProviderConfig[];\n let sourceDir: string;\n let isProduction = false;\n\n return {\n name: 'storefront-next:transform-target-placeholder',\n enforce: 'pre' as const, // run before Vite's default TS/JS transforms\n configResolved(config: ResolvedConfig) {\n // extract source directory from vite config\n sourceDir =\n config.resolve.alias.find((alias) => alias.find === '@')?.replacement ||\n path.resolve(__dirname, './src');\n isProduction = config.mode === 'production';\n },\n buildStart() {\n // Build the registry once at the start of the build\n ({ componentRegistry, contextProviders } = buildTargetRegistry(sourceDir, { isProduction }));\n },\n\n transform(code: string, id: string) {\n // Skip UITarget transformation if dev mode is active\n // Let targetDevModePlugin handle it instead (adds visual markers)\n if (process.env.VITE_UI_TARGET_DEV_MODE === 'true') {\n return null;\n }\n\n try {\n const transformedCode = transformTargets(code, componentRegistry, contextProviders);\n if (transformedCode) {\n return {\n code: transformedCode,\n map: null,\n };\n }\n return null;\n } catch (err: unknown) {\n logger.error(`UITarget replace ERROR in ${id}: ${err instanceof Error ? err.stack : String(err)}`);\n throw err;\n }\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { buildTargetRegistry, type ActionHookRegistry } from '../extensibility/target-utils';\nimport path from 'path';\n\nconst ACTION_HOOKS_VIRTUAL_ID = 'virtual:action-hooks';\nconst ACTION_HOOKS_RESOLVED_ID = `\\0${ACTION_HOOKS_VIRTUAL_ID}`;\n\n/**\n * Generate the virtual module code for the action hook registry.\n *\n * The generated module exports a `hookRegistry` map of hookId → handler[],\n * and a `runHook` function that executes registered handlers in order.\n */\nexport function generateActionHooksModule(actionHookRegistry: ActionHookRegistry): string {\n const imports: string[] = [];\n const registryEntries: string[] = [];\n\n for (const [hookId, handlers] of Object.entries(actionHookRegistry)) {\n const handlerNames: string[] = [];\n for (const handler of handlers) {\n const importPath = `@/${handler.path.replace(/\\.(ts|tsx|js|jsx)$/, '')}`;\n imports.push(`import ${handler.handlerName} from '${importPath}';`);\n handlerNames.push(handler.handlerName);\n }\n registryEntries.push(` '${hookId}': [${handlerNames.join(', ')}]`);\n }\n\n return `${imports.join('\\n')}\n\nconst HANDLER_TIMEOUT_MS = 5000;\n\nconst hookRegistry = {\n${registryEntries.join(',\\n')}\n};\n\nfunction withTimeout(promise, ms, label) {\n return Promise.race([\n promise,\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error(\\`Action hook handler timed out after \\${ms}ms: \\${label}\\`)), ms)\n ),\n ]);\n}\n\nexport async function runHook(hookId, context, options = {}) {\n const handlers = hookRegistry[hookId];\n if (!handlers || handlers.length === 0) {\n return context;\n }\n let currentContext = context;\n for (const handler of handlers) {\n try {\n const result = await withTimeout(handler(currentContext), HANDLER_TIMEOUT_MS, hookId);\n currentContext = result ?? currentContext;\n } catch (error) {\n if (error && error.name === 'ActionHookError') {\n throw error;\n }\n if (options.blocking) {\n throw error;\n }\n console.error(\\`[action-hooks] handler for \"\\${hookId}\" failed, skipping to next handler\\`, error);\n }\n }\n return currentContext;\n}\n`;\n}\n\n/**\n * Vite plugin that resolves `virtual:action-hooks` to a generated module\n * mapping hookIds to their registered handlers.\n */\nexport function actionHooksPlugin(): Plugin {\n let actionHookRegistry: ActionHookRegistry;\n let sourceDir: string;\n let isProduction = false;\n\n return {\n name: 'storefront-next:action-hooks',\n enforce: 'pre' as const,\n configResolved(config: ResolvedConfig) {\n sourceDir =\n config.resolve.alias.find((alias) => alias.find === '@')?.replacement ||\n path.resolve(__dirname, './src');\n isProduction = config.mode === 'production';\n },\n buildStart() {\n const registry = buildTargetRegistry(sourceDir, { isProduction });\n actionHookRegistry = registry.actionHookRegistry;\n },\n resolveId(id: string) {\n if (id === ACTION_HOOKS_VIRTUAL_ID) {\n return ACTION_HOOKS_RESOLVED_ID;\n }\n },\n load(id: string) {\n if (id === ACTION_HOOKS_RESOLVED_ID) {\n return generateActionHooksModule(actionHookRegistry);\n }\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ViteDevServer, ResolvedConfig } from 'vite';\nimport path from 'path';\nimport { logger } from '../logger';\n\nexport const watchConfigFilesPlugin = () => {\n let viteConfig: ResolvedConfig;\n return {\n name: 'storefront-next:watch-config-files',\n configResolved(config: ResolvedConfig) {\n viteConfig = config;\n },\n configureServer(server: ViteDevServer) {\n const aliases = viteConfig.resolve.alias;\n const root = Object.values(aliases).find((alias) => alias.find === '@')?.replacement || 'src';\n // Use path.posix.join to ensure forward slashes for glob patterns (required even on Windows)\n const glob = path.posix.join(root, 'extensions', '**', 'target-config.json');\n server.watcher.add(glob);\n\n const onChange = (file: string) => {\n if (file.endsWith('target-config.json')) {\n logger.debug(`🔁 target-config.json changed: ${file}`);\n void server.restart();\n }\n };\n\n server.watcher.on('add', onChange);\n server.watcher.on('change', onChange);\n server.watcher.on('unlink', onChange);\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Plugin } from 'vite';\nimport { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { resolve, relative, dirname } from 'path';\nimport { glob } from 'glob';\nimport {\n Project,\n Node,\n type Decorator,\n type SourceFile,\n ts,\n type FunctionDeclaration,\n type VariableStatement,\n} from 'ts-morph';\nimport { logger } from '../logger';\n\n// Default component group when none is specified in the decorator\nconst DEFAULT_COMPONENT_GROUP = 'storefrontnext_base';\n\n/**\n * Information about a discovered component\n */\nexport interface ComponentInfo {\n /** Component ID from @Component decorator */\n id: string;\n /** Absolute path to the component file */\n filePath: string;\n /** Relative import path from registry.ts */\n relativePath: string;\n /** Whether the component has loader exports */\n hasLoader: boolean;\n /** Whether the component has clientLoader export */\n hasClientLoader: boolean;\n /** Whether the component has fallback export */\n hasFallback: boolean;\n}\n\n/**\n * Configuration options for the static registry plugin\n */\nexport interface StaticRegistryPluginConfig {\n /**\n * Path to the components directory to scan\n * @default 'src/components'\n */\n componentPath?: string;\n\n /**\n * Path to the registry file to update\n * Note: The registry file must contain STATIC_REGISTRY_START and STATIC_REGISTRY_END markers\n * and must export a 'registry' variable (or use registryIdentifier to specify a different name)\n * @default 'src/lib/registry.ts'\n */\n registryPath?: string;\n\n /**\n * Name of the registry variable to use in generated code\n * @default 'registry'\n */\n registryIdentifier?: string;\n\n /**\n * Whether to fail the build on registry generation errors\n * @default true\n */\n failOnError?: boolean;\n}\n\n/**\n * Extracts component ID and group from @Component decorator using ts-morph AST parsing\n */\nexport function extractComponentInfo(decorator: Decorator): { id: string; group: string } | null {\n const callExpression = decorator.getCallExpression();\n if (!callExpression) {\n return null;\n }\n\n const args = callExpression.getArguments();\n if (args.length === 0) {\n return null;\n }\n\n // First argument should be the component ID string (string literal or template literal)\n const firstArg = args[0];\n\n let baseComponentId: string;\n if (Node.isStringLiteral(firstArg)) {\n baseComponentId = firstArg.getLiteralValue();\n } else if (Node.isNoSubstitutionTemplateLiteral(firstArg)) {\n baseComponentId = firstArg.getText().slice(1, -1); // Remove backticks\n } else if (Node.isTemplateExpression(firstArg)) {\n // Template literals with interpolation cannot be resolved at build time\n throw new Error(\n `@Component id must be a simple string literal or backtick string without interpolation. Found: ${firstArg.getText()}`\n );\n } else {\n return null;\n }\n let group = DEFAULT_COMPONENT_GROUP;\n\n // Check if there's a second argument with metadata object\n if (args.length > 1) {\n const secondArg = args[1];\n if (Node.isObjectLiteralExpression(secondArg)) {\n // Look for group property in the metadata object\n const groupProperty = secondArg.getProperty('group');\n if (groupProperty && Node.isPropertyAssignment(groupProperty)) {\n const initializer = groupProperty.getInitializer();\n if (initializer && Node.isStringLiteral(initializer)) {\n group = initializer.getLiteralValue();\n }\n }\n }\n }\n\n return {\n id: `${group}.${baseComponentId}`,\n group,\n };\n}\n\n/**\n * Checks if a source file has a specific named export using ts-morph AST parsing\n */\nexport function hasNamedExport(sourceFile: SourceFile, exportName: string): boolean {\n // Check for function declarations: export function exportName(...)\n const functionDeclarations = sourceFile\n .getFunctions()\n .filter((func: FunctionDeclaration) => func.hasExportKeyword() && func.getName() === exportName);\n\n if (functionDeclarations.length > 0) {\n return true;\n }\n\n // Check for variable declarations: export const exportName = ...\n const variableStatements = sourceFile\n .getVariableStatements()\n .filter((stmt: VariableStatement) => stmt.hasExportKeyword());\n\n for (const stmt of variableStatements) {\n const declarations = stmt.getDeclarations();\n for (const decl of declarations) {\n if (decl.getName() === exportName) {\n return true;\n }\n }\n }\n\n // Check for export assignments: export { exportName } or export { localName as exportName }\n const exportDeclarations = sourceFile.getExportDeclarations();\n for (const exportDecl of exportDeclarations) {\n const namedExports = exportDecl.getNamedExports();\n for (const namedExport of namedExports) {\n // Check both the local name and the alias (if any)\n const localName = namedExport.getName();\n const aliasName = namedExport.getAliasNode()?.getText();\n\n if (localName === exportName || aliasName === exportName) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Checks if a source file has a fallback export (including default exports with 'fallback' in name)\n */\nexport function hasFallbackExport(sourceFile: SourceFile): boolean {\n // Check for named export 'fallback'\n if (hasNamedExport(sourceFile, 'fallback')) {\n return true;\n }\n\n // Check for default function declarations with 'fallback' in name\n const functions = sourceFile\n .getFunctions()\n .filter((func: FunctionDeclaration) => func.hasExportKeyword() && func.hasDefaultKeyword());\n\n for (const func of functions) {\n const name = func.getName();\n if (name && name.toLowerCase().includes('fallback')) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Scans all files in the component directory for @Component decorators and extracts metadata using ts-morph\n */\nexport async function scanComponents(\n project: Project,\n projectRoot: string,\n componentPath: string,\n registryPath: string\n): Promise<ComponentInfo[]> {\n // Scan all TypeScript/TSX files in the component directory recursively\n const componentPattern = `${componentPath}/**/*.{ts,tsx}`;\n const componentFiles = await glob(componentPattern, {\n cwd: projectRoot,\n absolute: true,\n });\n\n logger.debug(`🔍 Scanning ${componentFiles.length} files in ${componentPath}...`);\n\n const components: ComponentInfo[] = [];\n const registryDir = dirname(resolve(projectRoot, registryPath));\n\n for (const filePath of componentFiles) {\n try {\n // Read file content and create source file in ts-morph project\n const content = readFileSync(filePath, 'utf-8');\n const sourceFile = project.createSourceFile(filePath, content, { overwrite: true });\n\n // Find all classes with @Component decorator\n const classes = sourceFile.getClasses();\n\n for (const classDeclaration of classes) {\n const decorators = classDeclaration.getDecorators();\n\n for (const decorator of decorators) {\n const decoratorName = decorator.getName();\n\n if (decoratorName === 'Component') {\n const componentInfo = extractComponentInfo(decorator);\n\n if (componentInfo) {\n // Calculate relative path from registry.ts to component\n let relativePath = relative(registryDir, filePath)\n .replace(/\\\\/g, '/') // Normalize Windows paths\n .replace(/\\.(ts|tsx)$/, ''); // Remove extension\n\n // Ensure relative path starts with './' or '../'\n if (!relativePath.startsWith('.')) {\n relativePath = `./${relativePath}`;\n }\n\n // Check for React Router style loader exports using AST parsing\n const hasLoaderExport = hasNamedExport(sourceFile, 'loader');\n const hasClientLoaderExport = hasNamedExport(sourceFile, 'clientLoader');\n const hasFallback = hasFallbackExport(sourceFile);\n\n components.push({\n id: componentInfo.id,\n filePath,\n relativePath,\n hasLoader: hasLoaderExport,\n hasClientLoader: hasClientLoaderExport,\n hasFallback,\n });\n\n const exports = [];\n if (hasLoaderExport) {\n exports.push('loader');\n }\n if (hasClientLoaderExport) {\n exports.push('clientLoader');\n }\n if (hasFallback) {\n exports.push('fallback');\n }\n const exportsText = exports.length > 0 ? ` (with ${exports.join(', ')})` : '';\n logger.debug(` ✅ Found component: ${componentInfo.id} → ${relativePath}${exportsText}`);\n }\n }\n }\n }\n } catch (error) {\n logger.warn(`⚠️ Could not process ${filePath}: ${(error as Error).message}`);\n // Continue processing other files even if one fails\n }\n }\n\n return components;\n}\n\n/**\n * Generates the initializeRegistry function code\n */\nexport function generateRegistryCode(components: ComponentInfo[], registryIdentifier: string = 'registry'): string {\n // Ensure deterministic output: sort by component id, then by relativePath as a stable tiebreaker\n const sorted = [...components].sort(\n (a, b) => a.id.localeCompare(b.id) || a.relativePath.localeCompare(b.relativePath)\n );\n\n if (sorted.length === 0) {\n return `\n/* eslint-disable */\n/**\n * Initialize the static component registry.\n * This function is auto-generated by the staticRegistry Vite plugin.\n * \n * DO NOT EDIT THIS FUNCTION MANUALLY - it will be overwritten on next build.\n */\nexport function initializeRegistry(targetRegistry = ${registryIdentifier}): void {\n // No components found with @Component decorators\n}\n`;\n }\n\n const registrations = sorted\n .map(({ id, relativePath, hasLoader, hasClientLoader, hasFallback }) => {\n if (hasLoader || hasClientLoader || hasFallback) {\n // Register with metadata - tell registry the function/component names\n const metadata = [];\n if (hasLoader) {\n metadata.push(`loader: 'loader'`);\n }\n if (hasClientLoader) {\n metadata.push(`clientLoader: 'clientLoader'`);\n }\n if (hasFallback) {\n metadata.push(`fallback: 'fallback'`);\n }\n\n return ` targetRegistry.registerImporter('${id}', () => import('${relativePath}'), { ${metadata.join(', ')} });`;\n } else {\n return ` targetRegistry.registerImporter('${id}', () => import('${relativePath}'));`;\n }\n })\n .join('\\n');\n\n return `\n/* eslint-disable */\n/**\n * Initialize the static component registry.\n * This function is auto-generated by the staticRegistry Vite plugin.\n * \n * DO NOT EDIT THIS FUNCTION MANUALLY - it will be overwritten on next build.\n * \n * Components registered: ${sorted.map((c) => c.id).join(', ')}\n */\nexport function initializeRegistry(targetRegistry = ${registryIdentifier}): void {\n${registrations}\n}\n`;\n}\n\n/**\n * Updates the registry.ts file with the generated code\n */\nexport function updateRegistryFile(registryFilePath: string, generatedCode: string): boolean {\n let existingContent: string;\n\n // Check if file exists, if not create a basic one\n if (!existsSync(registryFilePath)) {\n logger.debug('📝 Creating new registry file...');\n\n // Create a basic registry file\n const basicRegistryContent = `import { ComponentRegistry } from '@/lib/component-registry';\n\n// Create the component registry instance\nexport const registry = new ComponentRegistry();\n\n// STATIC_REGISTRY_START\n// Generated content will be inserted here by the static registry plugin\n// STATIC_REGISTRY_END\n`;\n writeFileSync(registryFilePath, basicRegistryContent, 'utf-8');\n existingContent = basicRegistryContent;\n } else {\n try {\n existingContent = readFileSync(registryFilePath, 'utf-8');\n } catch (error) {\n throw new Error(`Failed to read registry file: ${(error as Error).message}`);\n }\n }\n\n // Use explicit markers for generated content\n const startMarker = '// STATIC_REGISTRY_START';\n const endMarker = '// STATIC_REGISTRY_END';\n\n const startIndex = existingContent.indexOf(startMarker);\n const endIndex = existingContent.indexOf(endMarker);\n\n if (startIndex === -1 || endIndex === -1) {\n throw new Error(\n `Registry file ${registryFilePath} is missing static registry markers. ` +\n `Please add \"${startMarker}\" and \"${endMarker}\" markers to define the generated content area.`\n );\n }\n\n const before = existingContent.slice(0, startIndex + startMarker.length);\n const after = existingContent.slice(endIndex);\n\n const updatedContent = `${before}\\n${generatedCode}\\n${after}`;\n\n // Skip write if content is unchanged to avoid triggering unnecessary HMR cascades.\n // Without this check, every component file save writes static-registry.ts even when\n // the registry hasn't changed, which triggers Vite's file watcher -> SSR page reload\n // -> root.tsx HMR -> app.css HMR in a rapid loop.\n if (updatedContent === existingContent) {\n logger.debug(`⏭️ Registry unchanged, skipping write: ${registryFilePath}`);\n return false;\n }\n\n try {\n writeFileSync(registryFilePath, updatedContent, 'utf-8');\n logger.debug(`💾 Updated registry file: ${registryFilePath}`);\n return true;\n } catch (error) {\n throw new Error(`Failed to write registry file: ${(error as Error).message}`);\n }\n}\n\n/**\n * Vite plugin that generates static component registry based on @Component decorators.\n *\n * This plugin scans component files for @Component decorators and automatically generates\n * a static registry function that pre-registers all components with their import paths.\n * This eliminates the need for manual component registration and provides build-time\n * optimization for component discovery.\n *\n * @param config - Configuration options for the plugin\n * @returns A Vite plugin that generates static component registrations\n *\n * @example\n * // In vite.config.ts\n * export default defineConfig({\n * plugins: [\n * staticRegistryPlugin({\n * componentPath: 'src/components',\n * registryPath: 'src/lib/registry.ts',\n * verbose: true\n * })\n * ]\n * })\n */\nexport const staticRegistryPlugin = (config: StaticRegistryPluginConfig = {}): Plugin => {\n const {\n componentPath = 'src/components',\n registryPath = 'src/lib/static-registry.ts',\n registryIdentifier = 'registry',\n failOnError = true,\n } = config;\n\n let projectRoot: string;\n\n const runRegistryGeneration = async () => {\n logger.debug('🚀 Starting static registry generation...');\n\n // Create a fresh Project for this run only\n const project = new Project({\n compilerOptions: {\n target: ts.ScriptTarget.Latest,\n module: ts.ModuleKind.ESNext,\n jsx: ts.JsxEmit.ReactJSX,\n allowJs: true,\n skipLibCheck: true,\n noEmit: true,\n },\n });\n\n // Build AST, extract plain data\n const components = await scanComponents(project, projectRoot, componentPath, registryPath);\n\n // From here on we do not need the AST any more.\n // `components` is just an array of plain objects.\n // `project` will fall out of scope after this function returns and can be GC'd.\n\n logger.debug(`📦 Found ${components.length} components with @Component decorators`);\n\n const generatedCode = generateRegistryCode(components, registryIdentifier);\n const registryFilePath = resolve(projectRoot, registryPath);\n const changed = updateRegistryFile(registryFilePath, generatedCode);\n\n logger.debug('✅ Static registry generation complete!');\n\n return { registryFilePath, changed };\n };\n\n return {\n name: 'storefrontnext:static-registry',\n\n configResolved(resolvedConfig) {\n projectRoot = resolvedConfig.root;\n },\n\n async buildStart() {\n try {\n await runRegistryGeneration();\n } catch (error) {\n logger.error(`❌ Static registry generation failed: ${(error as Error).message}`);\n if (failOnError) {\n throw error;\n }\n logger.warn('⚠️ Continuing build without static registry...');\n }\n },\n\n async handleHotUpdate({ file, server }) {\n const normalizedComponentPath = componentPath.replace(/\\\\/g, '/');\n const normalizedFile = file.replace(/\\\\/g, '/');\n\n if (\n normalizedFile.includes(`/${normalizedComponentPath}/`) &&\n (normalizedFile.endsWith('.ts') || normalizedFile.endsWith('.tsx'))\n ) {\n logger.debug(`🔄 Component file changed: ${file}, regenerating registry...`);\n\n try {\n const { registryFilePath, changed } = await runRegistryGeneration();\n\n // Only reload the registry module if the generated content actually changed.\n // This prevents an HMR cascade: write -> file watcher -> SSR reload ->\n // root.tsx HMR -> app.css HMR -> repeat.\n if (changed) {\n const registryModule = server.moduleGraph.getModuleById(registryFilePath);\n if (registryModule) {\n await server.reloadModule(registryModule);\n }\n logger.debug('✅ Registry regenerated successfully!');\n } else {\n logger.debug('⏭️ Registry unchanged, skipping reload');\n }\n } catch (error) {\n logger.error(`❌ Failed to regenerate registry: ${(error as Error).message}`);\n }\n\n return [];\n }\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { resolve } from 'path';\nimport { pathToFileURL } from 'url';\nimport { logger } from '../logger';\n\n/**\n * Adapter configuration with event toggles\n * Event toggles are a dynamic map - any event name can be toggled\n */\nexport interface AdapterConfig {\n enabled?: boolean;\n eventToggles?: Record<string, boolean>;\n}\n\n/**\n * Expected structure of engagement config from config.server.ts\n */\nexport interface EngagementConfig {\n adapters?: Record<string, AdapterConfig>;\n}\n\n/**\n * Load the engagement config from config.server.ts\n */\nexport async function loadEngagementConfig(projectRoot: string, configPath: string): Promise<EngagementConfig | null> {\n const absoluteConfigPath = resolve(projectRoot, configPath);\n\n try {\n // Use dynamic import with file URL for ESM compatibility\n const configUrl = pathToFileURL(absoluteConfigPath).href;\n const configModule = await import(configUrl);\n const config = configModule.default;\n\n logger.debug(`📄 Loaded config from ${configPath}`);\n\n // Navigate to engagement config\n const engagement = config?.app?.engagement as EngagementConfig | undefined;\n\n if (!engagement) {\n logger.debug(`⚠️ No engagement config found in ${configPath}`);\n return null;\n }\n\n return engagement;\n } catch (error) {\n // Config might not exist or have import errors - this is non-fatal\n logger.warn(`⚠️ Could not load config from ${configPath}: ${(error as Error).message}`);\n return null;\n }\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { glob } from 'glob';\nimport { readFileSync } from 'fs';\nimport { resolve, join } from 'path';\nimport { loadEngagementConfig, type EngagementConfig } from './configLoader';\nimport { logger } from '../logger';\n\n/**\n * Configuration options for the event instrumentation validator plugin\n */\nexport interface EventInstrumentationValidatorConfig {\n /**\n * Path to config module relative to project root\n * @default 'config.server.ts'\n */\n configPath?: string;\n\n /**\n * Directories to scan for trackEvent calls relative to project root\n * @default ['src']\n */\n scanPaths?: string[];\n\n /**\n * Whether to fail the build on missing instrumentation\n * @default false (warning only)\n */\n failOnMissing?: boolean;\n}\n\n/**\n * Extract all trackEvent calls from source files and return the event types found\n */\nasync function scanForInstrumentedEvents(projectRoot: string, scanPaths: string[]): Promise<Set<string>> {\n const instrumentedEvents = new Set<string>();\n\n // Regex patterns to match trackEvent calls\n // Pattern 1: trackEvent(..., ..., ..., 'event_type', ...)\n // The event type is the 4th argument\n const trackEventPattern = /trackEvent\\s*\\([^,]+,[^,]+,[^,]+,\\s*['\"]([^'\"]+)['\"]/g;\n\n // Pattern 2: sendViewPageEvent (special case for view_page)\n const sendViewPagePattern = /sendViewPageEvent\\s*\\(/g;\n\n // Pattern 3: createEvent('event_type', ...) - backup pattern\n const createEventPattern = /createEvent\\s*\\(\\s*['\"]([^'\"]+)['\"]/g;\n\n for (const scanPath of scanPaths) {\n const absoluteScanPath = resolve(projectRoot, scanPath);\n const pattern = join(absoluteScanPath, '**/*.{ts,tsx}');\n\n const files = await glob(pattern, {\n ignore: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx', '**/node_modules/**'],\n });\n\n logger.debug(`📂 Scanning ${files.length} files in ${scanPath}...`);\n\n for (const file of files) {\n try {\n const content = readFileSync(file, 'utf-8');\n\n // Find trackEvent calls\n let match;\n while ((match = trackEventPattern.exec(content)) !== null) {\n const eventType = match[1];\n instrumentedEvents.add(eventType);\n logger.debug(` ✓ Found trackEvent('${eventType}') in ${file}`);\n }\n\n // Check for sendViewPageEvent (implies view_page is instrumented)\n if (sendViewPagePattern.test(content)) {\n instrumentedEvents.add('view_page');\n logger.debug(` ✓ Found sendViewPageEvent() in ${file}`);\n }\n\n // Find createEvent calls as backup\n while ((match = createEventPattern.exec(content)) !== null) {\n const eventType = match[1];\n instrumentedEvents.add(eventType);\n logger.debug(` ✓ Found createEvent('${eventType}') in ${file}`);\n }\n\n // Reset regex lastIndex for next file\n trackEventPattern.lastIndex = 0;\n sendViewPagePattern.lastIndex = 0;\n createEventPattern.lastIndex = 0;\n } catch (error) {\n logger.warn(`⚠️ Could not read ${file}: ${(error as Error).message}`);\n }\n }\n }\n\n return instrumentedEvents;\n}\n\n/**\n * Extract enabled event toggles per adapter\n * Dynamically iterates over all keys in eventToggles - supports custom event types\n */\nfunction extractEnabledEvents(engagement: EngagementConfig): Map<string, Set<string>> {\n const adapterEvents = new Map<string, Set<string>>();\n\n if (!engagement.adapters) {\n return adapterEvents;\n }\n\n for (const [adapterName, adapterConfig] of Object.entries(engagement.adapters)) {\n // Skip disabled adapters\n if (!adapterConfig.enabled) {\n continue;\n }\n\n const enabledEvents = new Set<string>();\n\n if (adapterConfig.eventToggles) {\n // Dynamically iterate over all keys in eventToggles\n for (const [eventType, isEnabled] of Object.entries(adapterConfig.eventToggles)) {\n if (isEnabled === true) {\n enabledEvents.add(eventType);\n }\n }\n }\n\n if (enabledEvents.size > 0) {\n adapterEvents.set(adapterName, enabledEvents);\n }\n }\n\n return adapterEvents;\n}\n\n/**\n * Vite plugin that validates event instrumentation at build time.\n *\n * This plugin scans source files for trackEvent() calls and validates that\n * all enabled event toggles in config.server.ts have corresponding instrumentation.\n *\n * @param config - Configuration options for the plugin\n * @returns A Vite plugin that validates event instrumentation\n *\n * @example\n * // In vite.config.ts\n * export default defineConfig({\n * plugins: [\n * eventInstrumentationValidatorPlugin({\n * configPath: 'config.server.ts',\n * scanPaths: ['src'],\n * verbose: true\n * })\n * ]\n * })\n */\nexport const eventInstrumentationValidatorPlugin = (config: EventInstrumentationValidatorConfig = {}): Plugin => {\n const { configPath = 'config.server.ts', scanPaths = ['src'], failOnMissing = false } = config;\n\n let resolvedConfig: ResolvedConfig;\n\n return {\n name: 'storefrontnext:event-instrumentation-validator',\n apply: 'build',\n\n configResolved(viteConfig) {\n resolvedConfig = viteConfig;\n },\n\n async buildStart() {\n const projectRoot = resolvedConfig.root;\n\n logger.debug('🔍 Validating event instrumentation...');\n\n // Load engagement config\n const engagement = await loadEngagementConfig(projectRoot, configPath);\n\n if (!engagement) {\n logger.debug('ℹ️ Skipping validation - no engagement config found');\n return;\n }\n\n // Extract enabled events per adapter\n const adapterEvents = extractEnabledEvents(engagement);\n\n if (adapterEvents.size === 0) {\n logger.debug('ℹ️ No enabled adapters with event toggles found');\n return;\n }\n\n // Scan source files for instrumented events\n const instrumentedEvents = await scanForInstrumentedEvents(projectRoot, scanPaths);\n\n logger.debug(\n `🔎 Found ${instrumentedEvents.size} instrumented event types: ${[...instrumentedEvents].join(', ')}`\n );\n\n // Validate each adapter's enabled events\n const missingInstrumentation: Array<{ adapter: string; event: string }> = [];\n\n for (const [adapterName, enabledEvents] of adapterEvents) {\n for (const eventType of enabledEvents) {\n if (!instrumentedEvents.has(eventType)) {\n missingInstrumentation.push({\n adapter: adapterName,\n event: eventType,\n });\n }\n }\n }\n\n // Report results\n if (missingInstrumentation.length === 0) {\n logger.debug('✅ All enabled events are instrumented');\n return;\n }\n\n // Report missing instrumentation\n for (const { adapter, event } of missingInstrumentation) {\n logger.warn(`⚠️ ${adapter}.${event} is enabled but '${event}' is never instrumented`);\n }\n\n if (failOnMissing) {\n throw new Error(\n `[event-instrumentation] ${missingInstrumentation.length} event(s) are enabled but not instrumented. ` +\n `Either add instrumentation or disable the event toggles in config.server.ts.`\n );\n }\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { existsSync } from 'fs';\nimport { resolve } from 'path';\n\n/** Source filename for the middleware registry (project source). */\nconst MIDDLEWARE_REGISTRY_SOURCE_FILE = 'middleware-registry.ts';\n\n/** Subdirectory under build output where the compiled registry is written (must match server/index.ts expectations). */\nconst SERVER_OUT_SUBDIR = 'server';\n\n/**\n * Vite plugin that builds the middleware registry file for production.\n *\n * This plugin reads the template's middleware registry from the app's server directory\n * (e.g. `src/server/middleware-registry.ts` when appDirectory is `./src`) and compiles it\n * into the build output's server directory so the production server (Managed Runtime)\n * can load the custom Express middlewares.\n *\n * Compilation uses tsdown (single TypeScript file → ESM) instead of a full Vite build.\n * Paths are derived from the React Router plugin context (appDirectory, buildDirectory)\n * when available; there are no env vars for these paths in this package.\n *\n * If the middleware registry file does not exist, the plugin silently skips the build step.\n *\n * @returns {Plugin} A Vite plugin that compiles the middleware registry for production\n *\n * @example\n * // In vite.config.ts\n * export default defineConfig({\n * plugins: [\n * buildMiddlewareRegistryPlugin()\n * ]\n * })\n */\nexport const buildMiddlewareRegistryPlugin = (): Plugin => {\n let resolvedConfig: ResolvedConfig;\n let buildDirectory: string;\n /** App source directory (e.g. 'src' or './src') from React Router config. */\n let appDirectory: string;\n\n return {\n name: 'storefront-next:build-middleware-registry',\n apply: 'build',\n\n configResolved(config) {\n resolvedConfig = config;\n // React Router plugin context: appDirectory (e.g. './src') and buildDirectory (e.g. 'build') — no env vars in this package for these paths\n // @ts-expect-error: react-router plugin context is not typed\n const rr = config.__reactRouterPluginContext?.reactRouterConfig ?? {};\n buildDirectory = rr.buildDirectory ?? resolve(config.root, 'build');\n appDirectory = rr.appDirectory ?? 'src';\n },\n\n buildApp: {\n order: 'post',\n handler: async () => {\n const projectRoot = resolvedConfig.root;\n const middlewareRegistryPath = resolve(\n projectRoot,\n appDirectory,\n SERVER_OUT_SUBDIR,\n MIDDLEWARE_REGISTRY_SOURCE_FILE\n );\n\n if (!existsSync(middlewareRegistryPath)) {\n return;\n }\n\n const { build } = await import('tsdown');\n const serverOutDir = resolve(projectRoot, buildDirectory, SERVER_OUT_SUBDIR);\n const entryName = MIDDLEWARE_REGISTRY_SOURCE_FILE.replace(/\\.ts$/, '');\n\n await build({\n cwd: projectRoot,\n entry: { [entryName]: middlewareRegistryPath },\n outDir: serverOutDir,\n format: ['esm'],\n platform: 'node',\n outExtensions: () => ({ js: '.mjs', dts: '.d.ts' }),\n dts: false,\n clean: false,\n hash: false,\n noExternal: [/.*/],\n external: [/^node:/],\n });\n },\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { toPosixPath } from '../utils/paths';\n\n/**\n * File extensions to search when detecting ejected entry files.\n * Matches React Router's `entryExts` in its findEntry function.\n */\nconst ENTRY_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.mts'];\n\n/**\n * Query parameter appended to imports of ejected entry files within the\n * generated composition code. This creates a distinct module ID so Vite\n * treats it as a separate module from the one we intercept in `load`,\n * breaking what would otherwise be a circular import.\n *\n * Vite natively handles query parameters on file imports — it strips the\n * query for filesystem access but keeps it in the module ID for deduplication.\n */\nconst PASSTHROUGH_QUERY = '?platform-passthrough';\n\n/**\n * Finds a user-ejected entry file in the app directory.\n * Returns the absolute path if found, undefined otherwise.\n */\nfunction findUserEntry(appDirectory: string, basename: string): string | undefined {\n for (const ext of ENTRY_EXTENSIONS) {\n const filePath = path.resolve(appDirectory, basename + ext);\n if (fs.existsSync(filePath)) {\n return filePath;\n }\n }\n return undefined;\n}\n\n/**\n * Generates the virtual module code for the composed server entry.\n *\n * The generated module imports the app's entry (user-ejected or SDK default),\n * passes it through composeServerEntry(), and re-exports all ServerEntryModule\n * fields. This ensures platform features are always applied.\n */\nfunction generateServerEntryCode(appEntryImportPath: string): string {\n const importPath = JSON.stringify(toPosixPath(appEntryImportPath));\n return `\nimport * as _app from ${importPath};\nimport { composeServerEntry } from '@salesforce/storefront-next-dev/entry/server';\n\nconst _composed = composeServerEntry(_app);\n\n// Forward all named exports from the app entry so that any future\n// React Router exports are passed through without requiring a plugin update.\n// Explicit exports below take precedence over star re-exports per ESM spec.\nexport * from ${importPath};\n\n// Override with composed versions for exports the platform layer enhances.\nexport default _composed.default;\nexport const handleDataRequest = _composed.handleDataRequest;\nexport const handleError = _composed.handleError;\nexport const unstable_instrumentations = _composed.unstable_instrumentations;\nexport const streamTimeout = _composed.streamTimeout;\n`.trim();\n}\n\n/**\n * Generates the virtual module code for the composed client entry.\n *\n * Imports the platform client setup as a side-effect (runs before the app entry),\n * then re-exports everything from the app's client entry.\n */\nfunction generateClientEntryCode(appEntryImportPath: string): string {\n return `\nimport '@salesforce/storefront-next-dev/entry/client';\nexport * from ${JSON.stringify(toPosixPath(appEntryImportPath))};\n`.trim();\n}\n\ninterface ReactRouterPluginContext {\n reactRouterConfig: {\n appDirectory: string;\n buildDirectory: string;\n };\n entryClientFilePath: string;\n entryServerFilePath: string;\n}\n\n/**\n * Vite plugin that composes platform-level features into React Router entry files.\n *\n * This plugin uses the `load` hook to replace entry file contents with generated\n * composition code, while preserving the original file path as the module ID.\n * This is critical because React Router's post-build manifest lookup uses the\n * original entry file paths to find built chunks — changing the module ID (via\n * `resolveId`) would break that lookup.\n *\n * The plugin supports two modes:\n * - **Non-ejected:** No entry files in the app directory. The generated code\n * imports SDK default entries from `@salesforce/storefront-next-dev/entry/defaults/`.\n * - **Ejected:** Customer has created their own entry file(s). The generated code\n * imports the customer's file (with a `?platform-passthrough` query to avoid\n * circular imports) and wraps it with the platform layer.\n *\n * In both cases, the platform composition layer is always present. New platform\n * features ship via `npm update` by modifying the composition functions, without\n * changes to the plugin or customer code.\n */\nexport function platformEntryPlugin(): Plugin {\n let isTestMode = false;\n let serverEntryFilePath: string | undefined;\n let clientEntryFilePath: string | undefined;\n let appDirectory: string | undefined;\n let userServerEntryPath: string | undefined;\n let userClientEntryPath: string | undefined;\n\n return {\n name: 'storefront-next:platform-entry',\n enforce: 'pre',\n\n config(_config, { mode }) {\n isTestMode = mode === 'test';\n },\n\n configResolved(config: ResolvedConfig) {\n if (isTestMode) return;\n\n // @ts-expect-error: react-router plugin context is not typed\n const ctx: ReactRouterPluginContext | undefined = config.__reactRouterPluginContext;\n if (!ctx) return;\n\n appDirectory = ctx.reactRouterConfig.appDirectory;\n serverEntryFilePath = ctx.entryServerFilePath;\n clientEntryFilePath = ctx.entryClientFilePath;\n\n // Detect whether the user has ejected entry files and store the paths\n userServerEntryPath = findUserEntry(appDirectory, 'entry.server');\n userClientEntryPath = findUserEntry(appDirectory, 'entry.client');\n },\n\n load(id) {\n if (isTestMode || !serverEntryFilePath || !clientEntryFilePath || !appDirectory) return null;\n\n // Skip passthrough imports — these have the ?platform-passthrough query\n // and should load the real file content from disk. Vite handles this\n // natively by stripping the query for filesystem access.\n if (id.includes(PASSTHROUGH_QUERY)) return null;\n\n // Strip any existing query parameters for path comparison, but only\n // for matching — we still return null for IDs with unexpected queries.\n const idWithoutQuery = id.split('?')[0];\n\n if (path.normalize(idWithoutQuery) === path.normalize(serverEntryFilePath)) {\n // Always use the passthrough query on the original entry file path.\n // This ensures imports within the entry file (e.g., @react-router/node,\n // isbot) resolve in the app's dependency context, not the SDK's.\n // For ejected entries, this points to the user's file.\n // For non-ejected entries, this points to React Router's default.\n const appEntryPath = userServerEntryPath\n ? userServerEntryPath + PASSTHROUGH_QUERY\n : serverEntryFilePath + PASSTHROUGH_QUERY;\n\n return generateServerEntryCode(appEntryPath);\n }\n\n if (path.normalize(idWithoutQuery) === path.normalize(clientEntryFilePath)) {\n const appEntryPath = userClientEntryPath\n ? userClientEntryPath + PASSTHROUGH_QUERY\n : clientEntryFilePath + PASSTHROUGH_QUERY;\n\n return generateClientEntryCode(appEntryPath);\n }\n\n return null;\n },\n\n configureServer(server: ViteDevServer) {\n if (isTestMode || !appDirectory) return;\n\n // Capture as local const so TypeScript narrows the type in the callback\n const appDir = appDirectory;\n\n // Watch for creation/deletion of entry files in the app directory.\n // When a user ejects or un-ejects an entry file, restart the dev server\n // so the load hook re-evaluates which entry to import.\n const watcher = server.watcher;\n\n const checkEntryChange = (filePath: string) => {\n const relative = path.relative(appDir, filePath);\n const basename = path.basename(relative, path.extname(relative));\n const dir = path.dirname(relative);\n\n // Only react to entry files directly in the app directory (not nested)\n if (dir !== '.' || (basename !== 'entry.server' && basename !== 'entry.client')) {\n return;\n }\n\n const ext = path.extname(relative);\n if (!ENTRY_EXTENSIONS.includes(ext)) return;\n\n // Recheck ejection status\n const nowHasServer = findUserEntry(appDir, 'entry.server') !== undefined;\n const nowHasClient = findUserEntry(appDir, 'entry.client') !== undefined;\n\n const hadUserServerEntry = userServerEntryPath !== undefined;\n const hadUserClientEntry = userClientEntryPath !== undefined;\n if (nowHasServer !== hadUserServerEntry || nowHasClient !== hadUserClientEntry) {\n void server.restart();\n }\n };\n\n watcher.on('add', checkEntryChange);\n watcher.on('unlink', checkEntryChange);\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Server as HttpServer } from 'node:http';\nimport type { Plugin, HmrOptions } from 'vite';\n\n/**\n * Returns workspace-specific HMR configuration when running behind a workspace proxy.\n *\n * In workspace environments the OAuth2 proxy for the dev server's port is already\n * authenticated, so routing HMR WebSocket through the same HTTP server means it\n * shares the same proxy port and OAuth2 session. A separate port (e.g. port-8000)\n * would require its own OAuth2 login and return a 302 redirect that WebSocket\n * clients cannot follow.\n *\n * This is exported separately from the plugin because it requires the `httpServer`\n * reference created in `dev.ts`.\n *\n * @param httpServer - The Node HTTP server to attach the HMR WebSocket to.\n *\n * Environment variables:\n * - `EXTERNAL_DOMAIN_NAME` — The external hostname for the workspace proxy.\n * When it does not start with \"localhost\", workspace proxy mode is assumed.\n */\nexport function getWorkspaceHmrConfig(httpServer: HttpServer): HmrOptions | undefined {\n const externalDomain = process.env.EXTERNAL_DOMAIN_NAME;\n if (!externalDomain || externalDomain.startsWith('localhost')) return undefined;\n\n return {\n protocol: 'wss',\n host: externalDomain,\n clientPort: 443,\n server: httpServer,\n };\n}\n\n/**\n * Vite plugin that automatically configures workspace-specific settings when\n * SCAPI_PROXY_HOST is set. This includes:\n * - Disabling DIS (Dynamic Imaging Service) via PUBLIC__app__images__enableDis\n * - Adding dev server proxy rules for image paths (/dw/image, /on/demandware.static)\n * - Allowing all hosts for the dev server (workspace proxies use dynamic hostnames)\n *\n * Environment variables:\n * - `SCAPI_PROXY_HOST` — (Required) Base URL of the SCAPI proxy in workspace environments.\n * Enables workspace mode when set. Used as the proxy target for SCAPI requests and,\n * if JWEB_TARGET is not set, for static asset/image paths.\n * Example: `http://scw:25010`\n * - `JWEB_TARGET` — (Optional) Separate proxy target for JWeb static asset paths\n * (`/dw/image`, `/on/demandware.static`). Falls back to SCAPI_PROXY_HOST if not set.\n * Example: `http://jweb:8080`\n * - `PUBLIC__app__images__enableDis` — (Auto-set) Set to `'false'` when SCAPI_PROXY_HOST\n * is present, unless already explicitly configured. Controls whether the template\n * uses DIS for image format conversion and responsive srcsets.\n *\n * In workspace dev mode, this plugin also configures `optimizeDeps.entries` to scan all\n * source files upfront. Without this, Vite discovers deps lazily per-route and invalidates\n * the SSR module cache mid-session, leaving React in a partially-initialized state:\n * TypeError: Cannot read properties of null (reading 'useContext'/'useMemo')\n */\nexport const workspacePlugin = (): Plugin => {\n return {\n name: 'storefront-next-workspace',\n config(_, { mode }) {\n const scapiProxyHost = process.env.SCAPI_PROXY_HOST;\n if (!scapiProxyHost) return;\n\n // Disable DIS (Dynamic Imaging Service) in workspace environments.\n // Workspace JWeb doesn't support DIS, so the template handles all\n // DIS-related behavior changes based on this single flag.\n // Only set if not already explicitly configured.\n process.env.PUBLIC__app__images__enableDis ??= 'false';\n\n // Dev server proxy config (only in development mode)\n if (mode !== 'development') return;\n const jwebTarget = process.env.JWEB_TARGET;\n return {\n server: {\n allowedHosts: true as const,\n proxy: Object.fromEntries(\n ['/dw/image', '/on/demandware.static'].map((path) => [\n path,\n { target: jwebTarget || scapiProxyHost, changeOrigin: true, secure: false },\n ])\n ),\n },\n optimizeDeps: {\n // Scan all source files at startup so Vite pre-bundles every client dep\n // before serving requests. Without this, Vite discovers deps lazily per\n // route and invalidates the SSR module cache mid-session, causing React\n // context errors (TypeError: Cannot read properties of null).\n // Test files, stories, snapshots and .d.ts files are excluded because\n // they import Node.js-only packages (e.g. msw/node) that can't be\n // pre-bundled for the browser.\n entries: [\n './src/**/*.{ts,tsx}',\n '!./src/**/*.{test,spec}.{ts,tsx}',\n '!./src/**/*.stories.{ts,tsx}',\n '!./src/**/*-snapshot.tsx',\n '!./src/**/*.d.ts',\n ],\n },\n };\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\nimport { parse } from '@babel/parser';\nimport _traverse from '@babel/traverse';\nimport { generate as _generate } from '@babel/generator';\nimport { deadCodeElimination, findReferencedIdentifiers } from 'babel-dead-code-elimination';\nimport {\n type ArrayPattern,\n isArrayPattern,\n isClassDeclaration,\n isExportSpecifier,\n isFunctionDeclaration,\n isIdentifier,\n isMemberExpression,\n isObjectPattern,\n isObjectProperty,\n isRestElement,\n isVariableDeclaration,\n type Node,\n type ObjectPattern,\n} from '@babel/types';\n\n// Handle CJS/ESM interop for babel packages (same pattern as target-utils.ts)\nconst traverse = (_traverse as unknown as { default: typeof _traverse }).default || _traverse;\nconst generate = (_generate as unknown as { default: typeof _generate }).default || _generate;\n\n/**\n * Names of exports to strip per environment.\n *\n * - `loader` is server-only → strip from the **client** build\n * - `clientLoader` is client-only → strip from the **server** build\n */\nconst STRIP_FROM_CLIENT = ['loader'] as const;\nconst STRIP_FROM_SERVER = ['clientLoader'] as const;\n\n/**\n * Configuration options for the component loader strip plugin.\n */\nexport interface ComponentLoadersPluginConfig {\n /**\n * Path prefix (relative to project root) that limits which files are\n * processed. Only modules whose resolved id contains this path segment\n * will be transformed.\n *\n * @default 'src/components'\n */\n componentPath?: string;\n}\n\n/**\n * Determines which export names should be stripped for a given Vite environment.\n */\nfunction getExportsToStrip(environmentName: string): ReadonlyArray<string> {\n // React Router / Vite convention: \"client\" for the browser bundle, \"ssr\" for the server bundle\n if (environmentName === 'client') {\n return STRIP_FROM_CLIENT;\n }\n if (environmentName === 'ssr') {\n return STRIP_FROM_SERVER;\n }\n return [];\n}\n\n/**\n * Returns `true` when the source code contains at least one of the given export names as a quick pre-check before\n * running the full AST transform.\n */\nfunction hasExportCandidate(code: string, names: ReadonlyArray<string>): boolean {\n return names.some((name) => code.includes(name));\n}\n\n/**\n * Checks whether the AST contains at least one class declaration decorated with `@Component(…)`.\n */\nexport function hasComponentDecorator(ast: ReturnType<typeof parse>): boolean {\n let found = false;\n traverse(ast, {\n ClassDeclaration(path) {\n const decorators = path.node.decorators;\n if (!decorators) return;\n for (const decorator of decorators) {\n // @Component(…) → CallExpression with callee Identifier \"Component\"\n if (\n decorator.expression.type === 'CallExpression' &&\n isIdentifier(decorator.expression.callee) &&\n decorator.expression.callee.name === 'Component'\n ) {\n found = true;\n path.stop();\n return;\n }\n }\n },\n });\n return found;\n}\n\n/**\n * Strips the specified named exports from the given source code using a\n * Babel AST transform.\n *\n * The transform handles the following patterns:\n *\n * 1. `export const loader = …;`\n * 2. `export function loader(…) {…}`\n * 3. `export class Loader {…}`\n * 4. `export { loader }` / `export { foo as loader }`\n * 5. `export { loader } from './loaders'`\n *\n * Destructured exports (`export const { loader } = …` or\n * `export const [loader] = …`) cannot be safely removed and will\n * throw an error if encountered (matching React Router behaviour).\n *\n * After removing an export, the transform also:\n * - Removes top-level property assignments to the stripped export\n * (e.g. `clientLoader.hydrate = true`)\n * - Removes any import declarations that become unused as a result\n *\n * @see {@link https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/vite/remove-exports.ts React Router remove-exports}\n * @returns The transformed source code, or `null` if nothing was changed.\n */\nexport function stripExports(\n code: string,\n exportsToStrip: ReadonlyArray<string>,\n preParsedAst?: ReturnType<typeof parse>\n): string | null {\n const ast =\n preParsedAst ??\n parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx', 'decorators'],\n });\n\n let changed = false;\n\n // Snapshot referenced identifiers before any mutations so that `deadCodeElimination` can detect\n // newly-unreferenced bindings.\n const previouslyReferencedIdentifiers = findReferencedIdentifiers(ast);\n\n // Track local names of removed exports for property assignment cleanup (e.g. `clientLoader.hydrate = true`\n // after `export function clientLoader`)\n const removedExportLocalNames = new Set<string>();\n\n traverse(ast, {\n ExportNamedDeclaration(path) {\n const { declaration, specifiers } = path.node;\n\n // --- Pattern 1: export const <name> = … ---\n if (declaration && isVariableDeclaration(declaration)) {\n const remaining = declaration.declarations.filter((decl) => {\n if (isIdentifier(decl.id) && exportsToStrip.includes(decl.id.name)) {\n removedExportLocalNames.add(decl.id.name);\n return false; // remove this declarator\n }\n\n // Destructured exports cannot be safely removed — throw\n // (matching React Router behaviour)\n if (isArrayPattern(decl.id) || isObjectPattern(decl.id)) {\n validateDestructuredExports(decl.id, exportsToStrip);\n }\n\n return true;\n });\n\n if (remaining.length < declaration.declarations.length) {\n changed = true;\n if (remaining.length === 0) {\n // Remove the eslint-disable comment on the line above if present\n removeLeadingEslintDisableComment(path);\n path.remove();\n } else {\n declaration.declarations = remaining;\n }\n }\n return;\n }\n\n // --- Pattern 2: export function <name>() {} ---\n if (declaration && isFunctionDeclaration(declaration)) {\n if (declaration.id && exportsToStrip.includes(declaration.id.name)) {\n changed = true;\n removedExportLocalNames.add(declaration.id.name);\n removeLeadingEslintDisableComment(path);\n path.remove();\n return;\n }\n }\n\n // --- Pattern 3: export class <name> {} ---\n if (declaration && isClassDeclaration(declaration)) {\n if (declaration.id && exportsToStrip.includes(declaration.id.name)) {\n changed = true;\n removedExportLocalNames.add(declaration.id.name);\n removeLeadingEslintDisableComment(path);\n path.remove();\n return;\n }\n }\n\n // --- Pattern 4: export { loader } / export { foo as loader } ---\n // --- Pattern 5: export { loader } from './loaders' (re-export with source) ---\n if (specifiers.length > 0) {\n const remaining = specifiers.filter((spec) => {\n if (isExportSpecifier(spec)) {\n const exportedName = isIdentifier(spec.exported) ? spec.exported.name : spec.exported.value;\n if (exportsToStrip.includes(exportedName)) {\n // Track for property assignment cleanup\n // (e.g. `export { myFunc as loader }` → remove `loader.x = …`)\n removedExportLocalNames.add(spec.local.name);\n return false;\n }\n }\n return true;\n });\n\n if (remaining.length < specifiers.length) {\n changed = true;\n if (remaining.length === 0) {\n removeLeadingEslintDisableComment(path);\n path.remove();\n } else {\n path.node.specifiers = remaining;\n }\n }\n }\n },\n });\n\n // Second pass: remove top-level property assignments to stripped exports\n // (e.g. `clientLoader.hydrate = true`, `loader.displayName = \"...\"`)\n if (changed) {\n traverse(ast, {\n ExpressionStatement(path) {\n // Only handle top-level statements\n if (!path.parentPath?.isProgram()) {\n return;\n }\n\n if (path.node.expression.type === 'AssignmentExpression') {\n const left = path.node.expression.left;\n if (\n isMemberExpression(left) &&\n isIdentifier(left.object) &&\n (exportsToStrip.includes(left.object.name) || removedExportLocalNames.has(left.object.name))\n ) {\n removeLeadingEslintDisableComment(path);\n path.remove();\n }\n }\n },\n });\n }\n\n // Third pass: transitively remove all newly-unreferenced bindings (imports, helper functions, constants, etc.)\n // using the same library as React Router's remove-exports.\n if (changed) {\n deadCodeElimination(ast, previouslyReferencedIdentifiers);\n }\n\n if (!changed) {\n return null;\n }\n\n const output = generate(ast, { retainLines: true }, code);\n return output.code;\n}\n\n/**\n * Validates that no destructured export patterns contain names that should\n * be stripped. Destructured exports cannot be safely removed, so we throw\n * an error instead (matching React Router behaviour).\n */\nfunction validateDestructuredExports(id: ArrayPattern | ObjectPattern, exportsToStrip: ReadonlyArray<string>): void {\n if (isArrayPattern(id)) {\n for (const element of id.elements) {\n if (!element) continue;\n\n if (isIdentifier(element) && exportsToStrip.includes(element.name)) {\n throw new Error(`Cannot remove destructured export \"${element.name}\"`);\n }\n\n if (\n isRestElement(element) &&\n isIdentifier(element.argument) &&\n exportsToStrip.includes(element.argument.name)\n ) {\n throw new Error(`Cannot remove destructured export \"${element.argument.name}\"`);\n }\n\n if (isArrayPattern(element) || isObjectPattern(element)) {\n validateDestructuredExports(element, exportsToStrip);\n }\n }\n }\n\n if (isObjectPattern(id)) {\n for (const property of id.properties) {\n if (!property) continue;\n\n if (isObjectProperty(property) && isIdentifier(property.key)) {\n if (isIdentifier(property.value) && exportsToStrip.includes(property.value.name)) {\n throw new Error(`Cannot remove destructured export \"${property.value.name}\"`);\n }\n\n if (isArrayPattern(property.value) || isObjectPattern(property.value)) {\n validateDestructuredExports(property.value, exportsToStrip);\n }\n }\n\n if (\n isRestElement(property) &&\n isIdentifier(property.argument) &&\n exportsToStrip.includes(property.argument.name)\n ) {\n throw new Error(`Cannot remove destructured export \"${property.argument.name}\"`);\n }\n }\n }\n}\n\n/**\n * Removes a leading `// eslint-disable-next-line …` comment that sits on\n * the line immediately before the given path.\n */\nfunction removeLeadingEslintDisableComment(path: { node: Node }): void {\n const leadingComments = path.node.leadingComments;\n if (!leadingComments || leadingComments.length === 0) return;\n\n const last = leadingComments[leadingComments.length - 1];\n if (last.type === 'CommentLine' && last.value.includes('eslint-disable')) {\n leadingComments.pop();\n }\n}\n\n/**\n * Vite plugin that strips environment-specific loader exports from\n * component modules.\n *\n * Following the React Router convention:\n * - `export const loader` → server-only, stripped from the **client** bundle\n * - `export const clientLoader` → client-only, stripped from the **server** bundle\n *\n * This ensures that server-only code (e.g. API calls, database access) is\n * never included in the client bundle, and vice versa.\n *\n * The plugin only processes files that:\n * 1. Are under the configured `componentPath` directory\n * 2. Contain a `@Component` decorator (i.e. are Page Designer components)\n * 3. Are not test or story files\n */\nexport function componentLoadersPlugin(config: ComponentLoadersPluginConfig = {}): Plugin {\n const { componentPath = 'src/components' } = config;\n\n let isTestMode = false;\n\n return {\n name: 'storefrontnext:component-loaders',\n // Run before other transforms so stripped exports and their imports are\n // never seen by downstream plugins (TypeScript, React Refresh, etc.),\n // ensuring clean tree-shaking of server/client-only code.\n enforce: 'pre',\n\n configResolved(resolvedConfig) {\n isTestMode = resolvedConfig.mode === 'test';\n },\n\n transform(code, id) {\n // Do not strip exports during test runs — tests need the full module surface.\n if (isTestMode) {\n return null;\n }\n\n // Only process TS/TSX files in the component directory\n if (!id.includes(componentPath)) {\n return null;\n }\n if (!/\\.[mc]?[jt]sx?$/.test(id)) {\n return null;\n }\n // Skip test and story files\n if (/\\.(test|spec|stories)\\.[jt]sx?$/.test(id)) {\n return null;\n }\n\n // Access the Vite environment name (available in Vite 6+)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const environmentName: string | undefined = (this as any).environment?.name;\n if (!environmentName) {\n return null;\n }\n\n const exportsToStrip = getExportsToStrip(environmentName);\n if (exportsToStrip.length === 0) {\n return null;\n }\n\n // Quick string check before parsing the full AST\n if (!hasExportCandidate(code, exportsToStrip)) {\n return null;\n }\n\n // Parse once and reuse for both the `@Component` decorator check and stripping\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx', 'decorators'],\n });\n\n // Only process Page Designer components (classes with `@Component` decorator)\n if (!hasComponentDecorator(ast)) {\n return null;\n }\n\n const transformed = stripExports(code, exportsToStrip, ast);\n if (!transformed) {\n return null;\n }\n\n return {\n code: transformed,\n map: null,\n };\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { DevEnvironment, Plugin, ViteDevServer } from 'vite';\n\n// Inline sourcemap prefix produced by Vite's `genSourceMapUrl()`.\n// This is the only format Vite emits — it always uses base64 encoding.\nconst INLINE_SOURCEMAP_PREFIX = '//# sourceMappingURL=data:application/json;base64,';\n\n/**\n * Vite plugin that fixes SSR sourcemap `sources` to use full module paths\n * instead of bare basenames.\n *\n * **Problem:** When the React Router `v8_viteEnvironmentApi` future flag is\n * enabled, SSR modules are evaluated by Vite's Environment API module runner.\n * Vite's `ssrTransform` generates inline sourcemaps with\n * `sources: [path.basename(url)]` (e.g., `\"search.tsx\"` or `\"index.tsx\"`).\n * V8's debugger cannot resolve bare basenames back to files on disk, so\n * Chrome DevTools (via `--inspect`) displays the wrong source file content\n * when pausing at breakpoints — even for files with unique names.\n *\n * **Why `fetchModule`:** A Vite transform plugin cannot fix this because\n * `ssrTransform` runs *after* the plugin transform pipeline and overwrites\n * `map.sources` with `[path.basename(url)]`. The only viable interception\n * point is `fetchModule` — the public API method on `DevEnvironment` that\n * returns the final transformed code (with inline sourcemaps already embedded)\n * to the module runner.\n *\n * **Removable:** If Vite updates `ssrTransform` to use full paths instead of\n * `path.basename()`, this plugin can be deleted with no other changes.\n *\n * Only active in development mode (`configureServer` does not run in build).\n */\nexport function ssrSourcemapFixPlugin(): Plugin {\n return {\n name: 'storefront-next:ssr-sourcemap-fix',\n configureServer(server: ViteDevServer) {\n const ssrEnv: DevEnvironment | undefined = server.environments.ssr;\n if (!ssrEnv) return;\n\n const originalFetchModule = ssrEnv.fetchModule.bind(ssrEnv);\n\n ssrEnv.fetchModule = async (\n id: string,\n importer?: string,\n options?: { cached?: boolean; startOffset?: number }\n ) => {\n const result = await originalFetchModule(id, importer, options);\n\n // Only patch inlined modules (not externalized or cached ones)\n if (!result || 'externalize' in result || 'cache' in result || !('code' in result)) {\n return result;\n }\n\n const smIndex = result.code.lastIndexOf(INLINE_SOURCEMAP_PREFIX);\n if (smIndex === -1) {\n return result;\n }\n\n try {\n const base64Start = smIndex + INLINE_SOURCEMAP_PREFIX.length;\n const base64End = result.code.indexOf('\\n', base64Start);\n const base64Data =\n base64End === -1\n ? result.code.slice(base64Start).trim()\n : result.code.slice(base64Start, base64End).trim();\n\n const mapJson = JSON.parse(Buffer.from(base64Data, 'base64').toString('utf-8'));\n\n if (!mapJson.sources || !Array.isArray(mapJson.sources)) {\n return result;\n }\n\n // Use the absolute file path from the fetch result.\n // `file` is the on-disk path; `id` is the module graph identifier.\n // For virtual modules both may be null/empty — in that case we skip patching.\n const fileId = result.file || result.id;\n if (!fileId) {\n return result;\n }\n\n // ssrTransform always produces exactly one source entry. If a future\n // transform produces multiple sources, bail out — replacing every entry\n // with the same fileId would corrupt the sourcemap.\n if (mapJson.sources.length !== 1) {\n return result;\n }\n\n // A bare basename has no path separator. Vite normalizes all\n // paths to forward slashes, so checking for '/' is sufficient\n // on all platforms.\n const source: string | null = mapJson.sources[0];\n if (!source || source.includes('/')) {\n return result;\n }\n\n mapJson.sources = [fileId];\n\n const patchedBase64 = Buffer.from(JSON.stringify(mapJson)).toString('base64');\n result.code =\n result.code.slice(0, base64Start) +\n patchedBase64 +\n (base64End === -1 ? '' : result.code.slice(base64End));\n } catch {\n // Decoding or patching failed — return the original result unchanged.\n }\n\n return result;\n };\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\n\nexport interface I18nPluginConfig {\n /**\n * Pattern to match locale module IDs. Must contain a capture group for the language code.\n *\n * @default /\\/src\\/locales\\/([^/]+)\\//\n */\n localePattern?: RegExp;\n}\n\n/**\n * Vite plugin that splits locale translation files into per-language chunks.\n *\n * Wraps any existing `manualChunks` configuration so that locale modules are\n * assigned to `locales-{lang}` chunks (e.g., `locales-en-GB`) while all other\n * module IDs are delegated to the user's original `manualChunks` function or object.\n *\n * @param config - Optional configuration for custom locale patterns\n * @returns A Vite plugin that configures locale-based code splitting\n */\nexport function i18nPlugin(config?: I18nPluginConfig): Plugin {\n const pattern = config?.localePattern ?? /\\/src\\/locales\\/([^/]+)\\//;\n\n return {\n name: 'storefront-next:i18n',\n apply: 'build',\n config(viteConfig) {\n const output = viteConfig.build?.rollupOptions?.output;\n if (Array.isArray(output)) return;\n\n const existingManualChunks = output?.manualChunks;\n\n return {\n build: {\n rollupOptions: {\n output: {\n manualChunks(id, meta) {\n const localeMatch = id.match(pattern);\n if (localeMatch) {\n return `locales-${localeMatch[1]}`;\n }\n\n if (typeof existingManualChunks === 'function') {\n return existingManualChunks.call(this, id, meta);\n }\n if (existingManualChunks && typeof existingManualChunks === 'object') {\n for (const [name, ids] of Object.entries(existingManualChunks)) {\n if (ids.includes(id)) return name;\n }\n }\n },\n },\n },\n },\n };\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\n\n/**\n * Vite plugin contributing the baseline Vite config required by the\n * Storefront Next framework. These settings are uniform across every\n * customer project and are not intended to be customized.\n *\n * Additional framework-level Vite defaults should be added here rather\n * than in the template's vite.config.ts or in a new single-purpose plugin.\n *\n * Current defaults:\n * - `resolve.dedupe`: prevents duplicate React / React Router copies on the\n * client. Duplicate React instances cause hooks to throw \"Invalid hook call\".\n * - `optimizeDeps.include`: forces Vite's dep optimizer to pre-bundle\n * `react-router` and its `/internal/react-server-client` entry so the\n * React Router dev plugin resolves a single shared instance on the client.\n * It also pre-bundles the React-importing entry points of the runtime SDK\n * (`/config`, `/security/react`, `/site-context`, `/design/react/core`,\n * `/routing/app-wrapper`, `/i18n/client`). Without this, Vite optimizes the\n * app's own React first, then *discovers* these SDK subpaths the first time a\n * component imports them at request time, triggers a dep re-optimization, and\n * forces a full-page reload. During that window the SDK's React hooks\n * (`useConfig` → `useContext`) momentarily resolve against a second, freshly\n * optimized React instance — surfacing as \"Invalid hook call\" / \"Cannot read\n * properties of null (reading 'useContext')\" until the reload settles.\n * Including them up front means a single first-pass optimization with one\n * shared React, so no mid-session re-optimize and no transient duplicate.\n * - `ssr.noExternal`: forces the SDK through Vite's SSR transform pipeline\n * in dev. The SDK exports module-level singletons (router contexts, etc.)\n * whose object identity is load-bearing — they're used as `Map` keys, so\n * reads and writes must reference the same object. In dev SSR, Vite can\n * externalize a package for some import sites (loaded by Node's native\n * ESM resolver) while transforming it for others (loaded by Vite's SSR\n * transform pipeline), producing two distinct module records for the\n * same file on disk. When that happens, the singletons are constructed\n * twice and the keys no longer match — context lookups silently return\n * the default value. `noExternal` collapses both paths into Vite's\n * transform cache so there is exactly one module record. Production\n * builds inline the SDK into the SSR bundle and are unaffected. We\n * don't blanket-`noExternal` every dependency because most third-party\n * packages are identity-agnostic (two copies work fine) and externalizing\n * keeps dev startup fast — only packages exporting identity-sensitive\n * singletons need this treatment.\n *\n * @returns {Plugin} A Vite plugin contributing the framework's base config.\n */\nexport const baseConfigPlugin = (): Plugin => ({\n name: 'storefront-next:base-config',\n config() {\n return {\n resolve: {\n dedupe: ['react', 'react-dom', 'react-router'],\n },\n optimizeDeps: {\n include: [\n 'react-router',\n 'react-router/internal/react-server-client',\n // React-importing runtime SDK entry points. Pre-bundling these\n // in the first optimizer pass prevents a late discovery +\n // re-optimize that transiently loads a second React instance\n // (see optimizeDeps note above).\n '@salesforce/storefront-next-runtime/config',\n '@salesforce/storefront-next-runtime/security/react',\n '@salesforce/storefront-next-runtime/site-context',\n '@salesforce/storefront-next-runtime/design/react/core',\n '@salesforce/storefront-next-runtime/routing/app-wrapper',\n '@salesforce/storefront-next-runtime/i18n/client',\n // i18n peer deps that the runtime SDK imports *internally* and the\n // template does NOT import directly from its own source. Vite's\n // initial crawl scans the app's source graph, so a dep used only\n // inside the SDK is discovered late — the first time the SDK module\n // executes — triggering a re-optimize + reload (the same reload that\n // transiently duplicates React). `react-i18next` does NOT need listing\n // here because the template imports it directly in many components, so\n // the initial crawl already finds it.\n // - i18next-browser-languagedetector: imported by `/i18n/client`.\n // - remix-i18next/middleware: imported by the SDK's `/i18n` barrel\n // (createI18nMiddleware), which the template reaches via getTranslation.\n 'i18next-browser-languagedetector',\n 'remix-i18next/middleware',\n ],\n },\n ssr: {\n noExternal: ['@salesforce/storefront-next-runtime'],\n },\n };\n },\n});\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\nimport { fixReactRouterManifestUrlsPlugin } from './plugins/fixReactRouterManifestUrls';\nimport { readableChunkFileNamesPlugin } from './plugins/readableChunkFileNames';\nimport { managedRuntimeBundlePlugin } from './plugins/managedRuntimeBundle';\nimport { patchReactRouterPlugin } from './plugins/patchReactRouter';\nimport { transformTargetPlaceholderPlugin } from './plugins/transformTargets';\nimport { actionHooksPlugin } from './plugins/actionHooks';\nimport { watchConfigFilesPlugin } from './plugins/watchConfigFiles';\nimport { staticRegistryPlugin, type StaticRegistryPluginConfig } from './plugins/staticRegistry';\nimport {\n eventInstrumentationValidatorPlugin,\n type EventInstrumentationValidatorConfig,\n} from './plugins/eventInstrumentationValidator';\nimport { buildMiddlewareRegistryPlugin } from './plugins/buildMiddlewareRegistry';\nimport { platformEntryPlugin } from './plugins/platformEntry';\nimport { workspacePlugin } from './plugins/workspace';\nimport { componentLoadersPlugin } from './plugins/componentLoaders';\nimport { ssrSourcemapFixPlugin } from './plugins/ssrSourcemapFix';\nimport { i18nPlugin } from './plugins/i18n';\nimport { baseConfigPlugin } from './plugins/baseConfig';\n\n/**\n * Configuration options for the Storefront Next Vite plugin.\n */\nexport interface StorefrontNextTargetsConfig {\n /**\n * Enable human-readable chunk file names for easier debugging in production builds.\n * When enabled, chunk files will be named based on their source location\n * rather than just the file name and random hashes.\n *\n * This is useful to identify the chunk files and is usually used in development,\n * in conjunction with the bundle analyzer.\n *\n * Example:\n *\n * ```\n * (package)-(pkg-name)-index.[hash].js\n * (components)-(ui)-(inputs)-(TextField)-index.[hash].js\n * ```\n *\n * Instead of:\n *\n * ```\n * index.[hash].js\n * ```\n *\n * @default false\n */\n readableChunkNames?: boolean;\n\n /**\n * Configuration for the static registry plugin that automatically generates\n * component registrations based on @Component decorators.\n *\n * Set to `false` to disable the static registry plugin entirely.\n *\n * @default { componentPath: 'src/components', registryPath: 'src/lib/registry.ts' }\n */\n staticRegistry?: StaticRegistryPluginConfig;\n\n /**\n * Configuration for the event instrumentation validator plugin that validates\n * all enabled analytics event toggles have corresponding trackEvent() calls.\n *\n * Set to `false` to disable the validator entirely.\n *\n * @default { configPath: 'config.server.ts', scanPaths: ['src'], failOnMissing: false }\n */\n eventInstrumentationValidator?: EventInstrumentationValidatorConfig | false;\n}\n\n/**\n * Storefront Next Vite plugin that powers the React Router app.\n * Supports building and optimizing for the managed runtime environment.\n *\n * @param config - Configuration options for the plugin\n * @returns {Plugin[]} An array of Vite plugins for Storefront Next functionality\n *\n * @example\n * // With default options\n * export default defineConfig({\n * plugins: [storefrontNextTargets()]\n * })\n *\n * @example\n * // Disable readable chunk names\n * export default defineConfig({\n * plugins: [storefrontNextTargets({ readableChunkNames: false })]\n * })\n */\nexport function storefrontNextTargets(config: StorefrontNextTargetsConfig = {}): Plugin[] {\n const {\n readableChunkNames = false,\n staticRegistry = {\n componentPath: '',\n registryPath: '',\n },\n eventInstrumentationValidator = {\n configPath: 'config.server.ts',\n scanPaths: ['src'],\n failOnMissing: false,\n },\n } = config;\n\n const plugins: Plugin[] = [\n baseConfigPlugin(),\n ...(process.env.SCAPI_PROXY_HOST ? [workspacePlugin()] : []),\n i18nPlugin(),\n managedRuntimeBundlePlugin(),\n fixReactRouterManifestUrlsPlugin(),\n patchReactRouterPlugin(),\n platformEntryPlugin(),\n transformTargetPlaceholderPlugin(),\n actionHooksPlugin(),\n watchConfigFilesPlugin(),\n buildMiddlewareRegistryPlugin(),\n ssrSourcemapFixPlugin(),\n ];\n\n // Add static registry plugin if enabled\n if (staticRegistry?.componentPath && staticRegistry?.registryPath) {\n plugins.push(staticRegistryPlugin(staticRegistry));\n\n // Strip server-only `loader` exports from the client bundle and client-only `clientLoader` exports from the\n // server bundle\n plugins.push(\n componentLoadersPlugin({\n componentPath: staticRegistry.componentPath,\n })\n );\n }\n\n // Add event instrumentation validator plugin if not explicitly disabled\n if (eventInstrumentationValidator !== false) {\n plugins.push(eventInstrumentationValidatorPlugin(eventInstrumentationValidator));\n }\n\n if (readableChunkNames) {\n plugins.push(readableChunkFileNamesPlugin());\n }\n\n return plugins;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\nimport { parse } from '@babel/parser';\nimport traverseModule from '@babel/traverse';\nimport { generate } from '@babel/generator';\nimport {\n type JSXAttribute,\n isJSXIdentifier,\n isJSXAttribute,\n isStringLiteral,\n jsxIdentifier,\n jsxAttribute,\n jsxExpressionContainer,\n booleanLiteral,\n importDeclaration,\n importSpecifier,\n identifier,\n stringLiteral,\n} from '@babel/types';\n\n// Handle CJS/ESM interop for @babel/traverse (same pattern as target-utils.ts)\nconst traverse = (traverseModule as unknown as { default: typeof traverseModule }).default || traverseModule;\nimport { logger } from '../logger';\n\n// Pre-compiled regex — avoids false substring matches on 'UITargetProviders'.\n// Mirrors TARGET_COMPONENT_JSX_RE in target-utils.ts.\nconst UI_TARGET_JSX_RE = /<UITarget[\\s/>]/;\n\nexport interface UITargetDevModeConfig {\n /**\n * Enable dev mode visual markers. Defaults to false.\n * Set via VITE_UI_TARGET_DEV_MODE=true environment variable.\n */\n enabled?: boolean;\n\n /**\n * Build-time filter: only show targets matching a category prefix.\n * Set via VITE_TARGET_FILTER_CATEGORY=pdp environment variable.\n * @example filterCategory: 'pdp' — only shows targets whose id starts with \"pdp.\"\n */\n filterCategory?: string;\n\n /**\n * Map of targetId → hint label for overlay grouping/filtering.\n * Built from target-config.json at config time and injected into each dev marker\n * as a __hint__ prop, which is emitted as a data-ui-target-hint DOM attribute.\n * Example: { 'emailSignUp.consent.marketing': 't/my-branch/W-123', 'checkout.contactInfo': 'pr:1384' }\n */\n hintMap?: Record<string, string>;\n}\n\n/**\n * Vite plugin that adds visual markers to UITarget components in development.\n *\n * PRODUCTION: This plugin is completely inactive - zero overhead.\n * DEVELOPMENT: Transforms UITarget JSX to add visual debugging markers.\n *\n * @example\n * // Source code:\n * <UITarget targetId=\"pdp.loyalty.badge\">\n * <Widget />\n * </UITarget>\n *\n * // Transformed in DEV mode:\n * <UITargetDevMarker\n * targetId=\"pdp.loyalty.badge\"\n * __file__=\"/src/components/product.tsx\"\n * __hasChildren__={true}\n * >\n * <Widget />\n * </UITargetDevMarker>\n */\nexport function uiTargetDevModePlugin(config: UITargetDevModeConfig = {}): Plugin {\n // Production: return no-op plugin\n if (process.env.NODE_ENV === 'production') {\n return {\n name: 'storefront-next:ui-target-dev-mode-noop',\n };\n }\n\n // Development: check if enabled\n const enabled = config.enabled ?? process.env.VITE_UI_TARGET_DEV_MODE === 'true';\n\n if (!enabled) {\n return {\n name: 'storefront-next:ui-target-dev-mode-disabled',\n };\n }\n\n logger.info('🎯 UITarget Dev Mode enabled');\n\n if (config.filterCategory) {\n logger.info(` Filtering to category: ${config.filterCategory} (build-time)`);\n }\n\n return {\n name: 'storefront-next:ui-target-dev-mode',\n\n enforce: 'pre', // Run BEFORE the production transform so UITarget is still in the source\n\n transform(code: string, id: string) {\n // Only process .tsx/.jsx files\n if (!id.match(/\\.(tsx|jsx)$/)) {\n return null;\n }\n\n // Skip node_modules\n if (id.includes('node_modules')) {\n return null;\n }\n\n // Skip the UITarget component itself\n if (id.includes('/targets/ui-target.tsx')) {\n return null;\n }\n\n // Quick check: does this file contain UITarget JSX?\n // UI_TARGET_JSX_RE avoids false matches on 'UITargetProviders' (which contains 'UITarget').\n // Also verify the import is present to avoid processing files that just reference the string.\n if (!UI_TARGET_JSX_RE.test(code) || !code.includes(\"from '@/targets/ui-target'\")) {\n return null;\n }\n\n try {\n // Parse AST\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n });\n\n let hasTransforms = false;\n const targetIds: string[] = [];\n\n // Find and transform UITarget usages\n traverse(ast, {\n JSXElement(path) {\n const openingElement = path.node.openingElement;\n\n // Check if this is <UITarget>\n if (!isJSXIdentifier(openingElement.name) || openingElement.name.name !== 'UITarget') {\n return;\n }\n\n // Extract targetId attribute\n const targetIdAttr = openingElement.attributes.find(\n (attr) =>\n isJSXAttribute(attr) && isJSXIdentifier(attr.name) && attr.name.name === 'targetId'\n ) as JSXAttribute | undefined;\n\n if (!targetIdAttr) {\n logger.warn(`UITarget without targetId in ${id}`);\n return;\n }\n\n // Get targetId value\n const targetId = isStringLiteral(targetIdAttr.value) ? targetIdAttr.value.value : null;\n\n if (!targetId) {\n logger.warn(`UITarget with non-string targetId in ${id}`);\n return;\n }\n\n // Apply build-time category filter\n if (config.filterCategory && !targetId.startsWith(`${config.filterCategory}.`)) {\n return;\n }\n\n // Determine if this target has children\n const hasChildren = path.node.children.length > 0;\n\n // Transform: UITarget → UITargetDevMarker\n openingElement.name = jsxIdentifier('UITargetDevMarker');\n if (path.node.closingElement) {\n path.node.closingElement.name = jsxIdentifier('UITargetDevMarker');\n }\n\n // Add metadata attributes\n const hint = config.hintMap?.[targetId];\n openingElement.attributes.push(\n jsxAttribute(jsxIdentifier('__file__'), stringLiteral(id)),\n jsxAttribute(\n jsxIdentifier('__hasChildren__'),\n jsxExpressionContainer(booleanLiteral(hasChildren))\n ),\n ...(hint ? [jsxAttribute(jsxIdentifier('__hint__'), stringLiteral(hint))] : [])\n );\n\n hasTransforms = true;\n targetIds.push(targetId);\n },\n });\n\n if (!hasTransforms) {\n return null;\n }\n\n // Add import for UITargetDevMarker at top of file\n const devMarkerImport = importDeclaration(\n [importSpecifier(identifier('UITargetDevMarker'), identifier('UITargetDevMarker'))],\n stringLiteral('@/lib/ui-target-dev-mode/marker')\n );\n\n ast.program.body.unshift(devMarkerImport);\n\n // Generate transformed code\n const output = generate(\n ast,\n {\n retainLines: true,\n compact: false,\n },\n code\n );\n\n logger.debug(`Transformed ${targetIds.length} targets in ${id.split('/').pop()}`);\n\n return {\n code: output.code,\n map: output.map,\n };\n } catch (error) {\n logger.error(`Failed to transform UITarget in ${id}:`, error);\n return null; // Return original code on error\n }\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Vite plugin for hybrid proxying between Storefront Next and legacy SFRA.\n *\n * **LOCAL DEVELOPMENT ONLY** - This plugin only works with `pnpm dev` (Vite dev server).\n * For MRT/production deployments, use Cloudflare eCDN routing instead.\n *\n * ## Dependency injection via `routeMatcher`\n *\n * This plugin accepts a `routeMatcher` callback instead of importing routing logic\n * directly. The template owns the routing logic (e.g., `ecdn-matcher.ts`) and passes\n * it in when configuring the plugin. This keeps the SDK package pure — it knows nothing\n * about template internals — while allowing the template to inject merchant-specific\n * routing rules.\n *\n * The same `routeMatcher` pattern can be used by the future MRT server middleware.\n *\n * ## How it works\n *\n * 1. Intercepts HTTP requests BEFORE React Router sees them\n * 2. Checks routing rules: matching routes → React Router, non-matching → proxy to SFCC\n * 3. Rewrites paths: /cart → /s/{siteId}/{locale}/cart (SFRA format)\n * 4. Rewrites cookies: Domain=.salesforce.com → Domain=localhost (session continuity)\n * 5. Rewrites HTML/JSON bodies: replaces SFCC origin URLs with localhost (keeps client-side nav proxied)\n *\n * This enables seamless navigation between Next pages (/) and SFRA pages (/cart) without\n * visible redirects or session loss. The browser URL stays localhost:5173/cart.\n *\n * ## Environment variables\n *\n * - HYBRID_PROXY_ENABLED (required) - 'true' to enable the plugin\n * - HYBRID_ROUTING_RULES (required) - Cloudflare routing expression (routes matching go to Next)\n * - SFCC_ORIGIN (required) - SFCC sandbox URL (e.g., https://zzrf-001.dx.commercecloud.salesforce.com)\n * - HYBRID_PROXY_LOCALE (optional) - Locale for SFRA path transformation (e.g., 'en-GB')\n *\n * The host plugin is responsible for sourcing `defaultSiteId` and `locale` from the\n * template's config (e.g. `config.server.ts`) and/or env vars and passing them in.\n */\n\nimport type { Plugin, ViteDevServer } from 'vite';\nimport httpProxy from 'http-proxy';\nimport type { IncomingMessage } from 'http';\nimport { gunzipSync, brotliDecompressSync, inflateSync } from 'zlib';\nimport { logger } from '../logger';\n\nexport interface HybridProxyPluginOptions {\n /** Whether hybrid proxying is enabled */\n enabled: boolean;\n /** SFCC origin URL to proxy non-matching routes to */\n targetOrigin: string;\n /** Cloudflare routing expression (routes matching go to Next) */\n routingRules: string;\n /**\n * Callback that decides if a pathname should be handled by Storefront Next.\n * Called for every request that isn't a Vite internal or SFCC path.\n * Receives the pathname and the raw `routingRules` string; returns true to\n * let React Router handle it, false to proxy to SFCC.\n *\n * @example\n * import { shouldRouteToNext } from './src/lib/ecdn-matcher';\n * hybridProxyPlugin({ routeMatcher: shouldRouteToNext, ... })\n */\n routeMatcher: (pathname: string, routingRules: string) => boolean;\n /** SFCC default site ID (e.g., 'RefArchGlobal'). Required when `enabled` is true. */\n defaultSiteId?: string;\n /** Locale for SFRA paths (e.g., 'en-GB'). Defaults to 'default' if not provided. */\n locale?: string;\n}\n\n/**\n * Check if a request path should skip proxying (Vite internals, assets, etc.)\n *\n * @param pathname - URL pathname to check\n * @returns true if the request should NOT be proxied\n */\nexport function shouldSkipProxy(pathname: string): boolean {\n // Vite virtual modules (@vite/client, @fs/, @id/, etc.)\n if (pathname.startsWith('/@')) return true;\n\n // Vite dev server internals\n if (pathname.startsWith('/__')) return true;\n\n // Source files served by Vite\n if (pathname.startsWith('/src/')) return true;\n\n // Node modules\n if (pathname.startsWith('/node_modules/')) return true;\n\n // React Router data requests\n if (pathname.endsWith('.data')) return true;\n\n // SCAPI proxy paths (handled by React Router)\n if (pathname.startsWith('/mobify/')) return true;\n\n // SFRA static assets - these MUST be proxied to SFCC\n // /on/demandware.static/... - static assets (CSS, JS, images)\n // /on/demandware.store/... - dynamic endpoints\n if (pathname.startsWith('/on/demandware.')) {\n return false; // DO proxy these to SFCC\n }\n\n // Vite build output and other asset files (served by Vite/React Router)\n // Only skip if NOT an SFRA path (checked above)\n if (/\\.(js|jsx|ts|tsx|css|json|map|woff2?|ttf|svg|png|jpe?g|gif|webp|ico|mp4)$/i.test(pathname)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Rewrite Set-Cookie header for localhost development.\n *\n * Rewrites SFCC Set-Cookie headers so they work on localhost during local development.\n *\n * **LOCAL DEVELOPMENT ONLY** — This function is part of the hybrid proxy Vite plugin\n * which only runs during `pnpm dev`. In production (MRT deployments), SFCC cookies\n * flow through the eCDN unmodified.\n *\n * Rewrites applied:\n * - **Domain**: `.salesforce.com` → `localhost` (browsers reject cross-domain cookies)\n *\n * Attributes intentionally preserved:\n * - **Secure**: Kept. Localhost is a secure context — browsers accept `Secure` cookies\n * on `http://localhost` (see https://w3c.github.io/webappsec-secure-contexts/).\n * - **SameSite**: Kept. `SameSite=None; Secure` is valid on localhost since `Secure`\n * is accepted. This keeps SFCC cookies transparent and in sync with Storefront Next\n * cookies, which is critical for hybrid auth session bridging.\n *\n * @param cookie - Original Set-Cookie header value from SFCC\n * @returns Rewritten cookie suitable for localhost\n *\n * @example\n * Input: \"dwsid=abc123; Domain=.salesforce.com; Path=/; Secure; SameSite=None; HttpOnly\"\n * Output: \"dwsid=abc123; Domain=localhost; Path=/; Secure; SameSite=None; HttpOnly\"\n */\nexport function rewriteCookieForLocalhost(cookie: string): string {\n let rewritten = cookie;\n\n // Replace Domain= with localhost (case-insensitive)\n rewritten = rewritten.replace(/Domain=[^;]+/gi, 'Domain=localhost');\n\n // Add Domain=localhost if not present\n if (!/Domain=/i.test(cookie)) {\n // Insert after first attribute (cookie name=value)\n rewritten = rewritten.replace(/^([^;]+)/, '$1; Domain=localhost');\n }\n\n return rewritten.trim();\n}\n\n/**\n * Inline script injected into proxied HTML responses to intercept `document.cookie` writes.\n *\n * **Why this is needed (Layer 3 cookie rewriting):**\n *\n * The hybrid proxy rewrites Set-Cookie headers from SFCC responses (Layer 1), but after\n * the SFRA page fully loads, client-side JavaScript sets cookies via `document.cookie`.\n * These writes bypass the proxy entirely.\n *\n * SFRA's JS typically checks `window.location.protocol` to decide whether to add `Secure`.\n * On `http://localhost`, it sees `http:` and omits `Secure`, producing cookies like:\n *\n * document.cookie = \"dwsid=abc; SameSite=None\" // No Secure → browser rejects\n *\n * This interceptor patches `document.cookie` to:\n * 1. Rewrite `Domain=...` → `Domain=localhost`\n * 2. Ensure `Secure` is present (localhost is a secure context)\n * 3. If `SameSite=None` is present without `Secure`, add `Secure`\n *\n * This keeps client-side cookie writes consistent with the proxy's Layer 1 rewrites\n * and ensures hybrid auth cookies (dwsid, cc-*) stay in sync between Storefront Next\n * and SFRA.\n *\n * **LOCAL DEVELOPMENT ONLY** — This script is only injected by the Vite dev server proxy.\n */\nconst COOKIE_INTERCEPTOR_SCRIPT = `<script data-hybrid-proxy=\"cookie-interceptor\">\n(function() {\n var desc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');\n if (!desc || !desc.set) return;\n Object.defineProperty(document, 'cookie', {\n get: function() { return desc.get.call(this); },\n set: function(val) {\n // Rewrite Domain to localhost\n val = val.replace(/Domain=[^;]+/gi, 'Domain=localhost');\n // Ensure Secure is present if SameSite=None (localhost is a secure context)\n if (/SameSite=None/i.test(val) && !/;\\\\s*Secure\\\\b/i.test(val)) {\n val += '; Secure';\n }\n desc.set.call(this, val);\n },\n configurable: true\n });\n})();\n</script>`;\n\n/**\n * Escape special regex characters in a string for use in `new RegExp()`.\n */\nfunction escapeRegExp(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Discriminated result of {@link rewriteLocationForProxy}.\n *\n * - `rewritten` — Location was same-origin; the proxy URL is in `url`.\n * - `off-origin` — Location points elsewhere; pass the original header through.\n * - `malformed` — Location couldn't be parsed; caller should warn and pass through.\n */\nexport type LocationRewriteResult = { kind: 'rewritten'; url: string } | { kind: 'off-origin' } | { kind: 'malformed' };\n\n/**\n * Rewrite an SFCC Location header so the user stays on the proxy origin AND so\n * any query params from the original request survive an SFCC redirect.\n *\n * SFCC frequently redirects bare paths like `/cart` to a canonical SFRA URL\n * (`/s/{siteId}/{locale}/Cart-Show`) without echoing the user's query string in\n * the Location header. Without this merge step, params like `?foo=bar` set by\n * the storefront — including ones the destination page expects — get dropped on\n * the cross-app hop.\n *\n * Resolution rules:\n * - Off-origin Location → `{ kind: 'off-origin' }` so the caller leaves the\n * header unchanged and the browser navigates as SFCC intended.\n * - Same-origin Location → rewrite the origin to the proxy host, then merge the\n * original request's query params into the redirect target's URL. Multi-value\n * keys (e.g. SFRA's `pmid=...&pmid=...`) are preserved on both sides. The\n * redirect target wins on collision so SFCC can intentionally override a key.\n * - Malformed Location → `{ kind: 'malformed' }` so the caller can warn.\n *\n * @param locationHeader - Raw Location header value from the SFCC response.\n * @param requestUrl - Original request URL on the proxy (e.g. `/cart?foo=bar`).\n * @param targetOrigin - SFCC origin used as the base for relative Location values.\n * @param proxyOrigin - Proxy origin (e.g. `http://localhost:5173`) the caller wants the user to stay on.\n */\nexport function rewriteLocationForProxy({\n locationHeader,\n requestUrl,\n targetOrigin,\n proxyOrigin,\n}: {\n locationHeader: string;\n requestUrl: string;\n targetOrigin: string;\n proxyOrigin: string;\n}): LocationRewriteResult {\n let locationUrl: URL;\n try {\n locationUrl = new URL(locationHeader, targetOrigin);\n } catch {\n return { kind: 'malformed' };\n }\n\n if (locationUrl.origin !== targetOrigin) {\n return { kind: 'off-origin' };\n }\n\n let requestQuery: URLSearchParams;\n try {\n requestQuery = new URL(requestUrl, proxyOrigin).searchParams;\n } catch {\n requestQuery = new URLSearchParams();\n }\n\n // Snapshot the redirect target's keys before mutating, so `has(key)` reflects\n // only what SFCC put there. Without this snapshot, the first appended value\n // for a multi-value key would mark the key \"present\" and drop the rest —\n // breaking SFRA patterns that repeat keys (e.g. `pmid=PROMO1&pmid=PROMO2`).\n const targetKeys = new Set(locationUrl.searchParams.keys());\n for (const [key, value] of requestQuery) {\n if (!targetKeys.has(key)) {\n locationUrl.searchParams.append(key, value);\n }\n }\n\n return {\n kind: 'rewritten',\n url: `${proxyOrigin}${locationUrl.pathname}${locationUrl.search}${locationUrl.hash}`,\n };\n}\n\n/** Cap loud values in debug logs so a long URL doesn't drown the dev console. */\nfunction truncateForLog(value: string, max = 120): string {\n return value.length > max ? `${value.slice(0, max)}…` : value;\n}\n\n/**\n * Vite plugin for hybrid proxying between Storefront Next and legacy SFRA.\n *\n * Uses http-proxy to silently forward non-matching requests to SFCC without visible\n * redirects. Rewrites Set-Cookie headers, Location headers, and HTML/JSON response\n * bodies to keep all navigation within the localhost proxy.\n *\n * Routing decisions are delegated to the `routeMatcher` callback injected via options,\n * keeping the SDK free of template-specific routing logic.\n *\n * @param options - Plugin configuration\n * @returns Vite plugin\n */\nexport function hybridProxyPlugin(options: HybridProxyPluginOptions): Plugin {\n if (!options.enabled) {\n logger.debug('Hybrid proxy disabled (HYBRID_PROXY_ENABLED is not true)');\n return {\n name: 'hybrid-proxy',\n };\n }\n\n if (!options.targetOrigin) {\n logger.warn('Hybrid proxy: no target origin configured (SFCC_ORIGIN required)');\n return {\n name: 'hybrid-proxy',\n };\n }\n\n if (!options.defaultSiteId) {\n throw new Error(\n 'Hybrid proxy is enabled but no default site ID was provided.\\n\\n' +\n 'Set PUBLIC__app__defaultSiteId in your .env file:\\n' +\n ' PUBLIC__app__defaultSiteId=RefArchGlobal\\n\\n' +\n 'See docs/README-HYBRID-PROXY.md for the full reference.'\n );\n }\n\n logger.info(`Hybrid proxy enabled → ${options.targetOrigin}`);\n logger.debug(`Hybrid proxy routing rules: ${options.routingRules.slice(0, 100)}...`);\n const locale = options.locale || 'default';\n const defaultSiteId = options.defaultSiteId;\n logger.debug(\n `Hybrid proxy path transformation: / → /s/${defaultSiteId}, /path → /s/${defaultSiteId}/${locale}/path`\n );\n\n // Pre-compile regex for URL rewriting in response bodies\n const targetOriginPattern = new RegExp(escapeRegExp(options.targetOrigin), 'g');\n\n // Create http-proxy instance\n // selfHandleResponse: true prevents http-proxy from automatically piping the\n // proxy response to the client. This lets us buffer HTML responses and rewrite\n // SFCC URLs to localhost, keeping client-side navigation within the proxy.\n const proxy = httpProxy.createProxyServer({\n changeOrigin: true,\n followRedirects: false,\n selfHandleResponse: true,\n });\n\n // Rewrite request path to SFRA format (/s/{siteId}/{locale}/path)\n proxy.on('proxyReq', (proxyReq, req) => {\n const url = new URL(req.url || '', `http://${req.headers.host}`);\n const pathname = url.pathname;\n\n // Check if path needs SFRA decoration\n // Skip if:\n // - Already has /s/ prefix\n // - Is an /on/demandware.* path (already in SFCC format)\n const needsTransformation = !pathname.startsWith('/s/') && !pathname.startsWith('/on/demandware.');\n\n if (needsTransformation) {\n const originalPath = proxyReq.path;\n /**\n * \"/\" maps to the SFRA/SiteGenesis site root — no locale in the path\n * This would simply proxy to SFCC hostname (eg.: https://zzrf-001.dx.commercecloud.salesforce.com/s/{siteId}/{locale}/) which is not a valid storefront URL.\n * We need to rewrite the path to /s/{siteId} so that it can be proxied to the correct SFCC URL.\n */\n if (pathname === '/') {\n proxyReq.path = `/s/${defaultSiteId}${url.search}`;\n } else {\n // Rewrite internal proxy path without changing browser URL\n proxyReq.path = `/s/${defaultSiteId}/${locale}${pathname}${url.search}`;\n }\n logger.debug(`Hybrid proxy path rewrite: ${originalPath} → ${proxyReq.path}`);\n }\n });\n\n // Handle all proxy responses manually (required by selfHandleResponse: true).\n // For HTML responses: buffer body, decompress, rewrite SFCC URLs to localhost.\n // For non-HTML responses: pipe through with header rewrites only.\n proxy.on('proxyRes', (proxyRes: IncomingMessage, req, res) => {\n const clientRes = res;\n\n // --- Safety net: detect SFCC error redirects ---\n // When SFCC doesn't recognize a URL it redirects to its 404 page and clears\n // session cookies. This usually means the routing rules are misconfigured —\n // a path that should go to Storefront Next is being proxied to SFCC instead.\n // Strip Set-Cookie headers from these responses to prevent cookie corruption.\n //\n // Both shapes of error redirect are caught: a real 3xx redirect to /404, and\n // a SFRA `plugin_redirect` 200+Location pointing at /404. The 200 case would\n // otherwise reach the 200→302 normalization below and forward SFRA's\n // session-clear cookies to the browser as a \"real\" redirect — wiping the\n // hybrid session even though the shopper can recover by navigating to a\n // valid URL.\n const locationHeader = proxyRes.headers.location;\n const statusCode = proxyRes.statusCode || 200;\n const isRedirectToError =\n typeof locationHeader === 'string' &&\n /\\/404\\b/.test(locationHeader) &&\n ((statusCode >= 300 && statusCode < 400) || statusCode === 200);\n\n if (isRedirectToError) {\n logger.warn(\n `⚠️ SFCC returned a redirect to 404 for ${req.url}. ` +\n `This usually means your HYBRID_ROUTING_RULES are missing a pattern for this path. ` +\n `Stripping Set-Cookie headers to prevent session cookie corruption. ` +\n `Fix: add a matching pattern to HYBRID_ROUTING_RULES (e.g., \"^${req.url?.split('?')[0]}.*\")`\n );\n delete proxyRes.headers['set-cookie'];\n }\n\n // --- Header rewrites (apply to ALL responses) ---\n\n // Rewrite Set-Cookie headers for localhost (skip if already stripped above)\n const setCookieHeaders = proxyRes.headers['set-cookie'];\n if (setCookieHeaders && Array.isArray(setCookieHeaders)) {\n proxyRes.headers['set-cookie'] = setCookieHeaders.map((cookie) => {\n const rewritten = rewriteCookieForLocalhost(cookie);\n logger.debug(`Hybrid proxy cookie rewrite: ${cookie.slice(0, 50)}... → ${rewritten.slice(0, 50)}...`);\n return rewritten;\n });\n }\n\n // Rewrite Location header in redirects to keep user on localhost AND merge\n // the original request's query params into the redirect target. SFCC redirects\n // bare paths like `/cart` to a canonical SFRA URL without echoing the request's\n // query string, so params like `?foo=bar` would be dropped on the cross-app hop\n // without this merge.\n //\n // Same-origin redirects also get the SFRA `plugin_redirect` 200+Location\n // normalization: that cartridge sometimes returns HTTP 200 with a Location\n // header instead of a proper 3xx redirect, and browsers only follow Location\n // on 3xx responses. We convert 200+Location into a 302 short-circuit so the\n // browser actually follows the redirect. The conversion is nested inside the\n // `rewritten` branch because (a) same-origin is the trust boundary we're\n // willing to coerce a 302 from, and (b) `result.url` is the value we need to\n // emit. An external 200+Location flows through unchanged via the `off-origin`\n // branch, matching the existing trust boundary.\n if (locationHeader && typeof locationHeader === 'string') {\n const proxyOrigin = `http://${req.headers.host}`;\n const result = rewriteLocationForProxy({\n locationHeader,\n requestUrl: req.url ?? '',\n targetOrigin: options.targetOrigin,\n proxyOrigin,\n });\n if (result.kind === 'rewritten') {\n proxyRes.headers.location = result.url;\n logger.debug(\n `Hybrid proxy location rewrite: ${truncateForLog(locationHeader)} → ${truncateForLog(result.url)}`\n );\n\n if (statusCode === 200) {\n proxyRes.resume(); // drain upstream — we're discarding the body\n const redirectHeaders: Record<string, string | string[]> = {\n location: result.url,\n };\n const setCookie = proxyRes.headers['set-cookie'];\n if (setCookie) {\n redirectHeaders['set-cookie'] = setCookie;\n }\n clientRes.writeHead(302, redirectHeaders);\n clientRes.end();\n logger.debug(\n `Hybrid proxy normalized 200+Location → 302 for ${req.url} (Location: ${truncateForLog(result.url)})`\n );\n return;\n }\n } else if (result.kind === 'malformed') {\n logger.warn(`Hybrid proxy: invalid Location header: ${truncateForLog(locationHeader)}`);\n }\n // off-origin: leave the header alone so the browser follows SFCC's intended redirect\n }\n\n // --- Response body handling ---\n\n const contentType = (proxyRes.headers['content-type'] || '').split(';')[0].trim();\n const isRewritable = contentType === 'text/html' || contentType === 'application/json';\n\n if (!isRewritable) {\n // Non-HTML/JSON: write headers and pipe body through unchanged\n clientRes.writeHead(proxyRes.statusCode || 200, proxyRes.headers);\n proxyRes.pipe(clientRes);\n return;\n }\n\n // HTML or JSON: buffer the response body for URL rewriting\n const chunks: Buffer[] = [];\n proxyRes.on('data', (chunk: Buffer) => chunks.push(chunk));\n proxyRes.on('end', () => {\n let body: Buffer<ArrayBufferLike> = Buffer.concat(chunks);\n\n // Decompress if needed\n const encoding = proxyRes.headers['content-encoding'];\n if (encoding === 'gzip') {\n body = gunzipSync(body);\n } else if (encoding === 'br') {\n body = brotliDecompressSync(body);\n } else if (encoding === 'deflate') {\n body = inflateSync(body);\n }\n\n // Rewrite SFCC origin URLs to localhost so client-side navigation stays proxied\n const proxyOrigin = `http://${req.headers.host}`;\n let text = body.toString('utf8');\n // Reset lastIndex since the regex has the global flag and is reused across calls\n targetOriginPattern.lastIndex = 0;\n text = text.replace(targetOriginPattern, proxyOrigin);\n\n // Inject document.cookie interceptor into HTML responses.\n // Must run before any SFRA script, so inject at the start of <head>.\n if (contentType === 'text/html') {\n const headIndex = text.indexOf('<head');\n if (headIndex !== -1) {\n const insertAfter = text.indexOf('>', headIndex);\n if (insertAfter !== -1) {\n text = text.slice(0, insertAfter + 1) + COOKIE_INTERCEPTOR_SCRIPT + text.slice(insertAfter + 1);\n }\n }\n }\n\n // Update headers: remove content-encoding (we decompressed) and fix content-length\n const headers = { ...proxyRes.headers };\n delete headers['content-encoding'];\n delete headers['transfer-encoding'];\n headers['content-length'] = String(Buffer.byteLength(text, 'utf8'));\n\n clientRes.writeHead(proxyRes.statusCode || 200, headers);\n clientRes.end(text);\n\n logger.debug(`Hybrid proxy rewrote ${contentType} body URLs for ${req.url}`);\n });\n });\n\n // Error handling\n proxy.on('error', (err, req, res) => {\n logger.error(`Hybrid proxy error: ${err.message} ${req.url}`);\n if ('writeHead' in res && !res.headersSent) {\n res.writeHead(502, { 'Content-Type': 'text/plain' });\n res.end('Bad Gateway: Failed to proxy to SFCC');\n }\n });\n\n return {\n name: 'hybrid-proxy',\n enforce: 'pre', // Run before Vite's internal middleware\n\n configureServer(server: ViteDevServer) {\n server.middlewares.use((req, res, next) => {\n const pathname = req.url?.split('?')[0] || '';\n\n // Skip Vite internals and assets\n if (shouldSkipProxy(pathname)) {\n return next();\n }\n\n // SFCC paths always proxy (even if routing rules don't explicitly exclude them)\n // These are internal SFCC endpoints and static assets\n const isSFCCPath = pathname.startsWith('/on/demandware.');\n\n // Check routing rules (unless it's an SFCC path)\n let shouldRouteToNextApp = false;\n if (!isSFCCPath) {\n try {\n shouldRouteToNextApp = options.routeMatcher(pathname, options.routingRules);\n } catch (error) {\n // Fail-safe: if routing check fails, let React Router handle it\n logger.error(`Hybrid proxy error checking routing rules: ${String(error)}`);\n return next();\n }\n\n if (shouldRouteToNextApp) {\n // Let React Router handle this route\n return next();\n }\n }\n\n // Proxy to SFCC\n logger.debug(`Hybrid proxy: ${req.method} ${pathname} → ${options.targetOrigin}`);\n\n try {\n proxy.web(req, res, {\n target: options.targetOrigin,\n });\n } catch (error) {\n logger.error(`Hybrid proxy failed to proxy request: ${String(error)}`);\n if (!res.headersSent) {\n res.writeHead(502, { 'Content-Type': 'text/plain' });\n res.end('Bad Gateway: Failed to proxy to SFCC');\n }\n }\n });\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { logger } from '../logger';\n\n/**\n * Cloudflare eCDN Routing Rule Matcher\n *\n * Parses Cloudflare-style routing expressions and tests pathnames against them.\n * This utility is environment-agnostic and works in both Node.js and browser contexts.\n *\n * @example\n * ```typescript\n * const rules = '(http.request.uri.path matches \"^/$\" or http.request.uri.path matches \"^/search.*\")';\n * shouldRouteToNext('/', rules); // true - route to Storefront Next\n * shouldRouteToNext('/search', rules); // true - route to Storefront Next\n * shouldRouteToNext('/checkout', rules); // false - proxy to SFRA/legacy\n * ```\n *\n * Environment variables used:\n * - HYBRID_PROXY_ENABLED (optional) - Boolean flag to enable/disable hybrid proxy\n * - HYBRID_ROUTING_RULES (optional) - Cloudflare routing expression string\n * - SFCC_ORIGIN (optional) - Base URL for SFCC sandbox redirects\n */\n\n// Regex cache to avoid recompiling patterns on every request\nconst regexCache = new Map<string, RegExp>();\n\n/**\n * Extracts regex patterns from a Cloudflare routing expression.\n *\n * Parses Cloudflare \"matches\" expressions like:\n * (http.request.uri.path matches \"^/$\" or http.request.uri.path matches \"^/category.*\")\n *\n * And extracts the regex patterns: [\"^/$\", \"^/category.*\"]\n *\n * @param expression - Cloudflare expression string\n * @returns Array of regex pattern strings\n *\n * @example\n * ```typescript\n * extractPatterns('(http.request.uri.path matches \"^/$\")');\n * // Returns: [\"^/$\"]\n *\n * extractPatterns('(http.request.uri.path matches \"^/$\" or http.request.uri.path matches \"^/search.*\")');\n * // Returns: [\"^/$\", \"^/search.*\"]\n * ```\n */\nexport function extractPatterns(expression: string): string[] {\n if (!expression || typeof expression !== 'string') {\n return [];\n }\n\n // Match: http.request.uri.path matches \"PATTERN\" or http.request.uri.path matches 'PATTERN'\n // Handles both single and double quotes\n const regex = /http\\.request\\.uri\\.path\\s+matches\\s+[\"']([^\"']+)[\"']/gi;\n const patterns: string[] = [];\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(expression)) !== null) {\n patterns.push(match[1]);\n }\n\n return patterns;\n}\n\n/**\n * Tests if a pathname matches any of the provided regex patterns (logical OR).\n * Uses caching to optimize repeated pattern compilations.\n *\n * @param pathname - URL pathname to test (e.g., \"/search\", \"/category/shoes\")\n * @param patterns - Array of regex pattern strings\n * @returns true if pathname matches any pattern, false otherwise\n *\n * @example\n * ```typescript\n * testPatterns('/category/shoes', ['^/category.*', '^/search.*']);\n * // Returns: true (matches first pattern)\n *\n * testPatterns('/checkout', ['^/category.*', '^/search.*']);\n * // Returns: false (matches no patterns)\n * ```\n */\nexport function testPatterns(pathname: string, patterns: string[]): boolean {\n if (!pathname || !patterns || patterns.length === 0) {\n return false;\n }\n\n // Test pathname against each pattern (logical OR - any match returns true)\n for (const pattern of patterns) {\n try {\n // Check cache first\n let regex = regexCache.get(pattern);\n if (!regex) {\n regex = new RegExp(pattern);\n regexCache.set(pattern, regex);\n }\n\n if (regex.test(pathname)) {\n return true;\n }\n } catch (error) {\n // Invalid regex pattern - log warning and skip\n logger.warn(`Invalid regex pattern: ${pattern} ${String(error)}`);\n continue;\n }\n }\n\n return false;\n}\n\n/**\n * Main function: Determines if a pathname should route to Storefront Next\n * or be proxied/redirected to SFRA/legacy backend.\n *\n * @param pathname - URL pathname (e.g., \"/search\", \"/checkout\")\n * @param routingRules - Cloudflare routing expression string (optional)\n * @returns true if should route to Storefront Next, false if should proxy to SFRA\n *\n * @example\n * ```typescript\n * const rules = '(http.request.uri.path matches \"^/$\" or http.request.uri.path matches \"^/category.*\")';\n *\n * shouldRouteToNext('/', rules); // true - route to Next\n * shouldRouteToNext('/category/mens', rules); // true - route to Next\n * shouldRouteToNext('/checkout', rules); // false - proxy to SFRA\n * shouldRouteToNext('/any-path', undefined); // true - no rules = default to Next\n * ```\n */\nexport function shouldRouteToNext(pathname: string, routingRules?: string): boolean {\n if (!routingRules) {\n // No rules configured - default to routing to Next (fail-safe)\n return true;\n }\n\n const patterns = extractPatterns(routingRules);\n\n if (patterns.length === 0) {\n // Malformed expression or no patterns found - default to Next (fail-safe)\n logger.warn('No valid patterns found in routing rules');\n return true;\n }\n\n return testPatterns(pathname, patterns);\n}\n\n/**\n * Clears the regex cache. Useful for testing or when routing rules change.\n *\n * @example\n * ```typescript\n * clearCache();\n * // All cached regex patterns are removed\n * ```\n */\nexport function clearCache(): void {\n regexCache.clear();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA8DA,MAAMA,iBAA2C;CAC7C,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACV;AAED,IAAIC;;;;;;AAOJ,SAAS,qBAA8B;CACnC,MAAM,MAAM,QAAQ,IAAI,OAAO,MAAM;AACrC,KAAI,CAAC,IAAK,QAAO;CACjB,MAAM,aAAa,IAAI,aAAa;AACpC,KAAI;EAAC;EAAK;EAAQ;EAAO;EAAK,CAAC,SAAS,WAAW,CAAE,QAAO;AAC5D,QAAO,IAAI,MAAM,IAAI,CAAC,MAAM,UAAU;EAClC,MAAM,QAAQ,MAAM,MAAM;AAC1B,SAAO,UAAU,OAAO,UAAU,YAAY,UAAU;GAC1D;;AAGN,SAAS,eAAyB;AAC9B,KAAI,cAAe,QAAO;CAC1B,MAAM,WAAW,QAAQ,IAAI,iBAAiB,QAAQ,IAAI;AAC1D,KAAI,YAAY,YAAY,eAAgB,QAAO;AACnD,KAAI,oBAAoB,CAAE,QAAO;AACjC,KAAI,QAAQ,IAAI,aAAa,aAAc,QAAO;AAClD,QAAO;;AAGX,SAAS,UAAU,OAA0B;AACzC,QAAO,eAAe,UAAU,eAAe,cAAc;;AAGjE,MAAa,SAAS;CAClB,MAAM,KAAa,GAAG,MAAuB;AACzC,MAAI,CAAC,UAAU,QAAQ,CAAE;AACzB,UAAQ,MAAM,MAAM,IAAI,iBAAiB,EAAE,KAAK,GAAG,KAAK;;CAE5D,KAAK,KAAa,GAAG,MAAuB;AACxC,MAAI,CAAC,UAAU,OAAO,CAAE;AACxB,UAAQ,KAAK,MAAM,OAAO,gBAAgB,EAAE,KAAK,GAAG,KAAK;;CAE7D,KAAK,KAAa,GAAG,MAAuB;AACxC,MAAI,CAAC,UAAU,OAAO,CAAE;AACxB,UAAQ,IAAI,MAAM,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAK;;CAE1D,MAAM,KAAa,GAAG,MAAuB;AACzC,MAAI,CAAC,UAAU,QAAQ,CAAE;AACzB,UAAQ,IAAI,MAAM,KAAK,iBAAiB,EAAE,KAAK,GAAG,KAAK;;CAE3D,SAAS,OAAmC;AACxC,kBAAgB;;CAEpB,WAAqB;AACjB,SAAO,cAAc;;CAE5B;;;;ACvGD,SAAS,iBAAiB,KAAa;CACnC,MAAM,UAAU,GAAG,YAAY,KAAK,EAAE,eAAe,MAAM,CAAC;AAE5D,MAAK,MAAM,SAAS,SAAS;EACzB,MAAM,WAAW,KAAK,KAAK,KAAK,MAAM,KAAK;AAC3C,MAAI,MAAM,aAAa,CACnB,kBAAiB,SAAS;WACnB,MAAM,QAAQ,IAAI,MAAM,KAAK,SAAS,MAAM,EAAE;GACrD,MAAM,UAAU,GAAG,aAAa,UAAU,QAAQ;AAClD,OAAI,QAAQ,SAAS,aAAY,IAAI,QAAQ,SAAS,YAAY,EAAE;AAEhE,OAAG,cACC,UACA,QAAQ,QAAQ,mBAAmB,6CAA0C,CAChF;AACD,WAAO,MAAM,kCAAkC,WAAW;;;;;;;;AAS1E,SAAgB,mCAA2C;CACvD,IAAIC;AAEJ,QAAO;EACH,MAAM;EACN,SAAS;EAET,eAAe,QAAwB;AACnC,oBAAiB;;EAIrB,cAAc;GACV,MAAM,iBAAiB,eAAe,aAAa,OAAO,MAAM;AAChE,OAAI,GAAG,WAAW,eAAe,CAE7B,kBAAiB,eAAe;;EAG3C;;;;;;;;;;;;;;;;;;;;;;;;;AC3CL,SAAgB,YAAY,UAA0B;AAClD,QAAO,SAAS,QAAQ,OAAO,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;ACkBvC,MAAa,0BAA0B,cAAuC;CAC1E,MAAM,YAAY,UAAU;CAC5B,MAAM,cAAc;AACpB,KAAI,CAAC,aAAa,UAAU,WAAW,EACnC,QAAO;CAIX,MAAM,eAAe,UAAU,UAAU,SAAS;CAElD,MAAM,eAAe,aAAqB;EACtC,MAAM,YAAY,YAAY,SAAS;AAGvC,SAFeC,OAAK,MAAM,MAAM,UAAU,CACd,KAAK,MAAM,IAAI,CAAC,GACxB,QAAQ,yBAAyB,GAAG;;CAG5D,MAAM,aAAa,aAAqB;AACpC,SAAO,UAAU,MAAM,IAAI,CAAC;;CAGhC,MAAM,qBAAqB,YAAY,aAAa;AAKpD,KAAI,mBAAmB,SAAS,QAAQ,EAAE;EAEtC,MAAM,QADc,YAAY,UAAU,aAAa,CAAC,CAC9B,MAAM,eAAe;AAC/C,MAAI,OAAO;GAEP,MAAM,QADe,MAAM,GACA,MAAM,IAAI;GAErC,MAAM,WAAW,YAAY,MAAM,MAAM,SAAS,GAAG;AAKrD,UAAO,UAHS,MAAM,MAAM,GAAG,GAAG,CAET,KAAK,MAAM,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,CAC7B,GAAG,SAAS;;;AAO9C,KAAI,mBAAmB,SAAS,iBAAiB,EAAE;EAG/C,MAAM,QAFc,YAAY,UAAU,aAAa,CAAC,CAE9B,MAAM,iBAAiB;EAGjD,MAAM,YAFmB,MAAM,MAAM,SAAS,GAEX,MAAM,IAAI;EAG7C,IAAI;EACJ,IAAI;AAEJ,MAAI,UAAU,GAAG,WAAW,IAAI,EAAE;AAE9B,iBAAc,GAAG,UAAU,GAAG,GAAG,UAAU;AAC3C,mBAAgB,UAAU,MAAM,EAAE;SAC/B;AAEH,iBAAc,UAAU;AACxB,mBAAgB,UAAU,MAAM,EAAE;;EAGtC,MAAM,WAAW,YAAY,cAAc,cAAc,SAAS,GAAG;EAErE,MAAM,UAAU,cAAc,MAAM,GAAG,GAAG;AAI1C,SAAO,UAFU;GAAC;GAAW;GAAa,GAAG;GAAQ,CAAC,KAAK,MAAM,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,CAE1D,GAAG,SAAS;;AAG1C,QAAO;;;;;;;;;;;;;;;;AAiBX,MAAa,qCAA6C;AACtD,QAAO;EACH,MAAM;EACN,OAAO;EACP,SAAS;AACL,UAAO,EACH,cAAc,EACV,QAAQ,EACJ,OAAO,EACH,eAAe,EACX,QAAQ;IACJ,gBAAgB;IAChB,gBAAgB;IACnB,EACJ,EACJ,EACJ,EACJ,EACJ;;EAER;;;;;ACvIL,MAAM,sBAAsB;AAC5B,MAAM,2BAA2B;;;;;;AAOjC,MAAa,mBAAmB,SAAgC;AAI5D,QADwB,QAAQ,IAAI,oBAAoB,uBAAuB,SAAS,eAC/D,2BAA2B;;;;;ACRxD,MAAMC,cAAYC,OAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;;;;;;;;AAS9D,MAAa,mCAA2C;CACpD,IAAIC;CAMJ,IAAIC;;;;;;;;;CAUJ,MAAM,mCAAmC,YAA2B;EAChE,MAAM,aAAaF,OAAK,QAAQ,gBAAgB,YAAY;EAE5D,MAAM,eAAe,GAAG,gBAAgB,gBAAgB,KAAK,CAAC;EAC9D,MAAM,eAAeA,OAAK,QAAQ,gBAAgB,aAAa;AAE/D,QAAM,GAAG,UAAU,eAAe;AAClC,QAAM,GAAG,WAAW,YAAY,sCAAsC;EAEtE,MAAM,uBAAuBA,OAAK,QAAQD,aAAW,SAAS,eAAe;AAC7E,QAAM,GAAG,KAAK,sBAAsB,aAAa;EAGjD,MAAM,0BAA0B,GAAG,qBAAqB;AACxD,MAAI,MAAM,GAAG,WAAW,wBAAwB,CAC5C,OAAM,GAAG,KAAK,yBAAyB,GAAG,aAAa,MAAM;EAKjE,MAAM,SAASC,OAAK,QAAQD,aAAW,QAAQ;AAC/C,MAAI,MAAM,GAAG,WAAW,OAAO,EAAE;GAC7B,MAAM,QAAQ,MAAM,GAAG,QAAQ,OAAO;AACtC,QAAK,MAAM,QAAQ,MACf,KAAI,KAAK,WAAW,iBAAiB,IAAI,KAAK,SAAS,OAAO,EAAE;AAC5D,UAAM,GAAG,KAAKC,OAAK,KAAK,QAAQ,KAAK,EAAEA,OAAK,QAAQ,gBAAgB,KAAK,CAAC;IAE1E,MAAM,UAAU,GAAG,KAAK;AACxB,QAAI,MAAM,SAAS,QAAQ,CACvB,OAAM,GAAG,KAAKA,OAAK,KAAK,QAAQ,QAAQ,EAAEA,OAAK,QAAQ,gBAAgB,QAAQ,CAAC;;;EAMhG,MAAM,kBAAkBA,OAAK,QAAQ,eAAe,MAAM,eAAe;EACzE,MAAM,uBAAuBA,OAAK,QAAQ,gBAAgB,eAAe;EAEzE,MAAM,cAAc,MAAM,GAAG,SAAS,gBAAgB;AAMtD,SAAO,YAAY;AACnB,QAAM,GAAG,UAAU,sBAAsB,aAAa,EAAE,QAAQ,GAAG,CAAC;;AAGxE,QAAO;EACH,MAAM;EACN,OAAO;EACP,OAAO,EAAE,QAAQ;AACb,UAAO;IACH,OAAO,EACH,eAAe,EAYX,MAAM,OAAO,KAAK,gBAAgB;AAC9B,SAAI,IAAI,SAAS,qBAAqB,IAAI,QAAQ,SAAS,4BAA4B,CACnF;AAEJ,oBAAe,OAAO,IAAI;OAEjC,EACJ;IACD,cAAc,EACV,KAAK,EACD,SAAS,EACL,YAAY,MACf,EACJ,EACJ;IACD,cAAc,EAwBV,eAAe,UAAU,EAAE,MAAM,YAAY;AACzC,SAAI,SAAS,cAAc,SAAS,WAAW,SAAS,WAAW;AAC/D,UAAI,aAAa,MACb,QAAO,EAAE,UAAU,MAAM;AAQ7B,aAAO,EACH,SAHgB,iKAAiK,KAAK,UAAU,SAAS,IAI5M;;OAGZ;IACJ;;EAEL,eAAe,QAAQ;AACnB,oBAAiB;AAGjB,oBAAiB,OAAO,2BAA2B,kBAAkB;;EAEzE,UAAU;GACN,OAAO;GACP,SAAS,YAAY;AACjB,UAAM,kCAAkC;;GAE/C;EACJ;;;;;ACtKL,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;;;;;;;AAQxB,MAAa,+BAAuC;CAChD,IAAI,aAAa;CACjB,IAAI,YAAY;AAEhB,QAAO;EACH,MAAM;EAIN,SAAS;EACT,OAAO,SAAS,EAAE,QAAQ;AAItB,gBAAa,SAAS;AAEtB,eAAY,SAAS;;EAEzB,kBAAkB,MAAM;AACpB,OAAI,WACA;AAKJ,OAAI,UACA;AAEJ,OAAI,SAAS,MAIT,QAAO,EACH,SAAS,EACL,YAAY,CAAC,eAAe,EAC/B,EACJ;;EAGT,UAAU,IAAI,UAAU;AAGpB,OAAI,cAAc,UACd,QAAO;AAEX,OAAI,OAAO,iBAAiB;AAKxB,QAAI,aAAa,qBAAqB,UAAU,SAAS,sBAAsB,CAC3E,QAAO;AAEX,WAAO;;AAEX,UAAO;;EAGX,KAAK,IAAI;AAGL,OAAI,cAAc,UACd,QAAO;AAEX,OAAI,OAAO,kBAOP,QAJa;;;;AAMjB,UAAO;;EAEd;;;;;AC/BL,MAAMG,aAAY,eAAiE,WAAW;AAE9F,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAK5B,MAAM,0CAA0B,IAAI,OAAO,IAAI,qBAAqB,SAAS;;;;;;AAO7E,SAAS,wBACL,SACA,kBACI;AACJ,KAAI,gBAAgB,QAAQ,KAAK,eAAe,MAAM,EAAE,MAAM,sBAAsB,CAAC,CAEjF,KAAI,iBAAiB,SAAS,GAAG;EAC7B,IAAI,SAAS,QAAQ,KAAK;AAC1B,OAAK,IAAI,IAAI,iBAAiB,SAAS,GAAG,KAAK,GAAG,KAAK;GAEnD,MAAM,gBADkB,iBAAiB,GACH;AAOtC,YAAS,CANe,WACpB,kBAAkB,cAAc,cAAc,EAAE,EAAE,EAAE,MAAM,EAC1D,kBAAkB,cAAc,cAAc,CAAC,EAC/C,QACA,MACH,CACyB;;AAE9B,UAAQ,oBAAoB,OAAO;OAInC,SAAQ,YAAY,YAAY,oBAAoB,EAAE,oBAAoB,EAAE,QAAQ,KAAK,SAAS,CAAC;;;;;;;;;AAY/G,SAAS,wBACL,eACA,SACA,gBACa;CACb,IAAI,mBAAmB;AACvB,KAAI,gBAAgB,QAAQ,KAAK,eAAe,MAAM,EAAE,MAAM,eAAe,CAAC,EAAE;EAC5E,IAAI,WAAW;AAGf,MAAI,MAAM,QAAQ,QAAQ,KAAK,eAAe,WAAW,EAAE;GACvD,MAAM,OAAO,QAAQ,KAAK,eAAe,WAAW,MAC/C,MAAM,eAAe,EAAE,IAAI,gBAAgB,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC,CACrF;GACD,MAAM,WACF,QAAQ,eAAe,KAAK,IAAI,KAAK,SAAS,WAAW,KAAK,QAAQ,KAAK,MAAM,QAAQ;AAC7F,OAAI,YAAY,KACZ,OAAM,IAAI,MAAM,6CAA6C;AAEjE,OAAI,eAAe,aAAa,eAAe,UAAU,SAAS,GAAG;AAEjE,QADoB,QAAQ,KAAK,SAAS,SAAS,GAClC;KAGb,IAAI,iBAAiB,QAAQ,KAAK;AAClC,UAAK,IAAI,IAAI,eAAe,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;MAC3D,MAAM,kBAAkB,eAAe,UAAU;AAOjD,uBAAiB,CANU,WACvB,kBAAkB,cAAc,gBAAgB,cAAc,EAAE,EAAE,EAAE,MAAM,EAC1E,kBAAkB,cAAc,gBAAgB,cAAc,CAAC,EAC/D,gBACA,MACH,CACoC;;AAEzC,aAAQ,YAAY,eAAe,GAAsB;WACtD;KAGH,MAAM,aAAa,eAAe,UAAU,KAAK,oBAA2C;AACxF,aAAO,WACH,kBAAkB,cAAc,gBAAgB,cAAc,EAAE,EAAE,EAAE,KAAK,EACzE,MACA,EAAE,EACF,KACH;OACH;AACF,SAAI,WAAW,SAAS,EACpB,SAAQ,YAAY,YAAY,oBAAoB,EAAE,oBAAoB,EAAE,WAAW,CAAC;SAExF,SAAQ,YAAY,WAAW,GAAG;;AAG1C,uBAAmB;AACnB,eAAW;;;AAGnB,MAAI,CAAC,SACD,KAAI,QAAQ,KAAK,YAAY,QAAQ,KAAK,SAAS,SAAS,EAGxD,SAAQ,YAAY,YAAY,oBAAoB,EAAE,oBAAoB,EAAE,QAAQ,KAAK,SAAS,CAAC;MAKnG,SAAQ,YAAY,YAAY,oBAAoB,EAAE,oBAAoB,EAAE,EAAE,CAAC,CAAC;;AAI5F,QAAO;;;;;;;;;;AAWX,SAAS,mBACL,KACA,SACA,iBAAiD,MACjD,mBAAyD,MAC9C;CACX,MAAM,oCAAoB,IAAI,KAAa;CAE3C,MAAM,oBAAoB,kBAA6C;AACnE,MAAI,gBAAgB;GAChB,MAAM,aAAa,wBAAwB,SAAS,eAAe,eAAe;AAClF,OAAI,WAAY,mBAAkB,IAAI,WAAW;aAC1C,iBACP,yBAAwB,eAAe,iBAAiB;;AAGhE,YAAS,KAAK;EAEV,oBAAoB,UAA8C;GAC9D,MAAM,mBAAmB,SAAS,IAAI,eAAe;GACrD,MAAM,oBAAoB,MAAM,QAAQ,iBAAiB,GAAG,mBAAmB,CAAC,iBAAiB;AAEjG,QAAK,MAAM,mBAAmB,mBAAmB;IAC7C,MAAM,WAAW,gBAAgB,IAAI,OAAO;AAC5C,QAAI,YAAY,aAAa,SAAS,KAAK,EAAE;KACzC,MAAM,UAAU,SAAS,SAAS,KAAK,CAAC;AACxC,0BAAI,IAAI,OAAO,KAAK,QAAQ,cAAc,EAAC,KAAK,QAAQ,EAAE;AAEtD,uBAAiB,SAAsC;AAGvD,eAAS,SAAS,EACd,WAAW,OAAkC;AACzC,wBAAiB,MAAM;SAE9B,CAAC;;;;;EAMlB,gBAAgB,UAA0C;GACtD,MAAM,MAAM,SAAS,KAAK;AAC1B,OAAI,CAAC,aAAa,IAAI,IAAI,CAAC,cAAc,IAAI,CACzC;AAEJ,YAAS,SAAS,EACd,WAAW,OAAkC;AACzC,qBAAiB,MAAM;MAE9B,CAAC;;EAET,CAAC;AACF,QAAO;;;;;;;;AASX,SAAS,iCAAiC,WAAwB,gBAAiD;CAC/G,MAAM,mCAAmB,IAAI,KAAa;AAC1C,MAAK,MAAM,YAAY,WAAW;EAC9B,MAAM,mBAAmB,eAAe;AACxC,OAAK,MAAM,mBAAmB,iBAC1B,kBAAiB,IACb,UAAU,gBAAgB,cAAc,WAAW,gBAAgB,KAAK,QAAQ,QAAQ,GAAG,CAAC,IAC/F;;AAGT,QAAO,MAAM,KAAK,iBAAiB,CAAC,KAAK,KAAK;;AAGlD,SAAgB,iBACZ,MACA,gBACA,kBACa;AACb,KAAI,CAAC,wBAAwB,KAAK,KAAK,IAAI,CAAC,KAAK,SAAS,qBAAqB,CAC3E,QAAO;CAEX,MAAM,MAAM,MAAM,MAAM;EACpB,YAAY;EACZ,SAAS;GAAC;GAAc;GAAO;GAAoB;EACtD,CAAC;AAGF,KAAI,wBAAwB,KAAK,KAAK,EAAE;EAEpC,MAAM,8BAA8B,iCADV,mBAAmB,KAAK,sBAAsB,gBAAgB,KAAK,EACL,eAAe;AAEvG,aAAS,KAAK,EACV,kBAAkB,UAA4C;AAE1D,OADe,SAAS,KAAK,OAAO,UACrB,sBACX,UAAS,YAAY,QAAQ,4BAA4B,CAAC;KAGrE,CAAC;;AAIN,KAAI,KAAK,SAAS,qBAAqB,EAAE;EAErC,MAAM,mCAAmB,IAAI,KAAa;AAC1C,OAAK,MAAM,mBAAmB,iBAC1B,kBAAiB,IACb,UAAU,gBAAgB,cAAc,WAAW,gBAAgB,KAAK,QAAQ,QAAQ,GAAG,CAAC,IAC/F;EAEL,MAAM,8BAA8B,MAAM,KAAK,iBAAiB,CAAC,KAAK,KAAK;AAE3E,aAAS,KAAK,EACV,kBAAkB,UAA4C;AAE1D,OADe,SAAS,KAAK,OAAO,UACrB,gCACX,UAAS,YAAY,QAAQ,4BAA4B,CAAC;KAGrE,CAAC;AACF,qBAAmB,KAAK,sBAAsB,MAAM,iBAAiB;;AAEzE,QAAO,SAAS,IAAI,CAAC;;;;;;;;AASzB,SAAgB,oBACZ,SACA,UAAsC,EAAE,EAK1C;CACE,MAAMC,oBAA6C,EAAE;CACrD,MAAMC,mBAAkD,EAAE;CAC1D,MAAMC,qBAAyC,EAAE;CACjD,MAAM,mBAAmBC,OAAK,KAAK,SAAS,aAAa;CACzD,MAAM,gBAAgB,GAAG,YAAY,kBAAkB,EAAE,eAAe,MAAM,CAAC;CAE/E,MAAM,gCAAgC,KAAgB,aAAqB;EACvE,MAAM,YAAY,IAAI,KACjB,MAAM,IAAI,CACV,KAAK,SAAiB,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,aAAa,CAAC,CACjF,KAAK,GAAG;AAUb,SAAO;GAAE;GAAW,eADE,GAAG,UAAU,IARlB,SACZ,MAAM,IAAI,CACV,KAAK,EACJ,QAAQ,sBAAsB,GAAG,GAEjC,MAAM,OAAO,CACd,KAAK,SAAiB,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,aAAa,CAAC,CACjF,KAAK,GAAG;GAEsB;;CAEvC,MAAM,yBAAyB;AAE/B,MAAK,MAAM,OAAO,cACd,KAAI,IAAI,aAAa,EAAE;EACnB,MAAM,aAAaA,OAAK,KAAK,kBAAkB,IAAI,MAAM,uBAAuB;AAChF,MAAI,GAAG,WAAW,WAAW,EAAE;GAC3B,MAAM,kBAAkB,GAAG,aAAa,WAAW;AAEnD,OAAI,QAAQ,gBAAgB,gBAAgB,YAAY,KACpD;AAEJ,OAAI,mBAAmB,gBAAgB,WACnC,MAAK,MAAM,aAAa,gBAAgB,YAAY;AAChD,QAAI,UAAU,YAAY,MAAO;IACjC,MAAM,EAAE,UAAU,MAAM,eAAe,QAAQ,MAAM;AACrD,QAAI,YAAY,eAAe;AAC3B,SAAI,CAAC,kBAAkB,UACnB,mBAAkB,YAAY,EAAE;KAEpC,MAAM,EAAE,WAAW,kBAAkB,6BAA6B,KAAK,cAAc;AACrF,uBAAkB,UAAU,KAAK;MAC7B;MACA,MAAM;MACN;MACA;MACA;MACH,CAAC;;;AAId,OAAI,mBAAmB,gBAAgB,iBACnC,MAAK,MAAM,mBAAmB,gBAAgB,kBAAkB;IAC5D,MAAM,EAAE,MAAM,cAAc,QAAQ,MAAM;AAC1C,QAAI,cAAc;KACd,MAAM,EAAE,WAAW,kBAAkB,6BAA6B,KAAK,aAAa;AACpF,sBAAiB,KAAK;MAAE,MAAM;MAAc;MAAW;MAAe;MAAO,CAAC;;;AAI1F,OAAI,mBAAmB,gBAAgB,YACnC,MAAK,MAAM,QAAQ,gBAAgB,aAAa;IAC5C,MAAM,EAAE,QAAQ,SAAS,QAAQ,MAAM;AACvC,QAAI,UAAU,SAAS;AACnB,SAAI,CAAC,mBAAmB,QACpB,oBAAmB,UAAU,EAAE;KAEnC,MAAM,EAAE,WAAW,eAAe,gBAAgB,6BAC9C,KACA,QACH;AACD,wBAAmB,QAAQ,KAAK;MAC5B;MACA,MAAM;MACN;MACA;MACA;MACH,CAAC;;;;;AAQ1B,MAAK,MAAM,YAAY,kBACnB,mBAAkB,UAAU,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;AAEjE,kBAAiB,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;AAClD,MAAK,MAAM,UAAU,mBACjB,oBAAmB,QAAQ,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;AAIhE,MAAK,MAAM,YAAY,mBAAmB;EACtC,MAAM,UAAU,kBAAkB;EAClC,MAAM,uBAAO,IAAI,KAAqB;AACtC,OAAK,MAAM,SAAS,SAAS;GACzB,MAAM,WAAW,KAAK,IAAI,MAAM,MAAM;AACtC,OAAI,SACA,QAAO,KACH,+BAA+B,SAAS,iBAAiB,SAAS,SAAS,MAAM,cAAc,yBAAyB,MAAM,MAAM,qFACvI;AAEL,QAAK,IAAI,MAAM,OAAO,MAAM,cAAc;;;AAGlD,MAAK,MAAM,UAAU,oBAAoB;EACrC,MAAM,UAAU,mBAAmB;EACnC,MAAM,uBAAO,IAAI,KAAqB;AACtC,OAAK,MAAM,SAAS,SAAS;GACzB,MAAM,WAAW,KAAK,IAAI,MAAM,MAAM;AACtC,OAAI,SACA,QAAO,KACH,kCAAkC,OAAO,eAAe,SAAS,SAAS,MAAM,YAAY,yBAAyB,MAAM,MAAM,qFACpI;AAEL,QAAK,IAAI,MAAM,OAAO,MAAM,YAAY;;;AAIhD,QAAO;EAAE;EAAmB;EAAkB;EAAoB;;;;;ACtbtE,SAAgB,mCAAmC;CAE/C,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAI,eAAe;AAEnB,QAAO;EACH,MAAM;EACN,SAAS;EACT,eAAe,QAAwB;AAEnC,eACI,OAAO,QAAQ,MAAM,MAAM,UAAU,MAAM,SAAS,IAAI,EAAE,eAC1DC,OAAK,QAAQ,WAAW,QAAQ;AACpC,kBAAe,OAAO,SAAS;;EAEnC,aAAa;AAET,IAAC,CAAE,mBAAmB,oBAAqB,oBAAoB,WAAW,EAAE,cAAc,CAAC;;EAG/F,UAAU,MAAc,IAAY;AAGhC,OAAI,QAAQ,IAAI,4BAA4B,OACxC,QAAO;AAGX,OAAI;IACA,MAAM,kBAAkB,iBAAiB,MAAM,mBAAmB,iBAAiB;AACnF,QAAI,gBACA,QAAO;KACH,MAAM;KACN,KAAK;KACR;AAEL,WAAO;YACFC,KAAc;AACnB,WAAO,MAAM,6BAA6B,GAAG,IAAI,eAAe,QAAQ,IAAI,QAAQ,OAAO,IAAI,GAAG;AAClG,UAAM;;;EAGjB;;;;;ACnDL,MAAM,0BAA0B;AAChC,MAAM,2BAA2B,KAAK;;;;;;;AAQtC,SAAgB,0BAA0B,oBAAgD;CACtF,MAAMC,UAAoB,EAAE;CAC5B,MAAMC,kBAA4B,EAAE;AAEpC,MAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,mBAAmB,EAAE;EACjE,MAAMC,eAAyB,EAAE;AACjC,OAAK,MAAM,WAAW,UAAU;GAC5B,MAAM,aAAa,KAAK,QAAQ,KAAK,QAAQ,sBAAsB,GAAG;AACtE,WAAQ,KAAK,UAAU,QAAQ,YAAY,SAAS,WAAW,IAAI;AACnE,gBAAa,KAAK,QAAQ,YAAY;;AAE1C,kBAAgB,KAAK,MAAM,OAAO,MAAM,aAAa,KAAK,KAAK,CAAC,GAAG;;AAGvE,QAAO,GAAG,QAAQ,KAAK,KAAK,CAAC;;;;;EAK/B,gBAAgB,KAAK,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyC9B,SAAgB,oBAA4B;CACxC,IAAIC;CACJ,IAAIC;CACJ,IAAI,eAAe;AAEnB,QAAO;EACH,MAAM;EACN,SAAS;EACT,eAAe,QAAwB;AACnC,eACI,OAAO,QAAQ,MAAM,MAAM,UAAU,MAAM,SAAS,IAAI,EAAE,eAC1DC,OAAK,QAAQ,WAAW,QAAQ;AACpC,kBAAe,OAAO,SAAS;;EAEnC,aAAa;AAET,wBADiB,oBAAoB,WAAW,EAAE,cAAc,CAAC,CACnC;;EAElC,UAAU,IAAY;AAClB,OAAI,OAAO,wBACP,QAAO;;EAGf,KAAK,IAAY;AACb,OAAI,OAAO,yBACP,QAAO,0BAA0B,mBAAmB;;EAG/D;;;;;ACjGL,MAAa,+BAA+B;CACxC,IAAIC;AACJ,QAAO;EACH,MAAM;EACN,eAAe,QAAwB;AACnC,gBAAa;;EAEjB,gBAAgB,QAAuB;GACnC,MAAM,UAAU,WAAW,QAAQ;GACnC,MAAM,OAAO,OAAO,OAAO,QAAQ,CAAC,MAAM,UAAU,MAAM,SAAS,IAAI,EAAE,eAAe;GAExF,MAAMC,SAAOC,OAAK,MAAM,KAAK,MAAM,cAAc,MAAM,qBAAqB;AAC5E,UAAO,QAAQ,IAAID,OAAK;GAExB,MAAM,YAAY,SAAiB;AAC/B,QAAI,KAAK,SAAS,qBAAqB,EAAE;AACrC,YAAO,MAAM,kCAAkC,OAAO;AACtD,KAAK,OAAO,SAAS;;;AAI7B,UAAO,QAAQ,GAAG,OAAO,SAAS;AAClC,UAAO,QAAQ,GAAG,UAAU,SAAS;AACrC,UAAO,QAAQ,GAAG,UAAU,SAAS;;EAE5C;;;;;ACZL,MAAM,0BAA0B;;;;AAsDhC,SAAgB,qBAAqB,WAA4D;CAC7F,MAAM,iBAAiB,UAAU,mBAAmB;AACpD,KAAI,CAAC,eACD,QAAO;CAGX,MAAM,OAAO,eAAe,cAAc;AAC1C,KAAI,KAAK,WAAW,EAChB,QAAO;CAIX,MAAM,WAAW,KAAK;CAEtB,IAAIE;AACJ,KAAI,KAAK,gBAAgB,SAAS,CAC9B,mBAAkB,SAAS,iBAAiB;UACrC,KAAK,gCAAgC,SAAS,CACrD,mBAAkB,SAAS,SAAS,CAAC,MAAM,GAAG,GAAG;UAC1C,KAAK,qBAAqB,SAAS,CAE1C,OAAM,IAAI,MACN,kGAAkG,SAAS,SAAS,GACvH;KAED,QAAO;CAEX,IAAI,QAAQ;AAGZ,KAAI,KAAK,SAAS,GAAG;EACjB,MAAM,YAAY,KAAK;AACvB,MAAI,KAAK,0BAA0B,UAAU,EAAE;GAE3C,MAAM,gBAAgB,UAAU,YAAY,QAAQ;AACpD,OAAI,iBAAiB,KAAK,qBAAqB,cAAc,EAAE;IAC3D,MAAM,cAAc,cAAc,gBAAgB;AAClD,QAAI,eAAe,KAAK,gBAAgB,YAAY,CAChD,SAAQ,YAAY,iBAAiB;;;;AAMrD,QAAO;EACH,IAAI,GAAG,MAAM,GAAG;EAChB;EACH;;;;;AAML,SAAgB,eAAe,YAAwB,YAA6B;AAMhF,KAJ6B,WACxB,cAAc,CACd,QAAQ,SAA8B,KAAK,kBAAkB,IAAI,KAAK,SAAS,KAAK,WAAW,CAE3E,SAAS,EAC9B,QAAO;CAIX,MAAM,qBAAqB,WACtB,uBAAuB,CACvB,QAAQ,SAA4B,KAAK,kBAAkB,CAAC;AAEjE,MAAK,MAAM,QAAQ,oBAAoB;EACnC,MAAM,eAAe,KAAK,iBAAiB;AAC3C,OAAK,MAAM,QAAQ,aACf,KAAI,KAAK,SAAS,KAAK,WACnB,QAAO;;CAMnB,MAAM,qBAAqB,WAAW,uBAAuB;AAC7D,MAAK,MAAM,cAAc,oBAAoB;EACzC,MAAM,eAAe,WAAW,iBAAiB;AACjD,OAAK,MAAM,eAAe,cAAc;GAEpC,MAAM,YAAY,YAAY,SAAS;GACvC,MAAM,YAAY,YAAY,cAAc,EAAE,SAAS;AAEvD,OAAI,cAAc,cAAc,cAAc,WAC1C,QAAO;;;AAKnB,QAAO;;;;;AAMX,SAAgB,kBAAkB,YAAiC;AAE/D,KAAI,eAAe,YAAY,WAAW,CACtC,QAAO;CAIX,MAAM,YAAY,WACb,cAAc,CACd,QAAQ,SAA8B,KAAK,kBAAkB,IAAI,KAAK,mBAAmB,CAAC;AAE/F,MAAK,MAAM,QAAQ,WAAW;EAC1B,MAAM,OAAO,KAAK,SAAS;AAC3B,MAAI,QAAQ,KAAK,aAAa,CAAC,SAAS,WAAW,CAC/C,QAAO;;AAIf,QAAO;;;;;AAMX,eAAsB,eAClB,SACA,aACA,eACA,cACwB;CAGxB,MAAM,iBAAiB,MAAM,KADJ,GAAG,cAAc,iBACU;EAChD,KAAK;EACL,UAAU;EACb,CAAC;AAEF,QAAO,MAAM,eAAe,eAAe,OAAO,YAAY,cAAc,KAAK;CAEjF,MAAMC,aAA8B,EAAE;CACtC,MAAM,cAAc,QAAQ,QAAQ,aAAa,aAAa,CAAC;AAE/D,MAAK,MAAM,YAAY,eACnB,KAAI;EAEA,MAAM,UAAU,aAAa,UAAU,QAAQ;EAC/C,MAAM,aAAa,QAAQ,iBAAiB,UAAU,SAAS,EAAE,WAAW,MAAM,CAAC;EAGnF,MAAM,UAAU,WAAW,YAAY;AAEvC,OAAK,MAAM,oBAAoB,SAAS;GACpC,MAAM,aAAa,iBAAiB,eAAe;AAEnD,QAAK,MAAM,aAAa,WAGpB,KAFsB,UAAU,SAAS,KAEnB,aAAa;IAC/B,MAAM,gBAAgB,qBAAqB,UAAU;AAErD,QAAI,eAAe;KAEf,IAAI,eAAe,SAAS,aAAa,SAAS,CAC7C,QAAQ,OAAO,IAAI,CACnB,QAAQ,eAAe,GAAG;AAG/B,SAAI,CAAC,aAAa,WAAW,IAAI,CAC7B,gBAAe,KAAK;KAIxB,MAAM,kBAAkB,eAAe,YAAY,SAAS;KAC5D,MAAM,wBAAwB,eAAe,YAAY,eAAe;KACxE,MAAM,cAAc,kBAAkB,WAAW;AAEjD,gBAAW,KAAK;MACZ,IAAI,cAAc;MAClB;MACA;MACA,WAAW;MACX,iBAAiB;MACjB;MACH,CAAC;KAEF,MAAM,UAAU,EAAE;AAClB,SAAI,gBACA,SAAQ,KAAK,SAAS;AAE1B,SAAI,sBACA,SAAQ,KAAK,eAAe;AAEhC,SAAI,YACA,SAAQ,KAAK,WAAW;KAE5B,MAAM,cAAc,QAAQ,SAAS,IAAI,UAAU,QAAQ,KAAK,KAAK,CAAC,KAAK;AAC3E,YAAO,MAAM,wBAAwB,cAAc,GAAG,KAAK,eAAe,cAAc;;;;UAKnG,OAAO;AACZ,SAAO,KAAK,yBAAyB,SAAS,IAAK,MAAgB,UAAU;;AAKrF,QAAO;;;;;AAMX,SAAgB,qBAAqB,YAA6B,qBAA6B,YAAoB;CAE/G,MAAM,SAAS,CAAC,GAAG,WAAW,CAAC,MAC1B,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,IAAI,EAAE,aAAa,cAAc,EAAE,aAAa,CACrF;AAED,KAAI,OAAO,WAAW,EAClB,QAAO;;;;;;;;sDAQuC,mBAAmB;;;;CAMrE,MAAM,gBAAgB,OACjB,KAAK,EAAE,IAAI,cAAc,WAAW,iBAAiB,kBAAkB;AACpE,MAAI,aAAa,mBAAmB,aAAa;GAE7C,MAAM,WAAW,EAAE;AACnB,OAAI,UACA,UAAS,KAAK,mBAAmB;AAErC,OAAI,gBACA,UAAS,KAAK,+BAA+B;AAEjD,OAAI,YACA,UAAS,KAAK,uBAAuB;AAGzC,UAAO,wCAAwC,GAAG,mBAAmB,aAAa,QAAQ,SAAS,KAAK,KAAK,CAAC;QAE9G,QAAO,wCAAwC,GAAG,mBAAmB,aAAa;GAExF,CACD,KAAK,KAAK;AAEf,QAAO;;;;;;;;4BAQiB,OAAO,KAAK,MAAM,EAAE,GAAG,CAAC,KAAK,KAAK,CAAC;;sDAET,mBAAmB;EACvE,cAAc;;;;;;;AAQhB,SAAgB,mBAAmB,kBAA0B,eAAgC;CACzF,IAAIC;AAGJ,KAAI,CAAC,WAAW,iBAAiB,EAAE;AAC/B,SAAO,MAAM,mCAAmC;EAGhD,MAAM,uBAAuB;;;;;;;;;AAS7B,gBAAc,kBAAkB,sBAAsB,QAAQ;AAC9D,oBAAkB;OAElB,KAAI;AACA,oBAAkB,aAAa,kBAAkB,QAAQ;UACpD,OAAO;AACZ,QAAM,IAAI,MAAM,iCAAkC,MAAgB,UAAU;;CAKpF,MAAM,cAAc;CACpB,MAAM,YAAY;CAElB,MAAM,aAAa,gBAAgB,QAAQ,YAAY;CACvD,MAAM,WAAW,gBAAgB,QAAQ,UAAU;AAEnD,KAAI,eAAe,MAAM,aAAa,GAClC,OAAM,IAAI,MACN,iBAAiB,iBAAiB,mDACf,YAAY,SAAS,UAAU,iDACrD;CAML,MAAM,iBAAiB,GAHR,gBAAgB,MAAM,GAAG,aAAa,GAAmB,CAGvC,IAAI,cAAc,IAFrC,gBAAgB,MAAM,SAAS;AAQ7C,KAAI,mBAAmB,iBAAiB;AACpC,SAAO,MAAM,2CAA2C,mBAAmB;AAC3E,SAAO;;AAGX,KAAI;AACA,gBAAc,kBAAkB,gBAAgB,QAAQ;AACxD,SAAO,MAAM,6BAA6B,mBAAmB;AAC7D,SAAO;UACF,OAAO;AACZ,QAAM,IAAI,MAAM,kCAAmC,MAAgB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BrF,MAAa,wBAAwB,SAAqC,EAAE,KAAa;CACrF,MAAM,EACF,gBAAgB,kBAChB,eAAe,8BACf,qBAAqB,YACrB,cAAc,SACd;CAEJ,IAAIC;CAEJ,MAAM,wBAAwB,YAAY;AACtC,SAAO,MAAM,4CAA4C;EAezD,MAAM,aAAa,MAAM,eAZT,IAAI,QAAQ,EACxB,iBAAiB;GACb,QAAQ,GAAG,aAAa;GACxB,QAAQ,GAAG,WAAW;GACtB,KAAK,GAAG,QAAQ;GAChB,SAAS;GACT,cAAc;GACd,QAAQ;GACX,EACJ,CAAC,EAG+C,aAAa,eAAe,aAAa;AAM1F,SAAO,MAAM,YAAY,WAAW,OAAO,wCAAwC;EAEnF,MAAM,gBAAgB,qBAAqB,YAAY,mBAAmB;EAC1E,MAAM,mBAAmB,QAAQ,aAAa,aAAa;EAC3D,MAAM,UAAU,mBAAmB,kBAAkB,cAAc;AAEnE,SAAO,MAAM,yCAAyC;AAEtD,SAAO;GAAE;GAAkB;GAAS;;AAGxC,QAAO;EACH,MAAM;EAEN,eAAe,gBAAgB;AAC3B,iBAAc,eAAe;;EAGjC,MAAM,aAAa;AACf,OAAI;AACA,UAAM,uBAAuB;YACxB,OAAO;AACZ,WAAO,MAAM,wCAAyC,MAAgB,UAAU;AAChF,QAAI,YACA,OAAM;AAEV,WAAO,KAAK,kDAAkD;;;EAItE,MAAM,gBAAgB,EAAE,MAAM,UAAU;GACpC,MAAM,0BAA0B,cAAc,QAAQ,OAAO,IAAI;GACjE,MAAM,iBAAiB,KAAK,QAAQ,OAAO,IAAI;AAE/C,OACI,eAAe,SAAS,IAAI,wBAAwB,GAAG,KACtD,eAAe,SAAS,MAAM,IAAI,eAAe,SAAS,OAAO,GACpE;AACE,WAAO,MAAM,8BAA8B,KAAK,4BAA4B;AAE5E,QAAI;KACA,MAAM,EAAE,kBAAkB,YAAY,MAAM,uBAAuB;AAKnE,SAAI,SAAS;MACT,MAAM,iBAAiB,OAAO,YAAY,cAAc,iBAAiB;AACzE,UAAI,eACA,OAAM,OAAO,aAAa,eAAe;AAE7C,aAAO,MAAM,uCAAuC;WAEpD,QAAO,MAAM,0CAA0C;aAEtD,OAAO;AACZ,YAAO,MAAM,oCAAqC,MAAgB,UAAU;;AAGhF,WAAO,EAAE;;;EAGpB;;;;;;;;ACtfL,eAAsB,qBAAqB,aAAqB,YAAsD;CAClH,MAAM,qBAAqB,QAAQ,aAAa,WAAW;AAE3D,KAAI;EAIA,MAAM,UADe,MAAM,OADT,cAAc,mBAAmB,CAAC,OAExB;AAE5B,SAAO,MAAM,yBAAyB,aAAa;EAGnD,MAAM,aAAa,QAAQ,KAAK;AAEhC,MAAI,CAAC,YAAY;AACb,UAAO,MAAM,qCAAqC,aAAa;AAC/D,UAAO;;AAGX,SAAO;UACF,OAAO;AAEZ,SAAO,KAAK,kCAAkC,WAAW,IAAK,MAAgB,UAAU;AACxF,SAAO;;;;;;;;;ACZf,eAAe,0BAA0B,aAAqB,WAA2C;CACrG,MAAM,qCAAqB,IAAI,KAAa;CAK5C,MAAM,oBAAoB;CAG1B,MAAM,sBAAsB;CAG5B,MAAM,qBAAqB;AAE3B,MAAK,MAAM,YAAY,WAAW;EAI9B,MAAM,QAAQ,MAAM,KAFJ,KADS,QAAQ,aAAa,SAAS,EAChB,gBAAgB,EAErB,EAC9B,QAAQ;GAAC;GAAgB;GAAiB;GAAgB;GAAiB;GAAqB,EACnG,CAAC;AAEF,SAAO,MAAM,eAAe,MAAM,OAAO,YAAY,SAAS,KAAK;AAEnE,OAAK,MAAM,QAAQ,MACf,KAAI;GACA,MAAM,UAAU,aAAa,MAAM,QAAQ;GAG3C,IAAI;AACJ,WAAQ,QAAQ,kBAAkB,KAAK,QAAQ,MAAM,MAAM;IACvD,MAAM,YAAY,MAAM;AACxB,uBAAmB,IAAI,UAAU;AACjC,WAAO,MAAM,yBAAyB,UAAU,QAAQ,OAAO;;AAInE,OAAI,oBAAoB,KAAK,QAAQ,EAAE;AACnC,uBAAmB,IAAI,YAAY;AACnC,WAAO,MAAM,oCAAoC,OAAO;;AAI5D,WAAQ,QAAQ,mBAAmB,KAAK,QAAQ,MAAM,MAAM;IACxD,MAAM,YAAY,MAAM;AACxB,uBAAmB,IAAI,UAAU;AACjC,WAAO,MAAM,0BAA0B,UAAU,QAAQ,OAAO;;AAIpE,qBAAkB,YAAY;AAC9B,uBAAoB,YAAY;AAChC,sBAAmB,YAAY;WAC1B,OAAO;AACZ,UAAO,KAAK,sBAAsB,KAAK,IAAK,MAAgB,UAAU;;;AAKlF,QAAO;;;;;;AAOX,SAAS,qBAAqB,YAAwD;CAClF,MAAM,gCAAgB,IAAI,KAA0B;AAEpD,KAAI,CAAC,WAAW,SACZ,QAAO;AAGX,MAAK,MAAM,CAAC,aAAa,kBAAkB,OAAO,QAAQ,WAAW,SAAS,EAAE;AAE5E,MAAI,CAAC,cAAc,QACf;EAGJ,MAAM,gCAAgB,IAAI,KAAa;AAEvC,MAAI,cAAc,cAEd;QAAK,MAAM,CAAC,WAAW,cAAc,OAAO,QAAQ,cAAc,aAAa,CAC3E,KAAI,cAAc,KACd,eAAc,IAAI,UAAU;;AAKxC,MAAI,cAAc,OAAO,EACrB,eAAc,IAAI,aAAa,cAAc;;AAIrD,QAAO;;;;;;;;;;;;;;;;;;;;;;;AAwBX,MAAa,uCAAuC,SAA8C,EAAE,KAAa;CAC7G,MAAM,EAAE,aAAa,oBAAoB,YAAY,CAAC,MAAM,EAAE,gBAAgB,UAAU;CAExF,IAAIC;AAEJ,QAAO;EACH,MAAM;EACN,OAAO;EAEP,eAAe,YAAY;AACvB,oBAAiB;;EAGrB,MAAM,aAAa;GACf,MAAM,cAAc,eAAe;AAEnC,UAAO,MAAM,yCAAyC;GAGtD,MAAM,aAAa,MAAM,qBAAqB,aAAa,WAAW;AAEtE,OAAI,CAAC,YAAY;AACb,WAAO,MAAM,uDAAuD;AACpE;;GAIJ,MAAM,gBAAgB,qBAAqB,WAAW;AAEtD,OAAI,cAAc,SAAS,GAAG;AAC1B,WAAO,MAAM,mDAAmD;AAChE;;GAIJ,MAAM,qBAAqB,MAAM,0BAA0B,aAAa,UAAU;AAElF,UAAO,MACH,YAAY,mBAAmB,KAAK,6BAA6B,CAAC,GAAG,mBAAmB,CAAC,KAAK,KAAK,GACtG;GAGD,MAAMC,yBAAoE,EAAE;AAE5E,QAAK,MAAM,CAAC,aAAa,kBAAkB,cACvC,MAAK,MAAM,aAAa,cACpB,KAAI,CAAC,mBAAmB,IAAI,UAAU,CAClC,wBAAuB,KAAK;IACxB,SAAS;IACT,OAAO;IACV,CAAC;AAMd,OAAI,uBAAuB,WAAW,GAAG;AACrC,WAAO,MAAM,wCAAwC;AACrD;;AAIJ,QAAK,MAAM,EAAE,SAAS,WAAW,uBAC7B,QAAO,KAAK,OAAO,QAAQ,GAAG,MAAM,mBAAmB,MAAM,yBAAyB;AAG1F,OAAI,cACA,OAAM,IAAI,MACN,2BAA2B,uBAAuB,OAAO,0HAE5D;;EAGZ;;;;;;AC7NL,MAAM,kCAAkC;;AAGxC,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;AA0B1B,MAAa,sCAA8C;CACvD,IAAIC;CACJ,IAAIC;;CAEJ,IAAIC;AAEJ,QAAO;EACH,MAAM;EACN,OAAO;EAEP,eAAe,QAAQ;AACnB,oBAAiB;GAGjB,MAAM,KAAK,OAAO,4BAA4B,qBAAqB,EAAE;AACrE,oBAAiB,GAAG,kBAAkB,QAAQ,OAAO,MAAM,QAAQ;AACnE,kBAAe,GAAG,gBAAgB;;EAGtC,UAAU;GACN,OAAO;GACP,SAAS,YAAY;IACjB,MAAM,cAAc,eAAe;IACnC,MAAM,yBAAyB,QAC3B,aACA,cACA,mBACA,gCACH;AAED,QAAI,CAAC,WAAW,uBAAuB,CACnC;IAGJ,MAAM,EAAE,UAAU,MAAM,OAAO;IAC/B,MAAM,eAAe,QAAQ,aAAa,gBAAgB,kBAAkB;AAG5E,UAAM,MAAM;KACR,KAAK;KACL,OAAO,GAJO,gCAAgC,QAAQ,SAAS,GAAG,GAI5C,wBAAwB;KAC9C,QAAQ;KACR,QAAQ,CAAC,MAAM;KACf,UAAU;KACV,sBAAsB;MAAE,IAAI;MAAQ,KAAK;MAAS;KAClD,KAAK;KACL,OAAO;KACP,MAAM;KACN,YAAY,CAAC,KAAK;KAClB,UAAU,CAAC,SAAS;KACvB,CAAC;;GAET;EACJ;;;;;;;;;AC7EL,MAAM,mBAAmB;CAAC;CAAO;CAAQ;CAAO;CAAQ;CAAQ;CAAO;;;;;;;;;;AAWvE,MAAM,oBAAoB;;;;;AAM1B,SAAS,cAAc,cAAsB,UAAsC;AAC/E,MAAK,MAAM,OAAO,kBAAkB;EAChC,MAAM,WAAW,KAAK,QAAQ,cAAc,WAAW,IAAI;AAC3D,MAAIC,KAAG,WAAW,SAAS,CACvB,QAAO;;;;;;;;;;AAanB,SAAS,wBAAwB,oBAAoC;CACjE,MAAM,aAAa,KAAK,UAAU,YAAY,mBAAmB,CAAC;AAClE,QAAO;wBACa,WAAW;;;;;;;;gBAQnB,WAAW;;;;;;;;EAQzB,MAAM;;;;;;;;AASR,SAAS,wBAAwB,oBAAoC;AACjE,QAAO;;gBAEK,KAAK,UAAU,YAAY,mBAAmB,CAAC,CAAC;EAC9D,MAAM;;;;;;;;;;;;;;;;;;;;;;AAgCR,SAAgB,sBAA8B;CAC1C,IAAI,aAAa;CACjB,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;CACJ,IAAIC;AAEJ,QAAO;EACH,MAAM;EACN,SAAS;EAET,OAAO,SAAS,EAAE,QAAQ;AACtB,gBAAa,SAAS;;EAG1B,eAAe,QAAwB;AACnC,OAAI,WAAY;GAGhB,MAAMC,MAA4C,OAAO;AACzD,OAAI,CAAC,IAAK;AAEV,kBAAe,IAAI,kBAAkB;AACrC,yBAAsB,IAAI;AAC1B,yBAAsB,IAAI;AAG1B,yBAAsB,cAAc,cAAc,eAAe;AACjE,yBAAsB,cAAc,cAAc,eAAe;;EAGrE,KAAK,IAAI;AACL,OAAI,cAAc,CAAC,uBAAuB,CAAC,uBAAuB,CAAC,aAAc,QAAO;AAKxF,OAAI,GAAG,SAAS,kBAAkB,CAAE,QAAO;GAI3C,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC;AAErC,OAAI,KAAK,UAAU,eAAe,KAAK,KAAK,UAAU,oBAAoB,CAUtE,QAAO,wBAJc,sBACf,sBAAsB,oBACtB,sBAAsB,kBAEgB;AAGhD,OAAI,KAAK,UAAU,eAAe,KAAK,KAAK,UAAU,oBAAoB,CAKtE,QAAO,wBAJc,sBACf,sBAAsB,oBACtB,sBAAsB,kBAEgB;AAGhD,UAAO;;EAGX,gBAAgB,QAAuB;AACnC,OAAI,cAAc,CAAC,aAAc;GAGjC,MAAM,SAAS;GAKf,MAAM,UAAU,OAAO;GAEvB,MAAM,oBAAoB,aAAqB;IAC3C,MAAMC,aAAW,KAAK,SAAS,QAAQ,SAAS;IAChD,MAAM,WAAW,KAAK,SAASA,YAAU,KAAK,QAAQA,WAAS,CAAC;AAIhE,QAHY,KAAK,QAAQA,WAAS,KAGtB,OAAQ,aAAa,kBAAkB,aAAa,eAC5D;IAGJ,MAAM,MAAM,KAAK,QAAQA,WAAS;AAClC,QAAI,CAAC,iBAAiB,SAAS,IAAI,CAAE;IAGrC,MAAM,eAAe,cAAc,QAAQ,eAAe,KAAK;IAC/D,MAAM,eAAe,cAAc,QAAQ,eAAe,KAAK;AAI/D,QAAI,kBAFuB,wBAAwB,WAER,kBADhB,wBAAwB,QAE/C,CAAK,OAAO,SAAS;;AAI7B,WAAQ,GAAG,OAAO,iBAAiB;AACnC,WAAQ,GAAG,UAAU,iBAAiB;;EAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7JL,MAAa,wBAAgC;AACzC,QAAO;EACH,MAAM;EACN,OAAO,GAAG,EAAE,QAAQ;GAChB,MAAM,iBAAiB,QAAQ,IAAI;AACnC,OAAI,CAAC,eAAgB;AAMrB,WAAQ,IAAI,mCAAmC;AAG/C,OAAI,SAAS,cAAe;GAC5B,MAAM,aAAa,QAAQ,IAAI;AAC/B,UAAO;IACH,QAAQ;KACJ,cAAc;KACd,OAAO,OAAO,YACV,CAAC,aAAa,wBAAwB,CAAC,KAAK,WAAS,CACjDC,QACA;MAAE,QAAQ,cAAc;MAAgB,cAAc;MAAM,QAAQ;MAAO,CAC9E,CAAC,CACL;KACJ;IACD,cAAc,EAQV,SAAS;KACL;KACA;KACA;KACA;KACA;KACH,EACJ;IACJ;;EAER;;;;;AC/EL,MAAMC,aAAYC,eAAuD,WAAWA;AACpF,MAAMC,aAAYC,SAAuD,WAAWA;;;;;;;AAQpF,MAAM,oBAAoB,CAAC,SAAS;AACpC,MAAM,oBAAoB,CAAC,eAAe;;;;AAmB1C,SAAS,kBAAkB,iBAAgD;AAEvE,KAAI,oBAAoB,SACpB,QAAO;AAEX,KAAI,oBAAoB,MACpB,QAAO;AAEX,QAAO,EAAE;;;;;;AAOb,SAAS,mBAAmB,MAAc,OAAuC;AAC7E,QAAO,MAAM,MAAM,SAAS,KAAK,SAAS,KAAK,CAAC;;;;;AAMpD,SAAgB,sBAAsB,KAAwC;CAC1E,IAAI,QAAQ;AACZ,YAAS,KAAK,EACV,iBAAiB,QAAM;EACnB,MAAM,aAAaC,OAAK,KAAK;AAC7B,MAAI,CAAC,WAAY;AACjB,OAAK,MAAM,aAAa,WAEpB,KACI,UAAU,WAAW,SAAS,oBAC9B,aAAa,UAAU,WAAW,OAAO,IACzC,UAAU,WAAW,OAAO,SAAS,aACvC;AACE,WAAQ;AACR,UAAK,MAAM;AACX;;IAIf,CAAC;AACF,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BX,SAAgB,aACZ,MACA,gBACA,cACa;CACb,MAAM,MACF,gBACA,MAAM,MAAM;EACR,YAAY;EACZ,SAAS;GAAC;GAAc;GAAO;GAAa;EAC/C,CAAC;CAEN,IAAI,UAAU;CAId,MAAM,kCAAkC,0BAA0B,IAAI;CAItE,MAAM,0CAA0B,IAAI,KAAa;AAEjD,YAAS,KAAK,EACV,uBAAuB,QAAM;EACzB,MAAM,EAAE,aAAa,eAAeA,OAAK;AAGzC,MAAI,eAAe,sBAAsB,YAAY,EAAE;GACnD,MAAM,YAAY,YAAY,aAAa,QAAQ,SAAS;AACxD,QAAI,aAAa,KAAK,GAAG,IAAI,eAAe,SAAS,KAAK,GAAG,KAAK,EAAE;AAChE,6BAAwB,IAAI,KAAK,GAAG,KAAK;AACzC,YAAO;;AAKX,QAAI,eAAe,KAAK,GAAG,IAAI,gBAAgB,KAAK,GAAG,CACnD,6BAA4B,KAAK,IAAI,eAAe;AAGxD,WAAO;KACT;AAEF,OAAI,UAAU,SAAS,YAAY,aAAa,QAAQ;AACpD,cAAU;AACV,QAAI,UAAU,WAAW,GAAG;AAExB,uCAAkCA,OAAK;AACvC,YAAK,QAAQ;UAEb,aAAY,eAAe;;AAGnC;;AAIJ,MAAI,eAAe,sBAAsB,YAAY,EACjD;OAAI,YAAY,MAAM,eAAe,SAAS,YAAY,GAAG,KAAK,EAAE;AAChE,cAAU;AACV,4BAAwB,IAAI,YAAY,GAAG,KAAK;AAChD,sCAAkCA,OAAK;AACvC,WAAK,QAAQ;AACb;;;AAKR,MAAI,eAAe,mBAAmB,YAAY,EAC9C;OAAI,YAAY,MAAM,eAAe,SAAS,YAAY,GAAG,KAAK,EAAE;AAChE,cAAU;AACV,4BAAwB,IAAI,YAAY,GAAG,KAAK;AAChD,sCAAkCA,OAAK;AACvC,WAAK,QAAQ;AACb;;;AAMR,MAAI,WAAW,SAAS,GAAG;GACvB,MAAM,YAAY,WAAW,QAAQ,SAAS;AAC1C,QAAI,kBAAkB,KAAK,EAAE;KACzB,MAAM,eAAe,aAAa,KAAK,SAAS,GAAG,KAAK,SAAS,OAAO,KAAK,SAAS;AACtF,SAAI,eAAe,SAAS,aAAa,EAAE;AAGvC,8BAAwB,IAAI,KAAK,MAAM,KAAK;AAC5C,aAAO;;;AAGf,WAAO;KACT;AAEF,OAAI,UAAU,SAAS,WAAW,QAAQ;AACtC,cAAU;AACV,QAAI,UAAU,WAAW,GAAG;AACxB,uCAAkCA,OAAK;AACvC,YAAK,QAAQ;UAEb,QAAK,KAAK,aAAa;;;IAK1C,CAAC;AAIF,KAAI,QACA,YAAS,KAAK,EACV,oBAAoB,QAAM;AAEtB,MAAI,CAACA,OAAK,YAAY,WAAW,CAC7B;AAGJ,MAAIA,OAAK,KAAK,WAAW,SAAS,wBAAwB;GACtD,MAAM,OAAOA,OAAK,KAAK,WAAW;AAClC,OACI,mBAAmB,KAAK,IACxB,aAAa,KAAK,OAAO,KACxB,eAAe,SAAS,KAAK,OAAO,KAAK,IAAI,wBAAwB,IAAI,KAAK,OAAO,KAAK,GAC7F;AACE,sCAAkCA,OAAK;AACvC,WAAK,QAAQ;;;IAI5B,CAAC;AAKN,KAAI,QACA,qBAAoB,KAAK,gCAAgC;AAG7D,KAAI,CAAC,QACD,QAAO;AAIX,QADeF,WAAS,KAAK,EAAE,aAAa,MAAM,EAAE,KAAK,CAC3C;;;;;;;AAQlB,SAAS,4BAA4B,IAAkC,gBAA6C;AAChH,KAAI,eAAe,GAAG,CAClB,MAAK,MAAM,WAAW,GAAG,UAAU;AAC/B,MAAI,CAAC,QAAS;AAEd,MAAI,aAAa,QAAQ,IAAI,eAAe,SAAS,QAAQ,KAAK,CAC9D,OAAM,IAAI,MAAM,sCAAsC,QAAQ,KAAK,GAAG;AAG1E,MACI,cAAc,QAAQ,IACtB,aAAa,QAAQ,SAAS,IAC9B,eAAe,SAAS,QAAQ,SAAS,KAAK,CAE9C,OAAM,IAAI,MAAM,sCAAsC,QAAQ,SAAS,KAAK,GAAG;AAGnF,MAAI,eAAe,QAAQ,IAAI,gBAAgB,QAAQ,CACnD,6BAA4B,SAAS,eAAe;;AAKhE,KAAI,gBAAgB,GAAG,CACnB,MAAK,MAAM,YAAY,GAAG,YAAY;AAClC,MAAI,CAAC,SAAU;AAEf,MAAI,iBAAiB,SAAS,IAAI,aAAa,SAAS,IAAI,EAAE;AAC1D,OAAI,aAAa,SAAS,MAAM,IAAI,eAAe,SAAS,SAAS,MAAM,KAAK,CAC5E,OAAM,IAAI,MAAM,sCAAsC,SAAS,MAAM,KAAK,GAAG;AAGjF,OAAI,eAAe,SAAS,MAAM,IAAI,gBAAgB,SAAS,MAAM,CACjE,6BAA4B,SAAS,OAAO,eAAe;;AAInE,MACI,cAAc,SAAS,IACvB,aAAa,SAAS,SAAS,IAC/B,eAAe,SAAS,SAAS,SAAS,KAAK,CAE/C,OAAM,IAAI,MAAM,sCAAsC,SAAS,SAAS,KAAK,GAAG;;;;;;;AAUhG,SAAS,kCAAkC,QAA4B;CACnE,MAAM,kBAAkBE,OAAK,KAAK;AAClC,KAAI,CAAC,mBAAmB,gBAAgB,WAAW,EAAG;CAEtD,MAAM,OAAO,gBAAgB,gBAAgB,SAAS;AACtD,KAAI,KAAK,SAAS,iBAAiB,KAAK,MAAM,SAAS,iBAAiB,CACpE,iBAAgB,KAAK;;;;;;;;;;;;;;;;;;AAoB7B,SAAgB,uBAAuB,SAAuC,EAAE,EAAU;CACtF,MAAM,EAAE,gBAAgB,qBAAqB;CAE7C,IAAI,aAAa;AAEjB,QAAO;EACH,MAAM;EAIN,SAAS;EAET,eAAe,gBAAgB;AAC3B,gBAAa,eAAe,SAAS;;EAGzC,UAAU,MAAM,IAAI;AAEhB,OAAI,WACA,QAAO;AAIX,OAAI,CAAC,GAAG,SAAS,cAAc,CAC3B,QAAO;AAEX,OAAI,CAAC,kBAAkB,KAAK,GAAG,CAC3B,QAAO;AAGX,OAAI,kCAAkC,KAAK,GAAG,CAC1C,QAAO;GAKX,MAAMC,kBAAuC,KAAa,aAAa;AACvE,OAAI,CAAC,gBACD,QAAO;GAGX,MAAM,iBAAiB,kBAAkB,gBAAgB;AACzD,OAAI,eAAe,WAAW,EAC1B,QAAO;AAIX,OAAI,CAAC,mBAAmB,MAAM,eAAe,CACzC,QAAO;GAIX,MAAM,MAAM,MAAM,MAAM;IACpB,YAAY;IACZ,SAAS;KAAC;KAAc;KAAO;KAAa;IAC/C,CAAC;AAGF,OAAI,CAAC,sBAAsB,IAAI,CAC3B,QAAO;GAGX,MAAM,cAAc,aAAa,MAAM,gBAAgB,IAAI;AAC3D,OAAI,CAAC,YACD,QAAO;AAGX,UAAO;IACH,MAAM;IACN,KAAK;IACR;;EAER;;;;;ACjaL,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;AA0BhC,SAAgB,wBAAgC;AAC5C,QAAO;EACH,MAAM;EACN,gBAAgB,QAAuB;GACnC,MAAMC,SAAqC,OAAO,aAAa;AAC/D,OAAI,CAAC,OAAQ;GAEb,MAAM,sBAAsB,OAAO,YAAY,KAAK,OAAO;AAE3D,UAAO,cAAc,OACjB,IACA,UACA,YACC;IACD,MAAM,SAAS,MAAM,oBAAoB,IAAI,UAAU,QAAQ;AAG/D,QAAI,CAAC,UAAU,iBAAiB,UAAU,WAAW,UAAU,EAAE,UAAU,QACvE,QAAO;IAGX,MAAM,UAAU,OAAO,KAAK,YAAY,wBAAwB;AAChE,QAAI,YAAY,GACZ,QAAO;AAGX,QAAI;KACA,MAAM,cAAc,UAAU;KAC9B,MAAM,YAAY,OAAO,KAAK,QAAQ,MAAM,YAAY;KACxD,MAAM,aACF,cAAc,KACR,OAAO,KAAK,MAAM,YAAY,CAAC,MAAM,GACrC,OAAO,KAAK,MAAM,aAAa,UAAU,CAAC,MAAM;KAE1D,MAAM,UAAU,KAAK,MAAM,OAAO,KAAK,YAAY,SAAS,CAAC,SAAS,QAAQ,CAAC;AAE/E,SAAI,CAAC,QAAQ,WAAW,CAAC,MAAM,QAAQ,QAAQ,QAAQ,CACnD,QAAO;KAMX,MAAM,SAAS,OAAO,QAAQ,OAAO;AACrC,SAAI,CAAC,OACD,QAAO;AAMX,SAAI,QAAQ,QAAQ,WAAW,EAC3B,QAAO;KAMX,MAAMC,SAAwB,QAAQ,QAAQ;AAC9C,SAAI,CAAC,UAAU,OAAO,SAAS,IAAI,CAC/B,QAAO;AAGX,aAAQ,UAAU,CAAC,OAAO;KAE1B,MAAM,gBAAgB,OAAO,KAAK,KAAK,UAAU,QAAQ,CAAC,CAAC,SAAS,SAAS;AAC7E,YAAO,OACH,OAAO,KAAK,MAAM,GAAG,YAAY,GACjC,iBACC,cAAc,KAAK,KAAK,OAAO,KAAK,MAAM,UAAU;YACrD;AAIR,WAAO;;;EAGlB;;;;;;;;;;;;;;;ACtFL,SAAgB,WAAW,QAAmC;CAC1D,MAAM,UAAU,QAAQ,iBAAiB;AAEzC,QAAO;EACH,MAAM;EACN,OAAO;EACP,OAAO,YAAY;GACf,MAAM,SAAS,WAAW,OAAO,eAAe;AAChD,OAAI,MAAM,QAAQ,OAAO,CAAE;GAE3B,MAAM,uBAAuB,QAAQ;AAErC,UAAO,EACH,OAAO,EACH,eAAe,EACX,QAAQ,EACJ,aAAa,IAAI,MAAM;IACnB,MAAM,cAAc,GAAG,MAAM,QAAQ;AACrC,QAAI,YACA,QAAO,WAAW,YAAY;AAGlC,QAAI,OAAO,yBAAyB,WAChC,QAAO,qBAAqB,KAAK,MAAM,IAAI,KAAK;AAEpD,QAAI,wBAAwB,OAAO,yBAAyB,UACxD;UAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,qBAAqB,CAC1D,KAAI,IAAI,SAAS,GAAG,CAAE,QAAO;;MAI5C,EACJ,EACJ,EACJ;;EAER;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACXL,MAAa,0BAAkC;CAC3C,MAAM;CACN,SAAS;AACL,SAAO;GACH,SAAS,EACL,QAAQ;IAAC;IAAS;IAAa;IAAe,EACjD;GACD,cAAc,EACV,SAAS;IACL;IACA;IAKA;IACA;IACA;IACA;IACA;IACA;IAYA;IACA;IACH,EACJ;GACD,KAAK,EACD,YAAY,CAAC,sCAAsC,EACtD;GACJ;;CAER;;;;;;;;;;;;;;;;;;;;;;;ACGD,SAAgB,sBAAsB,SAAsC,EAAE,EAAY;CACtF,MAAM,EACF,qBAAqB,OACrB,iBAAiB;EACb,eAAe;EACf,cAAc;EACjB,EACD,gCAAgC;EAC5B,YAAY;EACZ,WAAW,CAAC,MAAM;EAClB,eAAe;EAClB,KACD;CAEJ,MAAMC,UAAoB;EACtB,kBAAkB;EAClB,GAAI,QAAQ,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,GAAG,EAAE;EAC3D,YAAY;EACZ,4BAA4B;EAC5B,kCAAkC;EAClC,wBAAwB;EACxB,qBAAqB;EACrB,kCAAkC;EAClC,mBAAmB;EACnB,wBAAwB;EACxB,+BAA+B;EAC/B,uBAAuB;EAC1B;AAGD,KAAI,gBAAgB,iBAAiB,gBAAgB,cAAc;AAC/D,UAAQ,KAAK,qBAAqB,eAAe,CAAC;AAIlD,UAAQ,KACJ,uBAAuB,EACnB,eAAe,eAAe,eACjC,CAAC,CACL;;AAIL,KAAI,kCAAkC,MAClC,SAAQ,KAAK,oCAAoC,8BAA8B,CAAC;AAGpF,KAAI,mBACA,SAAQ,KAAK,8BAA8B,CAAC;AAGhD,QAAO;;;;;ACzHX,MAAM,WAAY,eAAiE,WAAW;AAK9F,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;AA8CzB,SAAgB,sBAAsB,SAAgC,EAAE,EAAU;AAE9E,KAAI,QAAQ,IAAI,aAAa,aACzB,QAAO,EACH,MAAM,2CACT;AAML,KAAI,EAFY,OAAO,WAAW,QAAQ,IAAI,4BAA4B,QAGtE,QAAO,EACH,MAAM,+CACT;AAGL,QAAO,KAAK,+BAA+B;AAE3C,KAAI,OAAO,eACP,QAAO,KAAK,6BAA6B,OAAO,eAAe,eAAe;AAGlF,QAAO;EACH,MAAM;EAEN,SAAS;EAET,UAAU,MAAc,IAAY;AAEhC,OAAI,CAAC,GAAG,MAAM,eAAe,CACzB,QAAO;AAIX,OAAI,GAAG,SAAS,eAAe,CAC3B,QAAO;AAIX,OAAI,GAAG,SAAS,yBAAyB,CACrC,QAAO;AAMX,OAAI,CAAC,iBAAiB,KAAK,KAAK,IAAI,CAAC,KAAK,SAAS,6BAA6B,CAC5E,QAAO;AAGX,OAAI;IAEA,MAAM,MAAM,MAAM,MAAM;KACpB,YAAY;KACZ,SAAS;MAAC;MAAc;MAAO;MAAoB;KACtD,CAAC;IAEF,IAAI,gBAAgB;IACpB,MAAMC,YAAsB,EAAE;AAG9B,aAAS,KAAK,EACV,WAAW,QAAM;KACb,MAAM,iBAAiBC,OAAK,KAAK;AAGjC,SAAI,CAAC,gBAAgB,eAAe,KAAK,IAAI,eAAe,KAAK,SAAS,WACtE;KAIJ,MAAM,eAAe,eAAe,WAAW,MAC1C,SACG,eAAe,KAAK,IAAI,gBAAgB,KAAK,KAAK,IAAI,KAAK,KAAK,SAAS,WAChF;AAED,SAAI,CAAC,cAAc;AACf,aAAO,KAAK,gCAAgC,KAAK;AACjD;;KAIJ,MAAM,WAAW,gBAAgB,aAAa,MAAM,GAAG,aAAa,MAAM,QAAQ;AAElF,SAAI,CAAC,UAAU;AACX,aAAO,KAAK,wCAAwC,KAAK;AACzD;;AAIJ,SAAI,OAAO,kBAAkB,CAAC,SAAS,WAAW,GAAG,OAAO,eAAe,GAAG,CAC1E;KAIJ,MAAM,cAAcA,OAAK,KAAK,SAAS,SAAS;AAGhD,oBAAe,OAAO,cAAc,oBAAoB;AACxD,SAAIA,OAAK,KAAK,eACV,QAAK,KAAK,eAAe,OAAO,cAAc,oBAAoB;KAItE,MAAM,OAAO,OAAO,UAAU;AAC9B,oBAAe,WAAW,KACtB,aAAa,cAAc,WAAW,EAAE,cAAc,GAAG,CAAC,EAC1D,aACI,cAAc,kBAAkB,EAChC,uBAAuB,eAAe,YAAY,CAAC,CACtD,EACD,GAAI,OAAO,CAAC,aAAa,cAAc,WAAW,EAAE,cAAc,KAAK,CAAC,CAAC,GAAG,EAAE,CACjF;AAED,qBAAgB;AAChB,eAAU,KAAK,SAAS;OAE/B,CAAC;AAEF,QAAI,CAAC,cACD,QAAO;IAIX,MAAM,kBAAkB,kBACpB,CAAC,gBAAgB,WAAW,oBAAoB,EAAE,WAAW,oBAAoB,CAAC,CAAC,EACnF,cAAc,kCAAkC,CACnD;AAED,QAAI,QAAQ,KAAK,QAAQ,gBAAgB;IAGzC,MAAM,SAAS,SACX,KACA;KACI,aAAa;KACb,SAAS;KACZ,EACD,KACH;AAED,WAAO,MAAM,eAAe,UAAU,OAAO,cAAc,GAAG,MAAM,IAAI,CAAC,KAAK,GAAG;AAEjF,WAAO;KACH,MAAM,OAAO;KACb,KAAK,OAAO;KACf;YACI,OAAO;AACZ,WAAO,MAAM,mCAAmC,GAAG,IAAI,MAAM;AAC7D,WAAO;;;EAGlB;;;;;;;;;;;ACrJL,SAAgB,gBAAgB,UAA2B;AAEvD,KAAI,SAAS,WAAW,KAAK,CAAE,QAAO;AAGtC,KAAI,SAAS,WAAW,MAAM,CAAE,QAAO;AAGvC,KAAI,SAAS,WAAW,QAAQ,CAAE,QAAO;AAGzC,KAAI,SAAS,WAAW,iBAAiB,CAAE,QAAO;AAGlD,KAAI,SAAS,SAAS,QAAQ,CAAE,QAAO;AAGvC,KAAI,SAAS,WAAW,WAAW,CAAE,QAAO;AAK5C,KAAI,SAAS,WAAW,kBAAkB,CACtC,QAAO;AAKX,KAAI,6EAA6E,KAAK,SAAS,CAC3F,QAAO;AAGX,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BX,SAAgB,0BAA0B,QAAwB;CAC9D,IAAI,YAAY;AAGhB,aAAY,UAAU,QAAQ,kBAAkB,mBAAmB;AAGnE,KAAI,CAAC,WAAW,KAAK,OAAO,CAExB,aAAY,UAAU,QAAQ,YAAY,uBAAuB;AAGrE,QAAO,UAAU,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4B3B,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;AAuBlC,SAAS,aAAa,KAAqB;AACvC,QAAO,IAAI,QAAQ,uBAAuB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCrD,SAAgB,wBAAwB,EACpC,gBACA,YACA,cACA,eAMsB;CACtB,IAAIC;AACJ,KAAI;AACA,gBAAc,IAAI,IAAI,gBAAgB,aAAa;SAC/C;AACJ,SAAO,EAAE,MAAM,aAAa;;AAGhC,KAAI,YAAY,WAAW,aACvB,QAAO,EAAE,MAAM,cAAc;CAGjC,IAAIC;AACJ,KAAI;AACA,iBAAe,IAAI,IAAI,YAAY,YAAY,CAAC;SAC5C;AACJ,iBAAe,IAAI,iBAAiB;;CAOxC,MAAM,aAAa,IAAI,IAAI,YAAY,aAAa,MAAM,CAAC;AAC3D,MAAK,MAAM,CAAC,KAAK,UAAU,aACvB,KAAI,CAAC,WAAW,IAAI,IAAI,CACpB,aAAY,aAAa,OAAO,KAAK,MAAM;AAInD,QAAO;EACH,MAAM;EACN,KAAK,GAAG,cAAc,YAAY,WAAW,YAAY,SAAS,YAAY;EACjF;;;AAIL,SAAS,eAAe,OAAe,MAAM,KAAa;AACtD,QAAO,MAAM,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK;;;;;;;;;;;;;;;AAgB5D,SAAgB,kBAAkB,SAA2C;AACzE,KAAI,CAAC,QAAQ,SAAS;AAClB,SAAO,MAAM,2DAA2D;AACxE,SAAO,EACH,MAAM,gBACT;;AAGL,KAAI,CAAC,QAAQ,cAAc;AACvB,SAAO,KAAK,mEAAmE;AAC/E,SAAO,EACH,MAAM,gBACT;;AAGL,KAAI,CAAC,QAAQ,cACT,OAAM,IAAI,MACN,2NAIH;AAGL,QAAO,KAAK,0BAA0B,QAAQ,eAAe;AAC7D,QAAO,MAAM,+BAA+B,QAAQ,aAAa,MAAM,GAAG,IAAI,CAAC,KAAK;CACpF,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,gBAAgB,QAAQ;AAC9B,QAAO,MACH,4CAA4C,cAAc,eAAe,cAAc,GAAG,OAAO,OACpG;CAGD,MAAM,sBAAsB,IAAI,OAAO,aAAa,QAAQ,aAAa,EAAE,IAAI;CAM/E,MAAM,QAAQ,UAAU,kBAAkB;EACtC,cAAc;EACd,iBAAiB;EACjB,oBAAoB;EACvB,CAAC;AAGF,OAAM,GAAG,aAAa,UAAU,QAAQ;EACpC,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,UAAU,IAAI,QAAQ,OAAO;EAChE,MAAM,WAAW,IAAI;AAQrB,MAF4B,CAAC,SAAS,WAAW,MAAM,IAAI,CAAC,SAAS,WAAW,kBAAkB,EAEzE;GACrB,MAAM,eAAe,SAAS;;;;;;AAM9B,OAAI,aAAa,IACb,UAAS,OAAO,MAAM,gBAAgB,IAAI;OAG1C,UAAS,OAAO,MAAM,cAAc,GAAG,SAAS,WAAW,IAAI;AAEnE,UAAO,MAAM,8BAA8B,aAAa,KAAK,SAAS,OAAO;;GAEnF;AAKF,OAAM,GAAG,aAAa,UAA2B,KAAK,QAAQ;EAC1D,MAAM,YAAY;EAclB,MAAM,iBAAiB,SAAS,QAAQ;EACxC,MAAM,aAAa,SAAS,cAAc;AAM1C,MAJI,OAAO,mBAAmB,YAC1B,UAAU,KAAK,eAAe,KAC5B,cAAc,OAAO,aAAa,OAAQ,eAAe,MAExC;AACnB,UAAO,KACH,2CAA2C,IAAI,IAAI,sNAGiB,IAAI,KAAK,MAAM,IAAI,CAAC,GAAG,MAC9F;AACD,UAAO,SAAS,QAAQ;;EAM5B,MAAM,mBAAmB,SAAS,QAAQ;AAC1C,MAAI,oBAAoB,MAAM,QAAQ,iBAAiB,CACnD,UAAS,QAAQ,gBAAgB,iBAAiB,KAAK,WAAW;GAC9D,MAAM,YAAY,0BAA0B,OAAO;AACnD,UAAO,MAAM,gCAAgC,OAAO,MAAM,GAAG,GAAG,CAAC,QAAQ,UAAU,MAAM,GAAG,GAAG,CAAC,KAAK;AACrG,UAAO;IACT;AAkBN,MAAI,kBAAkB,OAAO,mBAAmB,UAAU;GACtD,MAAM,cAAc,UAAU,IAAI,QAAQ;GAC1C,MAAM,SAAS,wBAAwB;IACnC;IACA,YAAY,IAAI,OAAO;IACvB,cAAc,QAAQ;IACtB;IACH,CAAC;AACF,OAAI,OAAO,SAAS,aAAa;AAC7B,aAAS,QAAQ,WAAW,OAAO;AACnC,WAAO,MACH,kCAAkC,eAAe,eAAe,CAAC,KAAK,eAAe,OAAO,IAAI,GACnG;AAED,QAAI,eAAe,KAAK;AACpB,cAAS,QAAQ;KACjB,MAAMC,kBAAqD,EACvD,UAAU,OAAO,KACpB;KACD,MAAM,YAAY,SAAS,QAAQ;AACnC,SAAI,UACA,iBAAgB,gBAAgB;AAEpC,eAAU,UAAU,KAAK,gBAAgB;AACzC,eAAU,KAAK;AACf,YAAO,MACH,kDAAkD,IAAI,IAAI,cAAc,eAAe,OAAO,IAAI,CAAC,GACtG;AACD;;cAEG,OAAO,SAAS,YACvB,QAAO,KAAK,0CAA0C,eAAe,eAAe,GAAG;;EAO/F,MAAM,eAAe,SAAS,QAAQ,mBAAmB,IAAI,MAAM,IAAI,CAAC,GAAG,MAAM;AAGjF,MAAI,EAFiB,gBAAgB,eAAe,gBAAgB,qBAEjD;AAEf,aAAU,UAAU,SAAS,cAAc,KAAK,SAAS,QAAQ;AACjE,YAAS,KAAK,UAAU;AACxB;;EAIJ,MAAMC,SAAmB,EAAE;AAC3B,WAAS,GAAG,SAAS,UAAkB,OAAO,KAAK,MAAM,CAAC;AAC1D,WAAS,GAAG,aAAa;GACrB,IAAIC,OAAgC,OAAO,OAAO,OAAO;GAGzD,MAAM,WAAW,SAAS,QAAQ;AAClC,OAAI,aAAa,OACb,QAAO,WAAW,KAAK;YAChB,aAAa,KACpB,QAAO,qBAAqB,KAAK;YAC1B,aAAa,UACpB,QAAO,YAAY,KAAK;GAI5B,MAAM,cAAc,UAAU,IAAI,QAAQ;GAC1C,IAAI,OAAO,KAAK,SAAS,OAAO;AAEhC,uBAAoB,YAAY;AAChC,UAAO,KAAK,QAAQ,qBAAqB,YAAY;AAIrD,OAAI,gBAAgB,aAAa;IAC7B,MAAM,YAAY,KAAK,QAAQ,QAAQ;AACvC,QAAI,cAAc,IAAI;KAClB,MAAM,cAAc,KAAK,QAAQ,KAAK,UAAU;AAChD,SAAI,gBAAgB,GAChB,QAAO,KAAK,MAAM,GAAG,cAAc,EAAE,GAAG,4BAA4B,KAAK,MAAM,cAAc,EAAE;;;GAM3G,MAAM,UAAU,EAAE,GAAG,SAAS,SAAS;AACvC,UAAO,QAAQ;AACf,UAAO,QAAQ;AACf,WAAQ,oBAAoB,OAAO,OAAO,WAAW,MAAM,OAAO,CAAC;AAEnE,aAAU,UAAU,SAAS,cAAc,KAAK,QAAQ;AACxD,aAAU,IAAI,KAAK;AAEnB,UAAO,MAAM,wBAAwB,YAAY,iBAAiB,IAAI,MAAM;IAC9E;GACJ;AAGF,OAAM,GAAG,UAAU,KAAK,KAAK,QAAQ;AACjC,SAAO,MAAM,uBAAuB,IAAI,QAAQ,GAAG,IAAI,MAAM;AAC7D,MAAI,eAAe,OAAO,CAAC,IAAI,aAAa;AACxC,OAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,OAAI,IAAI,uCAAuC;;GAErD;AAEF,QAAO;EACH,MAAM;EACN,SAAS;EAET,gBAAgB,QAAuB;AACnC,UAAO,YAAY,KAAK,KAAK,KAAK,SAAS;IACvC,MAAM,WAAW,IAAI,KAAK,MAAM,IAAI,CAAC,MAAM;AAG3C,QAAI,gBAAgB,SAAS,CACzB,QAAO,MAAM;IAKjB,MAAM,aAAa,SAAS,WAAW,kBAAkB;IAGzD,IAAI,uBAAuB;AAC3B,QAAI,CAAC,YAAY;AACb,SAAI;AACA,6BAAuB,QAAQ,aAAa,UAAU,QAAQ,aAAa;cACtE,OAAO;AAEZ,aAAO,MAAM,8CAA8C,OAAO,MAAM,GAAG;AAC3E,aAAO,MAAM;;AAGjB,SAAI,qBAEA,QAAO,MAAM;;AAKrB,WAAO,MAAM,iBAAiB,IAAI,OAAO,GAAG,SAAS,KAAK,QAAQ,eAAe;AAEjF,QAAI;AACA,WAAM,IAAI,KAAK,KAAK,EAChB,QAAQ,QAAQ,cACnB,CAAC;aACG,OAAO;AACZ,YAAO,MAAM,yCAAyC,OAAO,MAAM,GAAG;AACtE,SAAI,CAAC,IAAI,aAAa;AAClB,UAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,UAAI,IAAI,uCAAuC;;;KAGzD;;EAET;;;;;;;;;;;;;;;;;;;;;;;;ACtjBL,MAAM,6BAAa,IAAI,KAAqB;;;;;;;;;;;;;;;;;;;;;AAsB5C,SAAgB,gBAAgB,YAA8B;AAC1D,KAAI,CAAC,cAAc,OAAO,eAAe,SACrC,QAAO,EAAE;CAKb,MAAM,QAAQ;CACd,MAAMC,WAAqB,EAAE;CAC7B,IAAIC;AAEJ,SAAQ,QAAQ,MAAM,KAAK,WAAW,MAAM,KACxC,UAAS,KAAK,MAAM,GAAG;AAG3B,QAAO;;;;;;;;;;;;;;;;;;;AAoBX,SAAgB,aAAa,UAAkB,UAA6B;AACxE,KAAI,CAAC,YAAY,CAAC,YAAY,SAAS,WAAW,EAC9C,QAAO;AAIX,MAAK,MAAM,WAAW,SAClB,KAAI;EAEA,IAAI,QAAQ,WAAW,IAAI,QAAQ;AACnC,MAAI,CAAC,OAAO;AACR,WAAQ,IAAI,OAAO,QAAQ;AAC3B,cAAW,IAAI,SAAS,MAAM;;AAGlC,MAAI,MAAM,KAAK,SAAS,CACpB,QAAO;UAEN,OAAO;AAEZ,SAAO,KAAK,0BAA0B,QAAQ,GAAG,OAAO,MAAM,GAAG;AACjE;;AAIR,QAAO;;;;;;;;;;;;;;;;;;;;AAqBX,SAAgB,kBAAkB,UAAkB,cAAgC;AAChF,KAAI,CAAC,aAED,QAAO;CAGX,MAAM,WAAW,gBAAgB,aAAa;AAE9C,KAAI,SAAS,WAAW,GAAG;AAEvB,SAAO,KAAK,2CAA2C;AACvD,SAAO;;AAGX,QAAO,aAAa,UAAU,SAAS"}
1
+ {"version":3,"file":"index.js","names":["LEVEL_PRIORITY: Record<LogLevel, number>","overrideLevel: LogLevel | undefined","resolvedConfig: ResolvedConfig","path","__dirname","path","resolvedConfig: ResolvedConfig","buildDirectory: string","traverse","componentRegistry: TargetComponentRegistry","contextProviders: TargetContextProviderConfig[]","actionHookRegistry: ActionHookRegistry","path","orphaned: OrphanedTarget[]","componentRegistry: TargetComponentRegistry","contextProviders: TargetContextProviderConfig[]","sourceDir: string","path","err: unknown","imports: string[]","registryEntries: string[]","handlerNames: string[]","actionHookRegistry: ActionHookRegistry","sourceDir: string","path","viteConfig: ResolvedConfig","glob","path","baseComponentId: string","components: ComponentInfo[]","resolve","existingContent: string","projectRoot: string","alias: Record<string, string>","existsSync","readFileSync","sortedAlias: Record<string, string>","resolve","resolve","resolvedConfig: ResolvedConfig","missingInstrumentation: Array<{ adapter: string; event: string }>","resolvedConfig: ResolvedConfig","buildDirectory: string","appDirectory: string","resolve","fs","serverEntryFilePath: string | undefined","clientEntryFilePath: string | undefined","appDirectory: string | undefined","userServerEntryPath: string | undefined","userClientEntryPath: string | undefined","ctx: ReactRouterPluginContext | undefined","relative","path","traverse","_traverse","generate","_generate","path","environmentName: string | undefined","ssrEnv: DevEnvironment | undefined","source: string | null","plugins: Plugin[]","targetIds: string[]","path","locationUrl: URL","requestQuery: URLSearchParams","redirectHeaders: Record<string, string | string[]>","chunks: Buffer[]","body: Buffer<ArrayBufferLike>","patterns: string[]","match: RegExpExecArray | null"],"sources":["../src/utils/logger.ts","../src/plugins/fixReactRouterManifestUrls.ts","../src/utils/paths.ts","../src/plugins/readableChunkFileNames.ts","../src/mrt/utils.ts","../src/plugins/managedRuntimeBundle.ts","../src/plugins/patchReactRouter.ts","../src/extensibility/target-utils.ts","../src/plugins/transformTargets.ts","../src/plugins/actionHooks.ts","../src/plugins/watchConfigFiles.ts","../src/plugins/staticRegistry.ts","../src/server/ts-import.ts","../src/plugins/configLoader.ts","../src/plugins/eventInstrumentationValidator.ts","../src/plugins/buildMiddlewareRegistry.ts","../src/plugins/platformEntry.ts","../src/plugins/workspace.ts","../src/plugins/componentLoaders.ts","../src/plugins/ssrSourcemapFix.ts","../src/plugins/i18n.ts","../src/plugins/baseConfig.ts","../src/storefront-next-targets.ts","../src/plugins/uiTargetDevMode.ts","../src/plugins/hybridProxy.ts","../src/plugins/ecdnMatcher.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/* eslint-disable no-console */\nimport os from 'os';\nimport chalk from 'chalk';\nimport { createRequire } from 'module';\nimport type { ServerMode } from '../server/modes';\nimport pkg from '../../package.json' with { type: 'json' };\n\n/**\n * Get the local network IPv4 address\n */\nexport function getNetworkAddress(): string | undefined {\n const interfaces = os.networkInterfaces();\n for (const name of Object.keys(interfaces)) {\n const iface = interfaces[name];\n if (!iface) continue;\n for (const alias of iface) {\n if (alias.family === 'IPv4' && !alias.internal) {\n return alias.address;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Get the version of a package from the project's package.json\n */\nexport function getPackageVersion(packageName: string, projectDir: string): string {\n try {\n const require = createRequire(import.meta.url);\n const pkgPath = require.resolve(`${packageName}/package.json`, { paths: [projectDir] });\n const pkgJson = require(pkgPath) as { version: string };\n return pkgJson.version;\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Centralized, level-gated logger for the SDK.\n *\n * Log level is controlled by `SFCC_LOG_LEVEL` env var (`error` | `warn` | `info` | `debug`).\n * Falls back to: `DEBUG` targeting sfnext -> `debug`, `NODE_ENV=production` -> `warn`, otherwise `info`.\n */\n\nexport type LogLevel = 'error' | 'warn' | 'info' | 'debug';\n\nconst LEVEL_PRIORITY: Record<LogLevel, number> = {\n error: 0,\n warn: 1,\n info: 2,\n debug: 3,\n};\n\nlet overrideLevel: LogLevel | undefined;\n\n/**\n * Returns true when the `DEBUG` env var targets sfnext or is a general enable flag.\n * Avoids accidentally enabling debug mode when DEBUG is set for unrelated libraries\n * (e.g. `DEBUG=express:*`).\n */\nfunction debugEnablesSfnext(): boolean {\n const raw = process.env.DEBUG?.trim();\n if (!raw) return false;\n const normalized = raw.toLowerCase();\n if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;\n return raw.split(',').some((token) => {\n const value = token.trim();\n return value === '*' || value === 'sfnext' || value === 'sfnext:*';\n });\n}\n\nfunction resolveLevel(): LogLevel {\n if (overrideLevel) return overrideLevel;\n const envLevel = process.env.MRT_LOG_LEVEL ?? process.env.SFCC_LOG_LEVEL;\n if (envLevel && envLevel in LEVEL_PRIORITY) return envLevel as LogLevel;\n if (debugEnablesSfnext()) return 'debug';\n if (process.env.NODE_ENV === 'production') return 'warn';\n return 'info';\n}\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVEL_PRIORITY[level] <= LEVEL_PRIORITY[resolveLevel()];\n}\n\nexport const logger = {\n error(msg: string, ...args: unknown[]): void {\n if (!shouldLog('error')) return;\n console.error(chalk.red('[sfnext:error]'), msg, ...args);\n },\n warn(msg: string, ...args: unknown[]): void {\n if (!shouldLog('warn')) return;\n console.warn(chalk.yellow('[sfnext:warn]'), msg, ...args);\n },\n info(msg: string, ...args: unknown[]): void {\n if (!shouldLog('info')) return;\n console.log(chalk.cyan('[sfnext:info]'), msg, ...args);\n },\n debug(msg: string, ...args: unknown[]): void {\n if (!shouldLog('debug')) return;\n console.log(chalk.gray('[sfnext:debug]'), msg, ...args);\n },\n setLevel(level: LogLevel | undefined): void {\n overrideLevel = level;\n },\n getLevel(): LogLevel {\n return resolveLevel();\n },\n};\n\n/**\n * Print the server information banner with URLs and versions\n */\nexport function printServerInfo(mode: ServerMode, port: number, startTime: number, projectDir: string): void {\n const elapsed = Date.now() - startTime;\n const sfnextVersion = pkg.version;\n const reactVersion = getPackageVersion('react', projectDir);\n const reactRouterVersion = getPackageVersion('react-router', projectDir);\n const viteVersion = getPackageVersion('vite', projectDir);\n\n const modeLabel = mode === 'development' ? 'Development Mode' : 'Preview Mode';\n\n console.log();\n console.log(` ${chalk.cyan.bold('⚡ SFCC Storefront Next')} ${chalk.dim(`v${sfnextVersion}`)}`);\n console.log(` ${chalk.green.bold(modeLabel)}`);\n console.log();\n const logLevel = resolveLevel();\n const logLevelColors: Record<LogLevel, (s: string) => string> = {\n error: chalk.red,\n warn: chalk.yellow,\n info: chalk.cyan,\n debug: chalk.gray,\n };\n\n console.log(\n ` ${chalk.dim('react')} ${chalk.green(`v${reactVersion}`)} ${chalk.dim('|')} ` +\n `${chalk.dim('react-router')} ${chalk.green(`v${reactRouterVersion}`)} ${chalk.dim('|')} ` +\n `${chalk.dim('vite')} ${chalk.green(`v${viteVersion}`)}`\n );\n console.log(\n ` ${chalk.dim('log level')} ${logLevelColors[logLevel](logLevel)} ${chalk.dim('|')} ` +\n `${chalk.green(`ready in ${elapsed}ms`)}`\n );\n console.log();\n}\n\n/**\n * Print server configuration details (proxy, static, etc.)\n */\nexport function printServerConfig(config: {\n mode: ServerMode;\n port: number;\n enableProxy?: boolean;\n enableStaticServing?: boolean;\n enableCompression?: boolean;\n proxyPath?: string;\n proxyHost?: string;\n shortCode?: string;\n organizationId?: string;\n clientId?: string;\n}): void {\n const {\n port,\n enableProxy,\n enableStaticServing,\n enableCompression,\n proxyPath,\n proxyHost,\n shortCode,\n organizationId,\n clientId,\n } = config;\n\n console.log(` ${chalk.bold('Environment Configuration:')}`);\n\n if (enableProxy && proxyPath && proxyHost && shortCode) {\n console.log(\n ` ${chalk.green('✓')} ${chalk.bold('Proxy:')} ${chalk.cyan(`localhost:${port}${proxyPath}`)} ${chalk.dim('→')} ${chalk.cyan(proxyHost)}`\n );\n console.log(` ${chalk.dim('Short Code: ')}${chalk.dim(shortCode)}`);\n if (organizationId) {\n console.log(` ${chalk.dim('Organization ID: ')}${chalk.dim(organizationId)}`);\n }\n if (clientId) {\n console.log(` ${chalk.dim('Client ID: ')}${chalk.dim(clientId)}`);\n }\n } else {\n console.log(` ${chalk.bold('Proxy: ')} ${chalk.dim('disabled')}`);\n }\n\n if (enableStaticServing) {\n console.log(` ${chalk.bold('Static: ')} ${chalk.dim('enabled')}`);\n }\n\n if (enableCompression) {\n console.log(` ${chalk.bold('Compression: ')} ${chalk.dim('enabled')}`);\n }\n\n // URLs\n const localUrl = `http://localhost:${port}`;\n const showNetwork = process.env.SHOW_NETWORK === 'true';\n const networkAddress = showNetwork ? getNetworkAddress() : undefined;\n const networkUrl = networkAddress ? `http://${networkAddress}:${port}` : undefined;\n\n console.log();\n console.log(` ${chalk.bold('Local: ')} ${chalk.cyan(localUrl)}`);\n if (networkUrl) {\n console.log(` ${chalk.bold('Network:')} ${chalk.cyan(networkUrl)}`);\n }\n\n console.log();\n console.log(` ${chalk.dim('Press')} ${chalk.bold('Ctrl+C')} ${chalk.dim('to stop the server')}`);\n console.log();\n}\n\n/**\n * Print shutdown message\n */\nexport function printShutdownMessage(): void {\n console.log(`\\n ${chalk.yellow('⚡')} ${chalk.dim('Server shutting down...')}\\n`);\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport path from 'node:path';\nimport fs from 'fs-extra';\nimport { logger } from '../logger';\n\nfunction patchAssetsPaths(dir: string) {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n patchAssetsPaths(fullPath);\n } else if (entry.isFile() && entry.name.endsWith('.js')) {\n const content = fs.readFileSync(fullPath, 'utf-8');\n if (content.includes('\"/assets/') || content.includes(\"'/assets/\")) {\n // Transform asset URLs that start with /assets/ to use dynamic bundle path\n fs.writeFileSync(\n fullPath,\n content.replace(/[\"']\\/assets\\//g, '(window._BUNDLE_PATH || \"/\") + \"assets/')\n );\n logger.debug(`patched /assets/ references in ${fullPath}`);\n }\n }\n }\n}\n\n/**\n * Plugin to transform React Router client manifest URLs to use dynamic bundle paths\n */\nexport function fixReactRouterManifestUrlsPlugin(): Plugin {\n let resolvedConfig: ResolvedConfig;\n\n return {\n name: 'storefront-next:fix-react-router-manifest-urls',\n enforce: 'post', // Run after React Router plugin\n\n configResolved(config: ResolvedConfig) {\n resolvedConfig = config;\n },\n\n // Post-process client manifest files after they're written to disk\n closeBundle() {\n const clientBuildDir = resolvedConfig.environments.client.build.outDir;\n if (fs.existsSync(clientBuildDir)) {\n // Patch references to `/assets/` in files within React Router's client build directory\n patchAssetsPaths(clientBuildDir);\n }\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Normalize a file path to use forward slashes.\n * On Windows, Node APIs return backslash-separated paths, but ESM import\n * specifiers and Vite module IDs require forward slashes.\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replace(/\\\\/g, '/');\n}\n\n/**\n * Get the Commerce Cloud API URL from a short code\n */\nexport function getCommerceCloudApiUrl(shortCode: string, proxyHost?: string): string {\n return proxyHost || `https://${shortCode}.api.commercecloud.salesforce.com`;\n}\n\n/**\n * Get the configurable base path for the application.\n * Reads from MRT_ENV_BASE_PATH environment variable.\n *\n * The base path is used for CDN routing to the correct MRT environment.\n * It is prepended to all URLs: page routes, /mobify/bundle/ assets, and /mobify/proxy/api.\n *\n * Validation rules:\n * - Must be a single path segment starting with '/'\n * - Max 63 characters after the leading slash\n * - Only URL-safe characters allowed\n * - Returns empty string if not set\n *\n * @returns The sanitized base path (e.g., '/site-a' or '')\n *\n * @example\n * // No base path configured\n * getBasePath() // → ''\n *\n * // With base path '/storefront'\n * getBasePath() // → '/storefront'\n *\n * // Automatically sanitizes\n * // MRT_ENV_BASE_PATH='storefront/' → '/storefront'\n */\nexport function getBasePath(): string {\n const basePath = process.env.MRT_ENV_BASE_PATH?.trim();\n\n // Return empty string if not set or empty\n if (!basePath) {\n return '';\n }\n\n // Base path prefix must be a single path segment starting with '/', max 63 chars,\n // using only URL-safe characters (alphanumeric, hyphens, underscores, dots, and other safe symbols)\n // This aligns with the regex used by MRT\n if (!/^\\/[a-zA-Z0-9_.+$~\"'@:-]{1,63}$/.test(basePath)) {\n throw new Error(\n `Invalid base path: \"${basePath}\". ` +\n \"Base path must be a single segment starting with '/' (e.g., '/site-a'), \" +\n 'contain only URL-safe characters, and be at most 63 characters after the leading slash.'\n );\n }\n\n return basePath;\n}\n\n/**\n * Get the bundle path for static assets\n */\nexport function getBundlePath(bundleId: string): string {\n const basePath = getBasePath();\n return `${basePath}/mobify/bundle/${bundleId}/client/`;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin, Rollup } from 'vite';\nimport path from 'path';\nimport { toPosixPath } from '../utils/paths';\n\n/**\n * Generates human-readable chunk file names for better debugging in production builds.\n *\n * Transforms Rollup's default hash-based chunk names into structured paths that reflect\n * the original source location, making it easier to identify and debug specific chunks.\n *\n * @param chunkInfo - Rollup's pre-rendered chunk information containing module IDs and metadata\n * @returns A formatted string pattern for the chunk filename with one of these formats:\n * - `assets/(folder1)-(folder2)-filename.[hash].js` for source files in /src/\n * - `assets/(package)-(pkg-name)-(subfolder)-filename.[hash].js` for node_modules\n * - `assets/(chunk)-[name].[hash].js` as fallback for chunks without identifiable paths\n *\n * @example\n * // Source file: /src/components/ui/Button.tsx\n * // Output: assets/(components)-(ui)-Button.[hash].js\n *\n * @example\n * // Node module: /node_modules/@radix-ui/react-dialog/dist/index.js\n * // Output: assets/(package)-(@radix-ui)-(react-dialog)-(dist)-index.[hash].js\n */\nexport const readableChunkFileNames = (chunkInfo: Rollup.PreRenderedChunk) => {\n const moduleIds = chunkInfo.moduleIds;\n const defaultName = 'assets/(chunk)-[name].[hash].js';\n if (!moduleIds || moduleIds.length === 0) {\n return defaultName;\n }\n\n // Rollup moduleIds are in reverse order, the last moduleId is the one that was first in the source code\n const lastModuleId = moduleIds[moduleIds.length - 1];\n\n const getFileName = (pathname: string) => {\n const posixPath = toPosixPath(pathname);\n const parsed = path.posix.parse(posixPath);\n const withoutQuery = parsed.base.split('?')[0];\n return withoutQuery.replace(/\\.(tsx?|jsx?|mjs|js)$/, '');\n };\n\n const cleanPath = (pathname: string) => {\n return pathname?.split('?')[0];\n };\n\n const normalizedModuleId = toPosixPath(lastModuleId);\n\n // Check if the module is from the application source code (under /src/ directory)\n // This generates chunk names based on the folder structure within src/\n // Example: src/components/ui/Button.tsx → assets/(components)-(ui)-Button.[hash].js\n if (normalizedModuleId.includes('/src/')) {\n const cleanedPath = toPosixPath(cleanPath(lastModuleId));\n const match = cleanedPath.match(/\\/src\\/(.+)$/);\n if (match) {\n const pathAfterSrc = match[1];\n const parts = pathAfterSrc.split('/');\n\n const fileName = getFileName(parts[parts.length - 1]);\n\n const folders = parts.slice(0, -1);\n\n const segments = folders.map((f) => `(${f})`).join('-');\n return `assets/${segments}-${fileName}.[hash].js`;\n }\n }\n\n // Check if the module is from an external package (under /node_modules/ directory)\n // This generates chunk names that include the package name and internal path\n // Example: node_modules/@radix-ui/react-dialog/dist/index.js → assets/(package)-(@radix-ui)-(react-dialog)-(dist)-index.[hash].js\n if (normalizedModuleId.includes('/node_modules/')) {\n const cleanedPath = toPosixPath(cleanPath(lastModuleId));\n\n const parts = cleanedPath.split('/node_modules/');\n const afterNodeModules = parts[parts.length - 1];\n\n const pathParts = afterNodeModules.split('/');\n\n // Handle scoped packages (@org/package)\n let packageName;\n let remainingPath;\n\n if (pathParts[0].startsWith('@')) {\n // Scoped package: @org/package/rest/of/path\n packageName = `${pathParts[0]}-${pathParts[1]}`;\n remainingPath = pathParts.slice(2);\n } else {\n // Regular package: package/rest/of/path\n packageName = pathParts[0];\n remainingPath = pathParts.slice(1);\n }\n\n const fileName = getFileName(remainingPath[remainingPath.length - 1]);\n\n const folders = remainingPath.slice(0, -1);\n\n const segments = ['package', packageName, ...folders].map((s) => `(${s})`).join('-');\n\n return `assets/${segments}-${fileName}.[hash].js`;\n }\n\n return defaultName;\n};\n\n/**\n * Vite plugin that configures Rollup to use human-readable chunk file names in production builds.\n *\n * Applies the `readableChunkFileNames` naming strategy to both code-split chunks and entry files,\n * making it easier to identify the source of specific chunks when debugging production builds.\n *\n * @returns A Vite plugin that configures chunk naming for the client build environment\n *\n * @example\n * // In vite.config.ts\n * export default defineConfig({\n * plugins: [readableChunkFileNamesPlugin()]\n * })\n */\nexport const readableChunkFileNamesPlugin = (): Plugin => {\n return {\n name: 'storefront-next:readable-chunk-file-names',\n apply: 'build',\n config() {\n return {\n environments: {\n client: {\n build: {\n rollupOptions: {\n output: {\n chunkFileNames: readableChunkFileNames,\n entryFileNames: readableChunkFileNames,\n },\n },\n },\n },\n },\n };\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { APIGatewayProxyEvent } from 'aws-lambda';\n\nconst MRT_BUNDLE_TYPE_SSR = 'ssr' as const;\nconst MRT_STREAMING_ENTRY_FILE = 'streamingHandler' as const;\nexport type MrtBundleType = typeof MRT_BUNDLE_TYPE_SSR | typeof MRT_STREAMING_ENTRY_FILE;\n/**\n * Gets the MRT entry file for the given mode\n * @param mode - The mode to get the MRT entry file for\n * @returns The MRT entry file for the given mode\n */\nexport const getMrtEntryFile = (mode: string): MrtBundleType => {\n // TODO: Move the MRT_BUNDLE_TYPE env var to a command line option with sfnext\n // Streaming is enabled by default in production unless explicitly set to 'ssr'\n const enableStreaming = process.env.MRT_BUNDLE_TYPE !== MRT_BUNDLE_TYPE_SSR && mode === 'production';\n return enableStreaming ? MRT_STREAMING_ENTRY_FILE : MRT_BUNDLE_TYPE_SSR;\n};\n\n/**\n * Merges headers from event.headers into event.multiValueHeaders.\n *\n * @codegenie/serverless-express prefers multiValueHeaders over headers when both exist.\n * However, some headers (like x-correlation-id added by CloudFront/MRT) may only exist\n * in event.headers and not in event.multiValueHeaders, causing them to be lost.\n *\n * This function ensures all headers from event.headers are present in multiValueHeaders.\n */\nexport function mergeHeadersIntoMultiValueHeaders(event: APIGatewayProxyEvent): APIGatewayProxyEvent {\n if (!event.headers || !event.multiValueHeaders) {\n return event;\n }\n\n const mergedMultiValueHeaders = { ...event.multiValueHeaders };\n\n for (const [key, value] of Object.entries(event.headers)) {\n // Only add if not already in multiValueHeaders (case-insensitive check)\n const existingKey = Object.keys(mergedMultiValueHeaders).find((k) => k.toLowerCase() === key.toLowerCase());\n\n if (!existingKey && value !== undefined) {\n mergedMultiValueHeaders[key] = [value];\n }\n }\n\n return {\n ...event,\n multiValueHeaders: mergedMultiValueHeaders,\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport path from 'path';\nimport fs from 'fs-extra';\nimport { fileURLToPath } from 'url';\nimport { getMrtEntryFile } from '../mrt/utils';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n/**\n * This is a Vite plugin specifically for building the Managed Runtime production bundle.\n * This plugin relies on the @react-router/dev/vite plugin to work.\n * This plugin creates the Managed Runtime production bundle from the build output of the @react-router/dev/vite plugin.\n *\n * @returns {Plugin} A Vite plugin for building the Managed Runtime production react-router bundle\n */\nexport const managedRuntimeBundlePlugin = (): Plugin => {\n let resolvedConfig: ResolvedConfig;\n\n // Note: The react-router vite plugin does not use/respect vite's config.build.outDir\n // We must not use the resolvedConfig.build.outDir\n // Instead, react-router has a \"buildDirectory\" option from react-router.config.ts\n // Should we infer the build directory from the react-router config OR let the user configure it?\n let buildDirectory: string;\n\n /**\n * Creates the Managed Runtime production bundle assets\n * - ssr.mjs or streamingHandler.mjs\n * - loader.js\n * - package.json\n *\n * @returns {Promise<void>}\n */\n const createManagedRuntimeBundleAssets = async (): Promise<void> => {\n const loaderPath = path.resolve(buildDirectory, 'loader.js');\n // TODO: Move the MRT_BUNDLE_TYPE env var to a command line option with sfnext\n const mrtEntryFile = `${getMrtEntryFile(resolvedConfig?.mode)}.mjs`;\n const mrtEntryPath = path.resolve(buildDirectory, mrtEntryFile);\n\n await fs.ensureDir(buildDirectory);\n await fs.outputFile(loaderPath, '// This file is intentionally empty');\n\n const prebuiltMrtEntryPath = path.resolve(__dirname, `./mrt/${mrtEntryFile}`);\n await fs.copy(prebuiltMrtEntryPath, mrtEntryPath);\n\n // Copy source map for MRT entry point (if available)\n const prebuiltMrtEntryMapPath = `${prebuiltMrtEntryPath}.map`;\n if (await fs.pathExists(prebuiltMrtEntryMapPath)) {\n await fs.copy(prebuiltMrtEntryMapPath, `${mrtEntryPath}.map`);\n }\n\n // Copy shared chunks generated by the build process\n // These are identified by the 'sfnext-server-' prefix\n const mrtDir = path.resolve(__dirname, './mrt');\n if (await fs.pathExists(mrtDir)) {\n const files = await fs.readdir(mrtDir);\n for (const file of files) {\n if (file.startsWith('sfnext-server-') && file.endsWith('.mjs')) {\n await fs.copy(path.join(mrtDir, file), path.resolve(buildDirectory, file));\n // Copy source map for shared chunk (if available)\n const mapFile = `${file}.map`;\n if (files.includes(mapFile)) {\n await fs.copy(path.join(mrtDir, mapFile), path.resolve(buildDirectory, mapFile));\n }\n }\n }\n }\n\n const packageJsonPath = path.resolve(resolvedConfig.root, 'package.json');\n const buildPackageJsonPath = path.resolve(buildDirectory, 'package.json');\n\n const packageJson = await fs.readJson(packageJsonPath);\n\n // Currently MRT only supports CJS modules, and we are building a CJS bundle.\n // But we need to make sure the package.json doesn't have the \"type\" key to use ESM\n // otherwise the MRT environment will break because the node ESM resolution will not work\n // for our CJS bundle.\n delete packageJson.type;\n await fs.writeJson(buildPackageJsonPath, packageJson, { spaces: 2 });\n };\n\n return {\n name: 'storefront-next:managed-runtime-bundle',\n apply: 'build',\n config({ mode }) {\n return {\n build: {\n rollupOptions: {\n // Suppress benign sourcemap warnings emitted by Rollup during\n // the transform phase. These occur when Rollup tries to trace a\n // warning's location back through the sourcemap chain but the\n // intermediate sourcemaps (from TypeScript, Tailwind, etc.)\n // don't map position (1:0). The warnings are purely cosmetic —\n // actual sourcemaps in the build output are unaffected.\n //\n // This is the same approach used by @vitejs/plugin-react (PR #369)\n // and @react-router/dev's plugin.\n // See: https://github.com/vitejs/vite-plugin-react/pull/369\n // See: https://github.com/vitejs/vite/issues/15012\n onLog(level, log, defaultHandler) {\n if (log.code === 'SOURCEMAP_ERROR' && log.message.includes('resolve original location')) {\n return;\n }\n defaultHandler(level, log);\n },\n },\n },\n environments: {\n ssr: {\n resolve: {\n noExternal: true,\n },\n },\n },\n experimental: {\n // MRT serves each deploy's bundle under a per-deploy prefix:\n // {MRT_ENV_BASE_PATH}/mobify/bundle/{BUNDLE_ID}/client/...\n // Asset references must resolve under that prefix, and BUNDLE_ID\n // changes on every deploy, so we can't bake a static URL at build\n // time. The strategy differs between CSS and JS:\n //\n // - CSS url() resolves against the stylesheet's own URL per the CSS\n // spec. The emitted stylesheet already lives under the bundle\n // prefix, so a relative URL like `url(../fonts/x.woff2)` inherits\n // the correct BUNDLE_ID at request time — no runtime code needed.\n // (Vite also rejects runtime expressions inside CSS url(), so\n // relative is the only option for CSS regardless.)\n //\n // - JS strings have no equivalent \"relative to my module\" semantic\n // when handed to the DOM. A bare string in `href=` / `src=` would\n // resolve against document.baseURI — the current page URL — which\n // does NOT contain the bundle prefix, 404-ing the asset. And we\n // can't use `new URL(x, import.meta.url)` either: the SSR bundle\n // runs as CJS on MRT (see packageJson.type deletion below), where\n // import.meta.url is undefined. So JS has to produce a fully\n // qualified bundle-prefixed URL itself, by reading the prefix at\n // runtime from window._BUNDLE_PATH (client) or MRT_ENV_BASE_PATH +\n // BUNDLE_ID env vars (server SSR).\n renderBuiltUrl(filename, { type, hostType }) {\n if (mode !== 'preview' && (type === 'asset' || type === 'public')) {\n if (hostType === 'css') {\n return { relative: true };\n }\n\n // WARNING: This runtime code is embedded in the bundle for EVERY asset/public file.\n // At large scale (hundreds of assets), this code snippet is duplicated hundreds of times.\n // Keep this code as minimal as possible to avoid significant bundle size bloat.\n const runtimeCode = `(typeof window !== 'undefined' ? window._BUNDLE_PATH : ((process.env.MRT_ENV_BASE_PATH??'')+'/mobify/bundle/'+(process.env.BUNDLE_ID??'local')+'/client/')) + ${JSON.stringify(filename)}`;\n\n return {\n runtime: runtimeCode,\n };\n }\n },\n },\n };\n },\n configResolved(config) {\n resolvedConfig = config;\n\n // @ts-expect-error: react-router plugin context is not typed\n buildDirectory = config.__reactRouterPluginContext.reactRouterConfig.buildDirectory;\n },\n buildApp: {\n order: 'post',\n handler: async () => {\n await createManagedRuntimeBundleAssets();\n },\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\n\nconst VIRTUAL_MODULE_ID = '\\0patched-react-router';\nconst MODULE_TO_PATCH = 'react-router';\n\n/**\n * This plugin intercepts imports of 'react-router' and provides patched versions\n * of specific components (like Scripts) with custom logic.\n *\n * @returns {Plugin} A Vite plugin for patching react-router components\n */\nexport const patchReactRouterPlugin = (): Plugin => {\n let isTestMode = false;\n let isDevMode = false;\n\n return {\n name: 'storefront-next:patch-react-router',\n // must be enforce: 'pre'\n // otherwise the react-router plugin will resolve the module first\n // and we will not be able to enhance the module with our custom logic\n enforce: 'pre',\n config(_config, { mode }) {\n // Detect test mode to disable patching\n // Virtual module IDs with \\0 prefix cause path resolution errors on Windows\n // when Vitest tries to resolve them with vi.importActual\n isTestMode = mode === 'test';\n // Detect dev mode to avoid duplicate React Router instances\n isDevMode = mode === 'development';\n },\n configEnvironment(name) {\n if (isTestMode) {\n return;\n }\n // Skip noExternal in dev mode to avoid duplicate React Router instances\n // This is acceptable because bundle config injection is only needed for\n // MRT production deployments, not local development\n if (isDevMode) {\n return;\n }\n if (name === 'ssr') {\n // By default, on dev mode, Vite does not process external modules like react-router\n // but we need to patch it, so we mark react-router as noExternal\n // so that it is included in the Vite plugin pipeline, and we can patch it\n return {\n resolve: {\n noExternal: ['react-router'],\n },\n };\n }\n },\n resolveId(id, importer) {\n // Skip patching in test mode to avoid Windows path resolution errors\n // Skip patching in dev mode to avoid duplicate React Router instances\n if (isTestMode || isDevMode) {\n return null;\n }\n if (id === MODULE_TO_PATCH) {\n // In the virtual module, we need to import the same react-router module\n // and then re-export everything from it, and override a subset of the exports.\n // This following code is to make sure that the import from the virtual module\n // imports the same react-router module and not causing infinite loop.\n if (importer === VIRTUAL_MODULE_ID || importer?.includes('storefront-next-dev')) {\n return null;\n }\n return VIRTUAL_MODULE_ID;\n }\n return null;\n },\n\n load(id) {\n // Skip patching in test mode\n // Skip patching in dev mode to avoid duplicate React Router instances\n if (isTestMode || isDevMode) {\n return null;\n }\n if (id === VIRTUAL_MODULE_ID) {\n const scriptsImportPath = '@salesforce/storefront-next-dev/react-router/Scripts';\n\n const code = `\n export * from 'react-router';\n export { Scripts } from '${scriptsImportPath}';\n `;\n return code;\n }\n return null;\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { parse } from '@babel/parser';\nimport {\n isJSXIdentifier,\n isJSXAttribute,\n jsxText,\n isJSXElement,\n isJSXFragment,\n jsxElement,\n jsxOpeningElement,\n jsxIdentifier,\n jsxClosingElement,\n jsxFragment,\n jsxOpeningFragment,\n jsxClosingFragment,\n type JSXElement as BabelJSXElement,\n type File,\n type VariableDeclaration as BabelVariableDeclaration,\n type ReturnStatement as BabelReturnStatement,\n type ImportDeclaration as BabelImportDeclaration,\n} from '@babel/types';\nimport fs from 'fs-extra';\nimport { generate } from '@babel/generator';\nimport path from 'path';\n\nimport traverseModule, { type NodePath } from '@babel/traverse';\nimport { logger } from '../utils/logger';\n\nexport interface TargetComponentConfig {\n targetId: string;\n path: string;\n namespace: string;\n componentName: string;\n order: number;\n /** Optional label used by UITarget dev mode overlay for grouping/filtering (e.g. \"t/my-branch/W-123\", \"pr:1384\"). */\n hint?: string;\n}\n\nexport interface TargetContextProviderConfig {\n path: string;\n namespace: string;\n componentName: string;\n order: number;\n}\n\nexport interface ActionHookConfig {\n hookId: string;\n path: string;\n namespace: string;\n handlerName: string;\n order: number;\n}\n\nexport type TargetComponentRegistry = Record<string, TargetComponentConfig[]>;\nexport type ActionHookRegistry = Record<string, ActionHookConfig[]>;\n\nconst traverse = (traverseModule as unknown as { default: typeof traverseModule }).default || traverseModule;\n\nconst TARGET_COMPONENT_TAG = 'UITarget';\nconst TARGET_PROVIDERS_TAG = 'UITargetProviders';\nconst TARGET_ID_ATTRIBUTE = 'targetId';\n\n// Pre-compiled regex for JSX element detection — avoids false substring matches\n// (e.g. 'UITargetProviders'.includes('UITarget') === true).\n// Matches only when the tag is followed by whitespace, '/', or '>' as in valid JSX.\nconst TARGET_COMPONENT_JSX_RE = new RegExp(`<${TARGET_COMPONENT_TAG}[\\\\s/>]`);\n\n/**\n * Find and replace the TargetProviders tags with the corresponding context providers\n * @param element - the AST element to replace\n * @param contextProviders - the context providers to replace\n */\nfunction findAndReplaceProviders(\n element: NodePath<BabelJSXElement>,\n contextProviders: TargetContextProviderConfig[]\n): void {\n if (isJSXIdentifier(element.node.openingElement.name, { name: TARGET_PROVIDERS_TAG })) {\n // if there are context providers, replace the TargetProviders tag with the corresponding context providers in reverse order\n if (contextProviders.length > 0) {\n let nested = element.node.children;\n for (let i = contextProviders.length - 1; i >= 0; i--) {\n const contextProvider = contextProviders[i];\n const componentName = contextProvider.componentName;\n const providerElement = jsxElement(\n jsxOpeningElement(jsxIdentifier(componentName), [], false),\n jsxClosingElement(jsxIdentifier(componentName)),\n nested,\n false\n );\n nested = [providerElement];\n }\n element.replaceWithMultiple(nested);\n } else {\n // no replacement needed, just remove the TargetProviders tag,\n // but wrap the children in a JSXFragment\n element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), element.node.children));\n }\n }\n}\n\n/**\n * Find and replace the target component with the replacement code\n * @param componentName - the name of the component to replace\n * @param element - the AST element as the replacement candidate\n * @param targetRegistry - the target registry\n * @returns the targetId that was replaced, or null if no replacement was found\n */\nfunction findAndReplaceComponent(\n componentName: string,\n element: NodePath<BabelJSXElement>,\n targetRegistry: TargetComponentRegistry\n): string | null {\n let targetIdReplaced = null;\n if (isJSXIdentifier(element.node.openingElement.name, { name: componentName })) {\n let replaced = false;\n // Find the \"targetId\" property value from the JSX node, then replace the element with the replacement code\n // if no matching replacement is found, remove the element\n if (Array.isArray(element.node.openingElement.attributes)) {\n const attr = element.node.openingElement.attributes.find(\n (a) => isJSXAttribute(a) && isJSXIdentifier(a.name, { name: TARGET_ID_ATTRIBUTE })\n );\n const targetId =\n attr && isJSXAttribute(attr) && attr.value && 'value' in attr.value ? attr.value.value : undefined;\n if (targetId == null) {\n throw new Error(`UITarget must contain a targetId attribute`);\n }\n if (targetRegistry[targetId] && targetRegistry[targetId].length > 0) {\n const hasChildren = element.node.children.length > 0;\n if (hasChildren) {\n // Preserve wrapped children and nest replacement components in order,\n // so wrappers continue to work and remain valid in JSX expression contexts.\n let nestedChildren = element.node.children;\n for (let i = targetRegistry[targetId].length - 1; i >= 0; i--) {\n const targetComponent = targetRegistry[targetId][i];\n const replacementElement = jsxElement(\n jsxOpeningElement(jsxIdentifier(targetComponent.componentName), [], false),\n jsxClosingElement(jsxIdentifier(targetComponent.componentName)),\n nestedChildren,\n false\n );\n nestedChildren = [replacementElement];\n }\n element.replaceWith(nestedChildren[0] as BabelJSXElement);\n } else {\n // Keep historical behavior for self-closing UITarget:\n // emit each replacement as a sibling component.\n const components = targetRegistry[targetId].map((targetComponent: TargetComponentConfig) => {\n return jsxElement(\n jsxOpeningElement(jsxIdentifier(targetComponent.componentName), [], true),\n null,\n [],\n true\n );\n });\n if (components.length > 1) {\n element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), components));\n } else {\n element.replaceWith(components[0]);\n }\n }\n targetIdReplaced = targetId;\n replaced = true;\n }\n }\n if (!replaced) {\n if (element.node.children && element.node.children.length > 0) {\n // Keep children in a fragment so replacement is valid in all contexts,\n // including JSX expression containers (e.g. prop values).\n element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), element.node.children));\n } else {\n // No children and no target replacement:\n // use an empty fragment instead of remove() so UITarget inside\n // JSX expression containers (e.g. prop values) remains valid JSX.\n element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), []));\n }\n }\n }\n return targetIdReplaced;\n}\n\n/**\n * Run a replacement pass on the AST\n * @param ast - the AST to traverse\n * @param tagName - the name of the tag to replace\n * @param targetRegistry - the target registry\n * @param contextProviders - the context providers to replace\n * @returns a set of targetIds that were replaced\n */\nfunction runReplacementPass(\n ast: File,\n tagName: string,\n targetRegistry: TargetComponentRegistry | null = null,\n contextProviders: TargetContextProviderConfig[] | null = null\n): Set<string> {\n const targetIdsReplaced = new Set<string>();\n // Helper function to apply the replacement for a given path, targeting either the UITarget or TargetProviders tag\n const applyReplacement = (pathToReplace: NodePath<BabelJSXElement>) => {\n if (targetRegistry) {\n const replacedId = findAndReplaceComponent(tagName, pathToReplace, targetRegistry);\n if (replacedId) targetIdsReplaced.add(replacedId);\n } else if (contextProviders) {\n findAndReplaceProviders(pathToReplace, contextProviders);\n }\n };\n traverse(ast, {\n // look for variable declarations that contains <UITarget .../> in JSX fragment\n VariableDeclaration(nodePath: NodePath<BabelVariableDeclaration>) {\n const declarationPaths = nodePath.get('declarations');\n const declarationsArray = Array.isArray(declarationPaths) ? declarationPaths : [declarationPaths];\n\n for (const declarationPath of declarationsArray) {\n const initPath = declarationPath.get('init');\n if (initPath && isJSXElement(initPath.node)) {\n const content = generate(initPath.node).code;\n if (new RegExp(`<(${tagName})(\\\\s|\\\\/|>)`).test(content)) {\n // Handle the init node itself\n applyReplacement(initPath as NodePath<BabelJSXElement>);\n\n // Traverse nested JSX elements with the same handler\n initPath.traverse({\n JSXElement(inner: NodePath<BabelJSXElement>) {\n applyReplacement(inner);\n },\n });\n }\n }\n }\n },\n // look for return statements that contains the tag\n ReturnStatement(nodePath: NodePath<BabelReturnStatement>) {\n const arg = nodePath.node.argument;\n if (!isJSXElement(arg) && !isJSXFragment(arg)) {\n return;\n }\n nodePath.traverse({\n JSXElement(inner: NodePath<BabelJSXElement>) {\n applyReplacement(inner);\n },\n });\n },\n });\n return targetIdsReplaced;\n}\n\n/**\n * Build the import statements for the target components\n * @param targetIds - the targetIds that were replaced\n * @param targetRegistry - the target registry\n * @returns the import statements\n */\nfunction buildReplacementImportStatements(targetIds: Set<string>, targetRegistry: TargetComponentRegistry): string {\n const importStatements = new Set<string>();\n for (const targetId of targetIds) {\n const targetComponents = targetRegistry[targetId];\n for (const targetComponent of targetComponents) {\n importStatements.add(\n `import ${targetComponent.componentName} from '@/${targetComponent.path.replace('.tsx', '')}';`\n );\n }\n }\n return Array.from(importStatements).join('\\n');\n}\n\nexport function transformTargets(\n code: string,\n targetRegistry: TargetComponentRegistry,\n contextProviders: TargetContextProviderConfig[]\n): string | null {\n if (!TARGET_COMPONENT_JSX_RE.test(code) && !code.includes(TARGET_PROVIDERS_TAG)) {\n return null;\n }\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n });\n\n // replace UITarget tags with the corresponding components\n if (TARGET_COMPONENT_JSX_RE.test(code)) {\n const targetIdsReplaced = runReplacementPass(ast, TARGET_COMPONENT_TAG, targetRegistry, null);\n const replacementImportStatements = buildReplacementImportStatements(targetIdsReplaced, targetRegistry);\n // Single import rewrite pass — use exact equality to avoid matching '@/targets/ui-target-providers'\n traverse(ast, {\n ImportDeclaration(nodePath: NodePath<BabelImportDeclaration>) {\n const source = nodePath.node.source.value;\n if (source === '@/targets/ui-target') {\n nodePath.replaceWith(jsxText(replacementImportStatements));\n }\n },\n });\n }\n\n // replace TargetProviders tags with the corresponding components\n if (code.includes(TARGET_PROVIDERS_TAG)) {\n // add import statements for the context providers\n const importStatements = new Set<string>();\n for (const contextProvider of contextProviders) {\n importStatements.add(\n `import ${contextProvider.componentName} from '@/${contextProvider.path.replace('.tsx', '')}';`\n );\n }\n const replacementImportStatements = Array.from(importStatements).join('\\n');\n // Single import rewrite pass\n traverse(ast, {\n ImportDeclaration(nodePath: NodePath<BabelImportDeclaration>) {\n const source = nodePath.node.source.value;\n if (source === '@/targets/ui-target-providers') {\n nodePath.replaceWith(jsxText(replacementImportStatements));\n }\n },\n });\n runReplacementPass(ast, TARGET_PROVIDERS_TAG, null, contextProviders);\n }\n return generate(ast).code;\n}\n\n/**\n * Build the target registry from the extension directories\n * @param rootDir - the root directory of the project\n * @param sourceDir - the source directory of the project\n * @returns the target registry\n */\nexport function buildTargetRegistry(\n rootDir: string,\n options: { isProduction?: boolean } = {}\n): {\n componentRegistry: TargetComponentRegistry;\n contextProviders: TargetContextProviderConfig[];\n actionHookRegistry: ActionHookRegistry;\n} {\n const componentRegistry: TargetComponentRegistry = {};\n const contextProviders: TargetContextProviderConfig[] = [];\n const actionHookRegistry: ActionHookRegistry = {};\n const extensionDirPath = path.join(rootDir, 'extensions');\n const extensionDirs = fs.readdirSync(extensionDirPath, { withFileTypes: true });\n\n const getNamespaceAndComponentName = (dir: fs.Dirent, filePath: string) => {\n const namespace = dir.name\n .split('-')\n .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join('');\n const fileName = filePath\n .split('/')\n .pop()\n ?.replace(/\\.(tsx|ts|jsx|js)$/, '');\n const baseComponentName = fileName\n ?.split(/[-.]/)\n .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join('');\n const componentName = `${namespace}_${baseComponentName}`;\n return { namespace, componentName };\n };\n const TARGET_CONFIG_FILENAME = 'target-config.json';\n\n for (const dir of extensionDirs) {\n if (dir.isDirectory()) {\n const configPath = path.join(extensionDirPath, dir.name, TARGET_CONFIG_FILENAME);\n if (fs.existsSync(configPath)) {\n const extensionConfig = fs.readJsonSync(configPath);\n // Skip dev-only extensions in production builds\n if (options.isProduction && extensionConfig.devOnly === true) {\n continue;\n }\n if (extensionConfig && extensionConfig.components) {\n for (const component of extensionConfig.components) {\n if (component.enabled === false) continue;\n const { targetId, path: componentPath, order = 0 } = component;\n if (targetId && componentPath) {\n if (!componentRegistry[targetId]) {\n componentRegistry[targetId] = [];\n }\n const { namespace, componentName } = getNamespaceAndComponentName(dir, componentPath);\n componentRegistry[targetId].push({\n targetId,\n path: componentPath,\n order,\n namespace,\n componentName,\n });\n }\n }\n }\n if (extensionConfig && extensionConfig.contextProviders) {\n for (const contextProvider of extensionConfig.contextProviders) {\n const { path: providerPath, order = 0 } = contextProvider;\n if (providerPath) {\n const { namespace, componentName } = getNamespaceAndComponentName(dir, providerPath);\n contextProviders.push({ path: providerPath, namespace, componentName, order });\n }\n }\n }\n if (extensionConfig && extensionConfig.actionHooks) {\n for (const hook of extensionConfig.actionHooks) {\n const { hookId, handler, order = 0 } = hook;\n if (hookId && handler) {\n if (!actionHookRegistry[hookId]) {\n actionHookRegistry[hookId] = [];\n }\n const { namespace, componentName: handlerName } = getNamespaceAndComponentName(\n dir,\n handler\n );\n actionHookRegistry[hookId].push({\n hookId,\n path: handler,\n order,\n namespace,\n handlerName,\n });\n }\n }\n }\n }\n }\n }\n // Sort each extension's components by order\n for (const targetId in componentRegistry) {\n componentRegistry[targetId].sort((a, b) => a.order - b.order);\n }\n contextProviders.sort((a, b) => a.order - b.order);\n for (const hookId in actionHookRegistry) {\n actionHookRegistry[hookId].sort((a, b) => a.order - b.order);\n }\n\n // Warn about duplicate order values — execution order is non-deterministic\n for (const targetId in componentRegistry) {\n const entries = componentRegistry[targetId];\n const seen = new Map<number, string>();\n for (const entry of entries) {\n const existing = seen.get(entry.order);\n if (existing) {\n logger.warn(\n `[storefront-next] UITarget \"${targetId}\": components \"${existing}\" and \"${entry.componentName}\" have the same order (${entry.order}). Execution order between them is non-deterministic. Assign distinct order values.`\n );\n }\n seen.set(entry.order, entry.componentName);\n }\n }\n for (const hookId in actionHookRegistry) {\n const entries = actionHookRegistry[hookId];\n const seen = new Map<number, string>();\n for (const entry of entries) {\n const existing = seen.get(entry.order);\n if (existing) {\n logger.warn(\n `[storefront-next] Action hook \"${hookId}\": handlers \"${existing}\" and \"${entry.handlerName}\" have the same order (${entry.order}). Execution order between them is non-deterministic. Assign distinct order values.`\n );\n }\n seen.set(entry.order, entry.handlerName);\n }\n }\n\n return { componentRegistry, contextProviders, actionHookRegistry };\n}\n\nconst TARGET_ID_PATTERN = /<UITarget[\\s][^>]*targetId=[\"']([^\"']+)[\"']/g;\nconst EXCLUDED_DIRS = new Set(['ui-target-dev-mode', 'ui-target-smoke-test']);\n\n/**\n * Recursively collect all UITarget IDs declared in template source files.\n * Excludes extension directories so only \"real\" UITarget placements are counted.\n */\nexport function collectUITargetIds(sourceDir: string): Set<string> {\n const result = new Set<string>();\n\n function walk(dir: string) {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === 'extensions' || EXCLUDED_DIRS.has(entry.name)) continue;\n walk(fullPath);\n } else if (\n entry.isFile() &&\n /\\.(tsx?|jsx?)$/.test(entry.name) &&\n !/\\.test\\.(tsx?|jsx?)$/.test(entry.name)\n ) {\n const content = fs.readFileSync(fullPath, 'utf-8');\n TARGET_ID_PATTERN.lastIndex = 0;\n let match;\n while ((match = TARGET_ID_PATTERN.exec(content)) !== null) {\n result.add(match[1]);\n }\n }\n }\n }\n\n walk(sourceDir);\n return result;\n}\n\nexport interface OrphanedTarget {\n targetId: string;\n extension: string;\n componentPath: string;\n}\n\n/**\n * Validate that all targetIds in the component registry correspond to\n * UITarget declarations in the template source. Returns orphaned entries.\n */\nexport function validateTargetRegistry(\n componentRegistry: TargetComponentRegistry,\n declaredTargetIds: Set<string>\n): OrphanedTarget[] {\n const orphaned: OrphanedTarget[] = [];\n for (const targetId in componentRegistry) {\n if (!declaredTargetIds.has(targetId)) {\n for (const entry of componentRegistry[targetId]) {\n orphaned.push({\n targetId,\n extension: entry.namespace,\n componentPath: entry.path,\n });\n }\n }\n }\n return orphaned;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n buildTargetRegistry,\n transformTargets,\n collectUITargetIds,\n validateTargetRegistry,\n type TargetContextProviderConfig,\n type TargetComponentRegistry,\n} from '../extensibility/target-utils';\nimport path from 'path';\nimport type { ResolvedConfig } from 'vite';\nimport { logger } from '../logger';\n\n// --- Vite Plugin --------------------------------------------------------------\n\nexport function transformTargetPlaceholderPlugin() {\n // Memoize the extension registry - build it once and reuse across all file transformations\n let componentRegistry: TargetComponentRegistry;\n let contextProviders: TargetContextProviderConfig[];\n let sourceDir: string;\n let isProduction = false;\n\n return {\n name: 'storefront-next:transform-target-placeholder',\n enforce: 'pre' as const, // run before Vite's default TS/JS transforms\n configResolved(config: ResolvedConfig) {\n // extract source directory from vite config\n sourceDir =\n config.resolve.alias.find((alias) => alias.find === '@')?.replacement ||\n path.resolve(__dirname, './src');\n isProduction = config.mode === 'production';\n },\n buildStart() {\n // Build the registry once at the start of the build\n ({ componentRegistry, contextProviders } = buildTargetRegistry(sourceDir, { isProduction }));\n\n // Validate that all registered targetIds have a corresponding <UITarget> in the template\n const declaredTargetIds = collectUITargetIds(sourceDir);\n const orphaned = validateTargetRegistry(componentRegistry, declaredTargetIds);\n if (orphaned.length > 0) {\n const lines = orphaned.map(\n (o) => ` • \"${o.targetId}\" (extension: ${o.extension}, component: ${o.componentPath})`\n );\n throw new Error(\n `[storefront-next] ${orphaned.length} extension component(s) target UITarget IDs that do not exist in the template:\\n${lines.join(\n '\\n'\n )}\\n\\nEither add a <UITarget targetId=\"...\"> to the template or remove/disable the component in target-config.json.`\n );\n }\n },\n\n transform(code: string, id: string) {\n // Skip UITarget transformation if dev mode is active\n // Let targetDevModePlugin handle it instead (adds visual markers)\n if (process.env.VITE_UI_TARGET_DEV_MODE === 'true') {\n return null;\n }\n\n try {\n const transformedCode = transformTargets(code, componentRegistry, contextProviders);\n if (transformedCode) {\n return {\n code: transformedCode,\n map: null,\n };\n }\n return null;\n } catch (err: unknown) {\n logger.error(`UITarget replace ERROR in ${id}: ${err instanceof Error ? err.stack : String(err)}`);\n throw err;\n }\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { buildTargetRegistry, type ActionHookRegistry } from '../extensibility/target-utils';\nimport path from 'path';\n\nconst ACTION_HOOKS_VIRTUAL_ID = 'virtual:action-hooks';\nconst ACTION_HOOKS_RESOLVED_ID = `\\0${ACTION_HOOKS_VIRTUAL_ID}`;\n\n/**\n * Generate the virtual module code for the action hook registry.\n *\n * The generated module exports a `hookRegistry` map of hookId → handler[],\n * and a `runHook` function that executes registered handlers in order.\n */\nexport function generateActionHooksModule(actionHookRegistry: ActionHookRegistry): string {\n const imports: string[] = [];\n const registryEntries: string[] = [];\n\n for (const [hookId, handlers] of Object.entries(actionHookRegistry)) {\n const handlerNames: string[] = [];\n for (const handler of handlers) {\n const importPath = `@/${handler.path.replace(/\\.(ts|tsx|js|jsx)$/, '')}`;\n imports.push(`import ${handler.handlerName} from '${importPath}';`);\n handlerNames.push(handler.handlerName);\n }\n registryEntries.push(` '${hookId}': [${handlerNames.join(', ')}]`);\n }\n\n return `${imports.join('\\n')}\n\nconst HANDLER_TIMEOUT_MS = 5000;\n\nconst hookRegistry = {\n${registryEntries.join(',\\n')}\n};\n\nfunction withTimeout(promise, ms, label) {\n return Promise.race([\n promise,\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error(\\`Action hook handler timed out after \\${ms}ms: \\${label}\\`)), ms)\n ),\n ]);\n}\n\nexport async function runHook(hookId, context, options = {}) {\n const handlers = hookRegistry[hookId];\n if (!handlers || handlers.length === 0) {\n return context;\n }\n let currentContext = context;\n for (const handler of handlers) {\n try {\n const result = await withTimeout(handler(currentContext), HANDLER_TIMEOUT_MS, hookId);\n currentContext = result ?? currentContext;\n } catch (error) {\n if (error && error.name === 'ActionHookError') {\n throw error;\n }\n if (options.blocking) {\n throw error;\n }\n console.error(\\`[action-hooks] handler for \"\\${hookId}\" failed, skipping to next handler\\`, error);\n }\n }\n return currentContext;\n}\n`;\n}\n\n/**\n * Vite plugin that resolves `virtual:action-hooks` to a generated module\n * mapping hookIds to their registered handlers.\n */\nexport function actionHooksPlugin(): Plugin {\n let actionHookRegistry: ActionHookRegistry;\n let sourceDir: string;\n let isProduction = false;\n\n return {\n name: 'storefront-next:action-hooks',\n enforce: 'pre' as const,\n configResolved(config: ResolvedConfig) {\n sourceDir =\n config.resolve.alias.find((alias) => alias.find === '@')?.replacement ||\n path.resolve(__dirname, './src');\n isProduction = config.mode === 'production';\n },\n buildStart() {\n const registry = buildTargetRegistry(sourceDir, { isProduction });\n actionHookRegistry = registry.actionHookRegistry;\n },\n resolveId(id: string) {\n if (id === ACTION_HOOKS_VIRTUAL_ID) {\n return ACTION_HOOKS_RESOLVED_ID;\n }\n },\n load(id: string) {\n if (id === ACTION_HOOKS_RESOLVED_ID) {\n return generateActionHooksModule(actionHookRegistry);\n }\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { ViteDevServer, ResolvedConfig } from 'vite';\nimport path from 'path';\nimport { logger } from '../logger';\n\nexport const watchConfigFilesPlugin = () => {\n let viteConfig: ResolvedConfig;\n return {\n name: 'storefront-next:watch-config-files',\n configResolved(config: ResolvedConfig) {\n viteConfig = config;\n },\n configureServer(server: ViteDevServer) {\n const aliases = viteConfig.resolve.alias;\n const root = Object.values(aliases).find((alias) => alias.find === '@')?.replacement || 'src';\n // Use path.posix.join to ensure forward slashes for glob patterns (required even on Windows)\n const glob = path.posix.join(root, 'extensions', '**', 'target-config.json');\n server.watcher.add(glob);\n\n const onChange = (file: string) => {\n if (file.endsWith('target-config.json')) {\n logger.debug(`🔁 target-config.json changed: ${file}`);\n void server.restart();\n }\n };\n\n server.watcher.on('add', onChange);\n server.watcher.on('change', onChange);\n server.watcher.on('unlink', onChange);\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Plugin } from 'vite';\nimport { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { resolve, relative, dirname } from 'path';\nimport { createRequire } from 'module';\nimport { glob } from 'glob';\nimport {\n Project,\n Node,\n type Decorator,\n type SourceFile,\n ts,\n type FunctionDeclaration,\n type VariableStatement,\n} from 'ts-morph';\nimport { logger } from '../logger';\n\n// Default component group when none is specified in the decorator\nconst DEFAULT_COMPONENT_GROUP = 'storefrontnext_base';\n\n/**\n * Information about a discovered component\n */\nexport interface ComponentInfo {\n /** Component ID from @Component decorator */\n id: string;\n /** Absolute path to the component file */\n filePath: string;\n /** Relative import path from registry.ts */\n relativePath: string;\n /** Whether the component has loader exports */\n hasLoader: boolean;\n /** Whether the component has clientLoader export */\n hasClientLoader: boolean;\n /** Whether the component has fallback export */\n hasFallback: boolean;\n}\n\n/**\n * Configuration options for the static registry plugin\n */\nexport interface StaticRegistryPluginConfig {\n /**\n * Path to the components directory to scan\n * @default 'src/components'\n */\n componentPath?: string;\n\n /**\n * Path to the registry file to update\n * Note: The registry file must contain STATIC_REGISTRY_START and STATIC_REGISTRY_END markers\n * and must export a 'registry' variable (or use registryIdentifier to specify a different name)\n * @default 'src/lib/registry.ts'\n */\n registryPath?: string;\n\n /**\n * Name of the registry variable to use in generated code\n * @default 'registry'\n */\n registryIdentifier?: string;\n\n /**\n * Whether to fail the build on registry generation errors\n * @default true\n */\n failOnError?: boolean;\n}\n\n/**\n * Extracts component ID and group from @Component decorator using ts-morph AST parsing\n */\nexport function extractComponentInfo(decorator: Decorator): { id: string; group: string } | null {\n const callExpression = decorator.getCallExpression();\n if (!callExpression) {\n return null;\n }\n\n const args = callExpression.getArguments();\n if (args.length === 0) {\n return null;\n }\n\n // First argument should be the component ID string (string literal or template literal)\n const firstArg = args[0];\n\n let baseComponentId: string;\n if (Node.isStringLiteral(firstArg)) {\n baseComponentId = firstArg.getLiteralValue();\n } else if (Node.isNoSubstitutionTemplateLiteral(firstArg)) {\n baseComponentId = firstArg.getText().slice(1, -1); // Remove backticks\n } else if (Node.isTemplateExpression(firstArg)) {\n // Template literals with interpolation cannot be resolved at build time\n throw new Error(\n `@Component id must be a simple string literal or backtick string without interpolation. Found: ${firstArg.getText()}`\n );\n } else {\n return null;\n }\n let group = DEFAULT_COMPONENT_GROUP;\n\n // Check if there's a second argument with metadata object\n if (args.length > 1) {\n const secondArg = args[1];\n if (Node.isObjectLiteralExpression(secondArg)) {\n // Look for group property in the metadata object\n const groupProperty = secondArg.getProperty('group');\n if (groupProperty && Node.isPropertyAssignment(groupProperty)) {\n const initializer = groupProperty.getInitializer();\n if (initializer && Node.isStringLiteral(initializer)) {\n group = initializer.getLiteralValue();\n }\n }\n }\n }\n\n return {\n id: `${group}.${baseComponentId}`,\n group,\n };\n}\n\n/**\n * Checks if a source file has a specific named export using ts-morph AST parsing\n */\nexport function hasNamedExport(sourceFile: SourceFile, exportName: string): boolean {\n // Check for function declarations: export function exportName(...)\n const functionDeclarations = sourceFile\n .getFunctions()\n .filter((func: FunctionDeclaration) => func.hasExportKeyword() && func.getName() === exportName);\n\n if (functionDeclarations.length > 0) {\n return true;\n }\n\n // Check for variable declarations: export const exportName = ...\n const variableStatements = sourceFile\n .getVariableStatements()\n .filter((stmt: VariableStatement) => stmt.hasExportKeyword());\n\n for (const stmt of variableStatements) {\n const declarations = stmt.getDeclarations();\n for (const decl of declarations) {\n if (decl.getName() === exportName) {\n return true;\n }\n }\n }\n\n // Check for export assignments: export { exportName } or export { localName as exportName }\n const exportDeclarations = sourceFile.getExportDeclarations();\n for (const exportDecl of exportDeclarations) {\n const namedExports = exportDecl.getNamedExports();\n for (const namedExport of namedExports) {\n // Check both the local name and the alias (if any)\n const localName = namedExport.getName();\n const aliasName = namedExport.getAliasNode()?.getText();\n\n if (localName === exportName || aliasName === exportName) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Checks if a source file has a fallback export (including default exports with 'fallback' in name)\n */\nexport function hasFallbackExport(sourceFile: SourceFile): boolean {\n // Check for named export 'fallback'\n if (hasNamedExport(sourceFile, 'fallback')) {\n return true;\n }\n\n // Check for default function declarations with 'fallback' in name\n const functions = sourceFile\n .getFunctions()\n .filter((func: FunctionDeclaration) => func.hasExportKeyword() && func.hasDefaultKeyword());\n\n for (const func of functions) {\n const name = func.getName();\n if (name && name.toLowerCase().includes('fallback')) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Scans all files in the component directory for @Component decorators and extracts metadata using ts-morph\n */\nexport async function scanComponents(\n project: Project,\n projectRoot: string,\n componentPath: string,\n registryPath: string\n): Promise<ComponentInfo[]> {\n // Scan all TypeScript/TSX files in the component directory recursively\n const componentPattern = `${componentPath}/**/*.{ts,tsx}`;\n const componentFiles = await glob(componentPattern, {\n cwd: projectRoot,\n absolute: true,\n });\n\n logger.debug(`🔍 Scanning ${componentFiles.length} files in ${componentPath}...`);\n\n const components: ComponentInfo[] = [];\n const registryDir = dirname(resolve(projectRoot, registryPath));\n\n for (const filePath of componentFiles) {\n try {\n // Read file content and create source file in ts-morph project\n const content = readFileSync(filePath, 'utf-8');\n const sourceFile = project.createSourceFile(filePath, content, { overwrite: true });\n\n // Find all classes with @Component decorator\n const classes = sourceFile.getClasses();\n\n for (const classDeclaration of classes) {\n const decorators = classDeclaration.getDecorators();\n\n for (const decorator of decorators) {\n const decoratorName = decorator.getName();\n\n if (decoratorName === 'Component') {\n const componentInfo = extractComponentInfo(decorator);\n\n if (componentInfo) {\n // Calculate relative path from registry.ts to component\n let relativePath = relative(registryDir, filePath)\n .replace(/\\\\/g, '/') // Normalize Windows paths\n .replace(/\\.(ts|tsx)$/, ''); // Remove extension\n\n // Ensure relative path starts with './' or '../'\n if (!relativePath.startsWith('.')) {\n relativePath = `./${relativePath}`;\n }\n\n // Check for React Router style loader exports using AST parsing\n const hasLoaderExport = hasNamedExport(sourceFile, 'loader');\n const hasClientLoaderExport = hasNamedExport(sourceFile, 'clientLoader');\n const hasFallback = hasFallbackExport(sourceFile);\n\n components.push({\n id: componentInfo.id,\n filePath,\n relativePath,\n hasLoader: hasLoaderExport,\n hasClientLoader: hasClientLoaderExport,\n hasFallback,\n });\n\n const exports = [];\n if (hasLoaderExport) {\n exports.push('loader');\n }\n if (hasClientLoaderExport) {\n exports.push('clientLoader');\n }\n if (hasFallback) {\n exports.push('fallback');\n }\n const exportsText = exports.length > 0 ? ` (with ${exports.join(', ')})` : '';\n logger.debug(` ✅ Found component: ${componentInfo.id} → ${relativePath}${exportsText}`);\n }\n }\n }\n }\n } catch (error) {\n logger.warn(`⚠️ Could not process ${filePath}: ${(error as Error).message}`);\n // Continue processing other files even if one fails\n }\n }\n\n return components;\n}\n\n/**\n * Generates the initializeRegistry function code\n */\nexport function generateRegistryCode(components: ComponentInfo[], registryIdentifier: string = 'registry'): string {\n // Ensure deterministic output: sort by component id, then by relativePath as a stable tiebreaker\n const sorted = [...components].sort(\n (a, b) => a.id.localeCompare(b.id) || a.relativePath.localeCompare(b.relativePath)\n );\n\n if (sorted.length === 0) {\n return `\n/**\n * Initialize the static component registry.\n * This function is auto-generated by the staticRegistry Vite plugin.\n *\n * DO NOT EDIT THIS FUNCTION MANUALLY - it will be overwritten on next build.\n */\nexport function initializeRegistry(targetRegistry = ${registryIdentifier}): void {\n // No components found with @Component decorators\n}\n`;\n }\n\n const registrations = sorted\n .map(({ id, relativePath, hasLoader, hasClientLoader, hasFallback }) => {\n if (hasLoader || hasClientLoader || hasFallback) {\n // Register with metadata - tell registry the function/component names\n const metadata = [];\n if (hasLoader) {\n metadata.push(`loader: 'loader'`);\n }\n if (hasClientLoader) {\n metadata.push(`clientLoader: 'clientLoader'`);\n }\n if (hasFallback) {\n metadata.push(`fallback: 'fallback'`);\n }\n\n return ` targetRegistry.registerImporter('${id}', () => import('${relativePath}'), { ${metadata.join(', ')} });`;\n } else {\n return ` targetRegistry.registerImporter('${id}', () => import('${relativePath}'));`;\n }\n })\n .join('\\n');\n\n return `\n/**\n * Initialize the static component registry.\n * This function is auto-generated by the staticRegistry Vite plugin.\n *\n * DO NOT EDIT THIS FUNCTION MANUALLY - it will be overwritten on next build.\n *\n * Components registered: ${sorted.map((c) => c.id).join(', ')}\n */\nexport function initializeRegistry(targetRegistry = ${registryIdentifier}): void {\n${registrations}\n}\n`;\n}\n\n/**\n * Formats registry file content with the project's own Prettier so the written file\n * matches what the project's formatter would produce.\n *\n * Without this, a standalone `prettier --write` (pre-commit hook, format-on-save) rewrites\n * the generated file on every commit: the generator emits one `registerImporter` call per\n * line and cannot anticipate an arbitrary `printWidth`, so Prettier re-wraps long lines and\n * the regenerate -> format -> regenerate loop never settles.\n *\n * Prettier is resolved from the registry file's own location — i.e. the consuming project's\n * `node_modules`, not the SDK bundle (which keeps `node_modules` external). It is the project's\n * Prettier version and config whose output must be matched. Returns the content unchanged when\n * Prettier is absent or fails, so registry generation never breaks a build over formatting.\n */\n// Latches once the project has no Prettier, so the missing-Prettier warning fires once per process\n// instead of on every HMR save in dev. Real format failures (below) still warn each time.\nlet warnedNoPrettier = false;\n\nasync function formatWithProjectPrettier(content: string, registryFilePath: string): Promise<string> {\n try {\n const projectRequire = createRequire(registryFilePath);\n const prettier = projectRequire('prettier');\n // editorconfig: true matches the Prettier CLI default. Without it, a consumer who sets\n // printWidth/tabWidth via `.editorconfig` (not `.prettierrc`) gets different output from\n // this plugin than from their pre-commit `prettier --write` — reviving the churn.\n const config = await prettier.resolveConfig(registryFilePath, { editorconfig: true });\n return await prettier.format(content, { ...config, filepath: registryFilePath });\n } catch (error) {\n // A silent skip writes the registry unformatted, so a standalone `prettier --write` churns\n // it on every commit — the exact symptom this prevents. Surface it so a customer can\n // diagnose. Missing Prettier is a stable, once-per-process condition (warn once to avoid\n // spamming every HMR save); a format/config failure is per-invocation, so warn each time.\n if ((error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND') {\n if (!warnedNoPrettier) {\n logger.warn('⚠️ Prettier not found in the project; static registry will be written unformatted.');\n warnedNoPrettier = true;\n }\n } else {\n logger.warn(`⚠️ Skipping Prettier formatting for registry file: ${(error as Error).message}`);\n }\n return content;\n }\n}\n\n/**\n * Updates the registry.ts file with the generated code\n */\nexport async function updateRegistryFile(registryFilePath: string, generatedCode: string): Promise<boolean> {\n let existingContent: string;\n\n // Check if file exists, if not create a basic one\n if (!existsSync(registryFilePath)) {\n logger.debug('📝 Creating new registry file...');\n\n // Create a basic registry file\n const basicRegistryContent = `import { ComponentRegistry } from '@/lib/component-registry';\n\n// Create the component registry instance\nexport const registry = new ComponentRegistry();\n\n// STATIC_REGISTRY_START\n// Generated content will be inserted here by the static registry plugin\n// STATIC_REGISTRY_END\n`;\n writeFileSync(registryFilePath, basicRegistryContent, 'utf-8');\n existingContent = basicRegistryContent;\n } else {\n try {\n existingContent = readFileSync(registryFilePath, 'utf-8');\n } catch (error) {\n throw new Error(`Failed to read registry file: ${(error as Error).message}`);\n }\n }\n\n // Use explicit markers for generated content\n const startMarker = '// STATIC_REGISTRY_START';\n const endMarker = '// STATIC_REGISTRY_END';\n\n const startIndex = existingContent.indexOf(startMarker);\n const endIndex = existingContent.indexOf(endMarker);\n\n if (startIndex === -1 || endIndex === -1) {\n throw new Error(\n `Registry file ${registryFilePath} is missing static registry markers. ` +\n `Please add \"${startMarker}\" and \"${endMarker}\" markers to define the generated content area.`\n );\n }\n\n const before = existingContent.slice(0, startIndex + startMarker.length);\n const after = existingContent.slice(endIndex);\n\n // Format before the unchanged-check so the guard compares formatted-to-formatted. If the\n // raw content were compared instead, an already-formatted file on disk would never match,\n // every run would write, and the HMR cascade the guard below prevents would return.\n const updatedContent = await formatWithProjectPrettier(`${before}\\n${generatedCode}\\n${after}`, registryFilePath);\n\n // Skip write if content is unchanged to avoid triggering unnecessary HMR cascades.\n // Without this check, every component file save writes static-registry.ts even when\n // the registry hasn't changed, which triggers Vite's file watcher -> SSR page reload\n // -> root.tsx HMR -> app.css HMR in a rapid loop.\n if (updatedContent === existingContent) {\n logger.debug(`⏭️ Registry unchanged, skipping write: ${registryFilePath}`);\n return false;\n }\n\n try {\n writeFileSync(registryFilePath, updatedContent, 'utf-8');\n logger.debug(`💾 Updated registry file: ${registryFilePath}`);\n return true;\n } catch (error) {\n throw new Error(`Failed to write registry file: ${(error as Error).message}`);\n }\n}\n\n/**\n * Vite plugin that generates static component registry based on @Component decorators.\n *\n * This plugin scans component files for @Component decorators and automatically generates\n * a static registry function that pre-registers all components with their import paths.\n * This eliminates the need for manual component registration and provides build-time\n * optimization for component discovery.\n *\n * @param config - Configuration options for the plugin\n * @returns A Vite plugin that generates static component registrations\n *\n * @example\n * // In vite.config.ts\n * export default defineConfig({\n * plugins: [\n * staticRegistryPlugin({\n * componentPath: 'src/components',\n * registryPath: 'src/lib/registry.ts',\n * verbose: true\n * })\n * ]\n * })\n */\nexport const staticRegistryPlugin = (config: StaticRegistryPluginConfig = {}): Plugin => {\n const {\n componentPath = 'src/components',\n registryPath = 'src/lib/static-registry.ts',\n registryIdentifier = 'registry',\n failOnError = true,\n } = config;\n\n let projectRoot: string;\n\n const runRegistryGeneration = async () => {\n logger.debug('🚀 Starting static registry generation...');\n\n // Create a fresh Project for this run only\n const project = new Project({\n compilerOptions: {\n target: ts.ScriptTarget.Latest,\n module: ts.ModuleKind.ESNext,\n jsx: ts.JsxEmit.ReactJSX,\n allowJs: true,\n skipLibCheck: true,\n noEmit: true,\n },\n });\n\n // Build AST, extract plain data\n const components = await scanComponents(project, projectRoot, componentPath, registryPath);\n\n // From here on we do not need the AST any more.\n // `components` is just an array of plain objects.\n // `project` will fall out of scope after this function returns and can be GC'd.\n\n logger.debug(`📦 Found ${components.length} components with @Component decorators`);\n\n const generatedCode = generateRegistryCode(components, registryIdentifier);\n const registryFilePath = resolve(projectRoot, registryPath);\n const changed = await updateRegistryFile(registryFilePath, generatedCode);\n\n logger.debug('✅ Static registry generation complete!');\n\n return { registryFilePath, changed };\n };\n\n return {\n name: 'storefrontnext:static-registry',\n\n configResolved(resolvedConfig) {\n projectRoot = resolvedConfig.root;\n },\n\n async buildStart() {\n try {\n await runRegistryGeneration();\n } catch (error) {\n logger.error(`❌ Static registry generation failed: ${(error as Error).message}`);\n if (failOnError) {\n throw error;\n }\n logger.warn('⚠️ Continuing build without static registry...');\n }\n },\n\n async handleHotUpdate({ file, server }) {\n const normalizedComponentPath = componentPath.replace(/\\\\/g, '/');\n const normalizedFile = file.replace(/\\\\/g, '/');\n\n if (\n normalizedFile.includes(`/${normalizedComponentPath}/`) &&\n (normalizedFile.endsWith('.ts') || normalizedFile.endsWith('.tsx'))\n ) {\n logger.debug(`🔄 Component file changed: ${file}, regenerating registry...`);\n\n try {\n const { registryFilePath, changed } = await runRegistryGeneration();\n\n // Only reload the registry module if the generated content actually changed.\n // This prevents an HMR cascade: write -> file watcher -> SSR reload ->\n // root.tsx HMR -> app.css HMR -> repeat.\n if (changed) {\n const registryModule = server.moduleGraph.getModuleById(registryFilePath);\n if (registryModule) {\n await server.reloadModule(registryModule);\n }\n logger.debug('✅ Registry regenerated successfully!');\n } else {\n logger.debug('⏭️ Registry unchanged, skipping reload');\n }\n } catch (error) {\n logger.error(`❌ Failed to regenerate registry: ${(error as Error).message}`);\n }\n\n return [];\n }\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { resolve } from 'node:path';\nimport { existsSync, readFileSync } from 'node:fs';\n\n/**\n * Parse TypeScript paths from tsconfig.json and convert to jiti alias format.\n *\n * @param tsconfigPath - Path to tsconfig.json\n * @param projectDirectory - Project root directory for resolving relative paths\n * @returns Record of alias mappings for jiti\n *\n * @example\n * // tsconfig.json: { \"compilerOptions\": { \"paths\": { \"@/*\": [\"./src/*\"] } } }\n * // Returns: { \"@/\": \"/absolute/path/to/src/\" }\n */\nexport function parseTsconfigPaths(tsconfigPath: string, projectDirectory: string): Record<string, string> {\n const alias: Record<string, string> = {};\n\n if (!existsSync(tsconfigPath)) {\n return alias;\n }\n\n try {\n const tsconfigContent = readFileSync(tsconfigPath, 'utf-8');\n const tsconfig = JSON.parse(tsconfigContent) as {\n compilerOptions?: {\n paths?: Record<string, string[]>;\n baseUrl?: string;\n };\n };\n\n const paths = tsconfig.compilerOptions?.paths;\n const baseUrl = tsconfig.compilerOptions?.baseUrl || '.';\n\n if (paths) {\n for (const [key, values] of Object.entries(paths)) {\n if (values && values.length > 0) {\n // Convert TypeScript path pattern to jiti alias\n // e.g., \"@/*\": [\"./src/*\"] -> \"@/\": \"<projectDir>/src/\"\n const aliasKey = key.replace(/\\/\\*$/, '/');\n const aliasValue = values[0].replace(/\\/\\*$/, '/').replace(/^\\.\\//, '');\n alias[aliasKey] = resolve(projectDirectory, baseUrl, aliasValue);\n }\n }\n }\n } catch {\n // Ignore tsconfig parse errors - caller can work without aliases\n }\n\n // Sort by key length descending so specific aliases match before wildcards.\n const sortedAlias: Record<string, string> = {};\n Object.keys(alias)\n .sort((a, b) => b.length - a.length)\n .forEach((key) => {\n sortedAlias[key] = alias[key];\n });\n\n return sortedAlias;\n}\n\nexport interface TsImportOptions {\n /** Project directory for resolving paths */\n projectDirectory: string;\n /** Optional path to tsconfig.json (defaults to projectDirectory/tsconfig.json) */\n tsconfigPath?: string;\n}\n\n/**\n * Import a TypeScript file using jiti with proper path alias resolution.\n * This is a cross-platform alternative to tsx that works on Windows.\n *\n * @param filePath - Absolute path to the TypeScript file to import\n * @param options - Import options including project directory\n * @returns The imported module\n */\nexport async function importTypescript<T = unknown>(filePath: string, options: TsImportOptions): Promise<T> {\n const { projectDirectory, tsconfigPath = resolve(projectDirectory, 'tsconfig.json') } = options;\n\n const { createJiti } = await import('jiti');\n const alias = parseTsconfigPaths(tsconfigPath, projectDirectory);\n\n const jiti = createJiti(import.meta.url, {\n fsCache: false,\n interopDefault: true,\n alias,\n });\n\n return jiti.import(filePath);\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { resolve } from 'path';\nimport { importTypescript } from '../server/ts-import';\nimport { logger } from '../logger';\n\n/**\n * Adapter configuration with event toggles\n * Event toggles are a dynamic map - any event name can be toggled\n */\nexport interface AdapterConfig {\n enabled?: boolean;\n eventToggles?: Record<string, boolean>;\n}\n\n/**\n * Expected structure of engagement config from config.server.ts\n */\nexport interface EngagementConfig {\n adapters?: Record<string, AdapterConfig>;\n}\n\n/**\n * Load the engagement config from config.server.ts\n */\nexport async function loadEngagementConfig(projectRoot: string, configPath: string): Promise<EngagementConfig | null> {\n const absoluteConfigPath = resolve(projectRoot, configPath);\n\n try {\n // Load through jiti (via importTypescript), the same TS-aware loader used by every\n // other config loader in the SDK. This plugin runs in plain Node during a production\n // build (Rollup `buildStart`), where a native `import()` cannot resolve TypeScript's\n // extensionless relative imports (e.g. `./src/types/tracking-consent`) and throws\n // ERR_MODULE_NOT_FOUND. jiti transpiles the TS and resolves tsconfig path aliases.\n const configModule = await importTypescript<{ default?: { app?: { engagement?: EngagementConfig } } }>(\n absoluteConfigPath,\n { projectDirectory: projectRoot }\n );\n const config = configModule.default;\n\n logger.debug(`📄 Loaded config from ${configPath}`);\n\n // Navigate to engagement config\n const engagement = config?.app?.engagement;\n\n if (!engagement) {\n logger.debug(`⚠️ No engagement config found in ${configPath}`);\n return null;\n }\n\n return engagement;\n } catch (error) {\n // Config might not exist or have import errors - this is non-fatal\n logger.warn(`⚠️ Could not load config from ${configPath}: ${(error as Error).message}`);\n return null;\n }\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { glob } from 'glob';\nimport { readFileSync } from 'fs';\nimport { resolve, join } from 'path';\nimport { loadEngagementConfig, type EngagementConfig } from './configLoader';\nimport { logger } from '../logger';\n\n/**\n * Configuration options for the event instrumentation validator plugin\n */\nexport interface EventInstrumentationValidatorConfig {\n /**\n * Path to config module relative to project root\n * @default 'config.server.ts'\n */\n configPath?: string;\n\n /**\n * Directories to scan for trackEvent calls relative to project root\n * @default ['src']\n */\n scanPaths?: string[];\n\n /**\n * Whether to fail the build on missing instrumentation\n * @default false (warning only)\n */\n failOnMissing?: boolean;\n}\n\n/**\n * Extract all trackEvent calls from source files and return the event types found\n */\nasync function scanForInstrumentedEvents(projectRoot: string, scanPaths: string[]): Promise<Set<string>> {\n const instrumentedEvents = new Set<string>();\n\n // Regex patterns to match trackEvent calls\n // Pattern 1: trackEvent(..., ..., ..., 'event_type', ...)\n // The event type is the 4th argument\n const trackEventPattern = /trackEvent\\s*\\([^,]+,[^,]+,[^,]+,\\s*['\"]([^'\"]+)['\"]/g;\n\n // Pattern 2: sendViewPageEvent (special case for view_page)\n const sendViewPagePattern = /sendViewPageEvent\\s*\\(/g;\n\n // Pattern 3: createEvent('event_type', ...) - backup pattern\n const createEventPattern = /createEvent\\s*\\(\\s*['\"]([^'\"]+)['\"]/g;\n\n for (const scanPath of scanPaths) {\n const absoluteScanPath = resolve(projectRoot, scanPath);\n const pattern = join(absoluteScanPath, '**/*.{ts,tsx}');\n\n const files = await glob(pattern, {\n ignore: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx', '**/node_modules/**'],\n });\n\n logger.debug(`📂 Scanning ${files.length} files in ${scanPath}...`);\n\n for (const file of files) {\n try {\n const content = readFileSync(file, 'utf-8');\n\n // Find trackEvent calls\n let match;\n while ((match = trackEventPattern.exec(content)) !== null) {\n const eventType = match[1];\n instrumentedEvents.add(eventType);\n logger.debug(` ✓ Found trackEvent('${eventType}') in ${file}`);\n }\n\n // Check for sendViewPageEvent (implies view_page is instrumented)\n if (sendViewPagePattern.test(content)) {\n instrumentedEvents.add('view_page');\n logger.debug(` ✓ Found sendViewPageEvent() in ${file}`);\n }\n\n // Find createEvent calls as backup\n while ((match = createEventPattern.exec(content)) !== null) {\n const eventType = match[1];\n instrumentedEvents.add(eventType);\n logger.debug(` ✓ Found createEvent('${eventType}') in ${file}`);\n }\n\n // Reset regex lastIndex for next file\n trackEventPattern.lastIndex = 0;\n sendViewPagePattern.lastIndex = 0;\n createEventPattern.lastIndex = 0;\n } catch (error) {\n logger.warn(`⚠️ Could not read ${file}: ${(error as Error).message}`);\n }\n }\n }\n\n return instrumentedEvents;\n}\n\n/**\n * Extract enabled event toggles per adapter\n * Dynamically iterates over all keys in eventToggles - supports custom event types\n */\nfunction extractEnabledEvents(engagement: EngagementConfig): Map<string, Set<string>> {\n const adapterEvents = new Map<string, Set<string>>();\n\n if (!engagement.adapters) {\n return adapterEvents;\n }\n\n for (const [adapterName, adapterConfig] of Object.entries(engagement.adapters)) {\n // Skip disabled adapters\n if (!adapterConfig.enabled) {\n continue;\n }\n\n const enabledEvents = new Set<string>();\n\n if (adapterConfig.eventToggles) {\n // Dynamically iterate over all keys in eventToggles\n for (const [eventType, isEnabled] of Object.entries(adapterConfig.eventToggles)) {\n if (isEnabled === true) {\n enabledEvents.add(eventType);\n }\n }\n }\n\n if (enabledEvents.size > 0) {\n adapterEvents.set(adapterName, enabledEvents);\n }\n }\n\n return adapterEvents;\n}\n\n/**\n * Vite plugin that validates event instrumentation at build time.\n *\n * This plugin scans source files for trackEvent() calls and validates that\n * all enabled event toggles in config.server.ts have corresponding instrumentation.\n *\n * @param config - Configuration options for the plugin\n * @returns A Vite plugin that validates event instrumentation\n *\n * @example\n * // In vite.config.ts\n * export default defineConfig({\n * plugins: [\n * eventInstrumentationValidatorPlugin({\n * configPath: 'config.server.ts',\n * scanPaths: ['src'],\n * verbose: true\n * })\n * ]\n * })\n */\nexport const eventInstrumentationValidatorPlugin = (config: EventInstrumentationValidatorConfig = {}): Plugin => {\n const { configPath = 'config.server.ts', scanPaths = ['src'], failOnMissing = false } = config;\n\n let resolvedConfig: ResolvedConfig;\n\n return {\n name: 'storefrontnext:event-instrumentation-validator',\n apply: 'build',\n\n configResolved(viteConfig) {\n resolvedConfig = viteConfig;\n },\n\n async buildStart() {\n const projectRoot = resolvedConfig.root;\n\n logger.debug('🔍 Validating event instrumentation...');\n\n // Load engagement config\n const engagement = await loadEngagementConfig(projectRoot, configPath);\n\n if (!engagement) {\n logger.debug('ℹ️ Skipping validation - no engagement config found');\n return;\n }\n\n // Extract enabled events per adapter\n const adapterEvents = extractEnabledEvents(engagement);\n\n if (adapterEvents.size === 0) {\n logger.debug('ℹ️ No enabled adapters with event toggles found');\n return;\n }\n\n // Scan source files for instrumented events\n const instrumentedEvents = await scanForInstrumentedEvents(projectRoot, scanPaths);\n\n logger.debug(\n `🔎 Found ${instrumentedEvents.size} instrumented event types: ${[...instrumentedEvents].join(', ')}`\n );\n\n // Validate each adapter's enabled events\n const missingInstrumentation: Array<{ adapter: string; event: string }> = [];\n\n for (const [adapterName, enabledEvents] of adapterEvents) {\n for (const eventType of enabledEvents) {\n if (!instrumentedEvents.has(eventType)) {\n missingInstrumentation.push({\n adapter: adapterName,\n event: eventType,\n });\n }\n }\n }\n\n // Report results\n if (missingInstrumentation.length === 0) {\n logger.debug('✅ All enabled events are instrumented');\n return;\n }\n\n // Report missing instrumentation\n for (const { adapter, event } of missingInstrumentation) {\n logger.warn(`⚠️ ${adapter}.${event} is enabled but '${event}' is never instrumented`);\n }\n\n if (failOnMissing) {\n throw new Error(\n `[event-instrumentation] ${missingInstrumentation.length} event(s) are enabled but not instrumented. ` +\n `Either add instrumentation or disable the event toggles in config.server.ts.`\n );\n }\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { existsSync } from 'fs';\nimport { resolve } from 'path';\n\n/** Source filename for the middleware registry (project source). */\nconst MIDDLEWARE_REGISTRY_SOURCE_FILE = 'middleware-registry.ts';\n\n/** Subdirectory under build output where the compiled registry is written (must match server/index.ts expectations). */\nconst SERVER_OUT_SUBDIR = 'server';\n\n/**\n * Vite plugin that builds the middleware registry file for production.\n *\n * This plugin reads the template's middleware registry from the app's server directory\n * (e.g. `src/server/middleware-registry.ts` when appDirectory is `./src`) and compiles it\n * into the build output's server directory so the production server (Managed Runtime)\n * can load the custom Express middlewares.\n *\n * Compilation uses tsdown (single TypeScript file → ESM) instead of a full Vite build.\n * Paths are derived from the React Router plugin context (appDirectory, buildDirectory)\n * when available; there are no env vars for these paths in this package.\n *\n * If the middleware registry file does not exist, the plugin silently skips the build step.\n *\n * @returns {Plugin} A Vite plugin that compiles the middleware registry for production\n *\n * @example\n * // In vite.config.ts\n * export default defineConfig({\n * plugins: [\n * buildMiddlewareRegistryPlugin()\n * ]\n * })\n */\nexport const buildMiddlewareRegistryPlugin = (): Plugin => {\n let resolvedConfig: ResolvedConfig;\n let buildDirectory: string;\n /** App source directory (e.g. 'src' or './src') from React Router config. */\n let appDirectory: string;\n\n return {\n name: 'storefront-next:build-middleware-registry',\n apply: 'build',\n\n configResolved(config) {\n resolvedConfig = config;\n // React Router plugin context: appDirectory (e.g. './src') and buildDirectory (e.g. 'build') — no env vars in this package for these paths\n // @ts-expect-error: react-router plugin context is not typed\n const rr = config.__reactRouterPluginContext?.reactRouterConfig ?? {};\n buildDirectory = rr.buildDirectory ?? resolve(config.root, 'build');\n appDirectory = rr.appDirectory ?? 'src';\n },\n\n buildApp: {\n order: 'post',\n handler: async () => {\n const projectRoot = resolvedConfig.root;\n const middlewareRegistryPath = resolve(\n projectRoot,\n appDirectory,\n SERVER_OUT_SUBDIR,\n MIDDLEWARE_REGISTRY_SOURCE_FILE\n );\n\n if (!existsSync(middlewareRegistryPath)) {\n return;\n }\n\n const { build } = await import('tsdown');\n const serverOutDir = resolve(projectRoot, buildDirectory, SERVER_OUT_SUBDIR);\n const entryName = MIDDLEWARE_REGISTRY_SOURCE_FILE.replace(/\\.ts$/, '');\n\n await build({\n cwd: projectRoot,\n entry: { [entryName]: middlewareRegistryPath },\n outDir: serverOutDir,\n format: ['esm'],\n platform: 'node',\n outExtensions: () => ({ js: '.mjs', dts: '.d.ts' }),\n dts: false,\n clean: false,\n hash: false,\n noExternal: [/.*/],\n external: [/^node:/],\n });\n },\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { toPosixPath } from '../utils/paths';\n\n/**\n * File extensions to search when detecting ejected entry files.\n * Matches React Router's `entryExts` in its findEntry function.\n */\nconst ENTRY_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.mts'];\n\n/**\n * Query parameter appended to imports of ejected entry files within the\n * generated composition code. This creates a distinct module ID so Vite\n * treats it as a separate module from the one we intercept in `load`,\n * breaking what would otherwise be a circular import.\n *\n * Vite natively handles query parameters on file imports — it strips the\n * query for filesystem access but keeps it in the module ID for deduplication.\n */\nconst PASSTHROUGH_QUERY = '?platform-passthrough';\n\n/**\n * Finds a user-ejected entry file in the app directory.\n * Returns the absolute path if found, undefined otherwise.\n */\nfunction findUserEntry(appDirectory: string, basename: string): string | undefined {\n for (const ext of ENTRY_EXTENSIONS) {\n const filePath = path.resolve(appDirectory, basename + ext);\n if (fs.existsSync(filePath)) {\n return filePath;\n }\n }\n return undefined;\n}\n\n/**\n * Generates the virtual module code for the composed server entry.\n *\n * The generated module imports the app's entry (user-ejected or SDK default),\n * passes it through composeServerEntry(), and re-exports all ServerEntryModule\n * fields. This ensures platform features are always applied.\n */\nfunction generateServerEntryCode(appEntryImportPath: string): string {\n const importPath = JSON.stringify(toPosixPath(appEntryImportPath));\n return `\nimport * as _app from ${importPath};\nimport { composeServerEntry } from '@salesforce/storefront-next-dev/entry/server';\n\nconst _composed = composeServerEntry(_app);\n\n// Forward all named exports from the app entry so that any future\n// React Router exports are passed through without requiring a plugin update.\n// Explicit exports below take precedence over star re-exports per ESM spec.\nexport * from ${importPath};\n\n// Override with composed versions for exports the platform layer enhances.\nexport default _composed.default;\nexport const handleDataRequest = _composed.handleDataRequest;\nexport const handleError = _composed.handleError;\nexport const unstable_instrumentations = _composed.unstable_instrumentations;\nexport const streamTimeout = _composed.streamTimeout;\n`.trim();\n}\n\n/**\n * Generates the virtual module code for the composed client entry.\n *\n * Imports the platform client setup as a side-effect (runs before the app entry),\n * then re-exports everything from the app's client entry.\n */\nfunction generateClientEntryCode(appEntryImportPath: string): string {\n return `\nimport '@salesforce/storefront-next-dev/entry/client';\nexport * from ${JSON.stringify(toPosixPath(appEntryImportPath))};\n`.trim();\n}\n\ninterface ReactRouterPluginContext {\n reactRouterConfig: {\n appDirectory: string;\n buildDirectory: string;\n };\n entryClientFilePath: string;\n entryServerFilePath: string;\n}\n\n/**\n * Vite plugin that composes platform-level features into React Router entry files.\n *\n * This plugin uses the `load` hook to replace entry file contents with generated\n * composition code, while preserving the original file path as the module ID.\n * This is critical because React Router's post-build manifest lookup uses the\n * original entry file paths to find built chunks — changing the module ID (via\n * `resolveId`) would break that lookup.\n *\n * The plugin supports two modes:\n * - **Non-ejected:** No entry files in the app directory. The generated code\n * imports SDK default entries from `@salesforce/storefront-next-dev/entry/defaults/`.\n * - **Ejected:** Customer has created their own entry file(s). The generated code\n * imports the customer's file (with a `?platform-passthrough` query to avoid\n * circular imports) and wraps it with the platform layer.\n *\n * In both cases, the platform composition layer is always present. New platform\n * features ship via `npm update` by modifying the composition functions, without\n * changes to the plugin or customer code.\n */\nexport function platformEntryPlugin(): Plugin {\n let isTestMode = false;\n let serverEntryFilePath: string | undefined;\n let clientEntryFilePath: string | undefined;\n let appDirectory: string | undefined;\n let userServerEntryPath: string | undefined;\n let userClientEntryPath: string | undefined;\n\n return {\n name: 'storefront-next:platform-entry',\n enforce: 'pre',\n\n config(_config, { mode }) {\n isTestMode = mode === 'test';\n },\n\n configResolved(config: ResolvedConfig) {\n if (isTestMode) return;\n\n // @ts-expect-error: react-router plugin context is not typed\n const ctx: ReactRouterPluginContext | undefined = config.__reactRouterPluginContext;\n if (!ctx) return;\n\n appDirectory = ctx.reactRouterConfig.appDirectory;\n serverEntryFilePath = ctx.entryServerFilePath;\n clientEntryFilePath = ctx.entryClientFilePath;\n\n // Detect whether the user has ejected entry files and store the paths\n userServerEntryPath = findUserEntry(appDirectory, 'entry.server');\n userClientEntryPath = findUserEntry(appDirectory, 'entry.client');\n },\n\n load(id) {\n if (isTestMode || !serverEntryFilePath || !clientEntryFilePath || !appDirectory) return null;\n\n // Skip passthrough imports — these have the ?platform-passthrough query\n // and should load the real file content from disk. Vite handles this\n // natively by stripping the query for filesystem access.\n if (id.includes(PASSTHROUGH_QUERY)) return null;\n\n // Strip any existing query parameters for path comparison, but only\n // for matching — we still return null for IDs with unexpected queries.\n const idWithoutQuery = id.split('?')[0];\n\n if (path.normalize(idWithoutQuery) === path.normalize(serverEntryFilePath)) {\n // Always use the passthrough query on the original entry file path.\n // This ensures imports within the entry file (e.g., @react-router/node,\n // isbot) resolve in the app's dependency context, not the SDK's.\n // For ejected entries, this points to the user's file.\n // For non-ejected entries, this points to React Router's default.\n const appEntryPath = userServerEntryPath\n ? userServerEntryPath + PASSTHROUGH_QUERY\n : serverEntryFilePath + PASSTHROUGH_QUERY;\n\n return generateServerEntryCode(appEntryPath);\n }\n\n if (path.normalize(idWithoutQuery) === path.normalize(clientEntryFilePath)) {\n const appEntryPath = userClientEntryPath\n ? userClientEntryPath + PASSTHROUGH_QUERY\n : clientEntryFilePath + PASSTHROUGH_QUERY;\n\n return generateClientEntryCode(appEntryPath);\n }\n\n return null;\n },\n\n configureServer(server: ViteDevServer) {\n if (isTestMode || !appDirectory) return;\n\n // Capture as local const so TypeScript narrows the type in the callback\n const appDir = appDirectory;\n\n // Watch for creation/deletion of entry files in the app directory.\n // When a user ejects or un-ejects an entry file, restart the dev server\n // so the load hook re-evaluates which entry to import.\n const watcher = server.watcher;\n\n const checkEntryChange = (filePath: string) => {\n const relative = path.relative(appDir, filePath);\n const basename = path.basename(relative, path.extname(relative));\n const dir = path.dirname(relative);\n\n // Only react to entry files directly in the app directory (not nested)\n if (dir !== '.' || (basename !== 'entry.server' && basename !== 'entry.client')) {\n return;\n }\n\n const ext = path.extname(relative);\n if (!ENTRY_EXTENSIONS.includes(ext)) return;\n\n // Recheck ejection status\n const nowHasServer = findUserEntry(appDir, 'entry.server') !== undefined;\n const nowHasClient = findUserEntry(appDir, 'entry.client') !== undefined;\n\n const hadUserServerEntry = userServerEntryPath !== undefined;\n const hadUserClientEntry = userClientEntryPath !== undefined;\n if (nowHasServer !== hadUserServerEntry || nowHasClient !== hadUserClientEntry) {\n void server.restart();\n }\n };\n\n watcher.on('add', checkEntryChange);\n watcher.on('unlink', checkEntryChange);\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Server as HttpServer } from 'node:http';\nimport type { Plugin, HmrOptions } from 'vite';\n\n/**\n * Returns workspace-specific HMR configuration when running behind a workspace proxy.\n *\n * In workspace environments the OAuth2 proxy for the dev server's port is already\n * authenticated, so routing HMR WebSocket through the same HTTP server means it\n * shares the same proxy port and OAuth2 session. A separate port (e.g. port-8000)\n * would require its own OAuth2 login and return a 302 redirect that WebSocket\n * clients cannot follow.\n *\n * This is exported separately from the plugin because it requires the `httpServer`\n * reference created in `dev.ts`.\n *\n * @param httpServer - The Node HTTP server to attach the HMR WebSocket to.\n *\n * Environment variables:\n * - `EXTERNAL_DOMAIN_NAME` — The external hostname for the workspace proxy.\n * When it does not start with \"localhost\", workspace proxy mode is assumed.\n */\nexport function getWorkspaceHmrConfig(httpServer: HttpServer): HmrOptions | undefined {\n const externalDomain = process.env.EXTERNAL_DOMAIN_NAME;\n if (!externalDomain || externalDomain.startsWith('localhost')) return undefined;\n\n return {\n protocol: 'wss',\n host: externalDomain,\n clientPort: 443,\n server: httpServer,\n };\n}\n\n/**\n * Vite plugin that automatically configures workspace-specific settings when\n * SCAPI_PROXY_HOST is set. This includes:\n * - Disabling DIS (Dynamic Imaging Service) via PUBLIC__app__images__enableDis\n * - Adding dev server proxy rules for image paths (/dw/image, /on/demandware.static)\n * - Allowing all hosts for the dev server (workspace proxies use dynamic hostnames)\n *\n * Environment variables:\n * - `SCAPI_PROXY_HOST` — (Required) Base URL of the SCAPI proxy in workspace environments.\n * Enables workspace mode when set. Used as the proxy target for SCAPI requests and,\n * if JWEB_TARGET is not set, for static asset/image paths.\n * Example: `http://scw:25010`\n * - `JWEB_TARGET` — (Optional) Separate proxy target for JWeb static asset paths\n * (`/dw/image`, `/on/demandware.static`). Falls back to SCAPI_PROXY_HOST if not set.\n * Example: `http://jweb:8080`\n * - `PUBLIC__app__images__enableDis` — (Auto-set) Set to `'false'` when SCAPI_PROXY_HOST\n * is present, unless already explicitly configured. Controls whether the template\n * uses DIS for image format conversion and responsive srcsets.\n *\n * In workspace dev mode, this plugin also configures `optimizeDeps.entries` to scan all\n * source files upfront. Without this, Vite discovers deps lazily per-route and invalidates\n * the SSR module cache mid-session, leaving React in a partially-initialized state:\n * TypeError: Cannot read properties of null (reading 'useContext'/'useMemo')\n */\nexport const workspacePlugin = (): Plugin => {\n return {\n name: 'storefront-next-workspace',\n config(_, { mode }) {\n const scapiProxyHost = process.env.SCAPI_PROXY_HOST;\n if (!scapiProxyHost) return;\n\n // Disable DIS (Dynamic Imaging Service) in workspace environments.\n // Workspace JWeb doesn't support DIS, so the template handles all\n // DIS-related behavior changes based on this single flag.\n // Only set if not already explicitly configured.\n process.env.PUBLIC__app__images__enableDis ??= 'false';\n\n // Dev server proxy config (only in development mode)\n if (mode !== 'development') return;\n const jwebTarget = process.env.JWEB_TARGET;\n return {\n server: {\n allowedHosts: true as const,\n proxy: Object.fromEntries(\n ['/dw/image', '/on/demandware.static'].map((path) => [\n path,\n { target: jwebTarget || scapiProxyHost, changeOrigin: true, secure: false },\n ])\n ),\n },\n optimizeDeps: {\n // Scan all source files at startup so Vite pre-bundles every client dep\n // before serving requests. Without this, Vite discovers deps lazily per\n // route and invalidates the SSR module cache mid-session, causing React\n // context errors (TypeError: Cannot read properties of null).\n // Test files, stories, snapshots and .d.ts files are excluded because\n // they import Node.js-only packages (e.g. msw/node) that can't be\n // pre-bundled for the browser.\n entries: [\n './src/**/*.{ts,tsx}',\n '!./src/**/*.{test,spec}.{ts,tsx}',\n '!./src/**/*.stories.{ts,tsx}',\n '!./src/**/*-snapshot.tsx',\n '!./src/**/*.d.ts',\n ],\n },\n };\n },\n };\n};\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\nimport { parse } from '@babel/parser';\nimport _traverse from '@babel/traverse';\nimport { generate as _generate } from '@babel/generator';\nimport { deadCodeElimination, findReferencedIdentifiers } from 'babel-dead-code-elimination';\nimport {\n type ArrayPattern,\n isArrayPattern,\n isClassDeclaration,\n isExportSpecifier,\n isFunctionDeclaration,\n isIdentifier,\n isMemberExpression,\n isObjectPattern,\n isObjectProperty,\n isRestElement,\n isVariableDeclaration,\n type Node,\n type ObjectPattern,\n} from '@babel/types';\n\n// Handle CJS/ESM interop for babel packages (same pattern as target-utils.ts)\nconst traverse = (_traverse as unknown as { default: typeof _traverse }).default || _traverse;\nconst generate = (_generate as unknown as { default: typeof _generate }).default || _generate;\n\n/**\n * Names of exports to strip per environment.\n *\n * - `loader` is server-only → strip from the **client** build\n * - `clientLoader` is client-only → strip from the **server** build\n */\nconst STRIP_FROM_CLIENT = ['loader'] as const;\nconst STRIP_FROM_SERVER = ['clientLoader'] as const;\n\n/**\n * Configuration options for the component loader strip plugin.\n */\nexport interface ComponentLoadersPluginConfig {\n /**\n * Path prefix (relative to project root) that limits which files are\n * processed. Only modules whose resolved id contains this path segment\n * will be transformed.\n *\n * @default 'src/components'\n */\n componentPath?: string;\n}\n\n/**\n * Determines which export names should be stripped for a given Vite environment.\n */\nfunction getExportsToStrip(environmentName: string): ReadonlyArray<string> {\n // React Router / Vite convention: \"client\" for the browser bundle, \"ssr\" for the server bundle\n if (environmentName === 'client') {\n return STRIP_FROM_CLIENT;\n }\n if (environmentName === 'ssr') {\n return STRIP_FROM_SERVER;\n }\n return [];\n}\n\n/**\n * Returns `true` when the source code contains at least one of the given export names as a quick pre-check before\n * running the full AST transform.\n */\nfunction hasExportCandidate(code: string, names: ReadonlyArray<string>): boolean {\n return names.some((name) => code.includes(name));\n}\n\n/**\n * Checks whether the AST contains at least one class declaration decorated with `@Component(…)`.\n */\nexport function hasComponentDecorator(ast: ReturnType<typeof parse>): boolean {\n let found = false;\n traverse(ast, {\n ClassDeclaration(path) {\n const decorators = path.node.decorators;\n if (!decorators) return;\n for (const decorator of decorators) {\n // @Component(…) → CallExpression with callee Identifier \"Component\"\n if (\n decorator.expression.type === 'CallExpression' &&\n isIdentifier(decorator.expression.callee) &&\n decorator.expression.callee.name === 'Component'\n ) {\n found = true;\n path.stop();\n return;\n }\n }\n },\n });\n return found;\n}\n\n/**\n * Strips the specified named exports from the given source code using a\n * Babel AST transform.\n *\n * The transform handles the following patterns:\n *\n * 1. `export const loader = …;`\n * 2. `export function loader(…) {…}`\n * 3. `export class Loader {…}`\n * 4. `export { loader }` / `export { foo as loader }`\n * 5. `export { loader } from './loaders'`\n *\n * Destructured exports (`export const { loader } = …` or\n * `export const [loader] = …`) cannot be safely removed and will\n * throw an error if encountered (matching React Router behaviour).\n *\n * After removing an export, the transform also:\n * - Removes top-level property assignments to the stripped export\n * (e.g. `clientLoader.hydrate = true`)\n * - Removes any import declarations that become unused as a result\n *\n * @see {@link https://github.com/remix-run/react-router/blob/main/packages/react-router-dev/vite/remove-exports.ts React Router remove-exports}\n * @returns The transformed source code, or `null` if nothing was changed.\n */\nexport function stripExports(\n code: string,\n exportsToStrip: ReadonlyArray<string>,\n preParsedAst?: ReturnType<typeof parse>\n): string | null {\n const ast =\n preParsedAst ??\n parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx', 'decorators'],\n });\n\n let changed = false;\n\n // Snapshot referenced identifiers before any mutations so that `deadCodeElimination` can detect\n // newly-unreferenced bindings.\n const previouslyReferencedIdentifiers = findReferencedIdentifiers(ast);\n\n // Track local names of removed exports for property assignment cleanup (e.g. `clientLoader.hydrate = true`\n // after `export function clientLoader`)\n const removedExportLocalNames = new Set<string>();\n\n traverse(ast, {\n ExportNamedDeclaration(path) {\n const { declaration, specifiers } = path.node;\n\n // --- Pattern 1: export const <name> = … ---\n if (declaration && isVariableDeclaration(declaration)) {\n const remaining = declaration.declarations.filter((decl) => {\n if (isIdentifier(decl.id) && exportsToStrip.includes(decl.id.name)) {\n removedExportLocalNames.add(decl.id.name);\n return false; // remove this declarator\n }\n\n // Destructured exports cannot be safely removed — throw\n // (matching React Router behaviour)\n if (isArrayPattern(decl.id) || isObjectPattern(decl.id)) {\n validateDestructuredExports(decl.id, exportsToStrip);\n }\n\n return true;\n });\n\n if (remaining.length < declaration.declarations.length) {\n changed = true;\n if (remaining.length === 0) {\n // Remove the eslint-disable comment on the line above if present\n removeLeadingEslintDisableComment(path);\n path.remove();\n } else {\n declaration.declarations = remaining;\n }\n }\n return;\n }\n\n // --- Pattern 2: export function <name>() {} ---\n if (declaration && isFunctionDeclaration(declaration)) {\n if (declaration.id && exportsToStrip.includes(declaration.id.name)) {\n changed = true;\n removedExportLocalNames.add(declaration.id.name);\n removeLeadingEslintDisableComment(path);\n path.remove();\n return;\n }\n }\n\n // --- Pattern 3: export class <name> {} ---\n if (declaration && isClassDeclaration(declaration)) {\n if (declaration.id && exportsToStrip.includes(declaration.id.name)) {\n changed = true;\n removedExportLocalNames.add(declaration.id.name);\n removeLeadingEslintDisableComment(path);\n path.remove();\n return;\n }\n }\n\n // --- Pattern 4: export { loader } / export { foo as loader } ---\n // --- Pattern 5: export { loader } from './loaders' (re-export with source) ---\n if (specifiers.length > 0) {\n const remaining = specifiers.filter((spec) => {\n if (isExportSpecifier(spec)) {\n const exportedName = isIdentifier(spec.exported) ? spec.exported.name : spec.exported.value;\n if (exportsToStrip.includes(exportedName)) {\n // Track for property assignment cleanup\n // (e.g. `export { myFunc as loader }` → remove `loader.x = …`)\n removedExportLocalNames.add(spec.local.name);\n return false;\n }\n }\n return true;\n });\n\n if (remaining.length < specifiers.length) {\n changed = true;\n if (remaining.length === 0) {\n removeLeadingEslintDisableComment(path);\n path.remove();\n } else {\n path.node.specifiers = remaining;\n }\n }\n }\n },\n });\n\n // Second pass: remove top-level property assignments to stripped exports\n // (e.g. `clientLoader.hydrate = true`, `loader.displayName = \"...\"`)\n if (changed) {\n traverse(ast, {\n ExpressionStatement(path) {\n // Only handle top-level statements\n if (!path.parentPath?.isProgram()) {\n return;\n }\n\n if (path.node.expression.type === 'AssignmentExpression') {\n const left = path.node.expression.left;\n if (\n isMemberExpression(left) &&\n isIdentifier(left.object) &&\n (exportsToStrip.includes(left.object.name) || removedExportLocalNames.has(left.object.name))\n ) {\n removeLeadingEslintDisableComment(path);\n path.remove();\n }\n }\n },\n });\n }\n\n // Third pass: transitively remove all newly-unreferenced bindings (imports, helper functions, constants, etc.)\n // using the same library as React Router's remove-exports.\n if (changed) {\n deadCodeElimination(ast, previouslyReferencedIdentifiers);\n }\n\n if (!changed) {\n return null;\n }\n\n const output = generate(ast, { retainLines: true }, code);\n return output.code;\n}\n\n/**\n * Validates that no destructured export patterns contain names that should\n * be stripped. Destructured exports cannot be safely removed, so we throw\n * an error instead (matching React Router behaviour).\n */\nfunction validateDestructuredExports(id: ArrayPattern | ObjectPattern, exportsToStrip: ReadonlyArray<string>): void {\n if (isArrayPattern(id)) {\n for (const element of id.elements) {\n if (!element) continue;\n\n if (isIdentifier(element) && exportsToStrip.includes(element.name)) {\n throw new Error(`Cannot remove destructured export \"${element.name}\"`);\n }\n\n if (\n isRestElement(element) &&\n isIdentifier(element.argument) &&\n exportsToStrip.includes(element.argument.name)\n ) {\n throw new Error(`Cannot remove destructured export \"${element.argument.name}\"`);\n }\n\n if (isArrayPattern(element) || isObjectPattern(element)) {\n validateDestructuredExports(element, exportsToStrip);\n }\n }\n }\n\n if (isObjectPattern(id)) {\n for (const property of id.properties) {\n if (!property) continue;\n\n if (isObjectProperty(property) && isIdentifier(property.key)) {\n if (isIdentifier(property.value) && exportsToStrip.includes(property.value.name)) {\n throw new Error(`Cannot remove destructured export \"${property.value.name}\"`);\n }\n\n if (isArrayPattern(property.value) || isObjectPattern(property.value)) {\n validateDestructuredExports(property.value, exportsToStrip);\n }\n }\n\n if (\n isRestElement(property) &&\n isIdentifier(property.argument) &&\n exportsToStrip.includes(property.argument.name)\n ) {\n throw new Error(`Cannot remove destructured export \"${property.argument.name}\"`);\n }\n }\n }\n}\n\n/**\n * Removes a leading `// eslint-disable-next-line …` comment that sits on\n * the line immediately before the given path.\n */\nfunction removeLeadingEslintDisableComment(path: { node: Node }): void {\n const leadingComments = path.node.leadingComments;\n if (!leadingComments || leadingComments.length === 0) return;\n\n const last = leadingComments[leadingComments.length - 1];\n if (last.type === 'CommentLine' && last.value.includes('eslint-disable')) {\n leadingComments.pop();\n }\n}\n\n/**\n * Vite plugin that strips environment-specific loader exports from\n * component modules.\n *\n * Following the React Router convention:\n * - `export const loader` → server-only, stripped from the **client** bundle\n * - `export const clientLoader` → client-only, stripped from the **server** bundle\n *\n * This ensures that server-only code (e.g. API calls, database access) is\n * never included in the client bundle, and vice versa.\n *\n * The plugin only processes files that:\n * 1. Are under the configured `componentPath` directory\n * 2. Contain a `@Component` decorator (i.e. are Page Designer components)\n * 3. Are not test or story files\n */\nexport function componentLoadersPlugin(config: ComponentLoadersPluginConfig = {}): Plugin {\n const { componentPath = 'src/components' } = config;\n\n let isTestMode = false;\n\n return {\n name: 'storefrontnext:component-loaders',\n // Run before other transforms so stripped exports and their imports are\n // never seen by downstream plugins (TypeScript, React Refresh, etc.),\n // ensuring clean tree-shaking of server/client-only code.\n enforce: 'pre',\n\n configResolved(resolvedConfig) {\n isTestMode = resolvedConfig.mode === 'test';\n },\n\n transform(code, id) {\n // Do not strip exports during test runs — tests need the full module surface.\n if (isTestMode) {\n return null;\n }\n\n // Only process TS/TSX files in the component directory\n if (!id.includes(componentPath)) {\n return null;\n }\n if (!/\\.[mc]?[jt]sx?$/.test(id)) {\n return null;\n }\n // Skip test and story files\n if (/\\.(test|spec|stories)\\.[jt]sx?$/.test(id)) {\n return null;\n }\n\n // Access the Vite environment name (available in Vite 6+)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const environmentName: string | undefined = (this as any).environment?.name;\n if (!environmentName) {\n return null;\n }\n\n const exportsToStrip = getExportsToStrip(environmentName);\n if (exportsToStrip.length === 0) {\n return null;\n }\n\n // Quick string check before parsing the full AST\n if (!hasExportCandidate(code, exportsToStrip)) {\n return null;\n }\n\n // Parse once and reuse for both the `@Component` decorator check and stripping\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx', 'decorators'],\n });\n\n // Only process Page Designer components (classes with `@Component` decorator)\n if (!hasComponentDecorator(ast)) {\n return null;\n }\n\n const transformed = stripExports(code, exportsToStrip, ast);\n if (!transformed) {\n return null;\n }\n\n return {\n code: transformed,\n map: null,\n };\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { DevEnvironment, Plugin, ViteDevServer } from 'vite';\n\n// Inline sourcemap prefix produced by Vite's `genSourceMapUrl()`.\n// This is the only format Vite emits — it always uses base64 encoding.\nconst INLINE_SOURCEMAP_PREFIX = '//# sourceMappingURL=data:application/json;base64,';\n\n/**\n * Vite plugin that fixes SSR sourcemap `sources` to use full module paths\n * instead of bare basenames.\n *\n * **Problem:** When the React Router `v8_viteEnvironmentApi` future flag is\n * enabled, SSR modules are evaluated by Vite's Environment API module runner.\n * Vite's `ssrTransform` generates inline sourcemaps with\n * `sources: [path.basename(url)]` (e.g., `\"search.tsx\"` or `\"index.tsx\"`).\n * V8's debugger cannot resolve bare basenames back to files on disk, so\n * Chrome DevTools (via `--inspect`) displays the wrong source file content\n * when pausing at breakpoints — even for files with unique names.\n *\n * **Why `fetchModule`:** A Vite transform plugin cannot fix this because\n * `ssrTransform` runs *after* the plugin transform pipeline and overwrites\n * `map.sources` with `[path.basename(url)]`. The only viable interception\n * point is `fetchModule` — the public API method on `DevEnvironment` that\n * returns the final transformed code (with inline sourcemaps already embedded)\n * to the module runner.\n *\n * **Removable:** If Vite updates `ssrTransform` to use full paths instead of\n * `path.basename()`, this plugin can be deleted with no other changes.\n *\n * Only active in development mode (`configureServer` does not run in build).\n */\nexport function ssrSourcemapFixPlugin(): Plugin {\n return {\n name: 'storefront-next:ssr-sourcemap-fix',\n configureServer(server: ViteDevServer) {\n const ssrEnv: DevEnvironment | undefined = server.environments.ssr;\n if (!ssrEnv) return;\n\n const originalFetchModule = ssrEnv.fetchModule.bind(ssrEnv);\n\n ssrEnv.fetchModule = async (\n id: string,\n importer?: string,\n options?: { cached?: boolean; startOffset?: number }\n ) => {\n const result = await originalFetchModule(id, importer, options);\n\n // Only patch inlined modules (not externalized or cached ones)\n if (!result || 'externalize' in result || 'cache' in result || !('code' in result)) {\n return result;\n }\n\n const smIndex = result.code.lastIndexOf(INLINE_SOURCEMAP_PREFIX);\n if (smIndex === -1) {\n return result;\n }\n\n try {\n const base64Start = smIndex + INLINE_SOURCEMAP_PREFIX.length;\n const base64End = result.code.indexOf('\\n', base64Start);\n const base64Data =\n base64End === -1\n ? result.code.slice(base64Start).trim()\n : result.code.slice(base64Start, base64End).trim();\n\n const mapJson = JSON.parse(Buffer.from(base64Data, 'base64').toString('utf-8'));\n\n if (!mapJson.sources || !Array.isArray(mapJson.sources)) {\n return result;\n }\n\n // Use the absolute file path from the fetch result.\n // `file` is the on-disk path; `id` is the module graph identifier.\n // For virtual modules both may be null/empty — in that case we skip patching.\n const fileId = result.file || result.id;\n if (!fileId) {\n return result;\n }\n\n // ssrTransform always produces exactly one source entry. If a future\n // transform produces multiple sources, bail out — replacing every entry\n // with the same fileId would corrupt the sourcemap.\n if (mapJson.sources.length !== 1) {\n return result;\n }\n\n // A bare basename has no path separator. Vite normalizes all\n // paths to forward slashes, so checking for '/' is sufficient\n // on all platforms.\n const source: string | null = mapJson.sources[0];\n if (!source || source.includes('/')) {\n return result;\n }\n\n mapJson.sources = [fileId];\n\n const patchedBase64 = Buffer.from(JSON.stringify(mapJson)).toString('base64');\n result.code =\n result.code.slice(0, base64Start) +\n patchedBase64 +\n (base64End === -1 ? '' : result.code.slice(base64End));\n } catch {\n // Decoding or patching failed — return the original result unchanged.\n }\n\n return result;\n };\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\n\nexport interface I18nPluginConfig {\n /**\n * Pattern to match locale module IDs. Must contain a capture group for the language code.\n *\n * @default /\\/src\\/locales\\/([^/]+)\\//\n */\n localePattern?: RegExp;\n}\n\n/**\n * Vite plugin that splits locale translation files into per-language chunks.\n *\n * Wraps any existing `manualChunks` configuration so that locale modules are\n * assigned to `locales-{lang}` chunks (e.g., `locales-en-GB`) while all other\n * module IDs are delegated to the user's original `manualChunks` function or object.\n *\n * @param config - Optional configuration for custom locale patterns\n * @returns A Vite plugin that configures locale-based code splitting\n */\nexport function i18nPlugin(config?: I18nPluginConfig): Plugin {\n const pattern = config?.localePattern ?? /\\/src\\/locales\\/([^/]+)\\//;\n\n return {\n name: 'storefront-next:i18n',\n apply: 'build',\n config(viteConfig) {\n const output = viteConfig.build?.rollupOptions?.output;\n if (Array.isArray(output)) return;\n\n const existingManualChunks = output?.manualChunks;\n\n return {\n build: {\n rollupOptions: {\n output: {\n manualChunks(id, meta) {\n const localeMatch = id.match(pattern);\n if (localeMatch) {\n return `locales-${localeMatch[1]}`;\n }\n\n if (typeof existingManualChunks === 'function') {\n return existingManualChunks.call(this, id, meta);\n }\n if (existingManualChunks && typeof existingManualChunks === 'object') {\n for (const [name, ids] of Object.entries(existingManualChunks)) {\n if (ids.includes(id)) return name;\n }\n }\n },\n },\n },\n },\n };\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\n\n/**\n * Vite plugin contributing the baseline Vite config required by the\n * Storefront Next framework. These settings are uniform across every\n * customer project and are not intended to be customized.\n *\n * Additional framework-level Vite defaults should be added here rather\n * than in the template's vite.config.ts or in a new single-purpose plugin.\n *\n * Current defaults:\n * - `resolve.dedupe`: prevents duplicate React / React Router copies on the\n * client. Duplicate React instances cause hooks to throw \"Invalid hook call\".\n * - `optimizeDeps.include`: forces Vite's dep optimizer to pre-bundle\n * `react-router` and its `/internal/react-server-client` entry so the\n * React Router dev plugin resolves a single shared instance on the client.\n * It also pre-bundles the React-importing entry points of the runtime SDK\n * (`/config`, `/security/react`, `/site-context`, `/design/react/core`,\n * `/routing/app-wrapper`, `/i18n/client`). Without this, Vite optimizes the\n * app's own React first, then *discovers* these SDK subpaths the first time a\n * component imports them at request time, triggers a dep re-optimization, and\n * forces a full-page reload. During that window the SDK's React hooks\n * (`useConfig` → `useContext`) momentarily resolve against a second, freshly\n * optimized React instance — surfacing as \"Invalid hook call\" / \"Cannot read\n * properties of null (reading 'useContext')\" until the reload settles.\n * Including them up front means a single first-pass optimization with one\n * shared React, so no mid-session re-optimize and no transient duplicate.\n * - `ssr.noExternal`: forces the SDK through Vite's SSR transform pipeline\n * in dev. The SDK exports module-level singletons (router contexts, etc.)\n * whose object identity is load-bearing — they're used as `Map` keys, so\n * reads and writes must reference the same object. In dev SSR, Vite can\n * externalize a package for some import sites (loaded by Node's native\n * ESM resolver) while transforming it for others (loaded by Vite's SSR\n * transform pipeline), producing two distinct module records for the\n * same file on disk. When that happens, the singletons are constructed\n * twice and the keys no longer match — context lookups silently return\n * the default value. `noExternal` collapses both paths into Vite's\n * transform cache so there is exactly one module record. Production\n * builds inline the SDK into the SSR bundle and are unaffected. We\n * don't blanket-`noExternal` every dependency because most third-party\n * packages are identity-agnostic (two copies work fine) and externalizing\n * keeps dev startup fast — only packages exporting identity-sensitive\n * singletons need this treatment.\n *\n * @returns {Plugin} A Vite plugin contributing the framework's base config.\n */\nexport const baseConfigPlugin = (): Plugin => ({\n name: 'storefront-next:base-config',\n config() {\n return {\n resolve: {\n dedupe: ['react', 'react-dom', 'react-router'],\n },\n optimizeDeps: {\n include: [\n 'react-router',\n 'react-router/internal/react-server-client',\n // React-importing runtime SDK entry points. Pre-bundling these\n // in the first optimizer pass prevents a late discovery +\n // re-optimize that transiently loads a second React instance\n // (see optimizeDeps note above).\n '@salesforce/storefront-next-runtime/config',\n '@salesforce/storefront-next-runtime/security/react',\n '@salesforce/storefront-next-runtime/site-context',\n '@salesforce/storefront-next-runtime/design/react/core',\n '@salesforce/storefront-next-runtime/routing/app-wrapper',\n '@salesforce/storefront-next-runtime/i18n/client',\n // i18n peer deps that the runtime SDK imports *internally* and the\n // template does NOT import directly from its own source. Vite's\n // initial crawl scans the app's source graph, so a dep used only\n // inside the SDK is discovered late — the first time the SDK module\n // executes — triggering a re-optimize + reload (the same reload that\n // transiently duplicates React). `react-i18next` does NOT need listing\n // here because the template imports it directly in many components, so\n // the initial crawl already finds it.\n // - i18next-browser-languagedetector: imported by `/i18n/client`.\n // - remix-i18next/middleware: imported by the SDK's `/i18n` barrel\n // (createI18nMiddleware), which the template reaches via getTranslation.\n 'i18next-browser-languagedetector',\n 'remix-i18next/middleware',\n ],\n },\n ssr: {\n noExternal: ['@salesforce/storefront-next-runtime'],\n },\n };\n },\n});\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\nimport { fixReactRouterManifestUrlsPlugin } from './plugins/fixReactRouterManifestUrls';\nimport { readableChunkFileNamesPlugin } from './plugins/readableChunkFileNames';\nimport { managedRuntimeBundlePlugin } from './plugins/managedRuntimeBundle';\nimport { patchReactRouterPlugin } from './plugins/patchReactRouter';\nimport { transformTargetPlaceholderPlugin } from './plugins/transformTargets';\nimport { actionHooksPlugin } from './plugins/actionHooks';\nimport { watchConfigFilesPlugin } from './plugins/watchConfigFiles';\nimport { staticRegistryPlugin, type StaticRegistryPluginConfig } from './plugins/staticRegistry';\nimport {\n eventInstrumentationValidatorPlugin,\n type EventInstrumentationValidatorConfig,\n} from './plugins/eventInstrumentationValidator';\nimport { buildMiddlewareRegistryPlugin } from './plugins/buildMiddlewareRegistry';\nimport { platformEntryPlugin } from './plugins/platformEntry';\nimport { workspacePlugin } from './plugins/workspace';\nimport { componentLoadersPlugin } from './plugins/componentLoaders';\nimport { ssrSourcemapFixPlugin } from './plugins/ssrSourcemapFix';\nimport { i18nPlugin } from './plugins/i18n';\nimport { baseConfigPlugin } from './plugins/baseConfig';\n\n/**\n * Configuration options for the Storefront Next Vite plugin.\n */\nexport interface StorefrontNextTargetsConfig {\n /**\n * Enable human-readable chunk file names for easier debugging in production builds.\n * When enabled, chunk files will be named based on their source location\n * rather than just the file name and random hashes.\n *\n * This is useful to identify the chunk files and is usually used in development,\n * in conjunction with the bundle analyzer.\n *\n * Example:\n *\n * ```\n * (package)-(pkg-name)-index.[hash].js\n * (components)-(ui)-(inputs)-(TextField)-index.[hash].js\n * ```\n *\n * Instead of:\n *\n * ```\n * index.[hash].js\n * ```\n *\n * @default false\n */\n readableChunkNames?: boolean;\n\n /**\n * Configuration for the static registry plugin that automatically generates\n * component registrations based on @Component decorators.\n *\n * Set to `false` to disable the static registry plugin entirely.\n *\n * @default { componentPath: 'src/components', registryPath: 'src/lib/registry.ts' }\n */\n staticRegistry?: StaticRegistryPluginConfig;\n\n /**\n * Configuration for the event instrumentation validator plugin that validates\n * all enabled analytics event toggles have corresponding trackEvent() calls.\n *\n * Set to `false` to disable the validator entirely.\n *\n * @default { configPath: 'config.server.ts', scanPaths: ['src'], failOnMissing: false }\n */\n eventInstrumentationValidator?: EventInstrumentationValidatorConfig | false;\n}\n\n/**\n * Storefront Next Vite plugin that powers the React Router app.\n * Supports building and optimizing for the managed runtime environment.\n *\n * @param config - Configuration options for the plugin\n * @returns {Plugin[]} An array of Vite plugins for Storefront Next functionality\n *\n * @example\n * // With default options\n * export default defineConfig({\n * plugins: [storefrontNextTargets()]\n * })\n *\n * @example\n * // Disable readable chunk names\n * export default defineConfig({\n * plugins: [storefrontNextTargets({ readableChunkNames: false })]\n * })\n */\nexport function storefrontNextTargets(config: StorefrontNextTargetsConfig = {}): Plugin[] {\n const {\n readableChunkNames = false,\n staticRegistry = {\n componentPath: '',\n registryPath: '',\n },\n eventInstrumentationValidator = {\n configPath: 'config.server.ts',\n scanPaths: ['src'],\n failOnMissing: false,\n },\n } = config;\n\n const plugins: Plugin[] = [\n baseConfigPlugin(),\n ...(process.env.SCAPI_PROXY_HOST ? [workspacePlugin()] : []),\n i18nPlugin(),\n managedRuntimeBundlePlugin(),\n fixReactRouterManifestUrlsPlugin(),\n patchReactRouterPlugin(),\n platformEntryPlugin(),\n transformTargetPlaceholderPlugin(),\n actionHooksPlugin(),\n watchConfigFilesPlugin(),\n buildMiddlewareRegistryPlugin(),\n ssrSourcemapFixPlugin(),\n ];\n\n // Add static registry plugin if enabled\n if (staticRegistry?.componentPath && staticRegistry?.registryPath) {\n plugins.push(staticRegistryPlugin(staticRegistry));\n\n // Strip server-only `loader` exports from the client bundle and client-only `clientLoader` exports from the\n // server bundle\n plugins.push(\n componentLoadersPlugin({\n componentPath: staticRegistry.componentPath,\n })\n );\n }\n\n // Add event instrumentation validator plugin if not explicitly disabled\n if (eventInstrumentationValidator !== false) {\n plugins.push(eventInstrumentationValidatorPlugin(eventInstrumentationValidator));\n }\n\n if (readableChunkNames) {\n plugins.push(readableChunkFileNamesPlugin());\n }\n\n return plugins;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Plugin } from 'vite';\nimport { parse } from '@babel/parser';\nimport traverseModule from '@babel/traverse';\nimport { generate } from '@babel/generator';\nimport {\n type JSXAttribute,\n isJSXIdentifier,\n isJSXAttribute,\n isStringLiteral,\n jsxIdentifier,\n jsxAttribute,\n jsxExpressionContainer,\n booleanLiteral,\n importDeclaration,\n importSpecifier,\n identifier,\n stringLiteral,\n} from '@babel/types';\n\n// Handle CJS/ESM interop for @babel/traverse (same pattern as target-utils.ts)\nconst traverse = (traverseModule as unknown as { default: typeof traverseModule }).default || traverseModule;\nimport { logger } from '../logger';\n\n// Pre-compiled regex — avoids false substring matches on 'UITargetProviders'.\n// Mirrors TARGET_COMPONENT_JSX_RE in target-utils.ts.\nconst UI_TARGET_JSX_RE = /<UITarget[\\s/>]/;\n\nexport interface UITargetDevModeConfig {\n /**\n * Enable dev mode visual markers. Defaults to false.\n * Set via VITE_UI_TARGET_DEV_MODE=true environment variable.\n */\n enabled?: boolean;\n\n /**\n * Build-time filter: only show targets matching a category prefix.\n * Set via VITE_TARGET_FILTER_CATEGORY=pdp environment variable.\n * @example filterCategory: 'pdp' — only shows targets whose id starts with \"pdp.\"\n */\n filterCategory?: string;\n\n /**\n * Map of targetId → hint label for overlay grouping/filtering.\n * Built from target-config.json at config time and injected into each dev marker\n * as a __hint__ prop, which is emitted as a data-ui-target-hint DOM attribute.\n * Example: { 'emailSignUp.consent.marketing': 't/my-branch/W-123', 'checkout.contactInfo': 'pr:1384' }\n */\n hintMap?: Record<string, string>;\n}\n\n/**\n * Vite plugin that adds visual markers to UITarget components in development.\n *\n * PRODUCTION: This plugin is completely inactive - zero overhead.\n * DEVELOPMENT: Transforms UITarget JSX to add visual debugging markers.\n *\n * @example\n * // Source code:\n * <UITarget targetId=\"pdp.loyalty.badge\">\n * <Widget />\n * </UITarget>\n *\n * // Transformed in DEV mode:\n * <UITargetDevMarker\n * targetId=\"pdp.loyalty.badge\"\n * __file__=\"/src/components/product.tsx\"\n * __hasChildren__={true}\n * >\n * <Widget />\n * </UITargetDevMarker>\n */\nexport function uiTargetDevModePlugin(config: UITargetDevModeConfig = {}): Plugin {\n // Production: return no-op plugin\n if (process.env.NODE_ENV === 'production') {\n return {\n name: 'storefront-next:ui-target-dev-mode-noop',\n };\n }\n\n // Development: check if enabled\n const enabled = config.enabled ?? process.env.VITE_UI_TARGET_DEV_MODE === 'true';\n\n if (!enabled) {\n return {\n name: 'storefront-next:ui-target-dev-mode-disabled',\n };\n }\n\n logger.info('🎯 UITarget Dev Mode enabled');\n\n if (config.filterCategory) {\n logger.info(` Filtering to category: ${config.filterCategory} (build-time)`);\n }\n\n return {\n name: 'storefront-next:ui-target-dev-mode',\n\n enforce: 'pre', // Run BEFORE the production transform so UITarget is still in the source\n\n transform(code: string, id: string) {\n // Only process .tsx/.jsx files\n if (!id.match(/\\.(tsx|jsx)$/)) {\n return null;\n }\n\n // Skip node_modules\n if (id.includes('node_modules')) {\n return null;\n }\n\n // Skip the UITarget component itself\n if (id.includes('/targets/ui-target.tsx')) {\n return null;\n }\n\n // Quick check: does this file contain UITarget JSX?\n // UI_TARGET_JSX_RE avoids false matches on 'UITargetProviders' (which contains 'UITarget').\n // Also verify the import is present to avoid processing files that just reference the string.\n if (!UI_TARGET_JSX_RE.test(code) || !code.includes(\"from '@/targets/ui-target'\")) {\n return null;\n }\n\n try {\n // Parse AST\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n });\n\n let hasTransforms = false;\n const targetIds: string[] = [];\n\n // Find and transform UITarget usages\n traverse(ast, {\n JSXElement(path) {\n const openingElement = path.node.openingElement;\n\n // Check if this is <UITarget>\n if (!isJSXIdentifier(openingElement.name) || openingElement.name.name !== 'UITarget') {\n return;\n }\n\n // Extract targetId attribute\n const targetIdAttr = openingElement.attributes.find(\n (attr) =>\n isJSXAttribute(attr) && isJSXIdentifier(attr.name) && attr.name.name === 'targetId'\n ) as JSXAttribute | undefined;\n\n if (!targetIdAttr) {\n logger.warn(`UITarget without targetId in ${id}`);\n return;\n }\n\n // Get targetId value\n const targetId = isStringLiteral(targetIdAttr.value) ? targetIdAttr.value.value : null;\n\n if (!targetId) {\n logger.warn(`UITarget with non-string targetId in ${id}`);\n return;\n }\n\n // Apply build-time category filter\n if (config.filterCategory && !targetId.startsWith(`${config.filterCategory}.`)) {\n return;\n }\n\n // Determine if this target has children\n const hasChildren = path.node.children.length > 0;\n\n // Transform: UITarget → UITargetDevMarker\n openingElement.name = jsxIdentifier('UITargetDevMarker');\n if (path.node.closingElement) {\n path.node.closingElement.name = jsxIdentifier('UITargetDevMarker');\n }\n\n // Add metadata attributes\n const hint = config.hintMap?.[targetId];\n openingElement.attributes.push(\n jsxAttribute(jsxIdentifier('__file__'), stringLiteral(id)),\n jsxAttribute(\n jsxIdentifier('__hasChildren__'),\n jsxExpressionContainer(booleanLiteral(hasChildren))\n ),\n ...(hint ? [jsxAttribute(jsxIdentifier('__hint__'), stringLiteral(hint))] : [])\n );\n\n hasTransforms = true;\n targetIds.push(targetId);\n },\n });\n\n if (!hasTransforms) {\n return null;\n }\n\n // Add import for UITargetDevMarker at top of file\n const devMarkerImport = importDeclaration(\n [importSpecifier(identifier('UITargetDevMarker'), identifier('UITargetDevMarker'))],\n stringLiteral('@/lib/ui-target-dev-mode/marker')\n );\n\n ast.program.body.unshift(devMarkerImport);\n\n // Generate transformed code\n const output = generate(\n ast,\n {\n retainLines: true,\n compact: false,\n },\n code\n );\n\n logger.debug(`Transformed ${targetIds.length} targets in ${id.split('/').pop()}`);\n\n return {\n code: output.code,\n map: output.map,\n };\n } catch (error) {\n logger.error(`Failed to transform UITarget in ${id}:`, error);\n return null; // Return original code on error\n }\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Vite plugin for hybrid proxying between Storefront Next and legacy SFRA.\n *\n * **LOCAL DEVELOPMENT ONLY** - This plugin only works with `pnpm dev` (Vite dev server).\n * For MRT/production deployments, use Cloudflare eCDN routing instead.\n *\n * ## Dependency injection via `routeMatcher`\n *\n * This plugin accepts a `routeMatcher` callback instead of importing routing logic\n * directly. The template owns the routing logic (e.g., `ecdn-matcher.ts`) and passes\n * it in when configuring the plugin. This keeps the SDK package pure — it knows nothing\n * about template internals — while allowing the template to inject merchant-specific\n * routing rules.\n *\n * The same `routeMatcher` pattern can be used by the future MRT server middleware.\n *\n * ## How it works\n *\n * 1. Intercepts HTTP requests BEFORE React Router sees them\n * 2. Checks routing rules: matching routes → React Router, non-matching → proxy to SFCC\n * 3. Rewrites paths: /cart → /s/{siteId}/{locale}/cart (SFRA format)\n * 4. Rewrites cookies: Domain=.salesforce.com → Domain=localhost (session continuity)\n * 5. Rewrites HTML/JSON bodies: replaces SFCC origin URLs with localhost (keeps client-side nav proxied)\n *\n * This enables seamless navigation between Next pages (/) and SFRA pages (/cart) without\n * visible redirects or session loss. The browser URL stays localhost:5173/cart.\n *\n * ## Environment variables\n *\n * - HYBRID_PROXY_ENABLED (required) - 'true' to enable the plugin\n * - HYBRID_ROUTING_RULES (required) - Cloudflare routing expression (routes matching go to Next)\n * - SFCC_ORIGIN (required) - SFCC sandbox URL (e.g., https://zzrf-001.dx.commercecloud.salesforce.com)\n * - HYBRID_PROXY_LOCALE (optional) - Locale for SFRA path transformation (e.g., 'en-GB')\n *\n * The host plugin is responsible for sourcing `defaultSiteId` and `locale` from the\n * template's config (e.g. `config.server.ts`) and/or env vars and passing them in.\n */\n\nimport type { Plugin, ViteDevServer } from 'vite';\nimport httpProxy from 'http-proxy';\nimport type { IncomingMessage } from 'http';\nimport { gunzipSync, brotliDecompressSync, inflateSync } from 'zlib';\nimport { logger } from '../logger';\n\nexport interface HybridProxyPluginOptions {\n /** Whether hybrid proxying is enabled */\n enabled: boolean;\n /** SFCC origin URL to proxy non-matching routes to */\n targetOrigin: string;\n /** Cloudflare routing expression (routes matching go to Next) */\n routingRules: string;\n /**\n * Callback that decides if a pathname should be handled by Storefront Next.\n * Called for every request that isn't a Vite internal or SFCC path.\n * Receives the pathname and the raw `routingRules` string; returns true to\n * let React Router handle it, false to proxy to SFCC.\n *\n * @example\n * import { shouldRouteToNext } from './src/lib/ecdn-matcher';\n * hybridProxyPlugin({ routeMatcher: shouldRouteToNext, ... })\n */\n routeMatcher: (pathname: string, routingRules: string) => boolean;\n /** SFCC default site ID (e.g., 'RefArchGlobal'). Required when `enabled` is true. */\n defaultSiteId?: string;\n /** Locale for SFRA paths (e.g., 'en-GB'). Defaults to 'default' if not provided. */\n locale?: string;\n}\n\n/**\n * Check if a request path should skip proxying (Vite internals, assets, etc.)\n *\n * @param pathname - URL pathname to check\n * @returns true if the request should NOT be proxied\n */\nexport function shouldSkipProxy(pathname: string): boolean {\n // Vite virtual modules (@vite/client, @fs/, @id/, etc.)\n if (pathname.startsWith('/@')) return true;\n\n // Vite dev server internals\n if (pathname.startsWith('/__')) return true;\n\n // Source files served by Vite\n if (pathname.startsWith('/src/')) return true;\n\n // Node modules\n if (pathname.startsWith('/node_modules/')) return true;\n\n // React Router data requests\n if (pathname.endsWith('.data')) return true;\n\n // SCAPI proxy paths (handled by React Router)\n if (pathname.startsWith('/mobify/')) return true;\n\n // SFRA static assets - these MUST be proxied to SFCC\n // /on/demandware.static/... - static assets (CSS, JS, images)\n // /on/demandware.store/... - dynamic endpoints\n if (pathname.startsWith('/on/demandware.')) {\n return false; // DO proxy these to SFCC\n }\n\n // Vite build output and other asset files (served by Vite/React Router)\n // Only skip if NOT an SFRA path (checked above)\n if (/\\.(js|jsx|ts|tsx|css|json|map|woff2?|ttf|svg|png|jpe?g|gif|webp|ico|mp4)$/i.test(pathname)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Rewrite Set-Cookie header for localhost development.\n *\n * Rewrites SFCC Set-Cookie headers so they work on localhost during local development.\n *\n * **LOCAL DEVELOPMENT ONLY** — This function is part of the hybrid proxy Vite plugin\n * which only runs during `pnpm dev`. In production (MRT deployments), SFCC cookies\n * flow through the eCDN unmodified.\n *\n * Rewrites applied:\n * - **Domain**: `.salesforce.com` → `localhost` (browsers reject cross-domain cookies)\n *\n * Attributes intentionally preserved:\n * - **Secure**: Kept. Localhost is a secure context — browsers accept `Secure` cookies\n * on `http://localhost` (see https://w3c.github.io/webappsec-secure-contexts/).\n * - **SameSite**: Kept. `SameSite=None; Secure` is valid on localhost since `Secure`\n * is accepted. This keeps SFCC cookies transparent and in sync with Storefront Next\n * cookies, which is critical for hybrid auth session bridging.\n *\n * @param cookie - Original Set-Cookie header value from SFCC\n * @returns Rewritten cookie suitable for localhost\n *\n * @example\n * Input: \"dwsid=abc123; Domain=.salesforce.com; Path=/; Secure; SameSite=None; HttpOnly\"\n * Output: \"dwsid=abc123; Domain=localhost; Path=/; Secure; SameSite=None; HttpOnly\"\n */\nexport function rewriteCookieForLocalhost(cookie: string): string {\n let rewritten = cookie;\n\n // Replace Domain= with localhost (case-insensitive)\n rewritten = rewritten.replace(/Domain=[^;]+/gi, 'Domain=localhost');\n\n // Add Domain=localhost if not present\n if (!/Domain=/i.test(cookie)) {\n // Insert after first attribute (cookie name=value)\n rewritten = rewritten.replace(/^([^;]+)/, '$1; Domain=localhost');\n }\n\n return rewritten.trim();\n}\n\n/**\n * Inline script injected into proxied HTML responses to intercept `document.cookie` writes.\n *\n * **Why this is needed (Layer 3 cookie rewriting):**\n *\n * The hybrid proxy rewrites Set-Cookie headers from SFCC responses (Layer 1), but after\n * the SFRA page fully loads, client-side JavaScript sets cookies via `document.cookie`.\n * These writes bypass the proxy entirely.\n *\n * SFRA's JS typically checks `window.location.protocol` to decide whether to add `Secure`.\n * On `http://localhost`, it sees `http:` and omits `Secure`, producing cookies like:\n *\n * document.cookie = \"dwsid=abc; SameSite=None\" // No Secure → browser rejects\n *\n * This interceptor patches `document.cookie` to:\n * 1. Rewrite `Domain=...` → `Domain=localhost`\n * 2. Ensure `Secure` is present (localhost is a secure context)\n * 3. If `SameSite=None` is present without `Secure`, add `Secure`\n *\n * This keeps client-side cookie writes consistent with the proxy's Layer 1 rewrites\n * and ensures hybrid auth cookies (dwsid, cc-*) stay in sync between Storefront Next\n * and SFRA.\n *\n * **LOCAL DEVELOPMENT ONLY** — This script is only injected by the Vite dev server proxy.\n */\nconst COOKIE_INTERCEPTOR_SCRIPT = `<script data-hybrid-proxy=\"cookie-interceptor\">\n(function() {\n var desc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');\n if (!desc || !desc.set) return;\n Object.defineProperty(document, 'cookie', {\n get: function() { return desc.get.call(this); },\n set: function(val) {\n // Rewrite Domain to localhost\n val = val.replace(/Domain=[^;]+/gi, 'Domain=localhost');\n // Ensure Secure is present if SameSite=None (localhost is a secure context)\n if (/SameSite=None/i.test(val) && !/;\\\\s*Secure\\\\b/i.test(val)) {\n val += '; Secure';\n }\n desc.set.call(this, val);\n },\n configurable: true\n });\n})();\n</script>`;\n\n/**\n * Escape special regex characters in a string for use in `new RegExp()`.\n */\nfunction escapeRegExp(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Discriminated result of {@link rewriteLocationForProxy}.\n *\n * - `rewritten` — Location was same-origin; the proxy URL is in `url`.\n * - `off-origin` — Location points elsewhere; pass the original header through.\n * - `malformed` — Location couldn't be parsed; caller should warn and pass through.\n */\nexport type LocationRewriteResult = { kind: 'rewritten'; url: string } | { kind: 'off-origin' } | { kind: 'malformed' };\n\n/**\n * Rewrite an SFCC Location header so the user stays on the proxy origin AND so\n * any query params from the original request survive an SFCC redirect.\n *\n * SFCC frequently redirects bare paths like `/cart` to a canonical SFRA URL\n * (`/s/{siteId}/{locale}/Cart-Show`) without echoing the user's query string in\n * the Location header. Without this merge step, params like `?foo=bar` set by\n * the storefront — including ones the destination page expects — get dropped on\n * the cross-app hop.\n *\n * Resolution rules:\n * - Off-origin Location → `{ kind: 'off-origin' }` so the caller leaves the\n * header unchanged and the browser navigates as SFCC intended.\n * - Same-origin Location → rewrite the origin to the proxy host, then merge the\n * original request's query params into the redirect target's URL. Multi-value\n * keys (e.g. SFRA's `pmid=...&pmid=...`) are preserved on both sides. The\n * redirect target wins on collision so SFCC can intentionally override a key.\n * - Malformed Location → `{ kind: 'malformed' }` so the caller can warn.\n *\n * @param locationHeader - Raw Location header value from the SFCC response.\n * @param requestUrl - Original request URL on the proxy (e.g. `/cart?foo=bar`).\n * @param targetOrigin - SFCC origin used as the base for relative Location values.\n * @param proxyOrigin - Proxy origin (e.g. `http://localhost:5173`) the caller wants the user to stay on.\n */\nexport function rewriteLocationForProxy({\n locationHeader,\n requestUrl,\n targetOrigin,\n proxyOrigin,\n}: {\n locationHeader: string;\n requestUrl: string;\n targetOrigin: string;\n proxyOrigin: string;\n}): LocationRewriteResult {\n let locationUrl: URL;\n try {\n locationUrl = new URL(locationHeader, targetOrigin);\n } catch {\n return { kind: 'malformed' };\n }\n\n if (locationUrl.origin !== targetOrigin) {\n return { kind: 'off-origin' };\n }\n\n let requestQuery: URLSearchParams;\n try {\n requestQuery = new URL(requestUrl, proxyOrigin).searchParams;\n } catch {\n requestQuery = new URLSearchParams();\n }\n\n // Snapshot the redirect target's keys before mutating, so `has(key)` reflects\n // only what SFCC put there. Without this snapshot, the first appended value\n // for a multi-value key would mark the key \"present\" and drop the rest —\n // breaking SFRA patterns that repeat keys (e.g. `pmid=PROMO1&pmid=PROMO2`).\n const targetKeys = new Set(locationUrl.searchParams.keys());\n for (const [key, value] of requestQuery) {\n if (!targetKeys.has(key)) {\n locationUrl.searchParams.append(key, value);\n }\n }\n\n return {\n kind: 'rewritten',\n url: `${proxyOrigin}${locationUrl.pathname}${locationUrl.search}${locationUrl.hash}`,\n };\n}\n\n/** Cap loud values in debug logs so a long URL doesn't drown the dev console. */\nfunction truncateForLog(value: string, max = 120): string {\n return value.length > max ? `${value.slice(0, max)}…` : value;\n}\n\n/**\n * Vite plugin for hybrid proxying between Storefront Next and legacy SFRA.\n *\n * Uses http-proxy to silently forward non-matching requests to SFCC without visible\n * redirects. Rewrites Set-Cookie headers, Location headers, and HTML/JSON response\n * bodies to keep all navigation within the localhost proxy.\n *\n * Routing decisions are delegated to the `routeMatcher` callback injected via options,\n * keeping the SDK free of template-specific routing logic.\n *\n * @param options - Plugin configuration\n * @returns Vite plugin\n */\nexport function hybridProxyPlugin(options: HybridProxyPluginOptions): Plugin {\n if (!options.enabled) {\n logger.debug('Hybrid proxy disabled (HYBRID_PROXY_ENABLED is not true)');\n return {\n name: 'hybrid-proxy',\n };\n }\n\n if (!options.targetOrigin) {\n logger.warn('Hybrid proxy: no target origin configured (SFCC_ORIGIN required)');\n return {\n name: 'hybrid-proxy',\n };\n }\n\n if (!options.defaultSiteId) {\n throw new Error(\n 'Hybrid proxy is enabled but no default site ID was provided.\\n\\n' +\n 'Set PUBLIC__app__defaultSiteId in your .env file:\\n' +\n ' PUBLIC__app__defaultSiteId=RefArchGlobal\\n\\n' +\n 'See docs/README-HYBRID-PROXY.md for the full reference.'\n );\n }\n\n logger.info(`Hybrid proxy enabled → ${options.targetOrigin}`);\n logger.debug(`Hybrid proxy routing rules: ${options.routingRules.slice(0, 100)}...`);\n const locale = options.locale || 'default';\n const defaultSiteId = options.defaultSiteId;\n logger.debug(\n `Hybrid proxy path transformation: / → /s/${defaultSiteId}, /path → /s/${defaultSiteId}/${locale}/path`\n );\n\n // Pre-compile regex for URL rewriting in response bodies\n const targetOriginPattern = new RegExp(escapeRegExp(options.targetOrigin), 'g');\n\n // Create http-proxy instance\n // selfHandleResponse: true prevents http-proxy from automatically piping the\n // proxy response to the client. This lets us buffer HTML responses and rewrite\n // SFCC URLs to localhost, keeping client-side navigation within the proxy.\n const proxy = httpProxy.createProxyServer({\n changeOrigin: true,\n followRedirects: false,\n selfHandleResponse: true,\n });\n\n // Rewrite request path to SFRA format (/s/{siteId}/{locale}/path)\n proxy.on('proxyReq', (proxyReq, req) => {\n const url = new URL(req.url || '', `http://${req.headers.host}`);\n const pathname = url.pathname;\n\n // Check if path needs SFRA decoration\n // Skip if:\n // - Already has /s/ prefix\n // - Is an /on/demandware.* path (already in SFCC format)\n const needsTransformation = !pathname.startsWith('/s/') && !pathname.startsWith('/on/demandware.');\n\n if (needsTransformation) {\n const originalPath = proxyReq.path;\n /**\n * \"/\" maps to the SFRA/SiteGenesis site root — no locale in the path\n * This would simply proxy to SFCC hostname (eg.: https://zzrf-001.dx.commercecloud.salesforce.com/s/{siteId}/{locale}/) which is not a valid storefront URL.\n * We need to rewrite the path to /s/{siteId} so that it can be proxied to the correct SFCC URL.\n */\n if (pathname === '/') {\n proxyReq.path = `/s/${defaultSiteId}${url.search}`;\n } else {\n // Rewrite internal proxy path without changing browser URL\n proxyReq.path = `/s/${defaultSiteId}/${locale}${pathname}${url.search}`;\n }\n logger.debug(`Hybrid proxy path rewrite: ${originalPath} → ${proxyReq.path}`);\n }\n });\n\n // Handle all proxy responses manually (required by selfHandleResponse: true).\n // For HTML responses: buffer body, decompress, rewrite SFCC URLs to localhost.\n // For non-HTML responses: pipe through with header rewrites only.\n proxy.on('proxyRes', (proxyRes: IncomingMessage, req, res) => {\n const clientRes = res;\n\n // --- Safety net: detect SFCC error redirects ---\n // When SFCC doesn't recognize a URL it redirects to its 404 page and clears\n // session cookies. This usually means the routing rules are misconfigured —\n // a path that should go to Storefront Next is being proxied to SFCC instead.\n // Strip Set-Cookie headers from these responses to prevent cookie corruption.\n //\n // Both shapes of error redirect are caught: a real 3xx redirect to /404, and\n // a SFRA `plugin_redirect` 200+Location pointing at /404. The 200 case would\n // otherwise reach the 200→302 normalization below and forward SFRA's\n // session-clear cookies to the browser as a \"real\" redirect — wiping the\n // hybrid session even though the shopper can recover by navigating to a\n // valid URL.\n const locationHeader = proxyRes.headers.location;\n const statusCode = proxyRes.statusCode || 200;\n const isRedirectToError =\n typeof locationHeader === 'string' &&\n /\\/404\\b/.test(locationHeader) &&\n ((statusCode >= 300 && statusCode < 400) || statusCode === 200);\n\n if (isRedirectToError) {\n logger.warn(\n `⚠️ SFCC returned a redirect to 404 for ${req.url}. ` +\n `This usually means your HYBRID_ROUTING_RULES are missing a pattern for this path. ` +\n `Stripping Set-Cookie headers to prevent session cookie corruption. ` +\n `Fix: add a matching pattern to HYBRID_ROUTING_RULES (e.g., \"^${req.url?.split('?')[0]}.*\")`\n );\n delete proxyRes.headers['set-cookie'];\n }\n\n // --- Header rewrites (apply to ALL responses) ---\n\n // Rewrite Set-Cookie headers for localhost (skip if already stripped above)\n const setCookieHeaders = proxyRes.headers['set-cookie'];\n if (setCookieHeaders && Array.isArray(setCookieHeaders)) {\n proxyRes.headers['set-cookie'] = setCookieHeaders.map((cookie) => {\n const rewritten = rewriteCookieForLocalhost(cookie);\n logger.debug(`Hybrid proxy cookie rewrite: ${cookie.slice(0, 50)}... → ${rewritten.slice(0, 50)}...`);\n return rewritten;\n });\n }\n\n // Rewrite Location header in redirects to keep user on localhost AND merge\n // the original request's query params into the redirect target. SFCC redirects\n // bare paths like `/cart` to a canonical SFRA URL without echoing the request's\n // query string, so params like `?foo=bar` would be dropped on the cross-app hop\n // without this merge.\n //\n // Same-origin redirects also get the SFRA `plugin_redirect` 200+Location\n // normalization: that cartridge sometimes returns HTTP 200 with a Location\n // header instead of a proper 3xx redirect, and browsers only follow Location\n // on 3xx responses. We convert 200+Location into a 302 short-circuit so the\n // browser actually follows the redirect. The conversion is nested inside the\n // `rewritten` branch because (a) same-origin is the trust boundary we're\n // willing to coerce a 302 from, and (b) `result.url` is the value we need to\n // emit. An external 200+Location flows through unchanged via the `off-origin`\n // branch, matching the existing trust boundary.\n if (locationHeader && typeof locationHeader === 'string') {\n const proxyOrigin = `http://${req.headers.host}`;\n const result = rewriteLocationForProxy({\n locationHeader,\n requestUrl: req.url ?? '',\n targetOrigin: options.targetOrigin,\n proxyOrigin,\n });\n if (result.kind === 'rewritten') {\n proxyRes.headers.location = result.url;\n logger.debug(\n `Hybrid proxy location rewrite: ${truncateForLog(locationHeader)} → ${truncateForLog(result.url)}`\n );\n\n if (statusCode === 200) {\n proxyRes.resume(); // drain upstream — we're discarding the body\n const redirectHeaders: Record<string, string | string[]> = {\n location: result.url,\n };\n const setCookie = proxyRes.headers['set-cookie'];\n if (setCookie) {\n redirectHeaders['set-cookie'] = setCookie;\n }\n clientRes.writeHead(302, redirectHeaders);\n clientRes.end();\n logger.debug(\n `Hybrid proxy normalized 200+Location → 302 for ${req.url} (Location: ${truncateForLog(result.url)})`\n );\n return;\n }\n } else if (result.kind === 'malformed') {\n logger.warn(`Hybrid proxy: invalid Location header: ${truncateForLog(locationHeader)}`);\n }\n // off-origin: leave the header alone so the browser follows SFCC's intended redirect\n }\n\n // --- Response body handling ---\n\n const contentType = (proxyRes.headers['content-type'] || '').split(';')[0].trim();\n const isRewritable = contentType === 'text/html' || contentType === 'application/json';\n\n if (!isRewritable) {\n // Non-HTML/JSON: write headers and pipe body through unchanged\n clientRes.writeHead(proxyRes.statusCode || 200, proxyRes.headers);\n proxyRes.pipe(clientRes);\n return;\n }\n\n // HTML or JSON: buffer the response body for URL rewriting\n const chunks: Buffer[] = [];\n proxyRes.on('data', (chunk: Buffer) => chunks.push(chunk));\n proxyRes.on('end', () => {\n let body: Buffer<ArrayBufferLike> = Buffer.concat(chunks);\n\n // Decompress if needed\n const encoding = proxyRes.headers['content-encoding'];\n if (encoding === 'gzip') {\n body = gunzipSync(body);\n } else if (encoding === 'br') {\n body = brotliDecompressSync(body);\n } else if (encoding === 'deflate') {\n body = inflateSync(body);\n }\n\n // Rewrite SFCC origin URLs to localhost so client-side navigation stays proxied\n const proxyOrigin = `http://${req.headers.host}`;\n let text = body.toString('utf8');\n // Reset lastIndex since the regex has the global flag and is reused across calls\n targetOriginPattern.lastIndex = 0;\n text = text.replace(targetOriginPattern, proxyOrigin);\n\n // Inject document.cookie interceptor into HTML responses.\n // Must run before any SFRA script, so inject at the start of <head>.\n if (contentType === 'text/html') {\n const headIndex = text.indexOf('<head');\n if (headIndex !== -1) {\n const insertAfter = text.indexOf('>', headIndex);\n if (insertAfter !== -1) {\n text = text.slice(0, insertAfter + 1) + COOKIE_INTERCEPTOR_SCRIPT + text.slice(insertAfter + 1);\n }\n }\n }\n\n // Update headers: remove content-encoding (we decompressed) and fix content-length\n const headers = { ...proxyRes.headers };\n delete headers['content-encoding'];\n delete headers['transfer-encoding'];\n headers['content-length'] = String(Buffer.byteLength(text, 'utf8'));\n\n clientRes.writeHead(proxyRes.statusCode || 200, headers);\n clientRes.end(text);\n\n logger.debug(`Hybrid proxy rewrote ${contentType} body URLs for ${req.url}`);\n });\n });\n\n // Error handling\n proxy.on('error', (err, req, res) => {\n logger.error(`Hybrid proxy error: ${err.message} ${req.url}`);\n if ('writeHead' in res && !res.headersSent) {\n res.writeHead(502, { 'Content-Type': 'text/plain' });\n res.end('Bad Gateway: Failed to proxy to SFCC');\n }\n });\n\n return {\n name: 'hybrid-proxy',\n enforce: 'pre', // Run before Vite's internal middleware\n\n configureServer(server: ViteDevServer) {\n server.middlewares.use((req, res, next) => {\n const pathname = req.url?.split('?')[0] || '';\n\n // Skip Vite internals and assets\n if (shouldSkipProxy(pathname)) {\n return next();\n }\n\n // SFCC paths always proxy (even if routing rules don't explicitly exclude them)\n // These are internal SFCC endpoints and static assets\n const isSFCCPath = pathname.startsWith('/on/demandware.');\n\n // Check routing rules (unless it's an SFCC path)\n let shouldRouteToNextApp = false;\n if (!isSFCCPath) {\n try {\n shouldRouteToNextApp = options.routeMatcher(pathname, options.routingRules);\n } catch (error) {\n // Fail-safe: if routing check fails, let React Router handle it\n logger.error(`Hybrid proxy error checking routing rules: ${String(error)}`);\n return next();\n }\n\n if (shouldRouteToNextApp) {\n // Let React Router handle this route\n return next();\n }\n }\n\n // Proxy to SFCC\n logger.debug(`Hybrid proxy: ${req.method} ${pathname} → ${options.targetOrigin}`);\n\n try {\n proxy.web(req, res, {\n target: options.targetOrigin,\n });\n } catch (error) {\n logger.error(`Hybrid proxy failed to proxy request: ${String(error)}`);\n if (!res.headersSent) {\n res.writeHead(502, { 'Content-Type': 'text/plain' });\n res.end('Bad Gateway: Failed to proxy to SFCC');\n }\n }\n });\n },\n };\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { logger } from '../logger';\n\n/**\n * Cloudflare eCDN Routing Rule Matcher\n *\n * Parses Cloudflare-style routing expressions and tests pathnames against them.\n * This utility is environment-agnostic and works in both Node.js and browser contexts.\n *\n * @example\n * ```typescript\n * const rules = '(http.request.uri.path matches \"^/$\" or http.request.uri.path matches \"^/search.*\")';\n * shouldRouteToNext('/', rules); // true - route to Storefront Next\n * shouldRouteToNext('/search', rules); // true - route to Storefront Next\n * shouldRouteToNext('/checkout', rules); // false - proxy to SFRA/legacy\n * ```\n *\n * Environment variables used:\n * - HYBRID_PROXY_ENABLED (optional) - Boolean flag to enable/disable hybrid proxy\n * - HYBRID_ROUTING_RULES (optional) - Cloudflare routing expression string\n * - SFCC_ORIGIN (optional) - Base URL for SFCC sandbox redirects\n */\n\n// Regex cache to avoid recompiling patterns on every request\nconst regexCache = new Map<string, RegExp>();\n\n/**\n * Extracts regex patterns from a Cloudflare routing expression.\n *\n * Parses Cloudflare \"matches\" expressions like:\n * (http.request.uri.path matches \"^/$\" or http.request.uri.path matches \"^/category.*\")\n *\n * And extracts the regex patterns: [\"^/$\", \"^/category.*\"]\n *\n * @param expression - Cloudflare expression string\n * @returns Array of regex pattern strings\n *\n * @example\n * ```typescript\n * extractPatterns('(http.request.uri.path matches \"^/$\")');\n * // Returns: [\"^/$\"]\n *\n * extractPatterns('(http.request.uri.path matches \"^/$\" or http.request.uri.path matches \"^/search.*\")');\n * // Returns: [\"^/$\", \"^/search.*\"]\n * ```\n */\nexport function extractPatterns(expression: string): string[] {\n if (!expression || typeof expression !== 'string') {\n return [];\n }\n\n // Match: http.request.uri.path matches \"PATTERN\" or http.request.uri.path matches 'PATTERN'\n // Handles both single and double quotes\n const regex = /http\\.request\\.uri\\.path\\s+matches\\s+[\"']([^\"']+)[\"']/gi;\n const patterns: string[] = [];\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(expression)) !== null) {\n patterns.push(match[1]);\n }\n\n return patterns;\n}\n\n/**\n * Tests if a pathname matches any of the provided regex patterns (logical OR).\n * Uses caching to optimize repeated pattern compilations.\n *\n * @param pathname - URL pathname to test (e.g., \"/search\", \"/category/shoes\")\n * @param patterns - Array of regex pattern strings\n * @returns true if pathname matches any pattern, false otherwise\n *\n * @example\n * ```typescript\n * testPatterns('/category/shoes', ['^/category.*', '^/search.*']);\n * // Returns: true (matches first pattern)\n *\n * testPatterns('/checkout', ['^/category.*', '^/search.*']);\n * // Returns: false (matches no patterns)\n * ```\n */\nexport function testPatterns(pathname: string, patterns: string[]): boolean {\n if (!pathname || !patterns || patterns.length === 0) {\n return false;\n }\n\n // Test pathname against each pattern (logical OR - any match returns true)\n for (const pattern of patterns) {\n try {\n // Check cache first\n let regex = regexCache.get(pattern);\n if (!regex) {\n regex = new RegExp(pattern);\n regexCache.set(pattern, regex);\n }\n\n if (regex.test(pathname)) {\n return true;\n }\n } catch (error) {\n // Invalid regex pattern - log warning and skip\n logger.warn(`Invalid regex pattern: ${pattern} ${String(error)}`);\n continue;\n }\n }\n\n return false;\n}\n\n/**\n * Main function: Determines if a pathname should route to Storefront Next\n * or be proxied/redirected to SFRA/legacy backend.\n *\n * @param pathname - URL pathname (e.g., \"/search\", \"/checkout\")\n * @param routingRules - Cloudflare routing expression string (optional)\n * @returns true if should route to Storefront Next, false if should proxy to SFRA\n *\n * @example\n * ```typescript\n * const rules = '(http.request.uri.path matches \"^/$\" or http.request.uri.path matches \"^/category.*\")';\n *\n * shouldRouteToNext('/', rules); // true - route to Next\n * shouldRouteToNext('/category/mens', rules); // true - route to Next\n * shouldRouteToNext('/checkout', rules); // false - proxy to SFRA\n * shouldRouteToNext('/any-path', undefined); // true - no rules = default to Next\n * ```\n */\nexport function shouldRouteToNext(pathname: string, routingRules?: string): boolean {\n if (!routingRules) {\n // No rules configured - default to routing to Next (fail-safe)\n return true;\n }\n\n const patterns = extractPatterns(routingRules);\n\n if (patterns.length === 0) {\n // Malformed expression or no patterns found - default to Next (fail-safe)\n logger.warn('No valid patterns found in routing rules');\n return true;\n }\n\n return testPatterns(pathname, patterns);\n}\n\n/**\n * Clears the regex cache. Useful for testing or when routing rules change.\n *\n * @example\n * ```typescript\n * clearCache();\n * // All cached regex patterns are removed\n * ```\n */\nexport function clearCache(): void {\n regexCache.clear();\n}\n"],"mappings":"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"}