<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ritvik Nag</title>
    <description>The latest articles on Forem by Ritvik Nag (@ritvik-nag).</description>
    <link>https://forem.com/ritvik-nag</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1268058%2F2938998a-7802-4dd5-a37b-022961c28d34.jpg</url>
      <title>Forem: Ritvik Nag</title>
      <link>https://forem.com/ritvik-nag</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ritvik-nag"/>
    <language>en</language>
    <item>
      <title>How I Automated Parking Permit Purchases at GMU with Python</title>
      <dc:creator>Ritvik Nag</dc:creator>
      <pubDate>Fri, 05 Sep 2025 03:26:56 +0000</pubDate>
      <link>https://forem.com/ritvik-nag/how-i-automated-parking-permit-purchases-at-gmu-with-python-4498</link>
      <guid>https://forem.com/ritvik-nag/how-i-automated-parking-permit-purchases-at-gmu-with-python-4498</guid>
      <description>&lt;p&gt;As a grad student at &lt;strong&gt;George Mason University (GMU)&lt;/strong&gt;, I commute to Fairfax once a week for evening classes. Like many commuters, I need parking — but only for a few hours, usually &lt;strong&gt;4–7 PM&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Buying a semester-long permit didn’t make sense for my schedule (or wallet). Instead, the &lt;strong&gt;daily parking permits&lt;/strong&gt; turned out to be cheaper and more flexible.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So, being a CS student, I did what came naturally:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;I automated the entire process.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;Every week, I needed to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the GMU parking portal.&lt;/li&gt;
&lt;li&gt;Log in with my credentials.&lt;/li&gt;
&lt;li&gt;Navigate through a bunch of menus.&lt;/li&gt;
&lt;li&gt;Select the right permit.&lt;/li&gt;
&lt;li&gt;Enter payment details.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It was the same steps every time. Why not let Python handle it?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tool
&lt;/h2&gt;

&lt;p&gt;I built a small open-source tool in Python that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automates login to the GMU parking portal up to the &lt;strong&gt;Duo Mobile 2FA step&lt;/strong&gt; (which still requires approval on your phone).&lt;/li&gt;
&lt;li&gt;Selects the correct &lt;strong&gt;daily parking permit&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Completes the checkout flow.&lt;/li&gt;
&lt;li&gt;Emails me a confirmation when done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it — no more last-minute scrambling.&lt;/p&gt;

&lt;p&gt;📽️ &lt;strong&gt;Demo Video:&lt;/strong&gt; &lt;a href="https://youtu.be/9X5lc2zZq-k" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💻 &lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/rnag/GMU-Daily-Permit-Automation" rel="noopener noreferrer"&gt;GitHub – GMU Daily Permit Automation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Breakdown
&lt;/h2&gt;

&lt;p&gt;At its core, this project uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.selenium.dev/" rel="noopener noreferrer"&gt;Selenium&lt;/a&gt;&lt;/strong&gt; – to control a browser and interact with the GMU parking site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless mode&lt;/strong&gt; – so it runs quietly in the background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Config + Environment variables&lt;/strong&gt; – to keep credentials and payment info safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task scheduling (optional)&lt;/strong&gt; – you &lt;em&gt;could&lt;/em&gt; use &lt;code&gt;cron&lt;/code&gt; (Linux/Mac) or Task Scheduler (Windows) to kick off the script automatically. I usually just run it manually before class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duo Mobile 2FA&lt;/strong&gt; – you’ll still need to approve the login from your phone. The script pauses at this step until Duo is confirmed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a simplified example of what the login step looks like in Python with Selenium:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.common.keys&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Keys&lt;/span&gt;

&lt;span class="c1"&gt;# Start the browser (headless mode can be enabled too)
&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Go to GMU parking login page
&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://gmu.t2hosted.com/Account/Portal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Enter username
&lt;/span&gt;&lt;span class="n"&gt;username_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;username_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myusername&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Enter password
&lt;/span&gt;&lt;span class="n"&gt;password_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;password_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mypassword&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;password_input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RETURN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# (From here, the script navigates menus and purchases the permit)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real project handles edge cases, payment, and confirmation, but this snippet shows the basic automation flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;A few interesting takeaways while building this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;University systems often don’t have APIs, so &lt;strong&gt;web automation&lt;/strong&gt; is sometimes the only option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security matters&lt;/strong&gt; – never hardcode sensitive info; use config files or environment variables.&lt;/li&gt;
&lt;li&gt;Even small automations can save you from recurring stress (and are great coding practice).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;If you’re at GMU (or curious about automation), you can try it out:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/rnag/GMU-Daily-Permit-Automation" rel="noopener noreferrer"&gt;GitHub Project&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;README&lt;/code&gt; has setup instructions. Just remember: &lt;strong&gt;use responsibly&lt;/strong&gt; — the tool isn’t affiliated with GMU Parking Services, and you should only automate your own account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;While Duo Mobile means this isn’t 100% hands-free, it still saves me from repeating the same web clicks every week.&lt;/p&gt;




&lt;p&gt;As always, thanks for reading. If you liked the article please add a reaction or two so other users can find it more easily. Also feel free to give me a follow at &lt;a href="https://dev.to/ritvik-nag"&gt;&lt;code&gt;ritvik-nag&lt;/code&gt;&lt;/a&gt; to stay up to date with any articles I publish.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ritviknag.com/tech-tips/how-i-automated-parking-permit-purchases-at-gmu-with-python" rel="noopener noreferrer"&gt;https://ritviknag.com&lt;/a&gt; on September 4, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>selenium</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Sleep Mode: Automating Wi-Fi &amp; Bluetooth Off at Night on Apple Devices</title>
      <dc:creator>Ritvik Nag</dc:creator>
      <pubDate>Thu, 22 May 2025 04:53:28 +0000</pubDate>
      <link>https://forem.com/ritvik-nag/sleep-mode-automating-wi-fi-bluetooth-off-at-night-on-apple-devices-44fm</link>
      <guid>https://forem.com/ritvik-nag/sleep-mode-automating-wi-fi-bluetooth-off-at-night-on-apple-devices-44fm</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;You might ask: why go through the trouble of shutting off Wi-Fi and Bluetooth automatically? It’s a fair question. &lt;a href="https://sleepreviewmag.com/sleep-health/parameters/quality/study-raises-concerns-wi-fi-device-radiation-sleep-quality/" rel="noopener noreferrer"&gt;This study&lt;/a&gt; raises concerns that Wi-Fi radiation may impact sleep quality and increase the risk of insomnia. And this &lt;a href="https://neurologicwellnessinstitute.com/can-wifi-affect-sleep-quality/" rel="noopener noreferrer"&gt;video explanation&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Shortcuts
