@salesforce/storefront-next-dev 0.3.1 → 0.4.0-alpha.1

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.
Files changed (41) hide show
  1. package/README.md +12 -12
  2. package/bin/run.js +19 -9
  3. package/dist/cartridge-services/index.js +9 -1
  4. package/dist/cartridge-services/index.js.map +1 -1
  5. package/dist/commands/config/inspect.js +191 -0
  6. package/dist/commands/create-bundle.js +1 -1
  7. package/dist/commands/create-storefront.js +3 -3
  8. package/dist/commands/dev.js +1 -3
  9. package/dist/commands/extensions/install.js +1 -1
  10. package/dist/commands/extensions/list.js +1 -1
  11. package/dist/commands/extensions/remove.js +1 -1
  12. package/dist/commands/generate-cartridge.js +1 -1
  13. package/dist/commands/locales/aggregate-extensions.js +181 -0
  14. package/dist/commands/prepare-local.js +1 -1
  15. package/dist/commands/preview.js +1 -3
  16. package/dist/commands/scapi/add.js +298 -0
  17. package/dist/commands/scapi/list.js +35 -0
  18. package/dist/commands/scapi/remove.js +56 -0
  19. package/dist/commands/validate-cartridge.js +1 -1
  20. package/dist/configs/react-router.config.js +2 -1
  21. package/dist/configs/react-router.config.js.map +1 -1
  22. package/dist/data-store/local-provider.d.ts +42 -0
  23. package/dist/data-store/local-provider.d.ts.map +1 -0
  24. package/dist/data-store/local-provider.js +66 -0
  25. package/dist/data-store/local-provider.js.map +1 -0
  26. package/dist/flags.js +5 -3
  27. package/dist/generate-cartridge.js +9 -1
  28. package/dist/generate-custom-clients.js +73 -0
  29. package/dist/hooks/init.js +29 -4
  30. package/dist/index.d.ts +46 -2
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +260 -42
  33. package/dist/index.js.map +1 -1
  34. package/dist/logger.js +1 -1
  35. package/dist/mrt/ssr.mjs +3 -3
  36. package/dist/mrt/ssr.mjs.map +1 -1
  37. package/dist/mrt/streamingHandler.mjs +3 -3
  38. package/dist/mrt/streamingHandler.mjs.map +1 -1
  39. package/dist/schema-utils.js +64 -0
  40. package/dist/utils.js +3 -11
  41. package/package.json +19 -3
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import chalk from "chalk";
4
4
  import path$1, { dirname, join as join$1, relative, resolve as resolve$1 } from "path";
5
5
  import { fileURLToPath, pathToFileURL } from "url";
6
6
  import { parse } from "@babel/parser";
7
- import { isArrayPattern, isClassDeclaration, isExportSpecifier, isFunctionDeclaration, isIdentifier, isJSXAttribute, isJSXElement, isJSXFragment, isJSXIdentifier, isMemberExpression, isObjectPattern, isObjectProperty, isRestElement, isVariableDeclaration, jsxClosingElement, jsxClosingFragment, jsxElement, jsxFragment, jsxIdentifier, jsxOpeningElement, jsxOpeningFragment, jsxText } from "@babel/types";
7
+ import { booleanLiteral, identifier, importDeclaration, importSpecifier, isArrayPattern, isClassDeclaration, isExportSpecifier, isFunctionDeclaration, isIdentifier, isJSXAttribute, isJSXElement, isJSXFragment, isJSXIdentifier, isMemberExpression, isObjectPattern, isObjectProperty, isRestElement, isStringLiteral, isVariableDeclaration, jsxAttribute, jsxClosingElement, jsxClosingFragment, jsxElement, jsxExpressionContainer, jsxFragment, jsxIdentifier, jsxOpeningElement, jsxOpeningFragment, jsxText, stringLiteral } from "@babel/types";
8
8
  import { generate } from "@babel/generator";
9
9
  import traverseModule from "@babel/traverse";
10
10
  import fs$1, { existsSync, readFileSync, writeFileSync } from "fs";
