├── cartridges └── sfcc_dev_tools │ ├── package.json │ ├── cartridge │ ├── sfcc_dev_tools.properties │ ├── scripts │ │ ├── hooks.json │ │ ├── hooks │ │ │ ├── benchmark.js │ │ │ └── devtools.js │ │ └── util │ │ │ └── serialize.js │ ├── static │ │ └── default │ │ │ ├── js │ │ │ └── dev_tools.js.LICENSE.txt │ │ │ └── css │ │ │ └── dev_tools.css │ ├── templates │ │ └── default │ │ │ └── sfcc │ │ │ └── devtools.isml │ └── controllers │ │ └── DevTools.js │ ├── caches.json │ ├── .project │ └── README.md ├── LICENSE └── README.md /cartridges/sfcc_dev_tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": "./cartridge/scripts/hooks.json", 3 | "caches": "./caches.json" 4 | } 5 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/cartridge/sfcc_dev_tools.properties: -------------------------------------------------------------------------------- 1 | ## cartridge.properties for cartridge sfcc_dev_tools 2 | #Mon Oct 26 21:28:54 CDT 2020 3 | demandware.cartridges.sfcc_dev_tools.multipleLanguageStorefront=true 4 | demandware.cartridges.sfcc_dev_tools.id=sfcc_dev_tools 5 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/caches.json: -------------------------------------------------------------------------------- 1 | { 2 | "caches": [ 3 | { 4 | "id": "DevToolsCache", 5 | "expireAfterSeconds": 300 6 | }, 7 | { 8 | "id": "DevToolsBenchmarkCache", 9 | "expireAfterSeconds": 300 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/cartridge/scripts/hooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": [ 3 | { 4 | "name": "sfcc.util.devtools", 5 | "script": "./hooks/devtools" 6 | }, 7 | { 8 | "name": "app.template.afterFooter", 9 | "script": "./hooks/devtools" 10 | }, 11 | { 12 | "name": "app.server.registerRoute", 13 | "script": "./hooks/benchmark" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | sfcc_dev_tools 4 | 5 | 6 | 7 | 8 | 9 | com.demandware.studio.core.beehiveElementBuilder 10 | 11 | 12 | 13 | 14 | 15 | com.demandware.studio.core.beehiveNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 SFCC DevOps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/cartridge/static/default/js/dev_tools.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * Vue.js v2.7.14 3 | * (c) 2014-2022 Evan You 4 | * Released under the MIT License. 5 | */ 6 | 7 | /**! 8 | * @fileOverview Kickass library to create and place poppers near their reference elements. 9 | * @version 1.16.1 10 | * @license 11 | * Copyright (c) 2016 Federico Zivolo and contributors 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in all 21 | * copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/cartridge/scripts/hooks/benchmark.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Hook executed for SFRA-only 5 | * Registers SFRA route 6 | * @param route 7 | */ 8 | function registerRoute(route) { 9 | route.on('route:Start', function onRouteStartHandler(req, res) { 10 | var path = req.path.split('/'); 11 | var controller = path.pop(); 12 | var type = req.includeRequest ? 'remote-include' : 'route'; 13 | var name = (res.viewData) ? res.viewData.action + '|' + res.viewData.queryString : controller; 14 | 15 | if (dw.system.HookMgr.hasHook('sfcc.util.devtools')) { 16 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'benchmark', 'start', { 17 | name: name.replace(/\|$/, ''), 18 | type: type, 19 | start: new Date().getTime() 20 | }); 21 | } 22 | }); 23 | 24 | route.on('route:Redirect', function onRouteRedirectHandler() { 25 | // Redirect logic here 26 | }); 27 | 28 | route.on('route:Step', function onRouteStepHandler() { 29 | // Step logic here 30 | }); 31 | 32 | route.on('route:Complete', function onRouteCompleteHandler(req, res) { 33 | var path = req.path.split('/'); 34 | var controller = path.pop(); 35 | var name = (res.viewData) ? res.viewData.action + '|' + res.viewData.queryString : controller; 36 | 37 | if (dw.system.HookMgr.hasHook('sfcc.util.devtools')) { 38 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'benchmark', 'stop', name.replace(/\|$/, '')); 39 | } 40 | }); 41 | 42 | route.on('route:BeforeComplete', function onRouteBeforeCompleteHandler(req, res) { 43 | var path = req.path.split('/'); 44 | var controller = path.pop(); 45 | var name = (res.viewData) ? res.viewData.action + '|' + res.viewData.queryString : controller; 46 | 47 | if (dw.system.HookMgr.hasHook('sfcc.util.devtools')) { 48 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'benchmark', 'stop', name.replace(/\|$/, '')); 49 | } 50 | }); 51 | } 52 | 53 | /* Module Exports */ 54 | exports.registerRoute = registerRoute; 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://avatars.githubusercontent.com/u/151680118?s=200&v=4 "Logo") 2 | 3 | SFCC Developers Core - SFCC Cartridge 4 | === 5 | 6 | > A Salesforce Commerce Cloud (Demandware) Cartridge for Developers. 7 | 8 | ## Cartridges 9 | 10 | - [X] **[Dev Tools](./cartridges/sfcc_dev_tools/)** - Developer Tools for Debugging your Storefront 11 | 12 | > **:warning: NOTICE:** The author of the **Dev Console** has requested their cartridge be relocated to their personal GitHub account for future development. You can locate this cartridge at its **[New Home](https://github.com/z1haze/sfcc_dev_console)**. 13 | 14 | Installation 15 | --- 16 | 17 | [![Download](https://img.shields.io/badge/Download-blue.svg?logo=github&style=for-the-badge)](https://github.com/opensfcc/sfcc_developers_core/releases/latest) 18 | 19 | 1. Unzip and Rename the folder to `sfcc_developers_core` 20 | 2. Move `sfcc_developers_core` into the root of your SFCC Project 21 | 3. Add `sfcc_developers_core` to project or global `.gitignore` 22 | 4. Add `sfcc_dev_tools` to Business Manager Storefront `Cartridges` Path 23 | 5. Review Usage Instructions in each Cartridges README.md 24 | 25 | Contributing 26 | --- 27 | 28 | > Interested in making this tool better? Fork this Repository and we'll gladly accept Pull Requests. 29 | 30 | #### Developer Setup: 31 | 32 | ```bash 33 | git clone https://github.com/opensfcc/sfcc_developers_core.git 34 | cd sfcc_developers_core 35 | npm install 36 | npm run dev # one time build for development 37 | npm run watch # watch for changes and build for development 38 | npm run build # one time build for production 39 | ``` 40 | 41 | Once you have something you would like to share, check out our Contribution Guide. 42 | 43 | [![Contribution Guide](https://img.shields.io/badge/Contribution_Guide-EEEEEE.svg?logo=github&logoColor=black&style=for-the-badge)](https://github.com/opensfcc/sfcc_developers_core/blob/develop/.github/CONTRIBUTING.md) 44 | 45 | Disclaimer 46 | --- 47 | 48 | > The trademarks and product names of Salesforce®, including the mark Salesforce®, are the property of Salesforce.com. OpenSFCC is not affiliated with Salesforce.com, nor does Salesforce.com sponsor or endorse the OpenSFCC products or website. The use of the Salesforce® trademark on this project does not indicate an endorsement, recommendation, or business relationship between Salesforce.com and OpenSFCC. 49 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/cartridge/templates/default/sfcc/devtools.isml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 60 | 61 | 62 | 63 | 64 | 65 |
66 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/cartridge/controllers/DevTools.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Controller to handle AJAX Calls from Dev Tools Drawer 5 | * 6 | * @module controllers/DevTools 7 | */ 8 | 9 | var serialize = require('../scripts/util/serialize'); 10 | 11 | /** 12 | * Insert Dev Tools Drawer on Site 13 | */ 14 | function AfterFooter() { 15 | const System = require('dw/system/System'); 16 | const Response = require('dw/system/Response'); 17 | 18 | if (request.httpMethod !== 'GET' || System.getInstanceType() === System.PRODUCTION_SYSTEM) { 19 | return; 20 | } 21 | 22 | response.setHttpHeader(Response.CONTENT_SECURITY_POLICY, 'frame-ancestors \'self\''); 23 | response.setHttpHeader(Response.X_CONTENT_TYPE_OPTIONS, 'nosniff'); 24 | 25 | var ISML = require('dw/template/ISML'); 26 | ISML.renderTemplate('sfcc/devtools'); 27 | } 28 | 29 | /** 30 | * Fetch Server Data for Dev Drawer 31 | */ 32 | function GetData() { 33 | const System = require('dw/system/System'); 34 | const Response = require('dw/system/Response'); 35 | 36 | if (System.getInstanceType() === System.PRODUCTION_SYSTEM) { 37 | sendJSON({ 38 | error: true, 39 | message: 'Not available on production instance!' 40 | }, 403); 41 | 42 | return; 43 | } 44 | 45 | if (request.httpMethod !== 'GET') { 46 | return sendJSON({ 47 | error: true, 48 | message: 'Method Not Allowed' 49 | }, 405); 50 | } 51 | 52 | response.setHttpHeader(Response.CONTENT_SECURITY_POLICY, 'frame-ancestors \'self\''); 53 | response.setHttpHeader(Response.X_CONTENT_TYPE_OPTIONS, 'nosniff'); 54 | 55 | const location = request.getGeolocation(); 56 | 57 | // Get Basket Info 58 | var BasketMgr = require('dw/order/BasketMgr'); 59 | var basket = BasketMgr.getCurrentBasket(); 60 | 61 | // Get Preferences 62 | var Site = require('dw/system/Site'); 63 | var currentSite = Site.getCurrent(); 64 | var preferences = Site.getCurrent().getPreferences(); 65 | 66 | // Get Dev Tools Cache 67 | var devToolsCache = dw.system.CacheMgr.getCache('DevToolsCache'); 68 | var benchmarkCache = dw.system.CacheMgr.getCache('DevToolsBenchmarkCache'); 69 | 70 | // Send Content then Clear Logs 71 | sendJSON({ 72 | basket: serialize(basket), 73 | geolocation: serialize(location), 74 | benchmarks: benchmarkCache ? serialize(benchmarkCache.get('benchmarks')) : null, 75 | messages: { 76 | debug: devToolsCache ? devToolsCache.get('debug') : null, 77 | error: devToolsCache ? devToolsCache.get('error') : null, 78 | fatal: devToolsCache ? devToolsCache.get('fatal') : null, 79 | info: devToolsCache ? devToolsCache.get('info') : null, 80 | log: devToolsCache ? devToolsCache.get('log') : null, 81 | warn: devToolsCache ? devToolsCache.get('warn') : null 82 | }, 83 | preferences: serialize(preferences), 84 | session: serialize(session), 85 | site: serialize(currentSite) 86 | }) 87 | } 88 | 89 | /** 90 | * Helper to send a json response 91 | * 92 | * @param content 93 | * @param status 94 | */ 95 | function sendJSON(content, status) { 96 | response.setStatus(status || 200); 97 | response.setContentType('application/json'); 98 | response.getWriter().print(JSON.stringify(content)); 99 | } 100 | 101 | module.exports.AfterFooter = AfterFooter; 102 | module.exports.AfterFooter.public = true; 103 | 104 | module.exports.GetData = GetData; 105 | module.exports.GetData.public = true; 106 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/cartridge/scripts/util/serialize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const storedUUIDs = new dw.util.HashSet(); 4 | 5 | /** 6 | * Main function that should be able to serialize any type of sfcc object 7 | * 8 | * @param original 9 | * @param maxDepth 10 | * @param depth 11 | * @param pojo 12 | * @returns {string|string|[]|*} 13 | */ 14 | function serialize (original, maxDepth, depth, pojo) { 15 | // Set Max Depth if not defined 16 | if (!maxDepth) { 17 | maxDepth = 3; 18 | } 19 | 20 | // Set Current depth if not defined 21 | if (!depth) { 22 | depth = 0; 23 | } 24 | 25 | // return if primitive 26 | if (isPrimitive(original)) { 27 | return original; 28 | } 29 | 30 | // Prevent the cyclic loop caused by e.g. a customer has a profile, and a profile has a customer - around and around we go 31 | if ('UUID' in original) { 32 | if (storedUUIDs.contains(original.UUID)) { 33 | // return '{already returned}'; 34 | } 35 | 36 | storedUUIDs.add(original.UUID, pojo || {}); 37 | } 38 | 39 | if (depth > maxDepth) { 40 | if ('toString' in original) { 41 | return original.toString(); 42 | } 43 | 44 | return '{max depth reached}'; 45 | } 46 | 47 | // convert and return a collection 48 | if (original instanceof dw.util.Collection) { 49 | return serializeCollection(original, maxDepth, depth + 1); 50 | } 51 | 52 | // convert and return a map 53 | if (original instanceof dw.util.Map) { 54 | return serializeMap(original, depth + 1, maxDepth); 55 | } 56 | 57 | // return the toString for date 58 | if (original instanceof Date || original instanceof dw.util.Decimal) { 59 | return original.toString(); 60 | } 61 | 62 | if (original instanceof XML) { 63 | return original.toXMLString(); 64 | } 65 | 66 | // what we have left is an object (hopefully) 67 | // convert and return the object 68 | return serializeObject(original, maxDepth, depth, pojo || {}); 69 | } 70 | 71 | /** 72 | * Serializes a native SFCC object to a POJO 73 | * 74 | * @param object 75 | * @param maxDepth 76 | * @param depth 77 | * @param pojo 78 | * @returns {*} 79 | */ 80 | function serializeObject (object, maxDepth, depth, pojo) { 81 | for (let prop in object) { 82 | // have to do this because dw has some invalid properties on objects - very weird 83 | let k = null; 84 | 85 | try { 86 | k = object[prop]; 87 | } catch (e) { 88 | continue; 89 | } 90 | 91 | if (typeof k === 'function') { 92 | continue; 93 | } 94 | 95 | /** 96 | * For some reason SFCC applies the custom properties on certain object types that 97 | * it really shouldn't, and when trying to process those custom fields, it blows up. 98 | * This helps is to get around that by skipping them under certain scenarios. 99 | */ 100 | if (object instanceof dw.order.PaymentProcessor && prop === 'custom') { 101 | continue; 102 | } 103 | 104 | if (object instanceof dw.catalog.ProductActiveData && prop === 'custom') { 105 | continue; 106 | } 107 | 108 | pojo[prop] = serialize(k, maxDepth, depth + 1, {}); 109 | } 110 | 111 | return pojo; 112 | } 113 | 114 | /** 115 | * Serializes a native SFCC collection into an array 116 | * @param collection 117 | * @param maxDepth 118 | * @param depth 119 | * @returns {[]} 120 | */ 121 | function serializeCollection (collection, maxDepth, depth) { 122 | const iterator = collection.iterator(); 123 | const items = []; 124 | 125 | while (iterator.hasNext()) { 126 | let item = iterator.next(); 127 | items.push(serialize(item, maxDepth, depth, {})); 128 | } 129 | 130 | return items; 131 | } 132 | 133 | /** 134 | * Serializes a native SFCC map into a pojo 135 | * @param map 136 | * @param depth 137 | * @param maxDepth 138 | * @returns {string|*[]|*} 139 | */ 140 | function serializeMap (map, depth, maxDepth) { 141 | const obj = {}; 142 | 143 | map.keySet().toArray().forEach(function (item) { 144 | obj[item] = map.get(item); 145 | }); 146 | 147 | return serialize(obj, maxDepth, depth, {}); 148 | } 149 | 150 | function isPrimitive (v) { 151 | return typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null || typeof v === 'undefined'; 152 | } 153 | 154 | module.exports = serialize; 155 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/README.md: -------------------------------------------------------------------------------- 1 | ![Logo](https://avatars.githubusercontent.com/u/151680118?s=200&v=4 "Logo") 2 | 3 | SFCC Dev Tools Cartridge 4 | === 5 | 6 | > A Salesforce Commerce Cloud (Demandware) Cartridge for Developers. 7 | 8 | ![Screenshot](https://sfccdevops.s3.amazonaws.com/sfcc-devtools-drawer.gif "Screenshot") 9 | 10 | ## Features 11 | 12 | - [X] Server Side Debugging in ISML, JS & DS Files 13 | - [X] Client Side Rendering of Debug Output in Dev Tools Console 14 | - [X] Interactive Drawer with Console for Debugging and Testing Live Code 15 | - [X] Safety Measures to prevent running in Production Environments 16 | - [X] No Site Preferences or Misc Imports Required 17 | 18 | **NOTE:** The Debugging Console is not built for Native SFCC Messages, but specific to custom debuggers you add in your own code as outlined below. 19 | 20 | Installation 21 | --- 22 | 23 | 1. [Install Cartridge](../../README.md#installation) 24 | 2. Add `sfcc_dev_tools` to Storefront `Cartridges` Path 25 | 3. Add the Dev Tools to your Storefront using either SFRA or Site Genesis instructions below 26 | 27 | #### SFRA 28 | 29 | > You do not need to do any extra setup for SFRA. Our cartridge takes advantage of the `app.template.afterFooter` template hook baked into SFRA. 30 | 31 | #### Site Genesis 32 | 33 | > To render the Dev Tools on Site Genesis, stick this code snippet in an ISML template near the footer of your website as close to the closing `` tag as possible. 34 | 35 | ```html 36 | 37 | 38 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'render'); 39 | 40 | 41 | ``` 42 | 43 | Usage 44 | --- 45 | 46 | > **PRO TIP:** Make sure to use the `hasHook` conditional logic shown in the examples if there is a chance your debug code could end up on another instance that might not have the `sfcc_dev_tools` cartridge installed. 47 | 48 | #### Debug in ISML Files 49 | 50 | > If you are in an ISML file, you can use something like this. 51 | 52 | ```html 53 | 54 | 55 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'log', { 56 | someObject: someValue, 57 | anotherObject: anotherValue 58 | }); 59 | 60 | 61 | ``` 62 | 63 | #### Debug in JS or DS Files 64 | 65 | ```javascript 66 | if (dw.system.HookMgr.hasHook('sfcc.util.devtools')) { 67 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'log', { 68 | someObject: someValue, 69 | anotherObject: anotherValue 70 | }); 71 | } 72 | ``` 73 | 74 | #### Debug Options 75 | 76 | > We support the following Log Types: `debug`, `error`, `info`, `log` & `warn` 77 | 78 | ```javascript 79 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'debug', someObject); 80 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'error', someObject); 81 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'info', someObject); 82 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'log', someObject); 83 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'warn', someObject); 84 | ``` 85 | 86 | Benchmarks 87 | --- 88 | 89 | > Benchmarks help calculate the Server-Side timing of custom events. By default, this cartridge adds timing for route and remote includes. You can easily add your own custom benchmarks using the following hooks: 90 | 91 | #### Start Custom Benchmark 92 | 93 | To start timing a benchmark, place the following code at the very beginning of your custom server-side script ( change `My Benchmark` to whatever you want to call the benchmark ). 94 | 95 | ```javascript 96 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'benchmark', 'start', 'My Benchmark'); 97 | ``` 98 | 99 | #### Stop Custom Benchmark 100 | 101 | The stop timing a benchmark, just use the `stop` parameter and the same benchmark name. 102 | 103 | ```javascript 104 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'benchmark', 'stop', 'My Benchmark'); 105 | ``` 106 | 107 | #### Advanced Benchmarks 108 | 109 | Alternatively, you can pass over an object instead of a String that contains the following properties: 110 | 111 | - [X] `name`: Name used to Start & Stop Benchmark _( required )_ 112 | - [X] `parent`: If you are nesting benchmarks, provide the Parent Benchmark name _[ default: `null` ]_ 113 | - [X] `type`: Optional property to define the type of benchmark, which can be any value _[ default: `'custom'` ]_ 114 | 115 | ```javascript 116 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'benchmark', 'start', { 117 | name: 'Nested Benchmark', 118 | parent: 'My Benchmark', 119 | type: 'custom-type' 120 | }); 121 | ``` 122 | 123 | Then you can stop the benchmark by referencing its `name`: 124 | 125 | ```javascript 126 | dw.system.HookMgr.callHook('sfcc.util.devtools', 'benchmark', 'stop', 'Nested Benchmark'); 127 | ``` 128 | 129 | **IMPORTANT**: The benchmark `name` should be unique in order to prevent starting & stopping existing benchmarks. 130 | 131 | #### Screenshots 132 | 133 | Access the Benchmark from the new Toolbar Menu Option. 134 | 135 | ![toolbar](https://sfcc-devtools.s3.amazonaws.com/benchmark-toolbar.png "toolbar") 136 | 137 | Then your benchmarks will be listed in the new Benchmark Panel. 138 | 139 | ![drawer](https://sfccdevops.s3.amazonaws.com/sfcc_developers_core-v1.4.0.gif "drawer") 140 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/cartridge/scripts/hooks/devtools.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var logger = require('dw/system/Logger').getLogger('DevTools'); 4 | var serialize = require('../util/serialize'); 5 | 6 | /** 7 | * Placeholder to store Server Side Debugger Messages 8 | */ 9 | var Debugger = { 10 | debug: [], 11 | error: [], 12 | fatal: [], 13 | info: [], 14 | log: [], 15 | warn: [] 16 | }; 17 | 18 | /** 19 | * Generate Stack Trace for Better Debugging 20 | * @returns {Object} trace 21 | */ 22 | var stackTrace = function() { 23 | var trace = { 24 | fileName: null, 25 | lineNumber: null, 26 | requestID: request.requestID, 27 | requestType: (request.httpParameterMap.format.stringValue === 'ajax') ? 'ajax' : 'http', 28 | requestURL: request.httpURL.toString(), 29 | stack: null, 30 | timestamp: new Date().getTime(), 31 | uid: 'id' + Math.random().toString(36).substring(9) 32 | }; 33 | 34 | try { 35 | throw new Error(''); 36 | } 37 | catch (error) { 38 | trace.stack = error.stack || null; 39 | } 40 | 41 | if (trace.stack) { 42 | trace.stack = trace.stack.split('\n').map(function (line) { return line.trim(); }); 43 | trace.stack = trace.stack.splice(trace.stack[0] == 'Error' ? 3 : 2); 44 | trace.stack.pop(); 45 | 46 | // Get First File from Stack 47 | var lastStack = trace.stack[0].split(' ') 48 | var fileDetails = lastStack[1]; 49 | var lineNumber = fileDetails.split(':').pop() 50 | var fileName = fileDetails.replace(':' + lineNumber, '') 51 | 52 | trace.fileName = fileName; 53 | trace.lineNumber = parseInt(lineNumber); 54 | trace.ide = 'http://localhost:60606/target=/' + fileName; 55 | } 56 | 57 | return trace; 58 | } 59 | 60 | /** 61 | * Add Messages to Debugger 62 | * @param {String} method 63 | * @param {Object} messages 64 | * @param {Object} trace 65 | */ 66 | var addMessages = function (method, messages, trace) { 67 | if (!method || !Array.isArray(messages) || !trace) { 68 | return; 69 | } 70 | 71 | for (var i in messages) { 72 | trace.message = serialize(messages[i]); 73 | Debugger[method].push(trace); 74 | } 75 | }; 76 | 77 | /** 78 | * Run Benchmark 79 | * @param {*} action start|stop 80 | * @param {*} data { name: 'myBenchmark', parent: 'myParentBenchmark', type: 'myBenchmarkType' } 81 | */ 82 | var runBenchmark = function (action, data) { 83 | var cache = dw.system.CacheMgr.getCache('DevToolsBenchmarkCache'); 84 | var frozenCache = cache.get('benchmarks') || {}; 85 | var benchmarks = JSON.parse(JSON.stringify(frozenCache)); 86 | 87 | // Exit if No Data or Name 88 | if (!data || !data.name || !benchmarks) { 89 | return; 90 | } 91 | 92 | // Get Count of Benchmarks 93 | var count = Object.keys(benchmarks).length; 94 | 95 | if (action === 'start') { 96 | benchmarks[data.name] = { 97 | id: count, 98 | duration: null, 99 | label: data.name.replace(/\|/g, ' | ').replace(/ \| $/, ''), 100 | parent: data.parent || null, 101 | start: data.start || new Date().getTime(), 102 | type: data.type || 'custom', 103 | }; 104 | } else if (action === 'stop' && typeof benchmarks[data.name] !== 'undefined') { 105 | benchmarks[data.name].duration = new Date().getTime() - benchmarks[data.name].start; 106 | } 107 | 108 | cache.put('benchmarks', benchmarks); 109 | }; 110 | 111 | /** 112 | * Add Benchmark Metrics 113 | * @example: dw.system.HookMgr.callHook('sfcc.util.devtools', 'benchmark', 'start', 'myBenchmark'); 114 | * @example: dw.system.HookMgr.callHook('sfcc.util.devtools', 'benchmark', 'start', { name: 'myBenchmark', parent: 'myParentBenchmark', type: 'myBenchmarkType' }); 115 | * @example: dw.system.HookMgr.callHook('sfcc.util.devtools', 'benchmark', 'stop', 'myBenchmark'); 116 | */ 117 | function benchmark() { 118 | try { 119 | var params = Array.prototype.slice.call(arguments, 0) || []; 120 | var action = params[0] || null; 121 | var data = params[1] || null; 122 | 123 | // Set Default `action` if not defined 124 | if (!action) { 125 | action = 'start'; 126 | } 127 | 128 | // Convert `data` to Object if String 129 | if (typeof data === 'string') { 130 | data = { 131 | name: params[1] 132 | }; 133 | } 134 | 135 | // Run Benchmark 136 | runBenchmark(action, data); 137 | } catch (err) { 138 | logger.fatal(err); 139 | } 140 | } 141 | 142 | /** 143 | * Add Debug Messages to Debugger Queue 144 | * @example: dw.system.HookMgr.callHook('sfcc.util.devtools', 'debug', myObject); 145 | */ 146 | function debug() { 147 | try { 148 | var messages = Array.prototype.slice.call(arguments, 0) || []; 149 | addMessages('debug', messages, stackTrace()); 150 | logger.debug(messages); 151 | } catch (err) { 152 | Debugger.fatal.push(err); 153 | logger.fatal(err); 154 | } 155 | } 156 | 157 | /** 158 | * Add Error Messages to Debugger Queue 159 | * @example: dw.system.HookMgr.callHook('sfcc.util.devtools', 'error', myObject); 160 | */ 161 | function error() { 162 | try { 163 | var messages = Array.prototype.slice.call(arguments, 0) || []; 164 | addMessages('error', messages, stackTrace()); 165 | logger.error(messages); 166 | } catch (err) { 167 | Debugger.fatal.push(err); 168 | logger.fatal(err); 169 | } 170 | } 171 | 172 | /** 173 | * Add Info Messages to Debugger Queue 174 | * @example: dw.system.HookMgr.callHook('sfcc.util.devtools', 'info', myObject); 175 | */ 176 | function info() { 177 | try { 178 | var messages = Array.prototype.slice.call(arguments, 0) || []; 179 | addMessages('info', messages, stackTrace()); 180 | logger.info(messages); 181 | } catch (err) { 182 | Debugger.fatal.push(err); 183 | logger.fatal(err); 184 | } 185 | } 186 | 187 | /** 188 | * Add Log Messages to Debugger Queue 189 | * @example: dw.system.HookMgr.callHook('sfcc.util.devtools', 'log', myObject); 190 | */ 191 | function log() { 192 | try { 193 | var messages = Array.prototype.slice.call(arguments, 0) || []; 194 | addMessages('log', messages, stackTrace()); 195 | logger.debug(messages); 196 | } catch (err) { 197 | Debugger.fatal.push(err); 198 | logger.fatal(err); 199 | } 200 | } 201 | 202 | /** 203 | * Add Warn Messages to Debugger Queue 204 | * @example: dw.system.HookMgr.callHook('sfcc.util.devtools', 'warn', myObject); 205 | */ 206 | function warn() { 207 | try { 208 | var messages = Array.prototype.slice.call(arguments, 0) || []; 209 | addMessages('warn', messages, stackTrace()); 210 | logger.warn(messages); 211 | } catch (err) { 212 | Debugger.fatal.push(err); 213 | logger.fatal(err); 214 | } 215 | } 216 | 217 | /** 218 | * Support zero setup using `app.template.afterFooter` hook in SFRA 219 | */ 220 | function afterFooter() { 221 | // Get Cartridge Cache 222 | var cache = dw.system.CacheMgr.getCache('DevToolsCache'); 223 | 224 | // Write log types to their own cache key ( 128KB max per key ) 225 | cache.put('debug', Debugger.debug); 226 | cache.put('error', Debugger.error); 227 | cache.put('fatal', Debugger.fatal); 228 | cache.put('info', Debugger.info); 229 | cache.put('log', Debugger.log); 230 | cache.put('warn', Debugger.warn); 231 | 232 | // Render Remote Include to Prevent ISML Bug 233 | var velocity = require('dw/template/Velocity'); 234 | velocity.render('$velocity.remoteInclude(\'DevTools-AfterFooter\')', { 235 | velocity: velocity 236 | }); 237 | } 238 | 239 | /** 240 | * Support Dev Tool Rendering for SFSG Sites 241 | * @example: dw.system.HookMgr.callHook('sfcc.util.devtools', 'render'); 242 | */ 243 | function render() { 244 | var ISML = require('dw/template/ISML'); 245 | 246 | // Get Basket Info 247 | var BasketMgr = require('dw/order/BasketMgr'); 248 | var basket = BasketMgr.getCurrentBasket(); 249 | 250 | // Get Preferences 251 | var Site = require('dw/system/Site'); 252 | var currentSite = Site.getCurrent(); 253 | var preferences = Site.getCurrent().getPreferences(); 254 | 255 | // GeoLocation Data 256 | var location = request.getGeolocation() || {}; 257 | 258 | // Benchmark Data 259 | var cache = dw.system.CacheMgr.getCache('DevToolsBenchmarkCache'); 260 | var benchmarks = cache.get('benchmarks') || {}; 261 | 262 | ISML.renderTemplate('sfcc/devtools', { 263 | Debugger: { 264 | basket: serialize(basket), 265 | benchmarks: serialize(benchmarks), 266 | geolocation: serialize(location), 267 | messages: Debugger, 268 | preferences: serialize(preferences), 269 | session: serialize(session), 270 | site: serialize(currentSite) 271 | } 272 | }); 273 | } 274 | 275 | // Export Functions 276 | exports.benchmark = benchmark; 277 | exports.debug = debug; 278 | exports.error = error; 279 | exports.info = info; 280 | exports.log = log; 281 | exports.warn = warn; 282 | 283 | // Export Templates 284 | exports.afterFooter = afterFooter; 285 | exports.render = render; 286 | -------------------------------------------------------------------------------- /cartridges/sfcc_dev_tools/cartridge/static/default/css/dev_tools.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500&display=swap); 2 | #sfcc_dev_tools{line-height:1;box-sizing:border-box;padding:0;margin:0}#sfcc_dev_tools *,#sfcc_dev_tools *:before,#sfcc_dev_tools *:after{box-sizing:inherit;letter-spacing:initial;font-family:-apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif}#sfcc_dev_tools svg{vertical-align:unset}#sfcc_dev_tools a:active,#sfcc_dev_tools a:focus,#sfcc_dev_tools button:active,#sfcc_dev_tools button:focus{outline:2px auto #FFF;outline-offset:3px}@keyframes devtools_loading{to{transform:rotate(359deg)}}#sfcc_dev_tools{overflow:hidden}#sfcc_dev_tools .devtool-drawer{background:#222;border-top:2px solid #666;bottom:0;color:#fff;height:50vh;left:0;min-height:330px;position:fixed;width:100vw;z-index:99999}#sfcc_dev_tools .devtool-drawer .devtool-menu{background:#111;color:#fff;height:100%;left:0;position:absolute;text-align:left;top:0;width:160px}#sfcc_dev_tools .devtool-drawer .devtool-menu ul{display:block;width:100%;margin:0;padding:0;margin-top:10px}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li{display:block;width:100%;text-align:left;white-space:nowrap}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button{background:transparent;border:none;border-top:1px solid #111111;color:#999;display:block;font-size:16px;height:44px;line-height:44px;margin:0;padding:0 16px;position:relative;text-align:left;transition:all 0.25s;width:100%}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button span.count{color:#fff;display:inline-block;font-size:12px;height:20px;line-height:20px;margin:0;padding:0;position:absolute;right:10px;text-align:center;top:12px;width:20px;background:#000;color:#999;transition:all 0.25s}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button svg{height:16px;margin-right:8px;opacity:0.25;position:relative;top:2px;transition:all 0.25s;width:16px}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:hover,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:focus,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:active,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button.active{background:#222;color:white;outline:none}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:hover span.count,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:focus span.count,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:active span.count,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button.active span.count{color:white}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:hover span.count.notice-debug,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:focus span.count.notice-debug,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:active span.count.notice-debug,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button.active span.count.notice-debug{background:#6d980a}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:hover span.count.notice-error,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:focus span.count.notice-error,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:active span.count.notice-error,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button.active span.count.notice-error{background:#d70000}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:hover span.count.notice-fatal,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:focus span.count.notice-fatal,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:active span.count.notice-fatal,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button.active span.count.notice-fatal{background:#900}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:hover span.count.notice-info,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:focus span.count.notice-info,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:active span.count.notice-info,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button.active span.count.notice-info{background:#286981}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:hover span.count.notice-log,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:focus span.count.notice-log,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:active span.count.notice-log,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button.active span.count.notice-log{background:#444}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:hover span.count.notice-warn,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:focus span.count.notice-warn,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:active span.count.notice-warn,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button.active span.count.notice-warn{background:#d65507}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:hover span.count.notice-disabled,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:focus span.count.notice-disabled,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:active span.count.notice-disabled,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button.active span.count.notice-disabled{background:#555;color:#eee}#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:hover svg,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:focus svg,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button:active svg,#sfcc_dev_tools .devtool-drawer .devtool-menu ul li button.active svg{opacity:1}#sfcc_dev_tools .devtool-drawer .devtool-close-drawer{background:transparent;border:none;height:40px;margin:0;padding:0;position:absolute;right:10px;top:10px;width:40px;z-index:10000}#sfcc_dev_tools .devtool-drawer .devtool-close-drawer svg{width:16px;height:16px;opacity:0.33;transition:opacity 0.25s}#sfcc_dev_tools .devtool-drawer .devtool-close-drawer:hover,#sfcc_dev_tools .devtool-drawer .devtool-close-drawer:focus,#sfcc_dev_tools .devtool-drawer .devtool-close-drawer:active{opacity:1}#sfcc_dev_tools .devtool-drawer .devtool-close-drawer:hover svg,#sfcc_dev_tools .devtool-drawer .devtool-close-drawer:focus svg,#sfcc_dev_tools .devtool-drawer .devtool-close-drawer:active svg{opacity:0.33}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section{color:#fff;font-size:16px;height:100%;left:160px;overflow:auto;position:absolute;top:0;width:calc(100% - 160px)}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section.fade-enter-active:before,#sfcc_dev_tools .devtool-drawer .devtool-drawer-section.fade-leave-active:before{content:'';position:absolute;top:0;left:0;width:100%;height:100%;background-color:rgba(34,34,34,0.85);z-index:9999}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section.fade-enter-active:after,#sfcc_dev_tools .devtool-drawer .devtool-drawer-section.fade-leave-active:after{content:'';position:absolute;width:48px;height:48px;border:2px solid #fff;border-left-color:transparent;border-radius:50%;top:calc(50% - 24px);left:calc(50% - 24px);animation:devtools_loading 1s linear infinite;z-index:10000}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section.tree-view{padding:20px 0 20px 5px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .no-results{color:#fff;display:block;font-weight:300;padding:6px 18px;text-transform:uppercase}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .no-results a{color:#999;display:block;margin-top:14px;font-size:14px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .no-results a:hover,#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .no-results a:focus{color:#fff;text-decoration:underline}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper{white-space:nowrap;margin-top:18px;margin-left:18px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button{background:#000;color:#fff;text-transform:uppercase;font-size:12px;padding:8px 12px;margin-right:4px;transition:opacity 0.25s;width:80px;text-align:center}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button span.count{display:inline-block;margin-left:4px;opacity:0.33}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button span.label{opacity:0.5}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button.active{background:#ccc;color:#000;opacity:1;font-weight:600}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button.active span.count,#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button.active span.label{opacity:1}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button.notice-debug.active:not(.empty){background:#6d980a;color:#fff}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button.notice-error.active:not(.empty){background:#d70000;color:#fff}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button.notice-fatal.active:not(.empty){background:#900;color:#fff}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button.notice-info.active:not(.empty){background:#286981;color:#fff}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button.notice-log.active:not(.empty){background:#444;color:#fff}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button.notice-warn.active:not(.empty){background:#d65507;color:#fff}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection{-webkit-overflow-scrolling:touch;height:calc(100% - 62px);overflow:auto;position:relative;top:16px;width:100%;padding-bottom:20px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection span.no-results{display:block;margin-left:18px;padding:0}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul{margin:0;padding:0}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li{list-style:none;margin:0;padding:0}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file{margin-left:18px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a{color:#999;text-decoration:none;font-size:12px;transition:opacity 0.25s;position:relative}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a span,#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a strong{transition:opacity 0.25s;opacity:0.75}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a:hover,#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a:focus{color:#fff}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a:hover span,#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a:hover strong,#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a:focus span,#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a:focus strong{opacity:1}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a.loading::after{content:'';border:1px solid rgba(125,125,125,0.5);border-top-color:#fff;border-radius:50%;box-sizing:border-box;height:14px;position:absolute;right:-20px;top:0;width:14px;animation:devtools_loading 0.6s linear infinite}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .file a.success::after{content:'';border-bottom:1px solid #fff;border-right:1px solid #fff;box-sizing:border-box;height:14px;position:absolute;right:-17px;top:0;transform:rotate(45deg);width:7px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .message{margin-top:12px;font-family:'Roboto Mono', monospace;font-weight:normal;font-size:14px;line-height:22px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .message.string{margin-left:18px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .divider-line{width:calc(100% - 40px);height:1px;margin:30px 0 30px 20px;background:rgba(255,255,255,0.1)}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .stack-trace{margin-left:18px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .stack-trace button{color:#999;text-transform:uppercase;font-size:10px;background:#000;padding:8px 10px;font-weight:600;margin-top:12px;transition:color 0.25s}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .stack-trace button:hover,#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .stack-trace button:focus{color:#fff}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .stack-trace ul{font-size:12px;line-height:18px;margin-top:12px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .stack-trace ul.hidden-trace{display:none}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li .stack-trace ul span{display:inline-block;text-align:right;width:20px;margin-right:4px;opacity:0.5}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .subsection ul li:last-child .divider-line{display:none}#sfcc_dev_tools .search-input{background:#111111;width:calc(100% - 70px);position:sticky;top:10px;left:10px;padding:4px 8px;font-size:12px;color:#fff}#sfcc_dev_tools table.benchmarks{margin-top:20px;display:flex;flex-flow:column;height:calc(100% - 50px);width:100%}#sfcc_dev_tools table.benchmarks thead{flex:0 0 auto;width:100%}#sfcc_dev_tools table.benchmarks tbody{flex:1 1 auto;display:block;overflow-y:scroll}#sfcc_dev_tools table.benchmarks tbody tr{width:100%}#sfcc_dev_tools table.benchmarks thead,#sfcc_dev_tools table.benchmarks tbody tr{display:table;table-layout:fixed}#sfcc_dev_tools table.benchmarks td,#sfcc_dev_tools table.benchmarks th{padding:6px 24px 6px 12px}#sfcc_dev_tools table.benchmarks td:nth-child(1),#sfcc_dev_tools table.benchmarks th:nth-child(1){width:70px}#sfcc_dev_tools table.benchmarks td:nth-child(2),#sfcc_dev_tools table.benchmarks th:nth-child(2){width:auto}#sfcc_dev_tools table.benchmarks td:nth-child(3),#sfcc_dev_tools table.benchmarks th:nth-child(3){width:150px}#sfcc_dev_tools table.benchmarks td:nth-child(4),#sfcc_dev_tools table.benchmarks th:nth-child(4){width:100px}#sfcc_dev_tools table.benchmarks td:nth-child(5),#sfcc_dev_tools table.benchmarks th:nth-child(5){width:100px}#sfcc_dev_tools table.benchmarks th{font-size:12px;color:#FFF;border:1px solid #333;border-left:none;white-space:nowrap;cursor:pointer;position:relative}#sfcc_dev_tools table.benchmarks th:last-child{border-right:none}#sfcc_dev_tools table.benchmarks th.sorting.sort-asc::after{content:'▼';position:absolute;right:8px;display:block;top:6px;opacity:0.5}#sfcc_dev_tools table.benchmarks th.sorting.sort-desc::after{content:'▲';position:absolute;right:8px;display:block;top:6px;opacity:0.5}#sfcc_dev_tools table.benchmarks td{font-size:12px;color:#999;border-bottom:1px solid #333;white-space:nowrap}#sfcc_dev_tools table.benchmarks td.label{white-space:nowrap;line-height:16px;text-overflow:ellipsis;width:100%;overflow:hidden}#sfcc_dev_tools table.benchmarks td.warning{color:yellow}#sfcc_dev_tools table.benchmarks td.danger{color:orange}#sfcc_dev_tools table.benchmarks td.alert{color:orangered}#sfcc_dev_tools #sfcc-dev-tools-icons{display:none}@media only screen and (max-width: 768px){#sfcc_dev_tools .devtool-drawer .devtool-menu{width:50px}#sfcc_dev_tools .devtool-drawer .devtool-menu button span{display:none !important}#sfcc_dev_tools .devtool-drawer .devtool-menu button svg{margin-right:0}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section{width:calc(100% - 50px);left:50px}}@media only screen and (max-width: 480px){#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button{padding:0 10px}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button svg{margin-left:4px}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.no-count{padding:0 10px}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.no-count svg{margin-left:0}#sfcc_dev_tools .devtool-drawer .devtool-close-drawer{top:3px;right:0}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section{padding:10px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button{font-size:10px;padding:6px 4px;margin-right:0px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button span.count{display:none}}#sfcc_dev_tools .devtool-popover{background:#000;border-radius:0;border:none;bottom:40px;color:#FFF;display:inline-block;font-size:14px;font-weight:normal;left:0;padding:0;position:absolute;width:auto;z-index:99999;padding:14px 0}#sfcc_dev_tools .devtool-popover.fade-enter-active:before,#sfcc_dev_tools .devtool-popover.fade-leave-active:before{content:'';position:absolute;top:0;left:0;width:100%;height:100%;background-color:rgba(34,34,34,0.85);z-index:9999}#sfcc_dev_tools .devtool-popover.fade-enter-active:after,#sfcc_dev_tools .devtool-popover.fade-leave-active:after{content:'';position:absolute;width:24px;height:24px;border:2px solid #fff;border-left-color:transparent;border-radius:50%;top:calc(50% - 12px);left:calc(50% - 12px);animation:devtools_loading 1s linear infinite;z-index:10000}#sfcc_dev_tools .devtool-popover .popover__arrow{display:none}#sfcc_dev_tools .devtool-popover .table{text-align:left;line-height:24px;margin:0;padding:0}#sfcc_dev_tools .devtool-popover .table>button{background:transparent;border:none;color:white;display:block;font-size:14px;height:24px;line-height:24px;margin:0;padding:0 14px;position:relative;text-align:left;transition:all 0.25s}#sfcc_dev_tools .devtool-popover .table>button span.count{color:#FFF;display:inline-block;font-size:12px;height:20px;line-height:20px;margin:0;padding:0;position:absolute;right:14px;text-align:center;top:2px;width:20px}#sfcc_dev_tools .devtool-popover .table>button span.count.notice-debug,#sfcc_dev_tools .devtool-popover .table>button span.count.notice-good{background:#6d980a}#sfcc_dev_tools .devtool-popover .table>button span.count.notice-error,#sfcc_dev_tools .devtool-popover .table>button span.count.notice-bad{background:#d70000}#sfcc_dev_tools .devtool-popover .table>button span.count.notice-fatal{background:#900}#sfcc_dev_tools .devtool-popover .table>button span.count.notice-info{background:#286981}#sfcc_dev_tools .devtool-popover .table>button span.count.notice-log,#sfcc_dev_tools .devtool-popover .table>button span.count.notice-default{background:#444}#sfcc_dev_tools .devtool-popover .table>button span.count.notice-warn{background:#d65507}#sfcc_dev_tools .devtool-popover .table>button span.count.notice-disabled{background:#555;color:#EEE}#sfcc_dev_tools .devtool-popover .table>button span.label{display:inline-block;white-space:nowrap}#sfcc_dev_tools .devtool-popover .table>button:hover,#sfcc_dev_tools .devtool-popover .table>button:focus,#sfcc_dev_tools .devtool-popover .table>button:active{background:#222222;outline:none}#sfcc_dev_tools .devtool-popover .table>button.empty{color:#666}#sfcc_dev_tools .devtool-popover .table>button.empty span.count{background:#000;color:#999}#sfcc_dev_tools .devtool-popover .table>button.empty:hover,#sfcc_dev_tools .devtool-popover .table>button.empty:focus,#sfcc_dev_tools .devtool-popover .table>button.empty:active{color:#999;outline:none}#sfcc_dev_tools .devtool-popover .table>button.empty:hover span.count,#sfcc_dev_tools .devtool-popover .table>button.empty:focus span.count,#sfcc_dev_tools .devtool-popover .table>button.empty:active span.count{color:#BBB}#sfcc_dev_tools .devtool-popover.basket span.label{width:100px}#sfcc_dev_tools .devtool-popover.customer span.label{width:120px}#sfcc_dev_tools .devtool-popover.geolocation span.label{width:auto}#sfcc_dev_tools .devtool-popover.messages span.label{width:70px}#sfcc_dev_tools .devtool-popover.preferences span.label{width:70px}#sfcc_dev_tools .devtool-popover.session span.label{width:70px}#sfcc_dev_tools .devtool-popover.site span.label{width:70px}#sfcc_dev_tools .devtool-show-toolbar{background:#222;border-radius:0;border:2px solid #666;border-bottom:0;border-left:0;bottom:0;display:block;height:40px;left:0;margin:0;padding:0;position:fixed;transition:all 0.25s;width:40px;z-index:999999;-webkit-appearance:unset;line-height:unset}#sfcc_dev_tools .devtool-show-toolbar svg{width:24px;height:24px;fill:#FFF}#sfcc_dev_tools .devtool-show-toolbar:hover{background-color:#000}#sfcc_dev_tools .devtool-toolbar{background:#222;border-top:2px solid #666;position:fixed;bottom:0;left:0;width:100vw;height:40px;z-index:99999;color:#fff}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper{position:relative;display:inline-block;float:left}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button{border:none;color:#FFF;display:inline-block;font-size:14px;font-weight:500;height:40px;line-height:40px;margin:0;padding:0 12px}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button{background:transparent}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button svg{width:16px;height:16px;margin:0;padding:0;margin-left:4px;position:relative;top:4px}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button:hover,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button:focus,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button:active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.active{background:#000;outline:none}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.no-count{padding:0 14px}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.no-count svg{margin-left:0;top:2px}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-debug,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-good{background:#6d980a}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-debug:hover,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-debug:focus,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-debug:active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-debug.active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-good:hover,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-good:focus,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-good:active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-good.active{background:#000}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-error,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-bad{background:#d70000}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-error:hover,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-error:focus,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-error:active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-error.active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-bad:hover,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-bad:focus,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-bad:active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-bad.active{background:#000}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-fatal{background:#900}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-fatal:hover,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-fatal:focus,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-fatal:active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-fatal.active{background:#000}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-info{background:#286981}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-info:hover,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-info:focus,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-info:active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-info.active{background:#000}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-log,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-default{background:#444}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-log:hover,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-log:focus,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-log:active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-log.active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-default:hover,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-default:focus,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-default:active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-default.active{background:#000}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-warn{background:#d65507}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-warn:hover,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-warn:focus,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-warn:active,#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.notice-warn.active{background:#000}#sfcc_dev_tools .devtool-toolbar .hide-toolbar{background:#333;border:none;color:#FFF;display:inline-block;font-size:14px;font-weight:500;height:40px;line-height:40px;margin:0;padding:0 14px;position:absolute;right:0;top:0}#sfcc_dev_tools .devtool-toolbar .hide-toolbar svg{width:12px;height:12px;margin:0;padding:0;position:relative;top:2px}#sfcc_dev_tools .devtool-toolbar .hide-toolbar:hover{background:#000}#sfcc_dev_tools .devtool-toolbar .open-info{background:transparent;border:none;color:#FFF;display:inline-block;float:right;font-size:14px;font-weight:500;height:40px;line-height:40px;margin:0;padding:0 14px;position:absolute;right:40px;top:0;text-decoration:none}#sfcc_dev_tools .devtool-toolbar .open-info svg{width:14px;height:14px;margin:0;padding:0;position:relative;top:2px}#sfcc_dev_tools .devtool-toolbar .open-info:hover{background:#000}.devtool-tooltip{z-index:99999;padding:0;margin:0;font-size:14px;font-weight:normal}.devtool-tooltip .tooltip-inner,.devtool-tooltip .tooltip-inner:not([class*='background--']),.devtool-tooltip .tooltip-inner:not([class*='text--']),.devtool-tooltip .tooltip-inner:not([class*='font-size--']){background:black;color:white;border-radius:4px;padding:6px 12px}.devtool-tooltip .tooltip-arrow{width:0;height:0;border-style:solid;position:absolute;margin:5px;border-color:black;z-index:1}.devtool-tooltip[x-placement^="top"]{margin-bottom:5px}.devtool-tooltip[x-placement^="top"] .tooltip-arrow{border-width:5px 5px 0 5px;border-left-color:transparent !important;border-right-color:transparent !important;border-bottom-color:transparent !important;bottom:-5px;left:calc(50% - 5px);margin-top:0;margin-bottom:0}.devtool-tooltip[x-placement^="bottom"]{margin-top:5px}.devtool-tooltip[x-placement^="bottom"] .tooltip-arrow{border-width:0 5px 5px 5px;border-left-color:transparent !important;border-right-color:transparent !important;border-top-color:transparent !important;top:-5px;left:calc(50% - 5px);margin-top:0;margin-bottom:0}.devtool-tooltip[x-placement^="right"]{margin-left:5px}.devtool-tooltip[x-placement^="right"] .tooltip-arrow{border-width:5px 5px 5px 0;border-left-color:transparent !important;border-top-color:transparent !important;border-bottom-color:transparent !important;left:-5px;top:calc(50% - 5px);margin-left:0;margin-right:0}.devtool-tooltip[x-placement^="left"]{margin-right:5px}.devtool-tooltip[x-placement^="left"] .tooltip-arrow{border-width:5px 0 5px 5px;border-top-color:transparent !important;border-right-color:transparent !important;border-bottom-color:transparent !important;right:-5px;top:calc(50% - 5px);margin-left:0;margin-right:0}.devtool-tooltip.popover{font-size:12px}.devtool-tooltip.popover .popover-inner{background:#f9f9f9;color:black;padding:24px;border-radius:5px;box-shadow:0 5px 30px rgba(0,0,0,0.1)}.devtool-tooltip.popover .popover-arrow{border-color:#f9f9f9}.devtool-tooltip[aria-hidden='true']{visibility:hidden;opacity:0;transition:opacity .15s, visibility .15s}.devtool-tooltip[aria-hidden='false']{visibility:visible;opacity:1;transition:opacity .15s}.devtool-tooltip-disabled{display:none !important}.devtool-tooltip-wide .tooltip-inner{max-width:50rem}#sfcc_dev_tools .fade-enter-active,#sfcc_dev_tools .fade-leave-active{transition:opacity .25s}#sfcc_dev_tools .fade-enter,#sfcc_dev_tools .fade-leave-to{opacity:0}#sfcc_dev_tools .tree-view-wrapper{min-height:100%}#sfcc_dev_tools .tree-view-item-key{color:#fff;font-weight:300}#sfcc_dev_tools .tree-view-item-hint{color:#a2a2a3;font-weight:300 !important}#sfcc_dev_tools .tree-view-item,#sfcc_dev_tools .tree-view-item *,#sfcc_dev_tools pre{font-family:'Roboto Mono', monospace;font-weight:normal;font-size:14px;line-height:22px}#sfcc_dev_tools .tree-view-item-key-with-chevron{left:-14px;position:relative}#sfcc_dev_tools .tree-view-item-key-with-chevron::before,#sfcc_dev_tools .tree-view-item-key-with-chevron.opened::before{color:#fff;top:0 !important;opacity:0.25}#sfcc_dev_tools .tree-view-item-value{font-weight:500}#sfcc_dev_tools .tree-view-item-value.tree-view-item-value-string,#sfcc_dev_tools .tree-view-item-value.tree-view-item-value-string a{color:#ea7e52;white-space:pre-wrap}#sfcc_dev_tools .tree-view-item-value.tree-view-item-value-boolean{color:#3495e6}#sfcc_dev_tools .tree-view-item-value.tree-view-item-value-number{color:#abde91}#sfcc_dev_tools .tree-view-item-value.tree-view-item-value-null{color:#3495e6}#sfcc_dev_tools .tree-view-item-value.tree-view-item-value-string a{text-decoration:none}#sfcc_dev_tools .tree-view-item-value.tree-view-item-value-string a:hover{text-decoration:underline}#sfcc_dev_tools .tree-view-wrapper>.tree-view-item>.tree-view-item-leaf>.tree-view-item-node:first-of-type{display:none}#sfcc_dev_tools .subsection .tree-view-wrapper{min-height:unset}@media only screen and (max-width: 768px){#sfcc_dev_tools .devtool-drawer .devtool-menu{width:50px}#sfcc_dev_tools .devtool-drawer .devtool-menu button span{display:none !important}#sfcc_dev_tools .devtool-drawer .devtool-menu button svg{margin-right:0}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section{width:calc(100% - 50px);left:50px}}@media only screen and (max-width: 480px){#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button{padding:0 10px}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button svg{margin-left:4px}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.no-count{padding:0 10px}#sfcc_dev_tools .devtool-toolbar .toolbar-button-wrapper>button.toolbar-button.no-count svg{margin-left:0}#sfcc_dev_tools .devtool-drawer .devtool-close-drawer{top:3px;right:0}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section{padding:10px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button{font-size:10px;padding:6px 4px;margin-right:0px}#sfcc_dev_tools .devtool-drawer .devtool-drawer-section .button-wrapper button span.count{display:none}} 3 | 4 | --------------------------------------------------------------------------------