&lt;/h2&gt;

&lt;p&gt;The first step involves opening the Shortcuts app on a Mac laptop or device. The easiest way is to hit &lt;code&gt;Cmd + Space&lt;/code&gt; and search for &lt;code&gt;Shortcuts&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The icon should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhelp.apple.com%2Fassets%2F6712D663A5C9C17B38070C34%2F6712D668A5C9C17B38070C3A%2Fen_US%2Fd230a25cb974f8908871af04caad89a1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhelp.apple.com%2Fassets%2F6712D663A5C9C17B38070C34%2F6712D668A5C9C17B38070C3A%2Fen_US%2Fd230a25cb974f8908871af04caad89a1.png" width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve created two shortcuts. One called “&lt;strong&gt;Start Morning&lt;/strong&gt;” to enable Wi-Fi/Bluetooth, and another called “&lt;strong&gt;Night Time&lt;/strong&gt;” to disable Wi-Fi/Bluetooth and put the display to sleep, as shown below. They both use the “&lt;em&gt;Set Wi-Fi&lt;/em&gt;” and “&lt;em&gt;Set Bluetooth&lt;/em&gt;” actions, which you can find using the Action Library on the right. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feepklmodp1vcop3fc71y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feepklmodp1vcop3fc71y.png" alt="Start Morning" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fandtgk0r1ao4jox56tyw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fandtgk0r1ao4jox56tyw.png" alt="Night Time" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Automator Scripts
&lt;/h2&gt;

&lt;p&gt;Next, I opened up the &lt;strong&gt;Automator&lt;/strong&gt; app — the &lt;code&gt;Cmd + Space&lt;/code&gt; works here too — and go to &lt;em&gt;File &amp;gt; New&lt;/em&gt; to create a new automation. I need to do this twice, once for each action.&lt;/p&gt;

&lt;p&gt;Search for the “&lt;strong&gt;Run Shell Script&lt;/strong&gt;”  action. Then in the settings, select &lt;strong&gt;/bin/bash&lt;/strong&gt; as the Shell, and enter this code in the bash code block:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shortcuts run "Start Morning"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, &lt;code&gt;"Start Morning"&lt;/code&gt; is the name of your first shortcut. If you named it something else, update the name here accordingly. The executable command &lt;code&gt;shortcuts&lt;/code&gt; 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. &lt;/p&gt;

&lt;p&gt;Here’s a screenshot of this — you can name this &lt;code&gt;StartMorning.app&lt;/code&gt; and save it to your &lt;code&gt;/Applications&lt;/code&gt; folder, but here you can see I’ve saved it to under my &lt;code&gt;iCloud Drive &amp;gt; Automator&lt;/code&gt; , for an important reason I’ll cover later:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn7kplr7sw3q5l4iq957x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn7kplr7sw3q5l4iq957x.png" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, go to &lt;em&gt;File &amp;gt; New&lt;/em&gt; again and search for the “&lt;strong&gt;Run Shell Script&lt;/strong&gt;”  action. Then in the settings, select &lt;strong&gt;/bin/bash&lt;/strong&gt; as the Shell as last time, and enter this code in the bash code block:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shortcuts run "Night Time"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Replacing &lt;code&gt;"Night Time"&lt;/code&gt; if you chose to use a different name for the shortcut. I’ve again saved this to my iCloud Drive, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48tthrdi5t8bdhslppw4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48tthrdi5t8bdhslppw4.png" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling with Calendar
&lt;/h2&gt;

&lt;p&gt;Regardless, the next step is to open the &lt;strong&gt;Calendar&lt;/strong&gt; app on your Mac.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;/Applications&lt;/code&gt; works fine. But in my case, I use both a &lt;em&gt;MacBook Air&lt;/em&gt; (personal) and a &lt;em&gt;MacBook Pro&lt;/em&gt; (work). I wanted the Wi-Fi and Bluetooth toggling to sync across both devices.&lt;/p&gt;

&lt;p&gt;After some trial and error, I found that Calendar events sync automatically across Apple devices — but local file paths like &lt;code&gt;/Applications&lt;/code&gt; do not. So, if the script lives in &lt;code&gt;/Applications&lt;/code&gt; 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).&lt;/p&gt;

&lt;p&gt;First thing I did was hit the &lt;strong&gt;+&lt;/strong&gt; button at the top to create a new Calendar event. I called the first one “&lt;strong&gt;Start Morning&lt;/strong&gt;”, set it to &lt;strong&gt;repeat daily&lt;/strong&gt; at &lt;strong&gt;8:00 AM&lt;/strong&gt;, 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 &lt;code&gt;alert: None&lt;/code&gt;, click the dropdown, and go to &lt;code&gt;Custom...&lt;/code&gt;, choose &lt;code&gt;Open File&lt;/code&gt; and then in the second dropdown choose &lt;code&gt;Other...&lt;/code&gt; . In the Finder window, locate and select the file location for the Automator script &lt;code&gt;StartMorning.app&lt;/code&gt;  that you saved in previous step. Next, though technically not needed, I clicked the dropdown to alert &lt;strong&gt;15 Minutes before&lt;/strong&gt; and changed it to &lt;strong&gt;At time of event&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can see the event which turns on Wi-Fi/Bluetooth here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F544w4vv4brmh9lgm8699.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F544w4vv4brmh9lgm8699.png" alt="Start Morning Calendar Event" width="580" height="668"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, do the same for the “&lt;strong&gt;Night Time&lt;/strong&gt;” event! I set it to &lt;strong&gt;repeat daily&lt;/strong&gt; at &lt;strong&gt;11:00 PM&lt;/strong&gt; based on my sleep schedule, but feel free to adjust accordingly based on yours! I followed the same steps as above. I chose &lt;code&gt;Open File&lt;/code&gt; and then in the second dropdown choose &lt;code&gt;Other...&lt;/code&gt; . In the Finder window, locate and select the file location for the Automator script &lt;code&gt;NightTime.app&lt;/code&gt;  that you saved in previous step. &lt;/p&gt;