@@ -122,7 +122,7 @@ function patchAssetsPaths(dir) {
122
122
  function fixReactRouterManifestUrlsPlugin() {
123
123
  let resolvedConfig;
124
124
  return {
125
- name: "odyssey:fix-react-router-manifest-urls",
125
+ name: "storefront-next:fix-react-router-manifest-urls",
126
126
  enforce: "post",
127
127
  configResolved(config) {
128
128
  resolvedConfig = config;
@@ -284,7 +284,7 @@ const readableChunkFileNames = (chunkInfo) => {
284
284
  */
285
285
  const readableChunkFileNamesPlugin = () => {
286
286
  return {
287
- name: "odyssey:readable-chunk-file-names",
287
+ name: "storefront-next:readable-chunk-file-names",
288
288
  apply: "build",
289
289
  config() {
290
290
  return { environments: { client: { build: { rollupOptions: { output: {
@@ -355,7 +355,7 @@ const managedRuntimeBundlePlugin = () => {
355
355
  await fs.writeJson(buildPackageJsonPath, packageJson, { spaces: 2 });
356
356
  };
357
357
  return {
358
- name: "odyssey:managed-runtime-bundle",
358
+ name: "storefront-next:managed-runtime-bundle",
359
359
  apply: "build",
360
360
  config({ mode }) {
361
361
  return {
@@ -396,7 +396,7 @@ const patchReactRouterPlugin = () => {
396
396
  let isTestMode = false;
397
397
  let isDevMode = false;
398
398
  return {
399
- name: "odyssey:patch-react-router",
399
+ name: "storefront-next:patch-react-router",
400
400
  enforce: "pre",
401
401
  config(_config, { mode }) {
402
402
  isTestMode = mode === "test";
@@ -428,10 +428,11 @@ const patchReactRouterPlugin = () => {
428
428
 
429
429
  //#endregion
430
430
  //#region src/extensibility/target-utils.ts
431
- const traverse$1 = traverseModule.default || traverseModule;
431
+ const traverse$2 = traverseModule.default || traverseModule;
432
432
  const TARGET_COMPONENT_TAG = "UITarget";
433
- const TARGET_PROVIDERS_TAG = "TargetProviders";
433
+ const TARGET_PROVIDERS_TAG = "UITargetProviders";
434
434
  const TARGET_ID_ATTRIBUTE = "targetId";
435
+ const TARGET_COMPONENT_JSX_RE = /* @__PURE__ */ new RegExp(`<${TARGET_COMPONENT_TAG}[\\s/>]`);
435
436
  /**
436
437
  * Find and replace the TargetProviders tags with the corresponding context providers
437
438
  * @param element - the AST element to replace
@@ -463,17 +464,26 @@ function findAndReplaceComponent(componentName, element, targetRegistry) {
463
464
  const targetId = attr && isJSXAttribute(attr) && attr.value && "value" in attr.value ? attr.value.value : void 0;
464
465
  if (targetId == null) throw new Error(`UITarget must contain a targetId attribute`);
465
466
  if (targetRegistry[targetId] && targetRegistry[targetId].length > 0) {
466
- const components = targetRegistry[targetId].map((targetComponent) => {
467
- return jsxElement(jsxOpeningElement(jsxIdentifier(targetComponent.componentName), [], true), null, [], true);
468
- });
469
- if (components.length > 1) element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), components));
470
- else element.replaceWith(components[0]);
467
+ if (element.node.children.length > 0) {
468
+ let nestedChildren = element.node.children;
469
+ for (let i = targetRegistry[targetId].length - 1; i >= 0; i--) {
470
+ const targetComponent = targetRegistry[targetId][i];
471
+ nestedChildren = [jsxElement(jsxOpeningElement(jsxIdentifier(targetComponent.componentName), [], false), jsxClosingElement(jsxIdentifier(targetComponent.componentName)), nestedChildren, false)];
472
+ }
473
+ element.replaceWith(nestedChildren[0]);
474
+ } else {
475
+ const components = targetRegistry[targetId].map((targetComponent) => {
476
+ return jsxElement(jsxOpeningElement(jsxIdentifier(targetComponent.componentName), [], true), null, [], true);
477
+ });
478
+ if (components.length > 1) element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), components));
479
+ else element.replaceWith(components[0]);
480
+ }
471
481
  targetIdReplaced = targetId;
472
482
  replaced = true;
473
483
  }
474
484
  }
475
- if (!replaced) if (element.node.children && element.node.children.length > 0) element.replaceWithMultiple(element.node.children);
476
- else element.remove();
485
+ if (!replaced) if (element.node.children && element.node.children.length > 0) element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), element.node.children));
486
+ else element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), []));
477
487
  }
478
488
  return targetIdReplaced;
479
489
  }
@@ -493,7 +503,7 @@ function runReplacementPass(ast, tagName, targetRegistry = null, contextProvider
493
503
  if (replacedId) targetIdsReplaced.add(replacedId);
494
504
  } else if (contextProviders) findAndReplaceProviders(pathToReplace, contextProviders);
495
505
  };
496
- traverse$1(ast, {
506
+ traverse$2(ast, {
497
507
  VariableDeclaration(nodePath) {
498
508
  const declarationPaths = nodePath.get("declarations");
499
509
  const declarationsArray = Array.isArray(declarationPaths) ? declarationPaths : [declarationPaths];
@@ -535,7 +545,7 @@ function buildReplacementImportStatements(targetIds, targetRegistry) {
535
545
  return Array.from(importStatements).join("\n");
536
546
  }
537
547
  function transformTargets(code, targetRegistry, contextProviders) {
538
- if (!code.includes(TARGET_COMPONENT_TAG) && !code.includes(TARGET_PROVIDERS_TAG)) return null;
548
+ if (!TARGET_COMPONENT_JSX_RE.test(code) && !code.includes(TARGET_PROVIDERS_TAG)) return null;
539
549
  const ast = parse(code, {
540
550
  sourceType: "module",
541
551
  plugins: [
@@ -544,18 +554,18 @@ function transformTargets(code, targetRegistry, contextProviders) {
544
554
  "decorators-legacy"
545
555
  ]
546
556
  });
547
- if (code.includes(TARGET_COMPONENT_TAG)) {
557
+ if (TARGET_COMPONENT_JSX_RE.test(code)) {
548
558
  const replacementImportStatements = buildReplacementImportStatements(runReplacementPass(ast, TARGET_COMPONENT_TAG, targetRegistry, null), targetRegistry);
549
- traverse$1(ast, { ImportDeclaration(nodePath) {
550
- if (nodePath.node.source.value.includes("@/targets/ui-target")) nodePath.replaceWith(jsxText(replacementImportStatements));
559
+ traverse$2(ast, { ImportDeclaration(nodePath) {
560
+ if (nodePath.node.source.value === "@/targets/ui-target") nodePath.replaceWith(jsxText(replacementImportStatements));
551
561
  } });
552
562
  }
553
563
  if (code.includes(TARGET_PROVIDERS_TAG)) {
554
564
  const importStatements = /* @__PURE__ */ new Set();
555
565
  for (const contextProvider of contextProviders) importStatements.add(`import ${contextProvider.componentName} from '@/${contextProvider.path.replace(".tsx", "")}';`);
556
566
  const replacementImportStatements = Array.from(importStatements).join("\n");
557
- traverse$1(ast, { ImportDeclaration(nodePath) {
558
- if (nodePath.node.source.value.includes("@/targets/target-providers")) nodePath.replaceWith(jsxText(replacementImportStatements));
567
+ traverse$2(ast, { ImportDeclaration(nodePath) {
568
+ if (nodePath.node.source.value === "@/targets/ui-target-providers") nodePath.replaceWith(jsxText(replacementImportStatements));
559
569
  } });
560
570
  runReplacementPass(ast, TARGET_PROVIDERS_TAG, null, contextProviders);
561
571
  }
@@ -567,7 +577,7 @@ function transformTargets(code, targetRegistry, contextProviders) {
567
577
  * @param sourceDir - the source directory of the project
568
578
  * @returns the target registry
569
579
  */
570
- function buildTargetRegistry(rootDir) {
580
+ function buildTargetRegistry(rootDir, options = {}) {
571
581
  const componentRegistry = {};
572
582
  const contextProviders = [];
573
583
  const extensionDirPath = path$1.join(rootDir, "extensions");
@@ -584,6 +594,7 @@ function buildTargetRegistry(rootDir) {
584
594
  const configPath = path$1.join(extensionDirPath, dir.name, TARGET_CONFIG_FILENAME);
585
595
  if (fs.existsSync(configPath)) {
586
596
  const extensionConfig = fs.readJsonSync(configPath);
597
+ if (options.isProduction && extensionConfig.devOnly === true) continue;
587
598
  if (extensionConfig && extensionConfig.components) for (const component of extensionConfig.components) {
588
599
  const { targetId, path: componentPath, order = 0 } = component;
589
600
  if (targetId && componentPath) {
@@ -626,16 +637,19 @@ function transformTargetPlaceholderPlugin() {
626
637
  let componentRegistry;
627
638
  let contextProviders;
628
639
  let sourceDir;
640
+ let isProduction = false;
629
641
  return {
630
- name: "odyssey:transform-target-placeholder",
642
+ name: "storefront-next:transform-target-placeholder",
631
643
  enforce: "pre",
632
644
  configResolved(config) {
633
645
  sourceDir = config.resolve.alias.find((alias) => alias.find === "@")?.replacement || path$1.resolve(__dirname, "./src");
646
+ isProduction = config.mode === "production";
634
647
  },
635
648
  buildStart() {
636
- ({componentRegistry, contextProviders} = buildTargetRegistry(sourceDir));
649
+ ({componentRegistry, contextProviders} = buildTargetRegistry(sourceDir, { isProduction }));
637
650
  },
638
651
  transform(code, id) {
652
+ if (process.env.VITE_UI_TARGET_DEV_MODE === "true") return null;
639
653
  try {
640
654
  const transformedCode = transformTargets(code, componentRegistry, contextProviders);
641
655
  if (transformedCode) return {
@@ -656,7 +670,7 @@ function transformTargetPlaceholderPlugin() {
656
670
  const watchConfigFilesPlugin = () => {
657
671
  let viteConfig;
658
672
  return {
659
- name: "odyssey:watch-config-files",
673
+ name: "storefront-next:watch-config-files",
660
674
  configResolved(config) {
661
675
  viteConfig = config;
662
676
  },
@@ -680,7 +694,7 @@ const watchConfigFilesPlugin = () => {
680
694
 
681
695
  //#endregion
682
696
  //#region src/plugins/staticRegistry.ts
683
- const DEFAULT_COMPONENT_GROUP$1 = "odyssey_base";
697
+ const DEFAULT_COMPONENT_GROUP$1 = "storefrontnext_base";
684
698
  /**
685
699
  * Extracts component ID and group from @Component decorator using ts-morph AST parsing
686
700
  */
@@ -861,9 +875,14 @@ export const registry = new ComponentRegistry();
861
875
  const endIndex = existingContent.indexOf(endMarker);
862
876
  if (startIndex === -1 || endIndex === -1) throw new Error(`Registry file ${registryFilePath} is missing static registry markers. Please add "${startMarker}" and "${endMarker}" markers to define the generated content area.`);
863
877
  const updatedContent = `${existingContent.slice(0, startIndex + 24)}\n${generatedCode}\n${existingContent.slice(endIndex)}`;
878
+ if (updatedContent === existingContent) {
879
+ logger.debug(`⏭️ Registry unchanged, skipping write: ${registryFilePath}`);
880
+ return false;
881
+ }
864
882
  try {
865
883
  writeFileSync(registryFilePath, updatedContent, "utf-8");
866
884
  logger.debug(`💾 Updated registry file: ${registryFilePath}`);
885
+ return true;
867
886
  } catch (error) {
868
887
  throw new Error(`Failed to write registry file: ${error.message}`);
869
888
  }
@@ -907,9 +926,12 @@ const staticRegistryPlugin = (config = {}) => {
907
926
  logger.debug(`📦 Found ${components.length} components with @Component decorators`);
908
927
  const generatedCode = generateRegistryCode(components, registryIdentifier);
909
928
  const registryFilePath = resolve$1(projectRoot, registryPath);
910
- updateRegistryFile(registryFilePath, generatedCode);
929
+ const changed = updateRegistryFile(registryFilePath, generatedCode);
911
930
  logger.debug("✅ Static registry generation complete!");
912
- return registryFilePath;
931
+ return {
932
+ registryFilePath,
933
+ changed
934
+ };
913
935
  };
914
936
  return {
915
937
  name: "storefrontnext:static-registry",
@@ -931,10 +953,12 @@ const staticRegistryPlugin = (config = {}) => {
931
953
  if (normalizedFile.includes(`/${normalizedComponentPath}/`) && (normalizedFile.endsWith(".ts") || normalizedFile.endsWith(".tsx"))) {
932
954
  logger.debug(`🔄 Component file changed: ${file}, regenerating registry...`);
933
955
  try {
934
- const registryFilePath = await runRegistryGeneration();
935
- const registryModule = server.moduleGraph.getModuleById(registryFilePath);
936
- if (registryModule) await server.reloadModule(registryModule);
937
- logger.debug("✅ Registry regenerated successfully!");
956
+ const { registryFilePath, changed } = await runRegistryGeneration();
957
+ if (changed) {
958
+ const registryModule = server.moduleGraph.getModuleById(registryFilePath);
959
+ if (registryModule) await server.reloadModule(registryModule);
960
+ logger.debug("✅ Registry regenerated successfully!");
961
+ } else logger.debug("⏭️ Registry unchanged, skipping reload");
938
962
  } catch (error) {
939
963
  logger.error(`❌ Failed to regenerate registry: ${error.message}`);
940
964
  }
@@ -1124,7 +1148,7 @@ const buildMiddlewareRegistryPlugin = () => {
1124
1148
  /** App source directory (e.g. 'src' or './src') from React Router config. */
1125
1149
  let appDirectory;
1126
1150
  return {
1127
- name: "odyssey:build-middleware-registry",
1151
+ name: "storefront-next:build-middleware-registry",
1128
1152
  apply: "build",
1129
1153
  configResolved(config) {
1130
1154
  resolvedConfig = config;
@@ -1263,7 +1287,7 @@ function platformEntryPlugin() {
1263
1287
  let userServerEntryPath;
1264
1288
  let userClientEntryPath;
1265
1289
  return {
1266
- name: "odyssey:platform-entry",
1290
+ name: "storefront-next:platform-entry",
1267
1291
  enforce: "pre",
1268
1292
  config(_config, { mode }) {
1269
1293
  isTestMode = mode === "test";
@@ -1364,7 +1388,7 @@ const workspacePlugin = () => {
1364
1388
 
1365
1389
  //#endregion
1366
1390
  //#region src/plugins/componentLoaders.ts
1367
- const traverse = traverseModule.default || traverseModule;
1391
+ const traverse$1 = traverseModule.default || traverseModule;
1368
1392
  const generate$1 = generate.default || generate;
1369
1393
  /**
1370
1394
  * Names of exports to strip per environment.
@@ -1394,7 +1418,7 @@ function hasExportCandidate(code, names) {
1394
1418
  */
1395
1419
  function hasComponentDecorator(ast) {
1396
1420
  let found = false;
1397
- traverse(ast, { ClassDeclaration(path$2) {
1421
+ traverse$1(ast, { ClassDeclaration(path$2) {
1398
1422
  const decorators = path$2.node.decorators;
1399
1423
  if (!decorators) return;
1400
1424
  for (const decorator of decorators) if (decorator.expression.type === "CallExpression" && isIdentifier(decorator.expression.callee) && decorator.expression.callee.name === "Component") {
@@ -1441,7 +1465,7 @@ function stripExports(code, exportsToStrip, preParsedAst) {
1441
1465
  let changed = false;
1442
1466
  const previouslyReferencedIdentifiers = findReferencedIdentifiers(ast);
1443
1467
  const removedExportLocalNames = /* @__PURE__ */ new Set();
1444
- traverse(ast, { ExportNamedDeclaration(path$2) {
1468
+ traverse$1(ast, { ExportNamedDeclaration(path$2) {
1445
1469
  const { declaration, specifiers } = path$2.node;
1446
1470
  if (declaration && isVariableDeclaration(declaration)) {
1447
1471
  const remaining = declaration.declarations.filter((decl) => {
@@ -1499,7 +1523,7 @@ function stripExports(code, exportsToStrip, preParsedAst) {
1499
1523
  }
1500
1524
  }
1501
1525
  } });
1502
- if (changed) traverse(ast, { ExpressionStatement(path$2) {
1526
+ if (changed) traverse$1(ast, { ExpressionStatement(path$2) {
1503
1527
  if (!path$2.parentPath?.isProgram()) return;
1504
1528
  if (path$2.node.expression.type === "AssignmentExpression") {
1505
1529
  const left = path$2.node.expression.left;
@@ -1598,10 +1622,103 @@ function componentLoadersPlugin(config = {}) {
1598
1622
  };
1599
1623
  }
1600
1624
 
1625
+ //#endregion
1626
+ //#region src/plugins/ssrSourcemapFix.ts
1627
+ const INLINE_SOURCEMAP_PREFIX = "//# sourceMappingURL=data:application/json;base64,";
1628
+ /**
1629
+ * Vite plugin that fixes SSR sourcemap `sources` to use full module paths
1630
+ * instead of bare basenames.
1631
+ *
1632
+ * **Problem:** When the React Router `v8_viteEnvironmentApi` future flag is
1633
+ * enabled, SSR modules are evaluated by Vite's Environment API module runner.
1634
+ * Vite's `ssrTransform` generates inline sourcemaps with
1635
+ * `sources: [path.basename(url)]` (e.g., `"search.tsx"` or `"index.tsx"`).
1636
+ * V8's debugger cannot resolve bare basenames back to files on disk, so
1637
+ * Chrome DevTools (via `--inspect`) displays the wrong source file content
1638
+ * when pausing at breakpoints — even for files with unique names.
1639
+ *
1640
+ * **Why `fetchModule`:** A Vite transform plugin cannot fix this because
1641
+ * `ssrTransform` runs *after* the plugin transform pipeline and overwrites
1642
+ * `map.sources` with `[path.basename(url)]`. The only viable interception
1643
+ * point is `fetchModule` — the public API method on `DevEnvironment` that
1644
+ * returns the final transformed code (with inline sourcemaps already embedded)
1645
+ * to the module runner.
1646
+ *
1647
+ * **Removable:** If Vite updates `ssrTransform` to use full paths instead of
1648
+ * `path.basename()`, this plugin can be deleted with no other changes.
1649
+ *
1650
+ * Only active in development mode (`configureServer` does not run in build).
1651
+ */
1652
+ function ssrSourcemapFixPlugin() {
1653
+ return {
1654
+ name: "storefront-next:ssr-sourcemap-fix",
1655
+ configureServer(server) {
1656
+ const ssrEnv = server.environments.ssr;
1657
+ if (!ssrEnv) return;
1658
+ const originalFetchModule = ssrEnv.fetchModule.bind(ssrEnv);
1659
+ ssrEnv.fetchModule = async (id, importer, options) => {
1660
+ const result = await originalFetchModule(id, importer, options);
1661
+ if (!result || "externalize" in result || "cache" in result || !("code" in result)) return result;
1662
+ const smIndex = result.code.lastIndexOf(INLINE_SOURCEMAP_PREFIX);
1663
+ if (smIndex === -1) return result;
1664
+ try {
1665
+ const base64Start = smIndex + 50;
1666
+ const base64End = result.code.indexOf("\n", base64Start);
1667
+ const base64Data = base64End === -1 ? result.code.slice(base64Start).trim() : result.code.slice(base64Start, base64End).trim();
1668
+ const mapJson = JSON.parse(Buffer.from(base64Data, "base64").toString("utf-8"));
1669
+ if (!mapJson.sources || !Array.isArray(mapJson.sources)) return result;
1670
+ const fileId = result.file || result.id;
1671
+ if (!fileId) return result;
1672
+ if (mapJson.sources.length !== 1) return result;
1673
+ const source = mapJson.sources[0];
1674
+ if (!source || source.includes("/")) return result;
1675
+ mapJson.sources = [fileId];
1676
+ const patchedBase64 = Buffer.from(JSON.stringify(mapJson)).toString("base64");
1677
+ result.code = result.code.slice(0, base64Start) + patchedBase64 + (base64End === -1 ? "" : result.code.slice(base64End));
1678
+ } catch {}
1679
+ return result;
1680
+ };
1681
+ }
1682
+ };
1683
+ }
1684
+
1685
+ //#endregion
1686
+ //#region src/plugins/i18n.ts
1687
+ /**
1688
+ * Vite plugin that splits locale translation files into per-language chunks.
1689
+ *
1690
+ * Wraps any existing `manualChunks` configuration so that locale modules are
1691
+ * assigned to `locales-{lang}` chunks (e.g., `locales-en-GB`) while all other
1692
+ * module IDs are delegated to the user's original `manualChunks` function or object.
1693
+ *
1694
+ * @param config - Optional configuration for custom locale patterns
1695
+ * @returns A Vite plugin that configures locale-based code splitting
1696
+ */
1697
+ function i18nPlugin(config) {
1698
+ const pattern = config?.localePattern ?? /\/src\/locales\/([^/]+)\//;
1699
+ return {
1700
+ name: "storefront-next:i18n",
1701
+ apply: "build",
1702
+ config(viteConfig) {
1703
+ const output = viteConfig.build?.rollupOptions?.output;
1704
+ if (Array.isArray(output)) return;
1705
+ const existingManualChunks = output?.manualChunks;
1706
+ return { build: { rollupOptions: { output: { manualChunks(id, meta) {
1707
+ const localeMatch = id.match(pattern);
1708
+ if (localeMatch) return `locales-${localeMatch[1]}`;
1709
+ if (typeof existingManualChunks === "function") return existingManualChunks.call(this, id, meta);
1710
+ if (existingManualChunks && typeof existingManualChunks === "object") {
1711
+ for (const [name, ids] of Object.entries(existingManualChunks)) if (ids.includes(id)) return name;
1712
+ }
1713
+ } } } } };
1714
+ }
1715
+ };
1716
+ }
1717
+
1601
1718
  //#endregion
1602
1719
  //#region src/storefront-next-targets.ts
1603
1720
  /**
1604
- * Storefront Next Vite plugin that powers the React Router RSC app.
1721
+ * Storefront Next Vite plugin that powers the React Router app.
1605
1722
  * Supports building and optimizing for the managed runtime environment.
1606
1723
  *
1607
1724
  * @param config - Configuration options for the plugin
@@ -1630,13 +1747,15 @@ function storefrontNextTargets(config = {}) {
1630
1747
  } } = config;
1631
1748
  const plugins = [
1632
1749
  ...process.env.SCAPI_PROXY_HOST ? [workspacePlugin()] : [],
1750
+ i18nPlugin(),
1633
1751
  managedRuntimeBundlePlugin(),
1634
1752
  fixReactRouterManifestUrlsPlugin(),
1635
1753
  patchReactRouterPlugin(),
1636
1754
  platformEntryPlugin(),
1637
1755
  transformTargetPlaceholderPlugin(),
1638
1756
  watchConfigFilesPlugin(),
1639
- buildMiddlewareRegistryPlugin()
1757
+ buildMiddlewareRegistryPlugin(),
1758
+ ssrSourcemapFixPlugin()
1640
1759
  ];
1641
1760
  if (staticRegistry?.componentPath && staticRegistry?.registryPath) {
1642
1761
  plugins.push(staticRegistryPlugin(staticRegistry));
@@ -1647,6 +1766,97 @@ function storefrontNextTargets(config = {}) {
1647
1766
  return plugins;
1648
1767
  }
1649
1768
 
1769
+ //#endregion
1770
+ //#region src/plugins/uiTargetDevMode.ts
1771
+ const traverse = traverseModule.default || traverseModule;
1772
+ const UI_TARGET_JSX_RE = /<UITarget[\s/>]/;
1773
+ /**
1774
+ * Vite plugin that adds visual markers to UITarget components in development.
1775
+ *
1776
+ * PRODUCTION: This plugin is completely inactive - zero overhead.
1777
+ * DEVELOPMENT: Transforms UITarget JSX to add visual debugging markers.
1778
+ *
1779
+ * @example
1780
+ * // Source code:
1781
+ * <UITarget targetId="pdp.loyalty.badge">
1782
+ * <Widget />
1783
+ * </UITarget>
1784
+ *
1785
+ * // Transformed in DEV mode:
1786
+ * <UITargetDevMarker
1787
+ * targetId="pdp.loyalty.badge"
1788
+ * __file__="/src/components/product.tsx"
1789
+ * __hasChildren__={true}
1790
+ * >
1791
+ * <Widget />
1792
+ * </UITargetDevMarker>
1793
+ */
1794
+ function uiTargetDevModePlugin(config = {}) {
1795
+ if (process.env.NODE_ENV === "production") return { name: "storefront-next:ui-target-dev-mode-noop" };
1796
+ if (!(config.enabled ?? process.env.VITE_UI_TARGET_DEV_MODE === "true")) return { name: "storefront-next:ui-target-dev-mode-disabled" };
1797
+ logger.info("🎯 UITarget Dev Mode enabled");
1798
+ if (config.filterCategory) logger.info(` Filtering to category: ${config.filterCategory} (build-time)`);
1799
+ return {
1800
+ name: "storefront-next:ui-target-dev-mode",
1801
+ enforce: "pre",
1802
+ transform(code, id) {
1803
+ if (!id.match(/\.(tsx|jsx)$/)) return null;
1804
+ if (id.includes("node_modules")) return null;
1805
+ if (id.includes("/targets/ui-target.tsx")) return null;
1806
+ if (!UI_TARGET_JSX_RE.test(code) || !code.includes("from '@/targets/ui-target'")) return null;
1807
+ try {
1808
+ const ast = parse(code, {
1809
+ sourceType: "module",
1810
+ plugins: [
1811
+ "typescript",
1812
+ "jsx",
1813
+ "decorators-legacy"
1814
+ ]
1815
+ });
1816
+ let hasTransforms = false;
1817
+ const targetIds = [];
1818
+ traverse(ast, { JSXElement(path$2) {
1819
+ const openingElement = path$2.node.openingElement;
1820
+ if (!isJSXIdentifier(openingElement.name) || openingElement.name.name !== "UITarget") return;
1821
+ const targetIdAttr = openingElement.attributes.find((attr) => isJSXAttribute(attr) && isJSXIdentifier(attr.name) && attr.name.name === "targetId");
1822
+ if (!targetIdAttr) {
1823
+ logger.warn(`UITarget without targetId in ${id}`);
1824
+ return;
1825
+ }
1826
+ const targetId = isStringLiteral(targetIdAttr.value) ? targetIdAttr.value.value : null;
1827
+ if (!targetId) {
1828
+ logger.warn(`UITarget with non-string targetId in ${id}`);
1829
+ return;
1830
+ }
1831
+ if (config.filterCategory && !targetId.startsWith(`${config.filterCategory}.`)) return;
1832
+ const hasChildren = path$2.node.children.length > 0;
1833
+ openingElement.name = jsxIdentifier("UITargetDevMarker");
1834
+ if (path$2.node.closingElement) path$2.node.closingElement.name = jsxIdentifier("UITargetDevMarker");
1835
+ const hint = config.hintMap?.[targetId];
1836
+ openingElement.attributes.push(jsxAttribute(jsxIdentifier("__file__"), stringLiteral(id)), jsxAttribute(jsxIdentifier("__hasChildren__"), jsxExpressionContainer(booleanLiteral(hasChildren))), ...hint ? [jsxAttribute(jsxIdentifier("__hint__"), stringLiteral(hint))] : []);
1837
+ hasTransforms = true;
1838
+ targetIds.push(targetId);
1839
+ } });
1840
+ if (!hasTransforms) return null;
1841
+ const devMarkerImport = importDeclaration([importSpecifier(identifier("UITargetDevMarker"), identifier("UITargetDevMarker"))], stringLiteral("@/lib/ui-target-dev-mode/marker"));
1842
+ ast.program.body.unshift(devMarkerImport);
1843
+ const output = generate(ast, {
1844
+ retainLines: true,
1845
+ compact: false
1846
+ }, code);
1847
+ logger.debug(`Transformed ${targetIds.length} targets in ${id.split("/").pop()}`);
1848
+ return {
1849
+ code: output.code,
1850
+ map: output.map
1851
+ };
1852
+ } catch (error) {
1853
+ logger.error(`Failed to transform UITarget in ${id}:`, error);
1854
+ return null;
1855
+ }
1856
+ }
1857
+ };
1858
+ }
1859
+
1650
1860
  //#endregion
1651
1861
  //#region src/plugins/hybridProxy.ts
1652
1862
  /**
@@ -2918,7 +3128,7 @@ const SKIP_DIRECTORIES = [
2918
3128
  ".next",
2919
3129
  "coverage"
2920
3130
  ];
2921
- const DEFAULT_COMPONENT_GROUP = "odyssey_base";
3131
+ const DEFAULT_COMPONENT_GROUP = "storefrontnext_base";
2922
3132
  const ARCH_TYPE_HEADLESS = "headless";
2923
3133
  const VALID_ATTRIBUTE_TYPES = [
2924
3134
  "string",
@@ -3415,6 +3625,14 @@ async function generateMetadata(projectDirectory, metadataDirectory, options) {
3415
3625
  }
3416
3626
  };
3417
3627
  await scanDirectory(srcDir);
3628
+ const configMetadataDir = join(projectRoot, "config-metadata");
3629
+ try {
3630
+ await access(configMetadataDir);
3631
+ await scanDirectory(configMetadataDir);
3632
+ } catch (error) {
3633
+ if (error.code === "ENOENT") logger.debug(` - Directory not found (skipping): ${configMetadataDir}`);
3634
+ else logger.warn(` - Unable to access ${configMetadataDir}:`, error.message);
3635
+ }
3418
3636
  }
3419
3637
  const allComponents = [];
3420
3638
  const allPageTypes = [];
@@ -3471,5 +3689,5 @@ async function generateMetadata(projectDirectory, metadataDirectory, options) {
3471
3689
  }
3472
3690
 
3473
3691
  //#endregion
3474
- export { clearCache, createServer, storefrontNextTargets as default, extractPatterns, generateMetadata, hybridProxyPlugin, loadConfigFromEnv, loadProjectConfig, shouldRouteToNext, testPatterns, transformTargetPlaceholderPlugin, trimExtensions };
3692
+ export { clearCache, createServer, storefrontNextTargets as default, extractPatterns, generateMetadata, hybridProxyPlugin, loadConfigFromEnv, loadProjectConfig, shouldRouteToNext, testPatterns, transformTargetPlaceholderPlugin, trimExtensions, uiTargetDevModePlugin };
3475
3693
  //# sourceMappingURL=index.js.map