This is page 18 of 61. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .DS_Store ├── .github │ ├── dependabot.yml │ ├── instructions │ │ ├── mcp-node-tests.instructions.md │ │ └── mcp-yml-tests.instructions.md │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── documentation.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── PULL_REQUEST_TEMPLATE │ │ ├── bug_fix.md │ │ ├── documentation.md │ │ └── new_tool.md │ ├── pull_request_template.md │ └── workflows │ ├── ci.yml │ ├── deploy-pages.yml │ ├── publish.yml │ └── update-docs.yml ├── .gitignore ├── .husky │ └── pre-commit ├── aegis.config.docs-only.json ├── aegis.config.json ├── aegis.config.with-dw.json ├── AGENTS.md ├── ai-instructions │ ├── claude-desktop │ │ └── claude_custom_instructions.md │ ├── cursor │ │ └── .cursor │ │ └── rules │ │ ├── debugging-workflows.mdc │ │ ├── hooks-development.mdc │ │ ├── isml-templates.mdc │ │ ├── job-framework.mdc │ │ ├── performance-optimization.mdc │ │ ├── scapi-endpoints.mdc │ │ ├── security-patterns.mdc │ │ ├── sfcc-development.mdc │ │ ├── sfra-controllers.mdc │ │ ├── sfra-models.mdc │ │ ├── system-objects.mdc │ │ └── testing-patterns.mdc │ └── github-copilot │ └── copilot-instructions.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── docs │ ├── best-practices │ │ ├── cartridge_creation.md │ │ ├── isml_templates.md │ │ ├── job_framework.md │ │ ├── localserviceregistry.md │ │ ├── ocapi_hooks.md │ │ ├── performance.md │ │ ├── scapi_custom_endpoint.md │ │ ├── scapi_hooks.md │ │ ├── security.md │ │ ├── sfra_client_side_js.md │ │ ├── sfra_controllers.md │ │ ├── sfra_models.md │ │ └── sfra_scss.md │ ├── dw_campaign │ │ ├── ABTest.md │ │ ├── ABTestMgr.md │ │ ├── ABTestSegment.md │ │ ├── AmountDiscount.md │ │ ├── ApproachingDiscount.md │ │ ├── BonusChoiceDiscount.md │ │ ├── BonusDiscount.md │ │ ├── Campaign.md │ │ ├── CampaignMgr.md │ │ ├── CampaignStatusCodes.md │ │ ├── Coupon.md │ │ ├── CouponMgr.md │ │ ├── CouponRedemption.md │ │ ├── CouponStatusCodes.md │ │ ├── Discount.md │ │ ├── DiscountPlan.md │ │ ├── FixedPriceDiscount.md │ │ ├── FixedPriceShippingDiscount.md │ │ ├── FreeDiscount.md │ │ ├── FreeShippingDiscount.md │ │ ├── PercentageDiscount.md │ │ ├── PercentageOptionDiscount.md │ │ ├── PriceBookPriceDiscount.md │ │ ├── Promotion.md │ │ ├── PromotionMgr.md │ │ ├── PromotionPlan.md │ │ ├── SlotContent.md │ │ ├── SourceCodeGroup.md │ │ ├── SourceCodeInfo.md │ │ ├── SourceCodeStatusCodes.md │ │ └── TotalFixedPriceDiscount.md │ ├── dw_catalog │ │ ├── Catalog.md │ │ ├── CatalogMgr.md │ │ ├── Category.md │ │ ├── CategoryAssignment.md │ │ ├── CategoryLink.md │ │ ├── PriceBook.md │ │ ├── PriceBookMgr.md │ │ ├── Product.md │ │ ├── ProductActiveData.md │ │ ├── ProductAttributeModel.md │ │ ├── ProductAvailabilityLevels.md │ │ ├── ProductAvailabilityModel.md │ │ ├── ProductInventoryList.md │ │ ├── ProductInventoryMgr.md │ │ ├── ProductInventoryRecord.md │ │ ├── ProductLink.md │ │ ├── ProductMgr.md │ │ ├── ProductOption.md │ │ ├── ProductOptionModel.md │ │ ├── ProductOptionValue.md │ │ ├── ProductPriceInfo.md │ │ ├── ProductPriceModel.md │ │ ├── ProductPriceTable.md │ │ ├── ProductSearchHit.md │ │ ├── ProductSearchModel.md │ │ ├── ProductSearchRefinementDefinition.md │ │ ├── ProductSearchRefinements.md │ │ ├── ProductSearchRefinementValue.md │ │ ├── ProductVariationAttribute.md │ │ ├── ProductVariationAttributeValue.md │ │ ├── ProductVariationModel.md │ │ ├── Recommendation.md │ │ ├── SearchModel.md │ │ ├── SearchRefinementDefinition.md │ │ ├── SearchRefinements.md │ │ ├── SearchRefinementValue.md │ │ ├── SortingOption.md │ │ ├── SortingRule.md │ │ ├── Store.md │ │ ├── StoreGroup.md │ │ ├── StoreInventoryFilter.md │ │ ├── StoreInventoryFilterValue.md │ │ ├── StoreMgr.md │ │ ├── Variant.md │ │ └── VariationGroup.md │ ├── dw_content │ │ ├── Content.md │ │ ├── ContentMgr.md │ │ ├── ContentSearchModel.md │ │ ├── ContentSearchRefinementDefinition.md │ │ ├── ContentSearchRefinements.md │ │ ├── ContentSearchRefinementValue.md │ │ ├── Folder.md │ │ ├── Library.md │ │ ├── MarkupText.md │ │ └── MediaFile.md │ ├── dw_crypto │ │ ├── CertificateRef.md │ │ ├── CertificateUtils.md │ │ ├── Cipher.md │ │ ├── Encoding.md │ │ ├── JWE.md │ │ ├── JWEHeader.md │ │ ├── JWS.md │ │ ├── JWSHeader.md │ │ ├── KeyRef.md │ │ ├── Mac.md │ │ ├── MessageDigest.md │ │ ├── SecureRandom.md │ │ ├── Signature.md │ │ ├── WeakCipher.md │ │ ├── WeakMac.md │ │ ├── WeakMessageDigest.md │ │ ├── WeakSignature.md │ │ └── X509Certificate.md │ ├── dw_customer │ │ ├── AddressBook.md │ │ ├── AgentUserMgr.md │ │ ├── AgentUserStatusCodes.md │ │ ├── AuthenticationStatus.md │ │ ├── Credentials.md │ │ ├── Customer.md │ │ ├── CustomerActiveData.md │ │ ├── CustomerAddress.md │ │ ├── CustomerCDPData.md │ │ ├── CustomerContextMgr.md │ │ ├── CustomerGroup.md │ │ ├── CustomerList.md │ │ ├── CustomerMgr.md │ │ ├── CustomerPasswordConstraints.md │ │ ├── CustomerPaymentInstrument.md │ │ ├── CustomerStatusCodes.md │ │ ├── EncryptedObject.md │ │ ├── ExternalProfile.md │ │ ├── OrderHistory.md │ │ ├── ProductList.md │ │ ├── ProductListItem.md │ │ ├── ProductListItemPurchase.md │ │ ├── ProductListMgr.md │ │ ├── ProductListRegistrant.md │ │ ├── Profile.md │ │ └── Wallet.md │ ├── dw_extensions.applepay │ │ ├── ApplePayHookResult.md │ │ └── ApplePayHooks.md │ ├── dw_extensions.facebook │ │ ├── FacebookFeedHooks.md │ │ └── FacebookProduct.md │ ├── dw_extensions.paymentrequest │ │ ├── PaymentRequestHookResult.md │ │ └── PaymentRequestHooks.md │ ├── dw_extensions.payments │ │ ├── SalesforceBancontactPaymentDetails.md │ │ ├── SalesforceCardPaymentDetails.md │ │ ├── SalesforceEpsPaymentDetails.md │ │ ├── SalesforceIdealPaymentDetails.md │ │ ├── SalesforceKlarnaPaymentDetails.md │ │ ├── SalesforcePaymentDetails.md │ │ ├── SalesforcePaymentIntent.md │ │ ├── SalesforcePaymentMethod.md │ │ ├── SalesforcePaymentRequest.md │ │ ├── SalesforcePaymentsHooks.md │ │ ├── SalesforcePaymentsMgr.md │ │ ├── SalesforcePaymentsSiteConfiguration.md │ │ ├── SalesforcePayPalOrder.md │ │ ├── SalesforcePayPalOrderAddress.md │ │ ├── SalesforcePayPalOrderPayer.md │ │ ├── SalesforcePayPalPaymentDetails.md │ │ ├── SalesforceSepaDebitPaymentDetails.md │ │ └── SalesforceVenmoPaymentDetails.md │ ├── dw_extensions.pinterest │ │ ├── PinterestAvailability.md │ │ ├── PinterestFeedHooks.md │ │ ├── PinterestOrder.md │ │ ├── PinterestOrderHooks.md │ │ └── PinterestProduct.md │ ├── dw_io │ │ ├── CSVStreamReader.md │ │ ├── CSVStreamWriter.md │ │ ├── File.md │ │ ├── FileReader.md │ │ ├── FileWriter.md │ │ ├── InputStream.md │ │ ├── OutputStream.md │ │ ├── PrintWriter.md │ │ ├── RandomAccessFileReader.md │ │ ├── Reader.md │ │ ├── StringWriter.md │ │ ├── Writer.md │ │ ├── XMLIndentingStreamWriter.md │ │ ├── XMLStreamConstants.md │ │ ├── XMLStreamReader.md │ │ └── XMLStreamWriter.md │ ├── dw_job │ │ ├── JobExecution.md │ │ └── JobStepExecution.md │ ├── dw_net │ │ ├── FTPClient.md │ │ ├── FTPFileInfo.md │ │ ├── HTTPClient.md │ │ ├── HTTPRequestPart.md │ │ ├── Mail.md │ │ ├── SFTPClient.md │ │ ├── SFTPFileInfo.md │ │ ├── WebDAVClient.md │ │ └── WebDAVFileInfo.md │ ├── dw_object │ │ ├── ActiveData.md │ │ ├── CustomAttributes.md │ │ ├── CustomObject.md │ │ ├── CustomObjectMgr.md │ │ ├── Extensible.md │ │ ├── ExtensibleObject.md │ │ ├── Note.md │ │ ├── ObjectAttributeDefinition.md │ │ ├── ObjectAttributeGroup.md │ │ ├── ObjectAttributeValueDefinition.md │ │ ├── ObjectTypeDefinition.md │ │ ├── PersistentObject.md │ │ ├── SimpleExtensible.md │ │ └── SystemObjectMgr.md │ ├── dw_order │ │ ├── AbstractItem.md │ │ ├── AbstractItemCtnr.md │ │ ├── Appeasement.md │ │ ├── AppeasementItem.md │ │ ├── Basket.md │ │ ├── BasketMgr.md │ │ ├── BonusDiscountLineItem.md │ │ ├── CouponLineItem.md │ │ ├── CreateAgentBasketLimitExceededException.md │ │ ├── CreateBasketFromOrderException.md │ │ ├── CreateCouponLineItemException.md │ │ ├── CreateOrderException.md │ │ ├── CreateTemporaryBasketLimitExceededException.md │ │ ├── GiftCertificate.md │ │ ├── GiftCertificateLineItem.md │ │ ├── GiftCertificateMgr.md │ │ ├── GiftCertificateStatusCodes.md │ │ ├── Invoice.md │ │ ├── InvoiceItem.md │ │ ├── LineItem.md │ │ ├── LineItemCtnr.md │ │ ├── Order.md │ │ ├── OrderAddress.md │ │ ├── OrderItem.md │ │ ├── OrderMgr.md │ │ ├── OrderPaymentInstrument.md │ │ ├── OrderProcessStatusCodes.md │ │ ├── PaymentCard.md │ │ ├── PaymentInstrument.md │ │ ├── PaymentMethod.md │ │ ├── PaymentMgr.md │ │ ├── PaymentProcessor.md │ │ ├── PaymentStatusCodes.md │ │ ├── PaymentTransaction.md │ │ ├── PriceAdjustment.md │ │ ├── PriceAdjustmentLimitTypes.md │ │ ├── ProductLineItem.md │ │ ├── ProductShippingCost.md │ │ ├── ProductShippingLineItem.md │ │ ├── ProductShippingModel.md │ │ ├── Return.md │ │ ├── ReturnCase.md │ │ ├── ReturnCaseItem.md │ │ ├── ReturnItem.md │ │ ├── Shipment.md │ │ ├── ShipmentShippingCost.md │ │ ├── ShipmentShippingModel.md │ │ ├── ShippingLineItem.md │ │ ├── ShippingLocation.md │ │ ├── ShippingMethod.md │ │ ├── ShippingMgr.md │ │ ├── ShippingOrder.md │ │ ├── ShippingOrderItem.md │ │ ├── SumItem.md │ │ ├── TaxGroup.md │ │ ├── TaxItem.md │ │ ├── TaxMgr.md │ │ ├── TrackingInfo.md │ │ └── TrackingRef.md │ ├── dw_order.hooks │ │ ├── CalculateHooks.md │ │ ├── OrderHooks.md │ │ ├── PaymentHooks.md │ │ ├── ReturnHooks.md │ │ └── ShippingOrderHooks.md │ ├── dw_rpc │ │ ├── SOAPUtil.md │ │ ├── Stub.md │ │ └── WebReference.md │ ├── dw_suggest │ │ ├── BrandSuggestions.md │ │ ├── CategorySuggestions.md │ │ ├── ContentSuggestions.md │ │ ├── CustomSuggestions.md │ │ ├── ProductSuggestions.md │ │ ├── SearchPhraseSuggestions.md │ │ ├── SuggestedCategory.md │ │ ├── SuggestedContent.md │ │ ├── SuggestedPhrase.md │ │ ├── SuggestedProduct.md │ │ ├── SuggestedTerm.md │ │ ├── SuggestedTerms.md │ │ ├── Suggestions.md │ │ └── SuggestModel.md │ ├── dw_svc │ │ ├── FTPService.md │ │ ├── FTPServiceDefinition.md │ │ ├── HTTPFormService.md │ │ ├── HTTPFormServiceDefinition.md │ │ ├── HTTPService.md │ │ ├── HTTPServiceDefinition.md │ │ ├── LocalServiceRegistry.md │ │ ├── Result.md │ │ ├── Service.md │ │ ├── ServiceCallback.md │ │ ├── ServiceConfig.md │ │ ├── ServiceCredential.md │ │ ├── ServiceDefinition.md │ │ ├── ServiceProfile.md │ │ ├── ServiceRegistry.md │ │ ├── SOAPService.md │ │ └── SOAPServiceDefinition.md │ ├── dw_system │ │ ├── AgentUserStatusCodes.md │ │ ├── Cache.md │ │ ├── CacheMgr.md │ │ ├── HookMgr.md │ │ ├── InternalObject.md │ │ ├── JobProcessMonitor.md │ │ ├── Log.md │ │ ├── Logger.md │ │ ├── LogNDC.md │ │ ├── OrganizationPreferences.md │ │ ├── Pipeline.md │ │ ├── PipelineDictionary.md │ │ ├── RemoteInclude.md │ │ ├── Request.md │ │ ├── RequestHooks.md │ │ ├── Response.md │ │ ├── RESTErrorResponse.md │ │ ├── RESTResponseMgr.md │ │ ├── RESTSuccessResponse.md │ │ ├── SearchStatus.md │ │ ├── Session.md │ │ ├── Site.md │ │ ├── SitePreferences.md │ │ ├── Status.md │ │ ├── StatusItem.md │ │ ├── System.md │ │ └── Transaction.md │ ├── dw_util │ │ ├── ArrayList.md │ │ ├── Assert.md │ │ ├── BigInteger.md │ │ ├── Bytes.md │ │ ├── Calendar.md │ │ ├── Collection.md │ │ ├── Currency.md │ │ ├── DateUtils.md │ │ ├── Decimal.md │ │ ├── FilteringCollection.md │ │ ├── Geolocation.md │ │ ├── HashMap.md │ │ ├── HashSet.md │ │ ├── Iterator.md │ │ ├── LinkedHashMap.md │ │ ├── LinkedHashSet.md │ │ ├── List.md │ │ ├── Locale.md │ │ ├── Map.md │ │ ├── MapEntry.md │ │ ├── MappingKey.md │ │ ├── MappingMgr.md │ │ ├── PropertyComparator.md │ │ ├── SecureEncoder.md │ │ ├── SecureFilter.md │ │ ├── SeekableIterator.md │ │ ├── Set.md │ │ ├── SortedMap.md │ │ ├── SortedSet.md │ │ ├── StringUtils.md │ │ ├── Template.md │ │ └── UUIDUtils.md │ ├── dw_value │ │ ├── EnumValue.md │ │ ├── MimeEncodedText.md │ │ ├── Money.md │ │ └── Quantity.md │ ├── dw_web │ │ ├── ClickStream.md │ │ ├── ClickStreamEntry.md │ │ ├── Cookie.md │ │ ├── Cookies.md │ │ ├── CSRFProtection.md │ │ ├── Form.md │ │ ├── FormAction.md │ │ ├── FormElement.md │ │ ├── FormElementValidationResult.md │ │ ├── FormField.md │ │ ├── FormFieldOption.md │ │ ├── FormFieldOptions.md │ │ ├── FormGroup.md │ │ ├── FormList.md │ │ ├── FormListItem.md │ │ ├── Forms.md │ │ ├── HttpParameter.md │ │ ├── HttpParameterMap.md │ │ ├── LoopIterator.md │ │ ├── PageMetaData.md │ │ ├── PageMetaTag.md │ │ ├── PagingModel.md │ │ ├── Resource.md │ │ ├── URL.md │ │ ├── URLAction.md │ │ ├── URLParameter.md │ │ ├── URLRedirect.md │ │ ├── URLRedirectMgr.md │ │ └── URLUtils.md │ ├── sfra │ │ ├── account.md │ │ ├── address.md │ │ ├── billing.md │ │ ├── cart.md │ │ ├── categories.md │ │ ├── content.md │ │ ├── locale.md │ │ ├── order.md │ │ ├── payment.md │ │ ├── price-default.md │ │ ├── price-range.md │ │ ├── price-tiered.md │ │ ├── product-bundle.md │ │ ├── product-full.md │ │ ├── product-line-items.md │ │ ├── product-search.md │ │ ├── product-tile.md │ │ ├── querystring.md │ │ ├── render.md │ │ ├── request.md │ │ ├── response.md │ │ ├── server.md │ │ ├── shipping.md │ │ ├── store.md │ │ ├── stores.md │ │ └── totals.md │ └── TopLevel │ ├── APIException.md │ ├── arguments.md │ ├── Array.md │ ├── ArrayBuffer.md │ ├── BigInt.md │ ├── Boolean.md │ ├── ConversionError.md │ ├── DataView.md │ ├── Date.md │ ├── Error.md │ ├── ES6Iterator.md │ ├── EvalError.md │ ├── Fault.md │ ├── Float32Array.md │ ├── Float64Array.md │ ├── Function.md │ ├── Generator.md │ ├── global.md │ ├── Int16Array.md │ ├── Int32Array.md │ ├── Int8Array.md │ ├── InternalError.md │ ├── IOError.md │ ├── Iterable.md │ ├── Iterator.md │ ├── JSON.md │ ├── Map.md │ ├── Math.md │ ├── Module.md │ ├── Namespace.md │ ├── Number.md │ ├── Object.md │ ├── QName.md │ ├── RangeError.md │ ├── ReferenceError.md │ ├── RegExp.md │ ├── Set.md │ ├── StopIteration.md │ ├── String.md │ ├── Symbol.md │ ├── SyntaxError.md │ ├── SystemError.md │ ├── TypeError.md │ ├── Uint16Array.md │ ├── Uint32Array.md │ ├── Uint8Array.md │ ├── Uint8ClampedArray.md │ ├── URIError.md │ ├── WeakMap.md │ ├── WeakSet.md │ ├── XML.md │ ├── XMLList.md │ └── XMLStreamError.md ├── docs-site │ ├── .gitignore │ ├── App.tsx │ ├── components │ │ ├── Badge.tsx │ │ ├── BreadcrumbSchema.tsx │ │ ├── CodeBlock.tsx │ │ ├── Collapsible.tsx │ │ ├── ConfigBuilder.tsx │ │ ├── ConfigHero.tsx │ │ ├── ConfigModeTabs.tsx │ │ ├── icons.tsx │ │ ├── Layout.tsx │ │ ├── LightCodeContainer.tsx │ │ ├── NewcomerCTA.tsx │ │ ├── NextStepsStrip.tsx │ │ ├── OnThisPage.tsx │ │ ├── Search.tsx │ │ ├── SEO.tsx │ │ ├── Sidebar.tsx │ │ ├── StructuredData.tsx │ │ ├── ToolCard.tsx │ │ ├── ToolFilters.tsx │ │ ├── Typography.tsx │ │ └── VersionBadge.tsx │ ├── constants.tsx │ ├── index.html │ ├── main.tsx │ ├── metadata.json │ ├── package-lock.json │ ├── package.json │ ├── pages │ │ ├── AIInterfacesPage.tsx │ │ ├── ConfigurationPage.tsx │ │ ├── DevelopmentPage.tsx │ │ ├── ExamplesPage.tsx │ │ ├── FeaturesPage.tsx │ │ ├── HomePage.tsx │ │ ├── SecurityPage.tsx │ │ ├── ToolsPage.tsx │ │ └── TroubleshootingPage.tsx │ ├── postcss.config.js │ ├── public │ │ ├── .well-known │ │ │ └── security.txt │ │ ├── 404.html │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── explain-product-pricing-methods-no-mcp.png │ │ ├── explain-product-pricing-methods.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── llms.txt │ │ ├── robots.txt │ │ ├── site.webmanifest │ │ └── sitemap.xml │ ├── README.md │ ├── scripts │ │ ├── generate-search-index.js │ │ ├── generate-sitemap.js │ │ └── search-dev.js │ ├── src │ │ └── styles │ │ ├── input.css │ │ └── prism-theme.css │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── types.ts │ ├── utils │ │ ├── search.ts │ │ └── toolsData.ts │ └── vite.config.ts ├── eslint.config.js ├── jest.config.js ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── scripts │ └── convert-docs.js ├── SECURITY.md ├── server.json ├── src │ ├── clients │ │ ├── base │ │ │ ├── http-client.ts │ │ │ ├── oauth-token.ts │ │ │ └── ocapi-auth-client.ts │ │ ├── best-practices-client.ts │ │ ├── cartridge-generation-client.ts │ │ ├── docs │ │ │ ├── class-content-parser.ts │ │ │ ├── class-name-resolver.ts │ │ │ ├── documentation-scanner.ts │ │ │ ├── index.ts │ │ │ └── referenced-types-extractor.ts │ │ ├── docs-client.ts │ │ ├── log-client.ts │ │ ├── logs │ │ │ ├── index.ts │ │ │ ├── log-analyzer.ts │ │ │ ├── log-client.ts │ │ │ ├── log-constants.ts │ │ │ ├── log-file-discovery.ts │ │ │ ├── log-file-reader.ts │ │ │ ├── log-formatter.ts │ │ │ ├── log-processor.ts │ │ │ ├── log-types.ts │ │ │ └── webdav-client-manager.ts │ │ ├── ocapi │ │ │ ├── code-versions-client.ts │ │ │ ├── site-preferences-client.ts │ │ │ └── system-objects-client.ts │ │ ├── ocapi-client.ts │ │ └── sfra-client.ts │ ├── config │ │ ├── configuration-factory.ts │ │ └── dw-json-loader.ts │ ├── core │ │ ├── handlers │ │ │ ├── abstract-log-tool-handler.ts │ │ │ ├── base-handler.ts │ │ │ ├── best-practices-handler.ts │ │ │ ├── cartridge-handler.ts │ │ │ ├── client-factory.ts │ │ │ ├── code-version-handler.ts │ │ │ ├── docs-handler.ts │ │ │ ├── job-log-handler.ts │ │ │ ├── job-log-tool-config.ts │ │ │ ├── log-handler.ts │ │ │ ├── log-tool-config.ts │ │ │ ├── sfra-handler.ts │ │ │ ├── system-object-handler.ts │ │ │ └── validation-helpers.ts │ │ ├── server.ts │ │ └── tool-definitions.ts │ ├── index.ts │ ├── main.ts │ ├── services │ │ ├── file-system-service.ts │ │ ├── index.ts │ │ └── path-service.ts │ ├── tool-configs │ │ ├── best-practices-tool-config.ts │ │ ├── cartridge-tool-config.ts │ │ ├── code-version-tool-config.ts │ │ ├── docs-tool-config.ts │ │ ├── job-log-tool-config.ts │ │ ├── log-tool-config.ts │ │ ├── sfra-tool-config.ts │ │ └── system-object-tool-config.ts │ ├── types │ │ └── types.ts │ └── utils │ ├── cache.ts │ ├── job-log-tool-config.ts │ ├── job-log-utils.ts │ ├── log-cache.ts │ ├── log-tool-config.ts │ ├── log-tool-constants.ts │ ├── log-tool-utils.ts │ ├── logger.ts │ ├── ocapi-url-builder.ts │ ├── path-resolver.ts │ ├── query-builder.ts │ ├── utils.ts │ └── validator.ts ├── tests │ ├── __mocks__ │ │ ├── docs-client.ts │ │ ├── src │ │ │ └── clients │ │ │ └── base │ │ │ └── http-client.js │ │ └── webdav.js │ ├── base-handler.test.ts │ ├── base-http-client.test.ts │ ├── best-practices-handler.test.ts │ ├── cache.test.ts │ ├── cartridge-handler.test.ts │ ├── class-content-parser.test.ts │ ├── class-name-resolver.test.ts │ ├── client-factory.test.ts │ ├── code-version-handler.test.ts │ ├── code-versions-client.test.ts │ ├── config.test.ts │ ├── configuration-factory.test.ts │ ├── docs-handler.test.ts │ ├── documentation-scanner.test.ts │ ├── file-system-service.test.ts │ ├── job-log-handler.test.ts │ ├── job-log-utils.test.ts │ ├── log-client.test.ts │ ├── log-handler.test.ts │ ├── log-processor.test.ts │ ├── logger.test.ts │ ├── mcp │ │ ├── AGENTS.md │ │ ├── node │ │ │ ├── activate-code-version-advanced.full-mode.programmatic.test.js │ │ │ ├── code-versions.full-mode.programmatic.test.js │ │ │ ├── generate-cartridge-structure.docs-only.programmatic.test.js │ │ │ ├── get-available-best-practice-guides.docs-only.programmatic.test.js │ │ │ ├── get-available-sfra-documents.programmatic.test.js │ │ │ ├── get-best-practice-guide.docs-only.programmatic.test.js │ │ │ ├── get-hook-reference.docs-only.programmatic.test.js │ │ │ ├── get-job-execution-summary.full-mode.programmatic.test.js │ │ │ ├── get-job-log-entries.full-mode.programmatic.test.js │ │ │ ├── get-latest-debug.full-mode.programmatic.test.js │ │ │ ├── get-latest-error.full-mode.programmatic.test.js │ │ │ ├── get-latest-info.full-mode.programmatic.test.js │ │ │ ├── get-latest-job-log-files.full-mode.programmatic.test.js │ │ │ ├── get-latest-warn.full-mode.programmatic.test.js │ │ │ ├── get-log-file-contents.full-mode.programmatic.test.js │ │ │ ├── get-sfcc-class-documentation.docs-only.programmatic.test.js │ │ │ ├── get-sfcc-class-info.docs-only.programmatic.test.js │ │ │ ├── get-sfra-categories.docs-only.programmatic.test.js │ │ │ ├── get-sfra-document.programmatic.test.js │ │ │ ├── get-sfra-documents-by-category.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definition.full-mode.programmatic.test.js │ │ │ ├── get-system-object-definitions.docs-only.programmatic.test.js │ │ │ ├── get-system-object-definitions.full-mode.programmatic.test.js │ │ │ ├── list-log-files.full-mode.programmatic.test.js │ │ │ ├── list-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-best-practices.docs-only.programmatic.test.js │ │ │ ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-job-logs-by-name.full-mode.programmatic.test.js │ │ │ ├── search-job-logs.full-mode.programmatic.test.js │ │ │ ├── search-logs.full-mode.programmatic.test.js │ │ │ ├── search-sfcc-classes.docs-only.programmatic.test.js │ │ │ ├── search-sfcc-methods.docs-only.programmatic.test.js │ │ │ ├── search-sfra-documentation.docs-only.programmatic.test.js │ │ │ ├── search-site-preferences.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js │ │ │ ├── search-system-object-attribute-groups.full-mode.programmatic.test.js │ │ │ ├── summarize-logs.full-mode.programmatic.test.js │ │ │ ├── tools.docs-only.programmatic.test.js │ │ │ └── tools.full-mode.programmatic.test.js │ │ ├── README.md │ │ ├── test-fixtures │ │ │ └── dw.json │ │ └── yaml │ │ ├── activate-code-version.docs-only.test.mcp.yml │ │ ├── activate-code-version.full-mode.test.mcp.yml │ │ ├── get_latest_error.test.mcp.yml │ │ ├── get-available-best-practice-guides.docs-only.test.mcp.yml │ │ ├── get-available-best-practice-guides.full-mode.test.mcp.yml │ │ ├── get-available-sfra-documents.docs-only.test.mcp.yml │ │ ├── get-available-sfra-documents.full-mode.test.mcp.yml │ │ ├── get-best-practice-guide.docs-only.test.mcp.yml │ │ ├── get-best-practice-guide.full-mode.test.mcp.yml │ │ ├── get-code-versions.docs-only.test.mcp.yml │ │ ├── get-code-versions.full-mode.test.mcp.yml │ │ ├── get-hook-reference.docs-only.test.mcp.yml │ │ ├── get-hook-reference.full-mode.test.mcp.yml │ │ ├── get-job-execution-summary.full-mode.test.mcp.yml │ │ ├── get-job-log-entries.full-mode.test.mcp.yml │ │ ├── get-latest-debug.full-mode.test.mcp.yml │ │ ├── get-latest-error.full-mode.test.mcp.yml │ │ ├── get-latest-info.full-mode.test.mcp.yml │ │ ├── get-latest-job-log-files.full-mode.test.mcp.yml │ │ ├── get-latest-warn.full-mode.test.mcp.yml │ │ ├── get-log-file-contents.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-documentation.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-documentation.full-mode.test.mcp.yml │ │ ├── get-sfcc-class-info.docs-only.test.mcp.yml │ │ ├── get-sfcc-class-info.full-mode.test.mcp.yml │ │ ├── get-sfra-categories.docs-only.test.mcp.yml │ │ ├── get-sfra-categories.full-mode.test.mcp.yml │ │ ├── get-sfra-document.docs-only.test.mcp.yml │ │ ├── get-sfra-document.full-mode.test.mcp.yml │ │ ├── get-sfra-documents-by-category.docs-only.test.mcp.yml │ │ ├── get-sfra-documents-by-category.full-mode.test.mcp.yml │ │ ├── get-system-object-definition.docs-only.test.mcp.yml │ │ ├── get-system-object-definition.full-mode.test.mcp.yml │ │ ├── get-system-object-definitions.docs-only.test.mcp.yml │ │ ├── get-system-object-definitions.full-mode.test.mcp.yml │ │ ├── list-log-files.full-mode.test.mcp.yml │ │ ├── list-sfcc-classes.docs-only.test.mcp.yml │ │ ├── list-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-best-practices.docs-only.test.mcp.yml │ │ ├── search-best-practices.full-mode.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-custom-object-attribute-definitions.test.mcp.yml │ │ ├── search-job-logs-by-name.full-mode.test.mcp.yml │ │ ├── search-job-logs.full-mode.test.mcp.yml │ │ ├── search-logs.full-mode.test.mcp.yml │ │ ├── search-sfcc-classes.docs-only.test.mcp.yml │ │ ├── search-sfcc-classes.full-mode.test.mcp.yml │ │ ├── search-sfcc-methods.docs-only.test.mcp.yml │ │ ├── search-sfcc-methods.full-mode.test.mcp.yml │ │ ├── search-sfra-documentation.docs-only.test.mcp.yml │ │ ├── search-sfra-documentation.full-mode.test.mcp.yml │ │ ├── search-site-preferences.docs-only.test.mcp.yml │ │ ├── search-site-preferences.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml │ │ ├── search-system-object-attribute-groups.docs-only.test.mcp.yml │ │ ├── search-system-object-attribute-groups.full-mode.test.mcp.yml │ │ ├── summarize-logs.full-mode.test.mcp.yml │ │ ├── tools.docs-only.test.mcp.yml │ │ └── tools.full-mode.test.mcp.yml │ ├── oauth-token.test.ts │ ├── ocapi-auth-client.test.ts │ ├── ocapi-client.test.ts │ ├── path-service.test.ts │ ├── query-builder.test.ts │ ├── referenced-types-extractor.test.ts │ ├── servers │ │ ├── sfcc-mock-server │ │ │ ├── mock-data │ │ │ │ └── ocapi │ │ │ │ ├── code-versions.json │ │ │ │ ├── custom-object-attributes-customapi.json │ │ │ │ ├── custom-object-attributes-globalsettings.json │ │ │ │ ├── custom-object-attributes-versionhistory.json │ │ │ │ ├── site-preferences-ccv.json │ │ │ │ ├── site-preferences-fastforward.json │ │ │ │ ├── site-preferences-sfra.json │ │ │ │ ├── site-preferences-storefront.json │ │ │ │ ├── site-preferences-system.json │ │ │ │ ├── system-object-attribute-groups-campaign.json │ │ │ │ ├── system-object-attribute-groups-category.json │ │ │ │ ├── system-object-attribute-groups-order.json │ │ │ │ ├── system-object-attribute-groups-product.json │ │ │ │ ├── system-object-attribute-groups-sitepreferences.json │ │ │ │ ├── system-object-attributes-customeraddress.json │ │ │ │ ├── system-object-attributes-product-expanded.json │ │ │ │ ├── system-object-attributes-product.json │ │ │ │ ├── system-object-definition-category.json │ │ │ │ ├── system-object-definition-customer.json │ │ │ │ ├── system-object-definition-customeraddress.json │ │ │ │ ├── system-object-definition-order.json │ │ │ │ ├── system-object-definition-product.json │ │ │ │ ├── system-object-definitions-old.json │ │ │ │ └── system-object-definitions.json │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── README.md │ │ │ ├── scripts │ │ │ │ └── setup-logs.js │ │ │ ├── server.js │ │ │ └── src │ │ │ ├── app.js │ │ │ ├── config │ │ │ │ └── server-config.js │ │ │ ├── middleware │ │ │ │ ├── auth.js │ │ │ │ ├── cors.js │ │ │ │ └── logging.js │ │ │ ├── routes │ │ │ │ ├── ocapi │ │ │ │ │ ├── code-versions-handler.js │ │ │ │ │ ├── oauth-handler.js │ │ │ │ │ ├── ocapi-error-utils.js │ │ │ │ │ ├── ocapi-utils.js │ │ │ │ │ ├── site-preferences-handler.js │ │ │ │ │ └── system-objects-handler.js │ │ │ │ ├── ocapi.js │ │ │ │ └── webdav.js │ │ │ └── utils │ │ │ ├── mock-data-loader.js │ │ │ └── webdav-xml.js │ │ └── sfcc-mock-server-manager.ts │ ├── sfcc-mock-server.test.ts │ ├── site-preferences-client.test.ts │ ├── system-objects-client.test.ts │ ├── utils.test.ts │ ├── validation-helpers.test.ts │ └── validator.test.ts ├── tsconfig.json └── tsconfig.test.json ``` # Files -------------------------------------------------------------------------------- /docs/dw_order/Appeasement.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.order 2 | 3 | # Class Appeasement 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.Extensible 9 | - dw.order.AbstractItemCtnr 10 | - dw.order.Appeasement 11 | 12 | ## Description 13 | 14 | The Appeasement represents a shopper request for an order credit. Example: The buyer finds any problem with the products but he agrees to preserve them, if he would be compensated, rather than return them. The Appeasement contains 1..n appeasement items. Each appeasement item is associated with one OrderItem usually representing an Order ProductLineItem. An Appeasement can have one of these status values: OPEN - the appeasement is open and appeasement items could be added to it COMPLETED - the appeasement is complete and it is not allowed to add new items to it, this is a precondition for refunding the customer for an appeasement. Order post-processing APIs (gillian) are now inactive by default and will throw an exception if accessed. Activation needs preliminary approval by Product Management. Please contact support in this case. Existing customers using these APIs are not affected by this change and can use the APIs until further notice. 15 | 16 | ## Constants 17 | 18 | ### ORDERBY_ITEMID 19 | 20 | **Type:** Object 21 | 22 | Sorting by item id. Use with method getItems() as an argument to method FilteringCollection.sort(Object). 23 | 24 | ### ORDERBY_ITEMPOSITION 25 | 26 | **Type:** Object 27 | 28 | Sorting by the position of the related order item. Use with method getItems() as an argument to method FilteringCollection.sort(Object). 29 | 30 | ### ORDERBY_UNSORTED 31 | 32 | **Type:** Object 33 | 34 | Unsorted, as it is. Use with method getItems() as an argument to method FilteringCollection.sort(Object). 35 | 36 | ### QUALIFIER_PRODUCTITEMS 37 | 38 | **Type:** Object 39 | 40 | Selects the product items. Use with method getItems() as an argument to method FilteringCollection.select(Object). 41 | 42 | ### QUALIFIER_SERVICEITEMS 43 | 44 | **Type:** Object 45 | 46 | Selects the service items. Use with method getItems() as an argument to method FilteringCollection.select(Object). 47 | 48 | ### STATUS_COMPLETED 49 | 50 | **Type:** String = "COMPLETED" 51 | 52 | Constant for Appeasement Status COMPLETED 53 | 54 | ### STATUS_OPEN 55 | 56 | **Type:** String = "OPEN" 57 | 58 | Constant for Appeasement Status OPEN 59 | 60 | ## Properties 61 | 62 | ### appeasementNumber 63 | 64 | **Type:** String (Read Only) 65 | 66 | The appeasement number. 67 | 68 | ### invoice 69 | 70 | **Type:** Invoice (Read Only) 71 | 72 | Returns null or the previously created Invoice. 73 | 74 | ### invoiceNumber 75 | 76 | **Type:** String (Read Only) 77 | 78 | Returns null or the invoice-number. 79 | 80 | ### items 81 | 82 | **Type:** FilteringCollection (Read Only) 83 | 84 | A filtering collection of the appeasement items belonging to the appeasement. 85 | 86 | This FilteringCollection could be sorted / filtered using: 87 | 88 | FilteringCollection.sort(Object) with ORDERBY_ITEMID 89 | FilteringCollection.sort(Object) with ORDERBY_ITEMPOSITION 90 | FilteringCollection.sort(Object) with ORDERBY_UNSORTED 91 | FilteringCollection.select(Object) with QUALIFIER_PRODUCTITEMS 92 | FilteringCollection.select(Object) with QUALIFIER_SERVICEITEMS 93 | 94 | ### reasonCode 95 | 96 | **Type:** EnumValue 97 | 98 | The reason code for the appeasement. The list of reason codes can be updated 99 | by updating meta-data for Appeasement. 100 | 101 | ### reasonNote 102 | 103 | **Type:** String 104 | 105 | The reason note for the appeasement. 106 | 107 | ### status 108 | 109 | **Type:** EnumValue 110 | 111 | Gets the status of this appeasement. 112 | The possible values are STATUS_OPEN, STATUS_COMPLETED. 113 | 114 | ## Constructor Summary 115 | 116 | ## Method Summary 117 | 118 | ### addItems 119 | 120 | **Signature:** `addItems(totalAmount : Money, orderItems : List) : void` 121 | 122 | Creates appeasement items corresponding to certain order items and adds them to the appeasement. 123 | 124 | ### createInvoice 125 | 126 | **Signature:** `createInvoice() : Invoice` 127 | 128 | Creates a new Invoice based on this Appeasement. 129 | 130 | ### createInvoice 131 | 132 | **Signature:** `createInvoice(invoiceNumber : String) : Invoice` 133 | 134 | Creates a new Invoice based on this Appeasement. 135 | 136 | ### getAppeasementNumber 137 | 138 | **Signature:** `getAppeasementNumber() : String` 139 | 140 | Returns the appeasement number. 141 | 142 | ### getInvoice 143 | 144 | **Signature:** `getInvoice() : Invoice` 145 | 146 | Returns null or the previously created Invoice. 147 | 148 | ### getInvoiceNumber 149 | 150 | **Signature:** `getInvoiceNumber() : String` 151 | 152 | Returns null or the invoice-number. 153 | 154 | ### getItems 155 | 156 | **Signature:** `getItems() : FilteringCollection` 157 | 158 | Returns a filtering collection of the appeasement items belonging to the appeasement. 159 | 160 | ### getReasonCode 161 | 162 | **Signature:** `getReasonCode() : EnumValue` 163 | 164 | Returns the reason code for the appeasement. 165 | 166 | ### getReasonNote 167 | 168 | **Signature:** `getReasonNote() : String` 169 | 170 | Returns the reason note for the appeasement. 171 | 172 | ### getStatus 173 | 174 | **Signature:** `getStatus() : EnumValue` 175 | 176 | Gets the status of this appeasement. The possible values are STATUS_OPEN, STATUS_COMPLETED. 177 | 178 | ### setReasonCode 179 | 180 | **Signature:** `setReasonCode(reasonCode : String) : void` 181 | 182 | Set the reason code for the appeasement. 183 | 184 | ### setReasonNote 185 | 186 | **Signature:** `setReasonNote(reasonNote : String) : void` 187 | 188 | Sets the reason note for the appeasement. 189 | 190 | ### setStatus 191 | 192 | **Signature:** `setStatus(appeasementStatus : String) : void` 193 | 194 | Sets the appeasement status. 195 | 196 | ## Method Detail 197 | 198 | ## Method Details 199 | 200 | ### addItems 201 | 202 | **Signature:** `addItems(totalAmount : Money, orderItems : List) : void` 203 | 204 | **Description:** Creates appeasement items corresponding to certain order items and adds them to the appeasement. 205 | 206 | **Parameters:** 207 | 208 | - `totalAmount`: the appeasement amount corresponding to the provided order items; this amount is the net price when the order is net based and respectively - gross price when the order is gross based 209 | - `orderItems`: the order items for which appeasement items should be created 210 | 211 | --- 212 | 213 | ### createInvoice 214 | 215 | **Signature:** `createInvoice() : Invoice` 216 | 217 | **Description:** Creates a new Invoice based on this Appeasement. The appeasement-number will be used as the invoice-number. The method must not be called more than once for an Appeasement, nor may 2 invoices exist with the same invoice-number. The new Invoice is a credit-invoice with a Invoice.STATUS_NOT_PAID status, and should be passed to the refund payment-hook in a separate database transaction for processing. 218 | 219 | **Returns:** 220 | 221 | the created invoice 222 | 223 | --- 224 | 225 | ### createInvoice 226 | 227 | **Signature:** `createInvoice(invoiceNumber : String) : Invoice` 228 | 229 | **Description:** Creates a new Invoice based on this Appeasement. The invoice-number must be specified as an argument. The method must not be called more than once for an Appeasement, nor may 2 invoices exist with the same invoice-number. The new Invoice is a credit-invoice with a Invoice.STATUS_NOT_PAID status, and should be passed to the refund payment-hook in a separate database transaction for processing. 230 | 231 | **Parameters:** 232 | 233 | - `invoiceNumber`: the invoice-number to be used in the appeasement creation process 234 | 235 | **Returns:** 236 | 237 | the created invoice 238 | 239 | --- 240 | 241 | ### getAppeasementNumber 242 | 243 | **Signature:** `getAppeasementNumber() : String` 244 | 245 | **Description:** Returns the appeasement number. 246 | 247 | **Returns:** 248 | 249 | the appeasement number 250 | 251 | --- 252 | 253 | ### getInvoice 254 | 255 | **Signature:** `getInvoice() : Invoice` 256 | 257 | **Description:** Returns null or the previously created Invoice. 258 | 259 | **Returns:** 260 | 261 | null or the previously created invoice 262 | 263 | **See Also:** 264 | 265 | createInvoice(String) 266 | 267 | --- 268 | 269 | ### getInvoiceNumber 270 | 271 | **Signature:** `getInvoiceNumber() : String` 272 | 273 | **Description:** Returns null or the invoice-number. 274 | 275 | **Returns:** 276 | 277 | null or the number of the previously created invoice 278 | 279 | **See Also:** 280 | 281 | createInvoice(String) 282 | 283 | --- 284 | 285 | ### getItems 286 | 287 | **Signature:** `getItems() : FilteringCollection` 288 | 289 | **Description:** Returns a filtering collection of the appeasement items belonging to the appeasement. This FilteringCollection could be sorted / filtered using: FilteringCollection.sort(Object) with ORDERBY_ITEMID FilteringCollection.sort(Object) with ORDERBY_ITEMPOSITION FilteringCollection.sort(Object) with ORDERBY_UNSORTED FilteringCollection.select(Object) with QUALIFIER_PRODUCTITEMS FilteringCollection.select(Object) with QUALIFIER_SERVICEITEMS 290 | 291 | **Returns:** 292 | 293 | the filtering collection of the appeasement items 294 | 295 | --- 296 | 297 | ### getReasonCode 298 | 299 | **Signature:** `getReasonCode() : EnumValue` 300 | 301 | **Description:** Returns the reason code for the appeasement. The list of reason codes can be updated by updating meta-data for Appeasement. 302 | 303 | **Returns:** 304 | 305 | the appeasement reason code 306 | 307 | --- 308 | 309 | ### getReasonNote 310 | 311 | **Signature:** `getReasonNote() : String` 312 | 313 | **Description:** Returns the reason note for the appeasement. 314 | 315 | **Returns:** 316 | 317 | the reason note or null 318 | 319 | --- 320 | 321 | ### getStatus 322 | 323 | **Signature:** `getStatus() : EnumValue` 324 | 325 | **Description:** Gets the status of this appeasement. The possible values are STATUS_OPEN, STATUS_COMPLETED. 326 | 327 | **Returns:** 328 | 329 | the status 330 | 331 | --- 332 | 333 | ### setReasonCode 334 | 335 | **Signature:** `setReasonCode(reasonCode : String) : void` 336 | 337 | **Description:** Set the reason code for the appeasement. The list of reason codes can be updated by updating meta-data for Appeasement. 338 | 339 | **Parameters:** 340 | 341 | - `reasonCode`: the reason code to set 342 | 343 | --- 344 | 345 | ### setReasonNote 346 | 347 | **Signature:** `setReasonNote(reasonNote : String) : void` 348 | 349 | **Description:** Sets the reason note for the appeasement. 350 | 351 | **Parameters:** 352 | 353 | - `reasonNote`: the reason note for the appeasement to set 354 | 355 | --- 356 | 357 | ### setStatus 358 | 359 | **Signature:** `setStatus(appeasementStatus : String) : void` 360 | 361 | **Description:** Sets the appeasement status. The possible values are STATUS_OPEN, STATUS_COMPLETED. When set to status COMPLETED, only the the custom attributes of its appeasement items can be changed. 362 | 363 | **Parameters:** 364 | 365 | - `appeasementStatus`: the appeasement status to set. 366 | 367 | --- ``` -------------------------------------------------------------------------------- /tests/servers/sfcc-mock-server/mock-data/ocapi/code-versions.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "_v": "23.2", 3 | "_type": "code_version_result", 4 | "count": 19, 5 | "data": [ 6 | { 7 | "_type": "code_version", 8 | "activation_time": "2025-09-22T10:00:00.000Z", 9 | "active": true, 10 | "cartridges": ["reset_cartridge"], 11 | "compatibility_mode": "22.7", 12 | "id": "reset_version", 13 | "last_modification_time": "2025-09-22T10:00:00Z", 14 | "rollback": false, 15 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/reset_version" 16 | }, 17 | { 18 | "_type": "code_version", 19 | "active": false, 20 | "cartridges": [ 21 | "app_storefront_base", 22 | "bm_app_storefront_base", 23 | "bm_fastforward_backinstock", 24 | "bm_fastforward_base", 25 | "bm_fastforward_configurator", 26 | "bm_fastforward_configurator_pd", 27 | "bm_fastforward_customer_insights", 28 | "bm_fastforward_google", 29 | "bm_fastforward_gpt", 30 | "bm_fastforward_insights", 31 | "bm_fastforward_managed_runtime", 32 | "bm_fastforward_reviews", 33 | "bm_fastforward_starter_kit", 34 | "bm_fastforward_starter_kit_pd", 35 | "bm_fastforward_store_manager", 36 | "bm_fastforward_tools", 37 | "chance", 38 | "date-fns", 39 | "fast-xml-parser", 40 | "google", 41 | "jsPDF", 42 | "lodash", 43 | "modules", 44 | "moment", 45 | "openai", 46 | "plugin_backinstock", 47 | "plugin_demo_mcp", 48 | "plugin_demo_mcp_2", 49 | "plugin_fastforward_configurator", 50 | "plugin_fastforward_starter_kit", 51 | "plugin_mcp_example", 52 | "plugin_openai", 53 | "plugin_testlibraries", 54 | "ramda", 55 | "rhinodb", 56 | "salesforce-managed-runtime" 57 | ], 58 | "compatibility_mode": "22.7", 59 | "id": "SFRA_AP_01_24_2024", 60 | "last_modification_time": "2025-09-18T13:18:21Z", 61 | "rollback": false, 62 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/SFRA_AP_01_24_2024" 63 | }, 64 | { 65 | "_type": "code_version", 66 | "active": false, 67 | "cartridges": [ 68 | "bm_fastforward_base", 69 | "bm_fastforward_configurator", 70 | "bm_fastforward_configurator_pd", 71 | "plugin_fastforward_configurator" 72 | ], 73 | "compatibility_mode": "22.7", 74 | "id": "SFRA_AP_01_24_2024 ", 75 | "last_modification_time": "2025-03-05T14:55:22Z", 76 | "rollback": false, 77 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/SFRA_AP_01_24_2024 " 78 | }, 79 | { 80 | "_type": "code_version", 81 | "activation_time": "2025-09-18T13:41:07Z", 82 | "active": false, 83 | "cartridges": [ 84 | "bm_fastforward_base", 85 | "bm_fastforward_configurator", 86 | "bm_fastforward_configurator_pd", 87 | "plugin_fastforward_configurator" 88 | ], 89 | "compatibility_mode": "22.7", 90 | "id": "version1", 91 | "last_modification_time": "2025-03-05T14:12:00Z", 92 | "rollback": true, 93 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/version1" 94 | }, 95 | { 96 | "_type": "code_version", 97 | "active": false, 98 | "cartridges": ["test_cartridge"], 99 | "compatibility_mode": "22.7", 100 | "id": "test_activation", 101 | "last_modification_time": "2025-09-22T12:00:00Z", 102 | "rollback": false, 103 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/test_activation" 104 | }, 105 | { 106 | "_type": "code_version", 107 | "active": false, 108 | "cartridges": ["simple_cartridge"], 109 | "compatibility_mode": "22.7", 110 | "id": "simple_id", 111 | "last_modification_time": "2025-09-22T12:00:00Z", 112 | "rollback": false, 113 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/simple_id" 114 | }, 115 | { 116 | "_type": "code_version", 117 | "active": false, 118 | "cartridges": ["version_cartridge"], 119 | "compatibility_mode": "22.7", 120 | "id": "version-with-dashes", 121 | "last_modification_time": "2025-09-22T12:00:00Z", 122 | "rollback": false, 123 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/version-with-dashes" 124 | }, 125 | { 126 | "_type": "code_version", 127 | "active": false, 128 | "cartridges": ["version_cartridge"], 129 | "compatibility_mode": "22.7", 130 | "id": "version_with_underscores", 131 | "last_modification_time": "2025-09-22T12:00:00Z", 132 | "rollback": false, 133 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/version_with_underscores" 134 | }, 135 | { 136 | "_type": "code_version", 137 | "active": false, 138 | "cartridges": ["version_cartridge"], 139 | "compatibility_mode": "22.7", 140 | "id": "Version123", 141 | "last_modification_time": "2025-09-22T12:00:00Z", 142 | "rollback": false, 143 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/Version123" 144 | }, 145 | { 146 | "_type": "code_version", 147 | "active": false, 148 | "cartridges": ["test_cartridge"], 149 | "compatibility_mode": "22.7", 150 | "id": "seq_test_1", 151 | "last_modification_time": "2025-09-22T12:00:00Z", 152 | "rollback": false, 153 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/seq_test_1" 154 | }, 155 | { 156 | "_type": "code_version", 157 | "active": false, 158 | "cartridges": ["test_cartridge"], 159 | "compatibility_mode": "22.7", 160 | "id": "seq_test_2", 161 | "last_modification_time": "2025-09-22T12:00:00Z", 162 | "rollback": false, 163 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/seq_test_2" 164 | }, 165 | { 166 | "_type": "code_version", 167 | "active": false, 168 | "cartridges": ["test_cartridge"], 169 | "compatibility_mode": "22.7", 170 | "id": "seq_test_3", 171 | "last_modification_time": "2025-09-22T12:00:00Z", 172 | "rollback": false, 173 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/seq_test_3" 174 | }, 175 | { 176 | "_type": "code_version", 177 | "active": false, 178 | "cartridges": ["test_cartridge"], 179 | "compatibility_mode": "22.7", 180 | "id": "workflow_test_version", 181 | "last_modification_time": "2025-09-22T12:00:00Z", 182 | "rollback": false, 183 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/workflow_test_version" 184 | }, 185 | { 186 | "_type": "code_version", 187 | "active": false, 188 | "cartridges": ["recovery_cartridge"], 189 | "compatibility_mode": "22.7", 190 | "id": "recovery_test", 191 | "last_modification_time": "2025-09-22T12:00:00Z", 192 | "rollback": false, 193 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/recovery_test" 194 | }, 195 | { 196 | "_type": "code_version", 197 | "active": false, 198 | "cartridges": ["reliability_cartridge"], 199 | "compatibility_mode": "22.7", 200 | "id": "reliability_test_0", 201 | "last_modification_time": "2025-09-22T12:00:00Z", 202 | "rollback": false, 203 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/reliability_test_0" 204 | }, 205 | { 206 | "_type": "code_version", 207 | "active": false, 208 | "cartridges": ["reliability_cartridge"], 209 | "compatibility_mode": "22.7", 210 | "id": "reliability_test_1", 211 | "last_modification_time": "2025-09-22T12:00:00Z", 212 | "rollback": false, 213 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/reliability_test_1" 214 | }, 215 | { 216 | "_type": "code_version", 217 | "active": false, 218 | "cartridges": ["reliability_cartridge"], 219 | "compatibility_mode": "22.7", 220 | "id": "reliability_test_2", 221 | "last_modification_time": "2025-09-22T12:00:00Z", 222 | "rollback": false, 223 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/reliability_test_2" 224 | }, 225 | { 226 | "_type": "code_version", 227 | "active": false, 228 | "cartridges": ["deploy_cartridge"], 229 | "compatibility_mode": "22.7", 230 | "id": "deployment_v1.2.3", 231 | "last_modification_time": "2025-09-22T12:00:00Z", 232 | "rollback": false, 233 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/deployment_v1.2.3" 234 | }, 235 | { 236 | "_type": "code_version", 237 | "active": false, 238 | "cartridges": ["deploy_cartridge"], 239 | "compatibility_mode": "22.7", 240 | "id": "deployment_simulation_v1", 241 | "last_modification_time": "2025-09-22T12:00:00Z", 242 | "rollback": false, 243 | "web_dav_url": "https://development-na01-sandbox.dx.commercecloud.salesforce.com/on/demandware.servlet/webdav/Sites/Cartridges/deployment_simulation_v1" 244 | } 245 | ], 246 | "total": 19 247 | } 248 | ``` -------------------------------------------------------------------------------- /docs/dw_util/Decimal.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.util 2 | 3 | # Class Decimal 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.util.Decimal 9 | 10 | ## Description 11 | 12 | The Decimal class is a helper class to perform decimal arithmetic in scripts and to represent a decimal number with arbitrary length. The decimal class avoids arithmetic errors, which are typical for calculating with floating numbers, that are based on a binary mantissa. The class is designed in a way that it can be used very similar to a desktop calculator. var d = new Decimal( 10.0 ); var result = d.add( 2.0 ).sub( 3.0 ).get(); The above code will return 9 as result. 13 | 14 | ## Constructor Summary 15 | 16 | Decimal() Constructs a new Decimal with the value 0. 17 | 18 | Decimal(value : Number) Constructs a new decimal using the specified Number value. 19 | 20 | Decimal(value : BigInt) Constructs a new decimal using the specified BigInt value. 21 | 22 | Decimal(value : String) Constructs a new Decimal using the specified string representation of a number. 23 | 24 | ## Method Summary 25 | 26 | ### abs 27 | 28 | **Signature:** `abs() : Decimal` 29 | 30 | Returns a new Decimal with the absolute value of this Decimal. 31 | 32 | ### add 33 | 34 | **Signature:** `add(value : Number) : Decimal` 35 | 36 | Adds a Number value to this Decimal and returns the new Decimal. 37 | 38 | ### add 39 | 40 | **Signature:** `add(value : Decimal) : Decimal` 41 | 42 | Adds a Decimal value to this Decimal and returns the new Decimal. 43 | 44 | ### addPercent 45 | 46 | **Signature:** `addPercent(value : Number) : Decimal` 47 | 48 | Adds a percentage value to the current value of the decimal. 49 | 50 | ### addPercent 51 | 52 | **Signature:** `addPercent(value : Decimal) : Decimal` 53 | 54 | Adds a percentage value to the current value of the decimal. 55 | 56 | ### divide 57 | 58 | **Signature:** `divide(value : Number) : Decimal` 59 | 60 | Divides the specified Number value with this decimal and returns the new decimal. 61 | 62 | ### divide 63 | 64 | **Signature:** `divide(value : Decimal) : Decimal` 65 | 66 | Divides the specified Decimal value with this decimal and returns the new decimal. 67 | 68 | ### equals 69 | 70 | **Signature:** `equals(other : Object) : boolean` 71 | 72 | Compares two decimal values whether they are equivalent. 73 | 74 | ### get 75 | 76 | **Signature:** `get() : Number` 77 | 78 | Returns the value of the Decimal as a Number. 79 | 80 | ### hashCode 81 | 82 | **Signature:** `hashCode() : Number` 83 | 84 | Calculates the hash code for this decimal; 85 | 86 | ### multiply 87 | 88 | **Signature:** `multiply(value : Number) : Decimal` 89 | 90 | Multiples the specified Number value with this Decimal and returns the new Decimal. 91 | 92 | ### multiply 93 | 94 | **Signature:** `multiply(value : Decimal) : Decimal` 95 | 96 | Multiples the specified Decimal value with this Decimal and returns the new Decimal. 97 | 98 | ### negate 99 | 100 | **Signature:** `negate() : Decimal` 101 | 102 | Returns a new Decimal with the negated value of this Decimal. 103 | 104 | ### round 105 | 106 | **Signature:** `round(decimals : Number) : Decimal` 107 | 108 | Rounds the current value of the decimal using the specified number of decimals. 109 | 110 | ### subtract 111 | 112 | **Signature:** `subtract(value : Number) : Decimal` 113 | 114 | Subtracts the specified Number value from this Decimal and returns the new Decimal. 115 | 116 | ### subtract 117 | 118 | **Signature:** `subtract(value : Decimal) : Decimal` 119 | 120 | Subtracts the specified Decimal value from this Decimal and returns the new Decimal. 121 | 122 | ### subtractPercent 123 | 124 | **Signature:** `subtractPercent(value : Number) : Decimal` 125 | 126 | Subtracts a percentage value from the current value of the decimal. 127 | 128 | ### subtractPercent 129 | 130 | **Signature:** `subtractPercent(value : Decimal) : Decimal` 131 | 132 | Subtracts a percentage value from the current value of the decimal. 133 | 134 | ### toString 135 | 136 | **Signature:** `toString() : String` 137 | 138 | Returns a string representation of this object. 139 | 140 | ### valueOf 141 | 142 | **Signature:** `valueOf() : Object` 143 | 144 | The valueOf() method is called by the ECMAScript interpret to return the "natural" value of an object. 145 | 146 | ## Constructor Detail 147 | 148 | ## Method Detail 149 | 150 | ## Method Details 151 | 152 | ### abs 153 | 154 | **Signature:** `abs() : Decimal` 155 | 156 | **Description:** Returns a new Decimal with the absolute value of this Decimal. 157 | 158 | **Returns:** 159 | 160 | the new Decimal 161 | 162 | --- 163 | 164 | ### add 165 | 166 | **Signature:** `add(value : Number) : Decimal` 167 | 168 | **Description:** Adds a Number value to this Decimal and returns the new Decimal. 169 | 170 | **Parameters:** 171 | 172 | - `value`: the value to add to this decimal. 173 | 174 | **Returns:** 175 | 176 | the new decimal with the value added. 177 | 178 | --- 179 | 180 | ### add 181 | 182 | **Signature:** `add(value : Decimal) : Decimal` 183 | 184 | **Description:** Adds a Decimal value to this Decimal and returns the new Decimal. 185 | 186 | **Parameters:** 187 | 188 | - `value`: the value to add to this decimal. 189 | 190 | **Returns:** 191 | 192 | the new decimal with the value added. 193 | 194 | --- 195 | 196 | ### addPercent 197 | 198 | **Signature:** `addPercent(value : Number) : Decimal` 199 | 200 | **Description:** Adds a percentage value to the current value of the decimal. For example a value of 10 represent 10% or a value of 85 represents 85%. 201 | 202 | **Parameters:** 203 | 204 | - `value`: the value to add. 205 | 206 | **Returns:** 207 | 208 | a new decimal with the added percentage value. 209 | 210 | --- 211 | 212 | ### addPercent 213 | 214 | **Signature:** `addPercent(value : Decimal) : Decimal` 215 | 216 | **Description:** Adds a percentage value to the current value of the decimal. For example a value of 10 represent 10% or a value of 85 represents 85%. 217 | 218 | **Parameters:** 219 | 220 | - `value`: the value to add. 221 | 222 | **Returns:** 223 | 224 | a new decimal with the added percentage value. 225 | 226 | --- 227 | 228 | ### divide 229 | 230 | **Signature:** `divide(value : Number) : Decimal` 231 | 232 | **Description:** Divides the specified Number value with this decimal and returns the new decimal. When performing the division, 34 digits precision and a rounding mode of HALF_EVEN is used to prevent quotients with nonterminating decimal expansions. 233 | 234 | **Parameters:** 235 | 236 | - `value`: the value to use to divide this decimal. 237 | 238 | **Returns:** 239 | 240 | the new decimal. 241 | 242 | --- 243 | 244 | ### divide 245 | 246 | **Signature:** `divide(value : Decimal) : Decimal` 247 | 248 | **Description:** Divides the specified Decimal value with this decimal and returns the new decimal. When performing the division, 34 digits precision and a rounding mode of HALF_EVEN is used to prevent quotients with nonterminating decimal expansions. 249 | 250 | **Parameters:** 251 | 252 | - `value`: the value to use to divide this decimal. 253 | 254 | **Returns:** 255 | 256 | the new decimal. 257 | 258 | --- 259 | 260 | ### equals 261 | 262 | **Signature:** `equals(other : Object) : boolean` 263 | 264 | **Description:** Compares two decimal values whether they are equivalent. 265 | 266 | **Parameters:** 267 | 268 | - `other`: the object to comapre against this decimal. 269 | 270 | --- 271 | 272 | ### get 273 | 274 | **Signature:** `get() : Number` 275 | 276 | **Description:** Returns the value of the Decimal as a Number. 277 | 278 | **Returns:** 279 | 280 | the value of the Decimal. 281 | 282 | --- 283 | 284 | ### hashCode 285 | 286 | **Signature:** `hashCode() : Number` 287 | 288 | **Description:** Calculates the hash code for this decimal; 289 | 290 | --- 291 | 292 | ### multiply 293 | 294 | **Signature:** `multiply(value : Number) : Decimal` 295 | 296 | **Description:** Multiples the specified Number value with this Decimal and returns the new Decimal. 297 | 298 | **Parameters:** 299 | 300 | - `value`: the value to multiply with this decimal. 301 | 302 | **Returns:** 303 | 304 | the new decimal. 305 | 306 | --- 307 | 308 | ### multiply 309 | 310 | **Signature:** `multiply(value : Decimal) : Decimal` 311 | 312 | **Description:** Multiples the specified Decimal value with this Decimal and returns the new Decimal. 313 | 314 | **Parameters:** 315 | 316 | - `value`: the value to multiply with this decimal. 317 | 318 | **Returns:** 319 | 320 | the new decimal. 321 | 322 | --- 323 | 324 | ### negate 325 | 326 | **Signature:** `negate() : Decimal` 327 | 328 | **Description:** Returns a new Decimal with the negated value of this Decimal. 329 | 330 | **Returns:** 331 | 332 | the new Decimal 333 | 334 | --- 335 | 336 | ### round 337 | 338 | **Signature:** `round(decimals : Number) : Decimal` 339 | 340 | **Description:** Rounds the current value of the decimal using the specified number of decimals. The parameter specifies the number of digest after the decimal point. 341 | 342 | **Parameters:** 343 | 344 | - `decimals`: the number of decimals to use. 345 | 346 | **Returns:** 347 | 348 | the decimal that has been rounded. 349 | 350 | --- 351 | 352 | ### subtract 353 | 354 | **Signature:** `subtract(value : Number) : Decimal` 355 | 356 | **Description:** Subtracts the specified Number value from this Decimal and returns the new Decimal. 357 | 358 | **Parameters:** 359 | 360 | - `value`: the value to add to this decimal. 361 | 362 | **Returns:** 363 | 364 | the new decimal with the value subtraced. 365 | 366 | --- 367 | 368 | ### subtract 369 | 370 | **Signature:** `subtract(value : Decimal) : Decimal` 371 | 372 | **Description:** Subtracts the specified Decimal value from this Decimal and returns the new Decimal. 373 | 374 | **Parameters:** 375 | 376 | - `value`: the value to add to this decimal. 377 | 378 | **Returns:** 379 | 380 | the new decimal with the value subtraced. 381 | 382 | --- 383 | 384 | ### subtractPercent 385 | 386 | **Signature:** `subtractPercent(value : Number) : Decimal` 387 | 388 | **Description:** Subtracts a percentage value from the current value of the decimal. For example a value of 10 represent 10% or a value of 85 represents 85%. 389 | 390 | **Parameters:** 391 | 392 | - `value`: the value to subtract. 393 | 394 | **Returns:** 395 | 396 | a new decimal with the subtracted percentage value. 397 | 398 | --- 399 | 400 | ### subtractPercent 401 | 402 | **Signature:** `subtractPercent(value : Decimal) : Decimal` 403 | 404 | **Description:** Subtracts a percentage value from the current value of the decimal. For example a value of 10 represent 10% or a value of 85 represents 85%. 405 | 406 | **Parameters:** 407 | 408 | - `value`: the value to subtract. 409 | 410 | **Returns:** 411 | 412 | a new decimal with the subtracted percentage value. 413 | 414 | --- 415 | 416 | ### toString 417 | 418 | **Signature:** `toString() : String` 419 | 420 | **Description:** Returns a string representation of this object. 421 | 422 | **Returns:** 423 | 424 | a string representation of this object. 425 | 426 | --- 427 | 428 | ### valueOf 429 | 430 | **Signature:** `valueOf() : Object` 431 | 432 | **Description:** The valueOf() method is called by the ECMAScript interpret to return the "natural" value of an object. The Decimal object returns its current value as number. With this behavior script snippets can be written like: var d = new Decimal( 10.0 ); var x = 1.0 + d.add( 2.0 ); where x will be at the end 13.0. 433 | 434 | **Returns:** 435 | 436 | the value of this object. 437 | 438 | --- ``` -------------------------------------------------------------------------------- /docs-site/scripts/generate-search-index.js: -------------------------------------------------------------------------------- ```javascript 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Search Index Generator for SFCC Development MCP Server Documentation 5 | * 6 | * This script automatically generates a search index by parsing React components 7 | * and extracting searchable content. It replaces the manually maintained search 8 | * index with an automatically generated one during the build process. 9 | */ 10 | 11 | import fs from 'fs'; 12 | import path from 'path'; 13 | import { fileURLToPath } from 'url'; 14 | 15 | const __filename = fileURLToPath(import.meta.url); 16 | const __dirname = path.dirname(__filename); 17 | 18 | // Configuration 19 | const PAGES_DIR = path.join(__dirname, '../pages'); 20 | const OUTPUT_FILE = path.join(__dirname, '../src/generated-search-index.ts'); 21 | 22 | /** 23 | * Route mappings for pages to their URL paths 24 | */ 25 | const ROUTE_MAPPINGS = { 26 | 'HomePage.tsx': '/', 27 | 'ConfigurationPage.tsx': '/configuration/', 28 | 'AIInterfacesPage.tsx': '/ai-interfaces/', 29 | 'FeaturesPage.tsx': '/features/', 30 | 'ToolsPage.tsx': '/tools/', 31 | 'ExamplesPage.tsx': '/examples/', 32 | 'SecurityPage.tsx': '/security/', 33 | 'DevelopmentPage.tsx': '/development/', 34 | 'TroubleshootingPage.tsx': '/troubleshooting/', 35 | }; 36 | 37 | /** 38 | * Extract content from JSX strings and React elements 39 | */ 40 | function extractTextFromJSX(content) { 41 | const textContent = []; 42 | 43 | // Remove JSX tags but keep the text content 44 | const cleanContent = content 45 | // Remove comments 46 | .replace(/\/\*[\s\S]*?\*\//g, '') 47 | .replace(/\/\/.*$/gm, '') 48 | // Remove import statements 49 | .replace(/^import\s+.*$/gm, '') 50 | // Remove className and other JSX attributes 51 | .replace(/className=["'`][^"'`]*["'`]/g, '') 52 | .replace(/\w+={[^}]*}/g, ''); 53 | 54 | // Extract text between JSX tags 55 | const textRegex = />([^<]+)</g; 56 | let match; 57 | while ((match = textRegex.exec(cleanContent)) !== null) { 58 | const text = match[1].trim(); 59 | // Filter out empty strings, JSX expressions, and very short text 60 | if (text && 61 | !text.startsWith('{') && 62 | !text.includes('className') && 63 | !text.includes('src=') && 64 | text.length > 3 && 65 | !/^[{\s}]*$/.test(text)) { 66 | textContent.push(text); 67 | } 68 | } 69 | 70 | // Also extract string literals that might contain content 71 | const stringRegex = /["'`]([^"'`{]+)["'`]/g; 72 | while ((match = stringRegex.exec(cleanContent)) !== null) { 73 | const text = match[1].trim(); 74 | if (text && 75 | text.length > 10 && 76 | !text.includes('className') && 77 | !text.includes('src=') && 78 | !text.includes('href=') && 79 | !text.startsWith('http') && 80 | (!text.includes('.') || text.includes(' '))) { // Allow sentences with periods 81 | textContent.push(text); 82 | } 83 | } 84 | 85 | // Clean up the extracted text 86 | return textContent 87 | .map(text => text 88 | .replace(/\s+/g, ' ') // Normalize whitespace 89 | .replace(/[{}]/g, '') // Remove remaining braces 90 | .trim()) 91 | .filter(text => text.length > 5) 92 | .join(' '); 93 | } 94 | 95 | /** 96 | * Extract headings and sections from a React component 97 | */ 98 | function parseReactComponent(filePath, content) { 99 | const results = []; 100 | 101 | // Get the page title from the route mapping 102 | const relativePath = path.relative(PAGES_DIR, filePath); 103 | const routePath = ROUTE_MAPPINGS[relativePath]; 104 | 105 | if (!routePath) { 106 | console.warn(`Warning: No route mapping found for ${relativePath}`); 107 | return results; 108 | } 109 | 110 | // Extract page title from component name or H1 tags 111 | let pageTitle = 'Unknown Page'; 112 | const titleMatch = content.match(/pageTitle\s*[:=]\s*["'`]([^"'`]+)["'`]/); 113 | if (titleMatch) { 114 | pageTitle = titleMatch[1]; 115 | } else { 116 | // Fallback: extract from H1 tags 117 | const h1Match = content.match(/<h1[^>]*>([^<]+)<\/h1>/i); 118 | if (h1Match) { 119 | pageTitle = h1Match[1].replace(/\{[^}]*\}/g, '').trim(); 120 | } 121 | } 122 | 123 | // Extract sections based on headings (H1, H2, H3 components and regular h1-h3 tags) 124 | const jsxComponentPattern = /<H([123])[^>]*id=["']([^"']+)["'][^>]*>([^<]+)<\/H[123]>/gi; 125 | const regularHeadingPattern = /<(h[1-3])[^>]*(?:id=["']([^"']+)["'])?[^>]*>([^<]+)<\/(h[1-3])>/gi; 126 | const sections = []; 127 | let match; 128 | 129 | // First, extract JSX component headings (H1, H2, H3) 130 | while ((match = jsxComponentPattern.exec(content)) !== null) { 131 | const heading = match[3].trim(); 132 | const headingId = match[2]; 133 | const level = parseInt(match[1]); 134 | 135 | if (heading && heading.length > 1) { 136 | sections.push({ 137 | heading, 138 | headingId, 139 | level, 140 | position: match.index, 141 | }); 142 | } 143 | } 144 | 145 | // Reset regex for next pass 146 | regularHeadingPattern.lastIndex = 0; 147 | 148 | // Then, extract regular HTML headings 149 | while ((match = regularHeadingPattern.exec(content)) !== null) { 150 | const heading = match[3].replace(/\{[^}]*\}/g, '').trim(); 151 | const headingId = match[2]; 152 | const level = parseInt(match[1].charAt(1)); 153 | 154 | if (heading && heading.length > 1 && !heading.includes('className')) { 155 | sections.push({ 156 | heading, 157 | headingId, 158 | level, 159 | position: match.index, 160 | }); 161 | } 162 | } 163 | 164 | // Sort sections by their position in the document 165 | sections.sort((a, b) => a.position - b.position); 166 | 167 | // Filter out duplicate headings at the same position, preferring those with a headingId 168 | const filteredSections = []; 169 | for (let i = 0; i < sections.length; i++) { 170 | const curr = sections[i]; 171 | const next = sections[i + 1]; 172 | if ( 173 | next && 174 | curr.position === next.position && 175 | curr.heading === next.heading 176 | ) { 177 | // Prefer the one with headingId 178 | if (curr.headingId) { 179 | filteredSections.push(curr); 180 | } else if (next.headingId) { 181 | filteredSections.push(next); 182 | } 183 | i++; // Skip the next one 184 | } else { 185 | filteredSections.push(curr); 186 | } 187 | } 188 | 189 | if (filteredSections.length === 0) { 190 | // If no headings found, create a single entry for the whole page 191 | const textContent = extractTextFromJSX(content); 192 | results.push({ 193 | path: routePath, 194 | pageTitle, 195 | heading: pageTitle, 196 | headingId: null, // No heading ID for fallback case 197 | content: textContent.substring(0, 500), // Limit content length 198 | }); 199 | return results; 200 | } 201 | 202 | // Create search entries for each section 203 | filteredSections.forEach((section, index) => { 204 | const nextSection = filteredSections[index + 1]; 205 | const sectionStart = section.position; 206 | const sectionEnd = nextSection ? nextSection.position : content.length; 207 | 208 | const sectionContent = content.substring(sectionStart, sectionEnd); 209 | const textContent = extractTextFromJSX(sectionContent); 210 | 211 | if (textContent.trim().length > 10) { // Only include sections with meaningful content 212 | results.push({ 213 | path: routePath, 214 | pageTitle, 215 | heading: section.heading, 216 | headingId: section.headingId || null, // Ensure the property is always present 217 | content: textContent.substring(0, 300), // Limit content length per section 218 | }); 219 | } 220 | }); 221 | 222 | return results; 223 | } 224 | 225 | /** 226 | * Recursively find all React component files 227 | */ 228 | function findReactFiles(dir) { 229 | const files = []; 230 | 231 | function traverse(currentDir) { 232 | const entries = fs.readdirSync(currentDir, { withFileTypes: true }); 233 | 234 | for (const entry of entries) { 235 | const fullPath = path.join(currentDir, entry.name); 236 | 237 | if (entry.isDirectory()) { 238 | traverse(fullPath); 239 | } else if (entry.isFile() && entry.name.endsWith('.tsx')) { 240 | files.push(fullPath); 241 | } 242 | } 243 | } 244 | 245 | traverse(dir); 246 | return files; 247 | } 248 | 249 | /** 250 | * Generate the search index 251 | */ 252 | function generateSearchIndex() { 253 | console.log('🔍 Generating search index...'); 254 | 255 | const reactFiles = findReactFiles(PAGES_DIR); 256 | const searchIndex = []; 257 | 258 | console.log(`Found ${reactFiles.length} React files to process`); 259 | 260 | for (const filePath of reactFiles) { 261 | try { 262 | const content = fs.readFileSync(filePath, 'utf-8'); 263 | const entries = parseReactComponent(filePath, content); 264 | searchIndex.push(...entries); 265 | 266 | console.log(` ✓ Processed ${path.relative(PAGES_DIR, filePath)} (${entries.length} entries)`); 267 | } catch (error) { 268 | console.error(` ✗ Failed to process ${filePath}:`, error.message); 269 | } 270 | } 271 | 272 | console.log(`Generated ${searchIndex.length} search entries`); 273 | 274 | return searchIndex; 275 | } 276 | 277 | /** 278 | * Write the generated index to a TypeScript file 279 | */ 280 | function writeSearchIndex(searchIndex) { 281 | const outputDir = path.dirname(OUTPUT_FILE); 282 | if (!fs.existsSync(outputDir)) { 283 | fs.mkdirSync(outputDir, { recursive: true }); 284 | } 285 | 286 | const tsContent = `// This file is auto-generated by scripts/generate-search-index.js 287 | // Do not edit manually - changes will be overwritten during build 288 | 289 | export interface SearchableItem { 290 | path: string; 291 | pageTitle: string; 292 | heading: string; 293 | headingId?: string; 294 | content: string; 295 | } 296 | 297 | export const GENERATED_SEARCH_INDEX: SearchableItem[] = ${JSON.stringify(searchIndex, null, 2)}; 298 | `; 299 | 300 | fs.writeFileSync(OUTPUT_FILE, tsContent, 'utf-8'); 301 | console.log(`✅ Search index written to ${path.relative(process.cwd(), OUTPUT_FILE)}`); 302 | } 303 | 304 | /** 305 | * Main execution 306 | */ 307 | function main() { 308 | try { 309 | const searchIndex = generateSearchIndex(); 310 | writeSearchIndex(searchIndex); 311 | console.log('🎉 Search index generation complete!'); 312 | } catch (error) { 313 | console.error('❌ Failed to generate search index:', error); 314 | process.exit(1); 315 | } 316 | } 317 | 318 | // Run if called directly 319 | if (import.meta.url === `file://${process.argv[1]}`) { 320 | main(); 321 | } 322 | 323 | export { generateSearchIndex, writeSearchIndex }; 324 | ``` -------------------------------------------------------------------------------- /docs/dw_util/Assert.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.util 2 | 3 | # Class Assert 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.util.Assert 9 | 10 | ## Description 11 | 12 | The Assert class provides utility methods for assertion events. 13 | 14 | ## Constructor Summary 15 | 16 | ## Method Summary 17 | 18 | ### areEqual 19 | 20 | **Signature:** `static areEqual(arg1 : Object, arg2 : Object) : void` 21 | 22 | Propagates an assertion if the specified objects are not equal. 23 | 24 | ### areEqual 25 | 26 | **Signature:** `static areEqual(arg1 : Object, arg2 : Object, msg : String) : void` 27 | 28 | Propagates an assertion using the specified message if the specified objects are not equal. 29 | 30 | ### areSame 31 | 32 | **Signature:** `static areSame(arg1 : Object, arg2 : Object) : void` 33 | 34 | Propagates an assertion if the specified objects are not the same. 35 | 36 | ### areSame 37 | 38 | **Signature:** `static areSame(arg1 : Object, arg2 : Object, msg : String) : void` 39 | 40 | Propagates an assertion using the specified message if the specified objects are not the same. 41 | 42 | ### fail 43 | 44 | **Signature:** `static fail() : void` 45 | 46 | Propagates a failure assertion. 47 | 48 | ### fail 49 | 50 | **Signature:** `static fail(msg : String) : void` 51 | 52 | Propagates a failure assertion using the specified message. 53 | 54 | ### isEmpty 55 | 56 | **Signature:** `static isEmpty(arg : Object) : void` 57 | 58 | Propagates an assertion if the specified check does not evaluate to an empty object. 59 | 60 | ### isEmpty 61 | 62 | **Signature:** `static isEmpty(arg : Object, msg : String) : void` 63 | 64 | Propagates an assertion using the specified message if the specified check does not evaluate to an empty object. 65 | 66 | ### isFalse 67 | 68 | **Signature:** `static isFalse(check : boolean) : void` 69 | 70 | Propagates an assertion if the specified check does not evaluate to false. 71 | 72 | ### isFalse 73 | 74 | **Signature:** `static isFalse(check : boolean, msg : String) : void` 75 | 76 | Propagates an assertion using the specified message if the specified check does not evaluate to false. 77 | 78 | ### isInstanceOf 79 | 80 | **Signature:** `static isInstanceOf(clazz : Object, arg : Object) : void` 81 | 82 | Propagates an assertion if the specified object 'arg' is not an instance of the specified class 'clazz'. 83 | 84 | ### isInstanceOf 85 | 86 | **Signature:** `static isInstanceOf(clazz : Object, arg : Object, msg : String) : void` 87 | 88 | Propagates an assertion using the specified message if the specified object is not an instance of the specified class. 89 | 90 | ### isNotEmpty 91 | 92 | **Signature:** `static isNotEmpty(arg : Object) : void` 93 | 94 | Propagates an assertion if the specified object is empty. 95 | 96 | ### isNotEmpty 97 | 98 | **Signature:** `static isNotEmpty(arg : Object, msg : String) : void` 99 | 100 | Propagates an assertion using the specified message if the specified object is empty. 101 | 102 | ### isNotNull 103 | 104 | **Signature:** `static isNotNull(arg : Object) : void` 105 | 106 | Propagates an assertion if the specified object is null. 107 | 108 | ### isNotNull 109 | 110 | **Signature:** `static isNotNull(arg : Object, msg : String) : void` 111 | 112 | Propagates an assertion using the specified message if the specified object is null. 113 | 114 | ### isNull 115 | 116 | **Signature:** `static isNull(arg : Object) : void` 117 | 118 | Propagates an assertion if the specified object is not null. 119 | 120 | ### isNull 121 | 122 | **Signature:** `static isNull(arg : Object, msg : String) : void` 123 | 124 | Propagates an assertion using the specified message if the specified object is not null. 125 | 126 | ### isTrue 127 | 128 | **Signature:** `static isTrue(check : boolean) : void` 129 | 130 | Propagates an assertion if the specified check does not evaluate to true. 131 | 132 | ### isTrue 133 | 134 | **Signature:** `static isTrue(check : boolean, msg : String) : void` 135 | 136 | Propagates an assertion using the specified message if the specified check does not evaluate to true. 137 | 138 | ## Method Detail 139 | 140 | ## Method Details 141 | 142 | ### areEqual 143 | 144 | **Signature:** `static areEqual(arg1 : Object, arg2 : Object) : void` 145 | 146 | **Description:** Propagates an assertion if the specified objects are not equal. 147 | 148 | **Parameters:** 149 | 150 | - `arg1`: the first object to check. 151 | - `arg2`: the second object to check. 152 | 153 | --- 154 | 155 | ### areEqual 156 | 157 | **Signature:** `static areEqual(arg1 : Object, arg2 : Object, msg : String) : void` 158 | 159 | **Description:** Propagates an assertion using the specified message if the specified objects are not equal. 160 | 161 | **Parameters:** 162 | 163 | - `arg1`: the first object to check. 164 | - `arg2`: the second object to check. 165 | - `msg`: the assertion message. 166 | 167 | --- 168 | 169 | ### areSame 170 | 171 | **Signature:** `static areSame(arg1 : Object, arg2 : Object) : void` 172 | 173 | **Description:** Propagates an assertion if the specified objects are not the same. 174 | 175 | **Parameters:** 176 | 177 | - `arg1`: the first object to check. 178 | - `arg2`: the second object to check. 179 | 180 | --- 181 | 182 | ### areSame 183 | 184 | **Signature:** `static areSame(arg1 : Object, arg2 : Object, msg : String) : void` 185 | 186 | **Description:** Propagates an assertion using the specified message if the specified objects are not the same. 187 | 188 | **Parameters:** 189 | 190 | - `arg1`: the first object to check. 191 | - `arg2`: the second object to check. 192 | - `msg`: the assertion message. 193 | 194 | --- 195 | 196 | ### fail 197 | 198 | **Signature:** `static fail() : void` 199 | 200 | **Description:** Propagates a failure assertion. 201 | 202 | --- 203 | 204 | ### fail 205 | 206 | **Signature:** `static fail(msg : String) : void` 207 | 208 | **Description:** Propagates a failure assertion using the specified message. 209 | 210 | **Parameters:** 211 | 212 | - `msg`: the assertion message. 213 | 214 | --- 215 | 216 | ### isEmpty 217 | 218 | **Signature:** `static isEmpty(arg : Object) : void` 219 | 220 | **Description:** Propagates an assertion if the specified check does not evaluate to an empty object. 221 | 222 | **Parameters:** 223 | 224 | - `arg`: the object to check. 225 | 226 | --- 227 | 228 | ### isEmpty 229 | 230 | **Signature:** `static isEmpty(arg : Object, msg : String) : void` 231 | 232 | **Description:** Propagates an assertion using the specified message if the specified check does not evaluate to an empty object. 233 | 234 | **Parameters:** 235 | 236 | - `arg`: the object to check. 237 | - `msg`: the assertion message. 238 | 239 | --- 240 | 241 | ### isFalse 242 | 243 | **Signature:** `static isFalse(check : boolean) : void` 244 | 245 | **Description:** Propagates an assertion if the specified check does not evaluate to false. 246 | 247 | **Parameters:** 248 | 249 | - `check`: the condition to check. 250 | 251 | --- 252 | 253 | ### isFalse 254 | 255 | **Signature:** `static isFalse(check : boolean, msg : String) : void` 256 | 257 | **Description:** Propagates an assertion using the specified message if the specified check does not evaluate to false. 258 | 259 | **Parameters:** 260 | 261 | - `check`: the condition to check. 262 | - `msg`: the assertion message. 263 | 264 | --- 265 | 266 | ### isInstanceOf 267 | 268 | **Signature:** `static isInstanceOf(clazz : Object, arg : Object) : void` 269 | 270 | **Description:** Propagates an assertion if the specified object 'arg' is not an instance of the specified class 'clazz'. For example, the following call does not propagate an assertion: var test = new dw.util.HashMap(); dw.util.Assert.isInstanceOf(dw.util.HashMap, test); But the following call will propagate an assertion: var test = new dw.util.Set(); dw.util.Assert.isInstanceOf(dw.util.HashMap, test); Note that 'clazz' can only be a Demandware API Scripting class. 271 | 272 | **Parameters:** 273 | 274 | - `clazz`: the scripting class to use to check the object. 275 | - `arg`: the object to check. 276 | 277 | --- 278 | 279 | ### isInstanceOf 280 | 281 | **Signature:** `static isInstanceOf(clazz : Object, arg : Object, msg : String) : void` 282 | 283 | **Description:** Propagates an assertion using the specified message if the specified object is not an instance of the specified class. For example, the following call does not propagate an assertion: var test = new dw.util.HashMap(); dw.util.Assert.isInstanceOf(dw.util.HashMap, test); But the following call will propagate an assertion: var test = new dw.util.Set(); dw.util.Assert.isInstanceOf(dw.util.HashMap, test); Note that 'clazz' can only be a Demandware API Scripting class. 284 | 285 | **Parameters:** 286 | 287 | - `clazz`: the scripting class to use to check the object. 288 | - `arg`: the object to check. 289 | - `msg`: the assertion message. 290 | 291 | --- 292 | 293 | ### isNotEmpty 294 | 295 | **Signature:** `static isNotEmpty(arg : Object) : void` 296 | 297 | **Description:** Propagates an assertion if the specified object is empty. 298 | 299 | **Parameters:** 300 | 301 | - `arg`: the object to check. 302 | 303 | --- 304 | 305 | ### isNotEmpty 306 | 307 | **Signature:** `static isNotEmpty(arg : Object, msg : String) : void` 308 | 309 | **Description:** Propagates an assertion using the specified message if the specified object is empty. 310 | 311 | **Parameters:** 312 | 313 | - `arg`: the object to check. 314 | - `msg`: the assertion message. 315 | 316 | --- 317 | 318 | ### isNotNull 319 | 320 | **Signature:** `static isNotNull(arg : Object) : void` 321 | 322 | **Description:** Propagates an assertion if the specified object is null. 323 | 324 | **Parameters:** 325 | 326 | - `arg`: the object to check. 327 | 328 | --- 329 | 330 | ### isNotNull 331 | 332 | **Signature:** `static isNotNull(arg : Object, msg : String) : void` 333 | 334 | **Description:** Propagates an assertion using the specified message if the specified object is null. 335 | 336 | **Parameters:** 337 | 338 | - `arg`: the object to check. 339 | - `msg`: the assertion message. 340 | 341 | --- 342 | 343 | ### isNull 344 | 345 | **Signature:** `static isNull(arg : Object) : void` 346 | 347 | **Description:** Propagates an assertion if the specified object is not null. 348 | 349 | **Parameters:** 350 | 351 | - `arg`: the object to check. 352 | 353 | --- 354 | 355 | ### isNull 356 | 357 | **Signature:** `static isNull(arg : Object, msg : String) : void` 358 | 359 | **Description:** Propagates an assertion using the specified message if the specified object is not null. 360 | 361 | **Parameters:** 362 | 363 | - `arg`: the object to check. 364 | - `msg`: the assertion message. 365 | 366 | --- 367 | 368 | ### isTrue 369 | 370 | **Signature:** `static isTrue(check : boolean) : void` 371 | 372 | **Description:** Propagates an assertion if the specified check does not evaluate to true. 373 | 374 | **Parameters:** 375 | 376 | - `check`: the condition to check. 377 | 378 | --- 379 | 380 | ### isTrue 381 | 382 | **Signature:** `static isTrue(check : boolean, msg : String) : void` 383 | 384 | **Description:** Propagates an assertion using the specified message if the specified check does not evaluate to true. 385 | 386 | **Parameters:** 387 | 388 | - `check`: the condition to check. 389 | - `msg`: the assertion message. 390 | 391 | --- ``` -------------------------------------------------------------------------------- /docs/dw_object/ObjectAttributeDefinition.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.object 2 | 3 | # Class ObjectAttributeDefinition 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.object.ObjectAttributeDefinition 9 | 10 | ## Description 11 | 12 | Represents the definition of an object's attribute. 13 | 14 | ## Constants 15 | 16 | ### VALUE_TYPE_BOOLEAN 17 | 18 | **Type:** Number = 8 19 | 20 | Boolean value type. 21 | 22 | ### VALUE_TYPE_DATE 23 | 24 | **Type:** Number = 6 25 | 26 | Date value type. 27 | 28 | ### VALUE_TYPE_DATETIME 29 | 30 | **Type:** Number = 11 31 | 32 | Date and Time value type. 33 | 34 | ### VALUE_TYPE_EMAIL 35 | 36 | **Type:** Number = 12 37 | 38 | Email value type. 39 | 40 | ### VALUE_TYPE_ENUM_OF_INT 41 | 42 | **Type:** Number = 31 43 | 44 | Enum of int value type. 45 | 46 | ### VALUE_TYPE_ENUM_OF_STRING 47 | 48 | **Type:** Number = 33 49 | 50 | Enum of String value type. 51 | 52 | ### VALUE_TYPE_HTML 53 | 54 | **Type:** Number = 5 55 | 56 | HTML value type. 57 | 58 | ### VALUE_TYPE_IMAGE 59 | 60 | **Type:** Number = 7 61 | 62 | Image value type. 63 | 64 | ### VALUE_TYPE_INT 65 | 66 | **Type:** Number = 1 67 | 68 | int value type. 69 | 70 | ### VALUE_TYPE_MONEY 71 | 72 | **Type:** Number = 9 73 | 74 | Money value type. 75 | 76 | ### VALUE_TYPE_NUMBER 77 | 78 | **Type:** Number = 2 79 | 80 | Number value type. 81 | 82 | ### VALUE_TYPE_PASSWORD 83 | 84 | **Type:** Number = 13 85 | 86 | Password value type. 87 | 88 | ### VALUE_TYPE_QUANTITY 89 | 90 | **Type:** Number = 10 91 | 92 | Quantity value type. 93 | 94 | ### VALUE_TYPE_SET_OF_INT 95 | 96 | **Type:** Number = 21 97 | 98 | Set of int value type. 99 | 100 | ### VALUE_TYPE_SET_OF_NUMBER 101 | 102 | **Type:** Number = 22 103 | 104 | Set of Number value type. 105 | 106 | ### VALUE_TYPE_SET_OF_STRING 107 | 108 | **Type:** Number = 23 109 | 110 | Set of String value type. 111 | 112 | ### VALUE_TYPE_STRING 113 | 114 | **Type:** Number = 3 115 | 116 | String value type. 117 | 118 | ### VALUE_TYPE_TEXT 119 | 120 | **Type:** Number = 4 121 | 122 | Text value type. 123 | 124 | ## Properties 125 | 126 | ### attributeGroups 127 | 128 | **Type:** Collection (Read Only) 129 | 130 | All attribute groups the attribute is assigned to. 131 | 132 | ### defaultValue 133 | 134 | **Type:** ObjectAttributeValueDefinition (Read Only) 135 | 136 | Return the default value for the attribute or null if none is defined. 137 | 138 | ### displayName 139 | 140 | **Type:** String (Read Only) 141 | 142 | The display name for the attribute, which can be used in the 143 | user interface. 144 | 145 | ### ID 146 | 147 | **Type:** String (Read Only) 148 | 149 | The ID of the attribute definition. 150 | 151 | ### key 152 | 153 | **Type:** boolean (Read Only) 154 | 155 | Identifies if the attribute represents the primary key of the object. 156 | 157 | ### mandatory 158 | 159 | **Type:** boolean (Read Only) 160 | 161 | Checks if this attribute is mandatory. 162 | 163 | ### multiValueType 164 | 165 | **Type:** VALUE_TYPE_SET_OF_INT (Read Only) 166 | 167 | Returns true if the attribute can have multiple values. 168 | Attributes of the following types are multi-value capable: 169 | 170 | VALUE_TYPE_SET_OF_INT 171 | VALUE_TYPE_SET_OF_NUMBER 172 | VALUE_TYPE_SET_OF_STRING 173 | 174 | Additionally, attributes of the following types can be multi-value 175 | enabled: 176 | 177 | VALUE_TYPE_ENUM_OF_INT 178 | VALUE_TYPE_ENUM_OF_STRING 179 | 180 | ### objectTypeDefinition 181 | 182 | **Type:** ObjectTypeDefinition (Read Only) 183 | 184 | The object type definition in which this attribute is defined. 185 | 186 | ### setValueType 187 | 188 | **Type:** boolean (Read Only) 189 | 190 | Returns true if the attribute is of type 'Set of'. 191 | 192 | ### system 193 | 194 | **Type:** boolean (Read Only) 195 | 196 | Indicates if the attribute is a pre-defined system attribute 197 | or a custom attribute. 198 | 199 | ### unit 200 | 201 | **Type:** String (Read Only) 202 | 203 | The attribute's unit representation such as 204 | inches for length or pounds for weight. The value returned by 205 | this method is based on the attribute itself. 206 | 207 | ### values 208 | 209 | **Type:** Collection (Read Only) 210 | 211 | The list of attribute values. In the user interface only the 212 | values specified in this list should be offered as valid input values. 213 | 214 | The collection contains instances of ObjectAttributeValueDefinition. 215 | 216 | ### valueTypeCode 217 | 218 | **Type:** Number (Read Only) 219 | 220 | A code for the data type stored in the attribute. See constants 221 | defined in this class. 222 | 223 | ## Constructor Summary 224 | 225 | ## Method Summary 226 | 227 | ### getAttributeGroups 228 | 229 | **Signature:** `getAttributeGroups() : Collection` 230 | 231 | Returns all attribute groups the attribute is assigned to. 232 | 233 | ### getDefaultValue 234 | 235 | **Signature:** `getDefaultValue() : ObjectAttributeValueDefinition` 236 | 237 | Return the default value for the attribute or null if none is defined. 238 | 239 | ### getDisplayName 240 | 241 | **Signature:** `getDisplayName() : String` 242 | 243 | Returns the display name for the attribute, which can be used in the user interface. 244 | 245 | ### getID 246 | 247 | **Signature:** `getID() : String` 248 | 249 | Returns the ID of the attribute definition. 250 | 251 | ### getObjectTypeDefinition 252 | 253 | **Signature:** `getObjectTypeDefinition() : ObjectTypeDefinition` 254 | 255 | Returns the object type definition in which this attribute is defined. 256 | 257 | ### getUnit 258 | 259 | **Signature:** `getUnit() : String` 260 | 261 | Returns the attribute's unit representation such as inches for length or pounds for weight. 262 | 263 | ### getValues 264 | 265 | **Signature:** `getValues() : Collection` 266 | 267 | Returns the list of attribute values. 268 | 269 | ### getValueTypeCode 270 | 271 | **Signature:** `getValueTypeCode() : Number` 272 | 273 | Returns a code for the data type stored in the attribute. 274 | 275 | ### isKey 276 | 277 | **Signature:** `isKey() : boolean` 278 | 279 | Identifies if the attribute represents the primary key of the object. 280 | 281 | ### isMandatory 282 | 283 | **Signature:** `isMandatory() : boolean` 284 | 285 | Checks if this attribute is mandatory. 286 | 287 | ### isMultiValueType 288 | 289 | **Signature:** `isMultiValueType() : boolean` 290 | 291 | Returns true if the attribute can have multiple values. 292 | 293 | ### isSetValueType 294 | 295 | **Signature:** `isSetValueType() : boolean` 296 | 297 | Returns true if the attribute is of type 'Set of'. 298 | 299 | ### isSystem 300 | 301 | **Signature:** `isSystem() : boolean` 302 | 303 | Indicates if the attribute is a pre-defined system attribute or a custom attribute. 304 | 305 | ### requiresEncoding 306 | 307 | **Signature:** `requiresEncoding() : boolean` 308 | 309 | Returns a boolean flag indicating whether or not values of this attribute definition should be encoded using the encoding="off" flag in ISML templates. 310 | 311 | ## Method Detail 312 | 313 | ## Method Details 314 | 315 | ### getAttributeGroups 316 | 317 | **Signature:** `getAttributeGroups() : Collection` 318 | 319 | **Description:** Returns all attribute groups the attribute is assigned to. 320 | 321 | **Returns:** 322 | 323 | all attribute groups the attribute is assigned to. 324 | 325 | --- 326 | 327 | ### getDefaultValue 328 | 329 | **Signature:** `getDefaultValue() : ObjectAttributeValueDefinition` 330 | 331 | **Description:** Return the default value for the attribute or null if none is defined. 332 | 333 | **Returns:** 334 | 335 | the default value for the attribute or null if none is defined. 336 | 337 | --- 338 | 339 | ### getDisplayName 340 | 341 | **Signature:** `getDisplayName() : String` 342 | 343 | **Description:** Returns the display name for the attribute, which can be used in the user interface. 344 | 345 | **Returns:** 346 | 347 | the display name for the attribute, which can be used in the user interface. 348 | 349 | --- 350 | 351 | ### getID 352 | 353 | **Signature:** `getID() : String` 354 | 355 | **Description:** Returns the ID of the attribute definition. 356 | 357 | **Returns:** 358 | 359 | the ID of the attribute definition. 360 | 361 | --- 362 | 363 | ### getObjectTypeDefinition 364 | 365 | **Signature:** `getObjectTypeDefinition() : ObjectTypeDefinition` 366 | 367 | **Description:** Returns the object type definition in which this attribute is defined. 368 | 369 | **Returns:** 370 | 371 | the object type definition in which this attribute is defined. 372 | 373 | --- 374 | 375 | ### getUnit 376 | 377 | **Signature:** `getUnit() : String` 378 | 379 | **Description:** Returns the attribute's unit representation such as inches for length or pounds for weight. The value returned by this method is based on the attribute itself. 380 | 381 | **Returns:** 382 | 383 | the attribute's unit representation such as inches for length or pounds for weight. 384 | 385 | --- 386 | 387 | ### getValues 388 | 389 | **Signature:** `getValues() : Collection` 390 | 391 | **Description:** Returns the list of attribute values. In the user interface only the values specified in this list should be offered as valid input values. The collection contains instances of ObjectAttributeValueDefinition. 392 | 393 | **Returns:** 394 | 395 | a collection of ObjectAttributeValueDefinition instances representing the list of attribute values, or null if no values are specified. 396 | 397 | --- 398 | 399 | ### getValueTypeCode 400 | 401 | **Signature:** `getValueTypeCode() : Number` 402 | 403 | **Description:** Returns a code for the data type stored in the attribute. See constants defined in this class. 404 | 405 | **Returns:** 406 | 407 | a code for the data type stored in the attribute. See constants defined in this class. 408 | 409 | --- 410 | 411 | ### isKey 412 | 413 | **Signature:** `isKey() : boolean` 414 | 415 | **Description:** Identifies if the attribute represents the primary key of the object. 416 | 417 | **Returns:** 418 | 419 | true if the attribute represents the primary key, false otherwise. 420 | 421 | --- 422 | 423 | ### isMandatory 424 | 425 | **Signature:** `isMandatory() : boolean` 426 | 427 | **Description:** Checks if this attribute is mandatory. 428 | 429 | **Returns:** 430 | 431 | true, if this attribute is mandatory 432 | 433 | --- 434 | 435 | ### isMultiValueType 436 | 437 | **Signature:** `isMultiValueType() : boolean` 438 | 439 | **Description:** Returns true if the attribute can have multiple values. Attributes of the following types are multi-value capable: VALUE_TYPE_SET_OF_INT VALUE_TYPE_SET_OF_NUMBER VALUE_TYPE_SET_OF_STRING Additionally, attributes of the following types can be multi-value enabled: VALUE_TYPE_ENUM_OF_INT VALUE_TYPE_ENUM_OF_STRING 440 | 441 | **Returns:** 442 | 443 | true if attributes can have multiple values, otherwise false 444 | 445 | --- 446 | 447 | ### isSetValueType 448 | 449 | **Signature:** `isSetValueType() : boolean` 450 | 451 | **Description:** Returns true if the attribute is of type 'Set of'. 452 | 453 | **Deprecated:** 454 | 455 | Use isMultiValueType() instead. 456 | 457 | --- 458 | 459 | ### isSystem 460 | 461 | **Signature:** `isSystem() : boolean` 462 | 463 | **Description:** Indicates if the attribute is a pre-defined system attribute or a custom attribute. 464 | 465 | **Returns:** 466 | 467 | true if the the attribute is a pre-defined system attribute, false if it is a custom attribute. 468 | 469 | --- 470 | 471 | ### requiresEncoding 472 | 473 | **Signature:** `requiresEncoding() : boolean` 474 | 475 | **Description:** Returns a boolean flag indicating whether or not values of this attribute definition should be encoded using the encoding="off" flag in ISML templates. 476 | 477 | **Returns:** 478 | 479 | a boolean flag indicating whether or not values of this attribute definition should be encoded using the encoding="off" flag in ISML templates. 480 | 481 | --- ``` -------------------------------------------------------------------------------- /docs/dw_order.hooks/ShippingOrderHooks.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.order.hooks 2 | 3 | # Class ShippingOrderHooks 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - dw.order.hooks.ShippingOrderHooks 8 | 9 | ## Description 10 | 11 | This interface represents all script hooks that can be registered around shipping order lifecycle. It contains the extension points (hook names), and the functions that are called by each extension point. A function must be defined inside a JavaScript source and must be exported. The script with the exported hook function must be located inside a site cartridge. Inside the site cartridge a 'package.json' file with a 'hooks' entry must exist. "hooks": "./hooks.json" The hooks entry links to a json file, relative to the 'package.json' file. This file lists all registered hooks inside the hooks property: "hooks": [ {"name": "dw.order.shippingorder.updateShippingOrderItem", "script": "./shippingOrderUpdate.ds"}, ] A hook entry has a 'name' and a 'script' property. The 'name' contains the extension point, the hook name. The 'script' contains the script relative to the hooks file, with the exported hook function. Order post-processing APIs (gillian) are now inactive by default and will throw an exception if accessed. Activation needs preliminary approval by Product Management. Please contact support in this case. Existing customers using these APIs are not affected by this change and can use the APIs until further notice. 12 | 13 | ## Constants 14 | 15 | ## Properties 16 | 17 | ## Constructor Summary 18 | 19 | ## Method Summary 20 | 21 | ### afterStatusChange 22 | 23 | **Signature:** `afterStatusChange(shippingOrder : ShippingOrder) : Status` 24 | 25 | After Status change hook. 26 | 27 | ### changeStatus 28 | 29 | **Signature:** `changeStatus(shippingOrder : ShippingOrder, updateData : ShippingOrder) : Status` 30 | 31 | Change the status of a shipping order. 32 | 33 | ### createShippingOrders 34 | 35 | **Signature:** `createShippingOrders(order : Order) : Status` 36 | 37 | Called during shipping order creation for an order. 38 | 39 | ### notifyStatusChange 40 | 41 | **Signature:** `notifyStatusChange(shippingOrder : ShippingOrder) : Status` 42 | 43 | Notify Status change hook. 44 | 45 | ### prepareCreateShippingOrders 46 | 47 | **Signature:** `prepareCreateShippingOrders(order : Order) : Status` 48 | 49 | Called before shipping order creation for an order takes place. 50 | 51 | ### resolveShippingOrder 52 | 53 | **Signature:** `resolveShippingOrder(updateData : ShippingOrder) : ShippingOrder` 54 | 55 | Resolve the shipping order. 56 | 57 | ### setShippingOrderCancelled 58 | 59 | **Signature:** `setShippingOrderCancelled(updateData : ShippingOrder) : Order` 60 | 61 | Change the status of a shipping order to cancelled. 62 | 63 | ### setShippingOrderShipped 64 | 65 | **Signature:** `setShippingOrderShipped(updateData : ShippingOrder) : Order` 66 | 67 | Change the status of a shipping order to shipped. 68 | 69 | ### setShippingOrderWarehouse 70 | 71 | **Signature:** `setShippingOrderWarehouse(updateData : ShippingOrder) : Order` 72 | 73 | Change the status of a shipping order to warehouse. 74 | 75 | ### updateShippingOrderItem 76 | 77 | **Signature:** `updateShippingOrderItem(shippingOrder : ShippingOrder, updateItem : ShippingOrderItem) : Status` 78 | 79 | Updates the status of a shipping order item. 80 | 81 | ## Method Detail 82 | 83 | ## Method Details 84 | 85 | ### afterStatusChange 86 | 87 | **Signature:** `afterStatusChange(shippingOrder : ShippingOrder) : Status` 88 | 89 | **Description:** After Status change hook. The function is called by extension point extensionPointAfterStatusChange. The implementation of this hook is optional. If defined the hook is called after extensionPointChangeStatus or respectively after extensionPointShippingOrderShipped, extensionPointShippingOrderCancelled or extensionPointShippingOrderWarehouse Runs inside of a transaction. 90 | 91 | **Parameters:** 92 | 93 | - `shippingOrder`: the shipping order to be updated 94 | 95 | **Returns:** 96 | 97 | the resulting status 98 | 99 | --- 100 | 101 | ### changeStatus 102 | 103 | **Signature:** `changeStatus(shippingOrder : ShippingOrder, updateData : ShippingOrder) : Status` 104 | 105 | **Description:** Change the status of a shipping order. The function is called by extension point extensionPointChangeStatus. Runs inside a transaction together with the hooks extensionPointResolveShippingOrder extensionPointUpdateShippingOrderItem. Runs after the iteration over the input's items collection as the last step in this transaction. The implementation of this hook is mandatory. 106 | 107 | **Parameters:** 108 | 109 | - `shippingOrder`: the shipping order to be updated 110 | - `updateData`: the input data 111 | 112 | **Returns:** 113 | 114 | the resulting status 115 | 116 | --- 117 | 118 | ### createShippingOrders 119 | 120 | **Signature:** `createShippingOrders(order : Order) : Status` 121 | 122 | **Description:** Called during shipping order creation for an order. The function is called by extension point extensionPointCreateShippingOrders. It is responsible for creating shipping orders and its items for the order. Preparations for shipping order creation can be done before in hook extensionPointPrepareCreateShippingOrders. Runs inside of a transaction. The implementation of this hook is mandatory. 123 | 124 | **Parameters:** 125 | 126 | - `order`: the order to create shipping orders for 127 | 128 | **Returns:** 129 | 130 | the resulting status 131 | 132 | --- 133 | 134 | ### notifyStatusChange 135 | 136 | **Signature:** `notifyStatusChange(shippingOrder : ShippingOrder) : Status` 137 | 138 | **Description:** Notify Status change hook. The function is called by extension point extensionPointNotifyStatusChange. The implementation of this hook is optional. If defined the hook is called after extensionPointAfterStatusChange as the last step in the shipping order update process. Runs outside of a transaction. 139 | 140 | **Parameters:** 141 | 142 | - `shippingOrder`: the shipping order to be updated 143 | 144 | **Returns:** 145 | 146 | the resulting status 147 | 148 | --- 149 | 150 | ### prepareCreateShippingOrders 151 | 152 | **Signature:** `prepareCreateShippingOrders(order : Order) : Status` 153 | 154 | **Description:** Called before shipping order creation for an order takes place. Typically the hook is used to check the payment authorization status of the order. The function is called by extension point extensionPointPrepareCreateShippingOrders. Runs inside its own transaction. The value of the return status is used to control whether hook createShippingOrders(Order) is called for the order or not. The implementation of this hook is mandatory. 155 | 156 | **Parameters:** 157 | 158 | - `order`: the order to create shipping orders for 159 | 160 | **Returns:** 161 | 162 | Status.OK successful preparation - continue with shipping order creation for this order. Status.ERROR failed preparation - skip shipping order creation for this order. 163 | 164 | --- 165 | 166 | ### resolveShippingOrder 167 | 168 | **Signature:** `resolveShippingOrder(updateData : ShippingOrder) : ShippingOrder` 169 | 170 | **Description:** Resolve the shipping order. Will be called as first step of the update. The function is called by extension point extensionPointResolveShippingOrder. Runs inside a transaction together with the hooks extensionPointUpdateShippingOrderItem extensionPointChangeStatus. The implementation of this hook is mandatory. 171 | 172 | **Parameters:** 173 | 174 | - `updateData`: the input data 175 | 176 | **Returns:** 177 | 178 | the shipping order to update 179 | 180 | --- 181 | 182 | ### setShippingOrderCancelled 183 | 184 | **Signature:** `setShippingOrderCancelled(updateData : ShippingOrder) : Order` 185 | 186 | **Description:** Change the status of a shipping order to cancelled. The function is called by extension point extensionPointShippingOrderCancelled. This is an optional hook that can be implemented to have full control over status change to destination status Cancelled. The following hooks will be skipped if an implementation for this hook is registered: extensionPointResolveShippingOrder, extensionPointUpdateShippingOrderItem, extensionPointChangeStatus. Runs inside of a transaction. 187 | 188 | **Parameters:** 189 | 190 | - `updateData`: the input data 191 | 192 | **Returns:** 193 | 194 | the changed order or {code}null{code} 195 | 196 | --- 197 | 198 | ### setShippingOrderShipped 199 | 200 | **Signature:** `setShippingOrderShipped(updateData : ShippingOrder) : Order` 201 | 202 | **Description:** Change the status of a shipping order to shipped. The function is called by extension point extensionPointShippingOrderShipped. This is an optional hook that can be implemented to have full control over status change to destination status Shipped. The following hooks will be skipped if an implementation for this hook is registered: extensionPointResolveShippingOrder extensionPointUpdateShippingOrderItem, extensionPointChangeStatus. Runs inside of a transaction. 203 | 204 | **Parameters:** 205 | 206 | - `updateData`: the input data 207 | 208 | **Returns:** 209 | 210 | the changed order or {code}null{code} 211 | 212 | --- 213 | 214 | ### setShippingOrderWarehouse 215 | 216 | **Signature:** `setShippingOrderWarehouse(updateData : ShippingOrder) : Order` 217 | 218 | **Description:** Change the status of a shipping order to warehouse. The function is called by extension point extensionPointShippingOrderWarehouse. This is an optional hook that can be implemented to have full control over status change to destination status Warehouse. The following hooks will be skipped if an implementation for this hook is registered: extensionPointResolveShippingOrder, extensionPointUpdateShippingOrderItem, extensionPointChangeStatus. Runs inside of a transaction. 219 | 220 | **Parameters:** 221 | 222 | - `updateData`: the input data 223 | 224 | **Returns:** 225 | 226 | the changed order or {code}null{code} 227 | 228 | --- 229 | 230 | ### updateShippingOrderItem 231 | 232 | **Signature:** `updateShippingOrderItem(shippingOrder : ShippingOrder, updateItem : ShippingOrderItem) : Status` 233 | 234 | **Description:** Updates the status of a shipping order item. The function is called by extension point extensionPointUpdateShippingOrderItem. Runs inside a transaction together with the hooks extensionPointResolveShippingOrder extensionPointChangeStatus. The implementation of this hook is mandatory. 235 | 236 | **Parameters:** 237 | 238 | - `shippingOrder`: the shipping order 239 | - `updateItem`: the input data 240 | 241 | **Returns:** 242 | 243 | the resulting status 244 | 245 | --- ``` -------------------------------------------------------------------------------- /tests/mcp/yaml/search-site-preferences.docs-only.test.mcp.yml: -------------------------------------------------------------------------------- ```yaml 1 | # ================================================================================== 2 | # SFCC MCP Server - search_site_preferences Tool YAML Tests (Docs-Only Mode) 3 | # Validates that site preferences search tools are NOT available in docs-only mode 4 | # This tool requires SFCC credentials and should not be available without them 5 | # 6 | # Quick Test Commands: 7 | # aegis "tests/mcp/yaml/search-site-preferences.docs-only.test.mcp.yml" --config "aegis.config.docs-only.json" --verbose 8 | # aegis query search_site_preferences '{"groupId": "Storefront", "instanceType": "sandbox", "searchRequest": {"query": {"match_all_query": {}}}}' --config "aegis.config.docs-only.json" 9 | # ================================================================================== 10 | 11 | description: "search_site_preferences tool docs-only mode tests - Tool unavailability validation" 12 | 13 | tests: 14 | # ================================================================================== 15 | # TOOL UNAVAILABILITY IN DOCS-ONLY MODE 16 | # ================================================================================== 17 | - it: "should NOT list search_site_preferences tool in docs-only mode" 18 | request: 19 | jsonrpc: "2.0" 20 | id: "tool-not-available-docs" 21 | method: "tools/list" 22 | params: {} 23 | expect: 24 | response: 25 | jsonrpc: "2.0" 26 | id: "tool-not-available-docs" 27 | result: 28 | tools: 29 | match:arrayElements: 30 | match:partial: 31 | name: "match:type:string" 32 | description: "match:type:string" 33 | match:extractField: "tools.*.name" 34 | value: "match:not:arrayContains:search_site_preferences" 35 | stderr: "toBeEmpty" 36 | 37 | # ================================================================================== 38 | # AUTHENTICATION ERROR TESTS (Tool Can Be Called But Returns Error) 39 | # ================================================================================== 40 | - it: "should return authentication error when calling search_site_preferences in docs-only mode" 41 | request: 42 | jsonrpc: "2.0" 43 | id: "auth-error-storefront" 44 | method: "tools/call" 45 | params: 46 | name: "search_site_preferences" 47 | arguments: 48 | groupId: "Storefront" 49 | instanceType: "sandbox" 50 | searchRequest: 51 | query: 52 | match_all_query: {} 53 | expect: 54 | response: 55 | jsonrpc: "2.0" 56 | id: "auth-error-storefront" 57 | result: 58 | content: 59 | - type: "text" 60 | text: "match:contains:OCAPI client not configured" 61 | isError: true 62 | performance: 63 | maxResponseTime: "500ms" 64 | stderr: "toBeEmpty" 65 | 66 | - it: "should return authentication error for text query search requests" 67 | request: 68 | jsonrpc: "2.0" 69 | id: "auth-error-text-query" 70 | method: "tools/call" 71 | params: 72 | name: "search_site_preferences" 73 | arguments: 74 | groupId: "System" 75 | instanceType: "development" 76 | searchRequest: 77 | query: 78 | text_query: 79 | fields: ["id", "display_name"] 80 | search_phrase: "test" 81 | expect: 82 | response: 83 | jsonrpc: "2.0" 84 | id: "auth-error-text-query" 85 | result: 86 | content: 87 | - type: "text" 88 | text: "match:contains:credentials are provided" 89 | isError: true 90 | performance: 91 | maxResponseTime: "500ms" 92 | stderr: "toBeEmpty" 93 | 94 | - it: "should return authentication error for term query requests" 95 | request: 96 | jsonrpc: "2.0" 97 | id: "auth-error-term-query" 98 | method: "tools/call" 99 | params: 100 | name: "search_site_preferences" 101 | arguments: 102 | groupId: "SFRA" 103 | instanceType: "staging" 104 | searchRequest: 105 | query: 106 | term_query: 107 | fields: ["value_type"] 108 | operator: "is" 109 | values: ["string"] 110 | expect: 111 | response: 112 | jsonrpc: "2.0" 113 | id: "auth-error-term-query" 114 | result: 115 | content: 116 | - type: "text" 117 | text: "match:contains:OCAPI client not configured" 118 | isError: true 119 | performance: 120 | maxResponseTime: "500ms" 121 | stderr: "toBeEmpty" 122 | 123 | - it: "should return authentication error for boolean query requests" 124 | request: 125 | jsonrpc: "2.0" 126 | id: "auth-error-bool-query" 127 | method: "tools/call" 128 | params: 129 | name: "search_site_preferences" 130 | arguments: 131 | groupId: "Integration" 132 | instanceType: "production" 133 | searchRequest: 134 | query: 135 | bool_query: 136 | must: 137 | - text_query: 138 | fields: ["id"] 139 | search_phrase: "api" 140 | expect: 141 | response: 142 | jsonrpc: "2.0" 143 | id: "auth-error-bool-query" 144 | result: 145 | content: 146 | - type: "text" 147 | text: "match:contains:ensure credentials are provided" 148 | isError: true 149 | performance: 150 | maxResponseTime: "500ms" 151 | stderr: "toBeEmpty" 152 | 153 | - it: "should return authentication error regardless of instance type" 154 | request: 155 | jsonrpc: "2.0" 156 | id: "auth-error-sandbox" 157 | method: "tools/call" 158 | params: 159 | name: "search_site_preferences" 160 | arguments: 161 | groupId: "CCV" 162 | instanceType: "sandbox" 163 | searchRequest: 164 | query: 165 | match_all_query: {} 166 | count: 5 167 | expect: 168 | response: 169 | jsonrpc: "2.0" 170 | id: "auth-error-sandbox" 171 | result: 172 | content: 173 | - type: "text" 174 | text: "match:contains:OCAPI client not configured" 175 | isError: true 176 | performance: 177 | maxResponseTime: "500ms" 178 | stderr: "toBeEmpty" 179 | 180 | - it: "should return authentication error with options parameter" 181 | request: 182 | jsonrpc: "2.0" 183 | id: "auth-error-with-options" 184 | method: "tools/call" 185 | params: 186 | name: "search_site_preferences" 187 | arguments: 188 | groupId: "Storefront" 189 | instanceType: "sandbox" 190 | searchRequest: 191 | query: 192 | match_all_query: {} 193 | options: 194 | maskPasswords: false 195 | expand: "value" 196 | expect: 197 | response: 198 | jsonrpc: "2.0" 199 | id: "auth-error-with-options" 200 | result: 201 | content: 202 | - type: "text" 203 | text: "match:contains:OCAPI client not configured" 204 | isError: true 205 | performance: 206 | maxResponseTime: "500ms" 207 | stderr: "toBeEmpty" 208 | 209 | # ================================================================================== 210 | # PARAMETER VALIDATION IN DOCS-ONLY MODE 211 | # ================================================================================== 212 | - it: "should return authentication error even with missing required parameters" 213 | request: 214 | jsonrpc: "2.0" 215 | id: "auth-error-missing-params" 216 | method: "tools/call" 217 | params: 218 | name: "search_site_preferences" 219 | arguments: 220 | groupId: "Storefront" 221 | # Missing instanceType and searchRequest - but should get auth error first 222 | expect: 223 | response: 224 | jsonrpc: "2.0" 225 | id: "auth-error-missing-params" 226 | result: 227 | content: 228 | - type: "text" 229 | text: "match:regex:(OCAPI client not configured|required)" 230 | isError: true 231 | performance: 232 | maxResponseTime: "500ms" 233 | stderr: "toBeEmpty" 234 | 235 | # ================================================================================== 236 | # CONSISTENT ERROR MESSAGING 237 | # ================================================================================== 238 | - it: "should provide consistent error message across different preference groups" 239 | request: 240 | jsonrpc: "2.0" 241 | id: "consistent-error-message" 242 | method: "tools/call" 243 | params: 244 | name: "search_site_preferences" 245 | arguments: 246 | groupId: "FastForward" 247 | instanceType: "development" 248 | searchRequest: 249 | query: 250 | text_query: 251 | fields: ["description"] 252 | search_phrase: "feature" 253 | expect: 254 | response: 255 | jsonrpc: "2.0" 256 | id: "consistent-error-message" 257 | result: 258 | content: 259 | - type: "text" 260 | text: "match:regex:(OCAPI client not configured|ensure credentials are provided)" 261 | isError: true 262 | performance: 263 | maxResponseTime: "500ms" 264 | stderr: "toBeEmpty" 265 | 266 | # ================================================================================== 267 | # FAST ERROR RESPONSE VALIDATION 268 | # ================================================================================== 269 | - it: "should return authentication errors quickly without OCAPI calls" 270 | request: 271 | jsonrpc: "2.0" 272 | id: "fast-auth-error" 273 | method: "tools/call" 274 | params: 275 | name: "search_site_preferences" 276 | arguments: 277 | groupId: "Storefront" 278 | instanceType: "sandbox" 279 | searchRequest: 280 | query: 281 | match_all_query: {} 282 | count: 100 283 | start: 0 284 | expect: 285 | response: 286 | jsonrpc: "2.0" 287 | id: "fast-auth-error" 288 | result: 289 | content: 290 | - type: "text" 291 | text: "match:contains:OCAPI client not configured" 292 | isError: true 293 | performance: 294 | maxResponseTime: "300ms" # Should be very fast since no actual API call 295 | stderr: "toBeEmpty" ``` -------------------------------------------------------------------------------- /tests/config.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { loadSecureDwJson } from '../src/config/dw-json-loader.js'; 2 | import { ConfigurationFactory } from '../src/config/configuration-factory.js'; 3 | import { DwJsonConfig } from '../src/types/types.js'; 4 | import { existsSync, writeFileSync, unlinkSync, mkdirSync } from 'fs'; 5 | import { join } from 'path'; 6 | import { tmpdir } from 'os'; 7 | 8 | describe('dw-json-loader.ts and configuration-factory.ts', () => { 9 | // Create a temporary directory for test files 10 | const testDir = join(tmpdir(), 'sfcc-config-tests'); 11 | 12 | beforeAll(() => { 13 | if (!existsSync(testDir)) { 14 | mkdirSync(testDir, { recursive: true }); 15 | } 16 | }); 17 | 18 | afterAll(() => { 19 | // Clean up test files 20 | try { 21 | const testFiles = [ 22 | 'valid-dw.json', 23 | 'invalid-json.json', 24 | 'incomplete-dw.json', 25 | 'oauth-dw.json', 26 | 'invalid-hostname-dw.json', 27 | 'localhost-port-dw.json', 28 | 'custom-port-dw.json', 29 | ]; 30 | testFiles.forEach(file => { 31 | const filePath = join(testDir, file); 32 | if (existsSync(filePath)) { 33 | unlinkSync(filePath); 34 | } 35 | }); 36 | } catch { 37 | // Ignore cleanup errors 38 | } 39 | }); 40 | 41 | afterEach(() => { 42 | // Restore all mocks after each test 43 | jest.restoreAllMocks(); 44 | }); 45 | 46 | describe('loadSecureDwJson', () => { 47 | it('should load a valid dw.json file with basic auth', () => { 48 | const validDwJson: DwJsonConfig = { 49 | hostname: 'test-instance.demandware.net', 50 | username: 'testuser', 51 | password: 'testpass', 52 | 'client-id': '', 53 | 'client-secret': '', 54 | }; 55 | 56 | const testFile = join(testDir, 'valid-dw.json'); 57 | writeFileSync(testFile, JSON.stringify(validDwJson, null, 2)); 58 | 59 | const dwConfig = loadSecureDwJson(testFile); 60 | 61 | expect(dwConfig).toEqual(validDwJson); 62 | }); 63 | 64 | it('should load a valid dw.json file with OAuth credentials', () => { 65 | const oauthDwJson: DwJsonConfig = { 66 | hostname: 'test-instance.demandware.net', 67 | username: 'testuser', 68 | password: 'testpass', 69 | 'client-id': 'test-client-id', 70 | 'client-secret': 'test-client-secret', 71 | }; 72 | 73 | const testFile = join(testDir, 'oauth-dw.json'); 74 | writeFileSync(testFile, JSON.stringify(oauthDwJson, null, 2)); 75 | 76 | const dwConfig = loadSecureDwJson(testFile); 77 | 78 | expect(dwConfig).toEqual(oauthDwJson); 79 | }); 80 | 81 | it('should handle relative paths correctly', () => { 82 | const validDwJson: DwJsonConfig = { 83 | hostname: 'test-instance.demandware.net', 84 | username: 'testuser', 85 | password: 'testpass', 86 | }; 87 | 88 | const testFile = join(testDir, 'valid-dw.json'); 89 | writeFileSync(testFile, JSON.stringify(validDwJson, null, 2)); 90 | 91 | // Test with the actual test file path (not relative) 92 | const dwConfig = loadSecureDwJson(testFile); 93 | expect(dwConfig.hostname).toBe('test-instance.demandware.net'); 94 | }); 95 | 96 | it('should throw error for non-existent file', () => { 97 | const nonExistentFile = join(testDir, 'non-existent.json'); 98 | expect(() => { 99 | loadSecureDwJson(nonExistentFile); 100 | }).toThrow('Configuration file not found'); 101 | }); 102 | 103 | it('should throw error for invalid JSON', () => { 104 | const invalidJsonFile = join(testDir, 'invalid-json.json'); 105 | writeFileSync(invalidJsonFile, '{ invalid json }'); 106 | 107 | expect(() => { 108 | loadSecureDwJson(invalidJsonFile); 109 | }).toThrow('Invalid JSON in configuration file'); 110 | }); 111 | 112 | it('should throw error for incomplete configuration', () => { 113 | const incompleteDwJson = { 114 | hostname: 'test-instance.demandware.net', 115 | // Missing username and password 116 | }; 117 | 118 | const testFile = join(testDir, 'incomplete-dw.json'); 119 | writeFileSync(testFile, JSON.stringify(incompleteDwJson, null, 2)); 120 | 121 | expect(() => { 122 | loadSecureDwJson(testFile); 123 | }).toThrow('Configuration file must contain hostname, username, and password fields'); 124 | }); 125 | 126 | it('should throw error for invalid hostname format', () => { 127 | const invalidHostnameDwJson: DwJsonConfig = { 128 | hostname: 'invalid_hostname_with_underscores', 129 | username: 'testuser', 130 | password: 'testpass', 131 | }; 132 | 133 | const testFile = join(testDir, 'invalid-hostname-dw.json'); 134 | writeFileSync(testFile, JSON.stringify(invalidHostnameDwJson, null, 2)); 135 | 136 | expect(() => { 137 | loadSecureDwJson(testFile); 138 | }).toThrow('Invalid hostname format in configuration'); 139 | }); 140 | 141 | it('should load dw.json file with hostname containing port', () => { 142 | const dwJsonWithPort: DwJsonConfig = { 143 | hostname: 'localhost:3000', 144 | username: 'testuser', 145 | password: 'testpass', 146 | }; 147 | 148 | const testFile = join(testDir, 'localhost-port-dw.json'); 149 | writeFileSync(testFile, JSON.stringify(dwJsonWithPort, null, 2)); 150 | 151 | const dwConfig = loadSecureDwJson(testFile); 152 | expect(dwConfig.hostname).toBe('localhost:3000'); 153 | expect(dwConfig.username).toBe('testuser'); 154 | expect(dwConfig.password).toBe('testpass'); 155 | }); 156 | 157 | it('should load dw.json file with custom hostname and port', () => { 158 | const dwJsonWithCustomPort: DwJsonConfig = { 159 | hostname: 'test-instance.demandware.net:8080', 160 | username: 'testuser', 161 | password: 'testpass', 162 | }; 163 | 164 | const testFile = join(testDir, 'custom-port-dw.json'); 165 | writeFileSync(testFile, JSON.stringify(dwJsonWithCustomPort, null, 2)); 166 | 167 | const dwConfig = loadSecureDwJson(testFile); 168 | expect(dwConfig.hostname).toBe('test-instance.demandware.net:8080'); 169 | expect(dwConfig.username).toBe('testuser'); 170 | expect(dwConfig.password).toBe('testpass'); 171 | }); 172 | }); 173 | 174 | describe('ConfigurationFactory', () => { 175 | it('should create config from dw.json file', () => { 176 | const validDwJson: DwJsonConfig = { 177 | hostname: 'test-instance.demandware.net', 178 | username: 'testuser', 179 | password: 'testpass', 180 | }; 181 | 182 | const testFile = join(testDir, 'valid-dw.json'); 183 | writeFileSync(testFile, JSON.stringify(validDwJson, null, 2)); 184 | 185 | const config = ConfigurationFactory.create({ 186 | dwJsonPath: testFile, 187 | }); 188 | 189 | expect(config.hostname).toBe('test-instance.demandware.net'); 190 | expect(config.username).toBe('testuser'); 191 | expect(config.password).toBe('testpass'); 192 | }); 193 | 194 | it('should map OAuth credentials from dw.json', () => { 195 | const oauthDwJson: DwJsonConfig = { 196 | hostname: 'test-instance.demandware.net', 197 | username: 'testuser', 198 | password: 'testpass', 199 | 'client-id': 'test-client-id', 200 | 'client-secret': 'test-client-secret', 201 | }; 202 | 203 | const testFile = join(testDir, 'oauth-dw.json'); 204 | writeFileSync(testFile, JSON.stringify(oauthDwJson, null, 2)); 205 | 206 | const config = ConfigurationFactory.create({ 207 | dwJsonPath: testFile, 208 | }); 209 | 210 | expect(config.clientId).toBe('test-client-id'); 211 | expect(config.clientSecret).toBe('test-client-secret'); 212 | }); 213 | 214 | it('should override dw.json with command-line options', () => { 215 | const validDwJson: DwJsonConfig = { 216 | hostname: 'test-instance.demandware.net', 217 | username: 'testuser', 218 | password: 'testpass', 219 | }; 220 | 221 | const testFile = join(testDir, 'valid-dw.json'); 222 | writeFileSync(testFile, JSON.stringify(validDwJson, null, 2)); 223 | 224 | const config = ConfigurationFactory.create({ 225 | dwJsonPath: testFile, 226 | hostname: 'override-hostname.demandware.net', 227 | username: 'override-user', 228 | }); 229 | 230 | expect(config.hostname).toBe('override-hostname.demandware.net'); 231 | expect(config.username).toBe('override-user'); 232 | expect(config.password).toBe('testpass'); // Should keep from dw.json 233 | }); 234 | 235 | it('should create config from options only', () => { 236 | const config = ConfigurationFactory.create({ 237 | hostname: 'test-hostname.demandware.net', 238 | username: 'testuser', 239 | password: 'testpass', 240 | }); 241 | 242 | expect(config.hostname).toBe('test-hostname.demandware.net'); 243 | expect(config.username).toBe('testuser'); 244 | expect(config.password).toBe('testpass'); 245 | }); 246 | 247 | it('should create local mode config', () => { 248 | const config = ConfigurationFactory.createLocalMode(); 249 | 250 | expect(config.hostname).toBe(''); 251 | expect(config.username).toBeUndefined(); 252 | expect(config.password).toBeUndefined(); 253 | expect(config.clientId).toBeUndefined(); 254 | expect(config.clientSecret).toBeUndefined(); 255 | }); 256 | 257 | it('should validate configuration capabilities', () => { 258 | const config = ConfigurationFactory.create({ 259 | hostname: 'test-hostname.demandware.net', 260 | username: 'testuser', 261 | password: 'testpass', 262 | clientId: 'test-client-id', 263 | clientSecret: 'test-client-secret', 264 | }); 265 | 266 | const capabilities = ConfigurationFactory.getCapabilities(config); 267 | 268 | expect(capabilities.canAccessLogs).toBe(true); 269 | expect(capabilities.canAccessOCAPI).toBe(true); 270 | expect(capabilities.canAccessWebDAV).toBe(true); 271 | expect(capabilities.isLocalMode).toBe(false); 272 | }); 273 | 274 | it('should detect local mode', () => { 275 | const config = ConfigurationFactory.createLocalMode(); 276 | const capabilities = ConfigurationFactory.getCapabilities(config); 277 | 278 | expect(capabilities.canAccessLogs).toBe(false); 279 | expect(capabilities.canAccessOCAPI).toBe(false); 280 | expect(capabilities.canAccessWebDAV).toBe(false); 281 | expect(capabilities.isLocalMode).toBe(true); 282 | }); 283 | 284 | }); 285 | }); 286 | ``` -------------------------------------------------------------------------------- /docs/sfra/render.md: -------------------------------------------------------------------------------- ```markdown 1 | # Module render 2 | 3 | ## Description 4 | 5 | The SFRA render module is the core rendering engine that processes and outputs different types of response content in Salesforce Commerce Cloud's Storefront Reference Architecture (SFRA). This module handles the execution of rendering operations that have been queued in the Response object's renderings array during the request lifecycle. It supports multiple output formats including ISML templates, JSON responses, XML output, Page Designer pages, and direct text output. The render module serves as the final step in the SFRA request processing pipeline, converting accumulated view data and rendering instructions into the actual HTTP response content. 6 | 7 | ## Functions 8 | 9 | ### template 10 | 11 | **Signature:** `template(view, viewData) : void` 12 | 13 | Renders an ISML template with the provided data. 14 | 15 | ### json 16 | 17 | **Signature:** `json(data, response) : void` 18 | 19 | Renders an object as JSON output. 20 | 21 | ### xml 22 | 23 | **Signature:** `xml(viewData, response) : void` 24 | 25 | Renders an object as XML output with proper escaping. 26 | 27 | ### page 28 | 29 | **Signature:** `page(pageID, aspectAttributes, data, response) : void` 30 | 31 | Renders a Page Designer page with optional aspect attributes. 32 | 33 | ### applyRenderings 34 | 35 | **Signature:** `applyRenderings(res) : void` 36 | 37 | Processes all rendering instructions in the response object. 38 | 39 | ## Function Detail 40 | 41 | ### template 42 | 43 | **Signature:** `template(view, viewData) : void` 44 | 45 | **Description:** Renders an ISML template using the provided view data. Creates a shallow copy of the view data for isolation and uses SFCC's ISML rendering engine to process the template. 46 | 47 | **Parameters:** 48 | - `view` (String) - Path to the ISML template file 49 | - `viewData` (Object) - Data object to be passed as pdict to the template 50 | 51 | **Returns:** 52 | void 53 | 54 | **Throws:** 55 | Error with enhanced message including Java stack trace, file name, and line number information. 56 | 57 | **Processing:** 58 | 1. Creates shallow copy of view data to prevent modification 59 | 2. Calls `isml.renderTemplate()` with view path and data 60 | 3. Wraps any rendering errors with enhanced error information 61 | 62 | ### json 63 | 64 | **Signature:** `json(data, response) : void` 65 | 66 | **Description:** Renders the provided data as JSON output with proper content type headers and formatting. 67 | 68 | **Parameters:** 69 | - `data` (Object) - Object to be serialized as JSON 70 | - `response` (Response) - SFRA Response object 71 | 72 | **Returns:** 73 | void 74 | 75 | **Processing:** 76 | 1. Sets content type to `application/json` 77 | 2. Serializes data with JSON.stringify using 2-space indentation 78 | 3. Writes formatted JSON to response writer 79 | 80 | ### xml 81 | 82 | **Signature:** `xml(viewData, response) : void` 83 | 84 | **Description:** Renders the provided view data as XML output with proper character escaping and root element wrapping. 85 | 86 | **Parameters:** 87 | - `viewData` (Object) - Object containing data to be rendered as XML 88 | - `response` (Response) - SFRA Response object 89 | 90 | **Returns:** 91 | void 92 | 93 | **Throws:** 94 | Error with enhanced message including stack trace, file name, and line number information. 95 | 96 | **XML Character Escaping:** 97 | - `<` → `<` 98 | - `>` → `>` 99 | - `&` → `&` 100 | - `"` → `"` 101 | - `'` → `'` 102 | 103 | **Processing:** 104 | 1. Creates root `<response>` element 105 | 2. Processes each key in viewData: 106 | - If key is 'xml', includes raw XML content 107 | - Otherwise, creates element with escaped content 108 | 3. Closes root element 109 | 4. Sets content type to `application/xml` 110 | 5. Creates XML object and writes to response 111 | 112 | ### page 113 | 114 | **Signature:** `page(pageID, aspectAttributes, data, response) : void` 115 | 116 | **Description:** Renders a Page Designer page using SFCC's PageMgr with optional aspect attributes for advanced page management. 117 | 118 | **Parameters:** 119 | - `pageID` (String) - ID of the Page Designer page to render 120 | - `aspectAttributes` (dw.util.HashMap) - Optional aspect attributes for PageMgr 121 | - `data` (Object) - Data to be passed to the page 122 | - `response` (Response) - SFRA Response object 123 | 124 | **Returns:** 125 | void 126 | 127 | **Processing:** 128 | 1. Checks if aspect attributes exist and are not empty 129 | 2. Calls appropriate PageMgr.renderPage() method: 130 | - With aspect attributes: `PageMgr.renderPage(pageID, aspectAttributes, JSON.stringify(data))` 131 | - Without aspect attributes: `PageMgr.renderPage(pageID, JSON.stringify(data))` 132 | 3. Writes rendered page content to response writer 133 | 134 | ### applyRenderings 135 | 136 | **Signature:** `applyRenderings(res) : void` 137 | 138 | **Description:** Processes all queued rendering instructions in the response object's renderings array. This is the main entry point called by the Server during route completion. 139 | 140 | **Parameters:** 141 | - `res` (Response) - SFRA Response object containing renderings array 142 | 143 | **Returns:** 144 | void 145 | 146 | **Throws:** 147 | Error if no renderings are present or if invalid rendering type is encountered. 148 | 149 | **Rendering Types Processed:** 150 | 151 | **Render Instructions (`type: 'render'`):** 152 | - `subType: 'isml'` - Calls `template()` with view and viewData 153 | - `subType: 'json'` - Calls `json()` with viewData and response 154 | - `subType: 'xml'` - Calls `xml()` with viewData and response 155 | - `subType: 'page'` - Calls `page()` with page, aspectAttributes, viewData, and response 156 | 157 | **Print Instructions (`type: 'print'`):** 158 | - Directly writes message to response writer 159 | 160 | **Validation:** 161 | - Throws error if renderings array is empty 162 | - Throws error for unknown rendering types 163 | - Throws error for render instructions without valid subType 164 | 165 | ## Rendering Pipeline Integration 166 | 167 | ### Response Object Integration 168 | 169 | The render module works closely with the SFRA Response object: 170 | 171 | **Rendering Queue:** 172 | ```javascript 173 | res.renderings = [ 174 | { type: 'render', subType: 'isml', view: 'template/path' }, 175 | { type: 'print', message: 'Debug output' }, 176 | { type: 'render', subType: 'json' } 177 | ]; 178 | ``` 179 | 180 | **View Data Accumulation:** 181 | ```javascript 182 | res.viewData = { 183 | title: 'Page Title', 184 | products: [...], 185 | customer: {...} 186 | }; 187 | ``` 188 | 189 | ### Server Integration 190 | 191 | The Server class calls `applyRenderings()` during route completion: 192 | 193 | ```javascript 194 | route.on('route:Complete', function onRouteCompleteHandler(req, res) { 195 | // Cache and personalization handling 196 | if (res.redirectUrl) { 197 | // Handle redirects 198 | return; 199 | } 200 | render.applyRenderings(res); // Execute rendering pipeline 201 | }); 202 | ``` 203 | 204 | ## Content Type Management 205 | 206 | ### Automatic Content Types 207 | 208 | **JSON Responses:** 209 | - Sets `application/json` content type 210 | - Formats output with 2-space indentation 211 | 212 | **XML Responses:** 213 | - Sets `application/xml` content type 214 | - Automatically escapes special characters 215 | - Wraps content in root `<response>` element 216 | 217 | **ISML Templates:** 218 | - Content type determined by template output 219 | - Typically `text/html` for web pages 220 | 221 | **Page Designer:** 222 | - Content type managed by PageMgr 223 | - Usually `text/html` for rendered pages 224 | 225 | ## Error Handling 226 | 227 | ### Template Rendering Errors 228 | 229 | **ISML Template Errors:** 230 | - Catches Java exceptions from template engine 231 | - Enhances error with file name and line number 232 | - Includes original Java stack trace 233 | 234 | **XML Rendering Errors:** 235 | - Catches XML parsing/creation errors 236 | - Provides enhanced error messaging 237 | - Includes stack trace information 238 | 239 | ### Validation Errors 240 | 241 | **Missing Renderings:** 242 | - Throws error if renderings array is empty 243 | - Prevents silent failures in rendering pipeline 244 | 245 | **Invalid Rendering Types:** 246 | - Validates rendering instruction format 247 | - Throws descriptive errors for unknown types 248 | 249 | ## Usage Examples 250 | 251 | ### Basic Template Rendering 252 | ```javascript 253 | // In controller middleware 254 | res.render('product/productDetail', { 255 | product: productModel, 256 | breadcrumbs: breadcrumbsModel 257 | }); 258 | 259 | // Results in rendering instruction: 260 | { 261 | type: 'render', 262 | subType: 'isml', 263 | view: 'product/productDetail' 264 | } 265 | ``` 266 | 267 | ### JSON API Response 268 | ```javascript 269 | // In API controller 270 | res.json({ 271 | success: true, 272 | data: responseData, 273 | message: 'Operation completed' 274 | }); 275 | 276 | // Results in JSON output with proper headers 277 | ``` 278 | 279 | ### XML Response 280 | ```javascript 281 | // In XML endpoint 282 | res.xml({ 283 | status: 'success', 284 | xml: '<customData>...</customData>', 285 | timestamp: new Date().toISOString() 286 | }); 287 | 288 | // Results in properly escaped XML with root element 289 | ``` 290 | 291 | ### Page Designer Integration 292 | ```javascript 293 | // In Page Designer controller 294 | res.page('homepage', aspectAttributes, { 295 | banners: bannerData, 296 | featured: featuredProducts 297 | }); 298 | 299 | // Results in Page Designer page rendering 300 | ``` 301 | 302 | ### Mixed Output 303 | ```javascript 304 | // Debug output followed by template 305 | res.print('Debug: Processing complete'); 306 | res.render('checkout/confirmation', orderData); 307 | 308 | // Results in multiple rendering instructions executed in order 309 | ``` 310 | 311 | ## Performance Considerations 312 | 313 | ### Data Copying 314 | 315 | **Template Rendering:** 316 | - Creates shallow copy of view data for isolation 317 | - Prevents template modifications from affecting response object 318 | - Minimal performance impact for typical data sizes 319 | 320 | ### Memory Management 321 | 322 | **Large Data Sets:** 323 | - JSON serialization with formatting may increase memory usage 324 | - XML escaping creates additional string copies 325 | - Consider streaming for very large responses 326 | 327 | ### Caching Integration 328 | 329 | **Template Caching:** 330 | - ISML templates are cached by SFCC platform 331 | - View data is not cached at render level 332 | - Page Designer pages benefit from platform caching 333 | 334 | ## Security Considerations 335 | 336 | ### XML Security 337 | 338 | **Character Escaping:** 339 | - Prevents XML injection attacks 340 | - Escapes all potentially dangerous characters 341 | - Safe handling of user-generated content 342 | 343 | **Content Validation:** 344 | - XML content must be well-formed 345 | - Error handling prevents partial output 346 | 347 | ### Template Security 348 | 349 | **Data Isolation:** 350 | - Shallow copy prevents template-induced data corruption 351 | - Original response data remains unchanged 352 | - Safe passing of sensitive data to templates 353 | 354 | --- 355 | ``` -------------------------------------------------------------------------------- /tests/job-log-handler.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { JobLogToolHandler } from '../src/core/handlers/job-log-handler.js'; 2 | import { HandlerContext } from '../src/core/handlers/base-handler.js'; 3 | import { SFCCLogClient } from '../src/clients/log-client.js'; 4 | import { Logger } from '../src/utils/logger.js'; 5 | 6 | // Mock dependencies 7 | jest.mock('../src/clients/log-client.js'); 8 | jest.mock('../src/utils/logger.js'); 9 | 10 | describe('JobLogToolHandler', () => { 11 | let mockContext: HandlerContext; 12 | let handler: JobLogToolHandler; 13 | let mockLogger: jest.Mocked<Logger>; 14 | let mockLogClient: jest.Mocked<SFCCLogClient>; 15 | 16 | beforeEach(() => { 17 | jest.clearAllMocks(); 18 | 19 | mockLogger = { 20 | debug: jest.fn(), 21 | info: jest.fn(), 22 | warn: jest.fn(), 23 | error: jest.fn(), 24 | methodEntry: jest.fn(), 25 | methodExit: jest.fn(), 26 | timing: jest.fn(), 27 | log: jest.fn(), 28 | } as any; 29 | 30 | mockLogClient = { 31 | getLatestJobLogFiles: jest.fn(), 32 | searchJobLogsByName: jest.fn(), 33 | getJobLogEntries: jest.fn(), 34 | searchJobLogs: jest.fn(), 35 | getJobExecutionSummary: jest.fn(), 36 | } as any; 37 | 38 | // Mock the SFCCLogClient constructor 39 | (SFCCLogClient as jest.MockedClass<typeof SFCCLogClient>).mockImplementation(() => mockLogClient); 40 | 41 | // Mock Logger.getChildLogger 42 | jest.spyOn(Logger, 'getChildLogger').mockReturnValue(mockLogger); 43 | 44 | mockContext = { 45 | logger: mockLogger, 46 | config: { 47 | hostname: 'test.demandware.net', 48 | username: 'test', 49 | password: 'test', 50 | clientId: 'test', 51 | clientSecret: 'test', 52 | }, 53 | capabilities: { 54 | canAccessLogs: true, 55 | canAccessOCAPI: true, 56 | }, 57 | }; 58 | 59 | handler = new JobLogToolHandler(mockContext, 'job-log-handler'); 60 | }); 61 | 62 | afterEach(() => { 63 | jest.restoreAllMocks(); 64 | }); 65 | 66 | // Helper function to initialize handler 67 | const initializeHandler = async () => { 68 | await (handler as any).initialize(); 69 | }; 70 | 71 | describe('canHandle', () => { 72 | it('should handle job log tool names', () => { 73 | const jobLogTools = [ 74 | 'get_latest_job_log_files', 75 | 'search_job_logs_by_name', 76 | 'get_job_log_entries', 77 | 'search_job_logs', 78 | 'get_job_execution_summary', 79 | ]; 80 | 81 | jobLogTools.forEach(toolName => { 82 | expect(handler.canHandle(toolName)).toBe(true); 83 | }); 84 | }); 85 | 86 | it('should not handle non-job-log tool names', () => { 87 | const nonJobLogTools = [ 88 | 'get_latest_error', 89 | 'search_logs', 90 | 'get_log_file_contents', 91 | 'summarize_logs', 92 | 'list_log_files', 93 | 'unknown_tool', 94 | '', 95 | ]; 96 | 97 | nonJobLogTools.forEach(toolName => { 98 | expect(handler.canHandle(toolName)).toBe(false); 99 | }); 100 | }); 101 | 102 | it('should handle case-sensitive tool names only', () => { 103 | expect(handler.canHandle('GET_LATEST_JOB_LOG_FILES')).toBe(false); 104 | expect(handler.canHandle('get_Latest_Job_Log_Files')).toBe(false); 105 | expect(handler.canHandle('get_latest_job_log_files')).toBe(true); 106 | }); 107 | }); 108 | 109 | describe('handle method - get_latest_job_log_files', () => { 110 | beforeEach(async () => { 111 | await initializeHandler(); 112 | }); 113 | 114 | it('should handle get_latest_job_log_files with default limit', async () => { 115 | const mockResult = 'Latest job log files result'; 116 | mockLogClient.getLatestJobLogFiles.mockResolvedValue(mockResult); 117 | 118 | const result = await handler.handle('get_latest_job_log_files', {}, Date.now()); 119 | 120 | expect(mockLogClient.getLatestJobLogFiles).toHaveBeenCalledWith(10); // default limit 121 | expect(result.content[0].text).toContain(mockResult); 122 | }); 123 | 124 | it('should handle get_latest_job_log_files with custom limit', async () => { 125 | const mockResult = 'Latest job log files result'; 126 | mockLogClient.getLatestJobLogFiles.mockResolvedValue(mockResult); 127 | 128 | const result = await handler.handle('get_latest_job_log_files', { limit: 25 }, Date.now()); 129 | 130 | expect(mockLogClient.getLatestJobLogFiles).toHaveBeenCalledWith(25); 131 | expect(result.content[0].text).toContain(mockResult); 132 | }); 133 | 134 | it('should validate limit parameter', async () => { 135 | const result = await handler.handle('get_latest_job_log_files', { limit: -1 }, Date.now()); 136 | 137 | expect(result.isError).toBe(true); 138 | expect(result.content[0].text).toContain('Invalid limit'); 139 | }); 140 | }); 141 | 142 | describe('handle method - search_job_logs_by_name', () => { 143 | beforeEach(async () => { 144 | await initializeHandler(); 145 | }); 146 | 147 | it('should handle search_job_logs_by_name with required jobName', async () => { 148 | const mockResult = 'Search job logs by name result'; 149 | mockLogClient.searchJobLogsByName.mockResolvedValue(mockResult); 150 | 151 | const result = await handler.handle('search_job_logs_by_name', { 152 | jobName: 'TestJob', 153 | limit: 15, 154 | }, Date.now()); 155 | 156 | expect(mockLogClient.searchJobLogsByName).toHaveBeenCalledWith('TestJob', 15); 157 | expect(result.content[0].text).toContain(mockResult); 158 | }); 159 | 160 | it('should validate required jobName parameter', async () => { 161 | const result = await handler.handle('search_job_logs_by_name', {}, Date.now()); 162 | 163 | expect(result.isError).toBe(true); 164 | expect(result.content[0].text).toContain('jobName must be a non-empty string'); 165 | }); 166 | }); 167 | 168 | describe('handle method - get_job_log_entries', () => { 169 | beforeEach(async () => { 170 | await initializeHandler(); 171 | }); 172 | 173 | it('should handle get_job_log_entries with default parameters', async () => { 174 | const mockResult = 'Job log entries result'; 175 | mockLogClient.getJobLogEntries.mockResolvedValue(mockResult); 176 | 177 | const result = await handler.handle('get_job_log_entries', {}, Date.now()); 178 | 179 | expect(mockLogClient.getJobLogEntries).toHaveBeenCalledWith('all', 50, undefined); // default limit is 50 for job entries 180 | expect(result.content[0].text).toContain(mockResult); 181 | }); 182 | 183 | it('should validate log level parameter', async () => { 184 | const result = await handler.handle('get_job_log_entries', { level: 'invalid' }, Date.now()); 185 | 186 | expect(result.isError).toBe(true); 187 | expect(result.content[0].text).toContain('Invalid log level: invalid'); 188 | }); 189 | }); 190 | 191 | describe('handle method - search_job_logs', () => { 192 | beforeEach(async () => { 193 | await initializeHandler(); 194 | }); 195 | 196 | it('should handle search_job_logs with required pattern', async () => { 197 | const mockResult = 'Search job logs result'; 198 | mockLogClient.searchJobLogs.mockResolvedValue(mockResult); 199 | 200 | const result = await handler.handle('search_job_logs', { 201 | pattern: 'error-pattern', 202 | }, Date.now()); 203 | 204 | expect(mockLogClient.searchJobLogs).toHaveBeenCalledWith('error-pattern', 'all', 20, undefined); 205 | expect(result.content[0].text).toContain(mockResult); 206 | }); 207 | 208 | it('should validate required pattern parameter', async () => { 209 | const result = await handler.handle('search_job_logs', {}, Date.now()); 210 | 211 | expect(result.isError).toBe(true); 212 | expect(result.content[0].text).toContain('pattern must be a non-empty string'); 213 | }); 214 | }); 215 | 216 | describe('handle method - get_job_execution_summary', () => { 217 | beforeEach(async () => { 218 | await initializeHandler(); 219 | }); 220 | 221 | it('should handle get_job_execution_summary with required jobName', async () => { 222 | const mockResult = 'Job execution summary result'; 223 | mockLogClient.getJobExecutionSummary.mockResolvedValue(mockResult); 224 | 225 | const result = await handler.handle('get_job_execution_summary', { 226 | jobName: 'SummaryJob', 227 | }, Date.now()); 228 | 229 | expect(mockLogClient.getJobExecutionSummary).toHaveBeenCalledWith('SummaryJob'); 230 | expect(result.content[0].text).toContain(mockResult); 231 | }); 232 | 233 | it('should validate required jobName parameter', async () => { 234 | const result = await handler.handle('get_job_execution_summary', {}, Date.now()); 235 | 236 | expect(result.isError).toBe(true); 237 | expect(result.content[0].text).toContain('jobName must be a non-empty string'); 238 | }); 239 | }); 240 | 241 | describe('error handling', () => { 242 | beforeEach(async () => { 243 | await initializeHandler(); 244 | }); 245 | 246 | it('should handle client errors gracefully', async () => { 247 | const clientError = new Error('Client connection failed'); 248 | mockLogClient.getLatestJobLogFiles.mockRejectedValue(clientError); 249 | 250 | const result = await handler.handle('get_latest_job_log_files', {}, Date.now()); 251 | 252 | expect(result.isError).toBe(true); 253 | expect(result.content[0].text).toContain('Client connection failed'); 254 | }); 255 | 256 | it('should handle unsupported tool names', async () => { 257 | await expect(handler.handle('unsupported_tool', {}, Date.now())) 258 | .rejects.toThrow('Unsupported tool: unsupported_tool'); 259 | }); 260 | }); 261 | 262 | describe('initialization', () => { 263 | it('should initialize log client when capabilities allow', async () => { 264 | await initializeHandler(); 265 | 266 | expect(SFCCLogClient).toHaveBeenCalledWith(mockContext.config); 267 | expect(mockLogger.debug).toHaveBeenCalledWith('Log client initialized'); 268 | }); 269 | 270 | it('should handle missing capabilities gracefully', async () => { 271 | const contextWithoutLogs = { 272 | ...mockContext, 273 | capabilities: { canAccessLogs: false, canAccessOCAPI: false }, 274 | }; 275 | const handlerWithoutLogs = new JobLogToolHandler(contextWithoutLogs, 'job-log'); 276 | 277 | const result = await handlerWithoutLogs.handle('get_latest_job_log_files', {}, Date.now()); 278 | 279 | expect(result.isError).toBe(true); 280 | expect(result.content[0].text).toContain('Log client not configured'); 281 | }); 282 | }); 283 | }); 284 | ``` -------------------------------------------------------------------------------- /src/clients/logs/log-file-discovery.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Log file discovery, filtering, and metadata operations 3 | */ 4 | 5 | import type { WebDAVClient } from 'webdav'; 6 | import { Logger } from '../../utils/logger.js'; 7 | import { getCurrentDate, normalizeFilePath } from '../../utils/utils.js'; 8 | import { LOG_CONSTANTS, LOG_FILE_PATTERNS, JOB_LOG_CONSTANTS } from './log-constants.js'; 9 | import type { LogFileMetadata, LogFileInfo, LogLevel, LogFileFilter, JobLogInfo, JobLogFilter } from './log-types.js'; 10 | 11 | export class LogFileDiscovery { 12 | private logger: Logger; 13 | private webdavClient: WebDAVClient; 14 | 15 | constructor(webdavClient: WebDAVClient, logger: Logger) { 16 | this.webdavClient = webdavClient; 17 | this.logger = logger; 18 | } 19 | 20 | /** 21 | * Get list of log files for a specific date 22 | */ 23 | async getLogFiles(date?: string): Promise<LogFileMetadata[]> { 24 | const targetDate = date ?? getCurrentDate(); 25 | this.logger.methodEntry('getLogFiles', { date: targetDate }); 26 | 27 | const startTime = Date.now(); 28 | const contents = await this.webdavClient.getDirectoryContents('/'); 29 | this.logger.timing('webdav_getDirectoryContents', startTime); 30 | 31 | const logFiles = (contents as any[]) 32 | .filter((item: any) => 33 | item.type === 'file' && 34 | item.filename.includes(targetDate) && 35 | item.filename.endsWith('.log'), 36 | ) 37 | .map((item: any) => ({ 38 | filename: item.filename, 39 | lastmod: item.lastmod ?? new Date().toISOString(), // Fallback to current time if no lastmod 40 | })); 41 | 42 | this.logger.debug(`Found ${logFiles.length} log files for date ${targetDate}:`, logFiles.map((f: LogFileMetadata) => f.filename)); 43 | this.logger.methodExit('getLogFiles', { count: logFiles.length }); 44 | return logFiles; 45 | } 46 | 47 | /** 48 | * Filter log files by level and other criteria 49 | */ 50 | filterLogFiles(files: LogFileMetadata[], filter: LogFileFilter): LogFileMetadata[] { 51 | if (!filter.level) { 52 | return files; 53 | } 54 | 55 | const { level, includeCustom = true } = filter; 56 | 57 | return files.filter(file => { 58 | const filename = normalizeFilePath(file.filename); 59 | const standardPattern = LOG_FILE_PATTERNS.STANDARD(level); 60 | const customPattern = LOG_FILE_PATTERNS.CUSTOM(level); 61 | 62 | if (filename.startsWith(standardPattern)) { 63 | return true; 64 | } 65 | 66 | if (includeCustom && filename.startsWith(customPattern)) { 67 | return true; 68 | } 69 | 70 | return false; 71 | }); 72 | } 73 | 74 | /** 75 | * Get log files filtered by level with detailed logging 76 | */ 77 | async getLogFilesByLevel(level: LogLevel, date?: string): Promise<LogFileMetadata[]> { 78 | const targetDate = date ?? getCurrentDate(); 79 | const allFiles = await this.getLogFiles(targetDate); 80 | 81 | const filteredFiles = this.filterLogFiles(allFiles, { level, includeCustom: true }); 82 | 83 | this.logger.debug(`Filtered to ${filteredFiles.length} ${level} log files (including custom logs):`, 84 | filteredFiles.map(f => f.filename)); 85 | 86 | return filteredFiles; 87 | } 88 | 89 | /** 90 | * Sort log files by modification date 91 | */ 92 | sortFilesByDate(files: LogFileMetadata[], descending = true): LogFileMetadata[] { 93 | return files.sort((a, b) => { 94 | const dateA = new Date(a.lastmod).getTime(); 95 | const dateB = new Date(b.lastmod).getTime(); 96 | return descending ? dateB - dateA : dateA - dateB; 97 | }); 98 | } 99 | 100 | /** 101 | * Get all available log files with detailed metadata 102 | */ 103 | async getAllLogFiles(): Promise<LogFileInfo[]> { 104 | try { 105 | const contents = await this.webdavClient.getDirectoryContents('/'); 106 | const allLogFiles = (contents as any[]) 107 | .filter((item: any) => item.type === 'file' && item.filename.endsWith('.log')); 108 | 109 | const logFiles: LogFileInfo[] = allLogFiles 110 | .map((item: any) => ({ 111 | name: item.filename, 112 | size: item.size, 113 | lastModified: item.lastmod, 114 | })) 115 | // Sort by lastModified date in descending order (newest first) 116 | .sort((a: LogFileInfo, b: LogFileInfo) => { 117 | const dateA = new Date(a.lastModified).getTime(); 118 | const dateB = new Date(b.lastModified).getTime(); 119 | return dateB - dateA; 120 | }) 121 | // Limit to 50 most recent files 122 | .slice(0, LOG_CONSTANTS.MAX_LOG_FILES_DISPLAY); 123 | 124 | // Store total count for formatting 125 | (logFiles as any).totalCount = allLogFiles.length; 126 | 127 | return logFiles; 128 | } catch (error) { 129 | throw new Error(`Failed to list log files: ${error instanceof Error ? error.message : String(error)}`); 130 | } 131 | } 132 | 133 | /** 134 | * Get unique log levels available for a specific date 135 | */ 136 | async getAvailableLogLevels(date?: string): Promise<LogLevel[]> { 137 | const files = await this.getLogFiles(date); 138 | const levels = new Set<LogLevel>(); 139 | 140 | for (const file of files) { 141 | const filename = normalizeFilePath(file.filename); 142 | 143 | for (const level of LOG_CONSTANTS.LOG_LEVELS) { 144 | if (filename.startsWith(LOG_FILE_PATTERNS.STANDARD(level)) || 145 | filename.startsWith(LOG_FILE_PATTERNS.CUSTOM(level))) { 146 | levels.add(level); 147 | } 148 | } 149 | } 150 | 151 | return Array.from(levels); 152 | } 153 | 154 | /** 155 | * Get log file statistics for a date 156 | */ 157 | async getLogFileStats(date?: string): Promise<{ 158 | totalFiles: number; 159 | filesByLevel: Record<LogLevel, number>; 160 | totalSize: number; 161 | oldestFile?: string; 162 | newestFile?: string; 163 | }> { 164 | const files = await this.getLogFiles(date); 165 | const sortedFiles = this.sortFilesByDate(files, true); 166 | 167 | const stats = { 168 | totalFiles: files.length, 169 | filesByLevel: {} as Record<LogLevel, number>, 170 | totalSize: 0, 171 | oldestFile: sortedFiles[sortedFiles.length - 1]?.filename, 172 | newestFile: sortedFiles[0]?.filename, 173 | }; 174 | 175 | // Initialize level counts 176 | for (const level of LOG_CONSTANTS.LOG_LEVELS) { 177 | stats.filesByLevel[level] = 0; 178 | } 179 | 180 | // Count files by level 181 | for (const file of files) { 182 | for (const level of LOG_CONSTANTS.LOG_LEVELS) { 183 | const filteredFiles = this.filterLogFiles([file], { level }); 184 | if (filteredFiles.length > 0) { 185 | stats.filesByLevel[level]++; 186 | } 187 | } 188 | } 189 | 190 | return stats; 191 | } 192 | 193 | /** 194 | * Get job log files from the /jobs/ folder structure 195 | */ 196 | async getJobLogFiles(filter?: JobLogFilter): Promise<JobLogInfo[]> { 197 | this.logger.methodEntry('getJobLogFiles', filter); 198 | 199 | try { 200 | // List all directories in the jobs folder 201 | const jobsContents = await this.webdavClient.getDirectoryContents(JOB_LOG_CONSTANTS.JOBS_FOLDER); 202 | 203 | const jobDirs = (jobsContents as any[]) 204 | .filter((item: any) => item.type === 'directory') 205 | .map((item: any) => ({ 206 | name: item.filename.replace(JOB_LOG_CONSTANTS.JOBS_FOLDER, ''), 207 | path: item.filename, 208 | lastModified: item.lastmod ?? new Date().toISOString(), 209 | })); 210 | 211 | this.logger.debug(`Found ${jobDirs.length} job directories`); 212 | 213 | const jobLogInfos: JobLogInfo[] = []; 214 | 215 | // Process each job directory to find log files 216 | for (const jobDir of jobDirs) { 217 | try { 218 | const jobContents = await this.webdavClient.getDirectoryContents(jobDir.path); 219 | const logFiles = (jobContents as any[]) 220 | .filter((item: any) => 221 | item.type === 'file' && 222 | JOB_LOG_CONSTANTS.JOB_LOG_PATTERN.test(item.filename.split('/').pop() ?? ''), 223 | ); 224 | 225 | for (const logFile of logFiles) { 226 | const fileName = logFile.filename.split('/').pop() ?? ''; 227 | jobLogInfos.push({ 228 | jobName: decodeURIComponent(jobDir.name), 229 | jobId: this.extractJobIdFromFilename(fileName), 230 | logFile: logFile.filename, 231 | lastModified: logFile.lastmod ?? jobDir.lastModified, 232 | size: logFile.size, 233 | }); 234 | } 235 | } catch (error) { 236 | this.logger.warn(`Failed to read job directory ${jobDir.name}: ${error}`); 237 | } 238 | } 239 | 240 | // Apply filtering 241 | let filteredLogs = jobLogInfos; 242 | 243 | if (filter?.jobName) { 244 | const searchName = filter.jobName.toLowerCase(); 245 | filteredLogs = filteredLogs.filter(log => 246 | log.jobName.toLowerCase().includes(searchName), 247 | ); 248 | } 249 | 250 | // Sort by most recent first if requested (default behavior) 251 | if (filter?.sortByRecent !== false) { 252 | filteredLogs.sort((a, b) => 253 | new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime(), 254 | ); 255 | } 256 | 257 | // Apply limit 258 | if (filter?.limit) { 259 | filteredLogs = filteredLogs.slice(0, filter.limit); 260 | } 261 | 262 | this.logger.methodExit('getJobLogFiles', { count: filteredLogs.length }); 263 | return filteredLogs; 264 | 265 | } catch (error) { 266 | this.logger.error(`Failed to get job log files: ${error}`); 267 | throw new Error(`Failed to access job logs: ${error instanceof Error ? error.message : String(error)}`); 268 | } 269 | } 270 | 271 | /** 272 | * Get the latest job log files, sorted by modification date 273 | */ 274 | async getLatestJobLogFiles(limit?: number): Promise<JobLogInfo[]> { 275 | return this.getJobLogFiles({ 276 | limit: limit ?? JOB_LOG_CONSTANTS.DEFAULT_JOB_LOG_LIMIT, 277 | sortByRecent: true, 278 | }); 279 | } 280 | 281 | /** 282 | * Search for job logs by job name 283 | */ 284 | async searchJobLogsByName(jobName: string, limit?: number): Promise<JobLogInfo[]> { 285 | return this.getJobLogFiles({ 286 | jobName, 287 | limit: limit ?? JOB_LOG_CONSTANTS.DEFAULT_JOB_LOG_LIMIT, 288 | sortByRecent: true, 289 | }); 290 | } 291 | 292 | /** 293 | * Extract job ID from filename (Job-JobName-ID.log format) 294 | */ 295 | private extractJobIdFromFilename(filename: string): string { 296 | const match = filename.match(/Job-.+-([^.]+)\.log$/); 297 | return match ? match[1] : 'unknown'; 298 | } 299 | } 300 | ``` -------------------------------------------------------------------------------- /docs/dw_catalog/StoreMgr.md: -------------------------------------------------------------------------------- ```markdown 1 | ## Package: dw.catalog 2 | 3 | # Class StoreMgr 4 | 5 | ## Inheritance Hierarchy 6 | 7 | - Object 8 | - dw.catalog.StoreMgr 9 | 10 | ## Description 11 | 12 | Provides helper methods for getting stores based on id and querying for stores based on geolocation. 13 | 14 | ## Properties 15 | 16 | ### allStoreGroups 17 | 18 | **Type:** Collection (Read Only) 19 | 20 | All the store groups of the current site. 21 | 22 | ### storeIDFromSession 23 | 24 | **Type:** String (Read Only) 25 | 26 | Get the store id associated with the current session. By default, the session store id is null. 27 | 28 | ## Constructor Summary 29 | 30 | ## Method Summary 31 | 32 | ### getAllStoreGroups 33 | 34 | **Signature:** `static getAllStoreGroups() : Collection` 35 | 36 | Returns all the store groups of the current site. 37 | 38 | ### getStore 39 | 40 | **Signature:** `static getStore(storeID : String) : Store` 41 | 42 | Returns the store object with the specified id or null if store with this id does not exist in the site. 43 | 44 | ### getStoreGroup 45 | 46 | **Signature:** `static getStoreGroup(storeGroupID : String) : StoreGroup` 47 | 48 | Returns the store group with the specified id or null if the store group with this id does not exist in the current site. 49 | 50 | ### getStoreIDFromSession 51 | 52 | **Signature:** `static getStoreIDFromSession() : String` 53 | 54 | Get the store id associated with the current session. 55 | 56 | ### searchStoresByCoordinates 57 | 58 | **Signature:** `static searchStoresByCoordinates(latitude : Number, longitude : Number, distanceUnit : String, maxDistance : Number, queryString : String, args : Object...) : LinkedHashMap` 59 | 60 | Search for stores based on geo-coordinates. 61 | 62 | ### searchStoresByCoordinates 63 | 64 | **Signature:** `static searchStoresByCoordinates(latitude : Number, longitude : Number, distanceUnit : String, maxDistance : Number) : LinkedHashMap` 65 | 66 | Convenience method. 67 | 68 | ### searchStoresByPostalCode 69 | 70 | **Signature:** `static searchStoresByPostalCode(countryCode : String, postalCode : String, distanceUnit : String, maxDistance : Number, queryString : String, args : Object...) : LinkedHashMap` 71 | 72 | Search for stores by country/postal code and optionally by additional filter criteria. 73 | 74 | ### searchStoresByPostalCode 75 | 76 | **Signature:** `static searchStoresByPostalCode(countryCode : String, postalCode : String, distanceUnit : String, maxDistance : Number) : LinkedHashMap` 77 | 78 | Convenience method. 79 | 80 | ### setStoreIDToSession 81 | 82 | **Signature:** `static setStoreIDToSession(storeID : String) : void` 83 | 84 | Set the store id for the current session. 85 | 86 | ## Method Detail 87 | 88 | ## Method Details 89 | 90 | ### getAllStoreGroups 91 | 92 | **Signature:** `static getAllStoreGroups() : Collection` 93 | 94 | **Description:** Returns all the store groups of the current site. 95 | 96 | **Returns:** 97 | 98 | The store groups of the current site. 99 | 100 | --- 101 | 102 | ### getStore 103 | 104 | **Signature:** `static getStore(storeID : String) : Store` 105 | 106 | **Description:** Returns the store object with the specified id or null if store with this id does not exist in the site. 107 | 108 | **Parameters:** 109 | 110 | - `storeID`: the store identifier. 111 | 112 | **Returns:** 113 | 114 | Store for specified id or null. 115 | 116 | --- 117 | 118 | ### getStoreGroup 119 | 120 | **Signature:** `static getStoreGroup(storeGroupID : String) : StoreGroup` 121 | 122 | **Description:** Returns the store group with the specified id or null if the store group with this id does not exist in the current site. 123 | 124 | **Parameters:** 125 | 126 | - `storeGroupID`: the store group identifier. 127 | 128 | **Returns:** 129 | 130 | The store group for specified id or null. 131 | 132 | --- 133 | 134 | ### getStoreIDFromSession 135 | 136 | **Signature:** `static getStoreIDFromSession() : String` 137 | 138 | **Description:** Get the store id associated with the current session. By default, the session store id is null. 139 | 140 | **Returns:** 141 | 142 | store id, null is returned and means no store id is set on session 143 | 144 | --- 145 | 146 | ### searchStoresByCoordinates 147 | 148 | **Signature:** `static searchStoresByCoordinates(latitude : Number, longitude : Number, distanceUnit : String, maxDistance : Number, queryString : String, args : Object...) : LinkedHashMap` 149 | 150 | **Description:** Search for stores based on geo-coordinates. The method returns a list of stores for the current site that are within a specified distance of a location on the earth and which optionally satisfy additional filter criteria. If no additional criteria are specified, then this method behaves similar to GetNearestStores pipelet. The criteria are specified as a querystring, using the same syntax as SystemObjectMgr.querySystemObjects(String, String, String, Object...) The stores and their distance from the specified location are returned as a LinkedHashMap of Store objects to distances, sorting in ascending order by distance. The distance is interpreted either in miles or kilometers depending on the "distanceUnit" parameter. The latitude and longitude of each store is determined by first looking at Store.getLatitude() and Store.getLongitude(). If these are not set, then the system deduces the location of the store by looking for a configured geolocation matching the store's postal and country codes. 151 | 152 | **Parameters:** 153 | 154 | - `latitude`: Latitude coordinate which is the center of the search area. Must not be null or an exception is thrown. 155 | - `longitude`: Longitude coordinate which is the center of the search area. Must not be null or an exception is thrown. 156 | - `distanceUnit`: The distance unit to be used for the calculation. Supported values are 'mi' and 'km' (for miles and kilometers respectively). If an invalid value is passed then 'km' will be used. 157 | - `maxDistance`: Area (radius) in DistanceUnit where Stores will be searched for. If null is passed, a system default is used. 158 | - `queryString`: optional filter criteria specified as querystring. 159 | - `args`: the arguments to fill in the values in the querystring. 160 | 161 | **Returns:** 162 | 163 | The stores and their distance from the specified location, sorted in ascending order by distance. 164 | 165 | --- 166 | 167 | ### searchStoresByCoordinates 168 | 169 | **Signature:** `static searchStoresByCoordinates(latitude : Number, longitude : Number, distanceUnit : String, maxDistance : Number) : LinkedHashMap` 170 | 171 | **Description:** Convenience method. Same as searchStoresByCoordinates(latitude, longitude, distanceUnit, maxDistance, null). 172 | 173 | **Parameters:** 174 | 175 | - `latitude`: Latitude coordinate which is the center of the search area. Must not be null or an exception is thrown. 176 | - `longitude`: Longitude coordinate which is the center of the search area. Must not be null or an exception is thrown. 177 | - `distanceUnit`: The distance unit to be used for the calculation. Supported values are 'mi' and 'km' (for miles and kilometers respectively). If an invalid value is passed then 'km' will be used. 178 | - `maxDistance`: Area (radius) in DistanceUnit where Stores will be searched for. If null is passed, a system default is used. 179 | 180 | **Returns:** 181 | 182 | The stores and their distance from the specified location, sorted in ascending order by distance. 183 | 184 | --- 185 | 186 | ### searchStoresByPostalCode 187 | 188 | **Signature:** `static searchStoresByPostalCode(countryCode : String, postalCode : String, distanceUnit : String, maxDistance : Number, queryString : String, args : Object...) : LinkedHashMap` 189 | 190 | **Description:** Search for stores by country/postal code and optionally by additional filter criteria. This method is analagous to searchStoresByCoordinates(Number, Number, String, Number, String, Object...) but identifies a location on the earth indirectly using country and postal code. The method will look first in the saved geolocations of the system to find a geolocation matching the passed country and postal code. If none is found, this method will return an empty map. If one is found, it will use the latitude/longitude of the found geolocation as the center of the search. 191 | 192 | **Parameters:** 193 | 194 | - `countryCode`: The country code for the search area, must not be null. 195 | - `postalCode`: The postal code for the center of the search area, must not be null. 196 | - `distanceUnit`: The distance unit to be used for the calculation. Supported values are 'mi' and 'km' (for miles and kilometers respectively). If an invalid value is passed then 'km' will be used. 197 | - `maxDistance`: Area (radius) in DistanceUnit where Stores will be searched for. If null is passed, a system default is used. 198 | - `queryString`: An optional search querystring which provides additional criteria to filter stores by. 199 | - `args`: The arguments providing the dynamic values to the querystring. 200 | 201 | **Returns:** 202 | 203 | The stores and their distance from the specified location, sorted in ascending order by distance. 204 | 205 | --- 206 | 207 | ### searchStoresByPostalCode 208 | 209 | **Signature:** `static searchStoresByPostalCode(countryCode : String, postalCode : String, distanceUnit : String, maxDistance : Number) : LinkedHashMap` 210 | 211 | **Description:** Convenience method. Same as searchStoresByPostalCode(countryCode, postalCode, distanceUnit, maxDistance, null). 212 | 213 | **Parameters:** 214 | 215 | - `countryCode`: The country code for the search area, must not be null. 216 | - `postalCode`: The postal code for the center of the search area, must not be null. 217 | - `distanceUnit`: The distance unit to be used for the calculation. Supported values are 'mi' and 'km' (for miles and kilometers respectively). If an invalid value is passed then 'km' will be used. 218 | - `maxDistance`: Area (radius) in DistanceUnit where Stores will be searched for. If null is passed, a system default is used. 219 | 220 | **Returns:** 221 | 222 | The stores and their distance from the specified location, sorted in ascending order by distance. 223 | 224 | --- 225 | 226 | ### setStoreIDToSession 227 | 228 | **Signature:** `static setStoreIDToSession(storeID : String) : void` 229 | 230 | **Description:** Set the store id for the current session. The store id is also saved on the cookie with the cookie name "dw_store" with no expiration time. Null is allowed to remove store id from session, when null is passed in, the cookie will be removed when browser exits. 231 | 232 | **Parameters:** 233 | 234 | - `storeID`: the id of the store. The leading and trailing white spaces are removed by system from storeID 235 | 236 | --- ```