&lt;p&gt;You can see the final event which switches off Wi-Fi/Bluetooth here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8em9stbk4yx5h0ymdas.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8em9stbk4yx5h0ymdas.png" alt="Night Time Calendar Event" width="676" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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!&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling on iPhone
&lt;/h2&gt;

&lt;p&gt;Is it possible to schedule the same on an iPhone?&lt;/p&gt;

&lt;p&gt;You betcha! It’s actually a bit easier, since you only have to set Airplane Mode, rather than Wi-Fi and Bluetooth individually.&lt;/p&gt;

&lt;p&gt;I opened the “&lt;strong&gt;Shortcuts&lt;/strong&gt;” app on my iPhone. I created my first automation, which runs on a schedule — at &lt;strong&gt;11:00 PM, daily&lt;/strong&gt; in my case — and the action I chose is &lt;strong&gt;Set Airplane Mode &amp;gt; ON&lt;/strong&gt;. This is shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpbj3auuujd8sjuwefvu.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpbj3auuujd8sjuwefvu.jpeg" width="800" height="945"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;Set Airplane Mode &amp;gt; OFF&lt;/strong&gt;. This is shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljd82b1sy3ut3yhvns9n.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljd82b1sy3ut3yhvns9n.jpeg" width="800" height="1009"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling on Apple Watch
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Hope this was helpful — and if you end up building something similar, I’d love to hear how it goes.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;




&lt;p&gt;As always, thanks for reading. If you liked the article please add a reaction or two so other users can find it more easily. Also feel free to give me a follow at &lt;a href="https://dev.to/ritvik-nag"&gt;&lt;code&gt;ritvik-nag&lt;/code&gt;&lt;/a&gt; to stay up to date with any articles I publish.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ritviknag.com/tech-tips/sleep-mode-automating-wi-fi-&amp;amp;-bluetooth-off-at-night-on-apple-devices" rel="noopener noreferrer"&gt;https://ritviknag.com&lt;/a&gt; on May 22, 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>apple</category>
      <category>automation</category>
      <category>productivity</category>
      <category>health</category>
    </item>
    <item>
      <title>Navigating Medium API with JavaScript in 2024</title>
      <dc:creator>Ritvik Nag</dc:creator>
      <pubDate>Sun, 18 Feb 2024 02:56:06 +0000</pubDate>
      <link>https://forem.com/ritvik-nag/medium-api-with-javascript-in-2024-4h5a</link>
      <guid>https://forem.com/ritvik-nag/medium-api-with-javascript-in-2024-4h5a</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I'm trying to integrate Medium blogging into my personal site at &lt;em&gt;myname.com&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;.md&lt;/code&gt; file -- be posted to the two other blogging sites I'm currently signed up on, &lt;a href="https://medium.com" rel="noopener noreferrer"&gt;&lt;code&gt;medium.com&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://dev.to"&gt;&lt;code&gt;dev.to&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publish and/or update a post for a user&lt;/li&gt;
&lt;li&gt;Retrieve a list of posts for a user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Turns out, only the first point is currently possible via the &lt;a href="https://developers.medium.com/" rel="noopener noreferrer"&gt;Official Medium API&lt;/a&gt;, and that too, it's only &lt;em&gt;partially&lt;/em&gt; satisfied -- there is currently no route via the API to update a post on a Medium. One can only &lt;strong&gt;create&lt;/strong&gt; a new post on Medium via the API, but that's about it.&lt;/p&gt;

&lt;p&gt;With the Official &lt;a href="https://github.com/Medium/medium-api-docs" rel="noopener noreferrer"&gt;&lt;strong&gt;Medium API&lt;/strong&gt;&lt;/a&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Auto-Update a Medium Post
&lt;/h3&gt;

&lt;p&gt;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 &lt;code&gt;PUT&lt;/code&gt; request - for way too long, and didn't get anywhere with that at all. It's a real shame that we can only publish new posts via the API, and that it's not possible to update an existing post via an API.&lt;/p&gt;

&lt;p&gt;In fact, this proved the impetus or drive for me to publish an online web tool that easily translates posts from &lt;em&gt;markdown&lt;/em&gt; to &lt;em&gt;medium&lt;/em&gt;, which I have published on my website: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://ritviknag.com/markdown-to-medium" rel="noopener noreferrer"&gt;https://ritviknag.com/markdown-to-medium&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I made this above tool for myself, so I can take my personal blog posts, written in &lt;code&gt;.md&lt;/code&gt; files, paste them in there and copy the output to Medium when using the editor to update an existing post.&lt;/p&gt;




&lt;p&gt;Anyway, slight detour there...&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;.md&lt;/code&gt; file -- on my website, I want to publish a new post to Medium, but &lt;em&gt;only&lt;/em&gt; 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's even possible!&lt;/p&gt;

&lt;h2&gt;
  
  
  My Search
&lt;/h2&gt;

&lt;p&gt;My search led me to Google search for something along the lines of "&lt;strong&gt;medium api get all user posts&lt;/strong&gt;", which led me more or less to this somewhat informative article on StackOverflow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://stackoverflow.com/q/36097527/10237506" rel="noopener noreferrer"&gt;https://stackoverflow.com/q/36097527/10237506&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As the accepted answer says, the Medium API is only intended for &lt;em&gt;Publishing&lt;/em&gt; and is not intended to retrieve posts.&lt;/p&gt;

&lt;p&gt;A workaround that is suggested is to simply use the RSS feed that Medium offers, as such:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;https://medium.com/feed/@username&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, here is the RSS feed for my user:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://medium.com/feed/@ritviknag" rel="noopener noreferrer"&gt;https://medium.com/feed/@ritviknag&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One can simply get the RSS feed via &lt;code&gt;GET&lt;/code&gt;, then if it's needed in JSON format just use an NPM module like &lt;code&gt;rss-to-json&lt;/code&gt; and we're good to go.&lt;/p&gt;

&lt;p&gt;However, there is an issue with this approach, as another answer in the same SO post confirms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[...] has a limit of the &lt;strong&gt;latest 10 posts&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, there is a limit of only retrieving 10 posts for a given user using the RSS feed option.&lt;/p&gt;

&lt;p&gt;Looks like we need another solution!&lt;/p&gt;

