@salesforce/storefront-next-runtime 0.2.0-alpha.0 → 0.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/routing.js CHANGED
@@ -3,7 +3,6 @@ import { flatRoutes as flatRoutes$1 } from "@react-router/fs-routes";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import fs$1 from "node:fs";
6
- import { pathToFileURL } from "node:url";
7
6
 
8
7
  //#region src/routing/merge-routes.ts
9
8
  /**
@@ -81,8 +80,12 @@ function mergeRoutes(routes, extensionRoutes, extensionIdPrefix) {
81
80
  //#region src/config/load-config.ts
82
81
  /**
83
82
  * Dynamically imports `config.server.ts` from the project root (CWD) and returns
84
- * the `app` configuration object. This runs at route discovery time under vite-node
85
- * (typegen, dev, build), which handles the TS transformation.
83
+ * the `app` configuration object.
84
+ *
85
+ * Uses jiti to transpile TypeScript on the fly, which works regardless of whether
86
+ * the caller runs under vite-node, a plain Node process, or any other runtime.
87
+ * This avoids the fragile assumption that vite-node will intercept dynamic imports
88
+ * from pre-compiled npm packages (it won't — Vite externalizes node_modules).
86
89
  *
87
90
  * - If the config file is missing, warns and returns an empty config.
88
91
  * - If the config file exists but fails to import, throws with the original error as cause.
@@ -96,10 +99,11 @@ async function loadConfig() {
96
99
  return {};
97
100
  }
98
101
  try {
99
- const mod = await import(
100
- /* @vite-ignore */
101
- pathToFileURL(configPath).href
102
- );
102
+ const { createJiti } = await import("jiti");
103
+ const mod = await createJiti(import.meta.url, {
104
+ fsCache: false,
105
+ interopDefault: true
106
+ }).import(configPath);
103
107
  return (mod.default ?? mod)?.app ?? {};
