<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[roger.ml]]></title><description><![CDATA[Here I'll share tips and tricks about iOS development, Apple ecosystem, and other technology-related topics :)]]></description><link>https://www.roger.ml</link><image><url>https://substackcdn.com/image/fetch/$s_!ujSU!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac38d071-6ef0-467a-89bb-8c3198f464ac_132x132.png</url><title>roger.ml</title><link>https://www.roger.ml</link></image><generator>Substack</generator><lastBuildDate>Mon, 13 Apr 2026 06:46:50 GMT</lastBuildDate><atom:link href="https://www.roger.ml/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Roger Oba]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[rogeroba@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[rogeroba@substack.com]]></itunes:email><itunes:name><![CDATA[Roger]]></itunes:name></itunes:owner><itunes:author><![CDATA[Roger]]></itunes:author><googleplay:owner><![CDATA[rogeroba@substack.com]]></googleplay:owner><googleplay:email><![CDATA[rogeroba@substack.com]]></googleplay:email><googleplay:author><![CDATA[Roger]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[🛑 Apple Deprecates template_name for Provisioning Profiles]]></title><description><![CDATA[It broke fastlane match for everyone, but see how it affects teams using managed capabilities, formerly known as custom entitlements]]></description><link>https://www.roger.ml/p/apple-deprecates-template-name</link><guid isPermaLink="false">https://www.roger.ml/p/apple-deprecates-template-name</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Tue, 10 Jun 2025 03:00:52 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/077c927d-3ddb-46bd-86ca-8daea3a4e0d1_2742x862.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In May 2025, Apple made a breaking change to their provisioning profile APIs: they silently removed support for the undocumented <code>template_name</code> parameter. This deprecation had immediate consequences for teams using <em><a href="https://docs.fastlane.tools/actions/match/">fastlane match</a></em> to manage provisioning profiles &#8212; especially those working with special capabilities known as <strong>custom entitlements</strong>.</p><p>This post covers what changed, how it impacts your CI/CD pipelines, what <em>fastlane</em> did to patch the issue (for most users), and what to expect moving forward.</p><h2><strong>&#129300; So, what is this &#8220;template_name&#8221; thing?</strong></h2><p>This is what I asked myself when I saw this issue was gaining a lot of traction in <em>fastlane</em> issues and PRs, so here&#8217;s what I learned:</p><p>Apple provides certain entitlements only to approved developers &#8212; things like:</p><ul><li><p><code>com.apple.developer.healthkit.access</code> (advanced HealthKit access)</p></li><li><p><code>com.apple.developer.driverkit</code></p></li><li><p><code>com.apple.developer.carplay-messaging</code></p></li></ul><p>These are commonly referred to as <strong>custom entitlements</strong> or <strong>additional entitlements</strong>, but the new official term by Apple is <strong>&#8220;managed capabilities&#8221;</strong>. Apple requires that developers <strong>request access</strong> to them before they can be used and, upon approval, associates a special <strong>App ID template</strong> with the developer&#8217;s account.</p><p>The only way to generate a provisioning profile with those entitlements programmatically used to be passing that template&#8217;s identifier &#8212; the <code>template_name</code> &#8212; to Apple&#8217;s API when creating a profile.</p><p><em>fastlane</em> supported this through the <code>template_name</code> parameter in <em>match</em>. It worked reliably for years, despite the fact that Apple never documented or officially supported the parameter.</p><h2><strong>&#128556; Apple removes support for </strong><code>template_name</code></h2><p>If you saw an error like:</p><blockquote><p><code>The provided entity includes an unknown attribute - 'templateName' is not an attribute on the resource 'profiles' - /data/attributes/templateName</code></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2NS6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2NS6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png 424w, https://substackcdn.com/image/fetch/$s_!2NS6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png 848w, https://substackcdn.com/image/fetch/$s_!2NS6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png 1272w, https://substackcdn.com/image/fetch/$s_!2NS6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2NS6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png" width="1456" height="458" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:458,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:293743,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.roger.ml/i/165544239?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2NS6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png 424w, https://substackcdn.com/image/fetch/$s_!2NS6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png 848w, https://substackcdn.com/image/fetch/$s_!2NS6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png 1272w, https://substackcdn.com/image/fetch/$s_!2NS6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18a7d50e-a64b-4b2a-88ac-d4ba481b8835_2742x862.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It&#8217;s because in late May 2025, Apple removed support for the <code>template_name</code> parameter altogether. That change broke profile creation for <strong>all users of </strong><em><strong>fastlane</strong></em> &#8212; even those <strong>not using managed capabilities</strong> &#8212; since <em>fastlane</em> would serialize <code>template_name</code> parameter as <code>nil</code>, which Apple&#8217;s API also started rejecting.</p><p>See <a href="https://github.com/fastlane/fastlane/issues/29498">GitHub Issue #29498</a> for the initial bug report.</p><h2><strong>&#129657; The temporary fix for most users</strong></h2><p>To quickly unblock the majority of teams &#8212; the ones not using managed capabilities &#8212; a <a href="https://github.com/fastlane/fastlane/pull/29591">PR was merged</a> that removes the <code>template_name</code> parameter entirely for everyone.</p><p>If you&#8217;re not using managed capabilities, this fix is already available in the latest <em>fastlane</em> release <a href="https://github.com/fastlane/fastlane/releases/tag/2.228.0">v2.228.0</a>. You can resume generating profiles with <em>match</em> as usual.</p><h2><strong>&#128679; Still Broken for Managed Capabilities</strong></h2><p>If your app depends on one of Apple&#8217;s managed capabilities, this fix stops the crashes, but it will create provisioning profiles that don&#8217;t include your custom entitlements. <strong>There is currently no official way to programmatically specify which App ID template to use when creating a provisioning profile.</strong></p><p>To solve this, <a href="https://developer.apple.com/help/account/reference/provisioning-with-managed-capabilities">Apple recommends using automatic signing via Xcode or Xcode Cloud</a>, which automatically selects the right template when managing signing for your app, but we know how things end with automatic signing in large teams &#128517; which is the whole point of using <em>match</em>.</p><div><hr></div><h2><strong>&#128640; What&#8217;s Next?</strong></h2><p>The <em>fastlane</em> team deprecated the <code>template_name</code> parameter in <em>match</em> until Apple provides an official way to handle managed capabilities (fka custom entitlements) through automation. For now:</p><ul><li><p>If you don&#8217;t use custom entitlements: You&#8217;re safe. <em>fastlane</em> works as expected again.</p></li><li><p>If you do: You&#8217;ll need to manually manage profiles via the <a href="https://developer.apple.com/account/resources/profiles/list">Developer Portal</a> or use Xcode&#8217;s UI until Apple updates their APIs.</p><ul><li><p>See <a href="https://developer.apple.com/help/account/reference/provisioning-with-managed-capabilities">Apple - Provisioning for managed capabilities</a> to learn more.</p></li><li><p>Since Apple migrated from &#8220;additional entitlements&#8221; to &#8220;managed capabilities&#8221;, you may need to re-assign some of these entitlements to your App IDs, if they are missing. You can request additional entitlement migration to managed capabilities <a href="https://developer.apple.com/contact/request/entitlement-migration-requests/">via this contact form</a> (as the Account  Holder) if any previously assigned entitlements are not visible.</p></li></ul></li></ul><p>The team will be actively monitoring the situation and will update <em>fastlane</em>&#8217;s documentation and tooling once Apple provides a supported alternative.</p><p>Let us know if you&#8217;ve been affected by this change &#8212; or if you&#8217;ve found a workaround we should document!</p><div class="pullquote"><p>Special thanks to everyone in the community that contributed with knowledge, new findings, and test attempts in fastlane issues &amp; PRs. These were invaluable!</p><p>And thanks everyone for tagging me over the past couple weeks to bring this critical issue to my attention &#128584;</p><p>Join our discussion at <a href="https://github.com/fastlane/fastlane/discussions/29609">[match] creation of profiles with managed capabilities (custom entitlements)#29609</a> &#128172; to learn the latest developments and continue contributing with new findings - especially if you use managed capabilities! &#128591; </p></div><h2><strong>&#128218; Want to Learn More?</strong></h2><ul><li><p><a href="https://developer.apple.com/documentation/xcode/adding-capabilities-to-your-app">Apple: Adding capabilities to your app</a></p></li><li><p><a href="https://developer.apple.com/documentation/xcode/capabilities">Apple: Capabilities documentation</a></p></li><li><p><a href="https://developer.apple.com/contact/request/entitlement-migration-requests/">Apple: Entitlement migration request form</a></p></li><li><p><em><a href="https://github.com/fastlane/fastlane/issues/29498">fastlane</a></em><a href="https://github.com/fastlane/fastlane/issues/29498"> issue tracking the bug</a>, and the PR that fixed it: <a href="https://github.com/fastlane/fastlane/pull/29591">[sigh][match] fix issue where unknown attribute template_name is being sent when creating provisioning profiles #29591</a></p></li></ul><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[⚙️ Syncing GitHub repository settings at scale]]></title><description><![CDATA[How to configure GitHub repositories using config files, enabling Pull Requests for repository settings]]></description><link>https://www.roger.ml/p/syncing-github-repository-settings</link><guid isPermaLink="false">https://www.roger.ml/p/syncing-github-repository-settings</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Fri, 15 Dec 2023 19:10:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!pTtL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pTtL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pTtL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png 424w, https://substackcdn.com/image/fetch/$s_!pTtL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png 848w, https://substackcdn.com/image/fetch/$s_!pTtL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!pTtL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pTtL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:885956,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pTtL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png 424w, https://substackcdn.com/image/fetch/$s_!pTtL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png 848w, https://substackcdn.com/image/fetch/$s_!pTtL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!pTtL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec20c52c-4d67-441c-b9af-1a4e13a5c4f0_1792x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I&#8217;ve been using GitHub's "Settings" App to easily sync and maintain repository settings, as well as streamline the creation and configuration of new repositories. Today I&#8217;ll go through why I use it, its use cases, and how to avoid common pitfalls that I&#8217;ve faced! &#128522;</p><h2>Why should I sync settings using a config file?</h2><p>Synchronizing settings via a config file enhances consistency, efficiency, and transparency. It empowers teams to propose changes via Pull Requests, ensuring a democratized and traceable approach to repository configurations.</p><p>This is more evident when managing open source repositories, and even more critical at scale, when managing tens if not hundreds of repos. For example, if you have 10 repositories, you would need to manually update 10 repositories to add a new collaborator. With the GitHub "Settings" App, you can simply update the config file and create a Pull Request to update all 10 repositories. The same goes for label management, branch protections, and more.</p><p>Another benefit of using the GitHub "Settings" App is that it allows you to easily create new repositories with the correct settings. This is especially useful when you want to quickly create a new repository with the same settings as you're already used to, without having to manually configure them. I found that this streamlines my workflow of new project creation, and also ensures that I don't forget to add any important settings that I would otherwise stumble upon later.</p><h2>How can I implement this?</h2><p>To utilize this functionality, first, install the GitHub "Settings" App and choose which repositories you'd like it to have access to. I select "All repositories" so it applies to current and also future repos automatically.</p><p>Next, if you intend to use a single file to manage multiple repositories, it's a good idea to create a dedicated repository for this, called <code>YOUR_ORG_NAME/.github</code>, e.g. <code>https://github.com/rogerluan/.github</code>. This allows you to create a centralized config file, which will then be inherited in all the other repos you want to manage. Keep in mind you should still create a <code>.github/settings.yml</code> file in each repository you want to manage, as this is where the app will look for the settings file, but then you can simply point it to the centralized file. Here's a sample "child" file: <a href="https://github.com/rogerluan/arkana/blob/main/.github/settings.yml">https://github.com/rogerluan/arkana/blob/main/.github/settings.yml</a></p><p>Then, define the repository settings in a file named <code>.github/settings.yml</code>. This includes specifying details like collaborators, issue labels, and branch protections. As far as I can tell, the way this app works is that it will parse the raw yaml file and anything that is in it will be sent directly into GitHub's API and be used to update the settings. This means that there's no official list of supported settings for this app, as you can pass anything that is supported by the GitHub API, so the GitHub's API is what should be considered the source of truth.</p><p>For a sample list of settings, you can check out the <a href="https://github.com/apps/settings">GitHub "Settings" App documentation</a> or if you prefer, there's also <a href="https://github.com/rogerluan/.github/blob/main/.github/settings.yml">my personal settings file</a> that I use for my repositories.</p><h2>Common Issues</h2><p>When configuring your file, a small typo, indentation issue, or use of unsupported settings can cause the app to fail, and it fails silently. So <a href="https://github.com/repository-settings/app/issues/625">it's common to find users frustrated</a>, complaining that the settings aren&#8217;t syncing, because there's no user feedback pointing out what's wrong with your file. To overcome this issue, I suggest using a setting file that already works, and go from there. I particularly had issues <a href="https://github.com/repository-settings/app/issues/625#issuecomment-1272641823">configuring branch protection</a> and topics (topics can&#8217;t have spaces, FYI), but if you follow my file, you should be fine!</p><p>Another very important issue to keep in mind is the security implications of using this app. The app will have write access to all the repositories you grant it access to, so it's important to keep your settings file secure. I strongly recommend adding mechanisms to prevent PRs from being merged without someone reviewing that the settings file is being changed (e.g. <a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners">CODEOWNERS feature</a>, <a href="https://danger.systems">danger</a> code review to check if the settings file is being changed). Failing to do so could be catastrophic, as it would allow anyone to change your repository settings without your knowledge, like changing branch protection rules, adding collaborators, or even making repositories public.</p><h2>Conclusion</h2><p>The GitHub "Settings" App significantly simplifies repository settings management at scale, and I love using it for my open source projects! Its ability to centralize configuration and enable Pull Requests for settings adjustments makes it an invaluable tool for modern development workflows.</p><p>For more detailed information on the GitHub "Settings" App, you can visit their official page: <a href="https://github.com/apps/settings">GitHub Apps - Settings</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[🪵 Replacing your logging framework with OSLog]]></title><description><![CDATA[Eager to jump on the OSLog bandwagon (Apple Unified System Log), but not sure how? Look no further!]]></description><link>https://www.roger.ml/p/oslog</link><guid isPermaLink="false">https://www.roger.ml/p/oslog</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Thu, 23 Nov 2023 10:01:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PVBF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PVBF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PVBF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png 424w, https://substackcdn.com/image/fetch/$s_!PVBF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png 848w, https://substackcdn.com/image/fetch/$s_!PVBF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!PVBF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PVBF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:394254,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PVBF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png 424w, https://substackcdn.com/image/fetch/$s_!PVBF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png 848w, https://substackcdn.com/image/fetch/$s_!PVBF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!PVBF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ce914dc-a445-448b-9484-1b58375798f6_1792x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ever since this year&#8217;s WWDC, especially after watching the session <a href="https://www.wwdcnotes.com/notes/wwdc23/10226">Debug with structured logging</a>, I&#8217;ve been super excited to stop using 3rd party log utilities. First-class citizen treatment for logs is something taken for granted for developers that use other IDEs, like Android Studio, or any other JetBrains IDE. But for developers that dedicate their professional and sometimes personal lives to building software for Apple platforms, we were still in stone age&#8230; until now!</p><p>In this article I won&#8217;t dive into details about <strong>why</strong> you&#8217;d want to use OSLog; instead, you can check the awesome features that OSLog provides in the link above, which is a summary I wrote about that WWDC session.</p><p>The information shared in this article is a summary of multiple sources, from developers of open source logging utilities, to Apple employees.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_Wcq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_Wcq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png 424w, https://substackcdn.com/image/fetch/$s_!_Wcq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png 848w, https://substackcdn.com/image/fetch/$s_!_Wcq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png 1272w, https://substackcdn.com/image/fetch/$s_!_Wcq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_Wcq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png" width="1456" height="613" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:613,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Filtering&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Filtering" title="Filtering" srcset="https://substackcdn.com/image/fetch/$s_!_Wcq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png 424w, https://substackcdn.com/image/fetch/$s_!_Wcq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png 848w, https://substackcdn.com/image/fetch/$s_!_Wcq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png 1272w, https://substackcdn.com/image/fetch/$s_!_Wcq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660e47cb-7273-47db-a8c7-b580f3e7c165_1672x704.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>I want to use OSLog! What now?</h2><p>Despite the excitement around OSLog, it's essential to understand and acknowledge its capabilities and limitations.</p><p>Apple's unified system log offers a powerful and efficient logging solution. It's designed for classification and long-term persistence, aiding in debugging issues over extended periods. The log entries are categorized by subsystem, category, and type, with customizable settings for each. This level of detail is beneficial, especially when combined with tools like the Console app and Instruments, which provide sophisticated searching and log entry correlation capabilities.</p><p>However, it's crucial to be aware of some limitations of OSLog. For example, the system log API's deep integration with the compiler makes it challenging to be wrapped efficiently. Additionally, in iOS, the system log's reading capabilities are restricted: apps can only access log entries created by their current process. This means that if an app crashes, the subsequent launch, being a different process, won't have access to the logs from the previous instance. This limitation can be a significant drawback if you&#8217;re relying on these logs to debug crashes.</p><p>Because it pretty much can&#8217;t be wrapped, this means we can&#8217;t extend its functionality beyond what Apple offers out-of-the-box. Thus, we&#8217;re left with no remote logging, no logging to multiple destinations, nor custom logging format, etc.</p><p>If you&#8217;re starting a new project now, or if you don&#8217;t have anything in place and are still using good ol&#8217; <code>print(&#8230;)</code> statements, you can probably simply start using OSLog directly.</p><p>Now, if you&#8217;re interested in those features that OSLog doesn&#8217;t offer, you&#8217;ll have to choose between your current logging framework and OSLog, unfortunately.</p><h2>The middle-ground solution</h2><p>As someone who has been using logs mostly to log messages to the console while debugging features/bugs, I could easily replace all my logging utilities with the OSLog API directly. However, I wouldn&#8217;t like to give up on the ability to enable other features in the future, such as remote logging, exporting logs, etc. &#8212; features usually available in robust logging frameworks.</p><p>To help solve this, I found that a good middle-ground for me was to use a function identical to the official OSLog API on release builds, and use OSLog&#8217;s actual API in debug builds. In practice, this means:</p><pre><code>#if DEBUG
// In debug builds, we use the system-provided os_log function.
@_exported import OSLog
#else
/// A clone of OSLogType. Don't extend these types as they must match the OSLogType values.
enum MyLogType {
    /// The debug log level.
    case debug
    /// The informative log level.
    case info
    /// The default log level.
    case `default`
    /// The error log level.
    case error
    /// The fault log level.
    case fault
}

/// This overloaded function helps solve a compiler error when using the os_log function with an implicit logType.
/// This will be used in non-debug builds.
func os_log(
    _ message: StaticString,
    file: StaticString = #file,
    function: StaticString = #function,
    line: UInt = #line,
    args: Any...
) {
    os_log(.default, message, file: file, function: function, line: line, args: args)
}

/// This will be used in non-debug builds.
func os_log(
    _ logType: MyLogType = .default,
    _ message: StaticString,
    file: StaticString = #file,
    function: StaticString = #function,
    line: UInt = #line,
    args: Any...
) {
    let stringMessage = String(describing: message)
    switch logType {
    case .debug: myCustomDebugLog(stringMessage, file: file, function: function, line: line, params: args)
    case .info, .default: myCustomInfoLog(stringMessage, file: file, function: function, line: line, params: args)
    case .error, .fault: myCustomErrorLog(stringMessage, file: file, function: function, line: line, params: args)
    }
}
#endif</code></pre><p>Usage will be identical between debug and release builds, e.g.:</p><pre><code>os_log(.debug, "This is a log")</code></pre><blockquote><p><em>Yes, this means you&#8217;ll have to replace all those </em><code>myCustomDebugLog(&#8230;)</code><em> spread throughout your codebase with </em><code>os_log(&#8230;)</code><em>, but this would need to be done only once, and then from then on you&#8217;d start using just </em><code>os_log(&#8230;)</code><em> everywhere.</em></p></blockquote><p>This will result in good usability with Xcode&#8217;s console during development (debug builds), but using your own logging framework in release builds, which may include e.g. logging to multiple destinations, exporting logs to a file, etc.</p><h2>Conclusion</h2><p>While OSLog brings much-needed improvements and integrations for Apple's logging ecosystem, it is not a complete replacement for third-party robust logging frameworks like CocoaLumberjack, SwiftyBeaver, XCGLogger, and many others, especially for developers requiring advanced logging features and cross-platform support. But if you&#8217;re interested in just improving the DevX and productivity while implementing and debugging features locally, there&#8217;s a good middle-ground alternative you may consider!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h1>References</h1><ul><li><p><a href="https://www.wwdcnotes.com/notes/wwdc23/10226">Debug with structured logging</a></p></li><li><p><a href="https://github.com/CocoaLumberjack/CocoaLumberjack/discussions/1363">Should everyone migrate to OSLog? #1363</a></p></li><li><p><a href="https://developer.apple.com/forums/thread/705868">Your Friend the System Log</a></p></li><li><p>Not mentioned in this article but a good read: <a href="https://swiftwithmajid.com/2022/04/19/exporting-data-from-unified-logging-system-in-swift">Exporting data from Unified Logging System in Swift</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[🌉 Bridging gaps in tech: Statused's new Android support makes waves]]></title><description><![CDATA[Elevate team efficiency with Statused's new Android workflows, streamlining communication across departments for seamless app deployment.]]></description><link>https://www.roger.ml/p/launching-statused-android</link><guid isPermaLink="false">https://www.roger.ml/p/launching-statused-android</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Thu, 09 Nov 2023 09:03:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vxnr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vxnr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!vxnr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!vxnr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!vxnr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vxnr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:81939,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vxnr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!vxnr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!vxnr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!vxnr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8e82e1a-3560-4ab2-afa8-7d0f4b227ab0_1200x630.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>When I launched Statused, the goal was simple yet ambitious: to streamline communication across various departments involved in app development and deployment. I envisioned a tool where updates on app and build statuses would be just a Slack notification away, eliminating the back-and-forth and the endless wait for emails to come in. This tool would prevent the need for one developer to relay information to the rest of the team. And, thanks to the incredible support and feedback from all of you, Statused has come a long way.</p><p>Today, I&#8217;m thrilled to share that Statused is expanding its horizons even further. We&#8217;re bridging not just communication gaps but also platform ones. That's right - Android support is coming to Statused!</p><p>This leap forward means that our Android developer friends can join in on the ease and transparency that our iOS teams have been enjoying. All departments of the company will be on the same page when it comes to the different stages of the app release cycle, without the need for an Android or iOS developer being the middleman of the information.</p><p>As this product grows, so does our understanding of how valuable it is to have QA teams, marketing teams, engineering teams, and project managers all on the same page, effortlessly. Statused is not just a tool; it's a meeting ground, a communal space where each department can stay informed and in sync with each stage of the app&#8217;s deployment process.</p><p>So, to my fellow tech enthusiasts, whether you&#8217;re part of a scrappy startup or a tech titan - give Statused a whirl. And to our Android users, welcome aboard! Let&#8217;s continue to bridge gaps, one update at a time.</p><p>Oh, and we&#8217;re on Product Hunt too! Check out our launch there and consider upvoting and providing feedback if you&#8217;re keen!</p><p><a href="https://www.producthunt.com/posts/statused-for-android-apps">Open Statused for Android launch page on Product Hunt</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cK_p!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cK_p!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png 424w, https://substackcdn.com/image/fetch/$s_!cK_p!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png 848w, https://substackcdn.com/image/fetch/$s_!cK_p!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!cK_p!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cK_p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3608888,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cK_p!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png 424w, https://substackcdn.com/image/fetch/$s_!cK_p!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png 848w, https://substackcdn.com/image/fetch/$s_!cK_p!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!cK_p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f48e31d-1e8a-479d-8e79-180ef978ab28_1792x1024.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>What is Statused? &#128173;</h2><p>If this is your first time here and you haven&#8217;t heard about Statused yet, it&#8217;s a service that monitors your apps and delivers real-time Slack notifications when Apple &amp; Google review status changes (for example, when you submit it for review or when Apple approves or rejects your app), when there are new TestFlight builds available, and when your app finishes processing.</p><p>I launched Statused 2 months ago, in September 2023, and have been receiving great feedback from the community, which helped me improve the service even further &#128640;</p><p>You can check it out here: <a href="https://statused.com">https://statused.com</a></p><h2>Credits &#128170;</h2><p>Huge shout-out to our first customer and friends at <a href="https://skeelo.com/?ref=rogerml">Skeelo</a>, an ebook &amp; audiobook subscription service in Brazil. Their help was crucial in providing feedback to improve the tool and service, as well as in helping us test the alpha version of our Android workflows. &#127881;</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[🤖 How to create Google Play API key in 2023]]></title><description><![CDATA[Google's official guide has always been confusing, and this year's update is no different. This guide will walk you through the entire process, step by step.]]></description><link>https://www.roger.ml/p/how-to-create-google-play-api-key</link><guid isPermaLink="false">https://www.roger.ml/p/how-to-create-google-play-api-key</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Fri, 03 Nov 2023 10:20:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!lsC5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lsC5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lsC5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png 424w, https://substackcdn.com/image/fetch/$s_!lsC5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png 848w, https://substackcdn.com/image/fetch/$s_!lsC5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!lsC5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lsC5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3335876,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lsC5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png 424w, https://substackcdn.com/image/fetch/$s_!lsC5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png 848w, https://substackcdn.com/image/fetch/$s_!lsC5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!lsC5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F86ca853f-4970-402f-82c4-a0005f19e5ee_1792x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Google&#8217;s documentation for creating Google Play credentials can be confusing and hard to follow, specially without visuals. This step-by-step guide aims to resolve that.</p><blockquote><p><strong>Tip:</strong> If you see Google Play Console or Google Developer Console in your local language, add <code>&amp;hl=en</code> at the end of the URL (before any <code>#...</code>) to switch to English. All the links below already have this to make it easier to find the correct buttons.</p></blockquote><h2>1. Write down your Developer Account ID</h2><p>Open the <a href="https://play.google.com/console/?hl=en">Google Play Console</a>, click in <strong>Account Details</strong>, and write down the <strong>Developer Account ID</strong> listed there. You&#8217;ll use it later.</p><h2>2. Enable the Google Play Developer API</h2><p>Before using this API, you must enable it. Open the <a href="https://console.developers.google.com/apis/api/androidpublisher.googleapis.com/?hl=en">Google Play Developer API</a> page, select an existing Google Cloud Project that fits your needs, and hit <strong>ENABLE</strong>. If you don't have an existing project or prefer to have a dedicated one for your current needs (e.g. <em>fastlane, Statused)</em>, <a href="https://console.cloud.google.com/projectcreate/?hl=en">create a new one here</a> and follow the instructions. I personally like having a dedicated project called <em>Google Play Console Project </em>and use it for all my needs &#128522;</p><h2>3. Create a Service Account</h2><ol><li><p>Open <a href="https://console.cloud.google.com/iam-admin/serviceaccounts?hl=en">Service Accounts on Google Cloud</a> and select the project you'd like to use.</p></li><li><p>Click the <strong>CREATE SERVICE ACCOUNT</strong> button at the top of the <strong>Google Cloud Platform Console</strong> page.</p></li><li><p>Verify that you are on the correct Google Cloud Platform project by looking for the <strong>Developer Account ID</strong> from earlier within the light gray text in the second input, preceding <code>.iam.gserviceaccount.com</code>, or by checking the project name in the navigation bar. If you realize you don&#8217;t have the correct project selected, open the picker in the top navigation bar, and find the right one.</p></li><li><p>Provide a descriptive <code>Service account name</code>, depending on what you intend to use this account for, e.g. &#8220;<code>fastlane-supply&#8221;</code>, or &#8220;<code>statused&#8221;</code>, etc.</p></li><li><p>Copy the generated email address that is displayed below the <code>Service account-ID</code> field for later use.</p></li><li><p>Click <strong>DONE</strong> (don't click <strong>CREATE AND CONTINUE</strong> as the optional steps such as granting access are not needed):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MmXh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e192e3d-20d5-4758-8861-026518383052_1592x1134.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MmXh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e192e3d-20d5-4758-8861-026518383052_1592x1134.png 424w, https://substackcdn.com/image/fetch/$s_!MmXh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e192e3d-20d5-4758-8861-026518383052_1592x1134.png 848w, https://substackcdn.com/image/fetch/$s_!MmXh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e192e3d-20d5-4758-8861-026518383052_1592x1134.png 1272w, https://substackcdn.com/image/fetch/$s_!MmXh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e192e3d-20d5-4758-8861-026518383052_1592x1134.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MmXh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e192e3d-20d5-4758-8861-026518383052_1592x1134.png" width="1456" height="1037" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e192e3d-20d5-4758-8861-026518383052_1592x1134.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1037,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MmXh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e192e3d-20d5-4758-8861-026518383052_1592x1134.png 424w, https://substackcdn.com/image/fetch/$s_!MmXh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e192e3d-20d5-4758-8861-026518383052_1592x1134.png 848w, https://substackcdn.com/image/fetch/$s_!MmXh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e192e3d-20d5-4758-8861-026518383052_1592x1134.png 1272w, https://substackcdn.com/image/fetch/$s_!MmXh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e192e3d-20d5-4758-8861-026518383052_1592x1134.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div></li></ol><h2>4. Create your API key JSON</h2><p>After creating your service account, you&#8217;ll end up in a page that shows the list of your service accounts. Next:</p><ol><li><p>Click on the <strong>Actions</strong> vertical three-dot icon &#8220;<code>&#8942;&#8221;</code> of the service account you just created.</p></li><li><p>Select <strong>Manage keys</strong> on the menu.</p></li><li><p>Click <strong>ADD KEY</strong> &#8594; <strong>Create New Key.</strong></p></li><li><p>Make sure <strong>JSON</strong> is selected as the <code>Key type</code>, and click <strong>CREATE.</strong></p></li><li><p>Save the file on your computer when prompted and remember where it was saved at.</p></li></ol><h2>5. Invite your service account to your Google Play Console</h2><ol><li><p>Open the <a href="https://play.google.com/console/?hl=en">Google Play Console</a> and select <strong>Users and Permissions.</strong></p></li><li><p>Click <strong>Invite new users.</strong></p></li><li><p>Paste the email address you saved earlier into the email address field.</p></li><li><p>Make sure <strong>App Permissions</strong> tab has no apps in there.</p></li><li><p>Click on the <strong>Account Permissions</strong> tab.</p></li><li><p>Choose the permissions you'd like this account to have. This will depend on the purpose of the account you&#8217;re creating.</p><ol><li><p>For <em>fastlane</em>, I recommend <strong>Admin (all permissions)</strong>, to avoid permission issues down the road. If you feel adventurous, you may want to manually select some checkboxes and leave out some of the more permissive <strong>Release</strong> permissions such as <strong>Release to production, exclude devices, and use Play App Signing</strong>. Of course, this means your API key won&#8217;t be able to release to production programmatically, so you&#8217;ll have to do it manually, likely via the Google Play Console website.</p></li><li><p>For <em><a href="https://statused.com/?ref=rogerml_gplay_key">Statused</a></em>, the only permission you&#8217;ll need is <strong>View app information and download bulk reports (read-only)</strong>. This makes this API key more secure, as it&#8217;s read-only, which is enough for the access that <em>Statused</em> needs. It should look like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K2L-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K2L-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png 424w, https://substackcdn.com/image/fetch/$s_!K2L-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png 848w, https://substackcdn.com/image/fetch/$s_!K2L-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png 1272w, https://substackcdn.com/image/fetch/$s_!K2L-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K2L-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png" width="1456" height="1142" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1142,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:434513,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!K2L-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png 424w, https://substackcdn.com/image/fetch/$s_!K2L-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png 848w, https://substackcdn.com/image/fetch/$s_!K2L-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png 1272w, https://substackcdn.com/image/fetch/$s_!K2L-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36349949-87c4-48ae-9960-7e81e4850417_1996x1566.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div></li></ol></li><li><p>Click on <strong>Invite User.</strong></p></li></ol><h2>Validating your API key access</h2><p>You&#8217;re done creating and configuring your API key, congrats! Next, we should verify whether everything is working as expected.</p><p>If you're configuring an API key for <em><a href="https://statused.com/?ref=rogerml_gplay_key">Statused</a></em>, you can simply upload your key and the system will validate the key for you.</p><p>If you&#8217;re going to use it for fastlane, <a href="https://docs.fastlane.tools/actions/validate_play_store_json_key">there&#8217;s an action</a> to validate your key:</p><pre><code>bundle exec fastlane run validate_play_store_json_key json_key:path/to/your/api/key.json</code></pre><p>Keep in mind that this action will only validate whether your key can connect to the API successfully, but not whether it can access your apps to manage them. To check whether you configured your key correctly to access your apps, you&#8217;ll need to run one of the <em>fastlane supply</em> actions. If it&#8217;s not configured correctly, it will raise the following error:</p><pre><code>FastlaneCore::Interface::FastlaneError (Google Api Error: Invalid request - The caller does not have permission)</code></pre><h1>Conclusion</h1><p>After following this step-by-step guide, you should be up and running with your Google Play API key, ready to use it to manage your releases, upload builds, update metadata, and even monitor your phased rollout distribution percentage if you&#8217;re using <em><a href="https://statused.com/?ref=rogerml_gplay_key">Statused</a></em>.</p><p>If you&#8217;re still experiencing issues after following this guide, don&#8217;t hesitate to reach out! I&#8217;ll do my best to improve this guide to make it easier for everyone moving forward!</p><h2>References</h2><p><a href="https://developers.google.com/android-publisher/getting_started/?hl=en">Official documentation by Google</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[🚀 Launching A New Service: Statused.com!]]></title><description><![CDATA[Statused notifies your team of app and build status updates directly on Slack. No more checking the App Store Connect website or waiting for emails.]]></description><link>https://www.roger.ml/p/launching-statused</link><guid isPermaLink="false">https://www.roger.ml/p/launching-statused</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Thu, 07 Sep 2023 07:01:10 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wFJn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wFJn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png 424w, https://substackcdn.com/image/fetch/$s_!wFJn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png 848w, https://substackcdn.com/image/fetch/$s_!wFJn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png 1272w, https://substackcdn.com/image/fetch/$s_!wFJn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wFJn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:294559,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wFJn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png 424w, https://substackcdn.com/image/fetch/$s_!wFJn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png 848w, https://substackcdn.com/image/fetch/$s_!wFJn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png 1272w, https://substackcdn.com/image/fetch/$s_!wFJn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12ba482d-5f4b-497f-b956-9f100e3f1573_1456x832.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>How it all started</h1><p>Circa 2020, I was leading an iOS project involving multiple teams, such as design, marketing, and a dedicated QA team. As a startup, the work was fast-paced, and we shipped often. However, on every app release, the following would happen:</p><ul><li><p>I was asked whether the app was already live or not, by the project manager;</p></li><li><p>The QA team wasn't sure when the app was ready to be tested on TestFlight;</p></li><li><p>The marketing team didn't know precisely when a specific version of the app was released to the App Store so they could track the impact and effectiveness of onboarding and SEO changes in the app;</p></li><li><p>When the app got rejected for a silly reason, we would often only realize hours later. The weight was on the iOS team to identify the rejection and inform the rest of the team so we could fix it and resubmit the app.</p></li></ul><p>These issues were not only frustrating but also time-consuming. I was spending a lot of time on Slack, answering questions and providing status updates.</p><p>If the entire team had visibility into the app's status, these issues would be solved. I started looking for a tool that would provide this visibility. Still, at that time, Apple was migrating their APIs from iTunes Connect to App Store Connect, so most of the tools I found were outdated or didn't provide the information I needed. So I decided to build my own tool during my free time.</p><p>The first version of this tool was released in August 2020, and it was a simple script that would poll the app's status from App Store Connect and post a message on Slack when the app or build status changed. It was a simple solution, but it solved our issues, and the team loved it. At that time, I ran the tool locally on my machine, so I kept it running 24/7 (yeah, tell me about it!).</p><p>I open-sourced the tool, which got some traction on GitHub. I was happy that other teams were also using and contributing to it, and this led me to invest more time on it.</p><p>One of the pain points was setting it up and, of course, running it locally on a machine 24/7 wasn't ideal. Hosting it on the cloud has costs, so I wanted to create a SaaS version of the tool. But I never really pursued it because it would require knowledge in areas I wasn't familiar with, such as web dev and cloud infrastructure&#8230;</p><p>Until recently!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FPli!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FPli!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!FPli!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!FPli!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!FPli!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FPli!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:91728,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FPli!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!FPli!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!FPli!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!FPli!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8e97141-5c30-44ef-ba43-1b445e49f3fd_1200x630.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>How it's going</h1><p>I took the leap and invested time and money to build a SaaS version of the tool. I'm still learning a lot, but I'm enjoying the process. I must admit that web dev is not my favorite thing, but I'm learning a lot and happy to see the tool come to life.</p><p>The tool supports registering multiple App Store Connect accounts, apps, and Slack channels.</p><p>You can check it out here: <a href="https://statused.com">https://statused.com</a></p><h1>What's next</h1><p>If this tool is helping you and your team, consider subscribing to the SaaS version. This will help me cover the costs of hosting and development, and it will also help me prioritize new features and improvements.</p><p>If this service starts to get traction, I'll be able to invest more time in it, and I'll be able to add more features such as:</p><ul><li><p>Support for more platforms such as Google Play;</p><ul><li><p>UPDATE: As of Nov 9, 2023, Statused fully supports monitoring Google Play Store to notify you about the status of your Android apps! Check it the integration docs <a href="https://docs.statused.com/integrations/google-play-console">here</a>.</p></li></ul></li><li><p>Support for more notification channels such as Discord, Telegram, Microsoft Teams, and email;</p><ul><li><p>UPDATE: As of Jan 13, 2025, Statused supports custom webhooks, which allows you to trigger custom workflows but also integrate with any tool that supports it - such as Discord, Telegram, Teams. Check out the <a href="https://docs.statused.com/integrations/webhooks">documentation</a> for more details.</p></li></ul></li><li><p>Provide metrics and insights about your app releases.</p></li></ul><p>But I also would love to hear your ideas! If you read this story and thought about something cool that would help you and your team, feel free to reach out to me at <a href="mailto:roger@statused.com">roger@statused.com</a> &#129303;</p><p>I hope this tool helps your team as much as it helped mine!</p><p>Oh, and we&#8217;re on Product Hunt too! Check out our launch there and consider upvoting and providing feedback if you&#8217;re keen!</p><p><a href="https://www.producthunt.com/posts/statused">Go to Statused launch page on Product Hunt</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[🤫 Where are you storing your secrets in your mobile projects?]]></title><description><![CDATA[I hope they&#8217;re not in plaintext in your source code.]]></description><link>https://www.roger.ml/p/store-app-secrets-safely</link><guid isPermaLink="false">https://www.roger.ml/p/store-app-secrets-safely</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Mon, 28 Aug 2023 15:18:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!eIg_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eIg_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eIg_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png 424w, https://substackcdn.com/image/fetch/$s_!eIg_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png 848w, https://substackcdn.com/image/fetch/$s_!eIg_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png 1272w, https://substackcdn.com/image/fetch/$s_!eIg_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eIg_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:414896,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eIg_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png 424w, https://substackcdn.com/image/fetch/$s_!eIg_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png 848w, https://substackcdn.com/image/fetch/$s_!eIg_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png 1272w, https://substackcdn.com/image/fetch/$s_!eIg_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00a29dc0-572f-4e31-8524-8f082fad62a0_1456x832.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Nearly every mobile project will ship with secrets like API keys from services, webhook URLs, or maybe even some specific seed data you need. Choosing where to store them properly is <em>key</em> (pun intended), as it could potentially cause your company to go bankrupt over a leaked API key that earns a massive bill. Yes, that&#8217;s the worst-case scenario regarding billing, as it&#8217;s the case with e.g. the Google Maps SDK API key if you haven&#8217;t restricted its access to your app domain only.</p><p>In backend and web frontend projects, it&#8217;s a common practice to store your keys locally in <code>.env</code> files and load them from env vars in CI. But for mobile, I think that not enough importance is given to storing your keys away from your codebase in plaintext or &#8212; worse &#8212; in your <code>Info.plist</code>.</p><p>Some businesses offer remote secret management services so that you can fetch your secrets from their API during runtime. But some services can&#8217;t wait for you to fetch API keys from another service. Sometimes you need them right away during the app launch. Also, depending on how those services implement their authentication, you&#8217;ll have a local API key anyway, so you&#8217;ll need to think about how to store that, too.</p><p>Another way is to obfuscate your app secrets locally.</p><h1>Enters Arkana</h1><p><a href="https://github.com/rogerluan/arkana">Arkana</a> is an open-source project I created to obfuscate the secrets used in your codebase. </p><p>Arkana uses code generation to provide your app with its secrets. Secrets are fetched from env vars during Arkana runtime (not your app&#8217;s runtime), their values are encoded using a salt generated on each run, and source code is generated using the provided keys and the generated encoded values.</p><p>During your app&#8217;s runtime, the encoded value is decoded so your app can use the values initially stored in your env vars.</p><p>This encoding mechanism makes it difficult for attackers to read your secrets in plain text from your app&#8217;s binary (for instance, by using <a href="https://man7.org/linux/man-pages/man1/strings.1.html">unix strings</a> or other tools like <a href="https://github.com/stefanesser/dumpdecrypted">stefanesser/dumpdecrypted</a> and <a href="https://github.com/Yelp/detect-secrets">Yelp/detect-secrets</a>).</p><p>Arkana supports Xcode projects (iOS/macOS) and works with Swift Package Manager, CocoaPods, and local CocoaPods (aka Development Pods). Although it was built with Android support by design, it doesn&#8217;t support Android projects just yet.</p><h1>FAQ</h1><p>I&#8217;ve written more about this project FAQs on its page itself, so you might want to check it out directly:</p><ul><li><p><a href="https://github.com/rogerluan/arkana#installation">How can I install Arkana?</a></p></li><li><p><a href="https://github.com/rogerluan/arkana#usage">How should I use Arkana?</a></p></li><li><p><a href="https://github.com/rogerluan/arkana#is-this-safe">Is this safe?</a></p></li><li><p><a href="https://github.com/rogerluan/arkana#why-not-cocoapods-keys">Why not cocoapods-keys?</a></p></li><li><p><a href="https://github.com/rogerluan/arkana#why-not-a-cocoapods-plugin">Why not a CocoaPods Plugin?</a></p></li></ul><h1>Conclusion</h1><p>Arkana has helped me immensely, and nowadays, I don&#8217;t start new projects that need secrets without it. Setting it up takes a few minutes, and it will pass security audits from rigorous top-tier cybersecurity companies (yup!). If you think your company&#8217;s app secrets are not so safe, perhaps it&#8217;s time to bring this up with your team. &#128170;</p><p>We&#8217;re waiting for an eager, community-driven Android contributor to open a PR &#10024; to say we officially support both platforms. If that&#8217;s you, we can start discussions <a href="https://github.com/rogerluan/arkana/issues/1">here</a>. &#129303; If you know someone who could help, I&#8217;d love for you to share this project with them!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[🗺️ How to generate and upload linkmaps to Emerge]]></title><description><![CDATA[Learn what linkmaps are, how to send them to Emerge, and common pitfalls.]]></description><link>https://www.roger.ml/p/emerge-linkmaps</link><guid isPermaLink="false">https://www.roger.ml/p/emerge-linkmaps</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Sat, 03 Jun 2023 14:17:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!QW42!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QW42!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QW42!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png 424w, https://substackcdn.com/image/fetch/$s_!QW42!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png 848w, https://substackcdn.com/image/fetch/$s_!QW42!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png 1272w, https://substackcdn.com/image/fetch/$s_!QW42!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QW42!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png" width="1456" height="766" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:766,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:458710,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QW42!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png 424w, https://substackcdn.com/image/fetch/$s_!QW42!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png 848w, https://substackcdn.com/image/fetch/$s_!QW42!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png 1272w, https://substackcdn.com/image/fetch/$s_!QW42!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc69a2dfe-e9c8-4601-860d-4e0a1b0c5b67_1925x1013.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Source: <a href="https://www.emergetools.com/?utm_source=roger.ml&amp;utm_medium=tutorial&amp;utm_campaign=linkmaps">emergetools.com</a></figcaption></figure></div><p><a href="https://www.emergetools.com/?utm_source=roger.ml&amp;utm_medium=tutorial&amp;utm_campaign=linkmaps">Emerge</a> is a service that provides lots of tools to improve your app in many different ways, like reducing the app size, improving launch and runtime performance, identifying dead code in your codebase during runtime, and more.</p><p>If you&#8217;re fortunate enough to be using Emerge in your pipeline, you&#8217;ll notice that some features are only possible if you submit your linkmaps to them. A linkmap file is defined as a map file which details all symbols and their addresses in the output image.</p><p>The <a href="https://docs.emergetools.com/docs/linkmaps?utm_source=roger.ml&amp;utm_medium=tutorial&amp;utm_campaign=linkmaps">official documentation from Emerge</a> shows where you should put your linkmap, but here we&#8217;ll go over detailed instructions on how to achieve that.</p><div class="pullquote"><p>Note: this article assumes you&#8217;re already uploading your <code>.xcarchive</code> to Emerge using their <em>fastlane</em> plugin.</p></div><h2>Enable linkmap generation</h2><p>Linkmap generation is disabled by default on Xcode, so we have to enable it. Xcode has the <code>LD_GENERATE_MAP_FILE</code> build setting, defined as:</p><blockquote><p>Activating this setting will cause the linker to write a map file to disk, which details all symbols and their addresses in the output image. The path to the map file is defined by the Path to Link Map File (LD_MAP_FILE_PATH) setting. Defaults to NO.</p></blockquote><p>If you set your build settings via Xcode, search for <code>LD_GENERATE_MAP_FILE</code> (or <code>Write Link Map File</code>) in Xcode&#8217;s Build Settings and set it to <code>YES</code>. But if you configure your build settings using config files like me, open your base <code>.xcconfig</code> file and set:</p><pre><code><code>LD_GENERATE_MAP_FILE = YES</code></code></pre><p>Next, there&#8217;s the <code>LD_GENERATE_MAP_FILE</code> build setting:</p><blockquote><p>This setting defines the path to the map file written by the linker when the Write Link Map File (LD_GENERATE_MAP_FILE) setting is activated. By default, a separate file will be written for each architecture and build variant, and these will be generated in the Intermediates directory for the target whose product is being linked.</p><p>Defaults to $(TARGET_TEMP_DIR)/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt</p></blockquote><p>I recommend customizing the path where the linkmaps are generated, so you can easily find them later (we&#8217;ll need it):</p><pre><code>LD_MAP_FILE_PATH = $(SRCROOT)/.build/Linkmaps/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt</code></pre><p>You can also set that value via Xcode&#8217;s Build Settings panel if you prefer.</p><p>If you decide to customize your <code>LD_MAP_FILE_PATH</code>, you will also need to make sure its directory exists <strong>prior</strong> to building your app, <strong>otherwise the linkmap files won&#8217;t be generated</strong>. You can do this as part of your build pipeline, e.g. by adding a new Run Script to your Build Phases, or adding a step in your CI scripts, like <em>fastlane</em>.</p><p><em>Note: some projects might generate multiple linkmaps, and others might generate just one, and that&#8217;s expected.</em></p><h2>Attaching your linkmaps to <code>.xcarchive</code></h2><p>Emerge requires you to submit your linkmaps to the <code>.xcarchive</code> file you upload to them, like this:</p><pre><code>&#9500;&#9472;&#9472; MyApp.xcarchive
&#9474;   &#9500;&#9472;&#9472; Info.plist
&#9474;   &#9500;&#9472;&#9472; dSYMs
&#9474;   &#9474;   &#9492;&#9472;&#9472; MyApp.app.dSYM
&#9474;   &#9500;&#9472;&#9472; Products
&#9474;   &#9474;   &#9492;&#9472;&#9472; Applications
&#9474;   &#9474;       &#9492;&#9472;&#9472; MyApp.app
&#9474;   &#9492;&#9472;&#9472; Linkmaps
&#9474;       &#9492;&#9472;&#9472; MyApp-LinkMap.txt</code></pre><p>Let&#8217;s see how that would look like, via <em>fastlane</em>:</p><pre><code># In your `fastlane/Fastfile`

SRCROOT = File.expand_path("..") # Adjust this accordingly to your project's structure
LINKMAPS_DIR = File.join(SRCROOT, ".build/Linkmaps")

lane :archive do
  &#8230;
  FileUtils.mkdir_p(LINKMAPS_DIR) # This directory must exist before building your app
  build_app(&#8230;) # Code that will build your app
end

lane :distribute do
  distribute_to_emerge
  distribute_to_app_store
end

private_lane :distribute_to_emerge do
  attach_linkmap_to_xcarchive
  emerge(&#8230;) # Code that will upload your .xcarchive to emerge
end

private_lane :attach_linkmap_to_xcarchive do
  xcarchive_path = Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::XCODEBUILD_ARCHIVE]
  source_dir = LINKMAPS_DIR
  destination_dir = "#{xcarchive_path}/Linkmaps"
  FileUtils.cp_r(source_dir, destination_dir, remove_destination: true)
  UI.success("Copied linkmaps to xcarchive: #{destination_dir}")
end</code></pre><h2>Conclusion</h2><p>In this article you learned what linkmaps are, how to generate them, and how to send them to Emerge. These are needed to enable certain features from Emerge.</p><p>If you still don&#8217;t use Emerge in your projects, you can sign up <a href="https://www.emergetools.com/?utm_source=roger.ml&amp;utm_medium=tutorial&amp;utm_campaign=linkmaps">here</a>. </p><p>Subscribe to this publication to be notified when more articles come out :)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.roger.ml/subscribe?"><span>Subscribe now</span></a></p><div class="pullquote"><p>Disclaimer: this post wasn&#8217;t sponsored by Emerge in any way.</p></div>]]></content:encoded></item><item><title><![CDATA[🦾 How to run xcodebuild using Rosetta on Xcode 14.3+]]></title><description><![CDATA[Apple changed how Xcode is supposed to run Rosetta simulators on Xcode 14.3. Learn how this affects xcodebuild.]]></description><link>https://www.roger.ml/p/run-xcodebuild-using-rosetta-xcode-14-3</link><guid isPermaLink="false">https://www.roger.ml/p/run-xcodebuild-using-rosetta-xcode-14-3</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Wed, 24 May 2023 14:16:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!2GZC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Xcode 14.3 removed the Rosetta mode from the application&#8217;s &#8220;info&#8221; panel, and moved it to simulators instead. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2GZC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2GZC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png 424w, https://substackcdn.com/image/fetch/$s_!2GZC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png 848w, https://substackcdn.com/image/fetch/$s_!2GZC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png 1272w, https://substackcdn.com/image/fetch/$s_!2GZC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2GZC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png" width="1420" height="1064" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1064,&quot;width&quot;:1420,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1217612,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2GZC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png 424w, https://substackcdn.com/image/fetch/$s_!2GZC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png 848w, https://substackcdn.com/image/fetch/$s_!2GZC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png 1272w, https://substackcdn.com/image/fetch/$s_!2GZC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6cb8aca-3078-4469-8214-9c7b55f4537e_1420x1064.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Notice how the &#8220;Open using Rosetta&#8221; option is gone on Xcode 14.3</figcaption></figure></div><p>If you catch yourself in need to run Xcode 14.3 under Rosetta via the UI, check out this awesome article made by <strong><a href="https://twitter.com/sarunw">@sarunw</a></strong>: <strong><a href="https://sarunw.com/posts/open-using-rosetta-in-xcode-14-3">https://sarunw.com/posts/open-using-rosetta-in-xcode-14-3</a></strong></p><p>Now, if you&#8217;re looking to run Xcode 14.3 under Rosetta via CLI (using xcodebuild), you need to use the same trick that was used to run macOS apps:</p><pre><code>xcodebuild -workspace MyWorkspace.xcworkspace -scheme MyScheme -destination 'platform=iOS Simulator,name=iPhone 14 Pro,arch=x86_64' clean build test</code></pre><p>In other words, simply append <code>,arch=x86_64</code> to your <code>destination</code> argument.</p><p>And if you&#8217;re using <em>fastlane</em>, since <strong><a href="https://github.com/fastlane/fastlane/releases/tag/2.213.0">2.113.0</a></strong> you can pass this argument to <em>fastlane scan</em> CLI:</p><pre><code><code>bundle exec fastlane scan --clean --workspace MyWorkspace.xcworkspace --scheme MyScheme --device "iPhone 14 Pro" --run_rosetta_simulator</code></code></pre><p>Or add the same argument to your scan action in your Fastfile:</p><pre><code>desc "Runs all the tests"
lane :test do
  scan(
    workspace: "MyWorkspace.xcworkspace",
    scheme: "MyScheme",
    device: "iPhone 14 Pro",
    run_rosetta_simulator: true,
  )
end</code></pre><h2>Conclusion</h2><p>Since Apple enforced Xcode 14.1 to be installed as of late April, when Xcode 14.3 was already available, many of us saw ourselves upgrading to Xcode 14.3 straight away, instead of going through 14.1 or 14.2. For those of you who don&#8217;t support running Xcode builds (and simulator runs) on native Apple Silicon, these config changes will be needed when running on Xcode 14.3.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber &#128522;</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[✅ Embedding Build Status Badges from a Private Jenkins Instance]]></title><description><![CDATA[You wouldn't think you need TWO plugins to achieve this simple task, right?]]></description><link>https://www.roger.ml/p/jenkins-embedded-build-status-badge</link><guid isPermaLink="false">https://www.roger.ml/p/jenkins-embedded-build-status-badge</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Fri, 19 May 2023 19:44:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!8Nxu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8Nxu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8Nxu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png 424w, https://substackcdn.com/image/fetch/$s_!8Nxu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png 848w, https://substackcdn.com/image/fetch/$s_!8Nxu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png 1272w, https://substackcdn.com/image/fetch/$s_!8Nxu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8Nxu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:711374,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8Nxu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png 424w, https://substackcdn.com/image/fetch/$s_!8Nxu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png 848w, https://substackcdn.com/image/fetch/$s_!8Nxu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png 1272w, https://substackcdn.com/image/fetch/$s_!8Nxu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c6ffd5-f366-49dd-974e-318ab8ddb563_1456x832.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I think that embedding a build status badge to your project&#8217;s README file is one of the first things you do after you set up your CI and get a running build for the first time. It&#8217;s such a small detail but gives so much confidence to whoever&#8217;s viewing your project, that the main branch is green, all tests and checks are passing, etc. This is even more important in open source projects.</p><p>But in case your project is closed-source and/or at least your Jenkins instance is, you&#8217;re gonna have a hard time understanding how to achieve this. Although there&#8217;s a plugin that offers the badges itself, the badges alone aren&#8217;t going to help if to view the badge image, you need to enter a username and password &#128517;</p><p>This article shows you how to have the badge image provided by Jenkins to become fully public, while keeping the rest of your Jenkins safe and private.</p><h3>Preparing the ground</h3><ol><li><p>Install these plugins:</p><ul><li><p><a href="https://plugins.jenkins.io/embeddable-build-status">https://plugins.jenkins.io/embeddable-build-status</a></p></li><li><p><a href="https://plugins.jenkins.io/role-strategy">https://plugins.jenkins.io/role-strategy</a></p></li></ul></li><li><p>Navigate to &#8220;Manage Jenkins&#8221; &#8594; &#8220;Configure Global Security&#8221; &#8594; Set "Role-Based Strategy", if you haven&#8217;t already.</p><ol><li><p>If you&#8217;re setting this up for the first time, beware that you&#8217;ll have to set up role-based permission strategy for your entire team. Until you do so, once you enable this setting, your entire team will lose access to Jenkins and only your current (admin) account will have access.</p></li></ol></li><li><p>Now that you installed and enabled role-based permissions, the plugin can be configured from &#8220;Manage Jenkins&#8221; &#8594; &#8220;Manage and Assign Roles&#8221;.</p></li></ol><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/p/jenkins-embedded-build-status-badge?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">If you believe this article could be useful for friends and/or colleagues, consider sharing it! This one is public &#128521;</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/p/jenkins-embedded-build-status-badge?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.roger.ml/p/jenkins-embedded-build-status-badge?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div><h3>Configure a new public role to view badges</h3><p>Under the &#8220;Global roles&#8221; section, add a new role (e.g. &#8220;Public&#8221;) and check the "Job ViewStatus" checkbox as its only permission:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Yl8D!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Yl8D!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Yl8D!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Yl8D!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Yl8D!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Yl8D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg" width="727" height="282.6112637362637" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:566,&quot;width&quot;:1456,&quot;resizeWidth&quot;:727,&quot;bytes&quot;:263470,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Yl8D!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Yl8D!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Yl8D!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Yl8D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8eb4d334-c8e1-4e90-9dda-843253d887e5_3024x1176.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Then scroll down to the &#8220;Item roles&#8220; and create a new role (e.g. &#8220;View Status Badge Permission&#8220;), setting the name of your Jenkins Job in the &#8220;Pattern&#8221; field, and check the &#8220;Job Read&#8221; permission:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!loeg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!loeg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg 424w, https://substackcdn.com/image/fetch/$s_!loeg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg 848w, https://substackcdn.com/image/fetch/$s_!loeg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!loeg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!loeg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg" width="1456" height="558" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:558,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:194942,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!loeg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg 424w, https://substackcdn.com/image/fetch/$s_!loeg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg 848w, https://substackcdn.com/image/fetch/$s_!loeg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!loeg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6457cf5a-2511-4d74-a1d6-e456c5565617_3024x1158.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Don&#8217;t forget to hit the &#8220;Save&#8221; button at the bottom of the page, before continuing.</p><p>Then, navigate to &#8220;Assign Roles&#8221; view in the left menu, and check both checkboxes: &#8220;Anonymous &#8594; Public&#8221; and &#8220;Anonymous &#8594; View Status Badge Permission&#8221; that you just created:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rtlV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rtlV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg 424w, https://substackcdn.com/image/fetch/$s_!rtlV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg 848w, https://substackcdn.com/image/fetch/$s_!rtlV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!rtlV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rtlV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg" width="1456" height="830" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:830,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:217330,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rtlV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg 424w, https://substackcdn.com/image/fetch/$s_!rtlV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg 848w, https://substackcdn.com/image/fetch/$s_!rtlV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!rtlV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3b38bb49-e659-4fde-adb5-e932d427a34d_3024x1724.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Again, don&#8217;t forget to hit the &#8220;Save&#8221; button at the bottom of the page to make sure your changes are persisted.</p><h3>Getting the badge image URL</h3><p>Navigate to the Job that you want to display badges for. In my case, that&#8217;s under <code>job/protected-branches-multibranch-pipeline/job/master</code>, and then click on the &#8220;Embeddable Build Status&#8221; menu in the left menu:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2zxb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2zxb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png 424w, https://substackcdn.com/image/fetch/$s_!2zxb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png 848w, https://substackcdn.com/image/fetch/$s_!2zxb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png 1272w, https://substackcdn.com/image/fetch/$s_!2zxb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2zxb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png" width="1456" height="961" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:961,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:168322,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2zxb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png 424w, https://substackcdn.com/image/fetch/$s_!2zxb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png 848w, https://substackcdn.com/image/fetch/$s_!2zxb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png 1272w, https://substackcdn.com/image/fetch/$s_!2zxb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F991fb22b-38d9-44a5-9aee-3109f5fe11ed_1928x1272.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>That should take you to a page with preformatted options for your badge, e.g. in markdown, HTML, etc. Use the &#8220;unprotected&#8221; ones in your README file (or wherever you&#8217;re going to display them).</p><h2>Conclusion</h2><p>With the setup seen in this article, you&#8217;ll open up your build status to be seen by the public (with no authentication whatsoever), and only that. The rest of your Jenkins jobs, builds, code coverage, reports, artifacts, etc, will all remain behind user authentication.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber &#128522;</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[🖼️ iOS Best Practices: Don't Use Dynamic Strings to Init Images]]></title><description><![CDATA[Understand why using dynamic strings (e.g. string concatenation and interpolation) to initialize UIImages is a bad idea.]]></description><link>https://www.roger.ml/p/dont-use-dynamic-strings-to-init-images</link><guid isPermaLink="false">https://www.roger.ml/p/dont-use-dynamic-strings-to-init-images</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Tue, 16 May 2023 12:41:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!gFXG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gFXG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gFXG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png 424w, https://substackcdn.com/image/fetch/$s_!gFXG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png 848w, https://substackcdn.com/image/fetch/$s_!gFXG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png 1272w, https://substackcdn.com/image/fetch/$s_!gFXG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gFXG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:840215,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gFXG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png 424w, https://substackcdn.com/image/fetch/$s_!gFXG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png 848w, https://substackcdn.com/image/fetch/$s_!gFXG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png 1272w, https://substackcdn.com/image/fetch/$s_!gFXG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7199d279-5376-44b3-ad76-1ab4f3f12bfe_1456x832.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As an engineer and avid programmer, you&#8217;re always identifying patterns in everything you see, so if you face something like this:</p><pre><code>enum Direction {
    case left, right, up, down

    var icon: UIImage {
        switch self {
        case .left: return UIImage(named: "ic-left")!
        case .right: return UIImage(named: "ic-right")!
        case .up: return UIImage(named: "ic-up")!
        case .down: return UIImage(named: "ic-down")!
    }    
}</code></pre><p>Your OCD is triggered and your hands start shaking immediately. <em>&#8220;I MUST refactor this code</em> <em>to simplify it! Let&#8217;s factor out the similar logic, and make everything much simpler!&#8221;</em></p><pre><code>enum Direction: String {
    case left, right, up, down

    var icon: UIImage {
        return UIImage(named: "ic-\(rawValue)")!
    }    
}</code></pre><p><em>Ahhh, much better! I just improved this code so much!</em></p><p>Let&#8217;s understand below why this is not a good idea when scaling your codebase.</p><blockquote><p><em>For the purposes of this article, &#8220;dynamic strings&#8221; refer to any string value that can&#8217;t be represented as a</em> <em><a href="https://developer.apple.com/documentation/swift/staticstring">StaticString</a>, i.e. text that isn&#8217;t known during compile time. This includes strings composed by string interpolation, string concatenation, and using string variables.</em></p></blockquote><h2>1. Unfortunately, UIImage initialization isn't very safe in iOS</h2><p>Due to the fact that we&#8217;re force unwrapping the UIImage initialization above, if anything goes wrong when initializing that image, that code would crash. This could happen when introducing a new enum case, for instance: the developer adding the new enum case could easily miss the fact that the icon computed variable is using the enum&#8217;s raw value to initialize (and force unwrap) an image, and forget to add its associated image to the project&#8217;s xcassets resource folder, resulting in a crash.</p><p>When opting for the explicit initialization of each image (the first snippet), the developer would get a compile-time error when they add a new enum case, which would force them to think about how to handle it, and thus add the image resource to the project. </p><div class="pullquote"><p><strong>Whenever possible, always opt for compile-time safety measures.</strong></p></div><h2>2. Tools that identify unused resources aren&#8217;t as effective</h2><p>There are amazing tools out there like <a href="https://github.com/tinymind/LSUnusedResources">LSUnusedResources</a> that help you identify and delete unused assets in your project. However, these tools will most likely use regex expressions to search for usages of your assets in your codebase, and if you disguise your UIImages using string concatenation, interpolation, or other forms of dynamic strings, it clearly won&#8217;t find them and thus wrongfully mark them as unused assets. If you blindly trust the tool (something you shouldn&#8217;t be doing anyway) and delete those assets, you&#8217;ll get runtime crashes the next time you run your app and try to access those assets.</p><h2>3. Developers looking for usage of assets won&#8217;t find them</h2><p>A curious developer sees those assets named <code>ic-down</code>, <code>ic-left</code>, etc&#8230; and thinks:</p><p>&#8212;  <em>I wonder where they&#8217;re being used?</em></p><p>He then searches for "ic-down&#8221;, &#8220;ic-left&#8221;, etc&#8230; but can&#8217;t find anything other than the asset itself. No use cases.</p><p>He has two options now: prematurely conclude that they&#8217;re not being used and delete them (which would be the wrong thing to do), or try searching for substrings (randomly), such as &#8220;left&#8221; (which would earn hundreds of unrelated results), or try to narrow them down by searching for &#8220;ic-&#8221; (again, potentially hundreds of unrelated results), or &#8220;-left&#8221; (potentially no results, in our example). This developer now has to guess what the developer who originally implemented the code was thinking and how they decided to implement the code that accesses these assets. Frustrating, right? Not to mention it&#8217;s unproductive. Not the best DevX.</p><h2>4. Refactors become a nightmare</h2><p>Scenarios 1 and 3 can happen in isolation, but those scenarios become particularly more critical and recurring when the project needs to undergo some sort of refactor, be it an architecture change, or a file structure re-organization, simply refactoring files from UIKit to SwiftUI, or from Objective-C to Swift, etc. Each change you make you need to be more careful, double and triple check your changes, double down on review, double down on manual exploration testing (the most expensive type of tests), just to ensure that everything will go out smoothly. Whereas, if the project (and the team) followed the <em>convention</em> of never initializing images using dynamic strings, certain refactors would be a lot smoother.</p><p>Needless to say I&#8217;m a big fan of codebase standardization, consistency, and <a href="https://en.wikipedia.org/wiki/Convention_over_configuration">Convention Over Configuration</a>. &#129299;</p><h1>Meta-programming kicks in</h1><p>Of course. I couldn&#8217;t write this article without mentioning meta-programming, and amazing developer tools like SwiftGen and Sourcery.</p><p>Meta-programming helps not only reduce the pain of manually writing out boilerplate code, but in this case the most important benefit is making your images compile-time safe. Without digging into its implementation detail (there are a bunch of articles explaining just that e.g. <a href="https://github.com/SwiftGen/SwiftGen#asset-catalog">the official one from SwiftGen</a>, <a href="https://www.kodeco.com/23709326-swiftgen-tutorial-for-ios">this one from Kodeco (former Ray Wenderlich)</a> and <a href="https://metova.com/how-to-use-swiftgen-to-generate-an-enum-from-your-asset-catalog/">this other one</a>), your code could become something like this:</p><pre><code>enum Direction {
    case left, right, up, down

    var icon: UIImage {
        switch self {
        case .left: return UIImage.Icons.Direction.left
        case .right: return UIImage.Icons.Direction.right
        case .up: return UIImage.Icons.Direction.up
        case .down: return UIImage.Icons.Direction.down
    }
}</code></pre><p>And, in case anything goes wrong with your asset clean up or refactor, your code won&#8217;t compile anymore and you will know exactly where things went wrong. Typos in asset names will be a thing from the past, and developers can easily find where the assets are being consumed (in the generated files, by searching like they normally would).</p><h1>Exceptions</h1><blockquote><p><em>My image identifiers come from the backend, and my images are stored locally</em></p></blockquote><p>Here I&#8217;d say it depends on the quantity. I would probably avoid having backend-driven image identifiers and locally-stored images if possible, but if that&#8217;s not the best approach for your project, I&#8217;d probably still declare images explicitly (without using dynamic strings) if there&#8217;s a reasonable number of images to be mapped. If there are thousands of them, then probably not. &#128579;</p><blockquote><p>It&#8217;s too complicated to use meta-programming</p></blockquote><p>Believe me, it&#8217;s not! But, of course, if you&#8217;re an indie developer bootstrapping a pet project, or if it&#8217;s a small project with 10-20 images, it&#8217;s okay to declare them using string literals. But as the project grows, the advantages of having your image references compile-time safe start to stand out.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/p/dont-use-dynamic-strings-to-init-images?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thank you for reading roger.ml. If you found this post insightful, consider sharing it with your friends and colleagues &#128522;</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/p/dont-use-dynamic-strings-to-init-images?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.roger.ml/p/dont-use-dynamic-strings-to-init-images?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div><h1>Conclusion</h1><p>Keeping your image initialization code static has multiple benefits:</p><ul><li><p>Code safety</p></li><li><p>Smoother usage of tools that identify and clean up unused assets</p></li><li><p>It&#8217;s more productive to have your codebase follow a single standard, contributing to your team&#8217;s DevX</p></li><li><p>Keep your future self (or colleagues) sane, by making refactors easier</p></li></ul><p>If you or your team is still using dynamic strings in your codebase, consider if that&#8217;s really the best approach for your project. Maybe you&#8217;ve already faced some of the issues highlighted in this article.</p><p>And if you have legitimate use cases to initialize UIImages using dynamic strings, reach out to me on <a href="https://twitter.com/rogerluan_">@rogerluan_</a>, I&#8217;d love to learn more!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.roger.ml/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[🔗 Troubleshooting Universal Links (aka Deep Links) in iOS]]></title><description><![CDATA[Your one-stop shop for the most common Universal Link issues in iOS. Troubleshooting your deep links starts here!]]></description><link>https://www.roger.ml/p/troubleshooting-ios-deep-links</link><guid isPermaLink="false">https://www.roger.ml/p/troubleshooting-ios-deep-links</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Fri, 05 May 2023 12:13:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BWTd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BWTd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BWTd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png 424w, https://substackcdn.com/image/fetch/$s_!BWTd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png 848w, https://substackcdn.com/image/fetch/$s_!BWTd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png 1272w, https://substackcdn.com/image/fetch/$s_!BWTd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BWTd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png" width="1200" height="628" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:628,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:117095,&quot;alt&quot;:&quot;A mobile phone showing the text \&quot;Troubleshooting iOS Deep Links &#8212; Your One Stop Shop\&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A mobile phone showing the text &quot;Troubleshooting iOS Deep Links &#8212; Your One Stop Shop&quot;" title="A mobile phone showing the text &quot;Troubleshooting iOS Deep Links &#8212; Your One Stop Shop&quot;" srcset="https://substackcdn.com/image/fetch/$s_!BWTd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png 424w, https://substackcdn.com/image/fetch/$s_!BWTd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png 848w, https://substackcdn.com/image/fetch/$s_!BWTd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png 1272w, https://substackcdn.com/image/fetch/$s_!BWTd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2e8d10-eecc-4fae-9580-1d20a36a5768_1200x628.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Over the course of years, I worked closely with deep links, facing many different issues. Some of those issues were reported repeatedly as they could be sometimes user error, or lack of support in certain 3rd party apps. As I learned from these mistakes, I put together this page with links to articles and documentation that helped me solve common setup issues and also recurring ones &#8212; even when the issue could be tied to a user&#8217;s device specifically.</p><p>The links below are mostly from Branch and AppsFlyer, two of the most popular deep link providers, but that doesn&#8217;t mean that each resource applies only to their respective services or SDKs. In fact, most of them are independent of the service and apply to deep links in iOS in general, while some even apply to Android deep links as well.</p><blockquote><p>Disclaimer: for the purpose of keeping the lingo simple, I&#8217;ll use &#8220;Universal Link&#8221; and &#8220;deep link&#8221; interchangeably in this article. Universal Links are Apple&#8217;s brand name for what&#8217;s popularly known as &#8220;deep links&#8221;, while Google calls theirs &#8220;App Links&#8221;. Deep links can also be interpreted as those app-specific URL schemes like &#8220;<code>whatsapp://&#8221;</code>, but popularly most people generalize and call those, as well as Universal Links and App Links as simply &#8220;deep links&#8221;.</p></blockquote><h1>Troubleshooting Deep Link Issues</h1><ul><li><p><a href="https://help.branch.io/developers-hub/docs/ios-troubleshooting">Branch - iOS Troubleshooting</a>: Must-read resource on how to solve common deep link issues (Branch-specific, or not). Contains potential solutions to problems that include (but are not limited to):</p><ul><li><p>App not opening when using deep link.</p></li><li><p>App not passing data when using deep link.</p></li><li><p>Deferred deep linking not working.</p></li><li><p>How to re-enable Apple Universal Links.</p></li><li><p>Determining if deep link is from Branch or not (without network connection).</p></li></ul></li><li><p><a href="https://help.branch.io/developers-hub/docs/ios-universal-links">Branch - Apple Universal Links</a></p><ul><li><p>Explains how Universal Links work.</p></li><li><p>Lists examples of known apps that might not support deep links very well, e.g. Chrome, Facebook, WeChat, Twitter, LinkedIn, Instagram, Pinterest, etc.</p></li><li><p>Contains other valuable troubleshooting FAQ that are not included elsewhere.</p></li></ul></li><li><p><a href="https://help.branch.io/faq/docs/deep-links-do-not-open-the-app">Branch - Deep links do not open the app</a></p><ul><li><p>Explains great examples of why your deep links might not be opening the app. Definitely a must-read.</p></li></ul></li><li><p><a href="https://help.branch.io/faq/docs/viewing-deep-link-data">Branch - Viewing deep link data</a></p><ul><li><p>Example specific to Branch on how to view deep link data: simply add&nbsp;<code>?debug=1</code>&nbsp;to the end of your deep link. (Works for Branch&#8217;s deep links only)</p></li></ul></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><ul><li><p><a href="https://www.branch.io/resources/blog/3-steps-to-troubleshoot-ios-9-and-ios-10-universal-links/">Branch - 3 Steps to Troubleshoot iOS 9 and iOS 10 Universal Links</a></p></li></ul><ul><li><p><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438">AppsFlyer - OneLink troubleshooting and FAQ</a>: This is a great resource for troubleshooting OneLink issues, including (but not limited) to answering these questions:</p><ul><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#which-method-should-i-use-for-opening-apps%E2%80%94universal-links-app-links-or-uri-schemes">Which method should I use for opening apps &#8212; Universal Links, App Links, or URI schemes?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#which-operating-systems-allow-uri-schemes-to-open-the-app">Which operating systems allow URI schemes to open the app?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#do-i-need-to-update-the-app-after-ios-universal-links-and-android-app-links-setup">Do I need to update the app after iOS Universal Links and Android App Links setup?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-redirect-existing-users-to-a-web-page-instead-of-the-app">Can I redirect existing users to a web page instead of the app?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-redirect-tablet-users-to-a-different-app-or-url-then-mobile-app-users">Can I redirect tablet users to a different app or URL then mobile app users?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-assign-an-app-to-multiple-onelink-templates">Can I assign an app to multiple OneLink templates?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-delete-a-onelink-template">Can I delete a OneLink template?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-change-the-subdomain-in-a-onelink-template">Can I change the subdomain in a OneLink template?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-the-domainsubdomain-of-a-onelink-template-be-applied-to-the-template-id-of-another-template">Can the domain/subdomain of a OneLink template be applied to the template ID of another template?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-use-the-same-subdomain-for-more-than-one-template">Can I use the same subdomain for more than one template?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#how-can-i-redirect-desktop-users">How can I redirect desktop users?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#how-can-i-redirect-chrome-os-users">How can I redirect Chrome OS users?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#does-onelink-work-without-the-appsflyer-sdk">Does OneLink work without the AppsFlyer SDK?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#what-methods-are-available-for-creating-onelink-custom-links">What methods are available for creating OneLink custom links?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-onelink-custom-links-open-the-app-from-a-browser">Can OneLink custom links open the app from a browser?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#why-doesnt-the-onelink-url-open-the-app-or-deep-link-existing-users">Why doesn't the OneLink URL open the app or deep link existing users?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#why-does-my-onelink-url-open-a-blank-page-in-chrome">Why does my OneLink URL open a blank page in Chrome?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-use-onelink-for-redirection-on-brave">Can I use OneLink for redirection on Brave?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#do-onelink-custom-links-take-longer-to-load-than-other-links">Do OneLink custom links take longer to load than other links?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#how-can-i-record-the-cost-per-install-when-using-onelink">How can I record the cost per install when using OneLink?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#how-can-i-prevent-parameter-forwarding-on-redirections">How can I prevent parameter forwarding on redirections?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#are-singleplatform-custom-links-still-available">Are single-platform custom links still available?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-change-the-short-url-id-after-saving-a-link">Can I change the short URL ID after saving a link?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-wrap-and-shorten-onelink-custom-links">Can I wrap and shorten OneLink (or deep links)?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-use-onelink-custom-links-in-emails">Can I use OneLink (or deep links) in emails?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#how-do-users-get-redirected-to-their-regional-app-store">How do users get redirected to their regional app store?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#why-isnt-my-onelink-url-redirecting-users-to-the-specified-domain">Why isn't my OneLink URL redirecting users to the specified domain?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#what-parameters-are-mandatory-for-onelink-custom-links">What parameters are mandatory for OneLink custom links?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-parameters-be-appended-to-the-short-url">Can parameters be appended to the short URL?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#what-custom-media-source-names-can-i-use">What custom media source names can I use?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#what-attribution-parameters-can-be-added-to-a-onelink">What attribution parameters can be added to a OneLink?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#are-custom-parameters-added-to-onelink-custom-links-available-in-raw-data">Are custom parameters added to OneLink custom links available in raw data?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#are-parameter-names-case-sensitive">Are parameter names case sensitive?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-use-onelink-in-social-apps-unpaid">Can I use deep links in social apps (unpaid)?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-use-onelink-with-ad-networks-paid">Can I use deep links with ad networks (paid)?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#how-can-i-solve-the-ios-applaunching-issue-from-social-media-apps">How can I solve the iOS app-launching issue from social media apps?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-use-onelink-on-instagram">Can I use deep links on Instagram?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-i-use-onelink-for-redirection-on-wechat">Can I use deep links for redirection on WeChat?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#why-are-android-users-with-the-app-installed-being-redirected-to-a-web-url-and-not-into-the-app-when-they-click-a-link-on-facebook-webview">Why are Android users with the app installed being redirected to a web URL and not into the app when they click a link on Facebook webview?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#how-can-i-ab-test-the-social-app-landing-page">How can I A/B test the Social app landing page?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#how-can-i-bypass-the-app-selection-dialog">How can I bypass the app selection dialog?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#what-deep-linking-methods-should-be-implemented">What deep linking methods should be implemented?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#can-a-uri-scheme-af_dp-parameter-be-used-for-deep-linking">Can a URI scheme (af_dp parameter) be used for deep linking?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#does-onelink-deep-linking-work-for-ios-14">Does OneLink deep linking work for iOS 14?</a></em></p></li><li><p><em><a href="https://support.appsflyer.com/hc/en-us/articles/360014821438#does-onelink-deferred-deep-linking-work-for-ios-14">Does OneLink deferred deep linking work for iOS 14?</a></em></p></li></ul></li></ul><h1>Deep Link Developer Documentation</h1><ul><li><p><a href="https://dev.appsflyer.com/hc/docs/dl_ios_unified_deep_linking">AppsFlyer - iOS Unified Deep Linking</a></p><ul><li><p>Has a super helpful visual diagram of how deep linking works on iOS, and explains some implementation details.</p></li></ul></li><li><p><a href="https://help.branch.io/using-branch/docs/ad-links">Branch - Create Ad Links</a></p><ul><li><p>Explains how Ad Links should be created.</p></li></ul></li><li><p><a href="https://help.branch.io/using-branch/docs/universal-email-integration-guide">Branch - Universal Email Integration Guide</a></p><ul><li><p>Thorough guide explaining how to integrate Branch's Universal Email solution with your Email Service Provider (ESP).</p></li><li><p>If you're using Iterable as your ESP, a must-read is&nbsp;<a href="https://support.iterable.com/hc/en-us/articles/115002651226-Deep-Links-Setup-">Iterable - Deep Links Setup</a>.</p></li></ul></li></ul><h1>Understanding Deep Links Concepts</h1><ul><li><p><a href="https://www.branch.io/glossary/deterministic-matching/">Branch - Deterministic Matching</a></p></li><li><p><a href="https://www.branch.io/glossary/predictive-modeling/">Branch - Predictive Modeling</a></p></li><li><p><a href="https://www.branch.io/glossary/probabilistic-modeling/">Branch - Probabilistic Modeling</a></p></li><li><p><a href="https://help.branch.io/using-branch/docs/skadnetwork">Branch - SKAdNetwork</a></p><ul><li><p>Explains how SKAdNetwork works, in particular with Branch.</p></li><li><p>Contains lots of helpful tables and a visual diagram of how SKAdNetwork works.</p></li></ul></li><li><p><a href="https://www.appsflyer.com/blog/mobile-marketing/universal-links-app-links/">AppsFlyer - Making sense of Universal Links and App Links</a></p></li><li><p><a href="https://www.branch.io/resources/blog/best-practices-user-opt-in-ios-14/">Branch - Best Practices to Secure User Opt-In on iOS 14</a></p></li><li><p><a href="https://www.branch.io/resources/blog/guide-to-skadnetwork-conversion-values/">Branch - A Marketer-Friendly Guide to SKAdNetwork Conversion Values</a></p></li><li><p><a href="https://www.branch.io/resources/blog/nativelink-and-app-clips-solving-the-deferred-deep-linking-challenges-of-private-relay/">Branch - NativeLink and App Clips: Solving the Deferred Deep Linking Challenges of Private Relay</a></p></li><li><p><a href="https://www.branch.io/glossary/">Branch - Glossary</a></p><ul><li><p>An extensive glossary on common terms used around the industry, engineers, and marketers.</p></li></ul></li><li><p><a href="https://www.appsflyer.com/glossary/">AppsFlyer - Glossary</a></p><ul><li><p>A mobile marketing glossary featuring must-know industry terms, topics, and concepts to help you keep up.</p></li></ul></li></ul><h1>App Tracking Transparency Prompts</h1><p>When the subject is deep links, we&#8217;re often associating them with ad attribution, thus, App Tracking Transparency requests. I found this very useful resource that shows examples of prompts that other apps are using in the wild. Definitely worth checking out if you show this prompt to users:</p><div class="pullquote"><p><strong><a href="https://www.attprompts.com/?ref=roger.ml">A gallery of App Tracking Transparency (ATT) prompts &#8212; </a></strong><a href="https://www.attprompts.com/?ref=roger.ml">A collaborative effort to gather as many live App Tracking Transparency (ATT) prompt examples as possible.</a></p></div><h1>Status Pages of Deep Link Services</h1><p>Sometimes Branch and AppsFlyer go down. This causes issues creating deep links (which would impact your BE if you&#8217;re creating them through their API), resolving deep links (or their parameters) in the app, and so on. If you identify an uncommon issue, check if the services are up and running:</p><ul><li><p><a href="https://status.appsflyer.com/">https://status.appsflyer.com</a></p></li><li><p><a href="https://status.branch.io/">https://status.branch.io</a></p></li></ul><h1>Online Deep Link Validators</h1><p>Online deep link validators check if a given URL is properly set up for Universal Links (iOS) and App Links (Android). This allows you to check if the <code>apple-app-site-association</code> and <code>assetlinks.json</code> on a website is properly formatted for deep links as defined by Apple and Google, respectively.</p><ul><li><p><a href="https://branch.io/resources/aasa-validator/">https://branch.io/resources/aasa-validator</a> - iOS only.</p></li><li><p><a href="https://www.appsflyer.com/tools/link-validator/">https://www.appsflyer.com/tools/link-validator</a> - Android and iOS.</p></li><li><p><a href="https://yurl.chayev.com">https://yurl.chayev.com</a> - Android and iOS.</p></li></ul><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/p/troubleshooting-ios-deep-links?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">If you found this resource useful, consider sharing it with your team! This one is public so anyone can access it :)</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/p/troubleshooting-ios-deep-links?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.roger.ml/p/troubleshooting-ios-deep-links?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[🔖 Slack Improves Task Management and Introduces "Later" View]]></title><description><![CDATA[The feature hasn't rolled out to everyone yet, but it should help your team organize those reminders.]]></description><link>https://www.roger.ml/p/slack-task-management</link><guid isPermaLink="false">https://www.roger.ml/p/slack-task-management</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Fri, 28 Apr 2023 12:41:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!nsqw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you&#8217;re like me and heavily use the Slackbot Reminders feature in Slack, you&#8217;ll be pleased to know that Slack is releasing an update that helps us manage those reminders, or &#8220;tasks&#8221;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nsqw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nsqw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png 424w, https://substackcdn.com/image/fetch/$s_!nsqw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png 848w, https://substackcdn.com/image/fetch/$s_!nsqw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png 1272w, https://substackcdn.com/image/fetch/$s_!nsqw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nsqw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png" width="1456" height="393" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:393,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:94320,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nsqw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png 424w, https://substackcdn.com/image/fetch/$s_!nsqw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png 848w, https://substackcdn.com/image/fetch/$s_!nsqw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png 1272w, https://substackcdn.com/image/fetch/$s_!nsqw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3c02cd0e-68c7-40a3-8d7f-f213e45c78c1_1638x442.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this new &#8220;Later&#8221; view, that has been rolling out since March 2023 according to <a href="https://slack.com/help/articles/13453851074067-Save-it-for-%22Later%22">their announcement</a>, you have three tabs:</p><ul><li><p>In progress: for all reminders that are still pending and haven&#8217;t been marked as completed yet. This includes overdue and upcoming reminders.</p></li><li><p>Archived: for messages you had previously marked as &#8220;bookmarked&#8221;. Slack actually merged two features into the &#8220;Later&#8221; feature: the &#8220;Slackbot Reminders&#8221; and the &#8220;bookmarked&#8221; messages.</p></li><li><p>Completed: for reminders you marked as completed.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gLsJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gLsJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png 424w, https://substackcdn.com/image/fetch/$s_!gLsJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png 848w, https://substackcdn.com/image/fetch/$s_!gLsJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png 1272w, https://substackcdn.com/image/fetch/$s_!gLsJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gLsJ!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png" width="1200" height="551.3736263736264" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:669,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:218360,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gLsJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png 424w, https://substackcdn.com/image/fetch/$s_!gLsJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png 848w, https://substackcdn.com/image/fetch/$s_!gLsJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png 1272w, https://substackcdn.com/image/fetch/$s_!gLsJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4633c6a2-8475-41db-8b93-f4ebc0883cdf_2098x964.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A preview of how the Later view looks like, with sample tasks.</figcaption></figure></div><p>You can also quickly create a new reminder by clicking the &#8220;New reminder&#8221; button at the top right corner of the page. I believe this wasn&#8217;t possible via UI before, you had to write a command in Slack like <code>&#8220;/remind remind me to review all open pull requests by 5pm&#8220;</code>, so for those of you who are not used to the command proper syntax/format, having this feature readily available in a visual interface should be really helpful and lower the barrier of entry for users that still don&#8217;t use Slack reminders.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YhiB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YhiB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png 424w, https://substackcdn.com/image/fetch/$s_!YhiB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png 848w, https://substackcdn.com/image/fetch/$s_!YhiB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png 1272w, https://substackcdn.com/image/fetch/$s_!YhiB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YhiB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png" width="357" height="286.4022471910112" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:714,&quot;width&quot;:890,&quot;resizeWidth&quot;:357,&quot;bytes&quot;:48055,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YhiB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png 424w, https://substackcdn.com/image/fetch/$s_!YhiB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png 848w, https://substackcdn.com/image/fetch/$s_!YhiB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png 1272w, https://substackcdn.com/image/fetch/$s_!YhiB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F85977d47-6b44-4e77-b8b1-41e8c9d72190_890x714.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The new reminder creation prompt.</figcaption></figure></div><h3><em>Why would I manage tasks/reminders within Slack? Why not a proper project management tool like Jira, GitHub Projects, Trello, etc?</em></h3><p>If you&#8217;re curious as to what are the use cases that justify using Slack Reminders, check out my other blog post where I fully utilize Slack features and share some tips &amp; tricks to become a Slack super user:</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;a59ee09f-5dda-4eb2-ba0d-070c2830b0f3&quot;,&quot;caption&quot;:&quot;This article has a few goals: &#127947;&#65039;&#8205;&#9794;&#65039; Make it nearly impossible to miss anything important in Slack &#129470; Help make you accountable for everything that goes on in Slack &#127919; Improve your productivity When combined, this will make you become a&#8230; Slack super user! &#129464;&#8205;&#9794;&#65039;&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The ultimate guide to becoming a Slack super user &#129464;&#8205;&#9794;&#65039;&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:69564182,&quot;name&quot;:&quot;Roger&quot;,&quot;bio&quot;:&quot;Lead Mobile Engineer @tellushome. Core contributor of @FastlaneTools. Traveler @RemoteYear #swift #swiftlang&quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/33f6af46-6af3-433e-9633-532785a0d229_3088x2316.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-03-11T14:28:05.015Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c50415a0-d99a-49e4-b1c0-634f6cb0df63_768x512.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.roger.ml/p/slack-super-user&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:107022888,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;roger.ml&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac38d071-6ef0-467a-89bb-8c3198f464ac_132x132.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><h1>Conclusion</h1><p>I think this is an exciting new feature to use within Slack, and I can&#8217;t wait for it to become available to everyone. I believe it provides a clearer view into what&#8217;s overdue and what&#8217;s next, by properly displaying the reminders by due date, helping us prioritize them.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[✍️ Step-by-Step Jenkins Setup for iOS in 2023 | Ultimate Guide]]></title><description><![CDATA[I spent over 3 weeks setting it up the first time. Don't make the same mistakes I did!]]></description><link>https://www.roger.ml/p/jenkins-ios-setup</link><guid isPermaLink="false">https://www.roger.ml/p/jenkins-ios-setup</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Thu, 27 Apr 2023 13:00:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!sner!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sner!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sner!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png 424w, https://substackcdn.com/image/fetch/$s_!sner!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png 848w, https://substackcdn.com/image/fetch/$s_!sner!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png 1272w, https://substackcdn.com/image/fetch/$s_!sner!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sner!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png" width="1200" height="628" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:628,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:260761,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sner!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png 424w, https://substackcdn.com/image/fetch/$s_!sner!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png 848w, https://substackcdn.com/image/fetch/$s_!sner!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png 1272w, https://substackcdn.com/image/fetch/$s_!sner!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6384f1a2-81bb-43a0-878f-b643b2a7716c_1200x628.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It&#8217;s 2023 and you and your team decided to move away from the current CI provider you have, towards a self-hosted Jenkins CI. Great! In this article we won't cover the pros/cons of using one CI system against another, but rather be laser focused in showing how to set up a fully working Jenkins CI environment for iOS.</p><p>Although you can probably get an environment up and running in a couple hours by discovering Jenkins on your own, and even be able to run an iOS build on it, there are lots of small issues that compound over time and make such naive implementation quite unsustainable.</p><p>I did a lot of experimenting, and struggled for a few weeks to get everything right, specially after getting multiple cryptic errors alongside issues that would happen &#8220;every once in awhile&#8221;. After surprisingly not finding a comprehensive guide on &#8220;Jenkins best practices&#8221;, I decided to put this one together to share knowledge I learned the hard way. &#129394;</p><h1>Preconditions</h1><p>I won&#8217;t be covering the steps needed to get yourself a macOS machine. This article assumes you have a bare metal machine running macOS (yes, macOS is required), and that you have already installed Jenkins in your machine. If you haven&#8217;t installed Jenkins yet, here&#8217;s the official guide (this step is straightforward): <a href="https://www.jenkins.io/doc/book/installing/macos/">https://www.jenkins.io/doc/book/installing/macos</a></p><p>Furthermore, in this article we&#8217;re going to be using <a href="https://brew.sh">Homebrew</a>, <a href="https://github.com/rbenv/rbenv">rbenv</a>, <a href="https://github.com/RobotsAndPencils/xcodes">xcodes</a>, and <a href="https://bundler.io">Bundler</a>. I won&#8217;t go into details as to why I recommend each one of these (maybe in a future post &#128521;), but feel free to reach out to me if you&#8217;re curious!</p><h1>Installing dependencies</h1><p>First and foremost, we need to install Homebrew. You should follow the instructions in <a href="https://brew.sh">this link</a> &#8212; it&#8217;s quite straightforward.</p><h2>Updating your <code>~/.zshrc</code> file</h2><p>These settings should get you up and running more easily, and features a security measure for <em>fastlane</em>. Append this to your ~/.zshrc file:</p><pre><code><code># Initialize rbenv if it's already installed
export PATH=$PATH:/usr/local/bin:$HOME/.rbenv/bin:$HOME/.rbenv/shims
if which rbenv &gt; /dev/null; then
&nbsp; eval "$(rbenv init -)"
fi

# Set these according to your project's needs/configuration
export XCODE_VERSION="14.3"
export BUNDLER_VERSION="2.2.32"
export RUBY_VERSION="3.1.2"

# For security measures, ask fastlane to not store your fastlane password.
# Even though your CI workflows and scripts probably doesn't even use App Store Connect User+Password anymore, if you happen to run fastlane manually in the CI machine, you don't want it to accidentally store your own Apple ID password in its Keychain.
export FASTLANE_DONT_STORE_PASSWORD="1"

# Required by fastlane (to prevent issues with unicode)
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
export LANGUAGE=en_US.UTF-8</code></code></pre><blockquote><p>Note that depending on which cloud provider you&#8217;re using (e.g. AWS, Azure, etc), your existing <code>~/.zshrc</code> file might already have some content in it, thus, simply append the snippet above at the end of the file, to avoid issues.</p></blockquote><p>After updating your <code>~/.zshrc</code> file, source it to apply the changes (or kill your SSH session and start a new one):</p><pre><code>source ~/.zshrc</code></pre><p>Then, you can copy and paste this snippet in your Terminal to get your dependencies set up more easily:</p><pre><code>echo "Installing rbenv and the right ruby version that your project uses"
brew install rbenv ruby-build
rbenv install $RUBY_VERSION
rbenv global $RUBY_VERSION

echo "Speeding up gem installs"
echo "gem: --no-document" &gt;&gt; ~/.gemrc

echo "Initializing rbenv (will run the initialization code that we just saved in the ~/.zshrc file)"
source ~/.zshrc

echo "Installing bundler"
gem install rubygems-update
gem update --system
gem install bundler -v $BUNDLER_VERSION

echo "Installing xcodes"
brew install xcodesorg/made/xcodes

echo "Installing the Xcode version your team uses"
echo "Note that this one is gonna take a long while (maybe 10-20 minutes). Take a break, and once it's, done you're gonna need to enter the sudo password so the installation completes"
brew install aria2
xcodes install $XCODE_VERSION --experimental-unxip --select --update</code></pre><h1>Configuring your git credentials</h1><p>For this step, I wrote a dedicated article. Pause reading this article and follow the instructions on this other article to get your git credentials configured in your Jenkins machine:</p><div class="embedded-post-wrap" data-attrs="{&quot;id&quot;:117232398,&quot;url&quot;:&quot;https://www.roger.ml/p/jenkins-ci-github-app-authentication&quot;,&quot;publication_id&quot;:1334710,&quot;publication_name&quot;:&quot;roger.ml&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac38d071-6ef0-467a-89bb-8c3198f464ac_132x132.png&quot;,&quot;title&quot;:&quot;Authenticating with GitHub on Jenkins CI using a GitHub App&quot;,&quot;truncated_body_text&quot;:&quot;This guide is targeted to users who want to use a GitHub App to perform GitHub authenticated requests (such as cloning repos, pushing commits, opening PRs, listening to events, updating GitHub Checks, etc) from within Jenkins. Why a GitHub App? Why not a shared bot account?&quot;,&quot;date&quot;:&quot;2023-04-25T23:10:09.018Z&quot;,&quot;like_count&quot;:0,&quot;comment_count&quot;:0,&quot;bylines&quot;:[{&quot;id&quot;:69564182,&quot;name&quot;:&quot;Roger&quot;,&quot;previous_name&quot;:null,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/33f6af46-6af3-433e-9633-532785a0d229_3088x2316.jpeg&quot;,&quot;bio&quot;:&quot;Lead Mobile Engineer @tellushome. Core contributor of @FastlaneTools. Traveler @RemoteYear #swift #swiftlang&quot;,&quot;profile_set_up_at&quot;:&quot;2022-12-22T13:50:13.845Z&quot;,&quot;publicationUsers&quot;:[{&quot;id&quot;:1294612,&quot;user_id&quot;:69564182,&quot;publication_id&quot;:1334710,&quot;role&quot;:&quot;admin&quot;,&quot;public&quot;:true,&quot;is_primary&quot;:false,&quot;publication&quot;:{&quot;id&quot;:1334710,&quot;name&quot;:&quot;roger.ml&quot;,&quot;subdomain&quot;:&quot;rogeroba&quot;,&quot;custom_domain&quot;:&quot;www.roger.ml&quot;,&quot;custom_domain_optional&quot;:false,&quot;hero_text&quot;:&quot;Here I'll share tips and tricks about iOS development, Apple ecosystem, and other technology-related topics :)&quot;,&quot;logo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ac38d071-6ef0-467a-89bb-8c3198f464ac_132x132.png&quot;,&quot;author_id&quot;:69564182,&quot;theme_var_background_pop&quot;:&quot;#EA410B&quot;,&quot;created_at&quot;:&quot;2023-01-21T21:42:36.479Z&quot;,&quot;rss_website_url&quot;:null,&quot;email_from_name&quot;:&quot;&#128126; roger.ml&quot;,&quot;copyright&quot;:&quot;Roger Oba&quot;,&quot;founding_plan_name&quot;:&quot;Founding Member&quot;,&quot;community_enabled&quot;:true,&quot;invite_only&quot;:false,&quot;payments_state&quot;:&quot;enabled&quot;}}],&quot;twitter_screen_name&quot;:&quot;rogerluan_&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;utm_campaign&quot;:null,&quot;belowTheFold&quot;:true,&quot;type&quot;:&quot;newsletter&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="EmbeddedPostToDOM"><a class="embedded-post" native="true" href="https://www.roger.ml/p/jenkins-ci-github-app-authentication?utm_source=substack&amp;utm_campaign=post_embed&amp;utm_medium=web"><div class="embedded-post-header"><img class="embedded-post-publication-logo" src="https://substackcdn.com/image/fetch/$s_!ujSU!,w_56,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac38d071-6ef0-467a-89bb-8c3198f464ac_132x132.png" loading="lazy"><span class="embedded-post-publication-name">roger.ml</span></div><div class="embedded-post-title-wrapper"><div class="embedded-post-title">Authenticating with GitHub on Jenkins CI using a GitHub App</div></div><div class="embedded-post-body">This guide is targeted to users who want to use a GitHub App to perform GitHub authenticated requests (such as cloning repos, pushing commits, opening PRs, listening to events, updating GitHub Checks, etc) from within Jenkins. Why a GitHub App? Why not a shared bot account&#8230;</div><div class="embedded-post-cta-wrapper"><span class="embedded-post-cta">Read more</span></div><div class="embedded-post-meta">3 years ago &#183; Roger</div></a></div><h1>Configuring Jobs</h1><p>There is a race condition in how Jenkins picks up which branches to build when using different strategies to &#8220;discover&#8221; branches (i.e. &#8220;Exclude branches that are also filed as PRs&#8221; VS &#8220;Only branches that are also filed as PRs&#8221;). This causes problems like PRs being stuck in a forever &#8220;pending&#8221; state, thus blocking them from being merged. For this reason, we are going to need 2 Job pipelines: </p><ul><li><p>One will build all branches that aren&#8217;t intended to be filed as PRs, e.g. main, master, develop, staging, production (depends on how you call them).</p></li><li><p>Another to build all the other branches, that may become PRs.</p></li></ul><p>Took me a really long time to figure this out, as about 5-10% of the times the PRs would get stuck and become unable to get merged because they fell in some weird state. This was the only fully working solution I found to this problem.</p><h2>Configuring the Job that will build all branches that won&#8217;t become pull requests</h2><p>Go to <a href="https://example.com/view/all/newJob">https://&lt;your_jenkins_domain.com&gt;/view/all/newJob</a> to create a new job. Choose <strong>Multibranch Pipeline</strong> from the options:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!i-bm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!i-bm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png 424w, https://substackcdn.com/image/fetch/$s_!i-bm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png 848w, https://substackcdn.com/image/fetch/$s_!i-bm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png 1272w, https://substackcdn.com/image/fetch/$s_!i-bm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!i-bm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png" width="1456" height="1085" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1085,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:329616,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!i-bm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png 424w, https://substackcdn.com/image/fetch/$s_!i-bm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png 848w, https://substackcdn.com/image/fetch/$s_!i-bm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png 1272w, https://substackcdn.com/image/fetch/$s_!i-bm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a4a011-a606-4762-83b3-53ac575c9948_1940x1446.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p><em>I like appending the project type (in this case, Multibranch Pipeline) to the pipeline name, so I know which structure it uses, just by the name. Thus, in this case I&#8217;d name it something like </em><code>protected-branch-multibranch-pipeline</code><em> &#128522;</em></p></blockquote><p>When setting things up for this Job, these are the key settings you need to follow:</p><ul><li><p>GitHub Credentials: select the GitHub App Credentials you added in the <strong>&#8220;Configuring your git credentials&#8221;</strong> section above.</p></li><li><p>Add a new behavior<strong> &#8220;Discover branches&#8221;</strong> and select <strong>&#8220;All branches&#8221;</strong>.</p><ul><li><p>Add a <strong>&#8220;Filter by name (with regular expression)&#8221;</strong> filter under this section, with the text <code>&#8220;master&#8221; or &#8220;(master|staging|production)&#8221;</code></p></li></ul></li><li><p>Periodically if not otherwise run: &#9745;&#65039;</p><ul><li><p>Interval: 1 minute.</p></li><li><p>Honestly this one comes from a time where Jenkins wasn&#8217;t always picking up the builds it needed to run. But it was in the very beginning of the process, before I even use GitHub App, so I&#8217;m not sure whether this one can be turned off. Possibly.</p></li></ul></li></ul><p>All the remaining settings not mentioned above are up to you to decide how to fill (e.g. are specific to your project, or tastes).</p><div class="pullquote"><p>&#128075; Before continuing, a quick announcement!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QwrJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QwrJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QwrJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:91728,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!QwrJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Statused notifies your team automatically when your app changes status on App Store Connect or Google Play &#8212; submitted, approved, rejected &#8212; even pending contracts.</p><p>I built it as an indie dev to solve this exact pain. It&#8217;s like having the webhooks Apple &amp; Google never gave us.</p><p>&#8594; <a href="https://www.statused.com">Try Statused</a> &#8211; dev teams are loving it!</p></div><h2>Configuring the Job that will build pull requests</h2><p>Visit <a href="https://example.com/view/all/newJob">https://&lt;your_jenkins_domain.com&gt;/view/all/newJob</a> again to create a new job, and choose <strong>Multibranch Pipeline</strong> from the options. Again, I&#8217;ll recommend naming it something like <code>pull-requests-multibranch-pipeline</code>, so you can easily identify it in the future.</p><p>Follow the same steps as above, except the behavior you&#8217;re going to add is <strong>&#8220;Discover pull requests from origin&#8221;</strong>, selecting the strategy as <strong>&#8220;The current pull request revision&#8220;</strong>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uhOs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uhOs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png 424w, https://substackcdn.com/image/fetch/$s_!uhOs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png 848w, https://substackcdn.com/image/fetch/$s_!uhOs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png 1272w, https://substackcdn.com/image/fetch/$s_!uhOs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uhOs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png" width="1456" height="443" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:443,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63621,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uhOs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png 424w, https://substackcdn.com/image/fetch/$s_!uhOs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png 848w, https://substackcdn.com/image/fetch/$s_!uhOs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png 1272w, https://substackcdn.com/image/fetch/$s_!uhOs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F075641b9-4f5a-4db0-9956-7ee026582fa9_1946x592.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Configuring your Jenkinsfile</h1><p>You didn&#8217;t think I&#8217;d skip the most critical part, did you?</p><p>In the steps above, when configuring a new Job, you had to select the path of your Jenkinsfile. The way of setting up Jenkins explained in this article has to be matched with a particular way to configure your Jenkinsfile and your CI scripts, so I&#8217;ll cover them below.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Here&#8217;s a template you can use for the main Jenkinsfile, which can be used to run both <code>protected-branch-multibranch-pipeline and pull-requests-multibranch-pipeline:</code></p><pre><code>pipeline {
    agent any
    options {
        ansiColor('xterm') // Adds color to logs, enable via https://github.com/jenkinsci/ansicolor-plugin
        timeout(time: 8, unit: 'HOURS') // Set the timeout limit for builds
        disableConcurrentBuilds(abortPrevious: true) // Cancel the previous build upon pushing newer commits in the same branch
    }
    environment {
        // Set up all your secrets (aka credentials) here, e.g. API keys for fastlane, danger, etc.
        APP_STORE_CONNECT_API_KEY_ISSUER_ID = credentials('APP_STORE_CONNECT_API_KEY_ISSUER_ID')
        APP_STORE_CONNECT_API_KEY_KEY = credentials('APP_STORE_CONNECT_API_KEY_KEY')
        APP_STORE_CONNECT_API_KEY_KEY_ID = credentials('APP_STORE_CONNECT_API_KEY_KEY_ID')
        DANGER_GITHUB_API_TOKEN = credentials('DANGER_GITHUB_API_TOKEN')
        MATCH_PASSWORD = credentials('MATCH_PASSWORD')
        // &#8230;etc
    }
    stages {
        stage("1. Set Up") {
            steps {
                withCredentials([usernamePassword(credentialsId: '&lt;team_name&gt;_github_app', usernameVariable: 'GITHUB_APP_USERNAME_TOKEN', passwordVariable: 'GITHUB_APP_PASSWORD_TOKEN')]) {
                    sh '''
                    source ~/.zshrc # Needs to be run to set up the right PATH env var, initialize rbenv, and everything else we configured previously in the ~/.zshrc file

                    # Refs.: https://git-scm.com/docs/gitcredentials#_custom_helpers and https://stackoverflow.com/q/61146986/4075379
                    git config credential.username ${GITHUB_APP_USERNAME_TOKEN}
                    git config credential.helper "!echo password=${GITHUB_APP_PASSWORD_TOKEN}; echo"

                    # From now on you can add your scripts here, e.g. make, bundle install, pod install, xcodebuild build and test, etc.
                    make
                    '''
                }
            }
        }
        stage("2. Static Code Analysis") {
            steps {
                sh '''
                source ~/.zshrc # Yes, unfortunately you need to run this every time you declare a new "sh" shell script in your Jenkinsfile.
                bundle exec rake danger
                '''
            }
        }
        stage("3. Build &amp; Distribute") {
            steps {
                withCredentials([usernamePassword(credentialsId: '&lt;team_name&gt;_github_app', usernameVariable: 'GITHUB_APP_USERNAME_TOKEN', passwordVariable: 'GITHUB_APP_PASSWORD_TOKEN')]) {
                    sh '''
                    source ~/.zshrc
                    bundle exec fastlane archive_and_distribute # This action needs access to GITHUB_APP_USERNAME_TOKEN and GITHUB_APP_PASSWORD_TOKEN env vars directly
                    '''
                }
            }
        }
    }
    post {
        // Always clean your workspace after you finish using it, otherwise after a few builds you will end up with a full disk and your machine will likely die.
        always {
            cleanWs(cleanWhenAborted: true, cleanWhenFailure: true, cleanWhenNotBuilt: true, cleanWhenSuccess: true, cleanWhenUnstable: true, deleteDirs: true)
        }
    }
}</code></pre><h2>Explaining the &#8220;withCredentials&#8221; function</h2><p>The way the &#8220;<code>withCredentials&#8221; function works is as follows:</code></p><pre><code><code>// 1st parameter is the name of your GitHub App credentials ID, as you registered in Jenkins.
// 2nd and 3rd parameters are variable names you're declaring now, so you can name them whatever, but you will need to reference them later, so make them relatable.
withCredentials([usernamePassword(
    credentialsId: '&lt;team_name&gt;_github_app',
    usernameVariable: 'GITHUB_APP_USERNAME_TOKEN',
    passwordVariable: 'GITHUB_APP_PASSWORD_TOKEN'
)])</code></code></pre><p>The input is the Credentials ID that you registered earlier in your Jenkins credentials. That gives access to this Jenkins plugin to be able to generate ephemeral tokens (username and password) that can then be used to make HTTPS requests to GitHub. It&#8217;s critically important for you to understand that this is a HTTPS auth, and not SSH &#8212; so if you have git commands relying on SSH down the pipeline, you should identify that you&#8217;re running under a CI environment and switch them to use HTTPS instead. A common example for this, in an iOS environment, is the Git URL used by <em><a href="https://docs.fastlane.tools/actions/match/#match">fastlane match</a>.</em></p><p>The first time we&#8217;re generating such ephemeral credentials, we&#8217;re exposing them to <code>git config credential.helper</code>, so that any git operation (that uses HTTPS) after that moment (even in other Stages) will be able to run without asking for username and password again. In the example above, you can observe this on Stage 2, where we&#8217;re invoking <a href="https://danger.systems">danger</a> (which posts comments to GitHub, so it needs credentials), and we didn&#8217;t need to generate new credentials for it. Different from the Stage 3, where we need access to the credentials env vars directly, and the env vars don&#8217;t persist from one Stage to another, so we re-generate the credentials, thus, setting values to the env vars again.</p><p>If you&#8217;re wondering, here&#8217;s how you could configure the <em>fastlane</em> <em>match</em> action in your Fastfile:</p><pre><code>git_url = is_ci? ? "https://#{ENV["GITHUB_APP_USERNAME_TOKEN"]}:#{ENV["GITHUB_APP_PASSWORD_TOKEN"]}@github.com/myorg/myrepo.git" : "git@github.com:myorg/myrepo.git"

match(git_url: gir_url)</code></pre><blockquote><p><em>Note 1: you must have the <a href="https://plugins.jenkins.io/github-branch-source/">GitHub Branch Source plugin</a> on version </em><code>2.7.1</code><em> or above to use these APIs. This feature was introduced <a href="https://www.jenkins.io/blog/2020/04/16/github-app-authentication">and announced</a> in 2020.</em></p><p><em>Note 2: as the announcement states, the API token you get will only be valid for one hour. So don&#8217;t get it at the start of the pipeline and assume it will be valid all the way through.</em></p></blockquote><h1>Final touches</h1><p>There are two remaining things to do before you call it a day for your Jenkins setup journey.</p><h2>Builds are getting stuck forever</h2><p>This might happen when your pipeline tries to access git within the pipeline (e.g. when running pod install, or fetching SPMs, etc). It could be caused because your machine is asking for the Keychain password authentication, but that is not surfaced on the Jenkins logs. It&#8217;s unfortunate, but the only solution I found to be fully working in this case is to log on the machine with access to the user interface (e.g. VNC, not SSH), and clicking in &#8220;Always Allow" in the pop up that shows up asking for permission to access &#8220;login.keychain&#8221;. Enter the root password of the macOS machine when prompted.</p><p>You only need to do this once, then never again.</p><h2>Xcode git credentials clash with GitHub App&#8217;s credentials</h2><p>There&#8217;s an issue that causes builds to fail with error:</p><pre><code><code>stderr: remote: Invalid username or password</code></code></pre><p>This would happen in about 5-10% of the builds, breaking them. Turns out that Xcode&#8217;s git credentials may clash with the ones we&#8217;re setting and then it breaks. To fix this, simply follow my answer on this Stack Overflow question:</p><div class="pullquote"><p><a href="https://stackoverflow.com/questions/73154622/jenkins-github-authentication-fails-sometimes/73505061#73505061">Jenkins Github Authentication fails sometimes?</a></p></div><p>You will need to repeat those steps every time a new Xcode version is installed.</p><h1>Conclusion</h1><p>In this (admittedly) long guide, you learned a highly opinionated way to set up your Jenkins machine, tailored for an iOS environment. If you&#8217;re working with other environments, the majority of the tips shown in this article will help you get on the right track as well, such as for Android, React, Flutter, Node, etc. What will change is probably just your dependencies, and the specific examples I gave when setting up your Jenkinsfile.</p><p>When I initially set up my first Jenkins machine, I got my first build in a matter of hours, but it was far from an environment that would fit the team&#8217;s needs. GitHub Checks are definitely a must have for example (something that is not straightforward to set up), and the environment should be 100% stable, with no flakiness, otherwise the CI system won&#8217;t be trusted by your team. In other words, I got 80% of the work done very quickly, but the remaining 20% to get a polished CI pipeline took me literally weeks. I hope that by following this guide you won&#8217;t have to worry about the environment, and will be able to focus on what matters: building the perfect pipeline for your project, and your team &#129303;</p><p>In my next blog post, I&#8217;ll cover what you can do to preserve the hard work you did to set up your Jenkins machine. To put it simply: how to save a backup of your CI! Stay tuned.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[🔐 Authenticating with GitHub on Jenkins CI using a GitHub App]]></title><description><![CDATA[I spent over 90 hours figuring out which auth method works best. Save your time and check the right process here!]]></description><link>https://www.roger.ml/p/jenkins-ci-github-app-authentication</link><guid isPermaLink="false">https://www.roger.ml/p/jenkins-ci-github-app-authentication</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Tue, 25 Apr 2023 23:10:09 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/00d0634a-28e7-4e0a-ae19-2031ec6179b4_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!y96a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!y96a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!y96a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!y96a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!y96a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!y96a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:50392,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!y96a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!y96a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!y96a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!y96a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdd07a62e-a389-4b44-9464-8d8d6fccfbee_1200x630.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This guide is targeted to users who want to use a <a href="https://docs.github.com/en/rest/apps/apps">GitHub App</a> to perform GitHub authenticated requests (such as cloning repos, pushing commits, opening PRs, listening to events, updating GitHub Checks, etc) from within Jenkins.</p><h2><em>Why a GitHub App? Why not a shared bot account?</em></h2><ul><li><p>The <a href="https://docs.github.com/en/apps/creating-github-apps/setting-up-a-github-app/rate-limits-for-github-apps">Rate Limit</a> for a GitHub App scales with your organization size, whereas a user based token has a limit of 5000 requests per hour, regardless of how many repositories you have.</p></li><li><p>For organizations that enforce 2FA to be enabled, with GitHub Apps there&#8217;s no need to manage 2FA tokens for (potentially multiple) bot accounts.</p></li><li><p>To improve and tighten security: the Jenkins GitHub App that you will create requires a minimum, controlled set of privileges compared to a service user and its personal access token, which would require a much wider set of privileges.</p></li><li><p>Access to GitHub Checks API: GitHub Apps can access the the <a href="https://developer.github.com/v3/checks/">GitHub Checks API</a> to create check runs and check suites from Jenkins jobs and provide detailed feedback on commits as well as code annotation.</p></li></ul><h2><em>Why not an SSH key?</em></h2><ul><li><p>It&#8217;d <em>also</em> require you to have a bot account with shared credentials across the team.</p></li><li><p>Share the same limitations of username + password authentication explained above.</p></li><li><p>From experience, Jenkins doesn&#8217;t play well with SSH keys, and plugins that work with it are quite buggy.</p></li></ul><h2><em>Why should I trust you?</em></h2><p>I spent over 90 hours on trial-and-error process to figure out which authentication method works best, amongst <strong>username + password</strong>, <strong>personal access tokens</strong>, <strong>SSH keys</strong>, and <strong>GitHub App</strong>. If you&#8217;d like to figure out on your own all the reasons why to not use any other auth other than GitHub App, you&#8217;re in for a treat! &#128521;</p><h2>Getting started</h2><p>Before you get started make sure you have the required permissions:</p><h3>GitHub</h3><p>You'll need the permission to create a GitHub App.</p><p>If you're creating it on a personal account, then you can skip this requirement.</p><p>If you&#8217;re creating it in a organization, you need to be either the organization owner, or have been granted the <a href="https://help.github.com/en/github/setting-up-and-managing-organizations-and-teams/adding-github-app-managers-in-your-organization">permission to manage GitHub Apps</a>.</p><h3>Jenkins</h3><p>You'll need the permission to create a new credential and update a job&#8217;s configuration. The specific permissions are:</p><ul><li><p>Credentials/Create</p></li><li><p>Job/Configure</p></li></ul><h2>Creating the GitHub App</h2><p>Follow the <a href="https://docs.github.com/en/developers/apps/creating-a-github-app">official GitHub guide for creating an app</a>.</p><p>The only fields you need to fill out (currently) are:</p><ul><li><p>Github App name, e.g.: <code>Jenkins - &lt;team name&gt;</code></p></li><li><p>Homepage URL: your company&#8217;s website or a GitHub repository.</p></li><li><p>Webhook URL: your Jenkins instance, e.g. <code>https://&lt;jenkins-host&gt;/github-webhook/</code></p></li></ul><p>When setting up the required permissions for your GitHub App for a given repository, set the following permissions:</p><ul><li><p>Administration: <code>read-only.</code></p></li></ul><ul><li><p>Checks: <code>read &amp; write</code> (so it can update the GitHub Checks in your pull requests).</p></li></ul><ul><li><p>Contents: <code>read-only</code> (to read the Jenkinsfile and the repository content during git fetch).</p></li></ul><ul><li><p>Metadata: <code>read-only.</code></p></li></ul><ul><li><p>Pull requests: <code>read-only</code> (or <code>read &amp; write</code> if you intend to open pull requests using the bot at some point).</p></li></ul><ul><li><p>(Optional) Webhooks: If you want the plugin to manage webhooks for you, then select <code>read &amp; write</code>.</p></li></ul><ul><li><p>Commit statuses: <code>read &amp; write</code>.</p></li></ul><p>Under<em> </em>&#8220;Subscribe to events&#8221;, subscribe to all events, and proceed to creating the GitHub App.</p><h2>Authenticate to the GitHub App</h2><p>After creating the GitHub App, you will need to generate a private key to authenticate to the GitHub App. Simply click in &#8220;Generate a Private Key&#8221; button.</p><h3>Convert the private key for Jenkins</h3><p>After you have generating and downloading the private key, you&#8217;ll need to convert it into a different format that Jenkins can use with the following command:</p><pre><code>openssl pkcs8 -topk8 -inform PEM -outform PEM -in key-in-your-downloads-folder.pem -out converted-github-app.pem -nocrypt</code></pre><div class="pullquote"><p>&#128075; Before continuing, a quick announcement!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QwrJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QwrJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QwrJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:91728,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!QwrJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!QwrJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbab77fa8-a08b-4887-836b-d8badbf11182_1200x630.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Statused notifies your team automatically when your app changes status on App Store Connect or Google Play &#8212; submitted, approved, rejected &#8212; even pending contracts.</p><p>I built it as an indie dev to solve this exact pain. It&#8217;s like having the webhooks Apple &amp; Google never gave us.</p><p>&#8594; <a href="https://www.statused.com">Try Statused</a> &#8211; dev teams are loving it!</p></div><h2>Install the GitHub App to your organization</h2><p>From the install app section of the newly created app, install the app to your organization. From here you can install the app on all repositories of your organization or in select repositories only. Once installed, you will have configuration options for the app on your selected account.</p><h2>Add the Jenkins credential</h2><p>You can add the Jenkins credential via the UI, or using the <a href="https://github.com/jenkinsci/configuration-as-code-plugin">Jenkins Configuration as Code (a.k.a. JCasC) Plugin</a>.</p><h3>Via UI</h3><ul><li><p>Navigate to the Add Credentials page in your Jenkins instance</p></li><li><p>Fill out the form as follows:</p><ul><li><p>Kind: GitHub App.</p></li><li><p>ID: give an identifiable name to this credential (you will use this in your Jenkinsfile to load this credential). Example: &#8220;&lt;team_name&gt;_github_app&#8221;.</p></li><li><p>App ID: the GitHub App ID, which can be found in the About section of your GitHub app, under the General tab.</p></li><li><p>API Endpoint (optional): only required for GitHub Enterprise.</p></li><li><p>Key: paste the contents of the converted private key from the previous section.</p></li><li><p>Owned (optional): if you've installed your same GitHub app on multiple organizations you&#8217;ll need to specify the name of the organization or user this app is going to be used on, e.g. &#8220;exampleorganization&#8221; for <code>https://github.com/exampleorganization</code></p></li></ul></li><li><p>Click &#8220;Create&#8221;</p></li></ul><h3>Via <a href="https://github.com/jenkinsci/configuration-as-code-plugin">Jenkins Configuration as Code (a.k.a. JCasC) Plugin</a></h3><pre><code>credentials:
  system:
    domainCredentials:
      - credentials:
        - gitHubApp:
            appID: "1111"
            description: "GitHub app"
            id: "github-app"
            # apiUri: https://my-custom-github-enterprise.com/api/v3 # optional only required for GitHub enterprise
            privateKey: "${GITHUB_APP_KEY}"</code></pre><h1>Conclusion</h1><p>After following the steps in this tutorial, you should have a fully working GitHub App that can be used to access your repositories and perform actions on GitHub, such as cloning repos, pushing commits, creating PRs, listening to events, updating GitHub Checks in PRs, etc. In an upcoming tutorial I&#8217;ll cover how to use properly use this GitHub App credentials to set up the remaining of your Jenkins pipeline.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">roger.ml is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[💻 How to install Swift 5.8 in a Debian Distro]]></title><description><![CDATA[The official Swift.org Get Started page doesn't show how to install Swift in unsupported distros, like Debian. This guide will show how easy it is!]]></description><link>https://www.roger.ml/p/how-to-install-swift-58-in-a-debian</link><guid isPermaLink="false">https://www.roger.ml/p/how-to-install-swift-58-in-a-debian</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Sat, 08 Apr 2023 15:45:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!paIV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!paIV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!paIV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png 424w, https://substackcdn.com/image/fetch/$s_!paIV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png 848w, https://substackcdn.com/image/fetch/$s_!paIV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png 1272w, https://substackcdn.com/image/fetch/$s_!paIV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!paIV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png" width="1200" height="620" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:620,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37860,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!paIV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png 424w, https://substackcdn.com/image/fetch/$s_!paIV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png 848w, https://substackcdn.com/image/fetch/$s_!paIV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png 1272w, https://substackcdn.com/image/fetch/$s_!paIV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff22fcf9f-1887-4880-afa6-56aca94a636b_1200x620.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Today&#8217;s how-to guide will be quite short and simple. If you&#8217;re here, chances are you&#8217;re running a Debian distro and struggled to find Swift installation steps in <a href="https://www.swift.org/getting-started/">Swift&#8217;s Get Started</a> official page. Follow the steps below (tested only in Debian 11) to install Swift and be able to run Swift code in your distro:</p><ol><li><p>Update your packages:</p><pre><code>sudo apt update</code></pre></li><li><p>Install required dependencies:</p><pre><code>sudo apt-get install libncurses5 clang libcurl4 libpython2.7 libpython2.7-dev</code></pre><p>When being asked if you want to continue, press <strong>Y</strong> and <strong>Enter</strong>.<br></p></li><li><p>Download Swift:</p><ol><li><p>Find the Swift version you want to install in <a href="https://www.swift.org/download">Swift.org&#8217;s Official Download page</a>. At the time of writing, the latest version available was Swift 5.8, so I went with that.</p></li><li><p>Right click on x86_64 for the row Ubuntu 18.04 and copy the link. In this case, this is what I got: https://download.swift.org/swift-5.8-release/ubuntu1804/swift-5.8-RELEASE/swift-5.8-RELEASE-ubuntu18.04.tar.gz</p></li><li><p>Download the tar.gz in your Debian distro:</p><pre><code>wget https://download.swift.org/swift-5.8-release/ubuntu1804/swift-5.8-RELEASE/swift-5.8-RELEASE-ubuntu18.04.tar.gz</code></pre></li></ol></li><li><p>Extract the downloaded package:</p><pre><code>tar xzf swift-5.8-RELEASE-ubuntu18.04.tar.gz</code></pre></li></ol><ol start="5"><li><p>Move Swift&#8217;s binary to /opt/</p><pre><code>sudo mv swift-5.8-RELEASE-ubuntu18.04 /opt/swift/</code></pre></li></ol><ol start="6"><li><p>Configure your PATH environment variable:</p><pre><code>echo "export PATH=/opt/swift/usr/bin:$PATH" &gt;&gt; ~/.bashrc</code></pre><p>This will modify your ~/.bashrc file to include the path of the Swift executable you just installed.<br><br>Apply your changes in your current shell session via:</p><pre><code>source ~/.bashrc</code></pre></li></ol><ol start="7"><li><p>Verify if your installation succeeded:</p><pre><code>swift -version</code></pre><p>You should see something like this:</p><pre><code>Swift version 5.8 (swift-5.8-RELEASE)
Target: x86_64-unknown-linux-gnu</code></pre></li></ol><h1>Conclusion</h1><p>Installing Swift on a Debian server may seem daunting at first, but by following the step-by-step article, you can easily set up a functional Swift environment in no time! &#128521; </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading roger.ml! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[🦸 The ultimate guide to becoming a Slack power user]]></title><description><![CDATA[Tired of missing information in Slack? Forgot to reply to something? Can't find where a message is? Don't miss threads and mentions. You'll feel great, and your team will thank you!]]></description><link>https://www.roger.ml/p/slack-super-user</link><guid isPermaLink="false">https://www.roger.ml/p/slack-super-user</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Sat, 11 Mar 2023 14:28:05 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c50415a0-d99a-49e4-b1c0-634f6cb0df63_768x512.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article has a few goals:</p><ul><li><p>&#127947;&#65039;&#8205;&#9794;&#65039; Make it nearly impossible to miss anything important in Slack</p></li><li><p>&#129470; Help make you accountable for everything that goes on in Slack</p></li><li><p>&#127919; Improve your productivity</p></li></ul><p>When combined, this will make you become a&#8230;  Slack power user! &#129464;&#8205;&#9794;&#65039;</p><p>This article is divided in 8 main tricks:</p><ol><li><p>&#129302; Use Slackbot&#8217;s reminders</p></li><li><p>&#128483;&#65039; Chat with yourself</p></li><li><p>&#128236; Mark messages as unread</p></li><li><p>&#127919; Don't multitask</p></li><li><p>&#129529; Clean up your side bar</p></li><li><p>&#128269; Search</p></li><li><p>&#128277; Limit your surface area</p></li><li><p>&#9000;&#65039; Use your keyboard</p></li></ol><h2><strong>1. </strong>&#129302; Use Slackbot&#8217;s reminders</h2><p>Slackbot is your friend, and friends remind you to do things. In my opinion, Slack's ability to set reminders to yourself is the best tool to avoid dropping messages unanswered.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s71L!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s71L!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png 424w, https://substackcdn.com/image/fetch/$s_!s71L!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png 848w, https://substackcdn.com/image/fetch/$s_!s71L!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png 1272w, https://substackcdn.com/image/fetch/$s_!s71L!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s71L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png" width="398" height="265.3333333333333" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:772,&quot;width&quot;:1158,&quot;resizeWidth&quot;:398,&quot;bytes&quot;:131548,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s71L!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png 424w, https://substackcdn.com/image/fetch/$s_!s71L!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png 848w, https://substackcdn.com/image/fetch/$s_!s71L!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png 1272w, https://substackcdn.com/image/fetch/$s_!s71L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec9e5d65-5fa5-4d05-ad65-5ae762bb7419_1158x772.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Menu that shows how to ask Slackbot to remind you of a specific message.</figcaption></figure></div><p><strong>When to use:</strong> when a conversation isn&#8217;t quite done. There are 3 scenarios where a conversation isn&#8217;t over yet:</p><ol><li><p>You didn&#8217;t fully read it yet, so you don&#8217;t really know. Set reminders on messages so later you can continue reading where you left off. &#8220;I think this is important to be read but I don&#8217;t have the time now and I want to move on&#8221; type of message.</p><ul><li><p>It&#8217;s ok if you don&#8217;t feel like reading a long wall of text now, or the topic is not critical. But you don&#8217;t want to forget about getting back to that message, so set yourself a reminder about it and move on with your normal duties until you&#8217;re ready to read it (say, tomorrow).</p></li></ul></li><li><p>The conversation requires someone&#8217;s (your or other people&#8217;s) <strong>action</strong> to be taken, but this action is not the final step yet. Example: &#8220;I&#8217;ll investigate and report back.&#8221;</p><ul><li><p>In these cases, creating a Jira ticket is probably an overkill, but following up, say, a week later, is worth while, to make sure we don&#8217;t drop it. This is the one of the most common reasons tasks are forgotten if no one keeps track of them, because it&#8217;s not tracked anywhere else except in Slack.</p></li><li><p>If the pending action is the task itself (e.g. &#8220;I&#8217;ll fix this later&#8221;), it&#8217;s probably worth it to simply track it in your ticket-tracking system (e.g. Jira) instead of in a Slack reminder. This allows you to give closure to the Slack thread, as it already served its purpose (the discussion), and use a ticketing system to track&#8230; you guessed it: tickets.</p></li></ul></li><li><p>You&#8217;re waiting for someone to respond (you, or other people).</p><ul><li><p>Here I would most likely not schedule a reminder, unless it&#8217;s a hyper critical topic that can&#8217;t be dropped. I tend to avoid scheduling reminders for every conversation to avoid being overwhelmed by reminders that would just automatically get resolved. Instead, for low priority conversations, I trust that other team members will carry the conversation and I&#8217;ll be notified when that happens. When there&#8217;s a conclusion, we can act on it.</p></li></ul></li></ol><h3>What should I do with the Slack reminders once Slackbot gets back to me with them?</h3><p>Go through them, one by one, and check what&#8217;s the status of that task. You can choose to take 1 of 3 actions: mark it as completed, snooze it, or delete it. See this simple decision tree:</p><ul><li><p>Was the topic (or the pending action) resolved?</p><ul><li><p>Yes: great! Mark it as completed, and move on to the next reminder.</p></li><li><p>No: can you do it yourself?</p><ul><li><p>Yes: wanna do it now?</p><ul><li><p>Yes: go do it right now, no distractions. Come back to this reminder when you&#8217;re done. If you prefer, you can snooze it to when you think you&#8217;ll finish it (e.g. 20mins or 1h from now).</p></li><li><p>No: then snooze the reminder to be reminded about it again in the future, and move on to the next reminder.</p></li></ul></li><li><p>No: follow up with whoever should be responsible for getting it done. Then snooze the reminder to be reminded about it again in the near future, and move on to the next reminder.</p></li></ul></li></ul></li></ul><p>I rarely delete a reminder. When I do, it basically means &#8220;won&#8217;t do&#8221;: the topic is no longer relevant, or I don&#8217;t intend to do it anymore, for whatever reason.</p><h2><strong>2. </strong>&#128483;&#65039; <strong>Chat with yourself</strong></h2><p>It may sound a bit weird, but one of the most underrated features in Slack is the ability to chat with yourself. It's a private DM like any other, but with yourself and no one else. Messages sent there are editable forever, regardless of your Slack organization's message editing policies. They are also kept forever (according to your Slack&#8217;s retention plan), and indexed in searches.</p><p>According to Slack itself:</p><blockquote><p><em>This is your space. Draft messages, list your to-dos, or keep links and files handy. You can also talk to yourself here, but please bear in mind you&#8217;ll have to supply both sides of the conversation.</em></p></blockquote><p>I use this chat to:</p><ul><li><p>Take notes;</p></li><li><p>Journal;</p></li><li><p>Save anything that is not important enough to be permanently documented somewhere public (e.g. a wiki, Confluence, Notion), but at the same time I don't feel like deleting the information;</p></li><li><p>Paste Slack links from Shared Organizations. If you simply click them from a browser, they won't open;</p></li><li><p>Preview how messages will look before sending to a broader audience, specially regarding formatting. Since they're editable forever, I can just keep editing, saving, editing again, etc, until I'm happy with it;</p></li><li><p>&#8230; and anything else related to work that should remain private and I wanted to type somewhere.</p></li></ul><p>I probably overuse this chat, but it does help me keep everything organized, centralized, and within a single search scope: Slack. Some people prefer Notion, Google Docs, Notes, a physical notebook, text files in your computer. I usually replace all those with this chat.</p><p>This feature is so popular that it&#8217;s also available in WhatsApp and Telegram. In Telegram, it evolved into a new section in the app called "Saved Messages".</p><h2>3. &#128236; Mark messages as unread</h2><p>If you accidentally open a chat that you weren&#8217;t ready to read yet, you can mark it as unread:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9OW6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9OW6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png 424w, https://substackcdn.com/image/fetch/$s_!9OW6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png 848w, https://substackcdn.com/image/fetch/$s_!9OW6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png 1272w, https://substackcdn.com/image/fetch/$s_!9OW6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9OW6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png" width="386" height="269.62388059701493" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:702,&quot;width&quot;:1005,&quot;resizeWidth&quot;:386,&quot;bytes&quot;:133475,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9OW6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png 424w, https://substackcdn.com/image/fetch/$s_!9OW6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png 848w, https://substackcdn.com/image/fetch/$s_!9OW6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png 1272w, https://substackcdn.com/image/fetch/$s_!9OW6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9371d4c-df64-4bc7-b959-14a9389102ad_1005x702.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Select a message&#8217;s menu and click &#8220;Mark unread&#8221; to mark the channel as unread, starting from the message you selected.</figcaption></figure></div><p>This prevents you from forgetting to read that chat, even if you can&#8217;t read it right now (since the chat is automatically marked as read once you open it). You could also set a reminder too, but I prefer setting it as unread so that I know I shouldn&#8217;t continue engaging with that channel until I&#8217;ve read all the unread messages first.</p><h2>4. &#127919; Don't multitask</h2><p>Probably the top 1 reason why busy people (like executives) often misses messages &#8212; especially in threads &#8212; is because once they click a chat (e.g. a channel, a DM, a thread, or even the &#8220;Threads&#8221; view), all the messages in that chat are marked as read instantly. This is not a problem on its own, except if you get distracted and move away from that chat before you finish reading all the messages you just marked as read, you will most likely forget to go back to it. So, rule of thumb: don&#8217;t multitask while you&#8217;re catching up with your Slack channels. Instead, read all messages in a given chat in one go. This keeps you from getting distracted and changing between chats. If you ultimately need to change chats before finish reading the current one, mark the current one as unread at the last unread message (see previous section).</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">If you&#8217;re enjoying this article so far, consider subscribing for free to receive new posts and support my work :)</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>5. &#129529; Clean up your side bar</h2><p>This is just a tip to get your workspace a bit more organized, probably. The way I like to view my Slack side bar is not the default one that Slack gives you. Instead, I customize it like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oAWr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oAWr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png 424w, https://substackcdn.com/image/fetch/$s_!oAWr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png 848w, https://substackcdn.com/image/fetch/$s_!oAWr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!oAWr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oAWr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png" width="502" height="686.4957264957264" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1600,&quot;width&quot;:1170,&quot;resizeWidth&quot;:502,&quot;bytes&quot;:135097,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oAWr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png 424w, https://substackcdn.com/image/fetch/$s_!oAWr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png 848w, https://substackcdn.com/image/fetch/$s_!oAWr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!oAWr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6265c496-7c85-416b-b230-dcf158ceecc5_1170x1600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">My Slack sidebar settings. Most important item is the &#8220;Show&#8230; Custom, depending on the section&#8221; setting, which will show only Starred chats.</figcaption></figure></div><p>This results in a clean side menu with only channels that you explicitly star. I star people I chat with often and end up with a side bar that serves the purpose of showing whether that person is available or not, and nothing else:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ef7R!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d9f233a-5d34-4312-9284-7f0995178760_456x990.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ef7R!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d9f233a-5d34-4312-9284-7f0995178760_456x990.png 424w, https://substackcdn.com/image/fetch/$s_!Ef7R!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d9f233a-5d34-4312-9284-7f0995178760_456x990.png 848w, https://substackcdn.com/image/fetch/$s_!Ef7R!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d9f233a-5d34-4312-9284-7f0995178760_456x990.png 1272w, https://substackcdn.com/image/fetch/$s_!Ef7R!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d9f233a-5d34-4312-9284-7f0995178760_456x990.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ef7R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d9f233a-5d34-4312-9284-7f0995178760_456x990.png" width="208" height="451.57894736842104" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7d9f233a-5d34-4312-9284-7f0995178760_456x990.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:990,&quot;width&quot;:456,&quot;resizeWidth&quot;:208,&quot;bytes&quot;:85730,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ef7R!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d9f233a-5d34-4312-9284-7f0995178760_456x990.png 424w, https://substackcdn.com/image/fetch/$s_!Ef7R!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d9f233a-5d34-4312-9284-7f0995178760_456x990.png 848w, https://substackcdn.com/image/fetch/$s_!Ef7R!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d9f233a-5d34-4312-9284-7f0995178760_456x990.png 1272w, https://substackcdn.com/image/fetch/$s_!Ef7R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d9f233a-5d34-4312-9284-7f0995178760_456x990.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Real footage of my Slack sidebar.</figcaption></figure></div><p>If a chat receives a new message, it shows up in its respective section (e.g. Channels, Direct Messages). Once I read it, it&#8217;s gone from the menu. If I need to revisit it or open any channel, I press &#8984;K (you&#8217;ll read more about keyboard shortcuts in the last section of this article &#128522;).</p><div class="pullquote"><p>A clean side bar is a peaceful, distraction-free workplace.</p></div><h2>6. &#128269; Search</h2><p>If you feel like Slack is a black hole, you can&#8217;t find anything in there, can&#8217;t remember which channel a message you read was posted &#8212; you&#8217;re not alone. If that&#8217;s your case, I strongly suggest using the its search engine! It&#8217;s powerful, fast, and reliable. You can use lots of different filters:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pnjE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pnjE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png 424w, https://substackcdn.com/image/fetch/$s_!pnjE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png 848w, https://substackcdn.com/image/fetch/$s_!pnjE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png 1272w, https://substackcdn.com/image/fetch/$s_!pnjE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pnjE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png" width="1456" height="237" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:237,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51901,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pnjE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png 424w, https://substackcdn.com/image/fetch/$s_!pnjE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png 848w, https://substackcdn.com/image/fetch/$s_!pnjE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png 1272w, https://substackcdn.com/image/fetch/$s_!pnjE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6cb605-28c3-45ac-9b8a-0300de3ec884_1636x266.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Or use special search syntax if you want to speed up the process even more &#128526; Check out the full documentation here: <a href="https://slack.com/help/articles/202528808-Search-in-Slack">Search in Slack</a></p><h2>7. &#128277; Limit your surface area</h2><p>A huge complaint from Slack users is either &#8220;there are too many channels&#8221; or &#8220;I get too many notifications every day&#8221;. To address these, ask yourself:</p><ul><li><p>Do I need to be instantly notified of messages in channel X?</p><ul><li><p>If not, how about muting the channel? Slack offers lots of tools to limit the notifications you receive, on a per-channel basis:</p></li></ul></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vGfe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbce6897a-711f-493c-98e9-505556fc8d41_512x708.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vGfe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbce6897a-711f-493c-98e9-505556fc8d41_512x708.png 424w, https://substackcdn.com/image/fetch/$s_!vGfe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbce6897a-711f-493c-98e9-505556fc8d41_512x708.png 848w, https://substackcdn.com/image/fetch/$s_!vGfe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbce6897a-711f-493c-98e9-505556fc8d41_512x708.png 1272w, https://substackcdn.com/image/fetch/$s_!vGfe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbce6897a-711f-493c-98e9-505556fc8d41_512x708.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vGfe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbce6897a-711f-493c-98e9-505556fc8d41_512x708.png" width="228" height="315.28125" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bce6897a-711f-493c-98e9-505556fc8d41_512x708.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:708,&quot;width&quot;:512,&quot;resizeWidth&quot;:228,&quot;bytes&quot;:78742,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vGfe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbce6897a-711f-493c-98e9-505556fc8d41_512x708.png 424w, https://substackcdn.com/image/fetch/$s_!vGfe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbce6897a-711f-493c-98e9-505556fc8d41_512x708.png 848w, https://substackcdn.com/image/fetch/$s_!vGfe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbce6897a-711f-493c-98e9-505556fc8d41_512x708.png 1272w, https://substackcdn.com/image/fetch/$s_!vGfe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbce6897a-711f-493c-98e9-505556fc8d41_512x708.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p>Do I need read any message at all in channel X?</p><ul><li><p>If not, have you considered leaving the channel? You most likely don&#8217;t need to be part of it. If someone need your attention in that channel in the future, they will add you back in (Slack cleverly prompts the person to invite you back when needed).</p></li></ul></li></ul><p>Customizing notification settings on a per-channel basis, and leaving superfluous noisy channels help reduce the notifications you receive significantly, and also keep your &#8220;pool of channels&#8221; smaller. After all, it shouldn&#8217;t matter if your Slack organization have thousands of channels, if you&#8217;re only in 5 of them :)</p><h2>8. &#9000;&#65039; Use your keyboard</h2><p>When using Slack on desktop, become a power user by using keyboard shortcuts! The more you know and use them, the more productive you&#8217;ll be!</p><p>You can find a list of all the Slack keyboard shortcuts <a href="https://slack.com/help/articles/201374536-Slack-keyboard-shortcuts">here</a>, but these are my favorites:</p><ul><li><p>Jump to a conversation: &#8984;K</p><ul><li><p>This one is the most efficient shortcut I use. I use this to start new chats, to jump to channels, to open the chat with myself, etc. Even if you see the chat you want to open in your Slack sidebar, don&#8217;t use your mouse to click it &#8212; use your keyboard, type out the shortest string that would match that chat and hit enter to instantly open it. It&#8217;s multiple times faster than clicking it (specially if you&#8217;re a fast typer &#128521;).</p></li></ul></li><li><p>Navigate to previous/next conversation in navigation history by using the same trackpad shortcut you use in your browser go navigate back/forward. For instance, on my MacBook trackpad I swipe with 3 fingers left and right.</p></li><li><p>Edit your previous message: &#11014; (arrow up) while the chat&#8217;s text box is selected. Note that this must be enabled in your Slack preferences, otherwise this same shortcut will simply highlight the previous message in the chat.</p></li><li><p>Navigate up/down in the chat menu to select a different chat: option up/down. This one is not even in the Slack&#8217;s link above &#128519;</p></li><li><p>If you have multiple workspaces in your desktop app: switch to a specific workspace via &#8984;[number], e.g. &#8984;2, &#8984;3, &#8984;4, etc.</p></li></ul><h1><strong>Day-to-day operation</strong></h1><p>This is where I would explain how I operate Slack on a daily basis, but since this article is already too long, I decided to publish this in a separate article :)</p><p>Make sure you subscribe so you don&#8217;t miss it when it goes out!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.roger.ml/subscribe?"><span>Subscribe now</span></a></p><h1>Conclusion</h1><p>Following the tips and tricks shown in this article, you&#8217;re guaranteed to become more productive, miss less information when using Slack, and receive way less notifications too, contributing to a more serene Slack workplace. I hope you apply these tips to your daily life &#129303; </p><p>Feel free to comment below if you do any of the tricks listed in this article, or if any of them helped you :) I&#8217;d love to hear your stories!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/p/slack-super-user/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.roger.ml/p/slack-super-user/comments"><span>Leave a comment</span></a></p>]]></content:encoded></item><item><title><![CDATA[I Spent The Last 2 Months "Away & DND" on Work’s Slack]]></title><description><![CDATA[And my two goals were achieved. Will you want to make this change too?]]></description><link>https://www.roger.ml/p/social-experiment-spent-two-months-dnd-on-slack</link><guid isPermaLink="false">https://www.roger.ml/p/social-experiment-spent-two-months-dnd-on-slack</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Wed, 08 Mar 2023 16:35:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h4>And my two goals were achieved.</h4><p>Feeling overwhelmed and unproductive, I decided to change my status to &#8220;Away&#8221; and enable &#8220;Do Not Disturb&#8221; on my work&#8217;s Slack workspace.</p><p>After 2 months running this social experiment, here&#8217;s what I observed (in no particular order):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!U3ln!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U3ln!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png 424w, https://substackcdn.com/image/fetch/$s_!U3ln!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png 848w, https://substackcdn.com/image/fetch/$s_!U3ln!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png 1272w, https://substackcdn.com/image/fetch/$s_!U3ln!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U3ln!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png" width="400" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!U3ln!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png 424w, https://substackcdn.com/image/fetch/$s_!U3ln!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png 848w, https://substackcdn.com/image/fetch/$s_!U3ln!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png 1272w, https://substackcdn.com/image/fetch/$s_!U3ln!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8968bd4a-9239-4726-a9f1-8475fddd24ed_400x400.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@visuals?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">visuals</a> on&nbsp;<a href="https://unsplash.com/s/photos/do-not-disturb?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure></div><ul><li><p>Direct calls were made impossible, due to Slack&#8217;s DND policies. This obviously reduced unsolicited and unscheduled calls at random hours.</p></li><li><p>The amount of tagging and DMs reduced considerably.</p></li><li><p>Despite the number of tags and DMs received, the average number of messages sent (by me) didn&#8217;t change.</p></li></ul><blockquote><p>Something to consider is that I don&#8217;t have fixed working hours&#8202;&#8212;&#8202;sometimes you&#8217;ll find me working PST (GMT-8), sometimes CST (GMT+8), sometimes none of those, or something in-between. So letting people know when I&#8217;m actually &#8220;online&#8221; makes a difference, as opposed to someone that works 9&#8211;5 every day, for instance.</p></blockquote><h4>What changed after some&nbsp;time:</h4><ul><li><p>Some people noticed. They asked why I changed my status on Slack. Multiple times.</p></li><li><p>People adapted to my new normal, and started sending DMs regardless of my status. (This could be just a perception, though)</p></li><li><p>Other people adopted the same behavior and switched their statuses to "Away" permanently.</p></li><li><p>Some people that had access to other means of contact started using them to reach out to me when needed (such as Facebook Messenger and Telegram).</p></li></ul><h4>What didn&#8217;t&nbsp;change:</h4><ul><li><p>Emergency calls still took place. Sometimes I&#8217;d initiate them when needed, sometimes a group call would be posted in a public channel.</p></li><li><p>Scheduled calls still took place.</p></li><li><p>No work emails were received.</p></li><li><p>I still don&#8217;t get disturbed via my phone because my phone itself was on DND before and during the experiment. More about this can be explained in a separate post.</p></li></ul><h3>Goals</h3><p>I had two goals with this experiment, and I can say they were achieved.</p><h4>Eradicated unsolicited calls at random&nbsp;hours</h4><p>Calls and other means of synchronous communication is one of productivity&#8217;s worst enemies and should be avoided whenever <em>possible</em>.</p><h4>Reduced the number of&nbsp;DMs</h4><p>Direct messages are often used as a synchronous mean of communication (in which the parties often expect replies in a timely manner). They also centralize information somewhere the rest of the team can&#8217;t access. When DMs are discouraged, the sender will see themselves posting the message in channels (ideally public ones), which is better for a number of reasons. For instance, if the intended receiver of the message can&#8217;t answer at that time, someone might pick up the discussion sooner, and if this conversation needs to be referenced later, it can be.</p><p>DM is a good way to catch up on a personal level. Discuss things unrelated to work, things that don&#8217;t concern other colleagues, or use it as a synchronous mean of communication when you can&#8217;t be bothered to call.</p><h3>Conclusion</h3><p>It was an interesting social experiment to run. I could&#8217;ve asked people to schedule calls ahead of time, or asked them to post in public channels instead of sending me DMs, but that wouldn&#8217;t be as fun as being able to document this experiment&nbsp;;)</p><p>I&#8217;d say that it was more delightful to work with a little bit of extra calmness, but I&#8217;m ready to enable Slack notifications and show my online status back again. Things may be different now, from 2 months ago. We&#8217;ll see.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading roger.ml! Be notified when I write more stories on my productivity journey using Slack &#8212; subscribe below! :)</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="pullquote"><p>Disclaimer: this social experiment took place in late 2020, which is when I initially wrote this story&#8217;s draft.</p></div>]]></content:encoded></item><item><title><![CDATA[Hello, yet-another-blogging-platform!]]></title><description><![CDATA[This marks my first post written in this platform called Substack. I'll share what made me migrate again to a different platform.]]></description><link>https://www.roger.ml/p/coming-soon</link><guid isPermaLink="false">https://www.roger.ml/p/coming-soon</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Mon, 23 Jan 2023 02:33:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!FoXy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FoXy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FoXy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg 424w, https://substackcdn.com/image/fetch/$s_!FoXy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg 848w, https://substackcdn.com/image/fetch/$s_!FoXy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!FoXy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FoXy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:716452,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FoXy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg 424w, https://substackcdn.com/image/fetch/$s_!FoXy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg 848w, https://substackcdn.com/image/fetch/$s_!FoXy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!FoXy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45e96c8c-6054-4db7-91ae-5ca105330d90_3876x2584.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>My blogging journey started with <a href="https://medium.com">Medium</a>, back in 2015. I was living in Japan back then and wrote an article reflecting on whether I should move back to Brazil.</p><p>Since then, I&#8217;ve been blogging occasionally (a few times a year only). I usually write either to express something I&#8217;m thinking, or to share knowledge about something I learned recently that I couldn&#8217;t find useful information online &#8212; often related to iOS development or other engineering topics.</p><p>A couple years back I decided to move away from Medium to gain full control and ownership over my content, so I implemented my own static website generator to publish my articles, which were written in Markdown. I used Sundell&#8217;s <a href="https://github.com/JohnSundell/Publish">Publish</a> static site generator to accomplish that. The problem with this approach is that the website didn&#8217;t look good and there was too much I had to implement on my own to make it look good. It would be a one-off effort only, but still, too much work to be done before I could just focus on just writing.</p><p>Today, I am moving away from that self-hosted static website, and joining a large group of bloggers that use Substack. I found the tools available in Substack to be great, such as creating a community of people that may want to subscribe to my articles.</p><p>For now, I don&#8217;t plan on changing my writing cadence. Some people like to set goals of publishing new articles twice a week, or every other week, but I don&#8217;t see myself having enough time and content to commit to a given cadence. Maybe in the future? Subscribe and be notified of the next articles :)</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading roger.ml! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Testing multiple local Swift dependencies efficiently]]></title><description><![CDATA[Learn how to reduce the test time of a project that makes heavy use of multiple local Swift Packages to promote modularization.]]></description><link>https://www.roger.ml/p/testing-dependencies-efficiently</link><guid isPermaLink="false">https://www.roger.ml/p/testing-dependencies-efficiently</guid><dc:creator><![CDATA[Roger]]></dc:creator><pubDate>Sat, 27 Aug 2022 12:52:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!aOmX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For some time I struggled to reduce the test time when having multiple local dependencies, such as Development Pods or local Swift Packages. In a project with, say, 10 local dependencies, it can easily take 1 hour for your CI to run the unit tests for all of them, even if they only have 1 test inside each dependency.</p><blockquote><p><em>For the purposes of this article, I considered a project with local Swift Package dependencies, and not other types of local dependencies (like static libraries and frameworks, or Development Pods).</em></p></blockquote><h2>Why does this happen?</h2><p>What I figured is that xcodebuild's iOS Simulator startup time is too slow, and this causes the tests to take significantly longer. For each dependency you have, it will launch a new iOS Simulator process, and this process takes anywhere from 4 to 10 minutes, dependending on the machine it's run - even when launching a "headless" simulator.</p><h2>The solution: Test Plans</h2><p>Xcode Test Plans, which debuted in Xcode 11, serve the purpose of bundling tests together. You may want to create different sets of tests such as "Nightly", "UI", "Performance", etc, or you may simply bundle them up all together in the same Test Plan.</p><p>What I realized is that when using Test Plans, I obtained test runs up to 93% faster than by running individual Swift Package tests (<code>xcodebuild test &lt;swift package&gt;</code>) for <strong>iOS</strong> targets (more on this later).</p><h2>Show me the code!</h2><h3>Setting up your Test Plan</h3><p>Test Plans can be created via Xcode by creating a new file (cmd + N) and choosing "Test Plan" - yes, that simple. However, if you're using local Swift Packages, there's a trick here: your <code>.xcodeproj</code> or <code>.xcworkspace</code> must have the Swift Packages targets you want to test in its folder hierarchy otherwise they won't show up to be added in your Test Plan.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aOmX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aOmX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png 424w, https://substackcdn.com/image/fetch/$s_!aOmX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png 848w, https://substackcdn.com/image/fetch/$s_!aOmX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png 1272w, https://substackcdn.com/image/fetch/$s_!aOmX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aOmX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png" width="1440" height="1026" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1026,&quot;width&quot;:1440,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Creating a Test Plan file&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Creating a Test Plan file" title="Creating a Test Plan file" srcset="https://substackcdn.com/image/fetch/$s_!aOmX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png 424w, https://substackcdn.com/image/fetch/$s_!aOmX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png 848w, https://substackcdn.com/image/fetch/$s_!aOmX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png 1272w, https://substackcdn.com/image/fetch/$s_!aOmX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F935e6353-d42f-44fa-9603-36f0460ed239_1440x1026.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Once you have a Test Plan file, open it an click the <code>+</code> at the bottom left of the window and add all the targets you'd like to test.</p><p>Next, you may want to convert your one of your schemes of your main project (one that consumes those dependencies) to use Test Plans, or not. The only difference is that if you convert it, you can simply run <code>xcodebuild test -scheme &lt;your_scheme&gt;</code>, and if not, then you'll have to specify the name of your Test Plan explicitly, like <code>xcodebuild test -testPlan &lt;test_plan_name&gt;</code>.</p><p>To convert your scheme, open its Edit window on Xcode, and select <code>Convert to use Test Plans&#8230;</code>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OrJL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OrJL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png 424w, https://substackcdn.com/image/fetch/$s_!OrJL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png 848w, https://substackcdn.com/image/fetch/$s_!OrJL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png 1272w, https://substackcdn.com/image/fetch/$s_!OrJL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OrJL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png" width="1456" height="782" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:782,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Converting Scheme to use Test Plans&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Converting Scheme to use Test Plans" title="Converting Scheme to use Test Plans" srcset="https://substackcdn.com/image/fetch/$s_!OrJL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png 424w, https://substackcdn.com/image/fetch/$s_!OrJL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png 848w, https://substackcdn.com/image/fetch/$s_!OrJL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png 1272w, https://substackcdn.com/image/fetch/$s_!OrJL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa7bfc88-226a-4546-bc4b-407c432bb19b_1848x992.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Collecting code coverage</h3><p>Every setup is different, but I like using Rake tasks to run tests and collect code coverage. You may be using <em>fastlane</em>, in which case the APIs used will be different but the concepts remain the same. Also, I'll be using Code Climate in my example as it's the code coverage service I usually use for Swift projects.</p><p>First, we have to kick off the code coverage program that Code Climate distributes:</p><pre><code># Path to your Code Climate test reporter binary. You may want to fetch this from remote and do a checksum check before running it.
test_reporter_bin = "path/to/your/cc-test-reporter"

# Invoke the before-build function required by Code Climate, before collecting your code coverage
sh("#{test_reporter_bin} before-build")</code></pre><p>Then we build the Test</p><pre><code># This becomes your new Derived Data path, stored in your project directory (you can gitignore this new folder)
build_path = ".build"

xcodebuild = []
xcodebuild &lt;&lt; "arch -x86_64" # Only required if your project only runs on Rosetta 2
xcodebuild &lt;&lt; "xcodebuild test"
xcodebuild &lt;&lt; "-workspace your-project.xcworkspace"
xcodebuild &lt;&lt; "-scheme 'Your Scheme'"
xcodebuild &lt;&lt; "-destination 'platform=iOS Simulator,name=iPhone 14 Pro'"
xcodebuild &lt;&lt; "-enableCodeCoverage=YES"
xcodebuild &lt;&lt; "-derivedDataPath '#{build_path}/your-project'"
xcodebuild &lt;&lt; "-clonedSourcePackagesDirPath '#{build_path}/SourcePackages'"
xcodebuild &lt;&lt; "-quiet" # Optionally silents all xcodebuild output
sh(xcodebuild.join(" "))</code></pre><p>Since xcodebuild will collect code coverage for all the targets that it goes through, including 3rd party, we have to filter out which targets we're interested. Let's declare an array for this purpose, that will be used later:</p><pre><code># You may want to collect this array programmatically somehow via scripting, for instance by crawling your directory hierarchy and reading which packages have test targets. For simplicity sake here we're just declaring them statically. 
targets_to_keep = %w(
  foo
  bar
  baz
)</code></pre><p>Then we must interpret the <code>.xcresult</code> file produced by xcodebuild, and convert it to a format that Code Climate can interpret:</p><pre><code># This is a special path where your xcresult (test results) will be stored, within the Derived Data path specified by you earlier.
most_recent_xcresult = Dir.glob("#{build_path}/your-project/Logs/Test/*.xcresult").max_by { |f| File.mtime(f) }

# Specify where we're going to store the code coverage reports. Feel free to gitignore these directories or clean them up after collection
xcodebuild_reports_directory = "coverage/xcodebuild_only_reports"
xccov_report = File.absolute_path("#{xcodebuild_reports_directory}/your-project-xccov-report.json")
output_file = File.expand_path("coverage/your-project-codeclimate.json")
FileUtils.mkdir_p(xcodebuild_reports_directory)

# Use `xccov` (a built-in tool from Xcode) to convert the .xcresult into a json file that uses the xccov format
sh("xcrun xccov view --report --json '#{most_recent_xcresult}' &gt; #{xccov_report}")

# Invoke this function that will clean up the code coverage report from targets we're not interested in collecting data for
remove_all_targets_except(xccov_report, targets_to_keep)

# Finally, format the json report (that uses the xccov format) into another json format specific to Code Climate 
sh("#{test_reporter_bin} format-coverage '#{xccov_report}' --input-type xccov --output '#{output_file}'")</code></pre><p>Note that this snippet makes use of a function you must declare in your Rakefile called <code>remove_all_targets_except</code>. More about it here: <a href="https://gist.github.com/rogerluan/aaba6694a45ed5381e7e6b2259abd9f7">https://gist.github.com/rogerluan/aaba6694a45ed5381e7e6b2259abd9f7</a></p><p>Finally, submit the code coverage report to Code Climate:</p><pre><code>sh("#{test_reporter_bin} upload-coverage --input '#{output_file}'")</code></pre><h2>Monorepo setup</h2><p>If you repository has multiple projects in it, this means you'll probably have more than 1 TestPlans (one for each project). If that's your case, this means you'll still have to merge the code coverage reports of your multiple projects before uploading them to Code Climate, by using the <code>sum-coverage --parts &lt;number_of_parts&gt;</code> function of their code coverage reporter tool. Again, this is only for Code Climate, so your mileage may vary.</p><h1>Conclusion</h1><p>With the setup I described in this article, I was able to reduce build times from ~61mins to ~4mins (when running locally), and from ~90mins to ~20mins (when running on Jenkins). That's an improvement of 77-93%!</p><p>One caveat with Swift Packages specifically is that, if your package is capable of running on macOS target (e.g. has no iOS dependency like UIKit), then your fastest alternative will certainly be using <code>swift test</code>, because it usually takes under 1 second to run whatever test suite you may have (of course, as long as they're not time-dependent). Yes, really.</p><p>I hope this setup also helps you and your team to reduce build times significantly. And if you know other tricks to reduce test time, drop a note on my twitter <a href="https://twitter.com/rogerluan_">@rogerluan_</a>!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.roger.ml/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading roger.ml! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item></channel></rss>