&lt;h2&gt;
  
  
  Unofficial Medium API
&lt;/h2&gt;

&lt;p&gt;I would be remiss if I didn't mention an alternate approach.&lt;/p&gt;

&lt;p&gt;There is an &lt;strong&gt;Unofficial&lt;/strong&gt; Medium API that someone has come up with. This is the link to it below:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://mediumapi.com" rel="noopener noreferrer"&gt;https://mediumapi.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unfortunately, it's not an insignificant commitment to sign up with that API. Take a look below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ev0tla3sidq75ab1ikj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ev0tla3sidq75ab1ikj.png" alt="Unofficial Medium API Subscription" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As noted, there appears to be a subscription tier for using that API. &lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a RapidAPI key for &lt;code&gt;mediumapi.com&lt;/code&gt;, or directing users of the library to sign up on &lt;em&gt;another&lt;/em&gt; site and acquire a RapidAPI key for requests to &lt;code&gt;mediumapi.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rate limiting my library at &lt;strong&gt;150 requests / month&lt;/strong&gt;, assuming I want to hardcode and provide users with my RapidAPI key, in order to provide convenience and promote adoption of said library.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, this is likewise a disaster, albeit of different proportions. There is &lt;strong&gt;no way&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Thus, the unavoidable road to the &lt;strong&gt;&lt;em&gt;DIY project&lt;/em&gt;&lt;/strong&gt;, begins...&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Eureka!" Moment
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;Network&lt;/code&gt; tab. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;graphql&lt;/code&gt; endpoints, in particular, stood out to me:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc701gayh6eam0bkcuke4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc701gayh6eam0bkcuke4.png" alt="Chrome Dev GraphQL" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If curious, the URL to that is &lt;code&gt;https://medium.com/_/graphql&lt;/code&gt;, and it's an HTTP &lt;code&gt;POST&lt;/code&gt; request that gets sent.&lt;/p&gt;

&lt;p&gt;Shortly after, I fired up Google search again and this time inputted keywords "&lt;strong&gt;how to use medium api graphql to fetch all user posts&lt;/strong&gt;".&lt;/p&gt;

&lt;p&gt;The result was this highly informative post on using Medium's GraphQL endpoint to retrieve Medium posts for a user:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://medium.com/@mike820324/web-crawler-getting-medium-post-a6e52fd36fd6" rel="noopener noreferrer"&gt;https://medium.com/@mike820324/web-crawler-getting-medium-post-a6e52fd36fd6&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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 &lt;a href="https://npmjs.com/" rel="noopener noreferrer"&gt;&lt;code&gt;npmjs.com&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The hardest part for me was how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modify the GraphQL query to only return user post titles&lt;/li&gt;
&lt;li&gt;Paginate through the results, as GraphQL appears to have a limit of &lt;code&gt;25&lt;/code&gt; results (posts) per request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I managed to figure that out, even though it took me a long while.&lt;/p&gt;

&lt;p&gt;By the way, I adapted the code from &lt;a href="https://github.com/redco/medium-sdk-nodejs" rel="noopener noreferrer"&gt;a fork of the Medium SDK for Node.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I made my own changes, and a &lt;strong&gt;&lt;em&gt;lot&lt;/em&gt;&lt;/strong&gt; of updates along the way.&lt;/p&gt;

&lt;p&gt;Simply said, this was the very first &lt;em&gt;TypeScript NPM package&lt;/em&gt; I had ever written. So there was a lot of learning curve.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;The work was split into a few main parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Porting over existing Medium SDK code from &lt;em&gt;Node.js&lt;/em&gt; to &lt;em&gt;TypeScript&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The hurdle that come with publishing a TypeScript NPM package, such as setting up &lt;code&gt;tsconfig.json&lt;/code&gt; to allow support for &lt;a href="https://www.sensedeep.com/blog/posts/2021/how-to-create-single-source-npm-module.html" rel="noopener noreferrer"&gt;both ESM and CommonJS syntax&lt;/a&gt; simultaneously.&lt;/li&gt;
&lt;li&gt;Integrating pagination and GraphQL into the codebase.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project is the direct result of my hard work and effort:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rnag/medium-sdk-ts" rel="noopener noreferrer"&gt;https://github.com/rnag/medium-sdk-ts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is the package on the NPM registry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/medium-sdk-ts" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/medium-sdk-ts&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;

&lt;p&gt;The package can be installed with &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm&lt;/a&gt;, or a package manager of choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i medium-sdk-ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Import
&lt;/h3&gt;

&lt;p&gt;The library supports importing via the newer, more modern &lt;em&gt;ES Module (ESM)&lt;/em&gt; syntax prevalent in TypeScript code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;MediumClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PostContentFormat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PostPublishStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium-sdk-ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, for &lt;em&gt;CommonJS&lt;/em&gt; syntax in Node.js, using &lt;code&gt;require&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;MediumClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;PostContentFormat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;PostPublishStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium-sdk-ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;Now create a client for the &lt;strong&gt;Medium SDK&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Access Token is optional, can also be set&lt;/span&gt;
&lt;span class="c1"&gt;// as environment variable `MEDIUM_ACCESS_TOKEN`&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;medium&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MediumClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_ACCESS_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retrieve User Details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Publish a new &lt;em&gt;Draft Post&lt;/em&gt; under your user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createPost&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Only `title` and `content` are required to create a post&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'A new post',&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h1&amp;gt;A New Post&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;This is my new post.&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Optional below&lt;/span&gt;
    &lt;span class="na"&gt;contentFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PostContentFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Defaults to `markdown`&lt;/span&gt;
    &lt;span class="na"&gt;publishStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PostPublishStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DRAFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Defaults to `draft`&lt;/span&gt;
    &lt;span class="c1"&gt;// tags: ["my", "tags"],&lt;/span&gt;
    &lt;span class="c1"&gt;// canonicalUrl: "https://my-url.com",&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`New Post: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retrieve a User's Published Posts (using GraphQL endpoint, which does not require an access token):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get User's Published Posts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User Post: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, to simply retrieve the user's post titles only -- note that this results in a slightly more simplified GraphQL query being sent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get User's Published Posts (Title Only)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postTitles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPostTitles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User Post Titles: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postTitles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all for now!&lt;/p&gt;

&lt;p&gt;I hope this post was informative. Feel free to play around with this NPM package I have published.&lt;/p&gt;

