@salesforce/storefront-next-dev 0.2.0-alpha.2 → 0.3.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/cartridge-services/index.d.ts.map +1 -1
- package/dist/cartridge-services/index.js +171 -50
- package/dist/cartridge-services/index.js.map +1 -1
- package/dist/commands/create-bundle.js +12 -11
- package/dist/commands/create-instructions.js +7 -5
- package/dist/commands/create-storefront.js +18 -22
- package/dist/commands/deploy-cartridge.js +67 -26
- package/dist/commands/dev.js +6 -4
- package/dist/commands/extensions/create.js +2 -0
- package/dist/commands/extensions/install.js +3 -7
- package/dist/commands/extensions/list.js +2 -0
- package/dist/commands/extensions/remove.js +3 -7
- package/dist/commands/generate-cartridge.js +23 -2
- package/dist/commands/preview.js +15 -10
- package/dist/commands/push.js +25 -19
- package/dist/commands/validate-cartridge.js +51 -0
- package/dist/config.js +74 -47
- package/dist/configs/react-router.config.d.ts.map +1 -1
- package/dist/configs/react-router.config.js +36 -0
- package/dist/configs/react-router.config.js.map +1 -1
- package/dist/dependency-utils.js +14 -16
- package/dist/entry/server.d.ts.map +1 -1
- package/dist/entry/server.js +221 -11
- package/dist/entry/server.js.map +1 -1
- package/dist/generate-cartridge.js +106 -50
- package/dist/index.d.ts +127 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1147 -167
- package/dist/index.js.map +1 -1
- package/dist/local-dev-setup.js +13 -13
- package/dist/logger/index.d.ts +20 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +69 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger.js +79 -33
- package/dist/logger2.js +1 -0
- package/dist/manage-extensions.js +7 -13
- package/dist/mrt/ssr.mjs +60 -72
- package/dist/mrt/ssr.mjs.map +1 -1
- package/dist/mrt/streamingHandler.mjs +66 -78
- package/dist/mrt/streamingHandler.mjs.map +1 -1
- package/dist/react-router/Scripts.d.ts +1 -1
- package/dist/react-router/Scripts.d.ts.map +1 -1
- package/dist/react-router/Scripts.js +38 -2
- package/dist/react-router/Scripts.js.map +1 -1
- package/dist/server.js +296 -16
- package/dist/utils.js +4 -4
- package/dist/validate-cartridge.js +45 -0
- package/package.json +22 -5
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["resolvedConfig: ResolvedConfig","path","__dirname","path","resolvedConfig: ResolvedConfig","buildDirectory: string","componentRegistry: TargetComponentRegistry","contextProviders: TargetContextProviderConfig[]","path","componentRegistry: TargetComponentRegistry","contextProviders: TargetContextProviderConfig[]","sourceDir: string","path","err: unknown","viteConfig: ResolvedConfig","glob","path","DEFAULT_COMPONENT_GROUP","baseComponentId: string","verbose","components: ComponentInfo[]","resolve","existingContent: string","projectRoot: string","resolve","verbose","join","resolve","verbose","resolvedConfig: ResolvedConfig","missingInstrumentation: Array<{ adapter: string; event: string }>","resolvedConfig: ResolvedConfig","buildDirectory: string","appDirectory: string","resolve","basename","fs","serverEntryFilePath: string | undefined","clientEntryFilePath: string | undefined","appDirectory: string | undefined","userServerEntryPath: string | undefined","userClientEntryPath: string | undefined","ctx: ReactRouterPluginContext | undefined","relative","path","plugins: Plugin[]","alias: Record<string, string>","existsSync","readFileSync","sortedAlias: Record<string, string>","existsSync","path","colors: Record<string, typeof chalk.green>","colors","ServerModeFeatureMap: Record<ServerMode, ServerModeFeatures>","RELATIVE_MIDDLEWARE_REGISTRY_BUILT_PATHS: readonly string[]","registry: MiddlewareRegistry | null","existsSync","builtRegistryPath: string | null","path","pathToFileURL","FILE_EXTENSIONS: string[]","configuredExtensions: Record<string, unknown>","extensions: ExtensionsSelection","fs","path","e: unknown","newLines: string[]","blockMarkers: { extension: string; line: number }[]","err: unknown","isCliAvailable: boolean | null","readFileSync","existsSync","result: Array<{ id: string; path: string; file: string; index?: boolean }>","fullPath: string","VALID_ATTRIBUTE_TYPES: readonly AttributeType[]","TYPE_MAPPING: Record<string, string>","result: Record<string, unknown>","result: unknown[]","attributes: Record<string, unknown>[]","attribute: Record<string, unknown>","regionDefinitions: Record<string, unknown>[]","regionDefinition: Record<string, unknown>","components: unknown[]","pageTypes: unknown[]","aspects: unknown[]","cartridgeData: Record<string, unknown>","files: string[]","allComponents: unknown[]","allPageTypes: unknown[]","allAspects: unknown[]"],"sources":["../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/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/storefront-next-targets.ts","../src/server/ts-import.ts","../src/server/config.ts","../src/server/middleware/proxy.ts","../src/utils/logger.ts","../src/server/middleware/static.ts","../src/server/middleware/compression.ts","../src/server/middleware/logging.ts","../src/server/middleware/host-header.ts","../src/server/utils.ts","../src/server/modes.ts","../src/server/index.ts","../src/extensibility/path-util.ts","../src/extensibility/trim-extensions.ts","../src/cartridge-services/react-router-config.ts","../src/cartridge-services/generate-cartridge.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 */\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport path from 'node:path';\nimport fs from 'fs-extra';\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 // eslint-disable-next-line no-console\n console.log(`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: 'odyssey: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 bundle path for static assets\n */\nexport function getBundlePath(bundleId: string): string {\n return `/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: 'odyssey: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 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 }\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: 'odyssey:managed-runtime-bundle',\n apply: 'build',\n config({ mode }) {\n return {\n environments: {\n ssr: {\n resolve: {\n noExternal: true,\n },\n },\n },\n experimental: {\n renderBuiltUrl(filename, { type }) {\n if (mode !== 'preview' && (type === 'asset' || type === 'public')) {\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 : ('/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: 'odyssey: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';\n\nexport interface TargetComponentConfig {\n targetId: string;\n path: string;\n namespace: string;\n componentName: string;\n order: number;\n}\n\nexport interface TargetContextProviderConfig {\n path: string;\n namespace: string;\n componentName: string;\n order: number;\n}\n\nexport type TargetComponentRegistry = Record<string, TargetComponentConfig[]>;\n\nconst traverse = (traverseModule as unknown as { default: typeof traverseModule }).default || traverseModule;\n\nconst TARGET_COMPONENT_TAG = 'UITarget';\nconst TARGET_PROVIDERS_TAG = 'TargetProviders';\nconst TARGET_ID_ATTRIBUTE = 'targetId';\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 // Create JSX elements for each 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\n // If multiple components, wrap in a fragment; otherwise use single component\n if (components.length > 1) {\n element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), components));\n } else {\n element.replaceWith(components[0]);\n }\n targetIdReplaced = targetId;\n replaced = true;\n }\n }\n if (!replaced) {\n if (element.node.children && element.node.children.length > 0) {\n // replace the element with its children\n element.replaceWithMultiple(element.node.children);\n } else {\n // No children, just remove the element\n element.remove();\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 (!code.includes(TARGET_COMPONENT_TAG) && !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 (code.includes(TARGET_COMPONENT_TAG)) {\n const targetIdsReplaced = runReplacementPass(ast, TARGET_COMPONENT_TAG, targetRegistry, null);\n const replacementImportStatements = buildReplacementImportStatements(targetIdsReplaced, targetRegistry);\n // Single import rewrite pass\n traverse(ast, {\n ImportDeclaration(nodePath: NodePath<BabelImportDeclaration>) {\n const source = nodePath.node.source.value;\n if (source.includes('@/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.includes('@/targets/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(rootDir: string): {\n componentRegistry: TargetComponentRegistry;\n contextProviders: TargetContextProviderConfig[];\n} {\n const componentRegistry: TargetComponentRegistry = {};\n const contextProviders: TargetContextProviderConfig[] = [];\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.split('/').pop()?.replace('.tsx', '');\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 if (extensionConfig && extensionConfig.components) {\n for (const component of extensionConfig.components) {\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 }\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 return { componentRegistry, contextProviders };\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';\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\n return {\n name: 'odyssey: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 },\n buildStart() {\n // Build the registry once at the start of the build\n ({ componentRegistry, contextProviders } = buildTargetRegistry(sourceDir));\n },\n\n transform(code: string, id: string) {\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 // eslint-disable-next-line no-console\n console.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 { ViteDevServer, ResolvedConfig } from 'vite';\nimport path from 'path';\n\nexport const watchConfigFilesPlugin = () => {\n let viteConfig: ResolvedConfig;\n return {\n name: 'odyssey: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 // eslint-disable-next-line no-console\n console.log(`🔁 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 */\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';\n\n// Default component group when none is specified in the decorator\nconst DEFAULT_COMPONENT_GROUP = 'odyssey_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 * Enable verbose logging\n * @default false\n */\n verbose?: 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 verbose: boolean\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 if (verbose) {\n console.log(`🔍 Scanning ${componentFiles.length} files in ${componentPath}...`);\n }\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 if (verbose) {\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 console.log(\n ` ✅ Found component: ${componentInfo.id} → ${relativePath}${exportsText}`\n );\n }\n }\n }\n }\n }\n } catch (error) {\n if (verbose) {\n console.warn(`⚠️ Could not process ${filePath}:`, (error as Error).message);\n }\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, verbose: boolean): void {\n let existingContent: string;\n\n // Check if file exists, if not create a basic one\n if (!existsSync(registryFilePath)) {\n if (verbose) {\n console.log(`📝 Creating new registry file...`);\n }\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 try {\n writeFileSync(registryFilePath, updatedContent, 'utf-8');\n if (verbose) {\n console.log(`💾 Updated registry file: ${registryFilePath}`);\n }\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 verbose = false,\n } = config;\n\n let projectRoot: string;\n\n const runRegistryGeneration = async () => {\n if (verbose) {\n console.log('🚀 Starting static registry generation...');\n }\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, verbose);\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 if (verbose) {\n console.log(`📦 Found ${components.length} components with @Component decorators`);\n }\n\n const generatedCode = generateRegistryCode(components, registryIdentifier);\n const registryFilePath = resolve(projectRoot, registryPath);\n updateRegistryFile(registryFilePath, generatedCode, verbose);\n\n if (verbose) {\n console.log('✅ Static registry generation complete!');\n }\n\n return registryFilePath;\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 console.error(`❌ Static registry generation failed: ${(error as Error).message}`);\n if (failOnError) {\n throw error;\n }\n console.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 if (verbose) {\n console.log(`🔄 Component file changed: ${file}, regenerating registry...`);\n }\n\n try {\n const registryFilePath = await runRegistryGeneration();\n\n const registryModule = server.moduleGraph.getModuleById(registryFilePath);\n if (registryModule) {\n await server.reloadModule(registryModule);\n }\n\n if (verbose) {\n console.log('✅ Registry regenerated successfully!');\n }\n } catch (error) {\n console.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';\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(\n projectRoot: string,\n configPath: string,\n verbose: boolean\n): 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 if (verbose) {\n // eslint-disable-next-line no-console\n console.log(` 📄 Loaded config from ${configPath}`);\n }\n\n // Navigate to engagement config\n const engagement = config?.app?.engagement as EngagementConfig | undefined;\n\n if (!engagement) {\n if (verbose) {\n // eslint-disable-next-line no-console\n console.log(` ⚠️ No engagement config found in ${configPath}`);\n }\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 if (verbose) {\n // eslint-disable-next-line no-console\n console.warn(` ⚠️ Could not load config from ${configPath}: ${(error as Error).message}`);\n }\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 */\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';\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 * Enable verbose logging\n * @default false\n */\n verbose?: boolean;\n}\n\n/**\n * Extract all trackEvent calls from source files and return the event types found\n */\nasync function scanForInstrumentedEvents(\n projectRoot: string,\n scanPaths: string[],\n verbose: boolean\n): 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 if (verbose) {\n console.log(` 📂 Scanning ${files.length} files in ${scanPath}...`);\n }\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 if (verbose) {\n console.log(` ✓ Found trackEvent('${eventType}') in ${file}`);\n }\n }\n\n // Check for sendViewPageEvent (implies view_page is instrumented)\n if (sendViewPagePattern.test(content)) {\n instrumentedEvents.add('view_page');\n if (verbose) {\n console.log(` ✓ Found sendViewPageEvent() in ${file}`);\n }\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 if (verbose) {\n console.log(` ✓ Found createEvent('${eventType}') in ${file}`);\n }\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 if (verbose) {\n console.warn(` ⚠️ Could not read ${file}: ${(error as Error).message}`);\n }\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, verbose = 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 if (verbose) {\n console.log('\\n🔍 [event-instrumentation] Validating event instrumentation...');\n }\n\n // Load engagement config\n const engagement = await loadEngagementConfig(projectRoot, configPath, verbose);\n\n if (!engagement) {\n if (verbose) {\n console.log(' ℹ️ Skipping validation - no engagement config found\\n');\n }\n return;\n }\n\n // Extract enabled events per adapter\n const adapterEvents = extractEnabledEvents(engagement);\n\n if (adapterEvents.size === 0) {\n if (verbose) {\n console.log(' ℹ️ No enabled adapters with event toggles found\\n');\n }\n return;\n }\n\n // Scan source files for instrumented events\n const instrumentedEvents = await scanForInstrumentedEvents(projectRoot, scanPaths, verbose);\n\n if (verbose) {\n console.log(`\\n 🔎 Found ${instrumentedEvents.size} instrumented event types:`);\n for (const event of instrumentedEvents) {\n console.log(` - ${event}`);\n }\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 if (verbose) {\n console.log('\\n ✅ All enabled events are instrumented\\n');\n }\n return;\n }\n\n // Report missing instrumentation\n console.log('\\n');\n for (const { adapter, event } of missingInstrumentation) {\n console.warn(\n ` ⚠️ [event-instrumentation] ${adapter}.${event} is enabled but '${event}' is never instrumented`\n );\n }\n console.log('\\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: 'odyssey: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: 'odyssey: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 { 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 { 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';\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 RSC 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 verbose: false,\n },\n eventInstrumentationValidator = {\n configPath: 'config.server.ts',\n scanPaths: ['src'],\n failOnMissing: false,\n verbose: false,\n },\n } = config;\n\n const plugins: Plugin[] = [\n ...(process.env.SCAPI_PROXY_HOST ? [workspacePlugin()] : []),\n managedRuntimeBundlePlugin(),\n fixReactRouterManifestUrlsPlugin(),\n patchReactRouterPlugin(),\n platformEntryPlugin(),\n transformTargetPlaceholderPlugin(),\n watchConfigFilesPlugin(),\n buildMiddlewareRegistryPlugin(),\n ];\n\n // Add static registry plugin if enabled\n if (staticRegistry?.componentPath && staticRegistry?.registryPath) {\n plugins.push(staticRegistryPlugin(staticRegistry));\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 { 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 'node:path';\nimport { existsSync } from 'node:fs';\nimport { importTypescript } from './ts-import';\n\n/**\n * Server configuration extracted from environment variables\n */\nexport interface ServerConfig {\n commerce: {\n api: {\n shortCode: string;\n organizationId: string;\n clientId: string;\n siteId: string;\n proxy: string;\n proxyHost?: string;\n };\n };\n}\n\n/**\n * This is a temporary function before we move the config implementation from\n * template-retail-rsc-app to the SDK.\n *\n * @ TODO: Remove this function after we move the config implementation from\n * template-retail-rsc-app to the SDK.\n *\n */\nexport function loadConfigFromEnv(): ServerConfig {\n const shortCode = process.env.PUBLIC__app__commerce__api__shortCode;\n const organizationId = process.env.PUBLIC__app__commerce__api__organizationId;\n const clientId = process.env.PUBLIC__app__commerce__api__clientId;\n const siteId = process.env.PUBLIC__app__commerce__api__siteId;\n const proxy = process.env.PUBLIC__app__commerce__api__proxy || '/mobify/proxy/api';\n const proxyHost = process.env.SCAPI_PROXY_HOST;\n\n if (!shortCode && !proxyHost) {\n throw new Error(\n 'Missing PUBLIC__app__commerce__api__shortCode environment variable.\\n' +\n 'Please set it in your .env file or environment.'\n );\n }\n\n if (!organizationId) {\n throw new Error(\n 'Missing PUBLIC__app__commerce__api__organizationId environment variable.\\n' +\n 'Please set it in your .env file or environment.'\n );\n }\n\n if (!clientId) {\n throw new Error(\n 'Missing PUBLIC__app__commerce__api__clientId environment variable.\\n' +\n 'Please set it in your .env file or environment.'\n );\n }\n\n if (!siteId) {\n throw new Error(\n 'Missing PUBLIC__app__commerce__api__siteId environment variable.\\n' +\n 'Please set it in your .env file or environment.'\n );\n }\n\n return {\n commerce: {\n api: {\n shortCode: shortCode || '',\n organizationId,\n clientId,\n siteId,\n proxy,\n proxyHost,\n },\n },\n };\n}\n\n/**\n * Load storefront-next project configuration from config.server.ts.\n * Requires projectDirectory to be provided.\n *\n * @param projectDirectory - Project directory to load config.server.ts from\n * @throws Error if config.server.ts is not found or invalid\n */\nexport async function loadProjectConfig(projectDirectory: string): Promise<ServerConfig> {\n const configPath = resolve(projectDirectory, 'config.server.ts');\n const tsconfigPath = resolve(projectDirectory, 'tsconfig.json');\n\n if (!existsSync(configPath)) {\n throw new Error(\n `config.server.ts not found at ${configPath}.\\n` +\n 'Please ensure config.server.ts exists in your project root.'\n );\n }\n\n interface LoadedConfig {\n default?: {\n app?: {\n commerce?: {\n api?: {\n shortCode?: string;\n organizationId?: string;\n clientId?: string;\n siteId?: string;\n proxy?: string;\n };\n };\n };\n };\n }\n\n const loaded = await importTypescript<LoadedConfig>(configPath, {\n projectDirectory,\n tsconfigPath,\n });\n\n // Extract commerce API config from the loaded config\n const config = loaded.default;\n if (!config?.app?.commerce?.api) {\n throw new Error(\n `Invalid config.server.ts: missing app.commerce.api configuration.\\n` +\n 'Please ensure your config.server.ts has the commerce API configuration.'\n );\n }\n\n const api = config.app.commerce.api;\n const proxyHost = process.env.SCAPI_PROXY_HOST;\n\n // Validate required fields (shortCode not required when proxyHost is set)\n if (!api.shortCode && !proxyHost) {\n throw new Error('Missing shortCode in config.server.ts commerce.api configuration');\n }\n if (!api.organizationId) {\n throw new Error('Missing organizationId in config.server.ts commerce.api configuration');\n }\n if (!api.clientId) {\n throw new Error('Missing clientId in config.server.ts commerce.api configuration');\n }\n if (!api.siteId) {\n throw new Error('Missing siteId in config.server.ts commerce.api configuration');\n }\n\n return {\n commerce: {\n api: {\n shortCode: api.shortCode || '',\n organizationId: api.organizationId,\n clientId: api.clientId,\n siteId: api.siteId,\n proxy: api.proxy || '/mobify/proxy/api',\n proxyHost,\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 { createProxyMiddleware, type RequestHandler } from 'http-proxy-middleware';\nimport type { ServerConfig } from '../config';\nimport { getCommerceCloudApiUrl } from '../../utils/paths';\n\n/**\n * Create proxy middleware for Commerce Cloud API\n * Proxies requests from /mobify/proxy/api to the Commerce Cloud API\n */\nexport function createCommerceProxyMiddleware(config: ServerConfig): RequestHandler {\n const target = getCommerceCloudApiUrl(config.commerce.api.shortCode, config.commerce.api.proxyHost);\n\n return createProxyMiddleware({\n target,\n changeOrigin: true,\n // Disable SSL verification when using a custom proxy target (e.g. the local\n // SCW instance at https://scw:25010 which uses a self-signed certificate).\n // This is safe because the proxy middleware is only mounted in dev/preview\n // modes — production builds on Managed Runtime disable it (enableProxy: false).\n secure: !config.commerce.api.proxyHost,\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/* 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 * Logger utilities\n */\nconst colors = {\n warn: 'yellow',\n error: 'red',\n success: 'cyan',\n info: 'green',\n debug: 'gray',\n} as const;\n\nconst fancyLog = (level: keyof typeof colors, msg: string) => {\n const color = colors[level];\n const colorFn = chalk[color];\n console.log(`${colorFn(level)}: ${msg}`);\n};\n\nexport const info = (msg: string) => fancyLog('info', msg);\nexport const success = (msg: string) => fancyLog('success', msg);\nexport const warn = (msg: string) => fancyLog('warn', msg);\nexport const error = (msg: string) => fancyLog('error', msg);\n\nexport const debug = (msg: string, data?: unknown) => {\n // Only log debug messages if DEBUG environment variable is set or not in production\n if (process.env.DEBUG || process.env.NODE_ENV !== 'production') {\n fancyLog('debug', msg);\n if (data) {\n console.log(data);\n }\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\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 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.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 siteId?: string;\n}): void {\n const {\n port,\n enableProxy,\n enableStaticServing,\n enableCompression,\n proxyPath,\n proxyHost,\n shortCode,\n organizationId,\n clientId,\n siteId,\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 if (siteId) {\n console.log(` ${chalk.dim('Site ID: ')} ${chalk.dim(siteId)}`);\n }\n } else {\n console.log(` ${chalk.gray('○')} ${chalk.bold('Proxy: ')} ${chalk.dim('disabled')}`);\n }\n\n if (enableStaticServing) {\n console.log(` ${chalk.green('✓')} ${chalk.bold('Static: ')} ${chalk.dim('enabled')}`);\n }\n\n if (enableCompression) {\n console.log(` ${chalk.green('✓')} ${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.green('➜')} ${chalk.bold('Local: ')} ${chalk.cyan(localUrl)}`);\n if (networkUrl) {\n console.log(` ${chalk.green('➜')} ${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 express, { type RequestHandler } from 'express';\nimport path from 'path';\nimport { getBundlePath } from '../../utils/paths';\nimport { info } from '../../utils/logger';\n\n/**\n * Create static file serving middleware for client assets\n * Serves files from build/client at /mobify/bundle/{BUNDLE_ID}/client/\n */\nexport function createStaticMiddleware(bundleId: string, projectDirectory: string): RequestHandler {\n const bundlePath = getBundlePath(bundleId);\n const clientBuildDir = path.join(projectDirectory, 'build', 'client');\n\n info(`Serving static assets from ${clientBuildDir} at ${bundlePath}`);\n\n return express.static(clientBuildDir, {\n setHeaders: (res) => {\n res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');\n res.setHeader('x-local-static-cache-control', '1');\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 compression from 'compression';\nimport type { RequestHandler } from 'express';\nimport zlib from 'node:zlib';\nimport { warn } from '../../utils/logger';\n\n/**\n * Parse and validate COMPRESSION_LEVEL environment variable\n * @returns Valid compression level (0-9) or default compression level\n */\nfunction getCompressionLevel(): number {\n const raw = process.env.COMPRESSION_LEVEL;\n const DEFAULT = zlib.constants.Z_DEFAULT_COMPRESSION;\n\n if (raw == null || raw.trim() === '') {\n return DEFAULT;\n }\n\n const level = Number(raw);\n\n const isValid = Number.isInteger(level) && level >= 0 && level <= 9;\n\n if (!isValid) {\n warn(`[compression] Invalid COMPRESSION_LEVEL=\"${raw}\". ` + `Using default (${DEFAULT}).`);\n return DEFAULT;\n }\n\n return level;\n}\n\n/**\n * Create compression middleware for gzip/brotli compression\n * Used in preview mode to optimize response sizes\n */\nexport function createCompressionMiddleware(): RequestHandler {\n const compressionLevel = getCompressionLevel();\n\n return compression({\n filter: (req, res) => {\n if (req.headers['x-no-compression']) {\n return false;\n }\n return compression.filter(req, res);\n },\n // Compression level (0-9, higher = better compression but slower)\n // default is zlib.constants.Z_DEFAULT_COMPRESSION = -1\n level: compressionLevel,\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 { RequestHandler } from 'express';\nimport morgan from 'morgan';\nimport chalk from 'chalk';\nimport { minimatch } from 'minimatch';\n\n/**\n * Patterns for URLs to skip logging (static assets and Vite internals)\n */\nconst SKIP_PATTERNS = [\n '/@vite/**',\n '/@id/**',\n '/@fs/**',\n '/@react-router/**',\n '/src/**',\n '/node_modules/**',\n '**/*.js',\n '**/*.css',\n '**/*.ts',\n '**/*.tsx',\n '**/*.js.map',\n '**/*.css.map',\n];\n\n/**\n * Create request logging middleware\n * Used in dev and preview modes for request visibility\n */\nexport function createLoggingMiddleware(): RequestHandler {\n // Custom format with colors\n morgan.token('status-colored', (req, res) => {\n const status = res.statusCode;\n let color = chalk.green;\n\n if (status >= 500) {\n color = chalk.red;\n } else if (status >= 400) {\n color = chalk.yellow;\n } else if (status >= 300) {\n color = chalk.cyan;\n }\n\n return color(String(status));\n });\n\n morgan.token('method-colored', (req) => {\n const method = req.method;\n const colors: Record<string, typeof chalk.green> = {\n GET: chalk.green,\n POST: chalk.blue,\n PUT: chalk.yellow,\n DELETE: chalk.red,\n PATCH: chalk.magenta,\n };\n const color = (method && colors[method]) || chalk.white;\n return color(method);\n });\n\n // Format: [METHOD] /path - STATUS (response-time ms)\n return morgan(\n (tokens, req, res) => {\n return [\n chalk.gray('['),\n tokens['method-colored'](req, res),\n chalk.gray(']'),\n tokens.url(req, res),\n '-',\n tokens['status-colored'](req, res),\n chalk.gray(`(${tokens['response-time'](req, res)}ms)`),\n ].join(' ');\n },\n {\n // Skip logging for static assets to reduce noise\n skip: (req) => {\n return SKIP_PATTERNS.some((pattern) => minimatch(req.url, pattern, { dot: true }));\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 { RequestHandler } from 'express';\n\n/**\n * Normalizes the X-Forwarded-Host header to support React Router's CSRF validation features.\n *\n * NOTE: This middleware performs header manipulation as a temporary, internal\n * solution for MRT/Lambda environments. It may be updated or removed if React Router\n * introduces a first-class configuration for validating against forwarded headers.\n *\n * React Router v7.12+ uses the X-Forwarded-Host header (preferring it over Host)\n * to validate request origins for security. In Managed Runtime (MRT) with a vanity\n * domain, the eCDN automatically sets the X-Forwarded-Host to the vanity domain.\n * React Router handles cases where this header contains multiple comma-separated\n * values by prioritizing the first entry.\n *\n * This middleware ensures that X-Forwarded-Host is always present by falling back\n * to a configured public domain if the header is missing (e.g., local development).\n * By only modifying X-Forwarded-Host, we provide a consistent environment for\n * React Router's security checks without modifying the internal 'Host' header,\n * which is required for environment-specific routing logic (e.g., Hybrid Proxy).\n *\n * Priority order:\n * 1. X-Forwarded-Host: Automatically set by eCDN for vanity domains.\n * 2. EXTERNAL_DOMAIN_NAME: Fallback environment variable for the public domain\n * used when no forwarded headers are present (e.g., local development).\n */\nexport function createHostHeaderMiddleware(): RequestHandler {\n return (req, _res, next) => {\n // If X-Forwarded-Host is missing, populate it from the trusted fallback.\n // React Router v7 uses this header (preferring it over Host) to validate\n // against the 'Origin' for CSRF protection.\n if (!req.get('x-forwarded-host') && process.env.EXTERNAL_DOMAIN_NAME) {\n req.headers['x-forwarded-host'] = process.env.EXTERNAL_DOMAIN_NAME;\n }\n\n next();\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 { ServerBuild } from 'react-router';\nimport { getBundlePath } from '../utils/paths';\n\n/**\n * Patch React Router build to rewrite asset URLs with the correct bundle path\n * This is needed because the build output uses /assets/ but we preview at /mobify/bundle/{BUNDLE_ID}/client/assets/\n */\nexport function patchReactRouterBuild(build: ServerBuild, bundleId: string): ServerBuild {\n const bundlePath = getBundlePath(bundleId);\n\n // Clone the assets object and replace /assets/ paths with bundle path\n const assetsJson = JSON.stringify(build.assets);\n const patchedAssetsJson = assetsJson.replace(/\"\\/assets\\//g, `\"${bundlePath}assets/`);\n const newAssets = JSON.parse(patchedAssetsJson);\n\n // Return a new build object with patched publicPath and assets\n return Object.assign({}, build, {\n publicPath: bundlePath,\n assets: newAssets,\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 */\nexport type ServerMode = 'development' | 'preview' | 'production';\n\n/**\n * Feature flags for each server mode\n */\nexport interface ServerModeFeatures {\n /** Enable Commerce API proxy middleware to forward /mobify/proxy/api requests to SCAPI */\n enableProxy: boolean;\n\n /** Enable static file serving from build/client directory */\n enableStaticServing: boolean;\n\n /** Enable gzip/brotli compression middleware for responses */\n enableCompression: boolean;\n\n /** Enable HTTP request/response logging */\n enableLogging: boolean;\n\n /** Enable patching of asset URLs with bundle path (for CDN deployment) */\n enableAssetUrlPatching: boolean;\n}\n\n/**\n * Default feature configuration for each server mode\n */\nexport const ServerModeFeatureMap: Record<ServerMode, ServerModeFeatures> = {\n development: {\n enableProxy: true,\n enableStaticServing: false,\n enableCompression: false,\n enableLogging: true,\n enableAssetUrlPatching: false,\n },\n preview: {\n enableProxy: true,\n enableStaticServing: true,\n enableCompression: true,\n enableLogging: true,\n enableAssetUrlPatching: true,\n },\n production: {\n enableProxy: false,\n enableStaticServing: false,\n enableCompression: true,\n enableLogging: true,\n enableAssetUrlPatching: true,\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 express, { type Express } from 'express';\nimport { createRequestHandler } from '@react-router/express';\nimport { type ServerBuild } from 'react-router';\nimport type { ViteDevServer } from 'vite';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { loadConfigFromEnv, type ServerConfig } from './config';\nimport { importTypescript } from './ts-import';\nimport { createCommerceProxyMiddleware } from './middleware/proxy';\nimport { createStaticMiddleware } from './middleware/static';\nimport { createCompressionMiddleware } from './middleware/compression';\nimport { createLoggingMiddleware } from './middleware/logging';\nimport { createHostHeaderMiddleware } from './middleware/host-header';\nimport { patchReactRouterBuild } from './utils';\nimport { ServerModeFeatureMap, type ServerMode, type ServerModeFeatures } from './modes';\nimport { getBundlePath } from '../utils/paths';\n\n/** Relative path to the middleware registry TypeScript source (development). Must match appDirectory + server dir + filename used by buildMiddlewareRegistry plugin. */\nconst RELATIVE_MIDDLEWARE_REGISTRY_SOURCE = 'src/server/middleware-registry.ts';\n\n/** Extensions to try for the built middlewares module (ESM first, then CJS for backwards compatibility). */\nconst MIDDLEWARE_REGISTRY_BUILT_EXTENSIONS = ['.mjs', '.js', '.cjs'] as const;\n\n/** Base relative paths for the built middleware registry (production). Order: MRT bundle path, then local build path. */\nconst RELATIVE_MIDDLEWARE_REGISTRY_BUILT_BASES: readonly [string, string] = [\n 'bld/server/middleware-registry',\n 'build/server/middleware-registry',\n];\n\n/** All paths to try when loading the built middlewares (base + extension). */\nconst RELATIVE_MIDDLEWARE_REGISTRY_BUILT_PATHS: readonly string[] = RELATIVE_MIDDLEWARE_REGISTRY_BUILT_BASES.flatMap(\n (base) => MIDDLEWARE_REGISTRY_BUILT_EXTENSIONS.map((ext) => `${base}${ext}`)\n);\n\nexport interface ServerOptions extends Partial<ServerModeFeatures> {\n /** Server mode: development (with Vite), preview (preview), or production (minimal) */\n mode: ServerMode;\n\n /** Project root directory (optional, defaults to process.cwd()) */\n projectDirectory?: string;\n\n /** Server configuration (optional, will load from env vars if not provided) */\n config?: ServerConfig;\n\n /** Server port (optional, for logging) */\n port?: number;\n\n /** Vite dev server instance (required for development mode) */\n vite?: ViteDevServer;\n\n /** React Router server build (required for preview/production modes) */\n build?: ServerBuild;\n\n /** Enable streaming of responses */\n streaming?: boolean;\n}\n\n/**\n * Create a unified Express server for development, preview, or production mode\n */\nexport async function createServer(options: ServerOptions): Promise<Express> {\n const {\n mode,\n projectDirectory = process.cwd(),\n config: providedConfig,\n vite,\n build,\n streaming = false,\n enableProxy = ServerModeFeatureMap[mode].enableProxy,\n enableStaticServing = ServerModeFeatureMap[mode].enableStaticServing,\n enableCompression = ServerModeFeatureMap[mode].enableCompression,\n enableLogging = ServerModeFeatureMap[mode].enableLogging,\n enableAssetUrlPatching = ServerModeFeatureMap[mode].enableAssetUrlPatching,\n } = options;\n\n if (mode === 'development' && !vite) {\n throw new Error('Vite dev server instance is required for development mode');\n }\n\n if ((mode === 'preview' || mode === 'production') && !build) {\n throw new Error('React Router server build is required for preview/production mode');\n }\n\n // Use provided config or load from environment variables\n // TODO: move the config implementation from template-retail-rsc-app to the SDK.\n const config = providedConfig ?? loadConfigFromEnv();\n\n // Load bundle ID from environment\n const bundleId = process.env.BUNDLE_ID ?? 'local';\n\n // Create Express app\n const app = express();\n app.disable('x-powered-by');\n\n // Apply middleware based on mode\n if (enableLogging) {\n app.use(createLoggingMiddleware());\n }\n\n // If streaming is enabled then compression needs to be handled by the streaming handler\n // in the streamingHandler file\n if (enableCompression && !streaming) {\n app.use(createCompressionMiddleware());\n }\n\n if (enableStaticServing && build) {\n const bundlePath = getBundlePath(bundleId);\n app.use(bundlePath, createStaticMiddleware(bundleId, projectDirectory));\n }\n\n // Load and apply custom middlewares from the middleware registry.\n // In development, import the TypeScript source via jiti; in production/preview,\n // dynamically import the pre-built JS from the build output directory.\n interface MiddlewareRegistry {\n customMiddlewares?: Array<{ handler: express.RequestHandler }>;\n }\n\n let registry: MiddlewareRegistry | null = null;\n\n if (mode === 'development') {\n const middlewareRegistryPath = resolve(projectDirectory, RELATIVE_MIDDLEWARE_REGISTRY_SOURCE);\n if (existsSync(middlewareRegistryPath)) {\n registry = await importTypescript<MiddlewareRegistry>(middlewareRegistryPath, {\n projectDirectory,\n });\n }\n } else {\n const possiblePaths = RELATIVE_MIDDLEWARE_REGISTRY_BUILT_PATHS.map((p) => resolve(projectDirectory, p));\n\n let builtRegistryPath: string | null = null;\n for (const path of possiblePaths) {\n if (existsSync(path)) {\n builtRegistryPath = path;\n break;\n }\n }\n\n if (builtRegistryPath) {\n registry = (await import(pathToFileURL(builtRegistryPath).href)) as MiddlewareRegistry;\n }\n }\n\n if (registry?.customMiddlewares && Array.isArray(registry.customMiddlewares)) {\n registry.customMiddlewares.forEach((entry: { handler: express.RequestHandler }) => {\n app.use(entry.handler);\n });\n }\n\n if (mode === 'development' && vite) {\n // In development, Vite middleware handles HMR, transforms, and proxy\n app.use(vite.middlewares);\n }\n\n if (enableProxy) {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n app.use(config.commerce.api.proxy, createCommerceProxyMiddleware(config));\n }\n\n // Normalize the Host header for React Router's CSRF validation features\n app.use(createHostHeaderMiddleware());\n\n // SSR request handler\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n app.all('*', await createSSRHandler(mode, bundleId, vite, build, enableAssetUrlPatching));\n\n return app;\n}\n\n/**\n * Create the SSR request handler based on mode\n */\nasync function createSSRHandler(\n mode: ServerMode,\n bundleId: string,\n vite: ViteDevServer | undefined,\n build: ServerBuild | undefined,\n enableAssetUrlPatching: boolean\n) {\n if (mode === 'development' && vite) {\n // The vite package is not designed to be bundlable via build tools\n // You will run into a lot of build errors if you try to bundle it.\n // So, we dynamically import it here to avoid bundling it in production.\n const { isRunnableDevEnvironment } = await import('vite');\n\n return async (req: express.Request, res: express.Response, next: express.NextFunction) => {\n try {\n const ssrEnvironment = vite.environments.ssr;\n\n // Check if the environment is runnable (has a module runner)\n if (!isRunnableDevEnvironment(ssrEnvironment)) {\n const error = new Error(\n 'SSR environment is not runnable. Please ensure:\\n' +\n ' 1. \"@salesforce/storefront-next-dev\" plugin is added to vite.config.ts\\n' +\n ' 2. React Router config uses the Storefront Next preset'\n );\n next(error);\n return;\n }\n\n // Load server build using Vite Environment API\n // This gets the latest build with HMR updates\n const devBuild = await ssrEnvironment.runner.import('virtual:react-router/server-build');\n\n // Use the same request handler pattern as production\n const handler = createRequestHandler({\n build: devBuild,\n mode: process.env.NODE_ENV,\n });\n\n await handler(req, res, next);\n } catch (error) {\n // Let Vite handle SSR errors with nice error overlay\n vite.ssrFixStacktrace(error as Error);\n next(error);\n }\n };\n } else if (build) {\n // Serve/Production mode: static build\n let patchedBuild = build;\n\n if (enableAssetUrlPatching) {\n patchedBuild = patchReactRouterBuild(build, bundleId);\n }\n\n return createRequestHandler({\n build: patchedBuild,\n mode: process.env.NODE_ENV,\n });\n } else {\n throw new Error('Invalid server configuration: no vite or build provided');\n }\n}\n\n// Re-export config and types\nexport { loadProjectConfig, loadConfigFromEnv, type ServerConfig } from './config';\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 fs from 'fs';\nimport path from 'path';\n\n// Cache for tsconfig paths to avoid repeated disk reads and JSON parsing\nlet cachedTsconfigPaths: Record<string, string[] | string> | null = null;\nlet cachedTsconfigRoot: string | null = null;\n\nexport const FILE_EXTENSIONS: string[] = ['.tsx', '.ts', '.d.ts'];\n\n/**\n * Strip the comments from the JSON string\n * @param jsonString\n * @returns {string}\n */\nfunction stripJsonComments(jsonString: string): string {\n return jsonString\n .replace(/\\/\\/.*$/gm, '') // remove // comments\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, ''); // remove /* */ comments\n}\n\n/**\n * Load the tsconfig.json paths from the project root\n * @param projectRoot\n * @returns {Record<string, string[] | string>}\n */\nfunction loadTsconfigPaths(projectRoot: string): Record<string, string[] | string> | null {\n // If cache is valid for the same root, return it\n if (cachedTsconfigPaths && cachedTsconfigRoot === projectRoot) {\n return cachedTsconfigPaths;\n }\n\n const tsconfigPath = path.join(projectRoot, 'tsconfig.json');\n if (!fs.existsSync(tsconfigPath)) {\n cachedTsconfigPaths = {}; // empty object\n cachedTsconfigRoot = projectRoot;\n return cachedTsconfigPaths;\n }\n\n try {\n const tsconfigContent = stripJsonComments(fs.readFileSync(tsconfigPath, 'utf-8'));\n const tsconfig = JSON.parse(tsconfigContent);\n const paths = tsconfig?.compilerOptions?.paths;\n if (paths && typeof paths === 'object') {\n cachedTsconfigPaths = paths;\n } else {\n cachedTsconfigPaths = {}; // empty object\n }\n cachedTsconfigRoot = projectRoot;\n return cachedTsconfigPaths;\n } catch (error) {\n throw new Error(`Error parsing tsconfig.json for project ${projectRoot}: ${String(error)}`);\n }\n}\n\n/**\n * Resolve the path from the alias to the real path by consulting tsconfig.json paths configuration\n * @param {string} importPath\n * @param {string} projectRoot\n * @returns {string}\n */\nexport function resolvePathFromAlias(importPath: string, projectRoot: string): string {\n // First check if this is a relative import - if so, return as is\n if (importPath.startsWith('.')) {\n return importPath;\n }\n\n // Load and cache tsconfig paths\n const paths = loadTsconfigPaths(projectRoot);\n if (!paths || typeof paths !== 'object' || Object.keys(paths).length === 0) {\n return importPath;\n }\n\n // Find matching alias\n for (const [alias, mappings] of Object.entries(paths)) {\n // Convert TypeScript path pattern to regex (escape '+' to literal, keep '*' as wildcard)\n const aliasEscapedPlus = alias.replace(/\\+/g, '\\\\+');\n const aliasPattern = aliasEscapedPlus.replace(/\\*/g, '(.*)');\n const aliasRegex = new RegExp(`^${aliasPattern}$`);\n const match = importPath.match(aliasRegex);\n\n if (match) {\n const mappingArray = Array.isArray(mappings) ? mappings : [mappings];\n // Try each mapping until we find an existing file\n for (const mapping of mappingArray) {\n // Replace wildcards in the mapping with captured groups\n let resolvedPath = mapping;\n for (let i = 1; i < match.length; i++) {\n resolvedPath = resolvedPath.replace('*', match[i]);\n }\n\n // Remove leading \"./\" from the mapping if present\n if (resolvedPath.startsWith('./')) {\n resolvedPath = resolvedPath.substring(2);\n }\n\n const fullPath = path.resolve(projectRoot, resolvedPath);\n\n // Check if the file exists (with common extensions)\n for (const ext of FILE_EXTENSIONS) {\n const pathWithExt = fullPath + ext;\n if (fs.existsSync(pathWithExt)) {\n return pathWithExt;\n }\n }\n\n // Also check if it's a directory with index file\n if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {\n for (const indexFile of ['index.ts', 'index.tsx', 'index.js', 'index.jsx']) {\n const indexPath = path.join(fullPath, indexFile);\n if (fs.existsSync(indexPath)) {\n return indexPath;\n }\n }\n // If directory exists but no index file, return the directory path\n return fullPath;\n }\n }\n }\n }\n\n // If no existing file was found for this alias simply return the original import path\n return importPath;\n}\n\nexport function isSupportedFileExtension(fileName: string): boolean {\n return FILE_EXTENSIONS.some((ext) => fileName.endsWith(ext));\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 * Utility to trim the directory to remove unused components and unused extensions.\n * This is used to reduce the size of the project by removing the code that is not part of the selected extensions.\n */\n/* eslint-disable no-console */\nimport fs from 'fs';\nimport path from 'path';\nimport type ExtensionConfig from './extension-config';\nimport { isSupportedFileExtension } from './path-util';\n\ntype ExtensionsSelection = Record<string, boolean>;\n\nconst SINGLE_LINE_MARKER = '@sfdc-extension-line';\nconst BLOCK_MARKER_START = '@sfdc-extension-block-start';\nconst BLOCK_MARKER_END = '@sfdc-extension-block-end';\nconst FILE_MARKER = '@sfdc-extension-file';\nlet verbose = false;\n\nexport default function trimExtensions(\n directory: string,\n selectedExtensions?: Partial<ExtensionsSelection>,\n extensionConfig?: typeof ExtensionConfig,\n verboseOverride: boolean = false\n): void {\n const startTime = Date.now();\n verbose = verboseOverride ?? false;\n\n // read available extensions from config file\n const configuredExtensions: Record<string, unknown> = extensionConfig?.extensions || {};\n const extensions: ExtensionsSelection = {};\n Object.keys(configuredExtensions).forEach((targetKey) => {\n extensions[targetKey] = Boolean(selectedExtensions?.[targetKey]) || false;\n });\n\n if (Object.keys(extensions).length === 0) {\n if (verbose) {\n console.log('No targets found, skipping trim');\n }\n return;\n }\n\n const processDirectory = (dir: string): void => {\n const files = fs.readdirSync(dir);\n files.forEach((file) => {\n const filePath = path.join(dir, file);\n const stats = fs.statSync(filePath);\n\n if (!filePath.includes('node_modules')) {\n if (stats.isDirectory()) {\n processDirectory(filePath);\n } else if (isSupportedFileExtension(file)) {\n processFile(filePath, extensions);\n }\n }\n });\n };\n\n processDirectory(directory);\n if (extensionConfig?.extensions) {\n deleteExtensionFolders(directory, extensions, extensionConfig);\n updateExtensionConfig(directory, extensions);\n }\n const endTime = Date.now();\n if (verbose) {\n console.log(`Trim extensions took ${endTime - startTime}ms`);\n }\n}\n\n/**\n * Update the extension config file to only include the selected extensions.\n * @param projectDirectory - The project directory\n * @param extensionSelections - The selected extensions\n */\nfunction updateExtensionConfig(projectDirectory: string, extensionSelections: ExtensionsSelection) {\n const extensionConfigPath = path.join(projectDirectory, 'src', 'extensions', 'config.json');\n const extensionConfig = JSON.parse(fs.readFileSync(extensionConfigPath, 'utf8'));\n Object.keys(extensionConfig.extensions).forEach((extensionKey: string) => {\n if (!extensionSelections[extensionKey]) {\n delete extensionConfig.extensions[extensionKey];\n }\n });\n fs.writeFileSync(extensionConfigPath, JSON.stringify({ extensions: extensionConfig.extensions }, null, 4), 'utf8');\n}\n\n/**\n * Process a file to trim extension-specific code based on markers.\n * @param filePath - The file path to process\n * @param extensions - The extension selections\n */\nfunction processFile(filePath: string, extensions: ExtensionsSelection): void {\n const source = fs.readFileSync(filePath, 'utf-8');\n\n // If the file is guarded by a file-level marker and the extension is disabled, remove the file entirely\n if (source.includes(FILE_MARKER)) {\n // find() always returns a line since the if condition ensures marker exists, and FILE_MARKER has no newlines\n const markerLine = source.split('\\n').find((line) => line.includes(FILE_MARKER)) as string;\n const extMatch = Object.keys(extensions).find((ext) => markerLine.includes(ext));\n if (!extMatch) {\n if (verbose) {\n console.warn(\n `File ${filePath} is marked with ${markerLine} but it does not match any known extensions`\n );\n }\n } else if (extensions[extMatch] === false) {\n try {\n fs.unlinkSync(filePath);\n if (verbose) {\n console.log(`Deleted file ${filePath}`);\n }\n } catch (e: unknown) {\n const error = e as Error;\n console.error(`Error deleting file ${filePath}: ${error.message}`);\n throw e;\n }\n return;\n }\n }\n\n // extensions will always have keys since trimExtensions validates this before calling processFile\n const extKeys = Object.keys(extensions);\n const extensionRegex = new RegExp(extKeys.join('|'), 'g');\n if (extensionRegex.test(source)) {\n const lines = source.split('\\n');\n const newLines: string[] = [];\n const blockMarkers: { extension: string; line: number }[] = [];\n let skippingBlock = false;\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.includes(SINGLE_LINE_MARKER)) {\n const matchingExtension = Object.keys(extensions).find((extension) => line.includes(extension));\n if (matchingExtension && extensions[matchingExtension] === false) {\n // Skip the marker line and the next line (the actual code line)\n i += 2;\n continue;\n }\n } else if (line.includes(BLOCK_MARKER_START)) {\n const matchingExtension = Object.keys(extensions).find((extension) => line.includes(extension));\n if (matchingExtension) {\n blockMarkers.push({ extension: matchingExtension, line: i });\n skippingBlock = extensions[matchingExtension] === false;\n } else {\n if (verbose) {\n console.warn(`Warning: Unknown marker found in ${filePath} at line ${i}: \\n${line}`);\n }\n }\n } else if (line.includes(BLOCK_MARKER_END)) {\n const matchingExtension = Object.keys(extensions).find((extension) => line.includes(extension));\n if (matchingExtension) {\n const extension = Object.keys(extensions).find((p) => line.includes(p));\n if (blockMarkers.length === 0) {\n throw new Error(\n `Block marker mismatch in ${filePath}, encountered end marker ${extension} without a matching start marker at line ${i}:\\n${lines[i]}`\n );\n }\n const startMarker = blockMarkers.pop() as { extension: string; line: number };\n if (!extension || startMarker.extension !== extension) {\n throw new Error(\n `Block marker mismatch in ${filePath}, expected end marker for ${startMarker.extension} but got ${extension} at line ${i}:\\n${lines[i]}`\n );\n }\n if (extensions[extension] === false) {\n // Skip the entire block (marker lines are already skipped by skippingBlock)\n skippingBlock = false;\n i++;\n continue;\n }\n }\n }\n if (!skippingBlock) {\n newLines.push(line);\n }\n i++;\n }\n\n if (blockMarkers.length > 0) {\n throw new Error(\n `Unclosed end marker found in ${filePath}: ${blockMarkers[blockMarkers.length - 1].extension}`\n );\n }\n\n // Only write if content changed\n const newSource = newLines.join('\\n');\n if (newSource !== source) {\n try {\n fs.writeFileSync(filePath, newSource);\n if (verbose) {\n console.log(`Updated file ${filePath}`);\n }\n } catch (e: unknown) {\n const error = e as Error;\n console.error(`Error updating file ${filePath}: ${error.message}`);\n throw e;\n }\n }\n }\n}\n\n/**\n * Delete extension folders for disabled extensions.\n * @param projectRoot - The project root directory\n * @param extensions - The extension selections\n * @param extensionConfig - The extension configuration\n */\nfunction deleteExtensionFolders(\n projectRoot: string,\n extensions: ExtensionsSelection,\n extensionConfig: typeof ExtensionConfig & { extensions: Record<string, unknown> }\n): void {\n const extensionsDir = path.join(projectRoot, 'src', 'extensions');\n if (!fs.existsSync(extensionsDir)) {\n return;\n }\n\n const configuredExtensions = extensionConfig.extensions;\n const disabledExtensions = Object.keys(extensions).filter((ext) => extensions[ext] === false);\n\n disabledExtensions.forEach((extKey) => {\n const extensionMeta = configuredExtensions[extKey] as { folder?: string } | undefined;\n if (extensionMeta?.folder) {\n const extensionFolderPath = path.join(extensionsDir, extensionMeta.folder);\n if (fs.existsSync(extensionFolderPath)) {\n try {\n fs.rmSync(extensionFolderPath, { recursive: true, force: true });\n if (verbose) {\n console.log(`Deleted extension folder: ${extensionFolderPath}`);\n }\n } catch (err: unknown) {\n const error = err as Error & { code?: string };\n if (error.code === 'EPERM') {\n console.error(\n `Permission denied - cannot delete ${extensionFolderPath}. You may need to run with sudo or check permissions.`\n );\n } else {\n console.error(`Error deleting ${extensionFolderPath}: ${error.message}`);\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 { join } from 'node:path';\nimport { existsSync, readFileSync, unlinkSync } from 'node:fs';\nimport { execSync } from 'node:child_process';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport { npmRunPathEnv } from 'npm-run-path';\nimport type { RouteConfigEntry } from '@react-router/dev/routes';\n\nlet isCliAvailable: boolean | null = null;\n\nfunction checkReactRouterCli(projectDirectory: string): boolean {\n if (isCliAvailable !== null) {\n return isCliAvailable;\n }\n\n try {\n execSync('react-router --version', {\n cwd: projectDirectory,\n env: npmRunPathEnv(),\n stdio: 'pipe',\n });\n isCliAvailable = true;\n } catch {\n isCliAvailable = false;\n }\n return isCliAvailable;\n}\n\n/**\n * Get the fully resolved routes from React Router by invoking its CLI.\n * This ensures we get the exact same route resolution as React Router uses internally,\n * including all presets, file-system routes, and custom route configurations.\n * @param projectDirectory - The project root directory\n * @returns Array of resolved route config entries\n * @example\n * const routes = getReactRouterRoutes('/path/to/project');\n * // Returns the same structure as `react-router routes --json`\n */\nfunction getReactRouterRoutes(projectDirectory: string): RouteConfigEntry[] {\n if (!checkReactRouterCli(projectDirectory)) {\n throw new Error(\n 'React Router CLI is not available. Please make sure @react-router/dev is installed and accessible.'\n );\n }\n\n // Use a temp file to avoid Node.js buffer limits (8KB default)\n const tempFile = join(tmpdir(), `react-router-routes-${randomUUID()}.json`);\n\n try {\n // Redirect output to temp file to avoid buffer truncation\n execSync(`react-router routes --json > \"${tempFile}\"`, {\n cwd: projectDirectory,\n env: npmRunPathEnv(),\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const output = readFileSync(tempFile, 'utf-8');\n return JSON.parse(output) as RouteConfigEntry[];\n } catch (error) {\n throw new Error(`Failed to get routes from React Router CLI: ${(error as Error).message}`);\n } finally {\n // Clean up temp file\n try {\n if (existsSync(tempFile)) {\n unlinkSync(tempFile);\n }\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n\n/**\n * Convert a file path to its corresponding route path using React Router's CLI.\n * This ensures we get the exact same route resolution as React Router uses internally.\n * @param filePath - Absolute path to the route file\n * @param projectRoot - The project root directory\n * @returns The route path (e.g., '/cart', '/product/:productId')\n * @example\n * const route = filePathToRoute('/path/to/project/src/routes/_app.cart.tsx', '/path/to/project');\n * // Returns: '/cart'\n */\nexport function filePathToRoute(filePath: string, projectRoot: string): string {\n // Normalize paths to POSIX-style\n const filePathPosix = filePath.replace(/\\\\/g, '/');\n\n // Get all routes from React Router CLI\n const routes = getReactRouterRoutes(projectRoot);\n const flatRoutes = flattenRoutes(routes);\n\n // Find the route that matches this file\n for (const route of flatRoutes) {\n // Normalize the route file path for comparison\n const routeFilePosix = route.file.replace(/\\\\/g, '/');\n\n // Check if the file path ends with the route file (handles relative vs. absolute paths)\n if (filePathPosix.endsWith(routeFilePosix) || filePathPosix.endsWith(`/${routeFilePosix}`)) {\n return route.path;\n }\n\n // Also check without leading ./\n const routeFileNormalized = routeFilePosix.replace(/^\\.\\//, '');\n if (filePathPosix.endsWith(routeFileNormalized) || filePathPosix.endsWith(`/${routeFileNormalized}`)) {\n return route.path;\n }\n }\n\n // Fallback: if no match found, return a warning path\n console.warn(`Warning: Could not find route for file: ${filePath}`);\n return '/unknown';\n}\n\n/**\n * Flatten a nested route tree into a flat array with computed paths.\n * Each route will have its full path computed from parent paths.\n * @param routes - The nested route config entries\n * @param parentPath - The parent path prefix (used internally for recursion)\n * @returns Flat array of routes with their full paths\n */\nfunction flattenRoutes(\n routes: RouteConfigEntry[],\n parentPath = ''\n): Array<{ id: string; path: string; file: string; index?: boolean }> {\n const result: Array<{ id: string; path: string; file: string; index?: boolean }> = [];\n\n for (const route of routes) {\n // Compute the full path\n let fullPath: string;\n if (route.index) {\n fullPath = parentPath || '/';\n } else if (route.path) {\n // Handle paths that already start with / (absolute paths from extensions)\n const pathSegment = route.path.startsWith('/') ? route.path : `/${route.path}`;\n fullPath = parentPath ? `${parentPath}${pathSegment}`.replace(/\\/+/g, '/') : pathSegment;\n } else {\n // Layout route without path - use parent path\n fullPath = parentPath || '/';\n }\n\n // Add this route if it has an id\n if (route.id) {\n result.push({\n id: route.id,\n path: fullPath,\n file: route.file,\n index: route.index,\n });\n }\n\n // Recursively process children\n if (route.children && route.children.length > 0) {\n const childPath = route.path ? fullPath : parentPath;\n result.push(...flattenRoutes(route.children, childPath));\n }\n }\n\n return result;\n}\n","#!/usr/bin/env node\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/* eslint-disable no-console */\nimport { readdir, readFile, writeFile, mkdir, access, rm } from 'node:fs/promises';\nimport { join, extname, resolve, basename } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { Project, Node, type SourceFile, type PropertyDeclaration, type Decorator } from 'ts-morph';\nimport { filePathToRoute } from './react-router-config.js';\n\n// Re-export `filePathToRoute`\nexport { filePathToRoute };\n\nconst SKIP_DIRECTORIES = ['build', 'dist', 'node_modules', '.git', '.next', 'coverage'];\n\nconst DEFAULT_COMPONENT_GROUP = 'odyssey_base';\nconst ARCH_TYPE_HEADLESS = 'headless';\n\ntype AttributeType =\n | 'string'\n | 'text'\n | 'markup'\n | 'integer'\n | 'boolean'\n | 'product'\n | 'category'\n | 'file'\n | 'page'\n | 'image'\n | 'url'\n | 'enum'\n | 'custom'\n | 'cms_record';\n\nconst VALID_ATTRIBUTE_TYPES: readonly AttributeType[] = [\n 'string',\n 'text',\n 'markup',\n 'integer',\n 'boolean',\n 'product',\n 'category',\n 'file',\n 'page',\n 'image',\n 'url',\n 'enum',\n 'custom',\n 'cms_record',\n] as const;\n\n// Type mapping for TypeScript types to B2C Commerce attribute types\n// Based on official schema: https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/current/content/attributedefinition.json\nconst TYPE_MAPPING: Record<string, string> = {\n String: 'string',\n string: 'string',\n Number: 'integer',\n number: 'integer',\n Boolean: 'boolean',\n boolean: 'boolean',\n Date: 'string', // B2C Commerce doesn't have a native date type, use string\n URL: 'url',\n CMSRecord: 'cms_record',\n};\n\n// Resolve attribute type in order: decorator type -> ts-morph type inference -> fallback to string\nfunction resolveAttributeType(decoratorType?: string, tsMorphType?: string, fieldName?: string): string {\n // 1) If the type is set on the decorator, use that (with validation)\n if (decoratorType) {\n if (!VALID_ATTRIBUTE_TYPES.includes(decoratorType as AttributeType)) {\n console.error(\n `Error: Invalid attribute type '${decoratorType}' for field '${fieldName || 'unknown'}'. Valid types are: ${VALID_ATTRIBUTE_TYPES.join(', ')}`\n );\n process.exit(1);\n }\n return decoratorType;\n }\n\n // 2) Use the type from ts-morph type inference\n if (tsMorphType && TYPE_MAPPING[tsMorphType]) {\n return TYPE_MAPPING[tsMorphType];\n }\n\n // 3) Fall back to string\n return 'string';\n}\n\n// Convert field name to human-readable name\nfunction toHumanReadableName(fieldName: string): string {\n return fieldName\n .replace(/([A-Z])/g, ' $1') // Add space before capital letters\n .replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter\n .trim();\n}\n\n// Convert name to camelCase filename (handles spaces and hyphens, preserves existing camelCase)\nfunction toCamelCaseFileName(name: string): string {\n // If the name is already camelCase (no spaces or hyphens), return as-is\n if (!/[\\s-]/.test(name)) {\n return name;\n }\n\n return name\n .split(/[\\s-]+/) // Split by whitespace and hyphens\n .map((word, index) => {\n if (index === 0) {\n return word.toLowerCase(); // First word is all lowercase\n }\n return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); // Subsequent words are capitalized\n })\n .join(''); // Join without spaces or hyphens\n}\n\nfunction getTypeFromTsMorph(property: PropertyDeclaration, _sourceFile: SourceFile): string {\n try {\n const typeNode = property.getTypeNode();\n if (typeNode) {\n const typeText = typeNode.getText();\n // Extract the base type name from complex types\n const baseType = typeText.split('|')[0].split('&')[0].trim();\n return baseType;\n }\n } catch {\n // If type extraction fails, return string\n }\n\n return 'string';\n}\n\n// Helper function to parse any TypeScript expression into a JavaScript value\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction parseExpression(expression: any): unknown {\n if (Node.isStringLiteral(expression)) {\n return expression.getLiteralValue();\n } else if (Node.isNumericLiteral(expression)) {\n return expression.getLiteralValue();\n } else if (Node.isTrueLiteral(expression)) {\n return true;\n } else if (Node.isFalseLiteral(expression)) {\n return false;\n } else if (Node.isObjectLiteralExpression(expression)) {\n return parseNestedObject(expression);\n } else if (Node.isArrayLiteralExpression(expression)) {\n return parseArrayLiteral(expression);\n } else {\n return expression.getText();\n }\n}\n\n// Helper function to parse deeply nested object literals\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction parseNestedObject(objectLiteral: any): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n try {\n const properties = objectLiteral.getProperties();\n\n for (const property of properties) {\n if (Node.isPropertyAssignment(property)) {\n const name = property.getName();\n const initializer = property.getInitializer();\n\n if (initializer) {\n result[name] = parseExpression(initializer);\n }\n }\n }\n } catch (error) {\n console.warn(`Warning: Could not parse nested object: ${(error as Error).message}`);\n return result; // Return the result even if there was an error\n }\n\n return result;\n}\n\n// Helper function to parse array literals\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction parseArrayLiteral(arrayLiteral: any): unknown[] {\n const result: unknown[] = [];\n\n try {\n const elements = arrayLiteral.getElements();\n\n for (const element of elements) {\n result.push(parseExpression(element));\n }\n } catch (error) {\n console.warn(`Warning: Could not parse array literal: ${(error as Error).message}`);\n }\n\n return result;\n}\n\n// Parse decorator arguments using ts-morph\nfunction parseDecoratorArgs(decorator: Decorator): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n try {\n const args = decorator.getArguments();\n\n if (args.length === 0) {\n return result;\n }\n\n // Handle the first argument\n const firstArg = args[0];\n\n if (Node.isObjectLiteralExpression(firstArg)) {\n // First argument is an object literal - parse all its properties\n const properties = firstArg.getProperties();\n\n for (const property of properties) {\n if (Node.isPropertyAssignment(property)) {\n const name = property.getName();\n const initializer = property.getInitializer();\n\n if (initializer) {\n result[name] = parseExpression(initializer);\n }\n }\n }\n } else if (Node.isStringLiteral(firstArg)) {\n // First argument is a string literal - use it as the id\n result.id = parseExpression(firstArg);\n\n // Check if there's a second argument (options object)\n if (args.length > 1) {\n const secondArg = args[1];\n if (Node.isObjectLiteralExpression(secondArg)) {\n const properties = secondArg.getProperties();\n\n for (const property of properties) {\n if (Node.isPropertyAssignment(property)) {\n const name = property.getName();\n const initializer = property.getInitializer();\n\n if (initializer) {\n result[name] = parseExpression(initializer);\n }\n }\n }\n }\n }\n }\n\n return result;\n } catch (error) {\n console.warn(`Warning: Could not parse decorator arguments: ${(error as Error).message}`);\n return result;\n }\n}\n\nfunction extractAttributesFromSource(sourceFile: SourceFile, className: string): Record<string, unknown>[] {\n const attributes: Record<string, unknown>[] = [];\n\n try {\n // Find the class declaration\n const classDeclaration = sourceFile.getClass(className);\n if (!classDeclaration) {\n return attributes;\n }\n\n // Get all properties in the class\n const properties = classDeclaration.getProperties();\n\n for (const property of properties) {\n // Check if the property has an @AttributeDefinition decorator\n const attributeDecorator = property.getDecorator('AttributeDefinition');\n if (!attributeDecorator) {\n continue;\n }\n\n const fieldName = property.getName();\n const config = parseDecoratorArgs(attributeDecorator);\n\n const isRequired = !property.hasQuestionToken();\n\n const inferredType = (config.type as string) || getTypeFromTsMorph(property, sourceFile);\n\n const attribute: Record<string, unknown> = {\n id: config.id || fieldName,\n name: config.name || toHumanReadableName(fieldName),\n type: resolveAttributeType(config.type as string, inferredType, fieldName),\n required: config.required !== undefined ? config.required : isRequired,\n description: config.description || `Field: ${fieldName}`,\n };\n\n if (config.values) {\n attribute.values = config.values;\n }\n\n if (config.defaultValue !== undefined) {\n attribute.default_value = config.defaultValue;\n }\n\n attributes.push(attribute);\n }\n } catch (error) {\n console.warn(`Warning: Could not extract attributes from class ${className}: ${(error as Error).message}`);\n }\n\n return attributes;\n}\n\nfunction extractRegionDefinitionsFromSource(sourceFile: SourceFile, className: string): Record<string, unknown>[] {\n const regionDefinitions: Record<string, unknown>[] = [];\n\n try {\n // Find the class declaration\n const classDeclaration = sourceFile.getClass(className);\n if (!classDeclaration) {\n return regionDefinitions;\n }\n\n // Check for class-level @RegionDefinition decorator\n const classRegionDecorator = classDeclaration.getDecorator('RegionDefinition');\n if (classRegionDecorator) {\n const args = classRegionDecorator.getArguments();\n if (args.length > 0) {\n const firstArg = args[0];\n\n // Handle array literal argument (most common case)\n if (Node.isArrayLiteralExpression(firstArg)) {\n const elements = firstArg.getElements();\n for (const element of elements) {\n if (Node.isObjectLiteralExpression(element)) {\n const regionConfig = parseDecoratorArgs({\n getArguments: () => [element],\n } as unknown as Decorator);\n\n const regionDefinition: Record<string, unknown> = {\n id: regionConfig.id || 'region',\n name: regionConfig.name || 'Region',\n };\n\n // Add optional properties if they exist in the decorator\n if (regionConfig.componentTypes) {\n regionDefinition.component_types = regionConfig.componentTypes;\n }\n\n if (Array.isArray(regionConfig.componentTypeInclusions)) {\n regionDefinition.component_type_inclusions = regionConfig.componentTypeInclusions.map(\n (incl) => ({\n type_id: incl,\n })\n );\n }\n\n if (Array.isArray(regionConfig.componentTypeExclusions)) {\n regionDefinition.component_type_exclusions = regionConfig.componentTypeExclusions.map(\n (excl) => ({\n type_id: excl,\n })\n );\n }\n\n if (regionConfig.maxComponents !== undefined) {\n regionDefinition.max_components = regionConfig.maxComponents;\n }\n\n if (regionConfig.minComponents !== undefined) {\n regionDefinition.min_components = regionConfig.minComponents;\n }\n\n if (regionConfig.allowMultiple !== undefined) {\n regionDefinition.allow_multiple = regionConfig.allowMultiple;\n }\n\n if (regionConfig.defaultComponentConstructors) {\n regionDefinition.default_component_constructors =\n regionConfig.defaultComponentConstructors;\n }\n\n regionDefinitions.push(regionDefinition);\n }\n }\n }\n }\n }\n } catch (error) {\n console.warn(\n `Warning: Could not extract region definitions from class ${className}: ${(error as Error).message}`\n );\n }\n\n return regionDefinitions;\n}\n\nasync function processComponentFile(filePath: string, _projectRoot: string): Promise<unknown[]> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const components: unknown[] = [];\n\n // Check if file contains @Component decorator\n if (!content.includes('@Component')) {\n return components;\n }\n\n // Convert file path to module path (currently unused but may be needed in future)\n // const relativePath = relative(join(projectRoot, 'src'), filePath);\n // const modulePath = relativePath.replace(/\\.tsx?$/, '').replace(/\\\\/g, '/');\n\n try {\n // Create a ts-morph project and add the source file\n const project = new Project({\n useInMemoryFileSystem: true,\n skipAddingFilesFromTsConfig: true,\n });\n\n const sourceFile = project.createSourceFile(filePath, content);\n\n const classes = sourceFile.getClasses();\n\n for (const classDeclaration of classes) {\n const componentDecorator = classDeclaration.getDecorator('Component');\n if (!componentDecorator) {\n continue;\n }\n\n const className = classDeclaration.getName();\n if (!className) {\n continue;\n }\n\n const componentConfig = parseDecoratorArgs(componentDecorator);\n\n const attributes = extractAttributesFromSource(sourceFile, className);\n const regionDefinitions = extractRegionDefinitionsFromSource(sourceFile, className);\n\n const componentMetadata = {\n typeId: componentConfig.id || className.toLowerCase(),\n name: componentConfig.name || toHumanReadableName(className),\n group: componentConfig.group || DEFAULT_COMPONENT_GROUP,\n description: componentConfig.description || `Custom component: ${className}`,\n regionDefinitions,\n attributes,\n };\n\n components.push(componentMetadata);\n }\n } catch (error) {\n console.warn(`Warning: Could not process file ${filePath}:`, (error as Error).message);\n }\n\n return components;\n } catch (error) {\n console.warn(`Warning: Could not read file ${filePath}:`, (error as Error).message);\n return [];\n }\n}\n\nasync function processPageTypeFile(filePath: string, projectRoot: string): Promise<unknown[]> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const pageTypes: unknown[] = [];\n\n // Check if file contains @PageType decorator\n if (!content.includes('@PageType')) {\n return pageTypes;\n }\n\n try {\n // Create a ts-morph project and add the source file\n const project = new Project({\n useInMemoryFileSystem: true,\n skipAddingFilesFromTsConfig: true,\n });\n\n const sourceFile = project.createSourceFile(filePath, content);\n\n const classes = sourceFile.getClasses();\n\n for (const classDeclaration of classes) {\n const pageTypeDecorator = classDeclaration.getDecorator('PageType');\n if (!pageTypeDecorator) {\n continue;\n }\n\n const className = classDeclaration.getName();\n if (!className) {\n continue;\n }\n\n const pageTypeConfig = parseDecoratorArgs(pageTypeDecorator);\n\n const attributes = extractAttributesFromSource(sourceFile, className);\n const regionDefinitions = extractRegionDefinitionsFromSource(sourceFile, className);\n const route = filePathToRoute(filePath, projectRoot);\n\n const pageTypeMetadata = {\n typeId: pageTypeConfig.id || className.toLowerCase(),\n name: pageTypeConfig.name || toHumanReadableName(className),\n description: pageTypeConfig.description || `Custom page type: ${className}`,\n regionDefinitions,\n supportedAspectTypes: pageTypeConfig.supportedAspectTypes || [],\n attributes,\n route,\n };\n\n pageTypes.push(pageTypeMetadata);\n }\n } catch (error) {\n console.warn(`Warning: Could not process file ${filePath}:`, (error as Error).message);\n }\n\n return pageTypes;\n } catch (error) {\n console.warn(`Warning: Could not read file ${filePath}:`, (error as Error).message);\n return [];\n }\n}\n\nasync function processAspectFile(filePath: string, _projectRoot: string): Promise<unknown[]> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const aspects: unknown[] = [];\n\n // Check if file is a JSON aspect file\n if (!filePath.endsWith('.json') || !content.trim().startsWith('{')) {\n return aspects;\n }\n\n // Check if file is in the aspects directory\n if (!filePath.includes('/aspects/') && !filePath.includes('\\\\aspects\\\\')) {\n return aspects;\n }\n\n try {\n // Parse the JSON content\n const aspectData = JSON.parse(content);\n\n // Extract filename without extension as the aspect ID\n const fileName = basename(filePath, '.json');\n\n // Validate that it looks like an aspect file\n if (!aspectData.name || !aspectData.attribute_definitions) {\n return aspects;\n }\n\n const aspectMetadata = {\n id: fileName,\n name: aspectData.name,\n description: aspectData.description || `Aspect type: ${aspectData.name}`,\n attributeDefinitions: aspectData.attribute_definitions || [],\n supportedObjectTypes: aspectData.supported_object_types || [],\n };\n\n aspects.push(aspectMetadata);\n } catch (parseError) {\n console.warn(`Warning: Could not parse JSON in file ${filePath}:`, (parseError as Error).message);\n }\n\n return aspects;\n } catch (error) {\n console.warn(`Warning: Could not read file ${filePath}:`, (error as Error).message);\n return [];\n }\n}\n\nasync function generateComponentCartridge(\n component: Record<string, unknown>,\n outputDir: string,\n dryRun = false\n): Promise<void> {\n const fileName = toCamelCaseFileName(component.typeId as string);\n const groupDir = join(outputDir, component.group as string);\n const outputPath = join(groupDir, `${fileName}.json`);\n\n if (!dryRun) {\n // Ensure the group directory exists\n try {\n await mkdir(groupDir, { recursive: true });\n } catch {\n // Directory might already exist, which is fine\n }\n\n const attributeDefinitionGroups = [\n {\n id: component.typeId,\n name: component.name,\n description: component.description,\n attribute_definitions: component.attributes,\n },\n ];\n\n const cartridgeData = {\n name: component.name,\n description: component.description,\n group: component.group,\n arch_type: ARCH_TYPE_HEADLESS,\n region_definitions: component.regionDefinitions || [],\n attribute_definition_groups: attributeDefinitionGroups,\n };\n\n await writeFile(outputPath, JSON.stringify(cartridgeData, null, 2));\n }\n\n const prefix = dryRun ? ' - [DRY RUN]' : ' -';\n console.log(\n `${prefix} ${String(component.typeId)}: ${String(component.name)} (${String((component.attributes as unknown[]).length)} attributes) → ${fileName}.json`\n );\n}\n\nasync function generatePageTypeCartridge(\n pageType: Record<string, unknown>,\n outputDir: string,\n dryRun = false\n): Promise<void> {\n const fileName = toCamelCaseFileName(pageType.name as string);\n const outputPath = join(outputDir, `${fileName}.json`);\n\n if (!dryRun) {\n const cartridgeData: Record<string, unknown> = {\n name: pageType.name,\n description: pageType.description,\n arch_type: ARCH_TYPE_HEADLESS,\n region_definitions: pageType.regionDefinitions || [],\n };\n\n // Add attribute_definition_groups if there are attributes\n if (pageType.attributes && (pageType.attributes as unknown[]).length > 0) {\n const attributeDefinitionGroups = [\n {\n id: pageType.typeId || fileName,\n name: pageType.name,\n description: pageType.description,\n attribute_definitions: pageType.attributes,\n },\n ];\n cartridgeData.attribute_definition_groups = attributeDefinitionGroups;\n }\n\n // Add supported_aspect_types if specified\n if (pageType.supportedAspectTypes) {\n cartridgeData.supported_aspect_types = pageType.supportedAspectTypes;\n }\n\n if (pageType.route) {\n cartridgeData.route = pageType.route;\n }\n\n await writeFile(outputPath, JSON.stringify(cartridgeData, null, 2));\n }\n\n const prefix = dryRun ? ' - [DRY RUN]' : ' -';\n console.log(\n `${prefix} ${String(pageType.name)}: ${String(pageType.description)} (${String((pageType.attributes as unknown[]).length)} attributes) → ${fileName}.json`\n );\n}\n\nasync function generateAspectCartridge(\n aspect: Record<string, unknown>,\n outputDir: string,\n dryRun = false\n): Promise<void> {\n const fileName = toCamelCaseFileName(aspect.id as string);\n const outputPath = join(outputDir, `${fileName}.json`);\n\n if (!dryRun) {\n const cartridgeData: Record<string, unknown> = {\n name: aspect.name,\n description: aspect.description,\n arch_type: ARCH_TYPE_HEADLESS,\n attribute_definitions: aspect.attributeDefinitions || [],\n };\n\n // Add supported_object_types if specified\n if (aspect.supportedObjectTypes) {\n cartridgeData.supported_object_types = aspect.supportedObjectTypes;\n }\n\n await writeFile(outputPath, JSON.stringify(cartridgeData, null, 2));\n }\n\n const prefix = dryRun ? ' - [DRY RUN]' : ' -';\n console.log(\n `${prefix} ${String(aspect.name)}: ${String(aspect.description)} (${String((aspect.attributeDefinitions as unknown[]).length)} attributes) → ${fileName}.json`\n );\n}\n\n/**\n * Options for generateMetadata function\n */\nexport interface GenerateMetadataOptions {\n /**\n * Optional array of specific file paths to process.\n * If provided, only these files will be processed and existing cartridge files will NOT be deleted.\n * If omitted, the entire src/ directory will be scanned and all existing cartridge files will be deleted first.\n */\n filePaths?: string[];\n\n /**\n * Whether to run ESLint with --fix on generated JSON files to format them according to project settings.\n * Defaults to true.\n */\n lintFix?: boolean;\n\n /**\n * If true, scans files and reports what would be generated without actually writing any files or deleting directories.\n * Defaults to false.\n */\n dryRun?: boolean;\n}\n\n/**\n * Result returned by generateMetadata function\n */\nexport interface GenerateMetadataResult {\n componentsGenerated: number;\n pageTypesGenerated: number;\n aspectsGenerated: number;\n totalFiles: number;\n}\n\n/**\n * Runs ESLint with --fix on the specified directory to format JSON files.\n * This ensures generated JSON files match the project's Prettier/ESLint configuration.\n */\nfunction lintGeneratedFiles(metadataDir: string, projectRoot: string): void {\n try {\n console.log('🔧 Running ESLint --fix on generated JSON files...');\n\n // Run ESLint from the project root directory so it picks up the correct config\n // Use --no-error-on-unmatched-pattern to handle cases where no JSON files exist yet\n const command = `npx eslint \"${metadataDir}/**/*.json\" --fix --no-error-on-unmatched-pattern`;\n\n execSync(command, {\n cwd: projectRoot,\n stdio: 'pipe', // Suppress output unless there's an error\n encoding: 'utf-8',\n });\n\n console.log('✅ JSON files formatted successfully');\n } catch (error) {\n // ESLint returns non-zero exit code even when --fix resolves all issues\n // We only warn if there are actual unfixable issues\n const execError = error as { status?: number; stderr?: string; stdout?: string };\n\n // Exit code 1 usually means there were linting issues (some may have been fixed)\n // Exit code 2 means configuration error or other fatal error\n if (execError.status === 2) {\n const errMsg = execError.stderr || execError.stdout || 'Unknown error';\n console.warn(`⚠️ Warning: Could not run ESLint --fix: ${errMsg}`);\n } else if (execError.stderr && execError.stderr.includes('error')) {\n console.warn(`⚠️ Warning: Some linting issues could not be auto-fixed. Run ESLint manually to review.`);\n } else {\n // Exit code 1 with no errors in stderr usually means all issues were fixed\n console.log('✅ JSON files formatted successfully');\n }\n }\n}\n\n// Main function\nexport async function generateMetadata(\n projectDirectory: string,\n metadataDirectory: string,\n options?: GenerateMetadataOptions\n): Promise<GenerateMetadataResult> {\n try {\n const filePaths = options?.filePaths;\n const isIncrementalMode = filePaths && filePaths.length > 0;\n const dryRun = options?.dryRun || false;\n\n if (dryRun) {\n console.log('🔍 [DRY RUN] Scanning for decorated components and page types...');\n } else if (isIncrementalMode) {\n console.log(`🔍 Generating metadata for ${filePaths.length} specified file(s)...`);\n } else {\n console.log('🔍 Generating metadata for decorated components and page types...');\n }\n\n const projectRoot = resolve(projectDirectory);\n const srcDir = join(projectRoot, 'src');\n const metadataDir = resolve(metadataDirectory);\n const componentsOutputDir = join(metadataDir, 'components');\n const pagesOutputDir = join(metadataDir, 'pages');\n const aspectsOutputDir = join(metadataDir, 'aspects');\n\n // Skip directory operations in dry run mode\n if (!dryRun) {\n // Only delete existing directories in full scan mode (not incremental)\n if (!isIncrementalMode) {\n console.log('🗑️ Cleaning existing output directories...');\n for (const outputDir of [componentsOutputDir, pagesOutputDir, aspectsOutputDir]) {\n try {\n await rm(outputDir, { recursive: true, force: true });\n console.log(` - Deleted: ${outputDir}`);\n } catch {\n // Directory might not exist, which is fine\n console.log(` - Directory not found (skipping): ${outputDir}`);\n }\n }\n } else {\n console.log('📝 Incremental mode: existing cartridge files will be preserved/overwritten');\n }\n\n // Create output directories if they don't exist\n console.log('📁 Creating output directories...');\n for (const outputDir of [componentsOutputDir, pagesOutputDir, aspectsOutputDir]) {\n try {\n await mkdir(outputDir, { recursive: true });\n } catch (error) {\n try {\n await access(outputDir);\n // Directory exists, that's fine\n } catch {\n console.error(\n `❌ Error: Failed to create output directory ${outputDir}: ${(error as Error).message}`\n );\n process.exit(1);\n }\n }\n }\n } else if (isIncrementalMode) {\n console.log(`📝 [DRY RUN] Would process ${filePaths.length} specific file(s)`);\n } else {\n console.log('📝 [DRY RUN] Would clean and regenerate all metadata files');\n }\n\n let files: string[] = [];\n\n if (isIncrementalMode && filePaths) {\n // Use the specified file paths (resolve them relative to project root)\n files = filePaths.map((fp) => resolve(projectRoot, fp));\n console.log(`📂 Processing ${files.length} specified file(s)...`);\n } else {\n // Full scan mode: scan entire src directory\n const scanDirectory = async (dir: string): Promise<void> => {\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n\n if (entry.isDirectory()) {\n if (!SKIP_DIRECTORIES.includes(entry.name)) {\n await scanDirectory(fullPath);\n }\n } else if (\n entry.isFile() &&\n (extname(entry.name) === '.ts' ||\n extname(entry.name) === '.tsx' ||\n extname(entry.name) === '.json')\n ) {\n files.push(fullPath);\n }\n }\n };\n\n await scanDirectory(srcDir);\n }\n\n // Process each file for both components and page types\n const allComponents: unknown[] = [];\n const allPageTypes: unknown[] = [];\n const allAspects: unknown[] = [];\n\n for (const file of files) {\n const components = await processComponentFile(file, projectRoot);\n allComponents.push(...components);\n\n const pageTypes = await processPageTypeFile(file, projectRoot);\n allPageTypes.push(...pageTypes);\n\n const aspects = await processAspectFile(file, projectRoot);\n allAspects.push(...aspects);\n }\n\n if (allComponents.length === 0 && allPageTypes.length === 0 && allAspects.length === 0) {\n console.log('⚠️ No decorated components, page types, or aspect files found.');\n return {\n componentsGenerated: 0,\n pageTypesGenerated: 0,\n aspectsGenerated: 0,\n totalFiles: 0,\n };\n }\n\n // Generate component cartridge files\n if (allComponents.length > 0) {\n console.log(`✅ Found ${allComponents.length} decorated component(s):`);\n for (const component of allComponents) {\n await generateComponentCartridge(component as Record<string, unknown>, componentsOutputDir, dryRun);\n }\n if (dryRun) {\n console.log(\n `📄 [DRY RUN] Would generate ${allComponents.length} component metadata file(s) in: ${componentsOutputDir}`\n );\n } else {\n console.log(\n `📄 Generated ${allComponents.length} component metadata file(s) in: ${componentsOutputDir}`\n );\n }\n }\n\n // Generate page type cartridge files\n if (allPageTypes.length > 0) {\n console.log(`✅ Found ${allPageTypes.length} decorated page type(s):`);\n for (const pageType of allPageTypes) {\n await generatePageTypeCartridge(pageType as Record<string, unknown>, pagesOutputDir, dryRun);\n }\n if (dryRun) {\n console.log(\n `📄 [DRY RUN] Would generate ${allPageTypes.length} page type metadata file(s) in: ${pagesOutputDir}`\n );\n } else {\n console.log(`📄 Generated ${allPageTypes.length} page type metadata file(s) in: ${pagesOutputDir}`);\n }\n }\n\n if (allAspects.length > 0) {\n console.log(`✅ Found ${allAspects.length} decorated aspect(s):`);\n for (const aspect of allAspects) {\n await generateAspectCartridge(aspect as Record<string, unknown>, aspectsOutputDir, dryRun);\n }\n if (dryRun) {\n console.log(\n `📄 [DRY RUN] Would generate ${allAspects.length} aspect metadata file(s) in: ${aspectsOutputDir}`\n );\n } else {\n console.log(`📄 Generated ${allAspects.length} aspect metadata file(s) in: ${aspectsOutputDir}`);\n }\n }\n\n // Run ESLint --fix to format generated JSON files according to project settings\n const shouldLintFix = options?.lintFix !== false; // Default to true\n if (\n !dryRun &&\n shouldLintFix &&\n (allComponents.length > 0 || allPageTypes.length > 0 || allAspects.length > 0)\n ) {\n lintGeneratedFiles(metadataDir, projectRoot);\n }\n\n // Return statistics\n return {\n componentsGenerated: allComponents.length,\n pageTypesGenerated: allPageTypes.length,\n aspectsGenerated: allAspects.length,\n totalFiles: allComponents.length + allPageTypes.length + allAspects.length,\n };\n } catch (error) {\n console.error('❌ Error:', (error as Error).message);\n process.exit(1);\n }\n}\n"],"mappings":"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"}
|
|
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[]","path","componentRegistry: TargetComponentRegistry","contextProviders: TargetContextProviderConfig[]","sourceDir: string","path","err: unknown","viteConfig: ResolvedConfig","glob","path","DEFAULT_COMPONENT_GROUP","baseComponentId: string","components: ComponentInfo[]","resolve","existingContent: string","projectRoot: string","resolve","join","resolve","resolvedConfig: ResolvedConfig","missingInstrumentation: Array<{ adapter: string; event: string }>","resolvedConfig: ResolvedConfig","buildDirectory: string","appDirectory: string","resolve","basename","fs","serverEntryFilePath: string | undefined","clientEntryFilePath: string | undefined","appDirectory: string | undefined","userServerEntryPath: string | undefined","userClientEntryPath: string | undefined","ctx: ReactRouterPluginContext | undefined","relative","path","_traverse","generate","_generate","path","environmentName: string | undefined","plugins: Plugin[]","chunks: Buffer[]","body: Buffer<ArrayBufferLike>","patterns: string[]","match: RegExpExecArray | null","alias: Record<string, string>","existsSync","readFileSync","sortedAlias: Record<string, string>","existsSync","path","colors: Record<string, typeof chalk.green>","ServerModeFeatureMap: Record<ServerMode, ServerModeFeatures>","cachedTracer: Tracer | null","streamingSpan: Span | null","existsSync","path","readFileSync","healthResponse: {\n status: 'pass' | 'warn' | 'fail';\n version?: string;\n bundleId?: string;\n description?: string;\n notes?: string[];\n }","RELATIVE_MIDDLEWARE_REGISTRY_BUILT_PATHS: readonly string[]","registry: MiddlewareRegistry | null","existsSync","builtRegistryPath: string | null","path","pathToFileURL","FILE_EXTENSIONS: string[]","configuredExtensions: Record<string, unknown>","extensions: ExtensionsSelection","fs","path","e: unknown","newLines: string[]","blockMarkers: { extension: string; line: number }[]","err: unknown","isCliAvailable: boolean | null","readFileSync","existsSync","result: Array<{ id: string; path: string; file: string; index?: boolean }>","fullPath: string","VALID_ATTRIBUTE_TYPES: readonly AttributeType[]","TYPE_MAPPING: Record<string, string>","result: Record<string, unknown>","result: unknown[]","attributes: Record<string, unknown>[]","attribute: Record<string, unknown>","regionDefinitions: Record<string, unknown>[]","regionDefinition: Record<string, unknown>","components: unknown[]","pageTypes: unknown[]","aspects: unknown[]","cartridgeData: Record<string, unknown>","files: string[]","allComponents: unknown[]","allPageTypes: unknown[]","allAspects: unknown[]"],"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/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/storefront-next-targets.ts","../src/plugins/hybridProxy.ts","../src/plugins/ecdnMatcher.ts","../src/server/ts-import.ts","../src/server/config.ts","../src/config.ts","../src/server/middleware/proxy.ts","../src/server/middleware/static.ts","../src/server/middleware/compression.ts","../src/server/middleware/logging.ts","../src/server/middleware/host-header.ts","../src/server/utils.ts","../src/server/modes.ts","../src/otel/mrt-console-span-exporter.ts","../src/otel/setup.ts","../src/otel/express/middleware.ts","../src/server/handlers/health-check.ts","../src/server/index.ts","../src/extensibility/path-util.ts","../src/extensibility/trim-extensions.ts","../src/cartridge-services/react-router-config.ts","../src/cartridge-services/generate-cartridge.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: 'odyssey: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: 'odyssey: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: 'odyssey: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 RSC 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 renderBuiltUrl(filename, { type }) {\n if (mode !== 'preview' && (type === 'asset' || type === 'public')) {\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: 'odyssey: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';\n\nexport interface TargetComponentConfig {\n targetId: string;\n path: string;\n namespace: string;\n componentName: string;\n order: number;\n}\n\nexport interface TargetContextProviderConfig {\n path: string;\n namespace: string;\n componentName: string;\n order: number;\n}\n\nexport type TargetComponentRegistry = Record<string, TargetComponentConfig[]>;\n\nconst traverse = (traverseModule as unknown as { default: typeof traverseModule }).default || traverseModule;\n\nconst TARGET_COMPONENT_TAG = 'UITarget';\nconst TARGET_PROVIDERS_TAG = 'TargetProviders';\nconst TARGET_ID_ATTRIBUTE = 'targetId';\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 // Create JSX elements for each 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\n // If multiple components, wrap in a fragment; otherwise use single component\n if (components.length > 1) {\n element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), components));\n } else {\n element.replaceWith(components[0]);\n }\n targetIdReplaced = targetId;\n replaced = true;\n }\n }\n if (!replaced) {\n if (element.node.children && element.node.children.length > 0) {\n // replace the element with its children\n element.replaceWithMultiple(element.node.children);\n } else {\n // No children, just remove the element\n element.remove();\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 (!code.includes(TARGET_COMPONENT_TAG) && !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 (code.includes(TARGET_COMPONENT_TAG)) {\n const targetIdsReplaced = runReplacementPass(ast, TARGET_COMPONENT_TAG, targetRegistry, null);\n const replacementImportStatements = buildReplacementImportStatements(targetIdsReplaced, targetRegistry);\n // Single import rewrite pass\n traverse(ast, {\n ImportDeclaration(nodePath: NodePath<BabelImportDeclaration>) {\n const source = nodePath.node.source.value;\n if (source.includes('@/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.includes('@/targets/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(rootDir: string): {\n componentRegistry: TargetComponentRegistry;\n contextProviders: TargetContextProviderConfig[];\n} {\n const componentRegistry: TargetComponentRegistry = {};\n const contextProviders: TargetContextProviderConfig[] = [];\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.split('/').pop()?.replace('.tsx', '');\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 if (extensionConfig && extensionConfig.components) {\n for (const component of extensionConfig.components) {\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 }\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 return { componentRegistry, contextProviders };\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\n return {\n name: 'odyssey: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 },\n buildStart() {\n // Build the registry once at the start of the build\n ({ componentRegistry, contextProviders } = buildTargetRegistry(sourceDir));\n },\n\n transform(code: string, id: string) {\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 { ViteDevServer, ResolvedConfig } from 'vite';\nimport path from 'path';\nimport { logger } from '../logger';\n\nexport const watchConfigFilesPlugin = () => {\n let viteConfig: ResolvedConfig;\n return {\n name: 'odyssey: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 = 'odyssey_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): void {\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 try {\n writeFileSync(registryFilePath, updatedContent, 'utf-8');\n logger.debug(`💾 Updated registry file: ${registryFilePath}`);\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 updateRegistryFile(registryFilePath, generatedCode);\n\n logger.debug('✅ Static registry generation complete!');\n\n return registryFilePath;\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 = await runRegistryGeneration();\n\n const registryModule = server.moduleGraph.getModuleById(registryFilePath);\n if (registryModule) {\n await server.reloadModule(registryModule);\n }\n\n logger.debug('✅ Registry regenerated successfully!');\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: 'odyssey: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: 'odyssey: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 { 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 { 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';\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 RSC 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 ...(process.env.SCAPI_PROXY_HOST ? [workspacePlugin()] : []),\n managedRuntimeBundlePlugin(),\n fixReactRouterManifestUrlsPlugin(),\n patchReactRouterPlugin(),\n platformEntryPlugin(),\n transformTargetPlaceholderPlugin(),\n watchConfigFilesPlugin(),\n buildMiddlewareRegistryPlugin(),\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 */\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 * - PUBLIC__app__defaultSiteId (required) - Site ID for SFRA path transformation (e.g., 'RefArchGlobal')\n * - HYBRID_PROXY_LOCALE (optional) - Locale for SFRA path transformation (e.g., 'en-GB')\n * - PUBLIC__app__i18n__fallbackLng (fallback) - Used if HYBRID_PROXY_LOCALE not set\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 Site ID (e.g., 'RefArchGlobal') */\n siteId: 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 * 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 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 logger.debug(\n `Hybrid proxy path transformation: / → /s/${options.siteId}, /path → /s/${options.siteId}/${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 // - Missing siteId or locale config\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/${options.siteId}${url.search}`;\n } else {\n // Rewrite internal proxy path without changing browser URL\n proxyReq.path = `/s/${options.siteId}/${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 const locationHeader = proxyRes.headers.location;\n const statusCode = proxyRes.statusCode || 200;\n const isRedirectToError =\n statusCode >= 300 &&\n statusCode < 400 &&\n typeof locationHeader === 'string' &&\n /\\/404\\b/.test(locationHeader);\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\n if (locationHeader && typeof locationHeader === 'string') {\n try {\n const locationUrl = new URL(locationHeader, options.targetOrigin);\n if (locationUrl.origin === options.targetOrigin) {\n const localUrl = `http://${req.headers.host}${locationUrl.pathname}${locationUrl.search}${locationUrl.hash}`;\n proxyRes.headers.location = localUrl;\n logger.debug(`Hybrid proxy location rewrite: ${locationHeader} → ${localUrl}`);\n }\n } catch {\n logger.warn(`Hybrid proxy: invalid Location header: ${locationHeader}`);\n }\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","/**\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 'node:path';\nimport { existsSync } from 'node:fs';\nimport { importTypescript } from './ts-import';\n\n/**\n * Server configuration extracted from environment variables\n */\nexport interface ServerConfig {\n commerce: {\n api: {\n shortCode: string;\n organizationId: string;\n clientId: string;\n proxy: string;\n proxyHost?: string;\n };\n };\n}\n\n/**\n * This is a temporary function before we move the config implementation from\n * template-retail-rsc-app to the SDK.\n *\n * @ TODO: Remove this function after we move the config implementation from\n * template-retail-rsc-app to the SDK.\n *\n */\nexport function loadConfigFromEnv(): ServerConfig {\n const shortCode = process.env.PUBLIC__app__commerce__api__shortCode;\n const organizationId = process.env.PUBLIC__app__commerce__api__organizationId;\n const clientId = process.env.PUBLIC__app__commerce__api__clientId;\n const proxy = process.env.PUBLIC__app__commerce__api__proxy || '/mobify/proxy/api';\n const proxyHost = process.env.SCAPI_PROXY_HOST;\n\n if (!shortCode && !proxyHost) {\n throw new Error(\n 'Missing PUBLIC__app__commerce__api__shortCode environment variable.\\n' +\n 'Please set it in your .env file or environment.'\n );\n }\n\n if (!organizationId) {\n throw new Error(\n 'Missing PUBLIC__app__commerce__api__organizationId environment variable.\\n' +\n 'Please set it in your .env file or environment.'\n );\n }\n\n if (!clientId) {\n throw new Error(\n 'Missing PUBLIC__app__commerce__api__clientId environment variable.\\n' +\n 'Please set it in your .env file or environment.'\n );\n }\n\n return {\n commerce: {\n api: {\n shortCode: shortCode || '',\n organizationId,\n clientId,\n proxy,\n proxyHost,\n },\n },\n };\n}\n\n/**\n * Load storefront-next project configuration from config.server.ts.\n * Requires projectDirectory to be provided.\n *\n * @param projectDirectory - Project directory to load config.server.ts from\n * @throws Error if config.server.ts is not found or invalid\n */\nexport async function loadProjectConfig(projectDirectory: string): Promise<ServerConfig> {\n const configPath = resolve(projectDirectory, 'config.server.ts');\n const tsconfigPath = resolve(projectDirectory, 'tsconfig.json');\n\n if (!existsSync(configPath)) {\n throw new Error(\n `config.server.ts not found at ${configPath}.\\n` +\n 'Please ensure config.server.ts exists in your project root.'\n );\n }\n\n interface LoadedConfig {\n default?: {\n app?: {\n commerce?: {\n api?: {\n shortCode?: string;\n organizationId?: string;\n clientId?: string;\n proxy?: string;\n };\n };\n };\n };\n }\n\n const loaded = await importTypescript<LoadedConfig>(configPath, {\n projectDirectory,\n tsconfigPath,\n });\n\n // Extract commerce API config from the loaded config\n const config = loaded.default;\n if (!config?.app?.commerce?.api) {\n throw new Error(\n `Invalid config.server.ts: missing app.commerce.api configuration.\\n` +\n 'Please ensure your config.server.ts has the commerce API configuration.'\n );\n }\n\n const api = config.app.commerce.api;\n const proxyHost = process.env.SCAPI_PROXY_HOST;\n\n // Validate required fields (shortCode not required when proxyHost is set)\n if (!api.shortCode && !proxyHost) {\n throw new Error('Missing shortCode in config.server.ts commerce.api configuration');\n }\n if (!api.organizationId) {\n throw new Error('Missing organizationId in config.server.ts commerce.api configuration');\n }\n if (!api.clientId) {\n throw new Error('Missing clientId in config.server.ts commerce.api configuration');\n }\n return {\n commerce: {\n api: {\n shortCode: api.shortCode || '',\n organizationId: api.organizationId,\n clientId: api.clientId,\n proxy: api.proxy || '/mobify/proxy/api',\n proxyHost,\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 { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { getMrtEntryFile } from './mrt/utils';\nimport type { MrtSsrConfig } from './types';\n\nexport const CARTRIDGES_BASE_DIR = 'cartridges';\nexport const SFNEXT_BASE_CARTRIDGE_NAME = 'app_storefrontnext_base';\nexport const SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR = `${SFNEXT_BASE_CARTRIDGE_NAME}/cartridge/experience`;\nexport const SFNEXT_BASE_CARTRIDGE_VERSION = '0.0.1';\n\n/**\n * When enabled, automatically generates and deploys cartridge metadata before an MRT push.\n * This is useful for keeping Page Designer metadata in sync with component changes.\n *\n * When enabled:\n * 1. Generates cartridge metadata from decorated components\n * 2. Deploys the cartridge to Commerce Cloud (requires dw.json configuration)\n * 3. Proceeds with the MRT push\n *\n * To enable: Set this to `true` in your local config.ts\n * Default: false (manual cartridge generation/deployment via `sfnext generate-cartridge` and `sfnext deploy-cartridge`)\n */\nexport const GENERATE_AND_DEPLOY_CARTRIDGE_ON_MRT_PUSH = false;\n\n/**\n * Build MRT SSR configuration for bundle deployment\n *\n * Defines which files should be:\n * - Server-only (ssrOnly): Deployed only to Lambda functions\n * - Shared (ssrShared): Deployed to both Lambda and CDN\n *\n * @param buildDirectory - Path to the build output directory\n * @param projectDirectory - Path to the project root (reserved for future use)\n * @returns MRT SSR configuration with glob patterns\n */\ntype RuntimeConfig = {\n runtime?: {\n ssrOnly?: string[];\n ssrShared?: string[];\n ssrParameters?: Record<string, string | number | boolean>;\n };\n};\n\n/**\n * Merge override patterns with defaults while preserving required defaults.\n *\n * Overrides are prepended so they can add additional globs or exclusions, while\n * defaults remain in the list to prevent breaking essential artifacts.\n *\n * @example\n * const defaults = ['server/**', 'loader.js'];\n * const overrides = ['custom/**', '!static/**'];\n * mergePatterns(defaults, overrides);\n * // => ['custom/**', '!static/**', 'server/**', 'loader.js']\n */\nconst mergePatterns = (defaults: string[], overrides?: string[]): string[] => {\n if (!overrides?.length) {\n return defaults;\n }\n\n return Array.from(new Set([...overrides, ...defaults]));\n};\n\n/**\n * Load runtime config from config.server.ts for MRT bundle settings.\n *\n * Keep in sync with @salesforce/storefront-next-runtime/src/config/load-config.ts.\n */\nexport async function loadRuntimeConfig(projectDirectory?: string): Promise<RuntimeConfig['runtime'] | undefined> {\n if (!projectDirectory) {\n return undefined;\n }\n\n const configPath = resolve(projectDirectory, 'config.server.ts');\n if (!existsSync(configPath)) {\n return undefined;\n }\n\n try {\n const { createJiti } = await import('jiti');\n\n const jiti = createJiti(import.meta.url, {\n fsCache: false,\n interopDefault: true,\n });\n\n const mod = await jiti.import(configPath);\n const config = (mod as Record<string, unknown>).default ?? mod;\n return (config as RuntimeConfig).runtime;\n } catch (error) {\n throw new Error(`[storefront-next-dev] Found config.server.ts at ${configPath} but failed to import it.`, {\n cause: error,\n });\n }\n}\n\nexport const buildMrtConfig = async (_buildDirectory: string, projectDirectory?: string): Promise<MrtSsrConfig> => {\n // SSR-only files: Server bundles and entry points\n // These are deployed only to Lambda functions, not to CDN\n const ssrEntryPoint = getMrtEntryFile('production');\n\n const defaultSsrOnly = [\n 'server/**/*', // All server-side code\n 'package.json', // Build root package.json\n 'loader.js', // SSR entry point\n `${ssrEntryPoint}.{js,mjs,cjs}`, // SSR entry point (supports CJS and ESM formats)\n `${ssrEntryPoint}.{js,mjs,cjs}.map`, // SSR source maps\n '!static/**/*', // Exclude static assets from server\n // Include any shared server chunks prefixed with sfnext-server- (generated by the build process)\n 'sfnext-server-*.mjs',\n 'sfnext-server-*.mjs.map',\n ];\n\n // Shared files: Client bundles and static assets\n // These are deployed to both Lambda (for SSR) and CDN (for client)\n const defaultSsrShared = [\n 'client/**/*', // All client-side bundles (with content hashes)\n 'static/**/*', // Static assets (images, fonts, etc.)\n '**/*.css', // Stylesheets\n '**/*.png',\n '**/*.jpg',\n '**/*.jpeg',\n '**/*.gif',\n '**/*.svg',\n '**/*.ico',\n '**/*.woff',\n '**/*.woff2',\n '**/*.ttf',\n '**/*.eot',\n ];\n\n // SSR function parameters\n const defaultSsrParameters = {\n ssrFunctionNodeVersion: '24.x',\n };\n\n const runtimeConfig = await loadRuntimeConfig(projectDirectory);\n const ssrOnly = mergePatterns(defaultSsrOnly, runtimeConfig?.ssrOnly);\n const ssrShared = mergePatterns(defaultSsrShared, runtimeConfig?.ssrShared);\n const ssrParameters: MrtSsrConfig['ssrParameters'] = {\n ...defaultSsrParameters,\n ...(runtimeConfig?.ssrParameters ?? {}),\n };\n\n // Remove envBasePath if empty — MRT rejects empty string as invalid.\n // When not set, the key should be absent so MRT doesn't configure a base path.\n if (!ssrParameters.envBasePath) {\n delete ssrParameters.envBasePath;\n }\n\n return {\n ssrOnly,\n ssrShared,\n ssrParameters,\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 { createProxyMiddleware, type RequestHandler } from 'http-proxy-middleware';\nimport type { ServerConfig } from '../config';\nimport { getCommerceCloudApiUrl } from '../../utils/paths';\n\n/**\n * Create proxy middleware for Commerce Cloud API\n * Proxies requests from /mobify/proxy/api to the Commerce Cloud API\n */\nexport function createCommerceProxyMiddleware(config: ServerConfig): RequestHandler {\n const target = getCommerceCloudApiUrl(config.commerce.api.shortCode, config.commerce.api.proxyHost);\n\n return createProxyMiddleware({\n target,\n changeOrigin: true,\n // Disable SSL verification when using a custom proxy target (e.g. the local\n // SCW instance at https://scw:25010 which uses a self-signed certificate).\n // This is safe because the proxy middleware is only mounted in dev/preview\n // modes — production builds on Managed Runtime disable it (enableProxy: false).\n secure: !config.commerce.api.proxyHost,\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 express, { type RequestHandler } from 'express';\nimport path from 'path';\nimport { getBundlePath } from '../../utils/paths';\nimport { logger } from '../../logger';\n\n/**\n * Create static file serving middleware for client assets\n * Serves files from build/client at /mobify/bundle/{BUNDLE_ID}/client/\n */\nexport function createStaticMiddleware(bundleId: string, projectDirectory: string): RequestHandler {\n const bundlePath = getBundlePath(bundleId);\n const clientBuildDir = path.join(projectDirectory, 'build', 'client');\n\n logger.info(`Serving static assets from ${clientBuildDir} at ${bundlePath}`);\n\n return express.static(clientBuildDir, {\n setHeaders: (res) => {\n res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');\n res.setHeader('x-local-static-cache-control', '1');\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 compression from 'compression';\nimport type { RequestHandler } from 'express';\nimport zlib from 'node:zlib';\nimport { logger } from '../../logger';\n\n/**\n * Parse and validate COMPRESSION_LEVEL environment variable\n * @returns Valid compression level (0-9) or default compression level\n */\nfunction getCompressionLevel(): number {\n const raw = process.env.COMPRESSION_LEVEL;\n const DEFAULT = zlib.constants.Z_DEFAULT_COMPRESSION;\n\n if (raw == null || raw.trim() === '') {\n return DEFAULT;\n }\n\n const level = Number(raw);\n\n const isValid = Number.isInteger(level) && level >= 0 && level <= 9;\n\n if (!isValid) {\n logger.warn(`[compression] Invalid COMPRESSION_LEVEL=\"${raw}\". ` + `Using default (${DEFAULT}).`);\n return DEFAULT;\n }\n\n return level;\n}\n\n/**\n * Create compression middleware for gzip/brotli compression\n * Used in preview mode to optimize response sizes\n */\nexport function createCompressionMiddleware(): RequestHandler {\n const compressionLevel = getCompressionLevel();\n\n return compression({\n filter: (req, res) => {\n if (req.headers['x-no-compression']) {\n return false;\n }\n return compression.filter(req, res);\n },\n // Compression level (0-9, higher = better compression but slower)\n // default is zlib.constants.Z_DEFAULT_COMPRESSION = -1\n level: compressionLevel,\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 { RequestHandler } from 'express';\nimport morgan from 'morgan';\nimport chalk from 'chalk';\nimport { minimatch } from 'minimatch';\n\n/**\n * Patterns for URLs to skip logging (static assets and Vite internals)\n */\nconst SKIP_PATTERNS = [\n '/@vite/**',\n '/@id/**',\n '/@fs/**',\n '/@react-router/**',\n '/src/**',\n '/node_modules/**',\n '**/*.js',\n '**/*.css',\n '**/*.ts',\n '**/*.tsx',\n '**/*.js.map',\n '**/*.css.map',\n];\n\n/**\n * Create request logging middleware\n * Used in dev and preview modes for request visibility\n */\nexport function createLoggingMiddleware(): RequestHandler {\n // Custom format with colors\n morgan.token('status-colored', (req, res) => {\n const status = res.statusCode;\n let color = chalk.green;\n\n if (status >= 500) {\n color = chalk.red;\n } else if (status >= 400) {\n color = chalk.yellow;\n } else if (status >= 300) {\n color = chalk.cyan;\n }\n\n return color(String(status));\n });\n\n morgan.token('method-colored', (req) => {\n const method = req.method;\n const colors: Record<string, typeof chalk.green> = {\n GET: chalk.green,\n POST: chalk.blue,\n PUT: chalk.yellow,\n DELETE: chalk.red,\n PATCH: chalk.magenta,\n };\n const color = (method && colors[method]) || chalk.white;\n return color(method);\n });\n\n // Format: [METHOD] /path - STATUS (response-time ms)\n return morgan(\n (tokens, req, res) => {\n return [\n tokens['method-colored'](req, res),\n tokens.url(req, res),\n '-',\n tokens['status-colored'](req, res),\n chalk.gray(`(${tokens['response-time'](req, res)}ms)`),\n ].join(' ');\n },\n {\n // Skip logging for static assets to reduce noise\n skip: (req) => {\n return SKIP_PATTERNS.some((pattern) => minimatch(req.url, pattern, { dot: true }));\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 { RequestHandler } from 'express';\n\n/**\n * Normalizes the X-Forwarded-Host header to support React Router's CSRF validation features.\n *\n * NOTE: This middleware performs header manipulation as a temporary, internal\n * solution for MRT/Lambda environments. It may be updated or removed if React Router\n * introduces a first-class configuration for validating against forwarded headers.\n *\n * React Router v7.12+ uses the X-Forwarded-Host header (preferring it over Host)\n * to validate request origins for security. In Managed Runtime (MRT) with a vanity\n * domain, the eCDN automatically sets the X-Forwarded-Host to the vanity domain.\n * React Router handles cases where this header contains multiple comma-separated\n * values by prioritizing the first entry.\n *\n * This middleware ensures that X-Forwarded-Host is always present by falling back\n * to a configured public domain if the header is missing (e.g., local development).\n * By only modifying X-Forwarded-Host, we provide a consistent environment for\n * React Router's security checks without modifying the internal 'Host' header,\n * which is required for environment-specific routing logic (e.g., Hybrid Proxy).\n *\n * Priority order:\n * 1. X-Forwarded-Host: Automatically set by eCDN for vanity domains.\n * 2. EXTERNAL_DOMAIN_NAME: Fallback environment variable for the public domain\n * used when no forwarded headers are present (e.g., local development).\n */\nexport function createHostHeaderMiddleware(): RequestHandler {\n return (req, _res, next) => {\n // If X-Forwarded-Host is missing, populate it from the trusted fallback.\n // React Router v7 uses this header (preferring it over Host) to validate\n // against the 'Origin' for CSRF protection.\n if (!req.get('x-forwarded-host') && process.env.EXTERNAL_DOMAIN_NAME) {\n req.headers['x-forwarded-host'] = process.env.EXTERNAL_DOMAIN_NAME;\n }\n\n next();\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 { ServerBuild } from 'react-router';\nimport { getBundlePath, getBasePath } from '../utils/paths';\n\n/**\n * Patch React Router build to rewrite asset URLs with the correct bundle path\n * This is needed because the build output uses /assets/ but we preview at /mobify/bundle/{BUNDLE_ID}/client/assets/\n */\nexport function patchReactRouterBuild(build: ServerBuild, bundleId: string): ServerBuild {\n const bundlePath = getBundlePath(bundleId);\n const basePath = getBasePath();\n\n // Clone the assets object and replace /assets/ paths with bundle path\n const assetsJson = JSON.stringify(build.assets);\n const patchedAssetsJson = assetsJson.replace(/\"\\/assets\\//g, `\"${bundlePath}assets/`);\n const newAssets = JSON.parse(patchedAssetsJson);\n\n // Return a new build object with patched publicPath and assets\n return Object.assign({}, build, {\n publicPath: bundlePath,\n assets: newAssets,\n // Override basename at runtime from base path env var\n // This allows the same build to serve under different base paths\n ...(basePath && { basename: basePath }),\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 */\nexport type ServerMode = 'development' | 'preview' | 'production';\n\n/**\n * Feature flags for each server mode\n */\nexport interface ServerModeFeatures {\n /** Enable Commerce API proxy middleware to forward /mobify/proxy/api requests to SCAPI */\n enableProxy: boolean;\n\n /** Enable static file serving from build/client directory */\n enableStaticServing: boolean;\n\n /** Enable gzip/brotli compression middleware for responses */\n enableCompression: boolean;\n\n /** Enable HTTP request/response logging */\n enableLogging: boolean;\n\n /** Enable patching of asset URLs with bundle path (for CDN deployment) */\n enableAssetUrlPatching: boolean;\n}\n\n/**\n * Default feature configuration for each server mode\n */\nexport const ServerModeFeatureMap: Record<ServerMode, ServerModeFeatures> = {\n development: {\n enableProxy: true,\n enableStaticServing: false,\n enableCompression: false,\n enableLogging: true,\n enableAssetUrlPatching: false,\n },\n preview: {\n enableProxy: true,\n enableStaticServing: true,\n enableCompression: true,\n enableLogging: true,\n enableAssetUrlPatching: true,\n },\n production: {\n enableProxy: false,\n enableStaticServing: false,\n enableCompression: true,\n enableLogging: true,\n enableAssetUrlPatching: true,\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 * MRT-compatible console span exporter.\n *\n * Extends the default ConsoleSpanExporter to output structured JSON that\n * Managed Runtime's log infrastructure can parse. The default exporter uses\n * `console.dir` with a human-readable format; this override uses\n * `console.info(JSON.stringify(...))` to produce machine-parseable JSON\n * matching the format MRT expects.\n *\n * Inherits `shutdown()` and `forceFlush()` from ConsoleSpanExporter.\n */\n\nimport { ConsoleSpanExporter, type ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { ExportResultCode, type ExportResult, hrTimeToTimeStamp } from '@opentelemetry/core';\n\nexport class MrtConsoleSpanExporter extends ConsoleSpanExporter {\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {\n for (const span of spans) {\n try {\n const ctx = span.spanContext();\n const spanData = {\n traceId: ctx.traceId,\n parentId: span.parentSpanId,\n name: span.name,\n id: ctx.spanId,\n kind: span.kind,\n timestamp: hrTimeToTimeStamp(span.startTime),\n duration: span.duration,\n attributes: span.attributes,\n status: span.status,\n events: span.events,\n links: span.links,\n start_time: span.startTime,\n end_time: span.endTime,\n forwardTrace: process.env.SFNEXT_OTEL_ENABLED === 'true',\n };\n // eslint-disable-next-line no-console -- intentional: MRT collects stdout as the telemetry transport\n console.info(JSON.stringify(spanData));\n } catch {\n // Skip malformed spans — never let a serialization failure propagate\n }\n }\n resultCallback({ code: ExportResultCode.SUCCESS });\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 * OpenTelemetry tracer provider initialization.\n *\n * ## Exporter design: why ConsoleSpanExporter?\n *\n * This storefront runs in two environments:\n * - Local development on a developer's machine (Vite dev server)\n * - Salesforce Managed Runtime (MRT), a serverless environment backed by AWS Lambda\n *\n * Standard OpenTelemetry exporters (OTLP, Jaeger, Zipkin) are designed for\n * long-running processes: they maintain background agents, batch spans in memory,\n * and flush asynchronously. That model does not work in a serverless environment.\n * Lambda invocations must complete quickly — a background agent or async flush\n * after the response is sent would hold the Lambda open unnecessarily, increasing\n * cost and cold-start latency.\n *\n * Instead, we write spans synchronously to stdout via MrtConsoleSpanExporter — a\n * subclass of ConsoleSpanExporter that outputs structured JSON matching the format\n * MRT's log infrastructure expects. MRT collects stdout from every invocation and\n * forwards the log stream to downstream analytics systems. Telemetry reaches those\n * systems without any in-process agent or network connection from the Lambda\n * function itself.\n *\n * SimpleSpanProcessor is used (instead of BatchSpanProcessor) for the same reason:\n * immediate, synchronous export with no in-memory queue that could be lost if the\n * process exits.\n *\n * Uses NodeTracerProvider directly (instead of NodeSDK) to avoid async resource\n * detection. NodeSDK's autoDetectResources creates a Resource with\n * asyncAttributesPending=true, which causes SimpleSpanProcessor.onEnd() to\n * defer export via waitForAsyncAttributes(). In the Vite SSR module runner\n * context, that deferred microtask never executes. A synchronous Resource\n * ensures the immediate export path is always taken.\n *\n * @env SFNEXT_OTEL_ENABLED — set to `\"true\"` to enable (e.g. `SFNEXT_OTEL_ENABLED=true pnpm dev`)\n */\n\nimport type { Tracer } from '@opentelemetry/api';\nimport { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\nimport { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';\nimport { MrtConsoleSpanExporter } from './mrt-console-span-exporter';\nimport { Resource } from '@opentelemetry/resources';\nimport { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';\nimport { registerInstrumentations } from '@opentelemetry/instrumentation';\nimport { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici';\nimport { logger } from '../logger';\n\nexport const SERVICE_NAME = 'storefront-next';\n\n/**\n * Initializes OpenTelemetry and returns a Tracer from the provider directly.\n *\n * Returns the tracer via `provider.getTracer()` instead of the global\n * `trace.getTracer()` API. In the Vite SSR module runner, the built\n * dist/entry/server.js and the externalized @opentelemetry/sdk-trace-node\n * resolve @opentelemetry/api to different module instances (different paths\n * through pnpm's strict node_modules). Each instance has its own\n * ProxyTracerProvider singleton, so `provider.register()` sets the delegate\n * on sdk-trace-node's API instance while our code's `trace.getTracer()`\n * reads from a separate API instance with no delegate — returning a tracer\n * backed by a bare BasicTracerProvider with NoopSpanProcessor.\n *\n * Getting the tracer directly from the provider bypasses the global registry\n * entirely, guaranteeing the tracer uses our configured span processors.\n */\nlet cachedTracer: Tracer | null = null;\n\n// In the Vite SSR module runner, setup.ts may be loaded by two different module\n// instances (the Express server and the SSR bundle) — each with its own\n// `cachedTracer`. UndiciInstrumentation hooks into process-global\n// diagnostics_channel events, so registering it twice causes duplicate spans\n// for every fetch. This process-global flag ensures it is registered only once.\nconst UNDICI_REGISTERED_KEY = Symbol.for('sfnext.otel.undici_registered');\n\nexport function initTelemetry(): Tracer | null {\n if (cachedTracer) return cachedTracer;\n try {\n const provider = new NodeTracerProvider({\n resource: new Resource({ [ATTR_SERVICE_NAME]: SERVICE_NAME }),\n });\n\n provider.addSpanProcessor(new SimpleSpanProcessor(new MrtConsoleSpanExporter()));\n provider.register();\n\n // Guard against double-registration across Vite module boundaries.\n // See comment above UNDICI_REGISTERED_KEY.\n if (!(globalThis as Record<symbol, boolean>)[UNDICI_REGISTERED_KEY]) {\n (globalThis as Record<symbol, boolean>)[UNDICI_REGISTERED_KEY] = true;\n registerInstrumentations({\n tracerProvider: provider,\n instrumentations: [\n new UndiciInstrumentation({\n requestHook(span, request) {\n try {\n const method = request.method.toUpperCase();\n const url = `${request.origin}${request.path}`;\n span.updateName(`${method} ${url}`);\n } catch {\n // Non-fatal — default span name is acceptable\n }\n },\n }),\n ],\n });\n }\n\n cachedTracer = provider.getTracer(SERVICE_NAME);\n return cachedTracer;\n } catch (error) {\n logger.error('[otel] Failed to initialize OpenTelemetry:', error);\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\n/**\n * Express middleware that creates server and streaming OTel spans.\n *\n * - **Server span** wraps the entire Express request lifecycle (receive → response close).\n * - **Streaming span** starts at TTFB (first `writeHead` or `write`) and ends when\n * the response stream closes.\n *\n * Uses `startActiveSpan` for the server span so all downstream spans (React Router's\n * `request`, loaders, middleware) automatically become children via OTel context propagation.\n *\n * Listens to both `close` and `finish` events with a once-guard — dev server emits\n * `close`, MRT Lambda adapter emits `finish`.\n */\n\nimport type { RequestHandler } from 'express';\nimport { context, type Span, SpanStatusCode, trace } from '@opentelemetry/api';\nimport { initTelemetry } from '../setup';\n\nexport function createOtelExpressMiddleware(): RequestHandler {\n const maybeTracer = initTelemetry();\n if (!maybeTracer) return (_req, _res, next) => next();\n const tracer = maybeTracer;\n\n return (req, res, next) => {\n try {\n const url = new URL(req.originalUrl || req.url, 'http://localhost').pathname;\n const method = req.method;\n\n tracer.startActiveSpan(\n `[sfnext] server ${method} ${url}`,\n { attributes: { 'http.request.method': method, 'url.path': url } },\n (serverSpan) => {\n try {\n // Inject W3C traceparent header so trace ID is accessible from the browser\n const spanContext = trace.getSpan(context.active())?.spanContext();\n if (spanContext) {\n const flags = spanContext.traceFlags.toString(16).padStart(2, '0');\n const traceparent = `00-${spanContext.traceId}-${spanContext.spanId}-${flags}`;\n res.setHeader('traceparent', traceparent);\n }\n } catch {\n // traceparent header is non-essential — skip on failure\n }\n\n const serverCtx = context.active();\n const startTime = performance.now();\n let streamingSpan: Span | null = null;\n let ttfbMs = 0;\n let ended = false;\n\n function recordTTFB() {\n if (streamingSpan) return;\n try {\n ttfbMs = Math.round(performance.now() - startTime);\n serverSpan.setAttribute('sfnext.ttfb_ms', ttfbMs);\n streamingSpan = tracer.startSpan(\n `[sfnext] response streaming ${method} ${url}`,\n {\n attributes: {\n 'http.request.method': method,\n 'url.path': url,\n 'sfnext.ttfb_ms': ttfbMs,\n },\n },\n serverCtx\n );\n } catch {\n // Span creation failure is non-fatal\n }\n }\n\n // Patch writeHead + write to detect first byte\n const origWriteHead = res.writeHead.bind(res);\n res.writeHead = ((...args: Parameters<typeof origWriteHead>) => {\n recordTTFB();\n return origWriteHead(...args);\n }) as typeof origWriteHead;\n const origWrite = res.write.bind(res);\n res.write = ((...args: Parameters<typeof origWrite>) => {\n recordTTFB();\n return origWrite(...args);\n }) as typeof origWrite;\n\n function endSpans() {\n if (ended) return;\n ended = true;\n try {\n const totalMs = Math.round(performance.now() - startTime);\n const statusCode = res.statusCode;\n\n if (streamingSpan) {\n streamingSpan.setAttribute('http.streaming_duration_ms', totalMs - ttfbMs);\n streamingSpan.setAttribute('http.response.status_code', statusCode);\n if (statusCode >= 500) streamingSpan.setStatus({ code: SpanStatusCode.ERROR });\n streamingSpan.end();\n }\n serverSpan.setAttribute('http.response.status_code', statusCode);\n serverSpan.setAttribute('http.total_duration_ms', totalMs);\n if (statusCode >= 500) serverSpan.setStatus({ code: SpanStatusCode.ERROR });\n serverSpan.end();\n } catch {\n // Response is already closing — swallow OTel errors silently\n }\n }\n\n res.once('close', endSpans);\n res.once('finish', endSpans);\n next();\n }\n );\n } catch {\n // OTel failure must never prevent the request from being handled\n next();\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 { RequestHandler } from 'express';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { logger } from '../../logger';\n\nconst DEFAULT_HEALTH_DESCRIPTION = 'storefront-next-dev server health';\nconst PACKAGE_JSON_NAME = 'package.json';\nconst RUNTIME_PACKAGE_NAME = '@salesforce/storefront-next-runtime';\nconst DEV_PACKAGE_NAME = '@salesforce/storefront-next-dev';\nconst BUILD_FOLDER_NAME = 'build';\nconst LOCAL_BUNDLE_ID = 'local';\n\nexport const HEALTH_ENDPOINT_PATH = '/sfdc-health';\n\ntype PackageMetadata = {\n name?: string;\n version?: string;\n description?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n};\n\ntype HealthCheckOptions = {\n projectDirectory: string;\n bundleId: string;\n};\n\n/**\n * Reads a package.json file and returns selected metadata.\n *\n * @param path - Absolute path to a package.json file\n * @returns Parsed metadata, or null if missing/unreadable\n *\n * @example\n * ```ts\n * const metadata = readPackageMetadata('/app/package.json');\n * console.log(metadata?.version);\n * ```\n */\nfunction readPackageMetadata(path: string): PackageMetadata | null {\n // Print out the content of the path.cwd() + build folder here.\n if (!existsSync(path)) {\n return null;\n }\n\n try {\n const metadata = JSON.parse(readFileSync(path, 'utf8')) as PackageMetadata;\n return metadata;\n } catch (error) {\n logger.debug(`Health check: failed to parse package.json at ${path}`, error);\n return null;\n }\n}\n\n/**\n * Creates an Express handler that returns Health+JSON for the project.\n *\n * @param options - Handler options\n * @returns Express request handler for the health endpoint\n *\n * @example\n * ```ts\n * app.get(HEALTH_ENDPOINT_PATH, createHealthCheckHandler({\n * projectDirectory: process.cwd(),\n * bundleId: LOCAL_BUNDLE_ID,\n * }));\n * ```\n */\nexport function createHealthCheckHandler(options: HealthCheckOptions): RequestHandler {\n const { projectDirectory, bundleId } = options;\n const isLocalBundle = bundleId === LOCAL_BUNDLE_ID;\n const packageJsonPath = isLocalBundle\n ? resolve(projectDirectory, PACKAGE_JSON_NAME)\n : resolve(projectDirectory, BUILD_FOLDER_NAME, PACKAGE_JSON_NAME);\n const projectPackage = readPackageMetadata(packageJsonPath);\n const allDependencies = {\n ...projectPackage?.dependencies,\n ...projectPackage?.devDependencies,\n };\n const devVersion = allDependencies?.[DEV_PACKAGE_NAME];\n const runtimeVersion = allDependencies?.[RUNTIME_PACKAGE_NAME];\n const notes = [\n devVersion ? `Built using ${DEV_PACKAGE_NAME}@${devVersion}.` : null,\n runtimeVersion ? `Running ${RUNTIME_PACKAGE_NAME}@${runtimeVersion}.` : null,\n ].filter(Boolean) as string[];\n\n return (_req, res) => {\n const healthResponse: {\n status: 'pass' | 'warn' | 'fail';\n version?: string;\n bundleId?: string;\n description?: string;\n notes?: string[];\n } = {\n // TODO: Add support for configurable \"checks\" once available.\n status: 'pass',\n version: projectPackage?.version,\n bundleId,\n description: projectPackage?.description ?? DEFAULT_HEALTH_DESCRIPTION,\n notes: notes.length > 0 ? notes : undefined,\n };\n\n res.status(200).type('application/health+json').json(healthResponse);\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 express, { type Express } from 'express';\nimport { createRequestHandler } from '@react-router/express';\nimport { type ServerBuild } from 'react-router';\nimport type { ViteDevServer } from 'vite';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { loadConfigFromEnv, type ServerConfig } from './config';\nimport { loadRuntimeConfig } from '../config';\nimport { importTypescript } from './ts-import';\nimport { createCommerceProxyMiddleware } from './middleware/proxy';\nimport { createStaticMiddleware } from './middleware/static';\nimport { createCompressionMiddleware } from './middleware/compression';\nimport { createLoggingMiddleware } from './middleware/logging';\nimport { createHostHeaderMiddleware } from './middleware/host-header';\nimport { patchReactRouterBuild } from './utils';\nimport { ServerModeFeatureMap, type ServerMode, type ServerModeFeatures } from './modes';\nimport { getBundlePath, getBasePath } from '../utils/paths';\nimport { createOtelExpressMiddleware } from '../otel/express/middleware';\nimport { createHealthCheckHandler, HEALTH_ENDPOINT_PATH } from './handlers/health-check';\n\n/** Relative path to the middleware registry TypeScript source (development). Must match appDirectory + server dir + filename used by buildMiddlewareRegistry plugin. */\nconst RELATIVE_MIDDLEWARE_REGISTRY_SOURCE = 'src/server/middleware-registry.ts';\n\n/** Extensions to try for the built middlewares module (ESM first, then CJS for backwards compatibility). */\nconst MIDDLEWARE_REGISTRY_BUILT_EXTENSIONS = ['.mjs', '.js', '.cjs'] as const;\n\n/** Base relative paths for the built middleware registry (production). Order: MRT bundle path, then local build path. */\nconst RELATIVE_MIDDLEWARE_REGISTRY_BUILT_BASES: readonly [string, string] = [\n 'bld/server/middleware-registry',\n 'build/server/middleware-registry',\n];\n\n/** All paths to try when loading the built middlewares (base + extension). */\nconst RELATIVE_MIDDLEWARE_REGISTRY_BUILT_PATHS: readonly string[] = RELATIVE_MIDDLEWARE_REGISTRY_BUILT_BASES.flatMap(\n (base) => MIDDLEWARE_REGISTRY_BUILT_EXTENSIONS.map((ext) => `${base}${ext}`)\n);\n\nconst DEFAULT_BUNDLE_ID = 'local';\nexport interface ServerOptions extends Partial<ServerModeFeatures> {\n /** Server mode: development (with Vite), preview (preview), or production (minimal) */\n mode: ServerMode;\n\n /** Project root directory (optional, defaults to process.cwd()) */\n projectDirectory?: string;\n\n /** Server configuration (optional, will load from env vars if not provided) */\n config?: ServerConfig;\n\n /** Server port (optional, for logging) */\n port?: number;\n\n /** Vite dev server instance (required for development mode) */\n vite?: ViteDevServer;\n\n /** React Router server build (required for preview/production modes) */\n build?: ServerBuild;\n\n /** Enable streaming of responses */\n streaming?: boolean;\n}\n\n/**\n * Load MRT_ENV_BASE_PATH from config.server.ts so getBasePath() works in local dev/preview.\n * On MRT production, this env var is already set by the Lambda from ssrParameters.envBasePath.\n *\n * In dev mode this must be called before Vite starts, since the React Router preset\n * reads getBasePath() at config time to set the basename.\n *\n * @param projectDirectory - Project root directory\n */\nexport async function initBasePathEnv(projectDirectory: string): Promise<void> {\n const runtimeConfig = await loadRuntimeConfig(projectDirectory);\n if (runtimeConfig?.ssrParameters?.envBasePath) {\n process.env.MRT_ENV_BASE_PATH = String(runtimeConfig.ssrParameters.envBasePath);\n }\n}\n\n/**\n * Create a unified Express server for development, preview, or production mode\n */\nexport async function createServer(options: ServerOptions): Promise<Express> {\n const {\n mode,\n projectDirectory = process.cwd(),\n config: providedConfig,\n vite,\n build,\n streaming = false,\n enableProxy = ServerModeFeatureMap[mode].enableProxy,\n enableStaticServing = ServerModeFeatureMap[mode].enableStaticServing,\n enableCompression = ServerModeFeatureMap[mode].enableCompression,\n enableLogging = ServerModeFeatureMap[mode].enableLogging,\n enableAssetUrlPatching = ServerModeFeatureMap[mode].enableAssetUrlPatching,\n } = options;\n\n if (mode === 'development' && !vite) {\n throw new Error('Vite dev server instance is required for development mode');\n }\n\n if ((mode === 'preview' || mode === 'production') && !build) {\n throw new Error('React Router server build is required for preview/production mode');\n }\n\n // Use provided config or load from environment variables\n // TODO: move the config implementation from template-retail-rsc-app to the SDK.\n const config = providedConfig ?? loadConfigFromEnv();\n\n // Load bundle ID from environment\n const bundleId = process.env.BUNDLE_ID ?? DEFAULT_BUNDLE_ID;\n\n // Create Express app\n const app = express();\n app.disable('x-powered-by');\n\n // OTel server + streaming spans — must be first to wrap the entire request lifecycle\n if (process.env.SFNEXT_OTEL_ENABLED === 'true') {\n app.use(createOtelExpressMiddleware());\n }\n\n app.get(HEALTH_ENDPOINT_PATH, createHealthCheckHandler({ projectDirectory, bundleId }));\n\n // Apply middleware based on mode\n if (enableLogging) {\n app.use(createLoggingMiddleware());\n }\n // If streaming is enabled then compression needs to be handled by the streaming handler\n // in the streamingHandler file\n if (enableCompression && !streaming) {\n app.use(createCompressionMiddleware());\n }\n\n if (enableStaticServing && build) {\n const bundlePath = getBundlePath(bundleId);\n app.use(bundlePath, createStaticMiddleware(bundleId, projectDirectory));\n }\n\n // Load and apply custom middlewares from the middleware registry.\n // In development, import the TypeScript source via jiti; in production/preview,\n // dynamically import the pre-built JS from the build output directory.\n interface MiddlewareRegistry {\n customMiddlewares?: Array<{ handler: express.RequestHandler }>;\n }\n\n let registry: MiddlewareRegistry | null = null;\n\n if (mode === 'development') {\n const middlewareRegistryPath = resolve(projectDirectory, RELATIVE_MIDDLEWARE_REGISTRY_SOURCE);\n if (existsSync(middlewareRegistryPath)) {\n registry = await importTypescript<MiddlewareRegistry>(middlewareRegistryPath, {\n projectDirectory,\n });\n }\n } else {\n const possiblePaths = RELATIVE_MIDDLEWARE_REGISTRY_BUILT_PATHS.map((p) => resolve(projectDirectory, p));\n\n let builtRegistryPath: string | null = null;\n for (const path of possiblePaths) {\n if (existsSync(path)) {\n builtRegistryPath = path;\n break;\n }\n }\n\n if (builtRegistryPath) {\n registry = (await import(pathToFileURL(builtRegistryPath).href)) as MiddlewareRegistry;\n }\n }\n\n if (registry?.customMiddlewares && Array.isArray(registry.customMiddlewares)) {\n registry.customMiddlewares.forEach((entry: { handler: express.RequestHandler }) => {\n app.use(entry.handler);\n });\n }\n\n if (mode === 'development' && vite) {\n // In development, Vite middleware handles HMR, transforms, and proxy\n app.use(vite.middlewares);\n }\n\n if (enableProxy) {\n app.use(config.commerce.api.proxy, createCommerceProxyMiddleware(config));\n }\n\n // In dev/preview, redirect non-prefixed page requests to the prefixed path.\n // When a base path is configured, redirect non-prefixed requests to the prefixed path.\n // In dev/preview this improves DX (e.g., /category/womens → /shop/category/womens).\n // In production on MRT, requests arriving at the environment domain without the base path\n // (e.g., mrt-env.mobify-storefront.com/category/womens) are redirected to the prefixed URL,\n // since the CDN routes by base path on the vanity domain but direct MRT access skips it.\n const basePath = getBasePath();\n if (basePath) {\n app.use((req, res, next) => {\n if (req.path.startsWith(`${basePath}/`) || req.path === basePath) {\n return next();\n }\n // Don't redirect infrastructure paths\n if (req.path.startsWith('/mobify/')) {\n return next();\n }\n res.redirect(`${basePath}${req.originalUrl}`);\n });\n }\n\n // Normalize the Host header for React Router's CSRF validation features\n app.use(createHostHeaderMiddleware());\n // SSR request handler\n\n app.all('*splat', await createSSRHandler(mode, bundleId, vite, build, enableAssetUrlPatching));\n\n return app;\n}\n\n/**\n * Create the SSR request handler based on mode\n */\nasync function createSSRHandler(\n mode: ServerMode,\n bundleId: string,\n vite: ViteDevServer | undefined,\n build: ServerBuild | undefined,\n enableAssetUrlPatching: boolean\n) {\n if (mode === 'development' && vite) {\n // The vite package is not designed to be bundlable via build tools\n // You will run into a lot of build errors if you try to bundle it.\n // So, we dynamically import it here to avoid bundling it in production.\n const { isRunnableDevEnvironment } = await import('vite');\n\n return async (req: express.Request, res: express.Response, next: express.NextFunction) => {\n try {\n const ssrEnvironment = vite.environments.ssr;\n\n // Check if the environment is runnable (has a module runner)\n if (!isRunnableDevEnvironment(ssrEnvironment)) {\n const error = new Error(\n 'SSR environment is not runnable. Please ensure:\\n' +\n ' 1. \"@salesforce/storefront-next-dev\" plugin is added to vite.config.ts\\n' +\n ' 2. React Router config uses the Storefront Next preset'\n );\n next(error);\n return;\n }\n\n // Load server build using Vite Environment API\n // This gets the latest build with HMR updates\n const devBuild = await ssrEnvironment.runner.import('virtual:react-router/server-build');\n\n // Use the same request handler pattern as production\n const handler = createRequestHandler({\n build: devBuild,\n mode: process.env.NODE_ENV,\n });\n\n await handler(req, res, next);\n } catch (error) {\n // Let Vite handle SSR errors with nice error overlay\n vite.ssrFixStacktrace(error as Error);\n next(error);\n }\n };\n } else if (build) {\n // Serve/Production mode: static build\n let patchedBuild = build;\n\n if (enableAssetUrlPatching) {\n patchedBuild = patchReactRouterBuild(build, bundleId);\n }\n\n // When source maps are enabled (via MRT's enable_source_maps toggle or local preview),\n // use 'development' mode so React Router sends unsanitized errors to the browser.\n const sourceMapsEnabled = process.env.NODE_OPTIONS?.includes('--enable-source-maps');\n const requestHandlerMode = sourceMapsEnabled ? 'development' : process.env.NODE_ENV;\n\n return createRequestHandler({\n build: patchedBuild,\n mode: requestHandlerMode,\n });\n } else {\n throw new Error('Invalid server configuration: no vite or build provided');\n }\n}\n\n// Re-export config and types\nexport { loadProjectConfig, loadConfigFromEnv, type ServerConfig } from './config';\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 fs from 'fs';\nimport path from 'path';\n\n// Cache for tsconfig paths to avoid repeated disk reads and JSON parsing\nlet cachedTsconfigPaths: Record<string, string[] | string> | null = null;\nlet cachedTsconfigRoot: string | null = null;\n\nexport const FILE_EXTENSIONS: string[] = ['.tsx', '.ts', '.d.ts'];\n\n/**\n * Strip the comments from the JSON string\n * @param jsonString\n * @returns {string}\n */\nfunction stripJsonComments(jsonString: string): string {\n return jsonString\n .replace(/\\/\\/.*$/gm, '') // remove // comments\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, ''); // remove /* */ comments\n}\n\n/**\n * Load the tsconfig.json paths from the project root\n * @param projectRoot\n * @returns {Record<string, string[] | string>}\n */\nfunction loadTsconfigPaths(projectRoot: string): Record<string, string[] | string> | null {\n // If cache is valid for the same root, return it\n if (cachedTsconfigPaths && cachedTsconfigRoot === projectRoot) {\n return cachedTsconfigPaths;\n }\n\n const tsconfigPath = path.join(projectRoot, 'tsconfig.json');\n if (!fs.existsSync(tsconfigPath)) {\n cachedTsconfigPaths = {}; // empty object\n cachedTsconfigRoot = projectRoot;\n return cachedTsconfigPaths;\n }\n\n try {\n const tsconfigContent = stripJsonComments(fs.readFileSync(tsconfigPath, 'utf-8'));\n const tsconfig = JSON.parse(tsconfigContent);\n const paths = tsconfig?.compilerOptions?.paths;\n if (paths && typeof paths === 'object') {\n cachedTsconfigPaths = paths;\n } else {\n cachedTsconfigPaths = {}; // empty object\n }\n cachedTsconfigRoot = projectRoot;\n return cachedTsconfigPaths;\n } catch (error) {\n throw new Error(`Error parsing tsconfig.json for project ${projectRoot}: ${String(error)}`);\n }\n}\n\n/**\n * Resolve the path from the alias to the real path by consulting tsconfig.json paths configuration\n * @param {string} importPath\n * @param {string} projectRoot\n * @returns {string}\n */\nexport function resolvePathFromAlias(importPath: string, projectRoot: string): string {\n // First check if this is a relative import - if so, return as is\n if (importPath.startsWith('.')) {\n return importPath;\n }\n\n // Load and cache tsconfig paths\n const paths = loadTsconfigPaths(projectRoot);\n if (!paths || typeof paths !== 'object' || Object.keys(paths).length === 0) {\n return importPath;\n }\n\n // Find matching alias\n for (const [alias, mappings] of Object.entries(paths)) {\n // Convert TypeScript path pattern to regex (escape '+' to literal, keep '*' as wildcard)\n const aliasEscapedPlus = alias.replace(/\\+/g, '\\\\+');\n const aliasPattern = aliasEscapedPlus.replace(/\\*/g, '(.*)');\n const aliasRegex = new RegExp(`^${aliasPattern}$`);\n const match = importPath.match(aliasRegex);\n\n if (match) {\n const mappingArray = Array.isArray(mappings) ? mappings : [mappings];\n // Try each mapping until we find an existing file\n for (const mapping of mappingArray) {\n // Replace wildcards in the mapping with captured groups\n let resolvedPath = mapping;\n for (let i = 1; i < match.length; i++) {\n resolvedPath = resolvedPath.replace('*', match[i]);\n }\n\n // Remove leading \"./\" from the mapping if present\n if (resolvedPath.startsWith('./')) {\n resolvedPath = resolvedPath.substring(2);\n }\n\n const fullPath = path.resolve(projectRoot, resolvedPath);\n\n // Check if the file exists (with common extensions)\n for (const ext of FILE_EXTENSIONS) {\n const pathWithExt = fullPath + ext;\n if (fs.existsSync(pathWithExt)) {\n return pathWithExt;\n }\n }\n\n // Also check if it's a directory with index file\n if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {\n for (const indexFile of ['index.ts', 'index.tsx', 'index.js', 'index.jsx']) {\n const indexPath = path.join(fullPath, indexFile);\n if (fs.existsSync(indexPath)) {\n return indexPath;\n }\n }\n // If directory exists but no index file, return the directory path\n return fullPath;\n }\n }\n }\n }\n\n // If no existing file was found for this alias simply return the original import path\n return importPath;\n}\n\nexport function isSupportedFileExtension(fileName: string): boolean {\n return FILE_EXTENSIONS.some((ext) => fileName.endsWith(ext));\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 * Utility to trim the directory to remove unused components and unused extensions.\n * This is used to reduce the size of the project by removing the code that is not part of the selected extensions.\n */\nimport fs from 'fs';\nimport path from 'path';\nimport type ExtensionConfig from './extension-config';\nimport { isSupportedFileExtension } from './path-util';\nimport { logger } from '../logger';\n\ntype ExtensionsSelection = Record<string, boolean>;\n\nconst SINGLE_LINE_MARKER = '@sfdc-extension-line';\nconst BLOCK_MARKER_START = '@sfdc-extension-block-start';\nconst BLOCK_MARKER_END = '@sfdc-extension-block-end';\nconst FILE_MARKER = '@sfdc-extension-file';\n\nexport default function trimExtensions(\n directory: string,\n selectedExtensions?: Partial<ExtensionsSelection>,\n extensionConfig?: typeof ExtensionConfig\n): void {\n const startTime = Date.now();\n\n // read available extensions from config file\n const configuredExtensions: Record<string, unknown> = extensionConfig?.extensions || {};\n const extensions: ExtensionsSelection = {};\n Object.keys(configuredExtensions).forEach((targetKey) => {\n extensions[targetKey] = Boolean(selectedExtensions?.[targetKey]) || false;\n });\n\n if (Object.keys(extensions).length === 0) {\n logger.debug('No targets found, skipping trim');\n return;\n }\n\n const processDirectory = (dir: string): void => {\n const files = fs.readdirSync(dir);\n files.forEach((file) => {\n const filePath = path.join(dir, file);\n const stats = fs.statSync(filePath);\n\n if (!filePath.includes('node_modules')) {\n if (stats.isDirectory()) {\n processDirectory(filePath);\n } else if (isSupportedFileExtension(file)) {\n processFile(filePath, extensions);\n }\n }\n });\n };\n\n processDirectory(directory);\n if (extensionConfig?.extensions) {\n deleteExtensionFolders(directory, extensions, extensionConfig);\n updateExtensionConfig(directory, extensions);\n }\n const endTime = Date.now();\n logger.debug(`Trim extensions took ${endTime - startTime}ms`);\n}\n\n/**\n * Update the extension config file to only include the selected extensions.\n * @param projectDirectory - The project directory\n * @param extensionSelections - The selected extensions\n */\nfunction updateExtensionConfig(projectDirectory: string, extensionSelections: ExtensionsSelection) {\n const extensionConfigPath = path.join(projectDirectory, 'src', 'extensions', 'config.json');\n const extensionConfig = JSON.parse(fs.readFileSync(extensionConfigPath, 'utf8'));\n Object.keys(extensionConfig.extensions).forEach((extensionKey: string) => {\n if (!extensionSelections[extensionKey]) {\n delete extensionConfig.extensions[extensionKey];\n }\n });\n fs.writeFileSync(extensionConfigPath, JSON.stringify({ extensions: extensionConfig.extensions }, null, 4), 'utf8');\n}\n\n/**\n * Process a file to trim extension-specific code based on markers.\n * @param filePath - The file path to process\n * @param extensions - The extension selections\n */\nfunction processFile(filePath: string, extensions: ExtensionsSelection): void {\n const source = fs.readFileSync(filePath, 'utf-8');\n\n // If the file is guarded by a file-level marker and the extension is disabled, remove the file entirely\n if (source.includes(FILE_MARKER)) {\n // find() always returns a line since the if condition ensures marker exists, and FILE_MARKER has no newlines\n const markerLine = source.split('\\n').find((line) => line.includes(FILE_MARKER)) as string;\n const extMatch = Object.keys(extensions).find((ext) => markerLine.includes(ext));\n if (!extMatch) {\n logger.warn(`File ${filePath} is marked with ${markerLine} but it does not match any known extensions`);\n } else if (extensions[extMatch] === false) {\n try {\n fs.unlinkSync(filePath);\n logger.debug(`Deleted file ${filePath}`);\n } catch (e: unknown) {\n const error = e as Error;\n logger.error(`Error deleting file ${filePath}: ${error.message}`);\n throw e;\n }\n return;\n }\n }\n\n // extensions will always have keys since trimExtensions validates this before calling processFile\n const extKeys = Object.keys(extensions);\n const extensionRegex = new RegExp(extKeys.join('|'), 'g');\n if (extensionRegex.test(source)) {\n const lines = source.split('\\n');\n const newLines: string[] = [];\n const blockMarkers: { extension: string; line: number }[] = [];\n let skippingBlock = false;\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.includes(SINGLE_LINE_MARKER)) {\n const matchingExtension = Object.keys(extensions).find((extension) => line.includes(extension));\n if (matchingExtension && extensions[matchingExtension] === false) {\n // Skip the marker line and the next line (the actual code line)\n i += 2;\n continue;\n }\n } else if (line.includes(BLOCK_MARKER_START)) {\n const matchingExtension = Object.keys(extensions).find((extension) => line.includes(extension));\n if (matchingExtension) {\n blockMarkers.push({ extension: matchingExtension, line: i });\n skippingBlock = extensions[matchingExtension] === false;\n } else {\n logger.warn(`Unknown marker found in ${filePath} at line ${i}: \\n${line}`);\n }\n } else if (line.includes(BLOCK_MARKER_END)) {\n const matchingExtension = Object.keys(extensions).find((extension) => line.includes(extension));\n if (matchingExtension) {\n const extension = Object.keys(extensions).find((p) => line.includes(p));\n if (blockMarkers.length === 0) {\n throw new Error(\n `Block marker mismatch in ${filePath}, encountered end marker ${extension} without a matching start marker at line ${i}:\\n${lines[i]}`\n );\n }\n const startMarker = blockMarkers.pop() as { extension: string; line: number };\n if (!extension || startMarker.extension !== extension) {\n throw new Error(\n `Block marker mismatch in ${filePath}, expected end marker for ${startMarker.extension} but got ${extension} at line ${i}:\\n${lines[i]}`\n );\n }\n if (extensions[extension] === false) {\n // Skip the entire block (marker lines are already skipped by skippingBlock)\n skippingBlock = false;\n i++;\n continue;\n }\n }\n }\n if (!skippingBlock) {\n newLines.push(line);\n }\n i++;\n }\n\n if (blockMarkers.length > 0) {\n throw new Error(\n `Unclosed end marker found in ${filePath}: ${blockMarkers[blockMarkers.length - 1].extension}`\n );\n }\n\n // Only write if content changed\n const newSource = newLines.join('\\n');\n if (newSource !== source) {\n try {\n fs.writeFileSync(filePath, newSource);\n logger.debug(`Updated file ${filePath}`);\n } catch (e: unknown) {\n const error = e as Error;\n logger.error(`Error updating file ${filePath}: ${error.message}`);\n throw e;\n }\n }\n }\n}\n\n/**\n * Delete extension folders for disabled extensions.\n * @param projectRoot - The project root directory\n * @param extensions - The extension selections\n * @param extensionConfig - The extension configuration\n */\nfunction deleteExtensionFolders(\n projectRoot: string,\n extensions: ExtensionsSelection,\n extensionConfig: typeof ExtensionConfig & { extensions: Record<string, unknown> }\n): void {\n const extensionsDir = path.join(projectRoot, 'src', 'extensions');\n if (!fs.existsSync(extensionsDir)) {\n return;\n }\n\n const configuredExtensions = extensionConfig.extensions;\n const disabledExtensions = Object.keys(extensions).filter((ext) => extensions[ext] === false);\n\n disabledExtensions.forEach((extKey) => {\n const extensionMeta = configuredExtensions[extKey] as { folder?: string } | undefined;\n if (extensionMeta?.folder) {\n const extensionFolderPath = path.join(extensionsDir, extensionMeta.folder);\n if (fs.existsSync(extensionFolderPath)) {\n try {\n fs.rmSync(extensionFolderPath, { recursive: true, force: true });\n logger.debug(`Deleted extension folder: ${extensionFolderPath}`);\n } catch (err: unknown) {\n const error = err as Error & { code?: string };\n if (error.code === 'EPERM') {\n logger.error(\n `Permission denied - cannot delete ${extensionFolderPath}. You may need to run with sudo or check permissions.`\n );\n } else {\n logger.error(`Error deleting ${extensionFolderPath}: ${error.message}`);\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 { join } from 'node:path';\nimport { existsSync, readFileSync, unlinkSync } from 'node:fs';\nimport { execSync } from 'node:child_process';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport { npmRunPathEnv } from 'npm-run-path';\nimport type { RouteConfigEntry } from '@react-router/dev/routes';\nimport { logger } from '../logger';\n\nlet isCliAvailable: boolean | null = null;\n\nfunction checkReactRouterCli(projectDirectory: string): boolean {\n if (isCliAvailable !== null) {\n return isCliAvailable;\n }\n\n try {\n execSync('react-router --version', {\n cwd: projectDirectory,\n env: npmRunPathEnv(),\n stdio: 'pipe',\n });\n isCliAvailable = true;\n } catch {\n isCliAvailable = false;\n }\n return isCliAvailable;\n}\n\n/**\n * Get the fully resolved routes from React Router by invoking its CLI.\n * This ensures we get the exact same route resolution as React Router uses internally,\n * including all presets, file-system routes, and custom route configurations.\n * @param projectDirectory - The project root directory\n * @returns Array of resolved route config entries\n * @example\n * const routes = getReactRouterRoutes('/path/to/project');\n * // Returns the same structure as `react-router routes --json`\n */\nfunction getReactRouterRoutes(projectDirectory: string): RouteConfigEntry[] {\n if (!checkReactRouterCli(projectDirectory)) {\n throw new Error(\n 'React Router CLI is not available. Please make sure @react-router/dev is installed and accessible.'\n );\n }\n\n // Use a temp file to avoid Node.js buffer limits (8KB default)\n const tempFile = join(tmpdir(), `react-router-routes-${randomUUID()}.json`);\n\n try {\n // Redirect output to temp file to avoid buffer truncation\n execSync(`react-router routes --json > \"${tempFile}\"`, {\n cwd: projectDirectory,\n env: npmRunPathEnv(),\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const output = readFileSync(tempFile, 'utf-8');\n return JSON.parse(output) as RouteConfigEntry[];\n } catch (error) {\n throw new Error(`Failed to get routes from React Router CLI: ${(error as Error).message}`);\n } finally {\n // Clean up temp file\n try {\n if (existsSync(tempFile)) {\n unlinkSync(tempFile);\n }\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n\n/**\n * Convert a file path to its corresponding route path using React Router's CLI.\n * This ensures we get the exact same route resolution as React Router uses internally.\n * @param filePath - Absolute path to the route file\n * @param projectRoot - The project root directory\n * @returns The route path (e.g., '/cart', '/product/:productId')\n * @example\n * const route = filePathToRoute('/path/to/project/src/routes/_app.cart.tsx', '/path/to/project');\n * // Returns: '/cart'\n */\nexport function filePathToRoute(filePath: string, projectRoot: string): string {\n // Normalize paths to POSIX-style\n const filePathPosix = filePath.replace(/\\\\/g, '/');\n\n // Get all routes from React Router CLI\n const routes = getReactRouterRoutes(projectRoot);\n const flatRoutes = flattenRoutes(routes);\n\n // Find the route that matches this file\n for (const route of flatRoutes) {\n // Normalize the route file path for comparison\n const routeFilePosix = route.file.replace(/\\\\/g, '/');\n\n // Check if the file path ends with the route file (handles relative vs. absolute paths)\n if (filePathPosix.endsWith(routeFilePosix) || filePathPosix.endsWith(`/${routeFilePosix}`)) {\n return route.path;\n }\n\n // Also check without leading ./\n const routeFileNormalized = routeFilePosix.replace(/^\\.\\//, '');\n if (filePathPosix.endsWith(routeFileNormalized) || filePathPosix.endsWith(`/${routeFileNormalized}`)) {\n return route.path;\n }\n }\n\n // Fallback: if no match found, return a warning path\n logger.warn(`Could not find route for file: ${filePath}`);\n return '/unknown';\n}\n\n/**\n * Flatten a nested route tree into a flat array with computed paths.\n * Each route will have its full path computed from parent paths.\n * @param routes - The nested route config entries\n * @param parentPath - The parent path prefix (used internally for recursion)\n * @returns Flat array of routes with their full paths\n */\nfunction flattenRoutes(\n routes: RouteConfigEntry[],\n parentPath = ''\n): Array<{ id: string; path: string; file: string; index?: boolean }> {\n const result: Array<{ id: string; path: string; file: string; index?: boolean }> = [];\n\n for (const route of routes) {\n // Compute the full path\n let fullPath: string;\n if (route.index) {\n fullPath = parentPath || '/';\n } else if (route.path) {\n // Handle paths that already start with / (absolute paths from extensions)\n const pathSegment = route.path.startsWith('/') ? route.path : `/${route.path}`;\n fullPath = parentPath ? `${parentPath}${pathSegment}`.replace(/\\/+/g, '/') : pathSegment;\n } else {\n // Layout route without path - use parent path\n fullPath = parentPath || '/';\n }\n\n // Add this route if it has an id\n if (route.id) {\n result.push({\n id: route.id,\n path: fullPath,\n file: route.file,\n index: route.index,\n });\n }\n\n // Recursively process children\n if (route.children && route.children.length > 0) {\n const childPath = route.path ? fullPath : parentPath;\n result.push(...flattenRoutes(route.children, childPath));\n }\n }\n\n return result;\n}\n","#!/usr/bin/env node\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 { readdir, readFile, writeFile, mkdir, access, rm } from 'node:fs/promises';\nimport { join, extname, resolve, basename } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { Project, Node, type SourceFile, type PropertyDeclaration, type Decorator, type Expression } from 'ts-morph';\nimport { filePathToRoute } from './react-router-config.js';\nimport { logger } from '../logger';\n\n// Re-export `filePathToRoute`\nexport { filePathToRoute };\n\nconst SKIP_DIRECTORIES = ['build', 'dist', 'node_modules', '.git', '.next', 'coverage'];\n\nconst DEFAULT_COMPONENT_GROUP = 'odyssey_base';\nconst ARCH_TYPE_HEADLESS = 'headless';\n\ntype AttributeType =\n | 'string'\n | 'text'\n | 'markup'\n | 'integer'\n | 'boolean'\n | 'product'\n | 'category'\n | 'file'\n | 'page'\n | 'image'\n | 'url'\n | 'enum'\n | 'custom'\n | 'cms_record';\n\nconst VALID_ATTRIBUTE_TYPES: readonly AttributeType[] = [\n 'string',\n 'text',\n 'markup',\n 'integer',\n 'boolean',\n 'product',\n 'category',\n 'file',\n 'page',\n 'image',\n 'url',\n 'enum',\n 'custom',\n 'cms_record',\n] as const;\n\n// Type mapping for TypeScript types to B2C Commerce attribute types\n// Based on official schema: https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/current/content/attributedefinition.json\nconst TYPE_MAPPING: Record<string, string> = {\n String: 'string',\n string: 'string',\n Number: 'integer',\n number: 'integer',\n Boolean: 'boolean',\n boolean: 'boolean',\n Date: 'string', // B2C Commerce doesn't have a native date type, use string\n URL: 'url',\n CMSRecord: 'cms_record',\n};\n\n// Resolve attribute type in order: decorator type -> ts-morph type inference -> fallback to string\nfunction resolveAttributeType(decoratorType?: string, tsMorphType?: string, fieldName?: string): string {\n // 1) If the type is set on the decorator, use that (with validation)\n if (decoratorType) {\n if (!VALID_ATTRIBUTE_TYPES.includes(decoratorType as AttributeType)) {\n logger.error(\n `Invalid attribute type '${decoratorType}' for field '${fieldName || 'unknown'}'. Valid types are: ${VALID_ATTRIBUTE_TYPES.join(', ')}`\n );\n process.exit(1);\n }\n return decoratorType;\n }\n\n // 2) Use the type from ts-morph type inference\n if (tsMorphType && TYPE_MAPPING[tsMorphType]) {\n return TYPE_MAPPING[tsMorphType];\n }\n\n // 3) Fall back to string\n return 'string';\n}\n\n// Convert field name to human-readable name\nfunction toHumanReadableName(fieldName: string): string {\n return fieldName\n .replace(/([A-Z])/g, ' $1') // Add space before capital letters\n .replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter\n .trim();\n}\n\n// Convert name to camelCase filename (handles spaces and hyphens, preserves existing camelCase)\nfunction toCamelCaseFileName(name: string): string {\n // If the name is already camelCase (no spaces or hyphens), return as-is\n if (!/[\\s-]/.test(name)) {\n return name;\n }\n\n return name\n .split(/[\\s-]+/) // Split by whitespace and hyphens\n .map((word, index) => {\n if (index === 0) {\n return word.toLowerCase(); // First word is all lowercase\n }\n return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); // Subsequent words are capitalized\n })\n .join(''); // Join without spaces or hyphens\n}\n\nfunction getTypeFromTsMorph(property: PropertyDeclaration, _sourceFile: SourceFile): string {\n try {\n const typeNode = property.getTypeNode();\n if (typeNode) {\n const typeText = typeNode.getText();\n // Extract the base type name from complex types\n const baseType = typeText.split('|')[0].split('&')[0].trim();\n return baseType;\n }\n } catch {\n // If type extraction fails, return string\n }\n\n return 'string';\n}\n\n/**\n * Resolve a variable's initializer expression from the same source file,\n * unwrapping `as const` type assertions.\n */\nfunction resolveVariableInitializer(sourceFile: SourceFile, name: string): Expression | undefined {\n const varDecl = sourceFile.getVariableDeclaration(name);\n if (!varDecl) return undefined;\n let initializer = varDecl.getInitializer();\n if (initializer && Node.isAsExpression(initializer)) {\n initializer = initializer.getExpression();\n }\n return initializer;\n}\n\n/**\n * Check whether an AST node is a type that `parseExpression` can resolve to a\n * concrete JS value (as opposed to falling through to `getText()`).\n */\nfunction isResolvableLiteral(node: Expression): boolean {\n return (\n Node.isStringLiteral(node) ||\n Node.isNumericLiteral(node) ||\n Node.isTrueLiteral(node) ||\n Node.isFalseLiteral(node) ||\n Node.isObjectLiteralExpression(node) ||\n Node.isArrayLiteralExpression(node)\n );\n}\n\nclass UnresolvedConstantReferenceError extends Error {\n constructor(reference: string) {\n super(\n `Cannot resolve constant reference '${reference}'. ` +\n `Ensure the variable is declared in the same file as a literal value.`\n );\n this.name = 'UnresolvedConstantReferenceError';\n }\n}\n\n// Helper function to parse any TypeScript expression into a JavaScript value\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction parseExpression(expression: any): unknown {\n if (Node.isStringLiteral(expression)) {\n return expression.getLiteralValue();\n } else if (Node.isNumericLiteral(expression)) {\n return expression.getLiteralValue();\n } else if (Node.isTrueLiteral(expression)) {\n return true;\n } else if (Node.isFalseLiteral(expression)) {\n return false;\n } else if (Node.isObjectLiteralExpression(expression)) {\n return parseNestedObject(expression);\n } else if (Node.isArrayLiteralExpression(expression)) {\n return parseArrayLiteral(expression);\n } else if (Node.isPropertyAccessExpression(expression)) {\n const obj = expression.getExpression();\n const propName = expression.getName();\n if (Node.isIdentifier(obj)) {\n const resolved = resolveVariableInitializer(expression.getSourceFile(), obj.getText());\n if (resolved && Node.isObjectLiteralExpression(resolved)) {\n const prop = resolved.getProperty(propName);\n if (prop && Node.isPropertyAssignment(prop)) {\n const propInit = prop.getInitializer();\n if (propInit) return parseExpression(propInit);\n }\n }\n throw new UnresolvedConstantReferenceError(expression.getText());\n }\n return expression.getText();\n } else if (Node.isIdentifier(expression)) {\n const resolved = resolveVariableInitializer(expression.getSourceFile(), expression.getText());\n if (resolved && isResolvableLiteral(resolved)) {\n return parseExpression(resolved);\n }\n return expression.getText();\n } else {\n return expression.getText();\n }\n}\n\n// Helper function to parse deeply nested object literals\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction parseNestedObject(objectLiteral: any): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n try {\n const properties = objectLiteral.getProperties();\n\n for (const property of properties) {\n if (Node.isPropertyAssignment(property)) {\n const name = property.getName();\n const initializer = property.getInitializer();\n\n if (initializer) {\n result[name] = parseExpression(initializer);\n }\n }\n }\n } catch (error) {\n logger.warn(`Could not parse nested object: ${(error as Error).message}`);\n return result; // Return the result even if there was an error\n }\n\n return result;\n}\n\n// Helper function to parse array literals\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction parseArrayLiteral(arrayLiteral: any): unknown[] {\n const result: unknown[] = [];\n\n try {\n const elements = arrayLiteral.getElements();\n\n for (const element of elements) {\n result.push(parseExpression(element));\n }\n } catch (error) {\n logger.warn(`Could not parse array literal: ${(error as Error).message}`);\n }\n\n return result;\n}\n\n// Parse decorator arguments using ts-morph\nfunction parseDecoratorArgs(decorator: Decorator): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n try {\n const args = decorator.getArguments();\n\n if (args.length === 0) {\n return result;\n }\n\n // Handle the first argument\n const firstArg = args[0];\n\n if (Node.isObjectLiteralExpression(firstArg)) {\n // First argument is an object literal - parse all its properties\n const properties = firstArg.getProperties();\n\n for (const property of properties) {\n if (Node.isPropertyAssignment(property)) {\n const name = property.getName();\n const initializer = property.getInitializer();\n\n if (initializer) {\n result[name] = parseExpression(initializer);\n }\n }\n }\n } else if (Node.isStringLiteral(firstArg)) {\n // First argument is a string literal - use it as the id\n result.id = parseExpression(firstArg);\n\n // Check if there's a second argument (options object)\n if (args.length > 1) {\n const secondArg = args[1];\n if (Node.isObjectLiteralExpression(secondArg)) {\n const properties = secondArg.getProperties();\n\n for (const property of properties) {\n if (Node.isPropertyAssignment(property)) {\n const name = property.getName();\n const initializer = property.getInitializer();\n\n if (initializer) {\n result[name] = parseExpression(initializer);\n }\n }\n }\n }\n }\n }\n\n return result;\n } catch (error) {\n if (error instanceof UnresolvedConstantReferenceError) {\n throw error;\n }\n logger.warn(`Could not parse decorator arguments: ${(error as Error).message}`);\n return result;\n }\n}\n\nfunction extractAttributesFromSource(sourceFile: SourceFile, className: string): Record<string, unknown>[] {\n const attributes: Record<string, unknown>[] = [];\n\n try {\n // Find the class declaration\n const classDeclaration = sourceFile.getClass(className);\n if (!classDeclaration) {\n return attributes;\n }\n\n // Get all properties in the class\n const properties = classDeclaration.getProperties();\n\n for (const property of properties) {\n // Check if the property has an @AttributeDefinition decorator\n const attributeDecorator = property.getDecorator('AttributeDefinition');\n if (!attributeDecorator) {\n continue;\n }\n\n const fieldName = property.getName();\n const config = parseDecoratorArgs(attributeDecorator);\n\n const isRequired = !property.hasQuestionToken();\n\n const inferredType = (config.type as string) || getTypeFromTsMorph(property, sourceFile);\n\n const attribute: Record<string, unknown> = {\n id: config.id || fieldName,\n name: config.name || toHumanReadableName(fieldName),\n type: resolveAttributeType(config.type as string, inferredType, fieldName),\n required: config.required !== undefined ? config.required : isRequired,\n description: config.description || `Field: ${fieldName}`,\n };\n\n if (config.values) {\n attribute.values = config.values;\n }\n\n if (config.defaultValue !== undefined) {\n attribute.default_value = config.defaultValue;\n }\n\n attributes.push(attribute);\n }\n } catch (error) {\n if (error instanceof UnresolvedConstantReferenceError) {\n throw error;\n }\n logger.warn(`Could not extract attributes from class ${className}: ${(error as Error).message}`);\n }\n\n return attributes;\n}\n\nfunction normalizeComponentTypeId(typeId: string, defaultGroup: string): string {\n return typeId.includes('.') ? typeId : `${defaultGroup}.${typeId}`;\n}\n\nfunction extractRegionDefinitionsFromSource(\n sourceFile: SourceFile,\n className: string,\n defaultComponentGroup = DEFAULT_COMPONENT_GROUP\n): Record<string, unknown>[] {\n const regionDefinitions: Record<string, unknown>[] = [];\n\n try {\n // Find the class declaration\n const classDeclaration = sourceFile.getClass(className);\n if (!classDeclaration) {\n return regionDefinitions;\n }\n\n // Check for class-level @RegionDefinition decorator\n const classRegionDecorator = classDeclaration.getDecorator('RegionDefinition');\n if (classRegionDecorator) {\n const args = classRegionDecorator.getArguments();\n if (args.length > 0) {\n const firstArg = args[0];\n\n // Handle array literal argument (most common case)\n if (Node.isArrayLiteralExpression(firstArg)) {\n const elements = firstArg.getElements();\n for (const element of elements) {\n if (Node.isObjectLiteralExpression(element)) {\n const regionConfig = parseDecoratorArgs({\n getArguments: () => [element],\n } as unknown as Decorator);\n\n const regionDefinition: Record<string, unknown> = {\n id: regionConfig.id || 'region',\n name: regionConfig.name || 'Region',\n };\n\n // Add optional properties if they exist in the decorator\n if (regionConfig.componentTypes) {\n regionDefinition.component_types = regionConfig.componentTypes;\n }\n\n if (Array.isArray(regionConfig.componentTypeInclusions)) {\n regionDefinition.component_type_inclusions = regionConfig.componentTypeInclusions.map(\n (incl) => ({\n type_id: normalizeComponentTypeId(String(incl), defaultComponentGroup),\n })\n );\n }\n\n if (Array.isArray(regionConfig.componentTypeExclusions)) {\n regionDefinition.component_type_exclusions = regionConfig.componentTypeExclusions.map(\n (excl) => ({\n type_id: normalizeComponentTypeId(String(excl), defaultComponentGroup),\n })\n );\n }\n\n if (regionConfig.maxComponents !== undefined) {\n regionDefinition.max_components = regionConfig.maxComponents;\n }\n\n if (regionConfig.minComponents !== undefined) {\n regionDefinition.min_components = regionConfig.minComponents;\n }\n\n if (regionConfig.allowMultiple !== undefined) {\n regionDefinition.allow_multiple = regionConfig.allowMultiple;\n }\n\n if (regionConfig.defaultComponentConstructors) {\n regionDefinition.default_component_constructors =\n regionConfig.defaultComponentConstructors;\n }\n\n regionDefinitions.push(regionDefinition);\n }\n }\n }\n }\n }\n } catch (error) {\n logger.warn(\n `Warning: Could not extract region definitions from class ${className}: ${(error as Error).message}`\n );\n }\n\n return regionDefinitions;\n}\n\nasync function processComponentFile(filePath: string, _projectRoot: string): Promise<unknown[]> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const components: unknown[] = [];\n\n // Check if file contains @Component decorator\n if (!content.includes('@Component')) {\n return components;\n }\n\n // Convert file path to module path (currently unused but may be needed in future)\n // const relativePath = relative(join(projectRoot, 'src'), filePath);\n // const modulePath = relativePath.replace(/\\.tsx?$/, '').replace(/\\\\/g, '/');\n\n try {\n // Create a ts-morph project and add the source file\n const project = new Project({\n useInMemoryFileSystem: true,\n skipAddingFilesFromTsConfig: true,\n });\n\n const sourceFile = project.createSourceFile(filePath, content);\n\n const classes = sourceFile.getClasses();\n\n for (const classDeclaration of classes) {\n const componentDecorator = classDeclaration.getDecorator('Component');\n if (!componentDecorator) {\n continue;\n }\n\n const className = classDeclaration.getName();\n if (!className) {\n continue;\n }\n\n const componentConfig = parseDecoratorArgs(componentDecorator);\n const componentGroup = String(componentConfig.group || DEFAULT_COMPONENT_GROUP);\n\n const attributes = extractAttributesFromSource(sourceFile, className);\n const regionDefinitions = extractRegionDefinitionsFromSource(sourceFile, className, componentGroup);\n\n const componentMetadata = {\n typeId: componentConfig.id || className.toLowerCase(),\n name: componentConfig.name || toHumanReadableName(className),\n group: componentGroup,\n description: componentConfig.description || `Custom component: ${className}`,\n regionDefinitions,\n attributes,\n };\n\n components.push(componentMetadata);\n }\n } catch (error) {\n if (error instanceof UnresolvedConstantReferenceError) {\n throw error;\n }\n logger.warn(`Could not process file ${filePath}:`, (error as Error).message);\n }\n\n return components;\n } catch (error) {\n if (error instanceof UnresolvedConstantReferenceError) {\n throw error;\n }\n logger.warn(`Could not read file ${filePath}:`, (error as Error).message);\n return [];\n }\n}\n\nasync function processPageTypeFile(filePath: string, projectRoot: string): Promise<unknown[]> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const pageTypes: unknown[] = [];\n\n // Check if file contains @PageType decorator\n if (!content.includes('@PageType')) {\n return pageTypes;\n }\n\n try {\n // Create a ts-morph project and add the source file\n const project = new Project({\n useInMemoryFileSystem: true,\n skipAddingFilesFromTsConfig: true,\n });\n\n const sourceFile = project.createSourceFile(filePath, content);\n\n const classes = sourceFile.getClasses();\n\n for (const classDeclaration of classes) {\n const pageTypeDecorator = classDeclaration.getDecorator('PageType');\n if (!pageTypeDecorator) {\n continue;\n }\n\n const className = classDeclaration.getName();\n if (!className) {\n continue;\n }\n\n const pageTypeConfig = parseDecoratorArgs(pageTypeDecorator);\n\n const attributes = extractAttributesFromSource(sourceFile, className);\n const regionDefinitions = extractRegionDefinitionsFromSource(sourceFile, className);\n const route = filePathToRoute(filePath, projectRoot);\n\n const pageTypeMetadata = {\n typeId: pageTypeConfig.id || className.toLowerCase(),\n name: pageTypeConfig.name || toHumanReadableName(className),\n description: pageTypeConfig.description || `Custom page type: ${className}`,\n regionDefinitions,\n supportedAspectTypes: pageTypeConfig.supportedAspectTypes || [],\n attributes,\n route,\n };\n\n pageTypes.push(pageTypeMetadata);\n }\n } catch (error) {\n logger.warn(`Could not process file ${filePath}:`, (error as Error).message);\n }\n\n return pageTypes;\n } catch (error) {\n logger.warn(`Could not read file ${filePath}:`, (error as Error).message);\n return [];\n }\n}\n\nasync function processAspectFile(filePath: string, _projectRoot: string): Promise<unknown[]> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const aspects: unknown[] = [];\n\n // Check if file is a JSON aspect file\n if (!filePath.endsWith('.json') || !content.trim().startsWith('{')) {\n return aspects;\n }\n\n // Check if file is in the aspects directory\n if (!filePath.includes('/aspects/') && !filePath.includes('\\\\aspects\\\\')) {\n return aspects;\n }\n\n try {\n // Parse the JSON content\n const aspectData = JSON.parse(content);\n\n // Extract filename without extension as the aspect ID\n const fileName = basename(filePath, '.json');\n\n // Validate that it looks like an aspect file\n if (!aspectData.name || !aspectData.attribute_definitions) {\n return aspects;\n }\n\n const aspectMetadata = {\n id: fileName,\n name: aspectData.name,\n description: aspectData.description || `Aspect type: ${aspectData.name}`,\n attributeDefinitions: aspectData.attribute_definitions || [],\n supportedObjectTypes: aspectData.supported_object_types || [],\n };\n\n aspects.push(aspectMetadata);\n } catch (parseError) {\n logger.warn(`Could not parse JSON in file ${filePath}:`, (parseError as Error).message);\n }\n\n return aspects;\n } catch (error) {\n logger.warn(`Could not read file ${filePath}:`, (error as Error).message);\n return [];\n }\n}\n\nasync function generateComponentCartridge(\n component: Record<string, unknown>,\n outputDir: string,\n dryRun = false\n): Promise<void> {\n const fileName = toCamelCaseFileName(component.typeId as string);\n const groupDir = join(outputDir, component.group as string);\n const outputPath = join(groupDir, `${fileName}.json`);\n\n if (!dryRun) {\n // Ensure the group directory exists\n try {\n await mkdir(groupDir, { recursive: true });\n } catch {\n // Directory might already exist, which is fine\n }\n\n const attributeDefinitionGroups = [\n {\n id: component.typeId,\n name: component.name,\n description: component.description,\n attribute_definitions: component.attributes,\n },\n ];\n\n const cartridgeData = {\n name: component.name,\n description: component.description,\n group: component.group,\n arch_type: ARCH_TYPE_HEADLESS,\n region_definitions: component.regionDefinitions || [],\n attribute_definition_groups: attributeDefinitionGroups,\n };\n\n await writeFile(outputPath, JSON.stringify(cartridgeData, null, 2));\n }\n\n const prefix = dryRun ? ' - [DRY RUN]' : ' -';\n logger.debug(\n `${prefix} ${String(component.typeId)}: ${String(component.name)} (${String((component.attributes as unknown[]).length)} attributes) → ${fileName}.json`\n );\n}\n\nasync function generatePageTypeCartridge(\n pageType: Record<string, unknown>,\n outputDir: string,\n dryRun = false\n): Promise<void> {\n const fileName = toCamelCaseFileName(pageType.name as string);\n const outputPath = join(outputDir, `${fileName}.json`);\n\n if (!dryRun) {\n const cartridgeData: Record<string, unknown> = {\n name: pageType.name,\n description: pageType.description,\n arch_type: ARCH_TYPE_HEADLESS,\n region_definitions: pageType.regionDefinitions || [],\n };\n\n // Add attribute_definition_groups if there are attributes\n if (pageType.attributes && (pageType.attributes as unknown[]).length > 0) {\n const attributeDefinitionGroups = [\n {\n id: pageType.typeId || fileName,\n name: pageType.name,\n description: pageType.description,\n attribute_definitions: pageType.attributes,\n },\n ];\n cartridgeData.attribute_definition_groups = attributeDefinitionGroups;\n }\n\n // Add supported_aspect_types if specified\n if (pageType.supportedAspectTypes) {\n cartridgeData.supported_aspect_types = pageType.supportedAspectTypes;\n }\n\n if (pageType.route) {\n cartridgeData.route = pageType.route;\n }\n\n await writeFile(outputPath, JSON.stringify(cartridgeData, null, 2));\n }\n\n const prefix = dryRun ? ' - [DRY RUN]' : ' -';\n logger.debug(\n `${prefix} ${String(pageType.name)}: ${String(pageType.description)} (${String((pageType.attributes as unknown[]).length)} attributes) → ${fileName}.json`\n );\n}\n\nasync function generateAspectCartridge(\n aspect: Record<string, unknown>,\n outputDir: string,\n dryRun = false\n): Promise<void> {\n const fileName = toCamelCaseFileName(aspect.id as string);\n const outputPath = join(outputDir, `${fileName}.json`);\n\n if (!dryRun) {\n const cartridgeData: Record<string, unknown> = {\n name: aspect.name,\n description: aspect.description,\n arch_type: ARCH_TYPE_HEADLESS,\n attribute_definitions: aspect.attributeDefinitions || [],\n };\n\n // Add supported_object_types if specified\n if (aspect.supportedObjectTypes) {\n cartridgeData.supported_object_types = aspect.supportedObjectTypes;\n }\n\n await writeFile(outputPath, JSON.stringify(cartridgeData, null, 2));\n }\n\n const prefix = dryRun ? ' - [DRY RUN]' : ' -';\n logger.debug(\n `${prefix} ${String(aspect.name)}: ${String(aspect.description)} (${String((aspect.attributeDefinitions as unknown[]).length)} attributes) → ${fileName}.json`\n );\n}\n\n/**\n * Options for generateMetadata function\n */\nexport interface GenerateMetadataOptions {\n /**\n * Optional array of specific file paths to process.\n * If provided, only these files will be processed and existing cartridge files will NOT be deleted.\n * If omitted, the entire src/ directory will be scanned and all existing cartridge files will be deleted first.\n */\n filePaths?: string[];\n\n /**\n * Whether to run ESLint with --fix on generated JSON files to format them according to project settings.\n * Defaults to true.\n */\n lintFix?: boolean;\n\n /**\n * If true, scans files and reports what would be generated without actually writing any files or deleting directories.\n * Defaults to false.\n */\n dryRun?: boolean;\n}\n\n/**\n * Result returned by generateMetadata function\n */\nexport interface GenerateMetadataResult {\n componentsGenerated: number;\n pageTypesGenerated: number;\n aspectsGenerated: number;\n totalFiles: number;\n}\n\n/**\n * Runs ESLint with --fix on the specified directory to format JSON files.\n * This ensures generated JSON files match the project's Prettier/ESLint configuration.\n */\nfunction lintGeneratedFiles(metadataDir: string, projectRoot: string): void {\n try {\n logger.debug('🔧 Running ESLint --fix on generated JSON files...');\n\n // Run ESLint from the project root directory so it picks up the correct config\n // Use --no-error-on-unmatched-pattern to handle cases where no JSON files exist yet\n const command = `npx eslint \"${metadataDir}/**/*.json\" --fix --no-error-on-unmatched-pattern`;\n\n execSync(command, {\n cwd: projectRoot,\n stdio: 'pipe', // Suppress output unless there's an error\n encoding: 'utf-8',\n });\n\n logger.debug('✅ JSON files formatted successfully');\n } catch (error) {\n // ESLint returns non-zero exit code even when --fix resolves all issues\n // We only warn if there are actual unfixable issues\n const execError = error as { status?: number; stderr?: string; stdout?: string };\n\n // Exit code 1 usually means there were linting issues (some may have been fixed)\n // Exit code 2 means configuration error or other fatal error\n if (execError.status === 2) {\n const errMsg = execError.stderr || execError.stdout || 'Unknown error';\n logger.warn(`⚠️ Could not run ESLint --fix: ${errMsg}`);\n } else if (execError.stderr && execError.stderr.includes('error')) {\n logger.warn(`⚠️ Some linting issues could not be auto-fixed. Run ESLint manually to review.`);\n } else {\n // Exit code 1 with no errors in stderr usually means all issues were fixed\n logger.debug('✅ JSON files formatted successfully');\n }\n }\n}\n\n// Main function\nexport async function generateMetadata(\n projectDirectory: string,\n metadataDirectory: string,\n options?: GenerateMetadataOptions\n): Promise<GenerateMetadataResult> {\n try {\n const filePaths = options?.filePaths;\n const isIncrementalMode = filePaths && filePaths.length > 0;\n const dryRun = options?.dryRun || false;\n\n if (dryRun) {\n logger.debug('🔍 [DRY RUN] Scanning for decorated components and page types...');\n } else if (isIncrementalMode) {\n logger.debug(`🔍 Generating metadata for ${filePaths.length} specified file(s)...`);\n } else {\n logger.debug('🔍 Generating metadata for decorated components and page types...');\n }\n\n const projectRoot = resolve(projectDirectory);\n const srcDir = join(projectRoot, 'src');\n const metadataDir = resolve(metadataDirectory);\n const componentsOutputDir = join(metadataDir, 'components');\n const pagesOutputDir = join(metadataDir, 'pages');\n const aspectsOutputDir = join(metadataDir, 'aspects');\n\n // Skip directory operations in dry run mode\n if (!dryRun) {\n // Only delete existing directories in full scan mode (not incremental)\n if (!isIncrementalMode) {\n logger.debug('🗑️ Cleaning existing output directories...');\n for (const outputDir of [componentsOutputDir, pagesOutputDir, aspectsOutputDir]) {\n try {\n await rm(outputDir, { recursive: true, force: true });\n logger.debug(` - Deleted: ${outputDir}`);\n } catch {\n // Directory might not exist, which is fine\n logger.debug(` - Directory not found (skipping): ${outputDir}`);\n }\n }\n } else {\n logger.debug('📝 Incremental mode: existing cartridge files will be preserved/overwritten');\n }\n\n // Create output directories if they don't exist\n logger.debug('Creating output directories...');\n for (const outputDir of [componentsOutputDir, pagesOutputDir, aspectsOutputDir]) {\n try {\n await mkdir(outputDir, { recursive: true });\n } catch (error) {\n try {\n await access(outputDir);\n // Directory exists, that's fine\n } catch {\n const err = error as Error;\n logger.error(`❌ Failed to create output directory ${outputDir}: ${err.message}`);\n process.exit(1);\n throw err;\n }\n }\n }\n } else if (isIncrementalMode) {\n logger.debug(`📝 [DRY RUN] Would process ${filePaths.length} specific file(s)`);\n } else {\n logger.debug('📝 [DRY RUN] Would clean and regenerate all metadata files');\n }\n\n let files: string[] = [];\n\n if (isIncrementalMode && filePaths) {\n // Use the specified file paths (resolve them relative to project root)\n files = filePaths.map((fp) => resolve(projectRoot, fp));\n logger.debug(`📂 Processing ${files.length} specified file(s)...`);\n } else {\n // Full scan mode: scan entire src directory\n const scanDirectory = async (dir: string): Promise<void> => {\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n\n if (entry.isDirectory()) {\n if (!SKIP_DIRECTORIES.includes(entry.name)) {\n await scanDirectory(fullPath);\n }\n } else if (\n entry.isFile() &&\n (extname(entry.name) === '.ts' ||\n extname(entry.name) === '.tsx' ||\n extname(entry.name) === '.json')\n ) {\n files.push(fullPath);\n }\n }\n };\n\n await scanDirectory(srcDir);\n }\n\n // Process each file for both components and page types\n const allComponents: unknown[] = [];\n const allPageTypes: unknown[] = [];\n const allAspects: unknown[] = [];\n\n for (const file of files) {\n const components = await processComponentFile(file, projectRoot);\n allComponents.push(...components);\n\n const pageTypes = await processPageTypeFile(file, projectRoot);\n allPageTypes.push(...pageTypes);\n\n const aspects = await processAspectFile(file, projectRoot);\n allAspects.push(...aspects);\n }\n\n if (allComponents.length === 0 && allPageTypes.length === 0 && allAspects.length === 0) {\n logger.info('⚠️ No decorated components, page types, or aspect files found.');\n return {\n componentsGenerated: 0,\n pageTypesGenerated: 0,\n aspectsGenerated: 0,\n totalFiles: 0,\n };\n }\n\n // Generate component cartridge files\n if (allComponents.length > 0) {\n logger.debug(`✅ Found ${allComponents.length} decorated component(s)`);\n for (const component of allComponents) {\n await generateComponentCartridge(component as Record<string, unknown>, componentsOutputDir, dryRun);\n }\n if (dryRun) {\n logger.info(`[DRY RUN] Would generate ${allComponents.length} component metadata file(s)`);\n } else {\n logger.info(`Generated ${allComponents.length} component metadata file(s)`);\n }\n }\n\n // Generate page type cartridge files\n if (allPageTypes.length > 0) {\n logger.debug(`✅ Found ${allPageTypes.length} decorated page type(s)`);\n for (const pageType of allPageTypes) {\n await generatePageTypeCartridge(pageType as Record<string, unknown>, pagesOutputDir, dryRun);\n }\n if (dryRun) {\n logger.info(`[DRY RUN] Would generate ${allPageTypes.length} page type metadata file(s)`);\n } else {\n logger.info(`Generated ${allPageTypes.length} page type metadata file(s)`);\n }\n }\n\n if (allAspects.length > 0) {\n logger.debug(`✅ Found ${allAspects.length} decorated aspect(s)`);\n for (const aspect of allAspects) {\n await generateAspectCartridge(aspect as Record<string, unknown>, aspectsOutputDir, dryRun);\n }\n if (dryRun) {\n logger.info(`[DRY RUN] Would generate ${allAspects.length} aspect metadata file(s)`);\n } else {\n logger.info(`Generated ${allAspects.length} aspect metadata file(s)`);\n }\n }\n\n // Run ESLint --fix to format generated JSON files according to project settings\n const shouldLintFix = options?.lintFix !== false; // Default to true\n if (\n !dryRun &&\n shouldLintFix &&\n (allComponents.length > 0 || allPageTypes.length > 0 || allAspects.length > 0)\n ) {\n lintGeneratedFiles(metadataDir, projectRoot);\n }\n\n // Return statistics\n return {\n componentsGenerated: allComponents.length,\n pageTypesGenerated: allPageTypes.length,\n aspectsGenerated: allAspects.length,\n totalFiles: allComponents.length + allPageTypes.length + allAspects.length,\n };\n } catch (error) {\n const err = error as Error;\n logger.error('❌ Error:', err.message);\n process.exit(1);\n throw err;\n }\n}\n"],"mappings":"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"}
|