104
108
  } catch (error) {
105
109
  throw new Error(`[storefront-next-runtime] Found config.server.ts at ${configPath} but failed to import it.`, { cause: error });
@@ -1 +1 @@
1
- {"version":3,"file":"routing.js","names":["path","fs","_flatRoutes"],"sources":["../src/routing/merge-routes.ts","../src/config/load-config.ts","../src/routing/flat-routes.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { type RouteConfigEntry } from '@react-router/dev/routes';\n\n/**\n * Find the nearest route by its ID in the route tree\n * @param routes - The route subtree to search\n * @param layoutId - The route ID to find (e.g., \"routes/_app\" or \"routes/_app.account\")\n * @param rootPath - The full route path from the root to the current route (default: '')\n * @returns An object with routes array, routeIndex, and path, or null if not found. Returns exact match if found, otherwise returns route where route.id is a prefix of layoutId\n */\nfunction findNearestRoute(\n routes: RouteConfigEntry[],\n layoutId: string,\n rootPath: string = ''\n): { routes: RouteConfigEntry[]; routeIndex: number; path: string } | null {\n for (let i = 0; i < routes.length; i++) {\n const route = routes[i];\n const path = route.path ? `${rootPath}/${route.path}` : rootPath;\n if (route.id === layoutId) {\n return { routes, routeIndex: i, path };\n }\n if (route.children) {\n const found = findNearestRoute(route.children, layoutId, path);\n if (found) {\n return found;\n }\n }\n // Check if route.id is a prefix of layoutId, indicating a nested route\n if (route.id && layoutId.startsWith(route.id)) {\n return { routes, routeIndex: i, path };\n }\n }\n return null;\n}\n\n/**\n * Merges extension routes into the main routes array, handling route nesting.\n * Routes without IDs are added directly to the routes array. Routes with IDs are processed\n * to remove the extension prefix and are either:\n * - Added as children of existing routes (if a nearest route is found via prefix matching)\n * - Replace existing routes (if an exact match is found)\n * - Added directly to the routes array (if no matching route exists)\n *\n * When adding as a child, the parent route's path is clipped from the child route's path.\n *\n * @param routes - The main routes array to merge into (mutated in place)\n * @param extensionRoutes - The extension routes to merge\n * @param extensionIdPrefix - The prefix to remove from extension route IDs (e.g., \"extensions/store-locator/\")\n */\nexport function mergeRoutes(\n routes: RouteConfigEntry[],\n extensionRoutes: RouteConfigEntry[],\n extensionIdPrefix: string\n): void {\n for (const route of extensionRoutes) {\n if (!route.id) {\n routes.unshift(route);\n continue;\n }\n const routeId = route.id.replace(extensionIdPrefix, '');\n const nearestRouteResult = findNearestRoute(routes, routeId);\n if (nearestRouteResult) {\n const nearestRoute = nearestRouteResult.routes[nearestRouteResult.routeIndex];\n if (nearestRoute.id === routeId) {\n // Replacing an existing route, we assume we can just swap out the implementation\n nearestRouteResult.routes[nearestRouteResult.routeIndex].file = route.file;\n } else {\n // This is a new child of an existing route, insert it at the beginning of the children array\n // and clip out the parent path from the route path\n let path = route.path?.slice(nearestRouteResult.path.length);\n if (path?.startsWith('/')) {\n path = path.slice(1);\n }\n path = path ? path : undefined;\n if (!nearestRoute.children) {\n nearestRoute.children = [];\n }\n nearestRoute.children.unshift({\n ...route,\n id: routeId,\n path,\n });\n }\n } else {\n // This is a new route, insert it at the beginning of the routes array\n routes.unshift({\n ...route,\n id: routeId,\n });\n }\n }\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\n/**\n * Dynamically imports `config.server.ts` from the project root (CWD) and returns\n * the `app` configuration object. This runs at route discovery time under vite-node\n * (typegen, dev, build), which handles the TS transformation.\n *\n * - If the config file is missing, warns and returns an empty config.\n * - If the config file exists but fails to import, throws with the original error as cause.\n *\n * @returns The `app` configuration object, or an empty object if not available.\n */\n// TODO: add a proper type when config schema is moved to runtime from the template\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function loadConfig(): Promise<Record<string, any>> {\n const configPath = path.resolve(process.cwd(), 'config.server.ts');\n\n if (!fs.existsSync(configPath)) {\n console.warn(\n `[storefront-next-runtime] config.server.ts not found at ${configPath}. ` + `Returning empty config.`\n );\n return {};\n }\n\n try {\n // The @vite-ignore comment suppresses Vite's \"dynamic import cannot be analyzed\" warning.\n // It must be placed on the variable value (not inside import()) so that rolldown's\n // single-use variable inlining carries it into the import() expression in the built output.\n // See: https://github.com/nicolo-ribaudo/rolldown/issues/6263\n const importPath = /* @vite-ignore */ pathToFileURL(configPath).href;\n\n const mod = await import(importPath);\n const config = mod.default ?? mod;\n return config?.app ?? {};\n } catch (error) {\n throw new Error(`[storefront-next-runtime] Found config.server.ts at ${configPath} but failed to import it.`, {\n cause: error,\n });\n }\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { flatRoutes as _flatRoutes } from '@react-router/fs-routes';\nimport type { RouteConfigEntry } from '@react-router/dev/routes';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { mergeRoutes } from './merge-routes';\nimport { applyUrlConfig } from '../multi-site/apply-url-config';\nimport { loadConfig } from '../config/load-config';\n\nconst APP_SRC_DIR = 'src';\nconst EXTENSIONS_DIR = 'extensions';\n// This file must live at the root of `appDirectory` (src/app-wrapper.tsx) and must NOT\n// be moved into a subdirectory. React Router's typegen resolves route module types using\n// paths relative to `appDirectory` — placing it elsewhere breaks generated type references.\nconst APP_WRAPPER_FILE = 'app-wrapper.tsx';\n\n/**\n * Scans `src/extensions/` for extension route directories and merges any discovered\n * routes into the base route tree. Mutates `routes` in place via `mergeRoutes`.\n */\nasync function discoverExtensionRoutes(ignoredRouteFiles: string[], routes: RouteConfigEntry[]): Promise<void> {\n const extensionsDir = path.join('.', APP_SRC_DIR, EXTENSIONS_DIR);\n\n // Sort to ensure deterministic route order across platforms (readdir order is filesystem-dependent)\n const extensions = await fs.readdir(extensionsDir).then(\n (entries) => entries.sort(),\n () => []\n );\n for (const ext of extensions) {\n // React Router rootDirectory uses forward slashes regardless of OS\n const routesDir = `${EXTENSIONS_DIR}/${ext}/routes`;\n const routesDirFull = path.join('.', APP_SRC_DIR, EXTENSIONS_DIR, ext, 'routes');\n try {\n await fs.access(routesDirFull);\n const extensionRoutes = await _flatRoutes({\n ignoredRouteFiles,\n rootDirectory: routesDir,\n });\n mergeRoutes(routes, extensionRoutes, `${EXTENSIONS_DIR}/${ext}/`);\n } catch {\n // Extension has no routes directory — skip\n }\n }\n}\n\n/**\n * Discovers all file-based routes, merges extension routes, and applies multi-site\n * URL configuration if defined in the project's `config.server.ts`.\n *\n * 1. Discover routes from the filesystem using React Router's `flatRoutes`.\n * 2. Scans `src/extensions/` for extension routes and merges them into the route tree.\n * 3. Load `config.server.ts` from the project root and, if `app.url` is configured,\n * wraps routes under the URL prefix (e.g. `/:siteId/:localeId`).\n *\n * @param options.ignoredRouteFiles - Glob patterns for files to ignore. Defaults to test files.\n * @param options.rootDirectory - Root directory for route discovery, relative to appDirectory.\n * @returns The final route config entries for React Router.\n */\nexport async function flatRoutes(options?: {\n ignoredRouteFiles?: string[];\n rootDirectory?: string;\n}): Promise<RouteConfigEntry[]> {\n const { ignoredRouteFiles = ['**/*.test.{ts,tsx}'], rootDirectory } = options ?? {};\n\n // 1. Discover base routes from filesystem\n const routes = await _flatRoutes({ ignoredRouteFiles, rootDirectory });\n\n // 2. Discover and merge extension routes\n await discoverExtensionRoutes(ignoredRouteFiles, routes);\n\n // 3. Try to load URL config from template's config file\n const { url: urlConfig } = await loadConfig();\n if (urlConfig?.prefix) {\n try {\n await fs.access(path.join('.', APP_SRC_DIR, APP_WRAPPER_FILE));\n } catch {\n throw new Error(\n `[storefront-next-runtime] URL prefix \"${urlConfig.prefix}\" is configured but ` +\n `\"${APP_SRC_DIR}/${APP_WRAPPER_FILE}\" does not exist. ` +\n `Create this file with: export { default } from '@salesforce/storefront-next-runtime/routing/app-wrapper';`\n );\n }\n\n return applyUrlConfig({\n routes,\n urlConfig,\n wrapperFile: APP_WRAPPER_FILE,\n });\n }\n\n return routes;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAwBA,SAAS,iBACL,QACA,UACA,WAAmB,IACoD;AACvE,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACpC,MAAM,QAAQ,OAAO;EACrB,MAAMA,SAAO,MAAM,OAAO,GAAG,SAAS,GAAG,MAAM,SAAS;AACxD,MAAI,MAAM,OAAO,SACb,QAAO;GAAE;GAAQ,YAAY;GAAG;GAAM;AAE1C,MAAI,MAAM,UAAU;GAChB,MAAM,QAAQ,iBAAiB,MAAM,UAAU,UAAUA,OAAK;AAC9D,OAAI,MACA,QAAO;;AAIf,MAAI,MAAM,MAAM,SAAS,WAAW,MAAM,GAAG,CACzC,QAAO;GAAE;GAAQ,YAAY;GAAG;GAAM;;AAG9C,QAAO;;;;;;;;;;;;;;;;AAiBX,SAAgB,YACZ,QACA,iBACA,mBACI;AACJ,MAAK,MAAM,SAAS,iBAAiB;AACjC,MAAI,CAAC,MAAM,IAAI;AACX,UAAO,QAAQ,MAAM;AACrB;;EAEJ,MAAM,UAAU,MAAM,GAAG,QAAQ,mBAAmB,GAAG;EACvD,MAAM,qBAAqB,iBAAiB,QAAQ,QAAQ;AAC5D,MAAI,oBAAoB;GACpB,MAAM,eAAe,mBAAmB,OAAO,mBAAmB;AAClE,OAAI,aAAa,OAAO,QAEpB,oBAAmB,OAAO,mBAAmB,YAAY,OAAO,MAAM;QACnE;IAGH,IAAIA,SAAO,MAAM,MAAM,MAAM,mBAAmB,KAAK,OAAO;AAC5D,QAAIA,QAAM,WAAW,IAAI,CACrB,UAAOA,OAAK,MAAM,EAAE;AAExB,aAAOA,SAAOA,SAAO;AACrB,QAAI,CAAC,aAAa,SACd,cAAa,WAAW,EAAE;AAE9B,iBAAa,SAAS,QAAQ;KAC1B,GAAG;KACH,IAAI;KACJ;KACH,CAAC;;QAIN,QAAO,QAAQ;GACX,GAAG;GACH,IAAI;GACP,CAAC;;;;;;;;;;;;;;;;ACvEd,eAAsB,aAA2C;CAC7D,MAAM,aAAa,KAAK,QAAQ,QAAQ,KAAK,EAAE,mBAAmB;AAElE,KAAI,CAACC,KAAG,WAAW,WAAW,EAAE;AAC5B,UAAQ,KACJ,2DAA2D,WAAW,2BACzE;AACD,SAAO,EAAE;;AAGb,KAAI;EAOA,MAAM,MAAM,MAAM;;GAFoB,cAAc,WAAW,CAAC;;AAIhE,UADe,IAAI,WAAW,MACf,OAAO,EAAE;UACnB,OAAO;AACZ,QAAM,IAAI,MAAM,uDAAuD,WAAW,4BAA4B,EAC1G,OAAO,OACV,CAAC;;;;;;AC/BV,MAAM,cAAc;AACpB,MAAM,iBAAiB;AAIvB,MAAM,mBAAmB;;;;;AAMzB,eAAe,wBAAwB,mBAA6B,QAA2C;CAC3G,MAAM,gBAAgB,KAAK,KAAK,KAAK,aAAa,eAAe;CAGjE,MAAM,aAAa,MAAM,GAAG,QAAQ,cAAc,CAAC,MAC9C,YAAY,QAAQ,MAAM,QACrB,EAAE,CACX;AACD,MAAK,MAAM,OAAO,YAAY;EAE1B,MAAM,YAAY,GAAG,eAAe,GAAG,IAAI;EAC3C,MAAM,gBAAgB,KAAK,KAAK,KAAK,aAAa,gBAAgB,KAAK,SAAS;AAChF,MAAI;AACA,SAAM,GAAG,OAAO,cAAc;AAK9B,eAAY,QAJY,MAAMC,aAAY;IACtC;IACA,eAAe;IAClB,CAAC,EACmC,GAAG,eAAe,GAAG,IAAI,GAAG;UAC7D;;;;;;;;;;;;;;;;AAmBhB,eAAsB,WAAW,SAGD;CAC5B,MAAM,EAAE,oBAAoB,CAAC,qBAAqB,EAAE,kBAAkB,WAAW,EAAE;CAGnF,MAAM,SAAS,MAAMA,aAAY;EAAE;EAAmB;EAAe,CAAC;AAGtE,OAAM,wBAAwB,mBAAmB,OAAO;CAGxD,MAAM,EAAE,KAAK,cAAc,MAAM,YAAY;AAC7C,KAAI,WAAW,QAAQ;AACnB,MAAI;AACA,SAAM,GAAG,OAAO,KAAK,KAAK,KAAK,aAAa,iBAAiB,CAAC;UAC1D;AACJ,SAAM,IAAI,MACN,yCAAyC,UAAU,OAAO,uBAClD,YAAY,GAAG,iBAAiB,6HAE3C;;AAGL,SAAO,eAAe;GAClB;GACA;GACA,aAAa;GAChB,CAAC;;AAGN,QAAO"}
1
+ {"version":3,"file":"routing.js","names":["path","fs","_flatRoutes"],"sources":["../src/routing/merge-routes.ts","../src/config/load-config.ts","../src/routing/flat-routes.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { type RouteConfigEntry } from '@react-router/dev/routes';\n\n/**\n * Find the nearest route by its ID in the route tree\n * @param routes - The route subtree to search\n * @param layoutId - The route ID to find (e.g., \"routes/_app\" or \"routes/_app.account\")\n * @param rootPath - The full route path from the root to the current route (default: '')\n * @returns An object with routes array, routeIndex, and path, or null if not found. Returns exact match if found, otherwise returns route where route.id is a prefix of layoutId\n */\nfunction findNearestRoute(\n routes: RouteConfigEntry[],\n layoutId: string,\n rootPath: string = ''\n): { routes: RouteConfigEntry[]; routeIndex: number; path: string } | null {\n for (let i = 0; i < routes.length; i++) {\n const route = routes[i];\n const path = route.path ? `${rootPath}/${route.path}` : rootPath;\n if (route.id === layoutId) {\n return { routes, routeIndex: i, path };\n }\n if (route.children) {\n const found = findNearestRoute(route.children, layoutId, path);\n if (found) {\n return found;\n }\n }\n // Check if route.id is a prefix of layoutId, indicating a nested route\n if (route.id && layoutId.startsWith(route.id)) {\n return { routes, routeIndex: i, path };\n }\n }\n return null;\n}\n\n/**\n * Merges extension routes into the main routes array, handling route nesting.\n * Routes without IDs are added directly to the routes array. Routes with IDs are processed\n * to remove the extension prefix and are either:\n * - Added as children of existing routes (if a nearest route is found via prefix matching)\n * - Replace existing routes (if an exact match is found)\n * - Added directly to the routes array (if no matching route exists)\n *\n * When adding as a child, the parent route's path is clipped from the child route's path.\n *\n * @param routes - The main routes array to merge into (mutated in place)\n * @param extensionRoutes - The extension routes to merge\n * @param extensionIdPrefix - The prefix to remove from extension route IDs (e.g., \"extensions/store-locator/\")\n */\nexport function mergeRoutes(\n routes: RouteConfigEntry[],\n extensionRoutes: RouteConfigEntry[],\n extensionIdPrefix: string\n): void {\n for (const route of extensionRoutes) {\n if (!route.id) {\n routes.unshift(route);\n continue;\n }\n const routeId = route.id.replace(extensionIdPrefix, '');\n const nearestRouteResult = findNearestRoute(routes, routeId);\n if (nearestRouteResult) {\n const nearestRoute = nearestRouteResult.routes[nearestRouteResult.routeIndex];\n if (nearestRoute.id === routeId) {\n // Replacing an existing route, we assume we can just swap out the implementation\n nearestRouteResult.routes[nearestRouteResult.routeIndex].file = route.file;\n } else {\n // This is a new child of an existing route, insert it at the beginning of the children array\n // and clip out the parent path from the route path\n let path = route.path?.slice(nearestRouteResult.path.length);\n if (path?.startsWith('/')) {\n path = path.slice(1);\n }\n path = path ? path : undefined;\n if (!nearestRoute.children) {\n nearestRoute.children = [];\n }\n nearestRoute.children.unshift({\n ...route,\n id: routeId,\n path,\n });\n }\n } else {\n // This is a new route, insert it at the beginning of the routes array\n routes.unshift({\n ...route,\n id: routeId,\n });\n }\n }\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Dynamically imports `config.server.ts` from the project root (CWD) and returns\n * the `app` configuration object.\n *\n * Uses jiti to transpile TypeScript on the fly, which works regardless of whether\n * the caller runs under vite-node, a plain Node process, or any other runtime.\n * This avoids the fragile assumption that vite-node will intercept dynamic imports\n * from pre-compiled npm packages (it won't — Vite externalizes node_modules).\n *\n * - If the config file is missing, warns and returns an empty config.\n * - If the config file exists but fails to import, throws with the original error as cause.\n *\n * @returns The `app` configuration object, or an empty object if not available.\n */\n// TODO: add a proper type when config schema is moved to runtime from the template\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function loadConfig(): Promise<Record<string, any>> {\n const configPath = path.resolve(process.cwd(), 'config.server.ts');\n\n if (!fs.existsSync(configPath)) {\n console.warn(\n `[storefront-next-runtime] config.server.ts not found at ${configPath}. ` + `Returning empty config.`\n );\n return {};\n }\n\n try {\n const { createJiti } = await import('jiti');\n\n const jiti = createJiti(import.meta.url, {\n fsCache: false,\n interopDefault: true,\n });\n\n const mod = await jiti.import(configPath);\n const config = (mod as Record<string, unknown>).default ?? mod;\n return (config as Record<string, unknown>)?.app ?? {};\n } catch (error) {\n throw new Error(`[storefront-next-runtime] Found config.server.ts at ${configPath} but failed to import it.`, {\n cause: error,\n });\n }\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { flatRoutes as _flatRoutes } from '@react-router/fs-routes';\nimport type { RouteConfigEntry } from '@react-router/dev/routes';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { mergeRoutes } from './merge-routes';\nimport { applyUrlConfig } from '../multi-site/apply-url-config';\nimport { loadConfig } from '../config/load-config';\n\nconst APP_SRC_DIR = 'src';\nconst EXTENSIONS_DIR = 'extensions';\n// This file must live at the root of `appDirectory` (src/app-wrapper.tsx) and must NOT\n// be moved into a subdirectory. React Router's typegen resolves route module types using\n// paths relative to `appDirectory` — placing it elsewhere breaks generated type references.\nconst APP_WRAPPER_FILE = 'app-wrapper.tsx';\n\n/**\n * Scans `src/extensions/` for extension route directories and merges any discovered\n * routes into the base route tree. Mutates `routes` in place via `mergeRoutes`.\n */\nasync function discoverExtensionRoutes(ignoredRouteFiles: string[], routes: RouteConfigEntry[]): Promise<void> {\n const extensionsDir = path.join('.', APP_SRC_DIR, EXTENSIONS_DIR);\n\n // Sort to ensure deterministic route order across platforms (readdir order is filesystem-dependent)\n const extensions = await fs.readdir(extensionsDir).then(\n (entries) => entries.sort(),\n () => []\n );\n for (const ext of extensions) {\n // React Router rootDirectory uses forward slashes regardless of OS\n const routesDir = `${EXTENSIONS_DIR}/${ext}/routes`;\n const routesDirFull = path.join('.', APP_SRC_DIR, EXTENSIONS_DIR, ext, 'routes');\n try {\n await fs.access(routesDirFull);\n const extensionRoutes = await _flatRoutes({\n ignoredRouteFiles,\n rootDirectory: routesDir,\n });\n mergeRoutes(routes, extensionRoutes, `${EXTENSIONS_DIR}/${ext}/`);\n } catch {\n // Extension has no routes directory — skip\n }\n }\n}\n\n/**\n * Discovers all file-based routes, merges extension routes, and applies multi-site\n * URL configuration if defined in the project's `config.server.ts`.\n *\n * 1. Discover routes from the filesystem using React Router's `flatRoutes`.\n * 2. Scans `src/extensions/` for extension routes and merges them into the route tree.\n * 3. Load `config.server.ts` from the project root and, if `app.url` is configured,\n * wraps routes under the URL prefix (e.g. `/:siteId/:localeId`).\n *\n * @param options.ignoredRouteFiles - Glob patterns for files to ignore. Defaults to test files.\n * @param options.rootDirectory - Root directory for route discovery, relative to appDirectory.\n * @returns The final route config entries for React Router.\n */\nexport async function flatRoutes(options?: {\n ignoredRouteFiles?: string[];\n rootDirectory?: string;\n}): Promise<RouteConfigEntry[]> {\n const { ignoredRouteFiles = ['**/*.test.{ts,tsx}'], rootDirectory } = options ?? {};\n\n // 1. Discover base routes from filesystem\n const routes = await _flatRoutes({ ignoredRouteFiles, rootDirectory });\n\n // 2. Discover and merge extension routes\n await discoverExtensionRoutes(ignoredRouteFiles, routes);\n\n // 3. Try to load URL config from template's config file\n const { url: urlConfig } = await loadConfig();\n if (urlConfig?.prefix) {\n try {\n await fs.access(path.join('.', APP_SRC_DIR, APP_WRAPPER_FILE));\n } catch {\n throw new Error(\n `[storefront-next-runtime] URL prefix \"${urlConfig.prefix}\" is configured but ` +\n `\"${APP_SRC_DIR}/${APP_WRAPPER_FILE}\" does not exist. ` +\n `Create this file with: export { default } from '@salesforce/storefront-next-runtime/routing/app-wrapper';`\n );\n }\n\n return applyUrlConfig({\n routes,\n urlConfig,\n wrapperFile: APP_WRAPPER_FILE,\n });\n }\n\n return routes;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/storefront-next-runtime",
3
- "version": "0.2.0-alpha.0",
3
+ "version": "0.2.0-alpha.2",
4
4
  "description": "Runtime agnostic libraries for SFCC Storefront Next",
5
5
  "type": "module",
6
6
  "exports": {
@@ -99,6 +99,7 @@
99
99
  "author": "cc-pwa-kit@salesforce.com",
100
100
  "license": "Apache-2.0",
101
101
  "dependencies": {
102
+ "jiti": "^2.6.1",
102
103
  "openapi-fetch": "0.15.0"
103
104
  },
104
105
  "devDependencies": {