&lt;p&gt;Again, here is the source code for it: &lt;a href="https://github.com/rnag/medium-sdk-ts" rel="noopener noreferrer"&gt;https://github.com/rnag/medium-sdk-ts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this library has proved useful in your automation efforts, please let me know via the comments down below, and feel free to also &lt;strong&gt;star&lt;/strong&gt; the project on GitHub if you'd like.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;As always, thanks for reading. If you liked the article please add a reaction or two so other users can find it more easily. Also feel free to give me a follow at &lt;a href="https://dev.to/ritvik-nag"&gt;&lt;code&gt;ritvik-nag&lt;/code&gt;&lt;/a&gt; to stay up to date with any articles I publish.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ritviknag.com/tech-tips/navigating-medium-api-with-javascript-in-2024" rel="noopener noreferrer"&gt;https://ritviknag.com&lt;/a&gt; on February 17, 2024.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>medium</category>
      <category>graphql</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Mount Current Working Directory To Your Docker Container</title>
      <dc:creator>Ritvik Nag</dc:creator>
      <pubDate>Fri, 09 Feb 2024 17:25:31 +0000</pubDate>
      <link>https://forem.com/ritvik-nag/how-to-mount-current-working-directory-to-your-docker-container-5h6h</link>
      <guid>https://forem.com/ritvik-nag/how-to-mount-current-working-directory-to-your-docker-container-5h6h</guid>
      <description>&lt;p&gt;My team has been building out a new web app in &lt;em&gt;Express.js&lt;/em&gt;, and for local deployment and testing we use &lt;code&gt;nodemon&lt;/code&gt;, &lt;code&gt;babel&lt;/code&gt;, and Docker.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;nodemon&lt;/code&gt; can listen for local file changes when we develop, and automatically reload or restart the (containerized) web server as needed.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://medium.com/@kale.miller96/how-to-mount-your-current-working-directory-to-your-docker-container-in-windows-74e47fa104d7" rel="noopener noreferrer"&gt;&lt;strong&gt;How To Mount Your Current Working Directory To Your Docker Container In Windows&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As I and my team are developing on an (Apple Silicon-based) Mac, we don't care too much about the &lt;code&gt;docker run&lt;/code&gt; command being compatible between Mac and Windows overmuch.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Theory
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;Dockerfile&lt;/code&gt; - we use the same one for development and production - looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start from the Node.js image for AMD64 architecture&lt;/span&gt;
FROM &lt;span class="nt"&gt;--platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux/amd64 node:latest

&lt;span class="c"&gt;# Set the working directory for your Node.js app&lt;/span&gt;
WORKDIR /usr/app

ARG PORT
ARG NODE_ENV

ENV &lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PORT&lt;/span&gt;
ENV &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$NODE_ENV&lt;/span&gt;

&lt;span class="c"&gt;# Some installation steps&lt;/span&gt;
...

&lt;span class="c"&gt;# Copy your Node.js application files and the startup script&lt;/span&gt;
COPY &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
COPY start.sh &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Make the startup script executable&lt;/span&gt;
RUN &lt;span class="nb"&gt;chmod&lt;/span&gt; +x ./start.sh

&lt;span class="c"&gt;# Install Node.js dependencies&lt;/span&gt;
RUN npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev

&lt;span class="c"&gt;# Expose the port your app runs on&lt;/span&gt;
EXPOSE &lt;span class="nv"&gt;$PORT&lt;/span&gt;
EXPOSE 80

&lt;span class="c"&gt;# Command to run the startup script&lt;/span&gt;
CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./start.sh"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have a custom &lt;code&gt;start.sh&lt;/code&gt; script we use to start our web server. This basically calls &lt;code&gt;npm run start &amp;amp;&lt;/code&gt; within it - e.g. our &lt;strong&gt;npm&lt;/strong&gt; script &lt;code&gt;start&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The above &lt;code&gt;Dockerfile&lt;/code&gt; basically expects arguments to be passed in during the Docker &lt;code&gt;build&lt;/code&gt; step.&lt;/p&gt;

&lt;p&gt;This allows us to pass in environment variables (and Bash script values) such as the &lt;code&gt;PORT&lt;/code&gt; for the web server, and &lt;code&gt;NODE_ENV&lt;/code&gt; for the web app - such as &lt;code&gt;dev&lt;/code&gt; or &lt;code&gt;production&lt;/code&gt; .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tag&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TAG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That is, we only want to mount a local directory to the container into the location at &lt;code&gt;/usr/app&lt;/code&gt; - the &lt;code&gt;WORKDIR&lt;/code&gt; from the Docker instructions above - when we are in development mode, and we &lt;em&gt;don't&lt;/em&gt; want to mount anything when we are building a production Docker image, to later ship to &lt;a href="https://www.freecodecamp.org/news/build-and-push-docker-images-to-aws-ecr/" rel="noopener noreferrer"&gt;Amazon ECR&lt;/a&gt; for example.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We would rather not update &lt;code&gt;Dockerfile&lt;/code&gt; to mount a volume or a local folder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not necessary (or desirable) to mount local folder to a volume when running &lt;code&gt;docker build&lt;/code&gt; to create an image from a &lt;code&gt;Dockerfile&lt;/code&gt; . This is because need to create separate &lt;code&gt;docker build&lt;/code&gt; commands for development and non-development, which is harder to management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ideal solution will be to update only &lt;code&gt;docker run&lt;/code&gt; command. Which we only run locally anyway. We do not need to execute &lt;code&gt;docker run&lt;/code&gt; for production, as generally for production we just push the final image directly to ECR via a shell command.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;Now that theory is knocked out of the way, we understand that we prefer to update &lt;code&gt;docker run&lt;/code&gt; command syntax, rather than touch &lt;code&gt;docker build&lt;/code&gt; or &lt;code&gt;Dockerfile&lt;/code&gt; itself.&lt;/p&gt;

&lt;p&gt;Also, it is important here to go over the problem statement once more.&lt;/p&gt;

