@salesforce/storefront-next-dev 1.1.0 → 1.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["LEVEL_PRIORITY: Record<LogLevel, number>","overrideLevel: LogLevel | undefined","isCliAvailable: boolean | null","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[]","componentMetadata: Record<string, unknown>","pageTypes: unknown[]","aspects: unknown[]","cartridgeData: Record<string, unknown>","files: string[]","allComponents: unknown[]","allPageTypes: unknown[]","allAspects: unknown[]"],"sources":["../../src/utils/logger.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 { 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 // Skip root-duplicate routes — these are convenience clones created by applyUrlConfig\n // so the bare \"/\" URL still works. The canonical route (under the site-context prefix)\n // is the one that should appear in cartridge metadata.\n const canonicalRoutes = flatRoutes.filter((route) => !route.id.endsWith('--root-duplicate'));\n\n // Find the route that matches this file\n for (const route of canonicalRoutes) {\n // Normalize the route file path for comparison\n const routeFilePosix = route.file.replace(/\\\\/g, '/');\n const routeFileNormalized = routeFilePosix.replace(/^\\.\\//, '');\n\n // Check if the file path ends with the route file (handles relative vs. absolute paths)\n if (\n filePathPosix.endsWith(routeFilePosix) ||\n filePathPosix.endsWith(`/${routeFilePosix}`) ||\n filePathPosix.endsWith(routeFileNormalized) ||\n filePathPosix.endsWith(`/${routeFileNormalized}`)\n ) {\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 = 'storefrontnext_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 if (config.editorDefinition !== undefined) {\n attribute.editor_definition = config.editorDefinition;\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: Record<string, unknown> = {\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 if (typeof componentConfig.embedded === 'boolean') {\n componentMetadata.embedded = componentConfig.embedded;\n }\n if (componentConfig.component_id !== undefined) {\n componentMetadata.component_id = String(componentConfig.component_id);\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: Record<string, unknown> = {\n name: component.name,\n description: component.description,\n group: component.group,\n arch_type: ARCH_TYPE_HEADLESS,\n ...(typeof component.embedded === 'boolean' && { embedded: component.embedded }),\n component_id: component.component_id,\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 const configMetadataDir = join(projectRoot, 'config-metadata');\n try {\n await access(configMetadataDir);\n await scanDirectory(configMetadataDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n logger.debug(` - Directory not found (skipping): ${configMetadataDir}`);\n } else {\n logger.warn(` - Unable to access ${configMetadataDir}:`, (error as Error).message);\n }\n }\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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"}
1
+ {"version":3,"file":"index.js","names":["LEVEL_PRIORITY: Record<LogLevel, number>","overrideLevel: LogLevel | undefined","isCliAvailable: boolean | null","result: Array<{ id: string; path: string; file: string; index?: boolean }>","fullPath: string","join","VALID_ATTRIBUTE_TYPES: readonly AttributeType[]","TYPE_MAPPING: Record<string, string>","mapped: Record<string, unknown>","result: Record<string, unknown>","result: unknown[]","attributes: Record<string, unknown>[]","attribute: Record<string, unknown>","regionDefinitions: Record<string, unknown>[]","regionDefinition: Record<string, unknown>","components: unknown[]","componentMetadata: Record<string, unknown>","pageTypes: unknown[]","aspects: unknown[]","cartridgeData: Record<string, unknown>","files: string[]","allComponents: unknown[]","allPageTypes: unknown[]","allAspects: unknown[]"],"sources":["../../src/utils/logger.ts","../../src/cartridge-services/react-router-config.ts","../../src/utils/format-with-project-biome.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/* oxlint-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 { 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 // Skip root-duplicate routes — these are convenience clones created by applyUrlConfig\n // so the bare \"/\" URL still works. The canonical route (under the site-context prefix)\n // is the one that should appear in cartridge metadata.\n const canonicalRoutes = flatRoutes.filter((route) => !route.id.endsWith('--root-duplicate'));\n\n // Find the route that matches this file\n for (const route of canonicalRoutes) {\n // Normalize the route file path for comparison\n const routeFilePosix = route.file.replace(/\\\\/g, '/');\n const routeFileNormalized = routeFilePosix.replace(/^\\.\\//, '');\n\n // Check if the file path ends with the route file (handles relative vs. absolute paths)\n if (\n filePathPosix.endsWith(routeFilePosix) ||\n filePathPosix.endsWith(`/${routeFilePosix}`) ||\n filePathPosix.endsWith(routeFileNormalized) ||\n filePathPosix.endsWith(`/${routeFileNormalized}`)\n ) {\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","/**\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 { spawnSync } from 'child_process';\nimport { createRequire } from 'module';\nimport { basename, dirname, join } from 'path';\nimport { fileURLToPath } from 'url';\nimport { logger } from '../logger';\n\n/** This module's own path — the resolution root for the SDK-bundled Biome fallback. */\nconst HERE = fileURLToPath(import.meta.url);\n\n/**\n * Format generated file content with the *consuming project's* Biome so the written file\n * matches what the project's own `biome format` / `pnpm lint:fix` would produce.\n *\n * The SDK generates two kinds of files into a customer project — the extension `config.json`\n * (`trim-extensions.ts`) and the static component registry (`staticRegistry.ts`). Both must\n * be emitted in the exact byte shape the project's formatter produces, or the generated file\n * fails `pnpm lint` on a fresh project / churns on every `biome format --write` (W-23074938).\n *\n * Biome is preferred from the target file's own location — i.e. the consuming project's\n * `node_modules` — so it's the project's Biome version whose output is matched. When that\n * isn't installed yet (e.g. `create-storefront` runs `trim-extensions` BEFORE the generated\n * project's first `pnpm install`), we fall back to the SDK-bundled `@biomejs/biome` (a hard\n * dependency of this package, pinned to the template's version). Either way the file's\n * committed `biome.json` governs the output: Biome discovers its config by walking up from\n * the working directory, so we spawn it with `cwd` set to the file's directory and pass the\n * file's basename via `--stdin-file-path` (which drives both parser selection and formatting).\n *\n * Returns the content unchanged when no Biome can be resolved at all or the format fails, so\n * generation never breaks over formatting. A genuine format error is logged but non-fatal —\n * an unformatted-but-valid file is recoverable by the customer running `biome format --write`.\n *\n * @param content - The serialized file content to format.\n * @param filePath - The file's path (drives parser selection + config resolution + cwd).\n * @returns The Biome-formatted content, or the original content if Biome is unavailable.\n */\nexport function formatWithProjectBiome(content: string, filePath: string): string {\n const biomeBin = resolveBiomeBin(filePath);\n if (!biomeBin) {\n logger.warn(`⚠️ Biome could not be resolved; ${basename(filePath)} will be written unformatted.`);\n return content;\n }\n\n // Biome resolves biome.json by walking up from cwd, so run it from the file's directory\n // and identify the file by basename for parser selection.\n const result = spawnSync(process.execPath, [biomeBin, 'format', `--stdin-file-path=${basename(filePath)}`], {\n cwd: dirname(filePath),\n input: content,\n encoding: 'utf8',\n });\n\n if (result.status !== 0 || typeof result.stdout !== 'string') {\n const detail = result.stderr?.trim() || `exit code ${result.status}`;\n logger.warn(`⚠️ Skipping Biome formatting for ${basename(filePath)}: ${detail}`);\n return content;\n }\n\n return result.stdout;\n}\n\n/**\n * Format every file Biome recognizes under `directory` in place with the consuming project's\n * Biome, so generated files (e.g. cartridge metadata JSON) match `biome format` / `pnpm lint:fix`.\n *\n * Unlike {@link formatWithProjectBiome} (single file via stdin), this runs `biome format --write`\n * against the directory so Biome discovers the project's `biome.json` by walking up from it and\n * applies the project's formatting to the written files directly.\n *\n * Fail-safe: when Biome can't be resolved or the run fails, the files are left as written (valid,\n * just unformatted) and a warning is logged — generation never breaks over formatting.\n *\n * @param directory - Directory whose Biome-recognized files should be formatted in place.\n */\nexport function formatDirectoryWithProjectBiome(directory: string): void {\n // Resolve Biome as if from a file inside the directory, so a node_modules directly under\n // `directory` is on the resolution path (createRequire resolves relative to the file's dir).\n const biomeBin = resolveBiomeBin(join(directory, 'biome-resolution-root.js'));\n if (!biomeBin) {\n logger.warn(`⚠️ Biome could not be resolved; generated files in ${directory} were left unformatted.`);\n return;\n }\n\n // `--write` formats in place; Biome resolves biome.json by walking up from cwd, so run it\n // from the target directory. It exits non-zero if a file can't be fixed, which we treat as\n // non-fatal — the files are still valid, just not reformatted.\n const result = spawnSync(process.execPath, [biomeBin, 'format', '--write', '.'], {\n cwd: directory,\n encoding: 'utf8',\n });\n\n if (result.status !== 0) {\n const detail = result.stderr?.trim() || `exit code ${result.status}`;\n logger.warn(`⚠️ Some generated files in ${directory} could not be formatted by Biome: ${detail}`);\n return;\n }\n\n logger.debug(`✅ Formatted generated files in ${directory} with Biome`);\n}\n\n/**\n * Resolve a Biome CLI binary path, preferring the consuming project's install and falling back\n * to the SDK-bundled copy (available pre-install). Returns null when neither resolves.\n */\nfunction resolveBiomeBin(filePath: string): string | null {\n // Prefer the project's own @biomejs/biome (resolved from the target file's location);\n // fall back to this SDK package's bundled copy when the project isn't installed yet.\n for (const fromPath of [filePath, HERE]) {\n try {\n const req = createRequire(fromPath);\n const biomePkgJson = req.resolve('@biomejs/biome/package.json');\n const { bin } = req(biomePkgJson) as { bin: { biome: string } };\n return join(dirname(biomePkgJson), bin.biome);\n } catch {\n // Try the next resolution root.\n }\n }\n return null;\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 { Project, Node, type SourceFile, type PropertyDeclaration, type Decorator, type Expression } from 'ts-morph';\nimport { filePathToRoute } from './react-router-config.js';\nimport { logger } from '../logger';\nimport { formatDirectoryWithProjectBiome } from '../utils/format-with-project-biome';\n\n// Re-export `filePathToRoute`\nexport { filePathToRoute };\n\nconst SKIP_DIRECTORIES = ['build', 'dist', 'node_modules', '.git', '.next', 'coverage'];\n\nconst DEFAULT_COMPONENT_GROUP = 'storefrontnext_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// Attribute types for which the Page Designer metadefinition schema forbids `searching`.\nconst SEARCHING_FORBIDDEN_TYPES = new Set(['integer', 'boolean', 'file', 'page', 'image', 'url', 'enum']);\n\n/**\n * Validate an authored `searching` config against the resolved attribute type and map it to the\n * snake_case schema shape. The metadefinition schema gates `searching` by type; an invalid\n * combination fails cartridge generation (matching the strict behavior of `resolveAttributeType`).\n *\n * Rules (from attributedefinition.json):\n * - Forbidden entirely on integer/boolean/file/page/image/url/enum.\n * - `markup`: `sortable` must be false.\n * - `custom`/`cms_record`: `refinable` must be false; `boost_factor`/`sortable` not allowed.\n * - `string`/`text`/`product`/`category`: all fields allowed.\n */\nfunction mapAttributeSearching(\n searching: Record<string, unknown>,\n resolvedType: string,\n fieldName: string\n): Record<string, unknown> {\n const fail = (reason: string): never => {\n logger.error(`Invalid 'searching' config for attribute '${fieldName}' (type '${resolvedType}'): ${reason}`);\n process.exit(1);\n };\n\n if (SEARCHING_FORBIDDEN_TYPES.has(resolvedType)) {\n fail(`the '${resolvedType}' attribute type does not support searching.`);\n }\n\n const { searchable, refinable, boostFactor, sortable } = searching as {\n searchable?: unknown;\n refinable?: unknown;\n boostFactor?: unknown;\n sortable?: unknown;\n };\n\n if (typeof searchable !== 'boolean' || typeof refinable !== 'boolean') {\n fail(`'searchable' and 'refinable' are required booleans.`);\n }\n\n if (resolvedType === 'markup' && sortable === true) {\n fail(`'sortable' must be false for markup attributes.`);\n }\n\n if (resolvedType === 'custom' || resolvedType === 'cms_record') {\n if (refinable === true) {\n fail(`'refinable' must be false for ${resolvedType} attributes.`);\n }\n if (boostFactor !== undefined) {\n fail(`'boostFactor' is not supported for ${resolvedType} attributes.`);\n }\n if (sortable === true) {\n fail(`'sortable' must be false for ${resolvedType} attributes.`);\n }\n }\n\n const mapped: Record<string, unknown> = { searchable, refinable };\n if (boostFactor !== undefined) {\n mapped.boost_factor = boostFactor;\n }\n if (sortable !== undefined) {\n mapped.sortable = sortable;\n }\n return mapped;\n}\n\n/**\n * Validate an authored `dynamicLookup` config and map it to the snake_case schema shape.\n * The metadefinition schema allows `dynamic_lookup` on all attribute types but requires a\n * single `aspect_attribute_alias` field (`additionalProperties: false`); a missing alias fails\n * cartridge generation (matching the strict behavior of `resolveAttributeType`).\n */\nfunction mapAttributeDynamicLookup(dynamicLookup: Record<string, unknown>, fieldName: string): Record<string, unknown> {\n const { aspectAttributeAlias } = dynamicLookup as { aspectAttributeAlias?: unknown };\n\n if (typeof aspectAttributeAlias !== 'string' || aspectAttributeAlias.length === 0) {\n logger.error(\n `Invalid 'dynamicLookup' config for attribute '${fieldName}': 'aspectAttributeAlias' is required and must be a non-empty string.`\n );\n process.exit(1);\n }\n\n return { aspect_attribute_alias: aspectAttributeAlias };\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// oxlint-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// oxlint-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// oxlint-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 if (config.editorDefinition !== undefined) {\n attribute.editor_definition = config.editorDefinition;\n }\n\n if (config.searching !== undefined) {\n attribute.searching = mapAttributeSearching(\n config.searching as Record<string, unknown>,\n attribute.type as string,\n String(attribute.id)\n );\n }\n\n if (config.dynamicLookup !== undefined) {\n attribute.dynamic_lookup = mapAttributeDynamicLookup(\n config.dynamicLookup as Record<string, unknown>,\n String(attribute.id)\n );\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.description !== undefined) {\n regionDefinition.description = regionConfig.description;\n }\n\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 (Array.isArray(regionConfig.defaultComponentConstructors)) {\n // Map the decorator's authoring shape ({ id, typeId, data }) onto the\n // Page Designer componentconstructor schema, which only permits\n // `type_id` (required), `name`, and nested `region_component_constructors`.\n // The decorator's `id`/`data` fields have no place in the metadefinition\n // format (`additionalProperties: false`), so they are intentionally dropped.\n regionDefinition.default_component_constructors =\n regionConfig.defaultComponentConstructors.map((constructor) => {\n const { typeId, name } = constructor as {\n typeId?: unknown;\n name?: unknown;\n };\n const mapped: Record<string, unknown> = {\n type_id: normalizeComponentTypeId(String(typeId), defaultComponentGroup),\n };\n if (name !== undefined) {\n mapped.name = name;\n }\n return mapped;\n });\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: Record<string, unknown> = {\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 if (typeof componentConfig.embedded === 'boolean') {\n componentMetadata.embedded = componentConfig.embedded;\n }\n if (componentConfig.component_id !== undefined) {\n componentMetadata.component_id = String(componentConfig.component_id);\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 // Only carry `preview` when the decorator declared it, so undeclared page types\n // stay clean and the emitted JSON omits the field entirely.\n ...(pageTypeConfig.preview !== undefined && { preview: pageTypeConfig.preview }),\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: Record<string, unknown> = {\n name: component.name,\n description: component.description,\n group: component.group,\n arch_type: ARCH_TYPE_HEADLESS,\n ...(typeof component.embedded === 'boolean' && { embedded: component.embedded }),\n component_id: component.component_id,\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 // Add preview only when the page type declared it (omitted otherwise to keep existing\n // page-type JSON byte-stable). Use the same presence check as processPageTypeFile so the\n // \"declared\" gate is identical on both sides.\n if (pageType.preview !== undefined) {\n cartridgeData.preview = pageType.preview;\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 format generated JSON files with Biome so they match the project's formatting.\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 * Formats the generated JSON files in place so they match the project's `biome format` output\n * (and therefore pass `pnpm lint` out of the box). Delegates to the shared Biome helper, which\n * prefers the consuming project's Biome and is fail-safe (logs and leaves files valid-but-\n * unformatted rather than throwing) when Biome can't run.\n */\nfunction lintGeneratedFiles(metadataDir: string): void {\n logger.debug('🔧 Formatting generated JSON files with Biome...');\n formatDirectoryWithProjectBiome(metadataDir);\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 const configMetadataDir = join(projectRoot, 'config-metadata');\n try {\n await access(configMetadataDir);\n await scanDirectory(configMetadataDir);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n logger.debug(` - Directory not found (skipping): ${configMetadataDir}`);\n } else {\n logger.warn(` - Unable to access ${configMetadataDir}:`, (error as Error).message);\n }\n }\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 // Format generated JSON files with Biome so they match the project's formatting.\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);\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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"}
@@ -2,6 +2,7 @@ import { t as logger } from "../logger.js";
2
2
  import "../logger2.js";
3
3
  import { t as generateEnvFile } from "../utils.js";
4
4
  import { a as trimExtensions, i as validateNoCycles, n as resolveDependenciesForMultiple } from "../dependency-utils.js";
5
+ import "../format-with-project-biome.js";
5
6
  import { t as prepareForLocalDev } from "../local-dev-setup.js";
6
7
  import { Command, Flags } from "@oclif/core";
7
8
  import { execFileSync, execSync } from "child_process";
@@ -26,6 +27,18 @@ const VERTICALS = {
26
27
  cosmetic: {
27
28
  label: "Salesforce B2C Commerce Beauty Storefront (Cosmetic)",
28
29
  url: "https://github.com/SalesforceCommerceCloud/storefront-next-beauty"
30
+ },
31
+ foundations: {
32
+ label: "Salesforce B2C Commerce Foundations Storefront (Foundations)",
33
+ url: "https://github.com/SalesforceCommerceCloud/storefront-next-foundations"
34
+ },
35
+ footwear: {
36
+ label: "Salesforce B2C Commerce Footwear Storefront (Footwear)",
37
+ url: "https://github.com/SalesforceCommerceCloud/storefront-next-footwear"
38
+ },
39
+ furniture: {
40
+ label: "Salesforce B2C Commerce Furniture Storefront (Furniture)",
41
+ url: "https://github.com/SalesforceCommerceCloud/storefront-next-furniture"
29
42
  }
30
43
  };
31
44
  const DEFAULT_VERTICAL = "fashion";
@@ -239,7 +252,13 @@ var CreateStorefront = class CreateStorefront extends Command {
239
252
  vertical: Flags.string({
240
253
  char: "V",
241
254
  description: "Vertical template to generate from. Selects the matching published template repository. Ignored if --template is provided.",
242
- options: ["fashion", "cosmetic"]
255
+ options: [
256
+ "fashion",
257
+ "cosmetic",
258
+ "foundations",
259
+ "footwear",
260
+ "furniture"
261
+ ]
243
262
  }),
244
263
  "template-branch": Flags.string({
245
264
  char: "b",
@@ -1,6 +1,7 @@
1
1
  import "../../logger.js";
2
2
  import "../../logger2.js";
3
3
  import "../../dependency-utils.js";
4
+ import "../../format-with-project-biome.js";
4
5
  import { t as createExtension } from "../../manage-extensions.js";
5
6
  import { Command, Flags } from "@oclif/core";
6
7
 
@@ -1,6 +1,7 @@
1
1
  import "../../logger.js";
2
2
  import "../../logger2.js";
3
3
  import "../../dependency-utils.js";
4
+ import "../../format-with-project-biome.js";
4
5
  import { r as commonFlags } from "../../flags.js";
5
6
  import { r as manageExtensions } from "../../manage-extensions.js";
6
7
  import { Command, Flags } from "@oclif/core";
@@ -1,6 +1,7 @@
1
1
  import "../../logger.js";
2
2
  import "../../logger2.js";
3
3
  import "../../dependency-utils.js";
4
+ import "../../format-with-project-biome.js";
4
5
  import { r as commonFlags } from "../../flags.js";
5
6
  import { n as listExtensions } from "../../manage-extensions.js";
6
7
  import { Command } from "@oclif/core";
@@ -1,6 +1,7 @@
1
1
  import "../../logger.js";
2
2
  import "../../logger2.js";
3
3
  import "../../dependency-utils.js";
4
+ import "../../format-with-project-biome.js";
4
5
  import { r as commonFlags } from "../../flags.js";
5
6
  import { r as manageExtensions } from "../../manage-extensions.js";
6
7
  import { Command, Flags } from "@oclif/core";
@@ -1,5 +1,6 @@
1
1
  import "../logger.js";
2
2
  import "../logger2.js";
3
+ import "../format-with-project-biome.js";
3
4
  import { i as SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR, t as CARTRIDGES_BASE_DIR } from "../config.js";
4
5
  import { r as commonFlags } from "../flags.js";
5
6
  import { t as generateMetadata } from "../generate-cartridge.js";
@@ -1,6 +1,7 @@
1
1
  import "../logger.js";
2
2
  import "../logger2.js";
3
3
  import { n as getDefaultBuildDir, r as getDefaultMessage } from "../utils.js";
4
+ import "../format-with-project-biome.js";
4
5
  import { a as buildMrtConfig, i as SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR, n as GENERATE_AND_DEPLOY_CARTRIDGE_ON_MRT_PUSH, r as SFNEXT_BASE_CARTRIDGE_NAME, t as CARTRIDGES_BASE_DIR } from "../config.js";
5
6
  import { t as createBundle } from "../bundle.js";
6
7
  import { t as generateMetadata } from "../generate-cartridge.js";
@@ -54,6 +54,12 @@ const BUILT_IN_APIS = [
54
54
  apiVersion: "v1",
55
55
  clientKey: "shopperCustomers"
56
56
  },
57
+ {
58
+ apiFamily: "product",
59
+ apiName: "shopper-delivery-estimates",
60
+ apiVersion: "v1",
61
+ clientKey: "shopperDeliveryEstimates"
62
+ },
57
63
  {
58
64
  apiFamily: "experience",
59
65
  apiName: "shopper-experience",
@@ -5,7 +5,11 @@ import { Preset } from "@react-router/dev/config";
5
5
  /**
6
6
  * Storefront Next preset for React Router configuration.
7
7
  * This preset enforces standard configuration for SFCC Storefront Next applications.
8
- * Users cannot override these values - they will be validated and an error will be thrown if modified.
8
+ * Most pinned values (`serverModuleFormat`, `ssr`, the `v8` future flags, `basename`, and — in
9
+ * workspace environments — `allowedActionOrigins`) cannot be overridden: they are validated and an
10
+ * error is thrown if modified. `routeDiscovery.mode` is the one exception — it defaults to
11
+ * `'initial'` but may be overridden (e.g. to `'lazy'`); overriding it emits a warning rather than
12
+ * throwing, so customers can opt into other modes at their own risk.
9
13
  *
10
14
  * Environment variables:
11
15
  * - `SFW_FALCON_INSTANCE` — (Optional) The Falcon instance identifier (e.g., `aws-dev2-uswest2`).
@@ -1 +1 @@
1
- {"version":3,"file":"react-router.config.d.ts","names":[],"sources":["../../src/configs/react-router.config.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;iBA8BgB,oBAAA,CAAA,GAAwB"}
1
+ {"version":3,"file":"react-router.config.d.ts","names":[],"sources":["../../src/configs/react-router.config.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;iBAkCgB,oBAAA,CAAA,GAAwB"}
@@ -36,7 +36,11 @@ function getBasePath() {
36
36
  /**
37
37
  * Storefront Next preset for React Router configuration.
38
38
  * This preset enforces standard configuration for SFCC Storefront Next applications.
39
- * Users cannot override these values - they will be validated and an error will be thrown if modified.
39
+ * Most pinned values (`serverModuleFormat`, `ssr`, the `v8` future flags, `basename`, and — in
40
+ * workspace environments — `allowedActionOrigins`) cannot be overridden: they are validated and an
41
+ * error is thrown if modified. `routeDiscovery.mode` is the one exception — it defaults to
42
+ * `'initial'` but may be overridden (e.g. to `'lazy'`); overriding it emits a warning rather than
43
+ * throwing, so customers can opt into other modes at their own risk.
40
44
  *
41
45
  * Environment variables:
42
46
  * - `SFW_FALCON_INSTANCE` — (Optional) The Falcon instance identifier (e.g., `aws-dev2-uswest2`).
@@ -69,7 +73,7 @@ function storefrontNextPreset() {
69
73
  reactRouterConfig: () => presetConfig,
70
74
  reactRouterConfigResolved: ({ reactRouterConfig }) => {
71
75
  const errors = [];
72
- if (reactRouterConfig.routeDiscovery?.mode !== presetConfig.routeDiscovery.mode) errors.push(`routeDiscovery.mode: expected "${presetConfig.routeDiscovery.mode}", got "${reactRouterConfig.routeDiscovery?.mode}"`);
76
+ if (reactRouterConfig.routeDiscovery?.mode !== presetConfig.routeDiscovery.mode) console.warn(`[storefront-next] routeDiscovery.mode is "${reactRouterConfig.routeDiscovery?.mode}", but Storefront Next pins it to "${presetConfig.routeDiscovery.mode}". Overriding it is unsupported and may cause unexpected behavior — test thoroughly before deploying.`);
73
77
  if (reactRouterConfig.serverModuleFormat !== presetConfig.serverModuleFormat) errors.push(`serverModuleFormat: expected "${presetConfig.serverModuleFormat}", got "${reactRouterConfig.serverModuleFormat}"`);
74
78
  if (reactRouterConfig.ssr !== presetConfig.ssr) errors.push(`ssr: expected ${presetConfig.ssr}, got ${reactRouterConfig.ssr}`);
75
79
  if (reactRouterConfig.future?.v8_middleware !== presetConfig.future.v8_middleware) errors.push(`future.v8_middleware: expected ${presetConfig.future.v8_middleware}, got ${reactRouterConfig.future?.v8_middleware}`);