<?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:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ritvik Nag on Medium]]></title>
        <description><![CDATA[Stories by Ritvik Nag on Medium]]></description>
        <link>https://medium.com/@ritviknag?source=rss-d6fb304e5df6------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*7nFQExf_JdUnerPIa86fdQ.jpeg</url>
            <title>Stories by Ritvik Nag on Medium</title>
            <link>https://medium.com/@ritviknag?source=rss-d6fb304e5df6------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 28 May 2026 04:54:59 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ritviknag/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How I Automated Parking Permit Purchases at GMU with Python]]></title>
            <link>https://medium.com/@ritviknag/how-i-automated-parking-permit-purchases-at-gmu-with-python-9c6825247995?source=rss-d6fb304e5df6------2</link>
            <guid isPermaLink="false">https://medium.com/p/9c6825247995</guid>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[student-life]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[selenium]]></category>
            <category><![CDATA[productivity]]></category>
            <dc:creator><![CDATA[Ritvik Nag]]></dc:creator>
            <pubDate>Fri, 05 Sep 2025 03:45:09 GMT</pubDate>
            <atom:updated>2025-09-05T03:45:09.174Z</atom:updated>
            <content:encoded><![CDATA[<h4>A Python + Selenium tool to automate GMU daily parking permits. It’s open source, semi-automated with Duo Mobile 2FA, and saves me from last-minute stress.</h4><p>As a grad student at <strong>George Mason University (GMU)</strong>, I commute to Fairfax once a week for evening classes. Like many commuters, I need parking — but only for a few hours, usually <strong>4–7 PM</strong>.</p><p>Buying a semester-long permit didn’t make sense for my schedule (or wallet). Instead, the <strong>daily parking permits</strong> turned out to be cheaper and more flexible.</p><p>The problem? I always procrastinated. I’d remember to buy a permit at the last minute, sometimes right before class. It was stressful, repetitive, and just annoying.</p><p>So, being a CS student, I did what came naturally:</p><p>👉 <strong>I automated the entire process.</strong></p><h4>The Idea</h4><p>Every week, I needed to:</p><ol><li>Go to the GMU parking portal.</li><li>Log in with my credentials.</li><li>Navigate through a bunch of menus.</li><li>Select the right permit.</li><li>Enter payment details.</li></ol><p>It was the same steps every time. Why not let Python handle it?</p><h4>The Tool</h4><p>I built a small open-source tool in Python that:</p><ul><li>Automates login to the GMU parking portal up to the <strong>Duo Mobile 2FA step</strong> (which still requires approval on your phone).</li><li>Selects the correct <strong>daily parking permit</strong>.</li><li>Completes the checkout flow.</li><li>Emails me a confirmation when done.</li></ul><p>That’s it — no more last-minute scrambling.</p><p>📽️ <strong>Demo Video:</strong> <a href="https://youtu.be/9X5lc2zZq-k">YouTube</a></p><p>💻 <strong>Source Code:</strong> <a href="https://github.com/rnag/GMU-Daily-Permit-Automation">GitHub — GMU Daily Permit Automation</a></p><h4>Tech Breakdown</h4><p>At its core, this project uses:</p><ul><li><a href="https://www.selenium.dev/"><strong>Selenium</strong></a> — to control a browser and interact with the GMU parking site.</li><li><strong>Headless mode</strong> — so it runs quietly in the background.</li><li><strong>Config + Environment variables</strong> — to keep credentials and payment info safe.</li><li><strong>Task scheduling (optional)</strong> — you <em>could</em> use cron (Linux/Mac) or Task Scheduler (Windows) to kick off the script automatically. I usually just run it manually before class.</li><li><strong>Duo Mobile 2FA</strong> — you’ll still need to approve the login from your phone. The script pauses at this step until Duo is confirmed.</li></ul><p>Here’s a simplified example of what the login step looks like in Python with Selenium:</p><pre>from selenium import webdriver<br>from selenium.webdriver.common.by import By<br>from selenium.webdriver.common.keys import Keys<br><br># Start the browser (headless mode can be enabled too)<br>driver = webdriver.Chrome()<br><br># Go to GMU parking login page<br>driver.get(&quot;https://gmu.t2hosted.com/Account/Portal&quot;)<br><br># Enter username<br>username_input = driver.find_element(By.ID, &quot;username&quot;)<br>username_input.send_keys(&quot;myusername&quot;)<br><br># Enter password<br>password_input = driver.find_element(By.ID, &quot;password&quot;)<br>password_input.send_keys(&quot;mypassword&quot;)<br>password_input.send_keys(Keys.RETURN)<br><br># (From here, the script navigates menus and purchases the permit)</pre><p>The real project handles edge cases, payment, and confirmation, but this snippet shows the basic automation flow.</p><h4>Lessons Learned</h4><p>A few interesting takeaways while building this:</p><ul><li>University systems often don’t have APIs, so <strong>web automation</strong> is sometimes the only option.</li><li><strong>Security matters</strong> — never hardcode sensitive info; use config files or environment variables.</li><li>Even small automations can save you from recurring stress (and are great coding practice).</li></ul><h4>Try It Yourself</h4><p>If you’re at GMU (or curious about automation), you can try it out:</p><p>👉 <a href="https://github.com/rnag/GMU-Daily-Permit-Automation">GitHub Project</a></p><p>The README has setup instructions. Just remember: <strong>use responsibly</strong> — the tool isn’t affiliated with GMU Parking Services, and you should only automate your own account.</p><h4>Final Thoughts</h4><p>This project started as a way to save myself from procrastination, but it turned into a fun automation experiment. It’s one of those small quality-of-life improvements that adds up.</p><p>If you’re a fellow GMU commuter, hopefully this helps you too. And if you’re a developer, maybe it’ll inspire you to automate some of your own daily annoyances.</p><p>While Duo Mobile means this isn’t 100% hands-free, it still saves me from repeating the same web clicks every week.</p><p>As always, thanks for reading. If you liked the article please leave a few claps so other users can find it more easily. Also feel free to give me a follow at <a href="https://medium.com/@ritviknag">@ritviknag</a> to stay up to date with any articles I publish.</p><p><em>Originally published at </em><a href="https://ritviknag.com/tech-tips/how-i-automated-parking-permit-purchases-at-gmu-with-python"><em>https://ritviknag.com</em></a><em> on September 4, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9c6825247995" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sleep Mode: Automating Wi-Fi & Bluetooth Off at Night on Apple Devices]]></title>
            <link>https://medium.com/@ritviknag/sleep-mode-automating-wi-fi-bluetooth-off-at-night-on-apple-devices-5c1c129186ec?source=rss-d6fb304e5df6------2</link>
            <guid isPermaLink="false">https://medium.com/p/5c1c129186ec</guid>
            <category><![CDATA[sleep]]></category>
            <category><![CDATA[health]]></category>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[apple]]></category>
            <dc:creator><![CDATA[Ritvik Nag]]></dc:creator>
            <pubDate>Thu, 22 May 2025 05:49:43 GMT</pubDate>
            <atom:updated>2025-05-22T05:49:43.882Z</atom:updated>
            <content:encoded><![CDATA[<h4>How I automated turning off Wi-Fi and Bluetooth on my Apple devices at night to reduce distractions and improve sleep quality — step by step.</h4><p>As a runner and fitness enthusiast, sleep is essential for my recovery — and yet, it’s something I’m constantly working to improve. I aim for 8 hours a night, but my Apple Watch reports an average closer to 6.5 hours over the past few months, with some nights dipping as low as 5. Being a light sleeper doesn’t help either. I’ve experimented with wax earplugs to block out noise and blackout curtains to keep light at bay.</p><p>One thing I’ve wanted to automate for a while now is disabling Wi-Fi and Bluetooth on the devices near my bed — including both my personal and work MacBooks, my iPhone, and ideally even my Apple Watch (which I wear to track sleep). These devices all stay in my bedroom overnight, so minimizing potential sleep disruptors has become a personal experiment.</p><p>You might ask: why go through the trouble of shutting off Wi-Fi and Bluetooth automatically? It’s a fair question. <a href="https://sleepreviewmag.com/sleep-health/parameters/quality/study-raises-concerns-wi-fi-device-radiation-sleep-quality/">This study</a> raises concerns that Wi-Fi radiation may impact sleep quality and increase the risk of insomnia. And this <a href="https://neurologicwellnessinstitute.com/can-wifi-affect-sleep-quality/">video explanation</a> discusses the potential relationship between wireless radiation and disrupted sleep. While scientific research on the topic is still largely inconclusive, I figured that if there’s even a slight chance of improving sleep quality by reducing exposure, it’s worth exploring.</p><p>So for the past several months, I’ve had an automation in place that turns off Wi-Fi and Bluetooth on my Apple devices around bedtime — and in this post, I’ll share how I set that up in case you’re looking to try something similar.</p><h4>Creating the Shortcuts</h4><p>The first step involves opening the Shortcuts app on a Mac laptop or device. The easiest way is to hit Cmd + Space and search for Shortcuts.</p><p>The icon should look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/0*md_dNcyDA9NYBalt.png" /></figure><p>I’ve created two shortcuts. One called “<strong>Start Morning</strong>” to enable Wi-Fi/Bluetooth, and another called “<strong>Night Time</strong>” to disable Wi-Fi/Bluetooth and put the display to sleep, as shown below. They both use the “<em>Set Wi-Fi</em>” and “<em>Set Bluetooth</em>” actions, which you can find using the Action Library on the right.</p><figure><img alt="Start Morning" src="https://cdn-images-1.medium.com/max/1024/0*F-2PIRR4Ix3jlgZG.png" /></figure><figure><img alt="Night Time" src="https://cdn-images-1.medium.com/max/1024/0*yQXajBFPB4Aw2qyk.png" /></figure><h4>Creating Automator Scripts</h4><p>Next, I opened up the <strong>Automator</strong> app — the Cmd + Space works here too — and go to <em>File &gt; New</em> to create a new automation. I need to do this twice, once for each action.</p><p>Search for the “<strong>Run Shell Script</strong>” action. Then in the settings, select <strong>/bin/bash</strong> as the Shell, and enter this code in the bash code block:</p><pre>shortcuts run &quot;Start Morning&quot;</pre><p>Here, &quot;Start Morning&quot; is the name of your first shortcut. If you named it something else, update the name here accordingly. The executable command shortcuts can also be run from the Mac Terminal app, and is simply a handy, automated way to interact with your shortcuts on the Shortcuts app.</p><p>Here’s a screenshot of this — you can name this StartMorning.app and save it to your /Applications folder, but here you can see I’ve saved it to under my iCloud Drive &gt; Automator , for an important reason I’ll cover later:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*zPaEkN7SAB6skOGu.png" /></figure><p>Lastly, go to <em>File &gt; New</em> again and search for the “<strong>Run Shell Script</strong>” action. Then in the settings, select <strong>/bin/bash</strong> as the Shell as last time, and enter this code in the bash code block:</p><pre>shortcuts run &quot;Night Time&quot;</pre><p>Replacing &quot;Night Time&quot; if you chose to use a different name for the shortcut. I’ve again saved this to my iCloud Drive, as shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vL_tz3Z78bK8zspc.png" /></figure><p>Now, there’s a few way to schedule these two automations on a Mac. You can either set up a cron job or, in my case, I chose to use the Calendar app. I got all these steps from an article a long time ago, but now for the life of me I can’t seem to find that article — I guess that’s one reason to bookmark an interesting site that you come across.</p><h4>Scheduling with Calendar</h4><p>Regardless, the next step is to open the <strong>Calendar</strong> app on your Mac.</p><p>Now, remember how I saved the Automator scripts to iCloud Drive, inside an “Automator” folder? There’s a reason for that. If you’re using just one Mac — say, a personal laptop — saving the scripts to /Applications works fine. But in my case, I use both a <em>MacBook Air</em> (personal) and a <em>MacBook Pro</em> (work). I wanted the Wi-Fi and Bluetooth toggling to sync across both devices.</p><p>After some trial and error, I found that Calendar events sync automatically across Apple devices — but local file paths like /Applications do not. So, if the script lives in /Applications on one Mac, the synced calendar event on the second Mac will break. By storing the script in iCloud Drive, both Macs can access the same path, and I only have to set up the Calendar automation once (in theory, at least).</p><p>First thing I did was hit the <strong>+</strong> button at the top to create a new Calendar event. I called the first one “<strong>Start Morning</strong>”, set it to <strong>repeat daily</strong> at <strong>8:00 AM</strong>, and the important bit here is to expand the event, click on “Add Alert, Repeat, or Travel Time”, and then to go to where you see alert: None, click the dropdown, and go to Custom..., choose Open File and then in the second dropdown choose Other... . In the Finder window, locate and select the file location for the Automator script StartMorning.app that you saved in previous step. Next, though technically not needed, I clicked the dropdown to alert <strong>15 Minutes before</strong> and changed it to <strong>At time of event</strong>.</p><p>You can see the event which turns on Wi-Fi/Bluetooth here:</p><figure><img alt="Start Morning Calendar Event" src="https://cdn-images-1.medium.com/max/580/0*6CLtMC59bjyw_FxY.png" /></figure><p>Lastly, do the same for the “<strong>Night Time</strong>” event! I set it to <strong>repeat daily</strong> at <strong>11:00 PM</strong> based on my sleep schedule, but feel free to adjust accordingly based on yours! I followed the same steps as above. I chose Open File and then in the second dropdown choose Other... . In the Finder window, locate and select the file location for the Automator script NightTime.app that you saved in previous step.</p><p>You can see the final event which switches off Wi-Fi/Bluetooth here:</p><figure><img alt="Night Time Calendar Event" src="https://cdn-images-1.medium.com/max/676/0*sgu8fS81Cb_46ZmC.png" /></figure><p>And that’s it! All my Mac devices will effectively go into Airplane Mode at my sleep time at 11 PM, and wake up from Airplane Mode at 8 AM the next day automatically. Problem solved!</p><h4>Scheduling on iPhone</h4><p>Is it possible to schedule the same on an iPhone?</p><p>You betcha! It’s actually a bit easier, since you only have to set Airplane Mode, rather than Wi-Fi and Bluetooth individually.</p><p>I opened the “<strong>Shortcuts</strong>” app on my iPhone. I created my first automation, which runs on a schedule — at <strong>11:00 PM, daily</strong> in my case — and the action I chose is <strong>Set Airplane Mode &gt; ON</strong>. This is shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*62sz2PIvAcVeM3Ny.jpeg" /></figure><p>Lastly, rather than turn on my iPhone in the morning at a specific time, I chose to create an automation with a trigger that runs when my phone is disconnected from power — since I wirelessly charge it overnight — and the action I chose is <strong>Set Airplane Mode &gt; OFF</strong>. This is shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-Lq4Cs4oE3VYYkNf.jpeg" /></figure><h4>Scheduling on Apple Watch</h4><p>The final piece of the puzzle is my Apple Watch. Since I wear it every night to track sleep, I’d hoped to schedule Wi-Fi shutoff there as well. Unfortunately, even with the Shortcuts app, there doesn’t seem to be a way to automate Wi-Fi toggling on the Watch. For now, I’ve resorted to doing it manually: unlock the Watch, press the side button, and tap the blue Wi-Fi icon. Not ideal, but a small inconvenience — especially since everything else is automated.</p><p>Whether or not Wi-Fi exposure truly impacts sleep quality remains an open question. But for me, this setup offers peace of mind. And that, in itself, helps me sleep better.</p><p>Hope this was helpful — and if you end up building something similar, I’d love to hear how it goes.</p><p>Thanks for reading.</p><p>As always, thanks for reading. If you liked the article please leave a few claps so other users can find it more easily. Also feel free to give me a follow at <a href="https://medium.com/@ritviknag">@ritviknag</a> to stay up to date with any articles I publish.</p><p><em>Originally published at </em><a href="https://ritviknag.com/tech-tips/sleep-mode-automating-wi-fi-&amp;-bluetooth-off-at-night-on-apple-devices"><em>https://ritviknag.com</em></a><em> on May 22, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5c1c129186ec" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Navigating Medium API with JavaScript in 2024]]></title>
            <link>https://medium.com/@ritviknag/medium-api-with-javascript-in-2024-2e73440c7fa0?source=rss-d6fb304e5df6------2</link>
            <guid isPermaLink="false">https://medium.com/p/2e73440c7fa0</guid>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[medium-api]]></category>
            <category><![CDATA[npm-package]]></category>
            <dc:creator><![CDATA[Ritvik Nag]]></dc:creator>
            <pubDate>Sun, 18 Feb 2024 02:56:06 GMT</pubDate>
            <atom:updated>2024-02-18T05:22:42.088Z</atom:updated>
            <content:encoded><![CDATA[<h4>Leverage the Medium API in JavaScript to publish a post to medium.com, and retrieve a list of all (published) posts for a user.</h4><h4>Background</h4><p>I’m trying to integrate Medium blogging into my personal site at <em>myname.com</em>.</p><p>That is, I’m trying to automate the process of publishing an article on my personal blog, and have the contents of the Jekyll-based markdown post — a .md file -- be posted to the two other blogging sites I&#39;m currently signed up on, <a href="https://medium.com">medium.com</a> and <a href="https://dev.to">dev.to</a>.</p><p>Towards that end, I tried to search online to see if there is such as a thing as a Medium API that allows me to:</p><ul><li>Publish and/or update a post for a user</li><li>Retrieve a list of posts for a user</li></ul><p>Turns out, only the first point is currently possible via the <a href="https://developers.medium.com/">Official Medium API</a>, and that too, it’s only <em>partially</em> satisfied — there is currently no route via the API to update a post on a Medium. One can only <strong>create</strong> a new post on Medium via the API, but that’s about it.</p><p>With the Official <a href="https://github.com/Medium/medium-api-docs"><strong>Medium API</strong></a> as it stands — which is no longer supported or maintained by the Medium team — one can only retrieve user info and create a post, they cannot do anything much more useful than that, for example it is not possible to retrieve a list of a user’s posts via the API.</p><h4>How to Auto-Update a Medium Post</h4><p>This is slightly tangential to the matter, but I haven’t expressed my discontent with not being able to automate the updating of a post on Medium via an API. I fooled around this with their API — for example using an HTTP PUT request - for way too long, and didn&#39;t get anywhere with that at all. It&#39;s a real shame that we can only publish new posts via the API, and that it&#39;s not possible to update an existing post via an API.</p><p>In fact, this proved the impetus or drive for me to publish an online web tool that easily translates posts from <em>markdown</em> to <em>medium</em>, which I have published on my website:</p><blockquote><a href="https://ritviknag.com/markdown-to-medium">https://ritviknag.com/markdown-to-medium</a></blockquote><p>I made this above tool for myself, so I can take my personal blog posts, written in .md files, paste them in there and copy the output to Medium when using the editor to update an existing post.</p><p>Anyway, slight detour there…</p><p>All this to say that I was not able to find a solution via the API that worked for me, to list all the posts under a given user. The reason for this is purely for automation purposes, such that whenever I change a blog post — or .md file -- on my website, I want to publish a new post to Medium, but <em>only</em> if there is not already an existing post with that same title. So basically, I want to avoid re-publishing a post every time I make an update to it -- if that&#39;s even possible!</p><h4>My Search</h4><p>My search led me to Google search for something along the lines of “<strong>medium api get all user posts</strong>”, which led me more or less to this somewhat informative article on StackOverflow:</p><blockquote><a href="https://stackoverflow.com/q/36097527/10237506">https://stackoverflow.com/q/36097527/10237506</a></blockquote><p>As the accepted answer says, the Medium API is only intended for <em>Publishing</em> and is not intended to retrieve posts.</p><p>A workaround that is suggested is to simply use the RSS feed that Medium offers, as such:</p><blockquote><a href="https://medium.com/feed/@username"><em>https://medium.com/feed/@username</em></a></blockquote><p>For example, here is the RSS feed for my user:</p><blockquote><a href="https://medium.com/feed/@ritviknag">https://medium.com/feed/@ritviknag</a></blockquote><p>One can simply get the RSS feed via GET, then if it&#39;s needed in JSON format just use an NPM module like rss-to-json and we&#39;re good to go.</p><p>However, there is an issue with this approach, as another answer in the same SO post confirms:</p><blockquote>[…] has a limit of the <strong>latest 10 posts</strong>.</blockquote><p>So, there is a limit of only retrieving 10 posts for a given user using the RSS feed option.</p><p>Looks like we need another solution!</p><h4>Unofficial Medium API</h4><p>I would be remiss if I didn’t mention an alternate approach.</p><p>There is an <strong>Unofficial</strong> Medium API that someone has come up with. This is the link to it below:</p><blockquote><a href="https://mediumapi.com">https://mediumapi.com</a></blockquote><p>Unfortunately, it’s not an insignificant commitment to sign up with that API. Take a look below:</p><figure><img alt="Unofficial Medium API Subscription" src="https://cdn-images-1.medium.com/max/1024/0*dKxys6WA8Wwc4-Mz.png" /></figure><p>As noted, there appears to be a subscription tier for using that API.</p><p>If I, as a developer, want to build an API wrapper around the Medium SDK to retrieve a User’s posts, using this option I would be forced into:</p><ul><li>Create a RapidAPI key for mediumapi.com, or directing users of the library to sign up on <em>another</em> site and acquire a RapidAPI key for requests to mediumapi.com.</li><li>Rate limiting my library at <strong>150 requests / month</strong>, assuming I want to hardcode and provide users with my RapidAPI key, in order to provide convenience and promote adoption of said library.</li></ul><p>In short, this is likewise a disaster, albeit of different proportions. There is <strong>no way</strong> I can use a third-party, unofficial API, even though I have personally tested out this API endpoint and confirmed that it returns the correct and desired data.</p><p>Thus, the unavoidable road to the <strong><em>DIY project</em></strong>, begins…</p><h4>The “Eureka!” Moment</h4><p>To me, the lightbulb went off, when I was just bored and staring at a random Medium user’s profile, and I had Chrome Developer tools open and was just browsing casually through the Network tab.</p><p>The graphql endpoints, in particular, stood out to me:</p><figure><img alt="Chrome Dev GraphQL" src="https://cdn-images-1.medium.com/max/1024/0*CLzH3w_JvPE4NBwy.png" /></figure><p>If curious, the URL to that is https://medium.com/_/graphql, and it&#39;s an HTTP POST request that gets sent.</p><p>Shortly after, I fired up Google search again and this time inputted keywords “<strong>how to use medium api graphql to fetch all user posts</strong>”.</p><p>The result was this highly informative post on using Medium’s GraphQL endpoint to retrieve Medium posts for a user:</p><blockquote><a href="https://medium.com/@mike820324/web-crawler-getting-medium-post-a6e52fd36fd6">https://medium.com/@mike820324/web-crawler-getting-medium-post-a6e52fd36fd6</a></blockquote><p>I decided to implement the same logic in the JavaScript (Node.js + TypeScript) library I was writing, with an intent to publish as an NPM package to <a href="https://npmjs.com/">npmjs.com</a>.</p><p>The hardest part for me was how to:</p><ul><li>Modify the GraphQL query to only return user post titles</li><li>Paginate through the results, as GraphQL appears to have a limit of 25 results (posts) per request.</li></ul><p>I managed to figure that out, even though it took me a long while.</p><p>By the way, I adapted the code from <a href="https://github.com/redco/medium-sdk-nodejs">a fork of the Medium SDK for Node.js</a>.</p><p>I made my own changes, and a <strong><em>lot</em></strong> of updates along the way.</p><p>Simply said, this was the very first <em>TypeScript NPM package</em> I had ever written. So there was a lot of learning curve.</p><h4>The Result</h4><p>The work was split into a few main parts:</p><ul><li>Porting over existing Medium SDK code from <em>Node.js</em> to <em>TypeScript</em>.</li><li>The hurdle that come with publishing a TypeScript NPM package, such as setting up tsconfig.json to allow support for <a href="https://www.sensedeep.com/blog/posts/2021/how-to-create-single-source-npm-module.html">both ESM and CommonJS syntax</a> simultaneously.</li><li>Integrating pagination and GraphQL into the codebase.</li><li>Best practices for NPM projects in TypeScript, such as setting up code quality checks and CI/CD automation — such as publishing package to a registry (NPM) automatically.</li></ul><p>This project is the direct result of my hard work and effort:</p><p><a href="https://github.com/rnag/medium-sdk-ts">GitHub - rnag/medium-sdk-ts: TypeScript (TS) client for the Medium SDK</a></p><p>Below is the package on the NPM registry:</p><p><a href="https://www.npmjs.com/package/medium-sdk-ts">medium-sdk-ts</a></p><h4>Install</h4><p>The package can be installed with <a href="https://www.npmjs.com/">npm</a>, or a package manager of choice:</p><pre>npm i medium-sdk-ts</pre><h4>Import</h4><p>The library supports importing via the newer, more modern <em>ES Module (ESM)</em> syntax prevalent in TypeScript code:</p><pre>import {<br>  MediumClient,<br>  PostContentFormat,<br>  PostPublishStatus,<br>} from &#39;medium-sdk-ts&#39;;</pre><p>Or, for <em>CommonJS</em> syntax in Node.js, using require:</p><pre>const {<br>    MediumClient,<br>    PostContentFormat,<br>    PostPublishStatus,<br>} = require(&#39;medium-sdk-ts&#39;);</pre><h4>Usage</h4><p>Now create a client for the <strong>Medium SDK</strong>:</p><pre>// Access Token is optional, can also be set<br>// as environment variable `MEDIUM_ACCESS_TOKEN`<br>const medium = new MediumClient(&#39;YOUR_ACCESS_TOKEN&#39;);</pre><p>Retrieve User Details:</p><pre>const user = await medium.getUser();<br>console.log(`User: ${JSON.stringify(user, null, 2)}`);</pre><p>Publish a new <em>Draft Post</em> under your user:</p><pre>const post = await medium.createPost({<br>    // Only `title` and `content` are required to create a post<br>    title: &#39;A new post&#39;,<br>    content: &#39;&lt;h1&gt;A New Post&lt;/h1&gt;&lt;p&gt;This is my new post.&lt;/p&gt;&#39;,<br>    // Optional below<br>    contentFormat: PostContentFormat.HTML,   // Defaults to `markdown`<br>    publishStatus: PostPublishStatus.DRAFT,  // Defaults to `draft`<br>    // tags: [&quot;my&quot;, &quot;tags&quot;],<br>    // canonicalUrl: &quot;https://my-url.com&quot;,<br>});<br><br>console.log(`New Post: ${JSON.stringify(post, null, 2)}`);</pre><p>Retrieve a User’s Published Posts (using GraphQL endpoint, which does not require an access token):</p><pre>// Get User&#39;s Published Posts<br>const posts = await medium.getPosts(&#39;@username&#39;);<br>console.log(`User Post: ${JSON.stringify(posts, null, 2)}`);</pre><p>Or, to simply retrieve the user’s post titles only — note that this results in a slightly more simplified GraphQL query being sent:</p><pre>// Get User&#39;s Published Posts (Title Only)<br>const postTitles = await medium.getPostTitles(&#39;@username&#39;);<br>console.log(`User Post Titles: ${JSON.stringify(postTitles, null, 2)}`);</pre><p>That’s all for now!</p><p>I hope this post was informative. Feel free to play around with this NPM package I have published.</p><p>Again, here is the source code for it: <a href="https://github.com/rnag/medium-sdk-ts">https://github.com/rnag/medium-sdk-ts</a></p><p>If this library has proved useful in your automation efforts, please let me know via the comments down below, and feel free to also <strong>star</strong> the project on GitHub if you’d like.</p><p>As mentioned, this is my first-ever TypeScript library package I have published to NPM, so I would appreciate the support or feedback on it — do certainly let me know if there’s anything else I can improve on it.</p><p>As always, thanks for reading. If you liked the article please leave a few claps so other users can find it more easily. Also feel free to give me a follow at <a href="https://medium.com/@ritviknag">@ritviknag</a> to stay up to date with any articles I publish.</p><p><em>Originally published at </em><a href="https://ritviknag.com/tech-tips/navigating-medium-api-with-javascript-in-2024"><em>https://ritviknag.com</em></a><em> on February 17, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2e73440c7fa0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Auto-Publish Your Markdown Posts to Medium]]></title>
            <link>https://medium.com/@ritviknag/how-to-auto-publish-your-markdown-posts-to-medium-a9fe7519eaba?source=rss-d6fb304e5df6------2</link>
            <guid isPermaLink="false">https://medium.com/p/a9fe7519eaba</guid>
            <dc:creator><![CDATA[Ritvik Nag]]></dc:creator>
            <pubDate>Sun, 11 Feb 2024 21:57:00 GMT</pubDate>
            <atom:updated>2024-02-12T03:54:14.626Z</atom:updated>
            <content:encoded><![CDATA[<p>So… the <a href="https://developers.medium.com/">Medium API</a> appears to be woefully abandoned — it even says <strong><em>“deprecated, do not use!”</em></strong> on their page. They don’t also appear to have any API route to update posts on Medium.</p><p>I have a blog site at <em>myname.com </em>where I semi-frequently publish posts in Markdown format to, so this is a pretty major inconvenience to me.</p><p>This why I have decided to research extensively online for existing solutions to help “grease the wheels” and help move this (a bit more smoothly) along.</p><p>I ultimately came upon <a href="https://markdown-to-medium.surge.sh">a site</a> that seemed promising enough (<a href="https://github.com/fanderzon/markdown-to-medium-tool">source</a>) — it works very simply, and it get the job <em>mostly</em> done.</p><p>You paste Markdown content into the left-hand pane, and it spits out Medium formatted HTML rich text, that can be pasted into the Medium editor when writing a new article or updating an existing one.</p><p>This is the URL for that site:</p><blockquote><a href="https://markdown-to-medium.surge.sh">https://markdown-to-medium.surge.sh</a></blockquote><p>I decided to more or less check out the source code, touch it up, make it brand-new, update all node dependencies while I’m at it, and do some code re-haul, codebase cleanup, and general refactoring.</p><p>I also added some brand-new, shiny and <em>much needed</em> features:</p><ul><li>Ability to automatically paste rich text into the user’s clipboard. Huge win, and a welcome time-saver no doubt!</li><li>Support slightly alternate formats in input, such as <a href="https://jekyllrb.com/docs/front-matter/">YAML front matter</a> that is commonly used for Jekyll and static site generators especially when publishing blog posts to GitHub pages, and can be present in markdown .md files.</li><li>Sanitize and clean up formatting of the HTML <em>rich text </em>content which gets copied into the clipboard, before being later pasted when writing a new article or updating an existing post in the Medium editor. <strong><em>(Honestly, this part was a head-scratcher, and it took me the longest time — almost the whole weekend — to figure out!)</em></strong></li></ul><p>This tool is the direct, hard-earned fruit of the labors of all that hard work:</p><blockquote><a href="https://ritviknag.com/markdown-to-medium">https://ritviknag.com/markdown-to-medium</a></blockquote><p>Feel free to check it out, and play around with it. Maybe even take it for a test run, to see if copy-pasting from Markdown to the Medium editor works flawlessly now — <em>spoiler alert</em>, it definitely should, for the most part!</p><h3>Issues</h3><p>Of course, there are still existing issues with the first site that I looked into.</p><p>The minor HTML editor issues I want to fix look like the below, which seem to occur over when converting <em>markdown</em> -&gt; <em>rich text HTML</em>.</p><p><strong>Links with extraneous padding</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*pLo_lpdxpfM2ItNg" /></figure><p><strong>Code blocks get split</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gjXfWBQENxDv0rIT" /></figure><p><strong>Empty List items</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2FgoW36z2KO3oDTx" /></figure><p><strong>Blockquotes with extraneous padding</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4EdJKJyWpaT5ugTj" /></figure><p><strong>Text within underscores not replaced with italics</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xpdAsLUxP3YtBdK-" /></figure><p>This is pretty glaringly bad, if you ask me. It seems that Medium still need ways to go for their editor to be usable by all folks — especially those like me who publish their content elsewhere, and would like to sync up updates on their Medium posts every now and then.</p><p>The good news, is that I have managed to fix the above aforementioned issues.</p><p>I will have to do a formal writeup of this when time allows, and explain my thought process. This will likely be a busy week for me, but will likely update this post sometime next weekend!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a9fe7519eaba" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Mount Current Working Directory To Your Docker Container]]></title>
            <link>https://medium.com/@ritviknag/how-to-mount-current-working-directory-to-your-docker-container-90b50e7596fa?source=rss-d6fb304e5df6------2</link>
            <guid isPermaLink="false">https://medium.com/p/90b50e7596fa</guid>
            <category><![CDATA[expressjs]]></category>
            <category><![CDATA[linux]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[mac]]></category>
            <dc:creator><![CDATA[Ritvik Nag]]></dc:creator>
            <pubDate>Fri, 09 Feb 2024 15:43:50 GMT</pubDate>
            <atom:updated>2024-02-18T06:28:38.867Z</atom:updated>
            <content:encoded><![CDATA[<h4>Learn how to obviate the need to rebuild a Docker image every time an local file in the host’s current directory changes, with the power of Bind mounts!</h4><p>My team has been building out a new web app in <em>Express.js</em>, and for local deployment and testing we use nodemon, babel, and Docker.</p><p>One of the burning questions we wanted to find an answer to — apart from the ultimate question of all life, the universe, and everything — is how can we mount our local (current) directory to the running Docker container, so that nodemon can listen for local file changes when we develop, and automatically reload or restart the (containerized) web server as needed.</p><p>Towards that end, I hastily Googled online for “Can Docker mount local drive and listen for file changes”, and even kicked this same prompt to my work-approved AI assistant (*wink* ChatGPT alternative?). Anyway, the responses from the AI prompt are recorded separately below.</p><p>By the way, this article is inspired by some other articles on that I found on the web while researching if such a thing is possible — e.g. mounting a local folder to a Docker container — including the following article on Medium, which does a good job of condensing the main points:</p><blockquote><a href="https://medium.com/@kale.miller96/how-to-mount-your-current-working-directory-to-your-docker-container-in-windows-74e47fa104d7"><strong>How To Mount Your Current Working Directory To Your Docker Container In Windows</strong></a></blockquote><p>As I and my team are developing on an (Apple Silicon-based) Mac, we don’t care too much about the docker run command being compatible between Mac and Windows overmuch.</p><p>However, I certainly agree that Docker is super handy for anyone wanting to write software in a Linux environment while still running a Mac (or Windows) computer.</p><h4>Theory</h4><p>Our Dockerfile - we use the same one for development and production - looks something like this:</p><pre># Start from the Node.js image for AMD64 architecture<br>FROM --platform=linux/amd64 node:latest<br><br># Set the working directory for your Node.js app<br>WORKDIR /usr/app<br><br>ARG PORT<br>ARG NODE_ENV<br><br>ENV PORT=$PORT<br>ENV NODE_ENV=$NODE_ENV<br><br># Some installation steps<br>...<br><br># Copy your Node.js application files and the startup script<br>COPY . .<br>COPY start.sh .<br><br># Make the startup script executable<br>RUN chmod +x ./start.sh<br><br># Install Node.js dependencies<br>RUN npm install --include=dev<br><br># Expose the port your app runs on<br>EXPOSE $PORT<br>EXPOSE 80<br><br># Command to run the startup script<br>CMD [&quot;./start.sh&quot;]</pre><p>We have a custom start.sh script we use to start our web server. This basically calls npm run start &amp; within it - e.g. our <strong>npm</strong> script start.</p><p>The above Dockerfile basically expects arguments to be passed in during the Docker build step.</p><p>This allows us to pass in environment variables (and Bash script values) such as the PORT for the web server, and NODE_ENV for the web app - such as dev or production .</p><pre>docker build \<br>  --build-arg PORT=${PORT} \<br>  --build-arg NODE_ENV=${NODE_ENV} \<br>  --tag ${TAG} \<br>  .</pre><p>To the question of mounting a local folder to a Docker container, obviously we only want to enable this behavior when running in development mode.</p><p>That is, we only want to mount a local directory to the container into the location at /usr/app - the WORKDIR from the Docker instructions above - when we are in development mode, and we <em>don&#39;t</em> want to mount anything when we are building a production Docker image, to later ship to <a href="https://www.freecodecamp.org/news/build-and-push-docker-images-to-aws-ecr/">Amazon ECR</a> for example.</p><p>This is a great use case for the potential solution, which will be discussed below. Now that we have the theory in place of what we want to achieve:</p><ul><li>We would rather not update Dockerfile to mount a volume or a local folder.</li><li>Not necessary (or desirable) to mount local folder to a volume when running docker build to create an image from a Dockerfile . This is because need to create separate docker build commands for development and non-development, which is harder to management.</li><li>Ideal solution will be to update only docker run command. Which we only run locally anyway. We do not need to execute docker run for production, as generally for production we just push the final image directly to ECR via a shell command.</li></ul><h4>Goal</h4><p>Now that theory is knocked out of the way, we understand that we prefer to update docker run command syntax, rather than touch docker build or Dockerfile itself.</p><p>Also, it is important here to go over the problem statement once more.</p><p>The goal that we are trying to solve is <strong>how to obviate the need to rebuild the Docker image every time an HTML, JS, EJS, CSS, or similar file in the current local directory changes.</strong></p><p>The host’s current directory is the same that we are copying over to /usr/app on the Docker container, as specified in the above Dockerfile .</p><p>This can be a simple change because of renaming your project, or changing any of the code for the project itself. This is not ideal.</p><p>There is a way to mount a local folder as a volume on a Docker container. This will solve it for us, when we are running nodemon on the Docker container to listen for file changes on the /usr/app location on the container side. We can instead trick Docker to listen for local file changes instead - let us see how this is possible.</p><h4>Online Results</h4><p>When searching online for the prompt “Can Docker mount local drive and listen for file changes” — as this would be incredibly helpful for local development — I found the below articles to be helpful.</p><p>Also, note that the first result is from the official Docker documentation itself. This was helpful for me to determine if Docker can mount local drive and listen for file changes.</p><ul><li><a href="https://docs.docker.com/storage/bind-mounts/">https://docs.docker.com/storage/bind-mounts/</a></li><li><a href="https://medium.com/@kale.miller96/how-to-mount-your-current-working-directory-to-your-docker-container-in-windows-74e47fa104d7">https://medium.com/@kale.miller96/how-to-mount-your-current-working-directory-to-your-docker-container-in-windows-74e47fa104d7</a></li><li><a href="https://stackoverflow.com/q/23439126/10237506">https://stackoverflow.com/q/23439126/10237506</a></li></ul><p>In short, this appears to be a question of --volume vs. -- mount with a type:bind. The <a href="https://docs.docker.com/storage/bind-mounts/#differences-between--v-and---mount-behavior">Docker docs</a> (<em>see first link above)</em> mention that using --volume is the old style, and there is one important difference in behavior. Hence, using --mount explicitly going forward seems to be the safer (and more future-proof) approach.</p><p>However, note that either approach (--volume or --mount) should work for our use case.</p><h4>Ask an AI Assistant</h4><p>When I kicked the same prompt (more or less) to an AI assistant, this is what it replied with — rest of its response truncated for abbreviation sake.</p><p>Yes, Docker mount local drive to listen for file changes should certainly be possible.</p><h4>Mounting a Host Directory in a Docker Container:</h4><ul><li>You can use the -v flag with the docker run command to mount a local directory into a container. For example:</li></ul><pre>docker run -v /path/to/host/directory:/path/in/container my_image</pre><ul><li>Replace /path/to/host/directory with the actual path to your local directory and /path/in/container with the desired path inside the container.</li><li>This approach ensures that any updates made in the host directory are reflected within the container.</li></ul><p>Wow, that wasn’t too bad. It got us more than halfway there. Honestly, if we want to use the legacy --volume option, we can just go with the AI suggestion. But to future-proof it, we can make a small tweak and use --mount instead, as we&#39;ll see below.</p><h4>The Trick</h4><p>I’ve found the best, future-proof method is to use the --mount type=bind flag when running the docker run command. With this command, you can attach the local directory to your docker container at runtime instead of during the build process.</p><p>The --mount type=bind command takes a single parameter formatted like so:</p><blockquote><em>src=&lt;host directory&gt;,dst=&lt;target directory&gt;</em></blockquote><p>If we want to <strong><em>exclude</em></strong> any sub-folders in our &lt;host directory&gt; when mounting the local folder to the Docker container, we need to pass --mount type=volume with the full path to that sub-folder, and exclude the src argument, as follows:</p><blockquote><em>dst=&lt;host directory to not mount&gt;</em></blockquote><h4>Example</h4><p>It’s easier to understand the trick when put into practice.</p><p>Putting it all together, here I’ve created a scenario where I would like to:</p><ul><li>Mount my current working directory (/Users/rnag/Git-Projects/Work/my_project) into the alpine:latest image at the /usr/app location in the container.</li><li>Make the run command compatible for Apple-silicon-based Mac&#39;s, with --platform linux/amd64 .</li><li>Exclude (skip) copying over the node_modules sub-folder in my current working directory, when mounting my current working directory . to the /usr/app location in the container.</li></ul><p>We would do that as so:</p><blockquote><em>docker run -it --platform linux/amd64 --mount type=bind,src=.,dst=/usr/app --mount type=volume,dst=/usr/app/node_modules alpine:latest</em></blockquote><p>Or, prettified a bit more, for all you neat freaks (like me):</p><pre>docker run \<br>  -it \<br>  --platform linux/amd64 \<br>  --mount type=bind,src=.,dst=/usr/app \<br>  --mount type=volume,dst=/usr/app/node_modules \<br>  alpine:latest</pre><p>This should start up your container and attach your working directory. Now any changes you make locally (i.e. in your Mac or Windows machine) will be reflected in your Docker container!</p><p>In short, this will solve it for us, when we are running nodemon or similar on the Docker container to listen for file changes on the /usr/app location on the container side. We have successfully tricked Docker to listen for local file changes instead - what a win!</p><p>As always, thanks for reading. If you liked the article please leave a few claps so other users can find it more easily. Also feel free to give me a follow at <a href="https://medium.com/@ritviknag">@ritviknag</a> to stay up to date with any articles I publish.</p><p><em>Originally published at </em><a href="https://ritviknag.com/tech-tips/how-to-mount-current-working-directory-to-your-docker-container"><em>https://ritviknag.com</em></a><em> on February 9, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=90b50e7596fa" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ruby (Versioning) Hell with Jekyll & GitHub Pages]]></title>
            <link>https://medium.com/@ritviknag/ruby-versioning-trouble-with-jekyll-github-pages-fd2748bf4e1d?source=rss-d6fb304e5df6------2</link>
            <guid isPermaLink="false">https://medium.com/p/fd2748bf4e1d</guid>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[rbenv]]></category>
            <category><![CDATA[homebrew]]></category>
            <category><![CDATA[jekyll]]></category>
            <category><![CDATA[github-pages]]></category>
            <dc:creator><![CDATA[Ritvik Nag]]></dc:creator>
            <pubDate>Sun, 28 Jan 2024 22:56:00 GMT</pubDate>
            <atom:updated>2024-02-10T14:01:43.332Z</atom:updated>
            <content:encoded><![CDATA[<p>So, huge disclaimer, a lesser-known fact (who am I kidding, I’m not ashamed to admit it) is that I’m a bit of a ruby newbie.</p><p>That is, I’ve never cared to learn ruby before. Also, perhaps as a direct result of that, I am a little taken aback when a project asks me to install ruby and use tools that it provides such as bundler , because the assumption there is that I know what I am doing.</p><p>Just to set the record straight, I don’t know what I am doing when it comes to <em>anything</em> ruby .</p><p>Recently, a lot of stuff blew up and hell broke loose, when I inadvertently ran brew upgrade on my new Mac to update (or upgrade?) packages installed by <strong>homebrew</strong>.</p><p>But alas, I did not take ruby into account. A while back, I had installed ruby with homebrew:</p><pre>$ brew install ruby</pre><p>Homebrew, without my permission, apparently took it upon itself to upgrade the bundled ruby version — from 3.2.3 to 3.3.0.</p><p>Hell broke lose when I cd&#39;d into my project repo that I use for <a href="https://github.com/rnag/rnag.github.io">my website</a>, and ran bundle to install fresh dependencies — which was needed, since ruby version was upgraded, so it was a fresh environment with no gems available.</p><pre>$ bundle install</pre><p>The error I received was something like this:</p><pre>~/&lt;user&gt;/somewhere $ bundle exec jekyll serve<br>jekyll 3.9.3 | Error:  undefined method `[]&#39; for nil<br>/usr/local/Cellar/ruby/3.3.0/lib/ruby/3.3.0/logger.rb:384:in `level&#39;: undefined method `[]&#39; for nil (NoMethodError)<br><br>    @level_override[Fiber.current] || @level</pre><p>This indeed was the specific error message I saw:<br> Error: undefined method &#39;[]&#39; for nil</p><p>Searching online, you can see a whole slew of posts that mention this error, and apparently it only happens with ruby 3.3.0 and the jekyll version 3.9.3 that comes <a href="https://pages.github.com/versions/#:~:text=jekyll,3.9.3">bundled with </a><a href="https://pages.github.com/versions/#:~:text=jekyll,3.9.3">github-pages</a> .</p><ul><li><a href="https://stackoverflow.com/questions/77851863/bundle-exec-jekyll-serve-not-working-locally">https://stackoverflow.com/questions/77851863/bundle-exec-jekyll-serve-not-working-locally</a></li><li><a href="https://talk.jekyllrb.com/t/unable-to-install-jekyll-on-m3-macbook/8836">https://talk.jekyllrb.com/t/unable-to-install-jekyll-on-m3-macbook/8836</a></li><li><a href="https://talk.jekyllrb.com/t/error-when-executing-bundle-install/8822">https://talk.jekyllrb.com/t/error-when-executing-bundle-install/8822</a></li></ul><p>Hence, while it might not bear repeating, it helps to clarify — upgrading jeykll version for a project is a <strong>no-go</strong>, especially when the project Gemfile relies explicitly on github-pages dependency, which <em>itself</em> relies on jekyll :</p><pre>gem &quot;github-pages&quot;, group: :jekyll_plugins</pre><p>And yes, just to double check, the Gemfile.lock should show the lines that clearly show the dependency relationship between them:</p><pre>    github-pages (228)<br>      github-pages-health-check (= 1.17.9)<br>      jekyll (= 3.9.3)<br>    ...</pre><p>To recap, if one installs ruby with brew and runs brew upgrade or similar and results in ruby@3.3.0 being inadvertently installed, this can break when attempting to work with projects that rely on a specific version of jekyll, especially ones that rely on github-pages gem for deployment.</p><p>There does not appear to be any solution involving brew and ruby alone.</p><p>Upon catching the issue that the mismatch b/w the two gems caused the build error when running bundle exec jekyll serve , I immediately tried some steps, that first involved installing csv , as apparently there was a warning printed to terminal since that doesn’t come bundled in ruby 3.3.0</p><pre>$ bundle install csv</pre><p>Then I tried to upgrade all gem versions in project by running:</p><pre>$ bundle upgrade</pre><p>That didn’t help, so I tried to specify both gems explicitly:</p><pre>$ bundle update jekyll<br>$ bundle update github-pages</pre><p>Still no dice, and I was at wit’s end by now. Manually upgrading either of those gem versions specified in Gemfile.lock didn’t help either. There was a dependency conflict if I upgraded jekyll, as github-pages relied on a specific version.</p><p>I also tried deleting Gemfile.lock file completely and running bundle again, but still no dice.</p><p>In short, this was exactly what they call “dependency hell”. I cannot change either gem versions, and hence this is basically a “SNAFU!” situation. As in, there’s no solution to be had that allows me to keep the up-to-date ruby version 3.3.0 for use with my project.</p><p>I even tried uninstalling and re-installing specific ruby version 2.3.2 via <strong>homebrew</strong>, but if you can believe it there <em>still </em>was no dice:</p><pre>$ brew uninstall ruby<br>$ brew install ruby@3.2<br>$ echo &#39;export PATH=&quot;/opt/homebrew/opt/ruby@3.2/bin:$PATH&quot;&#39; &gt;&gt; ~/.zshrc<br>$ source ~/.zshrc<br>$ ruby -v<br>ruby 3.2.3<br>$ gem install bundler<br>$ bundle<br>&lt;error&gt;</pre><p>I’ll be honest, I felt like tearing my hair out at this point. Sweat started metaphorically trickling down my back. Being a ruby newb, I was about to throw in the towel, and as a last resort maybe open up an issue on the project page for one of the gems, or post about it on a forum asking other users for help.</p><p>Now that I think about it, maybe ChatGPT could have helped me. Hello ChatGPT? Can you explain this error please, and suggest me a solution &lt;<em>paste stack trace</em>&gt;.</p><p>However, thankfully, that was ultimately not needed. I found a workaround that helps me to maintain ruby version across different machines, consistently.</p><p>After researching online for quite a bit, I found out that someone was using rbenv and decided to look into what that is. Turns out, it’s something similar like pyenv for python, or basically a version management tool for a programming language.</p><p>Feeling I was on to something at this point, and shrugging my shoulders after realizing I had nothing to lose, I decided to install it after all:</p><pre>$ brew install rbenv ruby-build<br>$ echo &#39;eval &quot;$(rbenv init - zsh)&quot;&#39; &gt;&gt; ~/.zshrc<br>$ source ~/.zshrc</pre><p>That got me therbenv command, but of course I wasn’t done there yet.</p><p>I have to install my necessary previous <strong>ruby</strong> version 3.2.3 with rbenv :</p><pre>$ rbenv install 3.2.3</pre><p>After installing a version with rbenv , you can set it either <strong>globally</strong> (so that ruby version is changed regardless of your location in terminal) or <strong>locally</strong>, so it will only affect a project after you cd into its folder.</p><p>I decided to do <em>both</em>, because the default ruby version that comes bundled with my new Mac Air M2 laptop is something like 2.x , which is hella old. And I’m clearly never gonna use the built-in one.</p><pre>$ rbenv global 3.2.3<br>$ rbenv local 3.2.3</pre><p>Now we’re cooking! To double-check that we got the new <strong>ruby</strong> version installed:</p><pre>$ ruby -v<br>ruby 3.2.3 (2024-01-18 revision 52bb2ac0a6) [arm64-darwin23]</pre><p>Great! Now that’s out of the way, we want to install or upgrade bundler, and then we’re good to go.</p><pre>$ gem install bundler<br>$ bundle -v<br>Bundler version 2.5.5</pre><p>If it helps, one might need to delete and re-create Gemfile.lock by running bundle, especially if it says the gems were installed with another version of bundler :</p><pre>BUNDLED WITH<br>   2.5.4</pre><p>After that, you can run bundle exec or however you start up your project that relies on jekyll and github-pages , and then you should be all set.</p><p>If it helps, I’ve actually created an alias command jb , which serves that exact purpose in my case:</p><pre>$ which jb<br>jb: aliased to bundle exec jekyll serve -low</pre><p>It’s easy enough to add that alias in to bash or zsh shell configuration. To add it to zsh — my default interpreter — here’s what I did:</p><pre>echo &quot;alias jb=&#39;bundle exec jekyll serve -low&#39;&quot; &gt;&gt; ~/.zshenv</pre><p>Then source ~/.zshenv to reload the changes in the current terminal window.</p><p>All said, this whole ordeal took me <strong><em>&gt;1 hour</em></strong> of pointless debuggery. It was the exact opposite of fun. And, ideally not how I would have wanted to spend a Sunday morning.</p><p>However, the upshot is that the confusing <strong>ruby </strong>versioning hell along with two of the aforementioned gems — jekyll and github-pages — is now finally resolved, at long lost.</p><blockquote>Here then was a lesson learned: manage ruby versions with <strong>rbenv</strong> instead of <strong>homebrew</strong>.</blockquote><p>Praise be! Massive dependency hell solved, and headache (mostly) avoided. Hope this helps someone else out. Now go out there and get coding!</p><p>As always, thanks for reading. If you liked the article please leave a few claps so other users can find it more easily. Also feel free to give me a follow at <a href="/@ritviknag">@ritviknag</a> to stay up to date with any articles I publish.</p><p><em>Originally published at </em><a href="https://ritviknag.com/tech-tips/ruby-versioning-hell-with-jekyll-&amp;-github-pages"><em>https://ritviknag.com</em></a><em> on January 28, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fd2748bf4e1d" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>