&lt;p&gt;The goal that we are trying to solve is &lt;strong&gt;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.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The host's current directory is the same that we are copying over to &lt;code&gt;/usr/app&lt;/code&gt; on the Docker container, as specified in the above &lt;code&gt;Dockerfile&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;nodemon&lt;/code&gt; on the Docker container to listen for file changes on the &lt;code&gt;/usr/app&lt;/code&gt; location on the container side. We can instead trick Docker to listen for local file changes instead - let us see how this is possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Online Results
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.docker.com/storage/bind-mounts/" rel="noopener noreferrer"&gt;https://docs.docker.com/storage/bind-mounts/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/@kale.miller96/how-to-mount-your-current-working-directory-to-your-docker-container-in-windows-74e47fa104d7" rel="noopener noreferrer"&gt;https://medium.com/@kale.miller96/how-to-mount-your-current-working-directory-to-your-docker-container-in-windows-74e47fa104d7&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stackoverflow.com/q/23439126/10237506" rel="noopener noreferrer"&gt;https://stackoverflow.com/q/23439126/10237506&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, this appears to be a question of &lt;code&gt;--volume&lt;/code&gt; vs. &lt;code&gt;-- mount&lt;/code&gt; with a &lt;code&gt;type:bind&lt;/code&gt;. The &lt;a href="https://docs.docker.com/storage/bind-mounts/#differences-between--v-and---mount-behavior" rel="noopener noreferrer"&gt;Docker docs&lt;/a&gt; (&lt;em&gt;see first link above)&lt;/em&gt; mention that using &lt;code&gt;--volume&lt;/code&gt; is the old style, and there is one important difference in behavior. Hence, using &lt;code&gt;--mount&lt;/code&gt; explicitly going forward seems to be the safer (and more future-proof) approach.&lt;/p&gt;

&lt;p&gt;However, note that either approach (&lt;code&gt;--volume&lt;/code&gt; or &lt;code&gt;--mount&lt;/code&gt;) should work for our use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask an AI Assistant
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;Yes, Docker mount local drive to listen for file changes should certainly be possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mounting a Host Directory in a Docker Container:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You can use the &lt;code&gt;-v&lt;/code&gt; flag with the &lt;code&gt;docker run&lt;/code&gt; command to mount a local directory into a container. For example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-v&lt;/span&gt; /path/to/host/directory:/path/in/container my_image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Replace &lt;code&gt;/path/to/host/directory&lt;/code&gt; with the actual path to your local directory and &lt;code&gt;/path/in/container&lt;/code&gt; with the desired path inside the container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This approach ensures that any updates made in the host directory are reflected within the container.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Wow, that wasn't too bad. It got us more than halfway there. Honestly, if we want to use the legacy &lt;code&gt;--volume&lt;/code&gt; option, we can just go with the AI suggestion. But to future-proof it, we can make a small tweak and use &lt;code&gt;--mount&lt;/code&gt; instead, as we'll see below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trick
&lt;/h2&gt;

&lt;p&gt;I've found the best, future-proof method is to use the &lt;code&gt;--mount type=bind&lt;/code&gt; flag when running the &lt;code&gt;docker run&lt;/code&gt; command. With this command, you can attach the local directory to your docker container at runtime instead of during the build process.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--mount type=bind&lt;/code&gt; command takes a single parameter formatted like so:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;src=&amp;lt;host directory&amp;gt;,dst=&amp;lt;target directory&amp;gt;&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we want to &lt;em&gt;&lt;strong&gt;exclude&lt;/strong&gt;&lt;/em&gt; any sub-folders in our &lt;code&gt;&amp;lt;host directory&amp;gt;&lt;/code&gt; when mounting the local folder to the Docker container, we need to pass &lt;code&gt;--mount type=volume&lt;/code&gt; with the full path to that sub-folder, and exclude the &lt;code&gt;src&lt;/code&gt; argument, as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;dst=&amp;lt;host directory to not mount&amp;gt;&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;It's easier to understand the trick when put into practice.&lt;/p&gt;

&lt;p&gt;Putting it all together, here I've created a scenario where I would like to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Mount my current working directory (&lt;code&gt;/Users/rnag/Git-Projects/Work/my_project&lt;/code&gt;) into the &lt;code&gt;alpine:latest&lt;/code&gt; image at the &lt;code&gt;/usr/app&lt;/code&gt; location in the container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make the &lt;code&gt;run&lt;/code&gt; command compatible for Apple-silicon-based Mac's, with &lt;code&gt;--platform linux/amd64&lt;/code&gt; .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Exclude (skip) copying over the &lt;code&gt;node_modules&lt;/code&gt; sub-folder in my current working directory, when mounting my current working directory &lt;code&gt;.&lt;/code&gt; to the &lt;code&gt;/usr/app&lt;/code&gt; location in the container.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We would do that as so:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;docker run -it --platform linux/amd64 --mount type=bind,src=.,dst=/usr/app --mount type=volume,dst=/usr/app/node_modules alpine:latest&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or, prettified a bit more, for all you neat freaks (like me):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,src&lt;span class="o"&gt;=&lt;/span&gt;.,dst&lt;span class="o"&gt;=&lt;/span&gt;/usr/app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;volume,dst&lt;span class="o"&gt;=&lt;/span&gt;/usr/app/node_modules &lt;span class="se"&gt;\&lt;/span&gt;
  alpine:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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!&lt;/p&gt;

&lt;p&gt;In short, this will solve it for us, when we are running &lt;code&gt;nodemon&lt;/code&gt; or similar on the Docker container to listen for file changes on the &lt;code&gt;/usr/app&lt;/code&gt; location on the container side. We have successfully tricked Docker to listen for local file changes instead - what a win!&lt;/p&gt;




&lt;p&gt;As always, thanks for reading. If you liked the article please add a reaction or two so other users can find it more easily. Also feel free to give me a follow at &lt;a href="https://dev.to/ritvik-nag"&gt;&lt;code&gt;ritvik-nag&lt;/code&gt;&lt;/a&gt; to stay up to date with any articles I publish.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ritviknag.com/tech-tips/how-to-mount-current-working-directory-to-your-docker-container" rel="noopener noreferrer"&gt;https://ritviknag.com&lt;/a&gt; on February 9, 2024.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>mac</category>
      <category>express</category>
      <category>node</category>
    </item>
    <item>
      <title>Ruby (Versioning) Hell with Jekyll &amp; GitHub Pages</title>
      <dc:creator>Ritvik Nag</dc:creator>
      <pubDate>Mon, 29 Jan 2024 01:21:56 +0000</pubDate>
      <link>https://forem.com/ritvik-nag/ruby-versioning-hell-with-jekyll-github-pages-2l9l</link>
      <guid>https://forem.com/ritvik-nag/ruby-versioning-hell-with-jekyll-github-pages-2l9l</guid>
      <description>&lt;p&gt;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 &lt;code&gt;ruby&lt;/code&gt; newbie.&lt;/p&gt;

