@commerce-klaus/sfcc-module-resolver
Shared SFCC cartridge path and module resolution utilities.
This package centralizes SFCC-specific resolution for:
*/cartridge/...~/cartridge/...- cartridge alias paths such as
app_core/cartridge/... module.superModule- cartridge order detection from configuration, environment,
jsconfig, and optionalsite.xml - cartridge
hooks.jsonregistration lookups
Why this package?
Before centralization, resolution logic was spread across multiple packages, which made edge-case drift likely. With this package, all consumers use the same rules and fallback stack.
Typical consumers in this monorepo:
@commerce-klaus/typescript-sfcc@commerce-klaus/eslint-config-sfcc@commerce-klaus/babel-plugin-sfcc-modules@commerce-klaus/vite-plugin-sfcc-modules
Installation
Inside this workspace:
package.json
json
{
"dependencies": {
"@commerce-klaus/sfcc-module-resolver": "workspace:*"
}
}Quick start
resolver.ts
ts
import path from "node:path"
import { createSfccModuleResolver, inferCartridgeOrder } from "@commerce-klaus/sfcc-module-resolver"
const cwd = process.cwd()
const cartridgeRoots = inferCartridgeOrder({
cartridgesDir: "cartridges",
cwd,
})
const resolveSfccModule = createSfccModuleResolver(cartridgeRoots)
const importer = path.resolve("cartridges/app_custom/cartridge/controllers/Home.js")
const resolved = resolveSfccModule("*/cartridge/scripts/util", importer)Cartridge order (priority)
inferCartridgeOrder() uses the following precedence:
options.cartridgePathoptions.envCartridgePathorprocess.env.SFCC_CARTRIDGE_PATHjsconfigreferences (viasolutionConfigPath)site.xml(custom-cartridges) via(siteTemplatePath || DEFAULT_SITE_TEMPLATE_PATH)+site- filesystem fallback (alphabetical directory list)
Notes:
- Return values are absolute cartridge root paths.
- Non-existent cartridge entries are filtered out automatically.
API overview
Constants
SUPPORTED_RUNTIME_EXTENSIONS:readonly ["js", "ds", "json"]SUPER_MODULE_TOKEN:"__sfcc_superModule__"DEFAULT_SITE_TEMPLATE_PATH:"sites/site_template"
Cartridge order and paths
resolveCartridgesDir(cartridgesDir, cwd): stringresolveCartridgesBasePath(basePath, cwd, containingFile?): stringresolveCartridgeRoots(options): string[]findCartridgesDir(startDirectory): string | undefinedreadSolutionReferences(solutionConfigPath): string[]resolveSiteTemplatePath(siteTemplatePath, cwd, fallbackPath?): string | undefinedgetSiteTemplateCartridgePath(siteTemplatePath, site, cwd): string[]inferCartridgeOrder(options): string[]
Module resolution
createSfccModuleResolver(cartridgeRoots)- Returns
resolveSfccModule(moduleName, containingFile): string | undefined - Supports
server,server/*,~/,*/, and cartridge aliases (app_x/cartridge/...)
- Returns
resolveCandidateFile(basePath, moduleName): string | undefinedfindContainingCartridgeRoot(filePath, cartridgeRoots): string | undefined
SuperModule
resolveSuperModuleFilePath(filePath, cartridgeRoots): string | undefinedresolveSuperModuleSpecifier(filePath, cartridgeRoots): string | undefined- Returns a cartridge specifier, for example
app_storefront_base/cartridge/controllers/Page
- Returns a cartridge specifier, for example
transformSuperModuleSource(sourceCode, filePath, cartridgeRoots): stringinjectTopLevelStatement(sourceCode, statement): string
Hook registrations
findCartridgeRootForFile(filePath): string | undefined- Locates the cartridge root (the directory directly under
cartridges/) that contains a file
- Locates the cartridge root (the directory directly under
getCartridgeHooksJsonPath(cartridgeRoot): string | undefined- Reads the cartridge's
package.jsonand resolves its declaredhookspath, if any
- Reads the cartridge's
getHookRegistrationsFromDocument(document): HookRegistration[] | undefined- Validates a parsed
hooks.jsondocument and returns its{ name, script }entries
- Validates a parsed
resolveHookScriptPath(hooksDirectory, script): string | undefined- Resolves a registration's
scriptfield to an existing file, trying.js,.cjs,.mjs, and.ds
- Resolves a registration's
getHookRegistrationsForScriptFile(filePath): HookRegistration[]- Returns all Salesforce and project-specific hook registrations that resolve to a script file
getRequiredHookExportName(hookName): string | undefined- Infers the required export name for Salesforce
dw.*hooks only (the last segment of the extension point)
- Infers the required export name for Salesforce
getRequiredHookExportsForScriptFile(filePath): RequiredHookExport[]- Given a script file, returns every
{ hookName, exportName }it must statically export according to its cartridge'shooks.json
- Given a script file, returns every
Utilities
stripExt(filePath): stringtoPosixPath(filePath): string
Examples
1) Resolve */ and ~/
ts
const resolveSfccModule = createSfccModuleResolver(cartridgeRoots)
resolveSfccModule("*/cartridge/scripts/foo", importer)
resolveSfccModule("~/cartridge/scripts/local", importer)2) Rewrite module.superModule in CommonJS
ts
const nextSource = transformSuperModuleSource(source, filePath, cartridgeRoots)When a fallback exists, module.superModule is replaced by SUPER_MODULE_TOKEN and a matching require(...) line is injected at the top of the file. When no fallback exists, module.superModule is rewritten to undefined.
3) Read site.xml
ts
const order = getSiteTemplateCartridgePath(
"/workspace/sites/site_template",
"RefArch",
process.cwd(),
)4) Find hook registrations and required exports for a script file
hooks.ts
ts
import {
getHookRegistrationsForScriptFile,
getRequiredHookExportsForScriptFile,
} from "@commerce-klaus/sfcc-module-resolver"
const registrations = getHookRegistrationsForScriptFile(scriptPath)
// [{ name: "dw.ocapi.shop.basket.afterPOST", script: "./hooks/basket" }]
const requiredExports = getRequiredHookExportsForScriptFile(
scriptPath,
)
// [{ hookName: "dw.ocapi.shop.basket.afterPOST", exportName: "afterPOST" }]Design decisions
- A central resolver core with consumer-specific adapters kept in each package.
- Filesystem-based resolution is intentionally Node-only.
- Return formats are deterministic: absolute file paths for resolver hooks, cartridge-based specifiers for super module references.
Development
bash
vp test
vp check
vp packIf consumer tests in other packages need this resolver and exports point to dist/*, build this package first:
bash
cd packages/sfcc-module-resolver
vp pack