[{"data":1,"prerenderedAt":1761},["ShallowReactive",2],{"$TZCMGJBAKX":3},[4,100,300,395,461,549,833,1000],{"id":5,"title":6,"author":7,"body":8,"description":84,"extension":85,"intro":86,"meta":87,"name":86,"navigation":89,"path":90,"pubDate":91,"role":86,"seo":92,"stem":93,"tags":94,"__hash__":99},"content\u002Fblog\u002Fplaywright-webkit-testing.md","You Can Use Playwright to Test Your Site on WebKit","Danny Krämer",{"type":9,"value":10,"toc":75},"minimark",[11,15,20,25,28,32,50,53,64,70],[12,13,14],"p",{},"Cross-browser testing can be challenging, especially when testing WebKit browsers like Safari. If you are on Windows or Linux, you have to use tools like Browserstack to test on WebKit browsers like Safari because it is not available cross platform. But did you know that you can use Playwright to open a WebKit browser on any operating system? As Playwright can be used to run automated E2E-test cross-browser, it comes with its own bundled versions of every main browser engine and WebKit is one of them. Here is a very short tutorial on how it works.",[16,17,19],"h2",{"id":18},"tutorial","Tutorial",[21,22,24],"h3",{"id":23},"prerequisites","Prerequisites",[12,26,27],{},"You need to have installed Node.js and npm.",[21,29,31],{"id":30},"steps","Steps",[33,34,35,44],"ol",{},[36,37,38,39,43],"li",{},"Open up your terminal\u002Fpowershell and type ",[40,41,42],"code",{},"npx playwright install",".",[36,45,46,47],{},"To open a URL in WebKit run  the following command ",[40,48,49],{},"npx playwright wk \u003Cyour-url>",[12,51,52],{},"What you get is a WebKit browser with your URL opened. You can even use the WebKit Inspector. Depending on your operating system, not all features will be identical to the branded version of Safari. For that, you should use macOS. But still you can see how WebKit is rendering your site and identify some problems that may occur, without opening Browserstack or using an actual Apple device.",[12,54,55,56,63],{},"The Playwright ",[57,58,62],"a",{"href":59,"rel":60},"https:\u002F\u002Fplaywright.dev\u002Fdocs\u002Fbrowsers#open-pages",[61],"nofollow","documentation"," states the following:",[65,66,67],"blockquote",{},[12,68,69],{},"Playwright's WebKit is derived from the latest WebKit main branch sources, often before these updates are incorporated into Apple Safari and other WebKit-based browsers. This gives a lot of lead time to react on the potential browser update issues. Playwright doesn't work with the branded version of Safari since it relies on patches. Instead, you can test using the most recent WebKit build.",[65,71,72],{},[12,73,74],{},"Note that availability of certain features, which depend heavily on the underlying platform, may vary between operating systems. For example, available media codecs vary substantially between Linux, macOS and Windows. While running WebKit on Linux CI is usually the most affordable option, for the closest-to-Safari experience you should run WebKit on mac, for example if you do video playback.",{"title":76,"searchDepth":77,"depth":77,"links":78},"",2,[79],{"id":18,"depth":77,"text":19,"children":80},[81,83],{"id":23,"depth":82,"text":24},3,{"id":30,"depth":82,"text":31},"Playwright provides a convenient way to test websites in WebKit browsers across any operating system without requiring specialized tools like Browserstack or Apple devices. With just two simple commands, developers can install Playwright and launch a WebKit browser with inspector capabilities, though some platform-specific features may vary compared to Safari on macOS.","md",null,{"image":88},{"url":76,"alt":76},true,"\u002Fblog\u002Fplaywright-webkit-testing","2025-03-14",{"title":6,"description":84},"blog\u002Fplaywright-webkit-testing",[95,96,97,98],"testing","web development","playwright","frontend","5NHHCGbVcTjkpbTp06PG3jxop3My5KnPPQDigpGZ4qU",{"id":101,"title":102,"author":7,"body":103,"description":288,"extension":85,"intro":86,"meta":289,"name":86,"navigation":89,"path":291,"pubDate":292,"role":86,"seo":293,"stem":294,"tags":295,"__hash__":299},"content\u002Fblog\u002Fspa-db-aduit.md","Turn off DB_AUDIT Logs in SAP Commerce",{"type":9,"value":104,"toc":286},[105,116,125,134,147,152,178,183,197,202,268,282],[12,106,107,108,111,112,115],{},"SAP Commerce's database audit logging can get really annoying. Every database interaction creates a ",[40,109,110],{},"DB_AUDIT"," log from the ",[40,113,114],{},"AuditbaleActionsHandler",". Especially, if you want to debug a database heavy class, it can get really hard to find your beloved debug logs in between all the audit logs. But turning off the audit logs isn't that easy.",[12,117,118,119,124],{},"I found ",[57,120,123],{"href":121,"rel":122},"https:\u002F\u002Fcommunity.sap.com\u002Ft5\u002Fcrm-and-cx-blogs-by-members\u002Faudit-data-and-audit-logging-in-sap-commerce\u002Fba-p\u002F13738362",[61],"this article about audit logging",". It suggests to set the following properties:",[126,127,132],"pre",{"className":128,"code":130,"language":131},[129],"language-text","log4j2.logger.auditableActionsHandler.name=de.hybris.platform.audit.actions.impl.Slf4jAuditableActionHandler\nlog4j2.logger.auditableActionsHandler.level=OFF\n","text",[40,133,130],{"__ignoreMap":76},[12,135,136,137,140,141,146],{},"That did not work for me. Even after setting the properties in my ",[40,138,139],{},"local.properties"," I got all the logs. Also editing the Log4J config did not work. Then I found ",[57,142,145],{"href":143,"rel":144},"https:\u002F\u002Fme.sap.com\u002Fnotes\u002F3342126\u002FE",[61],"this note"," from SAP. The only thing that seems to work is to turn off the logging for every item type. But how do you know which types to set? Here is an easy step by step solution.",[33,148,149],{},[36,150,151],{},"You need to install xmlstarlet",[126,153,157],{"className":154,"code":155,"language":156,"meta":76,"style":76},"language-bash shiki shiki-themes github-dark github-light","sudo apt install xmlstarlet\n","bash",[40,158,159],{"__ignoreMap":76},[160,161,164,168,172,175],"span",{"class":162,"line":163},"line",1,[160,165,167],{"class":166},"s-Z4r","sudo",[160,169,171],{"class":170},"sg6BJ"," apt",[160,173,174],{"class":170}," install",[160,176,177],{"class":170}," xmlstarlet\n",[33,179,180],{"start":77},[36,181,182],{},"Go to your source",[126,184,186],{"className":154,"code":185,"language":156,"meta":76,"style":76},"cd path\u002Fto\u002Fhybris\u002Fbin  \n",[40,187,188],{"__ignoreMap":76},[160,189,190,194],{"class":162,"line":163},[160,191,193],{"class":192},"s0DvM","cd",[160,195,196],{"class":170}," path\u002Fto\u002Fhybris\u002Fbin\n",[33,198,199],{"start":82},[36,200,201],{},"Find all item types that are enabled for database audit logging.",[126,203,205],{"className":154,"code":204,"language":156,"meta":76,"style":76},"find . -name \"*-items.xml\" | xargs xmlstarlet select -t -m \"\u002F\u002Fitemtype\" -n -v \"concat(concat('dbaudit.', @code), '.disabled=true')\" | sort | uniq > dbaudit.txt\n",[40,206,207],{"__ignoreMap":76},[160,208,209,212,215,218,221,225,228,231,234,237,240,243,246,249,252,254,257,259,262,265],{"class":162,"line":163},[160,210,211],{"class":166},"find",[160,213,214],{"class":170}," .",[160,216,217],{"class":192}," -name",[160,219,220],{"class":170}," \"*-items.xml\"",[160,222,224],{"class":223},"scx8i"," |",[160,226,227],{"class":166}," xargs",[160,229,230],{"class":170}," xmlstarlet",[160,232,233],{"class":170}," select",[160,235,236],{"class":192}," -t",[160,238,239],{"class":192}," -m",[160,241,242],{"class":170}," \"\u002F\u002Fitemtype\"",[160,244,245],{"class":192}," -n",[160,247,248],{"class":192}," -v",[160,250,251],{"class":170}," \"concat(concat('dbaudit.', @code), '.disabled=true')\"",[160,253,224],{"class":223},[160,255,256],{"class":166}," sort",[160,258,224],{"class":223},[160,260,261],{"class":166}," uniq",[160,263,264],{"class":223}," >",[160,266,267],{"class":170}," dbaudit.txt\n",[12,269,270,271,274,275,278,279,281],{},"You will get a text file ",[40,272,273],{},"dbaudit.txt"," with all item types and the ",[40,276,277],{},"dbaudit.\u003Citemtype>.disbaled"," property set to true. Now, turn everything into lower case, and put them into your ",[40,280,139],{},". Voila! The logging is gone.",[283,284,285],"style",{},"html pre.shiki code .s-Z4r, html code.shiki .s-Z4r{--shiki-dark:#B392F0;--shiki-default:#6F42C1}html pre.shiki code .sg6BJ, html code.shiki .sg6BJ{--shiki-dark:#9ECBFF;--shiki-default:#032F62}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s0DvM, html code.shiki .s0DvM{--shiki-dark:#79B8FF;--shiki-default:#005CC5}html pre.shiki code .scx8i, html code.shiki .scx8i{--shiki-dark:#F97583;--shiki-default:#D73A49}",{"title":76,"searchDepth":77,"depth":77,"links":287},[],"The databse audit logging of SAP Commerce can fill up your logs. Then it is hard to use logs for debugging. This article covers how you can turn off the logging for local development.",{"image":290},{"url":76,"alt":76},"\u002Fblog\u002Fspa-db-aduit","2024-11-22",{"title":102,"description":288},"blog\u002Fspa-db-aduit",[296,297,298],"sap commerce","software development","configuration","8_IzlAecsNzzytI9_A_xBZPGMrreMgkJlsvuVu1eOUo",{"id":301,"title":302,"author":7,"body":303,"description":384,"extension":85,"intro":86,"meta":385,"name":86,"navigation":89,"path":387,"pubDate":388,"role":86,"seo":389,"stem":390,"tags":391,"__hash__":394},"content\u002Fblog\u002Fnotes-week-44.md","Weekly Notes 44\u002F24",{"type":9,"value":304,"toc":382},[305,320,323,336,338,346,358,360],[12,306,307,308,313,314,319],{},"After Twitter updated its blocking policy, there was a notable of exodus from Twitter to BlueSky. For example ",[57,309,312],{"href":310,"rel":311},"https:\u002F\u002Fbsky.app\u002Fprofile\u002Fkelseyhightower.com",[61],"Kelsey Hightower"," suspended his Twitter account and moved to BlueSky. Several other tech personalities, like ",[57,315,318],{"href":316,"rel":317},"https:\u002F\u002Fbsky.app\u002Fprofile\u002Fthorstenball.com",[61],"Thorsten Ball",", are now actively posting there. Could this signal a larger shift from Twitter to BlueSky?",[321,322],"hr",{},[12,324,325,326,331,332,335],{},"The ",[57,327,330],{"href":328,"rel":329},"https:\u002F\u002F2024.stateofcss.com\u002Fen-US",[61],"State of CSS 2024"," results have been released. The clear winner appears to be the ",[333,334],"has",{},"() pseudo-class. Beyond this highlight, the results were largely predictable: Tailwind continues to dominate as the preferred CSS framework, while SASS\u002FSCSS remains the most popular CSS preprocessor.",[321,337],{},[12,339,325,340,345],{},[57,341,344],{"href":342,"rel":343},"https:\u002F\u002Ftsh.io\u002Fstate-of-frontend\u002F",[61],"State of Frontend Survey 2024"," have also been published. Some results:",[347,348,349,352,355],"ul",{},[36,350,351],{},"TypeScript has become the de facto standard for frontend development",[36,353,354],{},"Vite has emerged as the predominant build tool",[36,356,357],{},"React maintains its position as the most widely used frontend framework",[321,359],{},[12,361,362,363,368,369,374,375,368,378,381],{},"Thorsten Ball had a great ",[57,364,367],{"href":365,"rel":366},"https:\u002F\u002Fregisterspill.thorstenball.com\u002Fp\u002Fin-conversation-david-albert",[61],"conversation with David Albert",", the co-founder of ",[57,370,373],{"href":371,"rel":372},"https:\u002F\u002Fwww.recurse.com",[61],"the Recurse Center",". Their discussion covered various topics, including the nature of learning, whether learning should be enjoyable, and the decision-making process behind building custom tools.\nThorsten Ball had a great ",[57,376,367],{"href":365,"rel":377},[61],[57,379,373],{"href":371,"rel":380},[61],". Their discussion covered various topics, including the nature of learning, whether learning should be enjoyable, and the decision-making process behind building custom tools.",{"title":76,"searchDepth":77,"depth":77,"links":383},[],"Weekly Notes of the week 44 of 2024. Results for the State of CSS and State of Frontend 2024 surveys are in.",{"image":386},{"url":76,"alt":76},"\u002Fblog\u002Fnotes-week-44","2024-11-02",{"title":302,"description":384},"blog\u002Fnotes-week-44",[392,297,393,98],"notes","CSS","2WI5hNRSvlysJjDz4uDovsqn8Jt24xSTspf4omMZ6X8",{"id":396,"title":397,"author":7,"body":398,"description":450,"extension":85,"intro":86,"meta":451,"name":86,"navigation":89,"path":453,"pubDate":454,"role":86,"seo":455,"stem":456,"tags":457,"__hash__":460},"content\u002Fblog\u002Fnotes-week-43.md","Weekly Notes 43\u002F24",{"type":9,"value":399,"toc":448},[400,408,411,416,427,429,438,440],[12,401,402,407],{},[57,403,406],{"href":404,"rel":405},"https:\u002F\u002Fsubstack.com\u002Fhome\u002Fpost\u002Fp-150382437",[61],"Adam Ard described"," an interesting point about Management. There is a continuum between no managers at all and having everyone be a manager. The interesting point is, that having no mangers is effectively the same as having only managers, because everyone needs to do management. Therefore, Founder Mode, described by Paul Graham, isn't that bad at all. If you have only one manager or benevolent dictator (as in open source projects), that means there is less bureaucracy and less surveillance - because how should one manager watch after everyone? The management role then becomes more about company culture than about micromanaging developers.",[12,409,410],{},"Also, there were two interesting quotes:",[65,412,413],{},[12,414,415],{},"“It doesn't make sense to hire smart people and then tell them what to to , We hire smart people so they can tell us what to do.” -Steve Jobs",[65,417,418],{},[12,419,420,421,426],{},"Don't listen to marketing people or designers or product managers just because of their job titles. If they have good ideas, use them, but it's up to you to decide; software has to be designed by hackers who understand design, not designers who know a little about software.\n(",[57,422,425],{"href":423,"rel":424},"https:\u002F\u002Fpaulgraham.com\u002Froad.html",[61],"Source",")",[321,428],{},[12,430,431,432,437],{},"I just finished reading ",[57,433,436],{"href":434,"rel":435},"https:\u002F\u002Fwww.susandavid.com\u002Fbook\u002F",[61],"Emotional Agility - Get Unstuck, Embrace Change and Thrive in Work and Life",". One metaphor that stood out to me was the following: It is not helpful to set yourself unrealistic goals for your life, like being always happy, never getting hurt, never getting stressed, or never making mistakes. Do you know who the only people are that never make mistakes? Dead people. So accept yourself, but also life, as what it is: a mess with good, bad, and ugly parts, but also with parts that are beautiful and fun as hell.",[321,439],{},[12,441,442,447],{},[57,443,446],{"href":444,"rel":445},"https:\u002F\u002Fnabeelqu.co\u002Freflections-on-palantir",[61],"Nabeel S. Qureshi post on Palantir"," is pretty interesting. Especially the concept of \"forward deployed engineers\" who work on site for companies to develop software products that those companies need to handle a specific problems, and engineers back at Palantir who take the lessons learned in those projects and build core products that can be sold to other companies. Also, the discussion of working for companies or organizations that handle morally thorny areas like defense was interesting.",{"title":76,"searchDepth":77,"depth":77,"links":449},[],"Weekly notes of the week 43\u002F2024 about how many managers a company should have, Emotional Agility and Palantir.",{"image":452},{"url":76,"alt":76},"\u002Fblog\u002Fnotes-week-43","2024-10-28",{"title":397,"description":450},"blog\u002Fnotes-week-43",[392,297,458,459],"emotional agility","management","suHVEOziYKt9R8rWwvfTGbvRqks98Wq-jMbuMKiuuGE",{"id":462,"title":463,"author":7,"body":464,"description":538,"extension":85,"intro":86,"meta":539,"name":86,"navigation":89,"path":541,"pubDate":542,"role":86,"seo":543,"stem":544,"tags":545,"__hash__":548},"content\u002Fblog\u002Fsap-commerce-frontend-template-addon.md","Loading a JSP for a Page Template From an Addon",{"type":9,"value":465,"toc":536},[466,473,490,496,512,533],[12,467,468,469,472],{},"If you use an Accelerator Storefront in your SAP Commerce project, you might want to introduce a new page template for a CMS site. A page template needs an associated JSP that is used to render the page. You add the path to the JSP in the field ",[40,470,471],{},"frontendTemplateName",". So, we could write an Impex like this:",[126,474,478],{"className":475,"code":476,"language":477,"meta":76,"style":76},"language-Impex shiki shiki-themes github-dark github-light","INSERT_UPDATE PageTemplate; $contentCV[unique = true]; uid[unique = true]; name; frontendTemplateName; restrictedPageTypes(code); active[default = true];  \n                          ;                          ; \u003CYourPageTemplate>; \u003CYourPageTemplateName>; \u002Fpages\u002Flayout\u002F\u003Cyour-template>; ContentPage;\n","Impex",[40,479,480,485],{"__ignoreMap":76},[160,481,482],{"class":162,"line":163},[160,483,484],{},"INSERT_UPDATE PageTemplate; $contentCV[unique = true]; uid[unique = true]; name; frontendTemplateName; restrictedPageTypes(code); active[default = true];  \n",[160,486,487],{"class":162,"line":77},[160,488,489],{},"                          ;                          ; \u003CYourPageTemplate>; \u003CYourPageTemplateName>; \u002Fpages\u002Flayout\u002F\u003Cyour-template>; ContentPage;\n",[12,491,492,493,43],{},"If we do it like this, the template is loaded from your storefront extension in the following directory: ",[40,494,495],{},"\u003Cyourstorefrontextension>\u002Fweb\u002Fwebroot\u002FWEB-INF\u002Fviews\u002Fresponsive\u002Fpages\u002Flayout",[12,497,498,499,504,505,508,509,43],{},"Now, I created a",[57,500,503],{"href":501,"rel":502},"https:\u002F\u002Fhelp.sap.com\u002Fdocs\u002FSAP_COMMERCE\u002Fb490bb4e85bc42a7aa09d513d0bcb18e\u002F8adf7365866910149ceb975f778d809d.html",[61]," storefront addon extension to modify an existing storefront",". JSPs from an addon get copied into the storefront in the ",[40,506,507],{},"views\u002Faddons\u002F\u003Cyouraddon>\u002Fresponsive\u002F",". So, how do we tell the system to look into that directory? It does not work to just write something like ",[40,510,511],{},"..\u002Faddons\u002F\u003Cyouraddon>\u002Fresponsive\u002Fpages\u002Flayout",[12,513,514,515,517,518,521,522,525,526,528,529,532],{},"It took me some time and extensive Google searches to find the solution: The string in the ",[40,516,471],{}," gets special parsing treatment. The storefront has a ",[40,519,520],{},"UiExperienceViewResolver",", and there the path to the template gets parsed. As it turns out, if you put ",[40,523,524],{},"addon:"," before your path, SAP Commerce searches in the addon directory instead of the base storefront template directory. So, if we change the string for the ",[40,527,471],{}," in the Impex to ",[40,530,531],{},"addon:\u002F\u003Cyouraddon>\u002Fpages\u002Flayout\u002F\u003Cyour-template>",", it works just fine.",[283,534,535],{},"html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":76,"searchDepth":77,"depth":77,"links":537},[],"A custom page template in an SAP Commerce Accelerator Storefront addon can be properly referenced by using the prefix \"addon:\" in the frontendTemplateName field of the PageTemplate Impex.",{"image":540},{"url":76,"alt":76},"\u002Fblog\u002Fsap-commerce-frontend-template-addon","2024-10-11",{"title":463,"description":538},"blog\u002Fsap-commerce-frontend-template-addon",[546,547],"sap-commerce","smartedit","65h-j5YB-tmFenHWUjwcBkdWtAVvabLgtZgpRSPxjG0",{"id":550,"title":551,"author":7,"body":552,"description":824,"extension":85,"intro":86,"meta":825,"name":86,"navigation":89,"path":827,"pubDate":828,"role":86,"seo":829,"stem":830,"tags":831,"__hash__":832},"content\u002Fblog\u002Fsap-commerce-intercepting-populators.md","SAP Commerce: Intercepting Populated Item Models Before Persistence",{"type":9,"value":553,"toc":822},[554,562,565,568,574,577,621,624,669,676,820],[12,555,556,557],{},"Let’s say you have written a new CMS Component and want to intercept the populated item model before persisting it to the database to make some transformations on its data. There is a pretty straightforward explanation on how to do this on the SAP Help ",[57,558,561],{"href":559,"rel":560},"https:\u002F\u002Fhelp.sap.com\u002Fdocs\u002FSAP_COMMERCE\u002F9d346683b0084da2938be8a285c0c27a\u002Fcf0445b16a4f44d9844381a34572d7b8.html",[61],"Extending the CMS Item API",[12,563,564],{},"Under \"Configuring the New Populator\" the explanation gets a bit confusing. So here is how it actually works:",[12,566,567],{},"After you created your populator, you create the bean for it:",[126,569,572],{"className":570,"code":571,"language":131},[129],"\u003Cbean id=\"customItemContentPopulator\" class=\"path.to.populator.CustomItemContentPopulator\"\u002F>\n",[40,573,571],{"__ignoreMap":76},[12,575,576],{},"Then the article says the following:",[65,578,579,582],{},[12,580,581],{},"You must then map your Custom Item Type to the new populator to make it visible to the CMS Item Type Populator Provider. The following code shows the configuration for the new populator:",[126,583,587],{"className":584,"code":585,"language":586,"meta":76,"style":76},"language-XML shiki shiki-themes github-dark github-light","\u003Cbean id=\"customItemContentPopulator\" class=\"path.to.populator.CustomItemContentPopulator\"\u002F>\n\n\u003Cbean depends-on=\"cmsContentItemTypePopulatorsMap\" parent=\"mapMergeDirective\">\n        \u003Cproperty name=\"key\" value=\"CustomItem\"\u002F>\n        \u003Cproperty name=\"value\" ref=\"customItemContentPopulator\"\u002F>\n\u003C\u002Fbean>\n","XML",[40,588,589,593,598,603,609,615],{"__ignoreMap":76},[160,590,591],{"class":162,"line":163},[160,592,571],{},[160,594,595],{"class":162,"line":77},[160,596,597],{"emptyLinePlaceholder":89},"\n",[160,599,600],{"class":162,"line":82},[160,601,602],{},"\u003Cbean depends-on=\"cmsContentItemTypePopulatorsMap\" parent=\"mapMergeDirective\">\n",[160,604,606],{"class":162,"line":605},4,[160,607,608],{},"        \u003Cproperty name=\"key\" value=\"CustomItem\"\u002F>\n",[160,610,612],{"class":162,"line":611},5,[160,613,614],{},"        \u003Cproperty name=\"value\" ref=\"customItemContentPopulator\"\u002F>\n",[160,616,618],{"class":162,"line":617},6,[160,619,620],{},"\u003C\u002Fbean>\n",[12,622,623],{},"If you do this, only your populator will be called for the component. That means other populators, like the one that will handle the saving of the content slot where the component is contained, will not be called. Often that is, of course, not what we want. The documentation then states the following:",[65,625,626,629],{},[12,627,628],{},"If you want to apply many populators to the same CMS item type, you can use a composite populator to configure the new populator as follows:",[126,630,632],{"className":584,"code":631,"language":586,"meta":76,"style":76},"\u003Cbean id=\"cmsContentItemTypePopulatorsMap\" parent=\"cmsCompositePopulator\">\n \u003Cproperty name=\"populators\">\n     \u003Clist merge=\"true\">\n       \u003Cbean id=\"customItemContentPopulator\" class=\"path.to.populator.CustomItemContentPopulator\"\u002F>\n     \u003C\u002Flist>\n  \u003C\u002Fproperty>\n\u003C\u002Fbean> \n",[40,633,634,639,644,649,654,659,664],{"__ignoreMap":76},[160,635,636],{"class":162,"line":163},[160,637,638],{},"\u003Cbean id=\"cmsContentItemTypePopulatorsMap\" parent=\"cmsCompositePopulator\">\n",[160,640,641],{"class":162,"line":77},[160,642,643],{}," \u003Cproperty name=\"populators\">\n",[160,645,646],{"class":162,"line":82},[160,647,648],{},"     \u003Clist merge=\"true\">\n",[160,650,651],{"class":162,"line":605},[160,652,653],{},"       \u003Cbean id=\"customItemContentPopulator\" class=\"path.to.populator.CustomItemContentPopulator\"\u002F>\n",[160,655,656],{"class":162,"line":611},[160,657,658],{},"     \u003C\u002Flist>\n",[160,660,661],{"class":162,"line":617},[160,662,663],{},"  \u003C\u002Fproperty>\n",[160,665,667],{"class":162,"line":666},7,[160,668,620],{},[12,670,671,672,675],{},"That is fine as far as it goes, but of course you have to add the other populators as well, or you will only have one populator in your map again. Also, you have to add your populators map to the ",[40,673,674],{},"cmsContentItemTypePopulatorsMap",". So your config should look something like this:",[126,677,679],{"className":584,"code":678,"language":586,"meta":76,"style":76},"\u003Calias name=\"dsCustomComponentContentPopulator\" alias=\"defaultCustomComponentContentPopulator\"\u002F>\n    \u003Cbean id=\"customComponentContentPopulator\" class=\"de.custom.components.populators.CustomComponentContentPopulator\" \u002F>\n\n    \u003Cbean id=\"cmsCustomComponentItemTypePopulatorsMap\" parent=\"cmsCompositePopulator\">\n        \u003Cproperty name=\"populators\">\n            \u003Clist merge=\"true\">\n                \u003Cref bean=\"defaultCustomComponentContentPopulator\"\u002F>\n                \u003Cbean class=\"de.hybris.platform.cmsfacades.cmsitems.populators.AbstractCMSComponentContentPopulator\">\n                    \u003Cproperty name=\"contentSlotAdminService\" ref=\"cmsAdminContentSlotService\"\u002F>\n                    \u003Cproperty name=\"uniqueItemIdentifierService\" ref=\"cmsUniqueItemIdentifierService\"\u002F>\n                    \u003Cproperty name=\"validationDtoFactory\" ref=\"validationDtoFactory\"\u002F>\n                    \u003Cproperty name=\"componentTypeAllowedForContentSlotPredicate\" ref=\"componentTypeAllowedForContentSlotPredicate\"\u002F>\n                    \u003Cproperty name=\"relationBetweenComponentsService\" ref=\"relationBetweenComponentsService\"\u002F>\n                \u003C\u002Fbean>\n                \u003Cbean class=\"de.hybris.platform.cmsfacades.cmsitems.populators.CMSItemLinkToggleDataToModelPopulator\">\n                    \u003Cproperty name=\"cmsModelContainsLinkTogglePredicate\" ref=\"cmsModelContainsLinkTogglePredicate\"\u002F>\n                \u003C\u002Fbean>\n            \u003C\u002Flist>\n        \u003C\u002Fproperty>\n    \u003C\u002Fbean>\n\n    \u003Cbean depends-on=\"cmsContentItemTypePopulatorsMap\" parent=\"mapMergeDirective\">\n        \u003Cproperty name=\"key\" value=\"CustomComponent\"\u002F>\n        \u003Cproperty name=\"value\" ref=\"customComponentItemTypePopulatorsMap\"\u002F>\n    \u003C\u002Fbean>\n",[40,680,681,686,691,695,700,705,710,715,721,727,733,739,745,751,757,763,769,774,780,786,792,797,803,809,815],{"__ignoreMap":76},[160,682,683],{"class":162,"line":163},[160,684,685],{},"\u003Calias name=\"dsCustomComponentContentPopulator\" alias=\"defaultCustomComponentContentPopulator\"\u002F>\n",[160,687,688],{"class":162,"line":77},[160,689,690],{},"    \u003Cbean id=\"customComponentContentPopulator\" class=\"de.custom.components.populators.CustomComponentContentPopulator\" \u002F>\n",[160,692,693],{"class":162,"line":82},[160,694,597],{"emptyLinePlaceholder":89},[160,696,697],{"class":162,"line":605},[160,698,699],{},"    \u003Cbean id=\"cmsCustomComponentItemTypePopulatorsMap\" parent=\"cmsCompositePopulator\">\n",[160,701,702],{"class":162,"line":611},[160,703,704],{},"        \u003Cproperty name=\"populators\">\n",[160,706,707],{"class":162,"line":617},[160,708,709],{},"            \u003Clist merge=\"true\">\n",[160,711,712],{"class":162,"line":666},[160,713,714],{},"                \u003Cref bean=\"defaultCustomComponentContentPopulator\"\u002F>\n",[160,716,718],{"class":162,"line":717},8,[160,719,720],{},"                \u003Cbean class=\"de.hybris.platform.cmsfacades.cmsitems.populators.AbstractCMSComponentContentPopulator\">\n",[160,722,724],{"class":162,"line":723},9,[160,725,726],{},"                    \u003Cproperty name=\"contentSlotAdminService\" ref=\"cmsAdminContentSlotService\"\u002F>\n",[160,728,730],{"class":162,"line":729},10,[160,731,732],{},"                    \u003Cproperty name=\"uniqueItemIdentifierService\" ref=\"cmsUniqueItemIdentifierService\"\u002F>\n",[160,734,736],{"class":162,"line":735},11,[160,737,738],{},"                    \u003Cproperty name=\"validationDtoFactory\" ref=\"validationDtoFactory\"\u002F>\n",[160,740,742],{"class":162,"line":741},12,[160,743,744],{},"                    \u003Cproperty name=\"componentTypeAllowedForContentSlotPredicate\" ref=\"componentTypeAllowedForContentSlotPredicate\"\u002F>\n",[160,746,748],{"class":162,"line":747},13,[160,749,750],{},"                    \u003Cproperty name=\"relationBetweenComponentsService\" ref=\"relationBetweenComponentsService\"\u002F>\n",[160,752,754],{"class":162,"line":753},14,[160,755,756],{},"                \u003C\u002Fbean>\n",[160,758,760],{"class":162,"line":759},15,[160,761,762],{},"                \u003Cbean class=\"de.hybris.platform.cmsfacades.cmsitems.populators.CMSItemLinkToggleDataToModelPopulator\">\n",[160,764,766],{"class":162,"line":765},16,[160,767,768],{},"                    \u003Cproperty name=\"cmsModelContainsLinkTogglePredicate\" ref=\"cmsModelContainsLinkTogglePredicate\"\u002F>\n",[160,770,772],{"class":162,"line":771},17,[160,773,756],{},[160,775,777],{"class":162,"line":776},18,[160,778,779],{},"            \u003C\u002Flist>\n",[160,781,783],{"class":162,"line":782},19,[160,784,785],{},"        \u003C\u002Fproperty>\n",[160,787,789],{"class":162,"line":788},20,[160,790,791],{},"    \u003C\u002Fbean>\n",[160,793,795],{"class":162,"line":794},21,[160,796,597],{"emptyLinePlaceholder":89},[160,798,800],{"class":162,"line":799},22,[160,801,802],{},"    \u003Cbean depends-on=\"cmsContentItemTypePopulatorsMap\" parent=\"mapMergeDirective\">\n",[160,804,806],{"class":162,"line":805},23,[160,807,808],{},"        \u003Cproperty name=\"key\" value=\"CustomComponent\"\u002F>\n",[160,810,812],{"class":162,"line":811},24,[160,813,814],{},"        \u003Cproperty name=\"value\" ref=\"customComponentItemTypePopulatorsMap\"\u002F>\n",[160,816,818],{"class":162,"line":817},25,[160,819,791],{},[283,821,535],{},{"title":76,"searchDepth":77,"depth":77,"links":823},[],"To configure a new CMS component populator in SAP Commerce and ensure it works alongside other populators, you need to create the populator, define it as a bean, and include it in a composite populator map to apply multiple populators to the same CMS item type.",{"image":826},{"url":76,"alt":76},"\u002Fblog\u002Fsap-commerce-intercepting-populators","2024-09-11",{"title":551,"description":824},"blog\u002Fsap-commerce-intercepting-populators",[296],"T6mSAVu3X1dMRvMhbbvnztwXRP9AbtSWV9NoQn5i7q0",{"id":834,"title":835,"author":7,"body":836,"description":989,"extension":85,"intro":86,"meta":990,"name":86,"navigation":89,"path":992,"pubDate":993,"role":86,"seo":994,"stem":995,"tags":996,"__hash__":999},"content\u002Fblog\u002Fsap-commerce-backoffice-config.md","SAP Commerce: Change Nested Editor in Backoffice config",{"type":9,"value":837,"toc":984},[838,842,845,853,856,860,900,904,907,979,982],[16,839,841],{"id":840},"editing-custom-fields-in-the-backoffice","Editing Custom Fields in the Backoffice",[12,843,844],{},"Recently, I created a custom CMS component and for that, I created a new type for localized maps. Out of the box, the SAP Commerce Backoffice is able to display and edit maps, but if those maps are localized (so you have one map for each language), the Backoffice just does not know which editor to show.",[12,846,847,848],{},"Of course, you could just write a completely new widget to edit the field, but that would actually be a bit overkill for this use case because we already have an editor for localized fields and an editor for maps; we just have to fuse them somehow. Luckily, in the SAP Help, there is a description of exactly this case. You can find it here:  ",[57,849,852],{"href":850,"rel":851},"https:\u002F\u002Fhelp.sap.com\u002Fdocs\u002FSAP_COMMERCE_CLOUD_PUBLIC_CLOUD\u002F9b5366ff6eb34df5be29881ff55f97d2\u002F8c163f19866910148f748048c64841eb.html?locale=en-US",[61],"SAP Help Nested Editors",[12,854,855],{},"But as I was struggling a bit to get it right and I found the documentation a bit confusing and short, here are some extra things to consider.",[16,857,859],{"id":858},"troubleshooting-the-process","Troubleshooting the Process",[347,861,862,873,884,894,897],{},[36,863,864,865,868,869,872],{},"Make sure your ",[40,866,867],{},"backoffice-config.xml"," follows the following naming convention: ",[40,870,871],{},"\u003Cmy-extension-name>-backoffice-config.xml"," and is located in the resources directory of your extension.",[36,874,875,876,879,880,883],{},"Your file must be a valid XML, so do not forget the ",[40,877,878],{},"\u003Cconfig>"," and ",[40,881,882],{},"\u003C?xml>"," tags.",[36,885,886,887,890,891,43],{},"If your extension is not a backoffice extension, then you need to add ",[40,888,889],{},"\u003Cmeta key=\"backoffice-module\" value=\"true\" \u002F>"," to your ",[40,892,893],{},"extensioninfo.xml",[36,895,896],{},"It is pretty confusing that in the help article, three lines per attribute are shown in the example. You actually only need one line per attribute.",[36,898,899],{},"For me, it was not possible to just change the editor for the fields in the unbound section. The only thing that worked for me was to add the fields to another section and then change the editor.",[16,901,903],{"id":902},"example","Example",[12,905,906],{},"So here is my final code example:",[126,908,912],{"className":909,"code":910,"language":911,"meta":76,"style":76},"language-xml shiki shiki-themes github-dark github-light","\u003C?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\u003Cconfig xmlns=\"http:\u002F\u002Fwww.hybris.com\u002Fcockpit\u002Fconfig\">\n    \u003Ccontext merge-by=\"type\" parent=\"SimpleCMSComponent\" type=\"CustomComponent\" component=\"editor-area\" module=\"myextension\">\n    \u003CeditorArea:editorArea xmlns:editorArea=\"http:\u002F\u002Fwww.hybris.com\u002Fcockpitng\u002Fcomponent\u002FeditorArea\">\n        \u003CeditorArea:tab name=\"hmc.properties\">\n            \u003CeditorArea:section name=\"hmc.essential\">\n                \u003CeditorArea:attribute editor=\"com.hybris.cockpitng.editor.localized(com.hybris.cockpitng.editor.defaultmap)\" qualifier=\"dynamicFields\"\u002F>\n                \u003CeditorArea:attribute qualifier=\"htmlMarkup\" \u002F>\n            \u003C\u002FeditorArea:section>\n        \u003C\u002FeditorArea:tab>\n    \u003C\u002FeditorArea:editorArea>\n\u003C\u002Fcontext>\n\u003C\u002Fconfig>\n","xml",[40,913,914,919,924,929,934,939,944,949,954,959,964,969,974],{"__ignoreMap":76},[160,915,916],{"class":162,"line":163},[160,917,918],{},"\u003C?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n",[160,920,921],{"class":162,"line":77},[160,922,923],{},"\u003Cconfig xmlns=\"http:\u002F\u002Fwww.hybris.com\u002Fcockpit\u002Fconfig\">\n",[160,925,926],{"class":162,"line":82},[160,927,928],{},"    \u003Ccontext merge-by=\"type\" parent=\"SimpleCMSComponent\" type=\"CustomComponent\" component=\"editor-area\" module=\"myextension\">\n",[160,930,931],{"class":162,"line":605},[160,932,933],{},"    \u003CeditorArea:editorArea xmlns:editorArea=\"http:\u002F\u002Fwww.hybris.com\u002Fcockpitng\u002Fcomponent\u002FeditorArea\">\n",[160,935,936],{"class":162,"line":611},[160,937,938],{},"        \u003CeditorArea:tab name=\"hmc.properties\">\n",[160,940,941],{"class":162,"line":617},[160,942,943],{},"            \u003CeditorArea:section name=\"hmc.essential\">\n",[160,945,946],{"class":162,"line":666},[160,947,948],{},"                \u003CeditorArea:attribute editor=\"com.hybris.cockpitng.editor.localized(com.hybris.cockpitng.editor.defaultmap)\" qualifier=\"dynamicFields\"\u002F>\n",[160,950,951],{"class":162,"line":717},[160,952,953],{},"                \u003CeditorArea:attribute qualifier=\"htmlMarkup\" \u002F>\n",[160,955,956],{"class":162,"line":723},[160,957,958],{},"            \u003C\u002FeditorArea:section>\n",[160,960,961],{"class":162,"line":729},[160,962,963],{},"        \u003C\u002FeditorArea:tab>\n",[160,965,966],{"class":162,"line":735},[160,967,968],{},"    \u003C\u002FeditorArea:editorArea>\n",[160,970,971],{"class":162,"line":741},[160,972,973],{},"\u003C\u002Fcontext>\n",[160,975,976],{"class":162,"line":747},[160,977,978],{},"\u003C\u002Fconfig>\n",[12,980,981],{},"As you can see, I just nested the localized editor with the default map editor and moved the fields to the properties tab in an essential section.",[283,983,535],{},{"title":76,"searchDepth":77,"depth":77,"links":985},[986,987,988],{"id":840,"depth":77,"text":841},{"id":858,"depth":77,"text":859},{"id":902,"depth":77,"text":903},"Changing the editor conifg in the SAP Commerce Backoffice can be a bit tricky. Here are some hints for the case you want to config a nested editor.",{"image":991},{"url":76,"alt":76},"\u002Fblog\u002Fsap-commerce-backoffice-config","2024-07-18",{"title":835,"description":989},"blog\u002Fsap-commerce-backoffice-config",[296,997,998],"backoffice","config","JwiGuaxgCywGAZUjs3_jMxAhMGBTuj2Y8sQXGKlGHYA",{"id":1001,"title":1002,"author":7,"body":1003,"description":1751,"extension":85,"intro":86,"meta":1752,"name":86,"navigation":89,"path":1754,"pubDate":1755,"role":86,"seo":1756,"stem":1757,"tags":1758,"__hash__":1760},"content\u002Fblog\u002Fgit-hooks-with-husky.md","Using Git Hooks With Husky.js",{"type":9,"value":1004,"toc":1743},[1005,1009,1012,1029,1037,1041,1078,1083,1121,1124,1128,1135,1138,1142,1149,1641,1645,1651,1723,1731,1740],[16,1006,1008],{"id":1007},"what-are-git-hooks-anyway","What are git hooks anyway?",[12,1010,1011],{},"Everybody knows this situation: You set up linters and formatting, you make sure the unit tests are running and all is green. You check out the development branch and the first thing you see is the linter screaming at you. Someone pushed to the development branch without pleasing the allmighty linter overloard. You jump onto slack and ask if the person could fix their code, because you know, we are caring about code quality here. Or more often than not you fix it yourself.",[12,1013,1014,1015,1020,1021,1024,1025,1028],{},"Here ",[57,1016,1019],{"href":1017,"rel":1018},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fgithooks",[61],"git hooks"," come in handy. Git hooks are a built in feature of git. Hooks are scripts that can be run in most of the stages of your git workflow (pre-commit, pre-push etc). All you have to do is put your scripts (they must be executable, so you can write them in whatever language you like) in the ",[40,1022,1023],{},"$GIT_DIR\u002Fhooks"," directory. But there is a problem: The ",[40,1026,1027],{},".git"," directoy is not part of the repository. You cannot commit them and share them with your team easily. Here husky.js comes in handy.",[65,1030,1031],{},[12,1032,1033,1034],{},"TIPP: Your scripts must be executable. So make sure that the access permissions are set right. On Linux machines you can do this with ",[40,1035,1036],{},"chmod +x \u003Cscript-file>",[16,1038,1040],{"id":1039},"huskyjs-for-managing-git-hooks","Husky.js for managing Git Hooks",[12,1042,1043,1048,1049,1052,1053,1056,1057,1060,1061,1063,1064,1067,1068,879,1071,1074,1075,43],{},[57,1044,1047],{"href":1045,"rel":1046},"https:\u002F\u002Ftypicode.github.io\u002Fhusky",[61],"Husky.js"," is a npm package that helps you to organize and share your git hooks. In theory you could just let every developer change the ",[40,1050,1051],{},"core.hooksPath"," property in their local git repository to a directory with the shared scripts. Husky helps you to automate this process. It adds a ",[40,1054,1055],{},"prepare"," script to your ",[40,1058,1059],{},"package.json"," that sets the ",[40,1062,1051],{}," property to a ",[40,1065,1066],{},".husky"," folder in the repository and builds some scaffolding around your hooks. All a new Dev in your project has to do is checkout the repository, run ",[40,1069,1070],{},"npm install",[40,1072,1073],{},"npm run prepare"," and the hooks will work for her. She can also add some of her own hooks for example with ",[40,1076,1077],{},"npx husky add .husky\u002Fpre-commit \"npm test\"",[65,1079,1080],{},[12,1081,1082],{},"TIPP: Here is a basic setup for a new project.",[33,1084,1085,1090,1106,1115],{},[36,1086,1087],{},[40,1088,1089],{},"npm install husky --save-dev",[36,1091,1092,1093,1095,1096,1098,1099,1102,1103],{},"If your ",[40,1094,1059],{}," is in the same directory as your ",[40,1097,1027],{}," directory run ",[40,1100,1101],{},"npx husky install"," otherwise you can install husky in a custom directory ",[40,1104,1105],{},"npx husky install ~\u002Fproject\u002F.husky",[36,1107,1108,1109,1111,1112,1114],{},"You can add the prepare lifecycle script into your ",[40,1110,1059],{},". The script will be run after ",[40,1113,1070],{},". If you don't want to auto install husky you can just give it another name and run it manually.",[36,1116,1117,1118],{},"Add a hook with ",[40,1119,1120],{},"npm husky add .husky\u002Fpre-commit \"npm test\"",[12,1122,1123],{},"So Husky is a lightweight package that takes an annoying task of setting up the shared git hooks of your team and basically wraps it into one terminal command.",[16,1125,1127],{"id":1126},"useful-scripts","Useful Scripts",[12,1129,1130],{},[1131,1132],"img",{"alt":1133,"src":1134},"Automate all the things meme","https:\u002F\u002Fwww.kitchensoap.com\u002Fwp-content\u002Fuploads\u002F2012\u002F07\u002Fautomate_all_the_things1.jpeg",[12,1136,1137],{},"But what are some useful scripts to run in your git workflow?",[21,1139,1141],{"id":1140},"prepare-commit-msg-check-for-jira-id","Prepare-commit-msg check for Jira ID",[12,1143,1144,1145,1148],{},"This hook will run if you ",[40,1146,1147],{},"git commit"," right after preparing the default log message, and before the editor is started. Here is a bash script that checks if the branch name contains a Jira ID. If so it checks if the Jira ID of the branch name and the ID of the commit message are the same. If the branch name contains a Jira ID and the commit message does not, it adds the Jira ID of the branch name to the commit mesasge.",[126,1150,1152],{"className":154,"code":1151,"language":156,"meta":76,"style":76},"#!\u002Fusr\u002Fbin\u002Fenv bash\n. \"$(dirname -- \"$3\")\u002F_\u002Fhusky.sh\"\n\n# Git Hook for JIRA_TASK_ID\n# Adds to the top of your commit message `JIRA_TASK_ID`, based on the prefix of the current branch `feature\u002FAWG-562-add-linter`\n# Example: `Add SwiftLint -> `AWG-562 Add SwiftLint\n\nif [ -z \"$BRANCHES_TO_SKIP\" ]; then\n  BRANCHES_TO_SKIP='master feature'\nfi\n\nCURRENT_BRANCH=$(git branch --show-current)\nfor BRANCH in $BRANCHES_TO_SKIP; do\n  if [ \"$BRANCH\" = \"$CURRENT_BRANCH\" ]; then\n    echo \"Info, skipping Jira ID check since current branch is included in the 'BRANCHES_TO_SKIP' variable\"\n    exit 0\n  fi\ndone\n\nCOMMIT_FILE=$1\nCOMMIT_MSG=$(cat \"$1\")\nCURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)\nJIRA_ID_REGEX=\"[A-Z0-9]{1,10}-?[A-Z0-9]+\"\nJIRA_ID_IN_CURRENT_BRANCH_NAME=$(echo \"$CURRENT_BRANCH\" | { grep -Eo \"$JIRA_ID_REGEX\" || true; })\nJIRA_ID_IN_COMMIT_MESSAGE=$(echo \"$COMMIT_MSG\" | { grep -Eo \"$JIRA_ID_REGEX\" || true; })\n\nif [ -n \"$JIRA_ID_IN_COMMIT_MESSAGE\" ]; then\n  if [ \"$JIRA_ID_IN_COMMIT_MESSAGE\" != \"$JIRA_ID_IN_CURRENT_BRANCH_NAME\" ]; then\n    echo \"Error, your commit message JIRA_TASK_ID='$JIRA_ID_IN_COMMIT_MESSAGE' is not equal to current branch JIRA_TASK_ID='$JIRA_ID_IN_CURRENT_BRANCH_NAME'\"\n    exit 1\n  fi\nelif [ -n \"$JIRA_ID_IN_CURRENT_BRANCH_NAME\" ]; then\n  echo \"$JIRA_ID_IN_CURRENT_BRANCH_NAME $COMMIT_MSG\" > \"$COMMIT_FILE\"\n  echo \"JIRA ID '$JIRA_ID_IN_CURRENT_BRANCH_NAME', matched in current branch name, prepended to commit message. (Use --no-verify to skip)\"\nfi\n",[40,1153,1154,1160,1182,1186,1191,1196,1201,1205,1231,1242,1247,1251,1273,1290,1318,1326,1334,1339,1344,1348,1359,1380,1401,1411,1456,1494,1499,1520,1547,1565,1573,1578,1598,1623,1636],{"__ignoreMap":76},[160,1155,1156],{"class":162,"line":163},[160,1157,1159],{"class":1158},"sryI4","#!\u002Fusr\u002Fbin\u002Fenv bash\n",[160,1161,1162,1164,1167,1170,1173,1176,1179],{"class":162,"line":77},[160,1163,43],{"class":192},[160,1165,1166],{"class":170}," \"$(",[160,1168,1169],{"class":166},"dirname",[160,1171,1172],{"class":192}," --",[160,1174,1175],{"class":170}," \"",[160,1177,1178],{"class":192},"$3",[160,1180,1181],{"class":170},"\")\u002F_\u002Fhusky.sh\"\n",[160,1183,1184],{"class":162,"line":82},[160,1185,597],{"emptyLinePlaceholder":89},[160,1187,1188],{"class":162,"line":605},[160,1189,1190],{"class":1158},"# Git Hook for JIRA_TASK_ID\n",[160,1192,1193],{"class":162,"line":611},[160,1194,1195],{"class":1158},"# Adds to the top of your commit message `JIRA_TASK_ID`, based on the prefix of the current branch `feature\u002FAWG-562-add-linter`\n",[160,1197,1198],{"class":162,"line":617},[160,1199,1200],{"class":1158},"# Example: `Add SwiftLint -> `AWG-562 Add SwiftLint\n",[160,1202,1203],{"class":162,"line":666},[160,1204,597],{"emptyLinePlaceholder":89},[160,1206,1207,1210,1214,1217,1219,1222,1225,1228],{"class":162,"line":717},[160,1208,1209],{"class":223},"if",[160,1211,1213],{"class":1212},"sQ3_J"," [ ",[160,1215,1216],{"class":223},"-z",[160,1218,1175],{"class":170},[160,1220,1221],{"class":1212},"$BRANCHES_TO_SKIP",[160,1223,1224],{"class":170},"\"",[160,1226,1227],{"class":1212}," ]; ",[160,1229,1230],{"class":223},"then\n",[160,1232,1233,1236,1239],{"class":162,"line":723},[160,1234,1235],{"class":1212},"  BRANCHES_TO_SKIP",[160,1237,1238],{"class":223},"=",[160,1240,1241],{"class":170},"'master feature'\n",[160,1243,1244],{"class":162,"line":729},[160,1245,1246],{"class":223},"fi\n",[160,1248,1249],{"class":162,"line":735},[160,1250,597],{"emptyLinePlaceholder":89},[160,1252,1253,1256,1258,1261,1264,1267,1270],{"class":162,"line":741},[160,1254,1255],{"class":1212},"CURRENT_BRANCH",[160,1257,1238],{"class":223},[160,1259,1260],{"class":1212},"$(",[160,1262,1263],{"class":166},"git",[160,1265,1266],{"class":170}," branch",[160,1268,1269],{"class":192}," --show-current",[160,1271,1272],{"class":1212},")\n",[160,1274,1275,1278,1281,1284,1287],{"class":162,"line":747},[160,1276,1277],{"class":223},"for",[160,1279,1280],{"class":1212}," BRANCH ",[160,1282,1283],{"class":223},"in",[160,1285,1286],{"class":1212}," $BRANCHES_TO_SKIP; ",[160,1288,1289],{"class":223},"do\n",[160,1291,1292,1295,1297,1299,1302,1304,1307,1309,1312,1314,1316],{"class":162,"line":753},[160,1293,1294],{"class":223},"  if",[160,1296,1213],{"class":1212},[160,1298,1224],{"class":170},[160,1300,1301],{"class":1212},"$BRANCH",[160,1303,1224],{"class":170},[160,1305,1306],{"class":223}," =",[160,1308,1175],{"class":170},[160,1310,1311],{"class":1212},"$CURRENT_BRANCH",[160,1313,1224],{"class":170},[160,1315,1227],{"class":1212},[160,1317,1230],{"class":223},[160,1319,1320,1323],{"class":162,"line":759},[160,1321,1322],{"class":192},"    echo",[160,1324,1325],{"class":170}," \"Info, skipping Jira ID check since current branch is included in the 'BRANCHES_TO_SKIP' variable\"\n",[160,1327,1328,1331],{"class":162,"line":765},[160,1329,1330],{"class":192},"    exit",[160,1332,1333],{"class":192}," 0\n",[160,1335,1336],{"class":162,"line":771},[160,1337,1338],{"class":223},"  fi\n",[160,1340,1341],{"class":162,"line":776},[160,1342,1343],{"class":223},"done\n",[160,1345,1346],{"class":162,"line":782},[160,1347,597],{"emptyLinePlaceholder":89},[160,1349,1350,1353,1355],{"class":162,"line":788},[160,1351,1352],{"class":1212},"COMMIT_FILE",[160,1354,1238],{"class":223},[160,1356,1358],{"class":1357},"sFbx2","$1\n",[160,1360,1361,1364,1366,1368,1371,1373,1376,1378],{"class":162,"line":794},[160,1362,1363],{"class":1212},"COMMIT_MSG",[160,1365,1238],{"class":223},[160,1367,1260],{"class":1212},[160,1369,1370],{"class":166},"cat",[160,1372,1175],{"class":170},[160,1374,1375],{"class":192},"$1",[160,1377,1224],{"class":170},[160,1379,1272],{"class":1212},[160,1381,1382,1384,1386,1388,1390,1393,1396,1399],{"class":162,"line":799},[160,1383,1255],{"class":1212},[160,1385,1238],{"class":223},[160,1387,1260],{"class":1212},[160,1389,1263],{"class":166},[160,1391,1392],{"class":170}," rev-parse",[160,1394,1395],{"class":192}," --abbrev-ref",[160,1397,1398],{"class":170}," HEAD",[160,1400,1272],{"class":1212},[160,1402,1403,1406,1408],{"class":162,"line":805},[160,1404,1405],{"class":1212},"JIRA_ID_REGEX",[160,1407,1238],{"class":223},[160,1409,1410],{"class":170},"\"[A-Z0-9]{1,10}-?[A-Z0-9]+\"\n",[160,1412,1413,1416,1418,1420,1423,1425,1427,1429,1431,1434,1437,1440,1442,1445,1447,1450,1453],{"class":162,"line":811},[160,1414,1415],{"class":1212},"JIRA_ID_IN_CURRENT_BRANCH_NAME",[160,1417,1238],{"class":223},[160,1419,1260],{"class":1212},[160,1421,1422],{"class":192},"echo",[160,1424,1175],{"class":170},[160,1426,1311],{"class":1212},[160,1428,1224],{"class":170},[160,1430,224],{"class":223},[160,1432,1433],{"class":1212}," { ",[160,1435,1436],{"class":166},"grep",[160,1438,1439],{"class":192}," -Eo",[160,1441,1175],{"class":170},[160,1443,1444],{"class":1212},"$JIRA_ID_REGEX",[160,1446,1224],{"class":170},[160,1448,1449],{"class":223}," ||",[160,1451,1452],{"class":192}," true",[160,1454,1455],{"class":1212},"; })\n",[160,1457,1458,1461,1463,1465,1467,1469,1472,1474,1476,1478,1480,1482,1484,1486,1488,1490,1492],{"class":162,"line":817},[160,1459,1460],{"class":1212},"JIRA_ID_IN_COMMIT_MESSAGE",[160,1462,1238],{"class":223},[160,1464,1260],{"class":1212},[160,1466,1422],{"class":192},[160,1468,1175],{"class":170},[160,1470,1471],{"class":1212},"$COMMIT_MSG",[160,1473,1224],{"class":170},[160,1475,224],{"class":223},[160,1477,1433],{"class":1212},[160,1479,1436],{"class":166},[160,1481,1439],{"class":192},[160,1483,1175],{"class":170},[160,1485,1444],{"class":1212},[160,1487,1224],{"class":170},[160,1489,1449],{"class":223},[160,1491,1452],{"class":192},[160,1493,1455],{"class":1212},[160,1495,1497],{"class":162,"line":1496},26,[160,1498,597],{"emptyLinePlaceholder":89},[160,1500,1502,1504,1506,1509,1511,1514,1516,1518],{"class":162,"line":1501},27,[160,1503,1209],{"class":223},[160,1505,1213],{"class":1212},[160,1507,1508],{"class":223},"-n",[160,1510,1175],{"class":170},[160,1512,1513],{"class":1212},"$JIRA_ID_IN_COMMIT_MESSAGE",[160,1515,1224],{"class":170},[160,1517,1227],{"class":1212},[160,1519,1230],{"class":223},[160,1521,1523,1525,1527,1529,1531,1533,1536,1538,1541,1543,1545],{"class":162,"line":1522},28,[160,1524,1294],{"class":223},[160,1526,1213],{"class":1212},[160,1528,1224],{"class":170},[160,1530,1513],{"class":1212},[160,1532,1224],{"class":170},[160,1534,1535],{"class":223}," !=",[160,1537,1175],{"class":170},[160,1539,1540],{"class":1212},"$JIRA_ID_IN_CURRENT_BRANCH_NAME",[160,1542,1224],{"class":170},[160,1544,1227],{"class":1212},[160,1546,1230],{"class":223},[160,1548,1550,1552,1555,1557,1560,1562],{"class":162,"line":1549},29,[160,1551,1322],{"class":192},[160,1553,1554],{"class":170}," \"Error, your commit message JIRA_TASK_ID='",[160,1556,1513],{"class":1212},[160,1558,1559],{"class":170},"' is not equal to current branch JIRA_TASK_ID='",[160,1561,1540],{"class":1212},[160,1563,1564],{"class":170},"'\"\n",[160,1566,1568,1570],{"class":162,"line":1567},30,[160,1569,1330],{"class":192},[160,1571,1572],{"class":192}," 1\n",[160,1574,1576],{"class":162,"line":1575},31,[160,1577,1338],{"class":223},[160,1579,1581,1584,1586,1588,1590,1592,1594,1596],{"class":162,"line":1580},32,[160,1582,1583],{"class":223},"elif",[160,1585,1213],{"class":1212},[160,1587,1508],{"class":223},[160,1589,1175],{"class":170},[160,1591,1540],{"class":1212},[160,1593,1224],{"class":170},[160,1595,1227],{"class":1212},[160,1597,1230],{"class":223},[160,1599,1601,1604,1606,1608,1611,1613,1615,1617,1620],{"class":162,"line":1600},33,[160,1602,1603],{"class":192},"  echo",[160,1605,1175],{"class":170},[160,1607,1540],{"class":1212},[160,1609,1610],{"class":1212}," $COMMIT_MSG",[160,1612,1224],{"class":170},[160,1614,264],{"class":223},[160,1616,1175],{"class":170},[160,1618,1619],{"class":1212},"$COMMIT_FILE",[160,1621,1622],{"class":170},"\"\n",[160,1624,1626,1628,1631,1633],{"class":162,"line":1625},34,[160,1627,1603],{"class":192},[160,1629,1630],{"class":170}," \"JIRA ID '",[160,1632,1540],{"class":1212},[160,1634,1635],{"class":170},"', matched in current branch name, prepended to commit message. (Use --no-verify to skip)\"\n",[160,1637,1639],{"class":162,"line":1638},35,[160,1640,1246],{"class":223},[21,1642,1644],{"id":1643},"pre-push-run-unit-tests-and-linters","Pre-push run unit tests and linters",[12,1646,1647,1648,1650],{},"This script runs before you push to remote. It goes to the directory of the ",[40,1649,1059],{}," and runs the unit tests and linting scripts. If one of those ends with an exit code other than 0 the push is aborted.",[126,1652,1654],{"className":154,"code":1653,"language":156,"meta":76,"style":76},"#!\u002Fusr\u002Fbin\u002Fenv sh\n. \"$(dirname -- \"$3\")\u002F_\u002Fhusky.sh\"\n\n# runs frontend unit tests and linters\ncd ~\u002Fproject\u002F && npm run test:prod && npm run lint:js && npm run lint:style\n",[40,1655,1656,1661,1677,1681,1686],{"__ignoreMap":76},[160,1657,1658],{"class":162,"line":163},[160,1659,1660],{"class":1158},"#!\u002Fusr\u002Fbin\u002Fenv sh\n",[160,1662,1663,1665,1667,1669,1671,1673,1675],{"class":162,"line":77},[160,1664,43],{"class":192},[160,1666,1166],{"class":170},[160,1668,1169],{"class":166},[160,1670,1172],{"class":192},[160,1672,1175],{"class":170},[160,1674,1178],{"class":192},[160,1676,1181],{"class":170},[160,1678,1679],{"class":162,"line":82},[160,1680,597],{"emptyLinePlaceholder":89},[160,1682,1683],{"class":162,"line":605},[160,1684,1685],{"class":1158},"# runs frontend unit tests and linters\n",[160,1687,1688,1690,1693,1696,1699,1702,1705,1707,1709,1711,1714,1716,1718,1720],{"class":162,"line":611},[160,1689,193],{"class":192},[160,1691,1692],{"class":170}," ~\u002Fproject\u002F",[160,1694,1695],{"class":1212}," && ",[160,1697,1698],{"class":166},"npm",[160,1700,1701],{"class":170}," run",[160,1703,1704],{"class":170}," test:prod",[160,1706,1695],{"class":1212},[160,1708,1698],{"class":166},[160,1710,1701],{"class":170},[160,1712,1713],{"class":170}," lint:js",[160,1715,1695],{"class":1212},[160,1717,1698],{"class":166},[160,1719,1701],{"class":170},[160,1721,1722],{"class":170}," lint:style\n",[12,1724,1725,1726],{},"If you want to see some more scripts that could be useful check out this repository: ",[57,1727,1730],{"href":1728,"rel":1729},"https:\u002F\u002Fgithub.com\u002Faitemr\u002Fawesome-git-hooks",[61],"awesome-git-hooks",[65,1732,1733],{},[12,1734,1735,1736,1739],{},"TIPP: You can skip all the git hooks if you add the flag ",[40,1737,1738],{},"--no-verify"," to your git command.",[283,1741,1742],{},"html pre.shiki code .sryI4, html code.shiki .sryI4{--shiki-dark:#6A737D;--shiki-default:#6A737D}html pre.shiki code .s0DvM, html code.shiki .s0DvM{--shiki-dark:#79B8FF;--shiki-default:#005CC5}html pre.shiki code .sg6BJ, html code.shiki .sg6BJ{--shiki-dark:#9ECBFF;--shiki-default:#032F62}html pre.shiki code .s-Z4r, html code.shiki .s-Z4r{--shiki-dark:#B392F0;--shiki-default:#6F42C1}html pre.shiki code .scx8i, html code.shiki .scx8i{--shiki-dark:#F97583;--shiki-default:#D73A49}html pre.shiki code .sQ3_J, html code.shiki .sQ3_J{--shiki-dark:#E1E4E8;--shiki-default:#24292E}html pre.shiki code .sFbx2, html code.shiki .sFbx2{--shiki-dark:#FFAB70;--shiki-default:#E36209}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":76,"searchDepth":77,"depth":77,"links":1744},[1745,1746,1747],{"id":1007,"depth":77,"text":1008},{"id":1039,"depth":77,"text":1040},{"id":1126,"depth":77,"text":1127,"children":1748},[1749,1750],{"id":1140,"depth":82,"text":1141},{"id":1643,"depth":82,"text":1644},"Git hooks are useful to automate tasks in your git workflow, but they are hard to maintain. Husky.js is a great way to share and maintain your git hooks with your team.",{"image":1753},{"url":76,"alt":76},"\u002Fblog\u002Fgit-hooks-with-husky","2024-01-24",{"title":1002,"description":1751},"blog\u002Fgit-hooks-with-husky",[1263,297,1759],"tooling","sxWEm6H91rhfDTOENfob3kj2sLQnt-Xv0BM3-OOc8S0",1775933921689]