&lt;p&gt;That is, I've never cared to learn &lt;code&gt;ruby&lt;/code&gt; before. Also, perhaps as a direct result of that, I am a little taken aback when a project asks me to install &lt;code&gt;ruby&lt;/code&gt; and use tools that it provides such as &lt;code&gt;bundler&lt;/code&gt; , because the assumption there is that I know what I am doing.&lt;/p&gt;

&lt;p&gt;Just to set the record straight, I don't know what I am doing when it comes to &lt;em&gt;anything&lt;/em&gt; &lt;code&gt;ruby&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;Recently, a lot of stuff blew up and hell broke loose, when I inadvertently ran &lt;code&gt;brew upgrade&lt;/code&gt; on my new Mac to update (or upgrade?) packages installed by &lt;strong&gt;homebrew&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But alas, I did not take &lt;code&gt;ruby&lt;/code&gt; into account. A while back, I had installed &lt;code&gt;ruby&lt;/code&gt; with homebrew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;brew&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Homebrew, without my permission, apparently took it upon itself to upgrade the bundled &lt;code&gt;ruby&lt;/code&gt; version - from 3.2.3 to 3.3.0.&lt;/p&gt;

&lt;p&gt;Hell broke lose when I &lt;code&gt;cd&lt;/code&gt;'d into my project repo that I use for &lt;a href="https://github.com/rnag/rnag.github.io" rel="noopener noreferrer"&gt;my website&lt;/a&gt;, and ran &lt;code&gt;bundle&lt;/code&gt; to install fresh dependencies - which was needed, since &lt;code&gt;ruby&lt;/code&gt; version was upgraded, so it was a fresh environment with no gems available.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error I received was something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/&amp;lt;user&amp;gt;/somewhere &lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;jekyll serve
jekyll 3.9.3 | Error:  undefined method &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="s1"&gt;' for nil
/usr/local/Cellar/ruby/3.3.0/lib/ruby/3.3.0/logger.rb:384:in `level'&lt;/span&gt;: undefined method &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="s1"&gt;' for nil (NoMethodError)

    @level_override[Fiber.current] || @level
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This indeed was the specific error message I saw:&lt;br&gt;
 &lt;code&gt;Error: undefined method '[]' for nil&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Searching online, you can see a whole slew of posts that mention this error, and apparently it only happens with ruby &lt;code&gt;3.3.0&lt;/code&gt; and the &lt;code&gt;jekyll&lt;/code&gt; version &lt;code&gt;3.9.3&lt;/code&gt; that comes bundled with &lt;code&gt;github-pages&lt;/code&gt; .&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/77851863/bundle-exec-jekyll-serve-not-working-locally" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/77851863/bundle-exec-jekyll-serve-not-working-locally&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://talk.jekyllrb.com/t/unable-to-install-jekyll-on-m3-macbook/8836" rel="noopener noreferrer"&gt;https://talk.jekyllrb.com/t/unable-to-install-jekyll-on-m3-macbook/8836&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://talk.jekyllrb.com/t/error-when-executing-bundle-install/8822" rel="noopener noreferrer"&gt;https://talk.jekyllrb.com/t/error-when-executing-bundle-install/8822&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hence, while it might not bear repeating, it helps to clarify - upgrading &lt;code&gt;jeykll&lt;/code&gt; version for a project is a &lt;strong&gt;no-go&lt;/strong&gt;, especially when the project &lt;code&gt;Gemfile&lt;/code&gt; relies explicitly on &lt;code&gt;github-pages&lt;/code&gt; dependency, which &lt;em&gt;itself&lt;/em&gt; relies on &lt;code&gt;jekyll&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s"&gt;"github-pages"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;jekyll_plugins&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And yes, just to double check, the &lt;code&gt;Gemfile.lock&lt;/code&gt; should show the lines that clearly show the dependency relationship between the&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;    &lt;span class="nt"&gt;github-pages&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;228&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="nt"&gt;github-pages-health-check&lt;/span&gt; &lt;span class="o"&gt;(=&lt;/span&gt; &lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="nc"&gt;.17.9&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="nt"&gt;jekyll&lt;/span&gt; &lt;span class="o"&gt;(=&lt;/span&gt; &lt;span class="nt"&gt;3&lt;/span&gt;&lt;span class="nc"&gt;.9.3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To recap, if one installs &lt;code&gt;ruby&lt;/code&gt; with &lt;code&gt;brew&lt;/code&gt; and runs &lt;code&gt;brew upgrade&lt;/code&gt; or similar and results in &lt;code&gt;ruby@3.3.0&lt;/code&gt; being inadvertently installed, this can break when attempting to work with projects that rely on a specific version of &lt;code&gt;jekyll&lt;/code&gt;, especially ones that rely on &lt;code&gt;github-pages&lt;/code&gt; gem for deployment.&lt;/p&gt;

&lt;p&gt;There does not appear to be any solution involving &lt;code&gt;brew&lt;/code&gt; and &lt;code&gt;ruby&lt;/code&gt; alone.&lt;/p&gt;

&lt;p&gt;Upon catching the issue that the mismatch b/w the two gems caused the build error when running &lt;code&gt;bundle exec jekyll serve&lt;/code&gt; , I immediately tried some steps, that first involved installing &lt;code&gt;csv&lt;/code&gt; , as apparently there was a warning printed to terminal since that doesn't come bundled in ruby 3.3.0&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I tried to upgrade all &lt;code&gt;gem&lt;/code&gt; versions in project by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="n"&gt;upgrade&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That didn't help, so I tried to specify both gems explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;jekyll&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still no dice, and I was at wit's end by now. Manually upgrading either of those &lt;code&gt;gem&lt;/code&gt; versions specified in &lt;code&gt;Gemfile.lock&lt;/code&gt; didn't help either. There was a dependency conflict if I upgraded &lt;code&gt;jekyll&lt;/code&gt;, as &lt;code&gt;github-pages&lt;/code&gt; relied on a specific version.&lt;/p&gt;

&lt;p&gt;I also tried deleting &lt;code&gt;Gemfile.lock&lt;/code&gt; file completely and running &lt;code&gt;bundle&lt;/code&gt; again, but still no dice.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;ruby&lt;/code&gt; version 3.3.0 for use with my project.&lt;/p&gt;

&lt;p&gt;I even tried uninstalling and re-installing specific ruby version 2.3.2 via &lt;strong&gt;homebrew&lt;/strong&gt;, but if you can believe it there &lt;em&gt;still&lt;/em&gt; was no dice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew uninstall ruby
&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;ruby@3.2
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="/opt/homebrew/opt/ruby@3.2/bin:$PATH"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;span class="nv"&gt;$ &lt;/span&gt;ruby &lt;span class="nt"&gt;-v&lt;/span&gt;
ruby 3.2.3
&lt;span class="nv"&gt;$ &lt;/span&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;bundler
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle
&amp;lt;error&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll be honest, I felt like tearing my hair out at this point. Sweat started metaphorically trickling down my back. Being a &lt;code&gt;ruby&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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 &amp;lt;&lt;em&gt;paste stack trace&lt;/em&gt;&amp;gt;.&lt;/p&gt;

&lt;p&gt;However, thankfully, that was ultimately not needed. I found a workaround that helps me to maintain &lt;code&gt;ruby&lt;/code&gt; version across different machines, consistently.&lt;/p&gt;

&lt;p&gt;After researching online for quite a bit, I found out that someone was using &lt;code&gt;rbenv&lt;/code&gt; and decided to look into what that is. Turns out, it's something similar like &lt;code&gt;pyenv&lt;/code&gt; for &lt;code&gt;python&lt;/code&gt;, or basically a version management tool for a programming language.&lt;/p&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;rbenv ruby-build
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'eval "$(rbenv init - zsh)"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That got me the&lt;code&gt;rbenv&lt;/code&gt; command, but of course I wasn't done there yet.&lt;/p&gt;

&lt;p&gt;I have to install my necessary previous &lt;strong&gt;ruby&lt;/strong&gt; version &lt;code&gt;3.2.3&lt;/code&gt; with &lt;code&gt;rbenv&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;rbenv&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing a version with &lt;code&gt;rbenv&lt;/code&gt; , you can set it either &lt;strong&gt;globally&lt;/strong&gt; (so that &lt;code&gt;ruby&lt;/code&gt; version is changed regardless of your location in terminal) or &lt;strong&gt;locally&lt;/strong&gt;, so it will only affect a project after you &lt;code&gt;cd&lt;/code&gt; into its folder.&lt;/p&gt;

&lt;p&gt;I decided to do &lt;em&gt;both&lt;/em&gt;, because the default &lt;code&gt;ruby&lt;/code&gt; version that comes bundled with my new Mac Air M2 laptop is something like &lt;code&gt;2.x&lt;/code&gt; , which is hella old. And I'm clearly never gonna use the built-in one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rbenv global 3.2.3
&lt;span class="nv"&gt;$ &lt;/span&gt;rbenv &lt;span class="nb"&gt;local &lt;/span&gt;3.2.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're cooking! To double-check that we got the new &lt;strong&gt;ruby&lt;/strong&gt; version installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="nt"&gt;ruby&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="nt"&gt;ruby&lt;/span&gt; &lt;span class="nt"&gt;3&lt;/span&gt;&lt;span class="nc"&gt;.2.3&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;2024-01-18&lt;/span&gt; &lt;span class="nt"&gt;revision&lt;/span&gt; &lt;span class="nt"&gt;52bb2ac0a6&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;arm64-darwin23&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now that's out of the way, we want to install or upgrade &lt;code&gt;bundler&lt;/code&gt;, and then we're good to go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;bundler&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="no"&gt;Bundler&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it helps, one might need to delete and re-create&lt;code&gt;Gemfile.lock&lt;/code&gt; by running &lt;code&gt;bundle&lt;/code&gt;, especially if it says the gems were installed with another version of &lt;code&gt;bundler&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;BUNDLED&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt;
   &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, you can run &lt;code&gt;bundle exec&lt;/code&gt; or however you start up your project that relies on &lt;code&gt;jekyll&lt;/code&gt; and &lt;code&gt;github-pages&lt;/code&gt; , and then you should be all set.&lt;/p&gt;

&lt;p&gt;If it helps, I've actually created an alias command &lt;code&gt;jb&lt;/code&gt; , which serves that exact purpose in my case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;which jb
jb: aliased to bundle &lt;span class="nb"&gt;exec &lt;/span&gt;jekyll serve &lt;span class="nt"&gt;-low&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's easy enough to add that alias in to &lt;code&gt;bash&lt;/code&gt; or &lt;code&gt;zsh&lt;/code&gt; shell configuration. To add it to &lt;code&gt;zsh&lt;/code&gt; - my default interpreter - here's what I did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"alias jb='bundle exec jekyll serve -low'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;source ~/.zshenv&lt;/code&gt; to reload the changes in the current terminal window.&lt;/p&gt;

&lt;p&gt;All said, this whole ordeal took me &lt;em&gt;&lt;strong&gt;&amp;gt;1 hour&lt;/strong&gt;&lt;/em&gt; of pointless debuggery. It was the exact opposite of fun. And, ideally not how I would have wanted to spend a Sunday morning.&lt;/p&gt;

&lt;p&gt;However, the upshot is that the confusing &lt;strong&gt;ruby&lt;/strong&gt; versioning hell along with two of the aforementioned gems - &lt;code&gt;jekyll&lt;/code&gt; and &lt;code&gt;github-pages&lt;/code&gt; - is now finally resolved, at long lost.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here then was a lesson learned: manage &lt;code&gt;ruby&lt;/code&gt; versions with &lt;strong&gt;rbenv&lt;/strong&gt; instead of &lt;strong&gt;homebrew&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Praise be! Massive dependency hell solved, and headache (mostly) avoided. Hope this helps someone else out. Now go out there and get coding!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://ritviknag.com/tech-tips/ruby-versioning-hell-with-jekyll-&amp;amp;-github-pages" rel="noopener noreferrer"&gt;https://ritviknag.com&lt;/a&gt; on January 28, 2024.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>jekyll</category>
      <category>github</category>
      <category>homebrew</category>
    </item>
  </channel>
</rss>
